Add editor highlight for type-safe lines
The line number is hightlighted to indicate that the line contains only type-safe code.
This commit is contained in:
parent
a2305cd8b2
commit
03746da73f
|
@ -213,7 +213,7 @@ public:
|
|||
virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const = 0;
|
||||
virtual void make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script) {}
|
||||
virtual bool is_using_templates() { return false; }
|
||||
virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = NULL) const = 0;
|
||||
virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = NULL, Set<int> *r_safe_lines = NULL) const = 0;
|
||||
virtual String validate_path(const String &p_path) const { return ""; }
|
||||
virtual Script *create_script() const = 0;
|
||||
virtual bool has_named_classes() const = 0;
|
||||
|
|
|
@ -364,6 +364,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
|
|||
|
||||
_initial_set("text_editor/highlighting/highlight_all_occurrences", true);
|
||||
_initial_set("text_editor/highlighting/highlight_current_line", true);
|
||||
_initial_set("text_editor/highlighting/highlight_type_safe_lines", true);
|
||||
_initial_set("text_editor/cursor/scroll_past_end_of_file", false);
|
||||
|
||||
_initial_set("text_editor/indent/type", 0);
|
||||
|
@ -593,6 +594,7 @@ void EditorSettings::_load_default_text_editor_theme() {
|
|||
_initial_set("text_editor/highlighting/completion_font_color", Color::html("aaaaaa"));
|
||||
_initial_set("text_editor/highlighting/text_color", Color::html("aaaaaa"));
|
||||
_initial_set("text_editor/highlighting/line_number_color", Color::html("66aaaaaa"));
|
||||
_initial_set("text_editor/highlighting/safe_line_number_color", Color::html("99aac8aa"));
|
||||
_initial_set("text_editor/highlighting/caret_color", Color::html("aaaaaa"));
|
||||
_initial_set("text_editor/highlighting/caret_background_color", Color::html("000000"));
|
||||
_initial_set("text_editor/highlighting/text_selected_color", Color::html("000000"));
|
||||
|
|
|
@ -1089,6 +1089,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
|
|||
const Color completion_font_color = font_color;
|
||||
const Color text_color = font_color;
|
||||
const Color line_number_color = dim_color;
|
||||
const Color safe_line_number_color = dim_color * Color(1, 1.2, 1, 1.5);
|
||||
const Color caret_color = mono_color;
|
||||
const Color caret_background_color = mono_color.inverted();
|
||||
const Color text_selected_color = dark_color_3;
|
||||
|
@ -1123,6 +1124,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
|
|||
setting->set_initial_value("text_editor/highlighting/completion_font_color", completion_font_color, true);
|
||||
setting->set_initial_value("text_editor/highlighting/text_color", text_color, true);
|
||||
setting->set_initial_value("text_editor/highlighting/line_number_color", line_number_color, true);
|
||||
setting->set_initial_value("text_editor/highlighting/safe_line_number_color", safe_line_number_color, true);
|
||||
setting->set_initial_value("text_editor/highlighting/caret_color", caret_color, true);
|
||||
setting->set_initial_value("text_editor/highlighting/caret_background_color", caret_background_color, true);
|
||||
setting->set_initial_value("text_editor/highlighting/text_selected_color", text_selected_color, true);
|
||||
|
|
|
@ -116,6 +116,7 @@ void ScriptTextEditor::_load_theme_settings() {
|
|||
Color completion_font_color = EDITOR_GET("text_editor/highlighting/completion_font_color");
|
||||
Color text_color = EDITOR_GET("text_editor/highlighting/text_color");
|
||||
Color line_number_color = EDITOR_GET("text_editor/highlighting/line_number_color");
|
||||
Color safe_line_number_color = EDITOR_GET("text_editor/highlighting/safe_line_number_color");
|
||||
Color caret_color = EDITOR_GET("text_editor/highlighting/caret_color");
|
||||
Color caret_background_color = EDITOR_GET("text_editor/highlighting/caret_background_color");
|
||||
Color text_selected_color = EDITOR_GET("text_editor/highlighting/text_selected_color");
|
||||
|
@ -147,6 +148,7 @@ void ScriptTextEditor::_load_theme_settings() {
|
|||
text_edit->add_color_override("completion_font_color", completion_font_color);
|
||||
text_edit->add_color_override("font_color", text_color);
|
||||
text_edit->add_color_override("line_number_color", line_number_color);
|
||||
text_edit->add_color_override("safe_line_number_color", safe_line_number_color);
|
||||
text_edit->add_color_override("caret_color", caret_color);
|
||||
text_edit->add_color_override("caret_background_color", caret_background_color);
|
||||
text_edit->add_color_override("font_selected_color", text_selected_color);
|
||||
|
@ -589,6 +591,7 @@ void ScriptTextEditor::set_edited_script(const Ref<Script> &p_script) {
|
|||
|
||||
emit_signal("name_changed");
|
||||
code_editor->update_line_and_column();
|
||||
call_deferred("_validate_script");
|
||||
}
|
||||
|
||||
void ScriptTextEditor::_validate_script() {
|
||||
|
@ -599,8 +602,9 @@ void ScriptTextEditor::_validate_script() {
|
|||
|
||||
String text = te->get_text();
|
||||
List<String> fnc;
|
||||
Set<int> safe_lines;
|
||||
|
||||
if (!script->get_language()->validate(text, line, col, errortxt, script->get_path(), &fnc)) {
|
||||
if (!script->get_language()->validate(text, line, col, errortxt, script->get_path(), &fnc, &safe_lines)) {
|
||||
String error_text = "error(" + itos(line) + "," + itos(col) + "): " + errortxt;
|
||||
code_editor->set_error(error_text);
|
||||
} else {
|
||||
|
@ -621,8 +625,23 @@ void ScriptTextEditor::_validate_script() {
|
|||
}
|
||||
|
||||
line--;
|
||||
bool highlight_safe = EDITOR_DEF("text_editor/highlighting/highlight_type_safe_lines", true);
|
||||
bool last_is_safe = false;
|
||||
for (int i = 0; i < te->get_line_count(); i++) {
|
||||
te->set_line_as_marked(i, line == i);
|
||||
if (highlight_safe) {
|
||||
if (safe_lines.has(i + 1)) {
|
||||
te->set_line_as_safe(i, true);
|
||||
last_is_safe = true;
|
||||
} else if (last_is_safe && (te->is_line_comment(i) || te->get_line(i).strip_edges().empty())) {
|
||||
te->set_line_as_safe(i, true);
|
||||
} else {
|
||||
te->set_line_as_safe(i, false);
|
||||
last_is_safe = false;
|
||||
}
|
||||
} else {
|
||||
te->set_line_as_safe(i, false);
|
||||
}
|
||||
}
|
||||
|
||||
emit_signal("name_changed");
|
||||
|
|
|
@ -1053,7 +1053,7 @@ Ref<Script> NativeScriptLanguage::get_template(const String &p_class_name, const
|
|||
s->set_class_name(p_class_name);
|
||||
return Ref<NativeScript>(s);
|
||||
}
|
||||
bool NativeScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions) const {
|
||||
bool NativeScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, Set<int> *r_safe_lines) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -295,7 +295,7 @@ public:
|
|||
virtual void get_comment_delimiters(List<String> *p_delimiters) const;
|
||||
virtual void get_string_delimiters(List<String> *p_delimiters) const;
|
||||
virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const;
|
||||
virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions) const;
|
||||
virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, Set<int> *r_safe_lines = NULL) const;
|
||||
virtual Script *create_script() const;
|
||||
virtual bool has_named_classes() const;
|
||||
virtual bool supports_builtin_mode() const;
|
||||
|
|
|
@ -108,7 +108,7 @@ Ref<Script> PluginScriptLanguage::get_template(const String &p_class_name, const
|
|||
return script;
|
||||
}
|
||||
|
||||
bool PluginScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions) const {
|
||||
bool PluginScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, Set<int> *r_safe_lines) const {
|
||||
PoolStringArray functions;
|
||||
if (_desc.validate) {
|
||||
bool ret = _desc.validate(
|
||||
|
|
|
@ -74,7 +74,7 @@ public:
|
|||
virtual void get_comment_delimiters(List<String> *p_delimiters) const;
|
||||
virtual void get_string_delimiters(List<String> *p_delimiters) const;
|
||||
virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const;
|
||||
virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = NULL) const;
|
||||
virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = NULL, Set<int> *r_safe_lines = NULL) const;
|
||||
virtual Script *create_script() const;
|
||||
virtual bool has_named_classes() const;
|
||||
virtual bool supports_builtin_mode() const;
|
||||
|
|
|
@ -397,7 +397,7 @@ public:
|
|||
virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const;
|
||||
virtual bool is_using_templates();
|
||||
virtual void make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script);
|
||||
virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = NULL) const;
|
||||
virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = NULL, Set<int> *r_safe_lines = NULL) const;
|
||||
virtual Script *create_script() const;
|
||||
virtual bool has_named_classes() const;
|
||||
virtual bool supports_builtin_mode() const;
|
||||
|
|
|
@ -115,11 +115,11 @@ void GDScriptLanguage::make_template(const String &p_class_name, const String &p
|
|||
p_script->set_source_code(src);
|
||||
}
|
||||
|
||||
bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions) const {
|
||||
bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, Set<int> *r_safe_lines) const {
|
||||
|
||||
GDScriptParser parser;
|
||||
|
||||
Error err = parser.parse(p_script, p_path.get_base_dir(), true, p_path);
|
||||
Error err = parser.parse(p_script, p_path.get_base_dir(), true, p_path, false, r_safe_lines);
|
||||
if (err) {
|
||||
r_line_error = parser.get_error_line();
|
||||
r_col_error = parser.get_error_column();
|
||||
|
|
|
@ -2464,6 +2464,11 @@ void GDScriptParser::_transform_match_statment(MatchNode *p_match_statement) {
|
|||
id->name = "#match_value";
|
||||
id->line = p_match_statement->line;
|
||||
id->datatype = _reduce_node_type(p_match_statement->val_to_match);
|
||||
if (id->datatype.has_type) {
|
||||
_mark_line_as_safe(id->line);
|
||||
} else {
|
||||
_mark_line_as_unsafe(id->line);
|
||||
}
|
||||
|
||||
if (error_set) {
|
||||
return;
|
||||
|
@ -2480,6 +2485,7 @@ void GDScriptParser::_transform_match_statment(MatchNode *p_match_statement) {
|
|||
|
||||
for (int j = 0; j < branch->patterns.size(); j++) {
|
||||
PatternNode *pattern = branch->patterns[j];
|
||||
_mark_line_as_safe(pattern->line);
|
||||
|
||||
Map<StringName, Node *> bindings;
|
||||
Node *resulting_node = NULL;
|
||||
|
@ -2633,6 +2639,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
|
|||
_set_error("Expected ';' or <NewLine>.");
|
||||
return;
|
||||
}
|
||||
_mark_line_as_safe(tokenizer->get_token_line());
|
||||
tokenizer->advance();
|
||||
if (tokenizer->get_token() == GDScriptTokenizer::TK_SEMICOLON) {
|
||||
// Ignore semicolon after 'pass'
|
||||
|
@ -3328,6 +3335,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
|
|||
} break;
|
||||
case GDScriptTokenizer::TK_PR_EXTENDS: {
|
||||
|
||||
_mark_line_as_safe(tokenizer->get_token_line());
|
||||
_parse_extends(p_class);
|
||||
if (error_set)
|
||||
return;
|
||||
|
@ -5693,6 +5701,8 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
|
|||
return DataType();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_mark_line_as_unsafe(cn->line);
|
||||
}
|
||||
|
||||
node_type = cn->cast_type;
|
||||
|
@ -5804,6 +5814,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
|
|||
DataType argument_a_type = _reduce_node_type(op->arguments[0]);
|
||||
DataType argument_b_type = _reduce_node_type(op->arguments[1]);
|
||||
if (!argument_a_type.has_type || !argument_b_type.has_type) {
|
||||
_mark_line_as_unsafe(op->line);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -5902,6 +5913,8 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
|
|||
} else {
|
||||
node_type = _reduce_identifier_type(&base_type, member_id->name, op->line);
|
||||
}
|
||||
} else {
|
||||
_mark_line_as_unsafe(op->line);
|
||||
}
|
||||
if (error_set) {
|
||||
return DataType();
|
||||
|
@ -5928,6 +5941,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
|
|||
DataType index_type = _reduce_node_type(op->arguments[1]);
|
||||
|
||||
if (!base_type.has_type) {
|
||||
_mark_line_as_unsafe(op->line);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -6013,6 +6027,8 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
|
|||
}
|
||||
} break;
|
||||
}
|
||||
} else {
|
||||
_mark_line_as_unsafe(op->line);
|
||||
}
|
||||
} else if (!for_completion && (index_type.kind != DataType::BUILTIN || index_type.builtin_type != Variant::STRING)) {
|
||||
_set_error("Only strings can be used as index in the base type '" + base_type.to_string() + "'.", op->line);
|
||||
|
@ -6386,6 +6402,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat
|
|||
}
|
||||
|
||||
if (!base_type.has_type || (base_type.kind == DataType::BUILTIN && base_type.builtin_type == Variant::NIL)) {
|
||||
_mark_line_as_unsafe(p_call->line);
|
||||
return DataType();
|
||||
}
|
||||
|
||||
|
@ -6395,7 +6412,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat
|
|||
|
||||
if (check_types) {
|
||||
if (!tmp.has_method(callee_name)) {
|
||||
_set_error("Method '" + String(callee_name) + "()' is not declared in base '" + base_type.to_string() + "'.", p_call->line);
|
||||
_set_error("Method '" + callee_name + "' is not declared on base '" + base_type.to_string() + "'.", p_call->line);
|
||||
return DataType();
|
||||
}
|
||||
|
||||
|
@ -6446,12 +6463,6 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat
|
|||
default_args_count, is_static, is_vararg);
|
||||
}
|
||||
|
||||
String base_name;
|
||||
if (p_call->arguments[0]->type == Node::TYPE_SELF) {
|
||||
base_name = current_class->name == StringName() ? "self" : current_class->name;
|
||||
} else {
|
||||
base_name = original_type.to_string();
|
||||
}
|
||||
if (!valid) {
|
||||
if (p_call->arguments[0]->type == Node::TYPE_SELF) {
|
||||
_set_error("Method '" + callee_name + "' is not declared in the current class.", p_call->line);
|
||||
|
@ -6496,12 +6507,19 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!_is_type_compatible(arg_types[i - arg_diff], par_type, true)) {
|
||||
_set_error("At '" + callee_name + "()' call, argument " + itos(i - arg_diff + 1) + ". Assigned type (" +
|
||||
par_type.to_string() + ") doesn't match the function argument's type (" +
|
||||
arg_types[i - arg_diff].to_string() + ").",
|
||||
p_call->line);
|
||||
return DataType();
|
||||
if (!par_type.has_type) {
|
||||
_mark_line_as_unsafe(p_call->line);
|
||||
} else if (!_is_type_compatible(arg_types[i - arg_diff], par_type, true)) {
|
||||
// Supertypes are acceptable for dynamic compliance
|
||||
if (!_is_type_compatible(par_type, arg_types[i - arg_diff])) {
|
||||
_set_error("At '" + callee_name + "()' call, argument " + itos(i - arg_diff + 1) + ". Assigned type (" +
|
||||
par_type.to_string() + ") doesn't match the function argument's type (" +
|
||||
arg_types[i - arg_diff].to_string() + ").",
|
||||
p_call->line);
|
||||
return DataType();
|
||||
} else {
|
||||
_mark_line_as_unsafe(p_call->line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6858,23 +6876,23 @@ GDScriptParser::DataType GDScriptParser::_reduce_identifier_type(const DataType
|
|||
return member_type;
|
||||
}
|
||||
|
||||
if (!check_types) {
|
||||
return DataType();
|
||||
if (!p_base_type) {
|
||||
// This means looking in the current class, which type is always known
|
||||
_set_error("Identifier '" + p_identifier.operator String() + "' is not declared in the current scope.", p_line);
|
||||
}
|
||||
|
||||
if (!p_base_type) {
|
||||
_set_error("Identifier '" + String(p_identifier) + "' is not declared in the current scope.", p_line);
|
||||
} else {
|
||||
_set_error("Identifier '" + String(p_identifier) + "' is not declared in base '" + p_base_type->to_string() + "'.", p_line);
|
||||
}
|
||||
_mark_line_as_unsafe(p_line);
|
||||
return DataType();
|
||||
}
|
||||
|
||||
void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
|
||||
|
||||
_mark_line_as_safe(p_class->line);
|
||||
|
||||
// Constants
|
||||
for (Map<StringName, ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) {
|
||||
ClassNode::Constant &c = E->get();
|
||||
_mark_line_as_safe(c.expression->line);
|
||||
DataType cont = _resolve_type(c.type, c.expression->line);
|
||||
DataType expr = _resolve_type(c.expression->get_datatype(), c.expression->line);
|
||||
|
||||
|
@ -6910,40 +6928,44 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!v.data_type.has_type) continue;
|
||||
|
||||
_mark_line_as_safe(v.line);
|
||||
v.data_type = _resolve_type(v.data_type, v.line);
|
||||
|
||||
if (v.expression) {
|
||||
DataType expr_type = _reduce_node_type(v.expression);
|
||||
|
||||
if (!_is_type_compatible(v.data_type, expr_type)) {
|
||||
// Try with implicit conversion
|
||||
if (v.data_type.kind != DataType::BUILTIN || !_is_type_compatible(v.data_type, expr_type, true)) {
|
||||
_set_error("Assigned expression type (" + expr_type.to_string() + ") doesn't match the variable's type (" +
|
||||
v.data_type.to_string() + ").",
|
||||
v.line);
|
||||
return;
|
||||
// Try supertype test
|
||||
if (_is_type_compatible(expr_type, v.data_type)) {
|
||||
_mark_line_as_unsafe(v.line);
|
||||
} else {
|
||||
// Try with implicit conversion
|
||||
if (v.data_type.kind != DataType::BUILTIN || !_is_type_compatible(v.data_type, expr_type, true)) {
|
||||
_set_error("Assigned expression type (" + expr_type.to_string() + ") doesn't match the variable's type (" +
|
||||
v.data_type.to_string() + ").",
|
||||
v.line);
|
||||
return;
|
||||
}
|
||||
|
||||
// Replace assigment with implict conversion
|
||||
BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>();
|
||||
convert->line = v.line;
|
||||
convert->function = GDScriptFunctions::TYPE_CONVERT;
|
||||
|
||||
ConstantNode *tgt_type = alloc_node<ConstantNode>();
|
||||
tgt_type->line = v.line;
|
||||
tgt_type->value = (int)v.data_type.builtin_type;
|
||||
|
||||
OperatorNode *convert_call = alloc_node<OperatorNode>();
|
||||
convert_call->line = v.line;
|
||||
convert_call->op = OperatorNode::OP_CALL;
|
||||
convert_call->arguments.push_back(convert);
|
||||
convert_call->arguments.push_back(v.expression);
|
||||
convert_call->arguments.push_back(tgt_type);
|
||||
|
||||
v.expression = convert_call;
|
||||
v.initial_assignment->arguments[1] = convert_call;
|
||||
}
|
||||
|
||||
// Replace assigment with implict conversion
|
||||
BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>();
|
||||
convert->line = v.line;
|
||||
convert->function = GDScriptFunctions::TYPE_CONVERT;
|
||||
|
||||
ConstantNode *tgt_type = alloc_node<ConstantNode>();
|
||||
tgt_type->line = v.line;
|
||||
tgt_type->value = (int)v.data_type.builtin_type;
|
||||
|
||||
OperatorNode *convert_call = alloc_node<OperatorNode>();
|
||||
convert_call->line = v.line;
|
||||
convert_call->op = OperatorNode::OP_CALL;
|
||||
convert_call->arguments.push_back(convert);
|
||||
convert_call->arguments.push_back(v.expression);
|
||||
convert_call->arguments.push_back(tgt_type);
|
||||
|
||||
v.expression = convert_call;
|
||||
v.initial_assignment->arguments[1] = convert_call;
|
||||
}
|
||||
} else if (v.data_type.has_type && v.data_type.kind == DataType::BUILTIN) {
|
||||
// Create default value based on the type
|
||||
|
@ -6972,7 +6994,7 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
|
|||
}
|
||||
|
||||
// Check export hint
|
||||
if (v._export.type != Variant::NIL) {
|
||||
if (v.data_type.has_type && v._export.type != Variant::NIL) {
|
||||
DataType export_type = _type_from_property(v._export);
|
||||
if (!_is_type_compatible(v.data_type, export_type, true)) {
|
||||
_set_error("Export hint type (" + export_type.to_string() + ") doesn't match the variable's type (" +
|
||||
|
@ -7149,6 +7171,7 @@ void GDScriptParser::_check_class_blocks_types(ClassNode *p_class) {
|
|||
for (int i = 0; i < p_class->static_functions.size(); i++) {
|
||||
current_function = p_class->static_functions[i];
|
||||
current_block = current_function->body;
|
||||
_mark_line_as_safe(current_function->line);
|
||||
_check_block_types(current_block);
|
||||
current_block = NULL;
|
||||
current_function = NULL;
|
||||
|
@ -7158,6 +7181,7 @@ void GDScriptParser::_check_class_blocks_types(ClassNode *p_class) {
|
|||
for (int i = 0; i < p_class->functions.size(); i++) {
|
||||
current_function = p_class->functions[i];
|
||||
current_block = current_function->body;
|
||||
_mark_line_as_safe(current_function->line);
|
||||
_check_block_types(current_block);
|
||||
current_block = NULL;
|
||||
current_function = NULL;
|
||||
|
@ -7175,6 +7199,8 @@ void GDScriptParser::_check_class_blocks_types(ClassNode *p_class) {
|
|||
|
||||
void GDScriptParser::_check_block_types(BlockNode *p_block) {
|
||||
|
||||
Node *last_var_assign = NULL;
|
||||
|
||||
// Check each statement
|
||||
for (List<Node *>::Element *E = p_block->statements.front(); E; E = E->next()) {
|
||||
Node *statement = E->get();
|
||||
|
@ -7187,37 +7213,47 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
|
|||
case Node::TYPE_LOCAL_VAR: {
|
||||
LocalVarNode *lv = static_cast<LocalVarNode *>(statement);
|
||||
lv->datatype = _resolve_type(lv->datatype, lv->line);
|
||||
_mark_line_as_safe(lv->line);
|
||||
|
||||
if (lv->assign) {
|
||||
DataType assign_type = _reduce_node_type(lv->assign);
|
||||
if (!_is_type_compatible(lv->datatype, assign_type)) {
|
||||
// Try implict conversion
|
||||
if (lv->datatype.kind != DataType::BUILTIN || !_is_type_compatible(lv->datatype, assign_type, true)) {
|
||||
_set_error("Assigned value type (" + assign_type.to_string() + ") doesn't match the variable's type (" +
|
||||
lv->datatype.to_string() + ").",
|
||||
lv->line);
|
||||
return;
|
||||
// Try supertype test
|
||||
if (_is_type_compatible(assign_type, lv->datatype)) {
|
||||
_mark_line_as_unsafe(lv->line);
|
||||
} else {
|
||||
// Try implict conversion
|
||||
if (lv->datatype.kind != DataType::BUILTIN || !_is_type_compatible(lv->datatype, assign_type, true)) {
|
||||
_set_error("Assigned value type (" + assign_type.to_string() + ") doesn't match the variable's type (" +
|
||||
lv->datatype.to_string() + ").",
|
||||
lv->line);
|
||||
return;
|
||||
}
|
||||
// Replace assigment with implict conversion
|
||||
BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>();
|
||||
convert->line = lv->line;
|
||||
convert->function = GDScriptFunctions::TYPE_CONVERT;
|
||||
|
||||
ConstantNode *tgt_type = alloc_node<ConstantNode>();
|
||||
tgt_type->line = lv->line;
|
||||
tgt_type->value = (int)lv->datatype.builtin_type;
|
||||
|
||||
OperatorNode *convert_call = alloc_node<OperatorNode>();
|
||||
convert_call->line = lv->line;
|
||||
convert_call->op = OperatorNode::OP_CALL;
|
||||
convert_call->arguments.push_back(convert);
|
||||
convert_call->arguments.push_back(lv->assign);
|
||||
convert_call->arguments.push_back(tgt_type);
|
||||
|
||||
lv->assign = convert_call;
|
||||
lv->assign_op->arguments[1] = convert_call;
|
||||
}
|
||||
// Replace assigment with implict conversion
|
||||
BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>();
|
||||
convert->line = lv->line;
|
||||
convert->function = GDScriptFunctions::TYPE_CONVERT;
|
||||
|
||||
ConstantNode *tgt_type = alloc_node<ConstantNode>();
|
||||
tgt_type->line = lv->line;
|
||||
tgt_type->value = (int)lv->datatype.builtin_type;
|
||||
|
||||
OperatorNode *convert_call = alloc_node<OperatorNode>();
|
||||
convert_call->line = lv->line;
|
||||
convert_call->op = OperatorNode::OP_CALL;
|
||||
convert_call->arguments.push_back(convert);
|
||||
convert_call->arguments.push_back(lv->assign);
|
||||
convert_call->arguments.push_back(tgt_type);
|
||||
|
||||
lv->assign = convert_call;
|
||||
lv->assign_op->arguments[1] = convert_call;
|
||||
}
|
||||
if (lv->datatype.has_type && !assign_type.has_type) {
|
||||
_mark_line_as_unsafe(lv->line);
|
||||
}
|
||||
}
|
||||
last_var_assign = lv->assign;
|
||||
|
||||
// TODO: Make a warning
|
||||
/*
|
||||
|
@ -7247,6 +7283,13 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (op->arguments[1] == last_var_assign) {
|
||||
// Assignment was already checked
|
||||
break;
|
||||
}
|
||||
|
||||
_mark_line_as_safe(op->line);
|
||||
|
||||
DataType lh_type = _reduce_node_type(op->arguments[0]);
|
||||
|
||||
if (error_set) {
|
||||
|
@ -7254,10 +7297,10 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
|
|||
}
|
||||
|
||||
if (!lh_type.has_type) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (lh_type.is_constant) {
|
||||
if (op->arguments[0]->type == Node::TYPE_OPERATOR) {
|
||||
_mark_line_as_unsafe(op->line);
|
||||
}
|
||||
} else if (lh_type.is_constant) {
|
||||
_set_error("Cannot assign a new value to a constant.", op->line);
|
||||
return;
|
||||
}
|
||||
|
@ -7267,6 +7310,7 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
|
|||
// Validate operation
|
||||
DataType arg_type = _reduce_node_type(op->arguments[1]);
|
||||
if (!arg_type.has_type) {
|
||||
_mark_line_as_unsafe(op->line);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -7285,38 +7329,49 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
|
|||
}
|
||||
|
||||
if (!_is_type_compatible(lh_type, rh_type)) {
|
||||
// Try implict conversion
|
||||
if (lh_type.kind != DataType::BUILTIN || !_is_type_compatible(lh_type, rh_type, true)) {
|
||||
_set_error("Assigned value type (" + rh_type.to_string() + ") doesn't match the variable's type (" +
|
||||
lh_type.to_string() + ").",
|
||||
op->line);
|
||||
return;
|
||||
// Try supertype test
|
||||
if (_is_type_compatible(rh_type, lh_type)) {
|
||||
_mark_line_as_unsafe(op->line);
|
||||
} else {
|
||||
// Try implict conversion
|
||||
if (lh_type.kind != DataType::BUILTIN || !_is_type_compatible(lh_type, rh_type, true)) {
|
||||
_set_error("Assigned value type (" + rh_type.to_string() + ") doesn't match the variable's type (" +
|
||||
lh_type.to_string() + ").",
|
||||
op->line);
|
||||
return;
|
||||
}
|
||||
// Replace assigment with implict conversion
|
||||
BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>();
|
||||
convert->line = op->line;
|
||||
convert->function = GDScriptFunctions::TYPE_CONVERT;
|
||||
|
||||
ConstantNode *tgt_type = alloc_node<ConstantNode>();
|
||||
tgt_type->line = op->line;
|
||||
tgt_type->value = (int)lh_type.builtin_type;
|
||||
|
||||
OperatorNode *convert_call = alloc_node<OperatorNode>();
|
||||
convert_call->line = op->line;
|
||||
convert_call->op = OperatorNode::OP_CALL;
|
||||
convert_call->arguments.push_back(convert);
|
||||
convert_call->arguments.push_back(op->arguments[1]);
|
||||
convert_call->arguments.push_back(tgt_type);
|
||||
|
||||
op->arguments[1] = convert_call;
|
||||
}
|
||||
// Replace assigment with implict conversion
|
||||
BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>();
|
||||
convert->line = op->line;
|
||||
convert->function = GDScriptFunctions::TYPE_CONVERT;
|
||||
|
||||
ConstantNode *tgt_type = alloc_node<ConstantNode>();
|
||||
tgt_type->line = op->line;
|
||||
tgt_type->value = (int)lh_type.builtin_type;
|
||||
|
||||
OperatorNode *convert_call = alloc_node<OperatorNode>();
|
||||
convert_call->line = op->line;
|
||||
convert_call->op = OperatorNode::OP_CALL;
|
||||
convert_call->arguments.push_back(convert);
|
||||
convert_call->arguments.push_back(op->arguments[1]);
|
||||
convert_call->arguments.push_back(tgt_type);
|
||||
|
||||
op->arguments[1] = convert_call;
|
||||
}
|
||||
if (!rh_type.has_type && (op->op != OperatorNode::OP_ASSIGN || lh_type.has_type || op->arguments[0]->type == Node::TYPE_OPERATOR)) {
|
||||
_mark_line_as_unsafe(op->line);
|
||||
}
|
||||
} break;
|
||||
case OperatorNode::OP_CALL:
|
||||
case OperatorNode::OP_PARENT_CALL: {
|
||||
_mark_line_as_safe(op->line);
|
||||
_reduce_function_call_type(op);
|
||||
if (error_set) return;
|
||||
} break;
|
||||
default: {
|
||||
_mark_line_as_safe(op->line);
|
||||
_reduce_node_type(op); // Test for safety anyway
|
||||
// TODO: Make this a warning
|
||||
/*_set_error("Standalone expression, nothing is done in this line.", statement->line);
|
||||
return; */
|
||||
|
@ -7325,11 +7380,20 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
|
|||
} break;
|
||||
case Node::TYPE_CONTROL_FLOW: {
|
||||
ControlFlowNode *cf = static_cast<ControlFlowNode *>(statement);
|
||||
_mark_line_as_safe(cf->line);
|
||||
|
||||
switch (cf->cf_type) {
|
||||
case ControlFlowNode::CF_RETURN: {
|
||||
DataType function_type = current_function->get_datatype();
|
||||
|
||||
DataType ret_type;
|
||||
if (cf->arguments.size() > 0) {
|
||||
ret_type = _reduce_node_type(cf->arguments[0]);
|
||||
if (error_set) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_type.has_type) break;
|
||||
|
||||
if (function_type.kind == DataType::BUILTIN && function_type.builtin_type == Variant::NIL) {
|
||||
|
@ -7345,10 +7409,6 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
|
|||
return;
|
||||
}
|
||||
|
||||
DataType ret_type = _reduce_node_type(cf->arguments[0]);
|
||||
if (error_set) {
|
||||
return;
|
||||
}
|
||||
if (!_is_type_compatible(function_type, ret_type)) {
|
||||
_set_error("Returned value type (" + ret_type.to_string() + ") doesn't match the function return type (" +
|
||||
function_type.to_string() + ").",
|
||||
|
@ -7361,6 +7421,14 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
|
|||
MatchNode *match_node = cf->match;
|
||||
_transform_match_statment(match_node);
|
||||
} break;
|
||||
default: {
|
||||
if (cf->body_else) {
|
||||
_mark_line_as_safe(cf->body_else->line);
|
||||
}
|
||||
for (int i = 0; i < cf->arguments.size(); i++) {
|
||||
_reduce_node_type(cf->arguments[i]);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
} break;
|
||||
case Node::TYPE_CONSTANT: {
|
||||
|
@ -7371,6 +7439,8 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
|
|||
}
|
||||
} // falthrough
|
||||
default: {
|
||||
_mark_line_as_safe(statement->line);
|
||||
_reduce_node_type(statement); // Test for safety anyway
|
||||
// TODO: Make this a warning
|
||||
/* _set_error("Standalone expression, nothing is done in this line.", statement->line);
|
||||
return; */
|
||||
|
@ -7479,7 +7549,7 @@ Error GDScriptParser::parse_bytecode(const Vector<uint8_t> &p_bytecode, const St
|
|||
return ret;
|
||||
}
|
||||
|
||||
Error GDScriptParser::parse(const String &p_code, const String &p_base_path, bool p_just_validate, const String &p_self_path, bool p_for_completion) {
|
||||
Error GDScriptParser::parse(const String &p_code, const String &p_base_path, bool p_just_validate, const String &p_self_path, bool p_for_completion, Set<int> *r_safe_lines) {
|
||||
|
||||
clear();
|
||||
|
||||
|
@ -7489,6 +7559,9 @@ Error GDScriptParser::parse(const String &p_code, const String &p_base_path, boo
|
|||
|
||||
validating = p_just_validate;
|
||||
for_completion = p_for_completion;
|
||||
#ifdef DEBUG_ENABLED
|
||||
safe_lines = r_safe_lines;
|
||||
#endif // DEBUG_ENABLED
|
||||
tokenizer = tt;
|
||||
Error ret = _parse(p_base_path);
|
||||
memdelete(tt);
|
||||
|
@ -7543,6 +7616,9 @@ void GDScriptParser::clear() {
|
|||
current_export.type = Variant::NIL;
|
||||
check_types = true;
|
||||
error = "";
|
||||
#ifdef DEBUG_ENABLED
|
||||
safe_lines = NULL;
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
|
||||
GDScriptParser::CompletionType GDScriptParser::get_completion_type() {
|
||||
|
|
|
@ -512,6 +512,9 @@ private:
|
|||
int error_line;
|
||||
int error_column;
|
||||
bool check_types;
|
||||
#ifdef DEBUG_ENABLED
|
||||
Set<int> *safe_lines;
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
int pending_newline;
|
||||
|
||||
|
@ -583,6 +586,16 @@ private:
|
|||
void _check_class_blocks_types(ClassNode *p_class);
|
||||
void _check_function_types(FunctionNode *p_function);
|
||||
void _check_block_types(BlockNode *p_block);
|
||||
_FORCE_INLINE_ void _mark_line_as_safe(int p_line) const {
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (safe_lines) safe_lines->insert(p_line);
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
_FORCE_INLINE_ void _mark_line_as_unsafe(int p_line) const {
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (safe_lines) safe_lines->erase(p_line);
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
|
||||
Error _parse(const String &p_base_path);
|
||||
|
||||
|
@ -590,7 +603,7 @@ public:
|
|||
String get_error() const;
|
||||
int get_error_line() const;
|
||||
int get_error_column() const;
|
||||
Error parse(const String &p_code, const String &p_base_path = "", bool p_just_validate = false, const String &p_self_path = "", bool p_for_completion = false);
|
||||
Error parse(const String &p_code, const String &p_base_path = "", bool p_just_validate = false, const String &p_self_path = "", bool p_for_completion = false, Set<int> *r_safe_lines = NULL);
|
||||
Error parse_bytecode(const Vector<uint8_t> &p_bytecode, const String &p_base_path = "", const String &p_self_path = "");
|
||||
|
||||
bool is_tool_script() const;
|
||||
|
|
|
@ -292,7 +292,7 @@ public:
|
|||
virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const;
|
||||
virtual bool is_using_templates();
|
||||
virtual void make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script);
|
||||
/* TODO */ virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions) const { return true; }
|
||||
/* TODO */ virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, Set<int> *r_safe_lines = NULL) const { return true; }
|
||||
virtual String validate_path(const String &p_path) const;
|
||||
virtual Script *create_script() const;
|
||||
virtual bool has_named_classes() const;
|
||||
|
|
|
@ -2402,7 +2402,7 @@ void VisualScriptLanguage::make_template(const String &p_class_name, const Strin
|
|||
script->set_instance_base_type(p_base_class_name);
|
||||
}
|
||||
|
||||
bool VisualScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions) const {
|
||||
bool VisualScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, Set<int> *r_safe_lines) const {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -563,7 +563,7 @@ public:
|
|||
virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const;
|
||||
virtual bool is_using_templates();
|
||||
virtual void make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script);
|
||||
virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = NULL) const;
|
||||
virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = NULL, Set<int> *r_safe_lines = NULL) const;
|
||||
virtual Script *create_script() const;
|
||||
virtual bool has_named_classes() const;
|
||||
virtual bool supports_builtin_mode() const;
|
||||
|
|
|
@ -290,6 +290,7 @@ void TextEdit::Text::insert(int p_at, const String &p_text) {
|
|||
|
||||
Line line;
|
||||
line.marked = false;
|
||||
line.safe = false;
|
||||
line.breakpoint = false;
|
||||
line.hidden = false;
|
||||
line.width_cache = -1;
|
||||
|
@ -972,7 +973,7 @@ void TextEdit::_notification(int p_what) {
|
|||
fc = line_num_padding + fc;
|
||||
}
|
||||
|
||||
cache.font->draw(ci, Point2(cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + ofs_x, yofs + cache.font->get_ascent()), fc, cache.line_number_color);
|
||||
cache.font->draw(ci, Point2(cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + ofs_x, yofs + cache.font->get_ascent()), fc, text.is_safe(line) ? cache.safe_line_number_color : cache.line_number_color);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4314,6 +4315,7 @@ void TextEdit::_update_caches() {
|
|||
cache.caret_color = get_color("caret_color");
|
||||
cache.caret_background_color = get_color("caret_background_color");
|
||||
cache.line_number_color = get_color("line_number_color");
|
||||
cache.safe_line_number_color = get_color("safe_line_number_color");
|
||||
cache.font_color = get_color("font_color");
|
||||
cache.font_selected_color = get_color("font_selected_color");
|
||||
cache.keyword_color = get_color("keyword_color");
|
||||
|
@ -4885,6 +4887,17 @@ void TextEdit::set_line_as_marked(int p_line, bool p_marked) {
|
|||
update();
|
||||
}
|
||||
|
||||
void TextEdit::set_line_as_safe(int p_line, bool p_safe) {
|
||||
ERR_FAIL_INDEX(p_line, text.size());
|
||||
text.set_safe(p_line, p_safe);
|
||||
update();
|
||||
}
|
||||
|
||||
bool TextEdit::is_line_set_as_safe(int p_line) const {
|
||||
ERR_FAIL_INDEX_V(p_line, text.size(), false);
|
||||
return text.is_safe(p_line);
|
||||
}
|
||||
|
||||
bool TextEdit::is_line_set_as_breakpoint(int p_line) const {
|
||||
|
||||
ERR_FAIL_INDEX_V(p_line, text.size(), false);
|
||||
|
|
|
@ -76,6 +76,7 @@ public:
|
|||
bool marked : 1;
|
||||
bool breakpoint : 1;
|
||||
bool hidden : 1;
|
||||
bool safe : 1;
|
||||
int wrap_amount_cache : 24;
|
||||
Map<int, ColorRegionInfo> region_info;
|
||||
String data;
|
||||
|
@ -106,6 +107,8 @@ public:
|
|||
bool is_breakpoint(int p_line) const { return text[p_line].breakpoint; }
|
||||
void set_hidden(int p_line, bool p_hidden) { text[p_line].hidden = p_hidden; }
|
||||
bool is_hidden(int p_line) const { return text[p_line].hidden; }
|
||||
void set_safe(int p_line, bool p_safe) { text[p_line].safe = p_safe; }
|
||||
bool is_safe(int p_line) const { return text[p_line].safe; }
|
||||
void insert(int p_at, const String &p_text);
|
||||
void remove(int p_at);
|
||||
int size() const { return text.size(); }
|
||||
|
@ -165,6 +168,7 @@ private:
|
|||
Color caret_color;
|
||||
Color caret_background_color;
|
||||
Color line_number_color;
|
||||
Color safe_line_number_color;
|
||||
Color font_color;
|
||||
Color font_selected_color;
|
||||
Color keyword_color;
|
||||
|
@ -472,6 +476,8 @@ public:
|
|||
void set_line_as_marked(int p_line, bool p_marked);
|
||||
void set_line_as_breakpoint(int p_line, bool p_breakpoint);
|
||||
bool is_line_set_as_breakpoint(int p_line) const;
|
||||
void set_line_as_safe(int p_line, bool p_safe);
|
||||
bool is_line_set_as_safe(int p_line) const;
|
||||
void get_breakpoints(List<int> *p_breakpoints) const;
|
||||
Array get_breakpoints_array() const;
|
||||
void remove_breakpoints();
|
||||
|
|
|
@ -476,6 +476,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
|
|||
theme->set_color("symbol_color", "TextEdit", control_font_color_hover);
|
||||
theme->set_color("brace_mismatch_color", "TextEdit", Color(1, 0.2, 0.2));
|
||||
theme->set_color("line_number_color", "TextEdit", Color::html("66aaaaaa"));
|
||||
theme->set_color("safe_line_number_color", "TextEdit", Color::html("99aac8aa"));
|
||||
theme->set_color("function_color", "TextEdit", Color::html("66a2ce"));
|
||||
theme->set_color("member_variable_color", "TextEdit", Color::html("e64e59"));
|
||||
theme->set_color("number_color", "TextEdit", Color::html("EB9532"));
|
||||
|
|
Loading…
Reference in New Issue