Skip to content

Commit 06d9ce0

Browse files
committed
Add type arguments support to singleton types
Previously, singleton type arguments could not be supported by RBS. This adds support for them, enabling syntax like `singleton(Array)[String, Integer]`.
1 parent e566460 commit 06d9ce0

File tree

12 files changed

+179
-34
lines changed

12 files changed

+179
-34
lines changed

config.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,8 @@ nodes:
363363
fields:
364364
- name: name
365365
c_type: rbs_type_name
366+
- name: args
367+
c_type: rbs_node_list
366368
- name: RBS::Types::Function
367369
expose_location: false
368370
fields:

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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -885,6 +885,7 @@ VALUE rbs_struct_to_ruby_value(rbs_translation_context_t ctx, rbs_node_t *instan
885885
VALUE h = rb_hash_new();
886886
rb_hash_aset(h, ID2SYM(rb_intern("location")), rbs_loc_to_ruby_location(ctx, node->base.location));
887887
rb_hash_aset(h, ID2SYM(rb_intern("name")), rbs_struct_to_ruby_value(ctx, (rbs_node_t *) node->name)); // rbs_type_name
888+
rb_hash_aset(h, ID2SYM(rb_intern("args")), rbs_node_list_to_ruby_array(ctx, node->args));
888889

889890
return CLASS_NEW_INSTANCE(
890891
RBS_Types_ClassSingleton,

include/rbs/ast.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,7 @@ typedef struct rbs_types_class_singleton {
555555
rbs_node_t base;
556556

557557
struct rbs_type_name *name;
558+
struct rbs_node_list *args;
558559
} rbs_types_class_singleton_t;
559560

560561
typedef struct rbs_types_function {
@@ -729,7 +730,7 @@ rbs_types_bases_top_t *rbs_types_bases_top_new(rbs_allocator_t *allocator, rbs_l
729730
rbs_types_bases_void_t *rbs_types_bases_void_new(rbs_allocator_t *allocator, rbs_location_t *location);
730731
rbs_types_block_t *rbs_types_block_new(rbs_allocator_t *allocator, rbs_location_t *location, rbs_node_t *type, bool required, rbs_node_t *self_type);
731732
rbs_types_class_instance_t *rbs_types_class_instance_new(rbs_allocator_t *allocator, rbs_location_t *location, rbs_type_name_t *name, rbs_node_list_t *args);
732-
rbs_types_class_singleton_t *rbs_types_class_singleton_new(rbs_allocator_t *allocator, rbs_location_t *location, rbs_type_name_t *name);
733+
rbs_types_class_singleton_t *rbs_types_class_singleton_new(rbs_allocator_t *allocator, rbs_location_t *location, rbs_type_name_t *name, rbs_node_list_t *args);
733734
rbs_types_function_t *rbs_types_function_new(rbs_allocator_t *allocator, rbs_location_t *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);
734735
rbs_types_function_param_t *rbs_types_function_param_new(rbs_allocator_t *allocator, rbs_location_t *location, rbs_node_t *type, rbs_ast_symbol_t *name);
735736
rbs_types_interface_t *rbs_types_interface_new(rbs_allocator_t *allocator, rbs_location_t *location, rbs_type_name_t *name, rbs_node_list_t *args);

lib/rbs/types.rb

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -202,38 +202,52 @@ def with_nonreturn_void?
202202
class ClassSingleton
203203
attr_reader :name
204204
attr_reader :location
205+
attr_reader :args
205206

206-
def initialize(name:, location:)
207+
def initialize(name:, location:, args: [])
207208
@name = name
208209
@location = location
210+
@args = args
209211
end
210212

211213
def ==(other)
212-
other.is_a?(ClassSingleton) && other.name == name
214+
other.is_a?(ClassSingleton) && other.name == name && other.args == args
213215
end
214216

215217
alias eql? ==
216218

217219
def hash
218-
self.class.hash ^ name.hash
220+
self.class.hash ^ name.hash ^ args.hash
219221
end
220222

221223
include NoFreeVariables
222-
include NoSubst
224+
225+
def sub(s)
226+
return self if s.empty?
227+
228+
self.class.new(name: name,
229+
args: args.map {|ty| ty.sub(s) },
230+
location: location)
231+
end
223232

224233
def to_json(state = _ = nil)
225-
{ class: :class_singleton, name: name, location: location }.to_json(state)
234+
{ class: :class_singleton, name: name, args: args, location: location }.to_json(state)
226235
end
227236

228237
def to_s(level = 0)
229-
"singleton(#{name})"
238+
if args.empty?
239+
"singleton(#{name})"
240+
else
241+
"singleton(#{name})[#{args.join(", ")}]"
242+
end
230243
end
231244

232245
include EmptyEachType
233246

234-
def map_type_name(&)
247+
def map_type_name(&block)
235248
ClassSingleton.new(
236249
name: yield(name, location, self),
250+
args: args.map {|type| type.map_type_name(&block) },
237251
location: location
238252
)
239253
end

lib/rbs/unit_test/type_assertions.rb

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

233233
case type
234-
when Types::ClassInstance
235-
subst = RBS::Substitution.build(definition.type_params, type.args)
236-
definition.methods[method].defs.map do |type_def|
237-
type_def.update(
238-
type: type_def.type.sub(subst)
239-
)
234+
when Types::ClassInstance, Types::ClassSingleton
235+
if type.is_a?(Types::ClassSingleton) && type.args.empty?
236+
definition.methods[method].defs
237+
else
238+
subst = RBS::Substitution.build(definition.type_params, type.args)
239+
definition.methods[method].defs.map do |type_def|
240+
type_def.update(
241+
type: type_def.type.sub(subst)
242+
)
243+
end
240244
end
241-
when Types::ClassSingleton
242-
definition.methods[method].defs
243245
else
244246
raise
245247
end

sig/types.rbs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,12 +174,14 @@ module RBS
174174
# ^^^^^ => name
175175
type loc = Location[:name, bot]
176176

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

179179
attr_reader name: TypeName
180180

181181
attr_reader location: loc?
182182

183+
attr_reader args: Array[t]
184+
183185
include _TypeBase
184186
include NoFreeVariables
185187
include NoSubst
@@ -190,6 +192,8 @@ module RBS
190192
alias eql? ==
191193

192194
def hash: () -> Integer
195+
196+
def sub: (Substitution) -> ClassSingleton
193197
end
194198

195199
module Application

src/ast.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1138,7 +1138,7 @@ rbs_types_class_instance_t *rbs_types_class_instance_new(rbs_allocator_t *alloca
11381138
return instance;
11391139
}
11401140
#line 156 "prism/templates/src/ast.c.erb"
1141-
rbs_types_class_singleton_t *rbs_types_class_singleton_new(rbs_allocator_t *allocator, rbs_location_t *location, rbs_type_name_t *name) {
1141+
rbs_types_class_singleton_t *rbs_types_class_singleton_new(rbs_allocator_t *allocator, rbs_location_t *location, rbs_type_name_t *name, rbs_node_list_t *args) {
11421142
rbs_types_class_singleton_t *instance = rbs_allocator_alloc(allocator, rbs_types_class_singleton_t);
11431143

11441144
*instance = (rbs_types_class_singleton_t) {
@@ -1147,6 +1147,7 @@ rbs_types_class_singleton_t *rbs_types_class_singleton_new(rbs_allocator_t *allo
11471147
.location = location,
11481148
},
11491149
.name = name,
1150+
.args = args,
11501151
};
11511152

11521153
return instance;

src/parser.c

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,7 +1019,7 @@ static bool parse_instance_type(rbs_parser_t *parser, bool parse_alias, rbs_node
10191019
}
10201020

10211021
/*
1022-
singleton_type ::= {`singleton`} `(` type_name <`)`>
1022+
singleton_type ::= {`singleton`} `(` type_name <`)`> type_args?
10231023
*/
10241024
NODISCARD
10251025
static bool parse_singleton_type(rbs_parser_t *parser, rbs_types_class_singleton_t **singleton) {
@@ -1035,13 +1035,29 @@ static bool parse_singleton_type(rbs_parser_t *parser, rbs_types_class_singleton
10351035
CHECK_PARSE(parse_type_name(parser, CLASS_NAME, &name_range, &type_name));
10361036

10371037
ADVANCE_ASSERT(parser, pRPAREN);
1038-
type_range.end = parser->current_token.range.end;
1038+
1039+
rbs_node_list_t *types = rbs_node_list_new(ALLOCATOR());
1040+
1041+
rbs_range_t args_range;
1042+
if (parser->next_token.type == pLBRACKET) {
1043+
rbs_parser_advance(parser);
1044+
args_range.start = parser->current_token.range.start;
1045+
CHECK_PARSE(parse_type_list(parser, pRBRACKET, types));
1046+
ADVANCE_ASSERT(parser, pRBRACKET);
1047+
args_range.end = parser->current_token.range.end;
1048+
type_range.end = parser->current_token.range.end;
1049+
} else {
1050+
args_range = NULL_RANGE;
1051+
type_range.end = parser->current_token.range.end;
1052+
}
10391053

10401054
rbs_location_t *loc = rbs_location_new(ALLOCATOR(), type_range);
1041-
rbs_loc_alloc_children(ALLOCATOR(), loc, 1);
1055+
rbs_loc_alloc_children(ALLOCATOR(), loc, 2);
10421056
rbs_loc_add_required_child(loc, INTERN("name"), name_range);
1057+
rbs_loc_add_optional_child(loc, INTERN("args"), args_range);
1058+
1059+
*singleton = rbs_types_class_singleton_new(ALLOCATOR(), loc, type_name, types);
10431060

1044-
*singleton = rbs_types_class_singleton_new(ALLOCATOR(), loc, type_name);
10451061
return true;
10461062
}
10471063

test/rbs/singleton_type_test.rb

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
require "test_helper"
2+
3+
class RBS::SingletonTypeTest < Test::Unit::TestCase
4+
include TestHelper
5+
6+
Parser = RBS::Parser
7+
Buffer = RBS::Buffer
8+
Types = RBS::Types
9+
TypeName = RBS::TypeName
10+
Namespace = RBS::Namespace
11+
12+
def test_singleton_type_with_arguments
13+
Parser.parse_type("singleton(Array)[String]").yield_self do |type|
14+
assert_instance_of Types::ClassSingleton, type
15+
assert_equal TypeName.new(namespace: Namespace.empty, name: :Array), type.name
16+
assert_equal 1, type.args.size
17+
assert_instance_of Types::ClassInstance, type.args[0]
18+
assert_equal TypeName.new(namespace: Namespace.empty, name: :String), type.args[0].name
19+
assert_equal "singleton(Array)[String]", type.location.source
20+
end
21+
22+
Parser.parse_type("singleton(Hash)[Symbol, Integer]").yield_self do |type|
23+
assert_instance_of Types::ClassSingleton, type
24+
assert_equal TypeName.new(namespace: Namespace.empty, name: :Hash), type.name
25+
assert_equal 2, type.args.size
26+
assert_instance_of Types::ClassInstance, type.args[0]
27+
assert_instance_of Types::ClassInstance, type.args[1]
28+
assert_equal TypeName.new(namespace: Namespace.empty, name: :Symbol), type.args[0].name
29+
assert_equal TypeName.new(namespace: Namespace.empty, name: :Integer), type.args[1].name
30+
assert_equal "singleton(Hash)[Symbol, Integer]", type.location.source
31+
end
32+
33+
Parser.parse_type("singleton(::Foo::Bar)[Baz]").yield_self do |type|
34+
assert_instance_of Types::ClassSingleton, type
35+
assert_equal TypeName.new(namespace: Namespace.parse("::Foo"), name: :Bar), type.name
36+
assert_equal 1, type.args.size
37+
assert_instance_of Types::ClassInstance, type.args[0]
38+
assert_equal TypeName.new(namespace: Namespace.empty, name: :Baz), type.args[0].name
39+
assert_equal "singleton(::Foo::Bar)[Baz]", type.location.source
40+
end
41+
end
42+
43+
def test_singleton_type_equality
44+
type1 = parse_type("singleton(Array)[String]")
45+
type2 = parse_type("singleton(Array)[String]")
46+
type3 = parse_type("singleton(Array)[Integer]")
47+
type4 = parse_type("singleton(Hash)[String]")
48+
49+
assert_equal type1, type2
50+
refute_equal type1, type3
51+
refute_equal type1, type4
52+
end
53+
54+
def test_singleton_type_hash
55+
type1 = parse_type("singleton(Array)[String]")
56+
type2 = parse_type("singleton(Array)[String]")
57+
type3 = parse_type("singleton(Array)[Integer]")
58+
59+
assert_equal type1.hash, type2.hash
60+
refute_equal type1.hash, type3.hash
61+
end
62+
63+
def test_singleton_type_sub
64+
type = parse_type("singleton(Array)[T]", variables: [:T])
65+
subst = RBS::Substitution.build([:T], [parse_type("String")])
66+
67+
result = type.sub(subst)
68+
assert_instance_of Types::ClassSingleton, result
69+
assert_equal TypeName.new(namespace: Namespace.empty, name: :Array), result.name
70+
assert_equal 1, result.args.size
71+
assert_instance_of Types::ClassInstance, result.args[0]
72+
assert_equal TypeName.new(namespace: Namespace.empty, name: :String), result.args[0].name
73+
end
74+
75+
def test_singleton_type_map_type_name
76+
type = parse_type("singleton(Array)[String]")
77+
78+
mapped = type.map_type_name do |name, _, _|
79+
TypeName.new(namespace: Namespace.empty, name: :List)
80+
end
81+
82+
assert_instance_of Types::ClassSingleton, mapped
83+
assert_equal TypeName.new(namespace: Namespace.empty, name: :List), mapped.name
84+
assert_equal 1, mapped.args.size
85+
assert_instance_of Types::ClassInstance, mapped.args[0]
86+
assert_equal TypeName.new(namespace: Namespace.empty, name: :List), mapped.args[0].name
87+
end
88+
end

0 commit comments

Comments
 (0)