diff options
| author | Rémi Verschelde <rverschelde@gmail.com> | 2023-01-06 00:34:10 +0100 | 
|---|---|---|
| committer | Rémi Verschelde <rverschelde@gmail.com> | 2023-01-06 00:34:10 +0100 | 
| commit | 95ce236b7d6a70a06ecc13fb08d48da90ed98430 (patch) | |
| tree | 4da02e8aa6d604d0fc619843648f6b1d2d95ab06 | |
| parent | 158540e96ab225042c72eca597c6227a6df0c8d4 (diff) | |
| parent | a1d06749f18c3f47c6443ece2ec625d8ee5f1761 (diff) | |
Merge pull request #70464 from vonagam/unify-assignables
Unify typing of variables, constants and parameters in GDScript
17 files changed, 237 insertions, 357 deletions
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 96c8894586..eb660ef09f 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -758,80 +758,8 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,  		switch (member.type) {  			case GDScriptParser::ClassNode::Member::VARIABLE: {  				check_class_member_name_conflict(p_class, member.variable->identifier->name, member.variable); -  				member.variable->set_datatype(resolving_datatype); - -				GDScriptParser::DataType datatype; -				datatype.kind = GDScriptParser::DataType::VARIANT; -				datatype.type_source = GDScriptParser::DataType::UNDETECTED; - -				GDScriptParser::DataType specified_type; -				if (member.variable->datatype_specifier != nullptr) { -					specified_type = resolve_datatype(member.variable->datatype_specifier); -					specified_type.is_meta_type = false; -				} - -				if (member.variable->initializer != nullptr) { -					reduce_expression(member.variable->initializer); -					if ((member.variable->infer_datatype || (member.variable->datatype_specifier != nullptr && specified_type.has_container_element_type())) && member.variable->initializer->type == GDScriptParser::Node::ARRAY) { -						// Typed array. -						GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(member.variable->initializer); -						// Can only infer typed array if it has elements. -						if ((member.variable->infer_datatype && array->elements.size() > 0) || member.variable->datatype_specifier != nullptr) { -							update_array_literal_element_type(specified_type, array); -						} -					} -					datatype = member.variable->initializer->get_datatype(); - -					if (datatype.type_source != GDScriptParser::DataType::UNDETECTED) { -						datatype.type_source = GDScriptParser::DataType::INFERRED; -					} - -					if (!datatype.is_set()) { -						push_error(vformat(R"(Could not resolve initializer for member "%s".)", member.variable->identifier->name), member.variable->initializer); -						datatype.kind = GDScriptParser::DataType::VARIANT; -					} -				} - -				if (member.variable->datatype_specifier != nullptr) { -					datatype = specified_type; - -					if (member.variable->initializer != nullptr) { -						if (!is_type_compatible(datatype, member.variable->initializer->get_datatype(), true, member.variable->initializer)) { -							// Try reverse test since it can be a masked subtype. -							if (!is_type_compatible(member.variable->initializer->get_datatype(), datatype, true, member.variable->initializer)) { -								push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer); -							} else { -								// TODO: Add warning. -								mark_node_unsafe(member.variable->initializer); -								member.variable->use_conversion_assign = true; -							} -						} else if (datatype.builtin_type == Variant::INT && member.variable->initializer->get_datatype().builtin_type == Variant::FLOAT) { -#ifdef DEBUG_ENABLED -							parser->push_warning(member.variable->initializer, GDScriptWarning::NARROWING_CONVERSION); -#endif -						} -						if (member.variable->initializer->get_datatype().is_variant()) { -							// TODO: Warn unsafe assign. -							mark_node_unsafe(member.variable->initializer); -							member.variable->use_conversion_assign = true; -						} -					} -				} else if (member.variable->infer_datatype) { -					if (member.variable->initializer == nullptr) { -						push_error(vformat(R"(Cannot infer the type of "%s" variable because there's no default value.)", member.variable->identifier->name), member.variable->identifier); -					} else if (!datatype.is_set() || datatype.has_no_type()) { -						push_error(vformat(R"(Cannot infer the type of "%s" variable because the initial value doesn't have a set type.)", member.variable->identifier->name), member.variable->initializer); -					} else if (datatype.is_variant()) { -						push_error(vformat(R"(Cannot infer the type of "%s" variable because the initial value is Variant. Use explicit "Variant" type if this is intended.)", member.variable->identifier->name), member.variable->initializer); -					} else if (datatype.builtin_type == Variant::NIL) { -						push_error(vformat(R"(Cannot infer the type of "%s" variable because the initial value is "null".)", member.variable->identifier->name), member.variable->initializer); -					} -					datatype.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; -				} - -				datatype.is_constant = false; -				member.variable->set_datatype(datatype); +				resolve_variable(member.variable, false);  				// Apply annotations.  				for (GDScriptParser::AnnotationNode *&E : member.variable->annotations) { @@ -840,56 +768,8 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,  			} break;  			case GDScriptParser::ClassNode::Member::CONSTANT: {  				check_class_member_name_conflict(p_class, member.constant->identifier->name, member.constant); -  				member.constant->set_datatype(resolving_datatype); - -				GDScriptParser::DataType specified_type; -				if (member.constant->datatype_specifier != nullptr) { -					specified_type = resolve_datatype(member.constant->datatype_specifier); -					specified_type.is_meta_type = false; -				} - -				GDScriptParser::DataType datatype; -				if (member.constant->initializer) { -					reduce_expression(member.constant->initializer); -					datatype = member.constant->initializer->get_datatype(); - -					if (member.constant->initializer->type == GDScriptParser::Node::ARRAY) { -						GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(member.constant->initializer); -						const_fold_array(array); - -						// Can only infer typed array if it has elements. -						if (array->elements.size() > 0 || (member.constant->datatype_specifier != nullptr && specified_type.has_container_element_type())) { -							update_array_literal_element_type(specified_type, array); -						} -					} else if (member.constant->initializer->type == GDScriptParser::Node::DICTIONARY) { -						const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(member.constant->initializer)); -					} - -					if (!datatype.is_set()) { -						push_error(vformat(R"(Could not resolve initializer for member "%s".)", member.constant->identifier->name), member.constant->initializer); -						datatype.kind = GDScriptParser::DataType::VARIANT; -					} - -					if (!member.constant->initializer->is_constant) { -						push_error(R"(Initializer for a constant must be a constant expression.)", member.constant->initializer); -					} - -					if (member.constant->datatype_specifier != nullptr) { -						datatype = specified_type; - -						if (!is_type_compatible(datatype, member.constant->initializer->get_datatype(), true)) { -							push_error(vformat(R"(Value of type "%s" cannot be initialized to constant of type "%s".)", member.constant->initializer->get_datatype().to_string(), datatype.to_string()), member.constant->initializer); -						} else if (datatype.builtin_type == Variant::INT && member.constant->initializer->get_datatype().builtin_type == Variant::FLOAT) { -#ifdef DEBUG_ENABLED -							parser->push_warning(member.constant->initializer, GDScriptWarning::NARROWING_CONVERSION); -#endif -						} -					} -				} -				datatype.is_constant = true; - -				member.constant->set_datatype(datatype); +				resolve_constant(member.constant, false);  				// Apply annotations.  				for (GDScriptParser::AnnotationNode *&E : member.constant->annotations) { @@ -1310,7 +1190,7 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root  			}  			break;  		case GDScriptParser::Node::CONSTANT: -			resolve_constant(static_cast<GDScriptParser::ConstantNode *>(p_node)); +			resolve_constant(static_cast<GDScriptParser::ConstantNode *>(p_node), true);  			break;  		case GDScriptParser::Node::FOR:  			resolve_for(static_cast<GDScriptParser::ForNode *>(p_node)); @@ -1326,7 +1206,7 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root  			resolve_suite(static_cast<GDScriptParser::SuiteNode *>(p_node));  			break;  		case GDScriptParser::Node::VARIABLE: -			resolve_variable(static_cast<GDScriptParser::VariableNode *>(p_node)); +			resolve_variable(static_cast<GDScriptParser::VariableNode *>(p_node), true);  			break;  		case GDScriptParser::Node::WHILE:  			resolve_while(static_cast<GDScriptParser::WhileNode *>(p_node)); @@ -1426,11 +1306,11 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *  		is_shadowing(p_function->parameters[i]->identifier, "function parameter");  #endif // DEBUG_ENABLED  #ifdef TOOLS_ENABLED -		if (p_function->parameters[i]->default_value) { +		if (p_function->parameters[i]->initializer) {  			default_value_count++; -			if (p_function->parameters[i]->default_value->is_constant) { -				p_function->default_arg_values.push_back(p_function->parameters[i]->default_value->reduced_value); +			if (p_function->parameters[i]->initializer->is_constant) { +				p_function->default_arg_values.push_back(p_function->parameters[i]->initializer->reduced_value);  			} else {  				p_function->default_arg_values.push_back(Variant()); // Prevent shift.  			} @@ -1601,6 +1481,132 @@ void GDScriptAnalyzer::resolve_suite(GDScriptParser::SuiteNode *p_suite) {  	}  } +void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assignable, const char *p_kind) { +	GDScriptParser::DataType type; +	type.kind = GDScriptParser::DataType::VARIANT; + +	bool is_variable = p_assignable->type == GDScriptParser::Node::VARIABLE; +	bool is_constant = p_assignable->type == GDScriptParser::Node::CONSTANT; + +	GDScriptParser::DataType specified_type; +	bool has_specified_type = p_assignable->datatype_specifier != nullptr; +	if (has_specified_type) { +		specified_type = resolve_datatype(p_assignable->datatype_specifier); +		specified_type.is_meta_type = false; +		type = specified_type; +	} + +	if (p_assignable->initializer != nullptr) { +		reduce_expression(p_assignable->initializer); + +		if (p_assignable->initializer->type == GDScriptParser::Node::ARRAY) { +			GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(p_assignable->initializer); +			if ((p_assignable->infer_datatype && array->elements.size() > 0) || (has_specified_type && specified_type.has_container_element_type())) { +				update_array_literal_element_type(specified_type, array); +			} +		} + +		if (is_constant) { +			if (p_assignable->initializer->type == GDScriptParser::Node::ARRAY) { +				const_fold_array(static_cast<GDScriptParser::ArrayNode *>(p_assignable->initializer)); +			} else if (p_assignable->initializer->type == GDScriptParser::Node::DICTIONARY) { +				const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(p_assignable->initializer)); +			} +			if (!p_assignable->initializer->is_constant) { +				push_error(vformat(R"(Assigned value for %s "%s" isn't a constant expression.)", p_kind, p_assignable->identifier->name), p_assignable->initializer); +			} +		} + +		GDScriptParser::DataType initializer_type = p_assignable->initializer->get_datatype(); + +		if (p_assignable->infer_datatype) { +			if (!initializer_type.is_set() || initializer_type.has_no_type()) { +				push_error(vformat(R"(Cannot infer the type of "%s" %s because the value doesn't have a set type.)", p_assignable->identifier->name, p_kind), p_assignable->initializer); +			} else if (initializer_type.is_variant() && !initializer_type.is_hard_type()) { +				push_error(vformat(R"(Cannot infer the type of "%s" %s because the value is Variant. Use explicit "Variant" type if this is intended.)", p_assignable->identifier->name, p_kind), p_assignable->initializer); +			} else if (initializer_type.kind == GDScriptParser::DataType::BUILTIN && initializer_type.builtin_type == Variant::NIL && !is_constant) { +				push_error(vformat(R"(Cannot infer the type of "%s" %s because the value is "null".)", p_assignable->identifier->name, p_kind), p_assignable->initializer); +			} +		} else { +			if (!initializer_type.is_set()) { +				push_error(vformat(R"(Could not resolve type for %s "%s".)", p_kind, p_assignable->identifier->name), p_assignable->initializer); +			} +		} + +		if (!has_specified_type) { +			type = initializer_type; + +			if (!type.is_set() || (type.is_hard_type() && type.kind == GDScriptParser::DataType::BUILTIN && type.builtin_type == Variant::NIL && !is_constant)) { +				type.kind = GDScriptParser::DataType::VARIANT; +			} + +			if (p_assignable->infer_datatype || is_constant) { +				type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; +			} else { +				type.type_source = GDScriptParser::DataType::INFERRED; +			} +		} else if (!specified_type.is_variant()) { +			if (initializer_type.is_variant() || !initializer_type.is_hard_type()) { +				mark_node_unsafe(p_assignable->initializer); +				if (is_variable) { +					static_cast<GDScriptParser::VariableNode *>(p_assignable)->use_conversion_assign = true; +				} +			} else if (!is_type_compatible(specified_type, initializer_type, true, p_assignable->initializer)) { +				if (is_variable && is_type_compatible(initializer_type, specified_type, true, p_assignable->initializer)) { +					mark_node_unsafe(p_assignable->initializer); +					static_cast<GDScriptParser::VariableNode *>(p_assignable)->use_conversion_assign = true; +				} else { +					push_error(vformat(R"(Cannot assign a value of type %s to %s "%s" with specified type %s.)", initializer_type.to_string(), p_kind, p_assignable->identifier->name, specified_type.to_string()), p_assignable->initializer); +				} +#ifdef DEBUG_ENABLED +			} else if (specified_type.builtin_type == Variant::INT && initializer_type.builtin_type == Variant::FLOAT) { +				parser->push_warning(p_assignable->initializer, GDScriptWarning::NARROWING_CONVERSION); +#endif +			} +		} +	} + +	type.is_constant = is_constant; +	p_assignable->set_datatype(type); +} + +void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable, bool p_is_local) { +	static constexpr const char *kind = "variable"; +	resolve_assignable(p_variable, kind); + +#ifdef DEBUG_ENABLED +	if (p_is_local) { +		if (p_variable->usages == 0 && !String(p_variable->identifier->name).begins_with("_")) { +			parser->push_warning(p_variable, GDScriptWarning::UNUSED_VARIABLE, p_variable->identifier->name); +		} else if (p_variable->assignments == 0) { +			parser->push_warning(p_variable, GDScriptWarning::UNASSIGNED_VARIABLE, p_variable->identifier->name); +		} + +		is_shadowing(p_variable->identifier, kind); +	} +#endif +} + +void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant, bool p_is_local) { +	static constexpr const char *kind = "constant"; +	resolve_assignable(p_constant, kind); + +#ifdef DEBUG_ENABLED +	if (p_is_local) { +		if (p_constant->usages == 0) { +			parser->push_warning(p_constant, GDScriptWarning::UNUSED_LOCAL_CONSTANT, p_constant->identifier->name); +		} + +		is_shadowing(p_constant->identifier, kind); +	} +#endif +} + +void GDScriptAnalyzer::resolve_parameter(GDScriptParser::ParameterNode *p_parameter) { +	static constexpr const char *kind = "parameter"; +	resolve_assignable(p_parameter, kind); +} +  void GDScriptAnalyzer::resolve_if(GDScriptParser::IfNode *p_if) {  	reduce_expression(p_if->condition); @@ -1728,148 +1734,6 @@ void GDScriptAnalyzer::resolve_while(GDScriptParser::WhileNode *p_while) {  	p_while->set_datatype(p_while->loop->get_datatype());  } -void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable) { -	GDScriptParser::DataType type; -	type.kind = GDScriptParser::DataType::VARIANT; // By default. - -	GDScriptParser::DataType specified_type; -	if (p_variable->datatype_specifier != nullptr) { -		specified_type = resolve_datatype(p_variable->datatype_specifier); -		specified_type.is_meta_type = false; -	} - -	if (p_variable->initializer != nullptr) { -		reduce_expression(p_variable->initializer); -		if ((p_variable->infer_datatype || (p_variable->datatype_specifier != nullptr && specified_type.has_container_element_type())) && p_variable->initializer->type == GDScriptParser::Node::ARRAY) { -			// Typed array. -			GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(p_variable->initializer); -			// Can only infer typed array if it has elements. -			if ((p_variable->infer_datatype && array->elements.size() > 0) || p_variable->datatype_specifier != nullptr) { -				update_array_literal_element_type(specified_type, array); -			} -		} - -		type = p_variable->initializer->get_datatype(); - -		if (p_variable->infer_datatype) { -			type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; - -			if (type.has_no_type()) { -				push_error(vformat(R"(Could not infer the type of the variable "%s" because the initial value does not have a set type.)", p_variable->identifier->name), p_variable->initializer); -			} else if (type.is_variant()) { -				push_error(vformat(R"(Could not infer the type of the variable "%s" because the initial value is a variant. Use explicit "Variant" type if this is intended.)", p_variable->identifier->name), p_variable->initializer); -			} else if (type.kind == GDScriptParser::DataType::BUILTIN && type.builtin_type == Variant::NIL) { -				push_error(vformat(R"(Could not infer the type of the variable "%s" because the initial value is "null".)", p_variable->identifier->name), p_variable->initializer); -			} -		} else { -			type.type_source = GDScriptParser::DataType::INFERRED; -		} -	} - -	if (p_variable->datatype_specifier != nullptr) { -		type = specified_type; -		type.is_meta_type = false; - -		if (p_variable->initializer != nullptr) { -			if (!is_type_compatible(type, p_variable->initializer->get_datatype(), true, p_variable->initializer)) { -				// Try reverse test since it can be a masked subtype. -				if (!is_type_compatible(p_variable->initializer->get_datatype(), type, true, p_variable->initializer)) { -					push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", p_variable->initializer->get_datatype().to_string(), type.to_string()), p_variable->initializer); -				} else { -					// TODO: Add warning. -					mark_node_unsafe(p_variable->initializer); -					p_variable->use_conversion_assign = true; -				} -#ifdef DEBUG_ENABLED -			} else if (type.builtin_type == Variant::INT && p_variable->initializer->get_datatype().builtin_type == Variant::FLOAT) { -				parser->push_warning(p_variable->initializer, GDScriptWarning::NARROWING_CONVERSION); -#endif -			} -			if (p_variable->initializer->get_datatype().is_variant() && !type.is_variant()) { -				// TODO: Warn unsafe assign. -				mark_node_unsafe(p_variable->initializer); -				p_variable->use_conversion_assign = true; -			} -		} -	} else if (p_variable->infer_datatype) { -		if (type.has_no_type()) { -			push_error(vformat(R"(Cannot infer the type of variable "%s" because the initial value doesn't have a set type.)", p_variable->identifier->name), p_variable->identifier); -		} -		type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; -	} - -	type.is_constant = false; -	p_variable->set_datatype(type); - -#ifdef DEBUG_ENABLED -	if (p_variable->usages == 0 && !String(p_variable->identifier->name).begins_with("_")) { -		parser->push_warning(p_variable, GDScriptWarning::UNUSED_VARIABLE, p_variable->identifier->name); -	} else if (p_variable->assignments == 0) { -		parser->push_warning(p_variable, GDScriptWarning::UNASSIGNED_VARIABLE, p_variable->identifier->name); -	} - -	is_shadowing(p_variable->identifier, "variable"); -#endif -} - -void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant) { -	GDScriptParser::DataType type; - -	GDScriptParser::DataType explicit_type; -	if (p_constant->datatype_specifier != nullptr) { -		explicit_type = resolve_datatype(p_constant->datatype_specifier); -		explicit_type.is_meta_type = false; -	} - -	if (p_constant->initializer != nullptr) { -		reduce_expression(p_constant->initializer); -		if (p_constant->initializer->type == GDScriptParser::Node::ARRAY) { -			GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(p_constant->initializer); -			const_fold_array(array); - -			// Can only infer typed array if it has elements. -			if (array->elements.size() > 0 || (p_constant->datatype_specifier != nullptr && explicit_type.has_container_element_type())) { -				update_array_literal_element_type(explicit_type, array); -			} -		} else if (p_constant->initializer->type == GDScriptParser::Node::DICTIONARY) { -			const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(p_constant->initializer)); -		} - -		if (!p_constant->initializer->is_constant) { -			push_error(vformat(R"(Assigned value for constant "%s" isn't a constant expression.)", p_constant->identifier->name), p_constant->initializer); -		} - -		type = p_constant->initializer->get_datatype(); -	} - -	if (p_constant->datatype_specifier != nullptr) { -		if (!is_type_compatible(explicit_type, type, true)) { -			push_error(vformat(R"(Assigned value for constant "%s" has type %s which is not compatible with defined type %s.)", p_constant->identifier->name, type.to_string(), explicit_type.to_string()), p_constant->initializer); -#ifdef DEBUG_ENABLED -		} else if (explicit_type.builtin_type == Variant::INT && type.builtin_type == Variant::FLOAT) { -			parser->push_warning(p_constant->initializer, GDScriptWarning::NARROWING_CONVERSION); -#endif -		} -		type = explicit_type; -	} else if (p_constant->infer_datatype) { -		if (type.has_no_type()) { -			push_error(vformat(R"(Cannot infer the type of constant "%s" because the initial value doesn't have a set type.)", p_constant->identifier->name), p_constant->identifier); -		} -		type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; -	} - -	type.is_constant = true; -	p_constant->set_datatype(type); - -#ifdef DEBUG_ENABLED -	if (p_constant->usages == 0) { -		parser->push_warning(p_constant, GDScriptWarning::UNUSED_LOCAL_CONSTANT, p_constant->identifier->name); -	} - -	is_shadowing(p_constant->identifier, "constant"); -#endif -} -  void GDScriptAnalyzer::resolve_assert(GDScriptParser::AssertNode *p_assert) {  	reduce_expression(p_assert->condition);  	if (p_assert->message != nullptr) { @@ -1981,41 +1845,6 @@ void GDScriptAnalyzer::resolve_match_pattern(GDScriptParser::PatternNode *p_matc  	p_match_pattern->set_datatype(result);  } -void GDScriptAnalyzer::resolve_parameter(GDScriptParser::ParameterNode *p_parameter) { -	GDScriptParser::DataType result; -	result.kind = GDScriptParser::DataType::VARIANT; - -	if (p_parameter->default_value != nullptr) { -		reduce_expression(p_parameter->default_value); -		result = p_parameter->default_value->get_datatype(); -		if (p_parameter->infer_datatype) { -			result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; -		} else { -			result.type_source = GDScriptParser::DataType::INFERRED; -		} -	} - -	if (p_parameter->datatype_specifier != nullptr) { -		result = resolve_datatype(p_parameter->datatype_specifier); -		result.is_meta_type = false; - -		if (p_parameter->default_value != nullptr) { -			if (!is_type_compatible(result, p_parameter->default_value->get_datatype())) { -				push_error(vformat(R"(Type of default value for parameter "%s" (%s) is not compatible with parameter type (%s).)", p_parameter->identifier->name, p_parameter->default_value->get_datatype().to_string(), p_parameter->datatype_specifier->get_datatype().to_string()), p_parameter->default_value); -			} else if (p_parameter->default_value->get_datatype().is_variant()) { -				mark_node_unsafe(p_parameter); -			} -		} -	} - -	if (result.builtin_type == Variant::Type::NIL && result.type_source == GDScriptParser::DataType::ANNOTATED_INFERRED && p_parameter->datatype_specifier == nullptr) { -		push_error(vformat(R"(Could not infer the type of the variable "%s" because the initial value is "null".)", p_parameter->identifier->name), p_parameter->default_value); -	} - -	result.is_constant = false; -	p_parameter->set_datatype(result); -} -  void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) {  	GDScriptParser::DataType result; @@ -4171,7 +4000,7 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo  		r_static = p_is_constructor || found_function->is_static;  		for (int i = 0; i < found_function->parameters.size(); i++) {  			r_par_types.push_back(found_function->parameters[i]->get_datatype()); -			if (found_function->parameters[i]->default_value != nullptr) { +			if (found_function->parameters[i]->initializer != nullptr) {  				r_default_arg_count++;  			}  		} diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 9af7264cb8..9ac33d674a 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -69,16 +69,17 @@ class GDScriptAnalyzer {  	void resolve_function_body(GDScriptParser::FunctionNode *p_function);  	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); +	void resolve_variable(GDScriptParser::VariableNode *p_variable, bool p_is_local); +	void resolve_constant(GDScriptParser::ConstantNode *p_constant, bool p_is_local); +	void resolve_parameter(GDScriptParser::ParameterNode *p_parameter);  	void resolve_if(GDScriptParser::IfNode *p_if);  	void resolve_for(GDScriptParser::ForNode *p_for);  	void resolve_while(GDScriptParser::WhileNode *p_while); -	void resolve_variable(GDScriptParser::VariableNode *p_variable); -	void resolve_constant(GDScriptParser::ConstantNode *p_constant);  	void resolve_assert(GDScriptParser::AssertNode *p_assert);  	void resolve_match(GDScriptParser::MatchNode *p_match);  	void resolve_match_branch(GDScriptParser::MatchBranchNode *p_match_branch, GDScriptParser::ExpressionNode *p_match_test);  	void resolve_match_pattern(GDScriptParser::PatternNode *p_match_pattern, GDScriptParser::ExpressionNode *p_match_test); -	void resolve_parameter(GDScriptParser::ParameterNode *p_parameter);  	void resolve_return(GDScriptParser::ReturnNode *p_return);  	// Reduction functions. diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index dcbb3f7363..db32ef22b0 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -2022,10 +2022,10 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_  		for (int i = 0; i < p_func->parameters.size(); i++) {  			const GDScriptParser::ParameterNode *parameter = p_func->parameters[i];  			GDScriptDataType par_type = _gdtype_from_datatype(parameter->get_datatype(), p_script); -			uint32_t par_addr = codegen.generator->add_parameter(parameter->identifier->name, parameter->default_value != nullptr, par_type); +			uint32_t par_addr = codegen.generator->add_parameter(parameter->identifier->name, parameter->initializer != nullptr, par_type);  			codegen.parameters[parameter->identifier->name] = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::FUNCTION_PARAMETER, par_addr, par_type); -			if (p_func->parameters[i]->default_value != nullptr) { +			if (p_func->parameters[i]->initializer != nullptr) {  				optional_parameters++;  			}  		} @@ -2097,7 +2097,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_  			codegen.generator->start_parameters();  			for (int i = p_func->parameters.size() - optional_parameters; i < p_func->parameters.size(); i++) {  				const GDScriptParser::ParameterNode *parameter = p_func->parameters[i]; -				GDScriptCodeGenerator::Address src_addr = _parse_expression(codegen, r_error, parameter->default_value); +				GDScriptCodeGenerator::Address src_addr = _parse_expression(codegen, r_error, parameter->initializer);  				if (r_error) {  					memdelete(codegen.generator);  					return nullptr; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 8027c41a8c..6f8b90bd06 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -683,37 +683,37 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio  			arghint += par->identifier->name.operator String() + ": " + par->get_datatype().to_string();  		} -		if (par->default_value) { +		if (par->initializer) {  			String def_val = "<unknown>"; -			switch (par->default_value->type) { +			switch (par->initializer->type) {  				case GDScriptParser::Node::LITERAL: { -					const GDScriptParser::LiteralNode *literal = static_cast<const GDScriptParser::LiteralNode *>(par->default_value); +					const GDScriptParser::LiteralNode *literal = static_cast<const GDScriptParser::LiteralNode *>(par->initializer);  					def_val = literal->value.get_construct_string();  				} break;  				case GDScriptParser::Node::IDENTIFIER: { -					const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(par->default_value); +					const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(par->initializer);  					def_val = id->name.operator String();  				} break;  				case GDScriptParser::Node::CALL: { -					const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(par->default_value); +					const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(par->initializer);  					if (call->is_constant && call->reduced) {  						def_val = call->function_name.operator String() + call->reduced_value.operator String();  					}  				} break;  				case GDScriptParser::Node::ARRAY: { -					const GDScriptParser::ArrayNode *arr = static_cast<const GDScriptParser::ArrayNode *>(par->default_value); +					const GDScriptParser::ArrayNode *arr = static_cast<const GDScriptParser::ArrayNode *>(par->initializer);  					if (arr->is_constant && arr->reduced) {  						def_val = arr->reduced_value.operator String();  					}  				} break;  				case GDScriptParser::Node::DICTIONARY: { -					const GDScriptParser::DictionaryNode *dict = static_cast<const GDScriptParser::DictionaryNode *>(par->default_value); +					const GDScriptParser::DictionaryNode *dict = static_cast<const GDScriptParser::DictionaryNode *>(par->initializer);  					if (dict->is_constant && dict->reduced) {  						def_val = dict->reduced_value.operator String();  					}  				} break;  				case GDScriptParser::Node::SUBSCRIPT: { -					const GDScriptParser::SubscriptNode *sub = static_cast<const GDScriptParser::SubscriptNode *>(par->default_value); +					const GDScriptParser::SubscriptNode *sub = static_cast<const GDScriptParser::SubscriptNode *>(par->initializer);  					if (sub->is_constant) {  						if (sub->datatype.kind == GDScriptParser::DataType::ENUM) {  							def_val = sub->get_datatype().to_string(); @@ -1856,9 +1856,9 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,  					}  					break;  				case GDScriptParser::SuiteNode::Local::PARAMETER: -					if (local.parameter->default_value) { -						last_assign_line = local.parameter->default_value->end_line; -						last_assigned_expression = local.parameter->default_value; +					if (local.parameter->initializer) { +						last_assign_line = local.parameter->initializer->end_line; +						last_assigned_expression = local.parameter->initializer;  					}  					is_function_parameter = true;  					break; @@ -1939,12 +1939,12 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,  						if ((!id_type.is_set() || id_type.is_variant()) && parameter->get_datatype().is_hard_type()) {  							id_type = parameter->get_datatype();  						} -						if (parameter->default_value) { +						if (parameter->initializer) {  							GDScriptParser::CompletionContext c = p_context;  							c.current_function = parent_function;  							c.current_class = base_type.class_type;  							c.base = nullptr; -							if (_guess_expression_type(c, parameter->default_value, r_type)) { +							if (_guess_expression_type(c, parameter->initializer, r_type)) {  								return true;  							}  						} diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index cb1005a461..6107bb37c8 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -1219,7 +1219,7 @@ GDScriptParser::ParameterNode *GDScriptParser::parse_parameter() {  	if (match(GDScriptTokenizer::Token::EQUAL)) {  		// Default value. -		parameter->default_value = parse_expression(false); +		parameter->initializer = parse_expression(false);  	}  	complete_extents(parameter); @@ -1250,7 +1250,7 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() {  				push_error("Expected signal parameter name.");  				break;  			} -			if (parameter->default_value != nullptr) { +			if (parameter->initializer != nullptr) {  				push_error(R"(Signal parameters cannot have a default value.)");  			}  			if (signal->parameters_indices.has(parameter->identifier->name)) { @@ -1395,7 +1395,7 @@ void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNod  			if (parameter == nullptr) {  				break;  			} -			if (parameter->default_value != nullptr) { +			if (parameter->initializer != nullptr) {  				default_used = true;  			} else {  				if (default_used) { @@ -4776,9 +4776,9 @@ void GDScriptParser::TreePrinter::print_parameter(ParameterNode *p_parameter) {  		push_text(" : ");  		print_type(p_parameter->datatype_specifier);  	} -	if (p_parameter->default_value != nullptr) { +	if (p_parameter->initializer != nullptr) {  		push_text(" = "); -		print_expression(p_parameter->default_value); +		print_expression(p_parameter->initializer);  	}  } diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index e0c8042162..65eace8088 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -57,6 +57,7 @@ public:  	struct AnnotationNode;  	struct ArrayNode;  	struct AssertNode; +	struct AssignableNode;  	struct AssignmentNode;  	struct AwaitNode;  	struct BinaryOpNode; @@ -354,6 +355,19 @@ public:  		}  	}; +	struct AssignableNode : public Node { +		IdentifierNode *identifier = nullptr; +		ExpressionNode *initializer = nullptr; +		TypeNode *datatype_specifier = nullptr; +		bool infer_datatype = false; +		int usages = 0; + +		virtual ~AssignableNode() {} + +	protected: +		AssignableNode() {} +	}; +  	struct AssignmentNode : public ExpressionNode {  		// Assignment is not really an expression but it's easier to parse as if it were.  		enum Operation { @@ -732,12 +746,7 @@ public:  		}  	}; -	struct ConstantNode : public Node { -		IdentifierNode *identifier = nullptr; -		ExpressionNode *initializer = nullptr; -		TypeNode *datatype_specifier = nullptr; -		bool infer_datatype = false; -		int usages = 0; +	struct ConstantNode : public AssignableNode {  #ifdef TOOLS_ENABLED  		String doc_description;  #endif // TOOLS_ENABLED @@ -902,13 +911,7 @@ public:  		}  	}; -	struct ParameterNode : public Node { -		IdentifierNode *identifier = nullptr; -		ExpressionNode *default_value = nullptr; -		TypeNode *datatype_specifier = nullptr; -		bool infer_datatype = false; -		int usages = 0; - +	struct ParameterNode : public AssignableNode {  		ParameterNode() {  			type = PARAMETER;  		} @@ -1157,18 +1160,13 @@ public:  		}  	}; -	struct VariableNode : public Node { +	struct VariableNode : public AssignableNode {  		enum PropertyStyle {  			PROP_NONE,  			PROP_INLINE,  			PROP_SETGET,  		}; -		IdentifierNode *identifier = nullptr; -		ExpressionNode *initializer = nullptr; -		TypeNode *datatype_specifier = nullptr; -		bool infer_datatype = false; -  		PropertyStyle property = PROP_NONE;  		union {  			FunctionNode *setter = nullptr; @@ -1184,7 +1182,6 @@ public:  		bool onready = false;  		PropertyInfo export_info;  		int assignments = 0; -		int usages = 0;  		bool use_conversion_assign = false;  #ifdef TOOLS_ENABLED  		String doc_description; diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index a96730b6ff..146ed10ceb 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -350,8 +350,8 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN  		if (parameter->get_datatype().is_hard_type()) {  			parameters += ": " + parameter->get_datatype().to_string();  		} -		if (parameter->default_value != nullptr) { -			parameters += " = " + parameter->default_value->reduced_value.to_json_string(); +		if (parameter->initializer != nullptr) { +			parameters += " = " + parameter->initializer->reduced_value.to_json_string();  		}  	}  	r_symbol.detail += parameters + ")"; @@ -695,8 +695,8 @@ Dictionary ExtendGDScriptParser::dump_function_api(const GDScriptParser::Functio  		Dictionary arg;  		arg["name"] = p_func->parameters[i]->identifier->name;  		arg["type"] = p_func->parameters[i]->get_datatype().to_string(); -		if (p_func->parameters[i]->default_value != nullptr) { -			arg["default_value"] = p_func->parameters[i]->default_value->reduced_value; +		if (p_func->parameters[i]->initializer != nullptr) { +			arg["default_value"] = p_func->parameters[i]->initializer->reduced_value;  		}  		parameters.push_back(arg);  	} diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out index b1710c798d..6fa2682d0a 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out @@ -1,2 +1,2 @@  GDTEST_ANALYZER_ERROR -Value of type "MyOtherEnum (enum)" cannot be assigned to a variable of type "MyEnum (enum)". +Cannot assign a value of type MyOtherEnum (enum) to variable "class_var" with specified type MyEnum (enum). diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out index b1710c798d..07fb19f1ff 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out @@ -1,2 +1,2 @@  GDTEST_ANALYZER_ERROR -Value of type "MyOtherEnum (enum)" cannot be assigned to a variable of type "MyEnum (enum)". +Cannot assign a value of type MyOtherEnum (enum) to variable "local_var" with specified type MyEnum (enum). diff --git a/modules/gdscript/tests/scripts/analyzer/errors/setter_parameter_uses_property_type.out b/modules/gdscript/tests/scripts/analyzer/errors/setter_parameter_uses_property_type.out index 9eb2a42ccd..2857cd53c8 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/setter_parameter_uses_property_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/setter_parameter_uses_property_type.out @@ -1,2 +1,2 @@  GDTEST_ANALYZER_ERROR -Value of type "int" cannot be assigned to a variable of type "String". +Cannot assign a value of type int to variable "x" with specified type String. diff --git a/modules/gdscript/tests/scripts/analyzer/features/default_arg_convertable.gd b/modules/gdscript/tests/scripts/analyzer/features/default_arg_convertable.gd new file mode 100644 index 0000000000..d0d04897e0 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/default_arg_convertable.gd @@ -0,0 +1,6 @@ +func check(arg: float = 3): +	return typeof(arg) == typeof(3.0) + +func test(): +	if check(): +		print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/default_arg_convertable.out b/modules/gdscript/tests/scripts/analyzer/features/default_arg_convertable.out new file mode 100644 index 0000000000..1b47ed10dc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/default_arg_convertable.out @@ -0,0 +1,2 @@ +GDTEST_OK +ok diff --git a/modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd b/modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd new file mode 100644 index 0000000000..5a413e2015 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd @@ -0,0 +1,32 @@ +func check(input: int) -> bool: +	return input == 1 + +var recur = null +var prop = null + +func check_arg(arg = null) -> void: +	if arg != null: +		print(check(arg)) + +func check_recur() -> void: +	if recur != null: +		print(check(recur)) +	else: +		recur = 1 +		check_recur() + +func test() -> void: +	check_arg(1) + +	check_recur() + +	if prop == null: +		set('prop', 1) +		print(check(prop)) +		set('prop', null) + +	var loop = null +	while loop != 2: +		if loop != null: +			print(check(loop)) +		loop = 1 if loop == null else 2 diff --git a/modules/gdscript/tests/scripts/analyzer/features/null_initializer.out b/modules/gdscript/tests/scripts/analyzer/features/null_initializer.out new file mode 100644 index 0000000000..f9783e4362 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/null_initializer.out @@ -0,0 +1,5 @@ +GDTEST_OK +true +true +true +true diff --git a/modules/gdscript/tests/scripts/analyzer/features/weak_initializer.gd b/modules/gdscript/tests/scripts/analyzer/features/weak_initializer.gd new file mode 100644 index 0000000000..c5f3ccc59e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/weak_initializer.gd @@ -0,0 +1,5 @@ +func test(): +	var bar = 1 +	var foo: float = bar +	print(typeof(foo)) +	print(foo is float) diff --git a/modules/gdscript/tests/scripts/analyzer/features/weak_initializer.out b/modules/gdscript/tests/scripts/analyzer/features/weak_initializer.out new file mode 100644 index 0000000000..5d798c1f24 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/weak_initializer.out @@ -0,0 +1,3 @@ +GDTEST_OK +3 +true diff --git a/modules/gdscript/tests/scripts/analyzer/typed_array_assignment.out b/modules/gdscript/tests/scripts/analyzer/typed_array_assignment.out index 26b6e13d4f..ad2e6558d7 100644 --- a/modules/gdscript/tests/scripts/analyzer/typed_array_assignment.out +++ b/modules/gdscript/tests/scripts/analyzer/typed_array_assignment.out @@ -1,2 +1,2 @@  GDTEST_ANALYZER_ERROR -Assigned value for constant "arr" has type Array[String] which is not compatible with defined type Array[int]. +Cannot assign a value of type Array[String] to constant "arr" with specified type Array[int].  |