Skip to content

Commit

Permalink
Disallow empty parameter and argument names (#11971)
Browse files Browse the repository at this point in the history
  • Loading branch information
HertzDevil committed May 17, 2022
1 parent 12de4e1 commit 8bdd9e5
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 0 deletions.
11 changes: 11 additions & 0 deletions spec/compiler/parser/parser_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ module Crystal
it_parses "def foo(x @var); end", Def.new("foo", [Arg.new("var", external_name: "x")], [Assign.new("@var".instance_var, "var".var)] of ASTNode)
it_parses "def foo(x @@var); end", Def.new("foo", [Arg.new("var", external_name: "x")], [Assign.new("@@var".class_var, "var".var)] of ASTNode)
assert_syntax_error "def foo(_ y); y; end"
assert_syntax_error "def foo(\"\" y); y; end", "external parameter name cannot be empty"

it_parses %(def foo("bar qux" y); y; end), Def.new("foo", args: [Arg.new("y", external_name: "bar qux")], body: "y".var)

Expand All @@ -360,6 +361,8 @@ module Crystal

it_parses "macro foo(**args)\n1\nend", Macro.new("foo", body: MacroLiteral.new("1\n"), double_splat: "args".arg)

assert_syntax_error "macro foo(\"\" y); end", "external parameter name cannot be empty"

assert_syntax_error "macro foo(x, *); 1; end", "named parameters must follow bare *"
assert_syntax_error "macro foo(**x, **y)", "only block parameter is allowed after double splat"
assert_syntax_error "macro foo(**x, y)", "only block parameter is allowed after double splat"
Expand Down Expand Up @@ -461,6 +464,8 @@ module Crystal
it_parses "foo(a: 1\n)", Call.new(nil, "foo", named_args: [NamedArgument.new("a", 1.int32)])
it_parses "foo(\na: 1,\n)", Call.new(nil, "foo", named_args: [NamedArgument.new("a", 1.int32)])

assert_syntax_error "foo(\"\": 1)", "named argument cannot have an empty name"

it_parses %(foo("foo bar": 1, "baz": 2)), Call.new(nil, "foo", named_args: [NamedArgument.new("foo bar", 1.int32), NamedArgument.new("baz", 2.int32)])
it_parses %(foo "foo bar": 1, "baz": 2), Call.new(nil, "foo", named_args: [NamedArgument.new("foo bar", 1.int32), NamedArgument.new("baz", 2.int32)])

Expand Down Expand Up @@ -620,6 +625,7 @@ module Crystal
it_parses "Foo(X: U, Y: V)", Generic.new("Foo".path, [] of ASTNode, named_args: [NamedArgument.new("X", "U".path), NamedArgument.new("Y", "V".path)])
assert_syntax_error "Foo(T, x: U)"
assert_syntax_error "Foo(x: T y: U)"
assert_syntax_error "Foo(\"\": T)", "named argument cannot have an empty name"

it_parses %(Foo("foo bar": U)), Generic.new("Foo".path, [] of ASTNode, named_args: [NamedArgument.new("foo bar", "U".path)])
it_parses %(Foo("foo": U, "bar": V)), Generic.new("Foo".path, [] of ASTNode, named_args: [NamedArgument.new("foo", "U".path), NamedArgument.new("bar", "V".path)])
Expand Down Expand Up @@ -884,6 +890,7 @@ module Crystal
it_parses "lib LibC\nfun SomeFun\nend", LibDef.new("LibC", [FunDef.new("SomeFun")] of ASTNode)

it_parses "fun foo(x : Int32) : Int64\nx\nend", FunDef.new("foo", [Arg.new("x", restriction: "Int32".path)], "Int64".path, body: "x".var)
assert_syntax_error "fun foo(Int32); end", "top-level fun parameter must have a name"
assert_syntax_error "fun Foo : Int64\nend"

it_parses "lib LibC; {{ 1 }}; end", LibDef.new("LibC", body: [MacroExpression.new(1.int32)] of ASTNode)
Expand Down Expand Up @@ -1168,6 +1175,8 @@ module Crystal
it_parses %({"foo": 1}), NamedTupleLiteral.new([NamedTupleLiteral::Entry.new("foo", 1.int32)])
it_parses %({"foo": 1, "bar": 2}), NamedTupleLiteral.new([NamedTupleLiteral::Entry.new("foo", 1.int32), NamedTupleLiteral::Entry.new("bar", 2.int32)])

assert_syntax_error "{\"\": 1}", "named tuple name cannot be empty"
assert_syntax_error "{a: 1, \"\": 2}", "named tuple name cannot be empty"
assert_syntax_error "{a: 1, a: 2}", "duplicated key: a"
assert_syntax_error "{a[0]: 1}", "expecting token '=>', not ':'"
assert_syntax_error "{a[]: 1}", "expecting token '=>', not ':'"
Expand Down Expand Up @@ -1429,6 +1438,8 @@ module Crystal
it_parses "@[Foo(\n1, foo: 2\n)]", Annotation.new("Foo".path, [1.int32] of ASTNode, [NamedArgument.new("foo", 2.int32)])
it_parses "@[Foo::Bar]", Annotation.new(Path.new(["Foo", "Bar"]))

assert_syntax_error "@[Foo(\"\": 1)]"

it_parses "lib LibC\n@[Bar]; end", LibDef.new("LibC", Annotation.new("Bar".path))

it_parses "Foo(_)", Generic.new("Foo".path, [Underscore.new] of ASTNode)
Expand Down
27 changes: 27 additions & 0 deletions src/compiler/crystal/syntax/parser.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2423,6 +2423,7 @@ module Crystal
next_token_skip_space_or_newline
end

key_location = @token.location
first_key = parse_op_assign_no_control
first_key = Splat.new(first_key).at(location) if first_is_splat
case @token.type
Expand All @@ -2439,6 +2440,9 @@ module Crystal
unless allow_of
raise "can't use named tuple syntax for Hash-like literal, use '=>'", @token
end
if first_key.value.empty?
raise "named tuple name cannot be empty", key_location
end
return parse_named_tuple(location, first_key.value)
else
check :OP_EQ_GT
Expand Down Expand Up @@ -2603,6 +2607,7 @@ module Crystal
next_token_skip_space_or_newline

while !@token.type.op_rcurly?
key_location = @token.location
key = @token.value.to_s
if named_tuple_start?
next_token_never_a_symbol
Expand All @@ -2612,6 +2617,10 @@ module Crystal
raise "expected '}' or named tuple name, not #{@token}", @token
end

if key.empty?
raise "named tuple name cannot be empty", key_location
end

if @token.type.space?
raise "space not allowed between named argument name and ':'"
end
Expand Down Expand Up @@ -3892,6 +3901,7 @@ module Crystal
invalid_internal_name = nil

if allow_external_name && (@token.type.ident? || string_literal_start?)
name_location = @token.location
if @token.type.ident?
if @token.keyword? && invalid_internal_name?(@token.value)
invalid_internal_name = @token.dup
Expand All @@ -3902,6 +3912,11 @@ module Crystal
external_name = parse_string_without_interpolation("external name")
found_string_literal = true
end

if external_name.empty?
raise "external parameter name cannot be empty", name_location
end

found_space = @token.type.space? || @token.type.newline?
skip_space
do_next_token = false
Expand Down Expand Up @@ -4672,6 +4687,10 @@ module Crystal
end
end

if name.empty?
raise "named argument cannot have an empty name", location
end

if named_args.any? { |arg| arg.name == name }
raise "duplicated named argument: #{name}", location
end
Expand Down Expand Up @@ -5022,6 +5041,7 @@ module Crystal
named_args = [] of NamedArgument

while @token.type != end_token
name_location = @token.location
if named_tuple_start?
name = @token.value.to_s
next_token
Expand All @@ -5031,6 +5051,10 @@ module Crystal
raise "expected '#{end_token}' or named argument, not #{@token}", @token
end

if name.empty?
raise "named argument cannot have an empty name", name_location
end

if named_args.any? { |arg| arg.name == name }
raise "duplicated key: #{name}", @token
end
Expand Down Expand Up @@ -5627,6 +5651,9 @@ module Crystal

push_var_name arg_name if require_body
else
if top_level
raise "top-level fun parameter must have a name", @token
end
arg_type = parse_union_type
args << Arg.new("", nil, arg_type).at(arg_type.location)
end
Expand Down

0 comments on commit 8bdd9e5

Please sign in to comment.