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

allow rest type arg for tuples #1800

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions ext/rbs_extension/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -966,13 +966,44 @@ static VALUE parse_simple(parserstate *state) {
range rg;
rg.start = state->current_token.range.start;
VALUE types = rb_ary_new();
VALUE rest_type = Qnil;
// parses type args with additional rest type arg
if (state->next_token.type != pRBRACKET) {
parse_type_list(state, pRBRACKET, types);
while (true) {
rb_ary_push(types, parse_type(state));

if (state->next_token.type == pCOMMA) {
parser_advance(state);
if (state->next_token.type == pRBRACKET) {
break;
} else if (state->next_token.type == pSTAR) {
parser_advance(state);
rest_type = parse_type(state);
if (state->next_token.type != pRBRACKET) {
raise_syntax_error(
state,
state->next_token,
"tuple end expected"
);
}
break;
}
} else {
if (state->next_token.type == pRBRACKET) {
break;
}
raise_syntax_error(
state,
state->next_token,
"comma delimited type list is expected"
);
}
}
}
parser_advance_assert(state, pRBRACKET);
rg.end = state->current_token.range.end;

return rbs_tuple(types, rbs_new_location(state->buffer, rg));
return rbs_tuple2(types, rbs_new_location(state->buffer, rg), rest_type);
}
case pAREF_OPR: {
return rbs_tuple(rb_ary_new(), rbs_new_location(state->buffer, state->current_token.range));
Expand Down
15 changes: 15 additions & 0 deletions ext/rbs_extension/ruby_objs.c
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,21 @@ VALUE rbs_tuple(VALUE types, VALUE location) {
);
}

VALUE rbs_tuple2(VALUE types, VALUE location, VALUE rest_type) {
VALUE args = rb_hash_new();
rb_hash_aset(args, ID2SYM(rb_intern("types")), types);
rb_hash_aset(args, ID2SYM(rb_intern("location")), location);
if (rest_type) {
rb_hash_aset(args, ID2SYM(rb_intern("rest_type")), rest_type);
}

return CLASS_NEW_INSTANCE(
RBS_Types_Tuple,
1,
&args
);
}

VALUE rbs_optional(VALUE type, VALUE location) {
VALUE args = rb_hash_new();
rb_hash_aset(args, ID2SYM(rb_intern("type")), type);
Expand Down
1 change: 1 addition & 0 deletions ext/rbs_extension/ruby_objs.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ VALUE rbs_optional(VALUE type, VALUE location);
VALUE rbs_proc(VALUE function, VALUE block, VALUE location, VALUE self_type);
VALUE rbs_record(VALUE fields, VALUE location);
VALUE rbs_tuple(VALUE types, VALUE location);
VALUE rbs_tuple2(VALUE types, VALUE location, VALUE rest_type);
VALUE rbs_type_name(VALUE namespace, VALUE name);
VALUE rbs_union(VALUE types, VALUE location);
VALUE rbs_variable(VALUE name, VALUE location);
Expand Down
15 changes: 11 additions & 4 deletions lib/rbs/types.rb
Original file line number Diff line number Diff line change
Expand Up @@ -433,15 +433,17 @@ def map_type(&block)

class Tuple
attr_reader :types
attr_reader :rest_type
attr_reader :location

def initialize(types:, location:)
def initialize(types:, location:, rest_type: nil)
@types = types
@rest_type = rest_type
@location = location
end

def ==(other)
other.is_a?(Tuple) && other.types == types
other.is_a?(Tuple) && other.types == types && other.rest_type == rest_type
end

alias eql? ==
Expand All @@ -455,29 +457,32 @@ def free_variables(set = Set.new)
types.each do |type|
type.free_variables set
end
rest_type&.free_variables
end
end

def to_json(state = _ = nil)
{ class: :tuple, types: types, location: location }.to_json(state)
{ class: :tuple, types: types, rest_type: rest_type, location: location }.to_json(state)
end

def sub(s)
self.class.new(types: types.map {|ty| ty.sub(s) },
rest_type: rest_type&.sub(s),
location: location)
end

def to_s(level = 0)
if types.empty?
"[ ]"
else
"[ #{types.join(", ")} ]"
"[ #{types.join(", ")}#{", *#{rest_type}" if rest_type} ]"
end
end

def each_type(&block)
if block
types.each(&block)
block.call(rest_type) if rest_type
else
enum_for :each_type
end
Expand All @@ -486,6 +491,7 @@ def each_type(&block)
def map_type_name(&block)
Tuple.new(
types: types.map {|type| type.map_type_name(&block) },
rest_type: rest_type.map_type_name(&block),
location: location
)
end
Expand All @@ -494,6 +500,7 @@ def map_type(&block)
if block
Tuple.new(
types: types.map {|type| yield type },
rest_type: (yield(rest_type) if rest_type),
location: location
)
else
Expand Down
1 change: 1 addition & 0 deletions test/rbs/schema_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ def test_type_schema
refute_type parse_type("Foo"), "alias"

assert_type parse_type("[Integer]"), "tuple"
assert_type parse_type("[Integer, *String]"), "tuple"
refute_type parse_type("Foo"), "tuple"

assert_type parse_type("{ id: Integer, name: String }"), "record"
Expand Down
9 changes: 9 additions & 0 deletions test/rbs/type_parsing_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,15 @@ def test_tuple
assert_equal [], type.types
assert_equal "[]", type.location.source
end

Parser.parse_type("[untyped, *untyped]").yield_self do |type|
assert_instance_of Types::Tuple, type
assert_equal [
Types::Bases::Any.new(location: nil),
], type.types
assert_equal Types::Bases::Any.new(location: nil), type.rest_type
assert_equal "[untyped, *untyped]", type.location.source
end
end

def test_union_intersection
Expand Down
Loading