Skip to content

Commit 3881171

Browse files
authored
Merge pull request #2670 from ruby/module-normalization
Normalize modules during type name resolution
2 parents 28dfe32 + 052c009 commit 3881171

File tree

14 files changed

+595
-267
lines changed

14 files changed

+595
-267
lines changed

Rakefile

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,12 @@ test_config = lambda do |t|
2525
t.test_files = FileList["test/**/*_test.rb"].reject do |path|
2626
path =~ %r{test/stdlib/}
2727
end
28-
t.verbose = true
29-
t.options = '-v'
28+
if defined?(RubyMemcheck)
29+
if t.is_a?(RubyMemcheck::TestTask)
30+
t.verbose = true
31+
t.options = '-v'
32+
end
33+
end
3034
end
3135

3236
Rake::TestTask.new(test: :compile, &test_config)
@@ -550,4 +554,4 @@ task :prepare_profiling do
550554
Rake::Task[:"clobber"].invoke
551555
Rake::Task[:"templates"].invoke
552556
Rake::Task[:"compile"].invoke
553-
end
557+
end

docs/aliases.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Aliases
2+
3+
This document explains module/class aliases and type aliases.
4+
5+
## Module/class alias
6+
7+
Module/class aliases give another name to a module/class.
8+
This is useful for some syntaxes that has lexical constraints.
9+
10+
```rbs
11+
class C
12+
end
13+
14+
class D = C # ::D is an alias for ::C
15+
16+
class E < D # ::E inherits from ::D, which is actually ::C
17+
end
18+
```
19+
20+
Note that module/class aliases cannot be recursive.
21+
22+
So, we can define a *normalization* of aliased module/class names.
23+
Normalization follows the chain of alias definitions and resolves them to the original module/class defined with `module`/`class` syntax.
24+
25+
```rbs
26+
class C
27+
end
28+
29+
class D = C
30+
class E = D
31+
```
32+
33+
`::E` is defined as an alias, and it can be normalized to `::C`.
34+
35+
## Type alias
36+
37+
The biggest difference from module/class alias is that type alias can be recursive.
38+
39+
```rbs
40+
# cons_cell type is defined recursively
41+
type cons_cell = nil
42+
| [Integer, cons_cell]
43+
```
44+
45+
This means type aliases *cannot be* normalized generally.
46+
So, we provide another operation for type alias, `DefinitionBuilder#expand_alias` and its family.
47+
It substitutes with the immediate right hand side of a type alias.
48+
49+
```
50+
cons_cell ===> nil | [Integer, cons_cell] (expand 1 step)
51+
===> nil | [Integer, nil | [Integer, cons_cell]] (expand 2 steps)
52+
===> ... (expand will go infinitely)
53+
```
54+
55+
Note that the namespace of a type alias *can be* normalized, because they are module names.
56+
57+
```rbs
58+
module M
59+
type t = String
60+
end
61+
62+
module N = M
63+
```
64+
65+
With the type definition above, a type `::N::t` can be normalized to `::M::t`.
66+
And then it can be expanded to `::String`.
67+
68+
> [!NOTE]
69+
> This is something like an *unfold* operation in type theory.
70+
71+
## Type name resolution
72+
73+
Type name resolution in RBS usually rewrites *relative* type names to *absolute* type names.
74+
`Environment#resolve_type_names` converts all type names in the RBS type definitions, and returns a new `Environment` object.
75+
76+
It also *normalizes* modules names in type names.
77+
78+
- If the type name can be resolved and normalized successfully, the AST has *absolute* type names.
79+
- If the type name resolution/normalization fails, the AST has *relative* type names.

lib/rbs/cli/validate.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def validate_class_module_definition
137137

138138
self_params =
139139
if self_type.name.class?
140-
@env.normalized_module_entry(self_type.name)&.type_params
140+
@env.module_entry(self_type.name, normalized: true)&.type_params
141141
else
142142
@env.interface_decls[self_type.name]&.decl&.type_params
143143
end
@@ -186,7 +186,7 @@ def validate_class_module_definition
186186
when AST::Members::Mixin
187187
params =
188188
if member.name.class?
189-
module_decl = @env.normalized_module_entry(member.name) or raise
189+
module_decl = @env.module_entry(member.name, normalized: true) or raise
190190
module_decl.type_params
191191
else
192192
interface_decl = @env.interface_decls.fetch(member.name)

lib/rbs/definition_builder/ancestor_builder.rb

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ def one_instance_ancestors(type_name)
220220
InvalidTypeApplicationError.check2!(type_name: super_class.name, args: super_class.args, env: env, location: super_class.location)
221221
end
222222

223-
super_entry = env.normalized_class_entry(super_name) or raise
223+
super_entry = env.class_entry(super_name, normalized: true) or raise
224224
super_args = AST::TypeParam.normalize_args(super_entry.type_params, super_args)
225225

226226
ancestors = OneAncestors.class_instance(
@@ -248,7 +248,7 @@ def one_instance_ancestors(type_name)
248248

249249
module_name = module_self.name
250250
if module_name.class?
251-
module_entry = env.normalized_module_class_entry(module_name) or raise
251+
module_entry = env.module_class_entry(module_name, normalized: true) or raise
252252
module_name = module_entry.name
253253
self_args = AST::TypeParam.normalize_args(module_entry.type_params, module_self.args)
254254
end
@@ -361,7 +361,7 @@ def mixin_ancestors0(decl, type_name, align_params:, included_modules:, included
361361
MixinClassError.check!(type_name: type_name, env: env, member: member)
362362
NoMixinFoundError.check!(member.name, env: env, member: member)
363363

364-
module_decl = env.normalized_module_entry(module_name) or raise
364+
module_decl = env.module_entry(module_name, normalized: true) or raise
365365
module_args = AST::TypeParam.normalize_args(module_decl.type_params, module_args)
366366

367367
module_name = env.normalize_module_name(module_name)
@@ -380,7 +380,7 @@ def mixin_ancestors0(decl, type_name, align_params:, included_modules:, included
380380
MixinClassError.check!(type_name: type_name, env: env, member: member)
381381
NoMixinFoundError.check!(member.name, env: env, member: member)
382382

383-
module_decl = env.normalized_module_entry(member.name) or raise
383+
module_decl = env.module_entry(member.name, normalized: true) or raise
384384
module_name = module_decl.name
385385

386386
module_args = member.args.map {|type| align_params ? type.sub(align_params) : type }
@@ -398,7 +398,7 @@ def mixin_ancestors0(decl, type_name, align_params:, included_modules:, included
398398
MixinClassError.check!(type_name: type_name, env: env, member: member)
399399
NoMixinFoundError.check!(member.name, env: env, member: member)
400400

401-
module_decl = env.normalized_module_entry(module_name) or raise
401+
module_decl = env.module_entry(module_name, normalized: true) or raise
402402
module_args = AST::TypeParam.normalize_args(module_decl.type_params, module_args)
403403

404404
module_name = env.normalize_module_name(module_name)
@@ -427,7 +427,7 @@ def mixin_ancestors0(decl, type_name, align_params:, included_modules:, included
427427
end
428428

429429
# Check if module exists
430-
module_decl = env.normalized_module_entry(module_name) or raise NoMixinFoundError.new(type_name: module_name, member: member)
430+
module_decl = env.module_entry(module_name, normalized: true) or raise NoMixinFoundError.new(type_name: module_name, member: member)
431431
module_args = AST::TypeParam.normalize_args(module_decl.type_params, module_args)
432432
module_name = env.normalize_module_name(module_name)
433433
included_modules << Definition::Ancestor::Instance.new(name: module_name, args: module_args, source: member)
@@ -444,7 +444,7 @@ def mixin_ancestors0(decl, type_name, align_params:, included_modules:, included
444444
end
445445

446446
# Check if module exists
447-
module_decl = env.normalized_module_entry(module_name) or raise NoMixinFoundError.new(type_name: module_name, member: member)
447+
module_decl = env.module_entry(module_name, normalized: true) or raise NoMixinFoundError.new(type_name: module_name, member: member)
448448
module_args = AST::TypeParam.normalize_args(module_decl.type_params, module_args)
449449
module_name = env.normalize_module_name(module_name)
450450
extended_modules << Definition::Ancestor::Instance.new(name: module_name, args: module_args, source: member)
@@ -461,7 +461,7 @@ def mixin_ancestors0(decl, type_name, align_params:, included_modules:, included
461461
end
462462

463463
# Check if module exists
464-
module_decl = env.normalized_module_entry(module_name) or raise NoMixinFoundError.new(type_name: module_name, member: member)
464+
module_decl = env.module_entry(module_name, normalized: true) or raise NoMixinFoundError.new(type_name: module_name, member: member)
465465
module_args = AST::TypeParam.normalize_args(module_decl.type_params, module_args)
466466
module_name = env.normalize_module_name(module_name)
467467
prepended_modules << Definition::Ancestor::Instance.new(name: module_name, args: module_args, source: member)

lib/rbs/environment.rb

Lines changed: 64 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -122,21 +122,17 @@ def class_alias?(name)
122122
end
123123
end
124124

125-
def class_entry(type_name)
126-
case
127-
when (class_entry = class_decls[type_name]).is_a?(ClassEntry)
128-
class_entry
129-
when (class_alias = class_alias_decls[type_name]).is_a?(ClassAliasEntry)
130-
class_alias
125+
def class_entry(type_name, normalized: false)
126+
case entry = constant_entry(type_name, normalized: normalized || false)
127+
when ClassEntry, ClassAliasEntry
128+
entry
131129
end
132130
end
133131

134-
def module_entry(type_name)
135-
case
136-
when (module_entry = class_decls[type_name]).is_a?(ModuleEntry)
137-
module_entry
138-
when (module_alias = class_alias_decls[type_name]).is_a?(ModuleAliasEntry)
139-
module_alias
132+
def module_entry(type_name, normalized: false)
133+
case entry = constant_entry(type_name, normalized: normalized || false)
134+
when ModuleEntry, ModuleAliasEntry
135+
entry
140136
end
141137
end
142138

@@ -152,26 +148,40 @@ def normalized_class_entry(type_name)
152148
end
153149

154150
def normalized_module_entry(type_name)
155-
if name = normalize_module_name?(type_name)
156-
case entry = module_entry(name)
157-
when ModuleEntry, nil
158-
entry
159-
when ModuleAliasEntry
160-
raise
161-
end
162-
end
151+
module_entry(type_name, normalized: true)
163152
end
164153

165-
def module_class_entry(type_name)
166-
class_entry(type_name) || module_entry(type_name)
154+
def module_class_entry(type_name, normalized: false)
155+
entry = constant_entry(type_name, normalized: normalized || false)
156+
if entry.is_a?(ConstantEntry)
157+
nil
158+
else
159+
entry
160+
end
167161
end
168162

169163
def normalized_module_class_entry(type_name)
170-
normalized_class_entry(type_name) || normalized_module_entry(type_name)
164+
module_class_entry(type_name, normalized: true)
171165
end
172166

173-
def constant_entry(type_name)
174-
class_entry(type_name) || module_entry(type_name) || constant_decls[type_name]
167+
def constant_entry(type_name, normalized: false)
168+
if normalized
169+
if normalized_name = normalize_module_name?(type_name)
170+
class_decls.fetch(normalized_name, nil)
171+
else
172+
# The type_name may be declared with constant declaration
173+
unless type_name.namespace.empty?
174+
parent = type_name.namespace.to_type_name
175+
normalized_parent = normalize_module_name?(parent) or return
176+
constant_name = TypeName.new(name: type_name.name, namespace: normalized_parent.to_namespace)
177+
constant_decls.fetch(constant_name, nil)
178+
end
179+
end
180+
else
181+
class_decls.fetch(type_name, nil) ||
182+
class_alias_decls.fetch(type_name, nil) ||
183+
constant_decls.fetch(type_name, nil)
184+
end
175185
end
176186

177187
def normalize_type_name?(name)
@@ -206,6 +216,10 @@ def normalize_type_name!(name)
206216
end
207217
end
208218

219+
def normalize_type_name(name)
220+
normalize_type_name?(name) || name
221+
end
222+
209223
def normalized_type_name?(type_name)
210224
case
211225
when type_name.interface?
@@ -220,53 +234,44 @@ def normalized_type_name?(type_name)
220234
end
221235

222236
def normalized_type_name!(name)
223-
normalized_type_name?(name) or raise "Normalized type name is expected but given `#{name}`, which is normalized to `#{normalize_type_name?(name)}`"
237+
normalized_type_name?(name) or raise "Normalized type name is expected but given `#{name}`"
224238
name
225239
end
226240

227-
def normalize_type_name(name)
228-
normalize_type_name?(name) || name
229-
end
230-
231-
def normalize_module_name(name)
232-
normalize_module_name?(name) or name
233-
end
234-
235241
def normalize_module_name?(name)
236242
raise "Class/module name is expected: #{name}" unless name.class?
237243
name = name.absolute! unless name.absolute?
238244

239-
if @normalize_module_name_cache.key?(name)
240-
return @normalize_module_name_cache[name]
245+
original_name = name
246+
247+
if @normalize_module_name_cache.key?(original_name)
248+
return @normalize_module_name_cache[original_name]
241249
end
242250

243-
unless name.namespace.empty?
244-
parent = name.namespace.to_type_name
245-
if normalized_parent = normalize_module_name?(parent)
246-
type_name = TypeName.new(namespace: normalized_parent.to_namespace, name: name.name)
247-
else
248-
@normalize_module_name_cache[name] = nil
249-
return
251+
if alias_entry = class_alias_decls.fetch(name, nil)
252+
unless alias_entry.decl.old_name.absolute?
253+
# Having relative old_name means the type name resolution was failed.
254+
# Run TypeNameResolver for failure reason
255+
resolver = Resolver::TypeNameResolver.build(self)
256+
name = resolver.resolve_namespace(name, context: nil)
257+
@normalize_module_name_cache[original_name] = name
258+
return name
250259
end
251-
else
252-
type_name = name
253-
end
254260

255-
@normalize_module_name_cache[name] = false
261+
name = alias_entry.decl.old_name
262+
end
256263

257-
entry = constant_entry(type_name)
264+
if class_decls.key?(name)
265+
@normalize_module_name_cache[original_name] = name
266+
end
267+
end
258268

259-
normalized_type_name =
260-
case entry
261-
when ClassEntry, ModuleEntry
262-
type_name
263-
when ClassAliasEntry, ModuleAliasEntry
264-
normalize_module_name?(entry.decl.old_name)
265-
else
266-
nil
267-
end
269+
def normalize_module_name(name)
270+
normalize_module_name?(name) || name
271+
end
268272

269-
@normalize_module_name_cache[name] = normalized_type_name
273+
def normalize_module_name!(name)
274+
normalize_module_name?(name) or raise "Module name `#{name}` cannot be normalized"
270275
end
271276

272277
def insert_rbs_decl(decl, context:, namespace:)
@@ -515,7 +520,7 @@ def resolve_signature(resolver, table, dirs, decls, only: nil)
515520
end
516521

517522
def resolve_type_names(only: nil)
518-
resolver = Resolver::TypeNameResolver.new(self)
523+
resolver = Resolver::TypeNameResolver.build(self)
519524
env = Environment.new
520525

521526
table = UseMap::Table.new()

lib/rbs/errors.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ def self.check2!(env:, type_name:, args:, location:)
9393
params =
9494
case
9595
when type_name.class?
96-
decl = env.normalized_module_class_entry(type_name) or raise
96+
decl = env.module_class_entry(type_name, normalized: true) or raise
9797
decl.type_params
9898
when type_name.interface?
9999
env.interface_decls.fetch(type_name).decl.type_params
@@ -491,7 +491,7 @@ def self.check!(type_name:, env:, member:)
491491
else
492492
raise "Unknown member type: #{member.class}"
493493
end
494-
494+
495495
if env.class_decl?(name)
496496
raise new(type_name: type_name, member: member)
497497
end

0 commit comments

Comments
 (0)