ソースを参照

Add better local variable detection in GDScript parser

Also store Variant operator to avoid needing to do it repeatedly in
later compiling stages.
George Marques 5 年 前
コミット
17cd6347ba
2 ファイル変更361 行追加50 行削除
  1. 233 34
      modules/gdscript/gdscript_parser.cpp
  2. 128 16
      modules/gdscript/gdscript_parser.h

+ 233 - 34
modules/gdscript/gdscript_parser.cpp

@@ -33,6 +33,7 @@
 #include "core/io/resource_loader.h"
 #include "core/math/math_defs.h"
 #include "core/os/file_access.h"
+#include "gdscript.h"
 
 #ifdef DEBUG_ENABLED
 #include "core/os/os.h"
@@ -917,6 +918,10 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function() {
 	push_multiline(true);
 	consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected opening "(" after function name.)");
 
+	SuiteNode *body = alloc_node<SuiteNode>();
+	SuiteNode *previous_suite = current_suite;
+	current_suite = body;
+
 	if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) {
 		bool default_used = false;
 		do {
@@ -941,6 +946,7 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function() {
 			} else {
 				function->parameters_indices[parameter->identifier->name] = function->parameters.size();
 				function->parameters.push_back(parameter);
+				body->add_local(parameter);
 			}
 		} while (match(GDScriptTokenizer::Token::COMMA));
 	}
@@ -955,7 +961,8 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function() {
 	// TODO: Improve token consumption so it synchronizes to a statement boundary. This way we can get into the function body with unrecognized tokens.
 	consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after function declaration.)");
 
-	function->body = parse_suite("function declaration");
+	current_suite = previous_suite;
+	function->body = parse_suite("function declaration", body);
 
 	current_function = previous_function;
 	return function;
@@ -1030,8 +1037,8 @@ bool GDScriptParser::register_annotation(const MethodInfo &p_info, uint32_t p_ta
 	return true;
 }
 
-GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context) {
-	SuiteNode *suite = alloc_node<SuiteNode>();
+GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, SuiteNode *p_suite) {
+	SuiteNode *suite = p_suite != nullptr ? p_suite : alloc_node<SuiteNode>();
 	suite->parent_block = current_suite;
 	current_suite = suite;
 
@@ -1063,13 +1070,7 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context)
 				VariableNode *variable = static_cast<VariableNode *>(statement);
 				const SuiteNode::Local &local = current_suite->get_local(variable->identifier->name);
 				if (local.type != SuiteNode::Local::UNDEFINED) {
-					String name;
-					if (local.type == SuiteNode::Local::CONSTANT) {
-						name = "constant";
-					} else {
-						name = "variable";
-					}
-					push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", name, variable->identifier->name));
+					push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", local.get_name(), variable->identifier->name));
 				}
 				current_suite->add_local(variable);
 				break;
@@ -1256,7 +1257,10 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() {
 	can_break = true;
 	can_continue = true;
 
-	n_for->loop = parse_suite(R"("for" block)");
+	SuiteNode *suite = alloc_node<SuiteNode>();
+	suite->add_local(SuiteNode::Local(n_for->variable));
+
+	n_for->loop = parse_suite(R"("for" block)", suite);
 
 	// Reset break/continue state.
 	can_break = could_break;
@@ -1352,7 +1356,18 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() {
 	// Allow continue for match.
 	can_continue = true;
 
-	branch->block = parse_suite("match pattern block");
+	SuiteNode *suite = alloc_node<SuiteNode>();
+	if (branch->patterns.size() > 0) {
+		List<StringName> binds;
+		branch->patterns[0]->binds.get_key_list(&binds);
+
+		for (List<StringName>::Element *E = binds.front(); E != nullptr; E = E->next()) {
+			SuiteNode::Local local(branch->patterns[0]->binds[E->get()]);
+			suite->add_local(local);
+		}
+	}
+
+	branch->block = parse_suite("match pattern block", suite);
 
 	// Restore continue state.
 	can_continue = could_continue;
@@ -1360,7 +1375,7 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() {
 	return branch;
 }
 
-GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern() {
+GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_root_pattern) {
 	PatternNode *pattern = alloc_node<PatternNode>();
 
 	switch (current.type) {
@@ -1373,7 +1388,7 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern() {
 				return nullptr;
 			}
 			break;
-		case GDScriptTokenizer::Token::VAR:
+		case GDScriptTokenizer::Token::VAR: {
 			// Bind.
 			advance();
 			if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected bind name after "var".)")) {
@@ -1381,7 +1396,24 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern() {
 			}
 			pattern->pattern_type = PatternNode::PT_BIND;
 			pattern->bind = parse_identifier();
-			break;
+
+			PatternNode *root_pattern = p_root_pattern == nullptr ? pattern : p_root_pattern;
+
+			if (p_root_pattern != nullptr) {
+				if (p_root_pattern->has_bind(pattern->bind->name)) {
+					push_error(vformat(R"(Bind variable name "%s" was already used in this pattern.)", pattern->bind->name));
+					return nullptr;
+				}
+			}
+
+			if (current_suite->has_local(pattern->bind->name)) {
+				push_error(vformat(R"(There's already a %s named "%s" in this scope.)", current_suite->get_local(pattern->bind->name).get_name(), pattern->bind->name));
+				return nullptr;
+			}
+
+			root_pattern->binds[pattern->bind->name] = pattern->bind;
+
+		} break;
 		case GDScriptTokenizer::Token::UNDERSCORE:
 			// Wildcard.
 			advance();
@@ -1399,7 +1431,7 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern() {
 
 			if (!check(GDScriptTokenizer::Token::BRACKET_CLOSE)) {
 				do {
-					PatternNode *sub_pattern = parse_match_pattern();
+					PatternNode *sub_pattern = parse_match_pattern(p_root_pattern != nullptr ? p_root_pattern : pattern);
 					if (sub_pattern == nullptr) {
 						continue;
 					}
@@ -1438,7 +1470,7 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern() {
 						}
 						if (consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after dictionary pattern key)")) {
 							// Value pattern.
-							PatternNode *sub_pattern = parse_match_pattern();
+							PatternNode *sub_pattern = parse_match_pattern(p_root_pattern != nullptr ? p_root_pattern : pattern);
 							if (sub_pattern == nullptr) {
 								continue;
 							}
@@ -1472,6 +1504,14 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern() {
 	return pattern;
 }
 
+bool GDScriptParser::PatternNode::has_bind(const StringName &p_name) {
+	return binds.has(p_name);
+}
+
+GDScriptParser::IdentifierNode *GDScriptParser::PatternNode::get_bind(const StringName &p_name) {
+	return binds[p_name];
+}
+
 GDScriptParser::WhileNode *GDScriptParser::parse_while() {
 	WhileNode *n_while = alloc_node<WhileNode>();
 
@@ -1558,6 +1598,35 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode
 	}
 	IdentifierNode *identifier = alloc_node<IdentifierNode>();
 	identifier->name = previous.get_identifier();
+
+	if (current_suite != nullptr && current_suite->has_local(identifier->name)) {
+		const SuiteNode::Local &declaration = current_suite->get_local(identifier->name);
+		switch (declaration.type) {
+			case SuiteNode::Local::CONSTANT:
+				identifier->source = IdentifierNode::LOCAL_CONSTANT;
+				identifier->constant_source = declaration.constant;
+				break;
+			case SuiteNode::Local::VARIABLE:
+				identifier->source = IdentifierNode::LOCAL_VARIABLE;
+				identifier->variable_source = declaration.variable;
+				break;
+			case SuiteNode::Local::PARAMETER:
+				identifier->source = IdentifierNode::FUNCTION_PARAMETER;
+				identifier->parameter_source = declaration.parameter;
+				break;
+			case SuiteNode::Local::FOR_VARIABLE:
+				identifier->source = IdentifierNode::LOCAL_ITERATOR;
+				identifier->bind_source = declaration.bind;
+				break;
+			case SuiteNode::Local::PATTERN_BIND:
+				identifier->source = IdentifierNode::LOCAL_BIND;
+				identifier->bind_source = declaration.bind;
+				break;
+			case SuiteNode::Local::UNDEFINED:
+				ERR_FAIL_V_MSG(nullptr, "Undefined local found.");
+		}
+	}
+
 	return identifier;
 }
 
@@ -1614,19 +1683,23 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_unary_operator(ExpressionN
 	switch (op_type) {
 		case GDScriptTokenizer::Token::MINUS:
 			operation->operation = UnaryOpNode::OP_NEGATIVE;
+			operation->variant_op = Variant::OP_NEGATE;
 			operation->operand = parse_precedence(PREC_SIGN, false);
 			break;
 		case GDScriptTokenizer::Token::PLUS:
 			operation->operation = UnaryOpNode::OP_POSITIVE;
+			operation->variant_op = Variant::OP_POSITIVE;
 			operation->operand = parse_precedence(PREC_SIGN, false);
 			break;
 		case GDScriptTokenizer::Token::TILDE:
 			operation->operation = UnaryOpNode::OP_COMPLEMENT;
+			operation->variant_op = Variant::OP_BIT_NEGATE;
 			operation->operand = parse_precedence(PREC_BIT_NOT, false);
 			break;
 		case GDScriptTokenizer::Token::NOT:
 		case GDScriptTokenizer::Token::BANG:
 			operation->operation = UnaryOpNode::OP_LOGIC_NOT;
+			operation->variant_op = Variant::OP_NOT;
 			operation->operand = parse_precedence(PREC_LOGIC_NOT, false);
 			break;
 		default:
@@ -1648,68 +1721,89 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_operator(Expression
 		push_error(vformat(R"(Expected expression after "%s" operator.")", op.get_name()));
 	}
 
+	// TODO: Store the Variant operator here too (in the node).
+	// TODO: Also for unary, ternary, and assignment.
 	switch (op.type) {
 		case GDScriptTokenizer::Token::PLUS:
 			operation->operation = BinaryOpNode::OP_ADDITION;
+			operation->variant_op = Variant::OP_ADD;
 			break;
 		case GDScriptTokenizer::Token::MINUS:
 			operation->operation = BinaryOpNode::OP_SUBTRACTION;
+			operation->variant_op = Variant::OP_SUBTRACT;
 			break;
 		case GDScriptTokenizer::Token::STAR:
 			operation->operation = BinaryOpNode::OP_MULTIPLICATION;
+			operation->variant_op = Variant::OP_MULTIPLY;
 			break;
 		case GDScriptTokenizer::Token::SLASH:
 			operation->operation = BinaryOpNode::OP_DIVISION;
+			operation->variant_op = Variant::OP_DIVIDE;
 			break;
 		case GDScriptTokenizer::Token::PERCENT:
 			operation->operation = BinaryOpNode::OP_MODULO;
+			operation->variant_op = Variant::OP_MODULE;
 			break;
 		case GDScriptTokenizer::Token::LESS_LESS:
 			operation->operation = BinaryOpNode::OP_BIT_LEFT_SHIFT;
+			operation->variant_op = Variant::OP_SHIFT_LEFT;
 			break;
 		case GDScriptTokenizer::Token::GREATER_GREATER:
 			operation->operation = BinaryOpNode::OP_BIT_RIGHT_SHIFT;
+			operation->variant_op = Variant::OP_SHIFT_RIGHT;
 			break;
 		case GDScriptTokenizer::Token::AMPERSAND:
 			operation->operation = BinaryOpNode::OP_BIT_AND;
+			operation->variant_op = Variant::OP_BIT_AND;
 			break;
 		case GDScriptTokenizer::Token::PIPE:
-			operation->operation = BinaryOpNode::OP_BIT_AND;
+			operation->operation = BinaryOpNode::OP_BIT_OR;
+			operation->variant_op = Variant::OP_BIT_OR;
 			break;
 		case GDScriptTokenizer::Token::CARET:
 			operation->operation = BinaryOpNode::OP_BIT_XOR;
+			operation->variant_op = Variant::OP_BIT_XOR;
 			break;
 		case GDScriptTokenizer::Token::AND:
 		case GDScriptTokenizer::Token::AMPERSAND_AMPERSAND:
 			operation->operation = BinaryOpNode::OP_LOGIC_AND;
+			operation->variant_op = Variant::OP_AND;
 			break;
 		case GDScriptTokenizer::Token::OR:
 		case GDScriptTokenizer::Token::PIPE_PIPE:
 			operation->operation = BinaryOpNode::OP_LOGIC_OR;
+			operation->variant_op = Variant::OP_OR;
 			break;
 		case GDScriptTokenizer::Token::IS:
 			operation->operation = BinaryOpNode::OP_TYPE_TEST;
 			break;
 		case GDScriptTokenizer::Token::IN:
 			operation->operation = BinaryOpNode::OP_CONTENT_TEST;
+			operation->variant_op = Variant::OP_IN;
 			break;
 		case GDScriptTokenizer::Token::EQUAL_EQUAL:
 			operation->operation = BinaryOpNode::OP_COMP_EQUAL;
+			operation->variant_op = Variant::OP_EQUAL;
 			break;
 		case GDScriptTokenizer::Token::BANG_EQUAL:
 			operation->operation = BinaryOpNode::OP_COMP_NOT_EQUAL;
+			operation->variant_op = Variant::OP_NOT_EQUAL;
 			break;
 		case GDScriptTokenizer::Token::LESS:
 			operation->operation = BinaryOpNode::OP_COMP_LESS;
+			operation->variant_op = Variant::OP_LESS;
 			break;
 		case GDScriptTokenizer::Token::LESS_EQUAL:
 			operation->operation = BinaryOpNode::OP_COMP_LESS_EQUAL;
+			operation->variant_op = Variant::OP_LESS_EQUAL;
 			break;
 		case GDScriptTokenizer::Token::GREATER:
 			operation->operation = BinaryOpNode::OP_COMP_GREATER;
+			operation->variant_op = Variant::OP_GREATER;
 			break;
 		case GDScriptTokenizer::Token::GREATER_EQUAL:
 			operation->operation = BinaryOpNode::OP_COMP_GREATER_EQUAL;
+			operation->variant_op = Variant::OP_GREATER_EQUAL;
 			break;
 		default:
 			return nullptr; // Unreachable.
@@ -1964,17 +2058,34 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
 				pop_multiline();
 				return nullptr;
 			}
+			call->function_name = current_function->identifier->name;
 		} else {
 			consume(GDScriptTokenizer::Token::PERIOD, R"(Expected "." or "(" after "super".)");
 			if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after ".".)")) {
 				pop_multiline();
 				return nullptr;
 			}
-			call->callee = parse_identifier();
+			IdentifierNode *identifier = parse_identifier();
+			call->callee = identifier;
+			call->function_name = identifier->name;
 			consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after function name.)");
 		}
 	} else {
 		call->callee = p_previous_operand;
+
+		if (call->callee->type == Node::IDENTIFIER) {
+			call->function_name = static_cast<IdentifierNode *>(call->callee)->name;
+		} else if (call->callee->type == Node::SUBSCRIPT) {
+			SubscriptNode *attribute = static_cast<SubscriptNode *>(call->callee);
+			if (attribute->is_attribute) {
+				call->function_name = attribute->attribute->name;
+			} else {
+				// TODO: The analyzer can see if this is actually a Callable and give better error message.
+				push_error(R"*(Cannot call on an expression. Use ".call()" if it's a Callable.)*");
+			}
+		} else {
+			push_error(R"*(Cannot call on an expression. Use ".call()" if it's a Callable.)*");
+		}
 	}
 
 	if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
@@ -2095,18 +2206,17 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) {
 		// Leave error message to the caller who knows the context.
 		return nullptr;
 	}
+
 	TypeNode *type = alloc_node<TypeNode>();
-	IdentifierNode *type_base = parse_identifier();
+	IdentifierNode *type_element = parse_identifier();
 
-	// FIXME: This is likely not working with multiple chained attributes (A.B.C.D...).
-	// FIXME: I probably should use a list for this, not an attribute (since those aren't chained anymore).
-	if (match(GDScriptTokenizer::Token::PERIOD)) {
-		type->type_specifier = static_cast<SubscriptNode *>(parse_attribute(type_base, false));
-		if (type->type_specifier->index == nullptr) {
-			return nullptr;
+	type->type_chain.push_back(type_element);
+
+	while (match(GDScriptTokenizer::Token::PERIOD)) {
+		if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected inner type name after ".".)")) {
+			type_element = parse_identifier();
+			type->type_chain.push_back(type_element);
 		}
-	} else {
-		type->type_base = type_base;
 	}
 
 	return type;
@@ -2117,7 +2227,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty
 	// clang-format destroys the alignment here, so turn off for the table.
 	/* clang-format off */
 	static ParseRule rules[] = {
-		// PREFIX                                           INFIX                                           PRECEDENCE (for binary)
+		// PREFIX                                           INFIX                                           PRECEDENCE (for infix)
 		{ nullptr,                                          nullptr,                                        PREC_NONE }, // EMPTY,
 		// Basic
 		{ nullptr,                                          nullptr,                                        PREC_NONE }, // ANNOTATION,
@@ -2432,6 +2542,92 @@ bool GDScriptParser::network_annotations(const AnnotationNode *p_annotation, Nod
 	return true;
 }
 
+GDScriptParser::DataType GDScriptParser::SuiteNode::Local::get_datatype() const {
+	switch (type) {
+		case CONSTANT:
+			return constant->get_datatype();
+		case VARIABLE:
+			return variable->get_datatype();
+		case PARAMETER:
+			return parameter->get_datatype();
+		case FOR_VARIABLE:
+		case PATTERN_BIND:
+			return bind->get_datatype();
+		case UNDEFINED:
+			return DataType();
+	}
+	return DataType();
+}
+
+String GDScriptParser::SuiteNode::Local::get_name() const {
+	String name;
+	switch (type) {
+		case SuiteNode::Local::PARAMETER:
+			name = "parameter";
+			break;
+		case SuiteNode::Local::CONSTANT:
+			name = "constant";
+			break;
+		case SuiteNode::Local::VARIABLE:
+			name = "variable";
+			break;
+		case SuiteNode::Local::FOR_VARIABLE:
+			name = "for loop iterator";
+			break;
+		case SuiteNode::Local::PATTERN_BIND:
+			name = "pattern_bind";
+			break;
+		case SuiteNode::Local::UNDEFINED:
+			name = "<undefined>";
+			break;
+	}
+	return name;
+}
+
+String GDScriptParser::DataType::to_string() const {
+	switch (kind) {
+		case VARIANT:
+			return "Variant";
+		case BUILTIN:
+			if (builtin_type == Variant::NIL) {
+				return "null";
+			}
+			return Variant::get_type_name(builtin_type);
+		case NATIVE:
+			if (is_meta_type) {
+				return GDScriptNativeClass::get_class_static();
+			}
+			return native_type.operator String();
+		case CLASS:
+			if (is_meta_type) {
+				return GDScript::get_class_static();
+			}
+			if (class_type->identifier != nullptr) {
+				return class_type->identifier->name.operator String();
+			}
+			// TODO: GDScript FQCN
+			return "<unnamed GDScript class>";
+		case SCRIPT: {
+			if (is_meta_type) {
+				return script_type->get_class_name().operator String();
+			}
+			String name = script_type->get_name();
+			if (!name.empty()) {
+				return name;
+			}
+			name = script_type->get_path();
+			if (!name.empty()) {
+				return name;
+			}
+			return native_type.operator String();
+		}
+		case UNRESOLVED:
+			return "<unresolved type>";
+	}
+
+	ERR_FAIL_V_MSG("<unresolved type", "Kind set outside the enum range.");
+}
+
 /*---------- PRETTY PRINT FOR DEBUG ----------*/
 
 #ifdef DEBUG_ENABLED
@@ -3132,12 +3328,15 @@ void GDScriptParser::TreePrinter::print_ternary_op(TernaryOpNode *p_ternary_op)
 }
 
 void GDScriptParser::TreePrinter::print_type(TypeNode *p_type) {
-	if (p_type->type_specifier != nullptr) {
-		print_subscript(p_type->type_specifier);
-	} else if (p_type->type_base != nullptr) {
-		print_identifier(p_type->type_base);
-	} else {
+	if (p_type->type_chain.empty()) {
 		push_text("Void");
+	} else {
+		for (int i = 0; i < p_type->type_chain.size(); i++) {
+			if (i > 0) {
+				push_text(".");
+			}
+			print_identifier(p_type->type_chain[i]);
+		}
 	}
 }
 

+ 128 - 16
modules/gdscript/gdscript_parser.h

@@ -99,7 +99,9 @@ public:
 			NATIVE,
 			SCRIPT,
 			CLASS, // GDScript.
+			VARIANT, // Can be any type.
 			UNRESOLVED,
+			// TODO: Enum, Signal, Callable
 		};
 		Kind kind = UNRESOLVED;
 
@@ -113,14 +115,18 @@ public:
 
 		bool is_constant = false;
 		bool is_meta_type = false;
-		bool infer_type = false;
+		bool is_coroutine = false; // For function calls.
 
 		Variant::Type builtin_type = Variant::NIL;
 		StringName native_type;
 		Ref<Script> script_type;
-		ClassNode *gdscript_type = nullptr;
+		ClassNode *class_type = nullptr;
 
-		_FORCE_INLINE_ bool is_set() const { return type_source != UNDETECTED; }
+		MethodInfo method_info; // For callable/signals.
+
+		_FORCE_INLINE_ bool is_set() const { return kind != UNRESOLVED; }
+		_FORCE_INLINE_ bool has_no_type() const { return type_source == UNDETECTED; }
+		_FORCE_INLINE_ bool is_variant() const { return kind == VARIANT; }
 		String to_string() const;
 
 		bool operator==(const DataType &p_other) const {
@@ -137,6 +143,8 @@ public:
 			}
 
 			switch (kind) {
+				case VARIANT:
+					return true; // All variants are the same.
 				case BUILTIN:
 					return builtin_type == p_other.builtin_type;
 				case NATIVE:
@@ -144,13 +152,17 @@ public:
 				case SCRIPT:
 					return script_type == p_other.script_type;
 				case CLASS:
-					return gdscript_type == p_other.gdscript_type;
+					return class_type == p_other.class_type;
 				case UNRESOLVED:
 					break;
 			}
 
 			return false;
 		}
+
+		bool operator!=(const DataType &p_other) const {
+			return !(this->operator==(p_other));
+		}
 	};
 
 	struct ParserError {
@@ -215,8 +227,10 @@ public:
 		Node *next = nullptr;
 		List<AnnotationNode *> annotations;
 
-		virtual DataType get_datatype() const { return DataType(); }
-		virtual void set_datatype(const DataType &p_datatype) {}
+		DataType datatype;
+
+		virtual DataType get_datatype() const { return datatype; }
+		virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; }
 
 		virtual bool is_expression() const { return false; }
 
@@ -225,6 +239,10 @@ public:
 
 	struct ExpressionNode : public Node {
 		// Base type for all expression kinds.
+		bool reduced = false;
+		bool is_constant = false;
+		Variant reduced_value;
+
 		virtual bool is_expression() const { return true; }
 		virtual ~ExpressionNode() {}
 
@@ -281,6 +299,7 @@ public:
 		};
 
 		Operation operation = OP_NONE;
+		Variant::Operator variant_op = Variant::OP_MAX;
 		ExpressionNode *assignee = nullptr;
 		ExpressionNode *assigned_value = nullptr;
 
@@ -322,6 +341,7 @@ public:
 		};
 
 		OpType operation;
+		Variant::Operator variant_op = Variant::OP_MAX;
 		ExpressionNode *left_operand = nullptr;
 		ExpressionNode *right_operand = nullptr;
 
@@ -345,6 +365,7 @@ public:
 	struct CallNode : public ExpressionNode {
 		ExpressionNode *callee = nullptr;
 		Vector<ExpressionNode *> arguments;
+		StringName function_name; // TODO: Set this.
 		bool is_super = false;
 
 		CallNode() {
@@ -422,6 +443,34 @@ public:
 				return "";
 			}
 
+			DataType get_datatype() const {
+				switch (type) {
+					case CLASS:
+						return m_class->get_datatype();
+					case CONSTANT:
+						return constant->get_datatype();
+					case FUNCTION:
+						return function->get_datatype();
+					case VARIABLE:
+						return variable->get_datatype();
+					case ENUM_VALUE: {
+						// Always integer.
+						DataType type;
+						type.type_source = DataType::ANNOTATED_EXPLICIT;
+						type.kind = DataType::BUILTIN;
+						type.builtin_type = Variant::INT;
+						return type;
+					}
+					case ENUM:
+					case SIGNAL:
+						// TODO: Use special datatype kinds for these.
+						return DataType();
+					case UNDEFINED:
+						return DataType();
+				}
+				ERR_FAIL_V_MSG(DataType(), "Reaching unhandled type.");
+			}
+
 			Member() {}
 
 			Member(ClassNode *p_class) {
@@ -464,10 +513,17 @@ public:
 		String extends_path;
 		Vector<StringName> extends; // List for indexing: extends A.B.C
 		DataType base_type;
+		String fqcn; // Fully-qualified class name. Identifies uniquely any class in the project.
+
+		bool resolved_interface = false;
+		bool resolved_body = false;
 
 		Member get_member(const StringName &p_name) const {
 			return members[members_indices[p_name]];
 		}
+		bool has_member(const StringName &p_name) const {
+			return members_indices.has(p_name);
+		}
 		template <class T>
 		void add_member(T *p_member_node) {
 			members_indices[p_member_node->identifier->name] = members.size();
@@ -477,12 +533,6 @@ public:
 			members_indices[p_enum_value.identifier->name] = members.size();
 			members.push_back(Member(p_enum_value));
 		}
-		virtual DataType get_datatype() const {
-			return base_type;
-		}
-		virtual void set_datatype(const DataType &p_datatype) {
-			base_type = p_datatype;
-		}
 
 		ClassNode() {
 			type = CLASS;
@@ -543,6 +593,9 @@ public:
 		bool is_static = false;
 		MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
 
+		bool resolved_signature = false;
+		bool resolved_body = false;
+
 		FunctionNode() {
 			type = FUNCTION;
 		}
@@ -560,6 +613,24 @@ public:
 	struct IdentifierNode : public ExpressionNode {
 		StringName name;
 
+		enum Source {
+			UNDEFINED_SOURCE,
+			FUNCTION_PARAMETER,
+			LOCAL_CONSTANT,
+			LOCAL_VARIABLE,
+			LOCAL_ITERATOR, // `for` loop iterator.
+			LOCAL_BIND, // Pattern bind.
+			// TODO: Add higher sources to help compiling?
+		};
+		Source source = UNDEFINED_SOURCE;
+
+		union {
+			ParameterNode *parameter_source = nullptr;
+			ConstantNode *constant_source;
+			VariableNode *variable_source;
+			IdentifierNode *bind_source;
+		};
+
 		IdentifierNode() {
 			type = IDENTIFIER;
 		}
@@ -644,6 +715,11 @@ public:
 		};
 		Vector<Pair> dictionary;
 
+		HashMap<StringName, IdentifierNode *> binds;
+
+		bool has_bind(const StringName &p_name);
+		IdentifierNode *get_bind(const StringName &p_name);
+
 		PatternNode() {
 			type = PATTERN;
 		}
@@ -706,27 +782,57 @@ public:
 				UNDEFINED,
 				CONSTANT,
 				VARIABLE,
+				PARAMETER,
+				FOR_VARIABLE,
+				PATTERN_BIND,
 			};
 			Type type = UNDEFINED;
 			union {
 				ConstantNode *constant = nullptr;
 				VariableNode *variable;
+				ParameterNode *parameter;
+				IdentifierNode *bind;
 			};
+			StringName name;
+
+			int start_line = 0, end_line = 0;
+			int start_column = 0, end_column = 0;
+			int leftmost_column = 0, rightmost_column = 0;
+
+			DataType get_datatype() const;
+			String get_name() const;
 
 			Local() {}
 			Local(ConstantNode *p_constant) {
 				type = CONSTANT;
 				constant = p_constant;
+				name = p_constant->identifier->name;
 			}
 			Local(VariableNode *p_variable) {
 				type = VARIABLE;
 				variable = p_variable;
+				name = p_variable->identifier->name;
+			}
+			Local(ParameterNode *p_parameter) {
+				type = PARAMETER;
+				parameter = p_parameter;
+				name = p_parameter->identifier->name;
+			}
+			Local(IdentifierNode *p_identifier) {
+				type = FOR_VARIABLE;
+				bind = p_identifier;
+				name = p_identifier->name;
 			}
 		};
 		Local empty;
 		Vector<Local> locals;
 		HashMap<StringName, int> locals_indices;
 
+		FunctionNode *parent_function = nullptr;
+		ForNode *parent_for = nullptr;
+		IfNode *parent_if = nullptr;
+		PatternNode *parent_pattern = nullptr;
+
 		bool has_local(const StringName &p_name) const;
 		const Local &get_local(const StringName &p_name) const;
 		template <class T>
@@ -734,6 +840,10 @@ public:
 			locals_indices[p_local->identifier->name] = locals.size();
 			locals.push_back(Local(p_local));
 		}
+		void add_local(const Local &p_local) {
+			locals_indices[p_local.name] = locals.size();
+			locals.push_back(p_local);
+		}
 
 		SuiteNode() {
 			type = SUITE;
@@ -752,8 +862,7 @@ public:
 	};
 
 	struct TypeNode : public Node {
-		IdentifierNode *type_base = nullptr;
-		SubscriptNode *type_specifier = nullptr;
+		Vector<IdentifierNode *> type_chain;
 
 		TypeNode() {
 			type = TYPE;
@@ -769,6 +878,7 @@ public:
 		};
 
 		OpType operation;
+		Variant::Operator variant_op = Variant::OP_MAX;
 		ExpressionNode *operand = nullptr;
 
 		UnaryOpNode() {
@@ -861,6 +971,8 @@ private:
 	HashMap<StringName, AnnotationInfo> valid_annotations;
 	List<AnnotationNode *> annotation_stack;
 
+	Set<int> unsafe_lines;
+
 	typedef ExpressionNode *(GDScriptParser::*ParseFunction)(ExpressionNode *p_previous_operand, bool p_can_assign);
 	// Higher value means higher precedence (i.e. is evaluated first).
 	enum Precedence {
@@ -924,7 +1036,7 @@ private:
 	EnumNode *parse_enum();
 	ParameterNode *parse_parameter();
 	FunctionNode *parse_function();
-	SuiteNode *parse_suite(const String &p_context);
+	SuiteNode *parse_suite(const String &p_context, SuiteNode *p_suite = nullptr);
 	// Annotations
 	AnnotationNode *parse_annotation(uint32_t p_valid_targets);
 	bool register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, int p_optional_arguments = 0, bool p_is_vararg = false);
@@ -953,7 +1065,7 @@ private:
 	IfNode *parse_if(const String &p_token = "if");
 	MatchNode *parse_match();
 	MatchBranchNode *parse_match_branch();
-	PatternNode *parse_match_pattern();
+	PatternNode *parse_match_pattern(PatternNode *p_root_pattern = nullptr);
 	WhileNode *parse_while();
 	// Expressions.
 	ExpressionNode *parse_expression(bool p_can_assign, bool p_stop_on_assign = false);