Repository: godotengine/godot (language: cpp)

Issue:
[3.x] Autocomplete fails when typing the key before the value in a dictionary
**Godot version:**
Godot 3.2.stable.official

**OS/device including version:**
Windows 10 home

**Issue description:**
When adding a key to the dictionary, the autocomplete does not work until I enter the value. The workaround is to start by entering `:any_value` then moving the cursor back and entering the key for the autocomplete to work.

**Steps to reproduce:**
Create the following script and try to add a key value pair to `data`, the enum does not autocomplete until a value is added :
```
extends Reference

var	data = {InnerClass.INNER_ENUM_VALUE_1:1}

class InnerClass:
	enum {INNER_ENUM_VALUE_1}

```
**Minimal reproduction project:**
Don't need a project, just the above script.


Discussion / hints:

Fix patch:
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 077ab6f04667..4b95f5a83613 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -3123,13 +3123,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode
 				case DictionaryNode::LUA_TABLE:
 					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 }").)");
-						advance();
-						break;
 					}
 					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 }").)");
-						advance();
-						break;
 					}
 					if (!match(GDScriptTokenizer::Token::EQUAL)) {
 						if (match(GDScriptTokenizer::Token::COLON)) {
@@ -3169,6 +3165,21 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode
 			if (key != nullptr && value != nullptr) {
 				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());
 	}
 	pop_multiline();
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 5da2bc80207f..8afc6b5f05a6 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -1453,6 +1453,18 @@ class GDScriptParser {
 
 		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 push_error(const String &p_message, const Node *p_origin = nullptr);
 #ifdef DEBUG_ENABLED


Test patch:
diff --git a/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_key_1.cfg b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_key_1.cfg
new file mode 100644
index 000000000000..8920916a8984
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_key_1.cfg
@@ -0,0 +1,4 @@
+[output]
+exclude=[
+    {"display": "AUTO_TRANSLATE_MODE_INHERIT"},
+]
diff --git a/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_key_1.gd b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_key_1.gd
new file mode 100644
index 000000000000..7795c0636d4d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_key_1.gd
@@ -0,0 +1,6 @@
+extends Node
+
+var test = {
+    t = 1,
+    AutoTranslateMode.➡
+}
diff --git a/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_key_2.cfg b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_key_2.cfg
new file mode 100644
index 000000000000..689a99b70c6d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_key_2.cfg
@@ -0,0 +1,4 @@
+[output]
+exclude=[
+    {"display": "VALUE"},
+]
diff --git a/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_key_2.gd b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_key_2.gd
new file mode 100644
index 000000000000..9ba440a10db4
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_key_2.gd
@@ -0,0 +1,10 @@
+extends Node
+
+enum TestEnum {
+    VALUE,
+}
+
+var test = {
+    t = 1,
+    TestEnum.➡ = 1,
+}
diff --git a/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_value_1.cfg b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_value_1.cfg
new file mode 100644
index 000000000000..e464da23f817
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_value_1.cfg
@@ -0,0 +1,4 @@
+[output]
+include=[
+    {"display": "AUTO_TRANSLATE_MODE_INHERIT"},
+]
diff --git a/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_value_1.gd b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_value_1.gd
new file mode 100644
index 000000000000..d5f087b3bd66
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_value_1.gd
@@ -0,0 +1,5 @@
+extends Node
+
+var test = {
+    t = AutoTranslateMode.➡
+}
diff --git a/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_value_2.cfg b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_value_2.cfg
new file mode 100644
index 000000000000..5e69ae24f97d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_value_2.cfg
@@ -0,0 +1,4 @@
+[output]
+include=[
+    {"display": "VALUE"},
+]
diff --git a/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_value_2.gd b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_value_2.gd
new file mode 100644
index 000000000000..9eb72aed30b9
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_value_2.gd
@@ -0,0 +1,9 @@
+extends Node
+
+enum TestEnum {
+    VALUE,
+}
+
+var test = {
+    = TestEnum.➡
+}
diff --git a/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_value_3.cfg b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_value_3.cfg
new file mode 100644
index 000000000000..e464da23f817
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_value_3.cfg
@@ -0,0 +1,4 @@
+[output]
+include=[
+    {"display": "AUTO_TRANSLATE_MODE_INHERIT"},
+]
diff --git a/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_value_3.gd b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_value_3.gd
new file mode 100644
index 000000000000..394aa28b6319
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_value_3.gd
@@ -0,0 +1,6 @@
+extends Node
+
+var test = {
+    t = 1,
+    e AutoTranslateMode.➡,
+}
diff --git a/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_value_4.cfg b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_value_4.cfg
new file mode 100644
index 000000000000..5e69ae24f97d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_value_4.cfg
@@ -0,0 +1,4 @@
+[output]
+include=[
+    {"display": "VALUE"},
+]
diff --git a/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_value_4.gd b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_value_4.gd
new file mode 100644
index 000000000000..17fc79b52d14
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_value_4.gd
@@ -0,0 +1,9 @@
+extends Node
+
+enum TestEnum {
+    VALUE,
+}
+
+var test = {
+    1 = TestEnum.➡
+}
diff --git a/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_value_5.cfg b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_value_5.cfg
new file mode 100644
index 000000000000..e464da23f817
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_value_5.cfg
@@ -0,0 +1,4 @@
+[output]
+include=[
+    {"display": "AUTO_TRANSLATE_MODE_INHERIT"},
+]
diff --git a/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_value_5.gd b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_value_5.gd
new file mode 100644
index 000000000000..aa6c52ae8071
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/lua_value_5.gd
@@ -0,0 +1,6 @@
+extends Node
+
+var test = {
+    t = 1,
+    1 AutoTranslateMode.➡,
+}
diff --git a/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_key_1.cfg b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_key_1.cfg
new file mode 100644
index 000000000000..e464da23f817
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_key_1.cfg
@@ -0,0 +1,4 @@
+[output]
+include=[
+    {"display": "AUTO_TRANSLATE_MODE_INHERIT"},
+]
diff --git a/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_key_1.gd b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_key_1.gd
new file mode 100644
index 000000000000..ad3009532b56
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_key_1.gd
@@ -0,0 +1,5 @@
+extends Node
+
+var test = {
+    AutoTranslateMode.➡
+}
diff --git a/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_key_2.cfg b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_key_2.cfg
new file mode 100644
index 000000000000..e464da23f817
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_key_2.cfg
@@ -0,0 +1,4 @@
+[output]
+include=[
+    {"display": "AUTO_TRANSLATE_MODE_INHERIT"},
+]
diff --git a/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_key_2.gd b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_key_2.gd
new file mode 100644
index 000000000000..d5a50261cc52
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_key_2.gd
@@ -0,0 +1,5 @@
+extends Node
+
+var test = {
+    AutoTranslateMode.➡:
+}
diff --git a/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_key_3.cfg b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_key_3.cfg
new file mode 100644
index 000000000000..5e69ae24f97d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_key_3.cfg
@@ -0,0 +1,4 @@
+[output]
+include=[
+    {"display": "VALUE"},
+]
diff --git a/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_key_3.gd b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_key_3.gd
new file mode 100644
index 000000000000..a2b6fa0106e7
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_key_3.gd
@@ -0,0 +1,9 @@
+extends Node
+
+enum TestEnum {
+    VALUE,
+}
+
+var test = {
+    TestEnum.➡ "test"
+}
diff --git a/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_key_4.cfg b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_key_4.cfg
new file mode 100644
index 000000000000..e464da23f817
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_key_4.cfg
@@ -0,0 +1,4 @@
+[output]
+include=[
+    {"display": "AUTO_TRANSLATE_MODE_INHERIT"},
+]
diff --git a/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_key_4.gd b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_key_4.gd
new file mode 100644
index 000000000000..ac7b3f8614c1
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_key_4.gd
@@ -0,0 +1,5 @@
+extends Node
+
+var test = {
+    AutoTranslateMode.➡: 1
+}
diff --git a/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_value_1.cfg b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_value_1.cfg
new file mode 100644
index 000000000000..e464da23f817
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_value_1.cfg
@@ -0,0 +1,4 @@
+[output]
+include=[
+    {"display": "AUTO_TRANSLATE_MODE_INHERIT"},
+]
diff --git a/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_value_1.gd b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_value_1.gd
new file mode 100644
index 000000000000..5ea4cab76fc1
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_value_1.gd
@@ -0,0 +1,5 @@
+extends Node
+
+var test = {
+    1: AutoTranslateMode.➡
+}
diff --git a/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_value_2.cfg b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_value_2.cfg
new file mode 100644
index 000000000000..5e69ae24f97d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_value_2.cfg
@@ -0,0 +1,4 @@
+[output]
+include=[
+    {"display": "VALUE"},
+]
diff --git a/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_value_2.gd b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_value_2.gd
new file mode 100644
index 000000000000..fd14b2152ddd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_value_2.gd
@@ -0,0 +1,9 @@
+extends Node
+
+enum TestEnum {
+    VALUE,
+}
+
+var test = {
+    : TestEnum.➡
+}
diff --git a/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_value_3.cfg b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_value_3.cfg
new file mode 100644
index 000000000000..e464da23f817
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_value_3.cfg
@@ -0,0 +1,4 @@
+[output]
+include=[
+    {"display": "AUTO_TRANSLATE_MODE_INHERIT"},
+]
diff --git a/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_value_3.gd b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_value_3.gd
new file mode 100644
index 000000000000..befd4cc0cce9
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/enum_values_in_dictionary/py_value_3.gd
@@ -0,0 +1,5 @@
+extends Node
+
+var test = {
+    1 AutoTranslateMode.➡
+}