Merge pull request #71634 from dalexeev/gds-annotations-analyzer
GDScript: Allow constant expressions in annotations
This commit is contained in:
commit
a5c211641f
23 changed files with 158 additions and 190 deletions
|
|
@ -485,7 +485,7 @@
|
|||
Export a [NodePath] property with a filter for allowed node types.
|
||||
See also [constant PROPERTY_HINT_NODE_PATH_VALID_TYPES].
|
||||
[codeblock]
|
||||
@export_node_path(Button, TouchScreenButton) var some_button
|
||||
@export_node_path("Button", "TouchScreenButton") var some_button
|
||||
[/codeblock]
|
||||
</description>
|
||||
</annotation>
|
||||
|
|
|
|||
|
|
@ -2448,7 +2448,6 @@ bool GDScriptLanguage::handles_global_class_type(const String &p_type) const {
|
|||
}
|
||||
|
||||
String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path) const {
|
||||
Vector<uint8_t> sourcef;
|
||||
Error err;
|
||||
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
|
||||
if (err) {
|
||||
|
|
@ -2459,88 +2458,31 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
|
|||
|
||||
GDScriptParser parser;
|
||||
err = parser.parse(source, p_path, false);
|
||||
|
||||
// TODO: Simplify this code by using the analyzer to get full inheritance.
|
||||
if (err == OK) {
|
||||
const GDScriptParser::ClassNode *c = parser.get_tree();
|
||||
if (r_icon_path) {
|
||||
if (c->icon_path.is_empty() || c->icon_path.is_absolute_path()) {
|
||||
*r_icon_path = c->icon_path;
|
||||
} else if (c->icon_path.is_relative_path()) {
|
||||
*r_icon_path = p_path.get_base_dir().path_join(c->icon_path).simplify_path();
|
||||
}
|
||||
}
|
||||
if (r_base_type) {
|
||||
const GDScriptParser::ClassNode *subclass = c;
|
||||
String path = p_path;
|
||||
GDScriptParser subparser;
|
||||
while (subclass) {
|
||||
if (subclass->extends_used) {
|
||||
if (!subclass->extends_path.is_empty()) {
|
||||
if (subclass->extends.size() == 0) {
|
||||
get_global_class_name(subclass->extends_path, r_base_type);
|
||||
subclass = nullptr;
|
||||
break;
|
||||
} else {
|
||||
Vector<StringName> extend_classes = subclass->extends;
|
||||
|
||||
Ref<FileAccess> subfile = FileAccess::open(subclass->extends_path, FileAccess::READ);
|
||||
if (subfile.is_null()) {
|
||||
break;
|
||||
}
|
||||
String subsource = subfile->get_as_utf8_string();
|
||||
|
||||
if (subsource.is_empty()) {
|
||||
break;
|
||||
}
|
||||
String subpath = subclass->extends_path;
|
||||
if (subpath.is_relative_path()) {
|
||||
subpath = path.get_base_dir().path_join(subpath).simplify_path();
|
||||
}
|
||||
|
||||
if (OK != subparser.parse(subsource, subpath, false)) {
|
||||
break;
|
||||
}
|
||||
path = subpath;
|
||||
subclass = subparser.get_tree();
|
||||
|
||||
while (extend_classes.size() > 0) {
|
||||
bool found = false;
|
||||
for (int i = 0; i < subclass->members.size(); i++) {
|
||||
if (subclass->members[i].type != GDScriptParser::ClassNode::Member::CLASS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const GDScriptParser::ClassNode *inner_class = subclass->members[i].m_class;
|
||||
if (inner_class->identifier->name == extend_classes[0]) {
|
||||
extend_classes.remove_at(0);
|
||||
found = true;
|
||||
subclass = inner_class;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
subclass = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (subclass->extends.size() == 1) {
|
||||
*r_base_type = subclass->extends[0];
|
||||
subclass = nullptr;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
*r_base_type = "RefCounted";
|
||||
subclass = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
return c->identifier != nullptr ? String(c->identifier->name) : String();
|
||||
if (err) {
|
||||
return String();
|
||||
}
|
||||
|
||||
return String();
|
||||
GDScriptAnalyzer analyzer(&parser);
|
||||
err = analyzer.resolve_inheritance();
|
||||
if (err) {
|
||||
return String();
|
||||
}
|
||||
|
||||
const GDScriptParser::ClassNode *c = parser.get_tree();
|
||||
|
||||
if (r_base_type) {
|
||||
*r_base_type = c->get_datatype().native_type;
|
||||
}
|
||||
|
||||
if (r_icon_path) {
|
||||
if (c->icon_path.is_empty() || c->icon_path.is_absolute_path()) {
|
||||
*r_icon_path = c->icon_path.simplify_path();
|
||||
} else if (c->icon_path.is_relative_path()) {
|
||||
*r_icon_path = p_path.get_base_dir().path_join(c->icon_path).simplify_path();
|
||||
}
|
||||
}
|
||||
|
||||
return c->identifier != nullptr ? String(c->identifier->name) : String();
|
||||
}
|
||||
|
||||
GDScriptLanguage::GDScriptLanguage() {
|
||||
|
|
|
|||
|
|
@ -802,6 +802,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
|
|||
|
||||
// Apply annotations.
|
||||
for (GDScriptParser::AnnotationNode *&E : member.variable->annotations) {
|
||||
resolve_annotation(E);
|
||||
E->apply(parser, member.variable);
|
||||
}
|
||||
} break;
|
||||
|
|
@ -812,6 +813,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
|
|||
|
||||
// Apply annotations.
|
||||
for (GDScriptParser::AnnotationNode *&E : member.constant->annotations) {
|
||||
resolve_annotation(E);
|
||||
E->apply(parser, member.constant);
|
||||
}
|
||||
} break;
|
||||
|
|
@ -835,6 +837,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
|
|||
|
||||
// Apply annotations.
|
||||
for (GDScriptParser::AnnotationNode *&E : member.signal->annotations) {
|
||||
resolve_annotation(E);
|
||||
E->apply(parser, member.signal);
|
||||
}
|
||||
} break;
|
||||
|
|
@ -882,6 +885,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
|
|||
|
||||
// Apply annotations.
|
||||
for (GDScriptParser::AnnotationNode *&E : member.m_enum->annotations) {
|
||||
resolve_annotation(E);
|
||||
E->apply(parser, member.m_enum);
|
||||
}
|
||||
} break;
|
||||
|
|
@ -1064,6 +1068,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co
|
|||
if (member.type == GDScriptParser::ClassNode::Member::FUNCTION) {
|
||||
// Apply annotations.
|
||||
for (GDScriptParser::AnnotationNode *&E : member.function->annotations) {
|
||||
resolve_annotation(E);
|
||||
E->apply(parser, member.function);
|
||||
}
|
||||
|
||||
|
|
@ -1290,7 +1295,55 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root
|
|||
}
|
||||
|
||||
void GDScriptAnalyzer::resolve_annotation(GDScriptParser::AnnotationNode *p_annotation) {
|
||||
// TODO: Add second validation function for annotations, so they can use checked types.
|
||||
ERR_FAIL_COND_MSG(!parser->valid_annotations.has(p_annotation->name), vformat(R"(Annotation "%s" not found to validate.)", p_annotation->name));
|
||||
|
||||
const MethodInfo &annotation_info = parser->valid_annotations[p_annotation->name].info;
|
||||
|
||||
const List<PropertyInfo>::Element *E = annotation_info.arguments.front();
|
||||
for (int i = 0; i < p_annotation->arguments.size(); i++) {
|
||||
GDScriptParser::ExpressionNode *argument = p_annotation->arguments[i];
|
||||
const PropertyInfo &argument_info = E->get();
|
||||
|
||||
if (E->next() != nullptr) {
|
||||
E = E->next();
|
||||
}
|
||||
|
||||
reduce_expression(argument);
|
||||
|
||||
if (!argument->is_constant) {
|
||||
push_error(vformat(R"(Argument %d of annotation "%s" isn't a constant expression.)", i + 1, p_annotation->name), argument);
|
||||
return;
|
||||
}
|
||||
|
||||
Variant value = argument->reduced_value;
|
||||
|
||||
if (value.get_type() != argument_info.type) {
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (argument_info.type == Variant::INT && value.get_type() == Variant::FLOAT) {
|
||||
parser->push_warning(argument, GDScriptWarning::NARROWING_CONVERSION);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!Variant::can_convert_strict(value.get_type(), argument_info.type)) {
|
||||
push_error(vformat(R"(Invalid argument for annotation "%s": argument %d should be "%s" but is "%s".)", p_annotation->name, i + 1, Variant::get_type_name(argument_info.type), argument->get_datatype().to_string()), argument);
|
||||
return;
|
||||
}
|
||||
|
||||
Variant converted_to;
|
||||
const Variant *converted_from = &value;
|
||||
Callable::CallError call_error;
|
||||
Variant::construct(argument_info.type, converted_to, &converted_from, 1, call_error);
|
||||
|
||||
if (call_error.error != Callable::CallError::CALL_OK) {
|
||||
push_error(vformat(R"(Cannot convert argument %d of annotation "%s" from "%s" to "%s".)", i + 1, p_annotation->name, Variant::get_type_name(value.get_type()), Variant::get_type_name(argument_info.type)), argument);
|
||||
return;
|
||||
}
|
||||
|
||||
value = converted_to;
|
||||
}
|
||||
|
||||
p_annotation->resolved_arguments.push_back(value);
|
||||
}
|
||||
}
|
||||
|
||||
void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *p_function, const GDScriptParser::Node *p_source, bool p_is_lambda) {
|
||||
|
|
@ -1486,8 +1539,10 @@ void GDScriptAnalyzer::decide_suite_type(GDScriptParser::Node *p_suite, GDScript
|
|||
void GDScriptAnalyzer::resolve_suite(GDScriptParser::SuiteNode *p_suite) {
|
||||
for (int i = 0; i < p_suite->statements.size(); i++) {
|
||||
GDScriptParser::Node *stmt = p_suite->statements[i];
|
||||
for (GDScriptParser::AnnotationNode *&annotation : stmt->annotations) {
|
||||
annotation->apply(parser, stmt);
|
||||
// Apply annotations.
|
||||
for (GDScriptParser::AnnotationNode *&E : stmt->annotations) {
|
||||
resolve_annotation(E);
|
||||
E->apply(parser, stmt);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
|
|
@ -4551,6 +4606,11 @@ Ref<GDScriptParserRef> GDScriptAnalyzer::get_parser_for(const String &p_path) {
|
|||
}
|
||||
|
||||
Error GDScriptAnalyzer::resolve_inheritance() {
|
||||
// Apply annotations.
|
||||
for (GDScriptParser::AnnotationNode *&E : parser->head->annotations) {
|
||||
resolve_annotation(E);
|
||||
E->apply(parser, parser->head);
|
||||
}
|
||||
return resolve_class_inheritance(parser->head, true);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -782,6 +782,7 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a
|
|||
}
|
||||
} else if (p_annotation->name == SNAME("@export_node_path")) {
|
||||
ScriptLanguage::CodeCompletionOption node("Node", ScriptLanguage::CODE_COMPLETION_KIND_CLASS);
|
||||
node.insert_text = node.display.quote(p_quote_style);
|
||||
r_result.insert(node.display, node);
|
||||
List<StringName> node_types;
|
||||
ClassDB::get_inheriters_from_class("Node", &node_types);
|
||||
|
|
@ -790,11 +791,13 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a
|
|||
continue;
|
||||
}
|
||||
ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CLASS);
|
||||
option.insert_text = option.display.quote(p_quote_style);
|
||||
r_result.insert(option.display, option);
|
||||
}
|
||||
} else if (p_annotation->name == SNAME("@warning_ignore")) {
|
||||
for (int warning_code = 0; warning_code < GDScriptWarning::WARNING_MAX; warning_code++) {
|
||||
ScriptLanguage::CodeCompletionOption warning(GDScriptWarning::get_name_from_code((GDScriptWarning::Code)warning_code).to_lower(), ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
|
||||
warning.insert_text = warning.display.quote(p_quote_style);
|
||||
r_result.insert(warning.display, warning);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -529,7 +529,7 @@ void GDScriptParser::parse_program() {
|
|||
AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL);
|
||||
if (annotation != nullptr) {
|
||||
if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
|
||||
annotation->apply(this, head);
|
||||
head->annotations.push_back(annotation);
|
||||
} else {
|
||||
annotation_stack.push_back(annotation);
|
||||
// This annotation must appear after script-level annotations
|
||||
|
|
@ -771,7 +771,6 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)()
|
|||
return;
|
||||
}
|
||||
|
||||
// Apply annotations.
|
||||
for (AnnotationNode *&annotation : annotations) {
|
||||
member->annotations.push_back(annotation);
|
||||
}
|
||||
|
|
@ -848,7 +847,7 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
|
|||
if (previous.type != GDScriptTokenizer::Token::NEWLINE) {
|
||||
push_error(R"(Expected newline after a standalone annotation.)");
|
||||
}
|
||||
annotation->apply(this, head);
|
||||
head->annotations.push_back(annotation);
|
||||
} else {
|
||||
annotation_stack.push_back(annotation);
|
||||
}
|
||||
|
|
@ -1470,7 +1469,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
|
|||
match(GDScriptTokenizer::Token::NEWLINE); // Newline after annotation is optional.
|
||||
|
||||
if (valid) {
|
||||
valid = validate_annotation_arguments(annotation);
|
||||
valid = validate_annotation_argument_count(annotation);
|
||||
}
|
||||
|
||||
return valid ? annotation : nullptr;
|
||||
|
|
@ -1717,7 +1716,6 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
|
|||
}
|
||||
}
|
||||
|
||||
// Apply annotations to statement.
|
||||
while (!is_annotation && result != nullptr && !annotation_stack.is_empty()) {
|
||||
AnnotationNode *last_annotation = annotation_stack.back()->get();
|
||||
if (last_annotation->applies_to(AnnotationInfo::STATEMENT)) {
|
||||
|
|
@ -3598,7 +3596,7 @@ bool GDScriptParser::AnnotationNode::applies_to(uint32_t p_target_kinds) const {
|
|||
return (info->target_kind & p_target_kinds) > 0;
|
||||
}
|
||||
|
||||
bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) {
|
||||
bool GDScriptParser::validate_annotation_argument_count(AnnotationNode *p_annotation) {
|
||||
ERR_FAIL_COND_V_MSG(!valid_annotations.has(p_annotation->name), false, vformat(R"(Annotation "%s" not found to validate.)", p_annotation->name));
|
||||
|
||||
const MethodInfo &info = valid_annotations[p_annotation->name].info;
|
||||
|
|
@ -3613,62 +3611,6 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation)
|
|||
return false;
|
||||
}
|
||||
|
||||
const List<PropertyInfo>::Element *E = info.arguments.front();
|
||||
for (int i = 0; i < p_annotation->arguments.size(); i++) {
|
||||
ExpressionNode *argument = p_annotation->arguments[i];
|
||||
const PropertyInfo ¶meter = E->get();
|
||||
|
||||
if (E->next() != nullptr) {
|
||||
E = E->next();
|
||||
}
|
||||
|
||||
switch (parameter.type) {
|
||||
case Variant::STRING:
|
||||
case Variant::STRING_NAME:
|
||||
case Variant::NODE_PATH:
|
||||
// Allow "quote-less strings", as long as they are recognized as identifiers.
|
||||
if (argument->type == Node::IDENTIFIER) {
|
||||
IdentifierNode *string = static_cast<IdentifierNode *>(argument);
|
||||
Callable::CallError error;
|
||||
Vector<Variant> args = varray(string->name);
|
||||
const Variant *name = args.ptr();
|
||||
Variant r;
|
||||
Variant::construct(parameter.type, r, &(name), 1, error);
|
||||
p_annotation->resolved_arguments.push_back(r);
|
||||
if (error.error != Callable::CallError::CALL_OK) {
|
||||
push_error(vformat(R"(Expected %s as argument %d of annotation "%s".)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name));
|
||||
p_annotation->resolved_arguments.remove_at(p_annotation->resolved_arguments.size() - 1);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
default: {
|
||||
if (argument->type != Node::LITERAL) {
|
||||
push_error(vformat(R"(Expected %s as argument %d of annotation "%s".)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name));
|
||||
return false;
|
||||
}
|
||||
|
||||
Variant value = static_cast<LiteralNode *>(argument)->value;
|
||||
if (!Variant::can_convert_strict(value.get_type(), parameter.type)) {
|
||||
push_error(vformat(R"(Expected %s as argument %d of annotation "%s".)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name));
|
||||
return false;
|
||||
}
|
||||
Callable::CallError error;
|
||||
const Variant *args = &value;
|
||||
Variant r;
|
||||
Variant::construct(parameter.type, r, &(args), 1, error);
|
||||
p_annotation->resolved_arguments.push_back(r);
|
||||
if (error.error != Callable::CallError::CALL_OK) {
|
||||
push_error(vformat(R"(Expected %s as argument %d of annotation "%s".)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name));
|
||||
p_annotation->resolved_arguments.remove_at(p_annotation->resolved_arguments.size() - 1);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1401,7 +1401,7 @@ private:
|
|||
// Annotations
|
||||
AnnotationNode *parse_annotation(uint32_t p_valid_targets);
|
||||
bool register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, const Vector<Variant> &p_default_arguments = Vector<Variant>(), bool p_is_vararg = false);
|
||||
bool validate_annotation_arguments(AnnotationNode *p_annotation);
|
||||
bool validate_annotation_argument_count(AnnotationNode *p_annotation);
|
||||
void clear_unused_annotations();
|
||||
bool tool_annotation(const AnnotationNode *p_annotation, Node *p_target);
|
||||
bool icon_annotation(const AnnotationNode *p_annotation, Node *p_target);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
var num := 1
|
||||
|
||||
@export_range(num, 10) var a
|
||||
|
||||
func test():
|
||||
pass
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
GDTEST_ANALYZER_ERROR
|
||||
Argument 1 of annotation "@export_range" isn't a constant expression.
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
const BEFORE = 1
|
||||
|
||||
@export_range(-10, 10) var a = 0
|
||||
@export_range(1 + 2, absi(-10) + 1) var b = 5
|
||||
@export_range(BEFORE + 1, BEFORE + AFTER + 1) var c = 5
|
||||
|
||||
const AFTER = 10
|
||||
|
||||
func test():
|
||||
pass
|
||||
|
|
@ -0,0 +1 @@
|
|||
GDTEST_OK
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
@warning_ignore(unused_private_class_variable)
|
||||
@warning_ignore("unused_private_class_variable")
|
||||
var _unused = 2
|
||||
|
||||
@warning_ignore(unused_variable)
|
||||
@warning_ignore("unused_variable")
|
||||
func test():
|
||||
print("test")
|
||||
var unused = 3
|
||||
|
||||
@warning_ignore(redundant_await)
|
||||
@warning_ignore("redundant_await")
|
||||
print(await regular_func())
|
||||
|
||||
print("done")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# https://github.com/godotengine/godot/issues/50285
|
||||
|
||||
@warning_ignore(unused_local_constant)
|
||||
@warning_ignore("unused_local_constant")
|
||||
func test():
|
||||
const CONST_INNER_DICTIONARY = { "key": true }
|
||||
const CONST_NESTED_DICTIONARY_OLD_WORKAROUND = {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
@export var color: Color
|
||||
@export_color_no_alpha var color_no_alpha: Color
|
||||
@export_node_path(Sprite2D, Sprite3D, Control, Node) var nodepath := ^"hello"
|
||||
@export_node_path("Sprite2D", "Sprite3D", "Control", "Node") var nodepath := ^"hello"
|
||||
|
||||
|
||||
func test():
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@ func wait() -> void:
|
|||
pass
|
||||
|
||||
func test():
|
||||
@warning_ignore(redundant_await)
|
||||
@warning_ignore("redundant_await")
|
||||
await wait()
|
||||
print("end")
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@ func test():
|
|||
|
||||
func builtin_method():
|
||||
var pba := PackedByteArray()
|
||||
@warning_ignore(return_value_discarded)
|
||||
@warning_ignore("return_value_discarded")
|
||||
pba.resize(1) # Built-in validated.
|
||||
|
||||
|
||||
func builtin_method_static():
|
||||
var _pba := PackedByteArray()
|
||||
@warning_ignore(return_value_discarded)
|
||||
@warning_ignore("return_value_discarded")
|
||||
Vector2.from_angle(PI) # Static built-in validated.
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ class InnerClass:
|
|||
func _init() -> void:
|
||||
prints("Inner")
|
||||
'''
|
||||
@warning_ignore(return_value_discarded)
|
||||
@warning_ignore("return_value_discarded")
|
||||
gdscr.reload()
|
||||
|
||||
var inst = gdscr.new()
|
||||
|
||||
@warning_ignore(unsafe_method_access)
|
||||
@warning_ignore("unsafe_method_access")
|
||||
inst.test()
|
||||
|
|
|
|||
|
|
@ -20,26 +20,26 @@ func test_utility(v, f):
|
|||
assert(not f) # Test unary operator reading from `nil`.
|
||||
|
||||
func test_builtin_call(v, f):
|
||||
@warning_ignore(unsafe_method_access)
|
||||
@warning_ignore("unsafe_method_access")
|
||||
v.angle() # Built-in method call.
|
||||
assert(not f) # Test unary operator reading from `nil`.
|
||||
|
||||
func test_builtin_call_validated(v: Vector2, f):
|
||||
@warning_ignore(return_value_discarded)
|
||||
@warning_ignore("return_value_discarded")
|
||||
v.abs() # Built-in method call validated.
|
||||
assert(not f) # Test unary operator reading from `nil`.
|
||||
|
||||
func test_object_call(v, f):
|
||||
@warning_ignore(unsafe_method_access)
|
||||
@warning_ignore("unsafe_method_access")
|
||||
v.get_reference_count() # Native type method call.
|
||||
assert(not f) # Test unary operator reading from `nil`.
|
||||
|
||||
func test_object_call_method_bind(v: Resource, f):
|
||||
@warning_ignore(return_value_discarded)
|
||||
@warning_ignore("return_value_discarded")
|
||||
v.duplicate() # Native type method call with MethodBind.
|
||||
assert(not f) # Test unary operator reading from `nil`.
|
||||
|
||||
func test_object_call_ptrcall(v: RefCounted, f):
|
||||
@warning_ignore(return_value_discarded)
|
||||
@warning_ignore("return_value_discarded")
|
||||
v.get_reference_count() # Native type method call with ptrcall.
|
||||
assert(not f) # Test unary operator reading from `nil`.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# https://github.com/godotengine/godot/issues/71172
|
||||
|
||||
func test():
|
||||
@warning_ignore(narrowing_conversion)
|
||||
@warning_ignore("narrowing_conversion")
|
||||
var foo: int = 0.0
|
||||
print(typeof(foo) == TYPE_INT)
|
||||
var dict : Dictionary = {"a":0.0}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue