Browse Source

Merge pull request #70658 from vonagam/fix-lambdas-typing

GDScript: Fix typing of lambda functions
Rémi Verschelde 2 years ago
parent
commit
af48b296e6

+ 14 - 24
modules/gdscript/gdscript_analyzer.cpp

@@ -1195,10 +1195,6 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root
 		case GDScriptParser::Node::FOR:
 			resolve_for(static_cast<GDScriptParser::ForNode *>(p_node));
 			break;
-		case GDScriptParser::Node::FUNCTION:
-			resolve_function_signature(static_cast<GDScriptParser::FunctionNode *>(p_node));
-			resolve_function_body(static_cast<GDScriptParser::FunctionNode *>(p_node));
-			break;
 		case GDScriptParser::Node::IF:
 			resolve_if(static_cast<GDScriptParser::IfNode *>(p_node));
 			break;
@@ -1258,6 +1254,7 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root
 		case GDScriptParser::Node::BREAKPOINT:
 		case GDScriptParser::Node::CONTINUE:
 		case GDScriptParser::Node::ENUM:
+		case GDScriptParser::Node::FUNCTION:
 		case GDScriptParser::Node::PASS:
 		case GDScriptParser::Node::SIGNAL:
 			// Nothing to do.
@@ -1269,13 +1266,15 @@ void GDScriptAnalyzer::resolve_annotation(GDScriptParser::AnnotationNode *p_anno
 	// TODO: Add second validation function for annotations, so they can use checked types.
 }
 
-void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *p_function, const GDScriptParser::Node *p_source) {
+void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *p_function, const GDScriptParser::Node *p_source, bool p_is_lambda) {
 	if (p_source == nullptr) {
 		p_source = p_function;
 	}
 
+	StringName function_name = p_function->identifier != nullptr ? p_function->identifier->name : StringName();
+
 	if (p_function->get_datatype().is_resolving()) {
-		push_error(vformat(R"(Could not resolve function "%s": Cyclic reference.)", p_function->identifier->name), p_source);
+		push_error(vformat(R"(Could not resolve function "%s": Cyclic reference.)", function_name), p_source);
 		return;
 	}
 
@@ -1301,7 +1300,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
 		resolve_parameter(p_function->parameters[i]);
 #ifdef DEBUG_ENABLED
 		if (p_function->parameters[i]->usages == 0 && !String(p_function->parameters[i]->identifier->name).begins_with("_")) {
-			parser->push_warning(p_function->parameters[i]->identifier, GDScriptWarning::UNUSED_PARAMETER, p_function->identifier->name, p_function->parameters[i]->identifier->name);
+			parser->push_warning(p_function->parameters[i]->identifier, GDScriptWarning::UNUSED_PARAMETER, function_name, p_function->parameters[i]->identifier->name);
 		}
 		is_shadowing(p_function->parameters[i]->identifier, "function parameter");
 #endif // DEBUG_ENABLED
@@ -1318,7 +1317,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
 #endif // TOOLS_ENABLED
 	}
 
-	if (p_function->identifier->name == GDScriptLanguage::get_singleton()->strings._init) {
+	if (!p_is_lambda && function_name == GDScriptLanguage::get_singleton()->strings._init) {
 		// Constructor.
 		GDScriptParser::DataType return_type = parser->current_class->get_datatype();
 		return_type.is_meta_type = false;
@@ -1350,7 +1349,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
 		int default_par_count = 0;
 		bool is_static = false;
 		bool is_vararg = false;
-		if (get_function_signature(p_function, false, base_type, p_function->identifier->name, parent_return_type, parameters_types, default_par_count, is_static, is_vararg)) {
+		if (!p_is_lambda && get_function_signature(p_function, false, base_type, function_name, parent_return_type, parameters_types, default_par_count, is_static, is_vararg)) {
 			bool valid = p_function->is_static == is_static;
 			valid = valid && parent_return_type == p_function->get_datatype();
 
@@ -1365,7 +1364,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
 
 			if (!valid) {
 				// Compute parent signature as a string to show in the error message.
-				String parent_signature = p_function->identifier->name.operator String() + "(";
+				String parent_signature = function_name.operator String() + "(";
 				int j = 0;
 				for (const GDScriptParser::DataType &par_type : parameters_types) {
 					if (j > 0) {
@@ -1404,7 +1403,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
 	parser->current_function = previous_function;
 }
 
-void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_function) {
+void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_function, bool p_is_lambda) {
 	if (p_function->resolved_body) {
 		return;
 	}
@@ -1422,7 +1421,7 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun
 		return_type.type_source = GDScriptParser::DataType::INFERRED;
 		p_function->set_datatype(p_function->body->get_datatype());
 	} else if (p_function->get_datatype().is_hard_type() && (p_function->get_datatype().kind != GDScriptParser::DataType::BUILTIN || p_function->get_datatype().builtin_type != Variant::NIL)) {
-		if (!p_function->body->has_return && p_function->identifier->name != GDScriptLanguage::get_singleton()->strings._init) {
+		if (!p_function->body->has_return && (p_is_lambda || p_function->identifier->name != GDScriptLanguage::get_singleton()->strings._init)) {
 			push_error(R"(Not all code paths return a value.)", p_function);
 		}
 	}
@@ -3297,16 +3296,10 @@ void GDScriptAnalyzer::reduce_lambda(GDScriptParser::LambdaNode *p_lambda) {
 		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);
+	resolve_function_signature(p_lambda->function, p_lambda, true);
+	resolve_function_body(p_lambda->function, true);
+	lambda_stack.pop_back();
 
 	int captures_amount = p_lambda->captures.size();
 	if (captures_amount > 0) {
@@ -3331,9 +3324,6 @@ void GDScriptAnalyzer::reduce_lambda(GDScriptParser::LambdaNode *p_lambda) {
 			p_lambda->function->parameters_indices[capture->name] = i;
 		}
 	}
-
-	lambda_stack.pop_back();
-	parser->current_function = previous_function;
 }
 
 void GDScriptAnalyzer::reduce_literal(GDScriptParser::LiteralNode *p_literal) {

+ 2 - 2
modules/gdscript/gdscript_analyzer.h

@@ -65,8 +65,8 @@ class GDScriptAnalyzer {
 	void resolve_class_interface(GDScriptParser::ClassNode *p_class, bool p_recursive);
 	void resolve_class_body(GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_source = nullptr);
 	void resolve_class_body(GDScriptParser::ClassNode *p_class, bool p_recursive);
-	void resolve_function_signature(GDScriptParser::FunctionNode *p_function, const GDScriptParser::Node *p_source = nullptr);
-	void resolve_function_body(GDScriptParser::FunctionNode *p_function);
+	void resolve_function_signature(GDScriptParser::FunctionNode *p_function, const GDScriptParser::Node *p_source = nullptr, bool p_is_lambda = false);
+	void resolve_function_body(GDScriptParser::FunctionNode *p_function, bool p_is_lambda = false);
 	void resolve_node(GDScriptParser::Node *p_node, bool p_is_root = true);
 	void resolve_suite(GDScriptParser::SuiteNode *p_suite);
 	void resolve_assignable(GDScriptParser::AssignableNode *p_assignable, const char *p_kind);

+ 4 - 0
modules/gdscript/tests/scripts/analyzer/errors/lambda_no_return.gd

@@ -0,0 +1,4 @@
+func test():
+	var lambda := func() -> int:
+		print('no return')
+	lambda.call()

+ 2 - 0
modules/gdscript/tests/scripts/analyzer/errors/lambda_no_return.out

@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Not all code paths return a value.

+ 4 - 0
modules/gdscript/tests/scripts/analyzer/errors/lambda_wrong_return.gd

@@ -0,0 +1,4 @@
+func test():
+	var lambda := func() -> int:
+		return 'string'
+	print(lambda.call())

+ 2 - 0
modules/gdscript/tests/scripts/analyzer/errors/lambda_wrong_return.out

@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot return value of type "String" because the function return type is "int".

+ 12 - 0
modules/gdscript/tests/scripts/analyzer/features/lambda_typed.gd

@@ -0,0 +1,12 @@
+func test():
+	var lambda_0 := func() -> void:
+		print(0)
+	lambda_0.call()
+
+	var lambda_1 := func(printed: int) -> void:
+		print(printed)
+	lambda_1.call(1)
+
+	var lambda_2 := func(identity: int) -> int:
+		return identity
+	print(lambda_2.call(2))

+ 4 - 0
modules/gdscript/tests/scripts/analyzer/features/lambda_typed.out

@@ -0,0 +1,4 @@
+GDTEST_OK
+0
+1
+2

+ 6 - 0
modules/gdscript/tests/scripts/analyzer/warnings/lambda_shadowing_arg.gd

@@ -0,0 +1,6 @@
+var shadow: int
+
+func test():
+	var lambda := func(shadow: String) -> void:
+		print(shadow)
+	lambda.call('shadow')

+ 6 - 0
modules/gdscript/tests/scripts/analyzer/warnings/lambda_shadowing_arg.out

@@ -0,0 +1,6 @@
+GDTEST_OK
+>> WARNING
+>> Line: 4
+>> SHADOWED_VARIABLE
+>> The local function parameter "shadow" is shadowing an already-declared variable at line 1.
+shadow

+ 4 - 0
modules/gdscript/tests/scripts/analyzer/warnings/lambda_unused_arg.gd

@@ -0,0 +1,4 @@
+func test():
+	var lambda := func(unused: Variant) -> void:
+		pass
+	lambda.call()

+ 5 - 0
modules/gdscript/tests/scripts/analyzer/warnings/lambda_unused_arg.out

@@ -0,0 +1,5 @@
+GDTEST_OK
+>> WARNING
+>> Line: 2
+>> UNUSED_PARAMETER
+>>