GDScript: Fix @warning_ignore
annotation issues
This commit is contained in:
parent
61282068f4
commit
ef1909fca3
25 changed files with 603 additions and 209 deletions
|
@ -470,11 +470,12 @@
|
||||||
<member name="debug/gdscript/warnings/confusable_local_usage" type="int" setter="" getter="" default="1">
|
<member name="debug/gdscript/warnings/confusable_local_usage" type="int" setter="" getter="" default="1">
|
||||||
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when an identifier that will be shadowed below in the block is used.
|
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when an identifier that will be shadowed below in the block is used.
|
||||||
</member>
|
</member>
|
||||||
<member name="debug/gdscript/warnings/constant_used_as_function" type="int" setter="" getter="" default="1">
|
<member name="debug/gdscript/warnings/constant_used_as_function" type="int" setter="" getter="" default="1" deprecated="This warning is never produced. Instead, an error is generated if the expression type is known at compile time.">
|
||||||
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a constant is used as a function.
|
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a constant is used as a function.
|
||||||
</member>
|
</member>
|
||||||
<member name="debug/gdscript/warnings/deprecated_keyword" type="int" setter="" getter="" default="1">
|
<member name="debug/gdscript/warnings/deprecated_keyword" type="int" setter="" getter="" default="1">
|
||||||
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when deprecated keywords are used.
|
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when deprecated keywords are used.
|
||||||
|
[b]Note:[/b] There are currently no deprecated keywords, so this warning is never produced.
|
||||||
</member>
|
</member>
|
||||||
<member name="debug/gdscript/warnings/empty_file" type="int" setter="" getter="" default="1">
|
<member name="debug/gdscript/warnings/empty_file" type="int" setter="" getter="" default="1">
|
||||||
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when an empty file is parsed.
|
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when an empty file is parsed.
|
||||||
|
@ -485,7 +486,7 @@
|
||||||
<member name="debug/gdscript/warnings/exclude_addons" type="bool" setter="" getter="" default="true">
|
<member name="debug/gdscript/warnings/exclude_addons" type="bool" setter="" getter="" default="true">
|
||||||
If [code]true[/code], scripts in the [code]res://addons[/code] folder will not generate warnings.
|
If [code]true[/code], scripts in the [code]res://addons[/code] folder will not generate warnings.
|
||||||
</member>
|
</member>
|
||||||
<member name="debug/gdscript/warnings/function_used_as_property" type="int" setter="" getter="" default="1">
|
<member name="debug/gdscript/warnings/function_used_as_property" type="int" setter="" getter="" default="1" deprecated="This warning is never produced. When a function is used as a property, a [Callable] is returned.">
|
||||||
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when using a function as if it is a property.
|
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when using a function as if it is a property.
|
||||||
</member>
|
</member>
|
||||||
<member name="debug/gdscript/warnings/get_node_default_without_onready" type="int" setter="" getter="" default="2">
|
<member name="debug/gdscript/warnings/get_node_default_without_onready" type="int" setter="" getter="" default="2">
|
||||||
|
@ -519,7 +520,7 @@
|
||||||
<member name="debug/gdscript/warnings/onready_with_export" type="int" setter="" getter="" default="2">
|
<member name="debug/gdscript/warnings/onready_with_export" type="int" setter="" getter="" default="2">
|
||||||
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when the [code]@onready[/code] annotation is used together with the [code]@export[/code] annotation, since it may not behave as expected.
|
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when the [code]@onready[/code] annotation is used together with the [code]@export[/code] annotation, since it may not behave as expected.
|
||||||
</member>
|
</member>
|
||||||
<member name="debug/gdscript/warnings/property_used_as_function" type="int" setter="" getter="" default="1">
|
<member name="debug/gdscript/warnings/property_used_as_function" type="int" setter="" getter="" default="1" deprecated="This warning is never produced. Instead, an error is generated if the expression type is known at compile time.">
|
||||||
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when using a property as if it is a function.
|
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when using a property as if it is a function.
|
||||||
</member>
|
</member>
|
||||||
<member name="debug/gdscript/warnings/redundant_await" type="int" setter="" getter="" default="1">
|
<member name="debug/gdscript/warnings/redundant_await" type="int" setter="" getter="" default="1">
|
||||||
|
@ -593,7 +594,7 @@
|
||||||
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a private member variable is never used.
|
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a private member variable is never used.
|
||||||
</member>
|
</member>
|
||||||
<member name="debug/gdscript/warnings/unused_signal" type="int" setter="" getter="" default="1">
|
<member name="debug/gdscript/warnings/unused_signal" type="int" setter="" getter="" default="1">
|
||||||
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a signal is declared but never emitted.
|
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a signal is declared but never explicitly used in the class.
|
||||||
</member>
|
</member>
|
||||||
<member name="debug/gdscript/warnings/unused_variable" type="int" setter="" getter="" default="1">
|
<member name="debug/gdscript/warnings/unused_variable" type="int" setter="" getter="" default="1">
|
||||||
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a local variable is unused.
|
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a local variable is unused.
|
||||||
|
|
|
@ -562,6 +562,12 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c
|
||||||
class_type.native_type = result.native_type;
|
class_type.native_type = result.native_type;
|
||||||
p_class->set_datatype(class_type);
|
p_class->set_datatype(class_type);
|
||||||
|
|
||||||
|
// Apply annotations.
|
||||||
|
for (GDScriptParser::AnnotationNode *&E : p_class->annotations) {
|
||||||
|
resolve_annotation(E);
|
||||||
|
E->apply(parser, p_class, p_class->outer);
|
||||||
|
}
|
||||||
|
|
||||||
parser->current_class = previous_class;
|
parser->current_class = previous_class;
|
||||||
|
|
||||||
return OK;
|
return OK;
|
||||||
|
@ -912,7 +918,6 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
|
||||||
|
|
||||||
{
|
{
|
||||||
#ifdef DEBUG_ENABLED
|
#ifdef DEBUG_ENABLED
|
||||||
HashSet<GDScriptWarning::Code> previously_ignored_warnings = parser->ignored_warnings;
|
|
||||||
GDScriptParser::Node *member_node = member.get_source_node();
|
GDScriptParser::Node *member_node = member.get_source_node();
|
||||||
if (member_node && member_node->type != GDScriptParser::Node::ANNOTATION) {
|
if (member_node && member_node->type != GDScriptParser::Node::ANNOTATION) {
|
||||||
// Apply @warning_ignore annotations before resolving member.
|
// Apply @warning_ignore annotations before resolving member.
|
||||||
|
@ -922,9 +927,6 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
|
||||||
E->apply(parser, member.variable, p_class);
|
E->apply(parser, member.variable, p_class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (GDScriptWarning::Code ignored_warning : member_node->ignored_warnings) {
|
|
||||||
parser->ignored_warnings.insert(ignored_warning);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
switch (member.type) {
|
switch (member.type) {
|
||||||
|
@ -1061,6 +1063,13 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
|
||||||
|
|
||||||
enum_type.enum_values[element.identifier->name] = element.value;
|
enum_type.enum_values[element.identifier->name] = element.value;
|
||||||
dictionary[String(element.identifier->name)] = element.value;
|
dictionary[String(element.identifier->name)] = element.value;
|
||||||
|
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
// Named enum identifiers do not shadow anything since you can only access them with `NamedEnum.ENUM_VALUE`.
|
||||||
|
if (member.m_enum->identifier->name == StringName()) {
|
||||||
|
is_shadowing(element.identifier, "enum member", false);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
current_enum = prev_enum;
|
current_enum = prev_enum;
|
||||||
|
@ -1133,9 +1142,6 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
|
||||||
ERR_PRINT("Trying to resolve undefined member.");
|
ERR_PRINT("Trying to resolve undefined member.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#ifdef DEBUG_ENABLED
|
|
||||||
parser->ignored_warnings = previously_ignored_warnings;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
parser->current_class = previous_class;
|
parser->current_class = previous_class;
|
||||||
|
@ -1146,11 +1152,11 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
|
||||||
p_source = p_class;
|
p_source = p_class;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!p_class->resolved_interface) {
|
||||||
#ifdef DEBUG_ENABLED
|
#ifdef DEBUG_ENABLED
|
||||||
bool has_static_data = p_class->has_static_data;
|
bool has_static_data = p_class->has_static_data;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!p_class->resolved_interface) {
|
|
||||||
if (!parser->has_class(p_class)) {
|
if (!parser->has_class(p_class)) {
|
||||||
String script_path = p_class->get_datatype().script_path;
|
String script_path = p_class->get_datatype().script_path;
|
||||||
Ref<GDScriptParserRef> parser_ref = get_parser_for(script_path);
|
Ref<GDScriptParserRef> parser_ref = get_parser_for(script_path);
|
||||||
|
@ -1178,6 +1184,7 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
p_class->resolved_interface = true;
|
p_class->resolved_interface = true;
|
||||||
|
|
||||||
if (resolve_class_inheritance(p_class) != OK) {
|
if (resolve_class_inheritance(p_class) != OK) {
|
||||||
|
@ -1319,10 +1326,6 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co
|
||||||
GDScriptParser::ClassNode::Member member = p_class->members[i];
|
GDScriptParser::ClassNode::Member member = p_class->members[i];
|
||||||
if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) {
|
if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) {
|
||||||
#ifdef DEBUG_ENABLED
|
#ifdef DEBUG_ENABLED
|
||||||
HashSet<GDScriptWarning::Code> previously_ignored_warnings = parser->ignored_warnings;
|
|
||||||
for (GDScriptWarning::Code ignored_warning : member.variable->ignored_warnings) {
|
|
||||||
parser->ignored_warnings.insert(ignored_warning);
|
|
||||||
}
|
|
||||||
if (member.variable->usages == 0 && String(member.variable->identifier->name).begins_with("_")) {
|
if (member.variable->usages == 0 && String(member.variable->identifier->name).begins_with("_")) {
|
||||||
parser->push_warning(member.variable->identifier, GDScriptWarning::UNUSED_PRIVATE_CLASS_VARIABLE, member.variable->identifier->name);
|
parser->push_warning(member.variable->identifier, GDScriptWarning::UNUSED_PRIVATE_CLASS_VARIABLE, member.variable->identifier->name);
|
||||||
}
|
}
|
||||||
|
@ -1396,10 +1399,12 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (member.type == GDScriptParser::ClassNode::Member::SIGNAL) {
|
||||||
#ifdef DEBUG_ENABLED
|
#ifdef DEBUG_ENABLED
|
||||||
parser->ignored_warnings = previously_ignored_warnings;
|
if (member.signal->usages == 0) {
|
||||||
#endif // DEBUG_ENABLED
|
parser->push_warning(member.signal->identifier, GDScriptWarning::UNUSED_SIGNAL, member.signal->identifier->name);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1431,6 +1436,7 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root
|
||||||
case GDScriptParser::Node::NONE:
|
case GDScriptParser::Node::NONE:
|
||||||
break; // Unreachable.
|
break; // Unreachable.
|
||||||
case GDScriptParser::Node::CLASS:
|
case GDScriptParser::Node::CLASS:
|
||||||
|
// NOTE: Currently this route is never executed, `resolve_class_*()` is called directly.
|
||||||
if (OK == resolve_class_inheritance(static_cast<GDScriptParser::ClassNode *>(p_node), true)) {
|
if (OK == resolve_class_inheritance(static_cast<GDScriptParser::ClassNode *>(p_node), true)) {
|
||||||
resolve_class_interface(static_cast<GDScriptParser::ClassNode *>(p_node), true);
|
resolve_class_interface(static_cast<GDScriptParser::ClassNode *>(p_node), true);
|
||||||
resolve_class_body(static_cast<GDScriptParser::ClassNode *>(p_node), true);
|
resolve_class_body(static_cast<GDScriptParser::ClassNode *>(p_node), true);
|
||||||
|
@ -1584,13 +1590,6 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
|
||||||
}
|
}
|
||||||
p_function->resolved_signature = true;
|
p_function->resolved_signature = true;
|
||||||
|
|
||||||
#ifdef DEBUG_ENABLED
|
|
||||||
HashSet<GDScriptWarning::Code> previously_ignored_warnings = parser->ignored_warnings;
|
|
||||||
for (GDScriptWarning::Code ignored_warning : p_function->ignored_warnings) {
|
|
||||||
parser->ignored_warnings.insert(ignored_warning);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
GDScriptParser::FunctionNode *previous_function = parser->current_function;
|
GDScriptParser::FunctionNode *previous_function = parser->current_function;
|
||||||
parser->current_function = p_function;
|
parser->current_function = p_function;
|
||||||
bool previous_static_context = static_context;
|
bool previous_static_context = static_context;
|
||||||
|
@ -1775,9 +1774,6 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
|
||||||
p_function->set_datatype(prev_datatype);
|
p_function->set_datatype(prev_datatype);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef DEBUG_ENABLED
|
|
||||||
parser->ignored_warnings = previously_ignored_warnings;
|
|
||||||
#endif
|
|
||||||
parser->current_function = previous_function;
|
parser->current_function = previous_function;
|
||||||
static_context = previous_static_context;
|
static_context = previous_static_context;
|
||||||
}
|
}
|
||||||
|
@ -1788,13 +1784,6 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun
|
||||||
}
|
}
|
||||||
p_function->resolved_body = true;
|
p_function->resolved_body = true;
|
||||||
|
|
||||||
#ifdef DEBUG_ENABLED
|
|
||||||
HashSet<GDScriptWarning::Code> previously_ignored_warnings = parser->ignored_warnings;
|
|
||||||
for (GDScriptWarning::Code ignored_warning : p_function->ignored_warnings) {
|
|
||||||
parser->ignored_warnings.insert(ignored_warning);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
GDScriptParser::FunctionNode *previous_function = parser->current_function;
|
GDScriptParser::FunctionNode *previous_function = parser->current_function;
|
||||||
parser->current_function = p_function;
|
parser->current_function = p_function;
|
||||||
|
|
||||||
|
@ -1812,9 +1801,6 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef DEBUG_ENABLED
|
|
||||||
parser->ignored_warnings = previously_ignored_warnings;
|
|
||||||
#endif
|
|
||||||
parser->current_function = previous_function;
|
parser->current_function = previous_function;
|
||||||
static_context = previous_static_context;
|
static_context = previous_static_context;
|
||||||
}
|
}
|
||||||
|
@ -1852,23 +1838,11 @@ void GDScriptAnalyzer::resolve_suite(GDScriptParser::SuiteNode *p_suite) {
|
||||||
// Apply annotations.
|
// Apply annotations.
|
||||||
for (GDScriptParser::AnnotationNode *&E : stmt->annotations) {
|
for (GDScriptParser::AnnotationNode *&E : stmt->annotations) {
|
||||||
resolve_annotation(E);
|
resolve_annotation(E);
|
||||||
E->apply(parser, stmt, nullptr);
|
E->apply(parser, stmt, nullptr); // TODO: Provide `p_class`.
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef DEBUG_ENABLED
|
|
||||||
HashSet<GDScriptWarning::Code> previously_ignored_warnings = parser->ignored_warnings;
|
|
||||||
for (GDScriptWarning::Code ignored_warning : stmt->ignored_warnings) {
|
|
||||||
parser->ignored_warnings.insert(ignored_warning);
|
|
||||||
}
|
|
||||||
#endif // DEBUG_ENABLED
|
|
||||||
|
|
||||||
resolve_node(stmt);
|
resolve_node(stmt);
|
||||||
resolve_pending_lambda_bodies();
|
resolve_pending_lambda_bodies();
|
||||||
|
|
||||||
#ifdef DEBUG_ENABLED
|
|
||||||
parser->ignored_warnings = previously_ignored_warnings;
|
|
||||||
#endif // DEBUG_ENABLED
|
|
||||||
|
|
||||||
decide_suite_type(p_suite, stmt);
|
decide_suite_type(p_suite, stmt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2257,6 +2231,12 @@ void GDScriptAnalyzer::resolve_match(GDScriptParser::MatchNode *p_match) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GDScriptAnalyzer::resolve_match_branch(GDScriptParser::MatchBranchNode *p_match_branch, GDScriptParser::ExpressionNode *p_match_test) {
|
void GDScriptAnalyzer::resolve_match_branch(GDScriptParser::MatchBranchNode *p_match_branch, GDScriptParser::ExpressionNode *p_match_test) {
|
||||||
|
// Apply annotations.
|
||||||
|
for (GDScriptParser::AnnotationNode *&E : p_match_branch->annotations) {
|
||||||
|
resolve_annotation(E);
|
||||||
|
E->apply(parser, p_match_branch, nullptr); // TODO: Provide `p_class`.
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < p_match_branch->patterns.size(); i++) {
|
for (int i = 0; i < p_match_branch->patterns.size(); i++) {
|
||||||
resolve_match_pattern(p_match_branch->patterns[i], p_match_test);
|
resolve_match_pattern(p_match_branch->patterns[i], p_match_test);
|
||||||
}
|
}
|
||||||
|
@ -3746,6 +3726,8 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
|
||||||
if (is_base && !base.is_meta_type) {
|
if (is_base && !base.is_meta_type) {
|
||||||
p_identifier->set_datatype(member.get_datatype());
|
p_identifier->set_datatype(member.get_datatype());
|
||||||
p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_SIGNAL;
|
p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_SIGNAL;
|
||||||
|
p_identifier->signal_source = member.signal;
|
||||||
|
member.signal->usages += 1;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
|
@ -3930,6 +3912,8 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
|
||||||
found_source = true;
|
found_source = true;
|
||||||
break;
|
break;
|
||||||
case GDScriptParser::IdentifierNode::MEMBER_SIGNAL:
|
case GDScriptParser::IdentifierNode::MEMBER_SIGNAL:
|
||||||
|
p_identifier->signal_source->usages++;
|
||||||
|
[[fallthrough]];
|
||||||
case GDScriptParser::IdentifierNode::INHERITED_VARIABLE:
|
case GDScriptParser::IdentifierNode::INHERITED_VARIABLE:
|
||||||
mark_lambda_use_self();
|
mark_lambda_use_self();
|
||||||
break;
|
break;
|
||||||
|
@ -5636,21 +5620,20 @@ Error GDScriptAnalyzer::resolve_dependencies() {
|
||||||
|
|
||||||
Error GDScriptAnalyzer::analyze() {
|
Error GDScriptAnalyzer::analyze() {
|
||||||
parser->errors.clear();
|
parser->errors.clear();
|
||||||
Error err = OK;
|
|
||||||
|
|
||||||
err = resolve_inheritance();
|
Error err = resolve_inheritance();
|
||||||
if (err) {
|
if (err) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply annotations.
|
|
||||||
for (GDScriptParser::AnnotationNode *&E : parser->head->annotations) {
|
|
||||||
resolve_annotation(E);
|
|
||||||
E->apply(parser, parser->head, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve_interface();
|
resolve_interface();
|
||||||
resolve_body();
|
resolve_body();
|
||||||
|
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
// Apply here, after all `@warning_ignore`s have been resolved and applied.
|
||||||
|
parser->apply_pending_warnings();
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!parser->errors.is_empty()) {
|
if (!parser->errors.is_empty()) {
|
||||||
return ERR_PARSE_ERROR;
|
return ERR_PARSE_ERROR;
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,7 +127,7 @@ GDScriptParser::GDScriptParser() {
|
||||||
register_annotation(MethodInfo("@export_group", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_GROUP>, varray(""));
|
register_annotation(MethodInfo("@export_group", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_GROUP>, varray(""));
|
||||||
register_annotation(MethodInfo("@export_subgroup", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_SUBGROUP>, varray(""));
|
register_annotation(MethodInfo("@export_subgroup", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_SUBGROUP>, varray(""));
|
||||||
// Warning annotations.
|
// Warning annotations.
|
||||||
register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS | AnnotationInfo::VARIABLE | AnnotationInfo::SIGNAL | AnnotationInfo::CONSTANT | AnnotationInfo::FUNCTION | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, varray(), true);
|
register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS_LEVEL | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, varray(), true);
|
||||||
// Networking.
|
// Networking.
|
||||||
register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("authority", "call_remote", "unreliable", 0));
|
register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("authority", "call_remote", "unreliable", 0));
|
||||||
}
|
}
|
||||||
|
@ -181,47 +181,62 @@ void GDScriptParser::push_error(const String &p_message, const Node *p_origin) {
|
||||||
#ifdef DEBUG_ENABLED
|
#ifdef DEBUG_ENABLED
|
||||||
void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Vector<String> &p_symbols) {
|
void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Vector<String> &p_symbols) {
|
||||||
ERR_FAIL_NULL(p_source);
|
ERR_FAIL_NULL(p_source);
|
||||||
|
ERR_FAIL_INDEX(p_code, GDScriptWarning::WARNING_MAX);
|
||||||
|
|
||||||
if (is_ignoring_warnings) {
|
if (is_ignoring_warnings) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (GLOBAL_GET("debug/gdscript/warnings/exclude_addons").booleanize() && script_path.begins_with("res://addons/")) {
|
if (GLOBAL_GET("debug/gdscript/warnings/exclude_addons").booleanize() && script_path.begins_with("res://addons/")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
GDScriptWarning::WarnLevel warn_level = (GDScriptWarning::WarnLevel)(int)GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(p_code));
|
||||||
if (ignored_warnings.has(p_code)) {
|
if (warn_level == GDScriptWarning::IGNORE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int warn_level = (int)GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(p_code));
|
PendingWarning pw;
|
||||||
if (!warn_level) {
|
pw.source = p_source;
|
||||||
return;
|
pw.code = p_code;
|
||||||
}
|
pw.treated_as_error = warn_level == GDScriptWarning::ERROR;
|
||||||
|
pw.symbols = p_symbols;
|
||||||
|
|
||||||
GDScriptWarning warning;
|
pending_warnings.push_back(pw);
|
||||||
warning.code = p_code;
|
}
|
||||||
warning.symbols = p_symbols;
|
|
||||||
warning.start_line = p_source->start_line;
|
|
||||||
warning.end_line = p_source->end_line;
|
|
||||||
warning.leftmost_column = p_source->leftmost_column;
|
|
||||||
warning.rightmost_column = p_source->rightmost_column;
|
|
||||||
|
|
||||||
if (warn_level == GDScriptWarning::WarnLevel::ERROR) {
|
void GDScriptParser::apply_pending_warnings() {
|
||||||
push_error(warning.get_message() + String(" (Warning treated as error.)"), p_source);
|
for (const PendingWarning &pw : pending_warnings) {
|
||||||
return;
|
if (warning_ignored_lines[pw.code].has(pw.source->start_line)) {
|
||||||
}
|
continue;
|
||||||
|
}
|
||||||
List<GDScriptWarning>::Element *before = nullptr;
|
|
||||||
for (List<GDScriptWarning>::Element *E = warnings.front(); E; E = E->next()) {
|
GDScriptWarning warning;
|
||||||
if (E->get().start_line > warning.start_line) {
|
warning.code = pw.code;
|
||||||
break;
|
warning.symbols = pw.symbols;
|
||||||
|
warning.start_line = pw.source->start_line;
|
||||||
|
warning.end_line = pw.source->end_line;
|
||||||
|
warning.leftmost_column = pw.source->leftmost_column;
|
||||||
|
warning.rightmost_column = pw.source->rightmost_column;
|
||||||
|
|
||||||
|
if (pw.treated_as_error) {
|
||||||
|
push_error(warning.get_message() + String(" (Warning treated as error.)"), pw.source);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<GDScriptWarning>::Element *before = nullptr;
|
||||||
|
for (List<GDScriptWarning>::Element *E = warnings.front(); E; E = E->next()) {
|
||||||
|
if (E->get().start_line > warning.start_line) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
before = E;
|
||||||
|
}
|
||||||
|
if (before) {
|
||||||
|
warnings.insert_after(before, warning);
|
||||||
|
} else {
|
||||||
|
warnings.push_front(warning);
|
||||||
}
|
}
|
||||||
before = E;
|
|
||||||
}
|
|
||||||
if (before) {
|
|
||||||
warnings.insert_after(before, warning);
|
|
||||||
} else {
|
|
||||||
warnings.push_front(warning);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pending_warnings.clear();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -553,25 +568,53 @@ void GDScriptParser::end_statement(const String &p_context) {
|
||||||
|
|
||||||
void GDScriptParser::parse_program() {
|
void GDScriptParser::parse_program() {
|
||||||
head = alloc_node<ClassNode>();
|
head = alloc_node<ClassNode>();
|
||||||
|
head->start_line = 1;
|
||||||
|
head->end_line = 1;
|
||||||
head->fqcn = GDScript::canonicalize_path(script_path);
|
head->fqcn = GDScript::canonicalize_path(script_path);
|
||||||
current_class = head;
|
current_class = head;
|
||||||
bool can_have_class_or_extends = true;
|
bool can_have_class_or_extends = true;
|
||||||
|
|
||||||
|
#define PUSH_PENDING_ANNOTATIONS_TO_HEAD \
|
||||||
|
if (!annotation_stack.is_empty()) { \
|
||||||
|
for (AnnotationNode * annot : annotation_stack) { \
|
||||||
|
head->annotations.push_back(annot); \
|
||||||
|
} \
|
||||||
|
annotation_stack.clear(); \
|
||||||
|
}
|
||||||
|
|
||||||
while (!check(GDScriptTokenizer::Token::TK_EOF)) {
|
while (!check(GDScriptTokenizer::Token::TK_EOF)) {
|
||||||
if (match(GDScriptTokenizer::Token::ANNOTATION)) {
|
if (match(GDScriptTokenizer::Token::ANNOTATION)) {
|
||||||
AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL);
|
AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::CLASS_LEVEL | AnnotationInfo::STANDALONE);
|
||||||
if (annotation != nullptr) {
|
if (annotation != nullptr) {
|
||||||
if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
|
if (annotation->applies_to(AnnotationInfo::CLASS)) {
|
||||||
// `@icon` needs to be applied in the parser. See GH-72444.
|
// We do not know in advance what the annotation will be applied to: the `head` class or the subsequent inner class.
|
||||||
if (annotation->name == SNAME("@icon")) {
|
// If we encounter `class_name`, `extends` or pure `SCRIPT` annotation, then it's `head`, otherwise it's an inner class.
|
||||||
annotation->apply(this, head, nullptr);
|
annotation_stack.push_back(annotation);
|
||||||
|
} else if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
|
||||||
|
PUSH_PENDING_ANNOTATIONS_TO_HEAD;
|
||||||
|
if (annotation->name == SNAME("@tool") || annotation->name == SNAME("@icon")) {
|
||||||
|
// Some annotations need to be resolved in the parser.
|
||||||
|
annotation->apply(this, head, nullptr); // `head->outer == nullptr`.
|
||||||
} else {
|
} else {
|
||||||
head->annotations.push_back(annotation);
|
head->annotations.push_back(annotation);
|
||||||
}
|
}
|
||||||
|
} else if (annotation->applies_to(AnnotationInfo::STANDALONE)) {
|
||||||
|
if (previous.type != GDScriptTokenizer::Token::NEWLINE) {
|
||||||
|
push_error(R"(Expected newline after a standalone annotation.)");
|
||||||
|
}
|
||||||
|
if (annotation->name == SNAME("@export_category") || annotation->name == SNAME("@export_group") || annotation->name == SNAME("@export_subgroup")) {
|
||||||
|
head->add_member_group(annotation);
|
||||||
|
// This annotation must appear after script-level annotations and `class_name`/`extends`,
|
||||||
|
// so we stop looking for script-level stuff.
|
||||||
|
can_have_class_or_extends = false;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// For potential non-group standalone annotations.
|
||||||
|
push_error(R"(Unexpected standalone annotation.)");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
annotation_stack.push_back(annotation);
|
annotation_stack.push_back(annotation);
|
||||||
// This annotation must appear after script-level annotations
|
// This annotation must appear after script-level annotations and `class_name`/`extends`,
|
||||||
// and class_name/extends (ex: could be @onready or @export),
|
|
||||||
// so we stop looking for script-level stuff.
|
// so we stop looking for script-level stuff.
|
||||||
can_have_class_or_extends = false;
|
can_have_class_or_extends = false;
|
||||||
break;
|
break;
|
||||||
|
@ -592,6 +635,10 @@ void GDScriptParser::parse_program() {
|
||||||
// Order here doesn't matter, but there should be only one of each at most.
|
// Order here doesn't matter, but there should be only one of each at most.
|
||||||
switch (current.type) {
|
switch (current.type) {
|
||||||
case GDScriptTokenizer::Token::CLASS_NAME:
|
case GDScriptTokenizer::Token::CLASS_NAME:
|
||||||
|
PUSH_PENDING_ANNOTATIONS_TO_HEAD;
|
||||||
|
if (head->start_line == 1) {
|
||||||
|
reset_extents(head, current);
|
||||||
|
}
|
||||||
advance();
|
advance();
|
||||||
if (head->identifier != nullptr) {
|
if (head->identifier != nullptr) {
|
||||||
push_error(R"("class_name" can only be used once.)");
|
push_error(R"("class_name" can only be used once.)");
|
||||||
|
@ -600,6 +647,10 @@ void GDScriptParser::parse_program() {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case GDScriptTokenizer::Token::EXTENDS:
|
case GDScriptTokenizer::Token::EXTENDS:
|
||||||
|
PUSH_PENDING_ANNOTATIONS_TO_HEAD;
|
||||||
|
if (head->start_line == 1) {
|
||||||
|
reset_extents(head, current);
|
||||||
|
}
|
||||||
advance();
|
advance();
|
||||||
if (head->extends_used) {
|
if (head->extends_used) {
|
||||||
push_error(R"("extends" can only be used once.)");
|
push_error(R"("extends" can only be used once.)");
|
||||||
|
@ -608,6 +659,10 @@ void GDScriptParser::parse_program() {
|
||||||
end_statement("superclass");
|
end_statement("superclass");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case GDScriptTokenizer::Token::TK_EOF:
|
||||||
|
PUSH_PENDING_ANNOTATIONS_TO_HEAD;
|
||||||
|
can_have_class_or_extends = false;
|
||||||
|
break;
|
||||||
case GDScriptTokenizer::Token::LITERAL:
|
case GDScriptTokenizer::Token::LITERAL:
|
||||||
if (current.literal.get_type() == Variant::STRING) {
|
if (current.literal.get_type() == Variant::STRING) {
|
||||||
// Allow strings in class body as multiline comments.
|
// Allow strings in class body as multiline comments.
|
||||||
|
@ -629,6 +684,8 @@ void GDScriptParser::parse_program() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#undef PUSH_PENDING_ANNOTATIONS_TO_HEAD
|
||||||
|
|
||||||
parse_class_body(true);
|
parse_class_body(true);
|
||||||
complete_extents(head);
|
complete_extents(head);
|
||||||
|
|
||||||
|
@ -907,8 +964,8 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
|
||||||
case GDScriptTokenizer::Token::ANNOTATION: {
|
case GDScriptTokenizer::Token::ANNOTATION: {
|
||||||
advance();
|
advance();
|
||||||
|
|
||||||
// Check for standalone and class-level annotations.
|
// Check for class-level and standalone annotations.
|
||||||
AnnotationNode *annotation = parse_annotation(AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL);
|
AnnotationNode *annotation = parse_annotation(AnnotationInfo::CLASS_LEVEL | AnnotationInfo::STANDALONE);
|
||||||
if (annotation != nullptr) {
|
if (annotation != nullptr) {
|
||||||
if (annotation->applies_to(AnnotationInfo::STANDALONE)) {
|
if (annotation->applies_to(AnnotationInfo::STANDALONE)) {
|
||||||
if (previous.type != GDScriptTokenizer::Token::NEWLINE) {
|
if (previous.type != GDScriptTokenizer::Token::NEWLINE) {
|
||||||
|
@ -918,9 +975,9 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
|
||||||
current_class->add_member_group(annotation);
|
current_class->add_member_group(annotation);
|
||||||
} else {
|
} else {
|
||||||
// For potential non-group standalone annotations.
|
// For potential non-group standalone annotations.
|
||||||
push_error(R"(Unexpected standalone annotation in class body.)");
|
push_error(R"(Unexpected standalone annotation.)");
|
||||||
}
|
}
|
||||||
} else {
|
} else { // `AnnotationInfo::CLASS_LEVEL`.
|
||||||
annotation_stack.push_back(annotation);
|
annotation_stack.push_back(annotation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1342,22 +1399,9 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) {
|
||||||
break; // Allow trailing comma.
|
break; // Allow trailing comma.
|
||||||
}
|
}
|
||||||
if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for enum key.)")) {
|
if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for enum key.)")) {
|
||||||
EnumNode::Value item;
|
|
||||||
GDScriptParser::IdentifierNode *identifier = parse_identifier();
|
GDScriptParser::IdentifierNode *identifier = parse_identifier();
|
||||||
#ifdef DEBUG_ENABLED
|
|
||||||
if (!named) { // Named enum identifiers do not shadow anything since you can only access them with NamedEnum.ENUM_VALUE
|
EnumNode::Value item;
|
||||||
for (MethodInfo &info : gdscript_funcs) {
|
|
||||||
if (info.name == identifier->name) {
|
|
||||||
push_warning(identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, "enum member", identifier->name, "built-in function");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (Variant::has_utility_function(identifier->name)) {
|
|
||||||
push_warning(identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, "enum member", identifier->name, "built-in function");
|
|
||||||
} else if (ClassDB::class_exists(identifier->name)) {
|
|
||||||
push_warning(identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, "enum member", identifier->name, "global class");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
item.identifier = identifier;
|
item.identifier = identifier;
|
||||||
item.parent_enum = enum_node;
|
item.parent_enum = enum_node;
|
||||||
item.line = previous.start_line;
|
item.line = previous.start_line;
|
||||||
|
@ -1701,7 +1745,19 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
|
||||||
bool unreachable = current_suite->has_return && !current_suite->has_unreachable_code;
|
bool unreachable = current_suite->has_return && !current_suite->has_unreachable_code;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool is_annotation = false;
|
List<AnnotationNode *> annotations;
|
||||||
|
if (current.type != GDScriptTokenizer::Token::ANNOTATION) {
|
||||||
|
while (!annotation_stack.is_empty()) {
|
||||||
|
AnnotationNode *last_annotation = annotation_stack.back()->get();
|
||||||
|
if (last_annotation->applies_to(AnnotationInfo::STATEMENT)) {
|
||||||
|
annotations.push_front(last_annotation);
|
||||||
|
annotation_stack.pop_back();
|
||||||
|
} else {
|
||||||
|
push_error(vformat(R"(Annotation "%s" cannot be applied to a statement.)", last_annotation->name));
|
||||||
|
clear_unused_annotations();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch (current.type) {
|
switch (current.type) {
|
||||||
case GDScriptTokenizer::Token::PASS:
|
case GDScriptTokenizer::Token::PASS:
|
||||||
|
@ -1775,7 +1831,6 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
|
||||||
break;
|
break;
|
||||||
case GDScriptTokenizer::Token::ANNOTATION: {
|
case GDScriptTokenizer::Token::ANNOTATION: {
|
||||||
advance();
|
advance();
|
||||||
is_annotation = true;
|
|
||||||
AnnotationNode *annotation = parse_annotation(AnnotationInfo::STATEMENT);
|
AnnotationNode *annotation = parse_annotation(AnnotationInfo::STATEMENT);
|
||||||
if (annotation != nullptr) {
|
if (annotation != nullptr) {
|
||||||
annotation_stack.push_back(annotation);
|
annotation_stack.push_back(annotation);
|
||||||
|
@ -1804,10 +1859,9 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
|
||||||
#ifdef DEBUG_ENABLED
|
#ifdef DEBUG_ENABLED
|
||||||
if (expression != nullptr) {
|
if (expression != nullptr) {
|
||||||
switch (expression->type) {
|
switch (expression->type) {
|
||||||
case Node::CALL:
|
|
||||||
case Node::ASSIGNMENT:
|
case Node::ASSIGNMENT:
|
||||||
case Node::AWAIT:
|
case Node::AWAIT:
|
||||||
case Node::TERNARY_OPERATOR:
|
case Node::CALL:
|
||||||
// Fine.
|
// Fine.
|
||||||
break;
|
break;
|
||||||
case Node::LAMBDA:
|
case Node::LAMBDA:
|
||||||
|
@ -1815,11 +1869,14 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
|
||||||
push_error("Standalone lambdas cannot be accessed. Consider assigning it to a variable.", expression);
|
push_error("Standalone lambdas cannot be accessed. Consider assigning it to a variable.", expression);
|
||||||
break;
|
break;
|
||||||
case Node::LITERAL:
|
case Node::LITERAL:
|
||||||
if (static_cast<GDScriptParser::LiteralNode *>(expression)->value.get_type() == Variant::STRING) {
|
// Allow strings as multiline comments.
|
||||||
// Allow strings as multiline comments.
|
if (static_cast<GDScriptParser::LiteralNode *>(expression)->value.get_type() != Variant::STRING) {
|
||||||
break;
|
push_warning(expression, GDScriptWarning::STANDALONE_EXPRESSION);
|
||||||
}
|
}
|
||||||
[[fallthrough]];
|
break;
|
||||||
|
case Node::TERNARY_OPERATOR:
|
||||||
|
push_warning(expression, GDScriptWarning::STANDALONE_TERNARY);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
push_warning(expression, GDScriptWarning::STANDALONE_EXPRESSION);
|
push_warning(expression, GDScriptWarning::STANDALONE_EXPRESSION);
|
||||||
}
|
}
|
||||||
|
@ -1829,14 +1886,9 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (!is_annotation && result != nullptr && !annotation_stack.is_empty()) {
|
if (result != nullptr && !annotations.is_empty()) {
|
||||||
AnnotationNode *last_annotation = annotation_stack.back()->get();
|
for (AnnotationNode *&annotation : annotations) {
|
||||||
if (last_annotation->applies_to(AnnotationInfo::STATEMENT)) {
|
result->annotations.push_back(annotation);
|
||||||
result->annotations.push_front(last_annotation);
|
|
||||||
annotation_stack.pop_back();
|
|
||||||
} else {
|
|
||||||
push_error(vformat(R"(Annotation "%s" cannot be applied to a statement.)", last_annotation->name));
|
|
||||||
clear_unused_annotations();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2017,10 +2069,10 @@ GDScriptParser::IfNode *GDScriptParser::parse_if(const String &p_token) {
|
||||||
}
|
}
|
||||||
|
|
||||||
GDScriptParser::MatchNode *GDScriptParser::parse_match() {
|
GDScriptParser::MatchNode *GDScriptParser::parse_match() {
|
||||||
MatchNode *match = alloc_node<MatchNode>();
|
MatchNode *match_node = alloc_node<MatchNode>();
|
||||||
|
|
||||||
match->test = parse_expression(false);
|
match_node->test = parse_expression(false);
|
||||||
if (match->test == nullptr) {
|
if (match_node->test == nullptr) {
|
||||||
push_error(R"(Expected expression to test after "match".)");
|
push_error(R"(Expected expression to test after "match".)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2028,20 +2080,45 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() {
|
||||||
consume(GDScriptTokenizer::Token::NEWLINE, R"(Expected a newline after "match" statement.)");
|
consume(GDScriptTokenizer::Token::NEWLINE, R"(Expected a newline after "match" statement.)");
|
||||||
|
|
||||||
if (!consume(GDScriptTokenizer::Token::INDENT, R"(Expected an indented block after "match" statement.)")) {
|
if (!consume(GDScriptTokenizer::Token::INDENT, R"(Expected an indented block after "match" statement.)")) {
|
||||||
complete_extents(match);
|
complete_extents(match_node);
|
||||||
return match;
|
return match_node;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool all_have_return = true;
|
bool all_have_return = true;
|
||||||
bool have_wildcard = false;
|
bool have_wildcard = false;
|
||||||
|
|
||||||
|
List<AnnotationNode *> match_branch_annotation_stack;
|
||||||
|
|
||||||
while (!check(GDScriptTokenizer::Token::DEDENT) && !is_at_end()) {
|
while (!check(GDScriptTokenizer::Token::DEDENT) && !is_at_end()) {
|
||||||
|
if (match(GDScriptTokenizer::Token::PASS)) {
|
||||||
|
consume(GDScriptTokenizer::Token::NEWLINE, R"(Expected newline after "pass".)");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match(GDScriptTokenizer::Token::ANNOTATION)) {
|
||||||
|
AnnotationNode *annotation = parse_annotation(AnnotationInfo::STATEMENT);
|
||||||
|
if (annotation == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (annotation->name != SNAME("@warning_ignore")) {
|
||||||
|
push_error(vformat(R"(Annotation "%s" is not allowed in this level.)", annotation->name), annotation);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
match_branch_annotation_stack.push_back(annotation);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
MatchBranchNode *branch = parse_match_branch();
|
MatchBranchNode *branch = parse_match_branch();
|
||||||
if (branch == nullptr) {
|
if (branch == nullptr) {
|
||||||
advance();
|
advance();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (AnnotationNode *annotation : match_branch_annotation_stack) {
|
||||||
|
branch->annotations.push_back(annotation);
|
||||||
|
}
|
||||||
|
match_branch_annotation_stack.clear();
|
||||||
|
|
||||||
#ifdef DEBUG_ENABLED
|
#ifdef DEBUG_ENABLED
|
||||||
if (have_wildcard && !branch->patterns.is_empty()) {
|
if (have_wildcard && !branch->patterns.is_empty()) {
|
||||||
push_warning(branch->patterns[0], GDScriptWarning::UNREACHABLE_PATTERN);
|
push_warning(branch->patterns[0], GDScriptWarning::UNREACHABLE_PATTERN);
|
||||||
|
@ -2050,9 +2127,9 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() {
|
||||||
|
|
||||||
have_wildcard = have_wildcard || branch->has_wildcard;
|
have_wildcard = have_wildcard || branch->has_wildcard;
|
||||||
all_have_return = all_have_return && branch->block->has_return;
|
all_have_return = all_have_return && branch->block->has_return;
|
||||||
match->branches.push_back(branch);
|
match_node->branches.push_back(branch);
|
||||||
}
|
}
|
||||||
complete_extents(match);
|
complete_extents(match_node);
|
||||||
|
|
||||||
consume(GDScriptTokenizer::Token::DEDENT, R"(Expected an indented block after "match" statement.)");
|
consume(GDScriptTokenizer::Token::DEDENT, R"(Expected an indented block after "match" statement.)");
|
||||||
|
|
||||||
|
@ -2060,7 +2137,12 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() {
|
||||||
current_suite->has_return = true;
|
current_suite->has_return = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return match;
|
for (const AnnotationNode *annotation : match_branch_annotation_stack) {
|
||||||
|
push_error(vformat(R"(Annotation "%s" does not precede a valid target, so it will have no effect.)", annotation->name), annotation);
|
||||||
|
}
|
||||||
|
match_branch_annotation_stack.clear();
|
||||||
|
|
||||||
|
return match_node;
|
||||||
}
|
}
|
||||||
|
|
||||||
GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() {
|
GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() {
|
||||||
|
@ -2378,7 +2460,7 @@ GDScriptParser::IdentifierNode *GDScriptParser::parse_identifier() {
|
||||||
IdentifierNode *identifier = static_cast<IdentifierNode *>(parse_identifier(nullptr, false));
|
IdentifierNode *identifier = static_cast<IdentifierNode *>(parse_identifier(nullptr, false));
|
||||||
#ifdef DEBUG_ENABLED
|
#ifdef DEBUG_ENABLED
|
||||||
// Check for spoofing here (if available in TextServer) since this isn't called inside expressions. This is only relevant for declarations.
|
// Check for spoofing here (if available in TextServer) since this isn't called inside expressions. This is only relevant for declarations.
|
||||||
if (identifier && TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY) && TS->spoof_check(identifier->name.operator String())) {
|
if (identifier && TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY) && TS->spoof_check(identifier->name)) {
|
||||||
push_warning(identifier, GDScriptWarning::CONFUSABLE_IDENTIFIER, identifier->name.operator String());
|
push_warning(identifier, GDScriptWarning::CONFUSABLE_IDENTIFIER, identifier->name.operator String());
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -3161,6 +3243,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p
|
||||||
complete_extents(get_node);
|
complete_extents(get_node);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
get_node->full_path += "%";
|
get_node->full_path += "%";
|
||||||
|
|
||||||
path_state = PATH_STATE_PERCENT;
|
path_state = PATH_STATE_PERCENT;
|
||||||
|
@ -3204,6 +3287,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p
|
||||||
path_state = PATH_STATE_NODE_NAME;
|
path_state = PATH_STATE_NODE_NAME;
|
||||||
} else if (current.is_node_name()) {
|
} else if (current.is_node_name()) {
|
||||||
advance();
|
advance();
|
||||||
|
|
||||||
String identifier = previous.get_identifier();
|
String identifier = previous.get_identifier();
|
||||||
#ifdef DEBUG_ENABLED
|
#ifdef DEBUG_ENABLED
|
||||||
// Check spoofing.
|
// Check spoofing.
|
||||||
|
@ -3934,7 +4018,7 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// `@icon`'s argument needs to be resolved in the parser. See GH-72444.
|
// Some annotations need to be resolved in the parser.
|
||||||
if (p_annotation->name == SNAME("@icon")) {
|
if (p_annotation->name == SNAME("@icon")) {
|
||||||
ExpressionNode *argument = p_annotation->arguments[0];
|
ExpressionNode *argument = p_annotation->arguments[0];
|
||||||
|
|
||||||
|
@ -4401,23 +4485,77 @@ bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
|
bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
|
||||||
#ifdef DEBUG_ENABLED
|
#ifndef DEBUG_ENABLED
|
||||||
|
// Only available in debug builds.
|
||||||
|
return true;
|
||||||
|
#else // DEBUG_ENABLED
|
||||||
|
if (is_ignoring_warnings) {
|
||||||
|
return true; // We already ignore all warnings, let's optimize it.
|
||||||
|
}
|
||||||
|
|
||||||
bool has_error = false;
|
bool has_error = false;
|
||||||
for (const Variant &warning_name : p_annotation->resolved_arguments) {
|
for (const Variant &warning_name : p_annotation->resolved_arguments) {
|
||||||
GDScriptWarning::Code warning = GDScriptWarning::get_code_from_name(String(warning_name).to_upper());
|
GDScriptWarning::Code warning_code = GDScriptWarning::get_code_from_name(String(warning_name).to_upper());
|
||||||
if (warning == GDScriptWarning::WARNING_MAX) {
|
if (warning_code == GDScriptWarning::WARNING_MAX) {
|
||||||
push_error(vformat(R"(Invalid warning name: "%s".)", warning_name), p_annotation);
|
push_error(vformat(R"(Invalid warning name: "%s".)", warning_name), p_annotation);
|
||||||
has_error = true;
|
has_error = true;
|
||||||
} else {
|
} else {
|
||||||
p_target->ignored_warnings.push_back(warning);
|
int start_line = p_annotation->start_line;
|
||||||
|
int end_line = p_target->end_line;
|
||||||
|
|
||||||
|
switch (p_target->type) {
|
||||||
|
#define SIMPLE_CASE(m_type, m_class, m_property) \
|
||||||
|
case m_type: { \
|
||||||
|
m_class *node = static_cast<m_class *>(p_target); \
|
||||||
|
if (node->m_property == nullptr) { \
|
||||||
|
end_line = node->start_line; \
|
||||||
|
} else { \
|
||||||
|
end_line = node->m_property->end_line; \
|
||||||
|
} \
|
||||||
|
} break;
|
||||||
|
|
||||||
|
// Can contain properties (set/get).
|
||||||
|
SIMPLE_CASE(Node::VARIABLE, VariableNode, initializer)
|
||||||
|
|
||||||
|
// Contain bodies.
|
||||||
|
SIMPLE_CASE(Node::FOR, ForNode, list)
|
||||||
|
SIMPLE_CASE(Node::IF, IfNode, condition)
|
||||||
|
SIMPLE_CASE(Node::MATCH, MatchNode, test)
|
||||||
|
SIMPLE_CASE(Node::WHILE, WhileNode, condition)
|
||||||
|
#undef SIMPLE_CASE
|
||||||
|
|
||||||
|
case Node::CLASS: {
|
||||||
|
end_line = p_target->start_line;
|
||||||
|
for (const AnnotationNode *annotation : p_target->annotations) {
|
||||||
|
start_line = MIN(start_line, annotation->start_line);
|
||||||
|
end_line = MAX(end_line, annotation->end_line);
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case Node::FUNCTION: {
|
||||||
|
// `@warning_ignore` on function has a controversial feature that is used in tests.
|
||||||
|
// It's better not to remove it for now, while there is no way to mass-ignore warnings.
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case Node::MATCH_BRANCH: {
|
||||||
|
MatchBranchNode *branch = static_cast<MatchBranchNode *>(p_target);
|
||||||
|
end_line = branch->start_line;
|
||||||
|
for (int i = 0; i < branch->patterns.size(); i++) {
|
||||||
|
end_line = MAX(end_line, branch->patterns[i]->end_line);
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default: {
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
|
||||||
|
end_line = MAX(start_line, end_line); // Prevent infinite loop.
|
||||||
|
for (int line = start_line; line <= end_line; line++) {
|
||||||
|
warning_ignored_lines[warning_code].insert(line);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return !has_error;
|
return !has_error;
|
||||||
|
|
||||||
#else // ! DEBUG_ENABLED
|
|
||||||
// Only available in debug builds.
|
|
||||||
return true;
|
|
||||||
#endif // DEBUG_ENABLED
|
#endif // DEBUG_ENABLED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -338,9 +338,6 @@ public:
|
||||||
int leftmost_column = 0, rightmost_column = 0;
|
int leftmost_column = 0, rightmost_column = 0;
|
||||||
Node *next = nullptr;
|
Node *next = nullptr;
|
||||||
List<AnnotationNode *> annotations;
|
List<AnnotationNode *> annotations;
|
||||||
#ifdef DEBUG_ENABLED
|
|
||||||
Vector<GDScriptWarning::Code> ignored_warnings;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
DataType datatype;
|
DataType datatype;
|
||||||
|
|
||||||
|
@ -900,9 +897,10 @@ public:
|
||||||
|
|
||||||
union {
|
union {
|
||||||
ParameterNode *parameter_source = nullptr;
|
ParameterNode *parameter_source = nullptr;
|
||||||
ConstantNode *constant_source;
|
|
||||||
VariableNode *variable_source;
|
|
||||||
IdentifierNode *bind_source;
|
IdentifierNode *bind_source;
|
||||||
|
VariableNode *variable_source;
|
||||||
|
ConstantNode *constant_source;
|
||||||
|
SignalNode *signal_source;
|
||||||
};
|
};
|
||||||
FunctionNode *source_function = nullptr;
|
FunctionNode *source_function = nullptr;
|
||||||
|
|
||||||
|
@ -1051,6 +1049,8 @@ public:
|
||||||
MemberDocData doc_data;
|
MemberDocData doc_data;
|
||||||
#endif // TOOLS_ENABLED
|
#endif // TOOLS_ENABLED
|
||||||
|
|
||||||
|
int usages = 0;
|
||||||
|
|
||||||
SignalNode() {
|
SignalNode() {
|
||||||
type = SIGNAL;
|
type = SIGNAL;
|
||||||
}
|
}
|
||||||
|
@ -1334,9 +1334,17 @@ private:
|
||||||
List<ParserError> errors;
|
List<ParserError> errors;
|
||||||
|
|
||||||
#ifdef DEBUG_ENABLED
|
#ifdef DEBUG_ENABLED
|
||||||
|
struct PendingWarning {
|
||||||
|
const Node *source = nullptr;
|
||||||
|
GDScriptWarning::Code code = GDScriptWarning::WARNING_MAX;
|
||||||
|
bool treated_as_error = false;
|
||||||
|
Vector<String> symbols;
|
||||||
|
};
|
||||||
|
|
||||||
bool is_ignoring_warnings = false;
|
bool is_ignoring_warnings = false;
|
||||||
List<GDScriptWarning> warnings;
|
List<GDScriptWarning> warnings;
|
||||||
HashSet<GDScriptWarning::Code> ignored_warnings;
|
List<PendingWarning> pending_warnings;
|
||||||
|
HashSet<int> warning_ignored_lines[GDScriptWarning::WARNING_MAX];
|
||||||
HashSet<int> unsafe_lines;
|
HashSet<int> unsafe_lines;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -1368,7 +1376,7 @@ private:
|
||||||
FUNCTION = 1 << 5,
|
FUNCTION = 1 << 5,
|
||||||
STATEMENT = 1 << 6,
|
STATEMENT = 1 << 6,
|
||||||
STANDALONE = 1 << 7,
|
STANDALONE = 1 << 7,
|
||||||
CLASS_LEVEL = CLASS | VARIABLE | FUNCTION,
|
CLASS_LEVEL = CLASS | VARIABLE | CONSTANT | SIGNAL | FUNCTION,
|
||||||
};
|
};
|
||||||
uint32_t target_kind = 0; // Flags.
|
uint32_t target_kind = 0; // Flags.
|
||||||
AnnotationAction apply = nullptr;
|
AnnotationAction apply = nullptr;
|
||||||
|
@ -1438,6 +1446,7 @@ private:
|
||||||
void push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Symbols &...p_symbols) {
|
void push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Symbols &...p_symbols) {
|
||||||
push_warning(p_source, p_code, Vector<String>{ p_symbols... });
|
push_warning(p_source, p_code, Vector<String>{ p_symbols... });
|
||||||
}
|
}
|
||||||
|
void apply_pending_warnings();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void make_completion_context(CompletionType p_type, Node *p_node, int p_argument = -1, bool p_force = false);
|
void make_completion_context(CompletionType p_type, Node *p_node, int p_argument = -1, bool p_force = false);
|
||||||
|
|
|
@ -52,13 +52,13 @@ String GDScriptWarning::get_message() const {
|
||||||
return vformat(R"(The local constant "%s" is declared but never used in the block. If this is intended, prefix it with an underscore: "_%s".)", symbols[0], symbols[0]);
|
return vformat(R"(The local constant "%s" is declared but never used in the block. If this is intended, prefix it with an underscore: "_%s".)", symbols[0], symbols[0]);
|
||||||
case UNUSED_PRIVATE_CLASS_VARIABLE:
|
case UNUSED_PRIVATE_CLASS_VARIABLE:
|
||||||
CHECK_SYMBOLS(1);
|
CHECK_SYMBOLS(1);
|
||||||
return vformat(R"(The class variable "%s" is declared but never used in the script.)", symbols[0]);
|
return vformat(R"(The class variable "%s" is declared but never used in the class.)", symbols[0]);
|
||||||
case UNUSED_PARAMETER:
|
case UNUSED_PARAMETER:
|
||||||
CHECK_SYMBOLS(2);
|
CHECK_SYMBOLS(2);
|
||||||
return vformat(R"*(The parameter "%s" is never used in the function "%s()". If this is intended, prefix it with an underscore: "_%s".)*", symbols[1], symbols[0], symbols[1]);
|
return vformat(R"*(The parameter "%s" is never used in the function "%s()". If this is intended, prefix it with an underscore: "_%s".)*", symbols[1], symbols[0], symbols[1]);
|
||||||
case UNUSED_SIGNAL:
|
case UNUSED_SIGNAL:
|
||||||
CHECK_SYMBOLS(1);
|
CHECK_SYMBOLS(1);
|
||||||
return vformat(R"(The signal "%s" is declared but never emitted.)", symbols[0]);
|
return vformat(R"(The signal "%s" is declared but never explicitly used in the class.)", symbols[0]);
|
||||||
case SHADOWED_VARIABLE:
|
case SHADOWED_VARIABLE:
|
||||||
CHECK_SYMBOLS(4);
|
CHECK_SYMBOLS(4);
|
||||||
return vformat(R"(The local %s "%s" is shadowing an already-declared %s at line %s.)", symbols[0], symbols[1], symbols[2], symbols[3]);
|
return vformat(R"(The local %s "%s" is shadowing an already-declared %s at line %s.)", symbols[0], symbols[1], symbols[2], symbols[3]);
|
||||||
|
@ -76,18 +76,9 @@ String GDScriptWarning::get_message() const {
|
||||||
case STANDALONE_EXPRESSION:
|
case STANDALONE_EXPRESSION:
|
||||||
return "Standalone expression (the line has no effect).";
|
return "Standalone expression (the line has no effect).";
|
||||||
case STANDALONE_TERNARY:
|
case STANDALONE_TERNARY:
|
||||||
return "Standalone ternary conditional operator: the return value is being discarded.";
|
return "Standalone ternary operator: the return value is being discarded.";
|
||||||
case INCOMPATIBLE_TERNARY:
|
case INCOMPATIBLE_TERNARY:
|
||||||
return "Values of the ternary conditional are not mutually compatible.";
|
return "Values of the ternary operator are not mutually compatible.";
|
||||||
case PROPERTY_USED_AS_FUNCTION:
|
|
||||||
CHECK_SYMBOLS(2);
|
|
||||||
return vformat(R"*(The method "%s()" was not found in base "%s" but there's a property with the same name. Did you mean to access it?)*", symbols[0], symbols[1]);
|
|
||||||
case CONSTANT_USED_AS_FUNCTION:
|
|
||||||
CHECK_SYMBOLS(2);
|
|
||||||
return vformat(R"*(The method "%s()" was not found in base "%s" but there's a constant with the same name. Did you mean to access it?)*", symbols[0], symbols[1]);
|
|
||||||
case FUNCTION_USED_AS_PROPERTY:
|
|
||||||
CHECK_SYMBOLS(2);
|
|
||||||
return vformat(R"(The property "%s" was not found in base "%s" but there's a method with the same name. Did you mean to call it?)", symbols[0], symbols[1]);
|
|
||||||
case UNTYPED_DECLARATION:
|
case UNTYPED_DECLARATION:
|
||||||
CHECK_SYMBOLS(2);
|
CHECK_SYMBOLS(2);
|
||||||
if (symbols[0] == "Function") {
|
if (symbols[0] == "Function") {
|
||||||
|
@ -162,10 +153,17 @@ String GDScriptWarning::get_message() const {
|
||||||
return vformat(R"*(The default value is using "%s" which won't return nodes in the scene tree before "_ready()" is called. Use the "@onready" annotation to solve this.)*", symbols[0]);
|
return vformat(R"*(The default value is using "%s" which won't return nodes in the scene tree before "_ready()" is called. Use the "@onready" annotation to solve this.)*", symbols[0]);
|
||||||
case ONREADY_WITH_EXPORT:
|
case ONREADY_WITH_EXPORT:
|
||||||
return R"("@onready" will set the default value after "@export" takes effect and will override it.)";
|
return R"("@onready" will set the default value after "@export" takes effect and will override it.)";
|
||||||
|
#ifndef DISABLE_DEPRECATED
|
||||||
|
// Never produced. These warnings migrated from 3.x by mistake.
|
||||||
|
case PROPERTY_USED_AS_FUNCTION: // There is already an error.
|
||||||
|
case CONSTANT_USED_AS_FUNCTION: // There is already an error.
|
||||||
|
case FUNCTION_USED_AS_PROPERTY: // This is valid, returns `Callable`.
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
case WARNING_MAX:
|
case WARNING_MAX:
|
||||||
break; // Can't happen, but silences warning
|
break; // Can't happen, but silences warning.
|
||||||
}
|
}
|
||||||
ERR_FAIL_V_MSG(String(), "Invalid GDScript warning code: " + get_name_from_code(code) + ".");
|
ERR_FAIL_V_MSG(String(), vformat(R"(Invalid GDScript warning "%s".)", get_name_from_code(code)));
|
||||||
|
|
||||||
#undef CHECK_SYMBOLS
|
#undef CHECK_SYMBOLS
|
||||||
}
|
}
|
||||||
|
@ -206,9 +204,6 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
|
||||||
"STANDALONE_EXPRESSION",
|
"STANDALONE_EXPRESSION",
|
||||||
"STANDALONE_TERNARY",
|
"STANDALONE_TERNARY",
|
||||||
"INCOMPATIBLE_TERNARY",
|
"INCOMPATIBLE_TERNARY",
|
||||||
"PROPERTY_USED_AS_FUNCTION",
|
|
||||||
"CONSTANT_USED_AS_FUNCTION",
|
|
||||||
"FUNCTION_USED_AS_PROPERTY",
|
|
||||||
"UNTYPED_DECLARATION",
|
"UNTYPED_DECLARATION",
|
||||||
"INFERRED_DECLARATION",
|
"INFERRED_DECLARATION",
|
||||||
"UNSAFE_PROPERTY_ACCESS",
|
"UNSAFE_PROPERTY_ACCESS",
|
||||||
|
@ -236,6 +231,11 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
|
||||||
"NATIVE_METHOD_OVERRIDE",
|
"NATIVE_METHOD_OVERRIDE",
|
||||||
"GET_NODE_DEFAULT_WITHOUT_ONREADY",
|
"GET_NODE_DEFAULT_WITHOUT_ONREADY",
|
||||||
"ONREADY_WITH_EXPORT",
|
"ONREADY_WITH_EXPORT",
|
||||||
|
#ifndef DISABLE_DEPRECATED
|
||||||
|
"PROPERTY_USED_AS_FUNCTION",
|
||||||
|
"CONSTANT_USED_AS_FUNCTION",
|
||||||
|
"FUNCTION_USED_AS_PROPERTY",
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names.");
|
static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names.");
|
||||||
|
|
|
@ -50,9 +50,9 @@ public:
|
||||||
UNASSIGNED_VARIABLE_OP_ASSIGN, // Variable never assigned but used in an assignment operation (+=, *=, etc).
|
UNASSIGNED_VARIABLE_OP_ASSIGN, // Variable never assigned but used in an assignment operation (+=, *=, etc).
|
||||||
UNUSED_VARIABLE, // Local variable is declared but never used.
|
UNUSED_VARIABLE, // Local variable is declared but never used.
|
||||||
UNUSED_LOCAL_CONSTANT, // Local constant is declared but never used.
|
UNUSED_LOCAL_CONSTANT, // Local constant is declared but never used.
|
||||||
UNUSED_PRIVATE_CLASS_VARIABLE, // Class variable is declared private ("_" prefix) but never used in the file.
|
UNUSED_PRIVATE_CLASS_VARIABLE, // Class variable is declared private ("_" prefix) but never used in the class.
|
||||||
UNUSED_PARAMETER, // Function parameter is never used.
|
UNUSED_PARAMETER, // Function parameter is never used.
|
||||||
UNUSED_SIGNAL, // Signal is defined but never emitted.
|
UNUSED_SIGNAL, // Signal is defined but never explicitly used in the class.
|
||||||
SHADOWED_VARIABLE, // Variable name shadowed by other variable in same class.
|
SHADOWED_VARIABLE, // Variable name shadowed by other variable in same class.
|
||||||
SHADOWED_VARIABLE_BASE_CLASS, // Variable name shadowed by other variable in some base class.
|
SHADOWED_VARIABLE_BASE_CLASS, // Variable name shadowed by other variable in some base class.
|
||||||
SHADOWED_GLOBAL_IDENTIFIER, // A global class or function has the same name as variable.
|
SHADOWED_GLOBAL_IDENTIFIER, // A global class or function has the same name as variable.
|
||||||
|
@ -61,9 +61,6 @@ public:
|
||||||
STANDALONE_EXPRESSION, // Expression not assigned to a variable.
|
STANDALONE_EXPRESSION, // Expression not assigned to a variable.
|
||||||
STANDALONE_TERNARY, // Return value of ternary expression is discarded.
|
STANDALONE_TERNARY, // Return value of ternary expression is discarded.
|
||||||
INCOMPATIBLE_TERNARY, // Possible values of a ternary if are not mutually compatible.
|
INCOMPATIBLE_TERNARY, // Possible values of a ternary if are not mutually compatible.
|
||||||
PROPERTY_USED_AS_FUNCTION, // Function not found, but there's a property with the same name.
|
|
||||||
CONSTANT_USED_AS_FUNCTION, // Function not found, but there's a constant with the same name.
|
|
||||||
FUNCTION_USED_AS_PROPERTY, // Property not found, but there's a function with the same name.
|
|
||||||
UNTYPED_DECLARATION, // Variable/parameter/function has no static type, explicitly specified or implicitly inferred.
|
UNTYPED_DECLARATION, // Variable/parameter/function has no static type, explicitly specified or implicitly inferred.
|
||||||
INFERRED_DECLARATION, // Variable/constant/parameter has an implicitly inferred static type.
|
INFERRED_DECLARATION, // Variable/constant/parameter has an implicitly inferred static type.
|
||||||
UNSAFE_PROPERTY_ACCESS, // Property not found in the detected type (but can be in subtypes).
|
UNSAFE_PROPERTY_ACCESS, // Property not found in the detected type (but can be in subtypes).
|
||||||
|
@ -91,6 +88,11 @@ public:
|
||||||
NATIVE_METHOD_OVERRIDE, // The script method overrides a native one, this may not work as intended.
|
NATIVE_METHOD_OVERRIDE, // The script method overrides a native one, this may not work as intended.
|
||||||
GET_NODE_DEFAULT_WITHOUT_ONREADY, // A class variable uses `get_node()` (or the `$` notation) as its default value, but does not use the @onready annotation.
|
GET_NODE_DEFAULT_WITHOUT_ONREADY, // A class variable uses `get_node()` (or the `$` notation) as its default value, but does not use the @onready annotation.
|
||||||
ONREADY_WITH_EXPORT, // The `@onready` annotation will set the value after `@export` which is likely not intended.
|
ONREADY_WITH_EXPORT, // The `@onready` annotation will set the value after `@export` which is likely not intended.
|
||||||
|
#ifndef DISABLE_DEPRECATED
|
||||||
|
PROPERTY_USED_AS_FUNCTION, // Function not found, but there's a property with the same name.
|
||||||
|
CONSTANT_USED_AS_FUNCTION, // Function not found, but there's a constant with the same name.
|
||||||
|
FUNCTION_USED_AS_PROPERTY, // Property not found, but there's a function with the same name.
|
||||||
|
#endif
|
||||||
WARNING_MAX,
|
WARNING_MAX,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -110,9 +112,6 @@ public:
|
||||||
WARN, // STANDALONE_EXPRESSION
|
WARN, // STANDALONE_EXPRESSION
|
||||||
WARN, // STANDALONE_TERNARY
|
WARN, // STANDALONE_TERNARY
|
||||||
WARN, // INCOMPATIBLE_TERNARY
|
WARN, // INCOMPATIBLE_TERNARY
|
||||||
WARN, // PROPERTY_USED_AS_FUNCTION
|
|
||||||
WARN, // CONSTANT_USED_AS_FUNCTION
|
|
||||||
WARN, // FUNCTION_USED_AS_PROPERTY
|
|
||||||
IGNORE, // UNTYPED_DECLARATION // Static typing is optional, we don't want to spam warnings.
|
IGNORE, // UNTYPED_DECLARATION // Static typing is optional, we don't want to spam warnings.
|
||||||
IGNORE, // INFERRED_DECLARATION // Static typing is optional, we don't want to spam warnings.
|
IGNORE, // INFERRED_DECLARATION // Static typing is optional, we don't want to spam warnings.
|
||||||
IGNORE, // UNSAFE_PROPERTY_ACCESS // Too common in untyped scenarios.
|
IGNORE, // UNSAFE_PROPERTY_ACCESS // Too common in untyped scenarios.
|
||||||
|
@ -140,6 +139,11 @@ public:
|
||||||
ERROR, // NATIVE_METHOD_OVERRIDE // May not work as expected.
|
ERROR, // NATIVE_METHOD_OVERRIDE // May not work as expected.
|
||||||
ERROR, // GET_NODE_DEFAULT_WITHOUT_ONREADY // May not work as expected.
|
ERROR, // GET_NODE_DEFAULT_WITHOUT_ONREADY // May not work as expected.
|
||||||
ERROR, // ONREADY_WITH_EXPORT // May not work as expected.
|
ERROR, // ONREADY_WITH_EXPORT // May not work as expected.
|
||||||
|
#ifndef DISABLE_DEPRECATED
|
||||||
|
WARN, // PROPERTY_USED_AS_FUNCTION
|
||||||
|
WARN, // CONSTANT_USED_AS_FUNCTION
|
||||||
|
WARN, // FUNCTION_USED_AS_PROPERTY
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert((sizeof(default_warning_levels) / sizeof(default_warning_levels[0])) == WARNING_MAX, "Amount of default levels does not match the amount of warnings.");
|
static_assert((sizeof(default_warning_levels) / sizeof(default_warning_levels[0])) == WARNING_MAX, "Amount of default levels does not match the amount of warnings.");
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
@warning_ignore("unused_private_class_variable")
|
|
||||||
var _unused = 2
|
|
||||||
|
|
||||||
@warning_ignore("unused_variable")
|
|
||||||
func test():
|
|
||||||
print("test")
|
|
||||||
var unused = 3
|
|
||||||
|
|
||||||
@warning_ignore("redundant_await")
|
|
||||||
print(await regular_func())
|
|
||||||
|
|
||||||
print("done")
|
|
||||||
|
|
||||||
func regular_func() -> int:
|
|
||||||
return 0
|
|
|
@ -1,4 +0,0 @@
|
||||||
GDTEST_OK
|
|
||||||
test
|
|
||||||
0
|
|
||||||
done
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
@warning_ignore("confusable_identifier")
|
||||||
|
class MyClАss:
|
||||||
|
var my_vАr
|
||||||
|
|
||||||
|
@warning_ignore("narrowing_conversion")
|
||||||
|
var i: int = f:
|
||||||
|
get:
|
||||||
|
return f
|
||||||
|
|
||||||
|
var f: float
|
||||||
|
|
||||||
|
@warning_ignore("narrowing_conversion")
|
||||||
|
func test_func(_i: int = f):
|
||||||
|
i = f
|
||||||
|
|
||||||
|
func test():
|
||||||
|
@warning_ignore("narrowing_conversion")
|
||||||
|
if signi(f): # TODO: Allow `@warning_ignore` before `elif`?
|
||||||
|
i = f
|
||||||
|
|
||||||
|
@warning_ignore("narrowing_conversion")
|
||||||
|
match signi(f):
|
||||||
|
1:
|
||||||
|
i = f
|
||||||
|
@warning_ignore("confusable_identifier")
|
||||||
|
var my_vАr:
|
||||||
|
var _my_vАr: Variant = my_vАr
|
||||||
|
|
||||||
|
@warning_ignore("narrowing_conversion")
|
||||||
|
for j in signi(f):
|
||||||
|
i = f
|
||||||
|
|
||||||
|
@warning_ignore("narrowing_conversion")
|
||||||
|
while signi(f):
|
||||||
|
i = f
|
|
@ -0,0 +1,29 @@
|
||||||
|
GDTEST_OK
|
||||||
|
>> WARNING
|
||||||
|
>> Line: 3
|
||||||
|
>> CONFUSABLE_IDENTIFIER
|
||||||
|
>> The identifier "my_vАr" has misleading characters and might be confused with something else.
|
||||||
|
>> WARNING
|
||||||
|
>> Line: 8
|
||||||
|
>> NARROWING_CONVERSION
|
||||||
|
>> Narrowing conversion (float is converted to int and loses precision).
|
||||||
|
>> WARNING
|
||||||
|
>> Line: 19
|
||||||
|
>> NARROWING_CONVERSION
|
||||||
|
>> Narrowing conversion (float is converted to int and loses precision).
|
||||||
|
>> WARNING
|
||||||
|
>> Line: 24
|
||||||
|
>> NARROWING_CONVERSION
|
||||||
|
>> Narrowing conversion (float is converted to int and loses precision).
|
||||||
|
>> WARNING
|
||||||
|
>> Line: 27
|
||||||
|
>> CONFUSABLE_IDENTIFIER
|
||||||
|
>> The identifier "_my_vАr" has misleading characters and might be confused with something else.
|
||||||
|
>> WARNING
|
||||||
|
>> Line: 31
|
||||||
|
>> NARROWING_CONVERSION
|
||||||
|
>> Narrowing conversion (float is converted to int and loses precision).
|
||||||
|
>> WARNING
|
||||||
|
>> Line: 35
|
||||||
|
>> NARROWING_CONVERSION
|
||||||
|
>> Narrowing conversion (float is converted to int and loses precision).
|
|
@ -0,0 +1,156 @@
|
||||||
|
@warning_ignore("redundant_static_unload")
|
||||||
|
@static_unload
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
class A extends Node:
|
||||||
|
static func static_called_on_instance():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@warning_ignore("get_node_default_without_onready")
|
||||||
|
var get_node_default_without_onready = $Node
|
||||||
|
|
||||||
|
@warning_ignore("unused_private_class_variable")
|
||||||
|
var _unused_private_class_variable
|
||||||
|
|
||||||
|
@warning_ignore("onready_with_export")
|
||||||
|
@onready @export var onready_with_export = 1
|
||||||
|
|
||||||
|
var shadowed_variable
|
||||||
|
var confusable_local_usage
|
||||||
|
|
||||||
|
@warning_ignore("unused_signal")
|
||||||
|
signal unused_signal()
|
||||||
|
|
||||||
|
func variant_func() -> Variant:
|
||||||
|
return null
|
||||||
|
|
||||||
|
func int_func() -> int:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
@warning_ignore("unused_parameter")
|
||||||
|
func test_warnings(unused_private_class_variable):
|
||||||
|
var t = 1
|
||||||
|
|
||||||
|
@warning_ignore("unassigned_variable")
|
||||||
|
var unassigned_variable
|
||||||
|
print(unassigned_variable)
|
||||||
|
|
||||||
|
var _unassigned_variable_op_assign
|
||||||
|
@warning_ignore("unassigned_variable_op_assign")
|
||||||
|
_unassigned_variable_op_assign += t
|
||||||
|
|
||||||
|
@warning_ignore("unused_variable")
|
||||||
|
var unused_variable
|
||||||
|
|
||||||
|
@warning_ignore("unused_local_constant")
|
||||||
|
const unused_local_constant = 1
|
||||||
|
|
||||||
|
@warning_ignore("shadowed_variable")
|
||||||
|
var shadowed_variable = 1
|
||||||
|
print(shadowed_variable)
|
||||||
|
|
||||||
|
@warning_ignore("shadowed_variable_base_class")
|
||||||
|
var name = "test"
|
||||||
|
print(name)
|
||||||
|
|
||||||
|
@warning_ignore("shadowed_global_identifier")
|
||||||
|
var var_to_str = 1
|
||||||
|
print(var_to_str)
|
||||||
|
|
||||||
|
@warning_ignore("standalone_expression")
|
||||||
|
1 + 2
|
||||||
|
|
||||||
|
@warning_ignore("standalone_ternary")
|
||||||
|
1 if 2 else 3
|
||||||
|
|
||||||
|
@warning_ignore("incompatible_ternary")
|
||||||
|
t = 1 if 2 else false
|
||||||
|
|
||||||
|
@warning_ignore("unsafe_property_access")
|
||||||
|
self.unsafe_property_access = 1
|
||||||
|
|
||||||
|
var node: Node = null
|
||||||
|
@warning_ignore("unsafe_method_access")
|
||||||
|
node.unsafe_method_access()
|
||||||
|
|
||||||
|
@warning_ignore("unsafe_cast")
|
||||||
|
print(variant_func().x as int)
|
||||||
|
|
||||||
|
var key: Variant = "key"
|
||||||
|
@warning_ignore("unsafe_call_argument")
|
||||||
|
set(key, 1)
|
||||||
|
|
||||||
|
variant_func() # No warning (intended?).
|
||||||
|
@warning_ignore("return_value_discarded")
|
||||||
|
int_func()
|
||||||
|
|
||||||
|
var a: A = null
|
||||||
|
@warning_ignore("static_called_on_instance")
|
||||||
|
a.static_called_on_instance()
|
||||||
|
|
||||||
|
@warning_ignore("redundant_await")
|
||||||
|
await 1
|
||||||
|
|
||||||
|
@warning_ignore("assert_always_true")
|
||||||
|
assert(true)
|
||||||
|
|
||||||
|
assert(false) # No warning (intended).
|
||||||
|
@warning_ignore("assert_always_false")
|
||||||
|
assert(false and false)
|
||||||
|
|
||||||
|
@warning_ignore("integer_division")
|
||||||
|
var _integer_division = 5 / 2
|
||||||
|
|
||||||
|
@warning_ignore("narrowing_conversion")
|
||||||
|
var _narrowing_conversion: int = floorf(2.5)
|
||||||
|
|
||||||
|
@warning_ignore("int_as_enum_without_cast")
|
||||||
|
var _int_as_enum_without_cast: Variant.Type = 1
|
||||||
|
|
||||||
|
@warning_ignore("int_as_enum_without_cast", "int_as_enum_without_match")
|
||||||
|
var _int_as_enum_without_match: Variant.Type = 255
|
||||||
|
|
||||||
|
@warning_ignore("confusable_identifier")
|
||||||
|
var _cОnfusable_identifier = 1
|
||||||
|
|
||||||
|
if true:
|
||||||
|
@warning_ignore("confusable_local_declaration")
|
||||||
|
var _confusable_local_declaration = 1
|
||||||
|
var _confusable_local_declaration = 2
|
||||||
|
|
||||||
|
@warning_ignore("confusable_local_usage")
|
||||||
|
print(confusable_local_usage)
|
||||||
|
@warning_ignore("shadowed_variable")
|
||||||
|
var confusable_local_usage = 2
|
||||||
|
print(confusable_local_usage)
|
||||||
|
|
||||||
|
@warning_ignore("inference_on_variant")
|
||||||
|
var _inference_on_variant := variant_func()
|
||||||
|
|
||||||
|
func test_unreachable_code():
|
||||||
|
return
|
||||||
|
@warning_ignore("unreachable_code")
|
||||||
|
print(1)
|
||||||
|
|
||||||
|
func test_unreachable_pattern():
|
||||||
|
match 1:
|
||||||
|
_:
|
||||||
|
print(0)
|
||||||
|
@warning_ignore("unreachable_pattern")
|
||||||
|
1:
|
||||||
|
print(1)
|
||||||
|
|
||||||
|
func test_unsafe_void_return_variant() -> void:
|
||||||
|
return variant_func() # No warning (intended?).
|
||||||
|
|
||||||
|
func test_unsafe_void_return() -> void:
|
||||||
|
@warning_ignore("unsafe_method_access", "unsafe_void_return")
|
||||||
|
return variant_func().f()
|
||||||
|
|
||||||
|
@warning_ignore("native_method_override")
|
||||||
|
func get_class():
|
||||||
|
pass
|
||||||
|
|
||||||
|
# We don't want to execute it because of errors, just analyze.
|
||||||
|
func test():
|
||||||
|
pass
|
|
@ -0,0 +1 @@
|
||||||
|
GDTEST_OK
|
|
@ -2,8 +2,8 @@ GDTEST_OK
|
||||||
>> WARNING
|
>> WARNING
|
||||||
>> Line: 3
|
>> Line: 3
|
||||||
>> UNUSED_PRIVATE_CLASS_VARIABLE
|
>> UNUSED_PRIVATE_CLASS_VARIABLE
|
||||||
>> The class variable "_a" is declared but never used in the script.
|
>> The class variable "_a" is declared but never used in the class.
|
||||||
>> WARNING
|
>> WARNING
|
||||||
>> Line: 7
|
>> Line: 7
|
||||||
>> UNUSED_PRIVATE_CLASS_VARIABLE
|
>> UNUSED_PRIVATE_CLASS_VARIABLE
|
||||||
>> The class variable "_d" is declared but never used in the script.
|
>> The class variable "_d" is declared but never used in the class.
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
signal s1()
|
||||||
|
signal s2()
|
||||||
|
signal s3()
|
||||||
|
@warning_ignore("unused_signal")
|
||||||
|
signal s4()
|
||||||
|
|
||||||
|
func no_exec():
|
||||||
|
s1.emit()
|
||||||
|
print(s2)
|
||||||
|
|
||||||
|
func test():
|
||||||
|
pass
|
|
@ -0,0 +1,5 @@
|
||||||
|
GDTEST_OK
|
||||||
|
>> WARNING
|
||||||
|
>> Line: 3
|
||||||
|
>> UNUSED_SIGNAL
|
||||||
|
>> The signal "s3" is declared but never explicitly used in the class.
|
|
@ -1,2 +1,2 @@
|
||||||
GDTEST_ANALYZER_ERROR
|
GDTEST_PARSER_ERROR
|
||||||
"@tool" annotation can only be used once.
|
"@tool" annotation can only be used once.
|
||||||
|
|
|
@ -4,6 +4,7 @@ class Parent:
|
||||||
|
|
||||||
var parent_variable := 2
|
var parent_variable := 2
|
||||||
|
|
||||||
|
@warning_ignore("unused_signal")
|
||||||
signal parent_signal
|
signal parent_signal
|
||||||
|
|
||||||
var parent_attribute: int:
|
var parent_attribute: int:
|
||||||
|
|
|
@ -14,6 +14,7 @@ func test():
|
||||||
print(v)
|
print(v)
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
@warning_ignore("standalone_ternary")
|
||||||
v=func(): print(2) if false else print(3)
|
v=func(): print(2) if false else print(3)
|
||||||
@warning_ignore("unsafe_cast")
|
@warning_ignore("unsafe_cast")
|
||||||
(v as Callable).call()
|
(v as Callable).call()
|
||||||
|
|
|
@ -14,3 +14,6 @@ func test():
|
||||||
print("This won't match")
|
print("This won't match")
|
||||||
_:
|
_:
|
||||||
print("This will match")
|
print("This will match")
|
||||||
|
|
||||||
|
match 0:
|
||||||
|
pass
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
#GDTEST_OK
|
|
||||||
|
|
||||||
# No parentheses.
|
# No parentheses.
|
||||||
signal a
|
signal a
|
||||||
|
|
||||||
|
@ -16,5 +14,15 @@ signal d(
|
||||||
c,
|
c,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# With type hints.
|
||||||
|
signal e(a: int, b: Variant, c: Node)
|
||||||
|
|
||||||
|
func no_exec():
|
||||||
|
a.emit()
|
||||||
|
b.emit()
|
||||||
|
c.emit()
|
||||||
|
d.emit()
|
||||||
|
e.emit()
|
||||||
|
|
||||||
func test():
|
func test():
|
||||||
print("Ok")
|
print("Ok")
|
||||||
|
|
|
@ -2,4 +2,4 @@ GDTEST_OK
|
||||||
>> WARNING
|
>> WARNING
|
||||||
>> Line: 8
|
>> Line: 8
|
||||||
>> INCOMPATIBLE_TERNARY
|
>> INCOMPATIBLE_TERNARY
|
||||||
>> Values of the ternary conditional are not mutually compatible.
|
>> Values of the ternary operator are not mutually compatible.
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
func test():
|
||||||
|
1 if true else 2
|
||||||
|
print(1) if true else print(2)
|
|
@ -0,0 +1,10 @@
|
||||||
|
GDTEST_OK
|
||||||
|
>> WARNING
|
||||||
|
>> Line: 2
|
||||||
|
>> STANDALONE_TERNARY
|
||||||
|
>> Standalone ternary operator: the return value is being discarded.
|
||||||
|
>> WARNING
|
||||||
|
>> Line: 3
|
||||||
|
>> STANDALONE_TERNARY
|
||||||
|
>> Standalone ternary operator: the return value is being discarded.
|
||||||
|
1
|
|
@ -56,6 +56,16 @@ signal test_signal_6(a: Resource, b: Array[Resource])
|
||||||
signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo])
|
signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo])
|
||||||
signal test_signal_8(a: MyClass, b: Array[MyClass])
|
signal test_signal_8(a: MyClass, b: Array[MyClass])
|
||||||
|
|
||||||
|
func no_exec():
|
||||||
|
test_signal_1.emit()
|
||||||
|
test_signal_2.emit()
|
||||||
|
test_signal_3.emit()
|
||||||
|
test_signal_4.emit()
|
||||||
|
test_signal_5.emit()
|
||||||
|
test_signal_6.emit()
|
||||||
|
test_signal_7.emit()
|
||||||
|
test_signal_8.emit()
|
||||||
|
|
||||||
func test():
|
func test():
|
||||||
var script: Script = get_script()
|
var script: Script = get_script()
|
||||||
for property in script.get_property_list():
|
for property in script.get_property_list():
|
||||||
|
|
|
@ -11,7 +11,9 @@ class A:
|
||||||
static func test_static_func_a2(): pass
|
static func test_static_func_a2(): pass
|
||||||
func test_func_a1(): pass
|
func test_func_a1(): pass
|
||||||
func test_func_a2(): pass
|
func test_func_a2(): pass
|
||||||
|
@warning_ignore("unused_signal")
|
||||||
signal test_signal_a1()
|
signal test_signal_a1()
|
||||||
|
@warning_ignore("unused_signal")
|
||||||
signal test_signal_a2()
|
signal test_signal_a2()
|
||||||
|
|
||||||
class B extends A:
|
class B extends A:
|
||||||
|
@ -23,7 +25,9 @@ class B extends A:
|
||||||
static func test_static_func_b2(): pass
|
static func test_static_func_b2(): pass
|
||||||
func test_func_b1(): pass
|
func test_func_b1(): pass
|
||||||
func test_func_b2(): pass
|
func test_func_b2(): pass
|
||||||
|
@warning_ignore("unused_signal")
|
||||||
signal test_signal_b1()
|
signal test_signal_b1()
|
||||||
|
@warning_ignore("unused_signal")
|
||||||
signal test_signal_b2()
|
signal test_signal_b2()
|
||||||
|
|
||||||
func test():
|
func test():
|
||||||
|
|
Loading…
Reference in a new issue