Browse Source

Merge pull request #62918 from cdemirer/parser-properly-set-node-extents

George Marques 3 years ago
parent
commit
87ef6f554b
2 changed files with 186 additions and 38 deletions
  1. 178 31
      modules/gdscript/gdscript_parser.cpp
  2. 8 7
      modules/gdscript/gdscript_parser.h

+ 178 - 31
modules/gdscript/gdscript_parser.cpp

@@ -162,6 +162,7 @@ void GDScriptParser::clear() {
 	for_completion = false;
 	errors.clear();
 	multiline_stack.clear();
+	nodes_in_progress.clear();
 }
 
 void GDScriptParser::push_error(const String &p_message, const Node *p_origin) {
@@ -413,6 +414,9 @@ GDScriptTokenizer::Token GDScriptParser::advance() {
 		push_error(current.literal);
 		current = tokenizer.scan();
 	}
+	for (Node *n : nodes_in_progress) {
+		update_extents(n);
+	}
 	return previous;
 }
 
@@ -609,6 +613,7 @@ void GDScriptParser::parse_program() {
 	}
 
 	parse_class_body(true);
+	complete_extents(head);
 
 #ifdef TOOLS_ENABLED
 	for (const KeyValue<int, GDScriptTokenizer::CommentData> &E : tokenizer.get_comments()) {
@@ -649,6 +654,7 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() {
 
 	if (multiline && !consume(GDScriptTokenizer::Token::INDENT, R"(Expected indented block after class declaration.)")) {
 		current_class = previous_class;
+		complete_extents(n_class);
 		return n_class;
 	}
 
@@ -661,6 +667,7 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() {
 	}
 
 	parse_class_body(multiline);
+	complete_extents(n_class);
 
 	if (multiline) {
 		consume(GDScriptTokenizer::Token::DEDENT, R"(Missing unindent at the end of the class body.)");
@@ -870,11 +877,13 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable() {
 }
 
 GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_property) {
+	VariableNode *variable = alloc_node<VariableNode>();
+
 	if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected variable name after "var".)")) {
+		complete_extents(variable);
 		return nullptr;
 	}
 
-	VariableNode *variable = alloc_node<VariableNode>();
 	variable->identifier = parse_identifier();
 	variable->export_info.name = variable->identifier->name;
 
@@ -882,10 +891,10 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_proper
 		if (check(GDScriptTokenizer::Token::NEWLINE)) {
 			if (p_allow_property) {
 				advance();
-
 				return parse_property(variable, true);
 			} else {
 				push_error(R"(Expected type after ":")");
+				complete_extents(variable);
 				return nullptr;
 			}
 		} else if (check((GDScriptTokenizer::Token::EQUAL))) {
@@ -924,6 +933,7 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_proper
 		}
 	}
 
+	complete_extents(variable);
 	end_statement("variable declaration");
 
 	return variable;
@@ -932,6 +942,7 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_proper
 GDScriptParser::VariableNode *GDScriptParser::parse_property(VariableNode *p_variable, bool p_need_indent) {
 	if (p_need_indent) {
 		if (!consume(GDScriptTokenizer::Token::INDENT, R"(Expected indented block for property after ":".)")) {
+			complete_extents(p_variable);
 			return nullptr;
 		}
 	}
@@ -941,6 +952,7 @@ GDScriptParser::VariableNode *GDScriptParser::parse_property(VariableNode *p_var
 	make_completion_context(COMPLETION_PROPERTY_DECLARATION, property);
 
 	if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected "get" or "set" for property declaration.)")) {
+		complete_extents(p_variable);
 		return nullptr;
 	}
 
@@ -997,6 +1009,7 @@ GDScriptParser::VariableNode *GDScriptParser::parse_property(VariableNode *p_var
 		}
 		function = parse_identifier();
 	}
+	complete_extents(p_variable);
 
 	if (p_variable->property == VariableNode::PROP_SETGET) {
 		end_statement("property declaration");
@@ -1011,37 +1024,37 @@ GDScriptParser::VariableNode *GDScriptParser::parse_property(VariableNode *p_var
 void GDScriptParser::parse_property_setter(VariableNode *p_variable) {
 	switch (p_variable->property) {
 		case VariableNode::PROP_INLINE: {
-			consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "set".)");
-			if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected parameter name after "(".)")) {
-				p_variable->setter_parameter = parse_identifier();
-			}
-			consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after parameter name.)*");
-			consume(GDScriptTokenizer::Token::COLON, R"*(Expected ":" after ")".)*");
-
+			FunctionNode *function = alloc_node<FunctionNode>();
 			IdentifierNode *identifier = alloc_node<IdentifierNode>();
+			complete_extents(identifier);
 			identifier->name = "@" + p_variable->identifier->name + "_setter";
-
-			FunctionNode *function = alloc_node<FunctionNode>();
 			function->identifier = identifier;
 
-			FunctionNode *previous_function = current_function;
-			current_function = function;
+			consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "set".)");
 
 			ParameterNode *parameter = alloc_node<ParameterNode>();
-			parameter->identifier = p_variable->setter_parameter;
-
-			if (parameter->identifier != nullptr) {
+			if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected parameter name after "(".)")) {
+				reset_extents(parameter, previous);
+				p_variable->setter_parameter = parse_identifier();
+				parameter->identifier = p_variable->setter_parameter;
 				function->parameters_indices[parameter->identifier->name] = 0;
 				function->parameters.push_back(parameter);
+			}
+			complete_extents(parameter);
 
+			consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after parameter name.)*");
+			consume(GDScriptTokenizer::Token::COLON, R"*(Expected ":" after ")".)*");
+
+			FunctionNode *previous_function = current_function;
+			current_function = function;
+			if (p_variable->setter_parameter != nullptr) {
 				SuiteNode *body = alloc_node<SuiteNode>();
 				body->add_local(parameter, function);
-
 				function->body = parse_suite("setter declaration", body);
 				p_variable->setter = function;
 			}
-
 			current_function = previous_function;
+			complete_extents(function);
 			break;
 		}
 		case VariableNode::PROP_SETGET:
@@ -1059,12 +1072,13 @@ void GDScriptParser::parse_property_setter(VariableNode *p_variable) {
 void GDScriptParser::parse_property_getter(VariableNode *p_variable) {
 	switch (p_variable->property) {
 		case VariableNode::PROP_INLINE: {
+			FunctionNode *function = alloc_node<FunctionNode>();
+
 			consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "get".)");
 
 			IdentifierNode *identifier = alloc_node<IdentifierNode>();
+			complete_extents(identifier);
 			identifier->name = "@" + p_variable->identifier->name + "_getter";
-
-			FunctionNode *function = alloc_node<FunctionNode>();
 			function->identifier = identifier;
 
 			FunctionNode *previous_function = current_function;
@@ -1072,9 +1086,10 @@ void GDScriptParser::parse_property_getter(VariableNode *p_variable) {
 
 			SuiteNode *body = alloc_node<SuiteNode>();
 			function->body = parse_suite("getter declaration", body);
-
 			p_variable->getter = function;
+
 			current_function = previous_function;
+			complete_extents(function);
 			break;
 		}
 		case VariableNode::PROP_SETGET:
@@ -1090,11 +1105,12 @@ void GDScriptParser::parse_property_getter(VariableNode *p_variable) {
 }
 
 GDScriptParser::ConstantNode *GDScriptParser::parse_constant() {
+	ConstantNode *constant = alloc_node<ConstantNode>();
+
 	if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected constant name after "const".)")) {
 		return nullptr;
 	}
 
-	ConstantNode *constant = alloc_node<ConstantNode>();
 	constant->identifier = parse_identifier();
 
 	if (match(GDScriptTokenizer::Token::COLON)) {
@@ -1113,12 +1129,15 @@ GDScriptParser::ConstantNode *GDScriptParser::parse_constant() {
 
 		if (constant->initializer == nullptr) {
 			push_error(R"(Expected initializer expression for constant.)");
+			complete_extents(constant);
 			return nullptr;
 		}
 	} else {
+		complete_extents(constant);
 		return nullptr;
 	}
 
+	complete_extents(constant);
 	end_statement("constant declaration");
 
 	return constant;
@@ -1148,15 +1167,18 @@ GDScriptParser::ParameterNode *GDScriptParser::parse_parameter() {
 		parameter->default_value = parse_expression(false);
 	}
 
+	complete_extents(parameter);
 	return parameter;
 }
 
 GDScriptParser::SignalNode *GDScriptParser::parse_signal() {
+	SignalNode *signal = alloc_node<SignalNode>();
+
 	if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected signal name after "signal".)")) {
+		complete_extents(signal);
 		return nullptr;
 	}
 
-	SignalNode *signal = alloc_node<SignalNode>();
 	signal->identifier = parse_identifier();
 
 	if (check(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
@@ -1188,6 +1210,7 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() {
 		consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after signal parameters.)*");
 	}
 
+	complete_extents(signal);
 	end_statement("signal declaration");
 
 	return signal;
@@ -1299,6 +1322,7 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
 	}
 #endif // TOOLS_ENABLED
 
+	complete_extents(enum_node);
 	end_statement("enum");
 
 	return enum_node;
@@ -1350,19 +1374,22 @@ void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNod
 }
 
 GDScriptParser::FunctionNode *GDScriptParser::parse_function() {
+	FunctionNode *function = alloc_node<FunctionNode>();
+
 	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".)")) {
+			complete_extents(function);
 			return nullptr;
 		}
 		_static = true;
 	}
 
-	FunctionNode *function = alloc_node<FunctionNode>();
 	make_completion_context(COMPLETION_OVERRIDE_METHOD, function);
 
 	if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after "func".)")) {
+		complete_extents(function);
 		return nullptr;
 	}
 
@@ -1384,6 +1411,7 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function() {
 	function->body = parse_suite("function declaration", body);
 
 	current_function = previous_function;
+	complete_extents(function);
 	return function;
 }
 
@@ -1431,6 +1459,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
 		}
 		pop_completion_call();
 	}
+	complete_extents(annotation);
 
 	match(GDScriptTokenizer::Token::NEWLINE); // Newline after annotation is optional.
 
@@ -1480,9 +1509,11 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context,
 	if (multiline) {
 		if (!consume(GDScriptTokenizer::Token::INDENT, vformat(R"(Expected indented block after %s.)", p_context))) {
 			current_suite = suite->parent_block;
+			complete_extents(suite);
 			return suite;
 		}
 	}
+	reset_extents(suite, current);
 
 	int error_count = 0;
 
@@ -1532,6 +1563,8 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context,
 
 	} while ((multiline || previous.type == GDScriptTokenizer::Token::SEMICOLON) && !check(GDScriptTokenizer::Token::DEDENT) && !lambda_ended && !is_at_end());
 
+	complete_extents(suite);
+
 	if (multiline) {
 		if (!lambda_ended) {
 			consume(GDScriptTokenizer::Token::DEDENT, vformat(R"(Missing unindent at the end of %s.)", p_context));
@@ -1562,6 +1595,7 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
 		case GDScriptTokenizer::Token::PASS:
 			advance();
 			result = alloc_node<PassNode>();
+			complete_extents(result);
 			end_statement(R"("pass")");
 			break;
 		case GDScriptTokenizer::Token::VAR:
@@ -1609,6 +1643,7 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
 				// If this fails the expression will be nullptr, but that's the same as no return, so it's fine.
 				n_return->return_value = parse_expression(false);
 			}
+			complete_extents(n_return);
 			result = n_return;
 
 			current_suite->has_return = true;
@@ -1619,6 +1654,7 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
 		case GDScriptTokenizer::Token::BREAKPOINT:
 			advance();
 			result = alloc_node<BreakpointNode>();
+			complete_extents(result);
 			end_statement(R"("breakpoint")");
 			break;
 		case GDScriptTokenizer::Token::ASSERT:
@@ -1710,6 +1746,7 @@ GDScriptParser::AssertNode *GDScriptParser::parse_assert() {
 	assert->condition = parse_expression(false);
 	if (assert->condition == nullptr) {
 		push_error("Expected expression to assert.");
+		complete_extents(assert);
 		return nullptr;
 	}
 
@@ -1718,12 +1755,14 @@ GDScriptParser::AssertNode *GDScriptParser::parse_assert() {
 		assert->message = parse_expression(false);
 		if (assert->message == nullptr) {
 			push_error(R"(Expected error message for assert after ",".)");
+			complete_extents(assert);
 			return nullptr;
 		}
 	}
 
 	consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after assert expression.)*");
 
+	complete_extents(assert);
 	end_statement(R"("assert")");
 
 	return assert;
@@ -1733,8 +1772,10 @@ GDScriptParser::BreakNode *GDScriptParser::parse_break() {
 	if (!can_break) {
 		push_error(R"(Cannot use "break" outside of a loop.)");
 	}
+	BreakNode *break_node = alloc_node<BreakNode>();
+	complete_extents(break_node);
 	end_statement(R"("break")");
-	return alloc_node<BreakNode>();
+	return break_node;
 }
 
 GDScriptParser::ContinueNode *GDScriptParser::parse_continue() {
@@ -1742,9 +1783,10 @@ GDScriptParser::ContinueNode *GDScriptParser::parse_continue() {
 		push_error(R"(Cannot use "continue" outside of a loop or pattern matching block.)");
 	}
 	current_suite->has_continue = true;
-	end_statement(R"("continue")");
 	ContinueNode *cont = alloc_node<ContinueNode>();
 	cont->is_for_match = is_continue_match;
+	complete_extents(cont);
+	end_statement(R"("continue")");
 	return cont;
 }
 
@@ -1786,6 +1828,7 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() {
 	suite->parent_for = n_for;
 
 	n_for->loop = parse_suite(R"("for" block)", suite);
+	complete_extents(n_for);
 
 	// Reset break/continue state.
 	can_break = could_break;
@@ -1813,15 +1856,16 @@ GDScriptParser::IfNode *GDScriptParser::parse_if(const String &p_token) {
 	}
 
 	if (match(GDScriptTokenizer::Token::ELIF)) {
-		IfNode *elif = parse_if("elif");
-
 		SuiteNode *else_block = alloc_node<SuiteNode>();
+		IfNode *elif = parse_if("elif");
 		else_block->statements.push_back(elif);
+		complete_extents(else_block);
 		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)");
 	}
+	complete_extents(n_if);
 
 	if (n_if->false_block != nullptr && n_if->false_block->has_return && n_if->true_block->has_return) {
 		current_suite->has_return = true;
@@ -1845,6 +1889,7 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() {
 	consume(GDScriptTokenizer::Token::NEWLINE, R"(Expected a newline after "match" statement.)");
 
 	if (!consume(GDScriptTokenizer::Token::INDENT, R"(Expected an indented block after "match" statement.)")) {
+		complete_extents(match);
 		return match;
 	}
 
@@ -1878,6 +1923,7 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() {
 #endif
 		match->branches.push_back(branch);
 	}
+	complete_extents(match);
 
 	consume(GDScriptTokenizer::Token::DEDENT, R"(Expected an indented block after "match" statement.)");
 
@@ -1892,6 +1938,7 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() {
 
 GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() {
 	MatchBranchNode *branch = alloc_node<MatchBranchNode>();
+	reset_extents(branch, current);
 
 	bool has_bind = false;
 
@@ -1919,6 +1966,7 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() {
 	}
 
 	if (!consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" patterns.)")) {
+		complete_extents(branch);
 		return nullptr;
 	}
 
@@ -1939,6 +1987,7 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() {
 	}
 
 	branch->block = parse_suite("match pattern block", suite);
+	complete_extents(branch);
 
 	// Restore continue state.
 	can_continue = could_continue;
@@ -1949,12 +1998,14 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() {
 
 GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_root_pattern) {
 	PatternNode *pattern = alloc_node<PatternNode>();
+	reset_extents(pattern, current);
 
 	switch (current.type) {
 		case GDScriptTokenizer::Token::VAR: {
 			// Bind.
 			advance();
 			if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected bind name after "var".)")) {
+				complete_extents(pattern);
 				return nullptr;
 			}
 			pattern->pattern_type = PatternNode::PT_BIND;
@@ -1965,12 +2016,14 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_
 			if (p_root_pattern != nullptr) {
 				if (p_root_pattern->has_bind(pattern->bind->name)) {
 					push_error(vformat(R"(Bind variable name "%s" was already used in this pattern.)", pattern->bind->name));
+					complete_extents(pattern);
 					return nullptr;
 				}
 			}
 
 			if (current_suite->has_local(pattern->bind->name)) {
 				push_error(vformat(R"(There's already a %s named "%s" in this scope.)", current_suite->get_local(pattern->bind->name).get_name(), pattern->bind->name));
+				complete_extents(pattern);
 				return nullptr;
 			}
 
@@ -2023,6 +2076,7 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_
 						push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)");
 					} else {
 						PatternNode *sub_pattern = alloc_node<PatternNode>();
+						complete_extents(sub_pattern);
 						sub_pattern->pattern_type = PatternNode::PT_REST;
 						pattern->dictionary.push_back({ nullptr, sub_pattern });
 						pattern->rest_used = true;
@@ -2071,6 +2125,7 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_
 			break;
 		}
 	}
+	complete_extents(pattern);
 
 	return pattern;
 }
@@ -2104,6 +2159,7 @@ GDScriptParser::WhileNode *GDScriptParser::parse_while() {
 	is_continue_match = false;
 
 	n_while->loop = parse_suite(R"("while" block)");
+	complete_extents(n_while);
 
 	// Reset break/continue state.
 	can_break = could_break;
@@ -2176,6 +2232,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode
 		ERR_FAIL_V_MSG(nullptr, "Parser bug: parsing literal node without literal token.");
 	}
 	IdentifierNode *identifier = alloc_node<IdentifierNode>();
+	complete_extents(identifier);
 	identifier->name = previous.get_identifier();
 
 	if (current_suite != nullptr && current_suite->has_local(identifier->name)) {
@@ -2227,6 +2284,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_literal(ExpressionNode *p_
 	}
 
 	LiteralNode *literal = alloc_node<LiteralNode>();
+	complete_extents(literal);
 	literal->value = previous.literal;
 	return literal;
 }
@@ -2236,6 +2294,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_self(ExpressionNode *p_pre
 		push_error(R"(Cannot use "self" inside a static function.)");
 	}
 	SelfNode *self = alloc_node<SelfNode>();
+	complete_extents(self);
 	self->current_class = current_class;
 	return self;
 }
@@ -2243,6 +2302,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_self(ExpressionNode *p_pre
 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>();
+	complete_extents(constant);
 
 	switch (op_type) {
 		case GDScriptTokenizer::Token::CONST_PI:
@@ -2303,30 +2363,38 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_unary_operator(ExpressionN
 			}
 			break;
 		default:
+			complete_extents(operation);
 			return nullptr; // Unreachable.
 	}
+	complete_extents(operation);
 
 	return operation;
 }
 
 GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_not_in_operator(ExpressionNode *p_previous_operand, bool p_can_assign) {
 	// check that NOT is followed by IN by consuming it before calling parse_binary_operator which will only receive a plain IN
+	UnaryOpNode *operation = alloc_node<UnaryOpNode>();
+	reset_extents(operation, p_previous_operand);
+	update_extents(operation);
 	consume(GDScriptTokenizer::Token::IN, R"(Expected "in" after "not" in content-test operator.)");
 	ExpressionNode *in_operation = parse_binary_operator(p_previous_operand, p_can_assign);
-	UnaryOpNode *operation = alloc_node<UnaryOpNode>();
 	operation->operation = UnaryOpNode::OP_LOGIC_NOT;
 	operation->variant_op = Variant::OP_NOT;
 	operation->operand = in_operation;
+	complete_extents(operation);
 	return operation;
 }
 
 GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_operator(ExpressionNode *p_previous_operand, bool p_can_assign) {
 	GDScriptTokenizer::Token op = previous;
 	BinaryOpNode *operation = alloc_node<BinaryOpNode>();
+	reset_extents(operation, p_previous_operand);
+	update_extents(operation);
 
 	Precedence precedence = (Precedence)(get_rule(op.type)->precedence + 1);
 	operation->left_operand = p_previous_operand;
 	operation->right_operand = parse_precedence(precedence, false);
+	complete_extents(operation);
 
 	if (operation->right_operand == nullptr) {
 		push_error(vformat(R"(Expected expression after "%s" operator.")", op.get_name()));
@@ -2429,8 +2497,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_operator(Expression
 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;
+	reset_extents(operation, p_previous_operand);
+	update_extents(operation);
 
+	operation->true_expr = p_previous_operand;
 	operation->condition = parse_precedence(PREC_TERNARY, false);
 
 	if (operation->condition == nullptr) {
@@ -2445,6 +2515,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_ternary_operator(Expressio
 		push_error(R"(Expected expression after "else".)");
 	}
 
+	complete_extents(operation);
 	return operation;
 }
 
@@ -2497,6 +2568,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode
 	}
 
 	AssignmentNode *assignment = alloc_node<AssignmentNode>();
+	reset_extents(assignment, p_previous_operand);
+	update_extents(assignment);
+
 	make_completion_context(COMPLETION_ASSIGN, assignment);
 #ifdef DEBUG_ENABLED
 	bool has_operator = true;
@@ -2561,6 +2635,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode
 	if (assignment->assigned_value == nullptr) {
 		push_error(R"(Expected an expression after "=".)");
 	}
+	complete_extents(assignment);
 
 #ifdef DEBUG_ENABLED
 	if (source_variable != nullptr) {
@@ -2582,6 +2657,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_await(ExpressionNode *p_pr
 		push_error(R"(Expected signal or coroutine after "await".)");
 	}
 	await->to_await = element;
+	complete_extents(await);
 
 	if (current_function) { // Might be null in a getter or setter.
 		current_function->is_coroutine = true;
@@ -2610,6 +2686,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_array(ExpressionNode *p_pr
 	}
 	pop_multiline();
 	consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected closing "]" after array elements.)");
+	complete_extents(array);
 
 	return array;
 }
@@ -2701,6 +2778,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode
 	}
 	pop_multiline();
 	consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" after dictionary elements.)");
+	complete_extents(dictionary);
 
 	return dictionary;
 }
@@ -2718,6 +2796,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_grouping(ExpressionNode *p
 
 GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode *p_previous_operand, bool p_can_assign) {
 	SubscriptNode *attribute = alloc_node<SubscriptNode>();
+	reset_extents(attribute, p_previous_operand);
+	update_extents(attribute);
 
 	if (for_completion) {
 		bool is_builtin = false;
@@ -2737,17 +2817,21 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode *
 	attribute->base = p_previous_operand;
 
 	if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier after "." for attribute access.)")) {
+		complete_extents(attribute);
 		return attribute;
 	}
 
 	attribute->is_attribute = true;
 	attribute->attribute = parse_identifier();
 
+	complete_extents(attribute);
 	return attribute;
 }
 
 GDScriptParser::ExpressionNode *GDScriptParser::parse_subscript(ExpressionNode *p_previous_operand, bool p_can_assign) {
 	SubscriptNode *subscript = alloc_node<SubscriptNode>();
+	reset_extents(subscript, p_previous_operand);
+	update_extents(subscript);
 
 	make_completion_context(COMPLETION_SUBSCRIPT, subscript);
 
@@ -2760,15 +2844,19 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_subscript(ExpressionNode *
 
 	pop_multiline();
 	consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected "]" after subscription index.)");
+	complete_extents(subscript);
 
 	return subscript;
 }
 
 GDScriptParser::ExpressionNode *GDScriptParser::parse_cast(ExpressionNode *p_previous_operand, bool p_can_assign) {
 	CastNode *cast = alloc_node<CastNode>();
+	reset_extents(cast, p_previous_operand);
+	update_extents(cast);
 
 	cast->operand = p_previous_operand;
 	cast->cast_type = parse_type();
+	complete_extents(cast);
 
 	if (cast->cast_type == nullptr) {
 		push_error(R"(Expected type specifier after "as".)");
@@ -2780,6 +2868,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_cast(ExpressionNode *p_pre
 
 GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_previous_operand, bool p_can_assign) {
 	CallNode *call = alloc_node<CallNode>();
+	reset_extents(call, p_previous_operand);
 
 	if (previous.type == GDScriptTokenizer::Token::SUPER) {
 		// Super call.
@@ -2790,6 +2879,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
 			if (current_function == nullptr) {
 				push_error(R"(Cannot use implicit "super" call outside of a function.)");
 				pop_multiline();
+				complete_extents(call);
 				return nullptr;
 			}
 			if (current_function->identifier) {
@@ -2802,6 +2892,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
 			make_completion_context(COMPLETION_SUPER_METHOD, call, true);
 			if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after ".".)")) {
 				pop_multiline();
+				complete_extents(call);
 				return nullptr;
 			}
 			IdentifierNode *identifier = parse_identifier();
@@ -2858,6 +2949,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
 
 	pop_multiline();
 	consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after call arguments.)*");
+	complete_extents(call);
 
 	return call;
 }
@@ -2901,6 +2993,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p
 		if (previous.type == GDScriptTokenizer::Token::PERCENT) {
 			if (path_state != PATH_STATE_START && path_state != PATH_STATE_SLASH) {
 				push_error(R"("%" is only valid in the beginning of a node name (either after "$" or after "/"))");
+				complete_extents(get_node);
 				return nullptr;
 			}
 			get_node->full_path += "%";
@@ -2909,6 +3002,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p
 		} else if (previous.type == GDScriptTokenizer::Token::SLASH) {
 			if (path_state != PATH_STATE_START && path_state != PATH_STATE_NODE_NAME) {
 				push_error(R"("/" is only valid at the beginning of the path or after a node name.)");
+				complete_extents(get_node);
 				return nullptr;
 			}
 
@@ -2936,6 +3030,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p
 						break;
 				}
 				push_error(vformat(R"(Expected node path as string or identifier after "%s".)", previous_token));
+				complete_extents(get_node);
 				return nullptr;
 			}
 
@@ -2949,10 +3044,12 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p
 			path_state = PATH_STATE_NODE_NAME;
 		} else if (!check(GDScriptTokenizer::Token::SLASH) && !check(GDScriptTokenizer::Token::PERCENT)) {
 			push_error(vformat(R"(Unexpected "%s" in node path.)", current.get_name()));
+			complete_extents(get_node);
 			return nullptr;
 		}
 	} while (match(GDScriptTokenizer::Token::SLASH) || match(GDScriptTokenizer::Token::PERCENT));
 
+	complete_extents(get_node);
 	return get_node;
 }
 
@@ -2976,6 +3073,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_
 
 	pop_multiline();
 	consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after preload path.)*");
+	complete_extents(preload);
 
 	return preload;
 }
@@ -3025,6 +3123,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p
 	in_lambda = true;
 
 	function->body = parse_suite("lambda declaration", body, true);
+	complete_extents(function);
+	complete_extents(lambda);
 
 	pop_multiline();
 
@@ -3069,13 +3169,15 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) {
 	if (!match(GDScriptTokenizer::Token::IDENTIFIER)) {
 		if (match(GDScriptTokenizer::Token::VOID)) {
 			if (p_allow_void) {
-				TypeNode *void_type = alloc_node<TypeNode>();
+				complete_extents(type);
+				TypeNode *void_type = type;
 				return void_type;
 			} else {
 				push_error(R"("void" is only allowed for a function return type.)");
 			}
 		}
 		// Leave error message to the caller who knows the context.
+		complete_extents(type);
 		return nullptr;
 	}
 
@@ -3088,11 +3190,15 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) {
 		type->container_type = parse_type(false); // Don't allow void for array element type.
 		if (type->container_type == nullptr) {
 			push_error(R"(Expected type for collection after "[".)");
+			complete_extents(type);
 			type = nullptr;
 		} else if (type->container_type->container_type != nullptr) {
 			push_error("Nested typed collections are not supported.");
 		}
 		consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected closing "]" after collection type.)");
+		if (type != nullptr) {
+			complete_extents(type);
+		}
 		return type;
 	}
 
@@ -3105,6 +3211,7 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) {
 		}
 	}
 
+	complete_extents(type);
 	return type;
 }
 
@@ -3922,6 +4029,46 @@ GDScriptParser::DataType GDScriptParser::DataType::get_typed_container_type() co
 	return type;
 }
 
+void GDScriptParser::complete_extents(Node *p_node) {
+	while (!nodes_in_progress.is_empty() && nodes_in_progress.back()->get() != p_node) {
+		ERR_PRINT("Parser bug: Mismatch in extents tracking stack.");
+		nodes_in_progress.pop_back();
+	}
+	if (nodes_in_progress.is_empty()) {
+		ERR_PRINT("Parser bug: Extents tracking stack is empty.");
+	} else {
+		nodes_in_progress.pop_back();
+	}
+}
+
+void GDScriptParser::update_extents(Node *p_node) {
+	p_node->end_line = previous.end_line;
+	p_node->end_column = previous.end_column;
+	p_node->leftmost_column = MIN(p_node->leftmost_column, previous.leftmost_column);
+	p_node->rightmost_column = MAX(p_node->rightmost_column, previous.rightmost_column);
+}
+
+void GDScriptParser::reset_extents(Node *p_node, GDScriptTokenizer::Token p_token) {
+	p_node->start_line = p_token.start_line;
+	p_node->end_line = p_token.end_line;
+	p_node->start_column = p_token.start_column;
+	p_node->end_column = p_token.end_column;
+	p_node->leftmost_column = p_token.leftmost_column;
+	p_node->rightmost_column = p_token.rightmost_column;
+}
+
+void GDScriptParser::reset_extents(Node *p_node, Node *p_from) {
+	if (p_from == nullptr) {
+		return;
+	}
+	p_node->start_line = p_from->start_line;
+	p_node->end_line = p_from->end_line;
+	p_node->start_column = p_from->start_column;
+	p_node->end_column = p_from->end_column;
+	p_node->leftmost_column = p_from->leftmost_column;
+	p_node->rightmost_column = p_from->rightmost_column;
+}
+
 /*---------- PRETTY PRINT FOR DEBUG ----------*/
 
 #ifdef DEBUG_ENABLED

+ 8 - 7
modules/gdscript/gdscript_parser.h

@@ -1301,6 +1301,12 @@ private:
 	};
 	static ParseRule *get_rule(GDScriptTokenizer::Token::Type p_token_type);
 
+	List<Node *> nodes_in_progress;
+	void complete_extents(Node *p_node);
+	void update_extents(Node *p_node);
+	void reset_extents(Node *p_node, GDScriptTokenizer::Token p_token);
+	void reset_extents(Node *p_node, Node *p_from);
+
 	template <class T>
 	T *alloc_node() {
 		T *node = memnew(T);
@@ -1308,13 +1314,8 @@ private:
 		node->next = list;
 		list = node;
 
-		// TODO: Properly set positions for all nodes.
-		node->start_line = previous.start_line;
-		node->end_line = previous.end_line;
-		node->start_column = previous.start_column;
-		node->end_column = previous.end_column;
-		node->leftmost_column = previous.leftmost_column;
-		node->rightmost_column = previous.rightmost_column;
+		reset_extents(node, previous);
+		nodes_in_progress.push_back(node);
 
 		return node;
 	}