Skip to content

Commit b140e0f

Browse files
authored
Merge pull request #2502 from Shopify/Add_type_arguments_support_to_singleton_types
Add type arguments support to singleton types
2 parents c3337ad + 0f845be commit b140e0f

12 files changed

Lines changed: 230 additions & 86 deletions

File tree

config.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,8 +573,11 @@ nodes:
573573
fields:
574574
- name: name
575575
c_type: rbs_type_name
576+
- name: args
577+
c_type: rbs_node_list
576578
locations:
577579
- required: name
580+
- optional: args
578581
- name: RBS::Types::Function
579582
rust_name: FunctionTypeNode
580583
expose_location: false

docs/syntax.md

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@
33
## Types
44

55
```markdown
6-
_type_ ::= _class-name_ _type-arguments_ (Class instance type)
7-
| _interface-name_ _type-arguments_ (Interface type)
8-
| _alias-name_ _type-arguments_ (Alias type)
9-
| `singleton(` _class-name_ `)` (Class singleton type)
10-
| _literal_ (Literal type)
11-
| _type_ `|` _type_ (Union type)
12-
| _type_ `&` _type_ (Intersection type)
13-
| _type_ `?` (Optional type)
14-
| `{` _record-name_ `:` _type_ `,` etc. `}` (Record type)
15-
| `[]` | `[` _type_ `,` etc. `]` (Tuples)
16-
| _type-variable_ (Type variables)
6+
_type_ ::= _class-name_ _type-arguments_ (Class instance type)
7+
| _interface-name_ _type-arguments_ (Interface type)
8+
| _alias-name_ _type-arguments_ (Alias type)
9+
| `singleton(` _class-name_ `)` _type-arguments_ (Class singleton type)
10+
| _literal_ (Literal type)
11+
| _type_ `|` _type_ (Union type)
12+
| _type_ `&` _type_ (Intersection type)
13+
| _type_ `?` (Optional type)
14+
| `{` _record-name_ `:` _type_ `,` etc. `}` (Record type)
15+
| `[]` | `[` _type_ `,` etc. `]` (Tuples)
16+
| _type-variable_ (Type variables)
1717
| `self`
1818
| `instance`
1919
| `class`
@@ -85,7 +85,8 @@ Class singleton type denotes _the type of a singleton object of a class_.
8585

8686
```rbs
8787
singleton(String)
88-
singleton(::Hash) # Class singleton type cannot be parametrized.
88+
singleton(::Hash) # Class singleton type
89+
singleton(Array)[String] # Class singleton type with type application
8990
```
9091

9192
### Literal type

ext/rbs_extension/ast_translation.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1229,10 +1229,12 @@ VALUE rbs_struct_to_ruby_value(rbs_translation_context_t ctx, rbs_node_t *instan
12291229
VALUE h = rb_hash_new();
12301230
VALUE location = rbs_location_range_to_ruby_location(ctx, node->base.location);
12311231
rbs_loc *loc = rbs_check_location(location);
1232-
rbs_loc_legacy_alloc_children(loc, 1);
1232+
rbs_loc_legacy_alloc_children(loc, 2);
12331233
rbs_loc_legacy_add_required_child(loc, rb_intern("name"), (rbs_loc_range) { .start = node->name_range.start_char, .end = node->name_range.end_char });
1234+
rbs_loc_legacy_add_optional_child(loc, rb_intern("args"), (rbs_loc_range) { .start = node->args_range.start_char, .end = node->args_range.end_char });
12341235
rb_hash_aset(h, ID2SYM(rb_intern("location")), location);
12351236
rb_hash_aset(h, ID2SYM(rb_intern("name")), rbs_struct_to_ruby_value(ctx, (rbs_node_t *) node->name)); // rbs_type_name
1237+
rb_hash_aset(h, ID2SYM(rb_intern("args")), rbs_node_list_to_ruby_array(ctx, node->args));
12361238

12371239
return CLASS_NEW_INSTANCE(
12381240
RBS_Types_ClassSingleton,

include/rbs/ast.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -772,8 +772,10 @@ typedef struct rbs_types_class_singleton {
772772
rbs_node_t base;
773773

774774
struct rbs_type_name *name;
775+
struct rbs_node_list *args;
775776

776777
rbs_location_range name_range; /* Required */
778+
rbs_location_range args_range; /* Optional */
777779
} rbs_types_class_singleton_t;
778780

779781
typedef struct rbs_types_function {
@@ -947,7 +949,7 @@ rbs_types_bases_top_t *rbs_types_bases_top_new(rbs_allocator_t *allocator, rbs_l
947949
rbs_types_bases_void_t *rbs_types_bases_void_new(rbs_allocator_t *allocator, rbs_location_range location);
948950
rbs_types_block_t *rbs_types_block_new(rbs_allocator_t *allocator, rbs_location_range location, rbs_node_t *type, bool required, rbs_node_t *self_type);
949951
rbs_types_class_instance_t *rbs_types_class_instance_new(rbs_allocator_t *allocator, rbs_location_range location, rbs_type_name_t *name, rbs_node_list_t *args, rbs_location_range name_range);
950-
rbs_types_class_singleton_t *rbs_types_class_singleton_new(rbs_allocator_t *allocator, rbs_location_range location, rbs_type_name_t *name, rbs_location_range name_range);
952+
rbs_types_class_singleton_t *rbs_types_class_singleton_new(rbs_allocator_t *allocator, rbs_location_range location, rbs_type_name_t *name, rbs_node_list_t *args, rbs_location_range name_range);
951953
rbs_types_function_t *rbs_types_function_new(rbs_allocator_t *allocator, rbs_location_range location, rbs_node_list_t *required_positionals, rbs_node_list_t *optional_positionals, rbs_node_t *rest_positionals, rbs_node_list_t *trailing_positionals, rbs_hash_t *required_keywords, rbs_hash_t *optional_keywords, rbs_node_t *rest_keywords, rbs_node_t *return_type);
952954
rbs_types_function_param_t *rbs_types_function_param_new(rbs_allocator_t *allocator, rbs_location_range location, rbs_node_t *type, rbs_ast_symbol_t *name);
953955
rbs_types_interface_t *rbs_types_interface_new(rbs_allocator_t *allocator, rbs_location_range location, rbs_type_name_t *name, rbs_node_list_t *args, rbs_location_range name_range);

lib/rbs/types.rb

Lines changed: 62 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -199,58 +199,6 @@ def with_nonreturn_void?
199199
end
200200
end
201201

202-
class ClassSingleton
203-
attr_reader :name
204-
attr_reader :location
205-
206-
def initialize(name:, location:)
207-
@name = name
208-
@location = location
209-
end
210-
211-
def ==(other)
212-
other.is_a?(ClassSingleton) && other.name == name
213-
end
214-
215-
alias eql? ==
216-
217-
def hash
218-
self.class.hash ^ name.hash
219-
end
220-
221-
include NoFreeVariables
222-
include NoSubst
223-
224-
def to_json(state = nil)
225-
{ class: :class_singleton, name: name, location: location }.to_json(state)
226-
end
227-
228-
def to_s(level = 0)
229-
"singleton(#{name})"
230-
end
231-
232-
include EmptyEachType
233-
234-
def map_type_name(&)
235-
ClassSingleton.new(
236-
name: yield(name, location, self),
237-
location: location
238-
)
239-
end
240-
241-
def has_self_type?
242-
false
243-
end
244-
245-
def has_classish_type?
246-
false
247-
end
248-
249-
def with_nonreturn_void?
250-
false
251-
end
252-
end
253-
254202
module Application
255203
attr_reader :name
256204
attr_reader :args
@@ -309,6 +257,68 @@ def with_nonreturn_void?
309257
end
310258
end
311259

260+
class ClassSingleton
261+
attr_reader :location
262+
263+
include Application
264+
265+
def initialize(name:, location:, args: [])
266+
@name = name
267+
@location = location
268+
@args = args
269+
end
270+
271+
def ==(other)
272+
other.is_a?(ClassSingleton) && other.name == name && other.args == args
273+
end
274+
275+
alias eql? ==
276+
277+
def hash
278+
self.class.hash ^ name.hash ^ args.hash
279+
end
280+
281+
def sub(s)
282+
return self if s.empty?
283+
284+
self.class.new(name: name,
285+
args: args.map {|ty| ty.sub(s) },
286+
location: location)
287+
end
288+
289+
def to_json(state = _ = nil)
290+
{ class: :class_singleton, name: name, args: args, location: location }.to_json(state)
291+
end
292+
293+
def to_s(level = 0)
294+
if args.empty?
295+
"singleton(#{name})"
296+
else
297+
"singleton(#{name})[#{args.join(", ")}]"
298+
end
299+
end
300+
301+
def map_type_name(&block)
302+
ClassSingleton.new(
303+
name: yield(name, location, self),
304+
args: args.map {|type| type.map_type_name(&block) },
305+
location: location
306+
)
307+
end
308+
309+
def map_type(&block)
310+
if block
311+
ClassSingleton.new(
312+
name: name,
313+
args: args.map {|type| yield type },
314+
location: location
315+
)
316+
else
317+
enum_for :map_type
318+
end
319+
end
320+
end
321+
312322
class Interface
313323
attr_reader :location
314324

lib/rbs/unit_test/type_assertions.rb

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -256,15 +256,17 @@ def method_defs(method)
256256
type, definition = target
257257

258258
case type
259-
when Types::ClassInstance
260-
subst = RBS::Substitution.build(definition.type_params, type.args)
261-
definition.methods[method].defs.map do |type_def|
262-
type_def.update(
263-
type: type_def.type.sub(subst)
264-
)
259+
when Types::ClassInstance, Types::ClassSingleton
260+
if type.is_a?(Types::ClassSingleton) && type.args.empty?
261+
definition.methods[method].defs
262+
else
263+
subst = RBS::Substitution.build(definition.type_params, type.args)
264+
definition.methods[method].defs.map do |type_def|
265+
type_def.update(
266+
type: type_def.type.sub(subst)
267+
)
268+
end
265269
end
266-
when Types::ClassSingleton
267-
definition.methods[method].defs
268270
else
269271
raise
270272
end

sig/types.rbs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -172,24 +172,23 @@ module RBS
172172
class ClassSingleton
173173
# singleton(::Foo)
174174
# ^^^^^ => name
175-
type loc = Location[:name, bot]
176-
177-
def initialize: (name: TypeName, location: loc?) -> void
175+
type loc = Location[:name, :args]
178176

179-
attr_reader name: TypeName
177+
def initialize: (name: TypeName, location: loc?, ?args: Array[t]) -> void
180178

181179
attr_reader location: loc?
182180

183181
include _TypeBase
184-
include NoFreeVariables
185-
include NoSubst
186-
include EmptyEachType
182+
include Application
187183

188184
def ==: (untyped other) -> bool
189185

190186
alias eql? ==
191187

192188
def hash: () -> Integer
189+
190+
def map_type: () { (t) -> t } -> ClassSingleton
191+
| () -> Enumerator[t, ClassSingleton]
193192
end
194193

195194
module Application

src/ast.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1303,7 +1303,7 @@ rbs_types_class_instance_t *rbs_types_class_instance_new(rbs_allocator_t *alloca
13031303
return instance;
13041304
}
13051305
#line 140 "prism/templates/src/ast.c.erb"
1306-
rbs_types_class_singleton_t *rbs_types_class_singleton_new(rbs_allocator_t *allocator, rbs_location_range location, rbs_type_name_t *name, rbs_location_range name_range) {
1306+
rbs_types_class_singleton_t *rbs_types_class_singleton_new(rbs_allocator_t *allocator, rbs_location_range location, rbs_type_name_t *name, rbs_node_list_t *args, rbs_location_range name_range) {
13071307
rbs_types_class_singleton_t *instance = rbs_allocator_alloc(allocator, rbs_types_class_singleton_t);
13081308

13091309
*instance = (rbs_types_class_singleton_t) {
@@ -1312,7 +1312,9 @@ rbs_types_class_singleton_t *rbs_types_class_singleton_new(rbs_allocator_t *allo
13121312
.location = location,
13131313
},
13141314
.name = name,
1315+
.args = args,
13151316
.name_range = name_range,
1317+
.args_range = RBS_LOCATION_NULL_RANGE,
13161318
};
13171319

13181320
return instance;

src/parser.c

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,10 +1042,10 @@ static bool parse_instance_type(rbs_parser_t *parser, bool parse_alias, rbs_node
10421042
}
10431043

10441044
/*
1045-
singleton_type ::= {`singleton`} `(` type_name <`)`>
1045+
singleton_type ::= {`singleton`} `(` type_name <`)`> type_args?
10461046
*/
10471047
NODISCARD
1048-
static bool parse_singleton_type(rbs_parser_t *parser, rbs_types_class_singleton_t **singleton) {
1048+
static bool parse_singleton_type(rbs_parser_t *parser, rbs_types_class_singleton_t **singleton, bool self_allowed, bool classish_allowed) {
10491049
ASSERT_TOKEN(parser, kSINGLETON);
10501050

10511051
rbs_range_t type_range;
@@ -1058,9 +1058,26 @@ static bool parse_singleton_type(rbs_parser_t *parser, rbs_types_class_singleton
10581058
CHECK_PARSE(parse_type_name(parser, CLASS_NAME, &name_range, &type_name));
10591059

10601060
ADVANCE_ASSERT(parser, pRPAREN);
1061+
1062+
rbs_node_list_t *types = rbs_node_list_new(ALLOCATOR());
1063+
1064+
rbs_location_range args_range = RBS_LOCATION_NULL_RANGE;
1065+
if (parser->next_token.type == pLBRACKET) {
1066+
rbs_parser_advance(parser);
1067+
args_range.start_byte = parser->current_token.range.start.byte_pos;
1068+
args_range.start_char = parser->current_token.range.start.char_pos;
1069+
CHECK_PARSE(parse_type_list(parser, pRBRACKET, types, true, self_allowed, classish_allowed));
1070+
ADVANCE_ASSERT(parser, pRBRACKET);
1071+
args_range.end_byte = parser->current_token.range.end.byte_pos;
1072+
args_range.end_char = parser->current_token.range.end.char_pos;
1073+
}
1074+
10611075
type_range.end = parser->current_token.range.end;
1076+
rbs_location_range loc = RBS_RANGE_LEX2AST(type_range);
1077+
1078+
*singleton = rbs_types_class_singleton_new(ALLOCATOR(), loc, type_name, types, RBS_RANGE_LEX2AST(name_range));
1079+
(*singleton)->args_range = args_range;
10621080

1063-
*singleton = rbs_types_class_singleton_new(ALLOCATOR(), RBS_RANGE_LEX2AST(type_range), type_name, RBS_RANGE_LEX2AST(name_range));
10641081
return true;
10651082
}
10661083

@@ -1242,7 +1259,7 @@ static bool parse_simple(rbs_parser_t *parser, rbs_node_t **type, bool void_allo
12421259
}
12431260
case kSINGLETON: {
12441261
rbs_types_class_singleton_t *singleton = NULL;
1245-
CHECK_PARSE(parse_singleton_type(parser, &singleton));
1262+
CHECK_PARSE(parse_singleton_type(parser, &singleton, self_allowed, classish_allowed));
12461263
*type = (rbs_node_t *) singleton;
12471264
return true;
12481265
}

0 commit comments

Comments
 (0)