Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parser: Add missing locations of various AST nodes #13452

Merged
merged 10 commits into from
May 16, 2023
77 changes: 77 additions & 0 deletions spec/compiler/parser/parser_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2584,5 +2584,82 @@ module Crystal
exps.expressions[1].location.not_nil!.line_number.should eq(7)
end
end

it "sets correct location of parameter in proc literal" do
source = "->(foo : Bar, baz) { }"
args = Parser.parse(source).as(ProcLiteral).def.args
node_source(source, args[0]).should eq("foo : Bar")
node_source(source, args[1]).should eq("baz")
end

it "sets correct location of splat in multiple assignment" do
source = "*foo, bar = 1, 2"
node = Parser.parse(source).as(MultiAssign).targets[0]
node_source(source, node).should eq("*foo")

source = "foo, *bar = 1, 2"
node = Parser.parse(source).as(MultiAssign).targets[1]
node_source(source, node).should eq("*bar")
end

it "sets correct location of tuple type" do
source = "x : {Foo, Bar}"
node = Parser.parse(source).as(TypeDeclaration).declared_type
node_source(source, node).should eq("{Foo, Bar}")
end

it "sets correct location of named tuple type" do
source = "x : {foo: Bar}"
node = Parser.parse(source).as(TypeDeclaration).declared_type
node_source(source, node).should eq("{foo: Bar}")
end

it "sets correct location of argument in named tuple type" do
source = "x : {foo: Bar}"
node = Parser.parse(source).as(TypeDeclaration).declared_type.as(Generic).named_args.not_nil!.first
node_source(source, node).should eq("foo: Bar")
end

it "sets correct location of instance variable in proc pointer" do
source = "->@foo.x"
node = Parser.parse(source).as(ProcPointer).obj.not_nil!
node_source(source, node).should eq("@foo")
end

it "sets correct location of instance variable in proc pointer" do
source = "->@@foo.x"
node = Parser.parse(source).as(ProcPointer).obj.not_nil!
node_source(source, node).should eq("@@foo")
end

it "sets correct location of annotation on method parameter" do
source = "def x(@[Foo] y) end"
node = Parser.parse(source).as(Def).args.first.parsed_annotations.not_nil!.first
node_source(source, node).should eq("@[Foo]")
end

it "sets correct location of annotation in lib" do
source = "lib X; @[Foo]; end"
node = Parser.parse(source).as(LibDef).body
node_source(source, node).should eq("@[Foo]")
end

it "sets correct location of annotation in enum" do
source = "enum X; @[Foo]; end"
node = Parser.parse(source).as(EnumDef).members.first
node_source(source, node).should eq("@[Foo]")
end

it "sets correct location of private method in enum" do
source = "enum X; private def foo; end; end"
node = Parser.parse(source).as(EnumDef).members.first
node_source(source, node).should eq("private def foo; end")
end

it "sets correct location of protected macro in enum" do
source = "enum X; protected macro foo; end; end"
node = Parser.parse(source).as(EnumDef).members.first
node_source(source, node).should eq("protected macro foo; end")
end
end
end
12 changes: 12 additions & 0 deletions src/compiler/crystal/syntax/ast.cr
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,10 @@ module Crystal
NamedArgument.new(name, value.clone)
end

def end_location
@end_location || value.end_location
end

def_equals_and_hash name, value
end

Expand Down Expand Up @@ -1155,6 +1159,10 @@ module Crystal
@exp.accept visitor
end

def end_location
@end_location || @exp.end_location
end

def_equals_and_hash exp
end

Expand Down Expand Up @@ -1224,6 +1232,10 @@ module Crystal
VisibilityModifier.new(@modifier, @exp.clone)
end

def end_location
@end_location || @exp.end_location
end

def_equals_and_hash modifier, exp
end

Expand Down
58 changes: 32 additions & 26 deletions src/compiler/crystal/syntax/parser.cr
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ module Crystal
location = @token.location

if @token.type.op_star?
lhs_splat_index = 0
lhs_splat = {index: 0, location: @token.location}
next_token_skip_space
end

Expand All @@ -147,17 +147,17 @@ module Crystal
case @token.type
when .op_comma?
unless last_is_target
unexpected_token if lhs_splat_index
unexpected_token if lhs_splat
raise "Multiple assignment is not allowed for constants" if last.is_a?(Path)
unexpected_token
end
when .newline?, .op_semicolon?
unexpected_token if lhs_splat_index && !multi_assign_middle?(last)
return last unless lhs_splat_index
unexpected_token if lhs_splat && !multi_assign_middle?(last)
return last unless lhs_splat
else
if end_token?
unexpected_token if lhs_splat_index && !multi_assign_middle?(last)
return last unless lhs_splat_index
unexpected_token if lhs_splat && !multi_assign_middle?(last)
return last unless lhs_splat
else
unexpected_token
end
Expand All @@ -178,8 +178,8 @@ module Crystal

next_token_skip_space_or_newline
if @token.type.op_star?
raise "splat assignment already specified" if lhs_splat_index
lhs_splat_index = i
raise "splat assignment already specified" if lhs_splat
lhs_splat = {index: i, location: @token.location}
next_token_skip_space
end

Expand Down Expand Up @@ -217,13 +217,15 @@ module Crystal
raise "BUG: multi_assign index expression can only be Assign or Call"
end

if lhs_splat_index
targets[lhs_splat_index] = Splat.new(targets[lhs_splat_index])
if lhs_splat
lhs_splat_location = lhs_splat[:location]
lhs_splat_index = lhs_splat[:index]
targets[lhs_splat_index] = Splat.new(targets[lhs_splat_index]).at(lhs_splat_location)
end

values.concat exps[assign_index + 1..-1]
if values.size != 1
if lhs_splat_index
if lhs_splat
raise "Multiple assignment count mismatch", location if targets.size - 1 > values.size
else
raise "Multiple assignment count mismatch", location if targets.size != values.size
Expand Down Expand Up @@ -1340,6 +1342,7 @@ module Crystal

def parse_annotation
doc = @token.doc
location = @token.location

next_token_skip_space
name = parse_path
Expand Down Expand Up @@ -1369,10 +1372,11 @@ module Crystal
end
end
check :OP_RSQUARE
end_location = token_end_location
@wants_regex = false
next_token_skip_space

ann = Annotation.new(name, args, named_args)
ann = Annotation.new(name, args, named_args).at(location).at_end(end_location)
ann.doc = doc
ann
end
Expand Down Expand Up @@ -1880,10 +1884,9 @@ module Crystal
if @token.type.op_lparen?
next_token_skip_space_or_newline
while !@token.type.op_rparen?
param_location = @token.location
param = parse_fun_literal_param.at(param_location)
param = parse_fun_literal_param
if params.any? &.name.==(param.name)
raise "duplicated proc literal parameter name: #{param.name}", param_location
raise "duplicated proc literal parameter name: #{param.name}", param.location.not_nil!
end

params << param
Expand Down Expand Up @@ -1950,12 +1953,15 @@ module Crystal

def parse_fun_literal_param
name = check_ident
location = @token.location
end_location = token_end_location
next_token_skip_space_or_newline

if @token.type.op_colon?
next_token_skip_space_or_newline

type = parse_bare_proc_type
end_location = type.end_location
end

if @token.type.op_comma?
Expand All @@ -1965,7 +1971,7 @@ module Crystal
check :OP_RPAREN
end

Arg.new name, restriction: type
Arg.new(name, restriction: type).at(location).at_end(end_location)
end

def parse_fun_pointer
Expand Down Expand Up @@ -2005,15 +2011,15 @@ module Crystal
name = "#{name}=" if equals_sign
when .instance_var?
raise "ProcPointer of instance variable cannot be global", location if global
obj = InstanceVar.new(@token.value.to_s)
obj = InstanceVar.new(@token.value.to_s).at(location).at_end(token_end_location)
next_token_skip_space
check :OP_PERIOD
name = consume_def_or_macro_name
equals_sign, end_location = consume_def_equals_sign_skip_space
name = "#{name}=" if equals_sign
when .class_var?
raise "ProcPointer of class variable cannot be global", location if global
obj = ClassVar.new(@token.value.to_s)
obj = ClassVar.new(@token.value.to_s).at(location).at_end(token_end_location)
next_token_skip_space
check :OP_PERIOD
name = consume_def_or_macro_name
Expand Down Expand Up @@ -4977,8 +4983,9 @@ module Crystal
type = make_tuple_type parse_union_types(:OP_RCURLY, allow_splats: true)
end
check :OP_RCURLY
end_location = token_end_location
next_token_skip_space
type
type.at(location).at_end(end_location)
when .op_minus_gt?
parse_proc_type_output(nil, location)
when .op_lparen?
Expand Down Expand Up @@ -5153,7 +5160,7 @@ module Crystal
type = parse_bare_proc_type
skip_space

named_args << NamedArgument.new(name, type)
named_args << NamedArgument.new(name, type).at(name_location)

if @token.type.op_comma?
next_token_skip_space_or_newline
Expand Down Expand Up @@ -5396,7 +5403,7 @@ module Crystal
next_token_skip_space
exp = parse_op_assign

modifier = VisibilityModifier.new(modifier, exp).at(location).at_end(exp)
modifier = VisibilityModifier.new(modifier, exp).at(location)
modifier.doc = doc
exp.doc = doc
modifier
Expand Down Expand Up @@ -6027,9 +6034,9 @@ module Crystal
private def parse_enum_body_expressions
members = [] of ASTNode
until end_token?
location = @token.location
case @token.type
when .const?
location = @token.location
constant_name = @token.value.to_s
member_doc = @token.doc

Expand Down Expand Up @@ -6075,17 +6082,17 @@ module Crystal
case @token.value
when Keyword::DEF
member = parse_def.at(def_location)
member = VisibilityModifier.new(visibility, member) if visibility
member = VisibilityModifier.new(visibility, member).at(location) if visibility
members << member
when Keyword::MACRO
member = parse_macro.at(def_location)
member = VisibilityModifier.new(visibility, member) if visibility
member = VisibilityModifier.new(visibility, member).at(location) if visibility
members << member
else
unexpected_token
end
when .class_var?
class_var = ClassVar.new(@token.value.to_s).at(@token.location)
class_var = ClassVar.new(@token.value.to_s).at(location)

next_token_skip_space
check :OP_EQ
Expand All @@ -6096,7 +6103,6 @@ module Crystal
when .op_lcurly_lcurly?
members << parse_percent_macro_expression
when .op_lcurly_percent?
location = @token.location
members << parse_percent_macro_control.at(location)
when .op_at_lsquare?
members << parse_annotation
Expand Down
Loading