Ver Fonte

New GDScript tokenizer and parser

Sometimes to fix something you have to break it first.

This get GDScript mostly working with the new tokenizer and parser but
a lot of things isn't working yet. It compiles and it's usable, and that
should be enough for now.

Don't worry: other huge commits will come after this.
George Marques há 5 anos atrás
pai
commit
5d6e853806
29 ficheiros alterados com 7014 adições e 15732 exclusões
  1. 67 915
      main/tests/test_gdscript.cpp
  2. 2 2
      modules/gdscript/editor/gdscript_highlighter.cpp
  3. 147 310
      modules/gdscript/gdscript.cpp
  4. 3 1
      modules/gdscript/gdscript.h
  5. 283 0
      modules/gdscript/gdscript_analyzer.cpp
  6. 52 0
      modules/gdscript/gdscript_analyzer.h
  7. 1218 828
      modules/gdscript/gdscript_compiler.cpp
  8. 12 7
      modules/gdscript/gdscript_compiler.h
  9. 104 3038
      modules/gdscript/gdscript_editor.cpp
  10. 103 91
      modules/gdscript/gdscript_function.cpp
  11. 3 3
      modules/gdscript/gdscript_function.h
  12. 2574 8415
      modules/gdscript/gdscript_parser.cpp
  13. 829 492
      modules/gdscript/gdscript_parser.h
  14. 1082 1326
      modules/gdscript/gdscript_tokenizer.cpp
  15. 187 246
      modules/gdscript/gdscript_tokenizer.h
  16. 201 0
      modules/gdscript/gdscript_warning.cpp
  17. 84 0
      modules/gdscript/gdscript_warning.h
  18. 5 0
      modules/gdscript/language_server/gdscript_extend_parser.cpp
  19. 5 0
      modules/gdscript/language_server/gdscript_extend_parser.h
  20. 5 0
      modules/gdscript/language_server/gdscript_language_protocol.cpp
  21. 5 0
      modules/gdscript/language_server/gdscript_language_protocol.h
  22. 5 0
      modules/gdscript/language_server/gdscript_language_server.cpp
  23. 5 0
      modules/gdscript/language_server/gdscript_language_server.h
  24. 5 0
      modules/gdscript/language_server/gdscript_text_document.cpp
  25. 5 0
      modules/gdscript/language_server/gdscript_text_document.h
  26. 5 0
      modules/gdscript/language_server/gdscript_workspace.cpp
  27. 5 0
      modules/gdscript/language_server/gdscript_workspace.h
  28. 5 0
      modules/gdscript/language_server/lsp.hpp
  29. 8 58
      modules/gdscript/register_types.cpp

+ 67 - 915
main/tests/test_gdscript.cpp

@@ -33,854 +33,93 @@
 #include "core/os/file_access.h"
 #include "core/os/file_access.h"
 #include "core/os/main_loop.h"
 #include "core/os/main_loop.h"
 #include "core/os/os.h"
 #include "core/os/os.h"
+#include "core/string_builder.h"
 
 
 #include "modules/modules_enabled.gen.h"
 #include "modules/modules_enabled.gen.h"
 #ifdef MODULE_GDSCRIPT_ENABLED
 #ifdef MODULE_GDSCRIPT_ENABLED
 
 
-#include "modules/gdscript/gdscript.h"
-#include "modules/gdscript/gdscript_compiler.h"
 #include "modules/gdscript/gdscript_parser.h"
 #include "modules/gdscript/gdscript_parser.h"
 #include "modules/gdscript/gdscript_tokenizer.h"
 #include "modules/gdscript/gdscript_tokenizer.h"
 
 
-namespace TestGDScript {
-
-static void _print_indent(int p_ident, const String &p_text) {
-	String txt;
-	for (int i = 0; i < p_ident; i++) {
-		txt += '\t';
-	}
-
-	print_line(txt + p_text);
-}
+#ifdef TOOLS_ENABLED
+#include "editor/editor_settings.h"
+#endif
 
 
-static String _parser_extends(const GDScriptParser::ClassNode *p_class) {
-	String txt = "extends ";
-	if (String(p_class->extends_file) != "") {
-		txt += "\"" + p_class->extends_file + "\"";
-		if (p_class->extends_class.size()) {
-			txt += ".";
-		}
-	}
+namespace TestGDScript {
 
 
-	for (int i = 0; i < p_class->extends_class.size(); i++) {
-		if (i != 0) {
-			txt += ".";
-		}
+static void test_tokenizer(const String &p_code, const Vector<String> &p_lines) {
+	GDScriptTokenizer tokenizer;
+	tokenizer.set_source_code(p_code);
 
 
-		txt += p_class->extends_class[i];
+	int tab_size = 4;
+#ifdef TOOLS_ENABLED
+	if (EditorSettings::get_singleton()) {
+		tab_size = EditorSettings::get_singleton()->get_setting("text_editor/indent/size");
 	}
 	}
+#endif // TOOLS_ENABLED
+	String tab = String(" ").repeat(tab_size);
 
 
-	return txt;
-}
+	GDScriptTokenizer::Token current = tokenizer.scan();
+	while (current.type != GDScriptTokenizer::Token::TK_EOF) {
+		StringBuilder token;
+		token += " --> "; // Padding for line number.
 
 
-static String _parser_expr(const GDScriptParser::Node *p_expr) {
-	String txt;
-	switch (p_expr->type) {
-		case GDScriptParser::Node::TYPE_IDENTIFIER: {
-			const GDScriptParser::IdentifierNode *id_node = static_cast<const GDScriptParser::IdentifierNode *>(p_expr);
-			txt = id_node->name;
-		} break;
-		case GDScriptParser::Node::TYPE_CONSTANT: {
-			const GDScriptParser::ConstantNode *c_node = static_cast<const GDScriptParser::ConstantNode *>(p_expr);
-			if (c_node->value.get_type() == Variant::STRING) {
-				txt = "\"" + String(c_node->value) + "\"";
-			} else {
-				txt = c_node->value;
-			}
-
-		} break;
-		case GDScriptParser::Node::TYPE_SELF: {
-			txt = "self";
-		} break;
-		case GDScriptParser::Node::TYPE_ARRAY: {
-			const GDScriptParser::ArrayNode *arr_node = static_cast<const GDScriptParser::ArrayNode *>(p_expr);
-			txt += "[";
-			for (int i = 0; i < arr_node->elements.size(); i++) {
-				if (i > 0) {
-					txt += ", ";
-				}
-				txt += _parser_expr(arr_node->elements[i]);
-			}
-			txt += "]";
-		} break;
-		case GDScriptParser::Node::TYPE_DICTIONARY: {
-			const GDScriptParser::DictionaryNode *dict_node = static_cast<const GDScriptParser::DictionaryNode *>(p_expr);
-			txt += "{";
-			for (int i = 0; i < dict_node->elements.size(); i++) {
-				if (i > 0) {
-					txt += ", ";
-				}
+		for (int l = current.start_line; l <= current.end_line; l++) {
+			print_line(vformat("%04d %s", l, p_lines[l - 1]).replace("\t", tab));
+		}
 
 
-				const GDScriptParser::DictionaryNode::Pair &p = dict_node->elements[i];
-				txt += _parser_expr(p.key);
-				txt += ":";
-				txt += _parser_expr(p.value);
+		{
+			// Print carets to point at the token.
+			StringBuilder pointer;
+			pointer += "     "; // Padding for line number.
+			int rightmost_column = current.rightmost_column;
+			if (current.end_line > current.start_line) {
+				rightmost_column--; // Don't point to the newline as a column.
 			}
 			}
-			txt += "}";
-		} break;
-		case GDScriptParser::Node::TYPE_OPERATOR: {
-			const GDScriptParser::OperatorNode *c_node = static_cast<const GDScriptParser::OperatorNode *>(p_expr);
-			switch (c_node->op) {
-				case GDScriptParser::OperatorNode::OP_PARENT_CALL:
-					txt += ".";
-					[[fallthrough]];
-				case GDScriptParser::OperatorNode::OP_CALL: {
-					ERR_FAIL_COND_V(c_node->arguments.size() < 1, "");
-					String func_name;
-					const GDScriptParser::Node *nfunc = c_node->arguments[0];
-					int arg_ofs = 0;
-					if (nfunc->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) {
-						const GDScriptParser::BuiltInFunctionNode *bif_node = static_cast<const GDScriptParser::BuiltInFunctionNode *>(nfunc);
-						func_name = GDScriptFunctions::get_func_name(bif_node->function);
-						arg_ofs = 1;
-					} else if (nfunc->type == GDScriptParser::Node::TYPE_TYPE) {
-						const GDScriptParser::TypeNode *t_node = static_cast<const GDScriptParser::TypeNode *>(nfunc);
-						func_name = Variant::get_type_name(t_node->vtype);
-						arg_ofs = 1;
-					} else {
-						ERR_FAIL_COND_V(c_node->arguments.size() < 2, "");
-						nfunc = c_node->arguments[1];
-						ERR_FAIL_COND_V(nfunc->type != GDScriptParser::Node::TYPE_IDENTIFIER, "");
-
-						if (c_node->arguments[0]->type != GDScriptParser::Node::TYPE_SELF) {
-							func_name = _parser_expr(c_node->arguments[0]) + ".";
-						}
-
-						func_name += _parser_expr(nfunc);
-						arg_ofs = 2;
-					}
-
-					txt += func_name + "(";
-
-					for (int i = arg_ofs; i < c_node->arguments.size(); i++) {
-						const GDScriptParser::Node *arg = c_node->arguments[i];
-						if (i > arg_ofs) {
-							txt += ", ";
-						}
-						txt += _parser_expr(arg);
-					}
-
-					txt += ")";
-
-				} break;
-				case GDScriptParser::OperatorNode::OP_INDEX: {
-					ERR_FAIL_COND_V(c_node->arguments.size() != 2, "");
-
-					//index with []
-					txt = _parser_expr(c_node->arguments[0]) + "[" + _parser_expr(c_node->arguments[1]) + "]";
-
-				} break;
-				case GDScriptParser::OperatorNode::OP_INDEX_NAMED: {
-					ERR_FAIL_COND_V(c_node->arguments.size() != 2, "");
-
-					txt = _parser_expr(c_node->arguments[0]) + "." + _parser_expr(c_node->arguments[1]);
-
-				} break;
-				case GDScriptParser::OperatorNode::OP_NEG: {
-					txt = "-" + _parser_expr(c_node->arguments[0]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_NOT: {
-					txt = "not " + _parser_expr(c_node->arguments[0]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_BIT_INVERT: {
-					txt = "~" + _parser_expr(c_node->arguments[0]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_IN: {
-					txt = _parser_expr(c_node->arguments[0]) + " in " + _parser_expr(c_node->arguments[1]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_EQUAL: {
-					txt = _parser_expr(c_node->arguments[0]) + "==" + _parser_expr(c_node->arguments[1]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_NOT_EQUAL: {
-					txt = _parser_expr(c_node->arguments[0]) + "!=" + _parser_expr(c_node->arguments[1]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_LESS: {
-					txt = _parser_expr(c_node->arguments[0]) + "<" + _parser_expr(c_node->arguments[1]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_LESS_EQUAL: {
-					txt = _parser_expr(c_node->arguments[0]) + "<=" + _parser_expr(c_node->arguments[1]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_GREATER: {
-					txt = _parser_expr(c_node->arguments[0]) + ">" + _parser_expr(c_node->arguments[1]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_GREATER_EQUAL: {
-					txt = _parser_expr(c_node->arguments[0]) + ">=" + _parser_expr(c_node->arguments[1]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_AND: {
-					txt = _parser_expr(c_node->arguments[0]) + " and " + _parser_expr(c_node->arguments[1]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_OR: {
-					txt = _parser_expr(c_node->arguments[0]) + " or " + _parser_expr(c_node->arguments[1]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_ADD: {
-					txt = _parser_expr(c_node->arguments[0]) + "+" + _parser_expr(c_node->arguments[1]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_SUB: {
-					txt = _parser_expr(c_node->arguments[0]) + "-" + _parser_expr(c_node->arguments[1]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_MUL: {
-					txt = _parser_expr(c_node->arguments[0]) + "*" + _parser_expr(c_node->arguments[1]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_DIV: {
-					txt = _parser_expr(c_node->arguments[0]) + "/" + _parser_expr(c_node->arguments[1]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_MOD: {
-					txt = _parser_expr(c_node->arguments[0]) + "%" + _parser_expr(c_node->arguments[1]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_SHIFT_LEFT: {
-					txt = _parser_expr(c_node->arguments[0]) + "<<" + _parser_expr(c_node->arguments[1]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_SHIFT_RIGHT: {
-					txt = _parser_expr(c_node->arguments[0]) + ">>" + _parser_expr(c_node->arguments[1]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_ASSIGN: {
-					txt = _parser_expr(c_node->arguments[0]) + "=" + _parser_expr(c_node->arguments[1]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_ASSIGN_ADD: {
-					txt = _parser_expr(c_node->arguments[0]) + "+=" + _parser_expr(c_node->arguments[1]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_ASSIGN_SUB: {
-					txt = _parser_expr(c_node->arguments[0]) + "-=" + _parser_expr(c_node->arguments[1]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_ASSIGN_MUL: {
-					txt = _parser_expr(c_node->arguments[0]) + "*=" + _parser_expr(c_node->arguments[1]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_ASSIGN_DIV: {
-					txt = _parser_expr(c_node->arguments[0]) + "/=" + _parser_expr(c_node->arguments[1]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_ASSIGN_MOD: {
-					txt = _parser_expr(c_node->arguments[0]) + "%=" + _parser_expr(c_node->arguments[1]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_ASSIGN_SHIFT_LEFT: {
-					txt = _parser_expr(c_node->arguments[0]) + "<<=" + _parser_expr(c_node->arguments[1]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_ASSIGN_SHIFT_RIGHT: {
-					txt = _parser_expr(c_node->arguments[0]) + ">>=" + _parser_expr(c_node->arguments[1]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_ASSIGN_BIT_AND: {
-					txt = _parser_expr(c_node->arguments[0]) + "&=" + _parser_expr(c_node->arguments[1]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_ASSIGN_BIT_OR: {
-					txt = _parser_expr(c_node->arguments[0]) + "|=" + _parser_expr(c_node->arguments[1]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_ASSIGN_BIT_XOR: {
-					txt = _parser_expr(c_node->arguments[0]) + "^=" + _parser_expr(c_node->arguments[1]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_BIT_AND: {
-					txt = _parser_expr(c_node->arguments[0]) + "&" + _parser_expr(c_node->arguments[1]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_BIT_OR: {
-					txt = _parser_expr(c_node->arguments[0]) + "|" + _parser_expr(c_node->arguments[1]);
-				} break;
-				case GDScriptParser::OperatorNode::OP_BIT_XOR: {
-					txt = _parser_expr(c_node->arguments[0]) + "^" + _parser_expr(c_node->arguments[1]);
-				} break;
-				default: {
+			for (int col = 1; col < rightmost_column; col++) {
+				if (col < current.leftmost_column) {
+					pointer += " ";
+				} else {
+					pointer += "^";
 				}
 				}
 			}
 			}
-
-		} break;
-		case GDScriptParser::Node::TYPE_CAST: {
-			const GDScriptParser::CastNode *cast_node = static_cast<const GDScriptParser::CastNode *>(p_expr);
-			txt = _parser_expr(cast_node->source_node) + " as " + cast_node->cast_type.to_string();
-
-		} break;
-		case GDScriptParser::Node::TYPE_NEWLINE: {
-			//skippie
-		} break;
-		default: {
-			ERR_FAIL_V_MSG("", "Parser bug at " + itos(p_expr->line) + ", invalid expression type: " + itos(p_expr->type));
+			print_line(pointer.as_string());
 		}
 		}
-	}
 
 
-	return txt;
-}
+		token += current.get_name();
 
 
-static void _parser_show_block(const GDScriptParser::BlockNode *p_block, int p_indent) {
-	for (int i = 0; i < p_block->statements.size(); i++) {
-		const GDScriptParser::Node *statement = p_block->statements[i];
-
-		switch (statement->type) {
-			case GDScriptParser::Node::TYPE_CONTROL_FLOW: {
-				const GDScriptParser::ControlFlowNode *cf_node = static_cast<const GDScriptParser::ControlFlowNode *>(statement);
-				switch (cf_node->cf_type) {
-					case GDScriptParser::ControlFlowNode::CF_IF: {
-						ERR_FAIL_COND(cf_node->arguments.size() != 1);
-						String txt;
-						txt += "if ";
-						txt += _parser_expr(cf_node->arguments[0]);
-						txt += ":";
-						_print_indent(p_indent, txt);
-						ERR_FAIL_COND(!cf_node->body);
-						_parser_show_block(cf_node->body, p_indent + 1);
-						if (cf_node->body_else) {
-							_print_indent(p_indent, "else:");
-							_parser_show_block(cf_node->body_else, p_indent + 1);
-						}
-
-					} break;
-					case GDScriptParser::ControlFlowNode::CF_FOR: {
-						ERR_FAIL_COND(cf_node->arguments.size() != 2);
-						String txt;
-						txt += "for ";
-						txt += _parser_expr(cf_node->arguments[0]);
-						txt += " in ";
-						txt += _parser_expr(cf_node->arguments[1]);
-						txt += ":";
-						_print_indent(p_indent, txt);
-						ERR_FAIL_COND(!cf_node->body);
-						_parser_show_block(cf_node->body, p_indent + 1);
-
-					} break;
-					case GDScriptParser::ControlFlowNode::CF_WHILE: {
-						ERR_FAIL_COND(cf_node->arguments.size() != 1);
-						String txt;
-						txt += "while ";
-						txt += _parser_expr(cf_node->arguments[0]);
-						txt += ":";
-						_print_indent(p_indent, txt);
-						ERR_FAIL_COND(!cf_node->body);
-						_parser_show_block(cf_node->body, p_indent + 1);
-
-					} break;
-					case GDScriptParser::ControlFlowNode::CF_MATCH: {
-						// FIXME: Implement
-					} break;
-					case GDScriptParser::ControlFlowNode::CF_CONTINUE: {
-						_print_indent(p_indent, "continue");
-					} break;
-					case GDScriptParser::ControlFlowNode::CF_BREAK: {
-						_print_indent(p_indent, "break");
-					} break;
-					case GDScriptParser::ControlFlowNode::CF_RETURN: {
-						if (cf_node->arguments.size()) {
-							_print_indent(p_indent, "return " + _parser_expr(cf_node->arguments[0]));
-						} else {
-							_print_indent(p_indent, "return ");
-						}
-					} break;
-				}
-
-			} break;
-			case GDScriptParser::Node::TYPE_LOCAL_VAR: {
-				const GDScriptParser::LocalVarNode *lv_node = static_cast<const GDScriptParser::LocalVarNode *>(statement);
-				_print_indent(p_indent, "var " + String(lv_node->name));
-			} break;
-			default: {
-				//expression i guess
-				_print_indent(p_indent, _parser_expr(statement));
-			}
+		if (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::LITERAL || current.type == GDScriptTokenizer::Token::IDENTIFIER || current.type == GDScriptTokenizer::Token::ANNOTATION) {
+			token += "(";
+			token += Variant::get_type_name(current.literal.get_type());
+			token += ") ";
+			token += current.literal;
 		}
 		}
-	}
-}
 
 
-static void _parser_show_function(const GDScriptParser::FunctionNode *p_func, int p_indent, GDScriptParser::BlockNode *p_initializer = nullptr) {
-	String txt;
-	if (p_func->_static) {
-		txt = "static ";
-	}
-	txt += "func ";
-	if (p_func->name == "") { // initializer
-		txt += "[built-in-initializer]";
-	} else {
-		txt += String(p_func->name);
-	}
-	txt += "(";
-
-	for (int i = 0; i < p_func->arguments.size(); i++) {
-		if (i != 0) {
-			txt += ", ";
-		}
-		txt += "var " + String(p_func->arguments[i]);
-		if (i >= (p_func->arguments.size() - p_func->default_values.size())) {
-			int defarg = i - (p_func->arguments.size() - p_func->default_values.size());
-			txt += "=";
-			txt += _parser_expr(p_func->default_values[defarg]);
-		}
-	}
-
-	txt += ")";
-
-	//todo constructor check!
-
-	txt += ":";
-
-	_print_indent(p_indent, txt);
-	if (p_initializer) {
-		_parser_show_block(p_initializer, p_indent + 1);
-	}
-	_parser_show_block(p_func->body, p_indent + 1);
-}
-
-static void _parser_show_class(const GDScriptParser::ClassNode *p_class, int p_indent, const Vector<String> &p_code) {
-	if (p_indent == 0 && (String(p_class->extends_file) != "" || p_class->extends_class.size())) {
-		_print_indent(p_indent, _parser_extends(p_class));
-		print_line("\n");
-	}
-
-	for (int i = 0; i < p_class->subclasses.size(); i++) {
-		const GDScriptParser::ClassNode *subclass = p_class->subclasses[i];
-		String line = "class " + subclass->name;
-		if (String(subclass->extends_file) != "" || subclass->extends_class.size()) {
-			line += " " + _parser_extends(subclass);
-		}
-		line += ":";
-		_print_indent(p_indent, line);
-		_parser_show_class(subclass, p_indent + 1, p_code);
-		print_line("\n");
-	}
-
-	for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) {
-		const GDScriptParser::ClassNode::Constant &constant = E->get();
-		_print_indent(p_indent, "const " + String(E->key()) + "=" + _parser_expr(constant.expression));
-	}
-
-	for (int i = 0; i < p_class->variables.size(); i++) {
-		const GDScriptParser::ClassNode::Member &m = p_class->variables[i];
-
-		_print_indent(p_indent, "var " + String(m.identifier));
-	}
+		print_line(token.as_string());
 
 
-	print_line("\n");
+		print_line("-------------------------------------------------------");
 
 
-	for (int i = 0; i < p_class->static_functions.size(); i++) {
-		_parser_show_function(p_class->static_functions[i], p_indent);
-		print_line("\n");
+		current = tokenizer.scan();
 	}
 	}
 
 
-	for (int i = 0; i < p_class->functions.size(); i++) {
-		if (String(p_class->functions[i]->name) == "_init") {
-			_parser_show_function(p_class->functions[i], p_indent, p_class->initializer);
-		} else {
-			_parser_show_function(p_class->functions[i], p_indent);
-		}
-		print_line("\n");
-	}
-	//_parser_show_function(p_class->initializer,p_indent);
-	print_line("\n");
-}
-
-static String _disassemble_addr(const Ref<GDScript> &p_script, const GDScriptFunction &func, int p_addr) {
-	int addr = p_addr & GDScriptFunction::ADDR_MASK;
-
-	switch (p_addr >> GDScriptFunction::ADDR_BITS) {
-		case GDScriptFunction::ADDR_TYPE_SELF: {
-			return "self";
-		} break;
-		case GDScriptFunction::ADDR_TYPE_CLASS: {
-			return "class";
-		} break;
-		case GDScriptFunction::ADDR_TYPE_MEMBER: {
-			return "member(" + p_script->debug_get_member_by_index(addr) + ")";
-		} break;
-		case GDScriptFunction::ADDR_TYPE_CLASS_CONSTANT: {
-			return "class_const(" + func.get_global_name(addr) + ")";
-		} break;
-		case GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT: {
-			Variant v = func.get_constant(addr);
-			String txt;
-			if (v.get_type() == Variant::STRING || v.get_type() == Variant::NODE_PATH) {
-				txt = "\"" + String(v) + "\"";
-			} else {
-				txt = v;
-			}
-			return "const(" + txt + ")";
-		} break;
-		case GDScriptFunction::ADDR_TYPE_STACK: {
-			return "stack(" + itos(addr) + ")";
-		} break;
-		case GDScriptFunction::ADDR_TYPE_STACK_VARIABLE: {
-			return "var_stack(" + itos(addr) + ")";
-		} break;
-		case GDScriptFunction::ADDR_TYPE_GLOBAL: {
-			return "global(" + func.get_global_name(addr) + ")";
-		} break;
-		case GDScriptFunction::ADDR_TYPE_NIL: {
-			return "nil";
-		} break;
-	}
-
-	return "<err>";
+	print_line(current.get_name()); // Should be EOF
 }
 }
 
 
-static void _disassemble_class(const Ref<GDScript> &p_class, const Vector<String> &p_code) {
-	const Map<StringName, GDScriptFunction *> &mf = p_class->debug_get_member_functions();
+static void test_parser(const String &p_code, const String &p_script_path, const Vector<String> &p_lines) {
+	GDScriptParser parser;
+	Error err = parser.parse(p_code, p_script_path, false);
 
 
-	for (const Map<StringName, GDScriptFunction *>::Element *E = mf.front(); E; E = E->next()) {
-		const GDScriptFunction &func = *E->get();
-		const int *code = func.get_code();
-		int codelen = func.get_code_size();
-		String defargs;
-		if (func.get_default_argument_count()) {
-			defargs = "defarg at: ";
-			for (int i = 0; i < func.get_default_argument_count(); i++) {
-				if (i > 0) {
-					defargs += ",";
-				}
-				defargs += itos(func.get_default_argument_addr(i));
-			}
-			defargs += " ";
+	if (err != OK) {
+		const List<GDScriptParser::ParserError> &errors = parser.get_errors();
+		for (const List<GDScriptParser::ParserError>::Element *E = errors.front(); E != nullptr; E = E->next()) {
+			const GDScriptParser::ParserError &error = E->get();
+			print_line(vformat("%02d:%02d: %s", error.line, error.column, error.message));
 		}
 		}
-		print_line("== function " + String(func.get_name()) + "() :: stack size: " + itos(func.get_max_stack_size()) + " " + defargs + "==");
-
-#define DADDR(m_ip) (_disassemble_addr(p_class, func, code[ip + m_ip]))
-
-		for (int ip = 0; ip < codelen;) {
-			int incr = 0;
-			String txt = itos(ip) + " ";
-
-			switch (code[ip]) {
-				case GDScriptFunction::OPCODE_OPERATOR: {
-					int op = code[ip + 1];
-					txt += " op ";
-
-					String opname = Variant::get_operator_name(Variant::Operator(op));
-
-					txt += DADDR(4);
-					txt += " = ";
-					txt += DADDR(2);
-					txt += " " + opname + " ";
-					txt += DADDR(3);
-					incr += 5;
-
-				} break;
-				case GDScriptFunction::OPCODE_SET: {
-					txt += "set ";
-					txt += DADDR(1);
-					txt += "[";
-					txt += DADDR(2);
-					txt += "]=";
-					txt += DADDR(3);
-					incr += 4;
-
-				} break;
-				case GDScriptFunction::OPCODE_GET: {
-					txt += " get ";
-					txt += DADDR(3);
-					txt += "=";
-					txt += DADDR(1);
-					txt += "[";
-					txt += DADDR(2);
-					txt += "]";
-					incr += 4;
-
-				} break;
-				case GDScriptFunction::OPCODE_SET_NAMED: {
-					txt += " set_named ";
-					txt += DADDR(1);
-					txt += "[\"";
-					txt += func.get_global_name(code[ip + 2]);
-					txt += "\"]=";
-					txt += DADDR(3);
-					incr += 4;
-
-				} break;
-				case GDScriptFunction::OPCODE_GET_NAMED: {
-					txt += " get_named ";
-					txt += DADDR(3);
-					txt += "=";
-					txt += DADDR(1);
-					txt += "[\"";
-					txt += func.get_global_name(code[ip + 2]);
-					txt += "\"]";
-					incr += 4;
-
-				} break;
-				case GDScriptFunction::OPCODE_SET_MEMBER: {
-					txt += " set_member ";
-					txt += "[\"";
-					txt += func.get_global_name(code[ip + 1]);
-					txt += "\"]=";
-					txt += DADDR(2);
-					incr += 3;
-
-				} break;
-				case GDScriptFunction::OPCODE_GET_MEMBER: {
-					txt += " get_member ";
-					txt += DADDR(2);
-					txt += "=";
-					txt += "[\"";
-					txt += func.get_global_name(code[ip + 1]);
-					txt += "\"]";
-					incr += 3;
-
-				} break;
-				case GDScriptFunction::OPCODE_ASSIGN: {
-					txt += " assign ";
-					txt += DADDR(1);
-					txt += "=";
-					txt += DADDR(2);
-					incr += 3;
-
-				} break;
-				case GDScriptFunction::OPCODE_ASSIGN_TRUE: {
-					txt += " assign ";
-					txt += DADDR(1);
-					txt += "= true";
-					incr += 2;
-
-				} break;
-				case GDScriptFunction::OPCODE_ASSIGN_FALSE: {
-					txt += " assign ";
-					txt += DADDR(1);
-					txt += "= false";
-					incr += 2;
-
-				} break;
-				case GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN: {
-					txt += " assign typed builtin (";
-					txt += Variant::get_type_name((Variant::Type)code[ip + 1]);
-					txt += ") ";
-					txt += DADDR(2);
-					txt += " = ";
-					txt += DADDR(3);
-					incr += 4;
-
-				} break;
-				case GDScriptFunction::OPCODE_ASSIGN_TYPED_NATIVE: {
-					Variant className = func.get_constant(code[ip + 1]);
-					GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(className.operator Object *());
-
-					txt += " assign typed native (";
-					txt += nc->get_name().operator String();
-					txt += ") ";
-					txt += DADDR(2);
-					txt += " = ";
-					txt += DADDR(3);
-					incr += 4;
-
-				} break;
-				case GDScriptFunction::OPCODE_CAST_TO_SCRIPT: {
-					txt += " cast ";
-					txt += DADDR(3);
-					txt += "=";
-					txt += DADDR(1);
-					txt += " as ";
-					txt += DADDR(2);
-					incr += 4;
-
-				} break;
-				case GDScriptFunction::OPCODE_CONSTRUCT: {
-					Variant::Type t = Variant::Type(code[ip + 1]);
-					int argc = code[ip + 2];
-
-					txt += " construct ";
-					txt += DADDR(3 + argc);
-					txt += " = ";
-
-					txt += Variant::get_type_name(t) + "(";
-					for (int i = 0; i < argc; i++) {
-						if (i > 0) {
-							txt += ", ";
-						}
-						txt += DADDR(i + 3);
-					}
-					txt += ")";
-
-					incr = 4 + argc;
-
-				} break;
-				case GDScriptFunction::OPCODE_CONSTRUCT_ARRAY: {
-					int argc = code[ip + 1];
-					txt += " make_array ";
-					txt += DADDR(2 + argc);
-					txt += " = [ ";
-
-					for (int i = 0; i < argc; i++) {
-						if (i > 0) {
-							txt += ", ";
-						}
-						txt += DADDR(2 + i);
-					}
-
-					txt += "]";
-
-					incr += 3 + argc;
-
-				} break;
-				case GDScriptFunction::OPCODE_CONSTRUCT_DICTIONARY: {
-					int argc = code[ip + 1];
-					txt += " make_dict ";
-					txt += DADDR(2 + argc * 2);
-					txt += " = { ";
-
-					for (int i = 0; i < argc; i++) {
-						if (i > 0) {
-							txt += ", ";
-						}
-						txt += DADDR(2 + i * 2 + 0);
-						txt += ":";
-						txt += DADDR(2 + i * 2 + 1);
-					}
-
-					txt += "}";
-
-					incr += 3 + argc * 2;
-
-				} break;
-
-				case GDScriptFunction::OPCODE_CALL:
-				case GDScriptFunction::OPCODE_CALL_RETURN: {
-					bool ret = code[ip] == GDScriptFunction::OPCODE_CALL_RETURN;
-
-					if (ret) {
-						txt += " call-ret ";
-					} else {
-						txt += " call ";
-					}
-
-					int argc = code[ip + 1];
-					if (ret) {
-						txt += DADDR(4 + argc) + "=";
-					}
-
-					txt += DADDR(2) + ".";
-					txt += String(func.get_global_name(code[ip + 3]));
-					txt += "(";
-
-					for (int i = 0; i < argc; i++) {
-						if (i > 0) {
-							txt += ", ";
-						}
-						txt += DADDR(4 + i);
-					}
-					txt += ")";
-
-					incr = 5 + argc;
-
-				} break;
-				case GDScriptFunction::OPCODE_CALL_BUILT_IN: {
-					txt += " call-built-in ";
-
-					int argc = code[ip + 2];
-					txt += DADDR(3 + argc) + "=";
-
-					txt += GDScriptFunctions::get_func_name(GDScriptFunctions::Function(code[ip + 1]));
-					txt += "(";
-
-					for (int i = 0; i < argc; i++) {
-						if (i > 0) {
-							txt += ", ";
-						}
-						txt += DADDR(3 + i);
-					}
-					txt += ")";
-
-					incr = 4 + argc;
-
-				} break;
-				case GDScriptFunction::OPCODE_CALL_SELF_BASE: {
-					txt += " call-self-base ";
-
-					int argc = code[ip + 2];
-					txt += DADDR(3 + argc) + "=";
-
-					txt += func.get_global_name(code[ip + 1]);
-					txt += "(";
-
-					for (int i = 0; i < argc; i++) {
-						if (i > 0) {
-							txt += ", ";
-						}
-						txt += DADDR(3 + i);
-					}
-					txt += ")";
-
-					incr = 4 + argc;
-
-				} break;
-				case GDScriptFunction::OPCODE_YIELD: {
-					txt += " yield ";
-					incr = 1;
-
-				} break;
-				case GDScriptFunction::OPCODE_YIELD_SIGNAL: {
-					txt += " yield_signal ";
-					txt += DADDR(1);
-					txt += ",";
-					txt += DADDR(2);
-					incr = 3;
-				} break;
-				case GDScriptFunction::OPCODE_YIELD_RESUME: {
-					txt += " yield resume: ";
-					txt += DADDR(1);
-					incr = 2;
-				} break;
-				case GDScriptFunction::OPCODE_JUMP: {
-					txt += " jump ";
-					txt += itos(code[ip + 1]);
-
-					incr = 2;
-
-				} break;
-				case GDScriptFunction::OPCODE_JUMP_IF: {
-					txt += " jump-if ";
-					txt += DADDR(1);
-					txt += " to ";
-					txt += itos(code[ip + 2]);
-
-					incr = 3;
-				} break;
-				case GDScriptFunction::OPCODE_JUMP_IF_NOT: {
-					txt += " jump-if-not ";
-					txt += DADDR(1);
-					txt += " to ";
-					txt += itos(code[ip + 2]);
-
-					incr = 3;
-				} break;
-				case GDScriptFunction::OPCODE_JUMP_TO_DEF_ARGUMENT: {
-					txt += " jump-to-default-argument ";
-					incr = 1;
-				} break;
-				case GDScriptFunction::OPCODE_RETURN: {
-					txt += " return ";
-					txt += DADDR(1);
-
-					incr = 2;
-
-				} break;
-				case GDScriptFunction::OPCODE_ITERATE_BEGIN: {
-					txt += " for-init " + DADDR(4) + " in " + DADDR(2) + " counter " + DADDR(1) + " end " + itos(code[ip + 3]);
-					incr += 5;
-
-				} break;
-				case GDScriptFunction::OPCODE_ITERATE: {
-					txt += " for-loop " + DADDR(4) + " in " + DADDR(2) + " counter " + DADDR(1) + " end " + itos(code[ip + 3]);
-					incr += 5;
-
-				} break;
-				case GDScriptFunction::OPCODE_LINE: {
-					int line = code[ip + 1] - 1;
-					if (line >= 0 && line < p_code.size()) {
-						txt = "\n" + itos(line + 1) + ": " + p_code[line] + "\n";
-					} else {
-						txt = "";
-					}
-					incr += 2;
-				} break;
-				case GDScriptFunction::OPCODE_END: {
-					txt += " end";
-					incr += 1;
-				} break;
-				case GDScriptFunction::OPCODE_ASSERT: {
-					txt += " assert ";
-					txt += DADDR(1);
-					incr += 2;
+	}
 
 
-				} break;
-			}
+	GDScriptParser::TreePrinter printer;
 
 
-			if (incr == 0) {
-				ERR_BREAK_MSG(true, "Unhandled opcode: " + itos(code[ip]));
-			}
-
-			ip += incr;
-			if (txt != "") {
-				print_line(txt);
-			}
-		}
-	}
+	printer.print_tree(parser);
 }
 }
 
 
 MainLoop *test(TestType p_type) {
 MainLoop *test(TestType p_type) {
@@ -891,12 +130,12 @@ MainLoop *test(TestType p_type) {
 	}
 	}
 
 
 	String test = cmdlargs.back()->get();
 	String test = cmdlargs.back()->get();
-	if (!test.ends_with(".gd") && !test.ends_with(".gdc")) {
+	if (!test.ends_with(".gd")) {
 		print_line("This test expects a path to a GDScript file as its last parameter. Got: " + test);
 		print_line("This test expects a path to a GDScript file as its last parameter. Got: " + test);
 		return nullptr;
 		return nullptr;
 	}
 	}
 
 
-	FileAccess *fa = FileAccess::open(test, FileAccess::READ);
+	FileAccessRef fa = FileAccess::open(test, FileAccess::READ);
 	ERR_FAIL_COND_V_MSG(!fa, nullptr, "Could not open file: " + test);
 	ERR_FAIL_COND_V_MSG(!fa, nullptr, "Could not open file: " + test);
 
 
 	Vector<uint8_t> buf;
 	Vector<uint8_t> buf;
@@ -910,7 +149,6 @@ MainLoop *test(TestType p_type) {
 
 
 	Vector<String> lines;
 	Vector<String> lines;
 	int last = 0;
 	int last = 0;
-
 	for (int i = 0; i <= code.length(); i++) {
 	for (int i = 0; i <= code.length(); i++) {
 		if (code[i] == '\n' || code[i] == 0) {
 		if (code[i] == '\n' || code[i] == 0) {
 			lines.push_back(code.substr(last, i - last));
 			lines.push_back(code.substr(last, i - last));
@@ -918,104 +156,18 @@ MainLoop *test(TestType p_type) {
 		}
 		}
 	}
 	}
 
 
-	if (p_type == TEST_TOKENIZER) {
-		GDScriptTokenizerText tk;
-		tk.set_code(code);
-		int line = -1;
-		while (tk.get_token() != GDScriptTokenizer::TK_EOF) {
-			String text;
-			if (tk.get_token() == GDScriptTokenizer::TK_IDENTIFIER) {
-				text = "'" + tk.get_token_identifier() + "' (identifier)";
-			} else if (tk.get_token() == GDScriptTokenizer::TK_CONSTANT) {
-				const Variant &c = tk.get_token_constant();
-				if (c.get_type() == Variant::STRING) {
-					text = "\"" + String(c) + "\"";
-				} else {
-					text = c;
-				}
-
-				text = text + " (" + Variant::get_type_name(c.get_type()) + " constant)";
-			} else if (tk.get_token() == GDScriptTokenizer::TK_ERROR) {
-				text = "ERROR: " + tk.get_token_error();
-			} else if (tk.get_token() == GDScriptTokenizer::TK_NEWLINE) {
-				text = "newline (" + itos(tk.get_token_line()) + ") + indent: " + itos(tk.get_token_line_indent());
-			} else if (tk.get_token() == GDScriptTokenizer::TK_BUILT_IN_FUNC) {
-				text = "'" + String(GDScriptFunctions::get_func_name(tk.get_token_built_in_func())) + "' (built-in function)";
-			} else {
-				text = tk.get_token_name(tk.get_token());
-			}
-
-			if (tk.get_token_line() != line) {
-				int from = line + 1;
-				line = tk.get_token_line();
-
-				for (int i = from; i <= line; i++) {
-					int l = i - 1;
-					if (l >= 0 && l < lines.size()) {
-						print_line("\n" + itos(i) + ": " + lines[l] + "\n");
-					}
-				}
-			}
-			print_line("\t(" + itos(tk.get_token_column()) + "): " + text);
-			tk.advance();
-		}
-	}
-
-	if (p_type == TEST_PARSER) {
-		GDScriptParser parser;
-		Error err = parser.parse(code);
-		if (err) {
-			print_line("Parse Error:\n" + itos(parser.get_error_line()) + ":" + itos(parser.get_error_column()) + ":" + parser.get_error());
-			memdelete(fa);
-			return nullptr;
-		}
-
-		const GDScriptParser::Node *root = parser.get_parse_tree();
-		ERR_FAIL_COND_V(root->type != GDScriptParser::Node::TYPE_CLASS, nullptr);
-		const GDScriptParser::ClassNode *cnode = static_cast<const GDScriptParser::ClassNode *>(root);
-
-		_parser_show_class(cnode, 0, lines);
-	}
-
-	if (p_type == TEST_COMPILER) {
-		GDScriptParser parser;
-
-		Error err = parser.parse(code);
-		if (err) {
-			print_line("Parse Error:\n" + itos(parser.get_error_line()) + ":" + itos(parser.get_error_column()) + ":" + parser.get_error());
-			memdelete(fa);
-			return nullptr;
-		}
-
-		Ref<GDScript> gds;
-		gds.instance();
-
-		GDScriptCompiler gdc;
-		err = gdc.compile(&parser, gds.ptr());
-		if (err) {
-			print_line("Compile Error:\n" + itos(gdc.get_error_line()) + ":" + itos(gdc.get_error_column()) + ":" + gdc.get_error());
-			return nullptr;
-		}
-
-		Ref<GDScript> current = gds;
-
-		while (current.is_valid()) {
-			print_line("** CLASS **");
-			_disassemble_class(current, lines);
-
-			current = current->get_base();
-		}
-
-	} else if (p_type == TEST_BYTECODE) {
-		Vector<uint8_t> buf2 = GDScriptTokenizerBuffer::parse_code_string(code);
-		String dst = test.get_basename() + ".gdc";
-		FileAccess *fw = FileAccess::open(dst, FileAccess::WRITE);
-		fw->store_buffer(buf2.ptr(), buf2.size());
-		memdelete(fw);
+	switch (p_type) {
+		case TEST_TOKENIZER:
+			test_tokenizer(code, lines);
+			break;
+		case TEST_PARSER:
+			test_parser(code, test, lines);
+			break;
+		case TEST_COMPILER:
+		case TEST_BYTECODE:
+			print_line("Not implemented.");
 	}
 	}
 
 
-	memdelete(fa);
-
 	return nullptr;
 	return nullptr;
 }
 }
 
 

+ 2 - 2
modules/gdscript/editor/gdscript_highlighter.cpp

@@ -288,7 +288,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line)
 
 
 			if (str[k] == '(') {
 			if (str[k] == '(') {
 				in_function_name = true;
 				in_function_name = true;
-			} else if (previous_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::TK_PR_VAR)) {
+			} else if (previous_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::VAR)) {
 				in_variable_declaration = true;
 				in_variable_declaration = true;
 			}
 			}
 		}
 		}
@@ -357,7 +357,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line)
 		} else if (in_function_name) {
 		} else if (in_function_name) {
 			next_type = FUNCTION;
 			next_type = FUNCTION;
 
 
-			if (previous_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::TK_PR_FUNCTION)) {
+			if (previous_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) {
 				color = function_definition_color;
 				color = function_definition_color;
 			} else {
 			} else {
 				color = function_color;
 				color = function_color;

+ 147 - 310
modules/gdscript/gdscript.cpp

@@ -39,7 +39,9 @@
 #include "core/os/file_access.h"
 #include "core/os/file_access.h"
 #include "core/os/os.h"
 #include "core/os/os.h"
 #include "core/project_settings.h"
 #include "core/project_settings.h"
+#include "gdscript_analyzer.h"
 #include "gdscript_compiler.h"
 #include "gdscript_compiler.h"
+#include "gdscript_parser.h"
 
 
 ///////////////////////////
 ///////////////////////////
 
 
@@ -79,6 +81,17 @@ Object *GDScriptNativeClass::instance() {
 	return ClassDB::instance(name);
 	return ClassDB::instance(name);
 }
 }
 
 
+void GDScript::_super_implicit_constructor(GDScript *p_script, GDScriptInstance *p_instance, Callable::CallError &r_error) {
+	GDScript *base = p_script->_base;
+	if (base != nullptr) {
+		_super_implicit_constructor(base, p_instance, r_error);
+		if (r_error.error != Callable::CallError::CALL_OK) {
+			return;
+		}
+	}
+	p_script->implicit_initializer->call(p_instance, nullptr, 0, r_error);
+}
+
 GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_isref, Callable::CallError &r_error) {
 GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_isref, Callable::CallError &r_error) {
 	/* STEP 1, CREATE */
 	/* STEP 1, CREATE */
 
 
@@ -101,10 +114,8 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco
 		MutexLock lock(GDScriptLanguage::singleton->lock);
 		MutexLock lock(GDScriptLanguage::singleton->lock);
 		instances.insert(instance->owner);
 		instances.insert(instance->owner);
 	}
 	}
-	if (p_argcount < 0) {
-		return instance;
-	}
-	initializer->call(instance, p_args, p_argcount, r_error);
+
+	_super_implicit_constructor(this, instance, r_error);
 	if (r_error.error != Callable::CallError::CALL_OK) {
 	if (r_error.error != Callable::CallError::CALL_OK) {
 		instance->script = Ref<GDScript>();
 		instance->script = Ref<GDScript>();
 		instance->owner->set_script_instance(nullptr);
 		instance->owner->set_script_instance(nullptr);
@@ -114,6 +125,22 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco
 		}
 		}
 		ERR_FAIL_V_MSG(nullptr, "Error constructing a GDScriptInstance.");
 		ERR_FAIL_V_MSG(nullptr, "Error constructing a GDScriptInstance.");
 	}
 	}
+
+	if (p_argcount < 0) {
+		return instance;
+	}
+	if (initializer != nullptr) {
+		initializer->call(instance, p_args, p_argcount, r_error);
+		if (r_error.error != Callable::CallError::CALL_OK) {
+			instance->script = Ref<GDScript>();
+			instance->owner->set_script_instance(nullptr);
+			{
+				MutexLock lock(GDScriptLanguage::singleton->lock);
+				instances.erase(p_owner);
+			}
+			ERR_FAIL_V_MSG(nullptr, "Error constructing a GDScriptInstance.");
+		}
+	}
 	//@TODO make thread safe
 	//@TODO make thread safe
 	return instance;
 	return instance;
 }
 }
@@ -382,13 +409,11 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call) {
 		}
 		}
 
 
 		GDScriptParser parser;
 		GDScriptParser parser;
-		Error err = parser.parse(source, basedir, true, path);
-
-		if (err == OK) {
-			const GDScriptParser::Node *root = parser.get_parse_tree();
-			ERR_FAIL_COND_V(root->type != GDScriptParser::Node::TYPE_CLASS, false);
+		GDScriptAnalyzer analyzer(&parser);
+		Error err = parser.parse(source, path, false);
 
 
-			const GDScriptParser::ClassNode *c = static_cast<const GDScriptParser::ClassNode *>(root);
+		if (err == OK && analyzer.analyze() == OK) {
+			const GDScriptParser::ClassNode *c = parser.get_tree();
 
 
 			if (base_cache.is_valid()) {
 			if (base_cache.is_valid()) {
 				base_cache->inheriters_cache.erase(get_instance_id());
 				base_cache->inheriters_cache.erase(get_instance_id());
@@ -397,8 +422,8 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call) {
 
 
 			if (c->extends_used) {
 			if (c->extends_used) {
 				String path = "";
 				String path = "";
-				if (String(c->extends_file) != "" && String(c->extends_file) != get_path()) {
-					path = c->extends_file;
+				if (String(c->extends_path) != "" && String(c->extends_path) != get_path()) {
+					path = c->extends_path;
 					if (path.is_rel_path()) {
 					if (path.is_rel_path()) {
 						String base = get_path();
 						String base = get_path();
 						if (base == "" || base.is_rel_path()) {
 						if (base == "" || base.is_rel_path()) {
@@ -407,8 +432,8 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call) {
 							path = base.get_base_dir().plus_file(path);
 							path = base.get_base_dir().plus_file(path);
 						}
 						}
 					}
 					}
-				} else if (c->extends_class.size() != 0) {
-					String base = c->extends_class[0];
+				} else if (c->extends.size() != 0) {
+					const StringName &base = c->extends[0];
 
 
 					if (ScriptServer::is_global_class(base)) {
 					if (ScriptServer::is_global_class(base)) {
 						path = ScriptServer::get_global_class_path(base);
 						path = ScriptServer::get_global_class_path(base);
@@ -431,20 +456,37 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call) {
 
 
 			members_cache.clear();
 			members_cache.clear();
 			member_default_values_cache.clear();
 			member_default_values_cache.clear();
+			_signals.clear();
 
 
-			for (int i = 0; i < c->variables.size(); i++) {
-				if (c->variables[i]._export.type == Variant::NIL) {
-					continue;
-				}
-
-				members_cache.push_back(c->variables[i]._export);
-				member_default_values_cache[c->variables[i].identifier] = c->variables[i].default_value;
-			}
+			for (int i = 0; i < c->members.size(); i++) {
+				const GDScriptParser::ClassNode::Member &member = c->members[i];
 
 
-			_signals.clear();
+				switch (member.type) {
+					case GDScriptParser::ClassNode::Member::VARIABLE: {
+						if (!member.variable->exported) {
+							continue;
+						}
 
 
-			for (int i = 0; i < c->_signals.size(); i++) {
-				_signals[c->_signals[i].name] = c->_signals[i].arguments;
+						members_cache.push_back(member.variable->export_info);
+						// FIXME: Get variable's default value in non-literal cases.
+						Variant default_value;
+						if (member.variable->initializer != nullptr && member.variable->initializer->type == GDScriptParser::Node::LITERAL) {
+							default_value = static_cast<const GDScriptParser::LiteralNode *>(member.variable->initializer)->value;
+						}
+						member_default_values_cache[member.variable->identifier->name] = default_value;
+					} break;
+					case GDScriptParser::ClassNode::Member::SIGNAL: {
+						// TODO: Cache this in parser to avoid loops like this.
+						Vector<StringName> parameters_names;
+						parameters_names.resize(member.signal->parameters.size());
+						for (int j = 0; j < member.signal->parameters.size(); j++) {
+							parameters_names.write[j] = member.signal->parameters[j]->identifier->name;
+						}
+						_signals[member.signal->identifier->name] = parameters_names;
+					} break;
+					default:
+						break; // Nothing.
+				}
 			}
 			}
 		} else {
 		} else {
 			placeholder_fallback_enabled = true;
 			placeholder_fallback_enabled = true;
@@ -555,16 +597,29 @@ Error GDScript::reload(bool p_keep_state) {
 
 
 	valid = false;
 	valid = false;
 	GDScriptParser parser;
 	GDScriptParser parser;
-	Error err = parser.parse(source, basedir, false, path);
+	Error err = parser.parse(source, path, false);
 	if (err) {
 	if (err) {
 		if (EngineDebugger::is_active()) {
 		if (EngineDebugger::is_active()) {
-			GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), parser.get_error_line(), "Parser Error: " + parser.get_error());
+			GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), parser.get_errors().front()->get().line, "Parser Error: " + parser.get_errors().front()->get().message);
 		}
 		}
-		_err_print_error("GDScript::reload", path.empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_error_line(), ("Parse Error: " + parser.get_error()).utf8().get_data(), ERR_HANDLER_SCRIPT);
+		// TODO: Show all error messages.
+		_err_print_error("GDScript::reload", path.empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_errors().front()->get().line, ("Parse Error: " + parser.get_errors().front()->get().message).utf8().get_data(), ERR_HANDLER_SCRIPT);
 		ERR_FAIL_V(ERR_PARSE_ERROR);
 		ERR_FAIL_V(ERR_PARSE_ERROR);
 	}
 	}
 
 
-	bool can_run = ScriptServer::is_scripting_enabled() || parser.is_tool_script();
+	GDScriptAnalyzer analyzer(&parser);
+	err = analyzer.analyze();
+
+	if (err) {
+		if (EngineDebugger::is_active()) {
+			GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), parser.get_errors().front()->get().line, "Parser Error: " + parser.get_errors().front()->get().message);
+		}
+		// TODO: Show all error messages.
+		_err_print_error("GDScript::reload", path.empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_errors().front()->get().line, ("Parse Error: " + parser.get_errors().front()->get().message).utf8().get_data(), ERR_HANDLER_SCRIPT);
+		ERR_FAIL_V(ERR_PARSE_ERROR);
+	}
+
+	bool can_run = ScriptServer::is_scripting_enabled() || parser.is_tool();
 
 
 	GDScriptCompiler compiler;
 	GDScriptCompiler compiler;
 	err = compiler.compile(&parser, this, p_keep_state);
 	err = compiler.compile(&parser, this, p_keep_state);
@@ -581,13 +636,14 @@ Error GDScript::reload(bool p_keep_state) {
 		}
 		}
 	}
 	}
 #ifdef DEBUG_ENABLED
 #ifdef DEBUG_ENABLED
-	for (const List<GDScriptWarning>::Element *E = parser.get_warnings().front(); E; E = E->next()) {
-		const GDScriptWarning &warning = E->get();
-		if (EngineDebugger::is_active()) {
-			Vector<ScriptLanguage::StackInfo> si;
-			EngineDebugger::get_script_debugger()->send_error("", get_path(), warning.line, warning.get_name(), warning.get_message(), ERR_HANDLER_WARNING, si);
-		}
-	}
+	// FIXME: Add warnings.
+	// for (const List<GDScriptWarning>::Element *E = parser.get_warnings().front(); E; E = E->next()) {
+	// 	const GDScriptWarning &warning = E->get();
+	// 	if (EngineDebugger::is_active()) {
+	// 		Vector<ScriptLanguage::StackInfo> si;
+	// 		EngineDebugger::get_script_debugger()->send_error("", get_path(), warning.line, warning.get_name(), warning.get_message(), ERR_HANDLER_WARNING, si);
+	// 	}
+	// }
 #endif
 #endif
 
 
 	valid = true;
 	valid = true;
@@ -753,83 +809,12 @@ void GDScript::_bind_methods() {
 }
 }
 
 
 Vector<uint8_t> GDScript::get_as_byte_code() const {
 Vector<uint8_t> GDScript::get_as_byte_code() const {
-	GDScriptTokenizerBuffer tokenizer;
-	return tokenizer.parse_code_string(source);
+	return Vector<uint8_t>();
 };
 };
 
 
+// TODO: Fully remove this. There's not this kind of "bytecode" anymore.
 Error GDScript::load_byte_code(const String &p_path) {
 Error GDScript::load_byte_code(const String &p_path) {
-	Vector<uint8_t> bytecode;
-
-	if (p_path.ends_with("gde")) {
-		FileAccess *fa = FileAccess::open(p_path, FileAccess::READ);
-		ERR_FAIL_COND_V(!fa, ERR_CANT_OPEN);
-
-		FileAccessEncrypted *fae = memnew(FileAccessEncrypted);
-		ERR_FAIL_COND_V(!fae, ERR_CANT_OPEN);
-
-		Vector<uint8_t> key;
-		key.resize(32);
-		for (int i = 0; i < key.size(); i++) {
-			key.write[i] = script_encryption_key[i];
-		}
-
-		Error err = fae->open_and_parse(fa, key, FileAccessEncrypted::MODE_READ);
-
-		if (err) {
-			fa->close();
-			memdelete(fa);
-			memdelete(fae);
-
-			ERR_FAIL_COND_V(err, err);
-		}
-
-		bytecode.resize(fae->get_len());
-		fae->get_buffer(bytecode.ptrw(), bytecode.size());
-		fae->close();
-		memdelete(fae);
-
-	} else {
-		bytecode = FileAccess::get_file_as_array(p_path);
-	}
-
-	ERR_FAIL_COND_V(bytecode.size() == 0, ERR_PARSE_ERROR);
-	path = p_path;
-
-	String basedir = path;
-
-	if (basedir == "") {
-		basedir = get_path();
-	}
-
-	if (basedir != "") {
-		basedir = basedir.get_base_dir();
-	}
-
-	valid = false;
-	GDScriptParser parser;
-	Error err = parser.parse_bytecode(bytecode, basedir, get_path());
-	if (err) {
-		_err_print_error("GDScript::load_byte_code", path.empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_error_line(), ("Parse Error: " + parser.get_error()).utf8().get_data(), ERR_HANDLER_SCRIPT);
-		ERR_FAIL_V(ERR_PARSE_ERROR);
-	}
-
-	GDScriptCompiler compiler;
-	err = compiler.compile(&parser, this);
-
-	if (err) {
-		_err_print_error("GDScript::load_byte_code", path.empty() ? "built-in" : (const char *)path.utf8().get_data(), compiler.get_error_line(), ("Compile Error: " + compiler.get_error()).utf8().get_data(), ERR_HANDLER_SCRIPT);
-		ERR_FAIL_V(ERR_COMPILATION_FAILED);
-	}
-
-	valid = true;
-
-	for (Map<StringName, Ref<GDScript>>::Element *E = subclasses.front(); E; E = E->next()) {
-		_set_subclass_path(E->get(), path);
-	}
-
-	_init_rpc_methods_properties();
-
-	return OK;
+	return ERR_COMPILATION_FAILED;
 }
 }
 
 
 Error GDScript::load_source_code(const String &p_path) {
 Error GDScript::load_source_code(const String &p_path) {
@@ -1152,6 +1137,32 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const {
 			}
 			}
 		}
 		}
 
 
+		{
+			// Signals.
+			const GDScript *sl = sptr;
+			while (sl) {
+				const Map<StringName, Vector<StringName>>::Element *E = sl->_signals.find(p_name);
+				if (E) {
+					r_ret = Signal(this->owner, E->key());
+					return true; //index found
+				}
+				sl = sl->_base;
+			}
+		}
+
+		{
+			// Methods.
+			const GDScript *sl = sptr;
+			while (sl) {
+				const Map<StringName, GDScriptFunction *>::Element *E = sl->member_functions.find(p_name);
+				if (E) {
+					r_ret = Callable(this->owner, E->key());
+					return true; //index found
+				}
+				sl = sl->_base;
+			}
+		}
+
 		{
 		{
 			const Map<StringName, GDScriptFunction *>::Element *E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._get);
 			const Map<StringName, GDScriptFunction *>::Element *E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._get);
 			if (E) {
 			if (E) {
@@ -1304,6 +1315,7 @@ void GDScriptInstance::call_multilevel(const StringName &p_method, const Variant
 		Map<StringName, GDScriptFunction *>::Element *E = sptr->member_functions.find(p_method);
 		Map<StringName, GDScriptFunction *>::Element *E = sptr->member_functions.find(p_method);
 		if (E) {
 		if (E) {
 			E->get()->call(this, p_args, p_argcount, ce);
 			E->get()->call(this, p_args, p_argcount, ce);
+			return;
 		}
 		}
 		sptr = sptr->_base;
 		sptr = sptr->_base;
 	}
 	}
@@ -1827,6 +1839,7 @@ void GDScriptLanguage::frame() {
 
 
 /* EDITOR FUNCTIONS */
 /* EDITOR FUNCTIONS */
 void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
 void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
+	// TODO: Add annotations here?
 	static const char *_reserved_words[] = {
 	static const char *_reserved_words[] = {
 		// operators
 		// operators
 		"and",
 		"and",
@@ -1849,6 +1862,7 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
 		// functions
 		// functions
 		"as",
 		"as",
 		"assert",
 		"assert",
+		"await",
 		"breakpoint",
 		"breakpoint",
 		"class",
 		"class",
 		"class_name",
 		"class_name",
@@ -1856,15 +1870,11 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
 		"is",
 		"is",
 		"func",
 		"func",
 		"preload",
 		"preload",
-		"setget",
 		"signal",
 		"signal",
-		"tool",
 		"yield",
 		"yield",
 		// var
 		// var
 		"const",
 		"const",
 		"enum",
 		"enum",
-		"export",
-		"onready",
 		"static",
 		"static",
 		"var",
 		"var",
 		// control flow
 		// control flow
@@ -1878,12 +1888,6 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
 		"return",
 		"return",
 		"match",
 		"match",
 		"while",
 		"while",
-		"remote",
-		"master",
-		"puppet",
-		"remotesync",
-		"mastersync",
-		"puppetsync",
 		nullptr
 		nullptr
 	};
 	};
 
 
@@ -1914,10 +1918,11 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
 	String source = f->get_as_utf8_string();
 	String source = f->get_as_utf8_string();
 
 
 	GDScriptParser parser;
 	GDScriptParser parser;
-	parser.parse(source, p_path.get_base_dir(), true, p_path, false, nullptr, true);
+	err = parser.parse(source, p_path, false);
 
 
-	if (parser.get_parse_tree() && parser.get_parse_tree()->type == GDScriptParser::Node::TYPE_CLASS) {
-		const GDScriptParser::ClassNode *c = static_cast<const GDScriptParser::ClassNode *>(parser.get_parse_tree());
+	// 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 (r_icon_path) {
 			if (c->icon_path.empty() || c->icon_path.is_abs_path()) {
 			if (c->icon_path.empty() || c->icon_path.is_abs_path()) {
 				*r_icon_path = c->icon_path;
 				*r_icon_path = c->icon_path;
@@ -1931,15 +1936,15 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
 			GDScriptParser subparser;
 			GDScriptParser subparser;
 			while (subclass) {
 			while (subclass) {
 				if (subclass->extends_used) {
 				if (subclass->extends_used) {
-					if (subclass->extends_file) {
-						if (subclass->extends_class.size() == 0) {
-							get_global_class_name(subclass->extends_file, r_base_type);
+					if (!subclass->extends_path.empty()) {
+						if (subclass->extends.size() == 0) {
+							get_global_class_name(subclass->extends_path, r_base_type);
 							subclass = nullptr;
 							subclass = nullptr;
 							break;
 							break;
 						} else {
 						} else {
-							Vector<StringName> extend_classes = subclass->extends_class;
+							Vector<StringName> extend_classes = subclass->extends;
 
 
-							FileAccessRef subfile = FileAccess::open(subclass->extends_file, FileAccess::READ);
+							FileAccessRef subfile = FileAccess::open(subclass->extends_path, FileAccess::READ);
 							if (!subfile) {
 							if (!subfile) {
 								break;
 								break;
 							}
 							}
@@ -1948,25 +1953,26 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
 							if (subsource.empty()) {
 							if (subsource.empty()) {
 								break;
 								break;
 							}
 							}
-							String subpath = subclass->extends_file;
+							String subpath = subclass->extends_path;
 							if (subpath.is_rel_path()) {
 							if (subpath.is_rel_path()) {
 								subpath = path.get_base_dir().plus_file(subpath).simplify_path();
 								subpath = path.get_base_dir().plus_file(subpath).simplify_path();
 							}
 							}
 
 
-							if (OK != subparser.parse(subsource, subpath.get_base_dir(), true, subpath, false, nullptr, true)) {
+							if (OK != subparser.parse(subsource, subpath, false)) {
 								break;
 								break;
 							}
 							}
 							path = subpath;
 							path = subpath;
-							if (!subparser.get_parse_tree() || subparser.get_parse_tree()->type != GDScriptParser::Node::TYPE_CLASS) {
-								break;
-							}
-							subclass = static_cast<const GDScriptParser::ClassNode *>(subparser.get_parse_tree());
+							subclass = subparser.get_tree();
 
 
 							while (extend_classes.size() > 0) {
 							while (extend_classes.size() > 0) {
 								bool found = false;
 								bool found = false;
-								for (int i = 0; i < subclass->subclasses.size(); i++) {
-									const GDScriptParser::ClassNode *inner_class = subclass->subclasses[i];
-									if (inner_class->name == extend_classes[0]) {
+								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(0);
 										extend_classes.remove(0);
 										found = true;
 										found = true;
 										subclass = inner_class;
 										subclass = inner_class;
@@ -1979,8 +1985,8 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
 								}
 								}
 							}
 							}
 						}
 						}
-					} else if (subclass->extends_class.size() == 1) {
-						*r_base_type = subclass->extends_class[0];
+					} else if (subclass->extends.size() == 1) {
+						*r_base_type = subclass->extends[0];
 						subclass = nullptr;
 						subclass = nullptr;
 					} else {
 					} else {
 						break;
 						break;
@@ -1991,181 +1997,12 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
 				}
 				}
 			}
 			}
 		}
 		}
-		return c->name;
+		return c->identifier != nullptr ? String(c->identifier->name) : String();
 	}
 	}
 
 
 	return String();
 	return String();
 }
 }
 
 
-#ifdef DEBUG_ENABLED
-String GDScriptWarning::get_message() const {
-#define CHECK_SYMBOLS(m_amount) ERR_FAIL_COND_V(symbols.size() < m_amount, String());
-
-	switch (code) {
-		case UNASSIGNED_VARIABLE_OP_ASSIGN: {
-			CHECK_SYMBOLS(1);
-			return "Using assignment with operation but the variable '" + symbols[0] + "' was not previously assigned a value.";
-		} break;
-		case UNASSIGNED_VARIABLE: {
-			CHECK_SYMBOLS(1);
-			return "The variable '" + symbols[0] + "' was used but never assigned a value.";
-		} break;
-		case UNUSED_VARIABLE: {
-			CHECK_SYMBOLS(1);
-			return "The local variable '" + symbols[0] + "' is declared but never used in the block. If this is intended, prefix it with an underscore: '_" + symbols[0] + "'";
-		} break;
-		case SHADOWED_VARIABLE: {
-			CHECK_SYMBOLS(2);
-			return "The local variable '" + symbols[0] + "' is shadowing an already-defined variable at line " + symbols[1] + ".";
-		} break;
-		case UNUSED_CLASS_VARIABLE: {
-			CHECK_SYMBOLS(1);
-			return "The class variable '" + symbols[0] + "' is declared but never used in the script.";
-		} break;
-		case UNUSED_ARGUMENT: {
-			CHECK_SYMBOLS(2);
-			return "The argument '" + symbols[1] + "' is never used in the function '" + symbols[0] + "'. If this is intended, prefix it with an underscore: '_" + symbols[1] + "'";
-		} break;
-		case UNREACHABLE_CODE: {
-			CHECK_SYMBOLS(1);
-			return "Unreachable code (statement after return) in function '" + symbols[0] + "()'.";
-		} break;
-		case STANDALONE_EXPRESSION: {
-			return "Standalone expression (the line has no effect).";
-		} break;
-		case VOID_ASSIGNMENT: {
-			CHECK_SYMBOLS(1);
-			return "Assignment operation, but the function '" + symbols[0] + "()' returns void.";
-		} break;
-		case NARROWING_CONVERSION: {
-			return "Narrowing conversion (float is converted to int and loses precision).";
-		} break;
-		case FUNCTION_MAY_YIELD: {
-			CHECK_SYMBOLS(1);
-			return "Assigned variable is typed but the function '" + symbols[0] + "()' may yield and return a GDScriptFunctionState instead.";
-		} break;
-		case VARIABLE_CONFLICTS_FUNCTION: {
-			CHECK_SYMBOLS(1);
-			return "Variable declaration of '" + symbols[0] + "' conflicts with a function of the same name.";
-		} break;
-		case FUNCTION_CONFLICTS_VARIABLE: {
-			CHECK_SYMBOLS(1);
-			return "Function declaration of '" + symbols[0] + "()' conflicts with a variable of the same name.";
-		} break;
-		case FUNCTION_CONFLICTS_CONSTANT: {
-			CHECK_SYMBOLS(1);
-			return "Function declaration of '" + symbols[0] + "()' conflicts with a constant of the same name.";
-		} break;
-		case INCOMPATIBLE_TERNARY: {
-			return "Values of the ternary conditional are not mutually compatible.";
-		} break;
-		case UNUSED_SIGNAL: {
-			CHECK_SYMBOLS(1);
-			return "The signal '" + symbols[0] + "' is declared but never emitted.";
-		} break;
-		case RETURN_VALUE_DISCARDED: {
-			CHECK_SYMBOLS(1);
-			return "The function '" + symbols[0] + "()' returns a value, but this value is never used.";
-		} break;
-		case PROPERTY_USED_AS_FUNCTION: {
-			CHECK_SYMBOLS(2);
-			return "The method '" + symbols[0] + "()' was not found in base '" + symbols[1] + "' but there's a property with the same name. Did you mean to access it?";
-		} break;
-		case CONSTANT_USED_AS_FUNCTION: {
-			CHECK_SYMBOLS(2);
-			return "The method '" + symbols[0] + "()' was not found in base '" + symbols[1] + "' but there's a constant with the same name. Did you mean to access it?";
-		} break;
-		case FUNCTION_USED_AS_PROPERTY: {
-			CHECK_SYMBOLS(2);
-			return "The property '" + symbols[0] + "' was not found in base '" + symbols[1] + "' but there's a method with the same name. Did you mean to call it?";
-		} break;
-		case INTEGER_DIVISION: {
-			return "Integer division, decimal part will be discarded.";
-		} break;
-		case UNSAFE_PROPERTY_ACCESS: {
-			CHECK_SYMBOLS(2);
-			return "The property '" + symbols[0] + "' is not present on the inferred type '" + symbols[1] + "' (but may be present on a subtype).";
-		} break;
-		case UNSAFE_METHOD_ACCESS: {
-			CHECK_SYMBOLS(2);
-			return "The method '" + symbols[0] + "' is not present on the inferred type '" + symbols[1] + "' (but may be present on a subtype).";
-		} break;
-		case UNSAFE_CAST: {
-			CHECK_SYMBOLS(1);
-			return "The value is cast to '" + symbols[0] + "' but has an unknown type.";
-		} break;
-		case UNSAFE_CALL_ARGUMENT: {
-			CHECK_SYMBOLS(4);
-			return "The argument '" + symbols[0] + "' of the function '" + symbols[1] + "' requires a the subtype '" + symbols[2] + "' but the supertype '" + symbols[3] + "' was provided";
-		} break;
-		case DEPRECATED_KEYWORD: {
-			CHECK_SYMBOLS(2);
-			return "The '" + symbols[0] + "' keyword is deprecated and will be removed in a future release, please replace its uses by '" + symbols[1] + "'.";
-		} break;
-		case STANDALONE_TERNARY: {
-			return "Standalone ternary conditional operator: the return value is being discarded.";
-		}
-		case WARNING_MAX:
-			break; // Can't happen, but silences warning
-	}
-	ERR_FAIL_V_MSG(String(), "Invalid GDScript warning code: " + get_name_from_code(code) + ".");
-
-#undef CHECK_SYMBOLS
-}
-
-String GDScriptWarning::get_name() const {
-	return get_name_from_code(code);
-}
-
-String GDScriptWarning::get_name_from_code(Code p_code) {
-	ERR_FAIL_COND_V(p_code < 0 || p_code >= WARNING_MAX, String());
-
-	static const char *names[] = {
-		"UNASSIGNED_VARIABLE",
-		"UNASSIGNED_VARIABLE_OP_ASSIGN",
-		"UNUSED_VARIABLE",
-		"SHADOWED_VARIABLE",
-		"UNUSED_CLASS_VARIABLE",
-		"UNUSED_ARGUMENT",
-		"UNREACHABLE_CODE",
-		"STANDALONE_EXPRESSION",
-		"VOID_ASSIGNMENT",
-		"NARROWING_CONVERSION",
-		"FUNCTION_MAY_YIELD",
-		"VARIABLE_CONFLICTS_FUNCTION",
-		"FUNCTION_CONFLICTS_VARIABLE",
-		"FUNCTION_CONFLICTS_CONSTANT",
-		"INCOMPATIBLE_TERNARY",
-		"UNUSED_SIGNAL",
-		"RETURN_VALUE_DISCARDED",
-		"PROPERTY_USED_AS_FUNCTION",
-		"CONSTANT_USED_AS_FUNCTION",
-		"FUNCTION_USED_AS_PROPERTY",
-		"INTEGER_DIVISION",
-		"UNSAFE_PROPERTY_ACCESS",
-		"UNSAFE_METHOD_ACCESS",
-		"UNSAFE_CAST",
-		"UNSAFE_CALL_ARGUMENT",
-		"DEPRECATED_KEYWORD",
-		"STANDALONE_TERNARY",
-		nullptr
-	};
-
-	return names[(int)p_code];
-}
-
-GDScriptWarning::Code GDScriptWarning::get_code_from_name(const String &p_name) {
-	for (int i = 0; i < WARNING_MAX; i++) {
-		if (get_name_from_code((Code)i) == p_name) {
-			return (Code)i;
-		}
-	}
-
-	ERR_FAIL_V_MSG(WARNING_MAX, "Invalid GDScript warning name: " + p_name);
-}
-
-#endif // DEBUG_ENABLED
-
 GDScriptLanguage::GDScriptLanguage() {
 GDScriptLanguage::GDScriptLanguage() {
 	calls = 0;
 	calls = 0;
 	ERR_FAIL_COND(singleton);
 	ERR_FAIL_COND(singleton);
@@ -2296,7 +2133,7 @@ void ResourceFormatLoaderGDScript::get_dependencies(const String &p_path, List<S
 	}
 	}
 
 
 	GDScriptParser parser;
 	GDScriptParser parser;
-	if (OK != parser.parse(source, p_path.get_base_dir(), true, p_path, false, nullptr, true)) {
+	if (OK != parser.parse(source, p_path, false)) {
 		return;
 		return;
 	}
 	}
 
 

+ 3 - 1
modules/gdscript/gdscript.h

@@ -104,7 +104,8 @@ class GDScript : public Script {
 #endif
 #endif
 	Map<StringName, PropertyInfo> member_info;
 	Map<StringName, PropertyInfo> member_info;
 
 
-	GDScriptFunction *initializer; //direct pointer to _init , faster to locate
+	GDScriptFunction *implicit_initializer = nullptr;
+	GDScriptFunction *initializer = nullptr; //direct pointer to new , faster to locate
 
 
 	int subclass_count;
 	int subclass_count;
 	Set<Object *> instances;
 	Set<Object *> instances;
@@ -117,6 +118,7 @@ class GDScript : public Script {
 
 
 	SelfList<GDScriptFunctionState>::List pending_func_states;
 	SelfList<GDScriptFunctionState>::List pending_func_states;
 
 
+	void _super_implicit_constructor(GDScript *p_script, GDScriptInstance *p_instance, Callable::CallError &r_error);
 	GDScriptInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_isref, Callable::CallError &r_error);
 	GDScriptInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_isref, Callable::CallError &r_error);
 
 
 	void _set_subclass_path(Ref<GDScript> &p_sc, const String &p_path);
 	void _set_subclass_path(Ref<GDScript> &p_sc, const String &p_path);

+ 283 - 0
modules/gdscript/gdscript_analyzer.cpp

@@ -0,0 +1,283 @@
+/*************************************************************************/
+/*  gdscript_analyzer.cpp                                                */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#include "gdscript_analyzer.h"
+
+#include "core/class_db.h"
+#include "core/hash_map.h"
+#include "core/io/resource_loader.h"
+#include "core/script_language.h"
+
+Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive) {
+	GDScriptParser::DataType result;
+
+	if (p_class->base_type.is_set()) {
+		// Already resolved
+		return OK;
+	}
+
+	if (!p_class->extends_used) {
+		result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED;
+		result.kind = GDScriptParser::DataType::NATIVE;
+		result.native_type = "Reference";
+	} else {
+		result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+
+		GDScriptParser::DataType base;
+
+		if (!p_class->extends_path.empty()) {
+			base.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+			base.kind = GDScriptParser::DataType::CLASS;
+			// TODO: Don't load the script here to avoid the issue with cycles.
+			base.script_type = ResourceLoader::load(p_class->extends_path);
+			if (base.script_type.is_null() || !base.script_type->is_valid()) {
+				parser->push_error(vformat(R"(Could not load the parent script at "%s".)", p_class->extends_path));
+			}
+			// TODO: Get this from cache singleton.
+			base.gdscript_type = nullptr;
+		} else {
+			const StringName &name = p_class->extends[0];
+			base.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+
+			if (ScriptServer::is_global_class(name)) {
+				base.kind = GDScriptParser::DataType::CLASS;
+				// TODO: Get this from cache singleton.
+				base.gdscript_type = nullptr;
+				// TODO: Try singletons (create main unified source for those).
+			} else if (p_class->members_indices.has(name)) {
+				GDScriptParser::ClassNode::Member member = p_class->get_member(name);
+
+				if (member.type == member.CLASS) {
+					base.kind = GDScriptParser::DataType::CLASS;
+					base.gdscript_type = member.m_class;
+				} else if (member.type == member.CONSTANT) {
+					// FIXME: This could also be a native type or preloaded GDScript.
+					base.kind = GDScriptParser::DataType::CLASS;
+					base.gdscript_type = nullptr;
+				}
+			} else {
+				if (ClassDB::class_exists(name)) {
+					base.kind = GDScriptParser::DataType::NATIVE;
+					base.native_type = name;
+				}
+			}
+		}
+
+		// TODO: Extends with attributes (A.B.C).
+		result = base;
+	}
+
+	if (!result.is_set()) {
+		// TODO: More specific error messages.
+		parser->push_error(vformat(R"(Could not resolve inheritance for class "%s".)", p_class->identifier == nullptr ? "<main>" : p_class->identifier->name), p_class);
+		return ERR_PARSE_ERROR;
+	}
+
+	p_class->set_datatype(result);
+
+	if (p_recursive) {
+		for (int i = 0; i < p_class->members.size(); i++) {
+			if (p_class->members[i].type == GDScriptParser::ClassNode::Member::CLASS) {
+				resolve_inheritance(p_class->members[i].m_class, true);
+			}
+		}
+	}
+
+	return OK;
+}
+
+Error GDScriptAnalyzer::resolve_inheritance() {
+	return resolve_inheritance(parser->head);
+}
+
+// TODO: Move this to a central location (maybe core?).
+static HashMap<StringName, StringName> underscore_map;
+static const char *underscore_classes[] = {
+	"ClassDB",
+	"Directory",
+	"Engine",
+	"File",
+	"Geometry",
+	"GodotSharp",
+	"JSON",
+	"Marshalls",
+	"Mutex",
+	"OS",
+	"ResourceLoader",
+	"ResourceSaver",
+	"Semaphore",
+	"Thread",
+	"VisualScriptEditor",
+	nullptr,
+};
+static StringName get_real_class_name(const StringName &p_source) {
+	if (underscore_map.empty()) {
+		const char **class_name = underscore_classes;
+		while (*class_name != nullptr) {
+			underscore_map[*class_name] = String("_") + *class_name;
+			class_name++;
+		}
+	}
+	if (underscore_map.has(p_source)) {
+		return underscore_map[p_source];
+	}
+	return p_source;
+}
+
+GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(const GDScriptParser::TypeNode *p_type) {
+	GDScriptParser::DataType result;
+
+	if (p_type == nullptr) {
+		return result;
+	}
+
+	result.type_source = result.ANNOTATED_EXPLICIT;
+	if (p_type->type_base == nullptr) {
+		// void.
+		result.kind = GDScriptParser::DataType::BUILTIN;
+		result.builtin_type = Variant::NIL;
+		return result;
+	}
+
+	StringName first = p_type->type_base->name;
+
+	if (GDScriptParser::get_builtin_type(first) != Variant::NIL) {
+		// Built-in types.
+		// FIXME: I'm probably using this wrong here (well, I'm not really using it). Specifier *includes* the base the type.
+		if (p_type->type_specifier != nullptr) {
+			parser->push_error(R"(Built-in types don't contain subtypes.)", p_type->type_specifier);
+			return GDScriptParser::DataType();
+		}
+		result.kind = GDScriptParser::DataType::BUILTIN;
+		result.builtin_type = GDScriptParser::get_builtin_type(first);
+	} else if (ClassDB::class_exists(get_real_class_name(first))) {
+		// Native engine classes.
+		if (p_type->type_specifier != nullptr) {
+			parser->push_error(R"(Engine classes don't contain subtypes.)", p_type->type_specifier);
+			return GDScriptParser::DataType();
+		}
+		result.kind = GDScriptParser::DataType::NATIVE;
+		result.native_type = first;
+	} else if (ScriptServer::is_global_class(first)) {
+		// Global class_named classes.
+		// TODO: Global classes and singletons.
+		parser->push_error("GDScript analyzer: global class type not implemented.", p_type);
+		ERR_FAIL_V_MSG(GDScriptParser::DataType(), "GDScript analyzer: global class type not implemented.");
+	} else {
+		// Classes in current scope.
+		GDScriptParser::ClassNode *script_class = parser->current_class;
+		bool found = false;
+		while (!found && script_class != nullptr) {
+			if (script_class->members_indices.has(first)) {
+				GDScriptParser::ClassNode::Member member = script_class->members[script_class->members_indices[first]];
+				switch (member.type) {
+					case GDScriptParser::ClassNode::Member::CLASS:
+						result.kind = GDScriptParser::DataType::CLASS;
+						result.gdscript_type = member.m_class;
+						found = true;
+						break;
+					default:
+						// TODO: Get constants as types, disallow others explicitly.
+						parser->push_error(vformat(R"("%s" is a %s but does not contain a type.)", first, member.get_type_name()), p_type);
+						return GDScriptParser::DataType();
+				}
+			}
+			script_class = script_class->outer;
+		}
+
+		parser->push_error(vformat(R"("%s" is not a valid type.)", first), p_type);
+		return GDScriptParser::DataType();
+	}
+
+	// TODO: Allow subtypes.
+	if (p_type->type_specifier != nullptr) {
+		parser->push_error(R"(Subtypes are not yet supported.)", p_type->type_specifier);
+		return GDScriptParser::DataType();
+	}
+
+	return result;
+}
+
+Error GDScriptAnalyzer::resolve_datatypes(GDScriptParser::ClassNode *p_class) {
+	GDScriptParser::ClassNode *previous_class = parser->current_class;
+	parser->current_class = p_class;
+
+	for (int i = 0; i < p_class->members.size(); i++) {
+		GDScriptParser::ClassNode::Member member = p_class->members[i];
+
+		switch (member.type) {
+			case GDScriptParser::ClassNode::Member::VARIABLE: {
+				GDScriptParser::DataType datatype = resolve_datatype(member.variable->datatype_specifier);
+				if (datatype.is_set()) {
+					member.variable->set_datatype(datatype);
+					if (member.variable->export_info.hint == PROPERTY_HINT_TYPE_STRING) {
+						// @export annotation.
+						switch (datatype.kind) {
+							case GDScriptParser::DataType::BUILTIN:
+								member.variable->export_info.hint_string = Variant::get_type_name(datatype.builtin_type);
+								break;
+							case GDScriptParser::DataType::NATIVE:
+								if (ClassDB::is_parent_class(get_real_class_name(datatype.native_type), "Resource")) {
+									member.variable->export_info.hint_string = get_real_class_name(datatype.native_type);
+								} else {
+									parser->push_error(R"(Export type can only be built-in or a resource.)", member.variable);
+								}
+								break;
+							default:
+								// TODO: Allow custom user resources.
+								parser->push_error(R"(Export type can only be built-in or a resource.)", member.variable);
+								break;
+						}
+					}
+				}
+				break;
+			}
+			default:
+				// TODO
+				break;
+		}
+	}
+	parser->current_class = previous_class;
+
+	return parser->errors.size() > 0 ? ERR_PARSE_ERROR : OK;
+}
+
+Error GDScriptAnalyzer::analyze() {
+	parser->errors.clear();
+	Error err = resolve_inheritance(parser->head);
+	if (err) {
+		return err;
+	}
+	return resolve_datatypes(parser->head);
+}
+
+GDScriptAnalyzer::GDScriptAnalyzer(GDScriptParser *p_parser) {
+	parser = p_parser;
+}

+ 52 - 0
modules/gdscript/gdscript_analyzer.h

@@ -0,0 +1,52 @@
+/*************************************************************************/
+/*  gdscript_analyzer.h                                                  */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef GDSCRIPT_ANALYZER_H
+#define GDSCRIPT_ANALYZER_H
+
+#include "gdscript_parser.h"
+
+class GDScriptAnalyzer {
+	GDScriptParser *parser = nullptr;
+
+	Error resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive = true);
+	GDScriptParser::DataType resolve_datatype(const GDScriptParser::TypeNode *p_type);
+
+	// This traverses the tree to resolve all TypeNodes.
+	Error resolve_datatypes(GDScriptParser::ClassNode *p_class);
+
+public:
+	Error resolve_inheritance();
+	Error analyze();
+
+	GDScriptAnalyzer(GDScriptParser *p_parser);
+};
+
+#endif // GDSCRIPT_ANALYZER_H

Diff do ficheiro suprimidas por serem muito extensas
+ 1218 - 828
modules/gdscript/gdscript_compiler.cpp


+ 12 - 7
modules/gdscript/gdscript_compiler.h

@@ -33,6 +33,7 @@
 
 
 #include "core/set.h"
 #include "core/set.h"
 #include "gdscript.h"
 #include "gdscript.h"
+#include "gdscript_function.h"
 #include "gdscript_parser.h"
 #include "gdscript_parser.h"
 
 
 class GDScriptCompiler {
 class GDScriptCompiler {
@@ -52,6 +53,7 @@ class GDScriptCompiler {
 		List<GDScriptFunction::StackDebug> stack_debug;
 		List<GDScriptFunction::StackDebug> stack_debug;
 		List<Map<StringName, int>> block_identifier_stack;
 		List<Map<StringName, int>> block_identifier_stack;
 		Map<StringName, int> block_identifiers;
 		Map<StringName, int> block_identifiers;
+		Map<StringName, int> local_named_constants;
 
 
 		void add_stack_identifier(const StringName &p_id, int p_stackpos) {
 		void add_stack_identifier(const StringName &p_id, int p_stackpos) {
 			stack_identifiers[p_id] = p_stackpos;
 			stack_identifiers[p_id] = p_stackpos;
@@ -111,11 +113,11 @@ class GDScriptCompiler {
 
 
 		int get_constant_pos(const Variant &p_constant) {
 		int get_constant_pos(const Variant &p_constant) {
 			if (constant_map.has(p_constant)) {
 			if (constant_map.has(p_constant)) {
-				return constant_map[p_constant];
+				return constant_map[p_constant] | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS);
 			}
 			}
 			int pos = constant_map.size();
 			int pos = constant_map.size();
 			constant_map[p_constant] = pos;
 			constant_map[p_constant] = pos;
-			return pos;
+			return pos | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS);
 		}
 		}
 
 
 		Vector<int> opcodes;
 		Vector<int> opcodes;
@@ -140,14 +142,16 @@ class GDScriptCompiler {
 
 
 	void _set_error(const String &p_error, const GDScriptParser::Node *p_node);
 	void _set_error(const String &p_error, const GDScriptParser::Node *p_node);
 
 
-	bool _create_unary_operator(CodeGen &codegen, const GDScriptParser::OperatorNode *on, Variant::Operator op, int p_stack_level);
-	bool _create_binary_operator(CodeGen &codegen, const GDScriptParser::OperatorNode *on, Variant::Operator op, int p_stack_level, bool p_initializer = false, int p_index_addr = 0);
+	bool _create_unary_operator(CodeGen &codegen, const GDScriptParser::UnaryOpNode *on, Variant::Operator op, int p_stack_level);
+	bool _create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, int p_stack_level, bool p_initializer = false, int p_index_addr = 0);
+	bool _create_binary_operator(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_left_operand, const GDScriptParser::ExpressionNode *p_right_operand, Variant::Operator op, int p_stack_level, bool p_initializer = false, int p_index_addr = 0);
 
 
 	GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype) const;
 	GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype) const;
 
 
-	int _parse_assign_right_expression(CodeGen &codegen, const GDScriptParser::OperatorNode *p_expression, int p_stack_level, int p_index_addr = 0);
-	int _parse_expression(CodeGen &codegen, const GDScriptParser::Node *p_expression, int p_stack_level, bool p_root = false, bool p_initializer = false, int p_index_addr = 0);
-	Error _parse_block(CodeGen &codegen, const GDScriptParser::BlockNode *p_block, int p_stack_level = 0, int p_break_addr = -1, int p_continue_addr = -1);
+	int _parse_assign_right_expression(CodeGen &codegen, const GDScriptParser::AssignmentNode *p_assignment, int p_stack_level, int p_index_addr = 0);
+	int _parse_expression(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_expression, int p_stack_level, bool p_root = false, bool p_initializer = false, int p_index_addr = 0);
+	Error _parse_match_pattern(CodeGen &codegen, const GDScriptParser::PatternNode *p_pattern, int p_stack_level, int p_value_addr, int p_type_addr, int &r_bound_variables, Vector<int> &r_patch_addresses, Vector<int> &r_block_patch_address);
+	Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, int p_stack_level = 0, int p_break_addr = -1, int p_continue_addr = -1);
 	Error _parse_function(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false);
 	Error _parse_function(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false);
 	Error _parse_class_level(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
 	Error _parse_class_level(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
 	Error _parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
 	Error _parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
@@ -156,6 +160,7 @@ class GDScriptCompiler {
 	int err_column;
 	int err_column;
 	StringName source;
 	StringName source;
 	String error;
 	String error;
+	bool within_await = false;
 
 
 public:
 public:
 	Error compile(const GDScriptParser *p_parser, GDScript *p_script, bool p_keep_state = false);
 	Error compile(const GDScriptParser *p_parser, GDScript *p_script, bool p_keep_state = false);

+ 104 - 3038
modules/gdscript/gdscript_editor.cpp

@@ -34,6 +34,8 @@
 #include "core/global_constants.h"
 #include "core/global_constants.h"
 #include "core/os/file_access.h"
 #include "core/os/file_access.h"
 #include "gdscript_compiler.h"
 #include "gdscript_compiler.h"
+#include "gdscript_parser.h"
+#include "gdscript_tokenizer.h"
 
 
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
 #include "editor/editor_file_system.h"
 #include "editor/editor_file_system.h"
@@ -114,50 +116,47 @@ void GDScriptLanguage::make_template(const String &p_class_name, const String &p
 	p_script->set_source_code(_template);
 	p_script->set_source_code(_template);
 }
 }
 
 
+static void get_function_names_recursively(const GDScriptParser::ClassNode *p_class, const String &p_prefix, Map<int, String> &r_funcs) {
+	for (int i = 0; i < p_class->members.size(); i++) {
+		if (p_class->members[i].type == GDScriptParser::ClassNode::Member::FUNCTION) {
+			const GDScriptParser::FunctionNode *function = p_class->members[i].function;
+			r_funcs[function->start_line] = p_prefix.empty() ? String(function->identifier->name) : p_prefix + "." + String(function->identifier->name);
+		} else if (p_class->members[i].type == GDScriptParser::ClassNode::Member::CLASS) {
+			String new_prefix = p_class->members[i].m_class->identifier->name;
+			get_function_names_recursively(p_class->members[i].m_class, p_prefix.empty() ? new_prefix : p_prefix + "." + new_prefix, r_funcs);
+		}
+	}
+}
+
 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, List<ScriptLanguage::Warning> *r_warnings, Set<int> *r_safe_lines) 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, List<ScriptLanguage::Warning> *r_warnings, Set<int> *r_safe_lines) const {
 	GDScriptParser parser;
 	GDScriptParser parser;
 
 
-	Error err = parser.parse(p_script, p_path.get_base_dir(), true, p_path, false, r_safe_lines);
+	Error err = parser.parse(p_script, p_path, false);
 #ifdef DEBUG_ENABLED
 #ifdef DEBUG_ENABLED
-	if (r_warnings) {
-		for (const List<GDScriptWarning>::Element *E = parser.get_warnings().front(); E; E = E->next()) {
-			const GDScriptWarning &warn = E->get();
-			ScriptLanguage::Warning w;
-			w.line = warn.line;
-			w.code = (int)warn.code;
-			w.string_code = GDScriptWarning::get_name_from_code(warn.code);
-			w.message = warn.get_message();
-			r_warnings->push_back(w);
-		}
-	}
+	// FIXME: Warnings.
+	// if (r_warnings) {
+	// 	for (const List<GDScriptWarning>::Element *E = parser.get_warnings().front(); E; E = E->next()) {
+	// 		const GDScriptWarning &warn = E->get();
+	// 		ScriptLanguage::Warning w;
+	// 		w.line = warn.line;
+	// 		w.code = (int)warn.code;
+	// 		w.string_code = GDScriptWarning::get_name_from_code(warn.code);
+	// 		w.message = warn.get_message();
+	// 		r_warnings->push_back(w);
+	// 	}
+	// }
 #endif
 #endif
 	if (err) {
 	if (err) {
-		r_line_error = parser.get_error_line();
-		r_col_error = parser.get_error_column();
-		r_test_error = parser.get_error();
+		GDScriptParser::ParserError parse_error = parser.get_errors().front()->get();
+		r_line_error = parse_error.line;
+		r_col_error = parse_error.column;
+		r_test_error = parse_error.message;
 		return false;
 		return false;
 	} else {
 	} else {
-		const GDScriptParser::Node *root = parser.get_parse_tree();
-		ERR_FAIL_COND_V(root->type != GDScriptParser::Node::TYPE_CLASS, false);
-
-		const GDScriptParser::ClassNode *cl = static_cast<const GDScriptParser::ClassNode *>(root);
+		const GDScriptParser::ClassNode *cl = parser.get_tree();
 		Map<int, String> funcs;
 		Map<int, String> funcs;
-		for (int i = 0; i < cl->functions.size(); i++) {
-			funcs[cl->functions[i]->line] = cl->functions[i]->name;
-		}
 
 
-		for (int i = 0; i < cl->static_functions.size(); i++) {
-			funcs[cl->static_functions[i]->line] = cl->static_functions[i]->name;
-		}
-
-		for (int i = 0; i < cl->subclasses.size(); i++) {
-			for (int j = 0; j < cl->subclasses[i]->functions.size(); j++) {
-				funcs[cl->subclasses[i]->functions[j]->line] = String(cl->subclasses[i]->name) + "." + cl->subclasses[i]->functions[j]->name;
-			}
-			for (int j = 0; j < cl->subclasses[i]->static_functions.size(); j++) {
-				funcs[cl->subclasses[i]->static_functions[j]->line] = String(cl->subclasses[i]->name) + "." + cl->subclasses[i]->static_functions[j]->name;
-			}
-		}
+		get_function_names_recursively(cl, "", funcs);
 
 
 		for (Map<int, String>::Element *E = funcs.front(); E; E = E->next()) {
 		for (Map<int, String>::Element *E = funcs.front(); E; E = E->next()) {
 			r_functions->push_back(E->get() + ":" + itos(E->key()));
 			r_functions->push_back(E->get() + ":" + itos(E->key()));
@@ -176,20 +175,26 @@ bool GDScriptLanguage::supports_builtin_mode() const {
 }
 }
 
 
 int GDScriptLanguage::find_function(const String &p_function, const String &p_code) const {
 int GDScriptLanguage::find_function(const String &p_function, const String &p_code) const {
-	GDScriptTokenizerText tokenizer;
-	tokenizer.set_code(p_code);
+	GDScriptTokenizer tokenizer;
+	tokenizer.set_source_code(p_code);
 	int indent = 0;
 	int indent = 0;
-	while (tokenizer.get_token() != GDScriptTokenizer::TK_EOF && tokenizer.get_token() != GDScriptTokenizer::TK_ERROR) {
-		if (tokenizer.get_token() == GDScriptTokenizer::TK_NEWLINE) {
-			indent = tokenizer.get_token_line_indent();
+	GDScriptTokenizer::Token current = tokenizer.scan();
+	while (current.type != GDScriptTokenizer::Token::TK_EOF && current.type != GDScriptTokenizer::Token::ERROR) {
+		if (current.type == GDScriptTokenizer::Token::INDENT) {
+			indent++;
+		} else if (current.type == GDScriptTokenizer::Token::DEDENT) {
+			indent--;
 		}
 		}
-		if (indent == 0 && tokenizer.get_token() == GDScriptTokenizer::TK_PR_FUNCTION && tokenizer.get_token(1) == GDScriptTokenizer::TK_IDENTIFIER) {
-			String identifier = tokenizer.get_token_identifier(1);
-			if (identifier == p_function) {
-				return tokenizer.get_token_line();
+		if (indent == 0 && current.type == GDScriptTokenizer::Token::FUNC) {
+			current = tokenizer.scan();
+			if (current.is_identifier()) {
+				String identifier = current.get_identifier();
+				if (identifier == p_function) {
+					return current.start_line;
+				}
 			}
 			}
 		}
 		}
-		tokenizer.advance();
+		current = tokenizer.scan();
 	}
 	}
 	return -1;
 	return -1;
 }
 }
@@ -395,16 +400,6 @@ void GDScriptLanguage::get_public_functions(List<MethodInfo> *p_functions) const
 		mi.return_val = PropertyInfo(Variant::OBJECT, "", PROPERTY_HINT_RESOURCE_TYPE, "Resource");
 		mi.return_val = PropertyInfo(Variant::OBJECT, "", PROPERTY_HINT_RESOURCE_TYPE, "Resource");
 		p_functions->push_back(mi);
 		p_functions->push_back(mi);
 	}
 	}
-	{
-		MethodInfo mi;
-		mi.name = "yield";
-		mi.arguments.push_back(PropertyInfo(Variant::OBJECT, "object"));
-		mi.arguments.push_back(PropertyInfo(Variant::STRING, "signal"));
-		mi.default_arguments.push_back(Variant());
-		mi.default_arguments.push_back(String());
-		mi.return_val = PropertyInfo(Variant::OBJECT, "", PROPERTY_HINT_RESOURCE_TYPE, "GDScriptFunctionState");
-		p_functions->push_back(mi);
-	}
 	{
 	{
 		MethodInfo mi;
 		MethodInfo mi;
 		mi.name = "assert";
 		mi.name = "assert";
@@ -467,3027 +462,98 @@ String GDScriptLanguage::make_function(const String &p_class, const String &p_na
 
 
 //////// COMPLETION //////////
 //////// COMPLETION //////////
 
 
-#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED)
-
-struct GDScriptCompletionContext {
-	const GDScriptParser::ClassNode *_class = nullptr;
-	const GDScriptParser::FunctionNode *function = nullptr;
-	const GDScriptParser::BlockNode *block = nullptr;
-	Object *base = nullptr;
-	String base_path;
-	int line = 0;
-	uint32_t depth = 0;
-
-	GDScriptCompletionContext() {}
-};
-
-struct GDScriptCompletionIdentifier {
-	GDScriptParser::DataType type;
-	String enumeration;
-	Variant value;
-	const GDScriptParser::Node *assigned_expression = nullptr;
-
-	GDScriptCompletionIdentifier() {}
-};
-
-static void _get_directory_contents(EditorFileSystemDirectory *p_dir, Map<String, ScriptCodeCompletionOption> &r_list) {
-	const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\"";
-
-	for (int i = 0; i < p_dir->get_file_count(); i++) {
-		ScriptCodeCompletionOption option(p_dir->get_file_path(i), ScriptCodeCompletionOption::KIND_FILE_PATH);
-		option.insert_text = quote_style + option.display + quote_style;
-		r_list.insert(option.display, option);
-	}
-
-	for (int i = 0; i < p_dir->get_subdir_count(); i++) {
-		_get_directory_contents(p_dir->get_subdir(i), r_list);
-	}
-}
-
-static String _get_visual_datatype(const PropertyInfo &p_info, bool p_isarg = true) {
-	if (p_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
-		String enum_name = p_info.class_name;
-		if (enum_name.find(".") == -1) {
-			return enum_name;
-		}
-		return enum_name.get_slice(".", 1);
-	}
-
-	String n = p_info.name;
-	int idx = n.find(":");
-	if (idx != -1) {
-		return n.substr(idx + 1, n.length());
-	}
-
-	if (p_info.type == Variant::OBJECT) {
-		if (p_info.hint == PROPERTY_HINT_RESOURCE_TYPE) {
-			return p_info.hint_string;
-		} else {
-			return p_info.class_name.operator String();
-		}
-	}
-	if (p_info.type == Variant::NIL) {
-		if (p_isarg || (p_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) {
-			return "Variant";
-		} else {
-			return "void";
-		}
-	}
-
-	return Variant::get_type_name(p_info.type);
-}
-
-static GDScriptCompletionIdentifier _type_from_variant(const Variant &p_value) {
-	GDScriptCompletionIdentifier ci;
-	ci.value = p_value;
-	ci.type.is_constant = true;
-	ci.type.has_type = true;
-	ci.type.kind = GDScriptParser::DataType::BUILTIN;
-	ci.type.builtin_type = p_value.get_type();
-
-	if (ci.type.builtin_type == Variant::OBJECT) {
-		Object *obj = p_value.operator Object *();
-		if (!obj) {
-			return ci;
-		}
-		ci.type.native_type = obj->get_class_name();
-		Ref<Script> scr = p_value;
-		if (scr.is_valid()) {
-			ci.type.is_meta_type = true;
-		} else {
-			ci.type.is_meta_type = false;
-			scr = obj->get_script();
-		}
-		if (scr.is_valid()) {
-			ci.type.script_type = scr;
-			Ref<GDScript> gds = scr;
-			if (gds.is_valid()) {
-				ci.type.kind = GDScriptParser::DataType::GDSCRIPT;
-			} else {
-				ci.type.kind = GDScriptParser::DataType::SCRIPT;
-			}
-			ci.type.native_type = scr->get_instance_base_type();
-		} else {
-			ci.type.kind = GDScriptParser::DataType::NATIVE;
-		}
-	}
-
-	return ci;
-}
-
-static GDScriptCompletionIdentifier _type_from_property(const PropertyInfo &p_property) {
-	GDScriptCompletionIdentifier ci;
-
-	if (p_property.type == Variant::NIL) {
-		// Variant
-		return ci;
-	}
-
-	if (p_property.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
-		ci.enumeration = p_property.class_name;
-	}
-
-	ci.type.has_type = true;
-	ci.type.builtin_type = p_property.type;
-	if (p_property.type == Variant::OBJECT) {
-		ci.type.kind = GDScriptParser::DataType::NATIVE;
-		ci.type.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name;
-	} else {
-		ci.type.kind = GDScriptParser::DataType::BUILTIN;
-	}
-	return ci;
-}
-
-static GDScriptCompletionIdentifier _type_from_gdtype(const GDScriptDataType &p_gdtype) {
-	GDScriptCompletionIdentifier ci;
-	if (!p_gdtype.has_type) {
-		return ci;
-	}
-
-	ci.type.has_type = true;
-	ci.type.builtin_type = p_gdtype.builtin_type;
-	ci.type.native_type = p_gdtype.native_type;
-	ci.type.script_type = p_gdtype.script_type;
-
-	switch (p_gdtype.kind) {
-		case GDScriptDataType::UNINITIALIZED: {
-			ERR_PRINT("Uninitialized completion. Please report a bug.");
-		} break;
-		case GDScriptDataType::BUILTIN: {
-			ci.type.kind = GDScriptParser::DataType::BUILTIN;
-		} break;
-		case GDScriptDataType::NATIVE: {
-			ci.type.kind = GDScriptParser::DataType::NATIVE;
-		} break;
-		case GDScriptDataType::GDSCRIPT: {
-			ci.type.kind = GDScriptParser::DataType::GDSCRIPT;
-		} break;
-		case GDScriptDataType::SCRIPT: {
-			ci.type.kind = GDScriptParser::DataType::SCRIPT;
-		} break;
-	}
-	return ci;
+// FIXME: Readd completion
+Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint) {
+	return OK;
 }
 }
 
 
-static bool _guess_identifier_type(GDScriptCompletionContext &p_context, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type);
-static bool _guess_identifier_type_from_base(GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type);
-static bool _guess_method_return_type_from_base(GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, GDScriptCompletionIdentifier &r_type);
+//////// END COMPLETION //////////
 
 
-static bool _guess_expression_type(GDScriptCompletionContext &p_context, const GDScriptParser::Node *p_expression, GDScriptCompletionIdentifier &r_type) {
-	bool found = false;
+String GDScriptLanguage::_get_indentation() const {
+#ifdef TOOLS_ENABLED
+	if (Engine::get_singleton()->is_editor_hint()) {
+		bool use_space_indentation = EDITOR_DEF("text_editor/indent/type", false);
 
 
-	if (++p_context.depth > 100) {
-		print_error("Maximum _guess_expression_type depth limit reached. Please file a bugreport.");
-		return false;
-	}
+		if (use_space_indentation) {
+			int indent_size = EDITOR_DEF("text_editor/indent/size", 4);
 
 
-	switch (p_expression->type) {
-		case GDScriptParser::Node::TYPE_CONSTANT: {
-			const GDScriptParser::ConstantNode *cn = static_cast<const GDScriptParser::ConstantNode *>(p_expression);
-			r_type = _type_from_variant(cn->value);
-			found = true;
-		} break;
-		case GDScriptParser::Node::TYPE_SELF: {
-			if (p_context._class) {
-				r_type.type.has_type = true;
-				r_type.type.kind = GDScriptParser::DataType::CLASS;
-				r_type.type.class_type = const_cast<GDScriptParser::ClassNode *>(p_context._class);
-				r_type.type.is_constant = true;
-				r_type.value = p_context.base;
-				found = true;
-			}
-		} break;
-		case GDScriptParser::Node::TYPE_IDENTIFIER: {
-			const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(p_expression);
-			found = _guess_identifier_type(p_context, id->name, r_type);
-		} break;
-		case GDScriptParser::Node::TYPE_DICTIONARY: {
-			// Try to recreate the dictionary
-			const GDScriptParser::DictionaryNode *dn = static_cast<const GDScriptParser::DictionaryNode *>(p_expression);
-			Dictionary d;
-			bool full = true;
-			for (int i = 0; i < dn->elements.size(); i++) {
-				GDScriptCompletionIdentifier key;
-				if (_guess_expression_type(p_context, dn->elements[i].key, key)) {
-					GDScriptCompletionIdentifier value;
-					if (_guess_expression_type(p_context, dn->elements[i].value, value)) {
-						if (!value.type.is_constant) {
-							full = false;
-							break;
-						}
-						d[key.value] = value.value;
-					} else {
-						full = false;
-						break;
-					}
-				} else {
-					full = false;
-					break;
-				}
-			}
-			if (full) {
-				// If not fully constant, setting this value is detrimental to the inference
-				r_type.value = d;
-				r_type.type.is_constant = true;
-			}
-			r_type.type.has_type = true;
-			r_type.type.kind = GDScriptParser::DataType::BUILTIN;
-			r_type.type.builtin_type = Variant::DICTIONARY;
-		} break;
-		case GDScriptParser::Node::TYPE_ARRAY: {
-			// Try to recreate the array
-			const GDScriptParser::ArrayNode *an = static_cast<const GDScriptParser::ArrayNode *>(p_expression);
-			Array a;
-			bool full = true;
-			a.resize(an->elements.size());
-			for (int i = 0; i < an->elements.size(); i++) {
-				GDScriptCompletionIdentifier value;
-				if (_guess_expression_type(p_context, an->elements[i], value)) {
-					a[i] = value.value;
-				} else {
-					full = false;
-					break;
-				}
-			}
-			if (full) {
-				// If not fully constant, setting this value is detrimental to the inference
-				r_type.value = a;
-			}
-			r_type.type.has_type = true;
-			r_type.type.kind = GDScriptParser::DataType::BUILTIN;
-			r_type.type.builtin_type = Variant::ARRAY;
-		} break;
-		case GDScriptParser::Node::TYPE_CAST: {
-			const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression);
-			GDScriptCompletionIdentifier value;
-			if (_guess_expression_type(p_context, cn->source_node, r_type)) {
-				r_type.type = cn->get_datatype();
-				found = true;
-			}
-		} break;
-		case GDScriptParser::Node::TYPE_OPERATOR: {
-			const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(p_expression);
-			switch (op->op) {
-				case GDScriptParser::OperatorNode::OP_CALL: {
-					if (op->arguments[0]->type == GDScriptParser::Node::TYPE_TYPE) {
-						const GDScriptParser::TypeNode *tn = static_cast<const GDScriptParser::TypeNode *>(op->arguments[0]);
-						r_type.type.has_type = true;
-						r_type.type.kind = GDScriptParser::DataType::BUILTIN;
-						r_type.type.builtin_type = tn->vtype;
-						found = true;
-						break;
-					} else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) {
-						const GDScriptParser::BuiltInFunctionNode *bin = static_cast<const GDScriptParser::BuiltInFunctionNode *>(op->arguments[0]);
-						MethodInfo mi = GDScriptFunctions::get_info(bin->function);
-						r_type = _type_from_property(mi.return_val);
-						found = true;
-						break;
-					} else if (op->arguments.size() >= 2 && op->arguments[1]->type == GDScriptParser::Node::TYPE_IDENTIFIER) {
-						StringName id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1])->name;
-
-						GDScriptCompletionContext c = p_context;
-						c.line = op->line;
-
-						GDScriptCompletionIdentifier base;
-						if (!_guess_expression_type(c, op->arguments[0], base)) {
-							found = false;
-							break;
-						}
-
-						// Try call if constant methods with constant arguments
-						if (base.type.is_constant && base.value.get_type() == Variant::OBJECT) {
-							GDScriptParser::DataType native_type = base.type;
-
-							while (native_type.kind == GDScriptParser::DataType::CLASS) {
-								native_type = native_type.class_type->base_type;
-							}
-
-							while (native_type.kind == GDScriptParser::DataType::GDSCRIPT || native_type.kind == GDScriptParser::DataType::SCRIPT) {
-								if (native_type.script_type.is_valid()) {
-									Ref<Script> parent = native_type.script_type->get_base_script();
-									if (parent.is_valid()) {
-										native_type.script_type = parent;
-									} else {
-										native_type.kind = GDScriptParser::DataType::NATIVE;
-										native_type.native_type = native_type.script_type->get_instance_base_type();
-										if (!ClassDB::class_exists(native_type.native_type)) {
-											native_type.native_type = String("_") + native_type.native_type;
-											if (!ClassDB::class_exists(native_type.native_type)) {
-												native_type.has_type = false;
-											}
-										}
-									}
-								}
-							}
-
-							if (native_type.has_type && native_type.kind == GDScriptParser::DataType::NATIVE) {
-								MethodBind *mb = ClassDB::get_method(native_type.native_type, id);
-								if (mb && mb->is_const()) {
-									bool all_is_const = true;
-									Vector<Variant> args;
-									GDScriptCompletionContext c2 = p_context;
-									c2.line = op->line;
-									for (int i = 2; all_is_const && i < op->arguments.size(); i++) {
-										GDScriptCompletionIdentifier arg;
-
-										if (_guess_expression_type(c2, op->arguments[i], arg)) {
-											if (arg.type.has_type && arg.type.is_constant && arg.value.get_type() != Variant::OBJECT) {
-												args.push_back(arg.value);
-											} else {
-												all_is_const = false;
-											}
-										} else {
-											all_is_const = false;
-										}
-									}
-
-									Object *baseptr = base.value;
-
-									if (all_is_const && String(id) == "get_node" && ClassDB::is_parent_class(native_type.native_type, "Node") && args.size()) {
-										String arg1 = args[0];
-										if (arg1.begins_with("/root/")) {
-											String which = arg1.get_slice("/", 2);
-											if (which != "") {
-												// Try singletons first
-												if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(which)) {
-													r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[which]);
-													found = true;
-												} else {
-													List<PropertyInfo> props;
-													ProjectSettings::get_singleton()->get_property_list(&props);
-
-													for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
-														String s = E->get().name;
-														if (!s.begins_with("autoload/")) {
-															continue;
-														}
-														String name = s.get_slice("/", 1);
-														if (name == which) {
-															String script = ProjectSettings::get_singleton()->get(s);
-
-															if (script.begins_with("*")) {
-																script = script.right(1);
-															}
-
-															if (!script.begins_with("res://")) {
-																script = "res://" + script;
-															}
-
-															if (!script.ends_with(".gd")) {
-																//not a script, try find the script anyway,
-																//may have some success
-																script = script.get_basename() + ".gd";
-															}
-
-															if (FileAccess::exists(script)) {
-																Ref<Script> scr;
-																if (ScriptCodeCompletionCache::get_singleton()) {
-																	scr = ScriptCodeCompletionCache::get_singleton()->get_cached_resource(script);
-																} else {
-																	scr = ResourceLoader::load(script);
-																}
-																if (scr.is_valid()) {
-																	r_type.type.has_type = true;
-																	r_type.type.script_type = scr;
-																	r_type.type.is_constant = false;
-																	Ref<GDScript> gds = scr;
-																	if (gds.is_valid()) {
-																		r_type.type.kind = GDScriptParser::DataType::GDSCRIPT;
-																	} else {
-																		r_type.type.kind = GDScriptParser::DataType::SCRIPT;
-																	}
-																	r_type.value = Variant();
-																	found = true;
-																}
-															}
-															break;
-														}
-													}
-												}
-											}
-										}
-									}
-
-									if (!found && all_is_const && baseptr) {
-										Vector<const Variant *> argptr;
-										for (int i = 0; i < args.size(); i++) {
-											argptr.push_back(&args[i]);
-										}
-
-										Callable::CallError ce;
-										Variant ret = mb->call(baseptr, (const Variant **)argptr.ptr(), argptr.size(), ce);
-
-										if (ce.error == Callable::CallError::CALL_OK && ret.get_type() != Variant::NIL) {
-											if (ret.get_type() != Variant::OBJECT || ret.operator Object *() != nullptr) {
-												r_type = _type_from_variant(ret);
-												found = true;
-											}
-										}
-									}
-								}
-							}
-						}
-
-						if (!found) {
-							found = _guess_method_return_type_from_base(c, base, id, r_type);
-						}
-					}
-				} break;
-				case GDScriptParser::OperatorNode::OP_PARENT_CALL: {
-					if (!p_context._class || !op->arguments.size() || op->arguments[0]->type != GDScriptParser::Node::TYPE_IDENTIFIER) {
-						break;
-					}
-
-					StringName id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0])->name;
-
-					GDScriptCompletionIdentifier base;
-					base.value = p_context.base;
-					base.type = p_context._class->base_type;
-
-					GDScriptCompletionContext c = p_context;
-					c.line = op->line;
-
-					found = _guess_method_return_type_from_base(c, base, id, r_type);
-				} break;
-				case GDScriptParser::OperatorNode::OP_INDEX_NAMED: {
-					if (op->arguments.size() < 2 || op->arguments[1]->type != GDScriptParser::Node::TYPE_IDENTIFIER) {
-						found = false;
-						break;
-					}
-					const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1]);
-
-					GDScriptCompletionContext c = p_context;
-					c.line = op->line;
-
-					GDScriptCompletionIdentifier base;
-					if (!_guess_expression_type(c, op->arguments[0], base)) {
-						found = false;
-						break;
-					}
-
-					if (base.value.get_type() == Variant::DICTIONARY && base.value.operator Dictionary().has(String(id->name))) {
-						Variant value = base.value.operator Dictionary()[String(id->name)];
-						r_type = _type_from_variant(value);
-						found = true;
-						break;
-					}
-
-					const GDScriptParser::DictionaryNode *dn = nullptr;
-					if (op->arguments[0]->type == GDScriptParser::Node::TYPE_DICTIONARY) {
-						dn = static_cast<const GDScriptParser::DictionaryNode *>(op->arguments[0]);
-					} else if (base.assigned_expression && base.assigned_expression->type == GDScriptParser::Node::TYPE_DICTIONARY) {
-						dn = static_cast<const GDScriptParser::DictionaryNode *>(base.assigned_expression);
-					}
-
-					if (dn) {
-						for (int i = 0; i < dn->elements.size(); i++) {
-							GDScriptCompletionIdentifier key;
-							if (!_guess_expression_type(c, dn->elements[i].key, key)) {
-								continue;
-							}
-							if (key.value == String(id->name)) {
-								r_type.assigned_expression = dn->elements[i].value;
-								found = _guess_expression_type(c, dn->elements[i].value, r_type);
-								break;
-							}
-						}
-					}
-
-					if (!found) {
-						found = _guess_identifier_type_from_base(c, base, id->name, r_type);
-					}
-				} break;
-				case GDScriptParser::OperatorNode::OP_INDEX: {
-					if (op->arguments.size() < 2) {
-						found = false;
-						break;
-					}
-
-					GDScriptCompletionContext c = p_context;
-					c.line = op->line;
-
-					GDScriptCompletionIdentifier base;
-					if (!_guess_expression_type(c, op->arguments[0], base)) {
-						found = false;
-						break;
-					}
-
-					GDScriptCompletionIdentifier index;
-					if (!_guess_expression_type(c, op->arguments[1], index)) {
-						found = false;
-						break;
-					}
-
-					if (base.value.in(index.value)) {
-						Variant value = base.value.get(index.value);
-						r_type = _type_from_variant(value);
-						found = true;
-						break;
-					}
-
-					// Look if it is a dictionary node
-					const GDScriptParser::DictionaryNode *dn = nullptr;
-					if (op->arguments[0]->type == GDScriptParser::Node::TYPE_DICTIONARY) {
-						dn = static_cast<const GDScriptParser::DictionaryNode *>(op->arguments[0]);
-					} else if (base.assigned_expression && base.assigned_expression->type == GDScriptParser::Node::TYPE_DICTIONARY) {
-						dn = static_cast<const GDScriptParser::DictionaryNode *>(base.assigned_expression);
-					}
-
-					if (dn) {
-						for (int i = 0; i < dn->elements.size(); i++) {
-							GDScriptCompletionIdentifier key;
-							if (!_guess_expression_type(c, dn->elements[i].key, key)) {
-								continue;
-							}
-							if (key.value == index.value) {
-								r_type.assigned_expression = dn->elements[i].value;
-								found = _guess_expression_type(p_context, dn->elements[i].value, r_type);
-								break;
-							}
-						}
-					}
-
-					// Look if it is an array node
-					if (!found && index.value.is_num()) {
-						int idx = index.value;
-						const GDScriptParser::ArrayNode *an = nullptr;
-						if (op->arguments[0]->type == GDScriptParser::Node::TYPE_ARRAY) {
-							an = static_cast<const GDScriptParser::ArrayNode *>(op->arguments[0]);
-						} else if (base.assigned_expression && base.assigned_expression->type == GDScriptParser::Node::TYPE_ARRAY) {
-							an = static_cast<const GDScriptParser::ArrayNode *>(base.assigned_expression);
-						}
-
-						if (an && idx >= 0 && an->elements.size() > idx) {
-							r_type.assigned_expression = an->elements[idx];
-							found = _guess_expression_type(c, an->elements[idx], r_type);
-							break;
-						}
-					}
-
-					// Look for valid indexing in other types
-					if (!found && (index.value.get_type() == Variant::STRING || index.value.get_type() == Variant::NODE_PATH)) {
-						StringName id = index.value;
-						found = _guess_identifier_type_from_base(c, base, id, r_type);
-					} else if (!found && index.type.kind == GDScriptParser::DataType::BUILTIN) {
-						Callable::CallError err;
-						Variant base_val = Variant::construct(base.type.builtin_type, nullptr, 0, err);
-						bool valid = false;
-						Variant res = base_val.get(index.value, &valid);
-						if (valid) {
-							r_type = _type_from_variant(res);
-							r_type.value = Variant();
-							r_type.type.is_constant = false;
-							found = true;
-						}
-					}
-				} break;
-				default: {
-					if (op->arguments.size() < 2) {
-						found = false;
-						break;
-					}
-
-					Variant::Operator vop = Variant::OP_MAX;
-					switch (op->op) {
-						case GDScriptParser::OperatorNode::OP_ADD:
-							vop = Variant::OP_ADD;
-							break;
-						case GDScriptParser::OperatorNode::OP_SUB:
-							vop = Variant::OP_SUBTRACT;
-							break;
-						case GDScriptParser::OperatorNode::OP_MUL:
-							vop = Variant::OP_MULTIPLY;
-							break;
-						case GDScriptParser::OperatorNode::OP_DIV:
-							vop = Variant::OP_DIVIDE;
-							break;
-						case GDScriptParser::OperatorNode::OP_MOD:
-							vop = Variant::OP_MODULE;
-							break;
-						case GDScriptParser::OperatorNode::OP_SHIFT_LEFT:
-							vop = Variant::OP_SHIFT_LEFT;
-							break;
-						case GDScriptParser::OperatorNode::OP_SHIFT_RIGHT:
-							vop = Variant::OP_SHIFT_RIGHT;
-							break;
-						case GDScriptParser::OperatorNode::OP_BIT_AND:
-							vop = Variant::OP_BIT_AND;
-							break;
-						case GDScriptParser::OperatorNode::OP_BIT_OR:
-							vop = Variant::OP_BIT_OR;
-							break;
-						case GDScriptParser::OperatorNode::OP_BIT_XOR:
-							vop = Variant::OP_BIT_XOR;
-							break;
-						default: {
-						}
-					}
-
-					if (vop == Variant::OP_MAX) {
-						break;
-					}
-
-					GDScriptCompletionContext context = p_context;
-					context.line = op->line;
-
-					GDScriptCompletionIdentifier p1;
-					GDScriptCompletionIdentifier p2;
-
-					if (!_guess_expression_type(context, op->arguments[0], p1)) {
-						found = false;
-						break;
-					}
-
-					if (!_guess_expression_type(context, op->arguments[1], p2)) {
-						found = false;
-						break;
-					}
-
-					Callable::CallError ce;
-					bool v1_use_value = p1.value.get_type() != Variant::NIL && p1.value.get_type() != Variant::OBJECT;
-					Variant v1 = (v1_use_value) ? p1.value : Variant::construct(p1.type.builtin_type, nullptr, 0, ce);
-					bool v2_use_value = p2.value.get_type() != Variant::NIL && p2.value.get_type() != Variant::OBJECT;
-					Variant v2 = (v2_use_value) ? p2.value : Variant::construct(p2.type.builtin_type, nullptr, 0, ce);
-					// avoid potential invalid ops
-					if ((vop == Variant::OP_DIVIDE || vop == Variant::OP_MODULE) && v2.get_type() == Variant::INT) {
-						v2 = 1;
-						v2_use_value = false;
-					}
-					if (vop == Variant::OP_DIVIDE && v2.get_type() == Variant::FLOAT) {
-						v2 = 1.0;
-						v2_use_value = false;
-					}
-
-					Variant res;
-					bool valid;
-					Variant::evaluate(vop, v1, v2, res, valid);
-					if (!valid) {
-						found = false;
-						break;
-					}
-					r_type = _type_from_variant(res);
-					if (!v1_use_value || !v2_use_value) {
-						r_type.value = Variant();
-						r_type.type.is_constant = false;
-					}
-
-					found = true;
-				} break;
+			String space_indent = "";
+			for (int i = 0; i < indent_size; i++) {
+				space_indent += " ";
 			}
 			}
-		} break;
-		default: {
-		}
-	}
-
-	// It may have found a null, but that's never useful
-	if (found && r_type.type.has_type && r_type.type.kind == GDScriptParser::DataType::BUILTIN && r_type.type.builtin_type == Variant::NIL) {
-		found = false;
-	}
-
-	// Check type hint last. For collections we want chance to get the actual value first
-	// This way we can detect types from the content of dictionaries and arrays
-	if (!found && p_expression->get_datatype().has_type) {
-		r_type.type = p_expression->get_datatype();
-		if (!r_type.assigned_expression) {
-			r_type.assigned_expression = p_expression;
+			return space_indent;
 		}
 		}
-		found = true;
 	}
 	}
-
-	return found;
+#endif
+	return "\t";
 }
 }
 
 
-static bool _guess_identifier_type(GDScriptCompletionContext &p_context, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type) {
-	// Look in blocks first
-	const GDScriptParser::BlockNode *blk = p_context.block;
-	int last_assign_line = -1;
-	const GDScriptParser::Node *last_assigned_expression = nullptr;
-	GDScriptParser::DataType var_type;
-	while (blk) {
-		if (blk->variables.has(p_identifier)) {
-			if (blk->variables[p_identifier]->line > p_context.line) {
-				return false;
-			}
-
-			var_type = blk->variables[p_identifier]->datatype;
-
-			if (!last_assigned_expression && blk->variables[p_identifier]->assign && blk->variables[p_identifier]->assign->type == GDScriptParser::Node::TYPE_OPERATOR) {
-				const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(blk->variables[p_identifier]->assign);
-				if (op->op == GDScriptParser::OperatorNode::OP_ASSIGN && op->arguments.size() >= 2) {
-					last_assign_line = op->line;
-					last_assigned_expression = op->arguments[1];
-				}
-			}
-		}
-
-		for (const List<GDScriptParser::Node *>::Element *E = blk->statements.front(); E; E = E->next()) {
-			const GDScriptParser::Node *expr = E->get();
-			if (expr->line > p_context.line || expr->type != GDScriptParser::Node::TYPE_OPERATOR) {
-				continue;
-			}
-
-			const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(expr);
-			if (op->op != GDScriptParser::OperatorNode::OP_ASSIGN || op->line < last_assign_line) {
-				continue;
-			}
-
-			if (op->arguments.size() >= 2 && op->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER) {
-				const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0]);
-				if (id->name == p_identifier) {
-					last_assign_line = op->line;
-					last_assigned_expression = op->arguments[1];
-				}
-			}
-		}
-
-		if (blk->if_condition && blk->if_condition->type == GDScriptParser::Node::TYPE_OPERATOR && static_cast<const GDScriptParser::OperatorNode *>(blk->if_condition)->op == GDScriptParser::OperatorNode::OP_IS) {
-			//is used, check if identifier is in there! this helps resolve in blocks that are (if (identifier is value)): which are very common..
-			//super dirty hack, but very useful
-			//credit: Zylann
-			//TODO: this could be hacked to detect ANDed conditions too..
-			const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(blk->if_condition);
-			if (op->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER && static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0])->name == p_identifier) {
-				//bingo
-				GDScriptCompletionContext c = p_context;
-				c.line = op->line;
-				c.block = blk;
-				if (_guess_expression_type(p_context, op->arguments[1], r_type)) {
-					r_type.type.is_meta_type = false; // Right-hand of `is` will be a meta type, but the left-hand value is not
-					// Not an assignment, it shouldn't carry any value
-					r_type.value = Variant();
-					r_type.assigned_expression = nullptr;
-
-					return true;
-				}
-			}
-		}
-
-		blk = blk->parent_block;
-	}
-
-	if (last_assigned_expression && last_assign_line != p_context.line) {
-		GDScriptCompletionContext c = p_context;
-		c.line = last_assign_line;
-		r_type.assigned_expression = last_assigned_expression;
-		if (_guess_expression_type(c, last_assigned_expression, r_type)) {
-			if (var_type.has_type) {
-				r_type.type = var_type;
-			}
-			return true;
-		}
-	}
-
-	if (var_type.has_type) {
-		r_type.type = var_type;
-		return true;
-	}
+void GDScriptLanguage::auto_indent_code(String &p_code, int p_from_line, int p_to_line) const {
+	String indent = _get_indentation();
 
 
-	if (p_context.function) {
-		for (int i = 0; i < p_context.function->arguments.size(); i++) {
-			if (p_context.function->arguments[i] == p_identifier) {
-				if (p_context.function->argument_types[i].has_type) {
-					r_type.type = p_context.function->argument_types[i];
-					return true;
-				}
+	Vector<String> lines = p_code.split("\n");
+	List<int> indent_stack;
 
 
-				int def_from = p_context.function->arguments.size() - p_context.function->default_values.size();
-				if (i >= def_from) {
-					int def_idx = i - def_from;
-					if (p_context.function->default_values[def_idx]->type == GDScriptParser::Node::TYPE_OPERATOR) {
-						const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(p_context.function->default_values[def_idx]);
-						if (op->arguments.size() < 2) {
-							return false;
-						}
-						GDScriptCompletionContext c = p_context;
-						c.function = nullptr;
-						c.block = nullptr;
-						return _guess_expression_type(c, op->arguments[1], r_type);
-					}
-				}
+	for (int i = 0; i < lines.size(); i++) {
+		String l = lines[i];
+		int tc = 0;
+		for (int j = 0; j < l.length(); j++) {
+			if (l[j] == ' ' || l[j] == '\t') {
+				tc++;
+			} else {
 				break;
 				break;
 			}
 			}
 		}
 		}
 
 
-		GDScriptParser::DataType base_type = p_context._class->base_type;
-		while (base_type.has_type) {
-			switch (base_type.kind) {
-				case GDScriptParser::DataType::GDSCRIPT: {
-					Ref<GDScript> gds = base_type.script_type;
-					if (gds.is_valid() && gds->has_method(p_context.function->name)) {
-						GDScriptFunction *func = gds->get_member_functions()[p_context.function->name];
-						if (func) {
-							for (int i = 0; i < func->get_argument_count(); i++) {
-								if (func->get_argument_name(i) == p_identifier) {
-									r_type = _type_from_gdtype(func->get_argument_type(i));
-									return true;
-								}
-							}
-						}
-						Ref<GDScript> base_gds = gds->get_base_script();
-						if (base_gds.is_valid()) {
-							base_type.kind = GDScriptParser::DataType::GDSCRIPT;
-							base_type.script_type = base_gds;
-						} else {
-							base_type.kind = GDScriptParser::DataType::NATIVE;
-							base_type.native_type = gds->get_instance_base_type();
-						}
-					} else {
-						base_type.kind = GDScriptParser::DataType::NATIVE;
-						base_type.native_type = gds->get_instance_base_type();
-					}
-				} break;
-				case GDScriptParser::DataType::NATIVE: {
-					List<MethodInfo> methods;
-					ClassDB::get_method_list(base_type.native_type, &methods);
-					ClassDB::get_virtual_methods(base_type.native_type, &methods);
-
-					for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
-						if (E->get().name == p_context.function->name) {
-							MethodInfo &mi = E->get();
-							for (List<PropertyInfo>::Element *F = mi.arguments.front(); F; F = F->next()) {
-								if (F->get().name == p_identifier) {
-									r_type = _type_from_property(F->get());
-									return true;
-								}
-							}
-						}
-					}
-					base_type.has_type = false;
-				} break;
-				default: {
-					base_type.has_type = false;
-				} break;
-			}
-		}
-	}
-
-	// Check current class (including inheritance)
-	if (p_context._class) {
-		GDScriptCompletionIdentifier context_base;
-		context_base.value = p_context.base;
-		context_base.type.has_type = true;
-		context_base.type.kind = GDScriptParser::DataType::CLASS;
-		context_base.type.class_type = const_cast<GDScriptParser::ClassNode *>(p_context._class);
-		context_base.type.is_meta_type = p_context.function && p_context.function->_static;
-
-		if (_guess_identifier_type_from_base(p_context, context_base, p_identifier, r_type)) {
-			return true;
-		}
-	}
-
-	// Check named scripts
-	if (ScriptServer::is_global_class(p_identifier)) {
-		Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_identifier));
-		if (scr.is_valid()) {
-			r_type = _type_from_variant(scr);
-			r_type.type.is_meta_type = true;
-			return true;
+		String st = l.substr(tc, l.length()).strip_edges();
+		if (st == "" || st.begins_with("#")) {
+			continue; //ignore!
 		}
 		}
-		return false;
-	}
 
 
-	for (int i = 0; i < 2; i++) {
-		StringName target_id;
-		switch (i) {
-			case 0:
-				// Check ClassDB
-				target_id = p_identifier;
-				break;
-			case 1:
-				// ClassDB again for underscore-prefixed classes
-				target_id = String("_") + p_identifier;
-				break;
+		int ilevel = 0;
+		if (indent_stack.size()) {
+			ilevel = indent_stack.back()->get();
 		}
 		}
 
 
-		if (ClassDB::class_exists(target_id)) {
-			r_type.type.has_type = true;
-			r_type.type.kind = GDScriptParser::DataType::NATIVE;
-			r_type.type.native_type = target_id;
-			if (Engine::get_singleton()->has_singleton(target_id)) {
-				r_type.type.is_meta_type = false;
-				r_type.value = Engine::get_singleton()->get_singleton_object(target_id);
-			} else {
-				r_type.type.is_meta_type = true;
-				const Map<StringName, int>::Element *target_elem = GDScriptLanguage::get_singleton()->get_global_map().find(target_id);
-				// Check because classes like EditorNode are in ClassDB by now, but unknown to GDScript
-				if (!target_elem) {
-					return false;
-				}
-				int idx = target_elem->get();
-				r_type.value = GDScriptLanguage::get_singleton()->get_global_array()[idx];
+		if (tc > ilevel) {
+			indent_stack.push_back(tc);
+		} else if (tc < ilevel) {
+			while (indent_stack.size() && indent_stack.back()->get() > tc) {
+				indent_stack.pop_back();
 			}
 			}
-			return true;
-		}
-	}
-
-	// Check autoload singletons
-	if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(p_identifier)) {
-		r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[p_identifier]);
-		return true;
-	}
-
-	return false;
-}
-
-static bool _guess_identifier_type_from_base(GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type) {
-	GDScriptParser::DataType base_type = p_base.type;
-	bool _static = base_type.is_meta_type;
-	while (base_type.has_type) {
-		switch (base_type.kind) {
-			case GDScriptParser::DataType::CLASS: {
-				if (base_type.class_type->constant_expressions.has(p_identifier)) {
-					GDScriptParser::ClassNode::Constant c = base_type.class_type->constant_expressions[p_identifier];
-					r_type.type = c.type;
-					if (c.expression->type == GDScriptParser::Node::TYPE_CONSTANT) {
-						r_type.value = static_cast<const GDScriptParser::ConstantNode *>(c.expression)->value;
-					}
-					return true;
-				}
-
-				if (!_static) {
-					for (int i = 0; i < base_type.class_type->variables.size(); i++) {
-						GDScriptParser::ClassNode::Member m = base_type.class_type->variables[i];
-						if (m.identifier == p_identifier) {
-							if (m.expression) {
-								if (p_context.line == m.expression->line) {
-									// Variable used in the same expression
-									return false;
-								}
-
-								if (_guess_expression_type(p_context, m.expression, r_type)) {
-									return true;
-								}
-								if (m.expression->get_datatype().has_type) {
-									r_type.type = m.expression->get_datatype();
-									return true;
-								}
-							}
-							if (m.data_type.has_type) {
-								r_type.type = m.data_type;
-								return true;
-							}
-							return false;
-						}
-					}
-				}
-				base_type = base_type.class_type->base_type;
-			} break;
-			case GDScriptParser::DataType::GDSCRIPT: {
-				Ref<GDScript> gds = base_type.script_type;
-				if (gds.is_valid()) {
-					if (gds->get_constants().has(p_identifier)) {
-						r_type = _type_from_variant(gds->get_constants()[p_identifier]);
-						return true;
-					}
-					if (!_static) {
-						const Set<StringName>::Element *m = gds->get_members().find(p_identifier);
-						if (m) {
-							r_type = _type_from_gdtype(gds->get_member_type(p_identifier));
-							return true;
-						}
-					}
-					Ref<GDScript> parent = gds->get_base_script();
-					if (parent.is_valid()) {
-						base_type.script_type = parent;
-					} else {
-						base_type.kind = GDScriptParser::DataType::NATIVE;
-						base_type.native_type = gds->get_instance_base_type();
-					}
-				} else {
-					return false;
-				}
-			} break;
-			case GDScriptParser::DataType::SCRIPT: {
-				Ref<Script> scr = base_type.script_type;
-				if (scr.is_valid()) {
-					Map<StringName, Variant> constants;
-					scr->get_constants(&constants);
-					if (constants.has(p_identifier)) {
-						r_type = _type_from_variant(constants[p_identifier]);
-						return true;
-					}
-
-					if (!_static) {
-						List<PropertyInfo> members;
-						scr->get_script_property_list(&members);
-						for (const List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) {
-							const PropertyInfo &prop = E->get();
-							if (prop.name == p_identifier) {
-								r_type = _type_from_property(prop);
-								return true;
-							}
-						}
-					}
-					Ref<Script> parent = scr->get_base_script();
-					if (parent.is_valid()) {
-						base_type.script_type = parent;
-					} else {
-						base_type.kind = GDScriptParser::DataType::NATIVE;
-						base_type.native_type = scr->get_instance_base_type();
-					}
-				} else {
-					return false;
-				}
-			} break;
-			case GDScriptParser::DataType::NATIVE: {
-				StringName class_name = base_type.native_type;
-				if (!ClassDB::class_exists(class_name)) {
-					class_name = String("_") + class_name;
-					if (!ClassDB::class_exists(class_name)) {
-						return false;
-					}
-				}
-
-				// Skip constants since they're all integers. Type does not matter because int has no members
-
-				List<PropertyInfo> props;
-				ClassDB::get_property_list(class_name, &props);
-				for (const List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
-					const PropertyInfo &prop = E->get();
-					if (prop.name == p_identifier) {
-						StringName getter = ClassDB::get_property_getter(class_name, p_identifier);
-						if (getter != StringName()) {
-							MethodBind *g = ClassDB::get_method(class_name, getter);
-							if (g) {
-								r_type = _type_from_property(g->get_return_info());
-								return true;
-							}
-						} else {
-							r_type = _type_from_property(prop);
-							return true;
-						}
-						break;
-					}
-				}
-				return false;
-			} break;
-			case GDScriptParser::DataType::BUILTIN: {
-				Callable::CallError err;
-				Variant tmp = Variant::construct(base_type.builtin_type, nullptr, 0, err);
-
-				if (err.error != Callable::CallError::CALL_OK) {
-					return false;
-				}
-				bool valid = false;
-				Variant res = tmp.get(p_identifier, &valid);
-				if (valid) {
-					r_type = _type_from_variant(res);
-					r_type.value = Variant();
-					r_type.type.is_constant = false;
-					return true;
-				}
-				return false;
-			} break;
-			default: {
-				return false;
-			} break;
-		}
-	}
-
-	return false;
-}
-
-static bool _find_last_return_in_block(const GDScriptCompletionContext &p_context, int &r_last_return_line, const GDScriptParser::Node **r_last_returned_value) {
-	if (!p_context.block) {
-		return false;
-	}
-
-	for (int i = 0; i < p_context.block->statements.size(); i++) {
-		if (p_context.block->statements[i]->line < r_last_return_line) {
-			continue;
-		}
-		if (p_context.block->statements[i]->type != GDScriptParser::Node::TYPE_CONTROL_FLOW) {
-			continue;
-		}
 
 
-		const GDScriptParser::ControlFlowNode *cf = static_cast<const GDScriptParser::ControlFlowNode *>(p_context.block->statements[i]);
-		if (cf->cf_type == GDScriptParser::ControlFlowNode::CF_RETURN && cf->arguments.size() > 0) {
-			if (cf->line > r_last_return_line) {
-				r_last_return_line = cf->line;
-				*r_last_returned_value = cf->arguments[0];
+			if (indent_stack.size() && indent_stack.back()->get() != tc) {
+				indent_stack.push_back(tc); //this is not right but gets the job done
 			}
 			}
 		}
 		}
-	}
-
-	// Recurse into subblocks
-	for (int i = 0; i < p_context.block->sub_blocks.size(); i++) {
-		GDScriptCompletionContext c = p_context;
-		c.block = p_context.block->sub_blocks[i];
-		_find_last_return_in_block(c, r_last_return_line, r_last_returned_value);
-	}
-
-	return false;
-}
-
-static bool _guess_method_return_type_from_base(GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, GDScriptCompletionIdentifier &r_type) {
-	GDScriptParser::DataType base_type = p_base.type;
-	bool _static = base_type.is_meta_type;
-
-	if (_static && p_method == "new") {
-		r_type.type = base_type;
-		r_type.type.is_meta_type = false;
-		r_type.type.is_constant = false;
-		return true;
-	}
-
-	while (base_type.has_type) {
-		switch (base_type.kind) {
-			case GDScriptParser::DataType::CLASS: {
-				if (!base_type.class_type) {
-					base_type.has_type = false;
-					break;
-				}
-
-				for (int i = 0; i < base_type.class_type->static_functions.size(); i++) {
-					if (base_type.class_type->static_functions[i]->name == p_method) {
-						int last_return_line = -1;
-						const GDScriptParser::Node *last_returned_value = nullptr;
-						GDScriptCompletionContext c = p_context;
-						c._class = base_type.class_type;
-						c.function = base_type.class_type->static_functions[i];
-						c.block = c.function->body;
-
-						_find_last_return_in_block(c, last_return_line, &last_returned_value);
-						if (last_returned_value) {
-							c.line = c.block->end_line;
-							return _guess_expression_type(c, last_returned_value, r_type);
-						}
-					}
-				}
-				if (!_static) {
-					for (int i = 0; i < base_type.class_type->functions.size(); i++) {
-						if (base_type.class_type->functions[i]->name == p_method) {
-							int last_return_line = -1;
-							const GDScriptParser::Node *last_returned_value = nullptr;
-							GDScriptCompletionContext c = p_context;
-							c._class = base_type.class_type;
-							c.function = base_type.class_type->functions[i];
-							c.block = c.function->body;
-
-							_find_last_return_in_block(c, last_return_line, &last_returned_value);
-							if (last_returned_value) {
-								c.line = c.block->end_line;
-								return _guess_expression_type(c, last_returned_value, r_type);
-							}
-						}
-					}
-				}
-
-				base_type = base_type.class_type->base_type;
-			} break;
-			case GDScriptParser::DataType::GDSCRIPT: {
-				Ref<GDScript> gds = base_type.script_type;
-				if (gds.is_valid()) {
-					if (gds->get_member_functions().has(p_method)) {
-						r_type = _type_from_gdtype(gds->get_member_functions()[p_method]->get_return_type());
-						return true;
-					}
-					Ref<GDScript> base_script = gds->get_base_script();
-					if (base_script.is_valid()) {
-						base_type.script_type = base_script;
-					} else {
-						base_type.kind = GDScriptParser::DataType::NATIVE;
-						base_type.native_type = gds->get_instance_base_type();
-					}
-				} else {
-					return false;
-				}
-			} break;
-			case GDScriptParser::DataType::SCRIPT: {
-				Ref<Script> scr = base_type.script_type;
-				if (scr.is_valid()) {
-					List<MethodInfo> methods;
-					scr->get_script_method_list(&methods);
-					for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
-						MethodInfo &mi = E->get();
-						if (mi.name == p_method) {
-							r_type = _type_from_property(mi.return_val);
-							return true;
-						}
-					}
-					Ref<Script> base_script = scr->get_base_script();
-					if (base_script.is_valid()) {
-						base_type.script_type = base_script;
-					} else {
-						base_type.kind = GDScriptParser::DataType::NATIVE;
-						base_type.native_type = scr->get_instance_base_type();
-					}
-				} else {
-					return false;
-				}
-			} break;
-			case GDScriptParser::DataType::NATIVE: {
-				StringName native = base_type.native_type;
-				if (!ClassDB::class_exists(native)) {
-					native = String("_") + native;
-					if (!ClassDB::class_exists(native)) {
-						return false;
-					}
-				}
-				MethodBind *mb = ClassDB::get_method(native, p_method);
-				if (mb) {
-					r_type = _type_from_property(mb->get_return_info());
-					return true;
-				}
-				return false;
-			} break;
-			case GDScriptParser::DataType::BUILTIN: {
-				Callable::CallError err;
-				Variant tmp = Variant::construct(base_type.builtin_type, nullptr, 0, err);
-				if (err.error != Callable::CallError::CALL_OK) {
-					return false;
-				}
-
-				List<MethodInfo> methods;
-				tmp.get_method_list(&methods);
 
 
-				for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
-					MethodInfo &mi = E->get();
-					if (mi.name == p_method) {
-						r_type = _type_from_property(mi.return_val);
-						return true;
-					}
-				}
-				return false;
-			} break;
-			default: {
-				return false;
+		if (i >= p_from_line) {
+			l = "";
+			for (int j = 0; j < indent_stack.size(); j++) {
+				l += indent;
 			}
 			}
-		}
-	}
-	return false;
-}
-
-static String _make_arguments_hint(const MethodInfo &p_info, int p_arg_idx) {
-	String arghint = _get_visual_datatype(p_info.return_val, false) + " " + p_info.name + "(";
-
-	int def_args = p_info.arguments.size() - p_info.default_arguments.size();
-	int i = 0;
-	for (const List<PropertyInfo>::Element *E = p_info.arguments.front(); E; E = E->next()) {
-		if (i > 0) {
-			arghint += ", ";
-		}
-
-		if (i == p_arg_idx) {
-			arghint += String::chr(0xFFFF);
-		}
-		arghint += E->get().name + ": " + _get_visual_datatype(E->get(), true);
-
-		if (i - def_args >= 0) {
-			arghint += String(" = ") + p_info.default_arguments[i - def_args].get_construct_string();
-		}
+			l += st;
 
 
-		if (i == p_arg_idx) {
-			arghint += String::chr(0xFFFF);
+		} else if (i > p_to_line) {
+			break;
 		}
 		}
 
 
-		i++;
+		lines.write[i] = l;
 	}
 	}
 
 
-	if (p_info.flags & METHOD_FLAG_VARARG) {
-		if (p_info.arguments.size() > 0) {
-			arghint += ", ";
-		}
-		if (p_arg_idx >= p_info.arguments.size()) {
-			arghint += String::chr(0xFFFF);
-		}
-		arghint += "...";
-		if (p_arg_idx >= p_info.arguments.size()) {
-			arghint += String::chr(0xFFFF);
+	p_code = "";
+	for (int i = 0; i < lines.size(); i++) {
+		if (i > 0) {
+			p_code += "\n";
 		}
 		}
+		p_code += lines[i];
 	}
 	}
-
-	arghint += ")";
-
-	return arghint;
 }
 }
 
 
-static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_function, int p_arg_idx) {
-	String arghint = p_function->return_type.to_string() + " " + p_function->name.operator String() + "(";
-
-	int def_args = p_function->arguments.size() - p_function->default_values.size();
-	for (int i = 0; i < p_function->arguments.size(); i++) {
-		if (i > 0) {
-			arghint += ", ";
-		}
-
-		if (i == p_arg_idx) {
-			arghint += String::chr(0xFFFF);
-		}
-		arghint += p_function->arguments[i].operator String() + ": " + p_function->argument_types[i].to_string();
-
-		if (i - def_args >= 0) {
-			String def_val = "<unknown>";
-			if (p_function->default_values[i - def_args] && p_function->default_values[i - def_args]->type == GDScriptParser::Node::TYPE_OPERATOR) {
-				const GDScriptParser::OperatorNode *assign = static_cast<const GDScriptParser::OperatorNode *>(p_function->default_values[i - def_args]);
-
-				if (assign->arguments.size() >= 2) {
-					if (assign->arguments[1]->type == GDScriptParser::Node::TYPE_CONSTANT) {
-						const GDScriptParser::ConstantNode *cn = static_cast<const GDScriptParser::ConstantNode *>(assign->arguments[1]);
-						def_val = cn->value.get_construct_string();
-					} else if (assign->arguments[1]->type == GDScriptParser::Node::TYPE_IDENTIFIER) {
-						const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(assign->arguments[1]);
-						def_val = id->name.operator String();
-					}
-				}
-			}
-			arghint += " = " + def_val;
-		}
-		if (i == p_arg_idx) {
-			arghint += String::chr(0xFFFF);
-		}
-	}
-
-	arghint += ")";
-
-	return arghint;
-}
-
-static void _find_enumeration_candidates(const String p_enum_hint, Map<String, ScriptCodeCompletionOption> &r_result) {
-	if (p_enum_hint.find(".") == -1) {
-		// Global constant
-		StringName current_enum = p_enum_hint;
-		for (int i = 0; i < GlobalConstants::get_global_constant_count(); i++) {
-			if (GlobalConstants::get_global_constant_enum(i) == current_enum) {
-				ScriptCodeCompletionOption option(GlobalConstants::get_global_constant_name(i), ScriptCodeCompletionOption::KIND_ENUM);
-				r_result.insert(option.display, option);
-			}
-		}
-	} else {
-		String class_name = p_enum_hint.get_slice(".", 0);
-		String enum_name = p_enum_hint.get_slice(".", 1);
-
-		if (!ClassDB::class_exists(class_name)) {
-			return;
-		}
-
-		List<StringName> enum_constants;
-		ClassDB::get_enum_constants(class_name, enum_name, &enum_constants);
-		for (List<StringName>::Element *E = enum_constants.front(); E; E = E->next()) {
-			String candidate = class_name + "." + E->get();
-			ScriptCodeCompletionOption option(candidate, ScriptCodeCompletionOption::KIND_ENUM);
-			r_result.insert(option.display, option);
-		}
-	}
-}
-
-static void _find_identifiers_in_block(const GDScriptCompletionContext &p_context, Map<String, ScriptCodeCompletionOption> &r_result) {
-	for (Map<StringName, GDScriptParser::LocalVarNode *>::Element *E = p_context.block->variables.front(); E; E = E->next()) {
-		if (E->get()->line < p_context.line) {
-			ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_VARIABLE);
-			r_result.insert(option.display, option);
-		}
-	}
-	if (p_context.block->parent_block) {
-		GDScriptCompletionContext c = p_context;
-		c.block = p_context.block->parent_block;
-		_find_identifiers_in_block(c, r_result);
-	}
-}
-
-static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result);
-
-static void _find_identifiers_in_class(const GDScriptCompletionContext &p_context, bool p_static, bool p_only_functions, bool p_parent_only, Map<String, ScriptCodeCompletionOption> &r_result) {
-	if (!p_parent_only) {
-		if (!p_static && !p_only_functions) {
-			for (int i = 0; i < p_context._class->variables.size(); i++) {
-				ScriptCodeCompletionOption option(p_context._class->variables[i].identifier, ScriptCodeCompletionOption::KIND_MEMBER);
-				r_result.insert(option.display, option);
-			}
-		}
-
-		if (!p_only_functions) {
-			for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_context._class->constant_expressions.front(); E; E = E->next()) {
-				ScriptCodeCompletionOption option(E->key(), ScriptCodeCompletionOption::KIND_CONSTANT);
-				r_result.insert(option.display, option);
-			}
-			for (int i = 0; i < p_context._class->subclasses.size(); i++) {
-				ScriptCodeCompletionOption option(p_context._class->subclasses[i]->name, ScriptCodeCompletionOption::KIND_CLASS);
-				r_result.insert(option.display, option);
-			}
-		}
-
-		for (int i = 0; i < p_context._class->static_functions.size(); i++) {
-			ScriptCodeCompletionOption option(p_context._class->static_functions[i]->name.operator String(), ScriptCodeCompletionOption::KIND_FUNCTION);
-			if (p_context._class->static_functions[i]->arguments.size()) {
-				option.insert_text += "(";
-			} else {
-				option.insert_text += "()";
-			}
-			r_result.insert(option.display, option);
-		}
-
-		if (!p_static) {
-			for (int i = 0; i < p_context._class->functions.size(); i++) {
-				ScriptCodeCompletionOption option(p_context._class->functions[i]->name.operator String(), ScriptCodeCompletionOption::KIND_FUNCTION);
-				if (p_context._class->functions[i]->arguments.size()) {
-					option.insert_text += "(";
-				} else {
-					option.insert_text += "()";
-				}
-				r_result.insert(option.display, option);
-			}
-		}
-	}
-
-	// Parents
-	GDScriptCompletionIdentifier base_type;
-	base_type.type = p_context._class->base_type;
-	base_type.type.is_meta_type = p_static;
-	base_type.value = p_context.base;
-
-	GDScriptCompletionContext c = p_context;
-	c.block = nullptr;
-	c.function = nullptr;
-
-	_find_identifiers_in_base(c, base_type, p_only_functions, r_result);
-}
-
-static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result) {
-	GDScriptParser::DataType base_type = p_base.type;
-	bool _static = base_type.is_meta_type;
-
-	if (_static && base_type.kind != GDScriptParser::DataType::BUILTIN) {
-		ScriptCodeCompletionOption option("new", ScriptCodeCompletionOption::KIND_FUNCTION);
-		option.insert_text += "(";
-		r_result.insert(option.display, option);
-	}
-
-	while (base_type.has_type) {
-		switch (base_type.kind) {
-			case GDScriptParser::DataType::CLASS: {
-				GDScriptCompletionContext c = p_context;
-				c._class = base_type.class_type;
-				c.block = nullptr;
-				c.function = nullptr;
-				_find_identifiers_in_class(c, _static, p_only_functions, false, r_result);
-				base_type = base_type.class_type->base_type;
-			} break;
-			case GDScriptParser::DataType::GDSCRIPT: {
-				Ref<GDScript> script = base_type.script_type;
-				if (script.is_valid()) {
-					if (!_static && !p_only_functions) {
-						if (p_context.base && p_context.base->get_script_instance()) {
-							List<PropertyInfo> members;
-							p_context.base->get_script_instance()->get_property_list(&members);
-							for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) {
-								ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER);
-								r_result.insert(option.display, option);
-							}
-						}
-						for (const Set<StringName>::Element *E = script->get_members().front(); E; E = E->next()) {
-							ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_MEMBER);
-							r_result.insert(option.display, option);
-						}
-					}
-					if (!p_only_functions) {
-						for (const Map<StringName, Variant>::Element *E = script->get_constants().front(); E; E = E->next()) {
-							ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CONSTANT);
-							r_result.insert(option.display, option);
-						}
-					}
-					for (const Map<StringName, GDScriptFunction *>::Element *E = script->get_member_functions().front(); E; E = E->next()) {
-						if (!_static || E->get()->is_static()) {
-							ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_FUNCTION);
-							if (E->get()->get_argument_count()) {
-								option.insert_text += "(";
-							} else {
-								option.insert_text += "()";
-							}
-							r_result.insert(option.display, option);
-						}
-					}
-					if (!p_only_functions) {
-						for (const Map<StringName, Ref<GDScript>>::Element *E = script->get_subclasses().front(); E; E = E->next()) {
-							ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS);
-							r_result.insert(option.display, option);
-						}
-					}
-					base_type = GDScriptParser::DataType();
-					if (script->get_base().is_valid()) {
-						base_type.has_type = true;
-						base_type.kind = GDScriptParser::DataType::GDSCRIPT;
-						base_type.script_type = script->get_base();
-					} else {
-						base_type.has_type = script->get_instance_base_type() != StringName();
-						base_type.kind = GDScriptParser::DataType::NATIVE;
-						base_type.native_type = script->get_instance_base_type();
-					}
-				} else {
-					return;
-				}
-			} break;
-			case GDScriptParser::DataType::SCRIPT: {
-				Ref<Script> scr = base_type.script_type;
-				if (scr.is_valid()) {
-					if (!_static && !p_only_functions) {
-						List<PropertyInfo> members;
-						scr->get_script_property_list(&members);
-						for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) {
-							ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER);
-							r_result.insert(option.display, option);
-						}
-					}
-					if (!p_only_functions) {
-						Map<StringName, Variant> constants;
-						scr->get_constants(&constants);
-						for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) {
-							ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CONSTANT);
-							r_result.insert(option.display, option);
-						}
-					}
-
-					List<MethodInfo> methods;
-					scr->get_script_method_list(&methods);
-					for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
-						ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_FUNCTION);
-						if (E->get().arguments.size()) {
-							option.insert_text += "(";
-						} else {
-							option.insert_text += "()";
-						}
-						r_result.insert(option.display, option);
-					}
-
-					Ref<Script> base_script = scr->get_base_script();
-					if (base_script.is_valid()) {
-						base_type.script_type = base_script;
-					} else {
-						base_type.kind = GDScriptParser::DataType::NATIVE;
-						base_type.native_type = scr->get_instance_base_type();
-					}
-				} else {
-					return;
-				}
-			} break;
-			case GDScriptParser::DataType::NATIVE: {
-				StringName type = base_type.native_type;
-				if (!ClassDB::class_exists(type)) {
-					type = String("_") + type;
-					if (!ClassDB::class_exists(type)) {
-						return;
-					}
-				}
-
-				if (!p_only_functions) {
-					List<String> constants;
-					ClassDB::get_integer_constant_list(type, &constants);
-					for (List<String>::Element *E = constants.front(); E; E = E->next()) {
-						ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_CONSTANT);
-						r_result.insert(option.display, option);
-					}
-
-					if (!_static) {
-						List<PropertyInfo> pinfo;
-						ClassDB::get_property_list(type, &pinfo);
-						for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) {
-							if (E->get().usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP | PROPERTY_USAGE_CATEGORY)) {
-								continue;
-							}
-							if (E->get().name.find("/") != -1) {
-								continue;
-							}
-							ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER);
-							r_result.insert(option.display, option);
-						}
-					}
-				}
-
-				if (!_static) {
-					List<MethodInfo> methods;
-					bool is_autocompleting_getters = GLOBAL_GET("debug/gdscript/completion/autocomplete_setters_and_getters").booleanize();
-					ClassDB::get_method_list(type, &methods, false, !is_autocompleting_getters);
-					for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
-						if (E->get().name.begins_with("_")) {
-							continue;
-						}
-						ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_FUNCTION);
-						if (E->get().arguments.size()) {
-							option.insert_text += "(";
-						} else {
-							option.insert_text += "()";
-						}
-						r_result.insert(option.display, option);
-					}
-				}
-
-				return;
-			} break;
-			case GDScriptParser::DataType::BUILTIN: {
-				Callable::CallError err;
-				Variant tmp = Variant::construct(base_type.builtin_type, nullptr, 0, err);
-				if (err.error != Callable::CallError::CALL_OK) {
-					return;
-				}
-
-				if (!p_only_functions) {
-					List<PropertyInfo> members;
-					if (p_base.value.get_type() != Variant::NIL) {
-						p_base.value.get_property_list(&members);
-					} else {
-						tmp.get_property_list(&members);
-					}
-
-					for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) {
-						if (String(E->get().name).find("/") == -1) {
-							ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER);
-							r_result.insert(option.display, option);
-						}
-					}
-				}
-
-				List<MethodInfo> methods;
-				tmp.get_method_list(&methods);
-				for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
-					ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_FUNCTION);
-					if (E->get().arguments.size()) {
-						option.insert_text += "(";
-					} else {
-						option.insert_text += "()";
-					}
-					r_result.insert(option.display, option);
-				}
-
-				return;
-			} break;
-			default: {
-				return;
-			} break;
-		}
-	}
-}
-
-static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result) {
-	const GDScriptParser::BlockNode *block = p_context.block;
-
-	if (p_context.function) {
-		const GDScriptParser::FunctionNode *f = p_context.function;
-
-		for (int i = 0; i < f->arguments.size(); i++) {
-			ScriptCodeCompletionOption option(f->arguments[i].operator String(), ScriptCodeCompletionOption::KIND_PLAIN_TEXT);
-			r_result.insert(option.display, option);
-		}
-	}
-
-	if (!p_only_functions && block) {
-		GDScriptCompletionContext c = p_context;
-		c.block = block;
-		_find_identifiers_in_block(c, r_result);
-	}
-
-	const GDScriptParser::ClassNode *clss = p_context._class;
-	bool _static = p_context.function && p_context.function->_static;
-
-	while (clss) {
-		GDScriptCompletionContext c = p_context;
-		c._class = clss;
-		c.block = nullptr;
-		c.function = nullptr;
-		_find_identifiers_in_class(c, _static, p_only_functions, false, r_result);
-		_static = true;
-		clss = clss->owner;
-	}
-
-	for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) {
-		MethodInfo mi = GDScriptFunctions::get_info(GDScriptFunctions::Function(i));
-		ScriptCodeCompletionOption option(String(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))), ScriptCodeCompletionOption::KIND_FUNCTION);
-		if (mi.arguments.size() || (mi.flags & METHOD_FLAG_VARARG)) {
-			option.insert_text += "(";
-		} else {
-			option.insert_text += "()";
-		}
-		r_result.insert(option.display, option);
-	}
-
-	static const char *_type_names[Variant::VARIANT_MAX] = {
-		"null", "bool", "int", "float", "String", "Vector2", "Vector2i", "Rect2", "Rect2i", "Vector3", "Vector3i", "Transform2D", "Plane", "Quat", "AABB", "Basis", "Transform",
-		"Color", "StringName", "NodePath", "RID", "Object", "Callable", "Signal", "Dictionary", "Array", "PackedByteArray", "PackedInt32Array", "PackedInt64Array", "PackedFloat32Array", "PackedFloat64Array", "PackedStringArray",
-		"PackedVector2Array", "PackedVector3Array", "PackedColorArray"
-	};
-
-	for (int i = 0; i < Variant::VARIANT_MAX; i++) {
-		ScriptCodeCompletionOption option(_type_names[i], ScriptCodeCompletionOption::KIND_CLASS);
-		r_result.insert(option.display, option);
-	}
-
-	static const char *_keywords[] = {
-		"and", "in", "not", "or", "false", "PI", "TAU", "INF", "NAN", "self", "true", "as", "assert",
-		"breakpoint", "class", "extends", "is", "func", "preload", "setget", "signal", "tool", "yield",
-		"const", "enum", "export", "onready", "static", "var", "break", "continue", "if", "elif",
-		"else", "for", "pass", "return", "match", "while", "remote", "master", "puppet",
-		"remotesync", "mastersync", "puppetsync",
-		nullptr
-	};
-
-	const char **kw = _keywords;
-	while (*kw) {
-		ScriptCodeCompletionOption option(*kw, ScriptCodeCompletionOption::KIND_PLAIN_TEXT);
-		r_result.insert(option.display, option);
-		kw++;
-	}
-
-	// Autoload singletons
-	List<PropertyInfo> props;
-	ProjectSettings::get_singleton()->get_property_list(&props);
-	for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
-		String s = E->get().name;
-		if (!s.begins_with("autoload/")) {
-			continue;
-		}
-		String path = ProjectSettings::get_singleton()->get(s);
-		if (path.begins_with("*")) {
-			ScriptCodeCompletionOption option(s.get_slice("/", 1), ScriptCodeCompletionOption::KIND_CONSTANT);
-			r_result.insert(option.display, option);
-		}
-	}
-
-	// Named scripts
-	List<StringName> named_scripts;
-	ScriptServer::get_global_class_list(&named_scripts);
-	for (List<StringName>::Element *E = named_scripts.front(); E; E = E->next()) {
-		ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_CLASS);
-		r_result.insert(option.display, option);
-	}
-
-	// Native classes
-	for (const Map<StringName, int>::Element *E = GDScriptLanguage::get_singleton()->get_global_map().front(); E; E = E->next()) {
-		ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS);
-		r_result.insert(option.display, option);
-	}
-}
-
-static void _find_call_arguments(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, int p_argidx, bool p_static, Map<String, ScriptCodeCompletionOption> &r_result, String &r_arghint) {
-	Variant base = p_base.value;
-	GDScriptParser::DataType base_type = p_base.type;
-
-	const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\"";
-
-#define IS_METHOD_SIGNAL(m_method) (m_method == "connect" || m_method == "disconnect" || m_method == "is_connected" || m_method == "emit_signal")
-
-	while (base_type.has_type) {
-		switch (base_type.kind) {
-			case GDScriptParser::DataType::CLASS: {
-				for (int i = 0; i < base_type.class_type->static_functions.size(); i++) {
-					if (base_type.class_type->static_functions[i]->name == p_method) {
-						r_arghint = _make_arguments_hint(base_type.class_type->static_functions[i], p_argidx);
-						return;
-					}
-				}
-				for (int i = 0; i < base_type.class_type->functions.size(); i++) {
-					if (base_type.class_type->functions[i]->name == p_method) {
-						r_arghint = _make_arguments_hint(base_type.class_type->functions[i], p_argidx);
-						return;
-					}
-				}
-
-				if (IS_METHOD_SIGNAL(p_method) && p_argidx == 0) {
-					for (int i = 0; i < base_type.class_type->_signals.size(); i++) {
-						ScriptCodeCompletionOption option(base_type.class_type->_signals[i].name.operator String(), ScriptCodeCompletionOption::KIND_SIGNAL);
-						option.insert_text = quote_style + option.display + quote_style;
-						r_result.insert(option.display, option);
-					}
-				}
-
-				base_type = base_type.class_type->base_type;
-			} break;
-			case GDScriptParser::DataType::GDSCRIPT: {
-				Ref<GDScript> gds = base_type.script_type;
-				if (gds.is_valid()) {
-					if (IS_METHOD_SIGNAL(p_method) && p_argidx == 0) {
-						List<MethodInfo> signals;
-						gds->get_script_signal_list(&signals);
-						for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) {
-							ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_SIGNAL);
-							option.insert_text = quote_style + option.display + quote_style;
-							r_result.insert(option.display, option);
-						}
-					}
-					Ref<GDScript> base_script = gds->get_base_script();
-					if (base_script.is_valid()) {
-						base_type.script_type = base_script;
-					} else {
-						base_type.kind = GDScriptParser::DataType::NATIVE;
-						base_type.native_type = gds->get_instance_base_type();
-					}
-				} else {
-					return;
-				}
-			} break;
-			case GDScriptParser::DataType::NATIVE: {
-				StringName class_name = base_type.native_type;
-				if (!ClassDB::class_exists(class_name)) {
-					class_name = String("_") + class_name;
-					if (!ClassDB::class_exists(class_name)) {
-						base_type.has_type = false;
-						break;
-					}
-				}
-
-				List<MethodInfo> methods;
-				ClassDB::get_method_list(class_name, &methods);
-				ClassDB::get_virtual_methods(class_name, &methods);
-				int method_args = 0;
-
-				for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
-					if (E->get().name == p_method) {
-						method_args = E->get().arguments.size();
-						if (base.get_type() == Variant::OBJECT) {
-							Object *obj = base.operator Object *();
-							if (obj) {
-								List<String> options;
-								obj->get_argument_options(p_method, p_argidx, &options);
-								for (List<String>::Element *F = options.front(); F; F = F->next()) {
-									ScriptCodeCompletionOption option(F->get(), ScriptCodeCompletionOption::KIND_FUNCTION);
-									r_result.insert(option.display, option);
-								}
-							}
-						}
-
-						if (p_argidx < method_args) {
-							PropertyInfo arg_info = E->get().arguments[p_argidx];
-							if (arg_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
-								_find_enumeration_candidates(arg_info.class_name, r_result);
-							}
-						}
-
-						r_arghint = _make_arguments_hint(E->get(), p_argidx);
-						break;
-					}
-				}
-
-				if (IS_METHOD_SIGNAL(p_method) && p_argidx == 0) {
-					List<MethodInfo> signals;
-					ClassDB::get_signal_list(class_name, &signals);
-					for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) {
-						ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_SIGNAL);
-						option.insert_text = quote_style + option.display + quote_style;
-						r_result.insert(option.display, option);
-					}
-				}
-#undef IS_METHOD_SIGNAL
-
-				if (ClassDB::is_parent_class(class_name, "Node") && (p_method == "get_node" || p_method == "has_node") && p_argidx == 0) {
-					// Get autoloads
-					List<PropertyInfo> props;
-					ProjectSettings::get_singleton()->get_property_list(&props);
-
-					for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
-						String s = E->get().name;
-						if (!s.begins_with("autoload/")) {
-							continue;
-						}
-						String name = s.get_slice("/", 1);
-						ScriptCodeCompletionOption option("/root/" + name, ScriptCodeCompletionOption::KIND_NODE_PATH);
-						option.insert_text = quote_style + option.display + quote_style;
-						r_result.insert(option.display, option);
-					}
-				}
-
-				if (p_argidx == 0 && method_args > 0 && ClassDB::is_parent_class(class_name, "InputEvent") && p_method.operator String().find("action") != -1) {
-					// Get input actions
-					List<PropertyInfo> props;
-					ProjectSettings::get_singleton()->get_property_list(&props);
-					for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
-						String s = E->get().name;
-						if (!s.begins_with("input/")) {
-							continue;
-						}
-						String name = s.get_slice("/", 1);
-						ScriptCodeCompletionOption option(name, ScriptCodeCompletionOption::KIND_CONSTANT);
-						option.insert_text = quote_style + option.display + quote_style;
-						r_result.insert(option.display, option);
-					}
-				}
-
-				base_type.has_type = false;
-			} break;
-			case GDScriptParser::DataType::BUILTIN: {
-				if (base.get_type() == Variant::NIL) {
-					Callable::CallError err;
-					base = Variant::construct(base_type.builtin_type, nullptr, 0, err);
-					if (err.error != Callable::CallError::CALL_OK) {
-						return;
-					}
-				}
-
-				List<MethodInfo> methods;
-				base.get_method_list(&methods);
-				for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
-					if (E->get().name == p_method) {
-						r_arghint = _make_arguments_hint(E->get(), p_argidx);
-						return;
-					}
-				}
-
-				base_type.has_type = false;
-			} break;
-			default: {
-				base_type.has_type = false;
-			} break;
-		}
-	}
-}
-
-static void _find_call_arguments(GDScriptCompletionContext &p_context, const GDScriptParser::Node *p_node, int p_argidx, Map<String, ScriptCodeCompletionOption> &r_result, bool &r_forced, String &r_arghint) {
-	const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\"";
-
-	if (!p_node || p_node->type != GDScriptParser::Node::TYPE_OPERATOR) {
-		return;
-	}
-
-	Variant base;
-	GDScriptParser::DataType base_type;
-	StringName function;
-	bool _static = false;
-	const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(p_node);
-
-	GDScriptCompletionIdentifier connect_base;
-
-	if (op->op != GDScriptParser::OperatorNode::OP_CALL && op->op != GDScriptParser::OperatorNode::OP_PARENT_CALL) {
-		return;
-	}
-
-	if (!op->arguments.size()) {
-		return;
-	}
-
-	if (op->op == GDScriptParser::OperatorNode::OP_CALL) {
-		if (op->arguments[0]->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) {
-			// Complete built-in function
-			const GDScriptParser::BuiltInFunctionNode *fn = static_cast<const GDScriptParser::BuiltInFunctionNode *>(op->arguments[0]);
-			MethodInfo mi = GDScriptFunctions::get_info(fn->function);
-
-			if ((mi.name == "load" || mi.name == "preload") && bool(EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths"))) {
-				_get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), r_result);
-			}
-
-			r_arghint = _make_arguments_hint(mi, p_argidx);
-			return;
-
-		} else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_TYPE) {
-			// Complete constructor
-			const GDScriptParser::TypeNode *tn = static_cast<const GDScriptParser::TypeNode *>(op->arguments[0]);
-
-			List<MethodInfo> constructors;
-			Variant::get_constructor_list(tn->vtype, &constructors);
-
-			int i = 0;
-			for (List<MethodInfo>::Element *E = constructors.front(); E; E = E->next()) {
-				if (p_argidx >= E->get().arguments.size()) {
-					continue;
-				}
-				if (i > 0) {
-					r_arghint += "\n";
-				}
-				r_arghint += _make_arguments_hint(E->get(), p_argidx);
-				i++;
-			}
-			return;
-		} else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_SELF) {
-			if (op->arguments.size() < 2 || op->arguments[1]->type != GDScriptParser::Node::TYPE_IDENTIFIER) {
-				return;
-			}
-
-			base = p_context.base;
-
-			const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1]);
-			function = id->name;
-			base_type.has_type = true;
-			base_type.kind = GDScriptParser::DataType::CLASS;
-			base_type.class_type = const_cast<GDScriptParser::ClassNode *>(p_context._class);
-			_static = p_context.function && p_context.function->_static;
-
-			if (function == "connect" && op->arguments.size() >= 4) {
-				_guess_expression_type(p_context, op->arguments[3], connect_base);
-			}
-
-		} else {
-			if (op->arguments.size() < 2 || op->arguments[1]->type != GDScriptParser::Node::TYPE_IDENTIFIER) {
-				return;
-			}
-			const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1]);
-			function = id->name;
-
-			GDScriptCompletionIdentifier ci;
-			if (_guess_expression_type(p_context, op->arguments[0], ci)) {
-				base_type = ci.type;
-				base = ci.value;
-			} else {
-				return;
-			}
-			_static = ci.type.is_meta_type;
-
-			if (function == "connect" && op->arguments.size() >= 4) {
-				_guess_expression_type(p_context, op->arguments[3], connect_base);
-			}
-		}
-	} else {
-		if (!p_context._class || op->arguments.size() < 1 || op->arguments[0]->type != GDScriptParser::Node::TYPE_IDENTIFIER) {
-			return;
-		}
-		base_type.has_type = true;
-		base_type.kind = GDScriptParser::DataType::CLASS;
-		base_type.class_type = const_cast<GDScriptParser::ClassNode *>(p_context._class);
-		base_type.is_meta_type = p_context.function && p_context.function->_static;
-		base = p_context.base;
-
-		function = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0])->name;
-
-		if (function == "connect" && op->arguments.size() >= 4) {
-			_guess_expression_type(p_context, op->arguments[3], connect_base);
-		}
-	}
-
-	GDScriptCompletionIdentifier ci;
-	ci.type = base_type;
-	ci.value = base;
-	_find_call_arguments(p_context, ci, function, p_argidx, _static, r_result, r_arghint);
-
-	if (function == "connect" && p_argidx == 2) {
-		Map<String, ScriptCodeCompletionOption> methods;
-		_find_identifiers_in_base(p_context, connect_base, true, methods);
-		for (Map<String, ScriptCodeCompletionOption>::Element *E = methods.front(); E; E = E->next()) {
-			ScriptCodeCompletionOption &option = E->value();
-			option.insert_text = quote_style + option.display + quote_style;
-			r_result.insert(option.display, option);
-		}
-	}
-
-	r_forced = r_result.size() > 0;
-}
-
-Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint) {
-	const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\"";
-
-	GDScriptParser parser;
-
-	parser.parse(p_code, p_path.get_base_dir(), false, p_path, true);
-	r_forced = false;
-	Map<String, ScriptCodeCompletionOption> options;
-	GDScriptCompletionContext context;
-	context._class = parser.get_completion_class();
-	context.block = parser.get_completion_block();
-	context.function = parser.get_completion_function();
-	context.line = parser.get_completion_line();
-
-	if (!context._class || context._class->owner == nullptr) {
-		context.base = p_owner;
-		context.base_path = p_path.get_base_dir();
-	}
-
-	bool is_function = false;
-
-	switch (parser.get_completion_type()) {
-		case GDScriptParser::COMPLETION_NONE: {
-		} break;
-		case GDScriptParser::COMPLETION_BUILT_IN_TYPE_CONSTANT: {
-			List<StringName> constants;
-			Variant::get_constants_for_type(parser.get_completion_built_in_constant(), &constants);
-			for (List<StringName>::Element *E = constants.front(); E; E = E->next()) {
-				ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_CONSTANT);
-				options.insert(option.display, option);
-			}
-		} break;
-		case GDScriptParser::COMPLETION_PARENT_FUNCTION: {
-			_find_identifiers_in_class(context, !context.function || context.function->_static, true, true, options);
-		} break;
-		case GDScriptParser::COMPLETION_FUNCTION: {
-			is_function = true;
-			[[fallthrough]];
-		}
-		case GDScriptParser::COMPLETION_IDENTIFIER: {
-			_find_identifiers(context, is_function, options);
-		} break;
-		case GDScriptParser::COMPLETION_GET_NODE: {
-			if (p_owner) {
-				List<String> opts;
-				p_owner->get_argument_options("get_node", 0, &opts);
-
-				for (List<String>::Element *E = opts.front(); E; E = E->next()) {
-					String opt = E->get().strip_edges();
-					if (opt.is_quoted()) {
-						r_forced = true;
-						String idopt = opt.unquote();
-						if (idopt.replace("/", "_").is_valid_identifier()) {
-							ScriptCodeCompletionOption option(idopt, ScriptCodeCompletionOption::KIND_NODE_PATH);
-							options.insert(option.display, option);
-						} else {
-							ScriptCodeCompletionOption option(opt, ScriptCodeCompletionOption::KIND_NODE_PATH);
-							options.insert(option.display, option);
-						}
-					}
-				}
-
-				// Get autoloads
-				List<PropertyInfo> props;
-				ProjectSettings::get_singleton()->get_property_list(&props);
-
-				for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
-					String s = E->get().name;
-					if (!s.begins_with("autoload/")) {
-						continue;
-					}
-					String name = s.get_slice("/", 1);
-					ScriptCodeCompletionOption option(quote_style + "/root/" + name + quote_style, ScriptCodeCompletionOption::KIND_NODE_PATH);
-					options.insert(option.display, option);
-				}
-			}
-		} break;
-		case GDScriptParser::COMPLETION_METHOD: {
-			is_function = true;
-			[[fallthrough]];
-		}
-		case GDScriptParser::COMPLETION_INDEX: {
-			const GDScriptParser::Node *node = parser.get_completion_node();
-			if (node->type != GDScriptParser::Node::TYPE_OPERATOR) {
-				break;
-			}
-			const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(node);
-			if (op->arguments.size() < 1) {
-				break;
-			}
-
-			GDScriptCompletionIdentifier base;
-			if (!_guess_expression_type(context, op->arguments[0], base)) {
-				break;
-			}
-
-			GDScriptCompletionContext c = context;
-			c.function = nullptr;
-			c.block = nullptr;
-			c.base = base.value.get_type() == Variant::OBJECT ? base.value.operator Object *() : nullptr;
-			if (base.type.kind == GDScriptParser::DataType::CLASS) {
-				c._class = base.type.class_type;
-			} else {
-				c._class = nullptr;
-			}
-
-			_find_identifiers_in_base(c, base, is_function, options);
-		} break;
-		case GDScriptParser::COMPLETION_CALL_ARGUMENTS: {
-			_find_call_arguments(context, parser.get_completion_node(), parser.get_completion_argument_index(), options, r_forced, r_call_hint);
-		} break;
-		case GDScriptParser::COMPLETION_VIRTUAL_FUNC: {
-			GDScriptParser::DataType native_type = context._class->base_type;
-			while (native_type.has_type && native_type.kind != GDScriptParser::DataType::NATIVE) {
-				switch (native_type.kind) {
-					case GDScriptParser::DataType::CLASS: {
-						native_type = native_type.class_type->base_type;
-					} break;
-					case GDScriptParser::DataType::GDSCRIPT: {
-						Ref<GDScript> gds = native_type.script_type;
-						if (gds.is_valid()) {
-							Ref<GDScript> base = gds->get_base_script();
-							if (base.is_valid()) {
-								native_type.script_type = base;
-							} else {
-								native_type.native_type = gds->get_instance_base_type();
-								native_type.kind = GDScriptParser::DataType::NATIVE;
-							}
-						} else {
-							native_type.has_type = false;
-						}
-					} break;
-					default: {
-						native_type.has_type = false;
-					} break;
-				}
-			}
-
-			if (!native_type.has_type) {
-				break;
-			}
-
-			StringName class_name = native_type.native_type;
-			if (!ClassDB::class_exists(class_name)) {
-				class_name = String("_") + class_name;
-				if (!ClassDB::class_exists(class_name)) {
-					break;
-				}
-			}
-
-			bool use_type_hint = EditorSettings::get_singleton()->get_setting("text_editor/completion/add_type_hints").operator bool();
-
-			List<MethodInfo> virtual_methods;
-			ClassDB::get_virtual_methods(class_name, &virtual_methods);
-			for (List<MethodInfo>::Element *E = virtual_methods.front(); E; E = E->next()) {
-				MethodInfo &mi = E->get();
-				String method_hint = mi.name;
-				if (method_hint.find(":") != -1) {
-					method_hint = method_hint.get_slice(":", 0);
-				}
-				method_hint += "(";
-
-				if (mi.arguments.size()) {
-					for (int i = 0; i < mi.arguments.size(); i++) {
-						if (i > 0) {
-							method_hint += ", ";
-						}
-						String arg = mi.arguments[i].name;
-						if (arg.find(":") != -1) {
-							arg = arg.substr(0, arg.find(":"));
-						}
-						method_hint += arg;
-						if (use_type_hint && mi.arguments[i].type != Variant::NIL) {
-							method_hint += ": ";
-							if (mi.arguments[i].type == Variant::OBJECT && mi.arguments[i].class_name != StringName()) {
-								method_hint += mi.arguments[i].class_name.operator String();
-							} else {
-								method_hint += Variant::get_type_name(mi.arguments[i].type);
-							}
-						}
-					}
-				}
-				method_hint += ")";
-				if (use_type_hint && (mi.return_val.type != Variant::NIL || !(mi.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT))) {
-					method_hint += " -> ";
-					if (mi.return_val.type == Variant::NIL) {
-						method_hint += "void";
-					} else if (mi.return_val.type == Variant::OBJECT && mi.return_val.class_name != StringName()) {
-						method_hint += mi.return_val.class_name.operator String();
-					} else {
-						method_hint += Variant::get_type_name(mi.return_val.type);
-					}
-				}
-				method_hint += ":";
-
-				ScriptCodeCompletionOption option(method_hint, ScriptCodeCompletionOption::KIND_FUNCTION);
-				options.insert(option.display, option);
-			}
-		} break;
-		case GDScriptParser::COMPLETION_YIELD: {
-			const GDScriptParser::Node *node = parser.get_completion_node();
-
-			GDScriptCompletionContext c = context;
-			c.line = node->line;
-			GDScriptCompletionIdentifier type;
-			if (!_guess_expression_type(c, node, type)) {
-				break;
-			}
-
-			GDScriptParser::DataType base_type = type.type;
-			while (base_type.has_type) {
-				switch (base_type.kind) {
-					case GDScriptParser::DataType::CLASS: {
-						for (int i = 0; i < base_type.class_type->_signals.size(); i++) {
-							ScriptCodeCompletionOption option(base_type.class_type->_signals[i].name.operator String(), ScriptCodeCompletionOption::KIND_SIGNAL);
-							option.insert_text = quote_style + option.display + quote_style;
-							options.insert(option.display, option);
-						}
-						base_type = base_type.class_type->base_type;
-					} break;
-					case GDScriptParser::DataType::SCRIPT:
-					case GDScriptParser::DataType::GDSCRIPT: {
-						Ref<Script> scr = base_type.script_type;
-						if (scr.is_valid()) {
-							List<MethodInfo> signals;
-							scr->get_script_signal_list(&signals);
-							for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) {
-								ScriptCodeCompletionOption option(quote_style + E->get().name + quote_style, ScriptCodeCompletionOption::KIND_SIGNAL);
-								options.insert(option.display, option);
-							}
-							Ref<Script> base_script = scr->get_base_script();
-							if (base_script.is_valid()) {
-								base_type.script_type = base_script;
-							} else {
-								base_type.kind = GDScriptParser::DataType::NATIVE;
-								base_type.native_type = scr->get_instance_base_type();
-							}
-						} else {
-							base_type.has_type = false;
-						}
-					} break;
-					case GDScriptParser::DataType::NATIVE: {
-						base_type.has_type = false;
-
-						StringName class_name = base_type.native_type;
-						if (!ClassDB::class_exists(class_name)) {
-							class_name = String("_") + class_name;
-							if (!ClassDB::class_exists(class_name)) {
-								break;
-							}
-						}
-
-						List<MethodInfo> signals;
-						ClassDB::get_signal_list(class_name, &signals);
-						for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) {
-							ScriptCodeCompletionOption option(quote_style + E->get().name + quote_style, ScriptCodeCompletionOption::KIND_SIGNAL);
-							options.insert(option.display, option);
-						}
-					} break;
-					default: {
-						base_type.has_type = false;
-					}
-				}
-			}
-		} break;
-		case GDScriptParser::COMPLETION_RESOURCE_PATH: {
-			if (EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths")) {
-				_get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), options);
-				r_forced = true;
-			}
-		} break;
-		case GDScriptParser::COMPLETION_ASSIGN: {
-			GDScriptCompletionIdentifier type;
-			if (!_guess_expression_type(context, parser.get_completion_node(), type)) {
-				break;
-			}
-
-			if (!type.enumeration.empty()) {
-				_find_enumeration_candidates(type.enumeration, options);
-				r_forced = options.size() > 0;
-			}
-		} break;
-		case GDScriptParser::COMPLETION_TYPE_HINT: {
-			const GDScriptParser::ClassNode *clss = context._class;
-			while (clss) {
-				for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = clss->constant_expressions.front(); E; E = E->next()) {
-					GDScriptCompletionIdentifier constant;
-					GDScriptCompletionContext c = context;
-					c.function = nullptr;
-					c.block = nullptr;
-					c.line = E->value().expression->line;
-					if (_guess_expression_type(c, E->value().expression, constant)) {
-						if (constant.type.has_type && constant.type.is_meta_type) {
-							ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS);
-							options.insert(option.display, option);
-						}
-					}
-				}
-				for (int i = 0; i < clss->subclasses.size(); i++) {
-					if (clss->subclasses[i]->name != StringName()) {
-						ScriptCodeCompletionOption option(clss->subclasses[i]->name.operator String(), ScriptCodeCompletionOption::KIND_CLASS);
-						options.insert(option.display, option);
-					}
-				}
-				clss = clss->owner;
-				for (int i = 0; i < Variant::VARIANT_MAX; i++) {
-					ScriptCodeCompletionOption option(Variant::get_type_name((Variant::Type)i), ScriptCodeCompletionOption::KIND_CLASS);
-					options.insert(option.display, option);
-				}
-				List<PropertyInfo> props;
-				ProjectSettings::get_singleton()->get_property_list(&props);
-				for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
-					String s = E->get().name;
-					if (!s.begins_with("autoload/")) {
-						continue;
-					}
-					ScriptCodeCompletionOption option(s.get_slice("/", 1), ScriptCodeCompletionOption::KIND_CLASS);
-					options.insert(option.display, option);
-				}
-			}
-
-			List<StringName> native_classes;
-			ClassDB::get_class_list(&native_classes);
-			for (List<StringName>::Element *E = native_classes.front(); E; E = E->next()) {
-				String class_name = E->get().operator String();
-				if (class_name.begins_with("_")) {
-					class_name = class_name.right(1);
-				}
-				if (Engine::get_singleton()->has_singleton(class_name)) {
-					continue;
-				}
-				ScriptCodeCompletionOption option(class_name, ScriptCodeCompletionOption::KIND_CLASS);
-				options.insert(option.display, option);
-			}
-
-			// Named scripts
-			List<StringName> named_scripts;
-			ScriptServer::get_global_class_list(&named_scripts);
-			for (List<StringName>::Element *E = named_scripts.front(); E; E = E->next()) {
-				ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_CLASS);
-				options.insert(option.display, option);
-			}
-
-			if (parser.get_completion_identifier_is_function()) {
-				ScriptCodeCompletionOption option("void", ScriptCodeCompletionOption::KIND_PLAIN_TEXT);
-				options.insert(option.display, option);
-			}
-			r_forced = true;
-		} break;
-		case GDScriptParser::COMPLETION_TYPE_HINT_INDEX: {
-			GDScriptCompletionIdentifier base;
-			String index = parser.get_completion_cursor().operator String();
-			if (!_guess_identifier_type(context, index.get_slice(".", 0), base)) {
-				break;
-			}
-
-			GDScriptCompletionContext c = context;
-			c._class = nullptr;
-			c.function = nullptr;
-			c.block = nullptr;
-			bool finding = true;
-			index = index.right(index.find(".") + 1);
-			while (index.find(".") != -1) {
-				String id = index.get_slice(".", 0);
-
-				GDScriptCompletionIdentifier sub_base;
-				if (!_guess_identifier_type_from_base(c, base, id, sub_base)) {
-					finding = false;
-					break;
-				}
-				index = index.right(index.find(".") + 1);
-				base = sub_base;
-			}
-
-			if (!finding) {
-				break;
-			}
-
-			GDScriptParser::DataType base_type = base.type;
-			while (base_type.has_type) {
-				switch (base_type.kind) {
-					case GDScriptParser::DataType::CLASS: {
-						if (base_type.class_type) {
-							for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = base_type.class_type->constant_expressions.front(); E; E = E->next()) {
-								GDScriptCompletionIdentifier constant;
-								GDScriptCompletionContext c2 = context;
-								c2._class = base_type.class_type;
-								c2.function = nullptr;
-								c2.block = nullptr;
-								c2.line = E->value().expression->line;
-								if (_guess_expression_type(c2, E->value().expression, constant)) {
-									if (constant.type.has_type && constant.type.is_meta_type) {
-										ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS);
-										options.insert(option.display, option);
-									}
-								}
-							}
-							for (int i = 0; i < base_type.class_type->subclasses.size(); i++) {
-								if (base_type.class_type->subclasses[i]->name != StringName()) {
-									ScriptCodeCompletionOption option(base_type.class_type->subclasses[i]->name.operator String(), ScriptCodeCompletionOption::KIND_CLASS);
-									options.insert(option.display, option);
-								}
-							}
-
-							base_type = base_type.class_type->base_type;
-						} else {
-							base_type.has_type = false;
-						}
-					} break;
-					case GDScriptParser::DataType::SCRIPT:
-					case GDScriptParser::DataType::GDSCRIPT: {
-						Ref<Script> scr = base_type.script_type;
-						if (scr.is_valid()) {
-							Map<StringName, Variant> constants;
-							scr->get_constants(&constants);
-							for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) {
-								Ref<Script> const_scr = E->value();
-								if (const_scr.is_valid()) {
-									ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS);
-									options.insert(option.display, option);
-								}
-							}
-							Ref<Script> base_script = scr->get_base_script();
-							if (base_script.is_valid()) {
-								base_type.script_type = base_script;
-							} else {
-								base_type.has_type = false;
-							}
-						} else {
-							base_type.has_type = false;
-						}
-					} break;
-					default: {
-						base_type.has_type = false;
-					} break;
-				}
-			}
-			r_forced = options.size() > 0;
-		} break;
-	}
-
-	for (Map<String, ScriptCodeCompletionOption>::Element *E = options.front(); E; E = E->next()) {
-		r_options->push_back(E->get());
-	}
-
-	return OK;
-}
-
-#else
-
-Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint) {
-	return OK;
-}
-
-#endif
-
-//////// END COMPLETION //////////
-
-String GDScriptLanguage::_get_indentation() const {
-#ifdef TOOLS_ENABLED
-	if (Engine::get_singleton()->is_editor_hint()) {
-		bool use_space_indentation = EDITOR_DEF("text_editor/indent/type", false);
-
-		if (use_space_indentation) {
-			int indent_size = EDITOR_DEF("text_editor/indent/size", 4);
-
-			String space_indent = "";
-			for (int i = 0; i < indent_size; i++) {
-				space_indent += " ";
-			}
-			return space_indent;
-		}
-	}
-#endif
-	return "\t";
-}
-
-void GDScriptLanguage::auto_indent_code(String &p_code, int p_from_line, int p_to_line) const {
-	String indent = _get_indentation();
-
-	Vector<String> lines = p_code.split("\n");
-	List<int> indent_stack;
-
-	for (int i = 0; i < lines.size(); i++) {
-		String l = lines[i];
-		int tc = 0;
-		for (int j = 0; j < l.length(); j++) {
-			if (l[j] == ' ' || l[j] == '\t') {
-				tc++;
-			} else {
-				break;
-			}
-		}
-
-		String st = l.substr(tc, l.length()).strip_edges();
-		if (st == "" || st.begins_with("#")) {
-			continue; //ignore!
-		}
-
-		int ilevel = 0;
-		if (indent_stack.size()) {
-			ilevel = indent_stack.back()->get();
-		}
-
-		if (tc > ilevel) {
-			indent_stack.push_back(tc);
-		} else if (tc < ilevel) {
-			while (indent_stack.size() && indent_stack.back()->get() > tc) {
-				indent_stack.pop_back();
-			}
-
-			if (indent_stack.size() && indent_stack.back()->get() != tc) {
-				indent_stack.push_back(tc); //this is not right but gets the job done
-			}
-		}
-
-		if (i >= p_from_line) {
-			l = "";
-			for (int j = 0; j < indent_stack.size(); j++) {
-				l += indent;
-			}
-			l += st;
-
-		} else if (i > p_to_line) {
-			break;
-		}
-
-		lines.write[i] = l;
-	}
-
-	p_code = "";
-	for (int i = 0; i < lines.size(); i++) {
-		if (i > 0) {
-			p_code += "\n";
-		}
-		p_code += lines[i];
-	}
-}
-
-#ifdef TOOLS_ENABLED
-
-static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, const String &p_symbol, bool p_is_function, GDScriptLanguage::LookupResult &r_result) {
-	GDScriptParser::DataType base_type = p_base;
-
-	while (base_type.has_type) {
-		switch (base_type.kind) {
-			case GDScriptParser::DataType::CLASS: {
-				if (base_type.class_type) {
-					if (p_is_function) {
-						for (int i = 0; i < base_type.class_type->functions.size(); i++) {
-							if (base_type.class_type->functions[i]->name == p_symbol) {
-								r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
-								r_result.location = base_type.class_type->functions[i]->line;
-								return OK;
-							}
-						}
-						for (int i = 0; i < base_type.class_type->static_functions.size(); i++) {
-							if (base_type.class_type->static_functions[i]->name == p_symbol) {
-								r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
-								r_result.location = base_type.class_type->static_functions[i]->line;
-								return OK;
-							}
-						}
-					} else {
-						if (base_type.class_type->constant_expressions.has(p_symbol)) {
-							r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
-							r_result.location = base_type.class_type->constant_expressions[p_symbol].expression->line;
-							return OK;
-						}
-
-						for (int i = 0; i < base_type.class_type->variables.size(); i++) {
-							if (base_type.class_type->variables[i].identifier == p_symbol) {
-								r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
-								r_result.location = base_type.class_type->variables[i].line;
-								return OK;
-							}
-						}
-
-						for (int i = 0; i < base_type.class_type->subclasses.size(); i++) {
-							if (base_type.class_type->subclasses[i]->name == p_symbol) {
-								r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
-								r_result.location = base_type.class_type->subclasses[i]->line;
-								return OK;
-							}
-						}
-					}
-					base_type = base_type.class_type->base_type;
-				}
-			} break;
-			case GDScriptParser::DataType::SCRIPT:
-			case GDScriptParser::DataType::GDSCRIPT: {
-				Ref<Script> scr = base_type.script_type;
-				if (scr.is_valid()) {
-					int line = scr->get_member_line(p_symbol);
-					if (line >= 0) {
-						r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
-						r_result.location = line;
-						r_result.script = scr;
-						return OK;
-					}
-					Ref<Script> base_script = scr->get_base_script();
-					if (base_script.is_valid()) {
-						base_type.script_type = base_script;
-					} else {
-						base_type.kind = GDScriptParser::DataType::NATIVE;
-						base_type.native_type = scr->get_instance_base_type();
-					}
-				} else {
-					base_type.has_type = false;
-				}
-			} break;
-			case GDScriptParser::DataType::NATIVE: {
-				StringName class_name = base_type.native_type;
-				if (!ClassDB::class_exists(class_name)) {
-					class_name = String("_") + class_name;
-					if (!ClassDB::class_exists(class_name)) {
-						base_type.has_type = false;
-						break;
-					}
-				}
-
-				if (ClassDB::has_method(class_name, p_symbol, true)) {
-					r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD;
-					r_result.class_name = base_type.native_type;
-					r_result.class_member = p_symbol;
-					return OK;
-				}
-
-				List<MethodInfo> virtual_methods;
-				ClassDB::get_virtual_methods(class_name, &virtual_methods, true);
-				for (List<MethodInfo>::Element *E = virtual_methods.front(); E; E = E->next()) {
-					if (E->get().name == p_symbol) {
-						r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD;
-						r_result.class_name = base_type.native_type;
-						r_result.class_member = p_symbol;
-						return OK;
-					}
-				}
-
-				StringName enum_name = ClassDB::get_integer_constant_enum(class_name, p_symbol, true);
-				if (enum_name != StringName()) {
-					r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_ENUM;
-					r_result.class_name = base_type.native_type;
-					r_result.class_member = enum_name;
-					return OK;
-				}
-
-				List<String> constants;
-				ClassDB::get_integer_constant_list(class_name, &constants, true);
-				for (List<String>::Element *E = constants.front(); E; E = E->next()) {
-					if (E->get() == p_symbol) {
-						r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT;
-						r_result.class_name = base_type.native_type;
-						r_result.class_member = p_symbol;
-						return OK;
-					}
-				}
-
-				List<PropertyInfo> properties;
-				ClassDB::get_property_list(class_name, &properties, true);
-				for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) {
-					if (E->get().name == p_symbol) {
-						r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY;
-						r_result.class_name = base_type.native_type;
-						r_result.class_member = p_symbol;
-						return OK;
-					}
-				}
-
-				StringName parent = ClassDB::get_parent_class(class_name);
-				if (parent != StringName()) {
-					if (String(parent).begins_with("_")) {
-						base_type.native_type = String(parent).right(1);
-					} else {
-						base_type.native_type = parent;
-					}
-				} else {
-					base_type.has_type = false;
-				}
-			} break;
-			case GDScriptParser::DataType::BUILTIN: {
-				base_type.has_type = false;
-
-				if (Variant::has_constant(base_type.builtin_type, p_symbol)) {
-					r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT;
-					r_result.class_name = Variant::get_type_name(base_type.builtin_type);
-					r_result.class_member = p_symbol;
-					return OK;
-				}
-
-				Variant v;
-				REF v_ref;
-				if (base_type.builtin_type == Variant::OBJECT) {
-					v_ref.instance();
-					v = v_ref;
-				} else {
-					Callable::CallError err;
-					v = Variant::construct(base_type.builtin_type, nullptr, 0, err);
-					if (err.error != Callable::CallError::CALL_OK) {
-						break;
-					}
-				}
-
-				if (v.has_method(p_symbol)) {
-					r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD;
-					r_result.class_name = Variant::get_type_name(base_type.builtin_type);
-					r_result.class_member = p_symbol;
-					return OK;
-				}
-
-				bool valid = false;
-				v.get(p_symbol, &valid);
-				if (valid) {
-					r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY;
-					r_result.class_name = Variant::get_type_name(base_type.builtin_type);
-					r_result.class_member = p_symbol;
-					return OK;
-				}
-			} break;
-			default: {
-				base_type.has_type = false;
-			} break;
-		}
-	}
-
-	return ERR_CANT_RESOLVE;
-}
+#ifdef TOOLS_ENABLED
 
 
 Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol, const String &p_path, Object *p_owner, LookupResult &r_result) {
 Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol, const String &p_path, Object *p_owner, LookupResult &r_result) {
-	//before parsing, try the usual stuff
-	if (ClassDB::class_exists(p_symbol)) {
-		r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS;
-		r_result.class_name = p_symbol;
-		return OK;
-	} else {
-		String under_prefix = "_" + p_symbol;
-		if (ClassDB::class_exists(under_prefix)) {
-			r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS;
-			r_result.class_name = p_symbol;
-			return OK;
-		}
-	}
-
-	for (int i = 0; i < Variant::VARIANT_MAX; i++) {
-		Variant::Type t = Variant::Type(i);
-		if (Variant::get_type_name(t) == p_symbol) {
-			r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS;
-			r_result.class_name = Variant::get_type_name(t);
-			return OK;
-		}
-	}
-
-	for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) {
-		if (GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i)) == p_symbol) {
-			r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD;
-			r_result.class_name = "@GDScript";
-			r_result.class_member = p_symbol;
-			return OK;
-		}
-	}
-
-	if ("PI" == p_symbol || "TAU" == p_symbol || "INF" == p_symbol || "NAN" == p_symbol) {
-		r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT;
-		r_result.class_name = "@GDScript";
-		r_result.class_member = p_symbol;
-		return OK;
-	}
-
-	GDScriptParser parser;
-	parser.parse(p_code, p_path.get_base_dir(), false, p_path, true);
-
-	if (parser.get_completion_type() == GDScriptParser::COMPLETION_NONE) {
-		return ERR_CANT_RESOLVE;
-	}
-
-	GDScriptCompletionContext context;
-	context._class = parser.get_completion_class();
-	context.function = parser.get_completion_function();
-	context.block = parser.get_completion_block();
-	context.line = parser.get_completion_line();
-	context.base = p_owner;
-	context.base_path = p_path.get_base_dir();
-
-	if (context._class && context._class->extends_class.size() > 0) {
-		bool success = false;
-		ClassDB::get_integer_constant(context._class->extends_class[0], p_symbol, &success);
-		if (success) {
-			r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT;
-			r_result.class_name = context._class->extends_class[0];
-			r_result.class_member = p_symbol;
-			return OK;
-		}
-	}
-
-	bool is_function = false;
-
-	switch (parser.get_completion_type()) {
-		case GDScriptParser::COMPLETION_BUILT_IN_TYPE_CONSTANT: {
-			r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT;
-			r_result.class_name = Variant::get_type_name(parser.get_completion_built_in_constant());
-			r_result.class_member = p_symbol;
-			return OK;
-		} break;
-		case GDScriptParser::COMPLETION_PARENT_FUNCTION:
-		case GDScriptParser::COMPLETION_FUNCTION: {
-			is_function = true;
-			[[fallthrough]];
-		}
-		case GDScriptParser::COMPLETION_IDENTIFIER: {
-			if (!is_function) {
-				is_function = parser.get_completion_identifier_is_function();
-			}
-
-			GDScriptParser::DataType base_type;
-			if (context._class) {
-				if (parser.get_completion_type() != GDScriptParser::COMPLETION_PARENT_FUNCTION) {
-					base_type.has_type = true;
-					base_type.kind = GDScriptParser::DataType::CLASS;
-					base_type.class_type = const_cast<GDScriptParser::ClassNode *>(context._class);
-				} else {
-					base_type = context._class->base_type;
-				}
-			} else {
-				break;
-			}
-
-			if (!is_function && context.block) {
-				// Lookup local variables
-				const GDScriptParser::BlockNode *block = context.block;
-				while (block) {
-					if (block->variables.has(p_symbol)) {
-						r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
-						r_result.location = block->variables[p_symbol]->line;
-						return OK;
-					}
-					block = block->parent_block;
-				}
-			}
-
-			if (context.function && context.function->name != StringName()) {
-				// Lookup function arguments
-				for (int i = 0; i < context.function->arguments.size(); i++) {
-					if (context.function->arguments[i] == p_symbol) {
-						r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
-						r_result.location = context.function->line;
-						return OK;
-					}
-				}
-			}
-
-			if (_lookup_symbol_from_base(base_type, p_symbol, is_function, r_result) == OK) {
-				return OK;
-			}
-
-			if (!is_function) {
-				// Guess in autoloads as singletons
-				List<PropertyInfo> props;
-				ProjectSettings::get_singleton()->get_property_list(&props);
-
-				for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
-					String s = E->get().name;
-					if (!s.begins_with("autoload/")) {
-						continue;
-					}
-					String name = s.get_slice("/", 1);
-					if (name == String(p_symbol)) {
-						String path = ProjectSettings::get_singleton()->get(s);
-						if (path.begins_with("*")) {
-							String script = path.substr(1, path.length());
-
-							if (!script.ends_with(".gd")) {
-								// Not a script, try find the script anyway,
-								// may have some success
-								script = script.get_basename() + ".gd";
-							}
-
-							if (FileAccess::exists(script)) {
-								r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
-								r_result.location = 0;
-								r_result.script = ResourceLoader::load(script);
-								return OK;
-							}
-						}
-					}
-				}
-
-				// Global
-				Map<StringName, int> classes = GDScriptLanguage::get_singleton()->get_global_map();
-				if (classes.has(p_symbol)) {
-					Variant value = GDScriptLanguage::get_singleton()->get_global_array()[classes[p_symbol]];
-					if (value.get_type() == Variant::OBJECT) {
-						Object *obj = value;
-						if (obj) {
-							if (Object::cast_to<GDScriptNativeClass>(obj)) {
-								r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS;
-								r_result.class_name = Object::cast_to<GDScriptNativeClass>(obj)->get_name();
-							} else {
-								r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS;
-								r_result.class_name = obj->get_class();
-							}
-
-							// proxy class remove the underscore.
-							if (r_result.class_name.begins_with("_")) {
-								r_result.class_name = r_result.class_name.right(1);
-							}
-							return OK;
-						}
-					} else {
-						/*
-						// Because get_integer_constant_enum and get_integer_constant don't work on @GlobalScope
-						// We cannot determine the exact nature of the identifier here
-						// Otherwise these codes would work
-						StringName enumName = ClassDB::get_integer_constant_enum("@GlobalScope", p_symbol, true);
-						if (enumName != nullptr) {
-							r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_ENUM;
-							r_result.class_name = "@GlobalScope";
-							r_result.class_member = enumName;
-							return OK;
-						}
-						else {
-							r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT;
-							r_result.class_name = "@GlobalScope";
-							r_result.class_member = p_symbol;
-							return OK;
-						}*/
-						r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_TBD_GLOBALSCOPE;
-						r_result.class_name = "@GlobalScope";
-						r_result.class_member = p_symbol;
-						return OK;
-					}
-				}
-			}
-		} break;
-		case GDScriptParser::COMPLETION_METHOD: {
-			is_function = true;
-			[[fallthrough]];
-		}
-		case GDScriptParser::COMPLETION_INDEX: {
-			const GDScriptParser::Node *node = parser.get_completion_node();
-			if (node->type != GDScriptParser::Node::TYPE_OPERATOR) {
-				break;
-			}
-			GDScriptCompletionIdentifier base;
-			if (!_guess_expression_type(context, static_cast<const GDScriptParser::OperatorNode *>(node)->arguments[0], base)) {
-				break;
-			}
-
-			if (_lookup_symbol_from_base(base.type, p_symbol, is_function, r_result) == OK) {
-				return OK;
-			}
-		} break;
-		case GDScriptParser::COMPLETION_VIRTUAL_FUNC: {
-			GDScriptParser::DataType base_type = context._class->base_type;
-
-			if (_lookup_symbol_from_base(base_type, p_symbol, true, r_result) == OK) {
-				return OK;
-			}
-		} break;
-		case GDScriptParser::COMPLETION_TYPE_HINT: {
-			GDScriptParser::DataType base_type = context._class->base_type;
-			base_type.has_type = true;
-			base_type.kind = GDScriptParser::DataType::CLASS;
-			base_type.class_type = const_cast<GDScriptParser::ClassNode *>(context._class);
-
-			if (_lookup_symbol_from_base(base_type, p_symbol, false, r_result) == OK) {
-				return OK;
-			}
-		} break;
-		default: {
-		}
-	}
-
+	// FIXME: Implement lookup.
 	return ERR_CANT_RESOLVE;
 	return ERR_CANT_RESOLVE;
 }
 }
 
 

+ 103 - 91
modules/gdscript/gdscript_function.cpp

@@ -210,12 +210,12 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const
 		&&OPCODE_CONSTRUCT_DICTIONARY,        \
 		&&OPCODE_CONSTRUCT_DICTIONARY,        \
 		&&OPCODE_CALL,                        \
 		&&OPCODE_CALL,                        \
 		&&OPCODE_CALL_RETURN,                 \
 		&&OPCODE_CALL_RETURN,                 \
+		&&OPCODE_CALL_ASYNC,                  \
 		&&OPCODE_CALL_BUILT_IN,               \
 		&&OPCODE_CALL_BUILT_IN,               \
 		&&OPCODE_CALL_SELF,                   \
 		&&OPCODE_CALL_SELF,                   \
 		&&OPCODE_CALL_SELF_BASE,              \
 		&&OPCODE_CALL_SELF_BASE,              \
-		&&OPCODE_YIELD,                       \
-		&&OPCODE_YIELD_SIGNAL,                \
-		&&OPCODE_YIELD_RESUME,                \
+		&&OPCODE_AWAIT,                       \
+		&&OPCODE_AWAIT_RESUME,                \
 		&&OPCODE_JUMP,                        \
 		&&OPCODE_JUMP,                        \
 		&&OPCODE_JUMP_IF,                     \
 		&&OPCODE_JUMP_IF,                     \
 		&&OPCODE_JUMP_IF_NOT,                 \
 		&&OPCODE_JUMP_IF_NOT,                 \
@@ -227,7 +227,8 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const
 		&&OPCODE_BREAKPOINT,                  \
 		&&OPCODE_BREAKPOINT,                  \
 		&&OPCODE_LINE,                        \
 		&&OPCODE_LINE,                        \
 		&&OPCODE_END                          \
 		&&OPCODE_END                          \
-	};
+	};                                        \
+	static_assert((sizeof(switch_table_ops) / sizeof(switch_table_ops[0]) == (OPCODE_END + 1)), "Opcodes in jump table aren't the same as opcodes in enum.");
 
 
 #define OPCODE(m_op) \
 #define OPCODE(m_op) \
 	m_op:
 	m_op:
@@ -280,7 +281,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
 	int line = _initial_line;
 	int line = _initial_line;
 
 
 	if (p_state) {
 	if (p_state) {
-		//use existing (supplied) state (yielded)
+		//use existing (supplied) state (awaited)
 		stack = (Variant *)p_state->stack.ptr();
 		stack = (Variant *)p_state->stack.ptr();
 		call_args = (Variant **)&p_state->stack.ptr()[sizeof(Variant) * p_state->stack_size]; //ptr() to avoid bounds check
 		call_args = (Variant **)&p_state->stack.ptr()[sizeof(Variant) * p_state->stack_size]; //ptr() to avoid bounds check
 		line = p_state->line;
 		line = p_state->line;
@@ -411,7 +412,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
 		profile.frame_call_count++;
 		profile.frame_call_count++;
 	}
 	}
 	bool exit_ok = false;
 	bool exit_ok = false;
-	bool yielded = false;
+	bool awaited = false;
 #endif
 #endif
 
 
 #ifdef DEBUG_ENABLED
 #ifdef DEBUG_ENABLED
@@ -1006,10 +1007,14 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
 			}
 			}
 			DISPATCH_OPCODE;
 			DISPATCH_OPCODE;
 
 
+			OPCODE(OPCODE_CALL_ASYNC)
 			OPCODE(OPCODE_CALL_RETURN)
 			OPCODE(OPCODE_CALL_RETURN)
 			OPCODE(OPCODE_CALL) {
 			OPCODE(OPCODE_CALL) {
 				CHECK_SPACE(4);
 				CHECK_SPACE(4);
-				bool call_ret = _code_ptr[ip] == OPCODE_CALL_RETURN;
+				bool call_ret = _code_ptr[ip] != OPCODE_CALL;
+#ifdef DEBUG_ENABLED
+				bool call_async = _code_ptr[ip] == OPCODE_CALL_ASYNC;
+#endif
 
 
 				int argc = _code_ptr[ip + 1];
 				int argc = _code_ptr[ip + 1];
 				GET_VARIANT_PTR(base, 2);
 				GET_VARIANT_PTR(base, 2);
@@ -1040,6 +1045,22 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
 				if (call_ret) {
 				if (call_ret) {
 					GET_VARIANT_PTR(ret, argc);
 					GET_VARIANT_PTR(ret, argc);
 					base->call_ptr(*methodname, (const Variant **)argptrs, argc, ret, err);
 					base->call_ptr(*methodname, (const Variant **)argptrs, argc, ret, err);
+#ifdef DEBUG_ENABLED
+					if (!call_async && ret->get_type() == Variant::OBJECT) {
+						// Check if getting a function state without await.
+						bool was_freed = false;
+						Object *obj = ret->get_validated_object_with_check(was_freed);
+
+						if (was_freed) {
+							err_text = "Got a freed object as a result of the call.";
+							OPCODE_BREAK;
+						}
+						if (obj->is_class_ptr(GDScriptFunctionState::get_class_ptr_static())) {
+							err_text = R"(Trying to call an async function without "await".)";
+							OPCODE_BREAK;
+						}
+					}
+#endif
 				} else {
 				} else {
 					base->call_ptr(*methodname, (const Variant **)argptrs, argc, nullptr, err);
 					base->call_ptr(*methodname, (const Variant **)argptrs, argc, nullptr, err);
 				}
 				}
@@ -1192,105 +1213,96 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
 			}
 			}
 			DISPATCH_OPCODE;
 			DISPATCH_OPCODE;
 
 
-			OPCODE(OPCODE_YIELD)
-			OPCODE(OPCODE_YIELD_SIGNAL) {
-				int ipofs = 1;
-				if (_code_ptr[ip] == OPCODE_YIELD_SIGNAL) {
-					CHECK_SPACE(4);
-					ipofs += 2;
-				} else {
-					CHECK_SPACE(2);
-				}
+			OPCODE(OPCODE_AWAIT) {
+				int ipofs = 2;
+				CHECK_SPACE(3);
 
 
-				Ref<GDScriptFunctionState> gdfs = memnew(GDScriptFunctionState);
-				gdfs->function = this;
+				//do the oneshot connect
+				GET_VARIANT_PTR(argobj, 1);
+
+				Signal sig;
+				bool is_signal = true;
 
 
-				gdfs->state.stack.resize(alloca_size);
-				//copy variant stack
-				for (int i = 0; i < _stack_size; i++) {
-					memnew_placement(&gdfs->state.stack.write[sizeof(Variant) * i], Variant(stack[i]));
-				}
-				gdfs->state.stack_size = _stack_size;
-				gdfs->state.self = self;
-				gdfs->state.alloca_size = alloca_size;
-				gdfs->state.ip = ip + ipofs;
-				gdfs->state.line = line;
-				gdfs->state.script = _script;
 				{
 				{
-					MutexLock lock(GDScriptLanguage::get_singleton()->lock);
-					_script->pending_func_states.add(&gdfs->scripts_list);
-					if (p_instance) {
-						gdfs->state.instance = p_instance;
-						p_instance->pending_func_states.add(&gdfs->instances_list);
-					} else {
-						gdfs->state.instance = nullptr;
-					}
-				}
-#ifdef DEBUG_ENABLED
-				gdfs->state.function_name = name;
-				gdfs->state.script_path = _script->get_path();
-#endif
-				gdfs->state.defarg = defarg;
-				gdfs->function = this;
+					Variant result = *argobj;
 
 
-				retvalue = gdfs;
+					if (argobj->get_type() == Variant::OBJECT) {
+						bool was_freed = false;
+						Object *obj = argobj->get_validated_object_with_check(was_freed);
 
 
-				if (_code_ptr[ip] == OPCODE_YIELD_SIGNAL) {
-					//do the oneshot connect
-					GET_VARIANT_PTR(argobj, 1);
-					GET_VARIANT_PTR(argname, 2);
+						if (was_freed) {
+							err_text = "Trying to await on a freed object.";
+							OPCODE_BREAK;
+						}
 
 
-#ifdef DEBUG_ENABLED
-					if (argobj->get_type() != Variant::OBJECT) {
-						err_text = "First argument of yield() not of type object.";
-						OPCODE_BREAK;
-					}
-					if (argname->get_type() != Variant::STRING) {
-						err_text = "Second argument of yield() not a string (for signal name).";
-						OPCODE_BREAK;
+						// Is this even possible to be null at this point?
+						if (obj) {
+							if (obj->is_class_ptr(GDScriptFunctionState::get_class_ptr_static())) {
+								static StringName completed = _scs_create("completed");
+								result = Signal(obj, completed);
+							}
+						}
 					}
 					}
-#endif
 
 
-#ifdef DEBUG_ENABLED
-					bool was_freed;
-					Object *obj = argobj->get_validated_object_with_check(was_freed);
-					String signal = argname->operator String();
-
-					if (was_freed) {
-						err_text = "First argument of yield() is a previously freed instance.";
-						OPCODE_BREAK;
+					if (result.get_type() != Variant::SIGNAL) {
+						ip += 4; // Skip OPCODE_AWAIT_RESUME and its data.
+						// The stack pointer should be the same, so we don't need to set a return value.
+						is_signal = false;
+					} else {
+						sig = result;
 					}
 					}
+				}
 
 
-					if (!obj) {
-						err_text = "First argument of yield() is null.";
-						OPCODE_BREAK;
+				if (is_signal) {
+					Ref<GDScriptFunctionState> gdfs = memnew(GDScriptFunctionState);
+					gdfs->function = this;
+
+					gdfs->state.stack.resize(alloca_size);
+					//copy variant stack
+					for (int i = 0; i < _stack_size; i++) {
+						memnew_placement(&gdfs->state.stack.write[sizeof(Variant) * i], Variant(stack[i]));
 					}
 					}
-					if (signal.length() == 0) {
-						err_text = "Second argument of yield() is an empty string (for signal name).";
-						OPCODE_BREAK;
+					gdfs->state.stack_size = _stack_size;
+					gdfs->state.self = self;
+					gdfs->state.alloca_size = alloca_size;
+					gdfs->state.ip = ip + ipofs;
+					gdfs->state.line = line;
+					gdfs->state.script = _script;
+					{
+						MutexLock lock(GDScriptLanguage::get_singleton()->lock);
+						_script->pending_func_states.add(&gdfs->scripts_list);
+						if (p_instance) {
+							gdfs->state.instance = p_instance;
+							p_instance->pending_func_states.add(&gdfs->instances_list);
+						} else {
+							gdfs->state.instance = nullptr;
+						}
 					}
 					}
+#ifdef DEBUG_ENABLED
+					gdfs->state.function_name = name;
+					gdfs->state.script_path = _script->get_path();
+#endif
+					gdfs->state.defarg = defarg;
+					gdfs->function = this;
 
 
-					Error err = obj->connect_compat(signal, gdfs.ptr(), "_signal_callback", varray(gdfs), Object::CONNECT_ONESHOT);
+					retvalue = gdfs;
+
+					Error err = sig.connect(Callable(gdfs.ptr(), "_signal_callback"), varray(gdfs), Object::CONNECT_ONESHOT);
 					if (err != OK) {
 					if (err != OK) {
-						err_text = "Error connecting to signal: " + signal + " during yield().";
+						err_text = "Error connecting to signal: " + sig.get_name() + " during await.";
 						OPCODE_BREAK;
 						OPCODE_BREAK;
 					}
 					}
-#else
-					Object *obj = argobj->operator Object *();
-					String signal = argname->operator String();
-
-					obj->connect_compat(signal, gdfs.ptr(), "_signal_callback", varray(gdfs), Object::CONNECT_ONESHOT);
-#endif
-				}
 
 
 #ifdef DEBUG_ENABLED
 #ifdef DEBUG_ENABLED
-				exit_ok = true;
-				yielded = true;
+					exit_ok = true;
+					awaited = true;
 #endif
 #endif
-				OPCODE_BREAK;
+					OPCODE_BREAK;
+				}
 			}
 			}
+			DISPATCH_OPCODE; // Needed for synchronous calls (when result is immediately available).
 
 
-			OPCODE(OPCODE_YIELD_RESUME) {
+			OPCODE(OPCODE_AWAIT_RESUME) {
 				CHECK_SPACE(2);
 				CHECK_SPACE(2);
 #ifdef DEBUG_ENABLED
 #ifdef DEBUG_ENABLED
 				if (!p_state) {
 				if (!p_state) {
@@ -1556,11 +1568,11 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
 		GDScriptLanguage::get_singleton()->script_frame_time += time_taken - function_call_time;
 		GDScriptLanguage::get_singleton()->script_frame_time += time_taken - function_call_time;
 	}
 	}
 
 
-	// Check if this is the last time the function is resuming from yield
-	// Will be true if never yielded as well
+	// Check if this is the last time the function is resuming from await
+	// Will be true if never awaited as well
 	// When it's the last resume it will postpone the exit from stack,
 	// When it's the last resume it will postpone the exit from stack,
 	// so the debugger knows which function triggered the resume of the next function (if any)
 	// so the debugger knows which function triggered the resume of the next function (if any)
-	if (!p_state || yielded) {
+	if (!p_state || awaited) {
 		if (EngineDebugger::is_active()) {
 		if (EngineDebugger::is_active()) {
 			GDScriptLanguage::get_singleton()->exit_function();
 			GDScriptLanguage::get_singleton()->exit_function();
 		}
 		}
@@ -1786,14 +1798,14 @@ Variant GDScriptFunctionState::resume(const Variant &p_arg) {
 
 
 		if (!scripts_list.in_list()) {
 		if (!scripts_list.in_list()) {
 #ifdef DEBUG_ENABLED
 #ifdef DEBUG_ENABLED
-			ERR_FAIL_V_MSG(Variant(), "Resumed function '" + state.function_name + "()' after yield, but script is gone. At script: " + state.script_path + ":" + itos(state.line));
+			ERR_FAIL_V_MSG(Variant(), "Resumed function '" + state.function_name + "()' after await, but script is gone. At script: " + state.script_path + ":" + itos(state.line));
 #else
 #else
 			return Variant();
 			return Variant();
 #endif
 #endif
 		}
 		}
 		if (state.instance && !instances_list.in_list()) {
 		if (state.instance && !instances_list.in_list()) {
 #ifdef DEBUG_ENABLED
 #ifdef DEBUG_ENABLED
-			ERR_FAIL_V_MSG(Variant(), "Resumed function '" + state.function_name + "()' after yield, but class instance is gone. At script: " + state.script_path + ":" + itos(state.line));
+			ERR_FAIL_V_MSG(Variant(), "Resumed function '" + state.function_name + "()' after await, but class instance is gone. At script: " + state.script_path + ":" + itos(state.line));
 #else
 #else
 			return Variant();
 			return Variant();
 #endif
 #endif
@@ -1810,7 +1822,7 @@ Variant GDScriptFunctionState::resume(const Variant &p_arg) {
 	bool completed = true;
 	bool completed = true;
 
 
 	// If the return value is a GDScriptFunctionState reference,
 	// If the return value is a GDScriptFunctionState reference,
-	// then the function did yield again after resuming.
+	// then the function did awaited again after resuming.
 	if (ret.is_ref()) {
 	if (ret.is_ref()) {
 		GDScriptFunctionState *gdfs = Object::cast_to<GDScriptFunctionState>(ret);
 		GDScriptFunctionState *gdfs = Object::cast_to<GDScriptFunctionState>(ret);
 		if (gdfs && gdfs->function == function) {
 		if (gdfs && gdfs->function == function) {

+ 3 - 3
modules/gdscript/gdscript_function.h

@@ -180,12 +180,12 @@ public:
 		OPCODE_CONSTRUCT_DICTIONARY,
 		OPCODE_CONSTRUCT_DICTIONARY,
 		OPCODE_CALL,
 		OPCODE_CALL,
 		OPCODE_CALL_RETURN,
 		OPCODE_CALL_RETURN,
+		OPCODE_CALL_ASYNC,
 		OPCODE_CALL_BUILT_IN,
 		OPCODE_CALL_BUILT_IN,
 		OPCODE_CALL_SELF,
 		OPCODE_CALL_SELF,
 		OPCODE_CALL_SELF_BASE,
 		OPCODE_CALL_SELF_BASE,
-		OPCODE_YIELD,
-		OPCODE_YIELD_SIGNAL,
-		OPCODE_YIELD_RESUME,
+		OPCODE_AWAIT,
+		OPCODE_AWAIT_RESUME,
 		OPCODE_JUMP,
 		OPCODE_JUMP,
 		OPCODE_JUMP_IF,
 		OPCODE_JUMP_IF,
 		OPCODE_JUMP_IF_NOT,
 		OPCODE_JUMP_IF_NOT,

+ 2574 - 8415
modules/gdscript/gdscript_parser.cpp

@@ -30,8870 +30,3029 @@
 
 
 #include "gdscript_parser.h"
 #include "gdscript_parser.h"
 
 
-#include "core/core_string_names.h"
-#include "core/engine.h"
 #include "core/io/resource_loader.h"
 #include "core/io/resource_loader.h"
+#include "core/math/math_defs.h"
 #include "core/os/file_access.h"
 #include "core/os/file_access.h"
-#include "core/print_string.h"
-#include "core/project_settings.h"
-#include "core/reference.h"
-#include "core/script_language.h"
-#include "gdscript.h"
+
+#ifdef DEBUG_ENABLED
+#include "core/os/os.h"
+#include "core/string_builder.h"
+#endif // DEBUG_ENABLED
+
+static HashMap<StringName, Variant::Type> builtin_types;
+Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) {
+	if (builtin_types.empty()) {
+		builtin_types["bool"] = Variant::BOOL;
+		builtin_types["int"] = Variant::INT;
+		builtin_types["float"] = Variant::FLOAT;
+		builtin_types["String"] = Variant::STRING;
+		builtin_types["Vector2"] = Variant::VECTOR2;
+		builtin_types["Vector2i"] = Variant::VECTOR2I;
+		builtin_types["Rect2"] = Variant::RECT2;
+		builtin_types["Rect2i"] = Variant::RECT2I;
+		builtin_types["Transform2D"] = Variant::TRANSFORM2D;
+		builtin_types["Vector3"] = Variant::VECTOR3;
+		builtin_types["Vector3i"] = Variant::VECTOR3I;
+		builtin_types["AABB"] = Variant::AABB;
+		builtin_types["Plane"] = Variant::PLANE;
+		builtin_types["Quat"] = Variant::QUAT;
+		builtin_types["Basis"] = Variant::BASIS;
+		builtin_types["Transform"] = Variant::TRANSFORM;
+		builtin_types["Color"] = Variant::COLOR;
+		builtin_types["RID"] = Variant::_RID;
+		builtin_types["Object"] = Variant::OBJECT;
+		builtin_types["StringName"] = Variant::STRING_NAME;
+		builtin_types["NodePath"] = Variant::NODE_PATH;
+		builtin_types["Dictionary"] = Variant::DICTIONARY;
+		builtin_types["Callable"] = Variant::CALLABLE;
+		builtin_types["Signal"] = Variant::SIGNAL;
+		builtin_types["Array"] = Variant::ARRAY;
+		builtin_types["PackedByteArray"] = Variant::PACKED_BYTE_ARRAY;
+		builtin_types["PackedInt32Array"] = Variant::PACKED_INT32_ARRAY;
+		builtin_types["PackedInt64Array"] = Variant::PACKED_INT64_ARRAY;
+		builtin_types["PackedFloat32Array"] = Variant::PACKED_FLOAT32_ARRAY;
+		builtin_types["PackedFloat64Array"] = Variant::PACKED_FLOAT64_ARRAY;
+		builtin_types["PackedStringArray"] = Variant::PACKED_STRING_ARRAY;
+		builtin_types["PackedVector2Array"] = Variant::PACKED_VECTOR2_ARRAY;
+		builtin_types["PackedVector3Array"] = Variant::PACKED_VECTOR3_ARRAY;
+		builtin_types["PackedColorArray"] = Variant::PACKED_COLOR_ARRAY;
+		// NIL is not here, hence the -1.
+		if (builtin_types.size() != Variant::VARIANT_MAX - 1) {
+			ERR_PRINT("Outdated parser: amount of built-in types don't match the amount of types in Variant.");
+		}
+	}
+
+	if (builtin_types.has(p_type)) {
+		return builtin_types[p_type];
+	}
+	return Variant::VARIANT_MAX;
+}
+
+GDScriptFunctions::Function GDScriptParser::get_builtin_function(const StringName &p_name) {
+	for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) {
+		if (p_name == GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))) {
+			return GDScriptFunctions::Function(i);
+		}
+	}
+	return GDScriptFunctions::FUNC_MAX;
+}
+
+GDScriptParser::GDScriptParser() {
+	// Register valid annotations.
+	// TODO: Should this be static?
+	// TODO: Validate applicable types (e.g. a VARIABLE annotation that only applies to string variables).
+	register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation);
+	register_annotation(MethodInfo("@icon", { Variant::STRING, "icon_path" }), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation);
+	register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation);
+	// Export annotations.
+	register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_TYPE_STRING, Variant::NIL>);
+	register_annotation(MethodInfo("@export_enum", { Variant::STRING, "names" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_ENUM, Variant::INT>, 0, true);
+	register_annotation(MethodInfo("@export_file", { Variant::STRING, "filter" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FILE, Variant::STRING>, 1, true);
+	register_annotation(MethodInfo("@export_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_DIR, Variant::STRING>);
+	register_annotation(MethodInfo("@export_global_file", { Variant::STRING, "filter" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_FILE, Variant::STRING>, 1, true);
+	register_annotation(MethodInfo("@export_global_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_DIR, Variant::STRING>);
+	register_annotation(MethodInfo("@export_multiline"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_MULTILINE_TEXT, Variant::STRING>);
+	register_annotation(MethodInfo("@export_placeholder"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_PLACEHOLDER_TEXT, Variant::STRING>);
+	register_annotation(MethodInfo("@export_range", { Variant::FLOAT, "min" }, { Variant::FLOAT, "max" }, { Variant::FLOAT, "step" }, { Variant::STRING, "slider1" }, { Variant::STRING, "slider2" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_RANGE, Variant::FLOAT>, 3);
+	register_annotation(MethodInfo("@export_exp_range", { Variant::FLOAT, "min" }, { Variant::FLOAT, "max" }, { Variant::FLOAT, "step" }, { Variant::STRING, "slider1" }, { Variant::STRING, "slider2" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_EXP_RANGE, Variant::FLOAT>, 3);
+	register_annotation(MethodInfo("@export_exp_easing", { Variant::STRING, "hint1" }, { Variant::STRING, "hint2" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_EXP_EASING, Variant::FLOAT>, 2);
+	register_annotation(MethodInfo("@export_color_no_alpha"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_COLOR_NO_ALPHA, Variant::COLOR>);
+	register_annotation(MethodInfo("@export_node_path", { Variant::STRING, "type" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NODE_PATH_VALID_TYPES, Variant::NODE_PATH>, 1, true);
+	register_annotation(MethodInfo("@export_flags", { Variant::STRING, "names" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FLAGS, Variant::INT>, 0, true);
+	register_annotation(MethodInfo("@export_flags_2d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_RENDER, Variant::INT>);
+	register_annotation(MethodInfo("@export_flags_2d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_PHYSICS, Variant::INT>);
+	register_annotation(MethodInfo("@export_flags_3d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_RENDER, Variant::INT>);
+	register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>);
+	// Networking.
+	register_annotation(MethodInfo("@remote"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_REMOTE>);
+	register_annotation(MethodInfo("@master"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_MASTER>);
+	register_annotation(MethodInfo("@puppet"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_PUPPET>);
+	register_annotation(MethodInfo("@remotesync"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_REMOTESYNC>);
+	register_annotation(MethodInfo("@mastersync"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_MASTERSYNC>);
+	register_annotation(MethodInfo("@puppetsync"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_PUPPETSYNC>);
+	// TODO: Warning annotations.
+}
+
+GDScriptParser::~GDScriptParser() {
+	clear();
+}
 
 
 template <class T>
 template <class T>
 T *GDScriptParser::alloc_node() {
 T *GDScriptParser::alloc_node() {
-	T *t = memnew(T);
+	T *node = memnew(T);
 
 
-	t->next = list;
-	list = t;
+	node->next = list;
+	list = node;
 
 
-	if (!head) {
-		head = t;
+	// TODO: Properly set positions for all nodes.
+	node->start_line = previous.start_line;
+	node->end_line = previous.end_line;
+	node->leftmost_column = previous.leftmost_column;
+	node->rightmost_column = previous.rightmost_column;
+
+	return node;
+}
+
+void GDScriptParser::clear() {
+	while (list != nullptr) {
+		Node *element = list;
+		list = list->next;
+		memdelete(element);
 	}
 	}
 
 
-	t->line = tokenizer->get_token_line();
-	t->column = tokenizer->get_token_column();
-	return t;
+	head = nullptr;
+	list = nullptr;
+	_is_tool = false;
+	for_completion = false;
+	errors.clear();
+	multiline_stack.clear();
 }
 }
 
 
-#ifdef DEBUG_ENABLED
-static String _find_function_name(const GDScriptParser::OperatorNode *p_call);
-#endif // DEBUG_ENABLED
+void GDScriptParser::push_error(const String &p_message, const Node *p_origin) {
+	// TODO: Improve error reporting by pointing at source code.
+	// TODO: Errors might point at more than one place at once (e.g. show previous declaration).
+	panic_mode = true;
+	// TODO: Improve positional information.
+	if (p_origin == nullptr) {
+		errors.push_back({ p_message, current.start_line, current.start_column });
+	} else {
+		errors.push_back({ p_message, p_origin->start_line, p_origin->leftmost_column });
+	}
+}
+
+Error GDScriptParser::parse(const String &p_source_code, const String &p_script_path, bool p_for_completion) {
+	clear();
+	tokenizer.set_source_code(p_source_code);
+	current = tokenizer.scan();
+
+	push_multiline(false); // Keep one for the whole parsing.
+	parse_program();
+	pop_multiline();
 
 
-bool GDScriptParser::_end_statement() {
-	if (tokenizer->get_token() == GDScriptTokenizer::TK_SEMICOLON) {
-		tokenizer->advance();
-		return true; //handle next
-	} else if (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE || tokenizer->get_token() == GDScriptTokenizer::TK_EOF) {
-		return true; //will be handled properly
+#ifdef DEBUG_ENABLED
+	if (multiline_stack.size() > 0) {
+		ERR_PRINT("Parser bug: Imbalanced multiline stack.");
 	}
 	}
+#endif
 
 
-	return false;
+	if (errors.empty()) {
+		return OK;
+	} else {
+		return ERR_PARSE_ERROR;
+	}
 }
 }
 
 
-void GDScriptParser::_set_end_statement_error(String p_name) {
-	String error_msg;
-	if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER) {
-		error_msg = vformat("Expected end of statement (\"%s\"), got %s (\"%s\") instead.", p_name, tokenizer->get_token_name(tokenizer->get_token()), tokenizer->get_token_identifier());
-	} else {
-		error_msg = vformat("Expected end of statement (\"%s\"), got %s instead.", p_name, tokenizer->get_token_name(tokenizer->get_token()));
+GDScriptTokenizer::Token GDScriptParser::advance() {
+	if (current.type == GDScriptTokenizer::Token::TK_EOF) {
+		ERR_FAIL_COND_V_MSG(current.type == GDScriptTokenizer::Token::TK_EOF, current, "GDScript parser bug: Trying to advance past the end of stream.");
 	}
 	}
-	_set_error(error_msg);
+	previous = current;
+	current = tokenizer.scan();
+	while (current.type == GDScriptTokenizer::Token::ERROR) {
+		push_error(current.literal);
+		current = tokenizer.scan();
+	}
+	return previous;
 }
 }
 
 
-bool GDScriptParser::_enter_indent_block(BlockNode *p_block) {
-	if (tokenizer->get_token() != GDScriptTokenizer::TK_COLON) {
-		// report location at the previous token (on the previous line)
-		int error_line = tokenizer->get_token_line(-1);
-		int error_column = tokenizer->get_token_column(-1);
-		_set_error("':' expected at end of line.", error_line, error_column);
+bool GDScriptParser::match(GDScriptTokenizer::Token::Type p_token_type) {
+	if (!check(p_token_type)) {
 		return false;
 		return false;
 	}
 	}
-	tokenizer->advance();
+	advance();
+	return true;
+}
 
 
-	if (tokenizer->get_token() == GDScriptTokenizer::TK_EOF) {
-		return false;
+bool GDScriptParser::check(GDScriptTokenizer::Token::Type p_token_type) {
+	if (p_token_type == GDScriptTokenizer::Token::IDENTIFIER) {
+		return current.is_identifier();
 	}
 	}
+	return current.type == p_token_type;
+}
 
 
-	if (tokenizer->get_token() != GDScriptTokenizer::TK_NEWLINE) {
-		// be more python-like
-		IndentLevel current_level = indent_level.back()->get();
-		indent_level.push_back(current_level);
+bool GDScriptParser::consume(GDScriptTokenizer::Token::Type p_token_type, const String &p_error_message) {
+	if (match(p_token_type)) {
 		return true;
 		return true;
-		//_set_error("newline expected after ':'.");
-		//return false;
-	}
-
-	while (true) {
-		if (tokenizer->get_token() != GDScriptTokenizer::TK_NEWLINE) {
-			return false; //wtf
-		} else if (tokenizer->get_token(1) == GDScriptTokenizer::TK_EOF) {
-			return false;
-		} else if (tokenizer->get_token(1) != GDScriptTokenizer::TK_NEWLINE) {
-			int indent = tokenizer->get_token_line_indent();
-			int tabs = tokenizer->get_token_line_tab_indent();
-			IndentLevel current_level = indent_level.back()->get();
-			IndentLevel new_indent(indent, tabs);
-			if (new_indent.is_mixed(current_level)) {
-				_set_error("Mixed tabs and spaces in indentation.");
-				return false;
-			}
+	}
+	push_error(p_error_message);
+	return false;
+}
 
 
-			if (indent <= current_level.indent) {
-				return false;
-			}
+bool GDScriptParser::is_at_end() {
+	return check(GDScriptTokenizer::Token::TK_EOF);
+}
 
 
-			indent_level.push_back(new_indent);
-			tokenizer->advance();
-			return true;
+void GDScriptParser::synchronize() {
+	panic_mode = false;
+	while (!is_at_end()) {
+		if (previous.type == GDScriptTokenizer::Token::NEWLINE || previous.type == GDScriptTokenizer::Token::SEMICOLON) {
+			return;
+		}
 
 
-		} else if (p_block) {
-			NewLineNode *nl = alloc_node<NewLineNode>();
-			nl->line = tokenizer->get_token_line();
-			p_block->statements.push_back(nl);
+		switch (current.type) {
+			case GDScriptTokenizer::Token::CLASS:
+			case GDScriptTokenizer::Token::FUNC:
+			case GDScriptTokenizer::Token::STATIC:
+			case GDScriptTokenizer::Token::VAR:
+			case GDScriptTokenizer::Token::CONST:
+			case GDScriptTokenizer::Token::SIGNAL:
+			//case GDScriptTokenizer::Token::IF: // Can also be inside expressions.
+			case GDScriptTokenizer::Token::FOR:
+			case GDScriptTokenizer::Token::WHILE:
+			case GDScriptTokenizer::Token::MATCH:
+			case GDScriptTokenizer::Token::RETURN:
+			case GDScriptTokenizer::Token::ANNOTATION:
+				return;
+			default:
+				// Do nothing.
+				break;
 		}
 		}
 
 
-		tokenizer->advance(); // go to next newline
+		advance();
 	}
 	}
 }
 }
 
 
-bool GDScriptParser::_parse_arguments(Node *p_parent, Vector<Node *> &p_args, bool p_static, bool p_can_codecomplete, bool p_parsing_constant) {
-	if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-		tokenizer->advance();
-	} else {
-		parenthesis++;
-		int argidx = 0;
-
-		while (true) {
-			if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) {
-				_make_completable_call(argidx);
-				completion_node = p_parent;
-			} else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT && tokenizer->get_token_constant().get_type() == Variant::STRING && tokenizer->get_token(1) == GDScriptTokenizer::TK_CURSOR) {
-				//completing a string argument..
-				completion_cursor = tokenizer->get_token_constant();
-
-				_make_completable_call(argidx);
-				completion_node = p_parent;
-				tokenizer->advance(1);
-				return false;
-			}
+void GDScriptParser::push_multiline(bool p_state) {
+	multiline_stack.push_back(p_state);
+	tokenizer.set_multiline_mode(p_state);
+	if (p_state) {
+		// Consume potential whitespace tokens already waiting in line.
+		while (current.type == GDScriptTokenizer::Token::NEWLINE || current.type == GDScriptTokenizer::Token::INDENT || current.type == GDScriptTokenizer::Token::DEDENT) {
+			current = tokenizer.scan(); // Don't call advance() here, as we don't want to change the previous token.
+		}
+	}
+}
 
 
-			Node *arg = _parse_expression(p_parent, p_static, false, p_parsing_constant);
-			if (!arg) {
-				return false;
-			}
+void GDScriptParser::pop_multiline() {
+	ERR_FAIL_COND_MSG(multiline_stack.size() == 0, "Parser bug: trying to pop from multiline stack without available value.");
+	multiline_stack.pop_back();
+	tokenizer.set_multiline_mode(multiline_stack.size() > 0 ? multiline_stack.back()->get() : false);
+}
+
+bool GDScriptParser::is_statement_end() {
+	return check(GDScriptTokenizer::Token::NEWLINE) || check(GDScriptTokenizer::Token::SEMICOLON);
+}
 
 
-			p_args.push_back(arg);
+void GDScriptParser::end_statement(const String &p_context) {
+	bool found = false;
+	while (is_statement_end()) {
+		// Remove sequential newlines/semicolons.
+		found = true;
+		advance();
+	}
+	if (!found) {
+		push_error(vformat(R"(Expected end of statement after %s, found "%s" instead.)", p_context, current.get_name()));
+	}
+}
 
 
-			if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-				tokenizer->advance();
-				break;
+void GDScriptParser::parse_program() {
+	if (current.type == GDScriptTokenizer::Token::TK_EOF) {
+		// Empty file.
+		push_error("Source file is empty.");
+		return;
+	}
 
 
-			} else if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
-				if (tokenizer->get_token(1) == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-					_set_error("Expression expected");
-					return false;
-				}
+	head = alloc_node<ClassNode>();
+	current_class = head;
 
 
-				tokenizer->advance();
-				argidx++;
-			} else {
-				// something is broken
-				_set_error("Expected ',' or ')'");
-				return false;
+	if (match(GDScriptTokenizer::Token::ANNOTATION)) {
+		// Check for @tool annotation.
+		AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::CLASS_LEVEL);
+		if (annotation->name == "@tool") {
+			// TODO: don't allow @tool anywhere else. (Should all script annotations be the first thing?).
+			_is_tool = true;
+			if (previous.type != GDScriptTokenizer::Token::NEWLINE) {
+				push_error(R"(Expected newline after "@tool" annotation.)");
 			}
 			}
+			// @tool annotation has no specific target.
+			annotation->apply(this, nullptr);
+		} else {
+			annotation_stack.push_back(annotation);
 		}
 		}
-		parenthesis--;
 	}
 	}
 
 
-	return true;
-}
-
-void GDScriptParser::_make_completable_call(int p_arg) {
-	completion_cursor = StringName();
-	completion_type = COMPLETION_CALL_ARGUMENTS;
-	completion_class = current_class;
-	completion_function = current_function;
-	completion_line = tokenizer->get_token_line();
-	completion_argument = p_arg;
-	completion_block = current_block;
-	completion_found = true;
-	tokenizer->advance();
-}
+	for (bool should_break = false; !should_break;) {
+		// Order here doesn't matter, but there should be only one of each at most.
+		switch (current.type) {
+			case GDScriptTokenizer::Token::CLASS_NAME:
+				if (!annotation_stack.empty()) {
+					push_error(R"("class_name" should be used before annotations.)");
+				}
+				advance();
+				if (head->identifier != nullptr) {
+					push_error(R"("class_name" can only be used once.)");
+				} else {
+					parse_class_name();
+				}
+				break;
+			case GDScriptTokenizer::Token::EXTENDS:
+				if (!annotation_stack.empty()) {
+					push_error(R"("extends" should be used before annotations.)");
+				}
+				advance();
+				if (head->extends_used) {
+					push_error(R"("extends" can only be used once.)");
+				} else {
+					parse_extends();
+				}
+				break;
+			default:
+				should_break = true;
+				break;
+		}
 
 
-bool GDScriptParser::_get_completable_identifier(CompletionType p_type, StringName &identifier) {
-	identifier = StringName();
-	if (tokenizer->is_token_literal()) {
-		identifier = tokenizer->get_token_literal();
-		tokenizer->advance();
-	}
-	if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) {
-		completion_cursor = identifier;
-		completion_type = p_type;
-		completion_class = current_class;
-		completion_function = current_function;
-		completion_line = tokenizer->get_token_line();
-		completion_block = current_block;
-		completion_found = true;
-		completion_ident_is_call = false;
-		tokenizer->advance();
-
-		if (tokenizer->is_token_literal()) {
-			identifier = identifier.operator String() + tokenizer->get_token_literal().operator String();
-			tokenizer->advance();
+		if (panic_mode) {
+			synchronize();
 		}
 		}
+	}
 
 
-		if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
-			completion_ident_is_call = true;
+	if (match(GDScriptTokenizer::Token::ANNOTATION)) {
+		// Check for @icon annotation.
+		AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::CLASS_LEVEL);
+		if (annotation != nullptr) {
+			if (annotation->name == "@icon") {
+				if (previous.type != GDScriptTokenizer::Token::NEWLINE) {
+					push_error(R"(Expected newline after "@icon" annotation.)");
+				}
+				annotation->apply(this, head);
+			} else {
+				annotation_stack.push_back(annotation);
+			}
 		}
 		}
-		return true;
 	}
 	}
 
 
-	return false;
+	parse_class_body();
+
+	if (!check(GDScriptTokenizer::Token::TK_EOF)) {
+		push_error("Expected end of file.");
+	}
+
+	clear_unused_annotations();
 }
 }
 
 
-GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_static, bool p_allow_assign, bool p_parsing_constant) {
-	//Vector<Node*> expressions;
-	//Vector<OperatorNode::Operator> operators;
+GDScriptParser::ClassNode *GDScriptParser::parse_class() {
+	ClassNode *n_class = alloc_node<ClassNode>();
 
 
-	Vector<Expression> expression;
+	ClassNode *previous_class = current_class;
+	current_class = n_class;
+	n_class->outer = previous_class;
 
 
-	Node *expr = nullptr;
+	if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for the class name after "class".)")) {
+		n_class->identifier = parse_identifier();
+	}
 
 
-	int op_line = tokenizer->get_token_line(); // when operators are created at the bottom, the line might have been changed (\n found)
+	if (check(GDScriptTokenizer::Token::EXTENDS)) {
+		parse_extends();
+	}
 
 
-	while (true) {
-		/*****************/
-		/* Parse Operand */
-		/*****************/
+	consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after class declaration.)");
+	consume(GDScriptTokenizer::Token::NEWLINE, R"(Expected newline after class declaration.)");
 
 
-		if (parenthesis > 0) {
-			//remove empty space (only allowed if inside parenthesis
-			while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) {
-				tokenizer->advance();
-			}
-		}
+	if (!consume(GDScriptTokenizer::Token::INDENT, R"(Expected indented block after class declaration.)")) {
+		current_class = previous_class;
+		return n_class;
+	}
 
 
-		// Check that the next token is not TK_CURSOR and if it is, the offset should be incremented.
-		int next_valid_offset = 1;
-		if (tokenizer->get_token(next_valid_offset) == GDScriptTokenizer::TK_CURSOR) {
-			next_valid_offset++;
-			// There is a chunk of the identifier that also needs to be ignored (not always there!)
-			if (tokenizer->get_token(next_valid_offset) == GDScriptTokenizer::TK_IDENTIFIER) {
-				next_valid_offset++;
-			}
-		}
+	parse_class_body();
 
 
-		if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
-			//subexpression ()
-			tokenizer->advance();
-			parenthesis++;
-			Node *subexpr = _parse_expression(p_parent, p_static, p_allow_assign, p_parsing_constant);
-			parenthesis--;
-			if (!subexpr) {
-				return nullptr;
-			}
+	consume(GDScriptTokenizer::Token::DEDENT, R"(Missing unindent at the end of the class body.)");
 
 
-			if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-				_set_error("Expected ')' in expression");
-				return nullptr;
-			}
+	current_class = previous_class;
+	return n_class;
+}
 
 
-			tokenizer->advance();
-			expr = subexpr;
-		} else if (tokenizer->get_token() == GDScriptTokenizer::TK_DOLLAR) {
-			tokenizer->advance();
-
-			String path;
-
-			bool need_identifier = true;
-			bool done = false;
-			int line = tokenizer->get_token_line();
-
-			while (!done) {
-				switch (tokenizer->get_token()) {
-					case GDScriptTokenizer::TK_CURSOR: {
-						completion_type = COMPLETION_GET_NODE;
-						completion_class = current_class;
-						completion_function = current_function;
-						completion_line = tokenizer->get_token_line();
-						completion_cursor = path;
-						completion_argument = 0;
-						completion_block = current_block;
-						completion_found = true;
-						tokenizer->advance();
-					} break;
-					case GDScriptTokenizer::TK_CONSTANT: {
-						if (!need_identifier) {
-							done = true;
-							break;
-						}
+void GDScriptParser::parse_class_name() {
+	if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for the global class name after "class_name".)")) {
+		current_class->identifier = parse_identifier();
+	}
 
 
-						if (tokenizer->get_token_constant().get_type() != Variant::STRING) {
-							_set_error("Expected string constant or identifier after '$' or '/'.");
-							return nullptr;
-						}
+	// TODO: Move this to annotation
+	if (match(GDScriptTokenizer::Token::COMMA)) {
+		// Icon path.
+		if (consume(GDScriptTokenizer::Token::LITERAL, R"(Expected class icon path string after ",".)")) {
+			if (previous.literal.get_type() != Variant::STRING) {
+				push_error(vformat(R"(Only strings can be used for the class icon path, found "%s" instead.)", Variant::get_type_name(previous.literal.get_type())));
+			}
+			current_class->icon_path = previous.literal;
+		}
+	}
 
 
-						path += String(tokenizer->get_token_constant());
-						tokenizer->advance();
-						need_identifier = false;
+	if (match(GDScriptTokenizer::Token::EXTENDS)) {
+		// Allow extends on the same line.
+		parse_extends();
+	} else {
+		end_statement("class_name statement");
+	}
+}
 
 
-					} break;
-					case GDScriptTokenizer::TK_OP_DIV: {
-						if (need_identifier) {
-							done = true;
-							break;
-						}
+void GDScriptParser::parse_extends() {
+	current_class->extends_used = true;
 
 
-						path += "/";
-						tokenizer->advance();
-						need_identifier = true;
-
-					} break;
-					default: {
-						// Instead of checking for TK_IDENTIFIER, we check with is_token_literal, as this allows us to use match/sync/etc. as a name
-						if (need_identifier && tokenizer->is_token_literal()) {
-							path += String(tokenizer->get_token_literal());
-							tokenizer->advance();
-							need_identifier = false;
-						} else {
-							done = true;
-						}
+	if (match(GDScriptTokenizer::Token::LITERAL)) {
+		if (previous.literal.get_type() != Variant::STRING) {
+			push_error(vformat(R"(Only strings or identifiers can be used after "extends", found "%s" instead.)", Variant::get_type_name(previous.literal.get_type())));
+		}
+		current_class->extends_path = previous.literal;
 
 
-						break;
-					}
-				}
-			}
+		if (!match(GDScriptTokenizer::Token::PERIOD)) {
+			end_statement("superclass path");
+			return;
+		}
+	}
 
 
-			if (path == "") {
-				_set_error("Path expected after $.");
-				return nullptr;
-			}
+	if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected superclass name after "extends".)")) {
+		return;
+	}
+	current_class->extends.push_back(previous.literal);
 
 
-			OperatorNode *op = alloc_node<OperatorNode>();
-			op->op = OperatorNode::OP_CALL;
-			op->line = line;
-			op->arguments.push_back(alloc_node<SelfNode>());
-			op->arguments[0]->line = line;
-
-			IdentifierNode *funcname = alloc_node<IdentifierNode>();
-			funcname->name = "get_node";
-			funcname->line = line;
-			op->arguments.push_back(funcname);
-
-			ConstantNode *nodepath = alloc_node<ConstantNode>();
-			nodepath->value = NodePath(StringName(path));
-			nodepath->datatype = _type_from_variant(nodepath->value);
-			nodepath->line = line;
-			op->arguments.push_back(nodepath);
-
-			expr = op;
-
-		} else if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) {
-			tokenizer->advance();
-			continue; //no point in cursor in the middle of expression
-
-		} else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT) {
-			//constant defined by tokenizer
-			ConstantNode *constant = alloc_node<ConstantNode>();
-			constant->value = tokenizer->get_token_constant();
-			constant->datatype = _type_from_variant(constant->value);
-			tokenizer->advance();
-			expr = constant;
-		} else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_PI) {
-			//constant defined by tokenizer
-			ConstantNode *constant = alloc_node<ConstantNode>();
-			constant->value = Math_PI;
-			constant->datatype = _type_from_variant(constant->value);
-			tokenizer->advance();
-			expr = constant;
-		} else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_TAU) {
-			//constant defined by tokenizer
-			ConstantNode *constant = alloc_node<ConstantNode>();
-			constant->value = Math_TAU;
-			constant->datatype = _type_from_variant(constant->value);
-			tokenizer->advance();
-			expr = constant;
-		} else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_INF) {
-			//constant defined by tokenizer
-			ConstantNode *constant = alloc_node<ConstantNode>();
-			constant->value = Math_INF;
-			constant->datatype = _type_from_variant(constant->value);
-			tokenizer->advance();
-			expr = constant;
-		} else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_NAN) {
-			//constant defined by tokenizer
-			ConstantNode *constant = alloc_node<ConstantNode>();
-			constant->value = Math_NAN;
-			constant->datatype = _type_from_variant(constant->value);
-			tokenizer->advance();
-			expr = constant;
-		} else if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_PRELOAD) {
-			//constant defined by tokenizer
-			tokenizer->advance();
-			if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
-				_set_error("Expected '(' after 'preload'");
-				return nullptr;
-			}
-			tokenizer->advance();
-
-			if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) {
-				completion_cursor = StringName();
-				completion_node = p_parent;
-				completion_type = COMPLETION_RESOURCE_PATH;
-				completion_class = current_class;
-				completion_function = current_function;
-				completion_line = tokenizer->get_token_line();
-				completion_block = current_block;
-				completion_argument = 0;
-				completion_found = true;
-				tokenizer->advance();
-			}
+	while (match(GDScriptTokenizer::Token::PERIOD)) {
+		if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected superclass name after ".".)")) {
+			return;
+		}
+		current_class->extends.push_back(previous.literal);
+	}
 
 
-			String path;
-			bool found_constant = false;
-			bool valid = false;
-			ConstantNode *cn;
+	end_statement("superclass");
+}
 
 
-			Node *subexpr = _parse_and_reduce_expression(p_parent, p_static);
-			if (subexpr) {
-				if (subexpr->type == Node::TYPE_CONSTANT) {
-					cn = static_cast<ConstantNode *>(subexpr);
-					found_constant = true;
-				}
-				if (subexpr->type == Node::TYPE_IDENTIFIER) {
-					IdentifierNode *in = static_cast<IdentifierNode *>(subexpr);
-
-					// Try to find the constant expression by the identifier
-					if (current_class->constant_expressions.has(in->name)) {
-						Node *cn_exp = current_class->constant_expressions[in->name].expression;
-						if (cn_exp->type == Node::TYPE_CONSTANT) {
-							cn = static_cast<ConstantNode *>(cn_exp);
-							found_constant = true;
-						}
-					}
-				}
+template <class T>
+void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(), AnnotationInfo::TargetKind p_target, const String &p_member_kind) {
+	advance();
+	T *member = (this->*p_parse_function)();
+	if (member == nullptr) {
+		return;
+	}
+	// Consume annotations.
+	while (!annotation_stack.empty()) {
+		AnnotationNode *last_annotation = annotation_stack.back()->get();
+		if (last_annotation->applies_to(p_target)) {
+			last_annotation->apply(this, member);
+			member->annotations.push_front(last_annotation);
+			annotation_stack.pop_back();
+		} else {
+			push_error(vformat(R"(Annotation "%s" cannot be applied to a %s.)", last_annotation->name, p_member_kind));
+			clear_unused_annotations();
+			return;
+		}
+	}
+	if (member->identifier != nullptr) {
+		// Enums may be unnamed.
+		// TODO: Consider names in outer scope too, for constants and classes (and static functions?)
+		if (current_class->members_indices.has(member->identifier->name)) {
+			push_error(vformat(R"(%s "%s" has the same name as a previously declared %s.)", p_member_kind.capitalize(), member->identifier->name, current_class->get_member(member->identifier->name).get_type_name()));
+		} else {
+			current_class->add_member(member);
+		}
+	}
+}
 
 
-				if (found_constant && cn->value.get_type() == Variant::STRING) {
-					valid = true;
-					path = (String)cn->value;
+void GDScriptParser::parse_class_body() {
+	bool class_end = false;
+	while (!class_end && !is_at_end()) {
+		switch (current.type) {
+			case GDScriptTokenizer::Token::VAR:
+				parse_class_member(&GDScriptParser::parse_variable, AnnotationInfo::VARIABLE, "variable");
+				break;
+			case GDScriptTokenizer::Token::CONST:
+				parse_class_member(&GDScriptParser::parse_constant, AnnotationInfo::CONSTANT, "constant");
+				break;
+			case GDScriptTokenizer::Token::SIGNAL:
+				parse_class_member(&GDScriptParser::parse_signal, AnnotationInfo::SIGNAL, "signal");
+				break;
+			case GDScriptTokenizer::Token::STATIC:
+			case GDScriptTokenizer::Token::FUNC:
+				parse_class_member(&GDScriptParser::parse_function, AnnotationInfo::FUNCTION, "function");
+				break;
+			case GDScriptTokenizer::Token::CLASS:
+				parse_class_member(&GDScriptParser::parse_class, AnnotationInfo::CLASS, "class");
+				break;
+			case GDScriptTokenizer::Token::ENUM:
+				parse_class_member(&GDScriptParser::parse_enum, AnnotationInfo::NONE, "enum");
+				break;
+			case GDScriptTokenizer::Token::ANNOTATION: {
+				advance();
+				AnnotationNode *annotation = parse_annotation(AnnotationInfo::CLASS_LEVEL);
+				if (annotation != nullptr) {
+					annotation_stack.push_back(annotation);
 				}
 				}
+				break;
 			}
 			}
+			case GDScriptTokenizer::Token::PASS:
+				end_statement(R"("pass")");
+				break;
+			case GDScriptTokenizer::Token::DEDENT:
+				class_end = true;
+				break;
+			default:
+				push_error(vformat(R"(Unexpected "%s" in class body.)", current.get_name()));
+				advance();
+				break;
+		}
+		if (panic_mode) {
+			synchronize();
+		}
+	}
+}
 
 
-			if (!valid) {
-				_set_error("expected string constant as 'preload' argument.");
-				return nullptr;
-			}
+GDScriptParser::VariableNode *GDScriptParser::parse_variable() {
+	if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected variable name after "var".)")) {
+		return nullptr;
+	}
 
 
-			if (!path.is_abs_path() && base_path != "") {
-				path = base_path.plus_file(path);
-			}
-			path = path.replace("///", "//").simplify_path();
-			if (path == self_path) {
-				_set_error("Can't preload itself (use 'get_script()').");
-				return nullptr;
-			}
+	VariableNode *variable = alloc_node<VariableNode>();
+	variable->identifier = parse_identifier();
 
 
-			Ref<Resource> res;
-			dependencies.push_back(path);
-			if (!dependencies_only) {
-				if (!validating) {
-					//this can be too slow for just validating code
-					if (for_completion && ScriptCodeCompletionCache::get_singleton() && FileAccess::exists(path)) {
-						res = ScriptCodeCompletionCache::get_singleton()->get_cached_resource(path);
-					} else if (!for_completion || FileAccess::exists(path)) {
-						res = ResourceLoader::load(path);
-					}
-				} else {
-					if (!FileAccess::exists(path)) {
-						_set_error("Can't preload resource at path: " + path);
-						return nullptr;
-					} else if (ScriptCodeCompletionCache::get_singleton()) {
-						res = ScriptCodeCompletionCache::get_singleton()->get_cached_resource(path);
-					}
-				}
-				if (!res.is_valid()) {
-					_set_error("Can't preload resource at path: " + path);
-					return nullptr;
-				}
-			}
+	if (match(GDScriptTokenizer::Token::COLON)) {
+		if (check((GDScriptTokenizer::Token::EQUAL))) {
+			// Infer type.
+			variable->infer_datatype = true;
+		} else {
+			// Parse type.
+			variable->datatype_specifier = parse_type();
+		}
+	}
 
 
-			if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-				_set_error("Expected ')' after 'preload' path");
-				return nullptr;
-			}
+	if (match(GDScriptTokenizer::Token::EQUAL)) {
+		// Initializer.
+		variable->initializer = parse_expression(false);
+	}
 
 
-			Ref<GDScript> gds = res;
-			if (gds.is_valid() && !gds->is_valid()) {
-				_set_error("Couldn't fully preload the script, possible cyclic reference or compilation error. Use \"load()\" instead if a cyclic reference is intended.");
-				return nullptr;
-			}
+	end_statement("variable declaration");
 
 
-			tokenizer->advance();
+	variable->export_info.name = variable->identifier->name;
 
 
-			ConstantNode *constant = alloc_node<ConstantNode>();
-			constant->value = res;
-			constant->datatype = _type_from_variant(constant->value);
+	return variable;
+}
 
 
-			expr = constant;
-		} else if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_YIELD) {
-			if (!current_function) {
-				_set_error("\"yield()\" can only be used inside function blocks.");
-				return nullptr;
-			}
+GDScriptParser::ConstantNode *GDScriptParser::parse_constant() {
+	if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected constant name after "const".)")) {
+		return nullptr;
+	}
 
 
-			current_function->has_yield = true;
+	ConstantNode *constant = alloc_node<ConstantNode>();
+	constant->identifier = parse_identifier();
 
 
-			tokenizer->advance();
-			if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
-				_set_error("Expected \"(\" after \"yield\".");
-				return nullptr;
-			}
+	if (match(GDScriptTokenizer::Token::COLON)) {
+		if (check((GDScriptTokenizer::Token::EQUAL))) {
+			// Infer type.
+			constant->infer_datatype = true;
+		} else {
+			// Parse type.
+			constant->datatype_specifier = parse_type();
+		}
+	}
 
 
-			tokenizer->advance();
+	if (consume(GDScriptTokenizer::Token::EQUAL, R"(Expected initializer after constant name.)")) {
+		// Initializer.
+		constant->initializer = parse_expression(false);
 
 
-			OperatorNode *yield = alloc_node<OperatorNode>();
-			yield->op = OperatorNode::OP_YIELD;
+		if (constant->initializer == nullptr) {
+			push_error(R"(Expected initializer expression for constant.)");
+			return nullptr;
+		}
+	}
 
 
-			while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) {
-				tokenizer->advance();
-			}
+	end_statement("constant declaration");
 
 
-			if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-				expr = yield;
-				tokenizer->advance();
-			} else {
-				parenthesis++;
+	return constant;
+}
 
 
-				Node *object = _parse_and_reduce_expression(p_parent, p_static);
-				if (!object) {
-					return nullptr;
-				}
-				yield->arguments.push_back(object);
+GDScriptParser::ParameterNode *GDScriptParser::parse_parameter() {
+	if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected parameter name.)")) {
+		return nullptr;
+	}
 
 
-				if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) {
-					_set_error("Expected \",\" after the first argument of \"yield\".");
-					return nullptr;
-				}
+	ParameterNode *parameter = alloc_node<ParameterNode>();
+	parameter->identifier = parse_identifier();
 
 
-				tokenizer->advance();
-
-				if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) {
-					completion_cursor = StringName();
-					completion_node = object;
-					completion_type = COMPLETION_YIELD;
-					completion_class = current_class;
-					completion_function = current_function;
-					completion_line = tokenizer->get_token_line();
-					completion_argument = 0;
-					completion_block = current_block;
-					completion_found = true;
-					tokenizer->advance();
-				}
+	if (match(GDScriptTokenizer::Token::COLON)) {
+		if (check((GDScriptTokenizer::Token::EQUAL))) {
+			// Infer type.
+			parameter->infer_datatype = true;
+		} else {
+			// Parse type.
+			parameter->datatype_specifier = parse_type();
+		}
+	}
 
 
-				Node *signal = _parse_and_reduce_expression(p_parent, p_static);
-				if (!signal) {
-					return nullptr;
-				}
-				yield->arguments.push_back(signal);
+	if (match(GDScriptTokenizer::Token::EQUAL)) {
+		// Default value.
+		parameter->default_value = parse_expression(false);
+	}
 
 
-				if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-					_set_error("Expected \")\" after the second argument of \"yield\".");
-					return nullptr;
-				}
+	return parameter;
+}
 
 
-				parenthesis--;
+GDScriptParser::SignalNode *GDScriptParser::parse_signal() {
+	if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected signal name after "signal".)")) {
+		return nullptr;
+	}
 
 
-				tokenizer->advance();
+	SignalNode *signal = alloc_node<SignalNode>();
+	signal->identifier = parse_identifier();
 
 
-				expr = yield;
+	if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
+		while (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) {
+			ParameterNode *parameter = parse_parameter();
+			if (parameter == nullptr) {
+				break;
 			}
 			}
-
-		} else if (tokenizer->get_token() == GDScriptTokenizer::TK_SELF) {
-			if (p_static) {
-				_set_error("\"self\" isn't allowed in a static function or constant expression.");
-				return nullptr;
+			if (parameter->default_value != nullptr) {
+				push_error(R"(Signal parameters cannot have a default value.)");
 			}
 			}
-			//constant defined by tokenizer
-			SelfNode *self = alloc_node<SelfNode>();
-			tokenizer->advance();
-			expr = self;
-		} else if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_TYPE && tokenizer->get_token(1) == GDScriptTokenizer::TK_PERIOD) {
-			Variant::Type bi_type = tokenizer->get_token_type();
-			tokenizer->advance(2);
-
-			StringName identifier;
-
-			if (_get_completable_identifier(COMPLETION_BUILT_IN_TYPE_CONSTANT, identifier)) {
-				completion_built_in_constant = bi_type;
+			if (signal->parameters_indices.has(parameter->identifier->name)) {
+				push_error(vformat(R"(Parameter with name "%s" was already declared for this signal.)", parameter->identifier->name));
+			} else {
+				signal->parameters_indices[parameter->identifier->name] = signal->parameters.size();
+				signal->parameters.push_back(parameter);
 			}
 			}
+		}
+		consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after signal parameters.)*");
+	}
 
 
-			if (identifier == StringName()) {
-				_set_error("Built-in type constant or static function expected after \".\".");
-				return nullptr;
-			}
-			if (!Variant::has_constant(bi_type, identifier)) {
-				if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_OPEN &&
-						Variant::is_method_const(bi_type, identifier) &&
-						Variant::get_method_return_type(bi_type, identifier) == bi_type) {
-					tokenizer->advance();
+	end_statement("signal declaration");
 
 
-					OperatorNode *construct = alloc_node<OperatorNode>();
-					construct->op = OperatorNode::OP_CALL;
+	return signal;
+}
 
 
-					TypeNode *tn = alloc_node<TypeNode>();
-					tn->vtype = bi_type;
-					construct->arguments.push_back(tn);
+GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
+	EnumNode *enum_node = alloc_node<EnumNode>();
+	bool named = false;
 
 
-					OperatorNode *op = alloc_node<OperatorNode>();
-					op->op = OperatorNode::OP_CALL;
-					op->arguments.push_back(construct);
+	if (check(GDScriptTokenizer::Token::IDENTIFIER)) {
+		advance();
+		enum_node->identifier = parse_identifier();
+		named = true;
+	}
 
 
-					IdentifierNode *id = alloc_node<IdentifierNode>();
-					id->name = identifier;
-					op->arguments.push_back(id);
+	push_multiline(true);
+	consume(GDScriptTokenizer::Token::BRACE_OPEN, vformat(R"(Expected "{" after %s.)", named ? "enum name" : R"("enum")"));
 
 
-					if (!_parse_arguments(op, op->arguments, p_static, true, p_parsing_constant)) {
-						return nullptr;
-					}
+	int current_value = 0;
 
 
-					expr = op;
-				} else {
-					// Object is a special case
-					bool valid = false;
-					if (bi_type == Variant::OBJECT) {
-						int object_constant = ClassDB::get_integer_constant("Object", identifier, &valid);
-						if (valid) {
-							ConstantNode *cn = alloc_node<ConstantNode>();
-							cn->value = object_constant;
-							cn->datatype = _type_from_variant(cn->value);
-							expr = cn;
-						}
-					}
-					if (!valid) {
-						_set_error("Static constant  '" + identifier.operator String() + "' not present in built-in type " + Variant::get_type_name(bi_type) + ".");
-						return nullptr;
+	do {
+		if (check(GDScriptTokenizer::Token::BRACE_CLOSE)) {
+			break; // Allow trailing comma.
+		}
+		if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifer for enum key.)")) {
+			EnumNode::Value item;
+			item.identifier = parse_identifier();
+			bool found = false;
+
+			if (!named) {
+				// TODO: Abstract this recursive member check.
+				ClassNode *parent = current_class;
+				while (parent != nullptr) {
+					if (parent->members_indices.has(item.identifier->name)) {
+						push_error(vformat(R"(Name "%s" is already used as a class %s.)", item.identifier->name, parent->get_member(item.identifier->name).get_type_name()));
+						found = true;
+						break;
 					}
 					}
+					parent = parent->outer;
 				}
 				}
-			} else {
-				ConstantNode *cn = alloc_node<ConstantNode>();
-				cn->value = Variant::get_constant_value(bi_type, identifier);
-				cn->datatype = _type_from_variant(cn->value);
-				expr = cn;
 			}
 			}
 
 
-		} else if (tokenizer->get_token(next_valid_offset) == GDScriptTokenizer::TK_PARENTHESIS_OPEN && tokenizer->is_token_literal()) {
-			// We check with is_token_literal, as this allows us to use match/sync/etc. as a name
-			//function or constructor
-
-			OperatorNode *op = alloc_node<OperatorNode>();
-			op->op = OperatorNode::OP_CALL;
-
-			//Do a quick Array and Dictionary Check.  Replace if either require no arguments.
-			bool replaced = false;
-
-			if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_TYPE) {
-				Variant::Type ct = tokenizer->get_token_type();
-				if (!p_parsing_constant) {
-					if (ct == Variant::ARRAY) {
-						if (tokenizer->get_token(2) == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-							ArrayNode *arr = alloc_node<ArrayNode>();
-							expr = arr;
-							replaced = true;
-							tokenizer->advance(3);
-						}
-					}
-					if (ct == Variant::DICTIONARY) {
-						if (tokenizer->get_token(2) == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-							DictionaryNode *dict = alloc_node<DictionaryNode>();
-							expr = dict;
-							replaced = true;
-							tokenizer->advance(3);
-						}
+			if (match(GDScriptTokenizer::Token::EQUAL)) {
+				if (consume(GDScriptTokenizer::Token::LITERAL, R"(Expected integer value after "=".)")) {
+					item.custom_value = parse_literal();
+
+					if (item.custom_value->value.get_type() != Variant::INT) {
+						push_error(R"(Expected integer value after "=".)");
+						item.custom_value = nullptr;
+					} else {
+						current_value = item.custom_value->value;
 					}
 					}
 				}
 				}
+			}
+			item.value = current_value++;
+			enum_node->values.push_back(item);
+			if (!found) {
+				// Add as member of current class.
+				current_class->add_member(item);
+			}
+		}
+	} while (match(GDScriptTokenizer::Token::COMMA));
 
 
-				if (!replaced) {
-					TypeNode *tn = alloc_node<TypeNode>();
-					tn->vtype = tokenizer->get_token_type();
-					op->arguments.push_back(tn);
-					tokenizer->advance(2);
-				}
-			} else if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_FUNC) {
-				BuiltInFunctionNode *bn = alloc_node<BuiltInFunctionNode>();
-				bn->function = tokenizer->get_token_built_in_func();
-				op->arguments.push_back(bn);
-				tokenizer->advance(2);
-			} else {
-				SelfNode *self = alloc_node<SelfNode>();
-				op->arguments.push_back(self);
+	pop_multiline();
+	consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" for enum.)");
 
 
-				StringName identifier;
-				if (_get_completable_identifier(COMPLETION_FUNCTION, identifier)) {
-				}
+	end_statement("enum");
 
 
-				IdentifierNode *id = alloc_node<IdentifierNode>();
-				id->name = identifier;
-				op->arguments.push_back(id);
-				tokenizer->advance(1);
-			}
+	return enum_node;
+}
 
 
-			if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) {
-				_make_completable_call(0);
-				completion_node = op;
+GDScriptParser::FunctionNode *GDScriptParser::parse_function() {
+	bool _static = false;
+	if (previous.type == GDScriptTokenizer::Token::STATIC) {
+		// TODO: Improve message if user uses "static" with "var" or "const"
+		if (!consume(GDScriptTokenizer::Token::FUNC, R"(Expected "func" after "static".)")) {
+			return nullptr;
+		}
+		_static = true;
+	}
 
 
-				if (op->arguments[0]->type == GDScriptParser::Node::Type::TYPE_BUILT_IN_FUNCTION) {
-					BuiltInFunctionNode *bn = static_cast<BuiltInFunctionNode *>(op->arguments[0]);
-					if (bn->function == GDScriptFunctions::Function::RESOURCE_LOAD) {
-						completion_type = COMPLETION_RESOURCE_PATH;
-					}
-				}
+	if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after "func".)")) {
+		return nullptr;
+	}
+
+	FunctionNode *function = alloc_node<FunctionNode>();
+	FunctionNode *previous_function = current_function;
+	current_function = function;
+
+	function->identifier = parse_identifier();
+	function->is_static = _static;
+
+	push_multiline(true);
+	consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected opening "(" after function name.)");
+
+	if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) {
+		bool default_used = false;
+		do {
+			if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
+				// Allow for trailing comma.
+				break;
+			}
+			ParameterNode *parameter = parse_parameter();
+			if (parameter == nullptr) {
+				break;
 			}
 			}
-			if (!replaced) {
-				if (!_parse_arguments(op, op->arguments, p_static, true, p_parsing_constant)) {
-					return nullptr;
+			if (parameter->default_value != nullptr) {
+				default_used = true;
+			} else {
+				if (default_used) {
+					push_error("Cannot have a mandatory parameters after optional parameters.");
+					continue;
 				}
 				}
-				expr = op;
 			}
 			}
-		} else if (tokenizer->is_token_literal(0, true)) {
-			// We check with is_token_literal, as this allows us to use match/sync/etc. as a name
-			//identifier (reference)
-
-			const ClassNode *cln = current_class;
-			bool bfn = false;
-			StringName identifier;
-			int id_line = tokenizer->get_token_line();
-			if (_get_completable_identifier(COMPLETION_IDENTIFIER, identifier)) {
+			if (function->parameters_indices.has(parameter->identifier->name)) {
+				push_error(vformat(R"(Parameter with name "%s" was already declared for this function.)", parameter->identifier->name));
+			} else {
+				function->parameters_indices[parameter->identifier->name] = function->parameters.size();
+				function->parameters.push_back(parameter);
 			}
 			}
+		} while (match(GDScriptTokenizer::Token::COMMA));
+	}
 
 
-			BlockNode *b = current_block;
-			while (!bfn && b) {
-				if (b->variables.has(identifier)) {
-					IdentifierNode *id = alloc_node<IdentifierNode>();
-					id->name = identifier;
-					id->declared_block = b;
-					id->line = id_line;
-					expr = id;
-					bfn = true;
+	pop_multiline();
+	consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after function parameters.)*");
 
 
-#ifdef DEBUG_ENABLED
-					LocalVarNode *lv = b->variables[identifier];
-					switch (tokenizer->get_token()) {
-						case GDScriptTokenizer::TK_OP_ASSIGN_ADD:
-						case GDScriptTokenizer::TK_OP_ASSIGN_BIT_AND:
-						case GDScriptTokenizer::TK_OP_ASSIGN_BIT_OR:
-						case GDScriptTokenizer::TK_OP_ASSIGN_BIT_XOR:
-						case GDScriptTokenizer::TK_OP_ASSIGN_DIV:
-						case GDScriptTokenizer::TK_OP_ASSIGN_MOD:
-						case GDScriptTokenizer::TK_OP_ASSIGN_MUL:
-						case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_LEFT:
-						case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT:
-						case GDScriptTokenizer::TK_OP_ASSIGN_SUB: {
-							if (lv->assignments == 0) {
-								if (!lv->datatype.has_type) {
-									_set_error("Using assignment with operation on a variable that was never assigned.");
-									return nullptr;
-								}
-								_add_warning(GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, -1, identifier.operator String());
-							}
-							[[fallthrough]];
-						}
-						case GDScriptTokenizer::TK_OP_ASSIGN: {
-							lv->assignments += 1;
-							lv->usages--; // Assignment is not really usage
-						} break;
-						default: {
-							lv->usages++;
-						}
-					}
-#endif // DEBUG_ENABLED
-					break;
-				}
-				b = b->parent_block;
-			}
+	if (match(GDScriptTokenizer::Token::FORWARD_ARROW)) {
+		function->return_type = parse_type(true);
+	}
 
 
-			if (!bfn && p_parsing_constant) {
-				if (cln->constant_expressions.has(identifier)) {
-					expr = cln->constant_expressions[identifier].expression;
-					bfn = true;
-				} else if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) {
-					//check from constants
-					ConstantNode *constant = alloc_node<ConstantNode>();
-					constant->value = GDScriptLanguage::get_singleton()->get_global_array()[GDScriptLanguage::get_singleton()->get_global_map()[identifier]];
-					constant->datatype = _type_from_variant(constant->value);
-					constant->line = id_line;
-					expr = constant;
-					bfn = true;
-				}
+	// TODO: Improve token consumption so it synchronizes to a statement boundary. This way we can get into the function body with unrecognized tokens.
+	consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after function declaration.)");
 
 
-				if (!bfn && GDScriptLanguage::get_singleton()->get_named_globals_map().has(identifier)) {
-					//check from singletons
-					ConstantNode *constant = alloc_node<ConstantNode>();
-					constant->value = GDScriptLanguage::get_singleton()->get_named_globals_map()[identifier];
-					constant->datatype = _type_from_variant(constant->value);
-					expr = constant;
-					bfn = true;
-				}
+	function->body = parse_suite("function declaration");
 
 
-				if (!dependencies_only) {
-					if (!bfn && ScriptServer::is_global_class(identifier)) {
-						Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(identifier));
-						if (scr.is_valid() && scr->is_valid()) {
-							ConstantNode *constant = alloc_node<ConstantNode>();
-							constant->value = scr;
-							constant->datatype = _type_from_variant(constant->value);
-							expr = constant;
-							bfn = true;
-						}
-					}
+	current_function = previous_function;
+	return function;
+}
 
 
-					// Check parents for the constant
-					if (!bfn) {
-						// Using current_class instead of cln here, since cln is const*
-						_determine_inheritance(current_class, false);
-						if (cln->base_type.has_type && cln->base_type.kind == DataType::GDSCRIPT && cln->base_type.script_type->is_valid()) {
-							Map<StringName, Variant> parent_constants;
-							current_class->base_type.script_type->get_constants(&parent_constants);
-							if (parent_constants.has(identifier)) {
-								ConstantNode *constant = alloc_node<ConstantNode>();
-								constant->value = parent_constants[identifier];
-								constant->datatype = _type_from_variant(constant->value);
-								expr = constant;
-								bfn = true;
-							}
-						}
-					}
-				}
-			}
+GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_valid_targets) {
+	AnnotationNode *annotation = alloc_node<AnnotationNode>();
 
 
-			if (!bfn) {
-#ifdef DEBUG_ENABLED
-				if (current_function) {
-					int arg_idx = current_function->arguments.find(identifier);
-					if (arg_idx != -1) {
-						switch (tokenizer->get_token()) {
-							case GDScriptTokenizer::TK_OP_ASSIGN_ADD:
-							case GDScriptTokenizer::TK_OP_ASSIGN_BIT_AND:
-							case GDScriptTokenizer::TK_OP_ASSIGN_BIT_OR:
-							case GDScriptTokenizer::TK_OP_ASSIGN_BIT_XOR:
-							case GDScriptTokenizer::TK_OP_ASSIGN_DIV:
-							case GDScriptTokenizer::TK_OP_ASSIGN_MOD:
-							case GDScriptTokenizer::TK_OP_ASSIGN_MUL:
-							case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_LEFT:
-							case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT:
-							case GDScriptTokenizer::TK_OP_ASSIGN_SUB:
-							case GDScriptTokenizer::TK_OP_ASSIGN: {
-								// Assignment is not really usage
-							} break;
-							default: {
-								current_function->arguments_usage.write[arg_idx] = current_function->arguments_usage[arg_idx] + 1;
-							}
-						}
-					}
-				}
-#endif // DEBUG_ENABLED
-				IdentifierNode *id = alloc_node<IdentifierNode>();
-				id->name = identifier;
-				id->line = id_line;
-				expr = id;
-			}
+	annotation->name = previous.literal;
 
 
-		} else if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ADD || tokenizer->get_token() == GDScriptTokenizer::TK_OP_SUB || tokenizer->get_token() == GDScriptTokenizer::TK_OP_NOT || tokenizer->get_token() == GDScriptTokenizer::TK_OP_BIT_INVERT) {
-			//single prefix operators like !expr +expr -expr ++expr --expr
-			alloc_node<OperatorNode>();
-			Expression e;
-			e.is_op = true;
+	bool valid = true;
 
 
-			switch (tokenizer->get_token()) {
-				case GDScriptTokenizer::TK_OP_ADD:
-					e.op = OperatorNode::OP_POS;
-					break;
-				case GDScriptTokenizer::TK_OP_SUB:
-					e.op = OperatorNode::OP_NEG;
-					break;
-				case GDScriptTokenizer::TK_OP_NOT:
-					e.op = OperatorNode::OP_NOT;
-					break;
-				case GDScriptTokenizer::TK_OP_BIT_INVERT:
-					e.op = OperatorNode::OP_BIT_INVERT;
-					break;
-				default: {
+	if (!valid_annotations.has(annotation->name)) {
+		push_error(vformat(R"(Unrecognized annotation: "%s".)", annotation->name));
+		valid = false;
+	}
+
+	annotation->info = &valid_annotations[annotation->name];
+
+	if (!annotation->applies_to(p_valid_targets)) {
+		push_error(vformat(R"(Annotation "%s" is not allowed in this level.)", annotation->name));
+		valid = false;
+	}
+
+	if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
+		// Arguments.
+		if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) {
+			do {
+				ExpressionNode *argument = parse_expression(false);
+				if (argument == nullptr) {
+					valid = false;
+					continue;
 				}
 				}
-			}
+				annotation->arguments.push_back(argument);
+			} while (match(GDScriptTokenizer::Token::COMMA));
+
+			consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after annotation arguments.)*");
+		}
+	}
 
 
-			tokenizer->advance();
+	match(GDScriptTokenizer::Token::NEWLINE); // Newline after annotation is optional.
 
 
-			if (e.op != OperatorNode::OP_NOT && tokenizer->get_token() == GDScriptTokenizer::TK_OP_NOT) {
-				_set_error("Misplaced 'not'.");
-				return nullptr;
-			}
+	if (valid) {
+		valid = validate_annotation_arguments(annotation);
+	}
 
 
-			expression.push_back(e);
-			continue; //only exception, must continue...
+	return valid ? annotation : nullptr;
+}
 
 
-			/*
-			Node *subexpr=_parse_expression(op,p_static);
-			if (!subexpr)
-				return nullptr;
-			op->arguments.push_back(subexpr);
-			expr=op;*/
+void GDScriptParser::clear_unused_annotations() {
+	for (const List<AnnotationNode *>::Element *E = annotation_stack.front(); E != nullptr; E = E->next()) {
+		AnnotationNode *annotation = E->get();
+		push_error(vformat(R"(Annotation "%s" does not precedes a valid target, so it will have no effect.)", annotation->name));
+	}
 
 
-		} else if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_IS && tokenizer->get_token(1) == GDScriptTokenizer::TK_BUILT_IN_TYPE) {
-			// 'is' operator with built-in type
-			if (!expr) {
-				_set_error("Expected identifier before 'is' operator");
-				return nullptr;
-			}
-			OperatorNode *op = alloc_node<OperatorNode>();
-			op->op = OperatorNode::OP_IS_BUILTIN;
-			op->arguments.push_back(expr);
+	annotation_stack.clear();
+}
+
+bool GDScriptParser::register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, int p_optional_arguments, bool p_is_vararg) {
+	ERR_FAIL_COND_V_MSG(valid_annotations.has(p_info.name), false, vformat(R"(Annotation "%s" already registered.)", p_info.name));
 
 
-			tokenizer->advance();
+	AnnotationInfo new_annotation;
+	new_annotation.info = p_info;
+	new_annotation.info.default_arguments.resize(p_optional_arguments);
+	if (p_is_vararg) {
+		new_annotation.info.flags |= METHOD_FLAG_VARARG;
+	}
+	new_annotation.apply = p_apply;
+	new_annotation.target_kind = p_target_kinds;
 
 
-			TypeNode *tn = alloc_node<TypeNode>();
-			tn->vtype = tokenizer->get_token_type();
-			op->arguments.push_back(tn);
-			tokenizer->advance();
+	valid_annotations[p_info.name] = new_annotation;
+	return true;
+}
 
 
-			expr = op;
-		} else if (tokenizer->get_token() == GDScriptTokenizer::TK_BRACKET_OPEN) {
-			// array
-			tokenizer->advance();
+GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context) {
+	SuiteNode *suite = alloc_node<SuiteNode>();
+	suite->parent_block = current_suite;
+	current_suite = suite;
 
 
-			ArrayNode *arr = alloc_node<ArrayNode>();
-			bool expecting_comma = false;
+	bool multiline = false;
 
 
-			while (true) {
-				if (tokenizer->get_token() == GDScriptTokenizer::TK_EOF) {
-					_set_error("Unterminated array");
-					return nullptr;
+	if (check(GDScriptTokenizer::Token::NEWLINE)) {
+		multiline = true;
+	}
 
 
-				} else if (tokenizer->get_token() == GDScriptTokenizer::TK_BRACKET_CLOSE) {
-					tokenizer->advance();
-					break;
-				} else if (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) {
-					tokenizer->advance(); //ignore newline
-				} else if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
-					if (!expecting_comma) {
-						_set_error("expression or ']' expected");
-						return nullptr;
-					}
+	if (multiline) {
+		consume(GDScriptTokenizer::Token::NEWLINE, vformat(R"(Expected newline after %s.)", p_context));
 
 
-					expecting_comma = false;
-					tokenizer->advance(); //ignore newline
-				} else {
-					//parse expression
-					if (expecting_comma) {
-						_set_error("',' or ']' expected");
-						return nullptr;
+		if (!consume(GDScriptTokenizer::Token::INDENT, vformat(R"(Expected indented block after %s.)", p_context))) {
+			current_suite = suite->parent_block;
+			return suite;
+		}
+	}
+
+	do {
+		Node *statement = parse_statement();
+		if (statement == nullptr) {
+			continue;
+		}
+		suite->statements.push_back(statement);
+
+		// Register locals.
+		switch (statement->type) {
+			case Node::VARIABLE: {
+				VariableNode *variable = static_cast<VariableNode *>(statement);
+				const SuiteNode::Local &local = current_suite->get_local(variable->identifier->name);
+				if (local.type != SuiteNode::Local::UNDEFINED) {
+					String name;
+					if (local.type == SuiteNode::Local::CONSTANT) {
+						name = "constant";
+					} else {
+						name = "variable";
 					}
 					}
-					Node *n = _parse_expression(arr, p_static, p_allow_assign, p_parsing_constant);
-					if (!n) {
-						return nullptr;
+					push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", name, variable->identifier->name));
+				}
+				current_suite->add_local(variable);
+				break;
+			}
+			case Node::CONSTANT: {
+				ConstantNode *constant = static_cast<ConstantNode *>(statement);
+				const SuiteNode::Local &local = current_suite->get_local(constant->identifier->name);
+				if (local.type != SuiteNode::Local::UNDEFINED) {
+					String name;
+					if (local.type == SuiteNode::Local::CONSTANT) {
+						name = "constant";
+					} else {
+						name = "variable";
 					}
 					}
-					arr->elements.push_back(n);
-					expecting_comma = true;
+					push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", name, constant->identifier->name));
 				}
 				}
+				current_suite->add_local(constant);
+				break;
 			}
 			}
+			default:
+				break;
+		}
 
 
-			expr = arr;
-		} else if (tokenizer->get_token() == GDScriptTokenizer::TK_CURLY_BRACKET_OPEN) {
-			// array
-			tokenizer->advance();
-
-			DictionaryNode *dict = alloc_node<DictionaryNode>();
+	} while (multiline && !check(GDScriptTokenizer::Token::DEDENT) && !is_at_end());
 
 
-			enum DictExpect {
+	if (multiline) {
+		consume(GDScriptTokenizer::Token::DEDENT, vformat(R"(Missing unindent at the end of %s.)", p_context));
+	}
 
 
-				DICT_EXPECT_KEY,
-				DICT_EXPECT_COLON,
-				DICT_EXPECT_VALUE,
-				DICT_EXPECT_COMMA
+	current_suite = suite->parent_block;
+	return suite;
+}
 
 
-			};
+GDScriptParser::Node *GDScriptParser::parse_statement() {
+	Node *result = nullptr;
+	switch (current.type) {
+		case GDScriptTokenizer::Token::PASS:
+			advance();
+			result = alloc_node<PassNode>();
+			end_statement(R"("pass")");
+			break;
+		case GDScriptTokenizer::Token::VAR:
+			advance();
+			result = parse_variable();
+			break;
+		case GDScriptTokenizer::Token::CONST:
+			advance();
+			result = parse_constant();
+			break;
+		case GDScriptTokenizer::Token::IF:
+			advance();
+			result = parse_if();
+			break;
+		case GDScriptTokenizer::Token::FOR:
+			advance();
+			result = parse_for();
+			break;
+		case GDScriptTokenizer::Token::WHILE:
+			advance();
+			result = parse_while();
+			break;
+		case GDScriptTokenizer::Token::MATCH:
+			advance();
+			result = parse_match();
+			break;
+		case GDScriptTokenizer::Token::BREAK:
+			advance();
+			result = parse_break();
+			break;
+		case GDScriptTokenizer::Token::CONTINUE:
+			advance();
+			result = parse_continue();
+			break;
+		case GDScriptTokenizer::Token::RETURN: {
+			advance();
+			ReturnNode *n_return = alloc_node<ReturnNode>();
+			if (!is_statement_end()) {
+				n_return->return_value = parse_expression(false);
+			}
+			result = n_return;
+			end_statement("return statement");
+			break;
+		}
+		case GDScriptTokenizer::Token::BREAKPOINT:
+			advance();
+			result = alloc_node<BreakpointNode>();
+			end_statement(R"("breakpoint")");
+			break;
+		case GDScriptTokenizer::Token::ASSERT:
+			advance();
+			result = parse_assert();
+			break;
+		case GDScriptTokenizer::Token::ANNOTATION: {
+			advance();
+			AnnotationNode *annotation = parse_annotation(AnnotationInfo::STATEMENT);
+			if (annotation != nullptr) {
+				annotation_stack.push_back(annotation);
+			}
+			break;
+		}
+		default: {
+			// Expression statement.
+			ExpressionNode *expression = parse_expression(true); // Allow assignment here.
+			end_statement("expression");
+			result = expression;
+			break;
+		}
+	}
 
 
-			Node *key = nullptr;
-			Set<Variant> keys;
+	if (panic_mode) {
+		synchronize();
+	}
 
 
-			DictExpect expecting = DICT_EXPECT_KEY;
+	return result;
+}
 
 
-			while (true) {
-				if (tokenizer->get_token() == GDScriptTokenizer::TK_EOF) {
-					_set_error("Unterminated dictionary");
-					return nullptr;
+GDScriptParser::AssertNode *GDScriptParser::parse_assert() {
+	// TODO: Add assert message.
+	AssertNode *assert = alloc_node<AssertNode>();
 
 
-				} else if (tokenizer->get_token() == GDScriptTokenizer::TK_CURLY_BRACKET_CLOSE) {
-					if (expecting == DICT_EXPECT_COLON) {
-						_set_error("':' expected");
-						return nullptr;
-					}
-					if (expecting == DICT_EXPECT_VALUE) {
-						_set_error("value expected");
-						return nullptr;
-					}
-					tokenizer->advance();
-					break;
-				} else if (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) {
-					tokenizer->advance(); //ignore newline
-				} else if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
-					if (expecting == DICT_EXPECT_KEY) {
-						_set_error("key or '}' expected");
-						return nullptr;
-					}
-					if (expecting == DICT_EXPECT_VALUE) {
-						_set_error("value expected");
-						return nullptr;
-					}
-					if (expecting == DICT_EXPECT_COLON) {
-						_set_error("':' expected");
-						return nullptr;
-					}
+	consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "assert".)");
+	assert->condition = parse_expression(false);
+	if (assert->condition == nullptr) {
+		push_error("Expected expression to assert.");
+		return nullptr;
+	}
 
 
-					expecting = DICT_EXPECT_KEY;
-					tokenizer->advance(); //ignore newline
+	if (match(GDScriptTokenizer::Token::COMMA)) {
+		// Error message.
+		if (consume(GDScriptTokenizer::Token::LITERAL, R"(Expected error message for assert after ",".)")) {
+			assert->message = parse_literal();
+			if (assert->message->value.get_type() != Variant::STRING) {
+				push_error(R"(Expected string for assert error message.)");
+			}
+		} else {
+			return nullptr;
+		}
+	}
 
 
-				} else if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) {
-					if (expecting == DICT_EXPECT_KEY) {
-						_set_error("key or '}' expected");
-						return nullptr;
-					}
-					if (expecting == DICT_EXPECT_VALUE) {
-						_set_error("value expected");
-						return nullptr;
-					}
-					if (expecting == DICT_EXPECT_COMMA) {
-						_set_error("',' or '}' expected");
-						return nullptr;
-					}
+	consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after assert expression.)*");
 
 
-					expecting = DICT_EXPECT_VALUE;
-					tokenizer->advance(); //ignore newline
-				} else {
-					if (expecting == DICT_EXPECT_COMMA) {
-						_set_error("',' or '}' expected");
-						return nullptr;
-					}
-					if (expecting == DICT_EXPECT_COLON) {
-						_set_error("':' expected");
-						return nullptr;
-					}
+	end_statement(R"("assert")");
 
 
-					if (expecting == DICT_EXPECT_KEY) {
-						if (tokenizer->is_token_literal() && tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) {
-							// We check with is_token_literal, as this allows us to use match/sync/etc. as a name
-							//lua style identifier, easier to write
-							ConstantNode *cn = alloc_node<ConstantNode>();
-							cn->value = tokenizer->get_token_literal();
-							cn->datatype = _type_from_variant(cn->value);
-							key = cn;
-							tokenizer->advance(2);
-							expecting = DICT_EXPECT_VALUE;
-						} else {
-							//python/js style more flexible
-							key = _parse_expression(dict, p_static, p_allow_assign, p_parsing_constant);
-							if (!key) {
-								return nullptr;
-							}
-							expecting = DICT_EXPECT_COLON;
-						}
-					}
+	return assert;
+}
 
 
-					if (expecting == DICT_EXPECT_VALUE) {
-						Node *value = _parse_expression(dict, p_static, p_allow_assign, p_parsing_constant);
-						if (!value) {
-							return nullptr;
-						}
-						expecting = DICT_EXPECT_COMMA;
+GDScriptParser::BreakNode *GDScriptParser::parse_break() {
+	if (!can_break) {
+		push_error(R"(Cannot use "break" outside of a loop.)");
+	}
+	end_statement(R"("break")");
+	return alloc_node<BreakNode>();
+}
 
 
-						if (key->type == GDScriptParser::Node::TYPE_CONSTANT) {
-							Variant const &keyName = static_cast<const GDScriptParser::ConstantNode *>(key)->value;
+GDScriptParser::ContinueNode *GDScriptParser::parse_continue() {
+	if (!can_continue) {
+		push_error(R"(Cannot use "continue" outside of a loop or pattern matching block.)");
+	}
+	end_statement(R"("continue")");
+	return alloc_node<ContinueNode>();
+}
 
 
-							if (keys.has(keyName)) {
-								_set_error("Duplicate key found in Dictionary literal");
-								return nullptr;
-							}
-							keys.insert(keyName);
-						}
+GDScriptParser::ForNode *GDScriptParser::parse_for() {
+	ForNode *n_for = alloc_node<ForNode>();
 
 
-						DictionaryNode::Pair pair;
-						pair.key = key;
-						pair.value = value;
-						dict->elements.push_back(pair);
-						key = nullptr;
-					}
-				}
-			}
+	if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected loop variable name after "for".)")) {
+		n_for->variable = parse_identifier();
+	}
 
 
-			expr = dict;
+	consume(GDScriptTokenizer::Token::IN, R"(Expected "in" after "for" variable name.)");
 
 
-		} else if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD && (tokenizer->is_token_literal(1) || tokenizer->get_token(1) == GDScriptTokenizer::TK_CURSOR)) {
-			// We check with is_token_literal, as this allows us to use match/sync/etc. as a name
-			// parent call
+	n_for->list = parse_expression(false);
 
 
-			tokenizer->advance(); //goto identifier
-			OperatorNode *op = alloc_node<OperatorNode>();
-			op->op = OperatorNode::OP_PARENT_CALL;
+	consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "for" condition.)");
 
 
-			/*SelfNode *self = alloc_node<SelfNode>();
-			op->arguments.push_back(self);
-			forbidden for now */
-			StringName identifier;
-			bool is_completion = _get_completable_identifier(COMPLETION_PARENT_FUNCTION, identifier) && for_completion;
+	// Save break/continue state.
+	bool could_break = can_break;
+	bool could_continue = can_continue;
 
 
-			IdentifierNode *id = alloc_node<IdentifierNode>();
-			id->name = identifier;
-			op->arguments.push_back(id);
+	// Allow break/continue.
+	can_break = true;
+	can_continue = true;
 
 
-			if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
-				if (!is_completion) {
-					_set_error("Expected '(' for parent function call.");
-					return nullptr;
-				}
-			} else {
-				tokenizer->advance();
-				if (!_parse_arguments(op, op->arguments, p_static, false, p_parsing_constant)) {
-					return nullptr;
-				}
-			}
+	n_for->loop = parse_suite(R"("for" block)");
 
 
-			expr = op;
+	// Reset break/continue state.
+	can_break = could_break;
+	can_continue = could_continue;
 
 
-		} else if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_TYPE && expression.size() > 0 && expression[expression.size() - 1].is_op && expression[expression.size() - 1].op == OperatorNode::OP_IS) {
-			Expression e = expression[expression.size() - 1];
-			e.op = OperatorNode::OP_IS_BUILTIN;
-			expression.write[expression.size() - 1] = e;
+	return n_for;
+}
 
 
-			TypeNode *tn = alloc_node<TypeNode>();
-			tn->vtype = tokenizer->get_token_type();
-			expr = tn;
-			tokenizer->advance();
-		} else {
-			//find list [ or find dictionary {
-			_set_error("Error parsing expression, misplaced: " + String(tokenizer->get_token_name(tokenizer->get_token())));
-			return nullptr; //nothing
-		}
+GDScriptParser::IfNode *GDScriptParser::parse_if(const String &p_token) {
+	IfNode *n_if = alloc_node<IfNode>();
 
 
-		ERR_FAIL_COND_V_MSG(!expr, nullptr, "GDScriptParser bug, couldn't figure out what expression is.");
+	n_if->condition = parse_expression(false);
+	if (n_if->condition == nullptr) {
+		push_error(vformat(R"(Expected conditional expression after "%s".)", p_token));
+	}
 
 
-		/******************/
-		/* Parse Indexing */
-		/******************/
+	consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":" after "%s" condition.)", p_token));
 
 
-		while (true) {
-			//expressions can be indexed any number of times
+	n_if->true_block = parse_suite(vformat(R"("%s" block)", p_token));
 
 
-			if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD) {
-				//indexing using "."
+	if (match(GDScriptTokenizer::Token::ELIF)) {
+		IfNode *elif = parse_if("elif");
 
 
-				if (tokenizer->get_token(1) != GDScriptTokenizer::TK_CURSOR && !tokenizer->is_token_literal(1)) {
-					// We check with is_token_literal, as this allows us to use match/sync/etc. as a name
-					_set_error("Expected identifier as member");
-					return nullptr;
-				} else if (tokenizer->get_token(2) == GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
-					//call!!
-					OperatorNode *op = alloc_node<OperatorNode>();
-					op->op = OperatorNode::OP_CALL;
+		SuiteNode *else_block = alloc_node<SuiteNode>();
+		else_block->statements.push_back(elif);
+		n_if->false_block = else_block;
+	} else if (match(GDScriptTokenizer::Token::ELSE)) {
+		consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "else".)");
+		n_if->false_block = parse_suite(R"("else" block)");
+	}
 
 
-					tokenizer->advance();
+	return n_if;
+}
 
 
-					IdentifierNode *id = alloc_node<IdentifierNode>();
-					StringName identifier;
-					if (_get_completable_identifier(COMPLETION_METHOD, identifier)) {
-						completion_node = op;
-						//indexing stuff
-					}
+GDScriptParser::MatchNode *GDScriptParser::parse_match() {
+	MatchNode *match = alloc_node<MatchNode>();
 
 
-					id->name = identifier;
+	match->test = parse_expression(false);
+	if (match->test == nullptr) {
+		push_error(R"(Expected expression to test after "match".)");
+	}
 
 
-					op->arguments.push_back(expr); // call what
-					op->arguments.push_back(id); // call func
-					//get arguments
-					tokenizer->advance(1);
-					if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) {
-						_make_completable_call(0);
-						completion_node = op;
-					}
-					if (!_parse_arguments(op, op->arguments, p_static, true, p_parsing_constant)) {
-						return nullptr;
-					}
-					expr = op;
+	consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" expression.)");
+	consume(GDScriptTokenizer::Token::NEWLINE, R"(Expected a newline after "match" statement.)");
 
 
-				} else {
-					//simple indexing!
+	if (!consume(GDScriptTokenizer::Token::INDENT, R"(Expected an indented block after "match" statement.)")) {
+		return match;
+	}
 
 
-					OperatorNode *op = alloc_node<OperatorNode>();
-					op->op = OperatorNode::OP_INDEX_NAMED;
-					tokenizer->advance();
+	while (!check(GDScriptTokenizer::Token::DEDENT) && !is_at_end()) {
+		MatchBranchNode *branch = parse_match_branch();
+		if (branch == nullptr) {
+			continue;
+		}
+		match->branches.push_back(branch);
+	}
 
 
-					StringName identifier;
-					if (_get_completable_identifier(COMPLETION_INDEX, identifier)) {
-						if (identifier == StringName()) {
-							identifier = "@temp"; //so it parses alright
-						}
-						completion_node = op;
+	consume(GDScriptTokenizer::Token::DEDENT, R"(Expected an indented block after "match" statement.)");
 
 
-						//indexing stuff
-					}
+	return match;
+}
 
 
-					IdentifierNode *id = alloc_node<IdentifierNode>();
-					id->name = identifier;
+GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() {
+	MatchBranchNode *branch = alloc_node<MatchBranchNode>();
 
 
-					op->arguments.push_back(expr);
-					op->arguments.push_back(id);
+	bool has_bind = false;
 
 
-					expr = op;
-				}
+	do {
+		PatternNode *pattern = parse_match_pattern();
+		if (pattern == nullptr) {
+			continue;
+		}
+		if (pattern->pattern_type == PatternNode::PT_BIND) {
+			has_bind = true;
+		}
+		if (branch->patterns.size() > 0 && has_bind) {
+			push_error(R"(Cannot use a variable bind with multiple patterns.)");
+		}
+		if (pattern->pattern_type == PatternNode::PT_REST) {
+			push_error(R"(Rest pattern can only be used inside array and dictionary patterns.)");
+		}
+		branch->patterns.push_back(pattern);
+	} while (match(GDScriptTokenizer::Token::COMMA));
 
 
-			} else if (tokenizer->get_token() == GDScriptTokenizer::TK_BRACKET_OPEN) {
-				//indexing using "[]"
-				OperatorNode *op = alloc_node<OperatorNode>();
-				op->op = OperatorNode::OP_INDEX;
+	if (branch->patterns.empty()) {
+		push_error(R"(No pattern found for "match" branch.)");
+	}
 
 
-				tokenizer->advance(1);
+	consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" patterns.)");
 
 
-				Node *subexpr = _parse_expression(op, p_static, p_allow_assign, p_parsing_constant);
-				if (!subexpr) {
-					return nullptr;
-				}
+	// Save continue state.
+	bool could_continue = can_continue;
+	// Allow continue for match.
+	can_continue = true;
 
 
-				if (tokenizer->get_token() != GDScriptTokenizer::TK_BRACKET_CLOSE) {
-					_set_error("Expected ']'");
-					return nullptr;
-				}
+	branch->block = parse_suite("match pattern block");
 
 
-				op->arguments.push_back(expr);
-				op->arguments.push_back(subexpr);
-				tokenizer->advance(1);
-				expr = op;
+	// Restore continue state.
+	can_continue = could_continue;
 
 
-			} else {
-				break;
-			}
-		}
+	return branch;
+}
 
 
-		/*****************/
-		/* Parse Casting */
-		/*****************/
+GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern() {
+	PatternNode *pattern = alloc_node<PatternNode>();
 
 
-		bool has_casting = expr->type == Node::TYPE_CAST;
-		if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_AS) {
-			if (has_casting) {
-				_set_error("Unexpected 'as'.");
+	switch (current.type) {
+		case GDScriptTokenizer::Token::LITERAL:
+			advance();
+			pattern->pattern_type = PatternNode::PT_LITERAL;
+			pattern->literal = parse_literal();
+			if (pattern->literal == nullptr) {
+				// Error happened.
 				return nullptr;
 				return nullptr;
 			}
 			}
-			CastNode *cn = alloc_node<CastNode>();
-			if (!_parse_type(cn->cast_type)) {
-				_set_error("Expected type after 'as'.");
+			break;
+		case GDScriptTokenizer::Token::VAR:
+			// Bind.
+			advance();
+			if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected bind name after "var".)")) {
 				return nullptr;
 				return nullptr;
 			}
 			}
-			has_casting = true;
-			cn->source_node = expr;
-			expr = cn;
+			pattern->pattern_type = PatternNode::PT_BIND;
+			pattern->bind = parse_identifier();
+			break;
+		case GDScriptTokenizer::Token::UNDERSCORE:
+			// Wildcard.
+			advance();
+			pattern->pattern_type = PatternNode::PT_WILDCARD;
+			break;
+		case GDScriptTokenizer::Token::PERIOD_PERIOD:
+			// Rest.
+			advance();
+			pattern->pattern_type = PatternNode::PT_REST;
+			break;
+		case GDScriptTokenizer::Token::BRACKET_OPEN: {
+			// Array.
+			advance();
+			pattern->pattern_type = PatternNode::PT_ARRAY;
+
+			if (!check(GDScriptTokenizer::Token::BRACKET_CLOSE)) {
+				do {
+					PatternNode *sub_pattern = parse_match_pattern();
+					if (sub_pattern == nullptr) {
+						continue;
+					}
+					if (pattern->rest_used) {
+						push_error(R"(The ".." pattern must be the last element in the pattern array.)");
+					} else if (sub_pattern->pattern_type == PatternNode::PT_REST) {
+						pattern->rest_used = true;
+					}
+					pattern->array.push_back(sub_pattern);
+				} while (match(GDScriptTokenizer::Token::COMMA));
+			}
+			consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected "]" to close the array pattern.)");
+			break;
 		}
 		}
-
-		/******************/
-		/* Parse Operator */
-		/******************/
-
-		if (parenthesis > 0) {
-			//remove empty space (only allowed if inside parenthesis
-			while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) {
-				tokenizer->advance();
+		case GDScriptTokenizer::Token::BRACE_OPEN: {
+			// Dictionary.
+			advance();
+			pattern->pattern_type = PatternNode::PT_DICTIONARY;
+
+			if (!check(GDScriptTokenizer::Token::BRACE_CLOSE) && !is_at_end()) {
+				do {
+					if (match(GDScriptTokenizer::Token::PERIOD_PERIOD)) {
+						// Rest.
+						if (pattern->rest_used) {
+							push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)");
+						} else {
+							PatternNode *sub_pattern = alloc_node<PatternNode>();
+							sub_pattern->pattern_type = PatternNode::PT_REST;
+							pattern->dictionary.push_back({ nullptr, sub_pattern });
+							pattern->rest_used = true;
+						}
+					} else {
+						ExpressionNode *key = parse_expression(false);
+						if (key == nullptr) {
+							push_error(R"(Expected expression as key for dictionary pattern.)");
+						}
+						if (consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after dictionary pattern key)")) {
+							// Value pattern.
+							PatternNode *sub_pattern = parse_match_pattern();
+							if (sub_pattern == nullptr) {
+								continue;
+							}
+							if (pattern->rest_used) {
+								push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)");
+							} else if (sub_pattern->pattern_type == PatternNode::PT_REST) {
+								push_error(R"(The ".." pattern cannot be used as a value.)");
+							} else {
+								pattern->dictionary.push_back({ key, sub_pattern });
+							}
+						}
+					}
+				} while (match(GDScriptTokenizer::Token::COMMA));
+			}
+			consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected "}" to close the dictionary pattern.)");
+			break;
+		}
+		default: {
+			// Expression.
+			ExpressionNode *expression = parse_expression(false);
+			if (expression == nullptr) {
+				push_error(R"(Expected expression for match pattern.)");
+			} else {
+				pattern->pattern_type = PatternNode::PT_EXPRESSION;
+				pattern->expression = expression;
 			}
 			}
+			break;
 		}
 		}
+	}
 
 
-		Expression e;
-		e.is_op = false;
-		e.node = expr;
-		expression.push_back(e);
+	return pattern;
+}
 
 
-		// determine which operator is next
+GDScriptParser::WhileNode *GDScriptParser::parse_while() {
+	WhileNode *n_while = alloc_node<WhileNode>();
 
 
-		OperatorNode::Operator op;
-		bool valid = true;
+	n_while->condition = parse_expression(false);
+	if (n_while->condition == nullptr) {
+		push_error(R"(Expected conditional expression after "while".)");
+	}
 
 
-//assign, if allowed is only allowed on the first operator
-#define _VALIDATE_ASSIGN                  \
-	if (!p_allow_assign || has_casting) { \
-		_set_error("Unexpected assign."); \
-		return nullptr;                   \
-	}                                     \
-	p_allow_assign = false;
+	consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "while" condition.)");
 
 
-		switch (tokenizer->get_token()) { //see operator
+	// Save break/continue state.
+	bool could_break = can_break;
+	bool could_continue = can_continue;
 
 
-			case GDScriptTokenizer::TK_OP_IN:
-				op = OperatorNode::OP_IN;
-				break;
-			case GDScriptTokenizer::TK_OP_EQUAL:
-				op = OperatorNode::OP_EQUAL;
-				break;
-			case GDScriptTokenizer::TK_OP_NOT_EQUAL:
-				op = OperatorNode::OP_NOT_EQUAL;
-				break;
-			case GDScriptTokenizer::TK_OP_LESS:
-				op = OperatorNode::OP_LESS;
-				break;
-			case GDScriptTokenizer::TK_OP_LESS_EQUAL:
-				op = OperatorNode::OP_LESS_EQUAL;
-				break;
-			case GDScriptTokenizer::TK_OP_GREATER:
-				op = OperatorNode::OP_GREATER;
-				break;
-			case GDScriptTokenizer::TK_OP_GREATER_EQUAL:
-				op = OperatorNode::OP_GREATER_EQUAL;
-				break;
-			case GDScriptTokenizer::TK_OP_AND:
-				op = OperatorNode::OP_AND;
-				break;
-			case GDScriptTokenizer::TK_OP_OR:
-				op = OperatorNode::OP_OR;
-				break;
-			case GDScriptTokenizer::TK_OP_ADD:
-				op = OperatorNode::OP_ADD;
-				break;
-			case GDScriptTokenizer::TK_OP_SUB:
-				op = OperatorNode::OP_SUB;
-				break;
-			case GDScriptTokenizer::TK_OP_MUL:
-				op = OperatorNode::OP_MUL;
-				break;
-			case GDScriptTokenizer::TK_OP_DIV:
-				op = OperatorNode::OP_DIV;
-				break;
-			case GDScriptTokenizer::TK_OP_MOD:
-				op = OperatorNode::OP_MOD;
-				break;
-			//case GDScriptTokenizer::TK_OP_NEG: op=OperatorNode::OP_NEG ; break;
-			case GDScriptTokenizer::TK_OP_SHIFT_LEFT:
-				op = OperatorNode::OP_SHIFT_LEFT;
-				break;
-			case GDScriptTokenizer::TK_OP_SHIFT_RIGHT:
-				op = OperatorNode::OP_SHIFT_RIGHT;
-				break;
-			case GDScriptTokenizer::TK_OP_ASSIGN: {
-				_VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN;
-
-				if (tokenizer->get_token(1) == GDScriptTokenizer::TK_CURSOR) {
-					//code complete assignment
-					completion_type = COMPLETION_ASSIGN;
-					completion_node = expr;
-					completion_class = current_class;
-					completion_function = current_function;
-					completion_line = tokenizer->get_token_line();
-					completion_block = current_block;
-					completion_found = true;
-					tokenizer->advance();
-				}
+	// Allow break/continue.
+	can_break = true;
+	can_continue = true;
 
 
-			} break;
-			case GDScriptTokenizer::TK_OP_ASSIGN_ADD:
-				_VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_ADD;
-				break;
-			case GDScriptTokenizer::TK_OP_ASSIGN_SUB:
-				_VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_SUB;
-				break;
-			case GDScriptTokenizer::TK_OP_ASSIGN_MUL:
-				_VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_MUL;
-				break;
-			case GDScriptTokenizer::TK_OP_ASSIGN_DIV:
-				_VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_DIV;
-				break;
-			case GDScriptTokenizer::TK_OP_ASSIGN_MOD:
-				_VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_MOD;
-				break;
-			case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_LEFT:
-				_VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_SHIFT_LEFT;
-				break;
-			case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT:
-				_VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_SHIFT_RIGHT;
-				break;
-			case GDScriptTokenizer::TK_OP_ASSIGN_BIT_AND:
-				_VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_BIT_AND;
-				break;
-			case GDScriptTokenizer::TK_OP_ASSIGN_BIT_OR:
-				_VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_BIT_OR;
-				break;
-			case GDScriptTokenizer::TK_OP_ASSIGN_BIT_XOR:
-				_VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_BIT_XOR;
-				break;
-			case GDScriptTokenizer::TK_OP_BIT_AND:
-				op = OperatorNode::OP_BIT_AND;
-				break;
-			case GDScriptTokenizer::TK_OP_BIT_OR:
-				op = OperatorNode::OP_BIT_OR;
-				break;
-			case GDScriptTokenizer::TK_OP_BIT_XOR:
-				op = OperatorNode::OP_BIT_XOR;
-				break;
-			case GDScriptTokenizer::TK_PR_IS:
-				op = OperatorNode::OP_IS;
-				break;
-			case GDScriptTokenizer::TK_CF_IF:
-				op = OperatorNode::OP_TERNARY_IF;
-				break;
-			case GDScriptTokenizer::TK_CF_ELSE:
-				op = OperatorNode::OP_TERNARY_ELSE;
+	n_while->loop = parse_suite(R"("while" block)");
+
+	// Reset break/continue state.
+	can_break = could_break;
+	can_continue = could_continue;
+
+	return n_while;
+}
+
+GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_precedence, bool p_can_assign, bool p_stop_on_assign) {
+	// Switch multiline mode on for grouping tokens.
+	// Do this early to avoid the tokenizer generating whitespace tokens.
+	switch (current.type) {
+		case GDScriptTokenizer::Token::PARENTHESIS_OPEN:
+		case GDScriptTokenizer::Token::BRACE_OPEN:
+		case GDScriptTokenizer::Token::BRACKET_OPEN:
+			push_multiline(true);
+			break;
+		default:
+			break; // Nothing to do.
+	}
+
+	GDScriptTokenizer::Token token = advance();
+	ParseFunction prefix_rule = get_rule(token.type)->prefix;
+
+	if (prefix_rule == nullptr) {
+		// Expected expression. Let the caller give the proper error message.
+		return nullptr;
+	}
+
+	ExpressionNode *previous_operand = (this->*prefix_rule)(nullptr, p_can_assign);
+
+	while (p_precedence <= get_rule(current.type)->precedence) {
+		if (p_stop_on_assign && current.type == GDScriptTokenizer::Token::EQUAL) {
+			return previous_operand;
+		}
+		// Also switch multiline mode on here for infix operators.
+		switch (current.type) {
+			// case GDScriptTokenizer::Token::BRACE_OPEN: // Not an infix operator.
+			case GDScriptTokenizer::Token::PARENTHESIS_OPEN:
+			case GDScriptTokenizer::Token::BRACKET_OPEN:
+				push_multiline(true);
 				break;
 				break;
 			default:
 			default:
-				valid = false;
-				break;
-		}
-
-		if (valid) {
-			e.is_op = true;
-			e.op = op;
-			expression.push_back(e);
-			tokenizer->advance();
-		} else {
-			break;
+				break; // Nothing to do.
 		}
 		}
+		token = advance();
+		ParseFunction infix_rule = get_rule(token.type)->infix;
+		previous_operand = (this->*infix_rule)(previous_operand, p_can_assign);
 	}
 	}
 
 
-	/* Reduce the set set of expressions and place them in an operator tree, respecting precedence */
-
-	while (expression.size() > 1) {
-		int next_op = -1;
-		int min_priority = 0xFFFFF;
-		bool is_unary = false;
-		bool is_ternary = false;
+	return previous_operand;
+}
 
 
-		for (int i = 0; i < expression.size(); i++) {
-			if (!expression[i].is_op) {
-				continue;
-			}
+GDScriptParser::ExpressionNode *GDScriptParser::parse_expression(bool p_can_assign, bool p_stop_on_assign) {
+	return parse_precedence(PREC_ASSIGNMENT, p_can_assign, p_stop_on_assign);
+}
 
 
-			int priority;
+GDScriptParser::IdentifierNode *GDScriptParser::parse_identifier() {
+	return static_cast<IdentifierNode *>(parse_identifier(nullptr, false));
+}
 
 
-			bool unary = false;
-			bool ternary = false;
-			bool error = false;
-			bool right_to_left = false;
+GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode *p_previous_operand, bool p_can_assign) {
+	if (!previous.is_identifier()) {
+		ERR_FAIL_V_MSG(nullptr, "Parser bug: parsing literal node without literal token.");
+	}
+	IdentifierNode *identifier = alloc_node<IdentifierNode>();
+	identifier->name = previous.literal;
+	return identifier;
+}
 
 
-			switch (expression[i].op) {
-				case OperatorNode::OP_IS:
-				case OperatorNode::OP_IS_BUILTIN:
-					priority = -1;
-					break; //before anything
+GDScriptParser::LiteralNode *GDScriptParser::parse_literal() {
+	return static_cast<LiteralNode *>(parse_literal(nullptr, false));
+}
 
 
-				case OperatorNode::OP_BIT_INVERT:
-					priority = 0;
-					unary = true;
-					break;
-				case OperatorNode::OP_NEG:
-				case OperatorNode::OP_POS:
-					priority = 1;
-					unary = true;
-					break;
+GDScriptParser::ExpressionNode *GDScriptParser::parse_literal(ExpressionNode *p_previous_operand, bool p_can_assign) {
+	if (previous.type != GDScriptTokenizer::Token::LITERAL) {
+		push_error("Parser bug: parsing literal node without literal token.");
+		ERR_FAIL_V_MSG(nullptr, "Parser bug: parsing literal node without literal token.");
+	}
 
 
-				case OperatorNode::OP_MUL:
-					priority = 2;
-					break;
-				case OperatorNode::OP_DIV:
-					priority = 2;
-					break;
-				case OperatorNode::OP_MOD:
-					priority = 2;
-					break;
+	LiteralNode *literal = alloc_node<LiteralNode>();
+	literal->value = previous.literal;
+	return literal;
+}
 
 
-				case OperatorNode::OP_ADD:
-					priority = 3;
-					break;
-				case OperatorNode::OP_SUB:
-					priority = 3;
-					break;
+GDScriptParser::ExpressionNode *GDScriptParser::parse_self(ExpressionNode *p_previous_operand, bool p_can_assign) {
+	// FIXME: Don't allow "self" in a static context.
+	SelfNode *self = alloc_node<SelfNode>();
+	self->current_class = current_class;
+	return self;
+}
 
 
-				case OperatorNode::OP_SHIFT_LEFT:
-					priority = 4;
-					break;
-				case OperatorNode::OP_SHIFT_RIGHT:
-					priority = 4;
-					break;
+GDScriptParser::ExpressionNode *GDScriptParser::parse_builtin_constant(ExpressionNode *p_previous_operand, bool p_can_assign) {
+	GDScriptTokenizer::Token::Type op_type = previous.type;
+	LiteralNode *constant = alloc_node<LiteralNode>();
 
 
-				case OperatorNode::OP_BIT_AND:
-					priority = 5;
-					break;
-				case OperatorNode::OP_BIT_XOR:
-					priority = 6;
-					break;
-				case OperatorNode::OP_BIT_OR:
-					priority = 7;
-					break;
+	switch (op_type) {
+		case GDScriptTokenizer::Token::CONST_PI:
+			constant->value = Math_PI;
+			break;
+		case GDScriptTokenizer::Token::CONST_TAU:
+			constant->value = Math_TAU;
+			break;
+		case GDScriptTokenizer::Token::CONST_INF:
+			constant->value = Math_INF;
+			break;
+		case GDScriptTokenizer::Token::CONST_NAN:
+			constant->value = Math_NAN;
+			break;
+		default:
+			return nullptr; // Unreachable.
+	}
 
 
-				case OperatorNode::OP_LESS:
-					priority = 8;
-					break;
-				case OperatorNode::OP_LESS_EQUAL:
-					priority = 8;
-					break;
-				case OperatorNode::OP_GREATER:
-					priority = 8;
-					break;
-				case OperatorNode::OP_GREATER_EQUAL:
-					priority = 8;
-					break;
+	return constant;
+}
 
 
-				case OperatorNode::OP_EQUAL:
-					priority = 8;
-					break;
-				case OperatorNode::OP_NOT_EQUAL:
-					priority = 8;
-					break;
+GDScriptParser::ExpressionNode *GDScriptParser::parse_unary_operator(ExpressionNode *p_previous_operand, bool p_can_assign) {
+	GDScriptTokenizer::Token::Type op_type = previous.type;
+	UnaryOpNode *operation = alloc_node<UnaryOpNode>();
 
 
-				case OperatorNode::OP_IN:
-					priority = 10;
-					break;
+	switch (op_type) {
+		case GDScriptTokenizer::Token::MINUS:
+			operation->operation = UnaryOpNode::OP_NEGATIVE;
+			operation->operand = parse_precedence(PREC_SIGN, false);
+			break;
+		case GDScriptTokenizer::Token::PLUS:
+			operation->operation = UnaryOpNode::OP_POSITIVE;
+			operation->operand = parse_precedence(PREC_SIGN, false);
+			break;
+		case GDScriptTokenizer::Token::TILDE:
+			operation->operation = UnaryOpNode::OP_COMPLEMENT;
+			operation->operand = parse_precedence(PREC_BIT_NOT, false);
+			break;
+		case GDScriptTokenizer::Token::NOT:
+		case GDScriptTokenizer::Token::BANG:
+			operation->operation = UnaryOpNode::OP_LOGIC_NOT;
+			operation->operand = parse_precedence(PREC_LOGIC_NOT, false);
+			break;
+		default:
+			return nullptr; // Unreachable.
+	}
 
 
-				case OperatorNode::OP_NOT:
-					priority = 11;
-					unary = true;
-					break;
-				case OperatorNode::OP_AND:
-					priority = 12;
-					break;
-				case OperatorNode::OP_OR:
-					priority = 13;
-					break;
+	return operation;
+}
 
 
-				case OperatorNode::OP_TERNARY_IF:
-					priority = 14;
-					ternary = true;
-					right_to_left = true;
-					break;
-				case OperatorNode::OP_TERNARY_ELSE:
-					priority = 14;
-					error = true;
-					// Rigth-to-left should be false in this case, otherwise it would always error.
-					break;
+GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_operator(ExpressionNode *p_previous_operand, bool p_can_assign) {
+	GDScriptTokenizer::Token op = previous;
+	BinaryOpNode *operation = alloc_node<BinaryOpNode>();
 
 
-				case OperatorNode::OP_ASSIGN:
-					priority = 15;
-					break;
-				case OperatorNode::OP_ASSIGN_ADD:
-					priority = 15;
-					break;
-				case OperatorNode::OP_ASSIGN_SUB:
-					priority = 15;
-					break;
-				case OperatorNode::OP_ASSIGN_MUL:
-					priority = 15;
-					break;
-				case OperatorNode::OP_ASSIGN_DIV:
-					priority = 15;
-					break;
-				case OperatorNode::OP_ASSIGN_MOD:
-					priority = 15;
-					break;
-				case OperatorNode::OP_ASSIGN_SHIFT_LEFT:
-					priority = 15;
-					break;
-				case OperatorNode::OP_ASSIGN_SHIFT_RIGHT:
-					priority = 15;
-					break;
-				case OperatorNode::OP_ASSIGN_BIT_AND:
-					priority = 15;
-					break;
-				case OperatorNode::OP_ASSIGN_BIT_OR:
-					priority = 15;
-					break;
-				case OperatorNode::OP_ASSIGN_BIT_XOR:
-					priority = 15;
-					break;
+	Precedence precedence = (Precedence)(get_rule(op.type)->precedence + 1);
+	operation->left_operand = p_previous_operand;
+	operation->right_operand = parse_precedence(precedence, false);
 
 
-				default: {
-					_set_error("GDScriptParser bug, invalid operator in expression: " + itos(expression[i].op));
-					return nullptr;
-				}
-			}
+	if (operation->right_operand == nullptr) {
+		push_error(vformat(R"(Expected expression after "%s" operator.")", op.get_name()));
+	}
 
 
-			if (priority < min_priority || (right_to_left && priority == min_priority)) {
-				// < is used for left to right (default)
-				// <= is used for right to left
-				if (error) {
-					_set_error("Unexpected operator");
-					return nullptr;
-				}
-				next_op = i;
-				min_priority = priority;
-				is_unary = unary;
-				is_ternary = ternary;
-			}
-		}
+	switch (op.type) {
+		case GDScriptTokenizer::Token::PLUS:
+			operation->operation = BinaryOpNode::OP_ADDITION;
+			break;
+		case GDScriptTokenizer::Token::MINUS:
+			operation->operation = BinaryOpNode::OP_SUBTRACTION;
+			break;
+		case GDScriptTokenizer::Token::STAR:
+			operation->operation = BinaryOpNode::OP_MULTIPLICATION;
+			break;
+		case GDScriptTokenizer::Token::SLASH:
+			operation->operation = BinaryOpNode::OP_DIVISION;
+			break;
+		case GDScriptTokenizer::Token::PERCENT:
+			operation->operation = BinaryOpNode::OP_MODULO;
+			break;
+		case GDScriptTokenizer::Token::LESS_LESS:
+			operation->operation = BinaryOpNode::OP_BIT_LEFT_SHIFT;
+			break;
+		case GDScriptTokenizer::Token::GREATER_GREATER:
+			operation->operation = BinaryOpNode::OP_BIT_RIGHT_SHIFT;
+			break;
+		case GDScriptTokenizer::Token::AMPERSAND:
+			operation->operation = BinaryOpNode::OP_BIT_AND;
+			break;
+		case GDScriptTokenizer::Token::PIPE:
+			operation->operation = BinaryOpNode::OP_BIT_AND;
+			break;
+		case GDScriptTokenizer::Token::CARET:
+			operation->operation = BinaryOpNode::OP_BIT_XOR;
+			break;
+		case GDScriptTokenizer::Token::AND:
+		case GDScriptTokenizer::Token::AMPERSAND_AMPERSAND:
+			operation->operation = BinaryOpNode::OP_LOGIC_AND;
+			break;
+		case GDScriptTokenizer::Token::OR:
+		case GDScriptTokenizer::Token::PIPE_PIPE:
+			operation->operation = BinaryOpNode::OP_LOGIC_OR;
+			break;
+		case GDScriptTokenizer::Token::IS:
+			operation->operation = BinaryOpNode::OP_TYPE_TEST;
+			break;
+		case GDScriptTokenizer::Token::IN:
+			operation->operation = BinaryOpNode::OP_CONTENT_TEST;
+			break;
+		case GDScriptTokenizer::Token::EQUAL_EQUAL:
+			operation->operation = BinaryOpNode::OP_COMP_EQUAL;
+			break;
+		case GDScriptTokenizer::Token::BANG_EQUAL:
+			operation->operation = BinaryOpNode::OP_COMP_NOT_EQUAL;
+			break;
+		case GDScriptTokenizer::Token::LESS:
+			operation->operation = BinaryOpNode::OP_COMP_LESS;
+			break;
+		case GDScriptTokenizer::Token::LESS_EQUAL:
+			operation->operation = BinaryOpNode::OP_COMP_LESS_EQUAL;
+			break;
+		case GDScriptTokenizer::Token::GREATER:
+			operation->operation = BinaryOpNode::OP_COMP_GREATER;
+			break;
+		case GDScriptTokenizer::Token::GREATER_EQUAL:
+			operation->operation = BinaryOpNode::OP_COMP_GREATER_EQUAL;
+			break;
+		default:
+			return nullptr; // Unreachable.
+	}
 
 
-		if (next_op == -1) {
-			_set_error("Yet another parser bug....");
-			ERR_FAIL_V(nullptr);
-		}
+	return operation;
+}
 
 
-		// OK! create operator..
-		if (is_unary) {
-			int expr_pos = next_op;
-			while (expression[expr_pos].is_op) {
-				expr_pos++;
-				if (expr_pos == expression.size()) {
-					//can happen..
-					_set_error("Unexpected end of expression...");
-					return nullptr;
-				}
-			}
+GDScriptParser::ExpressionNode *GDScriptParser::parse_ternary_operator(ExpressionNode *p_previous_operand, bool p_can_assign) {
+	// Only one ternary operation exists, so no abstraction here.
+	TernaryOpNode *operation = alloc_node<TernaryOpNode>();
+	operation->true_expr = p_previous_operand;
 
 
-			//consecutively do unary operators
-			for (int i = expr_pos - 1; i >= next_op; i--) {
-				OperatorNode *op = alloc_node<OperatorNode>();
-				op->op = expression[i].op;
-				op->arguments.push_back(expression[i + 1].node);
-				op->line = op_line; //line might have been changed from a \n
-				expression.write[i].is_op = false;
-				expression.write[i].node = op;
-				expression.remove(i + 1);
-			}
+	operation->condition = parse_precedence(PREC_TERNARY, false);
 
 
-		} else if (is_ternary) {
-			if (next_op < 1 || next_op >= (expression.size() - 1)) {
-				_set_error("Parser bug...");
-				ERR_FAIL_V(nullptr);
-			}
+	if (operation->condition == nullptr) {
+		push_error(R"(Expected expression as ternary condition after "if".)");
+	}
 
 
-			if (next_op >= (expression.size() - 2) || expression[next_op + 2].op != OperatorNode::OP_TERNARY_ELSE) {
-				_set_error("Expected else after ternary if.");
-				return nullptr;
-			}
-			if (next_op >= (expression.size() - 3)) {
-				_set_error("Expected value after ternary else.");
-				return nullptr;
-			}
+	consume(GDScriptTokenizer::Token::ELSE, R"(Expected "else" after ternary operator condition.)");
 
 
-			OperatorNode *op = alloc_node<OperatorNode>();
-			op->op = expression[next_op].op;
-			op->line = op_line; //line might have been changed from a \n
+	operation->false_expr = parse_precedence(PREC_TERNARY, false);
 
 
-			if (expression[next_op - 1].is_op) {
-				_set_error("Parser bug...");
-				ERR_FAIL_V(nullptr);
-			}
+	return operation;
+}
 
 
-			if (expression[next_op + 1].is_op) {
-				// this is not invalid and can really appear
-				// but it becomes invalid anyway because no binary op
-				// can be followed by a unary op in a valid combination,
-				// due to how precedence works, unaries will always disappear first
+GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode *p_previous_operand, bool p_can_assign) {
+	if (!p_can_assign) {
+		push_error("Assignment is not allowed inside an expression.");
+		return parse_expression(false); // Return the following expression.
+	}
 
 
-				_set_error("Unexpected two consecutive operators after ternary if.");
-				return nullptr;
-			}
+	switch (p_previous_operand->type) {
+		case Node::IDENTIFIER:
+		case Node::SUBSCRIPT:
+			// Okay.
+			break;
+		default:
+			push_error(R"(Only identifier, attribute access, and subscription access can be used as assignment target.)");
+			return parse_expression(false); // Return the following expression.
+	}
 
 
-			if (expression[next_op + 3].is_op) {
-				// this is not invalid and can really appear
-				// but it becomes invalid anyway because no binary op
-				// can be followed by a unary op in a valid combination,
-				// due to how precedence works, unaries will always disappear first
+	AssignmentNode *assignment = alloc_node<AssignmentNode>();
+	switch (previous.type) {
+		case GDScriptTokenizer::Token::EQUAL:
+			assignment->operation = AssignmentNode::OP_NONE;
+			break;
+		case GDScriptTokenizer::Token::PLUS_EQUAL:
+			assignment->operation = AssignmentNode::OP_ADDITION;
+			break;
+		case GDScriptTokenizer::Token::MINUS_EQUAL:
+			assignment->operation = AssignmentNode::OP_SUBTRACTION;
+			break;
+		case GDScriptTokenizer::Token::STAR_EQUAL:
+			assignment->operation = AssignmentNode::OP_MULTIPLICATION;
+			break;
+		case GDScriptTokenizer::Token::SLASH_EQUAL:
+			assignment->operation = AssignmentNode::OP_DIVISION;
+			break;
+		case GDScriptTokenizer::Token::PERCENT_EQUAL:
+			assignment->operation = AssignmentNode::OP_MODULO;
+			break;
+		case GDScriptTokenizer::Token::LESS_LESS_EQUAL:
+			assignment->operation = AssignmentNode::OP_BIT_SHIFT_LEFT;
+			break;
+		case GDScriptTokenizer::Token::GREATER_GREATER_EQUAL:
+			assignment->operation = AssignmentNode::OP_BIT_SHIFT_RIGHT;
+			break;
+		case GDScriptTokenizer::Token::AMPERSAND_EQUAL:
+			assignment->operation = AssignmentNode::OP_BIT_AND;
+			break;
+		case GDScriptTokenizer::Token::PIPE_EQUAL:
+			assignment->operation = AssignmentNode::OP_BIT_OR;
+			break;
+		case GDScriptTokenizer::Token::CARET_EQUAL:
+			assignment->operation = AssignmentNode::OP_BIT_XOR;
+			break;
+		default:
+			break; // Unreachable.
+	}
+	assignment->assignee = p_previous_operand;
+	assignment->assigned_value = parse_expression(false);
 
 
-				_set_error("Unexpected two consecutive operators after ternary else.");
-				return nullptr;
-			}
+	return assignment;
+}
 
 
-			op->arguments.push_back(expression[next_op + 1].node); //next expression goes as first
-			op->arguments.push_back(expression[next_op - 1].node); //left expression goes as when-true
-			op->arguments.push_back(expression[next_op + 3].node); //expression after next goes as when-false
+GDScriptParser::ExpressionNode *GDScriptParser::parse_await(ExpressionNode *p_previous_operand, bool p_can_assign) {
+	AwaitNode *await = alloc_node<AwaitNode>();
+	await->to_await = parse_precedence(PREC_AWAIT, false);
 
 
-			//replace all 3 nodes by this operator and make it an expression
-			expression.write[next_op - 1].node = op;
-			expression.remove(next_op);
-			expression.remove(next_op);
-			expression.remove(next_op);
-			expression.remove(next_op);
-		} else {
-			if (next_op < 1 || next_op >= (expression.size() - 1)) {
-				_set_error("Parser bug...");
-				ERR_FAIL_V(nullptr);
-			}
+	return await;
+}
 
 
-			OperatorNode *op = alloc_node<OperatorNode>();
-			op->op = expression[next_op].op;
-			op->line = op_line; //line might have been changed from a \n
+GDScriptParser::ExpressionNode *GDScriptParser::parse_array(ExpressionNode *p_previous_operand, bool p_can_assign) {
+	ArrayNode *array = alloc_node<ArrayNode>();
 
 
-			if (expression[next_op - 1].is_op) {
-				_set_error("Parser bug...");
-				ERR_FAIL_V(nullptr);
+	if (!check(GDScriptTokenizer::Token::BRACKET_CLOSE)) {
+		do {
+			if (check(GDScriptTokenizer::Token::BRACKET_CLOSE)) {
+				// Allow for trailing comma.
+				break;
 			}
 			}
 
 
-			if (expression[next_op + 1].is_op) {
-				// this is not invalid and can really appear
-				// but it becomes invalid anyway because no binary op
-				// can be followed by a unary op in a valid combination,
-				// due to how precedence works, unaries will always disappear first
-
-				_set_error("Unexpected two consecutive operators.");
-				return nullptr;
+			ExpressionNode *element = parse_expression(false);
+			if (element == nullptr) {
+				push_error(R"(Expected expression as array element.)");
+			} else {
+				array->elements.push_back(element);
 			}
 			}
-
-			op->arguments.push_back(expression[next_op - 1].node); //expression goes as left
-			op->arguments.push_back(expression[next_op + 1].node); //next expression goes as right
-
-			//replace all 3 nodes by this operator and make it an expression
-			expression.write[next_op - 1].node = op;
-			expression.remove(next_op);
-			expression.remove(next_op);
-		}
+		} while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end());
 	}
 	}
+	pop_multiline();
+	consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected closing "]" after array elements.)");
 
 
-	return expression[0].node;
+	return array;
 }
 }
 
 
-GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to_const) {
-	switch (p_node->type) {
-		case Node::TYPE_BUILT_IN_FUNCTION: {
-			//many may probably be optimizable
-			return p_node;
-		} break;
-		case Node::TYPE_ARRAY: {
-			ArrayNode *an = static_cast<ArrayNode *>(p_node);
-			bool all_constants = true;
-
-			for (int i = 0; i < an->elements.size(); i++) {
-				an->elements.write[i] = _reduce_expression(an->elements[i], p_to_const);
-				if (an->elements[i]->type != Node::TYPE_CONSTANT) {
-					all_constants = false;
-				}
-			}
-
-			if (all_constants && p_to_const) {
-				//reduce constant array expression
+GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode *p_previous_operand, bool p_can_assign) {
+	DictionaryNode *dictionary = alloc_node<DictionaryNode>();
 
 
-				ConstantNode *cn = alloc_node<ConstantNode>();
-				Array arr;
-				arr.resize(an->elements.size());
-				for (int i = 0; i < an->elements.size(); i++) {
-					ConstantNode *acn = static_cast<ConstantNode *>(an->elements[i]);
-					arr[i] = acn->value;
-				}
-				cn->value = arr;
-				cn->datatype = _type_from_variant(cn->value);
-				return cn;
+	bool decided_style = false;
+	if (!check(GDScriptTokenizer::Token::BRACE_CLOSE)) {
+		do {
+			if (check(GDScriptTokenizer::Token::BRACE_CLOSE)) {
+				// Allow for trailing comma.
+				break;
 			}
 			}
 
 
-			return an;
+			// Key.
+			ExpressionNode *key = parse_expression(false, true); // Stop on "=" so we can check for Lua table style.
 
 
-		} break;
-		case Node::TYPE_DICTIONARY: {
-			DictionaryNode *dn = static_cast<DictionaryNode *>(p_node);
-			bool all_constants = true;
-
-			for (int i = 0; i < dn->elements.size(); i++) {
-				dn->elements.write[i].key = _reduce_expression(dn->elements[i].key, p_to_const);
-				if (dn->elements[i].key->type != Node::TYPE_CONSTANT) {
-					all_constants = false;
-				}
-				dn->elements.write[i].value = _reduce_expression(dn->elements[i].value, p_to_const);
-				if (dn->elements[i].value->type != Node::TYPE_CONSTANT) {
-					all_constants = false;
-				}
+			if (key == nullptr) {
+				push_error(R"(Expected expression as dictionary key.)");
 			}
 			}
 
 
-			if (all_constants && p_to_const) {
-				//reduce constant array expression
-
-				ConstantNode *cn = alloc_node<ConstantNode>();
-				Dictionary dict;
-				for (int i = 0; i < dn->elements.size(); i++) {
-					ConstantNode *key_c = static_cast<ConstantNode *>(dn->elements[i].key);
-					ConstantNode *value_c = static_cast<ConstantNode *>(dn->elements[i].value);
-
-					dict[key_c->value] = value_c->value;
+			if (!decided_style) {
+				switch (current.type) {
+					case GDScriptTokenizer::Token::COLON:
+						dictionary->style = DictionaryNode::PYTHON_DICT;
+						break;
+					case GDScriptTokenizer::Token::EQUAL:
+						dictionary->style = DictionaryNode::LUA_TABLE;
+						break;
+					default:
+						push_error(R"(Expected ":" or "=" after dictionary key.)");
+						break;
 				}
 				}
-				cn->value = dict;
-				cn->datatype = _type_from_variant(cn->value);
-				return cn;
+				decided_style = true;
 			}
 			}
 
 
-			return dn;
-
-		} break;
-		case Node::TYPE_OPERATOR: {
-			OperatorNode *op = static_cast<OperatorNode *>(p_node);
-
-			bool all_constants = true;
-			int last_not_constant = -1;
-
-			for (int i = 0; i < op->arguments.size(); i++) {
-				op->arguments.write[i] = _reduce_expression(op->arguments[i], p_to_const);
-				if (op->arguments[i]->type != Node::TYPE_CONSTANT) {
-					all_constants = false;
-					last_not_constant = i;
-				}
+			switch (dictionary->style) {
+				case DictionaryNode::LUA_TABLE:
+					if (key != nullptr && key->type != Node::IDENTIFIER) {
+						push_error("Expected identifier as dictionary key.");
+					}
+					if (!match(GDScriptTokenizer::Token::EQUAL)) {
+						if (match(GDScriptTokenizer::Token::COLON)) {
+							push_error(R"(Expected "=" after dictionary key. Mixing dictionary styles is not allowed.)");
+							advance(); // Consume wrong separator anyway.
+						} else {
+							push_error(R"(Expected "=" after dictionary key.)");
+						}
+					}
+					break;
+				case DictionaryNode::PYTHON_DICT:
+					if (!match(GDScriptTokenizer::Token::COLON)) {
+						if (match(GDScriptTokenizer::Token::EQUAL)) {
+							push_error(R"(Expected ":" after dictionary key. Mixing dictionary styles is not allowed.)");
+							advance(); // Consume wrong separator anyway.
+						} else {
+							push_error(R"(Expected ":" after dictionary key.)");
+						}
+					}
+					break;
 			}
 			}
 
 
-			if (op->op == OperatorNode::OP_IS) {
-				//nothing much
-				return op;
+			// Value.
+			ExpressionNode *value = parse_expression(false);
+			if (value == nullptr) {
+				push_error(R"(Expected expression as dictionary value.)");
 			}
 			}
-			if (op->op == OperatorNode::OP_PARENT_CALL) {
-				//nothing much
-				return op;
-
-			} else if (op->op == OperatorNode::OP_CALL) {
-				//can reduce base type constructors
-				if ((op->arguments[0]->type == Node::TYPE_TYPE || (op->arguments[0]->type == Node::TYPE_BUILT_IN_FUNCTION && GDScriptFunctions::is_deterministic(static_cast<BuiltInFunctionNode *>(op->arguments[0])->function))) && last_not_constant == 0) {
-					//native type constructor or intrinsic function
-					const Variant **vptr = nullptr;
-					Vector<Variant *> ptrs;
-					if (op->arguments.size() > 1) {
-						ptrs.resize(op->arguments.size() - 1);
-						for (int i = 0; i < ptrs.size(); i++) {
-							ConstantNode *cn = static_cast<ConstantNode *>(op->arguments[i + 1]);
-							ptrs.write[i] = &cn->value;
-						}
 
 
-						vptr = (const Variant **)&ptrs[0];
-					}
-
-					Callable::CallError ce;
-					Variant v;
+			if (key != nullptr && value != nullptr) {
+				dictionary->elements.push_back({ key, value });
+			}
+		} while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end());
+	}
+	pop_multiline();
+	consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" after dictionary elements.)");
 
 
-					if (op->arguments[0]->type == Node::TYPE_TYPE) {
-						TypeNode *tn = static_cast<TypeNode *>(op->arguments[0]);
-						v = Variant::construct(tn->vtype, vptr, ptrs.size(), ce);
+	return dictionary;
+}
 
 
-					} else {
-						GDScriptFunctions::Function func = static_cast<BuiltInFunctionNode *>(op->arguments[0])->function;
-						GDScriptFunctions::call(func, vptr, ptrs.size(), v, ce);
-					}
+GDScriptParser::ExpressionNode *GDScriptParser::parse_grouping(ExpressionNode *p_previous_operand, bool p_can_assign) {
+	ExpressionNode *grouped = parse_expression(false);
+	pop_multiline();
+	consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after grouping expression.)*");
+	return grouped;
+}
 
 
-					if (ce.error != Callable::CallError::CALL_OK) {
-						String errwhere;
-						if (op->arguments[0]->type == Node::TYPE_TYPE) {
-							TypeNode *tn = static_cast<TypeNode *>(op->arguments[0]);
-							errwhere = "'" + Variant::get_type_name(tn->vtype) + "' constructor";
+GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode *p_previous_operand, bool p_can_assign) {
+	SubscriptNode *attribute = alloc_node<SubscriptNode>();
 
 
-						} else {
-							GDScriptFunctions::Function func = static_cast<BuiltInFunctionNode *>(op->arguments[0])->function;
-							errwhere = String("'") + GDScriptFunctions::get_func_name(func) + "' intrinsic function";
-						}
+	attribute->is_attribute = true;
+	attribute->base = p_previous_operand;
 
 
-						switch (ce.error) {
-							case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: {
-								_set_error("Invalid argument (#" + itos(ce.argument + 1) + ") for " + errwhere + ".");
-
-							} break;
-							case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: {
-								_set_error("Too many arguments for " + errwhere + ".");
-							} break;
-							case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: {
-								_set_error("Too few arguments for " + errwhere + ".");
-							} break;
-							default: {
-								_set_error("Invalid arguments for " + errwhere + ".");
-
-							} break;
-						}
+	if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier after "." for attribute access.)")) {
+		return nullptr;
+	}
+	attribute->attribute = parse_identifier();
 
 
-						error_line = op->line;
+	return attribute;
+}
 
 
-						return p_node;
-					}
+GDScriptParser::ExpressionNode *GDScriptParser::parse_subscript(ExpressionNode *p_previous_operand, bool p_can_assign) {
+	SubscriptNode *subscript = alloc_node<SubscriptNode>();
 
 
-					ConstantNode *cn = alloc_node<ConstantNode>();
-					cn->value = v;
-					cn->datatype = _type_from_variant(v);
-					return cn;
-				}
+	subscript->base = p_previous_operand;
+	subscript->index = parse_expression(false);
 
 
-				return op; //don't reduce yet
+	pop_multiline();
+	consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected "]" after subscription index.)");
 
 
-			} else if (op->op == OperatorNode::OP_YIELD) {
-				return op;
+	return subscript;
+}
 
 
-			} else if (op->op == OperatorNode::OP_INDEX) {
-				//can reduce indices into constant arrays or dictionaries
+GDScriptParser::ExpressionNode *GDScriptParser::parse_cast(ExpressionNode *p_previous_operand, bool p_can_assign) {
+	CastNode *cast = alloc_node<CastNode>();
 
 
-				if (all_constants) {
-					ConstantNode *ca = static_cast<ConstantNode *>(op->arguments[0]);
-					ConstantNode *cb = static_cast<ConstantNode *>(op->arguments[1]);
+	cast->operand = p_previous_operand;
+	cast->cast_type = parse_type();
 
 
-					bool valid;
+	if (cast->cast_type == nullptr) {
+		push_error(R"(Expected type specifier after "as".)");
+		return p_previous_operand;
+	}
 
 
-					Variant v = ca->value.get(cb->value, &valid);
-					if (!valid) {
-						_set_error("invalid index in constant expression");
-						error_line = op->line;
-						return op;
-					}
+	return cast;
+}
 
 
-					ConstantNode *cn = alloc_node<ConstantNode>();
-					cn->value = v;
-					cn->datatype = _type_from_variant(v);
-					return cn;
-				}
+GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_previous_operand, bool p_can_assign) {
+	CallNode *call = alloc_node<CallNode>();
 
 
-				return op;
+	if (previous.type == GDScriptTokenizer::Token::SUPER) {
+		// Super call.
+		call->is_super = true;
+		push_multiline(true);
+		if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
+			// Implicit call to the parent method of the same name.
+			if (current_function == nullptr) {
+				push_error(R"(Cannot use implicit "super" call outside of a function.)");
+				pop_multiline();
+				return nullptr;
+			}
+		} else {
+			consume(GDScriptTokenizer::Token::PERIOD, R"(Expected "." or "(" after "super".)");
+			if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after ".".)")) {
+				pop_multiline();
+				return nullptr;
+			}
+			call->callee = parse_identifier();
+			consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after function name.)");
+		}
+	} else {
+		call->callee = p_previous_operand;
+	}
 
 
-			} else if (op->op == OperatorNode::OP_INDEX_NAMED) {
-				if (op->arguments[0]->type == Node::TYPE_CONSTANT && op->arguments[1]->type == Node::TYPE_IDENTIFIER) {
-					ConstantNode *ca = static_cast<ConstantNode *>(op->arguments[0]);
-					IdentifierNode *ib = static_cast<IdentifierNode *>(op->arguments[1]);
+	if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
+		// Arguments.
+		do {
+			if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
+				// Allow for trailing comma.
+				break;
+			}
+			ExpressionNode *argument = parse_expression(false);
+			if (argument == nullptr) {
+				push_error(R"(Expected expression as the function argument.)");
+			} else {
+				call->arguments.push_back(argument);
+			}
+		} while (match(GDScriptTokenizer::Token::COMMA));
+	}
 
 
-					bool valid;
-					Variant v = ca->value.get_named(ib->name, &valid);
-					if (!valid) {
-						_set_error("invalid index '" + String(ib->name) + "' in constant expression");
-						error_line = op->line;
-						return op;
-					}
+	pop_multiline();
+	consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after call arguments.)*");
 
 
-					ConstantNode *cn = alloc_node<ConstantNode>();
-					cn->value = v;
-					cn->datatype = _type_from_variant(v);
-					return cn;
-				}
+	return call;
+}
 
 
-				return op;
+GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p_previous_operand, bool p_can_assign) {
+	if (match(GDScriptTokenizer::Token::LITERAL)) {
+		if (previous.literal.get_type() != Variant::STRING) {
+			push_error(R"(Expect node path as string or identifer after "$".)");
+			return nullptr;
+		}
+		GetNodeNode *get_node = alloc_node<GetNodeNode>();
+		get_node->string = parse_literal();
+		return get_node;
+	} else if (check(GDScriptTokenizer::Token::IDENTIFIER)) {
+		GetNodeNode *get_node = alloc_node<GetNodeNode>();
+		do {
+			if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expect node identifer after "/".)")) {
+				return nullptr;
 			}
 			}
+			IdentifierNode *identifier = parse_identifier();
+			get_node->chain.push_back(identifier);
+		} while (match(GDScriptTokenizer::Token::SLASH));
+		return get_node;
+	} else {
+		push_error(R"(Expect node path as string or identifer after "$".)");
+		return nullptr;
+	}
+}
 
 
-			//validate assignment (don't assign to constant expression
-			switch (op->op) {
-				case OperatorNode::OP_ASSIGN:
-				case OperatorNode::OP_ASSIGN_ADD:
-				case OperatorNode::OP_ASSIGN_SUB:
-				case OperatorNode::OP_ASSIGN_MUL:
-				case OperatorNode::OP_ASSIGN_DIV:
-				case OperatorNode::OP_ASSIGN_MOD:
-				case OperatorNode::OP_ASSIGN_SHIFT_LEFT:
-				case OperatorNode::OP_ASSIGN_SHIFT_RIGHT:
-				case OperatorNode::OP_ASSIGN_BIT_AND:
-				case OperatorNode::OP_ASSIGN_BIT_OR:
-				case OperatorNode::OP_ASSIGN_BIT_XOR: {
-					if (op->arguments[0]->type == Node::TYPE_CONSTANT) {
-						_set_error("Can't assign to constant", tokenizer->get_token_line() - 1);
-						error_line = op->line;
-						return op;
-					} else if (op->arguments[0]->type == Node::TYPE_SELF) {
-						_set_error("Can't assign to self.", op->line);
-						error_line = op->line;
-						return op;
-					}
+GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_previous_operand, bool p_can_assign) {
+	PreloadNode *preload = alloc_node<PreloadNode>();
+	preload->resolved_path = "<missing path>";
 
 
-					if (op->arguments[0]->type == Node::TYPE_OPERATOR) {
-						OperatorNode *on = static_cast<OperatorNode *>(op->arguments[0]);
-						if (on->op != OperatorNode::OP_INDEX && on->op != OperatorNode::OP_INDEX_NAMED) {
-							_set_error("Can't assign to an expression", tokenizer->get_token_line() - 1);
-							error_line = op->line;
-							return op;
-						}
-					}
+	push_multiline(true);
+	consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "preload".)");
 
 
-				} break;
-				default: {
-					break;
-				}
-			}
-			//now se if all are constants
-			if (!all_constants) {
-				return op; //nothing to reduce from here on
-			}
-#define _REDUCE_UNARY(m_vop)                                                                               \
-	bool valid = false;                                                                                    \
-	Variant res;                                                                                           \
-	Variant::evaluate(m_vop, static_cast<ConstantNode *>(op->arguments[0])->value, Variant(), res, valid); \
-	if (!valid) {                                                                                          \
-		_set_error("Invalid operand for unary operator");                                                  \
-		error_line = op->line;                                                                             \
-		return p_node;                                                                                     \
-	}                                                                                                      \
-	ConstantNode *cn = alloc_node<ConstantNode>();                                                         \
-	cn->value = res;                                                                                       \
-	cn->datatype = _type_from_variant(res);                                                                \
-	return cn;
-
-#define _REDUCE_BINARY(m_vop)                                                                                                                         \
-	bool valid = false;                                                                                                                               \
-	Variant res;                                                                                                                                      \
-	Variant::evaluate(m_vop, static_cast<ConstantNode *>(op->arguments[0])->value, static_cast<ConstantNode *>(op->arguments[1])->value, res, valid); \
-	if (!valid) {                                                                                                                                     \
-		_set_error("Invalid operands for operator");                                                                                                  \
-		error_line = op->line;                                                                                                                        \
-		return p_node;                                                                                                                                \
-	}                                                                                                                                                 \
-	ConstantNode *cn = alloc_node<ConstantNode>();                                                                                                    \
-	cn->value = res;                                                                                                                                  \
-	cn->datatype = _type_from_variant(res);                                                                                                           \
-	return cn;
-
-			switch (op->op) {
-				//unary operators
-				case OperatorNode::OP_NEG: {
-					_REDUCE_UNARY(Variant::OP_NEGATE);
-				} break;
-				case OperatorNode::OP_POS: {
-					_REDUCE_UNARY(Variant::OP_POSITIVE);
-				} break;
-				case OperatorNode::OP_NOT: {
-					_REDUCE_UNARY(Variant::OP_NOT);
-				} break;
-				case OperatorNode::OP_BIT_INVERT: {
-					_REDUCE_UNARY(Variant::OP_BIT_NEGATE);
-				} break;
-				//binary operators (in precedence order)
-				case OperatorNode::OP_IN: {
-					_REDUCE_BINARY(Variant::OP_IN);
-				} break;
-				case OperatorNode::OP_EQUAL: {
-					_REDUCE_BINARY(Variant::OP_EQUAL);
-				} break;
-				case OperatorNode::OP_NOT_EQUAL: {
-					_REDUCE_BINARY(Variant::OP_NOT_EQUAL);
-				} break;
-				case OperatorNode::OP_LESS: {
-					_REDUCE_BINARY(Variant::OP_LESS);
-				} break;
-				case OperatorNode::OP_LESS_EQUAL: {
-					_REDUCE_BINARY(Variant::OP_LESS_EQUAL);
-				} break;
-				case OperatorNode::OP_GREATER: {
-					_REDUCE_BINARY(Variant::OP_GREATER);
-				} break;
-				case OperatorNode::OP_GREATER_EQUAL: {
-					_REDUCE_BINARY(Variant::OP_GREATER_EQUAL);
-				} break;
-				case OperatorNode::OP_AND: {
-					_REDUCE_BINARY(Variant::OP_AND);
-				} break;
-				case OperatorNode::OP_OR: {
-					_REDUCE_BINARY(Variant::OP_OR);
-				} break;
-				case OperatorNode::OP_ADD: {
-					_REDUCE_BINARY(Variant::OP_ADD);
-				} break;
-				case OperatorNode::OP_SUB: {
-					_REDUCE_BINARY(Variant::OP_SUBTRACT);
-				} break;
-				case OperatorNode::OP_MUL: {
-					_REDUCE_BINARY(Variant::OP_MULTIPLY);
-				} break;
-				case OperatorNode::OP_DIV: {
-					_REDUCE_BINARY(Variant::OP_DIVIDE);
-				} break;
-				case OperatorNode::OP_MOD: {
-					_REDUCE_BINARY(Variant::OP_MODULE);
-				} break;
-				case OperatorNode::OP_SHIFT_LEFT: {
-					_REDUCE_BINARY(Variant::OP_SHIFT_LEFT);
-				} break;
-				case OperatorNode::OP_SHIFT_RIGHT: {
-					_REDUCE_BINARY(Variant::OP_SHIFT_RIGHT);
-				} break;
-				case OperatorNode::OP_BIT_AND: {
-					_REDUCE_BINARY(Variant::OP_BIT_AND);
-				} break;
-				case OperatorNode::OP_BIT_OR: {
-					_REDUCE_BINARY(Variant::OP_BIT_OR);
-				} break;
-				case OperatorNode::OP_BIT_XOR: {
-					_REDUCE_BINARY(Variant::OP_BIT_XOR);
-				} break;
-				case OperatorNode::OP_TERNARY_IF: {
-					if (static_cast<ConstantNode *>(op->arguments[0])->value.booleanize()) {
-						return op->arguments[1];
-					} else {
-						return op->arguments[2];
-					}
-				} break;
-				default: {
-					ERR_FAIL_V(op);
+	preload->path = parse_expression(false);
+
+	if (preload->path == nullptr) {
+		push_error(R"(Expected resource path after "(".)");
+	} else if (preload->path->type != Node::LITERAL) {
+		push_error("Preloaded path must be a constant string.");
+	} else {
+		LiteralNode *path = static_cast<LiteralNode *>(preload->path);
+		if (path->value.get_type() != Variant::STRING) {
+			push_error("Preloaded path must be a constant string.");
+		} else {
+			preload->resolved_path = path->value;
+			// TODO: Save this as script dependency.
+			if (preload->resolved_path.is_rel_path()) {
+				preload->resolved_path = script_path.get_base_dir().plus_file(preload->resolved_path);
+			}
+			preload->resolved_path = preload->resolved_path.simplify_path();
+			if (!FileAccess::exists(preload->resolved_path)) {
+				push_error(vformat(R"(Preload file "%s" does not exist.)", preload->resolved_path));
+			} else {
+				// TODO: Don't load if validating: use completion cache.
+				preload->resource = ResourceLoader::load(preload->resolved_path);
+				if (preload->resource.is_null()) {
+					push_error(vformat(R"(Could not preload resource file "%s".)", preload->resolved_path));
 				}
 				}
 			}
 			}
-
-		} break;
-		default: {
-			return p_node;
-		} break;
+		}
 	}
 	}
+
+	pop_multiline();
+	consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after preload path.)*");
+
+	return preload;
 }
 }
 
 
-GDScriptParser::Node *GDScriptParser::_parse_and_reduce_expression(Node *p_parent, bool p_static, bool p_reduce_const, bool p_allow_assign) {
-	Node *expr = _parse_expression(p_parent, p_static, p_allow_assign, p_reduce_const);
-	if (!expr || error_set) {
-		return nullptr;
-	}
-	expr = _reduce_expression(expr, p_reduce_const);
-	if (!expr || error_set) {
-		return nullptr;
+GDScriptParser::ExpressionNode *GDScriptParser::parse_invalid_token(ExpressionNode *p_previous_operand, bool p_can_assign) {
+	// Just for better error messages.
+	GDScriptTokenizer::Token::Type invalid = previous.type;
+
+	switch (invalid) {
+		case GDScriptTokenizer::Token::QUESTION_MARK:
+			push_error(R"(Unexpected "?" in source. If you want a ternary operator, use "truthy_value if true_condition else falsy_value".)");
+			break;
+		default:
+			return nullptr; // Unreachable.
 	}
 	}
-	return expr;
+
+	// Return the previous expression.
+	return p_previous_operand;
 }
 }
 
 
-bool GDScriptParser::_reduce_export_var_type(Variant &p_value, int p_line) {
-	if (p_value.get_type() == Variant::ARRAY) {
-		Array arr = p_value;
-		for (int i = 0; i < arr.size(); i++) {
-			if (!_reduce_export_var_type(arr[i], p_line)) {
-				return false;
+GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) {
+	if (!match(GDScriptTokenizer::Token::IDENTIFIER)) {
+		if (match(GDScriptTokenizer::Token::VOID)) {
+			if (p_allow_void) {
+				TypeNode *type = alloc_node<TypeNode>();
+				return type;
+			} else {
+				push_error(R"("void" is only allowed for a function return type.)");
 			}
 			}
 		}
 		}
-		return true;
+		// Leave error message to the caller who knows the context.
+		return nullptr;
 	}
 	}
+	TypeNode *type = alloc_node<TypeNode>();
+	IdentifierNode *type_base = parse_identifier();
 
 
-	if (p_value.get_type() == Variant::DICTIONARY) {
-		Dictionary dict = p_value;
-		for (int i = 0; i < dict.size(); i++) {
-			Variant value = dict.get_value_at_index(i);
-			if (!_reduce_export_var_type(value, p_line)) {
-				return false;
-			}
+	// FIXME: This is likely not working with multiple chained attributes (A.B.C.D...).
+	// FIXME: I probably should use a list for this, not an attribute (since those aren't chained anymore).
+	if (match(GDScriptTokenizer::Token::PERIOD)) {
+		type->type_specifier = static_cast<SubscriptNode *>(parse_attribute(type_base, false));
+		if (type->type_specifier->index == nullptr) {
+			return nullptr;
 		}
 		}
+	} else {
+		type->type_base = type_base;
+	}
+
+	return type;
+}
+
+GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Type p_token_type) {
+	// Function table for expression parsing.
+	// clang-format destroys the alignment here, so turn off for the table.
+	/* clang-format off */
+	static ParseRule rules[] = {
+		// PREFIX                                           INFIX                                           PRECEDENCE (for binary)
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // EMPTY,
+		// Basic
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // ANNOTATION,
+		{ &GDScriptParser::parse_identifier,             	nullptr,                                        PREC_NONE }, // IDENTIFIER,
+		{ &GDScriptParser::parse_literal,                	nullptr,                                        PREC_NONE }, // LITERAL,
+		// Comparison
+		{ nullptr,                                          &GDScriptParser::parse_binary_operator,      	PREC_COMPARISON }, // LESS,
+		{ nullptr,                                          &GDScriptParser::parse_binary_operator,      	PREC_COMPARISON }, // LESS_EQUAL,
+		{ nullptr,                                          &GDScriptParser::parse_binary_operator,      	PREC_COMPARISON }, // GREATER,
+		{ nullptr,                                          &GDScriptParser::parse_binary_operator,      	PREC_COMPARISON }, // GREATER_EQUAL,
+		{ nullptr,                                          &GDScriptParser::parse_binary_operator,      	PREC_COMPARISON }, // EQUAL_EQUAL,
+		{ nullptr,                                          &GDScriptParser::parse_binary_operator,      	PREC_COMPARISON }, // BANG_EQUAL,
+		// Logical
+		{ nullptr,                                          &GDScriptParser::parse_binary_operator,      	PREC_LOGIC_AND }, // AND,
+		{ nullptr,                                          &GDScriptParser::parse_binary_operator,      	PREC_LOGIC_OR }, // OR,
+		{ &GDScriptParser::parse_unary_operator,         	nullptr,                                        PREC_NONE }, // NOT,
+		{ nullptr,                                          &GDScriptParser::parse_binary_operator,			PREC_LOGIC_AND }, // AMPERSAND_AMPERSAND,
+		{ nullptr,                                          &GDScriptParser::parse_binary_operator,			PREC_LOGIC_OR }, // PIPE_PIPE,
+		{ &GDScriptParser::parse_unary_operator,			nullptr,                                        PREC_NONE }, // BANG,
+		// Bitwise
+		{ nullptr,                                          &GDScriptParser::parse_binary_operator,      	PREC_BIT_AND }, // AMPERSAND,
+		{ nullptr,                                          &GDScriptParser::parse_binary_operator,      	PREC_BIT_OR }, // PIPE,
+		{ &GDScriptParser::parse_unary_operator,         	nullptr,                                        PREC_NONE }, // TILDE,
+		{ nullptr,                                          &GDScriptParser::parse_binary_operator,      	PREC_BIT_XOR }, // CARET,
+		{ nullptr,                                          &GDScriptParser::parse_binary_operator,      	PREC_BIT_SHIFT }, // LESS_LESS,
+		{ nullptr,                                          &GDScriptParser::parse_binary_operator,      	PREC_BIT_SHIFT }, // GREATER_GREATER,
+		// Math
+		{ &GDScriptParser::parse_unary_operator,         	&GDScriptParser::parse_binary_operator,      	PREC_ADDITION }, // PLUS,
+		{ &GDScriptParser::parse_unary_operator,         	&GDScriptParser::parse_binary_operator,      	PREC_SUBTRACTION }, // MINUS,
+		{ nullptr,                                          &GDScriptParser::parse_binary_operator,      	PREC_FACTOR }, // STAR,
+		{ nullptr,                                          &GDScriptParser::parse_binary_operator,      	PREC_FACTOR }, // SLASH,
+		{ nullptr,                                          &GDScriptParser::parse_binary_operator,      	PREC_FACTOR }, // PERCENT,
+		// Assignment
+		{ nullptr,                                          &GDScriptParser::parse_assignment,           	PREC_ASSIGNMENT }, // EQUAL,
+		{ nullptr,                                          &GDScriptParser::parse_assignment,           	PREC_ASSIGNMENT }, // PLUS_EQUAL,
+		{ nullptr,                                          &GDScriptParser::parse_assignment,           	PREC_ASSIGNMENT }, // MINUS_EQUAL,
+		{ nullptr,                                          &GDScriptParser::parse_assignment,           	PREC_ASSIGNMENT }, // STAR_EQUAL,
+		{ nullptr,                                          &GDScriptParser::parse_assignment,           	PREC_ASSIGNMENT }, // SLASH_EQUAL,
+		{ nullptr,                                          &GDScriptParser::parse_assignment,           	PREC_ASSIGNMENT }, // PERCENT_EQUAL,
+		{ nullptr,                                          &GDScriptParser::parse_assignment,           	PREC_ASSIGNMENT }, // LESS_LESS_EQUAL,
+		{ nullptr,                                          &GDScriptParser::parse_assignment,           	PREC_ASSIGNMENT }, // GREATER_GREATER_EQUAL,
+		{ nullptr,                                          &GDScriptParser::parse_assignment,           	PREC_ASSIGNMENT }, // AMPERSAND_EQUAL,
+		{ nullptr,                                          &GDScriptParser::parse_assignment,           	PREC_ASSIGNMENT }, // PIPE_EQUAL,
+		{ nullptr,                                          &GDScriptParser::parse_assignment,           	PREC_ASSIGNMENT }, // CARET_EQUAL,
+		// Control flow
+		{ nullptr,                                          &GDScriptParser::parse_ternary_operator,     	PREC_TERNARY }, // IF,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // ELIF,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // ELSE,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // FOR,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // WHILE,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // BREAK,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // CONTINUE,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // PASS,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // RETURN,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // MATCH,
+		// Keywords
+		{ nullptr,                                          &GDScriptParser::parse_cast,                 	PREC_CAST }, // AS,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // ASSERT,
+		{ &GDScriptParser::parse_await,                  	nullptr,                                        PREC_NONE }, // AWAIT,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // BREAKPOINT,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // CLASS,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // CLASS_NAME,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // CONST,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // ENUM,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // EXTENDS,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // FUNC,
+		{ nullptr,                                          &GDScriptParser::parse_binary_operator,      	PREC_CONTENT_TEST }, // IN,
+		{ nullptr,                                          &GDScriptParser::parse_binary_operator,      	PREC_TYPE_TEST }, // IS,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // NAMESPACE,
+		{ &GDScriptParser::parse_preload,					nullptr,                                        PREC_NONE }, // PRELOAD,
+		{ &GDScriptParser::parse_self,                   	nullptr,                                        PREC_NONE }, // SELF,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // SIGNAL,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // STATIC,
+		{ &GDScriptParser::parse_call,						nullptr,                                        PREC_NONE }, // SUPER,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // VAR,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // VOID,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // YIELD,
+		// Punctuation
+		{ &GDScriptParser::parse_array,                  	&GDScriptParser::parse_subscript,            	PREC_SUBSCRIPT }, // BRACKET_OPEN,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // BRACKET_CLOSE,
+		{ &GDScriptParser::parse_dictionary,             	nullptr,                                        PREC_NONE }, // BRACE_OPEN,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // BRACE_CLOSE,
+		{ &GDScriptParser::parse_grouping,               	&GDScriptParser::parse_call,                 	PREC_CALL }, // PARENTHESIS_OPEN,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // PARENTHESIS_CLOSE,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // COMMA,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // SEMICOLON,
+		{ nullptr,                                          &GDScriptParser::parse_attribute,            	PREC_ATTRIBUTE }, // PERIOD,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // PERIOD_PERIOD,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // COLON,
+		{ &GDScriptParser::parse_get_node,               	nullptr,                                        PREC_NONE }, // DOLLAR,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // FORWARD_ARROW,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // UNDERSCORE,
+		// Whitespace
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // NEWLINE,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // INDENT,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // DEDENT,
+		// Constants
+		{ &GDScriptParser::parse_builtin_constant,			nullptr,                                        PREC_NONE }, // CONST_PI,
+		{ &GDScriptParser::parse_builtin_constant,			nullptr,                                        PREC_NONE }, // CONST_TAU,
+		{ &GDScriptParser::parse_builtin_constant,			nullptr,                                        PREC_NONE }, // CONST_INF,
+		{ &GDScriptParser::parse_builtin_constant,			nullptr,                                        PREC_NONE }, // CONST_NAN,
+		// Error message improvement
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // VCS_CONFLICT_MARKER,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // BACKTICK,
+		{ nullptr,                                          &GDScriptParser::parse_invalid_token,        	PREC_CAST }, // QUESTION_MARK,
+		// Special
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // ERROR,
+		{ nullptr,                                          nullptr,                                        PREC_NONE }, // TK_EOF,
+	};
+	/* clang-format on */
+	// Avoid desync.
+	static_assert(sizeof(rules) / sizeof(rules[0]) == GDScriptTokenizer::Token::TK_MAX, "Amount of parse rules don't match the amount of token types.");
+
+	// Let's assume this this never invalid, since nothing generates a TK_MAX.
+	return &rules[p_token_type];
+}
+
+bool GDScriptParser::SuiteNode::has_local(const StringName &p_name) const {
+	if (locals_indices.has(p_name)) {
 		return true;
 		return true;
 	}
 	}
-
-	// validate type
-	DataType type = _type_from_variant(p_value);
-	if (type.kind == DataType::BUILTIN) {
-		return true;
-	} else if (type.kind == DataType::NATIVE) {
-		if (ClassDB::is_parent_class(type.native_type, "Resource")) {
-			return true;
-		}
+	if (parent_block != nullptr) {
+		return parent_block->has_local(p_name);
 	}
 	}
-	_set_error("Invalid export type. Only built-in and native resource types can be exported.", p_line);
 	return false;
 	return false;
 }
 }
 
 
-bool GDScriptParser::_recover_from_completion() {
-	if (!completion_found) {
-		return false; //can't recover if no completion
-	}
-	//skip stuff until newline
-	while (tokenizer->get_token() != GDScriptTokenizer::TK_NEWLINE && tokenizer->get_token() != GDScriptTokenizer::TK_EOF && tokenizer->get_token() != GDScriptTokenizer::TK_ERROR) {
-		tokenizer->advance();
+const GDScriptParser::SuiteNode::Local &GDScriptParser::SuiteNode::get_local(const StringName &p_name) const {
+	if (locals_indices.has(p_name)) {
+		return locals[locals_indices[p_name]];
 	}
 	}
-	completion_found = false;
-	error_set = false;
-	if (tokenizer->get_token() == GDScriptTokenizer::TK_ERROR) {
-		error_set = true;
+	if (parent_block != nullptr) {
+		return parent_block->get_local(p_name);
 	}
 	}
+	return empty;
+}
 
 
-	return true;
+bool GDScriptParser::AnnotationNode::apply(GDScriptParser *p_this, Node *p_target) const {
+	return (p_this->*(p_this->valid_annotations[name].apply))(this, p_target);
 }
 }
 
 
-GDScriptParser::PatternNode *GDScriptParser::_parse_pattern(bool p_static) {
-	PatternNode *pattern = alloc_node<PatternNode>();
-
-	GDScriptTokenizer::Token token = tokenizer->get_token();
-	if (error_set) {
-		return nullptr;
-	}
-
-	if (token == GDScriptTokenizer::TK_EOF) {
-		return nullptr;
-	}
-
-	switch (token) {
-		// array
-		case GDScriptTokenizer::TK_BRACKET_OPEN: {
-			tokenizer->advance();
-			pattern->pt_type = GDScriptParser::PatternNode::PT_ARRAY;
-			while (true) {
-				if (tokenizer->get_token() == GDScriptTokenizer::TK_BRACKET_CLOSE) {
-					tokenizer->advance();
-					break;
-				}
-
-				if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD && tokenizer->get_token(1) == GDScriptTokenizer::TK_PERIOD) {
-					// match everything
-					tokenizer->advance(2);
-					PatternNode *sub_pattern = alloc_node<PatternNode>();
-					sub_pattern->pt_type = GDScriptParser::PatternNode::PT_IGNORE_REST;
-					pattern->array.push_back(sub_pattern);
-					if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA && tokenizer->get_token(1) == GDScriptTokenizer::TK_BRACKET_CLOSE) {
-						tokenizer->advance(2);
-						break;
-					} else if (tokenizer->get_token() == GDScriptTokenizer::TK_BRACKET_CLOSE) {
-						tokenizer->advance(1);
-						break;
-					} else {
-						_set_error("'..' pattern only allowed at the end of an array pattern");
-						return nullptr;
-					}
-				}
-
-				PatternNode *sub_pattern = _parse_pattern(p_static);
-				if (!sub_pattern) {
-					return nullptr;
-				}
-
-				pattern->array.push_back(sub_pattern);
-
-				if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
-					tokenizer->advance();
-					continue;
-				} else if (tokenizer->get_token() == GDScriptTokenizer::TK_BRACKET_CLOSE) {
-					tokenizer->advance();
-					break;
-				} else {
-					_set_error("Not a valid pattern");
-					return nullptr;
-				}
-			}
-		} break;
-		// bind
-		case GDScriptTokenizer::TK_PR_VAR: {
-			tokenizer->advance();
-			if (!tokenizer->is_token_literal()) {
-				_set_error("Expected identifier for binding variable name.");
-				return nullptr;
-			}
-			pattern->pt_type = GDScriptParser::PatternNode::PT_BIND;
-			pattern->bind = tokenizer->get_token_literal();
-			// Check if variable name is already used
-			BlockNode *bl = current_block;
-			while (bl) {
-				if (bl->variables.has(pattern->bind)) {
-					_set_error("Binding name of '" + pattern->bind.operator String() + "' is already declared in this scope.");
-					return nullptr;
-				}
-				bl = bl->parent_block;
-			}
-			// Create local variable for proper identifier detection later
-			LocalVarNode *lv = alloc_node<LocalVarNode>();
-			lv->name = pattern->bind;
-			current_block->variables.insert(lv->name, lv);
-			tokenizer->advance();
-		} break;
-		// dictionary
-		case GDScriptTokenizer::TK_CURLY_BRACKET_OPEN: {
-			tokenizer->advance();
-			pattern->pt_type = GDScriptParser::PatternNode::PT_DICTIONARY;
-			while (true) {
-				if (tokenizer->get_token() == GDScriptTokenizer::TK_CURLY_BRACKET_CLOSE) {
-					tokenizer->advance();
-					break;
-				}
-
-				if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD && tokenizer->get_token(1) == GDScriptTokenizer::TK_PERIOD) {
-					// match everything
-					tokenizer->advance(2);
-					PatternNode *sub_pattern = alloc_node<PatternNode>();
-					sub_pattern->pt_type = PatternNode::PT_IGNORE_REST;
-					pattern->array.push_back(sub_pattern);
-					if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA && tokenizer->get_token(1) == GDScriptTokenizer::TK_CURLY_BRACKET_CLOSE) {
-						tokenizer->advance(2);
-						break;
-					} else if (tokenizer->get_token() == GDScriptTokenizer::TK_CURLY_BRACKET_CLOSE) {
-						tokenizer->advance(1);
-						break;
-					} else {
-						_set_error("'..' pattern only allowed at the end of a dictionary pattern");
-						return nullptr;
-					}
-				}
-
-				Node *key = _parse_and_reduce_expression(pattern, p_static);
-				if (!key) {
-					_set_error("Not a valid key in pattern");
-					return nullptr;
-				}
-
-				if (key->type != GDScriptParser::Node::TYPE_CONSTANT) {
-					_set_error("Not a constant expression as key");
-					return nullptr;
-				}
-
-				if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) {
-					tokenizer->advance();
-
-					PatternNode *value = _parse_pattern(p_static);
-					if (!value) {
-						_set_error("Expected pattern in dictionary value");
-						return nullptr;
-					}
-
-					pattern->dictionary.insert(static_cast<ConstantNode *>(key), value);
-				} else {
-					pattern->dictionary.insert(static_cast<ConstantNode *>(key), nullptr);
-				}
-
-				if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
-					tokenizer->advance();
-					continue;
-				} else if (tokenizer->get_token() == GDScriptTokenizer::TK_CURLY_BRACKET_CLOSE) {
-					tokenizer->advance();
-					break;
-				} else {
-					_set_error("Not a valid pattern");
-					return nullptr;
-				}
-			}
-		} break;
-		case GDScriptTokenizer::TK_WILDCARD: {
-			tokenizer->advance();
-			pattern->pt_type = PatternNode::PT_WILDCARD;
-		} break;
-		// all the constants like strings and numbers
-		default: {
-			Node *value = _parse_and_reduce_expression(pattern, p_static);
-			if (!value) {
-				_set_error("Expect constant expression or variables in a pattern");
-				return nullptr;
-			}
-
-			if (value->type == Node::TYPE_OPERATOR) {
-				// Maybe it's SomeEnum.VALUE
-				Node *current_value = value;
-
-				while (current_value->type == Node::TYPE_OPERATOR) {
-					OperatorNode *op_node = static_cast<OperatorNode *>(current_value);
-
-					if (op_node->op != OperatorNode::OP_INDEX_NAMED) {
-						_set_error("Invalid operator in pattern. Only index (`A.B`) is allowed");
-						return nullptr;
-					}
-					current_value = op_node->arguments[0];
-				}
-
-				if (current_value->type != Node::TYPE_IDENTIFIER) {
-					_set_error("Only constant expression or variables allowed in a pattern");
-					return nullptr;
-				}
-
-			} else if (value->type != Node::TYPE_IDENTIFIER && value->type != Node::TYPE_CONSTANT) {
-				_set_error("Only constant expressions or variables allowed in a pattern");
-				return nullptr;
-			}
-
-			pattern->pt_type = PatternNode::PT_CONSTANT;
-			pattern->constant = value;
-		} break;
-	}
-
-	return pattern;
-}
-
-void GDScriptParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBranchNode *> &p_branches, bool p_static) {
-	IndentLevel current_level = indent_level.back()->get();
-
-	p_block->has_return = true;
-
-	bool catch_all_appeared = false;
-
-	while (true) {
-		while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE && _parse_newline()) {
-			;
-		}
-
-		// GDScriptTokenizer::Token token = tokenizer->get_token();
-		if (error_set) {
-			return;
-		}
-
-		if (current_level.indent > indent_level.back()->get().indent) {
-			break; // go back a level
-		}
-
-		pending_newline = -1;
-
-		PatternBranchNode *branch = alloc_node<PatternBranchNode>();
-		branch->body = alloc_node<BlockNode>();
-		branch->body->parent_block = p_block;
-		p_block->sub_blocks.push_back(branch->body);
-		current_block = branch->body;
-
-		branch->patterns.push_back(_parse_pattern(p_static));
-		if (!branch->patterns[0]) {
-			break;
-		}
-
-		bool has_binding = branch->patterns[0]->pt_type == PatternNode::PT_BIND;
-		bool catch_all = has_binding || branch->patterns[0]->pt_type == PatternNode::PT_WILDCARD;
-
-#ifdef DEBUG_ENABLED
-		// Branches after a wildcard or binding are unreachable
-		if (catch_all_appeared && !current_function->has_unreachable_code) {
-			_add_warning(GDScriptWarning::UNREACHABLE_CODE, -1, current_function->name.operator String());
-			current_function->has_unreachable_code = true;
-		}
-#endif
-
-		while (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
-			tokenizer->advance();
-			branch->patterns.push_back(_parse_pattern(p_static));
-			if (!branch->patterns[branch->patterns.size() - 1]) {
-				return;
-			}
-
-			PatternNode::PatternType pt = branch->patterns[branch->patterns.size() - 1]->pt_type;
-
-			if (pt == PatternNode::PT_BIND) {
-				_set_error("Cannot use bindings with multipattern.");
-				return;
-			}
-
-			catch_all = catch_all || pt == PatternNode::PT_WILDCARD;
-		}
-
-		catch_all_appeared = catch_all_appeared || catch_all;
-
-		if (!_enter_indent_block()) {
-			_set_error("Expected block in pattern branch");
-			return;
-		}
-
-		_parse_block(branch->body, p_static);
-
-		current_block = p_block;
-
-		if (!branch->body->has_return) {
-			p_block->has_return = false;
-		}
-
-		p_branches.push_back(branch);
-	}
-
-	// Even if all branches return, there is possibility of default fallthrough
-	if (!catch_all_appeared) {
-		p_block->has_return = false;
-	}
-}
-
-void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_match, Node *&p_resulting_node, Map<StringName, Node *> &p_bindings) {
-	const DataType &to_match_type = p_node_to_match->get_datatype();
-
-	switch (p_pattern->pt_type) {
-		case PatternNode::PT_CONSTANT: {
-			DataType pattern_type = _reduce_node_type(p_pattern->constant);
-			if (error_set) {
-				return;
-			}
-
-			OperatorNode *type_comp = nullptr;
-
-			// static type check if possible
-			if (pattern_type.has_type && to_match_type.has_type) {
-				if (!_is_type_compatible(to_match_type, pattern_type) && !_is_type_compatible(pattern_type, to_match_type)) {
-					_set_error("The pattern type (" + pattern_type.to_string() + ") isn't compatible with the type of the value to match (" + to_match_type.to_string() + ").",
-							p_pattern->line);
-					return;
-				}
-			} else {
-				// runtime typecheck
-				BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>();
-				typeof_node->function = GDScriptFunctions::TYPE_OF;
-
-				OperatorNode *typeof_match_value = alloc_node<OperatorNode>();
-				typeof_match_value->op = OperatorNode::OP_CALL;
-				typeof_match_value->arguments.push_back(typeof_node);
-				typeof_match_value->arguments.push_back(p_node_to_match);
-
-				OperatorNode *typeof_pattern_value = alloc_node<OperatorNode>();
-				typeof_pattern_value->op = OperatorNode::OP_CALL;
-				typeof_pattern_value->arguments.push_back(typeof_node);
-				typeof_pattern_value->arguments.push_back(p_pattern->constant);
-
-				type_comp = alloc_node<OperatorNode>();
-				type_comp->op = OperatorNode::OP_EQUAL;
-				type_comp->arguments.push_back(typeof_match_value);
-				type_comp->arguments.push_back(typeof_pattern_value);
-			}
-
-			// compare the actual values
-			OperatorNode *value_comp = alloc_node<OperatorNode>();
-			value_comp->op = OperatorNode::OP_EQUAL;
-			value_comp->arguments.push_back(p_pattern->constant);
-			value_comp->arguments.push_back(p_node_to_match);
-
-			if (type_comp) {
-				OperatorNode *full_comparison = alloc_node<OperatorNode>();
-				full_comparison->op = OperatorNode::OP_AND;
-				full_comparison->arguments.push_back(type_comp);
-				full_comparison->arguments.push_back(value_comp);
-
-				p_resulting_node = full_comparison;
-			} else {
-				p_resulting_node = value_comp;
-			}
-
-		} break;
-		case PatternNode::PT_BIND: {
-			p_bindings[p_pattern->bind] = p_node_to_match;
-
-			// a bind always matches
-			ConstantNode *true_value = alloc_node<ConstantNode>();
-			true_value->value = Variant(true);
-			true_value->datatype = _type_from_variant(true_value->value);
-			p_resulting_node = true_value;
-		} break;
-		case PatternNode::PT_ARRAY: {
-			bool open_ended = false;
-
-			if (p_pattern->array.size() > 0) {
-				if (p_pattern->array[p_pattern->array.size() - 1]->pt_type == PatternNode::PT_IGNORE_REST) {
-					open_ended = true;
-				}
-			}
-
-			// typeof(value_to_match) == TYPE_ARRAY && value_to_match.size() >= length
-			// typeof(value_to_match) == TYPE_ARRAY && value_to_match.size() == length
-
-			{
-				OperatorNode *type_comp = nullptr;
-				// static type check if possible
-				if (to_match_type.has_type) {
-					// must be an array
-					if (to_match_type.kind != DataType::BUILTIN || to_match_type.builtin_type != Variant::ARRAY) {
-						_set_error("Cannot match an array pattern with a non-array expression.", p_pattern->line);
-						return;
-					}
-				} else {
-					// runtime typecheck
-					BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>();
-					typeof_node->function = GDScriptFunctions::TYPE_OF;
-
-					OperatorNode *typeof_match_value = alloc_node<OperatorNode>();
-					typeof_match_value->op = OperatorNode::OP_CALL;
-					typeof_match_value->arguments.push_back(typeof_node);
-					typeof_match_value->arguments.push_back(p_node_to_match);
-
-					IdentifierNode *typeof_array = alloc_node<IdentifierNode>();
-					typeof_array->name = "TYPE_ARRAY";
-
-					type_comp = alloc_node<OperatorNode>();
-					type_comp->op = OperatorNode::OP_EQUAL;
-					type_comp->arguments.push_back(typeof_match_value);
-					type_comp->arguments.push_back(typeof_array);
-				}
-
-				// size
-				ConstantNode *length = alloc_node<ConstantNode>();
-				length->value = Variant(open_ended ? p_pattern->array.size() - 1 : p_pattern->array.size());
-				length->datatype = _type_from_variant(length->value);
-
-				OperatorNode *call = alloc_node<OperatorNode>();
-				call->op = OperatorNode::OP_CALL;
-				call->arguments.push_back(p_node_to_match);
-
-				IdentifierNode *size = alloc_node<IdentifierNode>();
-				size->name = "size";
-				call->arguments.push_back(size);
-
-				OperatorNode *length_comparison = alloc_node<OperatorNode>();
-				length_comparison->op = open_ended ? OperatorNode::OP_GREATER_EQUAL : OperatorNode::OP_EQUAL;
-				length_comparison->arguments.push_back(call);
-				length_comparison->arguments.push_back(length);
-
-				if (type_comp) {
-					OperatorNode *type_and_length_comparison = alloc_node<OperatorNode>();
-					type_and_length_comparison->op = OperatorNode::OP_AND;
-					type_and_length_comparison->arguments.push_back(type_comp);
-					type_and_length_comparison->arguments.push_back(length_comparison);
-
-					p_resulting_node = type_and_length_comparison;
-				} else {
-					p_resulting_node = length_comparison;
-				}
-			}
-
-			for (int i = 0; i < p_pattern->array.size(); i++) {
-				PatternNode *pattern = p_pattern->array[i];
-
-				Node *condition = nullptr;
-
-				ConstantNode *index = alloc_node<ConstantNode>();
-				index->value = Variant(i);
-				index->datatype = _type_from_variant(index->value);
-
-				OperatorNode *indexed_value = alloc_node<OperatorNode>();
-				indexed_value->op = OperatorNode::OP_INDEX;
-				indexed_value->arguments.push_back(p_node_to_match);
-				indexed_value->arguments.push_back(index);
-
-				_generate_pattern(pattern, indexed_value, condition, p_bindings);
-
-				// concatenate all the patterns with &&
-				OperatorNode *and_node = alloc_node<OperatorNode>();
-				and_node->op = OperatorNode::OP_AND;
-				and_node->arguments.push_back(p_resulting_node);
-				and_node->arguments.push_back(condition);
-
-				p_resulting_node = and_node;
-			}
-
-		} break;
-		case PatternNode::PT_DICTIONARY: {
-			bool open_ended = false;
-
-			if (p_pattern->array.size() > 0) {
-				open_ended = true;
-			}
-
-			// typeof(value_to_match) == TYPE_DICTIONARY && value_to_match.size() >= length
-			// typeof(value_to_match) == TYPE_DICTIONARY && value_to_match.size() == length
-
-			{
-				OperatorNode *type_comp = nullptr;
-				// static type check if possible
-				if (to_match_type.has_type) {
-					// must be an dictionary
-					if (to_match_type.kind != DataType::BUILTIN || to_match_type.builtin_type != Variant::DICTIONARY) {
-						_set_error("Cannot match an dictionary pattern with a non-dictionary expression.", p_pattern->line);
-						return;
-					}
-				} else {
-					// runtime typecheck
-					BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>();
-					typeof_node->function = GDScriptFunctions::TYPE_OF;
-
-					OperatorNode *typeof_match_value = alloc_node<OperatorNode>();
-					typeof_match_value->op = OperatorNode::OP_CALL;
-					typeof_match_value->arguments.push_back(typeof_node);
-					typeof_match_value->arguments.push_back(p_node_to_match);
-
-					IdentifierNode *typeof_dictionary = alloc_node<IdentifierNode>();
-					typeof_dictionary->name = "TYPE_DICTIONARY";
-
-					type_comp = alloc_node<OperatorNode>();
-					type_comp->op = OperatorNode::OP_EQUAL;
-					type_comp->arguments.push_back(typeof_match_value);
-					type_comp->arguments.push_back(typeof_dictionary);
-				}
-
-				// size
-				ConstantNode *length = alloc_node<ConstantNode>();
-				length->value = Variant(open_ended ? p_pattern->dictionary.size() - 1 : p_pattern->dictionary.size());
-				length->datatype = _type_from_variant(length->value);
-
-				OperatorNode *call = alloc_node<OperatorNode>();
-				call->op = OperatorNode::OP_CALL;
-				call->arguments.push_back(p_node_to_match);
-
-				IdentifierNode *size = alloc_node<IdentifierNode>();
-				size->name = "size";
-				call->arguments.push_back(size);
-
-				OperatorNode *length_comparison = alloc_node<OperatorNode>();
-				length_comparison->op = open_ended ? OperatorNode::OP_GREATER_EQUAL : OperatorNode::OP_EQUAL;
-				length_comparison->arguments.push_back(call);
-				length_comparison->arguments.push_back(length);
-
-				if (type_comp) {
-					OperatorNode *type_and_length_comparison = alloc_node<OperatorNode>();
-					type_and_length_comparison->op = OperatorNode::OP_AND;
-					type_and_length_comparison->arguments.push_back(type_comp);
-					type_and_length_comparison->arguments.push_back(length_comparison);
-
-					p_resulting_node = type_and_length_comparison;
-				} else {
-					p_resulting_node = length_comparison;
-				}
-			}
-
-			for (Map<ConstantNode *, PatternNode *>::Element *e = p_pattern->dictionary.front(); e; e = e->next()) {
-				Node *condition = nullptr;
-
-				// check for has, then for pattern
-
-				IdentifierNode *has = alloc_node<IdentifierNode>();
-				has->name = "has";
-
-				OperatorNode *has_call = alloc_node<OperatorNode>();
-				has_call->op = OperatorNode::OP_CALL;
-				has_call->arguments.push_back(p_node_to_match);
-				has_call->arguments.push_back(has);
-				has_call->arguments.push_back(e->key());
-
-				if (e->value()) {
-					OperatorNode *indexed_value = alloc_node<OperatorNode>();
-					indexed_value->op = OperatorNode::OP_INDEX;
-					indexed_value->arguments.push_back(p_node_to_match);
-					indexed_value->arguments.push_back(e->key());
-
-					_generate_pattern(e->value(), indexed_value, condition, p_bindings);
-
-					OperatorNode *has_and_pattern = alloc_node<OperatorNode>();
-					has_and_pattern->op = OperatorNode::OP_AND;
-					has_and_pattern->arguments.push_back(has_call);
-					has_and_pattern->arguments.push_back(condition);
-
-					condition = has_and_pattern;
-
-				} else {
-					condition = has_call;
-				}
-
-				// concatenate all the patterns with &&
-				OperatorNode *and_node = alloc_node<OperatorNode>();
-				and_node->op = OperatorNode::OP_AND;
-				and_node->arguments.push_back(p_resulting_node);
-				and_node->arguments.push_back(condition);
-
-				p_resulting_node = and_node;
-			}
-
-		} break;
-		case PatternNode::PT_IGNORE_REST:
-		case PatternNode::PT_WILDCARD: {
-			// simply generate a `true`
-			ConstantNode *true_value = alloc_node<ConstantNode>();
-			true_value->value = Variant(true);
-			true_value->datatype = _type_from_variant(true_value->value);
-			p_resulting_node = true_value;
-		} break;
-		default: {
-		} break;
-	}
-}
-
-void GDScriptParser::_transform_match_statment(MatchNode *p_match_statement) {
-	IdentifierNode *id = alloc_node<IdentifierNode>();
-	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;
-	}
-
-	for (int i = 0; i < p_match_statement->branches.size(); i++) {
-		PatternBranchNode *branch = p_match_statement->branches[i];
-
-		MatchNode::CompiledPatternBranch compiled_branch;
-		compiled_branch.compiled_pattern = nullptr;
-
-		Map<StringName, Node *> binding;
-
-		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 = nullptr;
-			_generate_pattern(pattern, id, resulting_node, bindings);
-
-			if (!resulting_node) {
-				return;
-			}
-
-			if (!binding.empty() && !bindings.empty()) {
-				_set_error("Multipatterns can't contain bindings");
-				return;
-			} else {
-				binding = bindings;
-			}
-
-			// Result is always a boolean
-			DataType resulting_node_type;
-			resulting_node_type.has_type = true;
-			resulting_node_type.is_constant = true;
-			resulting_node_type.kind = DataType::BUILTIN;
-			resulting_node_type.builtin_type = Variant::BOOL;
-			resulting_node->set_datatype(resulting_node_type);
-
-			if (compiled_branch.compiled_pattern) {
-				OperatorNode *or_node = alloc_node<OperatorNode>();
-				or_node->op = OperatorNode::OP_OR;
-				or_node->arguments.push_back(compiled_branch.compiled_pattern);
-				or_node->arguments.push_back(resulting_node);
-
-				compiled_branch.compiled_pattern = or_node;
-			} else {
-				// single pattern | first one
-				compiled_branch.compiled_pattern = resulting_node;
-			}
-		}
-
-		// prepare the body ...hehe
-		for (Map<StringName, Node *>::Element *e = binding.front(); e; e = e->next()) {
-			if (!branch->body->variables.has(e->key())) {
-				_set_error("Parser bug: missing pattern bind variable.", branch->line);
-				ERR_FAIL();
-			}
-
-			LocalVarNode *local_var = branch->body->variables[e->key()];
-			local_var->assign = e->value();
-			local_var->set_datatype(local_var->assign->get_datatype());
-			local_var->assignments++;
-
-			IdentifierNode *id2 = alloc_node<IdentifierNode>();
-			id2->name = local_var->name;
-			id2->datatype = local_var->datatype;
-			id2->declared_block = branch->body;
-			id2->set_datatype(local_var->assign->get_datatype());
-
-			OperatorNode *op = alloc_node<OperatorNode>();
-			op->op = OperatorNode::OP_ASSIGN;
-			op->arguments.push_back(id2);
-			op->arguments.push_back(local_var->assign);
-			local_var->assign_op = op;
-
-			branch->body->statements.push_front(op);
-			branch->body->statements.push_front(local_var);
-		}
-
-		compiled_branch.body = branch->body;
-
-		p_match_statement->compiled_pattern_branches.push_back(compiled_branch);
-	}
-}
-
-void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
-	IndentLevel current_level = indent_level.back()->get();
-
-#ifdef DEBUG_ENABLED
-
-	pending_newline = -1; // reset for the new block
-
-	NewLineNode *nl = alloc_node<NewLineNode>();
-
-	nl->line = tokenizer->get_token_line();
-	p_block->statements.push_back(nl);
-#endif
-
-	bool is_first_line = true;
-
-	while (true) {
-		if (!is_first_line && indent_level.back()->prev() && indent_level.back()->prev()->get().indent == current_level.indent) {
-			if (indent_level.back()->prev()->get().is_mixed(current_level)) {
-				_set_error("Mixed tabs and spaces in indentation.");
-				return;
-			}
-			// pythonic single-line expression, don't parse future lines
-			indent_level.pop_back();
-			p_block->end_line = tokenizer->get_token_line();
-			return;
-		}
-		is_first_line = false;
-
-		GDScriptTokenizer::Token token = tokenizer->get_token();
-		if (error_set) {
-			return;
-		}
-
-		if (current_level.indent > indent_level.back()->get().indent) {
-			p_block->end_line = tokenizer->get_token_line();
-			return; //go back a level
-		}
-
-		if (pending_newline != -1) {
-			NewLineNode *nl2 = alloc_node<NewLineNode>();
-			nl2->line = pending_newline;
-			p_block->statements.push_back(nl2);
-			pending_newline = -1;
-		}
-
-#ifdef DEBUG_ENABLED
-		switch (token) {
-			case GDScriptTokenizer::TK_EOF:
-			case GDScriptTokenizer::TK_ERROR:
-			case GDScriptTokenizer::TK_NEWLINE:
-			case GDScriptTokenizer::TK_CF_PASS: {
-				// will check later
-			} break;
-			default: {
-				if (p_block->has_return && !current_function->has_unreachable_code) {
-					_add_warning(GDScriptWarning::UNREACHABLE_CODE, -1, current_function->name.operator String());
-					current_function->has_unreachable_code = true;
-				}
-			} break;
-		}
-#endif // DEBUG_ENABLED
-		switch (token) {
-			case GDScriptTokenizer::TK_EOF: {
-				p_block->end_line = tokenizer->get_token_line();
-				return; // End of file!
-			} break;
-			case GDScriptTokenizer::TK_ERROR: {
-				return;
-			} break;
-			case GDScriptTokenizer::TK_NEWLINE: {
-				int line = tokenizer->get_token_line();
-
-				if (!_parse_newline()) {
-					if (!error_set) {
-						p_block->end_line = tokenizer->get_token_line();
-						pending_newline = p_block->end_line;
-					}
-					return;
-				}
-
-				_mark_line_as_safe(line);
-				NewLineNode *nl2 = alloc_node<NewLineNode>();
-				nl2->line = line;
-				p_block->statements.push_back(nl2);
-
-			} break;
-			case GDScriptTokenizer::TK_CF_PASS: {
-				if (tokenizer->get_token(1) != GDScriptTokenizer::TK_SEMICOLON && tokenizer->get_token(1) != GDScriptTokenizer::TK_NEWLINE && tokenizer->get_token(1) != GDScriptTokenizer::TK_EOF) {
-					_set_error("Expected \";\" or a line break.");
-					return;
-				}
-				_mark_line_as_safe(tokenizer->get_token_line());
-				tokenizer->advance();
-				if (tokenizer->get_token() == GDScriptTokenizer::TK_SEMICOLON) {
-					// Ignore semicolon after 'pass'.
-					tokenizer->advance();
-				}
-			} break;
-			case GDScriptTokenizer::TK_PR_VAR: {
-				// Variable declaration and (eventual) initialization.
-
-				tokenizer->advance();
-				int var_line = tokenizer->get_token_line();
-				if (!tokenizer->is_token_literal(0, true)) {
-					_set_error("Expected an identifier for the local variable name.");
-					return;
-				}
-				StringName n = tokenizer->get_token_literal();
-				tokenizer->advance();
-				if (current_function) {
-					for (int i = 0; i < current_function->arguments.size(); i++) {
-						if (n == current_function->arguments[i]) {
-							_set_error("Variable \"" + String(n) + "\" already defined in the scope (at line " + itos(current_function->line) + ").");
-							return;
-						}
-					}
-				}
-				BlockNode *check_block = p_block;
-				while (check_block) {
-					if (check_block->variables.has(n)) {
-						_set_error("Variable \"" + String(n) + "\" already defined in the scope (at line " + itos(check_block->variables[n]->line) + ").");
-						return;
-					}
-					check_block = check_block->parent_block;
-				}
-
-				//must know when the local variable is declared
-				LocalVarNode *lv = alloc_node<LocalVarNode>();
-				lv->name = n;
-				lv->line = var_line;
-				p_block->statements.push_back(lv);
-
-				Node *assigned = nullptr;
-
-				if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) {
-					if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) {
-						lv->datatype = DataType();
-#ifdef DEBUG_ENABLED
-						lv->datatype.infer_type = true;
-#endif
-						tokenizer->advance();
-					} else if (!_parse_type(lv->datatype)) {
-						_set_error("Expected a type for the variable.");
-						return;
-					}
-				}
-
-				if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) {
-					tokenizer->advance();
-					Node *subexpr = _parse_and_reduce_expression(p_block, p_static);
-					if (!subexpr) {
-						if (_recover_from_completion()) {
-							break;
-						}
-						return;
-					}
-
-					lv->assignments++;
-					assigned = subexpr;
-				} else {
-					assigned = _get_default_value_for_type(lv->datatype, var_line);
-				}
-				//must be added later, to avoid self-referencing.
-				p_block->variables.insert(n, lv);
-
-				IdentifierNode *id = alloc_node<IdentifierNode>();
-				id->name = n;
-				id->declared_block = p_block;
-				id->line = var_line;
-
-				OperatorNode *op = alloc_node<OperatorNode>();
-				op->op = OperatorNode::OP_ASSIGN;
-				op->arguments.push_back(id);
-				op->arguments.push_back(assigned);
-				op->line = var_line;
-				p_block->statements.push_back(op);
-				lv->assign_op = op;
-				lv->assign = assigned;
-
-				if (!_end_statement()) {
-					_set_end_statement_error("var");
-					return;
-				}
-
-			} break;
-			case GDScriptTokenizer::TK_CF_IF: {
-				tokenizer->advance();
-
-				Node *condition = _parse_and_reduce_expression(p_block, p_static);
-				if (!condition) {
-					if (_recover_from_completion()) {
-						break;
-					}
-					return;
-				}
-
-				ControlFlowNode *cf_if = alloc_node<ControlFlowNode>();
-
-				cf_if->cf_type = ControlFlowNode::CF_IF;
-				cf_if->arguments.push_back(condition);
-
-				cf_if->body = alloc_node<BlockNode>();
-				cf_if->body->parent_block = p_block;
-				cf_if->body->if_condition = condition; //helps code completion
-
-				p_block->sub_blocks.push_back(cf_if->body);
-
-				if (!_enter_indent_block(cf_if->body)) {
-					_set_error("Expected an indented block after \"if\".");
-					p_block->end_line = tokenizer->get_token_line();
-					return;
-				}
-
-				current_block = cf_if->body;
-				_parse_block(cf_if->body, p_static);
-				current_block = p_block;
-
-				if (error_set) {
-					return;
-				}
-				p_block->statements.push_back(cf_if);
-
-				bool all_have_return = cf_if->body->has_return;
-				bool have_else = false;
-
-				while (true) {
-					while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE && _parse_newline()) {
-						;
-					}
-
-					if (indent_level.back()->get().indent < current_level.indent) { //not at current indent level
-						p_block->end_line = tokenizer->get_token_line();
-						return;
-					}
-
-					if (tokenizer->get_token() == GDScriptTokenizer::TK_CF_ELIF) {
-						if (indent_level.back()->get().indent > current_level.indent) {
-							_set_error("Invalid indentation.");
-							return;
-						}
-
-						tokenizer->advance();
-
-						cf_if->body_else = alloc_node<BlockNode>();
-						cf_if->body_else->parent_block = p_block;
-						p_block->sub_blocks.push_back(cf_if->body_else);
-
-						ControlFlowNode *cf_else = alloc_node<ControlFlowNode>();
-						cf_else->cf_type = ControlFlowNode::CF_IF;
-
-						//condition
-						Node *condition2 = _parse_and_reduce_expression(p_block, p_static);
-						if (!condition2) {
-							if (_recover_from_completion()) {
-								break;
-							}
-							return;
-						}
-						cf_else->arguments.push_back(condition2);
-						cf_else->cf_type = ControlFlowNode::CF_IF;
-
-						cf_if->body_else->statements.push_back(cf_else);
-						cf_if = cf_else;
-						cf_if->body = alloc_node<BlockNode>();
-						cf_if->body->parent_block = p_block;
-						p_block->sub_blocks.push_back(cf_if->body);
-
-						if (!_enter_indent_block(cf_if->body)) {
-							_set_error("Expected an indented block after \"elif\".");
-							p_block->end_line = tokenizer->get_token_line();
-							return;
-						}
-
-						current_block = cf_else->body;
-						_parse_block(cf_else->body, p_static);
-						current_block = p_block;
-						if (error_set) {
-							return;
-						}
-
-						all_have_return = all_have_return && cf_else->body->has_return;
-
-					} else if (tokenizer->get_token() == GDScriptTokenizer::TK_CF_ELSE) {
-						if (indent_level.back()->get().indent > current_level.indent) {
-							_set_error("Invalid indentation.");
-							return;
-						}
-
-						tokenizer->advance();
-						cf_if->body_else = alloc_node<BlockNode>();
-						cf_if->body_else->parent_block = p_block;
-						p_block->sub_blocks.push_back(cf_if->body_else);
-
-						if (!_enter_indent_block(cf_if->body_else)) {
-							_set_error("Expected an indented block after \"else\".");
-							p_block->end_line = tokenizer->get_token_line();
-							return;
-						}
-						current_block = cf_if->body_else;
-						_parse_block(cf_if->body_else, p_static);
-						current_block = p_block;
-						if (error_set) {
-							return;
-						}
-
-						all_have_return = all_have_return && cf_if->body_else->has_return;
-						have_else = true;
-
-						break; //after else, exit
-
-					} else {
-						break;
-					}
-				}
-
-				cf_if->body->has_return = all_have_return;
-				// If there's no else block, path out of the if might not have a return
-				p_block->has_return = all_have_return && have_else;
-
-			} break;
-			case GDScriptTokenizer::TK_CF_WHILE: {
-				tokenizer->advance();
-				Node *condition2 = _parse_and_reduce_expression(p_block, p_static);
-				if (!condition2) {
-					if (_recover_from_completion()) {
-						break;
-					}
-					return;
-				}
-
-				ControlFlowNode *cf_while = alloc_node<ControlFlowNode>();
-
-				cf_while->cf_type = ControlFlowNode::CF_WHILE;
-				cf_while->arguments.push_back(condition2);
-
-				cf_while->body = alloc_node<BlockNode>();
-				cf_while->body->parent_block = p_block;
-				cf_while->body->can_break = true;
-				cf_while->body->can_continue = true;
-				p_block->sub_blocks.push_back(cf_while->body);
-
-				if (!_enter_indent_block(cf_while->body)) {
-					_set_error("Expected an indented block after \"while\".");
-					p_block->end_line = tokenizer->get_token_line();
-					return;
-				}
-
-				current_block = cf_while->body;
-				_parse_block(cf_while->body, p_static);
-				current_block = p_block;
-				if (error_set) {
-					return;
-				}
-				p_block->statements.push_back(cf_while);
-			} break;
-			case GDScriptTokenizer::TK_CF_FOR: {
-				tokenizer->advance();
-
-				if (!tokenizer->is_token_literal(0, true)) {
-					_set_error("Identifier expected after \"for\".");
-				}
-
-				IdentifierNode *id = alloc_node<IdentifierNode>();
-				id->name = tokenizer->get_token_identifier();
-#ifdef DEBUG_ENABLED
-				for (int j = 0; j < current_class->variables.size(); j++) {
-					if (current_class->variables[j].identifier == id->name) {
-						_add_warning(GDScriptWarning::SHADOWED_VARIABLE, id->line, id->name, itos(current_class->variables[j].line));
-					}
-				}
-#endif // DEBUG_ENABLED
-
-				BlockNode *check_block = p_block;
-				while (check_block) {
-					if (check_block->variables.has(id->name)) {
-						_set_error("Variable \"" + String(id->name) + "\" already defined in the scope (at line " + itos(check_block->variables[id->name]->line) + ").");
-						return;
-					}
-					check_block = check_block->parent_block;
-				}
-
-				tokenizer->advance();
-
-				if (tokenizer->get_token() != GDScriptTokenizer::TK_OP_IN) {
-					_set_error("\"in\" expected after identifier.");
-					return;
-				}
-
-				tokenizer->advance();
-
-				Node *container = _parse_and_reduce_expression(p_block, p_static);
-				if (!container) {
-					if (_recover_from_completion()) {
-						break;
-					}
-					return;
-				}
-
-				DataType iter_type;
-
-				if (container->type == Node::TYPE_OPERATOR) {
-					OperatorNode *op = static_cast<OperatorNode *>(container);
-					if (op->op == OperatorNode::OP_CALL && op->arguments[0]->type == Node::TYPE_BUILT_IN_FUNCTION && static_cast<BuiltInFunctionNode *>(op->arguments[0])->function == GDScriptFunctions::GEN_RANGE) {
-						//iterating a range, so see if range() can be optimized without allocating memory, by replacing it by vectors (which can work as iterable too!)
-
-						Vector<Node *> args;
-						Vector<double> constants;
-
-						bool constant = true;
-
-						for (int i = 1; i < op->arguments.size(); i++) {
-							args.push_back(op->arguments[i]);
-							if (op->arguments[i]->type == Node::TYPE_CONSTANT) {
-								ConstantNode *c = static_cast<ConstantNode *>(op->arguments[i]);
-								if (c->value.get_type() == Variant::FLOAT || c->value.get_type() == Variant::INT) {
-									constants.push_back(c->value);
-								} else {
-									constant = false;
-								}
-							} else {
-								constant = false;
-							}
-						}
-
-						if (args.size() > 0 && args.size() < 4) {
-							if (constant) {
-								ConstantNode *cn = alloc_node<ConstantNode>();
-								switch (args.size()) {
-									case 1:
-										cn->value = (int64_t)constants[0];
-										break;
-									case 2:
-										cn->value = Vector2i(constants[0], constants[1]);
-										break;
-									case 3:
-										cn->value = Vector3i(constants[0], constants[1], constants[2]);
-										break;
-								}
-								cn->datatype = _type_from_variant(cn->value);
-								container = cn;
-							} else {
-								OperatorNode *on = alloc_node<OperatorNode>();
-								on->op = OperatorNode::OP_CALL;
-
-								TypeNode *tn = alloc_node<TypeNode>();
-								on->arguments.push_back(tn);
-
-								switch (args.size()) {
-									case 1:
-										tn->vtype = Variant::INT;
-										break;
-									case 2:
-										tn->vtype = Variant::VECTOR2I;
-										break;
-									case 3:
-										tn->vtype = Variant::VECTOR3I;
-										break;
-								}
-
-								for (int i = 0; i < args.size(); i++) {
-									on->arguments.push_back(args[i]);
-								}
-
-								container = on;
-							}
-						}
-
-						iter_type.has_type = true;
-						iter_type.kind = DataType::BUILTIN;
-						iter_type.builtin_type = Variant::INT;
-					}
-				}
-
-				ControlFlowNode *cf_for = alloc_node<ControlFlowNode>();
-
-				cf_for->cf_type = ControlFlowNode::CF_FOR;
-				cf_for->arguments.push_back(id);
-				cf_for->arguments.push_back(container);
-
-				cf_for->body = alloc_node<BlockNode>();
-				cf_for->body->parent_block = p_block;
-				cf_for->body->can_break = true;
-				cf_for->body->can_continue = true;
-				p_block->sub_blocks.push_back(cf_for->body);
-
-				if (!_enter_indent_block(cf_for->body)) {
-					_set_error("Expected indented block after \"for\".");
-					p_block->end_line = tokenizer->get_token_line();
-					return;
-				}
-
-				current_block = cf_for->body;
-
-				// this is for checking variable for redefining
-				// inside this _parse_block
-				LocalVarNode *lv = alloc_node<LocalVarNode>();
-				lv->name = id->name;
-				lv->line = id->line;
-				lv->assignments++;
-				id->declared_block = cf_for->body;
-				lv->set_datatype(iter_type);
-				id->set_datatype(iter_type);
-				cf_for->body->variables.insert(id->name, lv);
-				_parse_block(cf_for->body, p_static);
-				current_block = p_block;
-
-				if (error_set) {
-					return;
-				}
-				p_block->statements.push_back(cf_for);
-			} break;
-			case GDScriptTokenizer::TK_CF_CONTINUE: {
-				BlockNode *upper_block = p_block;
-				bool is_continue_valid = false;
-				while (upper_block) {
-					if (upper_block->can_continue) {
-						is_continue_valid = true;
-						break;
-					}
-					upper_block = upper_block->parent_block;
-				}
-
-				if (!is_continue_valid) {
-					_set_error("Unexpected keyword \"continue\" outside a loop.");
-					return;
-				}
-
-				_mark_line_as_safe(tokenizer->get_token_line());
-				tokenizer->advance();
-				ControlFlowNode *cf_continue = alloc_node<ControlFlowNode>();
-				cf_continue->cf_type = ControlFlowNode::CF_CONTINUE;
-				p_block->statements.push_back(cf_continue);
-				if (!_end_statement()) {
-					_set_end_statement_error("continue");
-					return;
-				}
-			} break;
-			case GDScriptTokenizer::TK_CF_BREAK: {
-				BlockNode *upper_block = p_block;
-				bool is_break_valid = false;
-				while (upper_block) {
-					if (upper_block->can_break) {
-						is_break_valid = true;
-						break;
-					}
-					upper_block = upper_block->parent_block;
-				}
-
-				if (!is_break_valid) {
-					_set_error("Unexpected keyword \"break\" outside a loop.");
-					return;
-				}
-
-				_mark_line_as_safe(tokenizer->get_token_line());
-				tokenizer->advance();
-				ControlFlowNode *cf_break = alloc_node<ControlFlowNode>();
-				cf_break->cf_type = ControlFlowNode::CF_BREAK;
-				p_block->statements.push_back(cf_break);
-				if (!_end_statement()) {
-					_set_end_statement_error("break");
-					return;
-				}
-			} break;
-			case GDScriptTokenizer::TK_CF_RETURN: {
-				tokenizer->advance();
-				ControlFlowNode *cf_return = alloc_node<ControlFlowNode>();
-				cf_return->cf_type = ControlFlowNode::CF_RETURN;
-				cf_return->line = tokenizer->get_token_line(-1);
-
-				if (tokenizer->get_token() == GDScriptTokenizer::TK_SEMICOLON || tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE || tokenizer->get_token() == GDScriptTokenizer::TK_EOF) {
-					//expect end of statement
-					p_block->statements.push_back(cf_return);
-					if (!_end_statement()) {
-						return;
-					}
-				} else {
-					//expect expression
-					Node *retexpr = _parse_and_reduce_expression(p_block, p_static);
-					if (!retexpr) {
-						if (_recover_from_completion()) {
-							break;
-						}
-						return;
-					}
-					cf_return->arguments.push_back(retexpr);
-					p_block->statements.push_back(cf_return);
-					if (!_end_statement()) {
-						_set_end_statement_error("return");
-						return;
-					}
-				}
-				p_block->has_return = true;
-
-			} break;
-			case GDScriptTokenizer::TK_CF_MATCH: {
-				tokenizer->advance();
-
-				MatchNode *match_node = alloc_node<MatchNode>();
-
-				Node *val_to_match = _parse_and_reduce_expression(p_block, p_static);
-
-				if (!val_to_match) {
-					if (_recover_from_completion()) {
-						break;
-					}
-					return;
-				}
-
-				match_node->val_to_match = val_to_match;
-
-				if (!_enter_indent_block()) {
-					_set_error("Expected indented pattern matching block after \"match\".");
-					return;
-				}
-
-				BlockNode *compiled_branches = alloc_node<BlockNode>();
-				compiled_branches->parent_block = p_block;
-				compiled_branches->parent_class = p_block->parent_class;
-				compiled_branches->can_continue = true;
-
-				p_block->sub_blocks.push_back(compiled_branches);
-
-				_parse_pattern_block(compiled_branches, match_node->branches, p_static);
-
-				if (error_set) {
-					return;
-				}
-
-				ControlFlowNode *match_cf_node = alloc_node<ControlFlowNode>();
-				match_cf_node->cf_type = ControlFlowNode::CF_MATCH;
-				match_cf_node->match = match_node;
-				match_cf_node->body = compiled_branches;
-
-				p_block->has_return = p_block->has_return || compiled_branches->has_return;
-				p_block->statements.push_back(match_cf_node);
-
-				_end_statement();
-			} break;
-			case GDScriptTokenizer::TK_PR_ASSERT: {
-				tokenizer->advance();
-
-				if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
-					_set_error("Expected '(' after assert");
-					return;
-				}
-
-				int assert_line = tokenizer->get_token_line();
-
-				tokenizer->advance();
-
-				Vector<Node *> args;
-				const bool result = _parse_arguments(p_block, args, p_static);
-				if (!result) {
-					return;
-				}
-
-				if (args.empty() || args.size() > 2) {
-					_set_error("Wrong number of arguments, expected 1 or 2", assert_line);
-					return;
-				}
-
-				AssertNode *an = alloc_node<AssertNode>();
-				an->condition = _reduce_expression(args[0], p_static);
-				an->line = assert_line;
-
-				if (args.size() == 2) {
-					an->message = _reduce_expression(args[1], p_static);
-				} else {
-					ConstantNode *message_node = alloc_node<ConstantNode>();
-					message_node->value = String();
-					message_node->datatype = _type_from_variant(message_node->value);
-					an->message = message_node;
-				}
-
-				p_block->statements.push_back(an);
-
-				if (!_end_statement()) {
-					_set_end_statement_error("assert");
-					return;
-				}
-			} break;
-			case GDScriptTokenizer::TK_PR_BREAKPOINT: {
-				tokenizer->advance();
-				BreakpointNode *bn = alloc_node<BreakpointNode>();
-				p_block->statements.push_back(bn);
-
-				if (!_end_statement()) {
-					_set_end_statement_error("breakpoint");
-					return;
-				}
-			} break;
-			default: {
-				Node *expression = _parse_and_reduce_expression(p_block, p_static, false, true);
-				if (!expression) {
-					if (_recover_from_completion()) {
-						break;
-					}
-					return;
-				}
-				p_block->statements.push_back(expression);
-				if (!_end_statement()) {
-					// Attempt to guess a better error message if the user "retypes" a variable
-					if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON && tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) {
-						_set_error("Unexpected ':=', use '=' instead. Expected end of statement after expression.");
-					} else {
-						_set_error(vformat("Expected end of statement after expression, got %s instead.", tokenizer->get_token_name(tokenizer->get_token())));
-					}
-					return;
-				}
-
-			} break;
-		}
-	}
-}
-
-bool GDScriptParser::_parse_newline() {
-	if (tokenizer->get_token(1) != GDScriptTokenizer::TK_EOF && tokenizer->get_token(1) != GDScriptTokenizer::TK_NEWLINE) {
-		IndentLevel current_level = indent_level.back()->get();
-		int indent = tokenizer->get_token_line_indent();
-		int tabs = tokenizer->get_token_line_tab_indent();
-		IndentLevel new_level(indent, tabs);
-
-		if (new_level.is_mixed(current_level)) {
-			_set_error("Mixed tabs and spaces in indentation.");
-			return false;
-		}
-
-		if (indent > current_level.indent) {
-			_set_error("Unexpected indentation.");
-			return false;
-		}
-
-		if (indent < current_level.indent) {
-			while (indent < current_level.indent) {
-				//exit block
-				if (indent_level.size() == 1) {
-					_set_error("Invalid indentation. Bug?");
-					return false;
-				}
-
-				indent_level.pop_back();
-
-				if (indent_level.back()->get().indent < indent) {
-					_set_error("Unindent does not match any outer indentation level.");
-					return false;
-				}
-
-				if (indent_level.back()->get().is_mixed(current_level)) {
-					_set_error("Mixed tabs and spaces in indentation.");
-					return false;
-				}
-
-				current_level = indent_level.back()->get();
-			}
-
-			tokenizer->advance();
-			return false;
-		}
-	}
-
-	tokenizer->advance();
-	return true;
-}
-
-void GDScriptParser::_parse_extends(ClassNode *p_class) {
-	if (p_class->extends_used) {
-		_set_error("\"extends\" can only be present once per script.");
-		return;
-	}
-
-	if (!p_class->constant_expressions.empty() || !p_class->subclasses.empty() || !p_class->functions.empty() || !p_class->variables.empty()) {
-		_set_error("\"extends\" must be used before anything else.");
-		return;
-	}
-
-	p_class->extends_used = true;
-
-	tokenizer->advance();
-
-	if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_TYPE && tokenizer->get_token_type() == Variant::OBJECT) {
-		p_class->extends_class.push_back(Variant::get_type_name(Variant::OBJECT));
-		tokenizer->advance();
-		return;
-	}
-
-	// see if inheritance happens from a file
-	if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT) {
-		Variant constant = tokenizer->get_token_constant();
-		if (constant.get_type() != Variant::STRING) {
-			_set_error("\"extends\" constant must be a string.");
-			return;
-		}
-
-		p_class->extends_file = constant;
-		tokenizer->advance();
-
-		// Add parent script as a dependency
-		String parent = constant;
-		if (parent.is_rel_path()) {
-			parent = base_path.plus_file(parent).simplify_path();
-		}
-		dependencies.push_back(parent);
-
-		if (tokenizer->get_token() != GDScriptTokenizer::TK_PERIOD) {
-			return;
-		} else {
-			tokenizer->advance();
-		}
-	}
-
-	while (true) {
-		switch (tokenizer->get_token()) {
-			case GDScriptTokenizer::TK_IDENTIFIER: {
-				StringName identifier = tokenizer->get_token_identifier();
-				p_class->extends_class.push_back(identifier);
-			} break;
-
-			case GDScriptTokenizer::TK_PERIOD:
-				break;
-
-			default: {
-				_set_error("Invalid \"extends\" syntax, expected string constant (path) and/or identifier (parent class).");
-				return;
-			}
-		}
-
-		tokenizer->advance(1);
-
-		switch (tokenizer->get_token()) {
-			case GDScriptTokenizer::TK_IDENTIFIER:
-			case GDScriptTokenizer::TK_PERIOD:
-				continue;
-
-			default:
-				return;
-		}
-	}
-}
-
-void GDScriptParser::_parse_class(ClassNode *p_class) {
-	IndentLevel current_level = indent_level.back()->get();
-
-	while (true) {
-		GDScriptTokenizer::Token token = tokenizer->get_token();
-		if (error_set) {
-			return;
-		}
-
-		if (current_level.indent > indent_level.back()->get().indent) {
-			p_class->end_line = tokenizer->get_token_line();
-			return; //go back a level
-		}
-
-		switch (token) {
-			case GDScriptTokenizer::TK_CURSOR: {
-				tokenizer->advance();
-			} break;
-			case GDScriptTokenizer::TK_EOF: {
-				p_class->end_line = tokenizer->get_token_line();
-				return; // End of file!
-			} break;
-			case GDScriptTokenizer::TK_ERROR: {
-				return; // Go back.
-			} break;
-			case GDScriptTokenizer::TK_NEWLINE: {
-				if (!_parse_newline()) {
-					if (!error_set) {
-						p_class->end_line = tokenizer->get_token_line();
-					}
-					return;
-				}
-			} break;
-			case GDScriptTokenizer::TK_PR_EXTENDS: {
-				_mark_line_as_safe(tokenizer->get_token_line());
-				_parse_extends(p_class);
-				if (error_set) {
-					return;
-				}
-				if (!_end_statement()) {
-					_set_end_statement_error("extends");
-					return;
-				}
-
-			} break;
-			case GDScriptTokenizer::TK_PR_CLASS_NAME: {
-				_mark_line_as_safe(tokenizer->get_token_line());
-				if (p_class->owner) {
-					_set_error("\"class_name\" is only valid for the main class namespace.");
-					return;
-				}
-				if (self_path.begins_with("res://") && self_path.find("::") != -1) {
-					_set_error("\"class_name\" isn't allowed in built-in scripts.");
-					return;
-				}
-				if (tokenizer->get_token(1) != GDScriptTokenizer::TK_IDENTIFIER) {
-					_set_error("\"class_name\" syntax: \"class_name <UniqueName>\"");
-					return;
-				}
-				if (p_class->classname_used) {
-					_set_error("\"class_name\" can only be present once per script.");
-					return;
-				}
-
-				p_class->classname_used = true;
-
-				p_class->name = tokenizer->get_token_identifier(1);
-
-				if (self_path != String() && ScriptServer::is_global_class(p_class->name) && ScriptServer::get_global_class_path(p_class->name) != self_path) {
-					_set_error("Unique global class \"" + p_class->name + "\" already exists at path: " + ScriptServer::get_global_class_path(p_class->name));
-					return;
-				}
-
-				if (ClassDB::class_exists(p_class->name)) {
-					_set_error("The class \"" + p_class->name + "\" shadows a native class.");
-					return;
-				}
-
-				if (p_class->classname_used && ProjectSettings::get_singleton()->has_setting("autoload/" + p_class->name)) {
-					const String autoload_path = ProjectSettings::get_singleton()->get_setting("autoload/" + p_class->name);
-					if (autoload_path.begins_with("*")) {
-						// It's a singleton, and not just a regular AutoLoad script.
-						_set_error("The class \"" + p_class->name + "\" conflicts with the AutoLoad singleton of the same name, and is therefore redundant. Remove the class_name declaration to fix this error.");
-					}
-					return;
-				}
-
-				tokenizer->advance(2);
-
-				if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
-					tokenizer->advance();
-
-					if ((tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT && tokenizer->get_token_constant().get_type() == Variant::STRING)) {
-#ifdef TOOLS_ENABLED
-						if (Engine::get_singleton()->is_editor_hint()) {
-							Variant constant = tokenizer->get_token_constant();
-							String icon_path = constant.operator String();
-
-							String abs_icon_path = icon_path.is_rel_path() ? self_path.get_base_dir().plus_file(icon_path).simplify_path() : icon_path;
-							if (!FileAccess::exists(abs_icon_path)) {
-								_set_error("No class icon found at: " + abs_icon_path);
-								return;
-							}
-
-							p_class->icon_path = icon_path;
-						}
-#endif
-
-						tokenizer->advance();
-					} else {
-						_set_error("The optional parameter after \"class_name\" must be a string constant file path to an icon.");
-						return;
-					}
-
-				} else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT) {
-					_set_error("The class icon must be separated by a comma.");
-					return;
-				}
-
-			} break;
-			case GDScriptTokenizer::TK_PR_TOOL: {
-				if (p_class->tool) {
-					_set_error("The \"tool\" keyword can only be present once per script.");
-					return;
-				}
-
-				p_class->tool = true;
-				tokenizer->advance();
-
-			} break;
-			case GDScriptTokenizer::TK_PR_CLASS: {
-				//class inside class :D
-
-				StringName name;
-
-				if (tokenizer->get_token(1) != GDScriptTokenizer::TK_IDENTIFIER) {
-					_set_error("\"class\" syntax: \"class <Name>:\" or \"class <Name> extends <BaseClass>:\"");
-					return;
-				}
-				name = tokenizer->get_token_identifier(1);
-				tokenizer->advance(2);
-
-				// Check if name is shadowing something else
-				if (ClassDB::class_exists(name) || ClassDB::class_exists("_" + name.operator String())) {
-					_set_error("The class \"" + String(name) + "\" shadows a native class.");
-					return;
-				}
-				if (ScriptServer::is_global_class(name)) {
-					_set_error("Can't override name of the unique global class \"" + name + "\". It already exists at: " + ScriptServer::get_global_class_path(p_class->name));
-					return;
-				}
-				ClassNode *outer_class = p_class;
-				while (outer_class) {
-					for (int i = 0; i < outer_class->subclasses.size(); i++) {
-						if (outer_class->subclasses[i]->name == name) {
-							_set_error("Another class named \"" + String(name) + "\" already exists in this scope (at line " + itos(outer_class->subclasses[i]->line) + ").");
-							return;
-						}
-					}
-					if (outer_class->constant_expressions.has(name)) {
-						_set_error("A constant named \"" + String(name) + "\" already exists in the outer class scope (at line" + itos(outer_class->constant_expressions[name].expression->line) + ").");
-						return;
-					}
-					for (int i = 0; i < outer_class->variables.size(); i++) {
-						if (outer_class->variables[i].identifier == name) {
-							_set_error("A variable named \"" + String(name) + "\" already exists in the outer class scope (at line " + itos(outer_class->variables[i].line) + ").");
-							return;
-						}
-					}
-
-					outer_class = outer_class->owner;
-				}
-
-				ClassNode *newclass = alloc_node<ClassNode>();
-				newclass->initializer = alloc_node<BlockNode>();
-				newclass->initializer->parent_class = newclass;
-				newclass->ready = alloc_node<BlockNode>();
-				newclass->ready->parent_class = newclass;
-				newclass->name = name;
-				newclass->owner = p_class;
-
-				p_class->subclasses.push_back(newclass);
-
-				if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_EXTENDS) {
-					_parse_extends(newclass);
-					if (error_set) {
-						return;
-					}
-				}
-
-				if (!_enter_indent_block()) {
-					_set_error("Indented block expected.");
-					return;
-				}
-				current_class = newclass;
-				_parse_class(newclass);
-				current_class = p_class;
-
-			} break;
-			/* this is for functions....
-			case GDScriptTokenizer::TK_CF_PASS: {
-
-				tokenizer->advance(1);
-			} break;
-			*/
-			case GDScriptTokenizer::TK_PR_STATIC: {
-				tokenizer->advance();
-				if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) {
-					_set_error("Expected \"func\".");
-					return;
-				}
-
-				[[fallthrough]];
-			}
-			case GDScriptTokenizer::TK_PR_FUNCTION: {
-				bool _static = false;
-				pending_newline = -1;
-
-				if (tokenizer->get_token(-1) == GDScriptTokenizer::TK_PR_STATIC) {
-					_static = true;
-				}
-
-				tokenizer->advance();
-				StringName name;
-
-				if (_get_completable_identifier(COMPLETION_VIRTUAL_FUNC, name)) {
-				}
-
-				if (name == StringName()) {
-					_set_error("Expected an identifier after \"func\" (syntax: \"func <identifier>([arguments]):\").");
-					return;
-				}
-
-				for (int i = 0; i < p_class->functions.size(); i++) {
-					if (p_class->functions[i]->name == name) {
-						_set_error("The function \"" + String(name) + "\" already exists in this class (at line " + itos(p_class->functions[i]->line) + ").");
-					}
-				}
-				for (int i = 0; i < p_class->static_functions.size(); i++) {
-					if (p_class->static_functions[i]->name == name) {
-						_set_error("The function \"" + String(name) + "\" already exists in this class (at line " + itos(p_class->static_functions[i]->line) + ").");
-					}
-				}
-
-#ifdef DEBUG_ENABLED
-				if (p_class->constant_expressions.has(name)) {
-					_add_warning(GDScriptWarning::FUNCTION_CONFLICTS_CONSTANT, -1, name);
-				}
-				for (int i = 0; i < p_class->variables.size(); i++) {
-					if (p_class->variables[i].identifier == name) {
-						_add_warning(GDScriptWarning::FUNCTION_CONFLICTS_VARIABLE, -1, name);
-					}
-				}
-				for (int i = 0; i < p_class->subclasses.size(); i++) {
-					if (p_class->subclasses[i]->name == name) {
-						_add_warning(GDScriptWarning::FUNCTION_CONFLICTS_CONSTANT, -1, name);
-					}
-				}
-#endif // DEBUG_ENABLED
-
-				if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
-					_set_error("Expected \"(\" after the identifier (syntax: \"func <identifier>([arguments]):\" ).");
-					return;
-				}
-
-				tokenizer->advance();
-
-				Vector<StringName> arguments;
-				Vector<DataType> argument_types;
-				Vector<Node *> default_values;
-#ifdef DEBUG_ENABLED
-				Vector<int> arguments_usage;
-#endif // DEBUG_ENABLED
-
-				int fnline = tokenizer->get_token_line();
-
-				if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-					//has arguments
-					bool defaulting = false;
-					while (true) {
-						if (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) {
-							tokenizer->advance();
-							continue;
-						}
-
-						if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_VAR) {
-							tokenizer->advance(); //var before the identifier is allowed
-						}
-
-						if (!tokenizer->is_token_literal(0, true)) {
-							_set_error("Expected an identifier for an argument.");
-							return;
-						}
-
-						StringName argname = tokenizer->get_token_identifier();
-						for (int i = 0; i < arguments.size(); i++) {
-							if (arguments[i] == argname) {
-								_set_error("The argument name \"" + String(argname) + "\" is defined multiple times.");
-								return;
-							}
-						}
-						arguments.push_back(argname);
-#ifdef DEBUG_ENABLED
-						arguments_usage.push_back(0);
-#endif // DEBUG_ENABLED
-
-						tokenizer->advance();
-
-						DataType argtype;
-						if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) {
-							if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) {
-								argtype.infer_type = true;
-								tokenizer->advance();
-							} else if (!_parse_type(argtype)) {
-								_set_error("Expected a type for an argument.");
-								return;
-							}
-						}
-						argument_types.push_back(argtype);
-
-						if (defaulting && tokenizer->get_token() != GDScriptTokenizer::TK_OP_ASSIGN) {
-							_set_error("Default parameter expected.");
-							return;
-						}
-
-						//tokenizer->advance();
-
-						if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) {
-							defaulting = true;
-							tokenizer->advance(1);
-							Node *defval = _parse_and_reduce_expression(p_class, _static);
-							if (!defval || error_set) {
-								return;
-							}
-
-							OperatorNode *on = alloc_node<OperatorNode>();
-							on->op = OperatorNode::OP_ASSIGN;
-							on->line = fnline;
-
-							IdentifierNode *in = alloc_node<IdentifierNode>();
-							in->name = argname;
-							in->line = fnline;
-
-							on->arguments.push_back(in);
-							on->arguments.push_back(defval);
-							/* no ..
-							if (defval->type!=Node::TYPE_CONSTANT) {
-
-								_set_error("default argument must be constant");
-							}
-							*/
-							default_values.push_back(on);
-						}
-
-						while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) {
-							tokenizer->advance();
-						}
-
-						if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
-							tokenizer->advance();
-							continue;
-						} else if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-							_set_error("Expected \",\" or \")\".");
-							return;
-						}
-
-						break;
-					}
-				}
-
-				tokenizer->advance();
-
-				BlockNode *block = alloc_node<BlockNode>();
-				block->parent_class = p_class;
-
-				FunctionNode *function = alloc_node<FunctionNode>();
-				function->name = name;
-				function->arguments = arguments;
-				function->argument_types = argument_types;
-				function->default_values = default_values;
-				function->_static = _static;
-				function->line = fnline;
-#ifdef DEBUG_ENABLED
-				function->arguments_usage = arguments_usage;
-#endif // DEBUG_ENABLED
-				function->rpc_mode = rpc_mode;
-				rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
-
-				if (name == "_init") {
-					if (_static) {
-						_set_error("The constructor cannot be static.");
-						return;
-					}
-
-					if (p_class->extends_used) {
-						OperatorNode *cparent = alloc_node<OperatorNode>();
-						cparent->op = OperatorNode::OP_PARENT_CALL;
-						block->statements.push_back(cparent);
-
-						IdentifierNode *id = alloc_node<IdentifierNode>();
-						id->name = "_init";
-						cparent->arguments.push_back(id);
-
-						if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD) {
-							tokenizer->advance();
-							if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
-								_set_error("Expected \"(\" for parent constructor arguments.");
-								return;
-							}
-							tokenizer->advance();
-
-							if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-								//has arguments
-								parenthesis++;
-								while (true) {
-									current_function = function;
-									Node *arg = _parse_and_reduce_expression(p_class, _static);
-									if (!arg) {
-										return;
-									}
-									current_function = nullptr;
-									cparent->arguments.push_back(arg);
-
-									if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
-										tokenizer->advance();
-										continue;
-									} else if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-										_set_error("Expected \",\" or \")\".");
-										return;
-									}
-
-									break;
-								}
-								parenthesis--;
-							}
-
-							tokenizer->advance();
-						}
-					} else {
-						if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD) {
-							_set_error("Parent constructor call found for a class without inheritance.");
-							return;
-						}
-					}
-				}
-
-				DataType return_type;
-				if (tokenizer->get_token() == GDScriptTokenizer::TK_FORWARD_ARROW) {
-					if (!_parse_type(return_type, true)) {
-						_set_error("Expected a return type for the function.");
-						return;
-					}
-				}
-
-				if (!_enter_indent_block(block)) {
-					_set_error(vformat("Indented block expected after declaration of \"%s\" function.", function->name));
-					return;
-				}
-
-				function->return_type = return_type;
-
-				if (_static) {
-					p_class->static_functions.push_back(function);
-				} else {
-					p_class->functions.push_back(function);
-				}
-
-				current_function = function;
-				function->body = block;
-				current_block = block;
-				_parse_block(block, _static);
-				current_block = nullptr;
-
-				//arguments
-			} break;
-			case GDScriptTokenizer::TK_PR_SIGNAL: {
-				tokenizer->advance();
-
-				if (!tokenizer->is_token_literal()) {
-					_set_error("Expected an identifier after \"signal\".");
-					return;
-				}
-
-				ClassNode::Signal sig;
-				sig.name = tokenizer->get_token_identifier();
-				sig.emissions = 0;
-				sig.line = tokenizer->get_token_line();
-
-				for (int i = 0; i < current_class->_signals.size(); i++) {
-					if (current_class->_signals[i].name == sig.name) {
-						_set_error("The signal \"" + sig.name + "\" already exists in this class (at line: " + itos(current_class->_signals[i].line) + ").");
-						return;
-					}
-				}
-
-				tokenizer->advance();
-
-				if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
-					tokenizer->advance();
-					while (true) {
-						if (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) {
-							tokenizer->advance();
-							continue;
-						}
-
-						if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-							tokenizer->advance();
-							break;
-						}
-
-						if (!tokenizer->is_token_literal(0, true)) {
-							_set_error("Expected an identifier in a \"signal\" argument.");
-							return;
-						}
-
-						sig.arguments.push_back(tokenizer->get_token_identifier());
-						tokenizer->advance();
-
-						while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) {
-							tokenizer->advance();
-						}
-
-						if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
-							tokenizer->advance();
-						} else if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-							_set_error("Expected \",\" or \")\" after a \"signal\" parameter identifier.");
-							return;
-						}
-					}
-				}
-
-				p_class->_signals.push_back(sig);
-
-				if (!_end_statement()) {
-					_set_end_statement_error("signal");
-					return;
-				}
-			} break;
-			case GDScriptTokenizer::TK_PR_EXPORT: {
-				tokenizer->advance();
-
-				if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
-#define _ADVANCE_AND_CONSUME_NEWLINES \
-	do {                              \
-		tokenizer->advance();         \
-	} while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE)
-
-					_ADVANCE_AND_CONSUME_NEWLINES;
-					parenthesis++;
-
-					String hint_prefix = "";
-					bool is_arrayed = false;
-
-					while (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_TYPE &&
-							tokenizer->get_token_type() == Variant::ARRAY &&
-							tokenizer->get_token(1) == GDScriptTokenizer::TK_COMMA) {
-						tokenizer->advance(); // Array
-						tokenizer->advance(); // Comma
-						if (is_arrayed) {
-							hint_prefix += itos(Variant::ARRAY) + ":";
-						} else {
-							is_arrayed = true;
-						}
-					}
-
-					if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_TYPE) {
-						Variant::Type type = tokenizer->get_token_type();
-						if (type == Variant::NIL) {
-							_set_error("Can't export null type.");
-							return;
-						}
-						if (type == Variant::OBJECT) {
-							_set_error("Can't export raw object type.");
-							return;
-						}
-						current_export.type = type;
-						current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
-						_ADVANCE_AND_CONSUME_NEWLINES;
-
-						if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
-							// hint expected next!
-							_ADVANCE_AND_CONSUME_NEWLINES;
-
-							switch (type) {
-								case Variant::INT: {
-									if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "FLAGS") {
-										_ADVANCE_AND_CONSUME_NEWLINES;
-
-										if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) {
-											_set_error("Expected \",\" in the bit flags hint.");
-											return;
-										}
-
-										current_export.hint = PROPERTY_HINT_FLAGS;
-										_ADVANCE_AND_CONSUME_NEWLINES;
-
-										bool first = true;
-										while (true) {
-											if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) {
-												current_export = PropertyInfo();
-												_set_error("Expected a string constant in the named bit flags hint.");
-												return;
-											}
-
-											String c = tokenizer->get_token_constant();
-											if (!first) {
-												current_export.hint_string += ",";
-											} else {
-												first = false;
-											}
-
-											current_export.hint_string += c.xml_escape();
-
-											_ADVANCE_AND_CONSUME_NEWLINES;
-											if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-												break;
-											}
-
-											if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) {
-												current_export = PropertyInfo();
-												_set_error("Expected \")\" or \",\" in the named bit flags hint.");
-												return;
-											}
-											_ADVANCE_AND_CONSUME_NEWLINES;
-										}
-
-										break;
-									}
-
-									if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "LAYERS_2D_RENDER") {
-										_ADVANCE_AND_CONSUME_NEWLINES;
-										if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-											_set_error("Expected \")\" in the layers 2D render hint.");
-											return;
-										}
-										current_export.hint = PROPERTY_HINT_LAYERS_2D_RENDER;
-										break;
-									}
-
-									if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "LAYERS_2D_PHYSICS") {
-										_ADVANCE_AND_CONSUME_NEWLINES;
-										if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-											_set_error("Expected \")\" in the layers 2D physics hint.");
-											return;
-										}
-										current_export.hint = PROPERTY_HINT_LAYERS_2D_PHYSICS;
-										break;
-									}
-
-									if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "LAYERS_3D_RENDER") {
-										_ADVANCE_AND_CONSUME_NEWLINES;
-										if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-											_set_error("Expected \")\" in the layers 3D render hint.");
-											return;
-										}
-										current_export.hint = PROPERTY_HINT_LAYERS_3D_RENDER;
-										break;
-									}
-
-									if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "LAYERS_3D_PHYSICS") {
-										_ADVANCE_AND_CONSUME_NEWLINES;
-										if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-											_set_error("Expected \")\" in the layers 3D physics hint.");
-											return;
-										}
-										current_export.hint = PROPERTY_HINT_LAYERS_3D_PHYSICS;
-										break;
-									}
-
-									if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT && tokenizer->get_token_constant().get_type() == Variant::STRING) {
-										//enumeration
-										current_export.hint = PROPERTY_HINT_ENUM;
-										bool first = true;
-										while (true) {
-											if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) {
-												current_export = PropertyInfo();
-												_set_error("Expected a string constant in the enumeration hint.");
-												return;
-											}
-
-											String c = tokenizer->get_token_constant();
-											if (!first) {
-												current_export.hint_string += ",";
-											} else {
-												first = false;
-											}
-
-											current_export.hint_string += c.xml_escape();
-
-											_ADVANCE_AND_CONSUME_NEWLINES;
-											if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-												break;
-											}
-
-											if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) {
-												current_export = PropertyInfo();
-												_set_error("Expected \")\" or \",\" in the enumeration hint.");
-												return;
-											}
-
-											_ADVANCE_AND_CONSUME_NEWLINES;
-										}
-
-										break;
-									}
-
-									[[fallthrough]];
-								}
-								case Variant::FLOAT: {
-									if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "EASE") {
-										current_export.hint = PROPERTY_HINT_EXP_EASING;
-										_ADVANCE_AND_CONSUME_NEWLINES;
-										if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-											_set_error("Expected \")\" in the hint.");
-											return;
-										}
-										break;
-									}
-
-									// range
-									if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "EXP") {
-										current_export.hint = PROPERTY_HINT_EXP_RANGE;
-										_ADVANCE_AND_CONSUME_NEWLINES;
-
-										if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-											break;
-										} else if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) {
-											_set_error("Expected \")\" or \",\" in the exponential range hint.");
-											return;
-										}
-										_ADVANCE_AND_CONSUME_NEWLINES;
-									} else {
-										current_export.hint = PROPERTY_HINT_RANGE;
-									}
-
-									float sign = 1.0;
-
-									if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_SUB) {
-										sign = -1;
-										_ADVANCE_AND_CONSUME_NEWLINES;
-									}
-									if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || !tokenizer->get_token_constant().is_num()) {
-										current_export = PropertyInfo();
-										_set_error("Expected a range in the numeric hint.");
-										return;
-									}
-
-									current_export.hint_string = rtos(sign * double(tokenizer->get_token_constant()));
-									_ADVANCE_AND_CONSUME_NEWLINES;
-
-									if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-										current_export.hint_string = "0," + current_export.hint_string;
-										break;
-									}
-
-									if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) {
-										current_export = PropertyInfo();
-										_set_error("Expected \",\" or \")\" in the numeric range hint.");
-										return;
-									}
-
-									_ADVANCE_AND_CONSUME_NEWLINES;
-
-									sign = 1.0;
-									if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_SUB) {
-										sign = -1;
-										_ADVANCE_AND_CONSUME_NEWLINES;
-									}
-
-									if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || !tokenizer->get_token_constant().is_num()) {
-										current_export = PropertyInfo();
-										_set_error("Expected a number as upper bound in the numeric range hint.");
-										return;
-									}
-
-									current_export.hint_string += "," + rtos(sign * double(tokenizer->get_token_constant()));
-									_ADVANCE_AND_CONSUME_NEWLINES;
-
-									if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-										break;
-									}
-
-									if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) {
-										current_export = PropertyInfo();
-										_set_error("Expected \",\" or \")\" in the numeric range hint.");
-										return;
-									}
-
-									_ADVANCE_AND_CONSUME_NEWLINES;
-									sign = 1.0;
-									if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_SUB) {
-										sign = -1;
-										_ADVANCE_AND_CONSUME_NEWLINES;
-									}
-
-									if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || !tokenizer->get_token_constant().is_num()) {
-										current_export = PropertyInfo();
-										_set_error("Expected a number as step in the numeric range hint.");
-										return;
-									}
-
-									current_export.hint_string += "," + rtos(sign * double(tokenizer->get_token_constant()));
-									_ADVANCE_AND_CONSUME_NEWLINES;
-
-								} break;
-								case Variant::STRING: {
-									if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT && tokenizer->get_token_constant().get_type() == Variant::STRING) {
-										//enumeration
-										current_export.hint = PROPERTY_HINT_ENUM;
-										bool first = true;
-										while (true) {
-											if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) {
-												current_export = PropertyInfo();
-												_set_error("Expected a string constant in the enumeration hint.");
-												return;
-											}
-
-											String c = tokenizer->get_token_constant();
-											if (!first) {
-												current_export.hint_string += ",";
-											} else {
-												first = false;
-											}
-
-											current_export.hint_string += c.xml_escape();
-											_ADVANCE_AND_CONSUME_NEWLINES;
-											if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-												break;
-											}
-
-											if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) {
-												current_export = PropertyInfo();
-												_set_error("Expected \")\" or \",\" in the enumeration hint.");
-												return;
-											}
-											_ADVANCE_AND_CONSUME_NEWLINES;
-										}
-
-										break;
-									}
-
-									if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "DIR") {
-										_ADVANCE_AND_CONSUME_NEWLINES;
-
-										if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-											current_export.hint = PROPERTY_HINT_DIR;
-										} else if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
-											_ADVANCE_AND_CONSUME_NEWLINES;
-
-											if (tokenizer->get_token() != GDScriptTokenizer::TK_IDENTIFIER || !(tokenizer->get_token_identifier() == "GLOBAL")) {
-												_set_error("Expected \"GLOBAL\" after comma in the directory hint.");
-												return;
-											}
-											if (!p_class->tool) {
-												_set_error("Global filesystem hints may only be used in tool scripts.");
-												return;
-											}
-											current_export.hint = PROPERTY_HINT_GLOBAL_DIR;
-											_ADVANCE_AND_CONSUME_NEWLINES;
-
-											if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-												_set_error("Expected \")\" in the hint.");
-												return;
-											}
-										} else {
-											_set_error("Expected \")\" or \",\" in the hint.");
-											return;
-										}
-										break;
-									}
-
-									if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "FILE") {
-										current_export.hint = PROPERTY_HINT_FILE;
-										_ADVANCE_AND_CONSUME_NEWLINES;
-
-										if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
-											_ADVANCE_AND_CONSUME_NEWLINES;
-
-											if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "GLOBAL") {
-												if (!p_class->tool) {
-													_set_error("Global filesystem hints may only be used in tool scripts.");
-													return;
-												}
-												current_export.hint = PROPERTY_HINT_GLOBAL_FILE;
-												_ADVANCE_AND_CONSUME_NEWLINES;
-
-												if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-													break;
-												} else if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
-													_ADVANCE_AND_CONSUME_NEWLINES;
-												} else {
-													_set_error("Expected \")\" or \",\" in the hint.");
-													return;
-												}
-											}
-
-											if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) {
-												if (current_export.hint == PROPERTY_HINT_GLOBAL_FILE) {
-													_set_error("Expected string constant with filter.");
-												} else {
-													_set_error("Expected \"GLOBAL\" or string constant with filter.");
-												}
-												return;
-											}
-											current_export.hint_string = tokenizer->get_token_constant();
-											_ADVANCE_AND_CONSUME_NEWLINES;
-										}
-
-										if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-											_set_error("Expected \")\" in the hint.");
-											return;
-										}
-										break;
-									}
-
-									if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "MULTILINE") {
-										current_export.hint = PROPERTY_HINT_MULTILINE_TEXT;
-										_ADVANCE_AND_CONSUME_NEWLINES;
-										if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-											_set_error("Expected \")\" in the hint.");
-											return;
-										}
-										break;
-									}
-								} break;
-								case Variant::COLOR: {
-									if (tokenizer->get_token() != GDScriptTokenizer::TK_IDENTIFIER) {
-										current_export = PropertyInfo();
-										_set_error("Color type hint expects RGB or RGBA as hints.");
-										return;
-									}
-
-									String identifier = tokenizer->get_token_identifier();
-									if (identifier == "RGB") {
-										current_export.hint = PROPERTY_HINT_COLOR_NO_ALPHA;
-									} else if (identifier == "RGBA") {
-										//none
-									} else {
-										current_export = PropertyInfo();
-										_set_error("Color type hint expects RGB or RGBA as hints.");
-										return;
-									}
-									_ADVANCE_AND_CONSUME_NEWLINES;
-
-								} break;
-								default: {
-									current_export = PropertyInfo();
-									_set_error("Type \"" + Variant::get_type_name(type) + "\" can't take hints.");
-									return;
-								} break;
-							}
-						}
-
-					} else {
-						parenthesis++;
-						Node *subexpr = _parse_and_reduce_expression(p_class, true, true);
-						if (!subexpr) {
-							if (_recover_from_completion()) {
-								break;
-							}
-							return;
-						}
-						parenthesis--;
-
-						if (subexpr->type != Node::TYPE_CONSTANT) {
-							current_export = PropertyInfo();
-							_set_error("Expected a constant expression.");
-						}
-
-						Variant constant = static_cast<ConstantNode *>(subexpr)->value;
-
-						if (constant.get_type() == Variant::OBJECT) {
-							GDScriptNativeClass *native_class = Object::cast_to<GDScriptNativeClass>(constant);
-
-							if (native_class && ClassDB::is_parent_class(native_class->get_name(), "Resource")) {
-								current_export.type = Variant::OBJECT;
-								current_export.hint = PROPERTY_HINT_RESOURCE_TYPE;
-								current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
-
-								current_export.hint_string = native_class->get_name();
-								current_export.class_name = native_class->get_name();
-
-							} else {
-								current_export = PropertyInfo();
-								_set_error("The export hint isn't a resource type.");
-							}
-						} else if (constant.get_type() == Variant::DICTIONARY) {
-							// Enumeration
-							bool is_flags = false;
-
-							if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
-								_ADVANCE_AND_CONSUME_NEWLINES;
-
-								if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "FLAGS") {
-									is_flags = true;
-									_ADVANCE_AND_CONSUME_NEWLINES;
-								} else {
-									current_export = PropertyInfo();
-									_set_error("Expected \"FLAGS\" after comma.");
-								}
-							}
-
-							current_export.type = Variant::INT;
-							current_export.hint = is_flags ? PROPERTY_HINT_FLAGS : PROPERTY_HINT_ENUM;
-							current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
-							Dictionary enum_values = constant;
-
-							List<Variant> keys;
-							enum_values.get_key_list(&keys);
-
-							bool first = true;
-							for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
-								if (enum_values[E->get()].get_type() == Variant::INT) {
-									if (!first) {
-										current_export.hint_string += ",";
-									} else {
-										first = false;
-									}
-
-									current_export.hint_string += E->get().operator String().camelcase_to_underscore(true).capitalize().xml_escape();
-									if (!is_flags) {
-										current_export.hint_string += ":";
-										current_export.hint_string += enum_values[E->get()].operator String().xml_escape();
-									}
-								}
-							}
-						} else {
-							current_export = PropertyInfo();
-							_set_error("Expected type for export.");
-							return;
-						}
-					}
-
-					if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
-						current_export = PropertyInfo();
-						_set_error("Expected \")\" or \",\" after the export hint.");
-						return;
-					}
-
-					tokenizer->advance();
-					parenthesis--;
-
-					if (is_arrayed) {
-						hint_prefix += itos(current_export.type);
-						if (current_export.hint) {
-							hint_prefix += "/" + itos(current_export.hint);
-						}
-						current_export.hint_string = hint_prefix + ":" + current_export.hint_string;
-						current_export.hint = PROPERTY_HINT_TYPE_STRING;
-						current_export.type = Variant::ARRAY;
-					}
-#undef _ADVANCE_AND_CONSUME_NEWLINES
-				}
-
-				if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_ONREADY && tokenizer->get_token() != GDScriptTokenizer::TK_PR_REMOTE && tokenizer->get_token() != GDScriptTokenizer::TK_PR_MASTER && tokenizer->get_token() != GDScriptTokenizer::TK_PR_PUPPET && tokenizer->get_token() != GDScriptTokenizer::TK_PR_REMOTESYNC && tokenizer->get_token() != GDScriptTokenizer::TK_PR_MASTERSYNC && tokenizer->get_token() != GDScriptTokenizer::TK_PR_PUPPETSYNC) {
-					current_export = PropertyInfo();
-					_set_error("Expected \"var\", \"onready\", \"remote\", \"master\", \"puppet\", \"remotesync\", \"mastersync\", \"puppetsync\".");
-					return;
-				}
-
-				continue;
-			} break;
-			case GDScriptTokenizer::TK_PR_ONREADY: {
-				//may be fallthrough from export, ignore if so
-				tokenizer->advance();
-				if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR) {
-					_set_error("Expected \"var\".");
-					return;
-				}
-
-				continue;
-			} break;
-			case GDScriptTokenizer::TK_PR_REMOTE: {
-				//may be fallthrough from export, ignore if so
-				tokenizer->advance();
-				if (current_export.type) {
-					if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR) {
-						_set_error("Expected \"var\".");
-						return;
-					}
-
-				} else {
-					if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) {
-						_set_error("Expected \"var\" or \"func\".");
-						return;
-					}
-				}
-				rpc_mode = MultiplayerAPI::RPC_MODE_REMOTE;
-
-				continue;
-			} break;
-			case GDScriptTokenizer::TK_PR_MASTER: {
-				//may be fallthrough from export, ignore if so
-				tokenizer->advance();
-				if (current_export.type) {
-					if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR) {
-						_set_error("Expected \"var\".");
-						return;
-					}
-
-				} else {
-					if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) {
-						_set_error("Expected \"var\" or \"func\".");
-						return;
-					}
-				}
-
-				rpc_mode = MultiplayerAPI::RPC_MODE_MASTER;
-				continue;
-			} break;
-			case GDScriptTokenizer::TK_PR_PUPPET: {
-				//may be fallthrough from export, ignore if so
-				tokenizer->advance();
-				if (current_export.type) {
-					if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR) {
-						_set_error("Expected \"var\".");
-						return;
-					}
-
-				} else {
-					if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) {
-						_set_error("Expected \"var\" or \"func\".");
-						return;
-					}
-				}
-
-				rpc_mode = MultiplayerAPI::RPC_MODE_PUPPET;
-				continue;
-			} break;
-			case GDScriptTokenizer::TK_PR_REMOTESYNC: {
-				//may be fallthrough from export, ignore if so
-				tokenizer->advance();
-				if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) {
-					if (current_export.type) {
-						_set_error("Expected \"var\".");
-					} else {
-						_set_error("Expected \"var\" or \"func\".");
-					}
-					return;
-				}
-
-				rpc_mode = MultiplayerAPI::RPC_MODE_REMOTESYNC;
-				continue;
-			} break;
-			case GDScriptTokenizer::TK_PR_MASTERSYNC: {
-				//may be fallthrough from export, ignore if so
-				tokenizer->advance();
-				if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) {
-					if (current_export.type) {
-						_set_error("Expected \"var\".");
-					} else {
-						_set_error("Expected \"var\" or \"func\".");
-					}
-					return;
-				}
-
-				rpc_mode = MultiplayerAPI::RPC_MODE_MASTERSYNC;
-				continue;
-			} break;
-			case GDScriptTokenizer::TK_PR_PUPPETSYNC: {
-				//may be fallthrough from export, ignore if so
-				tokenizer->advance();
-				if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) {
-					if (current_export.type) {
-						_set_error("Expected \"var\".");
-					} else {
-						_set_error("Expected \"var\" or \"func\".");
-					}
-					return;
-				}
-
-				rpc_mode = MultiplayerAPI::RPC_MODE_PUPPETSYNC;
-				continue;
-			} break;
-			case GDScriptTokenizer::TK_PR_VAR: {
-				// variable declaration and (eventual) initialization
-
-				ClassNode::Member member;
-
-				bool autoexport = tokenizer->get_token(-1) == GDScriptTokenizer::TK_PR_EXPORT;
-				if (current_export.type != Variant::NIL) {
-					member._export = current_export;
-					current_export = PropertyInfo();
-				}
-
-				bool onready = tokenizer->get_token(-1) == GDScriptTokenizer::TK_PR_ONREADY;
-
-				tokenizer->advance();
-				if (!tokenizer->is_token_literal(0, true)) {
-					_set_error("Expected an identifier for the member variable name.");
-					return;
-				}
-
-				member.identifier = tokenizer->get_token_literal();
-				member.expression = nullptr;
-				member._export.name = member.identifier;
-				member.line = tokenizer->get_token_line();
-				member.usages = 0;
-				member.rpc_mode = rpc_mode;
-
-				if (current_class->constant_expressions.has(member.identifier)) {
-					_set_error("A constant named \"" + String(member.identifier) + "\" already exists in this class (at line: " +
-							   itos(current_class->constant_expressions[member.identifier].expression->line) + ").");
-					return;
-				}
-
-				for (int i = 0; i < current_class->variables.size(); i++) {
-					if (current_class->variables[i].identifier == member.identifier) {
-						_set_error("Variable \"" + String(member.identifier) + "\" already exists in this class (at line: " +
-								   itos(current_class->variables[i].line) + ").");
-						return;
-					}
-				}
-
-				for (int i = 0; i < current_class->subclasses.size(); i++) {
-					if (current_class->subclasses[i]->name == member.identifier) {
-						_set_error("A class named \"" + String(member.identifier) + "\" already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ").");
-						return;
-					}
-				}
-#ifdef DEBUG_ENABLED
-				for (int i = 0; i < current_class->functions.size(); i++) {
-					if (current_class->functions[i]->name == member.identifier) {
-						_add_warning(GDScriptWarning::VARIABLE_CONFLICTS_FUNCTION, member.line, member.identifier);
-						break;
-					}
-				}
-				for (int i = 0; i < current_class->static_functions.size(); i++) {
-					if (current_class->static_functions[i]->name == member.identifier) {
-						_add_warning(GDScriptWarning::VARIABLE_CONFLICTS_FUNCTION, member.line, member.identifier);
-						break;
-					}
-				}
-#endif // DEBUG_ENABLED
-				tokenizer->advance();
-
-				rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
-
-				if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) {
-					if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) {
-						member.data_type = DataType();
-#ifdef DEBUG_ENABLED
-						member.data_type.infer_type = true;
-#endif
-						tokenizer->advance();
-					} else if (!_parse_type(member.data_type)) {
-						_set_error("Expected a type for the class variable.");
-						return;
-					}
-				}
-
-				if (autoexport && member.data_type.has_type) {
-					if (member.data_type.kind == DataType::BUILTIN) {
-						member._export.type = member.data_type.builtin_type;
-					} else if (member.data_type.kind == DataType::NATIVE) {
-						if (ClassDB::is_parent_class(member.data_type.native_type, "Resource")) {
-							member._export.type = Variant::OBJECT;
-							member._export.hint = PROPERTY_HINT_RESOURCE_TYPE;
-							member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
-							member._export.hint_string = member.data_type.native_type;
-							member._export.class_name = member.data_type.native_type;
-						} else {
-							_set_error("Invalid export type. Only built-in and native resource types can be exported.", member.line);
-							return;
-						}
-
-					} else {
-						_set_error("Invalid export type. Only built-in and native resource types can be exported.", member.line);
-						return;
-					}
-				}
-
-#ifdef TOOLS_ENABLED
-				Callable::CallError ce;
-				member.default_value = Variant::construct(member._export.type, nullptr, 0, ce);
-#endif
-
-				if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) {
-#ifdef DEBUG_ENABLED
-					int line = tokenizer->get_token_line();
-#endif
-					tokenizer->advance();
-
-					Node *subexpr = _parse_and_reduce_expression(p_class, false, autoexport || member._export.type != Variant::NIL);
-					if (!subexpr) {
-						if (_recover_from_completion()) {
-							break;
-						}
-						return;
-					}
-
-					//discourage common error
-					if (!onready && subexpr->type == Node::TYPE_OPERATOR) {
-						OperatorNode *op = static_cast<OperatorNode *>(subexpr);
-						if (op->op == OperatorNode::OP_CALL && op->arguments[0]->type == Node::TYPE_SELF && op->arguments[1]->type == Node::TYPE_IDENTIFIER) {
-							IdentifierNode *id = static_cast<IdentifierNode *>(op->arguments[1]);
-							if (id->name == "get_node") {
-								_set_error("Use \"onready var " + String(member.identifier) + " = get_node(...)\" instead.");
-								return;
-							}
-						}
-					}
-
-					member.expression = subexpr;
-
-					if (autoexport && !member.data_type.has_type) {
-						if (subexpr->type != Node::TYPE_CONSTANT) {
-							_set_error("Type-less export needs a constant expression assigned to infer type.");
-							return;
-						}
-
-						ConstantNode *cn = static_cast<ConstantNode *>(subexpr);
-						if (cn->value.get_type() == Variant::NIL) {
-							_set_error("Can't accept a null constant expression for inferring export type.");
-							return;
-						}
-
-						if (!_reduce_export_var_type(cn->value, member.line)) {
-							return;
-						}
-
-						member._export.type = cn->value.get_type();
-						member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
-						if (cn->value.get_type() == Variant::OBJECT) {
-							Object *obj = cn->value;
-							Resource *res = Object::cast_to<Resource>(obj);
-							if (res == nullptr) {
-								_set_error("The exported constant isn't a type or resource.");
-								return;
-							}
-							member._export.hint = PROPERTY_HINT_RESOURCE_TYPE;
-							member._export.hint_string = res->get_class();
-						}
-					}
-#ifdef TOOLS_ENABLED
-					if (subexpr->type == Node::TYPE_CONSTANT && (member._export.type != Variant::NIL || member.data_type.has_type)) {
-						ConstantNode *cn = static_cast<ConstantNode *>(subexpr);
-						if (cn->value.get_type() != Variant::NIL) {
-							if (member._export.type != Variant::NIL && cn->value.get_type() != member._export.type) {
-								if (Variant::can_convert(cn->value.get_type(), member._export.type)) {
-									Callable::CallError err;
-									const Variant *args = &cn->value;
-									cn->value = Variant::construct(member._export.type, &args, 1, err);
-								} else {
-									_set_error("Can't convert the provided value to the export type.");
-									return;
-								}
-							}
-							member.default_value = cn->value;
-						}
-					}
-#endif
-
-					IdentifierNode *id = alloc_node<IdentifierNode>();
-					id->name = member.identifier;
-					id->datatype = member.data_type;
-
-					OperatorNode *op = alloc_node<OperatorNode>();
-					op->op = OperatorNode::OP_INIT_ASSIGN;
-					op->arguments.push_back(id);
-					op->arguments.push_back(subexpr);
-
-#ifdef DEBUG_ENABLED
-					NewLineNode *nl2 = alloc_node<NewLineNode>();
-					nl2->line = line;
-					if (onready) {
-						p_class->ready->statements.push_back(nl2);
-					} else {
-						p_class->initializer->statements.push_back(nl2);
-					}
-#endif
-					if (onready) {
-						p_class->ready->statements.push_back(op);
-					} else {
-						p_class->initializer->statements.push_back(op);
-					}
-
-					member.initial_assignment = op;
-
-				} else {
-					if (autoexport && !member.data_type.has_type) {
-						_set_error("Type-less export needs a constant expression assigned to infer type.");
-						return;
-					}
-
-					Node *expr;
-
-					if (member.data_type.has_type) {
-						expr = _get_default_value_for_type(member.data_type);
-					} else {
-						DataType exported_type;
-						exported_type.has_type = true;
-						exported_type.kind = DataType::BUILTIN;
-						exported_type.builtin_type = member._export.type;
-						expr = _get_default_value_for_type(exported_type);
-					}
-
-					IdentifierNode *id = alloc_node<IdentifierNode>();
-					id->name = member.identifier;
-					id->datatype = member.data_type;
-
-					OperatorNode *op = alloc_node<OperatorNode>();
-					op->op = OperatorNode::OP_INIT_ASSIGN;
-					op->arguments.push_back(id);
-					op->arguments.push_back(expr);
-
-					p_class->initializer->statements.push_back(op);
-
-					member.initial_assignment = op;
-				}
-
-				if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_SETGET) {
-					tokenizer->advance();
-
-					if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) {
-						//just comma means using only getter
-						if (!tokenizer->is_token_literal()) {
-							_set_error("Expected an identifier for the setter function after \"setget\".");
-						}
-
-						member.setter = tokenizer->get_token_literal();
-
-						tokenizer->advance();
-					}
-
-					if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
-						//there is a getter
-						tokenizer->advance();
-
-						if (!tokenizer->is_token_literal()) {
-							_set_error("Expected an identifier for the getter function after \",\".");
-						}
-
-						member.getter = tokenizer->get_token_literal();
-						tokenizer->advance();
-					}
-				}
-
-				p_class->variables.push_back(member);
-
-				if (!_end_statement()) {
-					_set_end_statement_error("var");
-					return;
-				}
-			} break;
-			case GDScriptTokenizer::TK_PR_CONST: {
-				// constant declaration and initialization
-
-				ClassNode::Constant constant;
-
-				tokenizer->advance();
-				if (!tokenizer->is_token_literal(0, true)) {
-					_set_error("Expected an identifier for the constant.");
-					return;
-				}
-
-				StringName const_id = tokenizer->get_token_literal();
-				int line = tokenizer->get_token_line();
-
-				if (current_class->constant_expressions.has(const_id)) {
-					_set_error("Constant \"" + String(const_id) + "\" already exists in this class (at line " +
-							   itos(current_class->constant_expressions[const_id].expression->line) + ").");
-					return;
-				}
-
-				for (int i = 0; i < current_class->variables.size(); i++) {
-					if (current_class->variables[i].identifier == const_id) {
-						_set_error("A variable named \"" + String(const_id) + "\" already exists in this class (at line " +
-								   itos(current_class->variables[i].line) + ").");
-						return;
-					}
-				}
-
-				for (int i = 0; i < current_class->subclasses.size(); i++) {
-					if (current_class->subclasses[i]->name == const_id) {
-						_set_error("A class named \"" + String(const_id) + "\" already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ").");
-						return;
-					}
-				}
-
-				tokenizer->advance();
-
-				if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) {
-					if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) {
-						constant.type = DataType();
-#ifdef DEBUG_ENABLED
-						constant.type.infer_type = true;
-#endif
-						tokenizer->advance();
-					} else if (!_parse_type(constant.type)) {
-						_set_error("Expected a type for the class constant.");
-						return;
-					}
-				}
-
-				if (tokenizer->get_token() != GDScriptTokenizer::TK_OP_ASSIGN) {
-					_set_error("Constants must be assigned immediately.");
-					return;
-				}
-
-				tokenizer->advance();
-
-				Node *subexpr = _parse_and_reduce_expression(p_class, true, true);
-				if (!subexpr) {
-					if (_recover_from_completion()) {
-						break;
-					}
-					return;
-				}
-
-				if (subexpr->type != Node::TYPE_CONSTANT) {
-					_set_error("Expected a constant expression.", line);
-					return;
-				}
-				subexpr->line = line;
-				constant.expression = subexpr;
-
-				p_class->constant_expressions.insert(const_id, constant);
-
-				if (!_end_statement()) {
-					_set_end_statement_error("const");
-					return;
-				}
-
-			} break;
-			case GDScriptTokenizer::TK_PR_ENUM: {
-				//multiple constant declarations..
-
-				int last_assign = -1; // Incremented by 1 right before the assignment.
-				String enum_name;
-				Dictionary enum_dict;
-
-				tokenizer->advance();
-				if (tokenizer->is_token_literal(0, true)) {
-					enum_name = tokenizer->get_token_literal();
-
-					if (current_class->constant_expressions.has(enum_name)) {
-						_set_error("A constant named \"" + String(enum_name) + "\" already exists in this class (at line " +
-								   itos(current_class->constant_expressions[enum_name].expression->line) + ").");
-						return;
-					}
-
-					for (int i = 0; i < current_class->variables.size(); i++) {
-						if (current_class->variables[i].identifier == enum_name) {
-							_set_error("A variable named \"" + String(enum_name) + "\" already exists in this class (at line " +
-									   itos(current_class->variables[i].line) + ").");
-							return;
-						}
-					}
-
-					for (int i = 0; i < current_class->subclasses.size(); i++) {
-						if (current_class->subclasses[i]->name == enum_name) {
-							_set_error("A class named \"" + String(enum_name) + "\" already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ").");
-							return;
-						}
-					}
-
-					tokenizer->advance();
-				}
-				if (tokenizer->get_token() != GDScriptTokenizer::TK_CURLY_BRACKET_OPEN) {
-					_set_error("Expected \"{\" in the enum declaration.");
-					return;
-				}
-				tokenizer->advance();
-
-				while (true) {
-					if (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) {
-						tokenizer->advance(); // Ignore newlines
-					} else if (tokenizer->get_token() == GDScriptTokenizer::TK_CURLY_BRACKET_CLOSE) {
-						tokenizer->advance();
-						break; // End of enum
-					} else if (!tokenizer->is_token_literal(0, true)) {
-						if (tokenizer->get_token() == GDScriptTokenizer::TK_EOF) {
-							_set_error("Unexpected end of file.");
-						} else {
-							_set_error(String("Unexpected ") + GDScriptTokenizer::get_token_name(tokenizer->get_token()) + ", expected an identifier.");
-						}
-
-						return;
-					} else { // tokenizer->is_token_literal(0, true)
-						StringName const_id = tokenizer->get_token_literal();
-
-						tokenizer->advance();
-
-						ConstantNode *enum_value_expr;
-
-						if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) {
-							tokenizer->advance();
-
-							Node *subexpr = _parse_and_reduce_expression(p_class, true, true);
-							if (!subexpr) {
-								if (_recover_from_completion()) {
-									break;
-								}
-								return;
-							}
-
-							if (subexpr->type != Node::TYPE_CONSTANT) {
-								_set_error("Expected a constant expression.");
-								return;
-							}
-
-							enum_value_expr = static_cast<ConstantNode *>(subexpr);
-
-							if (enum_value_expr->value.get_type() != Variant::INT) {
-								_set_error("Expected an integer value for \"enum\".");
-								return;
-							}
-
-							last_assign = enum_value_expr->value;
-
-						} else {
-							last_assign = last_assign + 1;
-							enum_value_expr = alloc_node<ConstantNode>();
-							enum_value_expr->value = last_assign;
-							enum_value_expr->datatype = _type_from_variant(enum_value_expr->value);
-						}
-
-						if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
-							tokenizer->advance();
-						} else if (tokenizer->is_token_literal(0, true)) {
-							_set_error("Unexpected identifier.");
-							return;
-						}
-
-						if (enum_name != "") {
-							enum_dict[const_id] = enum_value_expr->value;
-						} else {
-							if (current_class->constant_expressions.has(const_id)) {
-								_set_error("A constant named \"" + String(const_id) + "\" already exists in this class (at line " +
-										   itos(current_class->constant_expressions[const_id].expression->line) + ").");
-								return;
-							}
-
-							for (int i = 0; i < current_class->variables.size(); i++) {
-								if (current_class->variables[i].identifier == const_id) {
-									_set_error("A variable named \"" + String(const_id) + "\" already exists in this class (at line " +
-											   itos(current_class->variables[i].line) + ").");
-									return;
-								}
-							}
-
-							for (int i = 0; i < current_class->subclasses.size(); i++) {
-								if (current_class->subclasses[i]->name == const_id) {
-									_set_error("A class named \"" + String(const_id) + "\" already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ").");
-									return;
-								}
-							}
-
-							ClassNode::Constant constant;
-							constant.type.has_type = true;
-							constant.type.kind = DataType::BUILTIN;
-							constant.type.builtin_type = Variant::INT;
-							constant.expression = enum_value_expr;
-							p_class->constant_expressions.insert(const_id, constant);
-						}
-					}
-				}
-
-				if (enum_name != "") {
-					ClassNode::Constant enum_constant;
-					ConstantNode *cn = alloc_node<ConstantNode>();
-					cn->value = enum_dict;
-					cn->datatype = _type_from_variant(cn->value);
-
-					enum_constant.expression = cn;
-					enum_constant.type = cn->datatype;
-					p_class->constant_expressions.insert(enum_name, enum_constant);
-				}
-
-				if (!_end_statement()) {
-					_set_end_statement_error("enum");
-					return;
-				}
-
-			} break;
-
-			case GDScriptTokenizer::TK_CONSTANT: {
-				if (tokenizer->get_token_constant().get_type() == Variant::STRING) {
-					tokenizer->advance();
-					// Ignore
-				} else {
-					_set_error(String() + "Unexpected constant of type: " + Variant::get_type_name(tokenizer->get_token_constant().get_type()));
-					return;
-				}
-			} break;
-
-			case GDScriptTokenizer::TK_CF_PASS: {
-				tokenizer->advance();
-			} break;
-
-			default: {
-				_set_error(String() + "Unexpected token: " + tokenizer->get_token_name(tokenizer->get_token()) + ":" + tokenizer->get_token_identifier());
-				return;
-
-			} break;
-		}
-	}
-}
-
-void GDScriptParser::_determine_inheritance(ClassNode *p_class, bool p_recursive) {
-	if (p_class->base_type.has_type) {
-		// Already determined
-	} else if (p_class->extends_used) {
-		//do inheritance
-		String path = p_class->extends_file;
-
-		Ref<GDScript> script;
-		StringName native;
-		ClassNode *base_class = nullptr;
-
-		if (path != "") {
-			//path (and optionally subclasses)
-
-			if (path.is_rel_path()) {
-				String base = base_path;
-
-				if (base == "" || base.is_rel_path()) {
-					_set_error("Couldn't resolve relative path for the parent class: " + path, p_class->line);
-					return;
-				}
-				path = base.plus_file(path).simplify_path();
-			}
-			script = ResourceLoader::load(path);
-			if (script.is_null()) {
-				_set_error("Couldn't load the base class: " + path, p_class->line);
-				return;
-			}
-			if (!script->is_valid()) {
-				_set_error("Script isn't fully loaded (cyclic preload?): " + path, p_class->line);
-				return;
-			}
-
-			if (p_class->extends_class.size()) {
-				for (int i = 0; i < p_class->extends_class.size(); i++) {
-					String sub = p_class->extends_class[i];
-					if (script->get_subclasses().has(sub)) {
-						Ref<Script> subclass = script->get_subclasses()[sub]; //avoid reference from disappearing
-						script = subclass;
-					} else {
-						_set_error("Couldn't find the subclass: " + sub, p_class->line);
-						return;
-					}
-				}
-			}
-
-		} else {
-			if (p_class->extends_class.size() == 0) {
-				_set_error("Parser bug: undecidable inheritance.", p_class->line);
-				ERR_FAIL();
-			}
-			//look around for the subclasses
-
-			int extend_iter = 1;
-			String base = p_class->extends_class[0];
-			ClassNode *p = p_class->owner;
-			Ref<GDScript> base_script;
-
-			if (ScriptServer::is_global_class(base)) {
-				base_script = ResourceLoader::load(ScriptServer::get_global_class_path(base));
-				if (!base_script.is_valid()) {
-					_set_error("The class \"" + base + "\" couldn't be fully loaded (script error or cyclic dependency).", p_class->line);
-					return;
-				}
-				p = nullptr;
-			} else {
-				List<PropertyInfo> props;
-				ProjectSettings::get_singleton()->get_property_list(&props);
-				for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
-					String s = E->get().name;
-					if (!s.begins_with("autoload/")) {
-						continue;
-					}
-					String name = s.get_slice("/", 1);
-					if (name == base) {
-						String singleton_path = ProjectSettings::get_singleton()->get(s);
-						if (singleton_path.begins_with("*")) {
-							singleton_path = singleton_path.right(1);
-						}
-						if (!singleton_path.begins_with("res://")) {
-							singleton_path = "res://" + singleton_path;
-						}
-						base_script = ResourceLoader::load(singleton_path);
-						if (!base_script.is_valid()) {
-							_set_error("Class '" + base + "' could not be fully loaded (script error or cyclic inheritance).", p_class->line);
-							return;
-						}
-						p = nullptr;
-					}
-				}
-			}
-
-			while (p) {
-				bool found = false;
-
-				for (int i = 0; i < p->subclasses.size(); i++) {
-					if (p->subclasses[i]->name == base) {
-						ClassNode *test = p->subclasses[i];
-						while (test) {
-							if (test == p_class) {
-								_set_error("Cyclic inheritance.", test->line);
-								return;
-							}
-							if (test->base_type.kind == DataType::CLASS) {
-								test = test->base_type.class_type;
-							} else {
-								break;
-							}
-						}
-						found = true;
-						if (extend_iter < p_class->extends_class.size()) {
-							// Keep looking at current classes if possible
-							base = p_class->extends_class[extend_iter++];
-							p = p->subclasses[i];
-						} else {
-							base_class = p->subclasses[i];
-						}
-						break;
-					}
-				}
-
-				if (base_class) {
-					break;
-				}
-				if (found) {
-					continue;
-				}
-
-				if (p->constant_expressions.has(base)) {
-					if (p->constant_expressions[base].expression->type != Node::TYPE_CONSTANT) {
-						_set_error("Couldn't resolve the constant \"" + base + "\".", p_class->line);
-						return;
-					}
-					const ConstantNode *cn = static_cast<const ConstantNode *>(p->constant_expressions[base].expression);
-					base_script = cn->value;
-					if (base_script.is_null()) {
-						_set_error("Constant isn't a class: " + base, p_class->line);
-						return;
-					}
-					break;
-				}
-
-				p = p->owner;
-			}
-
-			if (base_script.is_valid()) {
-				String ident = base;
-				Ref<GDScript> find_subclass = base_script;
-
-				for (int i = extend_iter; i < p_class->extends_class.size(); i++) {
-					String subclass = p_class->extends_class[i];
-
-					ident += ("." + subclass);
-
-					if (find_subclass->get_subclasses().has(subclass)) {
-						find_subclass = find_subclass->get_subclasses()[subclass];
-					} else if (find_subclass->get_constants().has(subclass)) {
-						Ref<GDScript> new_base_class = find_subclass->get_constants()[subclass];
-						if (new_base_class.is_null()) {
-							_set_error("Constant isn't a class: " + ident, p_class->line);
-							return;
-						}
-						find_subclass = new_base_class;
-					} else {
-						_set_error("Couldn't find the subclass: " + ident, p_class->line);
-						return;
-					}
-				}
-
-				script = find_subclass;
-
-			} else if (!base_class) {
-				if (p_class->extends_class.size() > 1) {
-					_set_error("Invalid inheritance (unknown class + subclasses).", p_class->line);
-					return;
-				}
-				//if not found, try engine classes
-				if (!GDScriptLanguage::get_singleton()->get_global_map().has(base)) {
-					_set_error("Unknown class: \"" + base + "\"", p_class->line);
-					return;
-				}
-
-				native = base;
-			}
-		}
-
-		if (base_class) {
-			p_class->base_type.has_type = true;
-			p_class->base_type.kind = DataType::CLASS;
-			p_class->base_type.class_type = base_class;
-		} else if (script.is_valid()) {
-			p_class->base_type.has_type = true;
-			p_class->base_type.kind = DataType::GDSCRIPT;
-			p_class->base_type.script_type = script;
-			p_class->base_type.native_type = script->get_instance_base_type();
-		} else if (native != StringName()) {
-			p_class->base_type.has_type = true;
-			p_class->base_type.kind = DataType::NATIVE;
-			p_class->base_type.native_type = native;
-		} else {
-			_set_error("Couldn't determine inheritance.", p_class->line);
-			return;
-		}
-
-	} else {
-		// without extends, implicitly extend Reference
-		p_class->base_type.has_type = true;
-		p_class->base_type.kind = DataType::NATIVE;
-		p_class->base_type.native_type = "Reference";
-	}
-
-	if (p_recursive) {
-		// Recursively determine subclasses
-		for (int i = 0; i < p_class->subclasses.size(); i++) {
-			_determine_inheritance(p_class->subclasses[i], p_recursive);
-		}
-	}
-}
-
-String GDScriptParser::DataType::to_string() const {
-	if (!has_type) {
-		return "var";
-	}
-	switch (kind) {
-		case BUILTIN: {
-			if (builtin_type == Variant::NIL) {
-				return "null";
-			}
-			return Variant::get_type_name(builtin_type);
-		} break;
-		case NATIVE: {
-			if (is_meta_type) {
-				return "GDScriptNativeClass";
-			}
-			return native_type.operator String();
-		} break;
-
-		case GDSCRIPT: {
-			Ref<GDScript> gds = script_type;
-			const String &gds_class = gds->get_script_class_name();
-			if (!gds_class.empty()) {
-				return gds_class;
-			}
-			[[fallthrough]];
-		}
-		case SCRIPT: {
-			if (is_meta_type) {
-				return script_type->get_class_name().operator String();
-			}
-			String name = script_type->get_name();
-			if (name != String()) {
-				return name;
-			}
-			name = script_type->get_path().get_file();
-			if (name != String()) {
-				return name;
-			}
-			return native_type.operator String();
-		} break;
-		case CLASS: {
-			ERR_FAIL_COND_V(!class_type, String());
-			if (is_meta_type) {
-				return "GDScript";
-			}
-			if (class_type->name == StringName()) {
-				return "self";
-			}
-			return class_type->name.operator String();
-		} break;
-		case UNRESOLVED: {
-		} break;
-	}
-
-	return "Unresolved";
-}
-
-bool GDScriptParser::_parse_type(DataType &r_type, bool p_can_be_void) {
-	tokenizer->advance();
-	r_type.has_type = true;
-
-	bool finished = false;
-	bool can_index = false;
-	String full_name;
-
-	if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) {
-		completion_cursor = StringName();
-		completion_type = COMPLETION_TYPE_HINT;
-		completion_class = current_class;
-		completion_function = current_function;
-		completion_line = tokenizer->get_token_line();
-		completion_argument = 0;
-		completion_block = current_block;
-		completion_found = true;
-		completion_ident_is_call = p_can_be_void;
-		tokenizer->advance();
-	}
-
-	switch (tokenizer->get_token()) {
-		case GDScriptTokenizer::TK_PR_VOID: {
-			if (!p_can_be_void) {
-				return false;
-			}
-			r_type.kind = DataType::BUILTIN;
-			r_type.builtin_type = Variant::NIL;
-		} break;
-		case GDScriptTokenizer::TK_BUILT_IN_TYPE: {
-			r_type.builtin_type = tokenizer->get_token_type();
-			if (tokenizer->get_token_type() == Variant::OBJECT) {
-				r_type.kind = DataType::NATIVE;
-				r_type.native_type = "Object";
-			} else {
-				r_type.kind = DataType::BUILTIN;
-			}
-		} break;
-		case GDScriptTokenizer::TK_IDENTIFIER: {
-			r_type.native_type = tokenizer->get_token_identifier();
-			if (ClassDB::class_exists(r_type.native_type) || ClassDB::class_exists("_" + r_type.native_type.operator String())) {
-				r_type.kind = DataType::NATIVE;
-			} else {
-				r_type.kind = DataType::UNRESOLVED;
-				can_index = true;
-				full_name = r_type.native_type;
-			}
-		} break;
-		default: {
-			return false;
-		}
-	}
-
-	tokenizer->advance();
-
-	if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) {
-		completion_cursor = r_type.native_type;
-		completion_type = COMPLETION_TYPE_HINT;
-		completion_class = current_class;
-		completion_function = current_function;
-		completion_line = tokenizer->get_token_line();
-		completion_argument = 0;
-		completion_block = current_block;
-		completion_found = true;
-		completion_ident_is_call = p_can_be_void;
-		tokenizer->advance();
-	}
-
-	if (can_index) {
-		while (!finished) {
-			switch (tokenizer->get_token()) {
-				case GDScriptTokenizer::TK_PERIOD: {
-					if (!can_index) {
-						_set_error("Unexpected \".\".");
-						return false;
-					}
-					can_index = false;
-					tokenizer->advance();
-				} break;
-				case GDScriptTokenizer::TK_IDENTIFIER: {
-					if (can_index) {
-						_set_error("Unexpected identifier.");
-						return false;
-					}
-
-					StringName id;
-					bool has_completion = _get_completable_identifier(COMPLETION_TYPE_HINT_INDEX, id);
-					if (id == StringName()) {
-						id = "@temp";
-					}
-
-					full_name += "." + id.operator String();
-					can_index = true;
-					if (has_completion) {
-						completion_cursor = full_name;
-					}
-				} break;
-				default: {
-					finished = true;
-				} break;
-			}
-		}
-
-		if (tokenizer->get_token(-1) == GDScriptTokenizer::TK_PERIOD) {
-			_set_error("Expected a subclass identifier.");
-			return false;
-		}
-
-		r_type.native_type = full_name;
-	}
-
-	return true;
-}
-
-GDScriptParser::DataType GDScriptParser::_resolve_type(const DataType &p_source, int p_line) {
-	if (!p_source.has_type) {
-		return p_source;
-	}
-	if (p_source.kind != DataType::UNRESOLVED) {
-		return p_source;
-	}
-
-	Vector<String> full_name = p_source.native_type.operator String().split(".", false);
-	int name_part = 0;
-
-	DataType result;
-	result.has_type = true;
-
-	while (name_part < full_name.size()) {
-		bool found = false;
-		StringName id = full_name[name_part];
-		DataType base_type = result;
-
-		ClassNode *p = nullptr;
-		if (name_part == 0) {
-			if (ScriptServer::is_global_class(id)) {
-				String script_path = ScriptServer::get_global_class_path(id);
-				if (script_path == self_path) {
-					result.kind = DataType::CLASS;
-					result.class_type = static_cast<ClassNode *>(head);
-				} else {
-					Ref<Script> script = ResourceLoader::load(script_path);
-					Ref<GDScript> gds = script;
-					if (gds.is_valid()) {
-						if (!gds->is_valid()) {
-							_set_error("The class \"" + id + "\" couldn't be fully loaded (script error or cyclic dependency).", p_line);
-							return DataType();
-						}
-						result.kind = DataType::GDSCRIPT;
-						result.script_type = gds;
-					} else if (script.is_valid()) {
-						result.kind = DataType::SCRIPT;
-						result.script_type = script;
-					} else {
-						_set_error("The class \"" + id + "\" was found in global scope, but its script couldn't be loaded.", p_line);
-						return DataType();
-					}
-				}
-				name_part++;
-				continue;
-			}
-			List<PropertyInfo> props;
-			ProjectSettings::get_singleton()->get_property_list(&props);
-			String singleton_path;
-			for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
-				String s = E->get().name;
-				if (!s.begins_with("autoload/")) {
-					continue;
-				}
-				String name = s.get_slice("/", 1);
-				if (name == id) {
-					singleton_path = ProjectSettings::get_singleton()->get(s);
-					if (singleton_path.begins_with("*")) {
-						singleton_path = singleton_path.right(1);
-					}
-					if (!singleton_path.begins_with("res://")) {
-						singleton_path = "res://" + singleton_path;
-					}
-					break;
-				}
-			}
-			if (!singleton_path.empty()) {
-				Ref<Script> script = ResourceLoader::load(singleton_path);
-				Ref<GDScript> gds = script;
-				if (gds.is_valid()) {
-					if (!gds->is_valid()) {
-						_set_error("Class '" + id + "' could not be fully loaded (script error or cyclic inheritance).", p_line);
-						return DataType();
-					}
-					result.kind = DataType::GDSCRIPT;
-					result.script_type = gds;
-				} else if (script.is_valid()) {
-					result.kind = DataType::SCRIPT;
-					result.script_type = script;
-				} else {
-					_set_error("Couldn't fully load singleton script '" + id + "' (possible cyclic reference or parse error).", p_line);
-					return DataType();
-				}
-				name_part++;
-				continue;
-			}
-
-			p = current_class;
-		} else if (base_type.kind == DataType::CLASS) {
-			p = base_type.class_type;
-		}
-		while (p) {
-			if (p->constant_expressions.has(id)) {
-				if (p->constant_expressions[id].expression->type != Node::TYPE_CONSTANT) {
-					_set_error("Parser bug: unresolved constant.", p_line);
-					ERR_FAIL_V(result);
-				}
-				const ConstantNode *cn = static_cast<const ConstantNode *>(p->constant_expressions[id].expression);
-				Ref<GDScript> gds = cn->value;
-				if (gds.is_valid()) {
-					result.kind = DataType::GDSCRIPT;
-					result.script_type = gds;
-					found = true;
-				} else {
-					Ref<Script> scr = cn->value;
-					if (scr.is_valid()) {
-						result.kind = DataType::SCRIPT;
-						result.script_type = scr;
-						found = true;
-					}
-				}
-				break;
-			}
-
-			// Inner classes
-			ClassNode *outer_class = p;
-			while (outer_class) {
-				if (outer_class->name == id) {
-					found = true;
-					result.kind = DataType::CLASS;
-					result.class_type = outer_class;
-					break;
-				}
-				for (int i = 0; i < outer_class->subclasses.size(); i++) {
-					if (outer_class->subclasses[i] == p) {
-						continue;
-					}
-					if (outer_class->subclasses[i]->name == id) {
-						found = true;
-						result.kind = DataType::CLASS;
-						result.class_type = outer_class->subclasses[i];
-						break;
-					}
-				}
-				if (found) {
-					break;
-				}
-				outer_class = outer_class->owner;
-			}
-
-			if (!found && p->base_type.kind == DataType::CLASS) {
-				p = p->base_type.class_type;
-			} else {
-				base_type = p->base_type;
-				break;
-			}
-		}
-
-		// Still look for class constants in parent scripts
-		if (!found && (base_type.kind == DataType::GDSCRIPT || base_type.kind == DataType::SCRIPT)) {
-			Ref<Script> scr = base_type.script_type;
-			ERR_FAIL_COND_V(scr.is_null(), result);
-			while (scr.is_valid()) {
-				Map<StringName, Variant> constants;
-				scr->get_constants(&constants);
-
-				if (constants.has(id)) {
-					Ref<GDScript> gds = constants[id];
-
-					if (gds.is_valid()) {
-						result.kind = DataType::GDSCRIPT;
-						result.script_type = gds;
-						found = true;
-					} else {
-						Ref<Script> scr2 = constants[id];
-						if (scr2.is_valid()) {
-							result.kind = DataType::SCRIPT;
-							result.script_type = scr2;
-							found = true;
-						}
-					}
-				}
-				if (found) {
-					break;
-				} else {
-					scr = scr->get_base_script();
-				}
-			}
-		}
-
-		if (!found && !for_completion) {
-			String base;
-			if (name_part == 0) {
-				base = "self";
-			} else {
-				base = result.to_string();
-			}
-			_set_error("The identifier \"" + String(id) + "\" isn't a valid type (not a script or class), or couldn't be found on base \"" +
-							   base + "\".",
-					p_line);
-			return DataType();
-		}
-
-		name_part++;
-	}
-
-	return result;
-}
-
-GDScriptParser::DataType GDScriptParser::_type_from_variant(const Variant &p_value) const {
-	DataType result;
-	result.has_type = true;
-	result.is_constant = true;
-	result.kind = DataType::BUILTIN;
-	result.builtin_type = p_value.get_type();
-
-	if (result.builtin_type == Variant::OBJECT) {
-		Object *obj = p_value.operator Object *();
-		if (!obj) {
-			return DataType();
-		}
-		result.native_type = obj->get_class_name();
-		Ref<Script> scr = p_value;
-		if (scr.is_valid()) {
-			result.is_meta_type = true;
-		} else {
-			result.is_meta_type = false;
-			scr = obj->get_script();
-		}
-		if (scr.is_valid()) {
-			result.script_type = scr;
-			Ref<GDScript> gds = scr;
-			if (gds.is_valid()) {
-				result.kind = DataType::GDSCRIPT;
-			} else {
-				result.kind = DataType::SCRIPT;
-			}
-			result.native_type = scr->get_instance_base_type();
-		} else {
-			result.kind = DataType::NATIVE;
-		}
-	}
-
-	return result;
-}
-
-GDScriptParser::DataType GDScriptParser::_type_from_property(const PropertyInfo &p_property, bool p_nil_is_variant) const {
-	DataType ret;
-	if (p_property.type == Variant::NIL && (p_nil_is_variant || (p_property.usage & PROPERTY_USAGE_NIL_IS_VARIANT))) {
-		// Variant
-		return ret;
-	}
-	ret.has_type = true;
-	ret.builtin_type = p_property.type;
-	if (p_property.type == Variant::OBJECT) {
-		ret.kind = DataType::NATIVE;
-		ret.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name;
-	} else {
-		ret.kind = DataType::BUILTIN;
-	}
-	return ret;
-}
-
-GDScriptParser::DataType GDScriptParser::_type_from_gdtype(const GDScriptDataType &p_gdtype) const {
-	DataType result;
-	if (!p_gdtype.has_type) {
-		return result;
-	}
-
-	result.has_type = true;
-	result.builtin_type = p_gdtype.builtin_type;
-	result.native_type = p_gdtype.native_type;
-	result.script_type = p_gdtype.script_type;
-
-	switch (p_gdtype.kind) {
-		case GDScriptDataType::UNINITIALIZED: {
-			ERR_PRINT("Uninitialized datatype. Please report a bug.");
-		} break;
-		case GDScriptDataType::BUILTIN: {
-			result.kind = DataType::BUILTIN;
-		} break;
-		case GDScriptDataType::NATIVE: {
-			result.kind = DataType::NATIVE;
-		} break;
-		case GDScriptDataType::GDSCRIPT: {
-			result.kind = DataType::GDSCRIPT;
-		} break;
-		case GDScriptDataType::SCRIPT: {
-			result.kind = DataType::SCRIPT;
-		} break;
-	}
-	return result;
-}
-
-GDScriptParser::DataType GDScriptParser::_get_operation_type(const Variant::Operator p_op, const DataType &p_a, const DataType &p_b, bool &r_valid) const {
-	if (!p_a.has_type || !p_b.has_type) {
-		r_valid = true;
-		return DataType();
-	}
-
-	Variant::Type a_type = p_a.kind == DataType::BUILTIN ? p_a.builtin_type : Variant::OBJECT;
-	Variant::Type b_type = p_b.kind == DataType::BUILTIN ? p_b.builtin_type : Variant::OBJECT;
-
-	Variant a;
-	REF a_ref;
-	if (a_type == Variant::OBJECT) {
-		a_ref.instance();
-		a = a_ref;
-	} else {
-		Callable::CallError err;
-		a = Variant::construct(a_type, nullptr, 0, err);
-		if (err.error != Callable::CallError::CALL_OK) {
-			r_valid = false;
-			return DataType();
-		}
-	}
-	Variant b;
-	REF b_ref;
-	if (b_type == Variant::OBJECT) {
-		b_ref.instance();
-		b = b_ref;
-	} else {
-		Callable::CallError err;
-		b = Variant::construct(b_type, nullptr, 0, err);
-		if (err.error != Callable::CallError::CALL_OK) {
-			r_valid = false;
-			return DataType();
-		}
-	}
-
-	// Avoid division by zero
-	if (a_type == Variant::INT || a_type == Variant::FLOAT) {
-		Variant::evaluate(Variant::OP_ADD, a, 1, a, r_valid);
-	}
-	if (b_type == Variant::INT || b_type == Variant::FLOAT) {
-		Variant::evaluate(Variant::OP_ADD, b, 1, b, r_valid);
-	}
-	if (a_type == Variant::STRING && b_type != Variant::ARRAY) {
-		a = "%s"; // Work around for formatting operator (%)
-	}
-
-	Variant ret;
-	Variant::evaluate(p_op, a, b, ret, r_valid);
-
-	if (r_valid) {
-		return _type_from_variant(ret);
-	}
-
-	return DataType();
-}
-
-Variant::Operator GDScriptParser::_get_variant_operation(const OperatorNode::Operator &p_op) const {
-	switch (p_op) {
-		case OperatorNode::OP_NEG: {
-			return Variant::OP_NEGATE;
-		} break;
-		case OperatorNode::OP_POS: {
-			return Variant::OP_POSITIVE;
-		} break;
-		case OperatorNode::OP_NOT: {
-			return Variant::OP_NOT;
-		} break;
-		case OperatorNode::OP_BIT_INVERT: {
-			return Variant::OP_BIT_NEGATE;
-		} break;
-		case OperatorNode::OP_IN: {
-			return Variant::OP_IN;
-		} break;
-		case OperatorNode::OP_EQUAL: {
-			return Variant::OP_EQUAL;
-		} break;
-		case OperatorNode::OP_NOT_EQUAL: {
-			return Variant::OP_NOT_EQUAL;
-		} break;
-		case OperatorNode::OP_LESS: {
-			return Variant::OP_LESS;
-		} break;
-		case OperatorNode::OP_LESS_EQUAL: {
-			return Variant::OP_LESS_EQUAL;
-		} break;
-		case OperatorNode::OP_GREATER: {
-			return Variant::OP_GREATER;
-		} break;
-		case OperatorNode::OP_GREATER_EQUAL: {
-			return Variant::OP_GREATER_EQUAL;
-		} break;
-		case OperatorNode::OP_AND: {
-			return Variant::OP_AND;
-		} break;
-		case OperatorNode::OP_OR: {
-			return Variant::OP_OR;
-		} break;
-		case OperatorNode::OP_ASSIGN_ADD:
-		case OperatorNode::OP_ADD: {
-			return Variant::OP_ADD;
-		} break;
-		case OperatorNode::OP_ASSIGN_SUB:
-		case OperatorNode::OP_SUB: {
-			return Variant::OP_SUBTRACT;
-		} break;
-		case OperatorNode::OP_ASSIGN_MUL:
-		case OperatorNode::OP_MUL: {
-			return Variant::OP_MULTIPLY;
-		} break;
-		case OperatorNode::OP_ASSIGN_DIV:
-		case OperatorNode::OP_DIV: {
-			return Variant::OP_DIVIDE;
-		} break;
-		case OperatorNode::OP_ASSIGN_MOD:
-		case OperatorNode::OP_MOD: {
-			return Variant::OP_MODULE;
-		} break;
-		case OperatorNode::OP_ASSIGN_BIT_AND:
-		case OperatorNode::OP_BIT_AND: {
-			return Variant::OP_BIT_AND;
-		} break;
-		case OperatorNode::OP_ASSIGN_BIT_OR:
-		case OperatorNode::OP_BIT_OR: {
-			return Variant::OP_BIT_OR;
-		} break;
-		case OperatorNode::OP_ASSIGN_BIT_XOR:
-		case OperatorNode::OP_BIT_XOR: {
-			return Variant::OP_BIT_XOR;
-		} break;
-		case OperatorNode::OP_ASSIGN_SHIFT_LEFT:
-		case OperatorNode::OP_SHIFT_LEFT: {
-			return Variant::OP_SHIFT_LEFT;
-		}
-		case OperatorNode::OP_ASSIGN_SHIFT_RIGHT:
-		case OperatorNode::OP_SHIFT_RIGHT: {
-			return Variant::OP_SHIFT_RIGHT;
-		}
-		default: {
-			return Variant::OP_MAX;
-		} break;
-	}
-}
-
-bool GDScriptParser::_is_type_compatible(const DataType &p_container, const DataType &p_expression, bool p_allow_implicit_conversion) const {
-	// Ignore for completion
-	if (!check_types || for_completion) {
-		return true;
-	}
-	// Can't test if not all have type
-	if (!p_container.has_type || !p_expression.has_type) {
-		return true;
-	}
-
-	// Should never get here unresolved
-	ERR_FAIL_COND_V(p_container.kind == DataType::UNRESOLVED, false);
-	ERR_FAIL_COND_V(p_expression.kind == DataType::UNRESOLVED, false);
-
-	if (p_container.kind == DataType::BUILTIN && p_expression.kind == DataType::BUILTIN) {
-		bool valid = p_container.builtin_type == p_expression.builtin_type;
-		if (p_allow_implicit_conversion) {
-			valid = valid || Variant::can_convert_strict(p_expression.builtin_type, p_container.builtin_type);
-		}
-		return valid;
-	}
-
-	if (p_container.kind == DataType::BUILTIN && p_container.builtin_type == Variant::OBJECT) {
-		// Object built-in is a special case, it's compatible with any object and with null
-		if (p_expression.kind == DataType::BUILTIN) {
-			return p_expression.builtin_type == Variant::NIL;
-		}
-		// If it's not a built-in, must be an object
-		return true;
-	}
-
-	if (p_container.kind == DataType::BUILTIN || (p_expression.kind == DataType::BUILTIN && p_expression.builtin_type != Variant::NIL)) {
-		// Can't mix built-ins with objects
-		return false;
-	}
-
-	// From now on everything is objects, check polymorphism
-	// The container must be the same class or a superclass of the expression
-
-	if (p_expression.kind == DataType::BUILTIN && p_expression.builtin_type == Variant::NIL) {
-		// Null can be assigned to object types
-		return true;
-	}
-
-	StringName expr_native;
-	Ref<Script> expr_script;
-	ClassNode *expr_class = nullptr;
-
-	switch (p_expression.kind) {
-		case DataType::NATIVE: {
-			if (p_container.kind != DataType::NATIVE) {
-				// Non-native type can't be a superclass of a native type
-				return false;
-			}
-			if (p_expression.is_meta_type) {
-				expr_native = GDScriptNativeClass::get_class_static();
-			} else {
-				expr_native = p_expression.native_type;
-			}
-		} break;
-		case DataType::SCRIPT:
-		case DataType::GDSCRIPT: {
-			if (p_container.kind == DataType::CLASS) {
-				// This cannot be resolved without cyclic dependencies, so just bail out
-				return false;
-			}
-			if (p_expression.is_meta_type) {
-				expr_native = p_expression.script_type->get_class_name();
-			} else {
-				expr_script = p_expression.script_type;
-				expr_native = expr_script->get_instance_base_type();
-			}
-		} break;
-		case DataType::CLASS: {
-			if (p_expression.is_meta_type) {
-				expr_native = GDScript::get_class_static();
-			} else {
-				expr_class = p_expression.class_type;
-				ClassNode *base = expr_class;
-				while (base->base_type.kind == DataType::CLASS) {
-					base = base->base_type.class_type;
-				}
-				expr_native = base->base_type.native_type;
-				expr_script = base->base_type.script_type;
-			}
-		} break;
-		case DataType::BUILTIN: // Already handled above
-		case DataType::UNRESOLVED: // Not allowed, see above
-			break;
-	}
-
-	// Some classes are prefixed with `_` internally
-	if (!ClassDB::class_exists(expr_native)) {
-		expr_native = "_" + expr_native;
-	}
-
-	switch (p_container.kind) {
-		case DataType::NATIVE: {
-			if (p_container.is_meta_type) {
-				return ClassDB::is_parent_class(expr_native, GDScriptNativeClass::get_class_static());
-			} else {
-				StringName container_native = ClassDB::class_exists(p_container.native_type) ? p_container.native_type : StringName("_" + p_container.native_type);
-				return ClassDB::is_parent_class(expr_native, container_native);
-			}
-		} break;
-		case DataType::SCRIPT:
-		case DataType::GDSCRIPT: {
-			if (p_container.is_meta_type) {
-				return ClassDB::is_parent_class(expr_native, GDScript::get_class_static());
-			}
-			if (expr_class == head && p_container.script_type->get_path() == self_path) {
-				// Special case: container is self script and expression is self
-				return true;
-			}
-			while (expr_script.is_valid()) {
-				if (expr_script == p_container.script_type) {
-					return true;
-				}
-				expr_script = expr_script->get_base_script();
-			}
-			return false;
-		} break;
-		case DataType::CLASS: {
-			if (p_container.is_meta_type) {
-				return ClassDB::is_parent_class(expr_native, GDScript::get_class_static());
-			}
-			if (p_container.class_type == head && expr_script.is_valid() && expr_script->get_path() == self_path) {
-				// Special case: container is self and expression is self script
-				return true;
-			}
-			while (expr_class) {
-				if (expr_class == p_container.class_type) {
-					return true;
-				}
-				expr_class = expr_class->base_type.class_type;
-			}
-			return false;
-		} break;
-		case DataType::BUILTIN: // Already handled above
-		case DataType::UNRESOLVED: // Not allowed, see above
-			break;
-	}
-
-	return false;
-}
-
-GDScriptParser::Node *GDScriptParser::_get_default_value_for_type(const DataType &p_type, int p_line) {
-	Node *result;
-
-	if (p_type.has_type && p_type.kind == DataType::BUILTIN && p_type.builtin_type != Variant::NIL && p_type.builtin_type != Variant::OBJECT) {
-		if (p_type.builtin_type == Variant::ARRAY) {
-			result = alloc_node<ArrayNode>();
-		} else if (p_type.builtin_type == Variant::DICTIONARY) {
-			result = alloc_node<DictionaryNode>();
-		} else {
-			ConstantNode *c = alloc_node<ConstantNode>();
-			Callable::CallError err;
-			c->value = Variant::construct(p_type.builtin_type, nullptr, 0, err);
-			c->datatype = _type_from_variant(c->value);
-			result = c;
-		}
-	} else {
-		ConstantNode *c = alloc_node<ConstantNode>();
-		c->value = Variant();
-		c->datatype = _type_from_variant(c->value);
-		result = c;
-	}
-
-	result->line = p_line;
-
-	return result;
-}
-
-GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
-#ifdef DEBUG_ENABLED
-	if (p_node->get_datatype().has_type && p_node->type != Node::TYPE_ARRAY && p_node->type != Node::TYPE_DICTIONARY) {
-#else
-	if (p_node->get_datatype().has_type) {
-#endif
-		return p_node->get_datatype();
-	}
-
-	DataType node_type;
-
-	switch (p_node->type) {
-		case Node::TYPE_CONSTANT: {
-			node_type = _type_from_variant(static_cast<ConstantNode *>(p_node)->value);
-		} break;
-		case Node::TYPE_TYPE: {
-			TypeNode *tn = static_cast<TypeNode *>(p_node);
-			node_type.has_type = true;
-			node_type.is_meta_type = true;
-			node_type.kind = DataType::BUILTIN;
-			node_type.builtin_type = tn->vtype;
-		} break;
-		case Node::TYPE_ARRAY: {
-			node_type.has_type = true;
-			node_type.kind = DataType::BUILTIN;
-			node_type.builtin_type = Variant::ARRAY;
-#ifdef DEBUG_ENABLED
-			// Check stuff inside the array
-			ArrayNode *an = static_cast<ArrayNode *>(p_node);
-			for (int i = 0; i < an->elements.size(); i++) {
-				_reduce_node_type(an->elements[i]);
-			}
-#endif // DEBUG_ENABLED
-		} break;
-		case Node::TYPE_DICTIONARY: {
-			node_type.has_type = true;
-			node_type.kind = DataType::BUILTIN;
-			node_type.builtin_type = Variant::DICTIONARY;
-#ifdef DEBUG_ENABLED
-			// Check stuff inside the dictionarty
-			DictionaryNode *dn = static_cast<DictionaryNode *>(p_node);
-			for (int i = 0; i < dn->elements.size(); i++) {
-				_reduce_node_type(dn->elements[i].key);
-				_reduce_node_type(dn->elements[i].value);
-			}
-#endif // DEBUG_ENABLED
-		} break;
-		case Node::TYPE_SELF: {
-			node_type.has_type = true;
-			node_type.kind = DataType::CLASS;
-			node_type.class_type = current_class;
-			node_type.is_constant = true;
-		} break;
-		case Node::TYPE_IDENTIFIER: {
-			IdentifierNode *id = static_cast<IdentifierNode *>(p_node);
-			if (id->declared_block) {
-				node_type = id->declared_block->variables[id->name]->get_datatype();
-				id->declared_block->variables[id->name]->usages += 1;
-			} else if (id->name == "#match_value") {
-				// It's a special id just for the match statetement, ignore
-				break;
-			} else if (current_function && current_function->arguments.find(id->name) >= 0) {
-				int idx = current_function->arguments.find(id->name);
-				node_type = current_function->argument_types[idx];
-			} else {
-				node_type = _reduce_identifier_type(nullptr, id->name, id->line, false);
-			}
-		} break;
-		case Node::TYPE_CAST: {
-			CastNode *cn = static_cast<CastNode *>(p_node);
-
-			DataType source_type = _reduce_node_type(cn->source_node);
-			cn->cast_type = _resolve_type(cn->cast_type, cn->line);
-			if (source_type.has_type) {
-				bool valid = false;
-				if (check_types) {
-					if (cn->cast_type.kind == DataType::BUILTIN && source_type.kind == DataType::BUILTIN) {
-						valid = Variant::can_convert(source_type.builtin_type, cn->cast_type.builtin_type);
-					}
-					if (cn->cast_type.kind != DataType::BUILTIN && source_type.kind != DataType::BUILTIN) {
-						valid = _is_type_compatible(cn->cast_type, source_type) || _is_type_compatible(source_type, cn->cast_type);
-					}
-
-					if (!valid) {
-						_set_error("Invalid cast. Cannot convert from \"" + source_type.to_string() +
-										   "\" to \"" + cn->cast_type.to_string() + "\".",
-								cn->line);
-						return DataType();
-					}
-				}
-			} else {
-#ifdef DEBUG_ENABLED
-				_add_warning(GDScriptWarning::UNSAFE_CAST, cn->line, cn->cast_type.to_string());
-#endif // DEBUG_ENABLED
-				_mark_line_as_unsafe(cn->line);
-			}
-
-			node_type = cn->cast_type;
-
-		} break;
-		case Node::TYPE_OPERATOR: {
-			OperatorNode *op = static_cast<OperatorNode *>(p_node);
-
-			switch (op->op) {
-				case OperatorNode::OP_CALL:
-				case OperatorNode::OP_PARENT_CALL: {
-					node_type = _reduce_function_call_type(op);
-				} break;
-				case OperatorNode::OP_YIELD: {
-					if (op->arguments.size() == 2) {
-						DataType base_type = _reduce_node_type(op->arguments[0]);
-						DataType signal_type = _reduce_node_type(op->arguments[1]);
-						// TODO: Check if signal exists when it's a constant
-						if (base_type.has_type && base_type.kind == DataType::BUILTIN && base_type.builtin_type != Variant::NIL && base_type.builtin_type != Variant::OBJECT) {
-							_set_error("The first argument of \"yield()\" must be an object.", op->line);
-							return DataType();
-						}
-						if (signal_type.has_type && (signal_type.kind != DataType::BUILTIN || signal_type.builtin_type != Variant::STRING)) {
-							_set_error("The second argument of \"yield()\" must be a string.", op->line);
-							return DataType();
-						}
-					}
-					// yield can return anything
-					node_type.has_type = false;
-				} break;
-				case OperatorNode::OP_IS:
-				case OperatorNode::OP_IS_BUILTIN: {
-					if (op->arguments.size() != 2) {
-						_set_error("Parser bug: binary operation without 2 arguments.", op->line);
-						ERR_FAIL_V(DataType());
-					}
-
-					DataType value_type = _reduce_node_type(op->arguments[0]);
-					DataType type_type = _reduce_node_type(op->arguments[1]);
-
-					if (check_types && type_type.has_type) {
-						if (!type_type.is_meta_type && (type_type.kind != DataType::NATIVE || !ClassDB::is_parent_class(type_type.native_type, "Script"))) {
-							_set_error("Invalid \"is\" test: the right operand isn't a type (neither a native type nor a script).", op->line);
-							return DataType();
-						}
-						type_type.is_meta_type = false; // Test the actual type
-						if (!_is_type_compatible(type_type, value_type) && !_is_type_compatible(value_type, type_type)) {
-							if (op->op == OperatorNode::OP_IS) {
-								_set_error("A value of type \"" + value_type.to_string() + "\" will never be an instance of \"" + type_type.to_string() + "\".", op->line);
-							} else {
-								_set_error("A value of type \"" + value_type.to_string() + "\" will never be of type \"" + type_type.to_string() + "\".", op->line);
-							}
-							return DataType();
-						}
-					}
-
-					node_type.has_type = true;
-					node_type.is_constant = true;
-					node_type.is_meta_type = false;
-					node_type.kind = DataType::BUILTIN;
-					node_type.builtin_type = Variant::BOOL;
-				} break;
-				// Unary operators
-				case OperatorNode::OP_NEG:
-				case OperatorNode::OP_POS:
-				case OperatorNode::OP_NOT:
-				case OperatorNode::OP_BIT_INVERT: {
-					DataType argument_type = _reduce_node_type(op->arguments[0]);
-					if (!argument_type.has_type) {
-						break;
-					}
-
-					Variant::Operator var_op = _get_variant_operation(op->op);
-					bool valid = false;
-					node_type = _get_operation_type(var_op, argument_type, argument_type, valid);
-
-					if (check_types && !valid) {
-						_set_error("Invalid operand type (\"" + argument_type.to_string() +
-										   "\") to unary operator \"" + Variant::get_operator_name(var_op) + "\".",
-								op->line, op->column);
-						return DataType();
-					}
-
-				} break;
-				// Binary operators
-				case OperatorNode::OP_IN:
-				case OperatorNode::OP_EQUAL:
-				case OperatorNode::OP_NOT_EQUAL:
-				case OperatorNode::OP_LESS:
-				case OperatorNode::OP_LESS_EQUAL:
-				case OperatorNode::OP_GREATER:
-				case OperatorNode::OP_GREATER_EQUAL:
-				case OperatorNode::OP_AND:
-				case OperatorNode::OP_OR:
-				case OperatorNode::OP_ADD:
-				case OperatorNode::OP_SUB:
-				case OperatorNode::OP_MUL:
-				case OperatorNode::OP_DIV:
-				case OperatorNode::OP_MOD:
-				case OperatorNode::OP_SHIFT_LEFT:
-				case OperatorNode::OP_SHIFT_RIGHT:
-				case OperatorNode::OP_BIT_AND:
-				case OperatorNode::OP_BIT_OR:
-				case OperatorNode::OP_BIT_XOR: {
-					if (op->arguments.size() != 2) {
-						_set_error("Parser bug: binary operation without 2 arguments.", op->line);
-						ERR_FAIL_V(DataType());
-					}
-
-					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;
-					}
-
-					Variant::Operator var_op = _get_variant_operation(op->op);
-					bool valid = false;
-					node_type = _get_operation_type(var_op, argument_a_type, argument_b_type, valid);
-
-					if (check_types && !valid) {
-						_set_error("Invalid operand types (\"" + argument_a_type.to_string() + "\" and \"" +
-										   argument_b_type.to_string() + "\") to operator \"" + Variant::get_operator_name(var_op) + "\".",
-								op->line, op->column);
-						return DataType();
-					}
-#ifdef DEBUG_ENABLED
-					if (var_op == Variant::OP_DIVIDE && argument_a_type.kind == DataType::BUILTIN && argument_a_type.builtin_type == Variant::INT &&
-							argument_b_type.kind == DataType::BUILTIN && argument_b_type.builtin_type == Variant::INT) {
-						_add_warning(GDScriptWarning::INTEGER_DIVISION, op->line);
-					}
-#endif // DEBUG_ENABLED
-
-				} break;
-				// Ternary operators
-				case OperatorNode::OP_TERNARY_IF: {
-					if (op->arguments.size() != 3) {
-						_set_error("Parser bug: ternary operation without 3 arguments.");
-						ERR_FAIL_V(DataType());
-					}
-
-					DataType true_type = _reduce_node_type(op->arguments[1]);
-					DataType false_type = _reduce_node_type(op->arguments[2]);
-					// Check arguments[0] errors.
-					_reduce_node_type(op->arguments[0]);
-
-					// If types are equal, then the expression is of the same type
-					// If they are compatible, return the broader type
-					if (true_type == false_type || _is_type_compatible(true_type, false_type)) {
-						node_type = true_type;
-					} else if (_is_type_compatible(false_type, true_type)) {
-						node_type = false_type;
-					} else {
-#ifdef DEBUG_ENABLED
-						_add_warning(GDScriptWarning::INCOMPATIBLE_TERNARY, op->line);
-#endif // DEBUG_ENABLED
-					}
-				} break;
-				// Assignment should never happen within an expression
-				case OperatorNode::OP_ASSIGN:
-				case OperatorNode::OP_ASSIGN_ADD:
-				case OperatorNode::OP_ASSIGN_SUB:
-				case OperatorNode::OP_ASSIGN_MUL:
-				case OperatorNode::OP_ASSIGN_DIV:
-				case OperatorNode::OP_ASSIGN_MOD:
-				case OperatorNode::OP_ASSIGN_SHIFT_LEFT:
-				case OperatorNode::OP_ASSIGN_SHIFT_RIGHT:
-				case OperatorNode::OP_ASSIGN_BIT_AND:
-				case OperatorNode::OP_ASSIGN_BIT_OR:
-				case OperatorNode::OP_ASSIGN_BIT_XOR:
-				case OperatorNode::OP_INIT_ASSIGN: {
-					_set_error("Assignment inside an expression isn't allowed (parser bug?).", op->line);
-					return DataType();
-
-				} break;
-				case OperatorNode::OP_INDEX_NAMED: {
-					if (op->arguments.size() != 2) {
-						_set_error("Parser bug: named index with invalid arguments.", op->line);
-						ERR_FAIL_V(DataType());
-					}
-					if (op->arguments[1]->type != Node::TYPE_IDENTIFIER) {
-						_set_error("Parser bug: named index without identifier argument.", op->line);
-						ERR_FAIL_V(DataType());
-					}
-
-					DataType base_type = _reduce_node_type(op->arguments[0]);
-					IdentifierNode *member_id = static_cast<IdentifierNode *>(op->arguments[1]);
-
-					if (base_type.has_type) {
-						if (check_types && base_type.kind == DataType::BUILTIN) {
-							// Variant type, just test if it's possible
-							DataType result;
-							switch (base_type.builtin_type) {
-								case Variant::NIL:
-								case Variant::DICTIONARY: {
-									result.has_type = false;
-								} break;
-								default: {
-									Callable::CallError err;
-									Variant temp = Variant::construct(base_type.builtin_type, nullptr, 0, err);
-
-									bool valid = false;
-									Variant res = temp.get(member_id->name.operator String(), &valid);
-
-									if (valid) {
-										result = _type_from_variant(res);
-									} else if (check_types) {
-										_set_error("Can't get index \"" + String(member_id->name.operator String()) + "\" on base \"" +
-														   base_type.to_string() + "\".",
-												op->line);
-										return DataType();
-									}
-								} break;
-							}
-							result.is_constant = false;
-							node_type = result;
-						} else {
-							node_type = _reduce_identifier_type(&base_type, member_id->name, op->line, true);
-#ifdef DEBUG_ENABLED
-							if (!node_type.has_type) {
-								_mark_line_as_unsafe(op->line);
-								_add_warning(GDScriptWarning::UNSAFE_PROPERTY_ACCESS, op->line, member_id->name.operator String(), base_type.to_string());
-							}
-#endif // DEBUG_ENABLED
-						}
-					} else {
-						_mark_line_as_unsafe(op->line);
-					}
-					if (error_set) {
-						return DataType();
-					}
-				} break;
-				case OperatorNode::OP_INDEX: {
-					if (op->arguments[1]->type == Node::TYPE_CONSTANT) {
-						ConstantNode *cn = static_cast<ConstantNode *>(op->arguments[1]);
-						if (cn->value.get_type() == Variant::STRING) {
-							// Treat this as named indexing
-
-							IdentifierNode *id = alloc_node<IdentifierNode>();
-							id->name = cn->value.operator StringName();
-							id->datatype = cn->datatype;
-
-							op->op = OperatorNode::OP_INDEX_NAMED;
-							op->arguments.write[1] = id;
-
-							return _reduce_node_type(op);
-						}
-					}
-
-					DataType base_type = _reduce_node_type(op->arguments[0]);
-					DataType index_type = _reduce_node_type(op->arguments[1]);
-
-					if (!base_type.has_type) {
-						_mark_line_as_unsafe(op->line);
-						break;
-					}
-
-					if (check_types && index_type.has_type) {
-						if (base_type.kind == DataType::BUILTIN) {
-							// Check if indexing is valid
-							bool error = index_type.kind != DataType::BUILTIN && base_type.builtin_type != Variant::DICTIONARY;
-							if (!error) {
-								switch (base_type.builtin_type) {
-									// Expect int or real as index
-									case Variant::PACKED_BYTE_ARRAY:
-									case Variant::PACKED_COLOR_ARRAY:
-									case Variant::PACKED_INT32_ARRAY:
-									case Variant::PACKED_INT64_ARRAY:
-									case Variant::PACKED_FLOAT32_ARRAY:
-									case Variant::PACKED_FLOAT64_ARRAY:
-									case Variant::PACKED_STRING_ARRAY:
-									case Variant::PACKED_VECTOR2_ARRAY:
-									case Variant::PACKED_VECTOR3_ARRAY:
-									case Variant::ARRAY:
-									case Variant::STRING: {
-										error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::FLOAT;
-									} break;
-									// Expect String only
-									case Variant::RECT2:
-									case Variant::PLANE:
-									case Variant::QUAT:
-									case Variant::AABB:
-									case Variant::OBJECT: {
-										error = index_type.builtin_type != Variant::STRING;
-									} break;
-									// Expect String or number
-									case Variant::VECTOR2:
-									case Variant::VECTOR3:
-									case Variant::TRANSFORM2D:
-									case Variant::BASIS:
-									case Variant::TRANSFORM: {
-										error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::FLOAT &&
-												index_type.builtin_type != Variant::STRING;
-									} break;
-									// Expect String or int
-									case Variant::COLOR: {
-										error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::STRING;
-									} break;
-									default: {
-									}
-								}
-							}
-							if (error) {
-								_set_error("Invalid index type (" + index_type.to_string() + ") for base \"" + base_type.to_string() + "\".",
-										op->line);
-								return DataType();
-							}
-
-							if (op->arguments[1]->type == GDScriptParser::Node::TYPE_CONSTANT) {
-								ConstantNode *cn = static_cast<ConstantNode *>(op->arguments[1]);
-								// Index is a constant, just try it if possible
-								switch (base_type.builtin_type) {
-									// Arrays/string have variable indexing, can't test directly
-									case Variant::STRING:
-									case Variant::ARRAY:
-									case Variant::DICTIONARY:
-									case Variant::PACKED_BYTE_ARRAY:
-									case Variant::PACKED_COLOR_ARRAY:
-									case Variant::PACKED_INT32_ARRAY:
-									case Variant::PACKED_INT64_ARRAY:
-									case Variant::PACKED_FLOAT32_ARRAY:
-									case Variant::PACKED_FLOAT64_ARRAY:
-									case Variant::PACKED_STRING_ARRAY:
-									case Variant::PACKED_VECTOR2_ARRAY:
-									case Variant::PACKED_VECTOR3_ARRAY: {
-										break;
-									}
-									default: {
-										Callable::CallError err;
-										Variant temp = Variant::construct(base_type.builtin_type, nullptr, 0, err);
-
-										bool valid = false;
-										Variant res = temp.get(cn->value, &valid);
-
-										if (valid) {
-											node_type = _type_from_variant(res);
-											node_type.is_constant = false;
-										} else if (check_types) {
-											_set_error("Can't get index \"" + String(cn->value) + "\" on base \"" +
-															   base_type.to_string() + "\".",
-													op->line);
-											return DataType();
-										}
-									} 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 an index in the base type \"" + base_type.to_string() + "\".", op->line);
-							return DataType();
-						}
-					}
-					if (check_types && !node_type.has_type && base_type.kind == DataType::BUILTIN) {
-						// Can infer indexing type for some variant types
-						DataType result;
-						result.has_type = true;
-						result.kind = DataType::BUILTIN;
-						switch (base_type.builtin_type) {
-							// Can't index at all
-							case Variant::NIL:
-							case Variant::BOOL:
-							case Variant::INT:
-							case Variant::FLOAT:
-							case Variant::NODE_PATH:
-							case Variant::_RID: {
-								_set_error("Can't index on a value of type \"" + base_type.to_string() + "\".", op->line);
-								return DataType();
-							} break;
-								// Return int
-							case Variant::PACKED_BYTE_ARRAY:
-							case Variant::PACKED_INT32_ARRAY:
-							case Variant::PACKED_INT64_ARRAY: {
-								result.builtin_type = Variant::INT;
-							} break;
-								// Return real
-							case Variant::PACKED_FLOAT32_ARRAY:
-							case Variant::PACKED_FLOAT64_ARRAY:
-							case Variant::VECTOR2:
-							case Variant::VECTOR3:
-							case Variant::QUAT: {
-								result.builtin_type = Variant::FLOAT;
-							} break;
-								// Return color
-							case Variant::PACKED_COLOR_ARRAY: {
-								result.builtin_type = Variant::COLOR;
-							} break;
-								// Return string
-							case Variant::PACKED_STRING_ARRAY:
-							case Variant::STRING: {
-								result.builtin_type = Variant::STRING;
-							} break;
-								// Return Vector2
-							case Variant::PACKED_VECTOR2_ARRAY:
-							case Variant::TRANSFORM2D:
-							case Variant::RECT2: {
-								result.builtin_type = Variant::VECTOR2;
-							} break;
-								// Return Vector3
-							case Variant::PACKED_VECTOR3_ARRAY:
-							case Variant::AABB:
-							case Variant::BASIS: {
-								result.builtin_type = Variant::VECTOR3;
-							} break;
-								// Depends on the index
-							case Variant::TRANSFORM:
-							case Variant::PLANE:
-							case Variant::COLOR:
-							default: {
-								result.has_type = false;
-							} break;
-						}
-						node_type = result;
-					}
-				} break;
-				default: {
-					_set_error("Parser bug: unhandled operation.", op->line);
-					ERR_FAIL_V(DataType());
-				}
-			}
-		} break;
-		default: {
-		}
-	}
-
-	node_type = _resolve_type(node_type, p_node->line);
-	p_node->set_datatype(node_type);
-	return node_type;
-}
-
-bool GDScriptParser::_get_function_signature(DataType &p_base_type, const StringName &p_function, DataType &r_return_type, List<DataType> &r_arg_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) const {
-	r_static = false;
-	r_default_arg_count = 0;
-
-	DataType original_type = p_base_type;
-	ClassNode *base = nullptr;
-	FunctionNode *callee = nullptr;
-
-	if (p_base_type.kind == DataType::CLASS) {
-		base = p_base_type.class_type;
-	}
-
-	// Look up the current file (parse tree)
-	while (!callee && base) {
-		for (int i = 0; i < base->static_functions.size(); i++) {
-			FunctionNode *func = base->static_functions[i];
-			if (p_function == func->name) {
-				r_static = true;
-				callee = func;
-				break;
-			}
-		}
-		if (!callee && !p_base_type.is_meta_type) {
-			for (int i = 0; i < base->functions.size(); i++) {
-				FunctionNode *func = base->functions[i];
-				if (p_function == func->name) {
-					callee = func;
-					break;
-				}
-			}
-		}
-		p_base_type = base->base_type;
-		if (p_base_type.kind == DataType::CLASS) {
-			base = p_base_type.class_type;
-		} else {
-			break;
-		}
-	}
-
-	if (callee) {
-		r_return_type = callee->get_datatype();
-		for (int i = 0; i < callee->argument_types.size(); i++) {
-			r_arg_types.push_back(callee->argument_types[i]);
-		}
-		r_default_arg_count = callee->default_values.size();
-		return true;
-	}
-
-	// Nothing in current file, check parent script
-	Ref<GDScript> base_gdscript;
-	Ref<Script> base_script;
-	StringName native;
-	if (p_base_type.kind == DataType::GDSCRIPT) {
-		base_gdscript = p_base_type.script_type;
-		if (base_gdscript.is_null() || !base_gdscript->is_valid()) {
-			// GDScript wasn't properly compíled, don't bother trying
-			return false;
-		}
-	} else if (p_base_type.kind == DataType::SCRIPT) {
-		base_script = p_base_type.script_type;
-	} else if (p_base_type.kind == DataType::NATIVE) {
-		native = p_base_type.native_type;
-	}
-
-	while (base_gdscript.is_valid()) {
-		native = base_gdscript->get_instance_base_type();
-
-		Map<StringName, GDScriptFunction *> funcs = base_gdscript->get_member_functions();
-
-		if (funcs.has(p_function)) {
-			GDScriptFunction *f = funcs[p_function];
-			r_static = f->is_static();
-			r_default_arg_count = f->get_default_argument_count();
-			r_return_type = _type_from_gdtype(f->get_return_type());
-			for (int i = 0; i < f->get_argument_count(); i++) {
-				r_arg_types.push_back(_type_from_gdtype(f->get_argument_type(i)));
-			}
-			return true;
-		}
-
-		base_gdscript = base_gdscript->get_base_script();
-	}
-
-	while (base_script.is_valid()) {
-		native = base_script->get_instance_base_type();
-		MethodInfo mi = base_script->get_method_info(p_function);
-
-		if (!(mi == MethodInfo())) {
-			r_return_type = _type_from_property(mi.return_val, false);
-			r_default_arg_count = mi.default_arguments.size();
-			for (List<PropertyInfo>::Element *E = mi.arguments.front(); E; E = E->next()) {
-				r_arg_types.push_back(_type_from_property(E->get()));
-			}
-			return true;
-		}
-		base_script = base_script->get_base_script();
-	}
-
-	if (native == StringName()) {
-		// Empty native class, might happen in some Script implementations
-		// Just ignore it
-		return false;
-	}
-
-#ifdef DEBUG_METHODS_ENABLED
-
-	// Only native remains
-	if (!ClassDB::class_exists(native)) {
-		native = "_" + native.operator String();
-	}
-	if (!ClassDB::class_exists(native)) {
-		if (!check_types) {
-			return false;
-		}
-		ERR_FAIL_V_MSG(false, "Parser bug: Class '" + String(native) + "' not found.");
-	}
-
-	MethodBind *method = ClassDB::get_method(native, p_function);
-
-	if (!method) {
-		// Try virtual methods
-		List<MethodInfo> virtuals;
-		ClassDB::get_virtual_methods(native, &virtuals);
-
-		for (const List<MethodInfo>::Element *E = virtuals.front(); E; E = E->next()) {
-			const MethodInfo &mi = E->get();
-			if (mi.name == p_function) {
-				r_default_arg_count = mi.default_arguments.size();
-				for (const List<PropertyInfo>::Element *pi = mi.arguments.front(); pi; pi = pi->next()) {
-					r_arg_types.push_back(_type_from_property(pi->get()));
-				}
-				r_return_type = _type_from_property(mi.return_val, false);
-				r_vararg = mi.flags & METHOD_FLAG_VARARG;
-				return true;
-			}
-		}
-
-		// If the base is a script, it might be trying to access members of the Script class itself
-		if (original_type.is_meta_type && !(p_function == "new") && (original_type.kind == DataType::SCRIPT || original_type.kind == DataType::GDSCRIPT)) {
-			method = ClassDB::get_method(original_type.script_type->get_class_name(), p_function);
-
-			if (method) {
-				r_static = true;
-			} else {
-				// Try virtual methods of the script type
-				virtuals.clear();
-				ClassDB::get_virtual_methods(original_type.script_type->get_class_name(), &virtuals);
-				for (const List<MethodInfo>::Element *E = virtuals.front(); E; E = E->next()) {
-					const MethodInfo &mi = E->get();
-					if (mi.name == p_function) {
-						r_default_arg_count = mi.default_arguments.size();
-						for (const List<PropertyInfo>::Element *pi = mi.arguments.front(); pi; pi = pi->next()) {
-							r_arg_types.push_back(_type_from_property(pi->get()));
-						}
-						r_return_type = _type_from_property(mi.return_val, false);
-						r_static = true;
-						r_vararg = mi.flags & METHOD_FLAG_VARARG;
-						return true;
-					}
-				}
-				return false;
-			}
-		} else {
-			return false;
-		}
-	}
-
-	r_default_arg_count = method->get_default_argument_count();
-	r_return_type = _type_from_property(method->get_return_info(), false);
-	r_vararg = method->is_vararg();
-
-	for (int i = 0; i < method->get_argument_count(); i++) {
-		r_arg_types.push_back(_type_from_property(method->get_argument_info(i)));
-	}
-	return true;
-#else
-	return false;
-#endif
-}
-
-GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const OperatorNode *p_call) {
-	if (p_call->arguments.size() < 1) {
-		_set_error("Parser bug: function call without enough arguments.", p_call->line);
-		ERR_FAIL_V(DataType());
-	}
-
-	DataType return_type;
-	List<DataType> arg_types;
-	int default_args_count = 0;
-	int arg_count = p_call->arguments.size();
-	String callee_name;
-	bool is_vararg = false;
-
-	switch (p_call->arguments[0]->type) {
-		case GDScriptParser::Node::TYPE_TYPE: {
-			// Built-in constructor, special case
-			TypeNode *tn = static_cast<TypeNode *>(p_call->arguments[0]);
-
-			Vector<DataType> par_types;
-			par_types.resize(p_call->arguments.size() - 1);
-			for (int i = 1; i < p_call->arguments.size(); i++) {
-				par_types.write[i - 1] = _reduce_node_type(p_call->arguments[i]);
-			}
-
-			if (error_set) {
-				return DataType();
-			}
-
-			// Special case: check copy constructor. Those are defined implicitly in Variant.
-			if (par_types.size() == 1) {
-				if (!par_types[0].has_type || (par_types[0].kind == DataType::BUILTIN && par_types[0].builtin_type == tn->vtype)) {
-					DataType result;
-					result.has_type = true;
-					result.kind = DataType::BUILTIN;
-					result.builtin_type = tn->vtype;
-					return result;
-				}
-			}
-
-			bool match = false;
-			List<MethodInfo> constructors;
-			Variant::get_constructor_list(tn->vtype, &constructors);
-			PropertyInfo return_type2;
-
-			for (List<MethodInfo>::Element *E = constructors.front(); E; E = E->next()) {
-				MethodInfo &mi = E->get();
-
-				if (p_call->arguments.size() - 1 < mi.arguments.size() - mi.default_arguments.size()) {
-					continue;
-				}
-				if (p_call->arguments.size() - 1 > mi.arguments.size()) {
-					continue;
-				}
-
-				bool types_match = true;
-				for (int i = 0; i < par_types.size(); i++) {
-					DataType arg_type;
-					if (mi.arguments[i].type != Variant::NIL) {
-						arg_type.has_type = true;
-						arg_type.kind = mi.arguments[i].type == Variant::OBJECT ? DataType::NATIVE : DataType::BUILTIN;
-						arg_type.builtin_type = mi.arguments[i].type;
-						arg_type.native_type = mi.arguments[i].class_name;
-					}
-
-					if (!_is_type_compatible(arg_type, par_types[i], true)) {
-						types_match = false;
-						break;
-					} else {
-#ifdef DEBUG_ENABLED
-						if (arg_type.kind == DataType::BUILTIN && arg_type.builtin_type == Variant::INT && par_types[i].kind == DataType::BUILTIN && par_types[i].builtin_type == Variant::FLOAT) {
-							_add_warning(GDScriptWarning::NARROWING_CONVERSION, p_call->line, Variant::get_type_name(tn->vtype));
-						}
-						if (par_types[i].may_yield && p_call->arguments[i + 1]->type == Node::TYPE_OPERATOR) {
-							_add_warning(GDScriptWarning::FUNCTION_MAY_YIELD, p_call->line, _find_function_name(static_cast<OperatorNode *>(p_call->arguments[i + 1])));
-						}
-#endif // DEBUG_ENABLED
-					}
-				}
-
-				if (types_match) {
-					match = true;
-					return_type2 = mi.return_val;
-					break;
-				}
-			}
-
-			if (match) {
-				return _type_from_property(return_type2, false);
-			} else if (check_types) {
-				String err = "No constructor of '";
-				err += Variant::get_type_name(tn->vtype);
-				err += "' matches the signature '";
-				err += Variant::get_type_name(tn->vtype) + "(";
-				for (int i = 0; i < par_types.size(); i++) {
-					if (i > 0) {
-						err += ", ";
-					}
-					err += par_types[i].to_string();
-				}
-				err += ")'.";
-				_set_error(err, p_call->line, p_call->column);
-				return DataType();
-			}
-			return DataType();
-		} break;
-		case GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION: {
-			BuiltInFunctionNode *func = static_cast<BuiltInFunctionNode *>(p_call->arguments[0]);
-			MethodInfo mi = GDScriptFunctions::get_info(func->function);
-
-			return_type = _type_from_property(mi.return_val, false);
-
-			// Check all arguments beforehand to solve warnings
-			for (int i = 1; i < p_call->arguments.size(); i++) {
-				_reduce_node_type(p_call->arguments[i]);
-			}
-
-			// Check arguments
-
-			is_vararg = mi.flags & METHOD_FLAG_VARARG;
-
-			default_args_count = mi.default_arguments.size();
-			callee_name = mi.name;
-			arg_count -= 1;
-
-			// Check each argument type
-			for (List<PropertyInfo>::Element *E = mi.arguments.front(); E; E = E->next()) {
-				arg_types.push_back(_type_from_property(E->get()));
-			}
-		} break;
-		default: {
-			if (p_call->op == OperatorNode::OP_CALL && p_call->arguments.size() < 2) {
-				_set_error("Parser bug: self method call without enough arguments.", p_call->line);
-				ERR_FAIL_V(DataType());
-			}
-
-			int arg_id = p_call->op == OperatorNode::OP_CALL ? 1 : 0;
-
-			if (p_call->arguments[arg_id]->type != Node::TYPE_IDENTIFIER) {
-				_set_error("Parser bug: invalid function call argument.", p_call->line);
-				ERR_FAIL_V(DataType());
-			}
-
-			// Check all arguments beforehand to solve warnings
-			for (int i = arg_id + 1; i < p_call->arguments.size(); i++) {
-				_reduce_node_type(p_call->arguments[i]);
-			}
-
-			IdentifierNode *func_id = static_cast<IdentifierNode *>(p_call->arguments[arg_id]);
-			callee_name = func_id->name;
-			arg_count -= 1 + arg_id;
-
-			DataType base_type;
-			if (p_call->op == OperatorNode::OP_PARENT_CALL) {
-				base_type = current_class->base_type;
-			} else {
-				base_type = _reduce_node_type(p_call->arguments[0]);
-			}
-
-			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();
-			}
-
-			if (base_type.kind == DataType::BUILTIN) {
-				Callable::CallError err;
-				Variant tmp = Variant::construct(base_type.builtin_type, nullptr, 0, err);
-
-				if (check_types) {
-					if (!tmp.has_method(callee_name)) {
-						_set_error("The method \"" + callee_name + "\" isn't declared on base \"" + base_type.to_string() + "\".", p_call->line);
-						return DataType();
-					}
-
-					default_args_count = Variant::get_method_default_arguments(base_type.builtin_type, callee_name).size();
-					const Vector<Variant::Type> &var_arg_types = Variant::get_method_argument_types(base_type.builtin_type, callee_name);
-
-					for (int i = 0; i < var_arg_types.size(); i++) {
-						DataType argtype;
-						if (var_arg_types[i] != Variant::NIL) {
-							argtype.has_type = true;
-							argtype.kind = DataType::BUILTIN;
-							argtype.builtin_type = var_arg_types[i];
-						}
-						arg_types.push_back(argtype);
-					}
-				}
-
-				bool rets = false;
-				return_type.has_type = true;
-				return_type.kind = DataType::BUILTIN;
-				return_type.builtin_type = Variant::get_method_return_type(base_type.builtin_type, callee_name, &rets);
-				// If the method returns, but it might return any type, (Variant::NIL), pretend we don't know the type.
-				// At least make sure we know that it returns
-				if (rets && return_type.builtin_type == Variant::NIL) {
-					return_type.has_type = false;
-				}
-				break;
-			}
-
-			DataType original_type = base_type;
-			bool is_initializer = callee_name == "new";
-			bool is_static = false;
-			bool valid = false;
-
-			if (is_initializer && original_type.is_meta_type) {
-				// Try to check it as initializer
-				base_type = original_type;
-				callee_name = "_init";
-				base_type.is_meta_type = false;
-
-				valid = _get_function_signature(base_type, callee_name, return_type, arg_types,
-						default_args_count, is_static, is_vararg);
-
-				return_type = original_type;
-				return_type.is_meta_type = false;
-
-				valid = true; // There's always an initializer, we can assume this is true
-			}
-
-			if (!valid) {
-				base_type = original_type;
-				return_type = DataType();
-				valid = _get_function_signature(base_type, callee_name, return_type, arg_types,
-						default_args_count, is_static, is_vararg);
-			}
-
-			if (!valid) {
-#ifdef DEBUG_ENABLED
-				if (p_call->arguments[0]->type == Node::TYPE_SELF) {
-					_set_error("The method \"" + callee_name + "\" isn't declared in the current class.", p_call->line);
-					return DataType();
-				}
-				DataType tmp_type;
-				valid = _get_member_type(original_type, func_id->name, tmp_type);
-				if (valid) {
-					if (tmp_type.is_constant) {
-						_add_warning(GDScriptWarning::CONSTANT_USED_AS_FUNCTION, p_call->line, callee_name, original_type.to_string());
-					} else {
-						_add_warning(GDScriptWarning::PROPERTY_USED_AS_FUNCTION, p_call->line, callee_name, original_type.to_string());
-					}
-				}
-				_add_warning(GDScriptWarning::UNSAFE_METHOD_ACCESS, p_call->line, callee_name, original_type.to_string());
-				_mark_line_as_unsafe(p_call->line);
-#endif // DEBUG_ENABLED
-				return DataType();
-			}
-
-#ifdef DEBUG_ENABLED
-			if (current_function && !for_completion && !is_static && p_call->arguments[0]->type == Node::TYPE_SELF && current_function->_static) {
-				_set_error("Can't call non-static function from a static function.", p_call->line);
-				return DataType();
-			}
-
-			if (check_types && !is_static && !is_initializer && base_type.is_meta_type) {
-				_set_error("Non-static function \"" + String(callee_name) + "\" can only be called from an instance.", p_call->line);
-				return DataType();
-			}
-
-			// Check signal emission for warnings
-			if (callee_name == "emit_signal" && p_call->op == OperatorNode::OP_CALL && p_call->arguments[0]->type == Node::TYPE_SELF && p_call->arguments.size() >= 3 && p_call->arguments[2]->type == Node::TYPE_CONSTANT) {
-				ConstantNode *sig = static_cast<ConstantNode *>(p_call->arguments[2]);
-				String emitted = sig->value.get_type() == Variant::STRING ? sig->value.operator String() : "";
-				for (int i = 0; i < current_class->_signals.size(); i++) {
-					if (current_class->_signals[i].name == emitted) {
-						current_class->_signals.write[i].emissions += 1;
-						break;
-					}
-				}
-			}
-#endif // DEBUG_ENABLED
-		} break;
-	}
-
-#ifdef DEBUG_ENABLED
-	if (!check_types) {
-		return return_type;
-	}
-
-	if (arg_count < arg_types.size() - default_args_count) {
-		_set_error("Too few arguments for \"" + callee_name + "()\" call. Expected at least " + itos(arg_types.size() - default_args_count) + ".", p_call->line);
-		return return_type;
-	}
-	if (!is_vararg && arg_count > arg_types.size()) {
-		_set_error("Too many arguments for \"" + callee_name + "()\" call. Expected at most " + itos(arg_types.size()) + ".", p_call->line);
-		return return_type;
-	}
-
-	int arg_diff = p_call->arguments.size() - arg_count;
-	for (int i = arg_diff; i < p_call->arguments.size(); i++) {
-		DataType par_type = _reduce_node_type(p_call->arguments[i]);
-
-		if ((i - arg_diff) >= arg_types.size()) {
-			continue;
-		}
-
-		DataType arg_type = arg_types[i - arg_diff];
-
-		if (!par_type.has_type) {
-			_mark_line_as_unsafe(p_call->line);
-			if (par_type.may_yield && p_call->arguments[i]->type == Node::TYPE_OPERATOR) {
-				_add_warning(GDScriptWarning::FUNCTION_MAY_YIELD, p_call->line, _find_function_name(static_cast<OperatorNode *>(p_call->arguments[i])));
-			}
-		} 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) + ". The passed argument's type (" +
-								   par_type.to_string() + ") doesn't match the function's expected argument type (" +
-								   arg_types[i - arg_diff].to_string() + ").",
-						p_call->line);
-				return DataType();
-			} else {
-				_mark_line_as_unsafe(p_call->line);
-			}
-		} else {
-			if (arg_type.kind == DataType::BUILTIN && arg_type.builtin_type == Variant::INT && par_type.kind == DataType::BUILTIN && par_type.builtin_type == Variant::FLOAT) {
-				_add_warning(GDScriptWarning::NARROWING_CONVERSION, p_call->line, callee_name);
-			}
-		}
-	}
-
-#endif // DEBUG_ENABLED
-
-	return return_type;
-}
-
-bool GDScriptParser::_get_member_type(const DataType &p_base_type, const StringName &p_member, DataType &r_member_type, bool *r_is_const) const {
-	DataType base_type = p_base_type;
-
-	// Check classes in current file
-	ClassNode *base = nullptr;
-	if (base_type.kind == DataType::CLASS) {
-		base = base_type.class_type;
-	}
-
-	while (base) {
-		if (base->constant_expressions.has(p_member)) {
-			if (r_is_const) {
-				*r_is_const = true;
-			}
-			r_member_type = base->constant_expressions[p_member].expression->get_datatype();
-			return true;
-		}
-
-		if (!base_type.is_meta_type) {
-			for (int i = 0; i < base->variables.size(); i++) {
-				if (base->variables[i].identifier == p_member) {
-					r_member_type = base->variables[i].data_type;
-					base->variables.write[i].usages += 1;
-					return true;
-				}
-			}
-		} else {
-			for (int i = 0; i < base->subclasses.size(); i++) {
-				ClassNode *c = base->subclasses[i];
-				if (c->name == p_member) {
-					DataType class_type;
-					class_type.has_type = true;
-					class_type.is_constant = true;
-					class_type.is_meta_type = true;
-					class_type.kind = DataType::CLASS;
-					class_type.class_type = c;
-					r_member_type = class_type;
-					return true;
-				}
-			}
-		}
-
-		base_type = base->base_type;
-		if (base_type.kind == DataType::CLASS) {
-			base = base_type.class_type;
-		} else {
-			break;
-		}
-	}
-
-	Ref<GDScript> gds;
-	if (base_type.kind == DataType::GDSCRIPT) {
-		gds = base_type.script_type;
-		if (gds.is_null() || !gds->is_valid()) {
-			// GDScript wasn't properly compíled, don't bother trying
-			return false;
-		}
-	}
-
-	Ref<Script> scr;
-	if (base_type.kind == DataType::SCRIPT) {
-		scr = base_type.script_type;
-	}
-
-	StringName native;
-	if (base_type.kind == DataType::NATIVE) {
-		native = base_type.native_type;
-	}
-
-	// Check GDScripts
-	while (gds.is_valid()) {
-		if (gds->get_constants().has(p_member)) {
-			Variant c = gds->get_constants()[p_member];
-			r_member_type = _type_from_variant(c);
-			return true;
-		}
-
-		if (!base_type.is_meta_type) {
-			if (gds->get_members().has(p_member)) {
-				r_member_type = _type_from_gdtype(gds->get_member_type(p_member));
-				return true;
-			}
-		}
-
-		native = gds->get_instance_base_type();
-		if (gds->get_base_script().is_valid()) {
-			gds = gds->get_base_script();
-			scr = gds->get_base_script();
-			bool is_meta = base_type.is_meta_type;
-			base_type = _type_from_variant(scr.operator Variant());
-			base_type.is_meta_type = is_meta;
-		} else {
-			break;
-		}
-	}
-
-#define IS_USAGE_MEMBER(m_usage) (!(m_usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP | PROPERTY_USAGE_CATEGORY)))
-
-	// Check other script types
-	while (scr.is_valid()) {
-		Map<StringName, Variant> constants;
-		scr->get_constants(&constants);
-		if (constants.has(p_member)) {
-			r_member_type = _type_from_variant(constants[p_member]);
-			return true;
-		}
-
-		List<PropertyInfo> properties;
-		scr->get_script_property_list(&properties);
-		for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) {
-			if (E->get().name == p_member && IS_USAGE_MEMBER(E->get().usage)) {
-				r_member_type = _type_from_property(E->get());
-				return true;
-			}
-		}
-
-		base_type = _type_from_variant(scr.operator Variant());
-		native = scr->get_instance_base_type();
-		scr = scr->get_base_script();
-	}
-
-	if (native == StringName()) {
-		// Empty native class, might happen in some Script implementations
-		// Just ignore it
-		return false;
-	}
-
-	// Check ClassDB
-	if (!ClassDB::class_exists(native)) {
-		native = "_" + native.operator String();
-	}
-	if (!ClassDB::class_exists(native)) {
-		if (!check_types) {
-			return false;
-		}
-		ERR_FAIL_V_MSG(false, "Parser bug: Class \"" + String(native) + "\" not found.");
-	}
-
-	bool valid = false;
-	ClassDB::get_integer_constant(native, p_member, &valid);
-	if (valid) {
-		DataType ct;
-		ct.has_type = true;
-		ct.is_constant = true;
-		ct.kind = DataType::BUILTIN;
-		ct.builtin_type = Variant::INT;
-		r_member_type = ct;
-		return true;
-	}
-
-	if (!base_type.is_meta_type) {
-		List<PropertyInfo> properties;
-		ClassDB::get_property_list(native, &properties);
-		for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) {
-			if (E->get().name == p_member && IS_USAGE_MEMBER(E->get().usage)) {
-				// Check if a getter exists
-				StringName getter_name = ClassDB::get_property_getter(native, p_member);
-				if (getter_name != StringName()) {
-					// Use the getter return type
-#ifdef DEBUG_METHODS_ENABLED
-					MethodBind *getter_method = ClassDB::get_method(native, getter_name);
-					if (getter_method) {
-						r_member_type = _type_from_property(getter_method->get_return_info());
-					} else {
-						r_member_type = DataType();
-					}
-#else
-					r_member_type = DataType();
-#endif
-				} else {
-					r_member_type = _type_from_property(E->get());
-				}
-				return true;
-			}
-		}
-	}
-
-	// If the base is a script, it might be trying to access members of the Script class itself
-	if (p_base_type.is_meta_type && (p_base_type.kind == DataType::SCRIPT || p_base_type.kind == DataType::GDSCRIPT)) {
-		native = p_base_type.script_type->get_class_name();
-		ClassDB::get_integer_constant(native, p_member, &valid);
-		if (valid) {
-			DataType ct;
-			ct.has_type = true;
-			ct.is_constant = true;
-			ct.kind = DataType::BUILTIN;
-			ct.builtin_type = Variant::INT;
-			r_member_type = ct;
-			return true;
-		}
-
-		List<PropertyInfo> properties;
-		ClassDB::get_property_list(native, &properties);
-		for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) {
-			if (E->get().name == p_member && IS_USAGE_MEMBER(E->get().usage)) {
-				// Check if a getter exists
-				StringName getter_name = ClassDB::get_property_getter(native, p_member);
-				if (getter_name != StringName()) {
-					// Use the getter return type
-#ifdef DEBUG_METHODS_ENABLED
-					MethodBind *getter_method = ClassDB::get_method(native, getter_name);
-					if (getter_method) {
-						r_member_type = _type_from_property(getter_method->get_return_info());
-					} else {
-						r_member_type = DataType();
-					}
-#else
-					r_member_type = DataType();
-#endif
-				} else {
-					r_member_type = _type_from_property(E->get());
-				}
-				return true;
-			}
-		}
-	}
-#undef IS_USAGE_MEMBER
-
-	return false;
-}
-
-GDScriptParser::DataType GDScriptParser::_reduce_identifier_type(const DataType *p_base_type, const StringName &p_identifier, int p_line, bool p_is_indexing) {
-	if (p_base_type && !p_base_type->has_type) {
-		return DataType();
-	}
-
-	DataType base_type;
-	DataType member_type;
-
-	if (!p_base_type) {
-		base_type.has_type = true;
-		base_type.is_constant = true;
-		base_type.kind = DataType::CLASS;
-		base_type.class_type = current_class;
-	} else {
-		base_type = DataType(*p_base_type);
-	}
-
-	bool is_const = false;
-	if (_get_member_type(base_type, p_identifier, member_type, &is_const)) {
-		if (!p_base_type && current_function && current_function->_static && !is_const) {
-			_set_error("Can't access member variable (\"" + p_identifier.operator String() + "\") from a static function.", p_line);
-			return DataType();
-		}
-		return member_type;
-	}
-
-	if (p_is_indexing) {
-		// Don't look for globals since this is an indexed identifier
-		return DataType();
-	}
-
-	if (!p_base_type) {
-		// Possibly this is a global, check before failing
-
-		if (ClassDB::class_exists(p_identifier) || ClassDB::class_exists("_" + p_identifier.operator String())) {
-			DataType result;
-			result.has_type = true;
-			result.is_constant = true;
-			result.is_meta_type = true;
-			if (Engine::get_singleton()->has_singleton(p_identifier) || Engine::get_singleton()->has_singleton("_" + p_identifier.operator String())) {
-				result.is_meta_type = false;
-			}
-			result.kind = DataType::NATIVE;
-			result.native_type = p_identifier;
-			return result;
-		}
-
-		ClassNode *outer_class = current_class;
-		while (outer_class) {
-			if (outer_class->name == p_identifier) {
-				DataType result;
-				result.has_type = true;
-				result.is_constant = true;
-				result.is_meta_type = true;
-				result.kind = DataType::CLASS;
-				result.class_type = outer_class;
-				return result;
-			}
-			if (outer_class->constant_expressions.has(p_identifier)) {
-				return outer_class->constant_expressions[p_identifier].type;
-			}
-			for (int i = 0; i < outer_class->subclasses.size(); i++) {
-				if (outer_class->subclasses[i] == current_class) {
-					continue;
-				}
-				if (outer_class->subclasses[i]->name == p_identifier) {
-					DataType result;
-					result.has_type = true;
-					result.is_constant = true;
-					result.is_meta_type = true;
-					result.kind = DataType::CLASS;
-					result.class_type = outer_class->subclasses[i];
-					return result;
-				}
-			}
-			outer_class = outer_class->owner;
-		}
-
-		if (ScriptServer::is_global_class(p_identifier)) {
-			Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_identifier));
-			if (scr.is_valid()) {
-				DataType result;
-				result.has_type = true;
-				result.script_type = scr;
-				result.is_constant = true;
-				result.is_meta_type = true;
-				Ref<GDScript> gds = scr;
-				if (gds.is_valid()) {
-					if (!gds->is_valid()) {
-						_set_error("The class \"" + p_identifier + "\" couldn't be fully loaded (script error or cyclic dependency).");
-						return DataType();
-					}
-					result.kind = DataType::GDSCRIPT;
-				} else {
-					result.kind = DataType::SCRIPT;
-				}
-				return result;
-			}
-			_set_error("The class \"" + p_identifier + "\" was found in global scope, but its script couldn't be loaded.");
-			return DataType();
-		}
-
-		if (GDScriptLanguage::get_singleton()->get_global_map().has(p_identifier)) {
-			int idx = GDScriptLanguage::get_singleton()->get_global_map()[p_identifier];
-			Variant g = GDScriptLanguage::get_singleton()->get_global_array()[idx];
-			return _type_from_variant(g);
-		}
-
-		if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(p_identifier)) {
-			Variant g = GDScriptLanguage::get_singleton()->get_named_globals_map()[p_identifier];
-			return _type_from_variant(g);
-		}
-
-		// Non-tool singletons aren't loaded, check project settings
-		List<PropertyInfo> props;
-		ProjectSettings::get_singleton()->get_property_list(&props);
-
-		for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
-			String s = E->get().name;
-			if (!s.begins_with("autoload/")) {
-				continue;
-			}
-			String name = s.get_slice("/", 1);
-			if (name == p_identifier) {
-				String script = ProjectSettings::get_singleton()->get(s);
-				if (script.begins_with("*")) {
-					script = script.right(1);
-				}
-				if (!script.begins_with("res://")) {
-					script = "res://" + script;
-				}
-				Ref<Script> singleton = ResourceLoader::load(script);
-				if (singleton.is_valid()) {
-					DataType result;
-					result.has_type = true;
-					result.is_constant = true;
-					result.script_type = singleton;
-
-					Ref<GDScript> gds = singleton;
-					if (gds.is_valid()) {
-						if (!gds->is_valid()) {
-							_set_error("Couldn't fully load the singleton script \"" + p_identifier + "\" (possible cyclic reference or parse error).", p_line);
-							return DataType();
-						}
-						result.kind = DataType::GDSCRIPT;
-					} else {
-						result.kind = DataType::SCRIPT;
-					}
-				}
-			}
-		}
-
-		// This means looking in the current class, which type is always known
-		_set_error("The identifier \"" + p_identifier.operator String() + "\" isn't declared in the current scope.", p_line);
-	}
-
-#ifdef DEBUG_ENABLED
-	{
-		DataType tmp_type;
-		List<DataType> arg_types;
-		int argcount;
-		bool _static;
-		bool vararg;
-		if (_get_function_signature(base_type, p_identifier, tmp_type, arg_types, argcount, _static, vararg)) {
-			_add_warning(GDScriptWarning::FUNCTION_USED_AS_PROPERTY, p_line, p_identifier.operator String(), base_type.to_string());
-		}
-	}
-#endif // DEBUG_ENABLED
-
-	_mark_line_as_unsafe(p_line);
-	return DataType();
+bool GDScriptParser::AnnotationNode::applies_to(uint32_t p_target_kinds) const {
+	return (info->target_kind & p_target_kinds) > 0;
 }
 }
 
 
-void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
-	// Names of internal object properties that we check to avoid overriding them.
-	// "__meta__" could also be in here, but since it doesn't really affect object metadata,
-	// it is okay to override it on script.
-	StringName script_name = CoreStringNames::get_singleton()->_script;
+bool GDScriptParser::validate_annotation_arguments(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));
 
 
-	_mark_line_as_safe(p_class->line);
+	const MethodInfo &info = valid_annotations[p_annotation->name].info;
 
 
-	// 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);
-
-		if (check_types && !_is_type_compatible(cont, expr)) {
-			_set_error("The constant value type (" + expr.to_string() + ") isn't compatible with declared type (" + cont.to_string() + ").",
-					c.expression->line);
-			return;
-		}
-
-		expr.is_constant = true;
-		c.type = expr;
-		c.expression->set_datatype(expr);
-
-		DataType tmp;
-		const StringName &constant_name = E->key();
-		if (constant_name == script_name || _get_member_type(p_class->base_type, constant_name, tmp)) {
-			_set_error("The member \"" + String(constant_name) + "\" already exists in a parent class.", c.expression->line);
-			return;
-		}
-	}
-
-	// Function declarations
-	for (int i = 0; i < p_class->static_functions.size(); i++) {
-		_check_function_types(p_class->static_functions[i]);
-		if (error_set) {
-			return;
-		}
+	if (((info.flags & METHOD_FLAG_VARARG) == 0) && p_annotation->arguments.size() > info.arguments.size()) {
+		push_error(vformat(R"(Annotation "%s" requires at most %d arguments, but %d were given.)", p_annotation->name, info.arguments.size(), p_annotation->arguments.size()));
+		return false;
 	}
 	}
 
 
-	for (int i = 0; i < p_class->functions.size(); i++) {
-		_check_function_types(p_class->functions[i]);
-		if (error_set) {
-			return;
-		}
+	if (p_annotation->arguments.size() < info.arguments.size() - info.default_arguments.size()) {
+		push_error(vformat(R"(Annotation "%s" requires at least %d arguments, but %d were given.)", p_annotation->name, info.arguments.size() - info.default_arguments.size(), p_annotation->arguments.size()));
+		return false;
 	}
 	}
 
 
-	// Class variables
-	for (int i = 0; i < p_class->variables.size(); i++) {
-		ClassNode::Member &v = p_class->variables.write[i];
-
-		DataType tmp;
-		if (v.identifier == script_name || _get_member_type(p_class->base_type, v.identifier, tmp)) {
-			_set_error("The member \"" + String(v.identifier) + "\" already exists in a parent class.", v.line);
-			return;
-		}
-
-		_mark_line_as_safe(v.line);
-		v.data_type = _resolve_type(v.data_type, v.line);
-		v.initial_assignment->arguments[0]->set_datatype(v.data_type);
-
-		if (v.expression) {
-			DataType expr_type = _reduce_node_type(v.expression);
-
-			if (check_types && !_is_type_compatible(v.data_type, expr_type)) {
-				// 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("The assigned expression's type (" + expr_type.to_string() + ") doesn't match the variable's type (" +
-										   v.data_type.to_string() + ").",
-								v.line);
-						return;
+	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 &parameter = 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();
+					p_annotation->resolved_arguments.push_back(Variant::construct(parameter.type, &(name), 1, error));
+					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(p_annotation->resolved_arguments.size() - 1);
+						return false;
 					}
 					}
-
-					// Replace assignment with implicit 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 = (int64_t)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.write[1] = convert_call;
-				}
-			}
-
-			if (v.data_type.infer_type) {
-				if (!expr_type.has_type) {
-					_set_error("The assigned value doesn't have a set type; the variable type can't be inferred.", v.line);
-					return;
+					break;
 				}
 				}
-				if (expr_type.kind == DataType::BUILTIN && expr_type.builtin_type == Variant::NIL) {
-					_set_error("The variable type cannot be inferred because its value is \"null\".", v.line);
-					return;
+				[[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;
 				}
 				}
-				v.data_type = expr_type;
-				v.data_type.is_constant = false;
-			}
-		}
-
-		// Check export hint
-		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("The export hint's type (" + export_type.to_string() + ") doesn't match the variable's type (" +
-								   v.data_type.to_string() + ").",
-						v.line);
-				return;
-			}
-		}
 
 
-		// Setter and getter
-		if (v.setter == StringName() && v.getter == StringName()) {
-			continue;
-		}
-
-		bool found_getter = false;
-		bool found_setter = false;
-		for (int j = 0; j < p_class->functions.size(); j++) {
-			if (v.setter == p_class->functions[j]->name) {
-				found_setter = true;
-				FunctionNode *setter = p_class->functions[j];
-
-				if (setter->get_required_argument_count() != 1 &&
-						!(setter->get_required_argument_count() == 0 && setter->default_values.size() > 0)) {
-					_set_error("The setter function needs to receive exactly 1 argument. See \"" + setter->name +
-									   "()\" definition at line " + itos(setter->line) + ".",
-							v.line);
-					return;
-				}
-				if (!_is_type_compatible(v.data_type, setter->argument_types[0])) {
-					_set_error("The setter argument's type (" + setter->argument_types[0].to_string() +
-									   ") doesn't match the variable's type (" + v.data_type.to_string() + "). See \"" +
-									   setter->name + "()\" definition at line " + itos(setter->line) + ".",
-							v.line);
-					return;
-				}
-				continue;
-			}
-			if (v.getter == p_class->functions[j]->name) {
-				found_getter = true;
-				FunctionNode *getter = p_class->functions[j];
-
-				if (getter->get_required_argument_count() != 0) {
-					_set_error("The getter function can't receive arguments. See \"" + getter->name +
-									   "()\" definition at line " + itos(getter->line) + ".",
-							v.line);
-					return;
+				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;
 				}
 				}
-				if (!_is_type_compatible(v.data_type, getter->get_datatype())) {
-					_set_error("The getter return type (" + getter->get_datatype().to_string() +
-									   ") doesn't match the variable's type (" + v.data_type.to_string() +
-									   "). See \"" + getter->name + "()\" definition at line " + itos(getter->line) + ".",
-							v.line);
-					return;
+				Callable::CallError error;
+				const Variant *args = &value;
+				p_annotation->resolved_arguments.push_back(Variant::construct(parameter.type, &(args), 1, error));
+				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(p_annotation->resolved_arguments.size() - 1);
+					return false;
 				}
 				}
-			}
-			if (found_getter && found_setter) {
 				break;
 				break;
 			}
 			}
 		}
 		}
-
-		if ((found_getter || v.getter == StringName()) && (found_setter || v.setter == StringName())) {
-			continue;
-		}
-
-		// Check for static functions
-		for (int j = 0; j < p_class->static_functions.size(); j++) {
-			if (v.setter == p_class->static_functions[j]->name) {
-				FunctionNode *setter = p_class->static_functions[j];
-				_set_error("The setter can't be a static function. See \"" + setter->name + "()\" definition at line " + itos(setter->line) + ".", v.line);
-				return;
-			}
-			if (v.getter == p_class->static_functions[j]->name) {
-				FunctionNode *getter = p_class->static_functions[j];
-				_set_error("The getter can't be a static function. See \"" + getter->name + "()\" definition at line " + itos(getter->line) + ".", v.line);
-				return;
-			}
-		}
-
-		if (!found_setter && v.setter != StringName()) {
-			_set_error("The setter function isn't defined.", v.line);
-			return;
-		}
-
-		if (!found_getter && v.getter != StringName()) {
-			_set_error("The getter function isn't defined.", v.line);
-			return;
-		}
-	}
-
-	// Signals
-	DataType base = p_class->base_type;
-
-	while (base.kind == DataType::CLASS) {
-		ClassNode *base_class = base.class_type;
-		for (int i = 0; i < p_class->_signals.size(); i++) {
-			for (int j = 0; j < base_class->_signals.size(); j++) {
-				if (p_class->_signals[i].name == base_class->_signals[j].name) {
-					_set_error("The signal \"" + p_class->_signals[i].name + "\" already exists in a parent class.", p_class->_signals[i].line);
-					return;
-				}
-			}
-		}
-		base = base_class->base_type;
-	}
-
-	StringName native;
-	if (base.kind == DataType::GDSCRIPT || base.kind == DataType::SCRIPT) {
-		Ref<Script> scr = base.script_type;
-		if (scr.is_valid() && scr->is_valid()) {
-			native = scr->get_instance_base_type();
-			for (int i = 0; i < p_class->_signals.size(); i++) {
-				if (scr->has_script_signal(p_class->_signals[i].name)) {
-					_set_error("The signal \"" + p_class->_signals[i].name + "\" already exists in a parent class.", p_class->_signals[i].line);
-					return;
-				}
-			}
-		}
-	} else if (base.kind == DataType::NATIVE) {
-		native = base.native_type;
-	}
-
-	if (native != StringName()) {
-		for (int i = 0; i < p_class->_signals.size(); i++) {
-			if (ClassDB::has_signal(native, p_class->_signals[i].name)) {
-				_set_error("The signal \"" + p_class->_signals[i].name + "\" already exists in a parent class.", p_class->_signals[i].line);
-				return;
-			}
-		}
 	}
 	}
 
 
-	// Inner classes
-	for (int i = 0; i < p_class->subclasses.size(); i++) {
-		current_class = p_class->subclasses[i];
-		_check_class_level_types(current_class);
-		if (error_set) {
-			return;
-		}
-		current_class = p_class;
-	}
+	return true;
 }
 }
 
 
-void GDScriptParser::_check_function_types(FunctionNode *p_function) {
-	p_function->return_type = _resolve_type(p_function->return_type, p_function->line);
-
-	// Arguments
-	int defaults_ofs = p_function->arguments.size() - p_function->default_values.size();
-	for (int i = 0; i < p_function->arguments.size(); i++) {
-		if (i < defaults_ofs) {
-			p_function->argument_types.write[i] = _resolve_type(p_function->argument_types[i], p_function->line);
-		} else {
-			if (p_function->default_values[i - defaults_ofs]->type != Node::TYPE_OPERATOR) {
-				_set_error("Parser bug: invalid argument default value.", p_function->line, p_function->column);
-				return;
-			}
-
-			OperatorNode *op = static_cast<OperatorNode *>(p_function->default_values[i - defaults_ofs]);
-
-			if (op->op != OperatorNode::OP_ASSIGN || op->arguments.size() != 2) {
-				_set_error("Parser bug: invalid argument default value operation.", p_function->line);
-				return;
-			}
-
-			DataType def_type = _reduce_node_type(op->arguments[1]);
-
-			if (p_function->argument_types[i].infer_type) {
-				def_type.is_constant = false;
-				p_function->argument_types.write[i] = def_type;
-			} else {
-				p_function->argument_types.write[i] = _resolve_type(p_function->argument_types[i], p_function->line);
-
-				if (!_is_type_compatible(p_function->argument_types[i], def_type, true)) {
-					String arg_name = p_function->arguments[i];
-					_set_error("Value type (" + def_type.to_string() + ") doesn't match the type of argument '" +
-									   arg_name + "' (" + p_function->argument_types[i].to_string() + ").",
-							p_function->line);
-				}
-			}
-		}
-#ifdef DEBUG_ENABLED
-		if (p_function->arguments_usage[i] == 0 && !p_function->arguments[i].operator String().begins_with("_")) {
-			_add_warning(GDScriptWarning::UNUSED_ARGUMENT, p_function->line, p_function->name, p_function->arguments[i].operator String());
-		}
-		for (int j = 0; j < current_class->variables.size(); j++) {
-			if (current_class->variables[j].identifier == p_function->arguments[i]) {
-				_add_warning(GDScriptWarning::SHADOWED_VARIABLE, p_function->line, p_function->arguments[i], itos(current_class->variables[j].line));
-			}
-		}
-#endif // DEBUG_ENABLED
-	}
-
-	if (!(p_function->name == "_init")) {
-		// Signature for the initializer may vary
-#ifdef DEBUG_ENABLED
-		DataType return_type;
-		List<DataType> arg_types;
-		int default_arg_count = 0;
-		bool _static = false;
-		bool vararg = false;
-
-		DataType base_type = current_class->base_type;
-		if (_get_function_signature(base_type, p_function->name, return_type, arg_types, default_arg_count, _static, vararg)) {
-			bool valid = _static == p_function->_static;
-			valid = valid && return_type == p_function->return_type;
-			int argsize_diff = p_function->arguments.size() - arg_types.size();
-			valid = valid && argsize_diff >= 0;
-			valid = valid && p_function->default_values.size() >= default_arg_count + argsize_diff;
-			int i = 0;
-			for (List<DataType>::Element *E = arg_types.front(); valid && E; E = E->next()) {
-				valid = valid && E->get() == p_function->argument_types[i++];
-			}
-
-			if (!valid) {
-				String parent_signature = return_type.has_type ? return_type.to_string() : "Variant";
-				if (parent_signature == "null") {
-					parent_signature = "void";
-				}
-				parent_signature += " " + p_function->name + "(";
-				if (arg_types.size()) {
-					int j = 0;
-					for (List<DataType>::Element *E = arg_types.front(); E; E = E->next()) {
-						if (E != arg_types.front()) {
-							parent_signature += ", ";
-						}
-						String arg = E->get().to_string();
-						if (arg == "null" || arg == "var") {
-							arg = "Variant";
-						}
-						parent_signature += arg;
-						if (j == arg_types.size() - default_arg_count) {
-							parent_signature += "=default";
-						}
+bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p_node) {
+	this->_is_tool = true;
+	return true;
+}
 
 
-						j++;
-					}
-				}
-				parent_signature += ")";
-				_set_error("The function signature doesn't match the parent. Parent signature is: \"" + parent_signature + "\".", p_function->line);
-				return;
-			}
-		}
-#endif // DEBUG_ENABLED
-	} else {
-		if (p_function->return_type.has_type && (p_function->return_type.kind != DataType::BUILTIN || p_function->return_type.builtin_type != Variant::NIL)) {
-			_set_error("The constructor can't return a value.", p_function->line);
-			return;
-		}
-	}
+bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p_node) {
+	ERR_FAIL_COND_V_MSG(p_node->type != Node::CLASS, false, R"("@icon" annotation can only be applied to classes.)");
+	ClassNode *p_class = static_cast<ClassNode *>(p_node);
+	p_class->icon_path = p_annotation->resolved_arguments[0];
+	return true;
+}
 
 
-	if (p_function->return_type.has_type && (p_function->return_type.kind != DataType::BUILTIN || p_function->return_type.builtin_type != Variant::NIL)) {
-		if (!p_function->body->has_return) {
-			_set_error("A non-void function must return a value in all possible paths.", p_function->line);
-			return;
-		}
-	}
+bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node *p_node) {
+	ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, R"("@onready" annotation can only be applied to class variables.)");
 
 
-	if (p_function->has_yield) {
-		// yield() will make the function return a GDScriptFunctionState, so the type is ambiguous
-		p_function->return_type.has_type = false;
-		p_function->return_type.may_yield = true;
+	VariableNode *variable = static_cast<VariableNode *>(p_node);
+	if (variable->onready) {
+		push_error(R"("@onready" annotation can only be used once per variable.)");
+		return false;
 	}
 	}
+	variable->onready = true;
+	current_class->onready_used = true;
+	return true;
 }
 }
 
 
-void GDScriptParser::_check_class_blocks_types(ClassNode *p_class) {
-	// Function blocks
-	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 = nullptr;
-		current_function = nullptr;
-		if (error_set) {
-			return;
-		}
-	}
-
-	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 = nullptr;
-		current_function = nullptr;
-		if (error_set) {
-			return;
-		}
-	}
+template <PropertyHint t_hint, Variant::Type t_type>
+bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node *p_node) {
+	ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
 
 
-#ifdef DEBUG_ENABLED
-	// Warnings
-	for (int i = 0; i < p_class->variables.size(); i++) {
-		if (p_class->variables[i].usages == 0) {
-			_add_warning(GDScriptWarning::UNUSED_CLASS_VARIABLE, p_class->variables[i].line, p_class->variables[i].identifier);
-		}
-	}
-	for (int i = 0; i < p_class->_signals.size(); i++) {
-		if (p_class->_signals[i].emissions == 0) {
-			_add_warning(GDScriptWarning::UNUSED_SIGNAL, p_class->_signals[i].line, p_class->_signals[i].name);
-		}
+	VariableNode *variable = static_cast<VariableNode *>(p_node);
+	if (variable->exported) {
+		push_error(vformat(R"(Annotation "%s" cannot be used with another "@export" annotation.)", p_annotation->name), p_annotation);
+		return false;
 	}
 	}
-#endif // DEBUG_ENABLED
 
 
-	// Inner classes
-	for (int i = 0; i < p_class->subclasses.size(); i++) {
-		current_class = p_class->subclasses[i];
-		_check_class_blocks_types(current_class);
-		if (error_set) {
-			return;
-		}
-		current_class = p_class;
-	}
-}
+	variable->exported = true;
+	// TODO: Improving setting type, especially for range hints, which can be int or float.
+	variable->export_info.type = t_type;
+	variable->export_info.hint = t_hint;
 
 
-#ifdef DEBUG_ENABLED
-static String _find_function_name(const GDScriptParser::OperatorNode *p_call) {
-	switch (p_call->arguments[0]->type) {
-		case GDScriptParser::Node::TYPE_TYPE: {
-			return Variant::get_type_name(static_cast<GDScriptParser::TypeNode *>(p_call->arguments[0])->vtype);
-		} break;
-		case GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION: {
-			return GDScriptFunctions::get_func_name(static_cast<GDScriptParser::BuiltInFunctionNode *>(p_call->arguments[0])->function);
-		} break;
-		default: {
-			int id_index = p_call->op == GDScriptParser::OperatorNode::OP_PARENT_CALL ? 0 : 1;
-			if (p_call->arguments.size() > id_index && p_call->arguments[id_index]->type == GDScriptParser::Node::TYPE_IDENTIFIER) {
-				return static_cast<GDScriptParser::IdentifierNode *>(p_call->arguments[id_index])->name;
+	if (p_annotation->name == "@export") {
+		if (variable->datatype_specifier == nullptr) {
+			if (variable->initializer == nullptr) {
+				push_error(R"(Cannot use "@export" annotation with variable without type or initializer, since type can't be inferred.)", p_annotation);
+				return false;
+			}
+			if (variable->initializer->type != Node::LITERAL) {
+				push_error(R"(To use "@export" annotation with type-less variable, the default value must be a literal.)", p_annotation);
+				return false;
 			}
 			}
-		} break;
+			variable->export_info.type = static_cast<LiteralNode *>(variable->initializer)->value.get_type();
+		} // else: Actual type will be set by the analyzer, which can infer the proper type.
 	}
 	}
-	return String();
-}
-#endif // DEBUG_ENABLED
-
-void GDScriptParser::_check_block_types(BlockNode *p_block) {
-	Node *last_var_assign = nullptr;
-
-	// Check each statement
-	for (List<Node *>::Element *E = p_block->statements.front(); E; E = E->next()) {
-		Node *statement = E->get();
-		switch (statement->type) {
-			case Node::TYPE_NEWLINE:
-			case Node::TYPE_BREAKPOINT: {
-				// Nothing to do
-			} break;
-			case Node::TYPE_ASSERT: {
-				AssertNode *an = static_cast<AssertNode *>(statement);
-				_mark_line_as_safe(an->line);
-				_reduce_node_type(an->condition);
-				_reduce_node_type(an->message);
-			} break;
-			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);
-
-				last_var_assign = lv->assign;
-				if (lv->assign) {
-					lv->assign_op->arguments[0]->set_datatype(lv->datatype);
-					DataType assign_type = _reduce_node_type(lv->assign);
-#ifdef DEBUG_ENABLED
-					if (assign_type.has_type && assign_type.kind == DataType::BUILTIN && assign_type.builtin_type == Variant::NIL) {
-						if (lv->assign->type == Node::TYPE_OPERATOR) {
-							OperatorNode *call = static_cast<OperatorNode *>(lv->assign);
-							if (call->op == OperatorNode::OP_CALL || call->op == OperatorNode::OP_PARENT_CALL) {
-								_add_warning(GDScriptWarning::VOID_ASSIGNMENT, lv->line, _find_function_name(call));
-							}
-						}
-					}
-					if (lv->datatype.has_type && assign_type.may_yield && lv->assign->type == Node::TYPE_OPERATOR) {
-						_add_warning(GDScriptWarning::FUNCTION_MAY_YIELD, lv->line, _find_function_name(static_cast<OperatorNode *>(lv->assign)));
-					}
-					for (int i = 0; i < current_class->variables.size(); i++) {
-						if (current_class->variables[i].identifier == lv->name) {
-							_add_warning(GDScriptWarning::SHADOWED_VARIABLE, lv->line, lv->name, itos(current_class->variables[i].line));
-						}
-					}
-#endif // DEBUG_ENABLED
-
-					if (!_is_type_compatible(lv->datatype, assign_type)) {
-						// Try supertype test
-						if (_is_type_compatible(assign_type, lv->datatype)) {
-							_mark_line_as_unsafe(lv->line);
-						} else {
-							// Try implicit conversion
-							if (lv->datatype.kind != DataType::BUILTIN || !_is_type_compatible(lv->datatype, assign_type, true)) {
-								_set_error("The assigned value type (" + assign_type.to_string() + ") doesn't match the variable's type (" +
-												   lv->datatype.to_string() + ").",
-										lv->line);
-								return;
-							}
-							// Replace assignment with implicit 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 = (int64_t)lv->datatype.builtin_type;
-							tgt_type->datatype = _type_from_variant(tgt_type->value);
-
-							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.write[1] = convert_call;
-#ifdef DEBUG_ENABLED
-							if (lv->datatype.builtin_type == Variant::INT && assign_type.builtin_type == Variant::FLOAT) {
-								_add_warning(GDScriptWarning::NARROWING_CONVERSION, lv->line);
-							}
-#endif // DEBUG_ENABLED
-						}
-					}
-					if (lv->datatype.infer_type) {
-						if (!assign_type.has_type) {
-							_set_error("The assigned value doesn't have a set type; the variable type can't be inferred.", lv->line);
-							return;
-						}
-						if (assign_type.kind == DataType::BUILTIN && assign_type.builtin_type == Variant::NIL) {
-							_set_error("The variable type cannot be inferred because its value is \"null\".", lv->line);
-							return;
-						}
-						lv->datatype = assign_type;
-						lv->datatype.is_constant = false;
-					}
-					if (lv->datatype.has_type && !assign_type.has_type) {
-						_mark_line_as_unsafe(lv->line);
-					}
-				}
-			} break;
-			case Node::TYPE_OPERATOR: {
-				OperatorNode *op = static_cast<OperatorNode *>(statement);
-
-				switch (op->op) {
-					case OperatorNode::OP_ASSIGN:
-					case OperatorNode::OP_ASSIGN_ADD:
-					case OperatorNode::OP_ASSIGN_SUB:
-					case OperatorNode::OP_ASSIGN_MUL:
-					case OperatorNode::OP_ASSIGN_DIV:
-					case OperatorNode::OP_ASSIGN_MOD:
-					case OperatorNode::OP_ASSIGN_SHIFT_LEFT:
-					case OperatorNode::OP_ASSIGN_SHIFT_RIGHT:
-					case OperatorNode::OP_ASSIGN_BIT_AND:
-					case OperatorNode::OP_ASSIGN_BIT_OR:
-					case OperatorNode::OP_ASSIGN_BIT_XOR: {
-						if (op->arguments.size() < 2) {
-							_set_error("Parser bug: operation without enough arguments.", op->line, op->column);
-							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) {
-							return;
-						}
-
-						if (check_types) {
-							if (!lh_type.has_type) {
-								if (op->arguments[0]->type == Node::TYPE_OPERATOR) {
-									_mark_line_as_unsafe(op->line);
-								}
-							}
-							if (lh_type.is_constant) {
-								_set_error("Can't assign a new value to a constant.", op->line);
-								return;
-							}
-						}
-
-						DataType rh_type;
-						if (op->op != OperatorNode::OP_ASSIGN) {
-							// Validate operation
-							DataType arg_type = _reduce_node_type(op->arguments[1]);
-							if (!arg_type.has_type) {
-								_mark_line_as_unsafe(op->line);
-								break;
-							}
-
-							Variant::Operator oper = _get_variant_operation(op->op);
-							bool valid = false;
-							rh_type = _get_operation_type(oper, lh_type, arg_type, valid);
 
 
-							if (check_types && !valid) {
-								_set_error("Invalid operand types (\"" + lh_type.to_string() + "\" and \"" + arg_type.to_string() +
-												   "\") to assignment operator \"" + Variant::get_operator_name(oper) + "\".",
-										op->line);
-								return;
-							}
-						} else {
-							rh_type = _reduce_node_type(op->arguments[1]);
-						}
-#ifdef DEBUG_ENABLED
-						if (rh_type.has_type && rh_type.kind == DataType::BUILTIN && rh_type.builtin_type == Variant::NIL) {
-							if (op->arguments[1]->type == Node::TYPE_OPERATOR) {
-								OperatorNode *call = static_cast<OperatorNode *>(op->arguments[1]);
-								if (call->op == OperatorNode::OP_CALL || call->op == OperatorNode::OP_PARENT_CALL) {
-									_add_warning(GDScriptWarning::VOID_ASSIGNMENT, op->line, _find_function_name(call));
-								}
-							}
-						}
-						if (lh_type.has_type && rh_type.may_yield && op->arguments[1]->type == Node::TYPE_OPERATOR) {
-							_add_warning(GDScriptWarning::FUNCTION_MAY_YIELD, op->line, _find_function_name(static_cast<OperatorNode *>(op->arguments[1])));
-						}
-
-#endif // DEBUG_ENABLED
-						bool type_match = lh_type.has_type && rh_type.has_type;
-						if (check_types && !_is_type_compatible(lh_type, rh_type)) {
-							type_match = false;
-
-							// Try supertype test
-							if (_is_type_compatible(rh_type, lh_type)) {
-								_mark_line_as_unsafe(op->line);
-							} else {
-								// Try implicit conversion
-								if (lh_type.kind != DataType::BUILTIN || !_is_type_compatible(lh_type, rh_type, true)) {
-									_set_error("The assigned value's type (" + rh_type.to_string() + ") doesn't match the variable's type (" +
-													   lh_type.to_string() + ").",
-											op->line);
-									return;
-								}
-								if (op->op == OperatorNode::OP_ASSIGN) {
-									// Replace assignment with implicit 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;
-									tgt_type->datatype = _type_from_variant(tgt_type->value);
-
-									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.write[1] = convert_call;
-
-									type_match = true; // Since we are converting, the type is matching
-								}
-#ifdef DEBUG_ENABLED
-								if (lh_type.builtin_type == Variant::INT && rh_type.builtin_type == Variant::FLOAT) {
-									_add_warning(GDScriptWarning::NARROWING_CONVERSION, op->line);
-								}
-#endif // DEBUG_ENABLED
-							}
-						}
-#ifdef DEBUG_ENABLED
-						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);
-						}
-#endif // DEBUG_ENABLED
-						op->datatype.has_type = type_match;
-					} break;
-					case OperatorNode::OP_CALL:
-					case OperatorNode::OP_PARENT_CALL: {
-						_mark_line_as_safe(op->line);
-						DataType func_type = _reduce_function_call_type(op);
-#ifdef DEBUG_ENABLED
-						if (func_type.has_type && (func_type.kind != DataType::BUILTIN || func_type.builtin_type != Variant::NIL)) {
-							// Figure out function name for warning
-							String func_name = _find_function_name(op);
-							if (func_name.empty()) {
-								func_name = "<undetected name>";
-							}
-							_add_warning(GDScriptWarning::RETURN_VALUE_DISCARDED, op->line, func_name);
-						}
-#endif // DEBUG_ENABLED
-						if (error_set) {
-							return;
-						}
-					} break;
-					case OperatorNode::OP_YIELD: {
-						_mark_line_as_safe(op->line);
-						_reduce_node_type(op);
-					} break;
-					default: {
-						_mark_line_as_safe(op->line);
-						_reduce_node_type(op); // Test for safety anyway
-#ifdef DEBUG_ENABLED
-						if (op->op == OperatorNode::OP_TERNARY_IF) {
-							_add_warning(GDScriptWarning::STANDALONE_TERNARY, statement->line);
-						} else {
-							_add_warning(GDScriptWarning::STANDALONE_EXPRESSION, statement->line);
-						}
-#endif // DEBUG_ENABLED
-					}
-				}
-			} 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;
-							}
-						}
+	String hint_string;
+	for (int i = 0; i < p_annotation->resolved_arguments.size(); i++) {
+		if (i > 0) {
+			hint_string += ",";
+		}
+		hint_string += String(p_annotation->resolved_arguments[i]);
+	}
 
 
-						if (!function_type.has_type) {
-							break;
-						}
+	variable->export_info.hint_string = hint_string;
 
 
-						if (function_type.kind == DataType::BUILTIN && function_type.builtin_type == Variant::NIL) {
-							// Return void, should not have arguments
-							if (cf->arguments.size() > 0) {
-								_set_error("A void function cannot return a value.", cf->line, cf->column);
-								return;
-							}
-						} else {
-							// Return something, cannot be empty
-							if (cf->arguments.size() == 0) {
-								_set_error("A non-void function must return a value.", cf->line, cf->column);
-								return;
-							}
+	return true;
+}
 
 
-							if (!_is_type_compatible(function_type, ret_type)) {
-								_set_error("The returned value type (" + ret_type.to_string() + ") doesn't match the function return type (" +
-												   function_type.to_string() + ").",
-										cf->line, cf->column);
-								return;
-							}
-						}
-					} break;
-					case ControlFlowNode::CF_MATCH: {
-						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: {
-				ConstantNode *cn = static_cast<ConstantNode *>(statement);
-				// Strings are fine since they can be multiline comments
-				if (cn->value.get_type() == Variant::STRING) {
-					break;
-				}
-				[[fallthrough]];
+bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Node *p_node) {
+	ERR_FAIL_V_MSG(false, "Not implemented.");
+}
+
+template <MultiplayerAPI::RPCMode t_mode>
+bool GDScriptParser::network_annotations(const AnnotationNode *p_annotation, Node *p_node) {
+	ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE && p_node->type != Node::FUNCTION, false, vformat(R"("%s" annotation can only be applied to variables and functions.)", p_annotation->name));
+
+	switch (p_node->type) {
+		case Node::VARIABLE: {
+			VariableNode *variable = static_cast<VariableNode *>(p_node);
+			if (variable->rpc_mode != MultiplayerAPI::RPC_MODE_DISABLED) {
+				push_error(R"(RPC annotations can only be used once per variable.)", p_annotation);
 			}
 			}
-			default: {
-				_mark_line_as_safe(statement->line);
-				_reduce_node_type(statement); // Test for safety anyway
-#ifdef DEBUG_ENABLED
-				_add_warning(GDScriptWarning::STANDALONE_EXPRESSION, statement->line);
-#endif // DEBUG_ENABLED
+			variable->rpc_mode = t_mode;
+			break;
+		}
+		case Node::FUNCTION: {
+			FunctionNode *function = static_cast<FunctionNode *>(p_node);
+			if (function->rpc_mode != MultiplayerAPI::RPC_MODE_DISABLED) {
+				push_error(R"(RPC annotations can only be used once per function.)", p_annotation);
 			}
 			}
+			function->rpc_mode = t_mode;
+			break;
 		}
 		}
+		default:
+			return false; // Unreachable.
 	}
 	}
 
 
-	// Parse sub blocks
-	for (int i = 0; i < p_block->sub_blocks.size(); i++) {
-		current_block = p_block->sub_blocks[i];
-		_check_block_types(current_block);
-		current_block = p_block;
-		if (error_set) {
-			return;
-		}
-	}
+	return true;
+}
+
+/*---------- PRETTY PRINT FOR DEBUG ----------*/
 
 
 #ifdef DEBUG_ENABLED
 #ifdef DEBUG_ENABLED
-	// Warnings check
-	for (Map<StringName, LocalVarNode *>::Element *E = p_block->variables.front(); E; E = E->next()) {
-		LocalVarNode *lv = E->get();
-		if (!lv->name.operator String().begins_with("_")) {
-			if (lv->usages == 0) {
-				_add_warning(GDScriptWarning::UNUSED_VARIABLE, lv->line, lv->name);
-			} else if (lv->assignments == 0) {
-				_add_warning(GDScriptWarning::UNASSIGNED_VARIABLE, lv->line, lv->name);
-			}
+
+void GDScriptParser::TreePrinter::increase_indent() {
+	indent_level++;
+	indent = "";
+	for (int i = 0; i < indent_level * 4; i++) {
+		if (i % 4 == 0) {
+			indent += "|";
+		} else {
+			indent += " ";
 		}
 		}
 	}
 	}
-#endif // DEBUG_ENABLED
 }
 }
 
 
-void GDScriptParser::_set_error(const String &p_error, int p_line, int p_column) {
-	if (error_set) {
-		return; //allow no further errors
+void GDScriptParser::TreePrinter::decrease_indent() {
+	indent_level--;
+	indent = "";
+	for (int i = 0; i < indent_level * 4; i++) {
+		if (i % 4 == 0) {
+			indent += "|";
+		} else {
+			indent += " ";
+		}
 	}
 	}
-
-	error = p_error;
-	error_line = p_line < 0 ? tokenizer->get_token_line() : p_line;
-	error_column = p_column < 0 ? tokenizer->get_token_column() : p_column;
-	error_set = true;
 }
 }
 
 
-#ifdef DEBUG_ENABLED
-void GDScriptParser::_add_warning(int p_code, int p_line, const String &p_symbol1, const String &p_symbol2, const String &p_symbol3, const String &p_symbol4) {
-	Vector<String> symbols;
-	if (!p_symbol1.empty()) {
-		symbols.push_back(p_symbol1);
-	}
-	if (!p_symbol2.empty()) {
-		symbols.push_back(p_symbol2);
+void GDScriptParser::TreePrinter::push_line(const String &p_line) {
+	if (!p_line.empty()) {
+		push_text(p_line);
 	}
 	}
-	if (!p_symbol3.empty()) {
-		symbols.push_back(p_symbol3);
-	}
-	if (!p_symbol4.empty()) {
-		symbols.push_back(p_symbol4);
-	}
-	_add_warning(p_code, p_line, symbols);
+	printed += "\n";
+	pending_indent = true;
 }
 }
 
 
-void GDScriptParser::_add_warning(int p_code, int p_line, const Vector<String> &p_symbols) {
-	if (GLOBAL_GET("debug/gdscript/warnings/exclude_addons").booleanize() && base_path.begins_with("res://addons/")) {
-		return;
-	}
-	if (tokenizer->is_ignoring_warnings() || !GLOBAL_GET("debug/gdscript/warnings/enable").booleanize()) {
-		return;
+void GDScriptParser::TreePrinter::push_text(const String &p_text) {
+	if (pending_indent) {
+		printed += indent;
+		pending_indent = false;
 	}
 	}
-	String warn_name = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)p_code).to_lower();
-	if (tokenizer->get_warning_global_skips().has(warn_name)) {
-		return;
+	printed += p_text;
+}
+
+void GDScriptParser::TreePrinter::print_annotation(AnnotationNode *p_annotation) {
+	push_text(p_annotation->name);
+	push_text(" (");
+	for (int i = 0; i < p_annotation->arguments.size(); i++) {
+		if (i > 0) {
+			push_text(" , ");
+		}
+		print_expression(p_annotation->arguments[i]);
 	}
 	}
-	if (!GLOBAL_GET("debug/gdscript/warnings/" + warn_name)) {
-		return;
+	push_line(")");
+}
+
+void GDScriptParser::TreePrinter::print_array(ArrayNode *p_array) {
+	push_text("[ ");
+	for (int i = 0; i < p_array->elements.size(); i++) {
+		if (i > 0) {
+			push_text(" , ");
+		}
+		print_expression(p_array->elements[i]);
 	}
 	}
+	push_text(" ]");
+}
 
 
-	GDScriptWarning warn;
-	warn.code = (GDScriptWarning::Code)p_code;
-	warn.symbols = p_symbols;
-	warn.line = p_line == -1 ? tokenizer->get_token_line() : p_line;
+void GDScriptParser::TreePrinter::print_assert(AssertNode *p_assert) {
+	push_text("Assert ( ");
+	print_expression(p_assert->condition);
+	push_line(" )");
+}
 
 
-	List<GDScriptWarning>::Element *before = nullptr;
-	for (List<GDScriptWarning>::Element *E = warnings.front(); E; E = E->next()) {
-		if (E->get().line > warn.line) {
+void GDScriptParser::TreePrinter::print_assignment(AssignmentNode *p_assignment) {
+	switch (p_assignment->assignee->type) {
+		case Node::IDENTIFIER:
+			print_identifier(static_cast<IdentifierNode *>(p_assignment->assignee));
 			break;
 			break;
-		}
-		before = E;
+		case Node::SUBSCRIPT:
+			print_subscript(static_cast<SubscriptNode *>(p_assignment->assignee));
+			break;
+		default:
+			break; // Unreachable.
 	}
 	}
-	if (before) {
-		warnings.insert_after(before, warn);
-	} else {
-		warnings.push_front(warn);
+
+	push_text(" ");
+	switch (p_assignment->operation) {
+		case AssignmentNode::OP_ADDITION:
+			push_text("+");
+			break;
+		case AssignmentNode::OP_SUBTRACTION:
+			push_text("-");
+			break;
+		case AssignmentNode::OP_MULTIPLICATION:
+			push_text("*");
+			break;
+		case AssignmentNode::OP_DIVISION:
+			push_text("/");
+			break;
+		case AssignmentNode::OP_MODULO:
+			push_text("%");
+			break;
+		case AssignmentNode::OP_BIT_SHIFT_LEFT:
+			push_text("<<");
+			break;
+		case AssignmentNode::OP_BIT_SHIFT_RIGHT:
+			push_text(">>");
+			break;
+		case AssignmentNode::OP_BIT_AND:
+			push_text("&");
+			break;
+		case AssignmentNode::OP_BIT_OR:
+			push_text("|");
+			break;
+		case AssignmentNode::OP_BIT_XOR:
+			push_text("^");
+			break;
+		case AssignmentNode::OP_NONE:
+			break;
 	}
 	}
+	push_text("= ");
+	print_expression(p_assignment->assigned_value);
+	push_line();
 }
 }
-#endif // DEBUG_ENABLED
 
 
-String GDScriptParser::get_error() const {
-	return error;
+void GDScriptParser::TreePrinter::print_await(AwaitNode *p_await) {
+	push_text("Await ");
+	print_expression(p_await->to_await);
 }
 }
 
 
-int GDScriptParser::get_error_line() const {
-	return error_line;
+void GDScriptParser::TreePrinter::print_binary_op(BinaryOpNode *p_binary_op) {
+	// Surround in parenthesis for disambiguation.
+	push_text("(");
+	print_expression(p_binary_op->left_operand);
+	switch (p_binary_op->operation) {
+		case BinaryOpNode::OP_ADDITION:
+			push_text(" + ");
+			break;
+		case BinaryOpNode::OP_SUBTRACTION:
+			push_text(" - ");
+			break;
+		case BinaryOpNode::OP_MULTIPLICATION:
+			push_text(" * ");
+			break;
+		case BinaryOpNode::OP_DIVISION:
+			push_text(" / ");
+			break;
+		case BinaryOpNode::OP_MODULO:
+			push_text(" % ");
+			break;
+		case BinaryOpNode::OP_BIT_LEFT_SHIFT:
+			push_text(" << ");
+			break;
+		case BinaryOpNode::OP_BIT_RIGHT_SHIFT:
+			push_text(" >> ");
+			break;
+		case BinaryOpNode::OP_BIT_AND:
+			push_text(" & ");
+			break;
+		case BinaryOpNode::OP_BIT_OR:
+			push_text(" | ");
+			break;
+		case BinaryOpNode::OP_BIT_XOR:
+			push_text(" ^ ");
+			break;
+		case BinaryOpNode::OP_LOGIC_AND:
+			push_text(" AND ");
+			break;
+		case BinaryOpNode::OP_LOGIC_OR:
+			push_text(" OR ");
+			break;
+		case BinaryOpNode::OP_TYPE_TEST:
+			push_text(" IS ");
+			break;
+		case BinaryOpNode::OP_CONTENT_TEST:
+			push_text(" IN ");
+			break;
+		case BinaryOpNode::OP_COMP_EQUAL:
+			push_text(" == ");
+			break;
+		case BinaryOpNode::OP_COMP_NOT_EQUAL:
+			push_text(" != ");
+			break;
+		case BinaryOpNode::OP_COMP_LESS:
+			push_text(" < ");
+			break;
+		case BinaryOpNode::OP_COMP_LESS_EQUAL:
+			push_text(" <= ");
+			break;
+		case BinaryOpNode::OP_COMP_GREATER:
+			push_text(" > ");
+			break;
+		case BinaryOpNode::OP_COMP_GREATER_EQUAL:
+			push_text(" >= ");
+			break;
+	}
+	print_expression(p_binary_op->right_operand);
+	// Surround in parenthesis for disambiguation.
+	push_text(")");
 }
 }
 
 
-int GDScriptParser::get_error_column() const {
-	return error_column;
+void GDScriptParser::TreePrinter::print_call(CallNode *p_call) {
+	if (p_call->is_super) {
+		push_text("super");
+		if (p_call->callee != nullptr) {
+			push_text(".");
+			print_expression(p_call->callee);
+		}
+	} else {
+		print_expression(p_call->callee);
+	}
+	push_text("( ");
+	for (int i = 0; i < p_call->arguments.size(); i++) {
+		if (i > 0) {
+			push_text(" , ");
+		}
+		print_expression(p_call->arguments[i]);
+	}
+	push_text(" )");
 }
 }
 
 
-bool GDScriptParser::has_error() const {
-	return error_set;
+void GDScriptParser::TreePrinter::print_cast(CastNode *p_cast) {
+	print_expression(p_cast->operand);
+	push_text(" AS ");
+	print_type(p_cast->cast_type);
 }
 }
 
 
-Error GDScriptParser::_parse(const String &p_base_path) {
-	base_path = p_base_path;
+void GDScriptParser::TreePrinter::print_class(ClassNode *p_class) {
+	push_text("Class ");
+	if (p_class->identifier == nullptr) {
+		push_text("<unnamed>");
+	} else {
+		print_identifier(p_class->identifier);
+	}
 
 
-	//assume class
-	ClassNode *main_class = alloc_node<ClassNode>();
-	main_class->initializer = alloc_node<BlockNode>();
-	main_class->initializer->parent_class = main_class;
-	main_class->ready = alloc_node<BlockNode>();
-	main_class->ready->parent_class = main_class;
-	current_class = main_class;
+	if (p_class->extends_used) {
+		bool first = true;
+		push_text(" Extends ");
+		if (!p_class->extends_path.empty()) {
+			push_text(vformat(R"("%s")", p_class->extends_path));
+			first = false;
+		}
+		for (int i = 0; i < p_class->extends.size(); i++) {
+			if (!first) {
+				push_text(".");
+			} else {
+				first = false;
+			}
+			push_text(p_class->extends[i]);
+		}
+	}
 
 
-	_parse_class(main_class);
+	push_line(" :");
 
 
-	if (tokenizer->get_token() == GDScriptTokenizer::TK_ERROR) {
-		error_set = false;
-		_set_error("Parse error: " + tokenizer->get_token_error());
-	}
+	increase_indent();
 
 
-	bool for_completion_error_set = false;
-	if (error_set && for_completion) {
-		for_completion_error_set = true;
-		error_set = false;
-	}
+	for (int i = 0; i < p_class->members.size(); i++) {
+		const ClassNode::Member &m = p_class->members[i];
 
 
-	if (error_set) {
-		return ERR_PARSE_ERROR;
+		switch (m.type) {
+			case ClassNode::Member::CLASS:
+				print_class(m.m_class);
+				break;
+			case ClassNode::Member::VARIABLE:
+				print_variable(m.variable);
+				break;
+			case ClassNode::Member::CONSTANT:
+				print_constant(m.constant);
+				break;
+			case ClassNode::Member::SIGNAL:
+				print_signal(m.signal);
+				break;
+			case ClassNode::Member::FUNCTION:
+				print_function(m.function);
+				break;
+			case ClassNode::Member::ENUM:
+				print_enum(m.m_enum);
+				break;
+			case ClassNode::Member::ENUM_VALUE:
+				break; // Nothing. Will be printed by enum.
+			case ClassNode::Member::UNDEFINED:
+				push_line("<unknown member>");
+				break;
+		}
 	}
 	}
 
 
-	if (dependencies_only) {
-		return OK;
-	}
+	decrease_indent();
+}
 
 
-	_determine_inheritance(main_class);
+void GDScriptParser::TreePrinter::print_constant(ConstantNode *p_constant) {
+	push_text("Constant ");
+	print_identifier(p_constant->identifier);
 
 
-	if (error_set) {
-		return ERR_PARSE_ERROR;
+	increase_indent();
+
+	push_line();
+	push_text("= ");
+	if (p_constant->initializer == nullptr) {
+		push_text("<missing value>");
+	} else {
+		print_expression(p_constant->initializer);
 	}
 	}
+	decrease_indent();
+	push_line();
+}
 
 
-	current_class = main_class;
-	current_function = nullptr;
-	current_block = nullptr;
+void GDScriptParser::TreePrinter::print_dictionary(DictionaryNode *p_dictionary) {
+	push_line("{");
+	increase_indent();
+	for (int i = 0; i < p_dictionary->elements.size(); i++) {
+		print_expression(p_dictionary->elements[i].key);
+		if (p_dictionary->style == DictionaryNode::PYTHON_DICT) {
+			push_text(" : ");
+		} else {
+			push_text(" = ");
+		}
+		print_expression(p_dictionary->elements[i].value);
+		push_line(" ,");
+	}
+	decrease_indent();
+	push_text("}");
+}
 
 
-	if (for_completion) {
-		check_types = false;
+void GDScriptParser::TreePrinter::print_expression(ExpressionNode *p_expression) {
+	switch (p_expression->type) {
+		case Node::ARRAY:
+			print_array(static_cast<ArrayNode *>(p_expression));
+			break;
+		case Node::ASSIGNMENT:
+			print_assignment(static_cast<AssignmentNode *>(p_expression));
+			break;
+		case Node::AWAIT:
+			print_await(static_cast<AwaitNode *>(p_expression));
+			break;
+		case Node::BINARY_OPERATOR:
+			print_binary_op(static_cast<BinaryOpNode *>(p_expression));
+			break;
+		case Node::CALL:
+			print_call(static_cast<CallNode *>(p_expression));
+			break;
+		case Node::CAST:
+			print_cast(static_cast<CastNode *>(p_expression));
+			break;
+		case Node::DICTIONARY:
+			print_dictionary(static_cast<DictionaryNode *>(p_expression));
+			break;
+		case Node::GET_NODE:
+			print_get_node(static_cast<GetNodeNode *>(p_expression));
+			break;
+		case Node::IDENTIFIER:
+			print_identifier(static_cast<IdentifierNode *>(p_expression));
+			break;
+		case Node::LITERAL:
+			print_literal(static_cast<LiteralNode *>(p_expression));
+			break;
+		case Node::PRELOAD:
+			print_preload(static_cast<PreloadNode *>(p_expression));
+			break;
+		case Node::SELF:
+			print_self(static_cast<SelfNode *>(p_expression));
+			break;
+		case Node::SUBSCRIPT:
+			print_subscript(static_cast<SubscriptNode *>(p_expression));
+			break;
+		case Node::TERNARY_OPERATOR:
+			print_ternary_op(static_cast<TernaryOpNode *>(p_expression));
+			break;
+		case Node::UNARY_OPERATOR:
+			print_unary_op(static_cast<UnaryOpNode *>(p_expression));
+			break;
+		default:
+			push_text(vformat("<unknown expression %d>", p_expression->type));
+			break;
 	}
 	}
+}
 
 
-	// Resolve all class-level stuff before getting into function blocks
-	_check_class_level_types(main_class);
+void GDScriptParser::TreePrinter::print_enum(EnumNode *p_enum) {
+	push_text("Enum ");
+	if (p_enum->identifier != nullptr) {
+		print_identifier(p_enum->identifier);
+	} else {
+		push_text("<unnamed>");
+	}
 
 
-	if (error_set) {
-		return ERR_PARSE_ERROR;
+	push_line(" {");
+	increase_indent();
+	for (int i = 0; i < p_enum->values.size(); i++) {
+		const EnumNode::Value &item = p_enum->values[i];
+		print_identifier(item.identifier);
+		push_text(" = ");
+		push_text(itos(item.value));
+		push_line(" ,");
 	}
 	}
+	decrease_indent();
+	push_line("}");
+}
 
 
-	// Resolve the function blocks
-	_check_class_blocks_types(main_class);
+void GDScriptParser::TreePrinter::print_for(ForNode *p_for) {
+	push_text("For ");
+	print_identifier(p_for->variable);
+	push_text(" IN ");
+	print_expression(p_for->list);
+	push_line(" :");
 
 
-	if (for_completion_error_set) {
-		error_set = true;
-	}
+	increase_indent();
 
 
-	if (error_set) {
-		return ERR_PARSE_ERROR;
-	}
+	print_suite(p_for->loop);
 
 
-#ifdef DEBUG_ENABLED
+	decrease_indent();
+}
 
 
-	// Resolve warning ignores
-	Vector<Pair<int, String>> warning_skips = tokenizer->get_warning_skips();
-	bool warning_is_error = GLOBAL_GET("debug/gdscript/warnings/treat_warnings_as_errors").booleanize();
-	for (List<GDScriptWarning>::Element *E = warnings.front(); E;) {
-		GDScriptWarning &w = E->get();
-		int skip_index = -1;
-		for (int i = 0; i < warning_skips.size(); i++) {
-			if (warning_skips[i].first >= w.line) {
-				break;
-			}
-			skip_index = i;
+void GDScriptParser::TreePrinter::print_function(FunctionNode *p_function) {
+	for (const List<AnnotationNode *>::Element *E = p_function->annotations.front(); E != nullptr; E = E->next()) {
+		print_annotation(E->get());
+	}
+	push_text("Function ");
+	print_identifier(p_function->identifier);
+	push_text("( ");
+	for (int i = 0; i < p_function->parameters.size(); i++) {
+		if (i > 0) {
+			push_text(" , ");
 		}
 		}
-		List<GDScriptWarning>::Element *next = E->next();
-		bool erase = false;
-		if (skip_index != -1) {
-			if (warning_skips[skip_index].second == GDScriptWarning::get_name_from_code(w.code).to_lower()) {
-				erase = true;
+		print_parameter(p_function->parameters[i]);
+	}
+	push_line(" ) :");
+	increase_indent();
+	print_suite(p_function->body);
+	decrease_indent();
+}
+
+void GDScriptParser::TreePrinter::print_get_node(GetNodeNode *p_get_node) {
+	push_text("$");
+	if (p_get_node->string != nullptr) {
+		print_literal(p_get_node->string);
+	} else {
+		for (int i = 0; i < p_get_node->chain.size(); i++) {
+			if (i > 0) {
+				push_text("/");
 			}
 			}
-			warning_skips.remove(skip_index);
-		}
-		if (erase) {
-			warnings.erase(E);
-		} else if (warning_is_error) {
-			_set_error(w.get_message() + " (warning treated as error)", w.line);
-			return ERR_PARSE_ERROR;
+			print_identifier(p_get_node->chain[i]);
 		}
 		}
-		E = next;
 	}
 	}
-#endif // DEBUG_ENABLED
-
-	return OK;
 }
 }
 
 
-Error GDScriptParser::parse_bytecode(const Vector<uint8_t> &p_bytecode, const String &p_base_path, const String &p_self_path) {
-	clear();
-
-	self_path = p_self_path;
-	GDScriptTokenizerBuffer *tb = memnew(GDScriptTokenizerBuffer);
-	tb->set_code_buffer(p_bytecode);
-	tokenizer = tb;
-	Error ret = _parse(p_base_path);
-	memdelete(tb);
-	tokenizer = nullptr;
-	return ret;
+void GDScriptParser::TreePrinter::print_identifier(IdentifierNode *p_identifier) {
+	push_text(p_identifier->name);
 }
 }
 
 
-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, bool p_dependencies_only) {
-	clear();
+void GDScriptParser::TreePrinter::print_if(IfNode *p_if, bool p_is_elif) {
+	if (p_is_elif) {
+		push_text("Elif ");
+	} else {
+		push_text("If ");
+	}
+	print_expression(p_if->condition);
+	push_line(" :");
 
 
-	self_path = p_self_path;
-	GDScriptTokenizerText *tt = memnew(GDScriptTokenizerText);
-	tt->set_code(p_code);
+	increase_indent();
+	print_suite(p_if->true_block);
+	decrease_indent();
 
 
-	validating = p_just_validate;
-	for_completion = p_for_completion;
-	dependencies_only = p_dependencies_only;
-#ifdef DEBUG_ENABLED
-	safe_lines = r_safe_lines;
-#endif // DEBUG_ENABLED
-	tokenizer = tt;
-	Error ret = _parse(p_base_path);
-	memdelete(tt);
-	tokenizer = nullptr;
-	return ret;
+	// FIXME: Properly detect "elif" blocks.
+	if (p_if->false_block != nullptr) {
+		push_line("Else :");
+		increase_indent();
+		print_suite(p_if->false_block);
+		decrease_indent();
+	}
 }
 }
 
 
-bool GDScriptParser::is_tool_script() const {
-	return (head && head->type == Node::TYPE_CLASS && static_cast<const ClassNode *>(head)->tool);
+void GDScriptParser::TreePrinter::print_literal(LiteralNode *p_literal) {
+	// Prefix for string types.
+	switch (p_literal->value.get_type()) {
+		case Variant::NODE_PATH:
+			push_text("^\"");
+			break;
+		case Variant::STRING:
+			push_text("\"");
+			break;
+		case Variant::STRING_NAME:
+			push_text("&\"");
+			break;
+		default:
+			break;
+	}
+	push_text(p_literal->value);
+	// Suffix for string types.
+	switch (p_literal->value.get_type()) {
+		case Variant::NODE_PATH:
+		case Variant::STRING:
+		case Variant::STRING_NAME:
+			push_text("\"");
+			break;
+		default:
+			break;
+	}
 }
 }
 
 
-const GDScriptParser::Node *GDScriptParser::get_parse_tree() const {
-	return head;
+void GDScriptParser::TreePrinter::print_match(MatchNode *p_match) {
+	push_text("Match ");
+	print_expression(p_match->test);
+	push_line(" :");
+
+	increase_indent();
+	for (int i = 0; i < p_match->branches.size(); i++) {
+		print_match_branch(p_match->branches[i]);
+	}
+	decrease_indent();
 }
 }
 
 
-void GDScriptParser::clear() {
-	while (list) {
-		Node *l = list;
-		list = list->next;
-		memdelete(l);
+void GDScriptParser::TreePrinter::print_match_branch(MatchBranchNode *p_match_branch) {
+	for (int i = 0; i < p_match_branch->patterns.size(); i++) {
+		if (i > 0) {
+			push_text(" , ");
+		}
+		print_match_pattern(p_match_branch->patterns[i]);
 	}
 	}
 
 
-	head = nullptr;
-	list = nullptr;
+	push_line(" :");
 
 
-	completion_type = COMPLETION_NONE;
-	completion_node = nullptr;
-	completion_class = nullptr;
-	completion_function = nullptr;
-	completion_block = nullptr;
-	current_block = nullptr;
-	current_class = nullptr;
+	increase_indent();
+	print_suite(p_match_branch->block);
+	decrease_indent();
+}
 
 
-	completion_found = false;
-	rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
+void GDScriptParser::TreePrinter::print_match_pattern(PatternNode *p_match_pattern) {
+	switch (p_match_pattern->pattern_type) {
+		case PatternNode::PT_LITERAL:
+			print_literal(p_match_pattern->literal);
+			break;
+		case PatternNode::PT_WILDCARD:
+			push_text("_");
+			break;
+		case PatternNode::PT_REST:
+			push_text("..");
+			break;
+		case PatternNode::PT_BIND:
+			push_text("Var ");
+			print_identifier(p_match_pattern->bind);
+			break;
+		case PatternNode::PT_EXPRESSION:
+			print_expression(p_match_pattern->expression);
+			break;
+		case PatternNode::PT_ARRAY:
+			push_text("[ ");
+			for (int i = 0; i < p_match_pattern->array.size(); i++) {
+				if (i > 0) {
+					push_text(" , ");
+				}
+				print_match_pattern(p_match_pattern->array[i]);
+			}
+			push_text(" ]");
+			break;
+		case PatternNode::PT_DICTIONARY:
+			push_text("{ ");
+			for (int i = 0; i < p_match_pattern->dictionary.size(); i++) {
+				if (i > 0) {
+					push_text(" , ");
+				}
+				if (p_match_pattern->dictionary[i].key != nullptr) {
+					// Key can be null for rest pattern.
+					print_expression(p_match_pattern->dictionary[i].key);
+					push_text(" : ");
+				}
+				print_match_pattern(p_match_pattern->dictionary[i].value_pattern);
+			}
+			push_text(" }");
+			break;
+	}
+}
 
 
-	current_function = nullptr;
+void GDScriptParser::TreePrinter::print_parameter(ParameterNode *p_parameter) {
+	print_identifier(p_parameter->identifier);
+	if (p_parameter->datatype_specifier != nullptr) {
+		push_text(" : ");
+		print_type(p_parameter->datatype_specifier);
+	}
+	if (p_parameter->default_value != nullptr) {
+		push_text(" = ");
+		print_expression(p_parameter->default_value);
+	}
+}
 
 
-	validating = false;
-	for_completion = false;
-	error_set = false;
-	indent_level.clear();
-	indent_level.push_back(IndentLevel(0, 0));
-	error_line = 0;
-	error_column = 0;
-	pending_newline = -1;
-	parenthesis = 0;
-	current_export.type = Variant::NIL;
-	check_types = true;
-	dependencies_only = false;
-	dependencies.clear();
-	error = "";
-#ifdef DEBUG_ENABLED
-	safe_lines = nullptr;
-#endif // DEBUG_ENABLED
+void GDScriptParser::TreePrinter::print_preload(PreloadNode *p_preload) {
+	push_text(R"(Preload ( ")");
+	push_text(p_preload->resolved_path);
+	push_text(R"(" )");
 }
 }
 
 
-GDScriptParser::CompletionType GDScriptParser::get_completion_type() {
-	return completion_type;
+void GDScriptParser::TreePrinter::print_return(ReturnNode *p_return) {
+	push_text("Return");
+	if (p_return->return_value != nullptr) {
+		push_text(" ");
+		print_expression(p_return->return_value);
+	}
+	push_line();
 }
 }
 
 
-StringName GDScriptParser::get_completion_cursor() {
-	return completion_cursor;
+void GDScriptParser::TreePrinter::print_self(SelfNode *p_self) {
+	push_text("Self(");
+	if (p_self->current_class->identifier != nullptr) {
+		print_identifier(p_self->current_class->identifier);
+	} else {
+		push_text("<main class>");
+	}
+	push_text(")");
 }
 }
 
 
-int GDScriptParser::get_completion_line() {
-	return completion_line;
+void GDScriptParser::TreePrinter::print_signal(SignalNode *p_signal) {
+	push_text("Signal ");
+	print_identifier(p_signal->identifier);
+	push_text("( ");
+	for (int i = 0; i < p_signal->parameters.size(); i++) {
+		print_parameter(p_signal->parameters[i]);
+	}
+	push_line(" )");
 }
 }
 
 
-Variant::Type GDScriptParser::get_completion_built_in_constant() {
-	return completion_built_in_constant;
+void GDScriptParser::TreePrinter::print_subscript(SubscriptNode *p_subscript) {
+	print_expression(p_subscript->base);
+	if (p_subscript->is_attribute) {
+		push_text(".");
+		print_identifier(p_subscript->attribute);
+	} else {
+		push_text("[ ");
+		print_expression(p_subscript->index);
+		push_text(" ]");
+	}
 }
 }
 
 
-GDScriptParser::Node *GDScriptParser::get_completion_node() {
-	return completion_node;
+void GDScriptParser::TreePrinter::print_statement(Node *p_statement) {
+	switch (p_statement->type) {
+		case Node::ASSERT:
+			print_assert(static_cast<AssertNode *>(p_statement));
+			break;
+		case Node::VARIABLE:
+			print_variable(static_cast<VariableNode *>(p_statement));
+			break;
+		case Node::CONSTANT:
+			print_constant(static_cast<ConstantNode *>(p_statement));
+			break;
+		case Node::IF:
+			print_if(static_cast<IfNode *>(p_statement));
+			break;
+		case Node::FOR:
+			print_for(static_cast<ForNode *>(p_statement));
+			break;
+		case Node::WHILE:
+			print_while(static_cast<WhileNode *>(p_statement));
+			break;
+		case Node::MATCH:
+			print_match(static_cast<MatchNode *>(p_statement));
+			break;
+		case Node::RETURN:
+			print_return(static_cast<ReturnNode *>(p_statement));
+			break;
+		case Node::BREAK:
+			push_line("Break");
+			break;
+		case Node::CONTINUE:
+			push_line("Continue");
+			break;
+		case Node::PASS:
+			push_line("Pass");
+			break;
+		case Node::BREAKPOINT:
+			push_line("Breakpoint");
+			break;
+		case Node::ASSIGNMENT:
+			print_assignment(static_cast<AssignmentNode *>(p_statement));
+			break;
+		default:
+			if (p_statement->is_expression()) {
+				print_expression(static_cast<ExpressionNode *>(p_statement));
+				push_line();
+			} else {
+				push_line(vformat("<unknown statement %d>", p_statement->type));
+			}
+			break;
+	}
 }
 }
 
 
-GDScriptParser::BlockNode *GDScriptParser::get_completion_block() {
-	return completion_block;
+void GDScriptParser::TreePrinter::print_suite(SuiteNode *p_suite) {
+	for (int i = 0; i < p_suite->statements.size(); i++) {
+		print_statement(p_suite->statements[i]);
+	}
 }
 }
 
 
-GDScriptParser::ClassNode *GDScriptParser::get_completion_class() {
-	return completion_class;
+void GDScriptParser::TreePrinter::print_ternary_op(TernaryOpNode *p_ternary_op) {
+	// Surround in parenthesis for disambiguation.
+	push_text("(");
+	print_expression(p_ternary_op->true_expr);
+	push_text(") IF (");
+	print_expression(p_ternary_op->condition);
+	push_text(") ELSE (");
+	print_expression(p_ternary_op->false_expr);
+	push_text(")");
 }
 }
 
 
-GDScriptParser::FunctionNode *GDScriptParser::get_completion_function() {
-	return completion_function;
+void GDScriptParser::TreePrinter::print_type(TypeNode *p_type) {
+	if (p_type->type_specifier != nullptr) {
+		print_subscript(p_type->type_specifier);
+	} else if (p_type->type_base != nullptr) {
+		print_identifier(p_type->type_base);
+	} else {
+		push_text("Void");
+	}
 }
 }
 
 
-int GDScriptParser::get_completion_argument_index() {
-	return completion_argument;
+void GDScriptParser::TreePrinter::print_unary_op(UnaryOpNode *p_unary_op) {
+	// Surround in parenthesis for disambiguation.
+	push_text("(");
+	switch (p_unary_op->operation) {
+		case UnaryOpNode::OP_POSITIVE:
+			push_text("+");
+			break;
+		case UnaryOpNode::OP_NEGATIVE:
+			push_text("-");
+			break;
+		case UnaryOpNode::OP_LOGIC_NOT:
+			push_text("NOT");
+			break;
+		case UnaryOpNode::OP_COMPLEMENT:
+			push_text("~");
+			break;
+	}
+	print_expression(p_unary_op->operand);
+	// Surround in parenthesis for disambiguation.
+	push_text(")");
 }
 }
 
 
-bool GDScriptParser::get_completion_identifier_is_function() {
-	return completion_ident_is_call;
+void GDScriptParser::TreePrinter::print_variable(VariableNode *p_variable) {
+	for (const List<AnnotationNode *>::Element *E = p_variable->annotations.front(); E != nullptr; E = E->next()) {
+		print_annotation(E->get());
+	}
+
+	push_text("Variable ");
+	print_identifier(p_variable->identifier);
+
+	increase_indent();
+
+	push_line();
+	push_text("= ");
+	if (p_variable->initializer == nullptr) {
+		push_text("<default value>");
+	} else {
+		print_expression(p_variable->initializer);
+	}
+	decrease_indent();
+	push_line();
 }
 }
 
 
-GDScriptParser::GDScriptParser() {
-	head = nullptr;
-	list = nullptr;
-	tokenizer = nullptr;
-	pending_newline = -1;
-	clear();
+void GDScriptParser::TreePrinter::print_while(WhileNode *p_while) {
+	push_text("While ");
+	print_expression(p_while->condition);
+	push_line(" :");
+
+	increase_indent();
+	print_suite(p_while->loop);
+	decrease_indent();
 }
 }
 
 
-GDScriptParser::~GDScriptParser() {
-	clear();
+void GDScriptParser::TreePrinter::print_tree(const GDScriptParser &p_parser) {
+	ERR_FAIL_COND_MSG(p_parser.get_tree() == nullptr, "Parse the code before printing the parse tree.");
+
+	if (p_parser.is_tool()) {
+		push_line("@tool");
+	}
+	if (!p_parser.get_tree()->icon_path.empty()) {
+		push_text(R"(@icon (")");
+		push_text(p_parser.get_tree()->icon_path);
+		push_line("\")");
+	}
+	print_class(p_parser.get_tree());
+
+	print_line(printed);
 }
 }
+
+#endif // DEBUG_ENABLED

+ 829 - 492
modules/gdscript/gdscript_parser.h

@@ -31,665 +31,1002 @@
 #ifndef GDSCRIPT_PARSER_H
 #ifndef GDSCRIPT_PARSER_H
 #define GDSCRIPT_PARSER_H
 #define GDSCRIPT_PARSER_H
 
 
+#include "core/hash_map.h"
+#include "core/io/multiplayer_api.h"
+#include "core/list.h"
 #include "core/map.h"
 #include "core/map.h"
-#include "core/object.h"
+#include "core/reference.h"
+#include "core/resource.h"
 #include "core/script_language.h"
 #include "core/script_language.h"
+#include "core/string_name.h"
+#include "core/ustring.h"
+#include "core/variant.h"
+#include "core/vector.h"
 #include "gdscript_functions.h"
 #include "gdscript_functions.h"
 #include "gdscript_tokenizer.h"
 #include "gdscript_tokenizer.h"
 
 
-struct GDScriptDataType;
-struct GDScriptWarning;
+#ifdef DEBUG_ENABLED
+#include "core/string_builder.h"
+#endif // DEBUG_ENABLED
 
 
 class GDScriptParser {
 class GDScriptParser {
+	struct AnnotationInfo;
+
 public:
 public:
+	// Forward-declare all parser nodes, to avoid ordering issues.
+	struct AnnotationNode;
+	struct ArrayNode;
+	struct AssertNode;
+	struct AssignmentNode;
+	struct AwaitNode;
+	struct BinaryOpNode;
+	struct BreakNode;
+	struct BreakpointNode;
+	struct CallNode;
+	struct CastNode;
 	struct ClassNode;
 	struct ClassNode;
+	struct ConstantNode;
+	struct ContinueNode;
+	struct DictionaryNode;
+	struct EnumNode;
+	struct ExpressionNode;
+	struct ForNode;
+	struct FunctionNode;
+	struct GetNodeNode;
+	struct IdentifierNode;
+	struct IfNode;
+	struct LiteralNode;
+	struct MatchNode;
+	struct MatchBranchNode;
+	struct ParameterNode;
+	struct PassNode;
+	struct PatternNode;
+	struct PreloadNode;
+	struct ReturnNode;
+	struct SelfNode;
+	struct SignalNode;
+	struct SubscriptNode;
+	struct SuiteNode;
+	struct TernaryOpNode;
+	struct TypeNode;
+	struct UnaryOpNode;
+	struct VariableNode;
+	struct WhileNode;
 
 
 	struct DataType {
 	struct DataType {
 		enum Kind {
 		enum Kind {
 			BUILTIN,
 			BUILTIN,
 			NATIVE,
 			NATIVE,
 			SCRIPT,
 			SCRIPT,
-			GDSCRIPT,
-			CLASS,
-			UNRESOLVED
+			CLASS, // GDScript.
+			UNRESOLVED,
 		};
 		};
-
 		Kind kind = UNRESOLVED;
 		Kind kind = UNRESOLVED;
 
 
-		bool has_type = false;
+		enum TypeSource {
+			UNDETECTED, // Can be any type.
+			INFERRED, // Has inferred type, but still dynamic.
+			ANNOTATED_EXPLICIT, // Has a specific type annotated.
+			ANNOTATED_INFERRED, // Has a static type but comes from the assigned value.
+		};
+		TypeSource type_source = UNDETECTED;
+
 		bool is_constant = false;
 		bool is_constant = false;
-		bool is_meta_type = false; // Whether the value can be used as a type
+		bool is_meta_type = false;
 		bool infer_type = false;
 		bool infer_type = false;
-		bool may_yield = false; // For function calls
 
 
 		Variant::Type builtin_type = Variant::NIL;
 		Variant::Type builtin_type = Variant::NIL;
 		StringName native_type;
 		StringName native_type;
 		Ref<Script> script_type;
 		Ref<Script> script_type;
-		ClassNode *class_type = nullptr;
+		ClassNode *gdscript_type = nullptr;
 
 
+		_FORCE_INLINE_ bool is_set() const { return type_source != UNDETECTED; }
 		String to_string() const;
 		String to_string() const;
 
 
-		bool operator==(const DataType &other) const {
-			if (!has_type || !other.has_type) {
-				return true; // Can be considered equal for parsing purpose
+		bool operator==(const DataType &p_other) const {
+			if (type_source == UNDETECTED || p_other.type_source == UNDETECTED) {
+				return true; // Can be consireded equal for parsing purposes.
+			}
+
+			if (type_source == INFERRED || p_other.type_source == INFERRED) {
+				return true; // Can be consireded equal for parsing purposes.
 			}
 			}
-			if (kind != other.kind) {
+
+			if (kind != p_other.kind) {
 				return false;
 				return false;
 			}
 			}
+
 			switch (kind) {
 			switch (kind) {
-				case BUILTIN: {
-					return builtin_type == other.builtin_type;
-				} break;
-				case NATIVE: {
-					return native_type == other.native_type;
-				} break;
-				case GDSCRIPT:
-				case SCRIPT: {
-					return script_type == other.script_type;
-				} break;
-				case CLASS: {
-					return class_type == other.class_type;
-				} break;
-				case UNRESOLVED: {
-				} break;
+				case BUILTIN:
+					return builtin_type == p_other.builtin_type;
+				case NATIVE:
+					return native_type == p_other.native_type;
+				case SCRIPT:
+					return script_type == p_other.script_type;
+				case CLASS:
+					return gdscript_type == p_other.gdscript_type;
+				case UNRESOLVED:
+					break;
 			}
 			}
+
 			return false;
 			return false;
 		}
 		}
+	};
 
 
-		DataType() {}
+	struct ParserError {
+		// TODO: Do I really need a "type"?
+		// enum Type {
+		//     NO_ERROR,
+		//     EMPTY_FILE,
+		//     CLASS_NAME_USED_TWICE,
+		//     EXTENDS_USED_TWICE,
+		//     EXPECTED_END_STATEMENT,
+		// };
+		// Type type = NO_ERROR;
+		String message;
+		int line = 0, column = 0;
 	};
 	};
 
 
 	struct Node {
 	struct Node {
 		enum Type {
 		enum Type {
-			TYPE_CLASS,
-			TYPE_FUNCTION,
-			TYPE_BUILT_IN_FUNCTION,
-			TYPE_BLOCK,
-			TYPE_IDENTIFIER,
-			TYPE_TYPE,
-			TYPE_CONSTANT,
-			TYPE_ARRAY,
-			TYPE_DICTIONARY,
-			TYPE_SELF,
-			TYPE_OPERATOR,
-			TYPE_CONTROL_FLOW,
-			TYPE_LOCAL_VAR,
-			TYPE_CAST,
-			TYPE_ASSERT,
-			TYPE_BREAKPOINT,
-			TYPE_NEWLINE,
+			NONE,
+			ANNOTATION,
+			ARRAY,
+			ASSERT,
+			ASSIGNMENT,
+			AWAIT,
+			BINARY_OPERATOR,
+			BREAK,
+			BREAKPOINT,
+			CALL,
+			CAST,
+			CLASS,
+			CONSTANT,
+			CONTINUE,
+			DICTIONARY,
+			ENUM,
+			FOR,
+			FUNCTION,
+			GET_NODE,
+			IDENTIFIER,
+			IF,
+			LITERAL,
+			MATCH,
+			MATCH_BRANCH,
+			PARAMETER,
+			PASS,
+			PATTERN,
+			PRELOAD,
+			RETURN,
+			SELF,
+			SIGNAL,
+			SUBSCRIPT,
+			SUITE,
+			TERNARY_OPERATOR,
+			TYPE,
+			UNARY_OPERATOR,
+			VARIABLE,
+			WHILE,
 		};
 		};
 
 
-		Node *next;
-		int line;
-		int column;
-		Type type;
+		Type type = NONE;
+		int start_line = 0, end_line = 0;
+		int leftmost_column = 0, rightmost_column = 0;
+		Node *next = nullptr;
+		List<AnnotationNode *> annotations;
 
 
 		virtual DataType get_datatype() const { return DataType(); }
 		virtual DataType get_datatype() const { return DataType(); }
 		virtual void set_datatype(const DataType &p_datatype) {}
 		virtual void set_datatype(const DataType &p_datatype) {}
 
 
+		virtual bool is_expression() const { return false; }
+
 		virtual ~Node() {}
 		virtual ~Node() {}
 	};
 	};
 
 
-	struct FunctionNode;
-	struct BlockNode;
-	struct ConstantNode;
-	struct LocalVarNode;
-	struct OperatorNode;
+	struct ExpressionNode : public Node {
+		// Base type for all expression kinds.
+		virtual bool is_expression() const { return true; }
+		virtual ~ExpressionNode() {}
 
 
-	struct ClassNode : public Node {
-		bool tool;
+	protected:
+		ExpressionNode() {}
+	};
+
+	struct AnnotationNode : public Node {
 		StringName name;
 		StringName name;
-		bool extends_used;
-		bool classname_used;
-		StringName extends_file;
-		Vector<StringName> extends_class;
-		DataType base_type;
-		String icon_path;
+		Vector<ExpressionNode *> arguments;
+		Vector<Variant> resolved_arguments;
 
 
-		struct Member {
-			PropertyInfo _export;
-#ifdef TOOLS_ENABLED
-			Variant default_value;
-#endif
-			StringName identifier;
-			DataType data_type;
-			StringName setter;
-			StringName getter;
-			int line;
-			Node *expression;
-			OperatorNode *initial_assignment;
-			MultiplayerAPI::RPCMode rpc_mode;
-			int usages;
-		};
+		AnnotationInfo *info = nullptr;
 
 
-		struct Constant {
-			Node *expression;
-			DataType type;
-		};
+		bool apply(GDScriptParser *p_this, Node *p_target) const;
+		bool applies_to(uint32_t p_target_kinds) const;
 
 
-		struct Signal {
-			StringName name;
-			Vector<StringName> arguments;
-			int emissions;
-			int line;
-		};
+		AnnotationNode() {
+			type = ANNOTATION;
+		}
+	};
 
 
-		Vector<ClassNode *> subclasses;
-		Vector<Member> variables;
-		Map<StringName, Constant> constant_expressions;
-		Vector<FunctionNode *> functions;
-		Vector<FunctionNode *> static_functions;
-		Vector<Signal> _signals;
-		BlockNode *initializer;
-		BlockNode *ready;
-		ClassNode *owner;
-		//Vector<Node*> initializers;
-		int end_line;
+	struct ArrayNode : public ExpressionNode {
+		Vector<ExpressionNode *> elements;
 
 
-		ClassNode() {
-			tool = false;
-			type = TYPE_CLASS;
-			extends_used = false;
-			classname_used = false;
-			end_line = -1;
-			owner = nullptr;
+		ArrayNode() {
+			type = ARRAY;
 		}
 		}
 	};
 	};
 
 
-	struct FunctionNode : public Node {
-		bool _static;
-		MultiplayerAPI::RPCMode rpc_mode;
-		bool has_yield;
-		bool has_unreachable_code;
-		StringName name;
-		DataType return_type;
-		Vector<StringName> arguments;
-		Vector<DataType> argument_types;
-		Vector<Node *> default_values;
-		BlockNode *body;
-#ifdef DEBUG_ENABLED
-		Vector<int> arguments_usage;
-#endif // DEBUG_ENABLED
-
-		virtual DataType get_datatype() const { return return_type; }
-		virtual void set_datatype(const DataType &p_datatype) { return_type = p_datatype; }
-		int get_required_argument_count() { return arguments.size() - default_values.size(); }
+	struct AssertNode : public Node {
+		ExpressionNode *condition = nullptr;
+		LiteralNode *message = nullptr;
 
 
-		FunctionNode() {
-			type = TYPE_FUNCTION;
-			_static = false;
-			rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
-			has_yield = false;
-			has_unreachable_code = false;
+		AssertNode() {
+			type = ASSERT;
 		}
 		}
 	};
 	};
 
 
-	struct BlockNode : public Node {
-		ClassNode *parent_class = nullptr;
-		BlockNode *parent_block = nullptr;
-		List<Node *> statements;
-		Map<StringName, LocalVarNode *> variables;
-		bool has_return = false;
-		bool can_break = false;
-		bool can_continue = false;
-
-		Node *if_condition = nullptr; //tiny hack to improve code completion on if () blocks
+	struct AssignmentNode : public ExpressionNode {
+		// Assignment is not really an expression but it's easier to parse as if it were.
+		enum Operation {
+			OP_NONE,
+			OP_ADDITION,
+			OP_SUBTRACTION,
+			OP_MULTIPLICATION,
+			OP_DIVISION,
+			OP_MODULO,
+			OP_BIT_SHIFT_LEFT,
+			OP_BIT_SHIFT_RIGHT,
+			OP_BIT_AND,
+			OP_BIT_OR,
+			OP_BIT_XOR,
+		};
 
 
-		//the following is useful for code completion
-		List<BlockNode *> sub_blocks;
-		int end_line = -1;
+		Operation operation = OP_NONE;
+		ExpressionNode *assignee = nullptr;
+		ExpressionNode *assigned_value = nullptr;
 
 
-		BlockNode() {
-			type = TYPE_BLOCK;
+		AssignmentNode() {
+			type = ASSIGNMENT;
 		}
 		}
 	};
 	};
 
 
-	struct TypeNode : public Node {
-		Variant::Type vtype;
+	struct AwaitNode : public ExpressionNode {
+		ExpressionNode *to_await = nullptr;
 
 
-		TypeNode() {
-			type = TYPE_TYPE;
+		AwaitNode() {
+			type = AWAIT;
 		}
 		}
 	};
 	};
 
 
-	struct BuiltInFunctionNode : public Node {
-		GDScriptFunctions::Function function;
+	struct BinaryOpNode : public ExpressionNode {
+		enum OpType {
+			OP_ADDITION,
+			OP_SUBTRACTION,
+			OP_MULTIPLICATION,
+			OP_DIVISION,
+			OP_MODULO,
+			OP_BIT_LEFT_SHIFT,
+			OP_BIT_RIGHT_SHIFT,
+			OP_BIT_AND,
+			OP_BIT_OR,
+			OP_BIT_XOR,
+			OP_LOGIC_AND,
+			OP_LOGIC_OR,
+			OP_TYPE_TEST,
+			OP_CONTENT_TEST,
+			OP_COMP_EQUAL,
+			OP_COMP_NOT_EQUAL,
+			OP_COMP_LESS,
+			OP_COMP_LESS_EQUAL,
+			OP_COMP_GREATER,
+			OP_COMP_GREATER_EQUAL,
+		};
 
 
-		BuiltInFunctionNode() {
-			type = TYPE_BUILT_IN_FUNCTION;
+		OpType operation;
+		ExpressionNode *left_operand = nullptr;
+		ExpressionNode *right_operand = nullptr;
+
+		BinaryOpNode() {
+			type = BINARY_OPERATOR;
 		}
 		}
 	};
 	};
 
 
-	struct IdentifierNode : public Node {
-		StringName name;
-		BlockNode *declared_block = nullptr; // Simplify lookup by checking if it is declared locally
-		DataType datatype;
-		virtual DataType get_datatype() const { return datatype; }
-		virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; }
+	struct BreakNode : public Node {
+		BreakNode() {
+			type = BREAK;
+		}
+	};
 
 
-		IdentifierNode() {
-			type = TYPE_IDENTIFIER;
+	struct BreakpointNode : public Node {
+		BreakpointNode() {
+			type = BREAKPOINT;
 		}
 		}
 	};
 	};
 
 
-	struct LocalVarNode : public Node {
-		StringName name;
-		Node *assign = nullptr;
-		OperatorNode *assign_op = nullptr;
-		int assignments = 0;
-		int usages = 0;
-		DataType datatype;
-		virtual DataType get_datatype() const { return datatype; }
-		virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; }
+	struct CallNode : public ExpressionNode {
+		ExpressionNode *callee = nullptr;
+		Vector<ExpressionNode *> arguments;
+		bool is_super = false;
 
 
-		LocalVarNode() {
-			type = TYPE_LOCAL_VAR;
+		CallNode() {
+			type = CALL;
 		}
 		}
 	};
 	};
 
 
-	struct ConstantNode : public Node {
-		Variant value;
-		DataType datatype;
-		virtual DataType get_datatype() const { return datatype; }
-		virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; }
+	struct CastNode : public ExpressionNode {
+		ExpressionNode *operand = nullptr;
+		TypeNode *cast_type = nullptr;
 
 
-		ConstantNode() {
-			type = TYPE_CONSTANT;
+		CastNode() {
+			type = CAST;
 		}
 		}
 	};
 	};
 
 
-	struct ArrayNode : public Node {
-		Vector<Node *> elements;
-		DataType datatype;
-		virtual DataType get_datatype() const { return datatype; }
-		virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; }
+	struct EnumNode : public Node {
+		struct Value {
+			IdentifierNode *identifier = nullptr;
+			LiteralNode *custom_value = nullptr;
+			int value = 0;
+		};
+		IdentifierNode *identifier = nullptr;
+		Vector<Value> values;
 
 
-		ArrayNode() {
-			type = TYPE_ARRAY;
-			datatype.has_type = true;
-			datatype.kind = DataType::BUILTIN;
-			datatype.builtin_type = Variant::ARRAY;
+		EnumNode() {
+			type = ENUM;
 		}
 		}
 	};
 	};
 
 
-	struct DictionaryNode : public Node {
-		struct Pair {
-			Node *key;
-			Node *value;
+	struct ClassNode : public Node {
+		struct Member {
+			enum Type {
+				UNDEFINED,
+				CLASS,
+				CONSTANT,
+				FUNCTION,
+				SIGNAL,
+				VARIABLE,
+				ENUM,
+				ENUM_VALUE, // For unnamed enums.
+			};
+
+			Type type = UNDEFINED;
+
+			union {
+				ClassNode *m_class = nullptr;
+				ConstantNode *constant;
+				FunctionNode *function;
+				SignalNode *signal;
+				VariableNode *variable;
+				EnumNode *m_enum;
+			};
+			EnumNode::Value enum_value;
+
+			String get_type_name() const {
+				switch (type) {
+					case UNDEFINED:
+						return "???";
+					case CLASS:
+						return "class";
+					case CONSTANT:
+						return "constant";
+					case FUNCTION:
+						return "function";
+					case SIGNAL:
+						return "signal";
+					case VARIABLE:
+						return "variable";
+					case ENUM:
+						return "enum";
+					case ENUM_VALUE:
+						return "enum value";
+				}
+				return "";
+			}
+
+			Member() {}
+
+			Member(ClassNode *p_class) {
+				type = CLASS;
+				m_class = p_class;
+			}
+			Member(ConstantNode *p_constant) {
+				type = CONSTANT;
+				constant = p_constant;
+			}
+			Member(VariableNode *p_variable) {
+				type = VARIABLE;
+				variable = p_variable;
+			}
+			Member(SignalNode *p_signal) {
+				type = SIGNAL;
+				signal = p_signal;
+			}
+			Member(FunctionNode *p_function) {
+				type = FUNCTION;
+				function = p_function;
+			}
+			Member(EnumNode *p_enum) {
+				type = ENUM;
+				m_enum = p_enum;
+			}
+			Member(const EnumNode::Value &p_enum_value) {
+				type = ENUM_VALUE;
+				enum_value = p_enum_value;
+			}
 		};
 		};
 
 
-		Vector<Pair> elements;
-		DataType datatype;
-		virtual DataType get_datatype() const { return datatype; }
-		virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; }
+		IdentifierNode *identifier = nullptr;
+		String icon_path;
+		Vector<Member> members;
+		HashMap<StringName, int> members_indices;
+		ClassNode *outer = nullptr;
+		bool extends_used = false;
+		bool onready_used = false;
+		String extends_path;
+		Vector<StringName> extends; // List for indexing: extends A.B.C
+		DataType base_type;
 
 
-		DictionaryNode() {
-			type = TYPE_DICTIONARY;
-			datatype.has_type = true;
-			datatype.kind = DataType::BUILTIN;
-			datatype.builtin_type = Variant::DICTIONARY;
+		Member get_member(const StringName &p_name) const {
+			return members[members_indices[p_name]];
+		}
+		template <class T>
+		void add_member(T *p_member_node) {
+			members_indices[p_member_node->identifier->name] = members.size();
+			members.push_back(Member(p_member_node));
+		}
+		void add_member(const EnumNode::Value &p_enum_value) {
+			members_indices[p_enum_value.identifier->name] = members.size();
+			members.push_back(Member(p_enum_value));
+		}
+		virtual DataType get_datatype() const {
+			return base_type;
+		}
+		virtual void set_datatype(const DataType &p_datatype) {
+			base_type = p_datatype;
+		}
+
+		ClassNode() {
+			type = CLASS;
 		}
 		}
 	};
 	};
 
 
-	struct SelfNode : public Node {
-		SelfNode() {
-			type = TYPE_SELF;
-		}
-	};
-
-	struct OperatorNode : public Node {
-		enum Operator {
-			//call/constructor operator
-			OP_CALL,
-			OP_PARENT_CALL,
-			OP_YIELD,
-			OP_IS,
-			OP_IS_BUILTIN,
-			//indexing operator
-			OP_INDEX,
-			OP_INDEX_NAMED,
-			//unary operators
-			OP_NEG,
-			OP_POS,
-			OP_NOT,
-			OP_BIT_INVERT,
-			//binary operators (in precedence order)
-			OP_IN,
-			OP_EQUAL,
-			OP_NOT_EQUAL,
-			OP_LESS,
-			OP_LESS_EQUAL,
-			OP_GREATER,
-			OP_GREATER_EQUAL,
-			OP_AND,
-			OP_OR,
-			OP_ADD,
-			OP_SUB,
-			OP_MUL,
-			OP_DIV,
-			OP_MOD,
-			OP_SHIFT_LEFT,
-			OP_SHIFT_RIGHT,
-			OP_INIT_ASSIGN,
-			OP_ASSIGN,
-			OP_ASSIGN_ADD,
-			OP_ASSIGN_SUB,
-			OP_ASSIGN_MUL,
-			OP_ASSIGN_DIV,
-			OP_ASSIGN_MOD,
-			OP_ASSIGN_SHIFT_LEFT,
-			OP_ASSIGN_SHIFT_RIGHT,
-			OP_ASSIGN_BIT_AND,
-			OP_ASSIGN_BIT_OR,
-			OP_ASSIGN_BIT_XOR,
-			OP_BIT_AND,
-			OP_BIT_OR,
-			OP_BIT_XOR,
-			//ternary operators
-			OP_TERNARY_IF,
-			OP_TERNARY_ELSE,
-		};
+	struct ConstantNode : public Node {
+		IdentifierNode *identifier = nullptr;
+		ExpressionNode *initializer = nullptr;
+		TypeNode *datatype_specifier = nullptr;
+		bool infer_datatype = false;
 
 
-		Operator op;
+		ConstantNode() {
+			type = CONSTANT;
+		}
+	};
 
 
-		Vector<Node *> arguments;
-		DataType datatype;
-		virtual DataType get_datatype() const { return datatype; }
-		virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; }
-		OperatorNode() {
-			type = TYPE_OPERATOR;
+	struct ContinueNode : public Node {
+		ContinueNode() {
+			type = CONTINUE;
 		}
 		}
 	};
 	};
 
 
-	struct PatternNode : public Node {
-		enum PatternType {
-			PT_CONSTANT,
-			PT_BIND,
-			PT_DICTIONARY,
-			PT_ARRAY,
-			PT_IGNORE_REST,
-			PT_WILDCARD
+	struct DictionaryNode : public ExpressionNode {
+		struct Pair {
+			ExpressionNode *key = nullptr;
+			ExpressionNode *value = nullptr;
 		};
 		};
+		Vector<Pair> elements;
 
 
-		PatternType pt_type;
+		enum Style {
+			LUA_TABLE,
+			PYTHON_DICT,
+		};
+		Style style = PYTHON_DICT;
 
 
-		Node *constant;
-		StringName bind;
-		Map<ConstantNode *, PatternNode *> dictionary;
-		Vector<PatternNode *> array;
+		DictionaryNode() {
+			type = DICTIONARY;
+		}
 	};
 	};
 
 
-	struct PatternBranchNode : public Node {
-		Vector<PatternNode *> patterns;
-		BlockNode *body;
+	struct ForNode : public Node {
+		IdentifierNode *variable = nullptr;
+		ExpressionNode *list = nullptr;
+		SuiteNode *loop = nullptr;
+
+		ForNode() {
+			type = FOR;
+		}
 	};
 	};
 
 
-	struct MatchNode : public Node {
-		Node *val_to_match;
-		Vector<PatternBranchNode *> branches;
+	struct FunctionNode : public Node {
+		IdentifierNode *identifier = nullptr;
+		Vector<ParameterNode *> parameters;
+		HashMap<StringName, int> parameters_indices;
+		TypeNode *return_type = nullptr;
+		SuiteNode *body = nullptr;
+		bool is_static = false;
+		MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
 
 
-		struct CompiledPatternBranch {
-			Node *compiled_pattern;
-			BlockNode *body;
-		};
+		FunctionNode() {
+			type = FUNCTION;
+		}
+	};
 
 
-		Vector<CompiledPatternBranch> compiled_pattern_branches;
+	struct GetNodeNode : public ExpressionNode {
+		LiteralNode *string = nullptr;
+		Vector<IdentifierNode *> chain;
+
+		GetNodeNode() {
+			type = GET_NODE;
+		}
 	};
 	};
 
 
-	struct ControlFlowNode : public Node {
-		enum CFType {
-			CF_IF,
-			CF_FOR,
-			CF_WHILE,
-			CF_BREAK,
-			CF_CONTINUE,
-			CF_RETURN,
-			CF_MATCH
-		};
+	struct IdentifierNode : public ExpressionNode {
+		StringName name;
 
 
-		CFType cf_type = CF_IF;
-		Vector<Node *> arguments;
-		BlockNode *body = nullptr;
-		BlockNode *body_else = nullptr;
+		IdentifierNode() {
+			type = IDENTIFIER;
+		}
+	};
 
 
-		MatchNode *match;
+	struct IfNode : public Node {
+		ExpressionNode *condition = nullptr;
+		SuiteNode *true_block = nullptr;
+		SuiteNode *false_block = nullptr;
 
 
-		ControlFlowNode *_else; //used for if
+		IfNode() {
+			type = IF;
+		}
+	};
 
 
-		ControlFlowNode() {
-			type = TYPE_CONTROL_FLOW;
+	struct LiteralNode : public ExpressionNode {
+		Variant value;
+
+		LiteralNode() {
+			type = LITERAL;
 		}
 		}
 	};
 	};
 
 
-	struct CastNode : public Node {
-		Node *source_node;
-		DataType cast_type;
-		DataType return_type;
-		virtual DataType get_datatype() const { return return_type; }
-		virtual void set_datatype(const DataType &p_datatype) { return_type = p_datatype; }
+	struct MatchNode : public Node {
+		ExpressionNode *test = nullptr;
+		Vector<MatchBranchNode *> branches;
 
 
-		CastNode() {
-			type = TYPE_CAST;
+		MatchNode() {
+			type = MATCH;
 		}
 		}
 	};
 	};
 
 
-	struct AssertNode : public Node {
-		Node *condition = nullptr;
-		Node *message = nullptr;
+	struct MatchBranchNode : public Node {
+		Vector<PatternNode *> patterns;
+		SuiteNode *block;
 
 
-		AssertNode() {
-			type = TYPE_ASSERT;
+		MatchBranchNode() {
+			type = MATCH_BRANCH;
 		}
 		}
 	};
 	};
 
 
-	struct BreakpointNode : public Node {
-		BreakpointNode() {
-			type = TYPE_BREAKPOINT;
+	struct ParameterNode : public Node {
+		IdentifierNode *identifier = nullptr;
+		ExpressionNode *default_value = nullptr;
+		TypeNode *datatype_specifier = nullptr;
+		bool infer_datatype = false;
+
+		ParameterNode() {
+			type = PARAMETER;
 		}
 		}
 	};
 	};
 
 
-	struct NewLineNode : public Node {
-		NewLineNode() {
-			type = TYPE_NEWLINE;
+	struct PassNode : public Node {
+		PassNode() {
+			type = PASS;
 		}
 		}
 	};
 	};
 
 
-	struct Expression {
-		bool is_op;
+	struct PatternNode : public Node {
+		enum Type {
+			PT_LITERAL,
+			PT_EXPRESSION,
+			PT_BIND,
+			PT_ARRAY,
+			PT_DICTIONARY,
+			PT_REST,
+			PT_WILDCARD,
+		};
+		Type pattern_type = PT_LITERAL;
+
 		union {
 		union {
-			OperatorNode::Operator op;
-			Node *node;
+			LiteralNode *literal = nullptr;
+			IdentifierNode *bind;
+			ExpressionNode *expression;
 		};
 		};
-	};
+		Vector<PatternNode *> array;
+		bool rest_used = false; // For array/dict patterns.
 
 
-	enum CompletionType {
-		COMPLETION_NONE,
-		COMPLETION_BUILT_IN_TYPE_CONSTANT,
-		COMPLETION_GET_NODE,
-		COMPLETION_FUNCTION,
-		COMPLETION_IDENTIFIER,
-		COMPLETION_PARENT_FUNCTION,
-		COMPLETION_METHOD,
-		COMPLETION_CALL_ARGUMENTS,
-		COMPLETION_RESOURCE_PATH,
-		COMPLETION_INDEX,
-		COMPLETION_VIRTUAL_FUNC,
-		COMPLETION_YIELD,
-		COMPLETION_ASSIGN,
-		COMPLETION_TYPE_HINT,
-		COMPLETION_TYPE_HINT_INDEX,
+		struct Pair {
+			ExpressionNode *key = nullptr;
+			PatternNode *value_pattern = nullptr;
+		};
+		Vector<Pair> dictionary;
+
+		PatternNode() {
+			type = PATTERN;
+		}
 	};
 	};
+	struct PreloadNode : public ExpressionNode {
+		ExpressionNode *path = nullptr;
+		String resolved_path;
+		Ref<Resource> resource;
 
 
-private:
-	GDScriptTokenizer *tokenizer;
+		PreloadNode() {
+			type = PRELOAD;
+		}
+	};
 
 
-	Node *head;
-	Node *list;
-	template <class T>
-	T *alloc_node();
+	struct ReturnNode : public Node {
+		ExpressionNode *return_value = nullptr;
 
 
-	bool validating;
-	bool for_completion;
-	int parenthesis;
-	bool error_set;
-	String error;
-	int error_line;
-	int error_column;
-	bool check_types;
-	bool dependencies_only;
-	List<String> dependencies;
-#ifdef DEBUG_ENABLED
-	Set<int> *safe_lines;
-#endif // DEBUG_ENABLED
+		ReturnNode() {
+			type = RETURN;
+		}
+	};
 
 
-#ifdef DEBUG_ENABLED
-	List<GDScriptWarning> warnings;
-#endif // DEBUG_ENABLED
+	struct SelfNode : public ExpressionNode {
+		ClassNode *current_class = nullptr;
 
 
-	int pending_newline;
+		SelfNode() {
+			type = SELF;
+		}
+	};
 
 
-	struct IndentLevel {
-		int indent = 0;
-		int tabs = 0;
+	struct SignalNode : public Node {
+		IdentifierNode *identifier = nullptr;
+		Vector<ParameterNode *> parameters;
+		HashMap<StringName, int> parameters_indices;
 
 
-		bool is_mixed(IndentLevel other) {
-			return (
-					(indent == other.indent && tabs != other.tabs) ||
-					(indent > other.indent && tabs < other.tabs) ||
-					(indent < other.indent && tabs > other.tabs));
+		SignalNode() {
+			type = SIGNAL;
 		}
 		}
+	};
 
 
-		IndentLevel() {}
+	struct SubscriptNode : public ExpressionNode {
+		ExpressionNode *base = nullptr;
+		union {
+			ExpressionNode *index = nullptr;
+			IdentifierNode *attribute;
+		};
 
 
-		IndentLevel(int p_indent, int p_tabs) :
-				indent(p_indent),
-				tabs(p_tabs) {}
+		bool is_attribute = false;
+
+		SubscriptNode() {
+			type = SUBSCRIPT;
+		}
 	};
 	};
 
 
-	List<IndentLevel> indent_level;
+	struct SuiteNode : public Node {
+		SuiteNode *parent_block = nullptr;
+		Vector<Node *> statements;
+		struct Local {
+			enum Type {
+				UNDEFINED,
+				CONSTANT,
+				VARIABLE,
+			};
+			Type type = UNDEFINED;
+			union {
+				ConstantNode *constant = nullptr;
+				VariableNode *variable;
+			};
+
+			Local() {}
+			Local(ConstantNode *p_constant) {
+				type = CONSTANT;
+				constant = p_constant;
+			}
+			Local(VariableNode *p_variable) {
+				type = VARIABLE;
+				variable = p_variable;
+			}
+		};
+		Local empty;
+		Vector<Local> locals;
+		HashMap<StringName, int> locals_indices;
+
+		bool has_local(const StringName &p_name) const;
+		const Local &get_local(const StringName &p_name) const;
+		template <class T>
+		void add_local(T *p_local) {
+			locals_indices[p_local->identifier->name] = locals.size();
+			locals.push_back(Local(p_local));
+		}
 
 
-	String base_path;
-	String self_path;
+		SuiteNode() {
+			type = SUITE;
+		}
+	};
 
 
-	ClassNode *current_class;
-	FunctionNode *current_function;
-	BlockNode *current_block;
+	struct TernaryOpNode : public ExpressionNode {
+		// Only one ternary operation exists, so no abstraction here.
+		ExpressionNode *condition = nullptr;
+		ExpressionNode *true_expr = nullptr;
+		ExpressionNode *false_expr = nullptr;
 
 
-	bool _get_completable_identifier(CompletionType p_type, StringName &identifier);
-	void _make_completable_call(int p_arg);
+		TernaryOpNode() {
+			type = TERNARY_OPERATOR;
+		}
+	};
 
 
-	CompletionType completion_type;
-	StringName completion_cursor;
-	Variant::Type completion_built_in_constant;
-	Node *completion_node;
-	ClassNode *completion_class;
-	FunctionNode *completion_function;
-	BlockNode *completion_block;
-	int completion_line;
-	int completion_argument;
-	bool completion_found;
-	bool completion_ident_is_call;
+	struct TypeNode : public Node {
+		IdentifierNode *type_base = nullptr;
+		SubscriptNode *type_specifier = nullptr;
 
 
-	PropertyInfo current_export;
+		TypeNode() {
+			type = TYPE;
+		}
+	};
 
 
-	MultiplayerAPI::RPCMode rpc_mode;
+	struct UnaryOpNode : public ExpressionNode {
+		enum OpType {
+			OP_POSITIVE,
+			OP_NEGATIVE,
+			OP_COMPLEMENT,
+			OP_LOGIC_NOT,
+		};
 
 
-	void _set_error(const String &p_error, int p_line = -1, int p_column = -1);
-#ifdef DEBUG_ENABLED
-	void _add_warning(int p_code, int p_line = -1, const String &p_symbol1 = String(), const String &p_symbol2 = String(), const String &p_symbol3 = String(), const String &p_symbol4 = String());
-	void _add_warning(int p_code, int p_line, const Vector<String> &p_symbols);
-#endif // DEBUG_ENABLED
-	bool _recover_from_completion();
-
-	bool _parse_arguments(Node *p_parent, Vector<Node *> &p_args, bool p_static, bool p_can_codecomplete = false, bool p_parsing_constant = false);
-	bool _enter_indent_block(BlockNode *p_block = nullptr);
-	bool _parse_newline();
-	Node *_parse_expression(Node *p_parent, bool p_static, bool p_allow_assign = false, bool p_parsing_constant = false);
-	Node *_reduce_expression(Node *p_node, bool p_to_const = false);
-	Node *_parse_and_reduce_expression(Node *p_parent, bool p_static, bool p_reduce_const = false, bool p_allow_assign = false);
-	bool _reduce_export_var_type(Variant &p_value, int p_line = 0);
-
-	PatternNode *_parse_pattern(bool p_static);
-	void _parse_pattern_block(BlockNode *p_block, Vector<PatternBranchNode *> &p_branches, bool p_static);
-	void _transform_match_statment(MatchNode *p_match_statement);
-	void _generate_pattern(PatternNode *p_pattern, Node *p_node_to_match, Node *&p_resulting_node, Map<StringName, Node *> &p_bindings);
-
-	void _parse_block(BlockNode *p_block, bool p_static);
-	void _parse_extends(ClassNode *p_class);
-	void _parse_class(ClassNode *p_class);
-	bool _end_statement();
-	void _set_end_statement_error(String p_name);
-
-	void _determine_inheritance(ClassNode *p_class, bool p_recursive = true);
-	bool _parse_type(DataType &r_type, bool p_can_be_void = false);
-	DataType _resolve_type(const DataType &p_source, int p_line);
-	DataType _type_from_variant(const Variant &p_value) const;
-	DataType _type_from_property(const PropertyInfo &p_property, bool p_nil_is_variant = true) const;
-	DataType _type_from_gdtype(const GDScriptDataType &p_gdtype) const;
-	DataType _get_operation_type(const Variant::Operator p_op, const DataType &p_a, const DataType &p_b, bool &r_valid) const;
-	Variant::Operator _get_variant_operation(const OperatorNode::Operator &p_op) const;
-	bool _get_function_signature(DataType &p_base_type, const StringName &p_function, DataType &r_return_type, List<DataType> &r_arg_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) const;
-	bool _get_member_type(const DataType &p_base_type, const StringName &p_member, DataType &r_member_type, bool *r_is_const = nullptr) const;
-	bool _is_type_compatible(const DataType &p_container, const DataType &p_expression, bool p_allow_implicit_conversion = false) const;
-	Node *_get_default_value_for_type(const DataType &p_type, int p_line = -1);
-
-	DataType _reduce_node_type(Node *p_node);
-	DataType _reduce_function_call_type(const OperatorNode *p_call);
-	DataType _reduce_identifier_type(const DataType *p_base_type, const StringName &p_identifier, int p_line, bool p_is_indexing);
-	void _check_class_level_types(ClassNode *p_class);
-	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);
+		OpType operation;
+		ExpressionNode *operand = nullptr;
+
+		UnaryOpNode() {
+			type = UNARY_OPERATOR;
 		}
 		}
-#endif // DEBUG_ENABLED
-	}
+	};
 
 
-	Error _parse(const String &p_base_path);
+	struct VariableNode : public Node {
+		IdentifierNode *identifier = nullptr;
+		ExpressionNode *initializer = nullptr;
+		TypeNode *datatype_specifier = nullptr;
+		bool infer_datatype = false;
+		bool exported = false;
+		bool onready = false;
+		PropertyInfo export_info;
+		MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
+
+		VariableNode() {
+			type = VARIABLE;
+		}
+	};
 
 
-public:
-	bool has_error() const;
-	String get_error() const;
-	int get_error_line() const;
-	int get_error_column() const;
-#ifdef DEBUG_ENABLED
-	const List<GDScriptWarning> &get_warnings() const { return warnings; }
-#endif // DEBUG_ENABLED
-	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 = nullptr, bool p_dependencies_only = false);
-	Error parse_bytecode(const Vector<uint8_t> &p_bytecode, const String &p_base_path = "", const String &p_self_path = "");
+	struct WhileNode : public Node {
+		ExpressionNode *condition = nullptr;
+		SuiteNode *loop = nullptr;
 
 
-	bool is_tool_script() const;
-	const Node *get_parse_tree() const;
+		WhileNode() {
+			type = WHILE;
+		}
+	};
 
 
-	//completion info
+private:
+	friend class GDScriptAnalyzer;
+
+	bool _is_tool = false;
+	String script_path;
+	bool for_completion = false;
+	bool panic_mode = false;
+	bool can_break = false;
+	bool can_continue = false;
+	List<bool> multiline_stack;
+
+	ClassNode *head = nullptr;
+	Node *list = nullptr;
+	List<ParserError> errors;
+
+	GDScriptTokenizer tokenizer;
+	GDScriptTokenizer::Token previous;
+	GDScriptTokenizer::Token current;
+
+	ClassNode *current_class = nullptr;
+	FunctionNode *current_function = nullptr;
+	SuiteNode *current_suite = nullptr;
+
+	typedef bool (GDScriptParser::*AnnotationAction)(const AnnotationNode *p_annotation, Node *p_target);
+	struct AnnotationInfo {
+		enum TargetKind {
+			NONE = 0,
+			SCRIPT = 1 << 0,
+			CLASS = 1 << 1,
+			VARIABLE = 1 << 2,
+			CONSTANT = 1 << 3,
+			SIGNAL = 1 << 4,
+			FUNCTION = 1 << 5,
+			STATEMENT = 1 << 6,
+			CLASS_LEVEL = CLASS | VARIABLE | FUNCTION,
+		};
+		uint32_t target_kind = 0; // Flags.
+		AnnotationAction apply = nullptr;
+		MethodInfo info;
+	};
+	HashMap<StringName, AnnotationInfo> valid_annotations;
+	List<AnnotationNode *> annotation_stack;
+
+	typedef ExpressionNode *(GDScriptParser::*ParseFunction)(ExpressionNode *p_previous_operand, bool p_can_assign);
+	// Higher value means higher precedence (i.e. is evaluated first).
+	enum Precedence {
+		PREC_NONE,
+		PREC_ASSIGNMENT,
+		PREC_CAST,
+		PREC_TERNARY,
+		PREC_LOGIC_OR,
+		PREC_LOGIC_AND,
+		PREC_LOGIC_NOT,
+		PREC_CONTENT_TEST,
+		PREC_COMPARISON,
+		PREC_BIT_OR,
+		PREC_BIT_XOR,
+		PREC_BIT_AND,
+		PREC_BIT_SHIFT,
+		PREC_SUBTRACTION,
+		PREC_ADDITION,
+		PREC_FACTOR,
+		PREC_SIGN,
+		PREC_BIT_NOT,
+		PREC_TYPE_TEST,
+		PREC_AWAIT,
+		PREC_CALL,
+		PREC_ATTRIBUTE,
+		PREC_SUBSCRIPT,
+		PREC_PRIMARY,
+	};
+	struct ParseRule {
+		ParseFunction prefix = nullptr;
+		ParseFunction infix = nullptr;
+		Precedence precedence = PREC_NONE;
+	};
+	static ParseRule *get_rule(GDScriptTokenizer::Token::Type p_token_type);
 
 
-	CompletionType get_completion_type();
-	StringName get_completion_cursor();
-	int get_completion_line();
-	Variant::Type get_completion_built_in_constant();
-	Node *get_completion_node();
-	ClassNode *get_completion_class();
-	BlockNode *get_completion_block();
-	FunctionNode *get_completion_function();
-	int get_completion_argument_index();
-	bool get_completion_identifier_is_function();
+	template <class T>
+	T *alloc_node();
+	void clear();
+	void push_error(const String &p_message, const Node *p_origin = nullptr);
+
+	GDScriptTokenizer::Token advance();
+	bool match(GDScriptTokenizer::Token::Type p_token_type);
+	bool check(GDScriptTokenizer::Token::Type p_token_type);
+	bool consume(GDScriptTokenizer::Token::Type p_token_type, const String &p_error_message);
+	bool is_at_end();
+	bool is_statement_end();
+	void end_statement(const String &p_context);
+	void synchronize();
+	void push_multiline(bool p_state);
+	void pop_multiline();
+
+	// Main blocks.
+	void parse_program();
+	ClassNode *parse_class();
+	void parse_class_name();
+	void parse_extends();
+	void parse_class_body();
+	template <class T>
+	void parse_class_member(T *(GDScriptParser::*p_parse_function)(), AnnotationInfo::TargetKind p_target, const String &p_member_kind);
+	SignalNode *parse_signal();
+	EnumNode *parse_enum();
+	ParameterNode *parse_parameter();
+	FunctionNode *parse_function();
+	SuiteNode *parse_suite(const String &p_context);
+	// Annotations
+	AnnotationNode *parse_annotation(uint32_t p_valid_targets);
+	bool register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, int p_optional_arguments = 0, bool p_is_vararg = false);
+	bool validate_annotation_arguments(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);
+	bool onready_annotation(const AnnotationNode *p_annotation, Node *p_target);
+	template <PropertyHint t_hint, Variant::Type t_type>
+	bool export_annotations(const AnnotationNode *p_annotation, Node *p_target);
+	bool warning_annotations(const AnnotationNode *p_annotation, Node *p_target);
+	template <MultiplayerAPI::RPCMode t_mode>
+	bool network_annotations(const AnnotationNode *p_annotation, Node *p_target);
+	// Statements.
+	Node *parse_statement();
+	VariableNode *parse_variable();
+	ConstantNode *parse_constant();
+	AssertNode *parse_assert();
+	BreakNode *parse_break();
+	ContinueNode *parse_continue();
+	ForNode *parse_for();
+	IfNode *parse_if(const String &p_token = "if");
+	MatchNode *parse_match();
+	MatchBranchNode *parse_match_branch();
+	PatternNode *parse_match_pattern();
+	WhileNode *parse_while();
+	// Expressions.
+	ExpressionNode *parse_expression(bool p_can_assign, bool p_stop_on_assign = false);
+	ExpressionNode *parse_precedence(Precedence p_precedence, bool p_can_assign, bool p_stop_on_assign = false);
+	ExpressionNode *parse_literal(ExpressionNode *p_previous_operand, bool p_can_assign);
+	LiteralNode *parse_literal();
+	ExpressionNode *parse_self(ExpressionNode *p_previous_operand, bool p_can_assign);
+	ExpressionNode *parse_identifier(ExpressionNode *p_previous_operand, bool p_can_assign);
+	IdentifierNode *parse_identifier();
+	ExpressionNode *parse_builtin_constant(ExpressionNode *p_previous_operand, bool p_can_assign);
+	ExpressionNode *parse_unary_operator(ExpressionNode *p_previous_operand, bool p_can_assign);
+	ExpressionNode *parse_binary_operator(ExpressionNode *p_previous_operand, bool p_can_assign);
+	ExpressionNode *parse_ternary_operator(ExpressionNode *p_previous_operand, bool p_can_assign);
+	ExpressionNode *parse_assignment(ExpressionNode *p_previous_operand, bool p_can_assign);
+	ExpressionNode *parse_array(ExpressionNode *p_previous_operand, bool p_can_assign);
+	ExpressionNode *parse_dictionary(ExpressionNode *p_previous_operand, bool p_can_assign);
+	ExpressionNode *parse_call(ExpressionNode *p_previous_operand, bool p_can_assign);
+	ExpressionNode *parse_get_node(ExpressionNode *p_previous_operand, bool p_can_assign);
+	ExpressionNode *parse_preload(ExpressionNode *p_previous_operand, bool p_can_assign);
+	ExpressionNode *parse_grouping(ExpressionNode *p_previous_operand, bool p_can_assign);
+	ExpressionNode *parse_cast(ExpressionNode *p_previous_operand, bool p_can_assign);
+	ExpressionNode *parse_await(ExpressionNode *p_previous_operand, bool p_can_assign);
+	ExpressionNode *parse_attribute(ExpressionNode *p_previous_operand, bool p_can_assign);
+	ExpressionNode *parse_subscript(ExpressionNode *p_previous_operand, bool p_can_assign);
+	ExpressionNode *parse_invalid_token(ExpressionNode *p_previous_operand, bool p_can_assign);
+	TypeNode *parse_type(bool p_allow_void = false);
 
 
-	const List<String> &get_dependencies() const { return dependencies; }
+public:
+	Error parse(const String &p_source_code, const String &p_script_path, bool p_for_completion);
+	ClassNode *get_tree() const { return head; }
+	bool is_tool() const { return _is_tool; }
+	static Variant::Type get_builtin_type(const StringName &p_type);
+	static GDScriptFunctions::Function get_builtin_function(const StringName &p_name);
+
+	const List<ParserError> &get_errors() const { return errors; }
+	const List<String> get_dependencies() const {
+		// TODO: Keep track of deps.
+		return List<String>();
+	}
 
 
-	void clear();
 	GDScriptParser();
 	GDScriptParser();
 	~GDScriptParser();
 	~GDScriptParser();
+
+#ifdef DEBUG_ENABLED
+	class TreePrinter {
+		int indent_level = 0;
+		String indent;
+		StringBuilder printed;
+		bool pending_indent = false;
+
+		void increase_indent();
+		void decrease_indent();
+		void push_line(const String &p_line = String());
+		void push_text(const String &p_text);
+
+		void print_annotation(AnnotationNode *p_annotation);
+		void print_array(ArrayNode *p_array);
+		void print_assert(AssertNode *p_assert);
+		void print_assignment(AssignmentNode *p_assignment);
+		void print_await(AwaitNode *p_await);
+		void print_binary_op(BinaryOpNode *p_binary_op);
+		void print_call(CallNode *p_call);
+		void print_cast(CastNode *p_cast);
+		void print_class(ClassNode *p_class);
+		void print_constant(ConstantNode *p_constant);
+		void print_dictionary(DictionaryNode *p_dictionary);
+		void print_expression(ExpressionNode *p_expression);
+		void print_enum(EnumNode *p_enum);
+		void print_for(ForNode *p_for);
+		void print_function(FunctionNode *p_function);
+		void print_get_node(GetNodeNode *p_get_node);
+		void print_if(IfNode *p_if, bool p_is_elif = false);
+		void print_identifier(IdentifierNode *p_identifier);
+		void print_literal(LiteralNode *p_literal);
+		void print_match(MatchNode *p_match);
+		void print_match_branch(MatchBranchNode *p_match_branch);
+		void print_match_pattern(PatternNode *p_match_pattern);
+		void print_parameter(ParameterNode *p_parameter);
+		void print_preload(PreloadNode *p_preload);
+		void print_return(ReturnNode *p_return);
+		void print_self(SelfNode *p_self);
+		void print_signal(SignalNode *p_signal);
+		void print_statement(Node *p_statement);
+		void print_subscript(SubscriptNode *p_subscript);
+		void print_suite(SuiteNode *p_suite);
+		void print_type(TypeNode *p_type);
+		void print_ternary_op(TernaryOpNode *p_ternary_op);
+		void print_unary_op(UnaryOpNode *p_unary_op);
+		void print_variable(VariableNode *p_variable);
+		void print_while(WhileNode *p_while);
+
+	public:
+		void print_tree(const GDScriptParser &p_parser);
+	};
+#endif // DEBUG_ENABLED
 };
 };
 
 
 #endif // GDSCRIPT_PARSER_H
 #endif // GDSCRIPT_PARSER_H

+ 1082 - 1326
modules/gdscript/gdscript_tokenizer.cpp

@@ -30,1476 +30,1232 @@
 
 
 #include "gdscript_tokenizer.h"
 #include "gdscript_tokenizer.h"
 
 
-#include "core/io/marshalls.h"
-#include "core/map.h"
-#include "core/print_string.h"
-#include "gdscript_functions.h"
-
-const char *GDScriptTokenizer::token_names[TK_MAX] = {
-	"Empty",
-	"Identifier",
-	"Constant",
-	"Self",
-	"Built-In Type",
-	"Built-In Func",
-	"In",
-	"'=='",
-	"'!='",
-	"'<'",
-	"'<='",
-	"'>'",
-	"'>='",
-	"'and'",
-	"'or'",
-	"'not'",
-	"'+'",
-	"'-'",
-	"'*'",
-	"'/'",
-	"'%'",
-	"'<<'",
-	"'>>'",
-	"'='",
-	"'+='",
-	"'-='",
-	"'*='",
-	"'/='",
-	"'%='",
-	"'<<='",
-	"'>>='",
-	"'&='",
-	"'|='",
-	"'^='",
-	"'&'",
-	"'|'",
-	"'^'",
-	"'~'",
-	//"Plus Plus",
-	//"Minus Minus",
-	"if",
-	"elif",
-	"else",
-	"for",
-	"while",
-	"break",
-	"continue",
-	"pass",
-	"return",
-	"match",
-	"func",
-	"class",
-	"class_name",
-	"extends",
-	"is",
-	"onready",
-	"tool",
-	"static",
-	"export",
-	"setget",
-	"const",
-	"var",
-	"as",
-	"void",
-	"enum",
-	"preload",
-	"assert",
-	"yield",
-	"signal",
-	"breakpoint",
-	"remote",
-	"master",
-	"puppet",
-	"remotesync",
-	"mastersync",
-	"puppetsync",
-	"'['",
-	"']'",
-	"'{'",
-	"'}'",
-	"'('",
-	"')'",
-	"','",
-	"';'",
-	"'.'",
-	"'?'",
-	"':'",
-	"'$'",
-	"'->'",
-	"'\\n'",
-	"PI",
-	"TAU",
-	"_",
-	"INF",
-	"NAN",
-	"Error",
-	"EOF",
-	"Cursor"
+#include "core/error_macros.h"
+
+#ifdef TOOLS_ENABLED
+#include "editor/editor_settings.h"
+#endif
+
+static const char *token_names[] = {
+	"Empty", // EMPTY,
+	// Basic
+	"Annotation", // ANNOTATION
+	"Identifier", // IDENTIFIER,
+	"Literal", // LITERAL,
+	// Comparison
+	"<", // LESS,
+	"<=", // LESS_EQUAL,
+	">", // GREATER,
+	">=", // GREATER_EQUAL,
+	"==", // EQUAL_EQUAL,
+	"!=", // BANG_EQUAL,
+	// Logical
+	"and", // AND,
+	"or", // OR,
+	"not", // NOT,
+	"&&", // AMPERSAND_AMPERSAND,
+	"||", // PIPE_PIPE,
+	"!", // BANG,
+	// Bitwise
+	"&", // AMPERSAND,
+	"|", // PIPE,
+	"~", // TILDE,
+	"^", // CARET,
+	"<<", // LESS_LESS,
+	">>", // GREATER_GREATER,
+	// Math
+	"+", // PLUS,
+	"-", // MINUS,
+	"*", // STAR,
+	"/", // SLASH,
+	"%", // PERCENT,
+	// Assignment
+	"=", // EQUAL,
+	"+=", // PLUS_EQUAL,
+	"-=", // MINUS_EQUAL,
+	"*=", // STAR_EQUAL,
+	"/=", // SLASH_EQUAL,
+	"%=", // PERCENT_EQUAL,
+	"<<=", // LESS_LESS_EQUAL,
+	">>=", // GREATER_GREATER_EQUAL,
+	"&=", // AMPERSAND_EQUAL,
+	"|=", // PIPE_EQUAL,
+	"^=", // CARET_EQUAL,
+	// Control flow
+	"if", // IF,
+	"elif", // ELIF,
+	"else", // ELSE,
+	"for", // FOR,
+	"while", // WHILE,
+	"break", // BREAK,
+	"continue", // CONTINUE,
+	"pass", // PASS,
+	"return", // RETURN,
+	"match", // MATCH,
+	// Keywords
+	"as", // AS,
+	"assert", // ASSERT,
+	"await", // AWAIT,
+	"breakpoint", // BREAKPOINT,
+	"class", // CLASS,
+	"class_name", // CLASS_NAME,
+	"const", // CONST,
+	"enum", // ENUM,
+	"extends", // EXTENDS,
+	"func", // FUNC,
+	"in", // IN,
+	"is", // IS,
+	"namespace", // NAMESPACE
+	"preload", // PRELOAD,
+	"self", // SELF,
+	"signal", // SIGNAL,
+	"static", // STATIC,
+	"super", // SUPER,
+	"var", // VAR,
+	"void", // VOID,
+	"yield", // YIELD,
+	// Punctuation
+	"[", // BRACKET_OPEN,
+	"]", // BRACKET_CLOSE,
+	"{", // BRACE_OPEN,
+	"}", // BRACE_CLOSE,
+	"(", // PARENTHESIS_OPEN,
+	")", // PARENTHESIS_CLOSE,
+	",", // COMMA,
+	";", // SEMICOLON,
+	".", // PERIOD,
+	"..", // PERIOD_PERIOD,
+	":", // COLON,
+	"$", // DOLLAR,
+	"->", // FORWARD_ARROW,
+	"_", // UNDERSCORE,
+	// Whitespace
+	"Newline", // NEWLINE,
+	"Indent", // INDENT,
+	"Dedent", // DEDENT,
+	// Constants
+	"PI", // CONST_PI,
+	"TAU", // CONST_TAU,
+	"INF", // CONST_INF,
+	"NaN", // CONST_NAN,
+	// Error message improvement
+	"VCS conflict marker", // VCS_CONFLICT_MARKER,
+	"`", // BACKTICK,
+	"?", // QUESTION_MARK,
+	// Special
+	"Error", // ERROR,
+	"End of file", // EOF,
 };
 };
 
 
-struct _bit {
-	Variant::Type type;
-	const char *text;
-};
-//built in types
-
-static const _bit _type_list[] = {
-	//types
-	{ Variant::BOOL, "bool" },
-	{ Variant::INT, "int" },
-	{ Variant::FLOAT, "float" },
-	{ Variant::STRING, "String" },
-	{ Variant::VECTOR2, "Vector2" },
-	{ Variant::VECTOR2I, "Vector2i" },
-	{ Variant::RECT2, "Rect2" },
-	{ Variant::RECT2I, "Rect2i" },
-	{ Variant::TRANSFORM2D, "Transform2D" },
-	{ Variant::VECTOR3, "Vector3" },
-	{ Variant::VECTOR3I, "Vector3i" },
-	{ Variant::AABB, "AABB" },
-	{ Variant::PLANE, "Plane" },
-	{ Variant::QUAT, "Quat" },
-	{ Variant::BASIS, "Basis" },
-	{ Variant::TRANSFORM, "Transform" },
-	{ Variant::COLOR, "Color" },
-	{ Variant::_RID, "RID" },
-	{ Variant::OBJECT, "Object" },
-	{ Variant::STRING_NAME, "StringName" },
-	{ Variant::NODE_PATH, "NodePath" },
-	{ Variant::DICTIONARY, "Dictionary" },
-	{ Variant::CALLABLE, "Callable" },
-	{ Variant::SIGNAL, "Signal" },
-	{ Variant::ARRAY, "Array" },
-	{ Variant::PACKED_BYTE_ARRAY, "PackedByteArray" },
-	{ Variant::PACKED_INT32_ARRAY, "PackedInt32Array" },
-	{ Variant::PACKED_INT64_ARRAY, "PackedInt64Array" },
-	{ Variant::PACKED_FLOAT32_ARRAY, "PackedFloat32Array" },
-	{ Variant::PACKED_FLOAT64_ARRAY, "PackedFloat64Array" },
-	{ Variant::PACKED_STRING_ARRAY, "PackedStringArray" },
-	{ Variant::PACKED_VECTOR2_ARRAY, "PackedVector2Array" },
-	{ Variant::PACKED_VECTOR3_ARRAY, "PackedVector3Array" },
-	{ Variant::PACKED_COLOR_ARRAY, "PackedColorArray" },
-	{ Variant::VARIANT_MAX, nullptr },
-};
-
-struct _kws {
-	GDScriptTokenizer::Token token;
-	const char *text;
-};
-
-static const _kws _keyword_list[] = {
-	//ops
-	{ GDScriptTokenizer::TK_OP_IN, "in" },
-	{ GDScriptTokenizer::TK_OP_NOT, "not" },
-	{ GDScriptTokenizer::TK_OP_OR, "or" },
-	{ GDScriptTokenizer::TK_OP_AND, "and" },
-	//func
-	{ GDScriptTokenizer::TK_PR_FUNCTION, "func" },
-	{ GDScriptTokenizer::TK_PR_CLASS, "class" },
-	{ GDScriptTokenizer::TK_PR_CLASS_NAME, "class_name" },
-	{ GDScriptTokenizer::TK_PR_EXTENDS, "extends" },
-	{ GDScriptTokenizer::TK_PR_IS, "is" },
-	{ GDScriptTokenizer::TK_PR_ONREADY, "onready" },
-	{ GDScriptTokenizer::TK_PR_TOOL, "tool" },
-	{ GDScriptTokenizer::TK_PR_STATIC, "static" },
-	{ GDScriptTokenizer::TK_PR_EXPORT, "export" },
-	{ GDScriptTokenizer::TK_PR_SETGET, "setget" },
-	{ GDScriptTokenizer::TK_PR_VAR, "var" },
-	{ GDScriptTokenizer::TK_PR_AS, "as" },
-	{ GDScriptTokenizer::TK_PR_VOID, "void" },
-	{ GDScriptTokenizer::TK_PR_PRELOAD, "preload" },
-	{ GDScriptTokenizer::TK_PR_ASSERT, "assert" },
-	{ GDScriptTokenizer::TK_PR_YIELD, "yield" },
-	{ GDScriptTokenizer::TK_PR_SIGNAL, "signal" },
-	{ GDScriptTokenizer::TK_PR_BREAKPOINT, "breakpoint" },
-	{ GDScriptTokenizer::TK_PR_REMOTE, "remote" },
-	{ GDScriptTokenizer::TK_PR_MASTER, "master" },
-	{ GDScriptTokenizer::TK_PR_PUPPET, "puppet" },
-	{ GDScriptTokenizer::TK_PR_REMOTESYNC, "remotesync" },
-	{ GDScriptTokenizer::TK_PR_MASTERSYNC, "mastersync" },
-	{ GDScriptTokenizer::TK_PR_PUPPETSYNC, "puppetsync" },
-	{ GDScriptTokenizer::TK_PR_CONST, "const" },
-	{ GDScriptTokenizer::TK_PR_ENUM, "enum" },
-	//controlflow
-	{ GDScriptTokenizer::TK_CF_IF, "if" },
-	{ GDScriptTokenizer::TK_CF_ELIF, "elif" },
-	{ GDScriptTokenizer::TK_CF_ELSE, "else" },
-	{ GDScriptTokenizer::TK_CF_FOR, "for" },
-	{ GDScriptTokenizer::TK_CF_WHILE, "while" },
-	{ GDScriptTokenizer::TK_CF_BREAK, "break" },
-	{ GDScriptTokenizer::TK_CF_CONTINUE, "continue" },
-	{ GDScriptTokenizer::TK_CF_RETURN, "return" },
-	{ GDScriptTokenizer::TK_CF_MATCH, "match" },
-	{ GDScriptTokenizer::TK_CF_PASS, "pass" },
-	{ GDScriptTokenizer::TK_SELF, "self" },
-	{ GDScriptTokenizer::TK_CONST_PI, "PI" },
-	{ GDScriptTokenizer::TK_CONST_TAU, "TAU" },
-	{ GDScriptTokenizer::TK_WILDCARD, "_" },
-	{ GDScriptTokenizer::TK_CONST_INF, "INF" },
-	{ GDScriptTokenizer::TK_CONST_NAN, "NAN" },
-	{ GDScriptTokenizer::TK_ERROR, nullptr }
-};
+// Avoid desync.
+static_assert(sizeof(token_names) / sizeof(token_names[0]) == GDScriptTokenizer::Token::TK_MAX, "Amount of token names don't match the amount of token types.");
 
 
-const char *GDScriptTokenizer::get_token_name(Token p_token) {
-	ERR_FAIL_INDEX_V(p_token, TK_MAX, "<error>");
-	return token_names[p_token];
+const char *GDScriptTokenizer::Token::get_name() const {
+	ERR_FAIL_INDEX_V_MSG(type, TK_MAX, "<error>", "Using token type out of the enum.");
+	return token_names[type];
 }
 }
 
 
-bool GDScriptTokenizer::is_token_literal(int p_offset, bool variable_safe) const {
-	switch (get_token(p_offset)) {
-		// Can always be literal:
-		case TK_IDENTIFIER:
-
-		case TK_PR_ONREADY:
-		case TK_PR_TOOL:
-		case TK_PR_STATIC:
-		case TK_PR_EXPORT:
-		case TK_PR_SETGET:
-		case TK_PR_SIGNAL:
-		case TK_PR_REMOTE:
-		case TK_PR_MASTER:
-		case TK_PR_PUPPET:
-		case TK_PR_REMOTESYNC:
-		case TK_PR_MASTERSYNC:
-		case TK_PR_PUPPETSYNC:
-			return true;
-
-		// Literal for non-variables only:
-		case TK_BUILT_IN_TYPE:
-		case TK_BUILT_IN_FUNC:
-
-		case TK_OP_IN:
-			//case TK_OP_NOT:
-			//case TK_OP_OR:
-			//case TK_OP_AND:
-
-		case TK_PR_CLASS:
-		case TK_PR_CONST:
-		case TK_PR_ENUM:
-		case TK_PR_PRELOAD:
-		case TK_PR_FUNCTION:
-		case TK_PR_EXTENDS:
-		case TK_PR_ASSERT:
-		case TK_PR_YIELD:
-		case TK_PR_VAR:
-
-		case TK_CF_IF:
-		case TK_CF_ELIF:
-		case TK_CF_ELSE:
-		case TK_CF_FOR:
-		case TK_CF_WHILE:
-		case TK_CF_BREAK:
-		case TK_CF_CONTINUE:
-		case TK_CF_RETURN:
-		case TK_CF_MATCH:
-		case TK_CF_PASS:
-		case TK_SELF:
-		case TK_CONST_PI:
-		case TK_CONST_TAU:
-		case TK_WILDCARD:
-		case TK_CONST_INF:
-		case TK_CONST_NAN:
-		case TK_ERROR:
-			return !variable_safe;
-
-		case TK_CONSTANT: {
-			switch (get_token_constant(p_offset).get_type()) {
-				case Variant::NIL:
-				case Variant::BOOL:
-					return true;
-				default:
-					return false;
-			}
-		}
-		default:
-			return false;
-	}
+String GDScriptTokenizer::get_token_name(Token::Type p_token_type) {
+	ERR_FAIL_INDEX_V_MSG(p_token_type, Token::TK_MAX, "<error>", "Using token type out of the enum.");
+	return token_names[p_token_type];
 }
 }
 
 
-StringName GDScriptTokenizer::get_token_literal(int p_offset) const {
-	Token token = get_token(p_offset);
-	switch (token) {
-		case TK_IDENTIFIER:
-			return get_token_identifier(p_offset);
-		case TK_BUILT_IN_TYPE: {
-			Variant::Type type = get_token_type(p_offset);
-			int idx = 0;
-
-			while (_type_list[idx].text) {
-				if (type == _type_list[idx].type) {
-					return _type_list[idx].text;
-				}
-				idx++;
-			}
-		} break; // Shouldn't get here, stuff happens
-		case TK_BUILT_IN_FUNC:
-			return GDScriptFunctions::get_func_name(get_token_built_in_func(p_offset));
-		case TK_CONSTANT: {
-			const Variant value = get_token_constant(p_offset);
-
-			switch (value.get_type()) {
-				case Variant::NIL:
-					return "null";
-				case Variant::BOOL:
-					return value ? "true" : "false";
-				default: {
-				}
-			}
-		} break;
-		case TK_OP_AND:
-		case TK_OP_OR:
-			break; // Don't get into default, since they can be non-literal
-		default: {
-			int idx = 0;
-
-			while (_keyword_list[idx].text) {
-				if (token == _keyword_list[idx].token) {
-					return _keyword_list[idx].text;
-				}
-				idx++;
-			}
-		}
+void GDScriptTokenizer::set_source_code(const String &p_source_code) {
+	source = p_source_code;
+	if (source.empty()) {
+		_source = L"";
+	} else {
+		_source = source.ptr();
 	}
 	}
-	ERR_FAIL_V_MSG("", "Failed to get token literal.");
+	_current = _source;
+	line = 1;
+	column = 1;
+	length = p_source_code.length();
+	position = 0;
 }
 }
 
 
-static bool _is_text_char(CharType c) {
-	return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
+void GDScriptTokenizer::set_cursor_position(int p_line, int p_column) {
+	cursor_line = p_line;
+	cursor_column = p_column;
 }
 }
 
 
-static bool _is_number(CharType c) {
-	return (c >= '0' && c <= '9');
+void GDScriptTokenizer::set_multiline_mode(bool p_state) {
+	multiline_mode = p_state;
 }
 }
 
 
-static bool _is_hex(CharType c) {
-	return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
+int GDScriptTokenizer::get_cursor_line() const {
+	return cursor_line;
 }
 }
 
 
-static bool _is_bin(CharType c) {
-	return (c == '0' || c == '1');
+int GDScriptTokenizer::get_cursor_column() const {
+	return cursor_column;
 }
 }
 
 
-void GDScriptTokenizerText::_make_token(Token p_type) {
-	TokenData &tk = tk_rb[tk_rb_pos];
-
-	tk.type = p_type;
-	tk.line = line;
-	tk.col = column;
+CharType GDScriptTokenizer::_advance() {
+	if (unlikely(_is_at_end())) {
+		return '\0';
+	}
+	_current++;
+	position++;
+	column++;
+	if (column > rightmost_column) {
+		rightmost_column = column;
+	}
+	if (unlikely(_is_at_end())) {
+		// Add extra newline even if it's not there, to satisfy the parser.
+		newline(true);
+		// Also add needed unindent.
+		check_indent();
+	}
+	return _peek(-1);
+}
 
 
-	tk_rb_pos = (tk_rb_pos + 1) % TK_RB_SIZE;
+void GDScriptTokenizer::push_paren(CharType p_char) {
+	paren_stack.push_back(p_char);
 }
 }
 
 
-void GDScriptTokenizerText::_make_identifier(const StringName &p_identifier) {
-	TokenData &tk = tk_rb[tk_rb_pos];
+bool GDScriptTokenizer::pop_paren(CharType p_expected) {
+	if (paren_stack.empty()) {
+		return false;
+	}
+	CharType actual = paren_stack.back()->get();
+	paren_stack.pop_back();
 
 
-	tk.type = TK_IDENTIFIER;
-	tk.identifier = p_identifier;
-	tk.line = line;
-	tk.col = column;
+	return actual == p_expected;
+}
 
 
-	tk_rb_pos = (tk_rb_pos + 1) % TK_RB_SIZE;
+GDScriptTokenizer::Token GDScriptTokenizer::pop_error() {
+	Token error = error_stack.back()->get();
+	error_stack.pop_back();
+	return error;
 }
 }
 
 
-void GDScriptTokenizerText::_make_built_in_func(GDScriptFunctions::Function p_func) {
-	TokenData &tk = tk_rb[tk_rb_pos];
+static bool _is_alphanumeric(CharType c) {
+	return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
+}
 
 
-	tk.type = TK_BUILT_IN_FUNC;
-	tk.func = p_func;
-	tk.line = line;
-	tk.col = column;
+static bool _is_digit(CharType c) {
+	return (c >= '0' && c <= '9');
+}
 
 
-	tk_rb_pos = (tk_rb_pos + 1) % TK_RB_SIZE;
+static bool _is_hex_digit(CharType c) {
+	return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
 }
 }
 
 
-void GDScriptTokenizerText::_make_constant(const Variant &p_constant) {
-	TokenData &tk = tk_rb[tk_rb_pos];
+static bool _is_binary_digit(CharType c) {
+	return (c == '0' || c == '1');
+}
 
 
-	tk.type = TK_CONSTANT;
-	tk.constant = p_constant;
-	tk.line = line;
-	tk.col = column;
+GDScriptTokenizer::Token GDScriptTokenizer::make_token(Token::Type p_type) const {
+	Token token(p_type);
+	token.start_line = start_line;
+	token.end_line = line;
+	token.start_column = start_column;
+	token.end_column = column;
+	token.leftmost_column = leftmost_column;
+	token.rightmost_column = rightmost_column;
 
 
-	tk_rb_pos = (tk_rb_pos + 1) % TK_RB_SIZE;
+	return token;
 }
 }
 
 
-void GDScriptTokenizerText::_make_type(const Variant::Type &p_type) {
-	TokenData &tk = tk_rb[tk_rb_pos];
-
-	tk.type = TK_BUILT_IN_TYPE;
-	tk.vtype = p_type;
-	tk.line = line;
-	tk.col = column;
+GDScriptTokenizer::Token GDScriptTokenizer::make_literal(const Variant &p_literal) const {
+	Token token = make_token(Token::LITERAL);
+	token.literal = p_literal;
+	return token;
+}
 
 
-	tk_rb_pos = (tk_rb_pos + 1) % TK_RB_SIZE;
+GDScriptTokenizer::Token GDScriptTokenizer::make_identifier(const StringName &p_identifier) const {
+	Token identifier = make_token(Token::IDENTIFIER);
+	identifier.literal = p_identifier;
+	return identifier;
 }
 }
 
 
-void GDScriptTokenizerText::_make_error(const String &p_error) {
-	error_flag = true;
-	last_error = p_error;
+GDScriptTokenizer::Token GDScriptTokenizer::make_error(const String &p_message) {
+	Token error = make_token(Token::ERROR);
+	error.literal = p_message;
 
 
-	TokenData &tk = tk_rb[tk_rb_pos];
-	tk.type = TK_ERROR;
-	tk.constant = p_error;
-	tk.line = line;
-	tk.col = column;
-	tk_rb_pos = (tk_rb_pos + 1) % TK_RB_SIZE;
+	return error;
 }
 }
 
 
-void GDScriptTokenizerText::_make_newline(int p_indentation, int p_tabs) {
-	TokenData &tk = tk_rb[tk_rb_pos];
-	tk.type = TK_NEWLINE;
-	tk.constant = Vector2(p_indentation, p_tabs);
-	tk.line = line;
-	tk.col = column;
-	tk_rb_pos = (tk_rb_pos + 1) % TK_RB_SIZE;
+void GDScriptTokenizer::push_error(const String &p_message) {
+	Token error = make_error(p_message);
+	error_stack.push_back(error);
 }
 }
 
 
-void GDScriptTokenizerText::_advance() {
-	if (error_flag) {
-		//parser broke
-		_make_error(last_error);
-		return;
-	}
+void GDScriptTokenizer::push_error(const Token &p_error) {
+	error_stack.push_back(p_error);
+}
 
 
-	if (code_pos >= len) {
-		_make_token(TK_EOF);
-		return;
-	}
-#define GETCHAR(m_ofs) ((m_ofs + code_pos) >= len ? 0 : _code[m_ofs + code_pos])
-#define INCPOS(m_amount)      \
-	{                         \
-		code_pos += m_amount; \
-		column += m_amount;   \
+GDScriptTokenizer::Token GDScriptTokenizer::make_paren_error(CharType p_paren) {
+	if (paren_stack.empty()) {
+		return make_error(vformat("Closing \"%c\" doesn't have an opening counterpart.", p_paren));
 	}
 	}
-	while (true) {
-		bool is_string_name = false;
-		StringMode string_mode = STRING_DOUBLE_QUOTE;
-
-		switch (GETCHAR(0)) {
-			case 0:
-				_make_token(TK_EOF);
-				break;
-			case '\\':
-				INCPOS(1);
-				if (GETCHAR(0) == '\r') {
-					INCPOS(1);
-				}
-
-				if (GETCHAR(0) != '\n') {
-					_make_error("Expected newline after '\\'.");
-					return;
-				}
-
-				INCPOS(1);
-				line++;
-
-				while (GETCHAR(0) == ' ' || GETCHAR(0) == '\t') {
-					INCPOS(1);
-				}
-
-				continue;
-			case '\t':
-			case '\r':
-			case ' ':
-				INCPOS(1);
-				continue;
-			case '#': { // line comment skip
-#ifdef DEBUG_ENABLED
-				String comment;
-#endif // DEBUG_ENABLED
-				while (GETCHAR(0) != '\n') {
-#ifdef DEBUG_ENABLED
-					comment += GETCHAR(0);
-#endif // DEBUG_ENABLED
-					code_pos++;
-					if (GETCHAR(0) == 0) { //end of file
-						//_make_error("Unterminated Comment");
-						_make_token(TK_EOF);
-						return;
-					}
-				}
-#ifdef DEBUG_ENABLED
-				String comment_content = comment.trim_prefix("#").trim_prefix(" ");
-				if (comment_content.begins_with("warning-ignore:")) {
-					String code = comment_content.get_slice(":", 1);
-					warning_skips.push_back(Pair<int, String>(line, code.strip_edges().to_lower()));
-				} else if (comment_content.begins_with("warning-ignore-all:")) {
-					String code = comment_content.get_slice(":", 1);
-					warning_global_skips.insert(code.strip_edges().to_lower());
-				} else if (comment_content.strip_edges() == "warnings-disable") {
-					ignore_warnings = true;
-				}
-#endif // DEBUG_ENABLED
-				[[fallthrough]];
-			}
-			case '\n': {
-				line++;
-				INCPOS(1);
-				bool used_spaces = false;
-				int tabs = 0;
-				column = 1;
-				int i = 0;
-				while (true) {
-					if (GETCHAR(i) == ' ') {
-						i++;
-						used_spaces = true;
-					} else if (GETCHAR(i) == '\t') {
-						if (used_spaces) {
-							_make_error("Spaces used before tabs on a line");
-							return;
-						}
-						i++;
-						tabs++;
-					} else {
-						break; // not indentation anymore
-					}
-				}
-
-				_make_newline(i, tabs);
-				return;
-			}
-			case '/': {
-				switch (GETCHAR(1)) {
-					case '=': { // diveq
-
-						_make_token(TK_OP_ASSIGN_DIV);
-						INCPOS(1);
-
-					} break;
-					default:
-						_make_token(TK_OP_DIV);
-				}
-			} break;
-			case '=': {
-				if (GETCHAR(1) == '=') {
-					_make_token(TK_OP_EQUAL);
-					INCPOS(1);
-
-				} else {
-					_make_token(TK_OP_ASSIGN);
-				}
-
-			} break;
-			case '<': {
-				if (GETCHAR(1) == '=') {
-					_make_token(TK_OP_LESS_EQUAL);
-					INCPOS(1);
-				} else if (GETCHAR(1) == '<') {
-					if (GETCHAR(2) == '=') {
-						_make_token(TK_OP_ASSIGN_SHIFT_LEFT);
-						INCPOS(1);
-					} else {
-						_make_token(TK_OP_SHIFT_LEFT);
-					}
-					INCPOS(1);
-				} else {
-					_make_token(TK_OP_LESS);
-				}
-
-			} break;
-			case '>': {
-				if (GETCHAR(1) == '=') {
-					_make_token(TK_OP_GREATER_EQUAL);
-					INCPOS(1);
-				} else if (GETCHAR(1) == '>') {
-					if (GETCHAR(2) == '=') {
-						_make_token(TK_OP_ASSIGN_SHIFT_RIGHT);
-						INCPOS(1);
-
-					} else {
-						_make_token(TK_OP_SHIFT_RIGHT);
-					}
-					INCPOS(1);
-				} else {
-					_make_token(TK_OP_GREATER);
-				}
-
-			} break;
-			case '!': {
-				if (GETCHAR(1) == '=') {
-					_make_token(TK_OP_NOT_EQUAL);
-					INCPOS(1);
-				} else {
-					_make_token(TK_OP_NOT);
-				}
-
-			} break;
-			//case '"' //string - no strings in shader
-			//case '\'' //string - no strings in shader
-			case '{':
-				_make_token(TK_CURLY_BRACKET_OPEN);
-				break;
-			case '}':
-				_make_token(TK_CURLY_BRACKET_CLOSE);
-				break;
-			case '[':
-				_make_token(TK_BRACKET_OPEN);
-				break;
-			case ']':
-				_make_token(TK_BRACKET_CLOSE);
-				break;
-			case '(':
-				_make_token(TK_PARENTHESIS_OPEN);
-				break;
-			case ')':
-				_make_token(TK_PARENTHESIS_CLOSE);
-				break;
-			case ',':
-				_make_token(TK_COMMA);
-				break;
-			case ';':
-				_make_token(TK_SEMICOLON);
-				break;
-			case '?':
-				_make_token(TK_QUESTION_MARK);
-				break;
-			case ':':
-				_make_token(TK_COLON); //for methods maybe but now useless.
-				break;
-			case '$':
-				_make_token(TK_DOLLAR); //for the get_node() shortener
-				break;
-			case '^': {
-				if (GETCHAR(1) == '=') {
-					_make_token(TK_OP_ASSIGN_BIT_XOR);
-					INCPOS(1);
-				} else {
-					_make_token(TK_OP_BIT_XOR);
-				}
-
-			} break;
-			case '~':
-				_make_token(TK_OP_BIT_INVERT);
-				break;
-			case '&': {
-				if (GETCHAR(1) == '&') {
-					_make_token(TK_OP_AND);
-					INCPOS(1);
-				} else if (GETCHAR(1) == '=') {
-					_make_token(TK_OP_ASSIGN_BIT_AND);
-					INCPOS(1);
-				} else {
-					_make_token(TK_OP_BIT_AND);
-				}
-			} break;
-			case '|': {
-				if (GETCHAR(1) == '|') {
-					_make_token(TK_OP_OR);
-					INCPOS(1);
-				} else if (GETCHAR(1) == '=') {
-					_make_token(TK_OP_ASSIGN_BIT_OR);
-					INCPOS(1);
-				} else {
-					_make_token(TK_OP_BIT_OR);
-				}
-			} break;
-			case '*': {
-				if (GETCHAR(1) == '=') {
-					_make_token(TK_OP_ASSIGN_MUL);
-					INCPOS(1);
-				} else {
-					_make_token(TK_OP_MUL);
-				}
-			} break;
-			case '+': {
-				if (GETCHAR(1) == '=') {
-					_make_token(TK_OP_ASSIGN_ADD);
-					INCPOS(1);
-					/*
-				}  else if (GETCHAR(1)=='+') {
-					_make_token(TK_OP_PLUS_PLUS);
-					INCPOS(1);
-				*/
-				} else {
-					_make_token(TK_OP_ADD);
-				}
-
-			} break;
-			case '-': {
-				if (GETCHAR(1) == '=') {
-					_make_token(TK_OP_ASSIGN_SUB);
-					INCPOS(1);
-				} else if (GETCHAR(1) == '>') {
-					_make_token(TK_FORWARD_ARROW);
-					INCPOS(1);
-				} else {
-					_make_token(TK_OP_SUB);
-				}
-			} break;
-			case '%': {
-				if (GETCHAR(1) == '=') {
-					_make_token(TK_OP_ASSIGN_MOD);
-					INCPOS(1);
-				} else {
-					_make_token(TK_OP_MOD);
-				}
-			} break;
-			case '@':
-				if (CharType(GETCHAR(1)) != '"' && CharType(GETCHAR(1)) != '\'') {
-					_make_error("Unexpected '@'");
-					return;
-				}
-				INCPOS(1);
-				is_string_name = true;
-				[[fallthrough]];
-			case '\'':
-			case '"': {
-				if (GETCHAR(0) == '\'') {
-					string_mode = STRING_SINGLE_QUOTE;
-				}
-
-				int i = 1;
-				if (string_mode == STRING_DOUBLE_QUOTE && GETCHAR(i) == '"' && GETCHAR(i + 1) == '"') {
-					i += 2;
-					string_mode = STRING_MULTILINE;
-				}
-
-				String str;
-				while (true) {
-					if (CharType(GETCHAR(i)) == 0) {
-						_make_error("Unterminated String");
-						return;
-					} else if (string_mode == STRING_DOUBLE_QUOTE && CharType(GETCHAR(i)) == '"') {
-						break;
-					} else if (string_mode == STRING_SINGLE_QUOTE && CharType(GETCHAR(i)) == '\'') {
-						break;
-					} else if (string_mode == STRING_MULTILINE && CharType(GETCHAR(i)) == '\"' && CharType(GETCHAR(i + 1)) == '\"' && CharType(GETCHAR(i + 2)) == '\"') {
-						i += 2;
-						break;
-					} else if (string_mode != STRING_MULTILINE && CharType(GETCHAR(i)) == '\n') {
-						_make_error("Unexpected EOL at String.");
-						return;
-					} else if (CharType(GETCHAR(i)) == 0xFFFF) {
-						//string ends here, next will be TK
-						i--;
-						break;
-					} else if (CharType(GETCHAR(i)) == '\\') {
-						//escaped characters...
-						i++;
-						CharType next = GETCHAR(i);
-						if (next == 0) {
-							_make_error("Unterminated String");
-							return;
-						}
-						CharType res = 0;
-
-						switch (next) {
-							case 'a':
-								res = '\a';
-								break;
-							case 'b':
-								res = '\b';
-								break;
-							case 't':
-								res = '\t';
-								break;
-							case 'n':
-								res = '\n';
-								break;
-							case 'v':
-								res = '\v';
-								break;
-							case 'f':
-								res = '\f';
-								break;
-							case 'r':
-								res = '\r';
-								break;
-							case '\'':
-								res = '\'';
-								break;
-							case '\"':
-								res = '\"';
-								break;
-							case '\\':
-								res = '\\';
-								break;
-
-							case 'u': {
-								// hex number
-								i += 1;
-								for (int j = 0; j < 4; j++) {
-									CharType c = GETCHAR(i + j);
-									if (c == 0) {
-										_make_error("Unterminated String");
-										return;
-									}
-
-									CharType v = 0;
-									if (c >= '0' && c <= '9') {
-										v = c - '0';
-									} else if (c >= 'a' && c <= 'f') {
-										v = c - 'a';
-										v += 10;
-									} else if (c >= 'A' && c <= 'F') {
-										v = c - 'A';
-										v += 10;
-									} else {
-										_make_error("Malformed hex constant in string");
-										return;
-									}
-
-									res <<= 4;
-									res |= v;
-								}
-								i += 3;
-
-							} break;
-							case '\n': {
-								line++;
-								column = 1;
-							} break;
-							default: {
-								_make_error("Invalid escape sequence");
-								return;
-							} break;
-						}
-
-						if (next != '\n') {
-							str += res;
-						}
-
-					} else {
-						if (CharType(GETCHAR(i)) == '\n') {
-							line++;
-							column = 1;
-						}
-
-						str += CharType(GETCHAR(i));
-					}
-					i++;
-				}
-				INCPOS(i);
-
-				if (is_string_name) {
-					_make_constant(StringName(str));
-				} else {
-					_make_constant(str);
-				}
-
-			} break;
-			case 0xFFFF: {
-				_make_token(TK_CURSOR);
-			} break;
-			default: {
-				if (_is_number(GETCHAR(0)) || (GETCHAR(0) == '.' && _is_number(GETCHAR(1)))) {
-					// parse number
-					bool period_found = false;
-					bool exponent_found = false;
-					bool hexa_found = false;
-					bool bin_found = false;
-					bool sign_found = false;
-
-					String str;
-					int i = 0;
-
-					while (true) {
-						if (GETCHAR(i) == '.') {
-							if (period_found || exponent_found) {
-								_make_error("Invalid numeric constant at '.'");
-								return;
-							} else if (bin_found) {
-								_make_error("Invalid binary constant at '.'");
-								return;
-							} else if (hexa_found) {
-								_make_error("Invalid hexadecimal constant at '.'");
-								return;
-							}
-							period_found = true;
-						} else if (GETCHAR(i) == 'x') {
-							if (hexa_found || bin_found || str.length() != 1 || !((i == 1 && str[0] == '0') || (i == 2 && str[1] == '0' && str[0] == '-'))) {
-								_make_error("Invalid numeric constant at 'x'");
-								return;
-							}
-							hexa_found = true;
-						} else if (hexa_found && _is_hex(GETCHAR(i))) {
-						} else if (!hexa_found && GETCHAR(i) == 'b') {
-							if (bin_found || str.length() != 1 || !((i == 1 && str[0] == '0') || (i == 2 && str[1] == '0' && str[0] == '-'))) {
-								_make_error("Invalid numeric constant at 'b'");
-								return;
-							}
-							bin_found = true;
-						} else if (!hexa_found && GETCHAR(i) == 'e') {
-							if (exponent_found || bin_found) {
-								_make_error("Invalid numeric constant at 'e'");
-								return;
-							}
-							exponent_found = true;
-						} else if (_is_number(GETCHAR(i))) {
-							//all ok
-
-						} else if (bin_found && _is_bin(GETCHAR(i))) {
-						} else if ((GETCHAR(i) == '-' || GETCHAR(i) == '+') && exponent_found) {
-							if (sign_found) {
-								_make_error("Invalid numeric constant at '-'");
-								return;
-							}
-							sign_found = true;
-						} else if (GETCHAR(i) == '_') {
-							i++;
-							continue; // Included for readability, shouldn't be a part of the string
-						} else {
-							break;
-						}
-
-						str += CharType(GETCHAR(i));
-						i++;
-					}
-
-					if (!(_is_number(str[str.length() - 1]) || (hexa_found && _is_hex(str[str.length() - 1])))) {
-						_make_error("Invalid numeric constant: " + str);
-						return;
-					}
-
-					INCPOS(i);
-					if (hexa_found) {
-						int64_t val = str.hex_to_int();
-						_make_constant(val);
-					} else if (bin_found) {
-						int64_t val = str.bin_to_int();
-						_make_constant(val);
-					} else if (period_found || exponent_found) {
-						double val = str.to_double();
-						_make_constant(val);
-					} else {
-						int64_t val = str.to_int();
-						_make_constant(val);
-					}
-
-					return;
-				}
-
-				if (GETCHAR(0) == '.') {
-					//parse period
-					_make_token(TK_PERIOD);
-					break;
-				}
-
-				if (_is_text_char(GETCHAR(0))) {
-					// parse identifier
-					String str;
-					str += CharType(GETCHAR(0));
-
-					int i = 1;
-					while (_is_text_char(GETCHAR(i))) {
-						str += CharType(GETCHAR(i));
-						i++;
-					}
-
-					bool identifier = false;
-
-					if (str == "null") {
-						_make_constant(Variant());
-
-					} else if (str == "true") {
-						_make_constant(true);
-
-					} else if (str == "false") {
-						_make_constant(false);
-					} else {
-						bool found = false;
-
-						{
-							int idx = 0;
-
-							while (_type_list[idx].text) {
-								if (str == _type_list[idx].text) {
-									_make_type(_type_list[idx].type);
-									found = true;
-									break;
-								}
-								idx++;
-							}
-						}
-
-						if (!found) {
-							//built in func?
-
-							for (int j = 0; j < GDScriptFunctions::FUNC_MAX; j++) {
-								if (str == GDScriptFunctions::get_func_name(GDScriptFunctions::Function(j))) {
-									_make_built_in_func(GDScriptFunctions::Function(j));
-									found = true;
-									break;
-								}
-							}
-						}
-
-						if (!found) {
-							//keyword
-
-							int idx = 0;
-							found = false;
-
-							while (_keyword_list[idx].text) {
-								if (str == _keyword_list[idx].text) {
-									_make_token(_keyword_list[idx].token);
-									found = true;
-									break;
-								}
-								idx++;
-							}
-						}
-
-						if (!found) {
-							identifier = true;
-						}
-					}
-
-					if (identifier) {
-						_make_identifier(str);
-					}
-					INCPOS(str.length());
-					return;
-				}
+	Token error = make_error(vformat("Closing \"%c\" doesn't match the opening \"%c\".", p_paren, paren_stack.back()->get()));
+	paren_stack.pop_back(); // Remove opening one anyway.
+	return error;
+}
 
 
-				_make_error("Unknown character");
-				return;
+GDScriptTokenizer::Token GDScriptTokenizer::check_vcs_marker(CharType p_test, Token::Type p_double_type) {
+	const CharType *next = _current + 1;
+	int chars = 2; // Two already matched.
 
 
-			} break;
+	// Test before consuming characters, since we don't want to consume more than needed.
+	while (*next == p_test) {
+		chars++;
+		next++;
+	}
+	if (chars >= 7) {
+		// It is a VCS conflict marker.
+		while (chars > 1) {
+			// Consume all characters (first was already consumed by scan()).
+			_advance();
+			chars--;
 		}
 		}
-
-		INCPOS(1);
-		break;
+		return make_token(Token::VCS_CONFLICT_MARKER);
+	} else {
+		// It is only a regular double character token, so we consume the second character.
+		_advance();
+		return make_token(p_double_type);
 	}
 	}
 }
 }
 
 
-void GDScriptTokenizerText::set_code(const String &p_code) {
-	code = p_code;
-	len = p_code.length();
-	if (len) {
-		_code = &code[0];
-	} else {
-		_code = nullptr;
+GDScriptTokenizer::Token GDScriptTokenizer::annotation() {
+	if (!_is_alphanumeric(_peek())) {
+		return make_error("Expected annotation identifier after \"@\".");
 	}
 	}
-	code_pos = 0;
-	line = 1; //it is stand-ar-ized that lines begin in 1 in code..
-	column = 1; //the same holds for columns
-	tk_rb_pos = 0;
-	error_flag = false;
-#ifdef DEBUG_ENABLED
-	ignore_warnings = false;
-#endif // DEBUG_ENABLED
-	last_error = "";
-	for (int i = 0; i < MAX_LOOKAHEAD + 1; i++) {
+	while (_is_alphanumeric(_peek())) {
+		// Consume all identifier characters.
 		_advance();
 		_advance();
 	}
 	}
+	Token annotation = make_token(Token::ANNOTATION);
+	annotation.literal = StringName(String(_start, _current - _start));
+	return annotation;
 }
 }
 
 
-GDScriptTokenizerText::Token GDScriptTokenizerText::get_token(int p_offset) const {
-	ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, TK_ERROR);
-	ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, TK_ERROR);
-
-	int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE;
-	return tk_rb[ofs].type;
-}
+GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() {
+#define KEYWORDS(KEYWORD_GROUP, KEYWORD)     \
+	KEYWORD_GROUP('a')                       \
+	KEYWORD("as", Token::AS)                 \
+	KEYWORD("and", Token::AND)               \
+	KEYWORD("assert", Token::ASSERT)         \
+	KEYWORD("await", Token::AWAIT)           \
+	KEYWORD_GROUP('b')                       \
+	KEYWORD("break", Token::BREAK)           \
+	KEYWORD("breakpoint", Token::BREAKPOINT) \
+	KEYWORD_GROUP('c')                       \
+	KEYWORD("class", Token::CLASS)           \
+	KEYWORD("class_name", Token::CLASS_NAME) \
+	KEYWORD("const", Token::CONST)           \
+	KEYWORD("continue", Token::CONTINUE)     \
+	KEYWORD_GROUP('e')                       \
+	KEYWORD("elif", Token::ELIF)             \
+	KEYWORD("else", Token::ELSE)             \
+	KEYWORD("enum", Token::ENUM)             \
+	KEYWORD("extends", Token::EXTENDS)       \
+	KEYWORD_GROUP('f')                       \
+	KEYWORD("for", Token::FOR)               \
+	KEYWORD("func", Token::FUNC)             \
+	KEYWORD_GROUP('i')                       \
+	KEYWORD("if", Token::IF)                 \
+	KEYWORD("in", Token::IN)                 \
+	KEYWORD("is", Token::IS)                 \
+	KEYWORD_GROUP('m')                       \
+	KEYWORD("match", Token::MATCH)           \
+	KEYWORD_GROUP('n')                       \
+	KEYWORD("namespace", Token::NAMESPACE)   \
+	KEYWORD("not", Token::NOT)               \
+	KEYWORD_GROUP('o')                       \
+	KEYWORD("or", Token::OR)                 \
+	KEYWORD_GROUP('p')                       \
+	KEYWORD("pass", Token::PASS)             \
+	KEYWORD("preload", Token::PRELOAD)       \
+	KEYWORD_GROUP('r')                       \
+	KEYWORD("return", Token::RETURN)         \
+	KEYWORD_GROUP('s')                       \
+	KEYWORD("self", Token::SELF)             \
+	KEYWORD("signal", Token::SIGNAL)         \
+	KEYWORD("static", Token::STATIC)         \
+	KEYWORD("super", Token::SUPER)           \
+	KEYWORD_GROUP('v')                       \
+	KEYWORD("var", Token::VAR)               \
+	KEYWORD("void", Token::VOID)             \
+	KEYWORD_GROUP('w')                       \
+	KEYWORD("while", Token::WHILE)           \
+	KEYWORD_GROUP('y')                       \
+	KEYWORD("yield", Token::YIELD)           \
+	KEYWORD_GROUP('I')                       \
+	KEYWORD("INF", Token::CONST_INF)         \
+	KEYWORD_GROUP('N')                       \
+	KEYWORD("NAN", Token::CONST_NAN)         \
+	KEYWORD_GROUP('P')                       \
+	KEYWORD("PI", Token::CONST_PI)           \
+	KEYWORD_GROUP('T')                       \
+	KEYWORD("TAU", Token::CONST_TAU)
+
+#define MIN_KEYWORD_LENGTH 2
+#define MAX_KEYWORD_LENGTH 10
+
+	// Consume all alphanumeric characters.
+	while (_is_alphanumeric(_peek())) {
+		_advance();
+	}
 
 
-int GDScriptTokenizerText::get_token_line(int p_offset) const {
-	ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, -1);
-	ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, -1);
+	int length = _current - _start;
 
 
-	int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE;
-	return tk_rb[ofs].line;
-}
+	if (length == 1 && _peek(-1) == '_') {
+		// Lone underscore.
+		return make_token(Token::UNDERSCORE);
+	}
 
 
-int GDScriptTokenizerText::get_token_column(int p_offset) const {
-	ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, -1);
-	ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, -1);
+	String name(_start, length);
+	if (length < MIN_KEYWORD_LENGTH || length > MAX_KEYWORD_LENGTH) {
+		// Cannot be a keyword, as the length doesn't match any.
+		return make_identifier(name);
+	}
 
 
-	int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE;
-	return tk_rb[ofs].col;
-}
+	// Define some helper macros for the switch case.
+#define KEYWORD_GROUP_CASE(char) \
+	break;                       \
+	case char:
+#define KEYWORD(keyword, token_type)                                                                                      \
+	{                                                                                                                     \
+		const int keyword_length = sizeof(keyword) - 1;                                                                   \
+		static_assert(keyword_length <= MAX_KEYWORD_LENGTH, "There's a keyword longer than the defined maximum length");  \
+		static_assert(keyword_length >= MIN_KEYWORD_LENGTH, "There's a keyword shorter than the defined minimum length"); \
+		if (keyword_length == length && name == keyword) {                                                                \
+			return make_token(token_type);                                                                                \
+		}                                                                                                                 \
+	}
 
 
-const Variant &GDScriptTokenizerText::get_token_constant(int p_offset) const {
-	ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, tk_rb[0].constant);
-	ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, tk_rb[0].constant);
+	// Find if it's a keyword.
+	switch (_start[0]) {
+		default:
+			KEYWORDS(KEYWORD_GROUP_CASE, KEYWORD)
+			break;
+	}
 
 
-	int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE;
-	ERR_FAIL_COND_V(tk_rb[ofs].type != TK_CONSTANT, tk_rb[0].constant);
-	return tk_rb[ofs].constant;
-}
+	// Check if it's a special literal
+	if (length == 4) {
+		if (name == "true") {
+			return make_literal(true);
+		} else if (name == "null") {
+			return make_literal(Variant());
+		}
+	} else if (length == 5) {
+		if (name == "false") {
+			return make_literal(false);
+		}
+	}
 
 
-StringName GDScriptTokenizerText::get_token_identifier(int p_offset) const {
-	ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, StringName());
-	ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, StringName());
+	// Not a keyword, so must be an identifier.
+	return make_identifier(name);
 
 
-	int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE;
-	ERR_FAIL_COND_V(tk_rb[ofs].type != TK_IDENTIFIER, StringName());
-	return tk_rb[ofs].identifier;
+#undef KEYWORDS
+#undef MIN_KEYWORD_LENGTH
+#undef MAX_KEYWORD_LENGTH
+#undef KEYWORD_GROUP_CASE
+#undef KEYWORD
 }
 }
 
 
-GDScriptFunctions::Function GDScriptTokenizerText::get_token_built_in_func(int p_offset) const {
-	ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, GDScriptFunctions::FUNC_MAX);
-	ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, GDScriptFunctions::FUNC_MAX);
+void GDScriptTokenizer::newline(bool p_make_token) {
+	// Don't overwrite previous newline, nor create if we want a line contination.
+	if (p_make_token && !pending_newline && !line_continuation) {
+		Token newline(Token::NEWLINE);
+		newline.start_line = line;
+		newline.end_line = line;
+		newline.start_column = column - 1;
+		newline.end_column = column;
+		newline.leftmost_column = newline.start_column;
+		newline.rightmost_column = newline.end_column;
+		pending_newline = true;
+		last_newline = newline;
+	}
 
 
-	int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE;
-	ERR_FAIL_COND_V(tk_rb[ofs].type != TK_BUILT_IN_FUNC, GDScriptFunctions::FUNC_MAX);
-	return tk_rb[ofs].func;
+	// Increment line/column counters.
+	line++;
+	column = 1;
+	leftmost_column = 1;
 }
 }
 
 
-Variant::Type GDScriptTokenizerText::get_token_type(int p_offset) const {
-	ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, Variant::NIL);
-	ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, Variant::NIL);
+GDScriptTokenizer::Token GDScriptTokenizer::number() {
+	int base = 10;
+	bool has_decimal = false;
+	bool has_exponent = false;
+	bool has_error = false;
+	bool (*digit_check_func)(CharType) = _is_digit;
+
+	if (_peek(-1) == '.') {
+		has_decimal = true;
+	} else if (_peek(-1) == '0') {
+		if (_peek() == 'x') {
+			// Hexadecimal.
+			base = 16;
+			digit_check_func = _is_hex_digit;
+			_advance();
+		} else if (_peek() == 'b') {
+			// Binary.
+			base = 2;
+			digit_check_func = _is_binary_digit;
+			_advance();
+		}
+	}
 
 
-	int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE;
-	ERR_FAIL_COND_V(tk_rb[ofs].type != TK_BUILT_IN_TYPE, Variant::NIL);
-	return tk_rb[ofs].vtype;
-}
+	// Allow '_' to be used in a number, for readability.
+	while (digit_check_func(_peek()) || _peek() == '_') {
+		_advance();
+	}
 
 
-int GDScriptTokenizerText::get_token_line_indent(int p_offset) const {
-	ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, 0);
-	ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, 0);
+	// It might be a ".." token (instead of decimal point) so we check if it's not.
+	if (_peek() == '.' && _peek(1) != '.') {
+		if (base == 10 && !has_decimal) {
+			has_decimal = true;
+		} else if (base == 10) {
+			Token error = make_error("Cannot use a decimal point twice in a number.");
+			error.start_column = column;
+			error.leftmost_column = column;
+			error.end_column = column + 1;
+			error.rightmost_column = column + 1;
+			push_error(error);
+			has_error = true;
+		} else if (base == 16) {
+			Token error = make_error("Cannot use a decimal point in a hexadecimal number.");
+			error.start_column = column;
+			error.leftmost_column = column;
+			error.end_column = column + 1;
+			error.rightmost_column = column + 1;
+			push_error(error);
+			has_error = true;
+		} else {
+			Token error = make_error("Cannot use a decimal point in a binary number.");
+			error.start_column = column;
+			error.leftmost_column = column;
+			error.end_column = column + 1;
+			error.rightmost_column = column + 1;
+			push_error(error);
+			has_error = true;
+		}
+		if (!has_error) {
+			_advance();
 
 
-	int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE;
-	ERR_FAIL_COND_V(tk_rb[ofs].type != TK_NEWLINE, 0);
-	return tk_rb[ofs].constant.operator Vector2().x;
-}
+			// Consume decimal digits.
+			while (_is_digit(_peek()) || _peek() == '_') {
+				_advance();
+			}
+		}
+	}
+	if (base == 10) {
+		if (_peek() == 'e' || _peek() == 'E') {
+			has_exponent = true;
+			_advance();
+			if (_peek() == '+' || _peek() == '-') {
+				// Exponent sign.
+				_advance();
+			}
+			// Consume exponent digits.
+			while (_is_digit(_peek()) || _peek() == '_') {
+				_advance();
+			}
+		}
+	}
 
 
-int GDScriptTokenizerText::get_token_line_tab_indent(int p_offset) const {
-	ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, 0);
-	ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, 0);
+	// Detect extra decimal point.
+	if (!has_error && has_decimal && _peek() == '.' && _peek(1) != '.') {
+		Token error = make_error("Cannot use a decimal point twice in a number.");
+		error.start_column = column;
+		error.leftmost_column = column;
+		error.end_column = column + 1;
+		error.rightmost_column = column + 1;
+		push_error(error);
+		has_error = true;
+	} else if (_is_alphanumeric(_peek())) {
+		// Letter at the end of the number.
+		push_error("Invalid numeric notation.");
+	}
 
 
-	int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE;
-	ERR_FAIL_COND_V(tk_rb[ofs].type != TK_NEWLINE, 0);
-	return tk_rb[ofs].constant.operator Vector2().y;
+	// Create a string with the whole number.
+	int length = _current - _start;
+	String number = String(_start, length).replace("_", "");
+
+	// Convert to the appropriate literal type.
+	if (base == 16) {
+		int64_t value = number.hex_to_int();
+		return make_literal(value);
+	} else if (base == 2) {
+		int64_t value = number.bin_to_int();
+		return make_literal(value);
+	} else if (has_decimal || has_exponent) {
+		double value = number.to_double();
+		return make_literal(value);
+	} else {
+		int64_t value = number.to_int();
+		return make_literal(value);
+	}
 }
 }
 
 
-String GDScriptTokenizerText::get_token_error(int p_offset) const {
-	ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, String());
-	ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, String());
+GDScriptTokenizer::Token GDScriptTokenizer::string() {
+	enum StringType {
+		STRING_REGULAR,
+		STRING_NAME,
+		STRING_NODEPATH,
+	};
 
 
-	int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE;
-	ERR_FAIL_COND_V(tk_rb[ofs].type != TK_ERROR, String());
-	return tk_rb[ofs].constant;
-}
+	bool is_multiline = false;
+	StringType type = STRING_REGULAR;
 
 
-void GDScriptTokenizerText::advance(int p_amount) {
-	ERR_FAIL_COND(p_amount <= 0);
-	for (int i = 0; i < p_amount; i++) {
+	if (_peek(-1) == '&') {
+		type = STRING_NAME;
+		_advance();
+	} else if (_peek(-1) == '^') {
+		type = STRING_NODEPATH;
 		_advance();
 		_advance();
 	}
 	}
-}
-
-//////////////////////////////////////////////////////////////////////////////////////////////////////
 
 
-#define BYTECODE_VERSION 13
+	CharType quote_char = _peek(-1);
 
 
-Error GDScriptTokenizerBuffer::set_code_buffer(const Vector<uint8_t> &p_buffer) {
-	const uint8_t *buf = p_buffer.ptr();
-	int total_len = p_buffer.size();
-	ERR_FAIL_COND_V(p_buffer.size() < 24 || p_buffer[0] != 'G' || p_buffer[1] != 'D' || p_buffer[2] != 'S' || p_buffer[3] != 'C', ERR_INVALID_DATA);
-
-	int version = decode_uint32(&buf[4]);
-	ERR_FAIL_COND_V_MSG(version > BYTECODE_VERSION, ERR_INVALID_DATA, "Bytecode is too recent! Please use a newer engine version.");
-
-	int identifier_count = decode_uint32(&buf[8]);
-	int constant_count = decode_uint32(&buf[12]);
-	int line_count = decode_uint32(&buf[16]);
-	int token_count = decode_uint32(&buf[20]);
+	if (_peek() == quote_char && _peek(1) == quote_char) {
+		is_multiline = true;
+		// Consume all quotes.
+		_advance();
+		_advance();
+	}
 
 
-	const uint8_t *b = &buf[24];
-	total_len -= 24;
+	String result;
 
 
-	identifiers.resize(identifier_count);
-	for (int i = 0; i < identifier_count; i++) {
-		int len = decode_uint32(b);
-		ERR_FAIL_COND_V(len > total_len, ERR_INVALID_DATA);
-		b += 4;
-		Vector<uint8_t> cs;
-		cs.resize(len);
-		for (int j = 0; j < len; j++) {
-			cs.write[j] = b[j] ^ 0xb6;
+	for (;;) {
+		// Consume actual string.
+		if (_is_at_end()) {
+			return make_error("Unterminated string.");
 		}
 		}
 
 
-		cs.write[cs.size() - 1] = 0;
-		String s;
-		s.parse_utf8((const char *)cs.ptr());
-		b += len;
-		total_len -= len + 4;
-		identifiers.write[i] = s;
-	}
+		CharType ch = _peek();
 
 
-	constants.resize(constant_count);
-	for (int i = 0; i < constant_count; i++) {
-		Variant v;
-		int len;
-		// An object cannot be constant, never decode objects
-		Error err = decode_variant(v, b, total_len, &len, false);
-		if (err) {
-			return err;
-		}
-		b += len;
-		total_len -= len;
-		constants.write[i] = v;
-	}
+		if (ch == '\\') {
+			// Escape pattern.
+			_advance();
+			if (_is_at_end()) {
+				return make_error("Unterminated string.");
+			}
 
 
-	ERR_FAIL_COND_V(line_count * 8 > total_len, ERR_INVALID_DATA);
+			// Grab escape character.
+			CharType code = _peek();
+			_advance();
+			if (_is_at_end()) {
+				return make_error("Unterminated string.");
+			}
 
 
-	for (int i = 0; i < line_count; i++) {
-		uint32_t token = decode_uint32(b);
-		b += 4;
-		uint32_t linecol = decode_uint32(b);
-		b += 4;
+			CharType escaped = 0;
+			bool valid_escape = true;
 
 
-		lines.insert(token, linecol);
-		total_len -= 8;
-	}
+			switch (code) {
+				case 'a':
+					escaped = '\a';
+					break;
+				case 'b':
+					escaped = '\b';
+					break;
+				case 'f':
+					escaped = '\f';
+					break;
+				case 'n':
+					escaped = '\n';
+					break;
+				case 'r':
+					escaped = '\r';
+					break;
+				case 't':
+					escaped = '\t';
+					break;
+				case 'v':
+					escaped = '\v';
+					break;
+				case '\'':
+					escaped = '\'';
+					break;
+				case '\"':
+					escaped = '\"';
+					break;
+				case '\\':
+					escaped = '\\';
+					break;
+				case 'u':
+					// Hexadecimal sequence.
+					for (int i = 0; i < 4; i++) {
+						if (_is_at_end()) {
+							return make_error("Unterminated string.");
+						}
 
 
-	tokens.resize(token_count);
+						CharType digit = _peek();
+						CharType value = 0;
+						if (digit >= '0' && digit <= '9') {
+							value = digit - '0';
+						} else if (digit >= 'a' && digit <= 'f') {
+							value = digit - 'a';
+							value += 10;
+						} else if (digit >= 'A' && digit <= 'F') {
+							value = digit - 'A';
+							value += 10;
+						} else {
+							// Make error, but keep parsing the string.
+							Token error = make_error("Invalid hexadecimal digit in unicode escape sequence.");
+							error.start_column = column;
+							error.leftmost_column = error.start_column;
+							error.end_column = column + 1;
+							error.rightmost_column = error.end_column;
+							push_error(error);
+							valid_escape = false;
+							break;
+						}
 
 
-	for (int i = 0; i < token_count; i++) {
-		ERR_FAIL_COND_V(total_len < 1, ERR_INVALID_DATA);
+						escaped <<= 4;
+						escaped |= value;
 
 
-		if ((*b) & TOKEN_BYTE_MASK) { //little endian always
-			ERR_FAIL_COND_V(total_len < 4, ERR_INVALID_DATA);
+						_advance();
+					}
+					break;
+				case '\r':
+					if (_peek() != '\n') {
+						// Carriage return without newline in string. (???)
+						// Just add it to the string and keep going.
+						result += ch;
+						_advance();
+						break;
+					}
+					[[fallthrough]];
+				case '\n':
+					// Escaping newline.
+					newline(false);
+					valid_escape = false; // Don't add to the string.
+					break;
+				default:
+					Token error = make_error("Invalid escape in string.");
+					error.start_column = column - 2;
+					error.leftmost_column = error.start_column;
+					push_error(error);
+					valid_escape = false;
+					break;
+			}
 
 
-			tokens.write[i] = decode_uint32(b) & ~TOKEN_BYTE_MASK;
-			b += 4;
+			if (valid_escape) {
+				result += escaped;
+			}
+		} else if (ch == quote_char) {
+			_advance();
+			if (is_multiline) {
+				if (_peek() == quote_char && _peek(1) == quote_char) {
+					// Ended the multiline string. Consume all quotes.
+					_advance();
+					_advance();
+					break;
+				}
+			} else {
+				// Ended single-line string.
+				break;
+			}
 		} else {
 		} else {
-			tokens.write[i] = *b;
-			b += 1;
-			total_len--;
+			result += ch;
+			_advance();
+			if (ch == '\n') {
+				newline(false);
+			}
 		}
 		}
 	}
 	}
 
 
-	token = 0;
+	// Make the literal.
+	Variant string;
+	switch (type) {
+		case STRING_NAME:
+			string = StringName(result);
+			break;
+		case STRING_NODEPATH:
+			string = NodePath(result);
+			break;
+		case STRING_REGULAR:
+			string = result;
+			break;
+	}
 
 
-	return OK;
+	return make_literal(string);
 }
 }
 
 
-Vector<uint8_t> GDScriptTokenizerBuffer::parse_code_string(const String &p_code) {
-	Vector<uint8_t> buf;
+void GDScriptTokenizer::check_indent() {
+	ERR_FAIL_COND_MSG(column != 1, "Checking tokenizer indentation in the middle of a line.");
 
 
-	Map<StringName, int> identifier_map;
-	HashMap<Variant, int, VariantHasher, VariantComparator> constant_map;
-	Map<uint32_t, int> line_map;
-	Vector<uint32_t> token_array;
+	if (_is_at_end()) {
+		// Send dedents for every indent level.
+		pending_indents -= indent_level();
+		indent_stack.clear();
+		return;
+	}
 
 
-	GDScriptTokenizerText tt;
-	tt.set_code(p_code);
-	int line = -1;
+	for (;;) {
+		CharType current_indent_char = _peek();
+		int indent_count = 0;
 
 
-	while (true) {
-		if (tt.get_token_line() != line) {
-			line = tt.get_token_line();
-			line_map[line] = token_array.size();
+		if (current_indent_char != ' ' && current_indent_char != '\t' && current_indent_char != '\r' && current_indent_char != '\n') {
+			// First character of the line is not whitespace, so we clear all indentation levels.
+			// Unless we are in a continuation or in multiline mode (inside expression).
+			if (line_continuation || multiline_mode) {
+				return;
+			}
+			pending_indents -= indent_level();
+			indent_stack.clear();
+			return;
 		}
 		}
 
 
-		uint32_t token = tt.get_token();
-		switch (tt.get_token()) {
-			case TK_IDENTIFIER: {
-				StringName id = tt.get_token_identifier();
-				if (!identifier_map.has(id)) {
-					int idx = identifier_map.size();
-					identifier_map[id] = idx;
-				}
-				token |= identifier_map[id] << TOKEN_BITS;
-			} break;
-			case TK_CONSTANT: {
-				const Variant &c = tt.get_token_constant();
-				if (!constant_map.has(c)) {
-					int idx = constant_map.size();
-					constant_map[c] = idx;
-				}
-				token |= constant_map[c] << TOKEN_BITS;
-			} break;
-			case TK_BUILT_IN_TYPE: {
-				token |= tt.get_token_type() << TOKEN_BITS;
-			} break;
-			case TK_BUILT_IN_FUNC: {
-				token |= tt.get_token_built_in_func() << TOKEN_BITS;
-
-			} break;
-			case TK_NEWLINE: {
-				token |= tt.get_token_line_indent() << TOKEN_BITS;
-			} break;
-			case TK_ERROR: {
-				ERR_FAIL_V(Vector<uint8_t>());
-			} break;
-			default: {
+		if (_peek() == '\r') {
+			_advance();
+			if (_peek() != '\n') {
+				push_error("Stray carriage return character in source code.");
 			}
 			}
-		};
-
-		token_array.push_back(token);
-
-		if (tt.get_token() == TK_EOF) {
-			break;
 		}
 		}
-		tt.advance();
-	}
-
-	//reverse maps
-
-	Map<int, StringName> rev_identifier_map;
-	for (Map<StringName, int>::Element *E = identifier_map.front(); E; E = E->next()) {
-		rev_identifier_map[E->get()] = E->key();
-	}
+		if (_peek() == '\n') {
+			// Empty line, keep going.
+			_advance();
+			newline(false);
+			continue;
+		}
 
 
-	Map<int, Variant> rev_constant_map;
-	const Variant *K = nullptr;
-	while ((K = constant_map.next(K))) {
-		rev_constant_map[constant_map[*K]] = *K;
-	}
+		// Check indent level.
+		bool mixed = false;
+		while (!_is_at_end()) {
+			CharType space = _peek();
+			if (space == '\t') {
+				// Consider individual tab columns.
+				column += tab_size - 1;
+				indent_count += tab_size;
+			} else if (space == ' ') {
+				indent_count += 1;
+			} else {
+				break;
+			}
+			mixed = mixed || space != current_indent_char;
+			_advance();
+		}
 
 
-	Map<int, uint32_t> rev_line_map;
-	for (Map<uint32_t, int>::Element *E = line_map.front(); E; E = E->next()) {
-		rev_line_map[E->get()] = E->key();
-	}
+		if (mixed) {
+			Token error = make_error("Mixed use of tabs and spaces for indentation.");
+			error.start_line = line;
+			error.start_column = 1;
+			error.leftmost_column = 1;
+			error.rightmost_column = column;
+			push_error(error);
+		}
 
 
-	//save header
-	buf.resize(24);
-	buf.write[0] = 'G';
-	buf.write[1] = 'D';
-	buf.write[2] = 'S';
-	buf.write[3] = 'C';
-	encode_uint32(BYTECODE_VERSION, &buf.write[4]);
-	encode_uint32(identifier_map.size(), &buf.write[8]);
-	encode_uint32(constant_map.size(), &buf.write[12]);
-	encode_uint32(line_map.size(), &buf.write[16]);
-	encode_uint32(token_array.size(), &buf.write[20]);
-
-	//save identifiers
-
-	for (Map<int, StringName>::Element *E = rev_identifier_map.front(); E; E = E->next()) {
-		CharString cs = String(E->get()).utf8();
-		int len = cs.length() + 1;
-		int extra = 4 - (len % 4);
-		if (extra == 4) {
-			extra = 0;
+		if (_is_at_end()) {
+			// Reached the end with an empty line, so just dedent as much as needed.
+			pending_indents -= indent_level();
+			indent_stack.clear();
+			return;
 		}
 		}
 
 
-		uint8_t ibuf[4];
-		encode_uint32(len + extra, ibuf);
-		for (int i = 0; i < 4; i++) {
-			buf.push_back(ibuf[i]);
+		if (_peek() == '\r') {
+			_advance();
+			if (_peek() != '\n') {
+				push_error("Stray carriage return character in source code.");
+			}
 		}
 		}
-		for (int i = 0; i < len; i++) {
-			buf.push_back(cs[i] ^ 0xb6);
+		if (_peek() == '\n') {
+			// Empty line, keep going.
+			_advance();
+			newline(false);
+			continue;
 		}
 		}
-		for (int i = 0; i < extra; i++) {
-			buf.push_back(0 ^ 0xb6);
+		if (_peek() == '#') {
+			// Comment. Advance to the next line.
+			while (_peek() != '\n' && !_is_at_end()) {
+				_advance();
+			}
+			if (_is_at_end()) {
+				// Reached the end with an empty line, so just dedent as much as needed.
+				pending_indents -= indent_level();
+				indent_stack.clear();
+				return;
+			}
+			_advance(); // Consume '\n'.
+			newline(false);
+			continue;
 		}
 		}
-	}
 
 
-	for (Map<int, Variant>::Element *E = rev_constant_map.front(); E; E = E->next()) {
-		int len;
-		// Objects cannot be constant, never encode objects
-		Error err = encode_variant(E->get(), nullptr, len, false);
-		ERR_FAIL_COND_V_MSG(err != OK, Vector<uint8_t>(), "Error when trying to encode Variant.");
-		int pos = buf.size();
-		buf.resize(pos + len);
-		encode_variant(E->get(), &buf.write[pos], len, false);
-	}
+		if (line_continuation || multiline_mode) {
+			// We cleared up all the whitespace at the beginning of the line.
+			// But if this is a continuation or multiline mode and we don't want any indentation change.
+			return;
+		}
 
 
-	for (Map<int, uint32_t>::Element *E = rev_line_map.front(); E; E = E->next()) {
-		uint8_t ibuf[8];
-		encode_uint32(E->key(), &ibuf[0]);
-		encode_uint32(E->get(), &ibuf[4]);
-		for (int i = 0; i < 8; i++) {
-			buf.push_back(ibuf[i]);
+		// Check if indentation character is consistent.
+		if (indent_char == '\0') {
+			// First time indenting, choose character now.
+			indent_char = current_indent_char;
+		} else if (current_indent_char != indent_char) {
+			Token error = make_error(vformat("Used \"%c\" for indentation instead \"%c\" as used before in the file.", String(&current_indent_char, 1).c_escape(), String(&indent_char, 1).c_escape()));
+			error.start_line = line;
+			error.start_column = 1;
+			error.leftmost_column = 1;
+			error.rightmost_column = column;
+			push_error(error);
 		}
 		}
-	}
 
 
-	for (int i = 0; i < token_array.size(); i++) {
-		uint32_t token = token_array[i];
+		// Now we can do actual indentation changes.
 
 
-		if (token & ~TOKEN_MASK) {
-			uint8_t buf4[4];
-			encode_uint32(token_array[i] | TOKEN_BYTE_MASK, &buf4[0]);
-			for (int j = 0; j < 4; j++) {
-				buf.push_back(buf4[j]);
-			}
+		// Check if indent or dedent.
+		int previous_indent = 0;
+		if (indent_level() > 0) {
+			previous_indent = indent_stack.back()->get();
+		}
+		if (indent_count == previous_indent) {
+			// No change in indentation.
+			return;
+		}
+		if (indent_count > previous_indent) {
+			// Indentation increased.
+			indent_stack.push_back(indent_count);
+			pending_indents++;
 		} else {
 		} else {
-			buf.push_back(token);
+			// Indentation decreased (dedent).
+			if (indent_level() == 0) {
+				push_error("Tokenizer bug: trying to dedent without previous indent.");
+				return;
+			}
+			while (indent_level() > 0 && indent_stack.back()->get() > indent_count) {
+				indent_stack.pop_back();
+				pending_indents--;
+			}
+			if ((indent_level() > 0 && indent_stack.back()->get() != indent_count) || (indent_level() == 0 && indent_count != 0)) {
+				// Mismatched indentation alignment.
+				Token error = make_error("Unindent doesn't match the previous indentation level.");
+				error.start_line = line;
+				error.start_column = 1;
+				error.leftmost_column = 1;
+				error.end_column = column + 1;
+				error.rightmost_column = column + 1;
+				push_error(error);
+				// Still, we'll be lenient and keep going, so keep this level in the stack.
+				indent_stack.push_back(indent_count);
+			}
 		}
 		}
+		break; // Get out of the loop in any case.
 	}
 	}
-
-	return buf;
 }
 }
 
 
-GDScriptTokenizerBuffer::Token GDScriptTokenizerBuffer::get_token(int p_offset) const {
-	int offset = token + p_offset;
-
-	if (offset < 0 || offset >= tokens.size()) {
-		return TK_EOF;
+void GDScriptTokenizer::_skip_whitespace() {
+	if (pending_indents != 0) {
+		// Still have some indent/dedent tokens to give.
+		return;
 	}
 	}
 
 
-	return GDScriptTokenizerBuffer::Token(tokens[offset] & TOKEN_MASK);
-}
-
-StringName GDScriptTokenizerBuffer::get_token_identifier(int p_offset) const {
-	int offset = token + p_offset;
+	bool is_bol = column == 1; // Beginning of line.
 
 
-	ERR_FAIL_INDEX_V(offset, tokens.size(), StringName());
-	uint32_t identifier = tokens[offset] >> TOKEN_BITS;
-	ERR_FAIL_UNSIGNED_INDEX_V(identifier, (uint32_t)identifiers.size(), StringName());
+	if (is_bol) {
+		check_indent();
+		return;
+	}
 
 
-	return identifiers[identifier];
+	for (;;) {
+		CharType c = _peek();
+		switch (c) {
+			case ' ':
+				_advance();
+				break;
+			case '\t':
+				_advance();
+				// Consider individual tab columns.
+				column += tab_size - 1;
+				break;
+			case '\r':
+				_advance(); // Consume either way.
+				if (_peek() != '\n') {
+					push_error("Stray carriage return character in source code.");
+					return;
+				}
+				break;
+			case '\n':
+				_advance();
+				newline(!is_bol); // Don't create new line token if line is empty.
+				check_indent();
+				break;
+			case '#':
+				// Comment.
+				while (_peek() != '\n' && !_is_at_end()) {
+					_advance();
+				}
+				if (_is_at_end()) {
+					return;
+				}
+				_advance(); // Consume '\n'
+				newline(!is_bol);
+				check_indent();
+				break;
+			default:
+				return;
+		}
+	}
 }
 }
 
 
-GDScriptFunctions::Function GDScriptTokenizerBuffer::get_token_built_in_func(int p_offset) const {
-	int offset = token + p_offset;
-	ERR_FAIL_INDEX_V(offset, tokens.size(), GDScriptFunctions::FUNC_MAX);
-	return GDScriptFunctions::Function(tokens[offset] >> TOKEN_BITS);
-}
+GDScriptTokenizer::Token GDScriptTokenizer::scan() {
+	if (has_error()) {
+		return pop_error();
+	}
 
 
-Variant::Type GDScriptTokenizerBuffer::get_token_type(int p_offset) const {
-	int offset = token + p_offset;
-	ERR_FAIL_INDEX_V(offset, tokens.size(), Variant::NIL);
+	_skip_whitespace();
 
 
-	return Variant::Type(tokens[offset] >> TOKEN_BITS);
-}
+	if (pending_newline) {
+		pending_newline = false;
+		if (!multiline_mode) {
+			// Don't return newline tokens on multine mode.
+			return last_newline;
+		}
+	}
 
 
-int GDScriptTokenizerBuffer::get_token_line(int p_offset) const {
-	int offset = token + p_offset;
-	int pos = lines.find_nearest(offset);
+	// Check for potential errors after skipping whitespace().
+	if (has_error()) {
+		return pop_error();
+	}
 
 
-	if (pos < 0) {
-		return -1;
+	_start = _current;
+	start_line = line;
+	start_column = column;
+	leftmost_column = column;
+	rightmost_column = column;
+
+	if (pending_indents != 0) {
+		// Adjust position for indent.
+		_start -= start_column - 1;
+		start_column = 1;
+		leftmost_column = 1;
+		if (pending_indents > 0) {
+			// Indents.
+			pending_indents--;
+			return make_token(Token::INDENT);
+		} else {
+			// Dedents.
+			pending_indents++;
+			Token dedent = make_token(Token::DEDENT);
+			dedent.end_column += 1;
+			dedent.rightmost_column += 1;
+			return dedent;
+		}
 	}
 	}
-	if (pos >= lines.size()) {
-		pos = lines.size() - 1;
+
+	if (_is_at_end()) {
+		return make_token(Token::TK_EOF);
 	}
 	}
 
 
-	uint32_t l = lines.getv(pos);
-	return l & TOKEN_LINE_MASK;
-}
+	const CharType c = _advance();
 
 
-int GDScriptTokenizerBuffer::get_token_column(int p_offset) const {
-	int offset = token + p_offset;
-	int pos = lines.find_nearest(offset);
-	if (pos < 0) {
-		return -1;
-	}
-	if (pos >= lines.size()) {
-		pos = lines.size() - 1;
+	if (c == '\\') {
+		// Line continuation with backslash.
+		if (_peek() == '\r') {
+			if (_peek(1) != '\n') {
+				return make_error("Unexpected carriage return character.");
+			}
+			_advance();
+		}
+		if (_peek() != '\n') {
+			return make_error("Expected new line after \"\\\".");
+		}
+		_advance();
+		newline(false);
+		line_continuation = true;
+		return scan(); // Recurse to get next token.
 	}
 	}
 
 
-	uint32_t l = lines.getv(pos);
-	return l >> TOKEN_LINE_BITS;
-}
+	line_continuation = false;
 
 
-int GDScriptTokenizerBuffer::get_token_line_indent(int p_offset) const {
-	int offset = token + p_offset;
-	ERR_FAIL_INDEX_V(offset, tokens.size(), 0);
-	return tokens[offset] >> TOKEN_BITS;
-}
+	if (_is_digit(c)) {
+		return number();
+	} else if (_is_alphanumeric(c)) {
+		return potential_identifier();
+	}
 
 
-const Variant &GDScriptTokenizerBuffer::get_token_constant(int p_offset) const {
-	int offset = token + p_offset;
-	ERR_FAIL_INDEX_V(offset, tokens.size(), nil);
-	uint32_t constant = tokens[offset] >> TOKEN_BITS;
-	ERR_FAIL_UNSIGNED_INDEX_V(constant, (uint32_t)constants.size(), nil);
-	return constants[constant];
-}
+	switch (c) {
+		// String literals.
+		case '"':
+		case '\'':
+			return string();
+
+		// Annotation.
+		case '@':
+			return annotation();
+
+		// Single characters.
+		case '~':
+			return make_token(Token::TILDE);
+		case ',':
+			return make_token(Token::COMMA);
+		case ':':
+			return make_token(Token::COLON);
+		case ';':
+			return make_token(Token::SEMICOLON);
+		case '$':
+			return make_token(Token::DOLLAR);
+		case '?':
+			return make_token(Token::QUESTION_MARK);
+		case '`':
+			return make_token(Token::BACKTICK);
+
+		// Parens.
+		case '(':
+			push_paren('(');
+			return make_token(Token::PARENTHESIS_OPEN);
+		case '[':
+			push_paren('[');
+			return make_token(Token::BRACKET_OPEN);
+		case '{':
+			push_paren('{');
+			return make_token(Token::BRACE_OPEN);
+		case ')':
+			if (!pop_paren('(')) {
+				return make_paren_error(c);
+			}
+			return make_token(Token::PARENTHESIS_CLOSE);
+		case ']':
+			if (!pop_paren('[')) {
+				return make_paren_error(c);
+			}
+			return make_token(Token::BRACKET_CLOSE);
+		case '}':
+			if (!pop_paren('{')) {
+				return make_paren_error(c);
+			}
+			return make_token(Token::BRACE_CLOSE);
+
+		// Double characters.
+		case '!':
+			if (_peek() == '=') {
+				_advance();
+				return make_token(Token::BANG_EQUAL);
+			} else {
+				return make_token(Token::BANG);
+			}
+		case '.':
+			if (_peek() == '.') {
+				_advance();
+				return make_token(Token::PERIOD_PERIOD);
+			} else if (_is_digit(_peek())) {
+				// Number starting with '.'.
+				return number();
+			} else {
+				return make_token(Token::PERIOD);
+			}
+		case '+':
+			if (_peek() == '=') {
+				_advance();
+				return make_token(Token::PLUS_EQUAL);
+			} else {
+				return make_token(Token::PLUS);
+			}
+		case '-':
+			if (_peek() == '=') {
+				_advance();
+				return make_token(Token::MINUS_EQUAL);
+			} else if (_peek() == '>') {
+				_advance();
+				return make_token(Token::FORWARD_ARROW);
+			} else {
+				return make_token(Token::MINUS);
+			}
+		case '*':
+			if (_peek() == '=') {
+				_advance();
+				return make_token(Token::STAR_EQUAL);
+			} else {
+				return make_token(Token::STAR);
+			}
+		case '/':
+			if (_peek() == '=') {
+				_advance();
+				return make_token(Token::SLASH_EQUAL);
+			} else {
+				return make_token(Token::SLASH);
+			}
+		case '%':
+			if (_peek() == '=') {
+				_advance();
+				return make_token(Token::PERCENT_EQUAL);
+			} else {
+				return make_token(Token::PERCENT);
+			}
+		case '^':
+			if (_peek() == '=') {
+				_advance();
+				return make_token(Token::CARET_EQUAL);
+			} else if (_peek() == '"' || _peek() == '\'') {
+				// Node path
+				return string();
+			} else {
+				return make_token(Token::CARET);
+			}
+		case '&':
+			if (_peek() == '&') {
+				_advance();
+				return make_token(Token::AMPERSAND_AMPERSAND);
+			} else if (_peek() == '=') {
+				_advance();
+				return make_token(Token::AMPERSAND_EQUAL);
+			} else if (_peek() == '"' || _peek() == '\'') {
+				// String Name
+				return string();
+			} else {
+				return make_token(Token::AMPERSAND);
+			}
+		case '|':
+			if (_peek() == '|') {
+				_advance();
+				return make_token(Token::PIPE_PIPE);
+			} else if (_peek() == '=') {
+				_advance();
+				return make_token(Token::PIPE_EQUAL);
+			} else {
+				return make_token(Token::PIPE);
+			}
 
 
-String GDScriptTokenizerBuffer::get_token_error(int p_offset) const {
-	ERR_FAIL_V(String());
-}
+		// Potential VCS conflict markers.
+		case '=':
+			if (_peek() == '=') {
+				return check_vcs_marker('=', Token::EQUAL_EQUAL);
+			} else {
+				return make_token(Token::EQUAL);
+			}
+		case '<':
+			if (_peek() == '=') {
+				_advance();
+				return make_token(Token::LESS_EQUAL);
+			} else if (_peek() == '<') {
+				if (_peek(1) == '=') {
+					_advance();
+					_advance(); // Advance both '<' and '='
+					return make_token(Token::LESS_LESS_EQUAL);
+				} else {
+					return check_vcs_marker('<', Token::LESS_LESS);
+				}
+			} else {
+				return make_token(Token::LESS);
+			}
+		case '>':
+			if (_peek() == '=') {
+				_advance();
+				return make_token(Token::GREATER_EQUAL);
+			} else if (_peek() == '>') {
+				if (_peek(1) == '=') {
+					_advance();
+					_advance(); // Advance both '>' and '='
+					return make_token(Token::GREATER_GREATER_EQUAL);
+				} else {
+					return check_vcs_marker('>', Token::GREATER_GREATER);
+				}
+			} else {
+				return make_token(Token::GREATER);
+			}
 
 
-void GDScriptTokenizerBuffer::advance(int p_amount) {
-	ERR_FAIL_INDEX(p_amount + token, tokens.size());
-	token += p_amount;
+		default:
+			return make_error("Unknown character.");
+	}
 }
 }
 
 
-GDScriptTokenizerBuffer::GDScriptTokenizerBuffer() {
-	token = 0;
+GDScriptTokenizer::GDScriptTokenizer() {
+#ifdef TOOLS_ENABLED
+	if (EditorSettings::get_singleton()) {
+		tab_size = EditorSettings::get_singleton()->get_setting("text_editor/indent/size");
+	}
+#endif // TOOLS_ENABLED
 }
 }

+ 187 - 246
modules/gdscript/gdscript_tokenizer.h

@@ -31,268 +31,209 @@
 #ifndef GDSCRIPT_TOKENIZER_H
 #ifndef GDSCRIPT_TOKENIZER_H
 #define GDSCRIPT_TOKENIZER_H
 #define GDSCRIPT_TOKENIZER_H
 
 
-#include "core/pair.h"
+#include "core/list.h"
 #include "core/set.h"
 #include "core/set.h"
-#include "core/string_name.h"
-#include "core/ustring.h"
 #include "core/variant.h"
 #include "core/variant.h"
-#include "core/vmap.h"
-#include "gdscript_functions.h"
+#include "core/vector.h"
 
 
 class GDScriptTokenizer {
 class GDScriptTokenizer {
 public:
 public:
-	enum Token {
-
-		TK_EMPTY,
-		TK_IDENTIFIER,
-		TK_CONSTANT,
-		TK_SELF,
-		TK_BUILT_IN_TYPE,
-		TK_BUILT_IN_FUNC,
-		TK_OP_IN,
-		TK_OP_EQUAL,
-		TK_OP_NOT_EQUAL,
-		TK_OP_LESS,
-		TK_OP_LESS_EQUAL,
-		TK_OP_GREATER,
-		TK_OP_GREATER_EQUAL,
-		TK_OP_AND,
-		TK_OP_OR,
-		TK_OP_NOT,
-		TK_OP_ADD,
-		TK_OP_SUB,
-		TK_OP_MUL,
-		TK_OP_DIV,
-		TK_OP_MOD,
-		TK_OP_SHIFT_LEFT,
-		TK_OP_SHIFT_RIGHT,
-		TK_OP_ASSIGN,
-		TK_OP_ASSIGN_ADD,
-		TK_OP_ASSIGN_SUB,
-		TK_OP_ASSIGN_MUL,
-		TK_OP_ASSIGN_DIV,
-		TK_OP_ASSIGN_MOD,
-		TK_OP_ASSIGN_SHIFT_LEFT,
-		TK_OP_ASSIGN_SHIFT_RIGHT,
-		TK_OP_ASSIGN_BIT_AND,
-		TK_OP_ASSIGN_BIT_OR,
-		TK_OP_ASSIGN_BIT_XOR,
-		TK_OP_BIT_AND,
-		TK_OP_BIT_OR,
-		TK_OP_BIT_XOR,
-		TK_OP_BIT_INVERT,
-		//TK_OP_PLUS_PLUS,
-		//TK_OP_MINUS_MINUS,
-		TK_CF_IF,
-		TK_CF_ELIF,
-		TK_CF_ELSE,
-		TK_CF_FOR,
-		TK_CF_WHILE,
-		TK_CF_BREAK,
-		TK_CF_CONTINUE,
-		TK_CF_PASS,
-		TK_CF_RETURN,
-		TK_CF_MATCH,
-		TK_PR_FUNCTION,
-		TK_PR_CLASS,
-		TK_PR_CLASS_NAME,
-		TK_PR_EXTENDS,
-		TK_PR_IS,
-		TK_PR_ONREADY,
-		TK_PR_TOOL,
-		TK_PR_STATIC,
-		TK_PR_EXPORT,
-		TK_PR_SETGET,
-		TK_PR_CONST,
-		TK_PR_VAR,
-		TK_PR_AS,
-		TK_PR_VOID,
-		TK_PR_ENUM,
-		TK_PR_PRELOAD,
-		TK_PR_ASSERT,
-		TK_PR_YIELD,
-		TK_PR_SIGNAL,
-		TK_PR_BREAKPOINT,
-		TK_PR_REMOTE,
-		TK_PR_MASTER,
-		TK_PR_PUPPET,
-		TK_PR_REMOTESYNC,
-		TK_PR_MASTERSYNC,
-		TK_PR_PUPPETSYNC,
-		TK_BRACKET_OPEN,
-		TK_BRACKET_CLOSE,
-		TK_CURLY_BRACKET_OPEN,
-		TK_CURLY_BRACKET_CLOSE,
-		TK_PARENTHESIS_OPEN,
-		TK_PARENTHESIS_CLOSE,
-		TK_COMMA,
-		TK_SEMICOLON,
-		TK_PERIOD,
-		TK_QUESTION_MARK,
-		TK_COLON,
-		TK_DOLLAR,
-		TK_FORWARD_ARROW,
-		TK_NEWLINE,
-		TK_CONST_PI,
-		TK_CONST_TAU,
-		TK_WILDCARD,
-		TK_CONST_INF,
-		TK_CONST_NAN,
-		TK_ERROR,
-		TK_EOF,
-		TK_CURSOR, //used for code completion
-		TK_MAX
-	};
-
-protected:
-	enum StringMode {
-		STRING_SINGLE_QUOTE,
-		STRING_DOUBLE_QUOTE,
-		STRING_MULTILINE
-	};
-
-	static const char *token_names[TK_MAX];
-
-public:
-	static const char *get_token_name(Token p_token);
-
-	bool is_token_literal(int p_offset = 0, bool variable_safe = false) const;
-	StringName get_token_literal(int p_offset = 0) const;
-
-	virtual const Variant &get_token_constant(int p_offset = 0) const = 0;
-	virtual Token get_token(int p_offset = 0) const = 0;
-	virtual StringName get_token_identifier(int p_offset = 0) const = 0;
-	virtual GDScriptFunctions::Function get_token_built_in_func(int p_offset = 0) const = 0;
-	virtual Variant::Type get_token_type(int p_offset = 0) const = 0;
-	virtual int get_token_line(int p_offset = 0) const = 0;
-	virtual int get_token_column(int p_offset = 0) const = 0;
-	virtual int get_token_line_indent(int p_offset = 0) const = 0;
-	virtual int get_token_line_tab_indent(int p_offset = 0) const = 0;
-	virtual String get_token_error(int p_offset = 0) const = 0;
-	virtual void advance(int p_amount = 1) = 0;
-#ifdef DEBUG_ENABLED
-	virtual const Vector<Pair<int, String>> &get_warning_skips() const = 0;
-	virtual const Set<String> &get_warning_global_skips() const = 0;
-	virtual bool is_ignoring_warnings() const = 0;
-#endif // DEBUG_ENABLED
+	struct Token {
+		enum Type {
+			EMPTY,
+			// Basic
+			ANNOTATION,
+			IDENTIFIER,
+			LITERAL,
+			// Comparison
+			LESS,
+			LESS_EQUAL,
+			GREATER,
+			GREATER_EQUAL,
+			EQUAL_EQUAL,
+			BANG_EQUAL,
+			// Logical
+			AND,
+			OR,
+			NOT,
+			AMPERSAND_AMPERSAND,
+			PIPE_PIPE,
+			BANG,
+			// Bitwise
+			AMPERSAND,
+			PIPE,
+			TILDE,
+			CARET,
+			LESS_LESS,
+			GREATER_GREATER,
+			// Math
+			PLUS,
+			MINUS,
+			STAR,
+			SLASH,
+			PERCENT,
+			// Assignment
+			EQUAL,
+			PLUS_EQUAL,
+			MINUS_EQUAL,
+			STAR_EQUAL,
+			SLASH_EQUAL,
+			PERCENT_EQUAL,
+			LESS_LESS_EQUAL,
+			GREATER_GREATER_EQUAL,
+			AMPERSAND_EQUAL,
+			PIPE_EQUAL,
+			CARET_EQUAL,
+			// Control flow
+			IF,
+			ELIF,
+			ELSE,
+			FOR,
+			WHILE,
+			BREAK,
+			CONTINUE,
+			PASS,
+			RETURN,
+			MATCH,
+			// Keywords
+			AS,
+			ASSERT,
+			AWAIT,
+			BREAKPOINT,
+			CLASS,
+			CLASS_NAME,
+			CONST,
+			ENUM,
+			EXTENDS,
+			FUNC,
+			IN,
+			IS,
+			NAMESPACE,
+			PRELOAD,
+			SELF,
+			SIGNAL,
+			STATIC,
+			SUPER,
+			VAR,
+			VOID,
+			YIELD,
+			// Punctuation
+			BRACKET_OPEN,
+			BRACKET_CLOSE,
+			BRACE_OPEN,
+			BRACE_CLOSE,
+			PARENTHESIS_OPEN,
+			PARENTHESIS_CLOSE,
+			COMMA,
+			SEMICOLON,
+			PERIOD,
+			PERIOD_PERIOD,
+			COLON,
+			DOLLAR,
+			FORWARD_ARROW,
+			UNDERSCORE,
+			// Whitespace
+			NEWLINE,
+			INDENT,
+			DEDENT,
+			// Constants
+			CONST_PI,
+			CONST_TAU,
+			CONST_INF,
+			CONST_NAN,
+			// Error message improvement
+			VCS_CONFLICT_MARKER,
+			BACKTICK,
+			QUESTION_MARK,
+			// Special
+			ERROR,
+			TK_EOF, // "EOF" is reserved
+			TK_MAX
+		};
 
 
-	virtual ~GDScriptTokenizer() {}
-};
+		Type type = EMPTY;
+		Variant literal;
+		int start_line = 0, end_line = 0, start_column = 0, end_column = 0;
+		int leftmost_column = 0, rightmost_column = 0; // Column span for multiline tokens.
 
 
-class GDScriptTokenizerText : public GDScriptTokenizer {
-	enum {
-		MAX_LOOKAHEAD = 4,
-		TK_RB_SIZE = MAX_LOOKAHEAD * 2 + 1
+		const char *get_name() const;
+		// TODO: Allow some keywords as identifiers?
+		bool is_identifier() const { return type == IDENTIFIER; }
+		StringName get_identifier() const { return literal; }
 
 
-	};
+		Token(Type p_type) {
+			type = p_type;
+		}
 
 
-	struct TokenData {
-		Token type;
-		StringName identifier; //for identifier types
-		Variant constant; //for constant types
-		union {
-			Variant::Type vtype; //for type types
-			GDScriptFunctions::Function func; //function for built in functions
-			int warning_code; //for warning skip
-		};
-		int line, col;
-		TokenData() {
-			type = TK_EMPTY;
-			line = col = 0;
-			vtype = Variant::NIL;
+		Token() {
+			type = EMPTY;
 		}
 		}
 	};
 	};
 
 
-	void _make_token(Token p_type);
-	void _make_newline(int p_indentation = 0, int p_tabs = 0);
-	void _make_identifier(const StringName &p_identifier);
-	void _make_built_in_func(GDScriptFunctions::Function p_func);
-	void _make_constant(const Variant &p_constant);
-	void _make_type(const Variant::Type &p_type);
-	void _make_error(const String &p_error);
-
-	String code;
-	int len;
-	int code_pos;
-	const CharType *_code;
-	int line;
-	int column;
-	TokenData tk_rb[TK_RB_SIZE * 2 + 1];
-	int tk_rb_pos;
-	String last_error;
-	bool error_flag;
-
-#ifdef DEBUG_ENABLED
-	Vector<Pair<int, String>> warning_skips;
-	Set<String> warning_global_skips;
-	bool ignore_warnings;
-#endif // DEBUG_ENABLED
-
-	void _advance();
+private:
+	String source;
+	const CharType *_source = nullptr;
+	const CharType *_current = nullptr;
+	int line = 0, column = 0;
+	int cursor_line = 0, cursor_column = 0;
+	int tab_size = 4;
+
+	// Keep track of multichar tokens.
+	const CharType *_start = nullptr;
+	int start_line = 0, start_column = 0;
+	int leftmost_column = 0, rightmost_column = 0;
+
+	// Info cache.
+	bool line_continuation = false; // Whether this line is a continuation of the previous, like when using '\'.
+	bool multiline_mode = false;
+	List<Token> error_stack;
+	bool pending_newline = false;
+	Token last_newline;
+	int pending_indents = 0;
+	List<int> indent_stack;
+	List<CharType> paren_stack;
+	CharType indent_char = '\0';
+	int position = 0;
+	int length = 0;
+
+	_FORCE_INLINE_ bool _is_at_end() { return position >= length; }
+	_FORCE_INLINE_ CharType _peek(int p_offset = 0) { return position + p_offset >= 0 && position + p_offset < length ? _current[p_offset] : '\0'; }
+	int indent_level() const { return indent_stack.size(); }
+	bool has_error() const { return !error_stack.empty(); }
+	Token pop_error();
+	CharType _advance();
+	void _skip_whitespace();
+	void check_indent();
+
+	Token make_error(const String &p_message);
+	void push_error(const String &p_message);
+	void push_error(const Token &p_error);
+	Token make_paren_error(CharType p_paren);
+	Token make_token(Token::Type p_type) const;
+	Token make_literal(const Variant &p_literal) const;
+	Token make_identifier(const StringName &p_identifier) const;
+	Token check_vcs_marker(CharType p_test, Token::Type p_double_type);
+	void push_paren(CharType p_char);
+	bool pop_paren(CharType p_expected);
+
+	void newline(bool p_make_token);
+	Token number();
+	Token potential_identifier();
+	Token string();
+	Token annotation();
 
 
 public:
 public:
-	void set_code(const String &p_code);
-	virtual Token get_token(int p_offset = 0) const;
-	virtual StringName get_token_identifier(int p_offset = 0) const;
-	virtual GDScriptFunctions::Function get_token_built_in_func(int p_offset = 0) const;
-	virtual Variant::Type get_token_type(int p_offset = 0) const;
-	virtual int get_token_line(int p_offset = 0) const;
-	virtual int get_token_column(int p_offset = 0) const;
-	virtual int get_token_line_indent(int p_offset = 0) const;
-	virtual int get_token_line_tab_indent(int p_offset = 0) const;
-	virtual const Variant &get_token_constant(int p_offset = 0) const;
-	virtual String get_token_error(int p_offset = 0) const;
-	virtual void advance(int p_amount = 1);
-#ifdef DEBUG_ENABLED
-	virtual const Vector<Pair<int, String>> &get_warning_skips() const { return warning_skips; }
-	virtual const Set<String> &get_warning_global_skips() const { return warning_global_skips; }
-	virtual bool is_ignoring_warnings() const { return ignore_warnings; }
-#endif // DEBUG_ENABLED
-};
+	Token scan();
 
 
-class GDScriptTokenizerBuffer : public GDScriptTokenizer {
-	enum {
+	void set_source_code(const String &p_source_code);
 
 
-		TOKEN_BYTE_MASK = 0x80,
-		TOKEN_BITS = 8,
-		TOKEN_MASK = (1 << TOKEN_BITS) - 1,
-		TOKEN_LINE_BITS = 24,
-		TOKEN_LINE_MASK = (1 << TOKEN_LINE_BITS) - 1,
-	};
+	int get_cursor_line() const;
+	int get_cursor_column() const;
+	void set_cursor_position(int p_line, int p_column);
+	void set_multiline_mode(bool p_state);
+	static String get_token_name(Token::Type p_token_type);
 
 
-	Vector<StringName> identifiers;
-	Vector<Variant> constants;
-	VMap<uint32_t, uint32_t> lines;
-	Vector<uint32_t> tokens;
-	Variant nil;
-	int token;
-
-public:
-	Error set_code_buffer(const Vector<uint8_t> &p_buffer);
-	static Vector<uint8_t> parse_code_string(const String &p_code);
-	virtual Token get_token(int p_offset = 0) const;
-	virtual StringName get_token_identifier(int p_offset = 0) const;
-	virtual GDScriptFunctions::Function get_token_built_in_func(int p_offset = 0) const;
-	virtual Variant::Type get_token_type(int p_offset = 0) const;
-	virtual int get_token_line(int p_offset = 0) const;
-	virtual int get_token_column(int p_offset = 0) const;
-	virtual int get_token_line_indent(int p_offset = 0) const;
-	virtual int get_token_line_tab_indent(int p_offset = 0) const { return 0; }
-	virtual const Variant &get_token_constant(int p_offset = 0) const;
-	virtual String get_token_error(int p_offset = 0) const;
-	virtual void advance(int p_amount = 1);
-#ifdef DEBUG_ENABLED
-	virtual const Vector<Pair<int, String>> &get_warning_skips() const {
-		static Vector<Pair<int, String>> v;
-		return v;
-	}
-	virtual const Set<String> &get_warning_global_skips() const {
-		static Set<String> s;
-		return s;
-	}
-	virtual bool is_ignoring_warnings() const { return true; }
-#endif // DEBUG_ENABLED
-	GDScriptTokenizerBuffer();
+	GDScriptTokenizer();
 };
 };
 
 
-#endif // GDSCRIPT_TOKENIZER_H
+#endif

+ 201 - 0
modules/gdscript/gdscript_warning.cpp

@@ -0,0 +1,201 @@
+/*************************************************************************/
+/*  gdscript_warning.cpp                                                 */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#include "gdscript_warning.h"
+
+#ifdef DEBUG_ENABLED
+
+String GDScriptWarning::get_message() const {
+#define CHECK_SYMBOLS(m_amount) ERR_FAIL_COND_V(symbols.size() < m_amount, String());
+
+	switch (code) {
+		case UNASSIGNED_VARIABLE_OP_ASSIGN: {
+			CHECK_SYMBOLS(1);
+			return "Using assignment with operation but the variable '" + symbols[0] + "' was not previously assigned a value.";
+		} break;
+		case UNASSIGNED_VARIABLE: {
+			CHECK_SYMBOLS(1);
+			return "The variable '" + symbols[0] + "' was used but never assigned a value.";
+		} break;
+		case UNUSED_VARIABLE: {
+			CHECK_SYMBOLS(1);
+			return "The local variable '" + symbols[0] + "' is declared but never used in the block. If this is intended, prefix it with an underscore: '_" + symbols[0] + "'";
+		} break;
+		case SHADOWED_VARIABLE: {
+			CHECK_SYMBOLS(2);
+			return "The local variable '" + symbols[0] + "' is shadowing an already-defined variable at line " + symbols[1] + ".";
+		} break;
+		case UNUSED_CLASS_VARIABLE: {
+			CHECK_SYMBOLS(1);
+			return "The class variable '" + symbols[0] + "' is declared but never used in the script.";
+		} break;
+		case UNUSED_ARGUMENT: {
+			CHECK_SYMBOLS(2);
+			return "The argument '" + symbols[1] + "' is never used in the function '" + symbols[0] + "'. If this is intended, prefix it with an underscore: '_" + symbols[1] + "'";
+		} break;
+		case UNREACHABLE_CODE: {
+			CHECK_SYMBOLS(1);
+			return "Unreachable code (statement after return) in function '" + symbols[0] + "()'.";
+		} break;
+		case STANDALONE_EXPRESSION: {
+			return "Standalone expression (the line has no effect).";
+		} break;
+		case VOID_ASSIGNMENT: {
+			CHECK_SYMBOLS(1);
+			return "Assignment operation, but the function '" + symbols[0] + "()' returns void.";
+		} break;
+		case NARROWING_CONVERSION: {
+			return "Narrowing conversion (float is converted to int and loses precision).";
+		} break;
+		case FUNCTION_MAY_YIELD: {
+			CHECK_SYMBOLS(1);
+			return "Assigned variable is typed but the function '" + symbols[0] + "()' may yield and return a GDScriptFunctionState instead.";
+		} break;
+		case VARIABLE_CONFLICTS_FUNCTION: {
+			CHECK_SYMBOLS(1);
+			return "Variable declaration of '" + symbols[0] + "' conflicts with a function of the same name.";
+		} break;
+		case FUNCTION_CONFLICTS_VARIABLE: {
+			CHECK_SYMBOLS(1);
+			return "Function declaration of '" + symbols[0] + "()' conflicts with a variable of the same name.";
+		} break;
+		case FUNCTION_CONFLICTS_CONSTANT: {
+			CHECK_SYMBOLS(1);
+			return "Function declaration of '" + symbols[0] + "()' conflicts with a constant of the same name.";
+		} break;
+		case INCOMPATIBLE_TERNARY: {
+			return "Values of the ternary conditional are not mutually compatible.";
+		} break;
+		case UNUSED_SIGNAL: {
+			CHECK_SYMBOLS(1);
+			return "The signal '" + symbols[0] + "' is declared but never emitted.";
+		} break;
+		case RETURN_VALUE_DISCARDED: {
+			CHECK_SYMBOLS(1);
+			return "The function '" + symbols[0] + "()' returns a value, but this value is never used.";
+		} break;
+		case PROPERTY_USED_AS_FUNCTION: {
+			CHECK_SYMBOLS(2);
+			return "The method '" + symbols[0] + "()' was not found in base '" + symbols[1] + "' but there's a property with the same name. Did you mean to access it?";
+		} break;
+		case CONSTANT_USED_AS_FUNCTION: {
+			CHECK_SYMBOLS(2);
+			return "The method '" + symbols[0] + "()' was not found in base '" + symbols[1] + "' but there's a constant with the same name. Did you mean to access it?";
+		} break;
+		case FUNCTION_USED_AS_PROPERTY: {
+			CHECK_SYMBOLS(2);
+			return "The property '" + symbols[0] + "' was not found in base '" + symbols[1] + "' but there's a method with the same name. Did you mean to call it?";
+		} break;
+		case INTEGER_DIVISION: {
+			return "Integer division, decimal part will be discarded.";
+		} break;
+		case UNSAFE_PROPERTY_ACCESS: {
+			CHECK_SYMBOLS(2);
+			return "The property '" + symbols[0] + "' is not present on the inferred type '" + symbols[1] + "' (but may be present on a subtype).";
+		} break;
+		case UNSAFE_METHOD_ACCESS: {
+			CHECK_SYMBOLS(2);
+			return "The method '" + symbols[0] + "' is not present on the inferred type '" + symbols[1] + "' (but may be present on a subtype).";
+		} break;
+		case UNSAFE_CAST: {
+			CHECK_SYMBOLS(1);
+			return "The value is cast to '" + symbols[0] + "' but has an unknown type.";
+		} break;
+		case UNSAFE_CALL_ARGUMENT: {
+			CHECK_SYMBOLS(4);
+			return "The argument '" + symbols[0] + "' of the function '" + symbols[1] + "' requires a the subtype '" + symbols[2] + "' but the supertype '" + symbols[3] + "' was provided";
+		} break;
+		case DEPRECATED_KEYWORD: {
+			CHECK_SYMBOLS(2);
+			return "The '" + symbols[0] + "' keyword is deprecated and will be removed in a future release, please replace its uses by '" + symbols[1] + "'.";
+		} break;
+		case STANDALONE_TERNARY: {
+			return "Standalone ternary conditional operator: the return value is being discarded.";
+		}
+		case WARNING_MAX:
+			break; // Can't happen, but silences warning
+	}
+	ERR_FAIL_V_MSG(String(), "Invalid GDScript warning code: " + get_name_from_code(code) + ".");
+
+#undef CHECK_SYMBOLS
+}
+
+String GDScriptWarning::get_name() const {
+	return get_name_from_code(code);
+}
+
+String GDScriptWarning::get_name_from_code(Code p_code) {
+	ERR_FAIL_COND_V(p_code < 0 || p_code >= WARNING_MAX, String());
+
+	static const char *names[] = {
+		"UNASSIGNED_VARIABLE",
+		"UNASSIGNED_VARIABLE_OP_ASSIGN",
+		"UNUSED_VARIABLE",
+		"SHADOWED_VARIABLE",
+		"UNUSED_CLASS_VARIABLE",
+		"UNUSED_ARGUMENT",
+		"UNREACHABLE_CODE",
+		"STANDALONE_EXPRESSION",
+		"VOID_ASSIGNMENT",
+		"NARROWING_CONVERSION",
+		"FUNCTION_MAY_YIELD",
+		"VARIABLE_CONFLICTS_FUNCTION",
+		"FUNCTION_CONFLICTS_VARIABLE",
+		"FUNCTION_CONFLICTS_CONSTANT",
+		"INCOMPATIBLE_TERNARY",
+		"UNUSED_SIGNAL",
+		"RETURN_VALUE_DISCARDED",
+		"PROPERTY_USED_AS_FUNCTION",
+		"CONSTANT_USED_AS_FUNCTION",
+		"FUNCTION_USED_AS_PROPERTY",
+		"INTEGER_DIVISION",
+		"UNSAFE_PROPERTY_ACCESS",
+		"UNSAFE_METHOD_ACCESS",
+		"UNSAFE_CAST",
+		"UNSAFE_CALL_ARGUMENT",
+		"DEPRECATED_KEYWORD",
+		"STANDALONE_TERNARY",
+		nullptr
+	};
+
+	return names[(int)p_code];
+}
+
+GDScriptWarning::Code GDScriptWarning::get_code_from_name(const String &p_name) {
+	for (int i = 0; i < WARNING_MAX; i++) {
+		if (get_name_from_code((Code)i) == p_name) {
+			return (Code)i;
+		}
+	}
+
+	ERR_FAIL_V_MSG(WARNING_MAX, "Invalid GDScript warning name: " + p_name);
+}
+
+#endif // DEBUG_ENABLED

+ 84 - 0
modules/gdscript/gdscript_warning.h

@@ -0,0 +1,84 @@
+/*************************************************************************/
+/*  gdscript_warning.h                                                   */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef GDSCRIPT_WARNINGS
+#define GDSCRIPT_WARNINGS
+
+#ifdef DEBUG_ENABLED
+
+#include "core/ustring.h"
+#include "core/vector.h"
+
+class GDScriptWarning {
+public:
+	enum Code {
+		UNASSIGNED_VARIABLE, // Variable used but never assigned
+		UNASSIGNED_VARIABLE_OP_ASSIGN, // Variable never assigned but used in an assignment operation (+=, *=, etc)
+		UNUSED_VARIABLE, // Local variable is declared but never used
+		SHADOWED_VARIABLE, // Variable name shadowed by other variable
+		UNUSED_CLASS_VARIABLE, // Class variable is declared but never used in the file
+		UNUSED_ARGUMENT, // Function argument is never used
+		UNREACHABLE_CODE, // Code after a return statement
+		STANDALONE_EXPRESSION, // Expression not assigned to a variable
+		VOID_ASSIGNMENT, // Function returns void but it's assigned to a variable
+		NARROWING_CONVERSION, // Float value into an integer slot, precision is lost
+		FUNCTION_MAY_YIELD, // Typed assign of function call that yields (it may return a function state)
+		VARIABLE_CONFLICTS_FUNCTION, // Variable has the same name of a function
+		FUNCTION_CONFLICTS_VARIABLE, // Function has the same name of a variable
+		FUNCTION_CONFLICTS_CONSTANT, // Function has the same name of a constant
+		INCOMPATIBLE_TERNARY, // Possible values of a ternary if are not mutually compatible
+		UNUSED_SIGNAL, // Signal is defined but never emitted
+		RETURN_VALUE_DISCARDED, // Function call returns something but the value isn't used
+		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
+		INTEGER_DIVISION, // Integer divide by integer, decimal part is discarded
+		UNSAFE_PROPERTY_ACCESS, // Property not found in the detected type (but can be in subtypes)
+		UNSAFE_METHOD_ACCESS, // Function not found in the detected type (but can be in subtypes)
+		UNSAFE_CAST, // Cast used in an unknown type
+		UNSAFE_CALL_ARGUMENT, // Function call argument is of a supertype of the require argument
+		DEPRECATED_KEYWORD, // The keyword is deprecated and should be replaced
+		STANDALONE_TERNARY, // Return value of ternary expression is discarded
+		WARNING_MAX,
+	};
+
+	Code code = WARNING_MAX;
+	int line = -1;
+	Vector<String> symbols;
+
+	String get_name() const;
+	String get_message() const;
+	static String get_name_from_code(Code p_code);
+	static Code get_code_from_name(const String &p_name);
+};
+
+#endif // DEBUG_ENABLED
+
+#endif // GDSCRIPT_WARNINGS

+ 5 - 0
modules/gdscript/language_server/gdscript_extend_parser.cpp

@@ -28,6 +28,9 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /*************************************************************************/
 /*************************************************************************/
 
 
+// FIXME: Reenable LSP.
+#if 0
+
 #include "gdscript_extend_parser.h"
 #include "gdscript_extend_parser.h"
 
 
 #include "../gdscript.h"
 #include "../gdscript.h"
@@ -769,3 +772,5 @@ Error ExtendGDScriptParser::parse(const String &p_code, const String &p_path) {
 	update_document_links(p_code);
 	update_document_links(p_code);
 	return err;
 	return err;
 }
 }
+
+#endif

+ 5 - 0
modules/gdscript/language_server/gdscript_extend_parser.h

@@ -31,6 +31,9 @@
 #ifndef GDSCRIPT_EXTEND_PARSER_H
 #ifndef GDSCRIPT_EXTEND_PARSER_H
 #define GDSCRIPT_EXTEND_PARSER_H
 #define GDSCRIPT_EXTEND_PARSER_H
 
 
+// FIXME: Reenable LSP.
+#if 0
+
 #include "../gdscript_parser.h"
 #include "../gdscript_parser.h"
 #include "core/variant.h"
 #include "core/variant.h"
 #include "lsp.hpp"
 #include "lsp.hpp"
@@ -100,3 +103,5 @@ public:
 };
 };
 
 
 #endif
 #endif
+
+#endif

+ 5 - 0
modules/gdscript/language_server/gdscript_language_protocol.cpp

@@ -28,6 +28,9 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /*************************************************************************/
 /*************************************************************************/
 
 
+// FIXME: Reenable LSP.
+#if 0
+
 #include "gdscript_language_protocol.h"
 #include "gdscript_language_protocol.h"
 
 
 #include "core/io/json.h"
 #include "core/io/json.h"
@@ -303,3 +306,5 @@ GDScriptLanguageProtocol::GDScriptLanguageProtocol() {
 	set_scope("workspace", workspace.ptr());
 	set_scope("workspace", workspace.ptr());
 	workspace->root = ProjectSettings::get_singleton()->get_resource_path();
 	workspace->root = ProjectSettings::get_singleton()->get_resource_path();
 }
 }
+
+#endif

+ 5 - 0
modules/gdscript/language_server/gdscript_language_protocol.h

@@ -31,6 +31,9 @@
 #ifndef GDSCRIPT_PROTOCAL_SERVER_H
 #ifndef GDSCRIPT_PROTOCAL_SERVER_H
 #define GDSCRIPT_PROTOCAL_SERVER_H
 #define GDSCRIPT_PROTOCAL_SERVER_H
 
 
+// FIXME: Reenable LSP.
+#if 0
+
 #include "core/io/stream_peer.h"
 #include "core/io/stream_peer.h"
 #include "core/io/stream_peer_tcp.h"
 #include "core/io/stream_peer_tcp.h"
 #include "core/io/tcp_server.h"
 #include "core/io/tcp_server.h"
@@ -109,3 +112,5 @@ public:
 };
 };
 
 
 #endif
 #endif
+
+#endif

+ 5 - 0
modules/gdscript/language_server/gdscript_language_server.cpp

@@ -28,6 +28,9 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /*************************************************************************/
 /*************************************************************************/
 
 
+// FIXME: Reenable LSP.
+#if 0
+
 #include "gdscript_language_server.h"
 #include "gdscript_language_server.h"
 
 
 #include "core/os/file_access.h"
 #include "core/os/file_access.h"
@@ -114,3 +117,5 @@ void register_lsp_types() {
 	ClassDB::register_class<GDScriptTextDocument>();
 	ClassDB::register_class<GDScriptTextDocument>();
 	ClassDB::register_class<GDScriptWorkspace>();
 	ClassDB::register_class<GDScriptWorkspace>();
 }
 }
+
+#endif

+ 5 - 0
modules/gdscript/language_server/gdscript_language_server.h

@@ -31,6 +31,9 @@
 #ifndef GDSCRIPT_LANGUAGE_SERVER_H
 #ifndef GDSCRIPT_LANGUAGE_SERVER_H
 #define GDSCRIPT_LANGUAGE_SERVER_H
 #define GDSCRIPT_LANGUAGE_SERVER_H
 
 
+// FIXME: Reenable LSP.
+#if 0
+
 #include "../gdscript_parser.h"
 #include "../gdscript_parser.h"
 #include "editor/editor_plugin.h"
 #include "editor/editor_plugin.h"
 #include "gdscript_language_protocol.h"
 #include "gdscript_language_protocol.h"
@@ -60,4 +63,6 @@ public:
 
 
 void register_lsp_types();
 void register_lsp_types();
 
 
+#endif
+
 #endif // GDSCRIPT_LANGUAGE_SERVER_H
 #endif // GDSCRIPT_LANGUAGE_SERVER_H

+ 5 - 0
modules/gdscript/language_server/gdscript_text_document.cpp

@@ -38,6 +38,9 @@
 #include "gdscript_language_protocol.h"
 #include "gdscript_language_protocol.h"
 #include "servers/display_server.h"
 #include "servers/display_server.h"
 
 
+// FIXME: Reenable LSP.
+#if 0
+
 void GDScriptTextDocument::_bind_methods() {
 void GDScriptTextDocument::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("didOpen"), &GDScriptTextDocument::didOpen);
 	ClassDB::bind_method(D_METHOD("didOpen"), &GDScriptTextDocument::didOpen);
 	ClassDB::bind_method(D_METHOD("didChange"), &GDScriptTextDocument::didChange);
 	ClassDB::bind_method(D_METHOD("didChange"), &GDScriptTextDocument::didChange);
@@ -437,3 +440,5 @@ Array GDScriptTextDocument::find_symbols(const lsp::TextDocumentPositionParams &
 	}
 	}
 	return arr;
 	return arr;
 }
 }
+
+#endif

+ 5 - 0
modules/gdscript/language_server/gdscript_text_document.h

@@ -31,6 +31,9 @@
 #ifndef GDSCRIPT_TEXT_DOCUMENT_H
 #ifndef GDSCRIPT_TEXT_DOCUMENT_H
 #define GDSCRIPT_TEXT_DOCUMENT_H
 #define GDSCRIPT_TEXT_DOCUMENT_H
 
 
+// FIXME: Reenable LSP.
+#if 0
+
 #include "core/os/file_access.h"
 #include "core/os/file_access.h"
 #include "core/reference.h"
 #include "core/reference.h"
 #include "lsp.hpp"
 #include "lsp.hpp"
@@ -76,3 +79,5 @@ public:
 };
 };
 
 
 #endif
 #endif
+
+#endif

+ 5 - 0
modules/gdscript/language_server/gdscript_workspace.cpp

@@ -28,6 +28,9 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /*************************************************************************/
 /*************************************************************************/
 
 
+// FIXME: Reenable LSP.
+#if 0
+
 #include "gdscript_workspace.h"
 #include "gdscript_workspace.h"
 
 
 #include "../gdscript.h"
 #include "../gdscript.h"
@@ -618,3 +621,5 @@ GDScriptWorkspace::~GDScriptWorkspace() {
 		remove_cache_parser(E->get());
 		remove_cache_parser(E->get());
 	}
 	}
 }
 }
+
+#endif

+ 5 - 0
modules/gdscript/language_server/gdscript_workspace.h

@@ -31,6 +31,9 @@
 #ifndef GDSCRIPT_WORKSPACE_H
 #ifndef GDSCRIPT_WORKSPACE_H
 #define GDSCRIPT_WORKSPACE_H
 #define GDSCRIPT_WORKSPACE_H
 
 
+// FIXME: Reenable LSP.
+#if 0
+
 #include "../gdscript_parser.h"
 #include "../gdscript_parser.h"
 #include "core/variant.h"
 #include "core/variant.h"
 #include "editor/editor_file_system.h"
 #include "editor/editor_file_system.h"
@@ -95,3 +98,5 @@ public:
 };
 };
 
 
 #endif
 #endif
+
+#endif

+ 5 - 0
modules/gdscript/language_server/lsp.hpp

@@ -31,6 +31,9 @@
 #ifndef GODOT_LSP_H
 #ifndef GODOT_LSP_H
 #define GODOT_LSP_H
 #define GODOT_LSP_H
 
 
+// FIXME: Reenable LSP.
+#if 0
+
 #include "core/class_db.h"
 #include "core/class_db.h"
 #include "core/list.h"
 #include "core/list.h"
 #include "editor/doc_data.h"
 #include "editor/doc_data.h"
@@ -1785,3 +1788,5 @@ static String marked_documentation(const String &p_bbcode) {
 } // namespace lsp
 } // namespace lsp
 
 
 #endif
 #endif
+
+#endif

+ 8 - 58
modules/gdscript/register_types.cpp

@@ -50,10 +50,13 @@ Ref<ResourceFormatSaverGDScript> resource_saver_gd;
 #include "editor/gdscript_highlighter.h"
 #include "editor/gdscript_highlighter.h"
 #include "editor/gdscript_translation_parser_plugin.h"
 #include "editor/gdscript_translation_parser_plugin.h"
 
 
+// FIXME: Reenable LSP.
+#if 0
 #ifndef GDSCRIPT_NO_LSP
 #ifndef GDSCRIPT_NO_LSP
 #include "core/engine.h"
 #include "core/engine.h"
 #include "language_server/gdscript_language_server.h"
 #include "language_server/gdscript_language_server.h"
 #endif // !GDSCRIPT_NO_LSP
 #endif // !GDSCRIPT_NO_LSP
+#endif
 
 
 Ref<GDScriptEditorTranslationParserPlugin> gdscript_translation_parser_plugin;
 Ref<GDScriptEditorTranslationParserPlugin> gdscript_translation_parser_plugin;
 
 
@@ -76,64 +79,8 @@ public:
 			return;
 			return;
 		}
 		}
 
 
-		Vector<uint8_t> file = FileAccess::get_file_as_array(p_path);
-		if (file.empty()) {
-			return;
-		}
-
-		String txt;
-		txt.parse_utf8((const char *)file.ptr(), file.size());
-		file = GDScriptTokenizerBuffer::parse_code_string(txt);
-
-		if (!file.empty()) {
-			if (script_mode == EditorExportPreset::MODE_SCRIPT_ENCRYPTED) {
-				String tmp_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("script.gde");
-				FileAccess *fa = FileAccess::open(tmp_path, FileAccess::WRITE);
-
-				Vector<uint8_t> key;
-				key.resize(32);
-				for (int i = 0; i < 32; i++) {
-					int v = 0;
-					if (i * 2 < script_key.length()) {
-						CharType ct = script_key[i * 2];
-						if (ct >= '0' && ct <= '9') {
-							ct = ct - '0';
-						} else if (ct >= 'a' && ct <= 'f') {
-							ct = 10 + ct - 'a';
-						}
-						v |= ct << 4;
-					}
-
-					if (i * 2 + 1 < script_key.length()) {
-						CharType ct = script_key[i * 2 + 1];
-						if (ct >= '0' && ct <= '9') {
-							ct = ct - '0';
-						} else if (ct >= 'a' && ct <= 'f') {
-							ct = 10 + ct - 'a';
-						}
-						v |= ct;
-					}
-					key.write[i] = v;
-				}
-				FileAccessEncrypted *fae = memnew(FileAccessEncrypted);
-				Error err = fae->open_and_parse(fa, key, FileAccessEncrypted::MODE_WRITE_AES256);
-
-				if (err == OK) {
-					fae->store_buffer(file.ptr(), file.size());
-				}
-
-				memdelete(fae);
-
-				file = FileAccess::get_file_as_array(tmp_path);
-				add_file(p_path.get_basename() + ".gde", file, true);
-
-				// Clean up temporary file.
-				DirAccess::remove_file_or_error(tmp_path);
-
-			} else {
-				add_file(p_path.get_basename() + ".gdc", file, true);
-			}
-		}
+		// TODO: Readd compiled/encrypted GDScript on export.
+		return;
 	}
 	}
 };
 };
 
 
@@ -148,12 +95,15 @@ static void _editor_init() {
 	ScriptEditor::get_singleton()->register_syntax_highlighter(gdscript_syntax_highlighter);
 	ScriptEditor::get_singleton()->register_syntax_highlighter(gdscript_syntax_highlighter);
 #endif
 #endif
 
 
+// FIXME: Reenable LSP.
+#if 0
 #ifndef GDSCRIPT_NO_LSP
 #ifndef GDSCRIPT_NO_LSP
 	register_lsp_types();
 	register_lsp_types();
 	GDScriptLanguageServer *lsp_plugin = memnew(GDScriptLanguageServer);
 	GDScriptLanguageServer *lsp_plugin = memnew(GDScriptLanguageServer);
 	EditorNode::get_singleton()->add_editor_plugin(lsp_plugin);
 	EditorNode::get_singleton()->add_editor_plugin(lsp_plugin);
 	Engine::get_singleton()->add_singleton(Engine::Singleton("GDScriptLanguageProtocol", GDScriptLanguageProtocol::get_singleton()));
 	Engine::get_singleton()->add_singleton(Engine::Singleton("GDScriptLanguageProtocol", GDScriptLanguageProtocol::get_singleton()));
 #endif // !GDSCRIPT_NO_LSP
 #endif // !GDSCRIPT_NO_LSP
+#endif
 }
 }
 
 
 #endif // TOOLS_ENABLED
 #endif // TOOLS_ENABLED

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff