Skip to content

Commit beddc99

Browse files
authored
Merge pull request #2457 from Shopify/at-parse-type-params
Expose a method to parse type parameters
2 parents 91ab0d7 + fcc18af commit beddc99

File tree

6 files changed

+154
-0
lines changed

6 files changed

+154
-0
lines changed

ext/rbs_extension/main.c

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,58 @@ static VALUE rbsparser_parse_signature(VALUE self, VALUE buffer, VALUE start_pos
272272
return result;
273273
}
274274

275+
276+
struct parse_type_params_arg {
277+
VALUE buffer;
278+
rb_encoding *encoding;
279+
rbs_parser_t *parser;
280+
VALUE module_type_params;
281+
};
282+
283+
static VALUE parse_type_params_try(VALUE a) {
284+
struct parse_type_params_arg *arg = (struct parse_type_params_arg *)a;
285+
rbs_parser_t *parser = arg->parser;
286+
287+
if (parser->next_token.type == pEOF) {
288+
return Qnil;
289+
}
290+
291+
rbs_node_list_t *params = NULL;
292+
rbs_parse_type_params(parser, arg->module_type_params, &params);
293+
294+
raise_error_if_any(parser, arg->buffer);
295+
296+
rbs_translation_context_t ctx = rbs_translation_context_create(
297+
&parser->constant_pool,
298+
arg->buffer,
299+
arg->encoding
300+
);
301+
302+
return rbs_node_list_to_ruby_array(ctx, params);
303+
}
304+
305+
306+
static VALUE rbsparser_parse_type_params(VALUE self, VALUE buffer, VALUE start_pos, VALUE end_pos, VALUE module_type_params) {
307+
VALUE string = rb_funcall(buffer, rb_intern("content"), 0);
308+
StringValue(string);
309+
rb_encoding *encoding = rb_enc_get(string);
310+
311+
rbs_parser_t *parser = alloc_parser_from_buffer(buffer, FIX2INT(start_pos), FIX2INT(end_pos));
312+
struct parse_type_params_arg arg = {
313+
.buffer = buffer,
314+
.encoding = encoding,
315+
.parser = parser,
316+
.module_type_params = module_type_params
317+
};
318+
319+
VALUE result = rb_ensure(parse_type_params_try, (VALUE)&arg, ensure_free_parser, (VALUE)parser);
320+
321+
RB_GC_GUARD(string);
322+
323+
return result;
324+
}
325+
326+
275327
static VALUE parse_inline_leading_annotation_try(VALUE a) {
276328
struct parse_type_arg *arg = (struct parse_type_arg *) a;
277329
rbs_parser_t *parser = arg->parser;
@@ -391,6 +443,7 @@ void rbs__init_parser(void) {
391443
rb_define_singleton_method(RBS_Parser, "_parse_type", rbsparser_parse_type, 5);
392444
rb_define_singleton_method(RBS_Parser, "_parse_method_type", rbsparser_parse_method_type, 5);
393445
rb_define_singleton_method(RBS_Parser, "_parse_signature", rbsparser_parse_signature, 3);
446+
rb_define_singleton_method(RBS_Parser, "_parse_type_params", rbsparser_parse_type_params, 4);
394447
rb_define_singleton_method(RBS_Parser, "_parse_inline_leading_annotation", rbsparser_parse_inline_leading_annotation, 4);
395448
rb_define_singleton_method(RBS_Parser, "_parse_inline_trailing_annotation", rbsparser_parse_inline_trailing_annotation, 4);
396449
rb_define_singleton_method(RBS_Parser, "_lex", rbsparser_lex, 2);

include/rbs/parser.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ bool rbs_parse_type(rbs_parser_t *parser, rbs_node_t **type);
130130
bool rbs_parse_method_type(rbs_parser_t *parser, rbs_method_type_t **method_type);
131131
bool rbs_parse_signature(rbs_parser_t *parser, rbs_signature_t **signature);
132132

133+
bool rbs_parse_type_params(rbs_parser_t *parser, bool module_type_params, rbs_node_list_t **params);
134+
133135
/**
134136
* Parse an inline leading annotation from a string.
135137
*

lib/rbs/parser_aux.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ def self.parse_signature(source)
3535
[buf, dirs, decls]
3636
end
3737

38+
def self.parse_type_params(source, module_type_params: true)
39+
buf = buffer(source)
40+
_parse_type_params(buf, 0, buf.last_position, module_type_params)
41+
end
42+
3843
def self.magic_comment(buf)
3944
start_pos = 0
4045

sig/parser.rbs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,24 @@ module RBS
6868
#
6969
def self.parse_signature: (Buffer | String) -> [Buffer, Array[AST::Directives::t], Array[AST::Declarations::t]]
7070

71+
# Parse a list of type parameters and return it
72+
#
73+
# ```ruby
74+
# RBS::Parser.parse_type_params("") # => nil
75+
# RBS::Parser.parse_type_params("[U, V]") # => `[:U, :V]`
76+
# RBS::Parser.parse_type_params("[in U, V < Integer]") # => `[:U, :V]`
77+
# ```
78+
#
79+
# When `module_type_params` is `false`, an error is raised if `unchecked`, `in` or `out` are used.
80+
#
81+
# ```ruby
82+
# RBS::Parser.parse_type_params("[unchecked U]", module_type_params: false) # => Raises an error
83+
# RBS::Parser.parse_type_params("[out U]", module_type_params: false) # => Raises an error
84+
# RBS::Parser.parse_type_params("[in U]", module_type_params: false) # => Raises an error
85+
# ```
86+
#
87+
def self.parse_type_params: (Buffer | String, ?module_type_params: bool) -> Array[AST::TypeParam]
88+
7189
# Returns the magic comment from the buffer
7290
#
7391
def self.magic_comment: (Buffer) -> AST::Directives::ResolveTypeNames?
@@ -104,6 +122,8 @@ module RBS
104122

105123
def self._parse_signature: (Buffer, Integer start_pos, Integer end_pos) -> [Array[AST::Directives::t], Array[AST::Declarations::t]]
106124

125+
def self._parse_type_params: (Buffer, Integer start_pos, Integer end_pos, bool module_type_params) -> Array[AST::TypeParam]
126+
107127
def self._lex: (Buffer, Integer end_pos) -> Array[[Symbol, Location[untyped, untyped]]]
108128

109129
def self._parse_inline_leading_annotation: (Buffer, Integer start_pos, Integer end_pos, Array[Symbol] variables) -> AST::Ruby::Annotations::leading_annotation

src/parser.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3265,6 +3265,26 @@ bool rbs_parse_signature(rbs_parser_t *parser, rbs_signature_t **signature) {
32653265
return true;
32663266
}
32673267

3268+
bool rbs_parse_type_params(rbs_parser_t *parser, bool module_type_params, rbs_node_list_t **params) {
3269+
if (parser->next_token.type != pLBRACKET) {
3270+
rbs_parser_set_error(parser, parser->next_token, true, "expected a token `pLBRACKET`");
3271+
return false;
3272+
}
3273+
3274+
rbs_range_t rg = NULL_RANGE;
3275+
rbs_parser_push_typevar_table(parser, true);
3276+
bool res = parse_type_params(parser, &rg, module_type_params, params);
3277+
rbs_parser_push_typevar_table(parser, false);
3278+
3279+
rbs_parser_advance(parser);
3280+
if (parser->current_token.type != pEOF) {
3281+
rbs_parser_set_error(parser, parser->current_token, true, "expected a token `%s`", rbs_token_type_str(pEOF));
3282+
return false;
3283+
}
3284+
3285+
return res;
3286+
}
3287+
32683288
id_table *alloc_empty_table(rbs_allocator_t *allocator) {
32693289
id_table *table = rbs_allocator_alloc(allocator, id_table);
32703290

test/rbs/parser_test.rb

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -834,6 +834,60 @@ def test_proc__untyped_function
834834
end
835835
end
836836

837+
def test_parse_type_params
838+
RBS::Parser.parse_type_params(buffer("[T]")).tap do |params|
839+
assert_equal 1, params.size
840+
assert_equal :T, params[0].name
841+
assert_nil params[0].upper_bound
842+
end
843+
844+
RBS::Parser.parse_type_params(buffer("[T < Integer, U = String]")).tap do |params|
845+
assert_equal 2, params.size
846+
assert_equal :T, params[0].name
847+
assert_equal "Integer", params[0].upper_bound.to_s
848+
assert_equal :U, params[1].name
849+
assert_equal "String", params[1].default_type.to_s
850+
end
851+
852+
RBS::Parser.parse_type_params(buffer("[T, in U, out V]")).tap do |params|
853+
assert_equal 3, params.size
854+
assert_equal :T, params[0].name
855+
assert_equal "invariant", params[0].variance.to_s
856+
assert_equal :U, params[1].name
857+
assert_equal "contravariant", params[1].variance.to_s
858+
assert_equal :V, params[2].name
859+
assert_equal "covariant", params[2].variance.to_s
860+
end
861+
862+
RBS::Parser.parse_type_params(buffer("[T, unchecked U, unchecked out V = Integer]")).tap do |params|
863+
assert_equal 3, params.size
864+
assert_equal :T, params[0].name
865+
refute params[0].unchecked?
866+
assert_equal :U, params[1].name
867+
assert params[1].unchecked?
868+
assert_equal :V, params[2].name
869+
assert params[2].unchecked?
870+
assert_equal "covariant", params[2].variance.to_s
871+
assert_equal "Integer", params[2].default_type.to_s
872+
end
873+
874+
assert_raises RBS::ParsingError do
875+
RBS::Parser.parse_type_params(buffer("[]"))
876+
end
877+
878+
assert_raises RBS::ParsingError do
879+
RBS::Parser.parse_type_params(buffer("[T]A"))
880+
end
881+
882+
assert_raises RBS::ParsingError do
883+
RBS::Parser.parse_type_params(buffer("[in T]"), module_type_params: false)
884+
end
885+
886+
assert_raises RBS::ParsingError do
887+
RBS::Parser.parse_type_params(buffer("[unchecked T]"), module_type_params: false)
888+
end
889+
end
890+
837891
def test__lex
838892
content = <<~RBS
839893
# LineComment

0 commit comments

Comments
 (0)