Skip to content

Commit badd2d5

Browse files
authored
Merge pull request #31 from ruby/variance
Add generics variance syntax
2 parents 1245dda + 3588d8e commit badd2d5

13 files changed

Lines changed: 238 additions & 39 deletions

File tree

doc/syntax.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -402,13 +402,13 @@ _decl_ ::= _class-decl_ # Class declaration
402402
| _const-decl_ # Constant declaration
403403
| _global-decl_ # Global declaration
404404

405-
_class-decl_ ::= `class` _class-name_ _type-parameters_ _members_ `end`
406-
| `class` _class-name_ _type-parameters_ `<` _class-name_ _type-arguments_ _members_ `end`
405+
_class-decl_ ::= `class` _class-name_ _module-type-parameters_ _members_ `end`
406+
| `class` _class-name_ _module-type-parameters_ `<` _class-name_ _type-arguments_ _members_ `end`
407407

408-
_module-decl_ ::= `module` _module-name_ _type-parameters_ _members_ `end`
409-
| `module` _module-name_ _type-parameters_ `:` _class-name_ _type-arguments_ _members_ `end`
408+
_module-decl_ ::= `module` _module-name_ _module-type-parameters_ _members_ `end`
409+
| `module` _module-name_ _module-type-parameters_ `:` _class-name_ _type-arguments_ _members_ `end`
410410

411-
_interface-decl_ ::= `interface` _interface-name_ _type-parameters_ _interface-members_ `end`
411+
_interface-decl_ ::= `interface` _interface-name_ _module-type-parameters_ _interface-members_ `end`
412412

413413
_interface-members_ ::= _method-member_ # Method
414414
| _include-member_ # Mixin (include)
@@ -424,6 +424,12 @@ _global-decl_ ::= _global-name_ `:` _type_
424424

425425
_const-name_ ::= _namespace_ /[A-Z]\w*/
426426
_global-name_ ::= /$[a-zA-Z]\w+/ | ...
427+
428+
_module-type-parameters_ ::= # Empty
429+
| `[` _module-type-parameter_ `,` ... `]`
430+
431+
_module-type-parameter_ ::= _variance_ _type-variable_
432+
_variance_ ::= `out` | `in`
427433
```
428434

429435
### Class declaration

lib/ruby/signature/ast/declarations.rb

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,59 @@ module Ruby
22
module Signature
33
module AST
44
module Declarations
5+
class ModuleTypeParams
6+
attr_reader :params
7+
8+
TypeParam = Struct.new(:name, :variance, :skip_validation, keyword_init: true)
9+
10+
def initialize()
11+
@params = []
12+
end
13+
14+
def add(param)
15+
params << param
16+
self
17+
end
18+
19+
def ==(other)
20+
other.is_a?(ModuleTypeParams) && other.params == params
21+
end
22+
23+
def [](name)
24+
params.find {|p| p.name == name }
25+
end
26+
27+
def to_json(*a)
28+
{
29+
params: params
30+
}.to_json(*a)
31+
end
32+
33+
def each(&block)
34+
params.each(&block)
35+
end
36+
37+
def self.empty
38+
new
39+
end
40+
41+
def variance(name)
42+
self[name].variance
43+
end
44+
45+
def skip_validation?(name)
46+
self[name].skip_validation
47+
end
48+
49+
def empty?
50+
params.empty?
51+
end
52+
53+
def size
54+
params.size
55+
end
56+
end
57+
558
class Class
659
class Super
760
attr_reader :name

lib/ruby/signature/cli.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,9 @@ def run_ancestors(args, options)
157157
if env.class?(type_name)
158158
ancestor = case kind
159159
when :instance
160-
definition = env.find_class(type_name)
160+
decl = env.find_class(type_name)
161161
Definition::Ancestor::Instance.new(name: type_name,
162-
args: Types::Variable.build(definition.type_params))
162+
args: Types::Variable.build(decl.type_params.each.map(&:name)))
163163
when :singleton
164164
Definition::Ancestor::Singleton.new(name: type_name)
165165
end

lib/ruby/signature/definition.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,15 @@ def type_params
129129
@self_type.args.map(&:name)
130130
end
131131

132+
def type_params_decl
133+
case declaration
134+
when AST::Declarations::Extension
135+
nil
136+
else
137+
declaration.type_params
138+
end
139+
end
140+
132141
def each_type(&block)
133142
if block_given?
134143
methods.each_value do |method|

lib/ruby/signature/definition_builder.rb

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,16 @@ def build_ancestors(self_ancestor, ancestors: [], building_ancestors: [], locati
2323
case self_ancestor
2424
when Definition::Ancestor::Instance
2525
args = self_ancestor.args
26-
params = decl.type_params
26+
param_names = decl.type_params.each.map(&:name)
2727

2828
InvalidTypeApplicationError.check!(
2929
type_name: self_ancestor.name,
3030
args: args,
31-
params: params,
31+
params: decl.type_params,
3232
location: location || decl.location
3333
)
3434

35-
sub = Substitution.build(params, args)
35+
sub = Substitution.build(param_names, args)
3636

3737
case decl
3838
when AST::Declarations::Class
@@ -203,7 +203,7 @@ def build_instance(type_name)
203203
try_cache type_name, cache: instance_cache do
204204
decl = env.find_class(type_name)
205205
self_ancestor = Definition::Ancestor::Instance.new(name: type_name,
206-
args: Types::Variable.build(decl.type_params))
206+
args: Types::Variable.build(decl.type_params.each.map(&:name)))
207207
self_type = Types::ClassInstance.new(name: type_name, args: self_ancestor.args, location: nil)
208208

209209
case decl
@@ -323,7 +323,7 @@ def build_one_instance(type_name, extension_name: nil)
323323
case decl
324324
when AST::Declarations::Class, AST::Declarations::Module
325325
self_type = Types::ClassInstance.new(name: type_name,
326-
args: Types::Variable.build(decl.type_params),
326+
args: Types::Variable.build(decl.type_params.each.map(&:name)),
327327
location: nil)
328328
ancestors = [Definition::Ancestor::Instance.new(name: type_name, args: self_type.args)]
329329
when AST::Declarations::Extension
@@ -462,7 +462,7 @@ def build_one_instance(type_name, extension_name: nil)
462462
InvalidTypeApplicationError.check!(
463463
type_name: absolute_name,
464464
args: absolute_args,
465-
params: interface_definition.type_params,
465+
params: interface_definition.type_params_decl,
466466
location: member.location
467467
)
468468

@@ -584,7 +584,7 @@ def build_one_singleton(type_name, extension_name: nil)
584584
InvalidTypeApplicationError.check!(
585585
type_name: absolute_name,
586586
args: absolute_args,
587-
params: interface_definition.type_params,
587+
params: interface_definition.type_params_decl,
588588
location: member.location
589589
)
590590

@@ -705,7 +705,7 @@ def try_cache(type_name, cache:)
705705
def build_interface(type_name, declaration)
706706
self_type = Types::Interface.new(
707707
name: type_name,
708-
args: declaration.type_params.map {|x| Types::Variable.new(name: x, location: nil) },
708+
args: declaration.type_params.each.map {|p| Types::Variable.new(name: p.name, location: nil) },
709709
location: nil
710710
)
711711

@@ -728,7 +728,7 @@ def build_interface(type_name, declaration)
728728
location: member.location
729729
)
730730

731-
sub = Substitution.build(type_params, args)
731+
sub = Substitution.build(type_params.each.map(&:name), args)
732732
mixin.methods.each do |name, method|
733733
definition.methods[name] = method.sub(sub)
734734
end

lib/ruby/signature/errors.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def initialize(type_name:, args:, params:, location:)
1111
@args = args
1212
@params = params
1313
@location = location
14-
super "#{Location.to_string location}: #{type_name} expects parameters [#{params.join(", ")}], but given args [#{args.join(", ")}]"
14+
super "#{Location.to_string location}: #{type_name} expects parameters [#{params.each.map(&:name).join(", ")}], but given args [#{args.join(", ")}]"
1515
end
1616

1717
def self.check!(type_name:, args:, params:, location:)

lib/ruby/signature/parser.y

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class Ruby::Signature::Parser
1010
kINTERFACE kEND kINCLUDE kEXTEND kATTRREADER kATTRWRITER kATTRACCESSOR tOPERATOR tQUOTEDMETHOD
1111
kPREPEND kEXTENSION kINCOMPATIBLE
1212
type_TYPE type_SIGNATURE type_METHODTYPE tEOF
13+
kOUT kIN kUNCHECKED
1314

1415
prechigh
1516
nonassoc kQUESTION
@@ -78,13 +79,13 @@ rule
7879
extension_name: tUIDENT | tLIDENT
7980

8081
class_decl:
81-
annotations kCLASS start_new_scope class_name type_params super_class class_members kEND {
82+
annotations kCLASS start_new_scope class_name module_type_params super_class class_members kEND {
8283
reset_variable_scope
8384

8485
location = val[1].location + val[7].location
8586
result = Declarations::Class.new(
8687
name: val[3].value,
87-
type_params: val[4]&.value || [],
88+
type_params: val[4]&.value || Declarations::ModuleTypeParams.empty,
8889
super_class: val[5],
8990
members: val[6],
9091
annotations: val[0],
@@ -105,13 +106,13 @@ rule
105106
}
106107

107108
module_decl:
108-
annotations kMODULE start_new_scope class_name type_params module_self_type class_members kEND {
109+
annotations kMODULE start_new_scope class_name module_type_params module_self_type class_members kEND {
109110
reset_variable_scope
110111

111112
location = val[1].location + val[7].location
112113
result = Declarations::Module.new(
113114
name: val[3].value,
114-
type_params: val[4]&.value || [],
115+
type_params: val[4]&.value || Declarations::ModuleTypeParams.empty,
115116
self_type: val[5],
116117
members: val[6],
117118
annotations: val[0],
@@ -125,7 +126,7 @@ rule
125126
location = val[1].location + val[6].location
126127
result = Declarations::Module.new(
127128
name: val[3].value,
128-
type_params: [],
129+
type_params: Declarations::ModuleTypeParams.empty,
129130
self_type: val[4],
130131
members: val[5],
131132
annotations: val[0],
@@ -272,13 +273,13 @@ rule
272273
}
273274

274275
interface_decl:
275-
annotations kINTERFACE start_new_scope interface_name type_params interface_members kEND {
276+
annotations kINTERFACE start_new_scope interface_name module_type_params interface_members kEND {
276277
reset_variable_scope
277278

278279
location = val[1].location + val[6].location
279280
result = Declarations::Interface.new(
280281
name: val[3].value,
281-
type_params: val[4]&.value || [],
282+
type_params: val[4]&.value || Declarations::ModuleTypeParams.empty,
282283
members: val[5],
283284
annotations: val[0],
284285
location: location,
@@ -477,7 +478,7 @@ rule
477478

478479
method_name:
479480
tOPERATOR
480-
| kAMP | kHAT | kSTAR | kLT | kEXCLAMATION | kSTAR2 | kBAR
481+
| kAMP | kHAT | kSTAR | kLT | kEXCLAMATION | kSTAR2 | kBAR | kOUT | kIN
481482
| method_name0
482483
| method_name0 kQUESTION {
483484
unless val[0].location.pred?(val[1].location)
@@ -504,6 +505,40 @@ rule
504505
kCLASS | kVOID | kNIL | kANY | kTOP | kBOT | kINSTANCE | kBOOL | kSINGLETON
505506
| kTYPE | kMODULE | kPRIVATE | kPUBLIC | kEND | kINCLUDE | kEXTEND | kPREPEND
506507
| kATTRREADER | kATTRACCESSOR | kATTRWRITER | kDEF | kEXTENSION | kSELF | kINCOMPATIBLE
508+
| kUNCHECKED
509+
510+
module_type_params:
511+
{ result = nil }
512+
| kLBRACKET module_type_params0 kRBRACKET {
513+
val[1].each {|p| insert_bound_variable(p.name) }
514+
515+
result = LocatedValue.new(value: val[1], location: val[0].location + val[2].location)
516+
}
517+
518+
module_type_params0:
519+
module_type_param {
520+
result = Declarations::ModuleTypeParams.new()
521+
result.add(val[0])
522+
}
523+
| module_type_params0 kCOMMA module_type_param {
524+
result = val[0].add(val[2])
525+
}
526+
527+
module_type_param:
528+
type_param_check type_param_variance tUIDENT {
529+
result = Declarations::ModuleTypeParams::TypeParam.new(name: val[2].value.to_sym,
530+
variance: val[1],
531+
skip_validation: val[0])
532+
}
533+
534+
type_param_variance:
535+
{ result = :invariant }
536+
| kOUT { result = :covariant }
537+
| kIN { result = :contravariant }
538+
539+
type_param_check:
540+
{ result = false }
541+
| kUNCHECKED { result = true }
507542

508543
type_params:
509544
{ result = nil }
@@ -1126,7 +1161,10 @@ KEYWORDS = {
11261161
"private" => :kPRIVATE,
11271162
"alias" => :kALIAS,
11281163
"extension" => :kEXTENSION,
1129-
"incompatible" => :kINCOMPATIBLE
1164+
"incompatible" => :kINCOMPATIBLE,
1165+
"unchecked" => :kUNCHECKED,
1166+
"out" => :kOUT,
1167+
"in" => :kIN,
11301168
}
11311169
KEYWORDS_RE = /#{Regexp.union(*KEYWORDS.keys)}\b/
11321170

lib/ruby/signature/scaffold/rbi.rb

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def push_class(name, super_class, comment:)
4949
modules.push AST::Declarations::Class.new(
5050
name: nested_name(name),
5151
super_class: super_class && AST::Declarations::Class::Super.new(name: const_to_name(super_class), args: []),
52-
type_params: [],
52+
type_params: AST::Declarations::ModuleTypeParams.empty,
5353
members: [],
5454
annotations: [],
5555
location: nil,
@@ -66,7 +66,7 @@ def push_class(name, super_class, comment:)
6666
def push_module(name, comment:)
6767
modules.push AST::Declarations::Module.new(
6868
name: nested_name(name),
69-
type_params: [],
69+
type_params: AST::Declarations::ModuleTypeParams.empty,
7070
members: [],
7171
annotations: [],
7272
location: nil,
@@ -204,7 +204,19 @@ def process(node, outer: [], comments:)
204204
node.type == :HASH &&
205205
each_arg(node.children[0]).each_slice(2).any? {|a, _| a.type == :LIT && a.children[0] == :fixed }
206206
}
207-
current_module.type_params << node.children[0]
207+
if (a0 = each_arg(send.children[1]).to_a[0])&.type == :LIT
208+
variance = case a0.children[0]
209+
when :out
210+
:covariant
211+
when :in
212+
:contravariant
213+
end
214+
end
215+
216+
current_module.type_params.add(
217+
AST::Declarations::ModuleTypeParams::TypeParam.new(name: node.children[0],
218+
variance: variance || :invariant,
219+
skip_validation: false))
208220
end
209221
else
210222
name = node.children[0].yield_self do |n|
@@ -418,7 +430,7 @@ def type_of(type_node, variables:)
418430
def type_of0(type_node, variables:)
419431
case
420432
when type_node.type == :CONST
421-
if variables.include?(type_node.children[0])
433+
if variables.each.include?(type_node.children[0])
422434
Types::Variable.new(name: type_node.children[0], location: nil)
423435
else
424436
Types::ClassInstance.new(name: const_to_name(type_node), args: [], location: nil)

lib/ruby/signature/types.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ def self.build(v)
126126
new(name: v, location: nil)
127127
when Array
128128
v.map {|x| new(name: x, location: nil) }
129+
else
130+
raise
129131
end
130132
end
131133

0 commit comments

Comments
 (0)