Browse Source

GDScript: Add lambdas to the type analyzer

- Lambdas are always callables (no specific signature match).
- Captures from the current context are evaluated.
George Marques 4 years ago
parent
commit
3155368093

+ 71 - 13
modules/gdscript/gdscript_analyzer.cpp

@@ -856,6 +856,7 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node) {
 		case GDScriptParser::Node::DICTIONARY:
 		case GDScriptParser::Node::DICTIONARY:
 		case GDScriptParser::Node::GET_NODE:
 		case GDScriptParser::Node::GET_NODE:
 		case GDScriptParser::Node::IDENTIFIER:
 		case GDScriptParser::Node::IDENTIFIER:
+		case GDScriptParser::Node::LAMBDA:
 		case GDScriptParser::Node::LITERAL:
 		case GDScriptParser::Node::LITERAL:
 		case GDScriptParser::Node::PRELOAD:
 		case GDScriptParser::Node::PRELOAD:
 		case GDScriptParser::Node::SELF:
 		case GDScriptParser::Node::SELF:
@@ -872,9 +873,6 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node) {
 		case GDScriptParser::Node::SIGNAL:
 		case GDScriptParser::Node::SIGNAL:
 			// Nothing to do.
 			// Nothing to do.
 			break;
 			break;
-		case GDScriptParser::Node::LAMBDA:
-			// FIXME: Recurse into lambda.
-			break;
 	}
 	}
 }
 }
 
 
@@ -1461,6 +1459,9 @@ void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expre
 		case GDScriptParser::Node::IDENTIFIER:
 		case GDScriptParser::Node::IDENTIFIER:
 			reduce_identifier(static_cast<GDScriptParser::IdentifierNode *>(p_expression));
 			reduce_identifier(static_cast<GDScriptParser::IdentifierNode *>(p_expression));
 			break;
 			break;
+		case GDScriptParser::Node::LAMBDA:
+			reduce_lambda(static_cast<GDScriptParser::LambdaNode *>(p_expression));
+			break;
 		case GDScriptParser::Node::LITERAL:
 		case GDScriptParser::Node::LITERAL:
 			reduce_literal(static_cast<GDScriptParser::LiteralNode *>(p_expression));
 			reduce_literal(static_cast<GDScriptParser::LiteralNode *>(p_expression));
 			break;
 			break;
@@ -1492,7 +1493,6 @@ void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expre
 		case GDScriptParser::Node::FOR:
 		case GDScriptParser::Node::FOR:
 		case GDScriptParser::Node::FUNCTION:
 		case GDScriptParser::Node::FUNCTION:
 		case GDScriptParser::Node::IF:
 		case GDScriptParser::Node::IF:
-		case GDScriptParser::Node::LAMBDA:
 		case GDScriptParser::Node::MATCH:
 		case GDScriptParser::Node::MATCH:
 		case GDScriptParser::Node::MATCH_BRANCH:
 		case GDScriptParser::Node::MATCH_BRANCH:
 		case GDScriptParser::Node::PARAMETER:
 		case GDScriptParser::Node::PARAMETER:
@@ -2350,6 +2350,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
 				case GDScriptParser::ClassNode::Member::ENUM_VALUE:
 				case GDScriptParser::ClassNode::Member::ENUM_VALUE:
 					p_identifier->is_constant = true;
 					p_identifier->is_constant = true;
 					p_identifier->reduced_value = member.enum_value.value;
 					p_identifier->reduced_value = member.enum_value.value;
+					p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT;
 					break;
 					break;
 				case GDScriptParser::ClassNode::Member::VARIABLE:
 				case GDScriptParser::ClassNode::Member::VARIABLE:
 					p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_VARIABLE;
 					p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_VARIABLE;
@@ -2450,42 +2451,65 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
 		}
 		}
 	}
 	}
 
 
+	bool found_source = false;
 	// Check if identifier is local.
 	// Check if identifier is local.
 	// If that's the case, the declaration already was solved before.
 	// If that's the case, the declaration already was solved before.
 	switch (p_identifier->source) {
 	switch (p_identifier->source) {
 		case GDScriptParser::IdentifierNode::FUNCTION_PARAMETER:
 		case GDScriptParser::IdentifierNode::FUNCTION_PARAMETER:
 			p_identifier->set_datatype(p_identifier->parameter_source->get_datatype());
 			p_identifier->set_datatype(p_identifier->parameter_source->get_datatype());
-			return;
+			found_source = true;
+			break;
 		case GDScriptParser::IdentifierNode::LOCAL_CONSTANT:
 		case GDScriptParser::IdentifierNode::LOCAL_CONSTANT:
 		case GDScriptParser::IdentifierNode::MEMBER_CONSTANT:
 		case GDScriptParser::IdentifierNode::MEMBER_CONSTANT:
 			p_identifier->set_datatype(p_identifier->constant_source->get_datatype());
 			p_identifier->set_datatype(p_identifier->constant_source->get_datatype());
 			p_identifier->is_constant = true;
 			p_identifier->is_constant = true;
 			// TODO: Constant should have a value on the node itself.
 			// TODO: Constant should have a value on the node itself.
 			p_identifier->reduced_value = p_identifier->constant_source->initializer->reduced_value;
 			p_identifier->reduced_value = p_identifier->constant_source->initializer->reduced_value;
-			return;
+			found_source = true;
+			break;
 		case GDScriptParser::IdentifierNode::MEMBER_VARIABLE:
 		case GDScriptParser::IdentifierNode::MEMBER_VARIABLE:
 			p_identifier->variable_source->usages++;
 			p_identifier->variable_source->usages++;
 			[[fallthrough]];
 			[[fallthrough]];
 		case GDScriptParser::IdentifierNode::LOCAL_VARIABLE:
 		case GDScriptParser::IdentifierNode::LOCAL_VARIABLE:
 			p_identifier->set_datatype(p_identifier->variable_source->get_datatype());
 			p_identifier->set_datatype(p_identifier->variable_source->get_datatype());
-			return;
+			found_source = true;
+			break;
 		case GDScriptParser::IdentifierNode::LOCAL_ITERATOR:
 		case GDScriptParser::IdentifierNode::LOCAL_ITERATOR:
 			p_identifier->set_datatype(p_identifier->bind_source->get_datatype());
 			p_identifier->set_datatype(p_identifier->bind_source->get_datatype());
-			return;
+			found_source = true;
+			break;
 		case GDScriptParser::IdentifierNode::LOCAL_BIND: {
 		case GDScriptParser::IdentifierNode::LOCAL_BIND: {
 			GDScriptParser::DataType result = p_identifier->bind_source->get_datatype();
 			GDScriptParser::DataType result = p_identifier->bind_source->get_datatype();
 			result.is_constant = true;
 			result.is_constant = true;
 			p_identifier->set_datatype(result);
 			p_identifier->set_datatype(result);
-			return;
-		}
+			found_source = true;
+		} break;
 		case GDScriptParser::IdentifierNode::UNDEFINED_SOURCE:
 		case GDScriptParser::IdentifierNode::UNDEFINED_SOURCE:
 			break;
 			break;
 	}
 	}
 
 
 	// Not a local, so check members.
 	// Not a local, so check members.
-	reduce_identifier_from_base(p_identifier);
-	if (p_identifier->get_datatype().is_set()) {
-		// Found.
+	if (!found_source) {
+		reduce_identifier_from_base(p_identifier);
+		if (p_identifier->source != GDScriptParser::IdentifierNode::UNDEFINED_SOURCE || p_identifier->get_datatype().is_set()) {
+			// Found.
+			found_source = true;
+		}
+	}
+
+	if (found_source) {
+		// If the identifier is local, check if it's any kind of capture by comparing their source function.
+		// Only capture locals and members and enum values. Constants are still accessible from the lambda using the script reference.
+		if (p_identifier->source == GDScriptParser::IdentifierNode::UNDEFINED_SOURCE || p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_CONSTANT || lambda_stack.is_empty()) {
+			return;
+		}
+
+		GDScriptParser::FunctionNode *function_test = lambda_stack.back()->get()->function;
+		while (function_test != nullptr && function_test != p_identifier->source_function && function_test->source_lambda != nullptr && !function_test->source_lambda->captures_indices.has(p_identifier->name)) {
+			function_test->source_lambda->captures_indices[p_identifier->name] = function_test->source_lambda->captures.size();
+			function_test->source_lambda->captures.push_back(p_identifier);
+			function_test = function_test->source_lambda->parent_function;
+		}
 		return;
 		return;
 	}
 	}
 
 
@@ -2567,6 +2591,33 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
 	p_identifier->set_datatype(dummy); // Just so type is set to something.
 	p_identifier->set_datatype(dummy); // Just so type is set to something.
 }
 }
 
 
+void GDScriptAnalyzer::reduce_lambda(GDScriptParser::LambdaNode *p_lambda) {
+	// Lambda is always a Callable.
+	GDScriptParser::DataType lambda_type;
+	lambda_type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED;
+	lambda_type.kind = GDScriptParser::DataType::BUILTIN;
+	lambda_type.builtin_type = Variant::CALLABLE;
+	p_lambda->set_datatype(lambda_type);
+
+	if (p_lambda->function == nullptr) {
+		return;
+	}
+
+	GDScriptParser::FunctionNode *previous_function = parser->current_function;
+	parser->current_function = p_lambda->function;
+
+	lambda_stack.push_back(p_lambda);
+
+	for (int i = 0; i < p_lambda->function->parameters.size(); i++) {
+		resolve_parameter(p_lambda->function->parameters[i]);
+	}
+
+	resolve_suite(p_lambda->function->body);
+
+	lambda_stack.pop_back();
+	parser->current_function = previous_function;
+}
+
 void GDScriptAnalyzer::reduce_literal(GDScriptParser::LiteralNode *p_literal) {
 void GDScriptAnalyzer::reduce_literal(GDScriptParser::LiteralNode *p_literal) {
 	p_literal->reduced_value = p_literal->value;
 	p_literal->reduced_value = p_literal->value;
 	p_literal->is_constant = true;
 	p_literal->is_constant = true;
@@ -3526,6 +3577,13 @@ Ref<GDScriptParserRef> GDScriptAnalyzer::get_parser_for(const String &p_path) {
 	return ref;
 	return ref;
 }
 }
 
 
+const GDScriptParser::LambdaNode *GDScriptAnalyzer::get_current_lambda() const {
+	if (lambda_stack.size()) {
+		return lambda_stack.back()->get();
+	}
+	return nullptr;
+}
+
 Error GDScriptAnalyzer::resolve_inheritance() {
 Error GDScriptAnalyzer::resolve_inheritance() {
 	return resolve_inheritance(parser->head);
 	return resolve_inheritance(parser->head);
 }
 }

+ 3 - 0
modules/gdscript/gdscript_analyzer.h

@@ -42,6 +42,7 @@ class GDScriptAnalyzer {
 	HashMap<String, Ref<GDScriptParserRef>> depended_parsers;
 	HashMap<String, Ref<GDScriptParserRef>> depended_parsers;
 
 
 	const GDScriptParser::EnumNode *current_enum = nullptr;
 	const GDScriptParser::EnumNode *current_enum = nullptr;
+	List<const GDScriptParser::LambdaNode *> lambda_stack;
 
 
 	Error resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive = true);
 	Error resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive = true);
 	GDScriptParser::DataType resolve_datatype(GDScriptParser::TypeNode *p_type);
 	GDScriptParser::DataType resolve_datatype(GDScriptParser::TypeNode *p_type);
@@ -82,6 +83,7 @@ class GDScriptAnalyzer {
 	void reduce_get_node(GDScriptParser::GetNodeNode *p_get_node);
 	void reduce_get_node(GDScriptParser::GetNodeNode *p_get_node);
 	void reduce_identifier(GDScriptParser::IdentifierNode *p_identifier, bool can_be_builtin = false);
 	void reduce_identifier(GDScriptParser::IdentifierNode *p_identifier, bool can_be_builtin = false);
 	void reduce_identifier_from_base(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType *p_base = nullptr);
 	void reduce_identifier_from_base(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType *p_base = nullptr);
+	void reduce_lambda(GDScriptParser::LambdaNode *p_lambda);
 	void reduce_literal(GDScriptParser::LiteralNode *p_literal);
 	void reduce_literal(GDScriptParser::LiteralNode *p_literal);
 	void reduce_preload(GDScriptParser::PreloadNode *p_preload);
 	void reduce_preload(GDScriptParser::PreloadNode *p_preload);
 	void reduce_self(GDScriptParser::SelfNode *p_self);
 	void reduce_self(GDScriptParser::SelfNode *p_self);
@@ -109,6 +111,7 @@ class GDScriptAnalyzer {
 	void mark_node_unsafe(const GDScriptParser::Node *p_node);
 	void mark_node_unsafe(const GDScriptParser::Node *p_node);
 	bool class_exists(const StringName &p_class) const;
 	bool class_exists(const StringName &p_class) const;
 	Ref<GDScriptParserRef> get_parser_for(const String &p_path);
 	Ref<GDScriptParserRef> get_parser_for(const String &p_path);
+	const GDScriptParser::LambdaNode *get_current_lambda() const;
 #ifdef DEBUG_ENABLED
 #ifdef DEBUG_ENABLED
 	bool is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context);
 	bool is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context);
 #endif
 #endif

+ 20 - 5
modules/gdscript/gdscript_parser.cpp

@@ -1227,7 +1227,7 @@ void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNod
 			} else {
 			} else {
 				p_function->parameters_indices[parameter->identifier->name] = p_function->parameters.size();
 				p_function->parameters_indices[parameter->identifier->name] = p_function->parameters.size();
 				p_function->parameters.push_back(parameter);
 				p_function->parameters.push_back(parameter);
-				p_body->add_local(parameter);
+				p_body->add_local(parameter, current_function);
 			}
 			}
 		} while (match(GDScriptTokenizer::Token::COMMA));
 		} while (match(GDScriptTokenizer::Token::COMMA));
 	}
 	}
@@ -1365,6 +1365,7 @@ bool GDScriptParser::register_annotation(const MethodInfo &p_info, uint32_t p_ta
 GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, SuiteNode *p_suite, bool p_for_lambda) {
 GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, SuiteNode *p_suite, bool p_for_lambda) {
 	SuiteNode *suite = p_suite != nullptr ? p_suite : alloc_node<SuiteNode>();
 	SuiteNode *suite = p_suite != nullptr ? p_suite : alloc_node<SuiteNode>();
 	suite->parent_block = current_suite;
 	suite->parent_block = current_suite;
+	suite->parent_function = current_function;
 	current_suite = suite;
 	current_suite = suite;
 
 
 	bool multiline = false;
 	bool multiline = false;
@@ -1401,7 +1402,7 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context,
 				if (local.type != SuiteNode::Local::UNDEFINED) {
 				if (local.type != SuiteNode::Local::UNDEFINED) {
 					push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", local.get_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);
+				current_suite->add_local(variable, current_function);
 				break;
 				break;
 			}
 			}
 			case Node::CONSTANT: {
 			case Node::CONSTANT: {
@@ -1416,7 +1417,7 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context,
 					}
 					}
 					push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", name, constant->identifier->name));
 					push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", name, constant->identifier->name));
 				}
 				}
-				current_suite->add_local(constant);
+				current_suite->add_local(constant, current_function);
 				break;
 				break;
 			}
 			}
 			default:
 			default:
@@ -1647,7 +1648,7 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() {
 
 
 	SuiteNode *suite = alloc_node<SuiteNode>();
 	SuiteNode *suite = alloc_node<SuiteNode>();
 	if (n_for->variable) {
 	if (n_for->variable) {
-		suite->add_local(SuiteNode::Local(n_for->variable));
+		suite->add_local(SuiteNode::Local(n_for->variable, current_function));
 	}
 	}
 	suite->parent_for = n_for;
 	suite->parent_for = n_for;
 
 
@@ -1802,7 +1803,7 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() {
 		branch->patterns[0]->binds.get_key_list(&binds);
 		branch->patterns[0]->binds.get_key_list(&binds);
 
 
 		for (List<StringName>::Element *E = binds.front(); E != nullptr; E = E->next()) {
 		for (List<StringName>::Element *E = binds.front(); E != nullptr; E = E->next()) {
-			SuiteNode::Local local(branch->patterns[0]->binds[E->get()]);
+			SuiteNode::Local local(branch->patterns[0]->binds[E->get()], current_function);
 			suite->add_local(local);
 			suite->add_local(local);
 		}
 		}
 	}
 	}
@@ -2053,6 +2054,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode
 
 
 	if (current_suite != nullptr && current_suite->has_local(identifier->name)) {
 	if (current_suite != nullptr && current_suite->has_local(identifier->name)) {
 		const SuiteNode::Local &declaration = current_suite->get_local(identifier->name);
 		const SuiteNode::Local &declaration = current_suite->get_local(identifier->name);
+
+		identifier->source_function = declaration.source_function;
 		switch (declaration.type) {
 		switch (declaration.type) {
 			case SuiteNode::Local::CONSTANT:
 			case SuiteNode::Local::CONSTANT:
 				identifier->source = IdentifierNode::LOCAL_CONSTANT;
 				identifier->source = IdentifierNode::LOCAL_CONSTANT;
@@ -2731,7 +2734,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_
 
 
 GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_previous_operand, bool p_can_assign) {
 GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_previous_operand, bool p_can_assign) {
 	LambdaNode *lambda = alloc_node<LambdaNode>();
 	LambdaNode *lambda = alloc_node<LambdaNode>();
+	lambda->parent_function = current_function;
 	FunctionNode *function = alloc_node<FunctionNode>();
 	FunctionNode *function = alloc_node<FunctionNode>();
+	function->source_lambda = lambda;
+
+	function->is_static = current_function != nullptr ? current_function->is_static : false;
 
 
 	if (match(GDScriptTokenizer::Token::IDENTIFIER)) {
 	if (match(GDScriptTokenizer::Token::IDENTIFIER)) {
 		function->identifier = parse_identifier();
 		function->identifier = parse_identifier();
@@ -4024,6 +4031,14 @@ void GDScriptParser::TreePrinter::print_if(IfNode *p_if, bool p_is_elif) {
 
 
 void GDScriptParser::TreePrinter::print_lambda(LambdaNode *p_lambda) {
 void GDScriptParser::TreePrinter::print_lambda(LambdaNode *p_lambda) {
 	print_function(p_lambda->function, "Lambda");
 	print_function(p_lambda->function, "Lambda");
+	push_text("| captures [ ");
+	for (const Map<StringName, IdentifierNode *>::Element *E = p_lambda->captures.front(); E; E = E->next()) {
+		push_text(E->key().operator String());
+		if (E->next()) {
+			push_text(" , ");
+		}
+	}
+	push_line(" ]");
 }
 }
 
 
 void GDScriptParser::TreePrinter::print_literal(LiteralNode *p_literal) {
 void GDScriptParser::TreePrinter::print_literal(LiteralNode *p_literal) {

+ 16 - 6
modules/gdscript/gdscript_parser.h

@@ -76,6 +76,7 @@ public:
 	struct GetNodeNode;
 	struct GetNodeNode;
 	struct IdentifierNode;
 	struct IdentifierNode;
 	struct IfNode;
 	struct IfNode;
+	struct LambdaNode;
 	struct LiteralNode;
 	struct LiteralNode;
 	struct MatchNode;
 	struct MatchNode;
 	struct MatchBranchNode;
 	struct MatchBranchNode;
@@ -729,6 +730,7 @@ public:
 		bool is_coroutine = false;
 		bool is_coroutine = false;
 		MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
 		MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
 		MethodInfo info;
 		MethodInfo info;
+		LambdaNode *source_lambda = nullptr;
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
 		Vector<Variant> default_arg_values;
 		Vector<Variant> default_arg_values;
 		String doc_description;
 		String doc_description;
@@ -772,6 +774,7 @@ public:
 			VariableNode *variable_source;
 			VariableNode *variable_source;
 			IdentifierNode *bind_source;
 			IdentifierNode *bind_source;
 		};
 		};
+		FunctionNode *source_function = nullptr;
 
 
 		int usages = 0; // Useful for binds/iterator variable.
 		int usages = 0; // Useful for binds/iterator variable.
 
 
@@ -792,6 +795,8 @@ public:
 
 
 	struct LambdaNode : public ExpressionNode {
 	struct LambdaNode : public ExpressionNode {
 		FunctionNode *function = nullptr;
 		FunctionNode *function = nullptr;
+		FunctionNode *parent_function = nullptr;
+		Map<StringName, IdentifierNode *> captures;
 
 
 		bool has_name() const {
 		bool has_name() const {
 			return function && function->identifier;
 			return function && function->identifier;
@@ -955,6 +960,7 @@ public:
 				IdentifierNode *bind;
 				IdentifierNode *bind;
 			};
 			};
 			StringName name;
 			StringName name;
+			FunctionNode *source_function = nullptr;
 
 
 			int start_line = 0, end_line = 0;
 			int start_line = 0, end_line = 0;
 			int start_column = 0, end_column = 0;
 			int start_column = 0, end_column = 0;
@@ -964,10 +970,11 @@ public:
 			String get_name() const;
 			String get_name() const;
 
 
 			Local() {}
 			Local() {}
-			Local(ConstantNode *p_constant) {
+			Local(ConstantNode *p_constant, FunctionNode *p_source_function) {
 				type = CONSTANT;
 				type = CONSTANT;
 				constant = p_constant;
 				constant = p_constant;
 				name = p_constant->identifier->name;
 				name = p_constant->identifier->name;
+				source_function = p_source_function;
 
 
 				start_line = p_constant->start_line;
 				start_line = p_constant->start_line;
 				end_line = p_constant->end_line;
 				end_line = p_constant->end_line;
@@ -976,10 +983,11 @@ public:
 				leftmost_column = p_constant->leftmost_column;
 				leftmost_column = p_constant->leftmost_column;
 				rightmost_column = p_constant->rightmost_column;
 				rightmost_column = p_constant->rightmost_column;
 			}
 			}
-			Local(VariableNode *p_variable) {
+			Local(VariableNode *p_variable, FunctionNode *p_source_function) {
 				type = VARIABLE;
 				type = VARIABLE;
 				variable = p_variable;
 				variable = p_variable;
 				name = p_variable->identifier->name;
 				name = p_variable->identifier->name;
+				source_function = p_source_function;
 
 
 				start_line = p_variable->start_line;
 				start_line = p_variable->start_line;
 				end_line = p_variable->end_line;
 				end_line = p_variable->end_line;
@@ -988,10 +996,11 @@ public:
 				leftmost_column = p_variable->leftmost_column;
 				leftmost_column = p_variable->leftmost_column;
 				rightmost_column = p_variable->rightmost_column;
 				rightmost_column = p_variable->rightmost_column;
 			}
 			}
-			Local(ParameterNode *p_parameter) {
+			Local(ParameterNode *p_parameter, FunctionNode *p_source_function) {
 				type = PARAMETER;
 				type = PARAMETER;
 				parameter = p_parameter;
 				parameter = p_parameter;
 				name = p_parameter->identifier->name;
 				name = p_parameter->identifier->name;
+				source_function = p_source_function;
 
 
 				start_line = p_parameter->start_line;
 				start_line = p_parameter->start_line;
 				end_line = p_parameter->end_line;
 				end_line = p_parameter->end_line;
@@ -1000,10 +1009,11 @@ public:
 				leftmost_column = p_parameter->leftmost_column;
 				leftmost_column = p_parameter->leftmost_column;
 				rightmost_column = p_parameter->rightmost_column;
 				rightmost_column = p_parameter->rightmost_column;
 			}
 			}
-			Local(IdentifierNode *p_identifier) {
+			Local(IdentifierNode *p_identifier, FunctionNode *p_source_function) {
 				type = FOR_VARIABLE;
 				type = FOR_VARIABLE;
 				bind = p_identifier;
 				bind = p_identifier;
 				name = p_identifier->name;
 				name = p_identifier->name;
+				source_function = p_source_function;
 
 
 				start_line = p_identifier->start_line;
 				start_line = p_identifier->start_line;
 				end_line = p_identifier->end_line;
 				end_line = p_identifier->end_line;
@@ -1028,9 +1038,9 @@ public:
 		bool has_local(const StringName &p_name) const;
 		bool has_local(const StringName &p_name) const;
 		const Local &get_local(const StringName &p_name) const;
 		const Local &get_local(const StringName &p_name) const;
 		template <class T>
 		template <class T>
-		void add_local(T *p_local) {
+		void add_local(T *p_local, FunctionNode *p_source_function) {
 			locals_indices[p_local->identifier->name] = locals.size();
 			locals_indices[p_local->identifier->name] = locals.size();
-			locals.push_back(Local(p_local));
+			locals.push_back(Local(p_local, p_source_function));
 		}
 		}
 		void add_local(const Local &p_local) {
 		void add_local(const Local &p_local) {
 			locals_indices[p_local.name] = locals.size();
 			locals_indices[p_local.name] = locals.size();

+ 12 - 0
modules/gdscript/tests/test_gdscript.cpp

@@ -118,6 +118,18 @@ static void test_parser(const String &p_code, const String &p_script_path, const
 			print_line(vformat("%02d:%02d: %s", error.line, error.column, error.message));
 			print_line(vformat("%02d:%02d: %s", error.line, error.column, error.message));
 		}
 		}
 	}
 	}
+
+	GDScriptAnalyzer analyzer(&parser);
+	analyzer.analyze();
+
+	if (err != OK) {
+		const List<GDScriptParser::ParserError> &errors = parser.get_errors();
+		for (const List<GDScriptParser::ParserError>::Element *E = errors.front(); E != nullptr; E = E->next()) {
+			const GDScriptParser::ParserError &error = E->get();
+			print_line(vformat("%02d:%02d: %s", error.line, error.column, error.message));
+		}
+	}
+
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
 	GDScriptParser::TreePrinter printer;
 	GDScriptParser::TreePrinter printer;
 	printer.print_tree(parser);
 	printer.print_tree(parser);