1
0
mirror of https://github.com/godotengine/godot.git synced 2025-11-04 12:00:25 +00:00

Merge pull request #102218 from HolonProduction/dictionary-recovery

GDScript: Do phrase level recovery when parsing faulty dictionaries
This commit is contained in:
Thaddeus Crews
2025-04-14 19:39:50 -05:00
30 changed files with 177 additions and 4 deletions

View File

@@ -3122,13 +3122,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode
case DictionaryNode::LUA_TABLE: case DictionaryNode::LUA_TABLE:
if (key != nullptr && key->type != Node::IDENTIFIER && key->type != Node::LITERAL) { if (key != nullptr && key->type != Node::IDENTIFIER && key->type != Node::LITERAL) {
push_error(R"(Expected identifier or string as Lua-style dictionary key (e.g "{ key = value }").)"); push_error(R"(Expected identifier or string as Lua-style dictionary key (e.g "{ key = value }").)");
advance();
break;
} }
if (key != nullptr && key->type == Node::LITERAL && static_cast<LiteralNode *>(key)->value.get_type() != Variant::STRING) { if (key != nullptr && key->type == Node::LITERAL && static_cast<LiteralNode *>(key)->value.get_type() != Variant::STRING) {
push_error(R"(Expected identifier or string as Lua-style dictionary key (e.g "{ key = value }").)"); push_error(R"(Expected identifier or string as Lua-style dictionary key (e.g "{ key = value }").)");
advance();
break;
} }
if (!match(GDScriptTokenizer::Token::EQUAL)) { if (!match(GDScriptTokenizer::Token::EQUAL)) {
if (match(GDScriptTokenizer::Token::COLON)) { if (match(GDScriptTokenizer::Token::COLON)) {
@@ -3168,6 +3164,21 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode
if (key != nullptr && value != nullptr) { if (key != nullptr && value != nullptr) {
dictionary->elements.push_back({ key, value }); dictionary->elements.push_back({ key, value });
} }
// Do phrase level recovery by inserting an imaginary expression for missing keys or values.
// This ensures the successfully parsed expression is part of the AST and can be analyzed.
if (key != nullptr && value == nullptr) {
LiteralNode *dummy = alloc_recovery_node<LiteralNode>();
dummy->value = Variant();
dictionary->elements.push_back({ key, dummy });
} else if (key == nullptr && value != nullptr) {
LiteralNode *dummy = alloc_recovery_node<LiteralNode>();
dummy->value = Variant();
dictionary->elements.push_back({ dummy, value });
}
} while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end()); } while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end());
} }
pop_multiline(); pop_multiline();

View File

@@ -1451,6 +1451,18 @@ private:
return node; return node;
} }
// Allocates a node for patching up the parse tree when an error occurred.
// Such nodes don't track their extents as they don't relate to actual tokens.
template <typename T>
T *alloc_recovery_node() {
T *node = memnew(T);
node->next = list;
list = node;
return node;
}
void clear(); void clear();
void push_error(const String &p_message, const Node *p_origin = nullptr); void push_error(const String &p_message, const Node *p_origin = nullptr);
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED

View File

@@ -0,0 +1,4 @@
[output]
exclude=[
{"display": "AUTO_TRANSLATE_MODE_INHERIT"},
]

View File

@@ -0,0 +1,6 @@
extends Node
var test = {
t = 1,
AutoTranslateMode.
}

View File

@@ -0,0 +1,4 @@
[output]
exclude=[
{"display": "VALUE"},
]

View File

@@ -0,0 +1,10 @@
extends Node
enum TestEnum {
VALUE,
}
var test = {
t = 1,
TestEnum. = 1,
}

View File

@@ -0,0 +1,4 @@
[output]
include=[
{"display": "AUTO_TRANSLATE_MODE_INHERIT"},
]

View File

@@ -0,0 +1,5 @@
extends Node
var test = {
t = AutoTranslateMode.
}

View File

@@ -0,0 +1,4 @@
[output]
include=[
{"display": "VALUE"},
]

View File

@@ -0,0 +1,9 @@
extends Node
enum TestEnum {
VALUE,
}
var test = {
= TestEnum.
}

View File

@@ -0,0 +1,4 @@
[output]
include=[
{"display": "AUTO_TRANSLATE_MODE_INHERIT"},
]

View File

@@ -0,0 +1,6 @@
extends Node
var test = {
t = 1,
e AutoTranslateMode.,
}

View File

@@ -0,0 +1,4 @@
[output]
include=[
{"display": "VALUE"},
]

View File

@@ -0,0 +1,9 @@
extends Node
enum TestEnum {
VALUE,
}
var test = {
1 = TestEnum.
}

View File

@@ -0,0 +1,4 @@
[output]
include=[
{"display": "AUTO_TRANSLATE_MODE_INHERIT"},
]

View File

@@ -0,0 +1,6 @@
extends Node
var test = {
t = 1,
1 AutoTranslateMode.,
}

View File

@@ -0,0 +1,4 @@
[output]
include=[
{"display": "AUTO_TRANSLATE_MODE_INHERIT"},
]

View File

@@ -0,0 +1,5 @@
extends Node
var test = {
AutoTranslateMode.
}

View File

@@ -0,0 +1,4 @@
[output]
include=[
{"display": "AUTO_TRANSLATE_MODE_INHERIT"},
]

View File

@@ -0,0 +1,5 @@
extends Node
var test = {
AutoTranslateMode.:
}

View File

@@ -0,0 +1,4 @@
[output]
include=[
{"display": "VALUE"},
]

View File

@@ -0,0 +1,9 @@
extends Node
enum TestEnum {
VALUE,
}
var test = {
TestEnum. "test"
}

View File

@@ -0,0 +1,4 @@
[output]
include=[
{"display": "AUTO_TRANSLATE_MODE_INHERIT"},
]

View File

@@ -0,0 +1,5 @@
extends Node
var test = {
AutoTranslateMode.: 1
}

View File

@@ -0,0 +1,4 @@
[output]
include=[
{"display": "AUTO_TRANSLATE_MODE_INHERIT"},
]

View File

@@ -0,0 +1,5 @@
extends Node
var test = {
1: AutoTranslateMode.
}

View File

@@ -0,0 +1,4 @@
[output]
include=[
{"display": "VALUE"},
]

View File

@@ -0,0 +1,9 @@
extends Node
enum TestEnum {
VALUE,
}
var test = {
: TestEnum.
}

View File

@@ -0,0 +1,4 @@
[output]
include=[
{"display": "AUTO_TRANSLATE_MODE_INHERIT"},
]

View File

@@ -0,0 +1,5 @@
extends Node
var test = {
1 AutoTranslateMode.
}