Class: RBS::DefinitionBuilder

Inherits:
Object
  • Object
show all
Defined in:
sig/definition_builder.rbs,
lib/rbs/definition_builder.rb,
lib/rbs/definition_builder/method_builder.rb,
lib/rbs/definition_builder/ancestor_builder.rb,
sig/method_builder.rbs,
sig/ancestor_builder.rbs

Overview

DefinitionBuilder translates TypeName to Definition of the type

It also provides semantic utilities that depends on Environment.

Defined Under Namespace

Classes: AncestorBuilder

Constant Summary collapse

MethodBuilder =

Returns:

  • (:Methods module_methods,)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(env:, ancestor_builder: nil, method_builder: nil) ⇒ DefinitionBuilder

Returns a new instance of DefinitionBuilder.

Parameters:



14
15
16
17
18
19
20
21
22
23
# File 'lib/rbs/definition_builder.rb', line 14

def initialize(env:, ancestor_builder: nil, method_builder: nil)
  @env = env
  @ancestor_builder = ancestor_builder || AncestorBuilder.new(env: env)
  @method_builder = method_builder || MethodBuilder.new(env: env)

  @instance_cache = {}
  @singleton_cache = {}
  @singleton0_cache = {}
  @interface_cache = {}
end

Instance Attribute Details

#ancestor_builderAncestorBuilder (readonly)

Returns the value of attribute ancestor_builder.

Returns:



6
7
8
# File 'lib/rbs/definition_builder.rb', line 6

def ancestor_builder
  @ancestor_builder
end

#envEnvironment (readonly)

Returns the value of attribute env.

Returns:



5
6
7
# File 'lib/rbs/definition_builder.rb', line 5

def env
  @env
end

#instance_cacheHash[TypeName, Definition | false | nil] (readonly)

Returns the value of attribute instance_cache.

Returns:



9
10
11
# File 'lib/rbs/definition_builder.rb', line 9

def instance_cache
  @instance_cache
end

#interface_cacheHash[TypeName, Definition | false | nil] (readonly)

Returns the value of attribute interface_cache.

Returns:



12
13
14
# File 'lib/rbs/definition_builder.rb', line 12

def interface_cache
  @interface_cache
end

#method_builderMethodBuilder (readonly)

Returns the value of attribute method_builder.

Returns:



7
8
9
# File 'lib/rbs/definition_builder.rb', line 7

def method_builder
  @method_builder
end

#singleton0_cacheHash[TypeName, Definition | false | nil] (readonly)

Returns the value of attribute singleton0_cache.

Returns:



11
12
13
# File 'lib/rbs/definition_builder.rb', line 11

def singleton0_cache
  @singleton0_cache
end

#singleton_cacheHash[TypeName, Definition | false | nil] (readonly)

Returns the value of attribute singleton_cache.

Returns:



10
11
12
# File 'lib/rbs/definition_builder.rb', line 10

def singleton_cache
  @singleton_cache
end

Instance Method Details

#build_instance(type_name) ⇒ Definition

Returns a Definition of an instance type

If TypeName is a module and no_self_types is true, it won't have methods of self type constraints. This typically happens when definition is being calculated for mixin.

Parameters:

Returns:



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'sig/definition_builder.rbs', line 27

def build_instance(type_name)
  type_name = env.normalize_module_name(type_name)

  try_cache(type_name, cache: instance_cache) do
    entry = env.class_decls[type_name] or raise "Unknown name for build_instance: #{type_name}"
    ensure_namespace!(type_name.namespace, location: entry.primary_decl.location)

    ancestors = ancestor_builder.instance_ancestors(type_name)
    args = entry.type_params.map {|param| Types::Variable.new(name: param.name, location: param.location) }
    self_type = Types::ClassInstance.new(name: type_name, args: args, location: nil)

    Definition.new(type_name: type_name, entry: entry, self_type: self_type, ancestors: ancestors).tap do |definition|
      one_ancestors = ancestor_builder.one_instance_ancestors(type_name)
      methods = method_builder.build_instance(type_name)

      validate_type_params definition, methods: methods, ancestors: one_ancestors

      if entry.is_a?(Environment::ClassEntry)
        if super_class = one_ancestors.super_class
          super_class.is_a?(Definition::Ancestor::Instance) or raise

          build_instance(super_class.name).tap do |defn|
            unless super_class.args.empty?
              super_class.args.each do |arg|
                validate_type_presence(arg)
              end

              subst = tapp_subst(super_class.name, super_class.args)
              defn = defn.sub(subst)
            end

            definition.methods.merge!(defn.methods)
            definition.instance_variables.merge!(defn.instance_variables)
            definition.class_variables.merge!(defn.class_variables)
          end
        end
      end

      if entry.is_a?(Environment::ModuleEntry)
        if self_types = one_ancestors.self_types
          self_types.each do |ans|
            ans.args.each do |arg|
              validate_type_presence(arg)
            end

            subst = tapp_subst(ans.name, ans.args)
            if ans.name.interface?
              define_interface(definition, ans.name, subst)
            else
              define_instance(definition, ans.name, subst, define_class_vars: true)
            end
          end
        end
      end

      define_instance(definition, type_name, Substitution.new, define_class_vars: true)
    end
  end
end

#build_interface(type_name) ⇒ Definition

Returns a Definition of a interface

Parameters:

Returns:



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'sig/definition_builder.rbs', line 20

def build_interface(type_name)
  try_cache(type_name, cache: interface_cache) do
    entry = env.interface_decls[type_name] or raise "Unknown name for build_interface: #{type_name}"
    declaration = entry.decl
    ensure_namespace!(type_name.namespace, location: declaration.location)

    type_params = declaration.type_params.each.map(&:name)
    type_args = Types::Variable.build(type_params)
    self_type = Types::Interface.new(name: type_name, args: type_args, location: nil)

    subst = Substitution.build(type_params, type_args)

    ancestors = ancestor_builder.interface_ancestors(type_name)
    Definition.new(type_name: type_name, entry: entry, self_type: self_type, ancestors: ancestors).tap do |definition|
      methods = method_builder.build_interface(type_name)
      one_ancestors = ancestor_builder.one_interface_ancestors(type_name)
      validate_type_params(definition, methods: methods, ancestors: one_ancestors)

      define_interface(definition, type_name, subst)
    end
  end
end

#build_singleton(type_name) ⇒ Definition

Returns a Definition of a singleton type

Parameters:

Returns:



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'sig/definition_builder.rbs', line 30

def build_singleton(type_name)
  type_name = env.normalize_module_name(type_name)

  try_cache type_name, cache: singleton_cache do
    entry = env.class_decls[type_name] or raise "Unknown name for build_singleton: #{type_name}"
    ensure_namespace!(type_name.namespace, location: entry.primary_decl.location)

    ancestors = ancestor_builder.singleton_ancestors(type_name)
    self_type = Types::ClassSingleton.new(name: type_name, location: nil)

    Definition.new(type_name: type_name, entry: entry, self_type: self_type, ancestors: ancestors).tap do |definition|
      definition0 = build_singleton0(type_name)
      definition.methods.merge!(definition0.methods)
      definition.instance_variables.merge!(definition0.instance_variables)
      definition.class_variables.merge!(definition0.class_variables)

      if entry.is_a?(Environment::ClassEntry)
        new_method = definition.methods[:new]

        if new_method.defs.all? {|d| d.defined_in == BuiltinNames::Class.name }
          # The method is _untyped new_.

          alias_methods = definition.methods.each.with_object([]) do |entry, array|
            # @type var method: Definition::Method?
            name, method = entry
            while method
              if method.alias_of == new_method
                array << name
                break
              end
              method = method.alias_of
            end
          end

          instance = build_instance(type_name)
          initialize = instance.methods[:initialize]

          if initialize
            class_params = entry.type_params

            # Inject a virtual _typed new_.
            initialize_defs = initialize.defs
            typed_new = Definition::Method.new(
              super_method: new_method,
              defs: initialize_defs.map do |initialize_def|
                method_type = initialize_def.type

                class_type_param_vars = Set.new(class_params.map(&:name))
                method_type_param_vars = Set.new(method_type.type_params.map(&:name))

                if class_type_param_vars.intersect?(method_type_param_vars)
                  new_method_param_names = method_type.type_params.map do |method_param|
                    if class_type_param_vars.include?(method_param.name)
                      Types::Variable.fresh(method_param.name).name
                    else
                      method_param.name
                    end
                  end

                  sub = Substitution.build(
                    method_type.type_params.map(&:name),
                    Types::Variable.build(new_method_param_names)
                  )

                  method_params = class_params + AST::TypeParam.rename(method_type.type_params, new_names: new_method_param_names)
                  method_type = method_type
                    .update(type_params: [])
                    .sub(sub)
                    .update(type_params: method_params)
                else
                  method_type = method_type
                    .update(type_params: class_params + method_type.type_params)
                end

                method_type = method_type.map_type do |type|
                  case type
                  when Types::Bases::Self
                    Types::ClassInstance.new(
                      name: type_name,
                      args: entry.type_params.map {|param| Types::Variable.new(name: param.name, location: param.location) },
                      location: nil
                    )
                  else
                    type
                  end
                end

                method_type = method_type.update(
                  type: method_type.type.with_return_type(
                    Types::ClassInstance.new(
                      name: type_name,
                      args: entry.type_params.map {|param| Types::Variable.new(name: param.name, location: param.location) },
                      location: nil
                    )
                  )
                )

                Definition::Method::TypeDef.new(
                  type: method_type,
                  member: initialize_def.member,
                  defined_in: initialize_def.defined_in,
                  implemented_in: initialize_def.implemented_in
                ).tap do |type_def|
                  type_def.overload_annotations.replace(initialize_def.overload_annotations)
                end
              end,
              accessibility: :public,
              alias_of: nil,
              alias_member: nil
            )

            definition.methods[:new] = typed_new

            alias_methods.each do |alias_name|
              definition.methods[alias_name] = definition.methods[alias_name].update(
                alias_of: typed_new,
                defs: typed_new.defs
              )
            end
          end
        end
      end
    end
  end
end

#build_singleton0(type_name) ⇒ Definition

Builds a definition for singleton without .new method.

Parameters:

Returns:



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/rbs/definition_builder.rb', line 257

def build_singleton0(type_name)
  try_cache type_name, cache: singleton0_cache do
    entry = env.class_decls[type_name] or raise "Unknown name for build_singleton0: #{type_name}"
    ensure_namespace!(type_name.namespace, location: entry.primary_decl.location)

    ancestors = ancestor_builder.singleton_ancestors(type_name)
    self_type = Types::ClassSingleton.new(name: type_name, location: nil)

    Definition.new(type_name: type_name, entry: entry, self_type: self_type, ancestors: ancestors).tap do |definition|
      one_ancestors = ancestor_builder.one_singleton_ancestors(type_name)
      methods = method_builder.build_singleton(type_name)

      if super_class = one_ancestors.super_class
        case super_class
        when Definition::Ancestor::Instance
          defn = build_instance(super_class.name)
        when Definition::Ancestor::Singleton
          defn = build_singleton0(super_class.name)
        end

        definition.methods.merge!(defn.methods)
        definition.instance_variables.merge!(defn.instance_variables)
      end

      one_ancestors.each_extended_module do |mod|
        mod.args.each do |arg|
          validate_type_presence(arg)
        end

        subst = tapp_subst(mod.name, mod.args)
        define_instance(definition, mod.name, subst, define_class_vars: false)
      end

      all_interfaces = one_ancestors.each_extended_interface.flat_map do |interface|
        other_interfaces = ancestor_builder.interface_ancestors(interface.name).ancestors #: Array[Definition::Ancestor::Instance]
        other_interfaces = other_interfaces.select {|ancestor| ancestor.source }
        [interface, *other_interfaces]
      end
      interface_methods = interface_methods(all_interfaces)
      import_methods(definition, type_name, methods, interface_methods, Substitution.new, nil)

      entry.each_decl do |decl|
        decl.members.each do |member|
          case member
          when AST::Members::AttrReader, AST::Members::AttrAccessor, AST::Members::AttrWriter
            if member.kind == :singleton
              ivar_name = case member.ivar_name
                          when false
                            nil
                          else
                            member.ivar_name || :"@#{member.name}"
                          end

              if ivar_name
                insert_variable(type_name, definition.instance_variables, name: ivar_name, type: member.type, source: member)
              end
            end

          when AST::Members::ClassInstanceVariable
            insert_variable(type_name, definition.instance_variables, name: member.name, type: member.type, source: member)
          end
        end
      end

      instance_definition = build_instance(type_name)
      definition.class_variables.replace(instance_definition.class_variables)
    end
  end
end

#define_instance(definition, type_name, subst, define_class_vars:) ⇒ Object

Updates definition with methods and instance variables of type_name that can be a module or a class

It processes includes and prepends recursively. If define_class_vars: is falsy, it skips inserting class variables.



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'sig/definition_builder.rbs', line 160

def define_instance(definition, type_name, subst, define_class_vars:)
  one_ancestors = ancestor_builder.one_instance_ancestors(type_name)
  methods = method_builder.build_instance(type_name)

  self_type_methods = one_ancestors.each_self_type.with_object({}) do |self_type, hash| #$ Hash[Symbol, Definition::Method]
    self_type.args.each do |arg|
      validate_type_presence(arg)
    end

    self_type_defn = self_type.name.interface? ? build_interface(self_type.name) : build_instance(self_type.name)

    s = subst + tapp_subst(self_type.name, self_type.args)
    self_type_defn.methods.each do |method_name, method_def|
      hash[method_name] = method_def.sub(s)
    end
  end

  one_ancestors.each_included_module do |mod|
    mod.args.each do |arg|
      validate_type_presence(arg)
    end

    define_instance(definition, mod.name, subst + tapp_subst(mod.name, mod.args), define_class_vars: define_class_vars)
  end

  all_interfaces = one_ancestors.each_included_interface.flat_map do |interface|
    other_interfaces = ancestor_builder.interface_ancestors(interface.name).ancestors #: Array[Definition::Ancestor::Instance]
    other_interfaces = other_interfaces.select {|ancestor| ancestor.source }
    [interface, *other_interfaces]
  end
  interface_methods = interface_methods(all_interfaces)
  import_methods(definition, type_name, methods, interface_methods, subst, self_type_methods)

  one_ancestors.each_prepended_module do |mod|
    mod.args.each do |arg|
      validate_type_presence(arg)
    end

    define_instance(definition, mod.name, subst + tapp_subst(mod.name, mod.args), define_class_vars: define_class_vars)
  end

  entry = env.class_decls[type_name] or raise "Unknown name for build_instance: #{type_name}"
  args = entry.type_params.map {|param| Types::Variable.new(name: param.name, location: param.location) }

  entry.each_decl do |decl|
    subst_ = subst + Substitution.build(decl.type_params.each.map(&:name), args)

    decl.members.each do |member|
      case member
      when AST::Members::AttrReader, AST::Members::AttrAccessor, AST::Members::AttrWriter
        if member.kind == :instance
          ivar_name = case member.ivar_name
                      when false
                        nil
                      else
                        member.ivar_name || :"@#{member.name}"
                      end

          if ivar_name
            insert_variable(
              type_name,
              definition.instance_variables,
              name: ivar_name,
              type: member.type.sub(subst_),
              source: member
            )
          end
        end

      when AST::Ruby::Members::AttrReaderMember, AST::Ruby::Members::AttrWriterMember, AST::Ruby::Members::AttrAccessorMember
        member.names.each do |name|
          ivar_name = :"@#{name}"
          attr_type = member.type || Types::Bases::Any.new(location: nil)

          insert_variable(
            type_name,
            definition.instance_variables,
            name: ivar_name,
            type: attr_type,
            source: member
          )
        end

      when AST::Ruby::Members::InstanceVariableMember
        insert_variable(
          type_name,
          definition.instance_variables,
          name: member.name,
          type: member.type,
          source: member
        )

      when AST::Members::InstanceVariable
        insert_variable(
          type_name,
          definition.instance_variables,
          name: member.name,
          type: member.type.sub(subst_),
          source: member
        )

      when AST::Members::ClassVariable
        if define_class_vars
          insert_variable(type_name, definition.class_variables, name: member.name, type: member.type, source: member)
        end
      end
    end
  end
end

#define_interface(definition, type_name, subst) ⇒ void

This method returns an undefined value.

Updates definition with methods defined in an interface type_name

It processes includes recursively

Parameters:



171
172
173
174
175
176
177
178
179
# File 'sig/definition_builder.rbs', line 171

def define_interface(definition, type_name, subst)
  included_interfaces = ancestor_builder.interface_ancestors(type_name).ancestors #: Array[Definition::Ancestor::Instance]
  included_interfaces = included_interfaces.reject {|ancestor| ancestor.source == nil }

  interface_methods = interface_methods(included_interfaces)
  methods = method_builder.build_interface(type_name)

  import_methods(definition, type_name, methods, interface_methods, subst, nil)
end

#define_method(methods, definition, method, subst, self_type_methods, defined_in:, implemented_in: defined_in) ⇒ Object

Add method definition to methods, which will be merged to class_definition after defining all methods at this level -- class, module, or interface

class_definition is a definition of given type, but it has definitions of prior levels.



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
# File 'sig/definition_builder.rbs', line 124

def define_method(methods, definition, method, subst, self_type_methods, defined_in:, implemented_in: defined_in)
  existing_method = methods[method.name] || definition.methods[method.name]

  case original = method.original
  when AST::Members::Alias
    original_method = methods[original.old_name] || definition.methods[original.old_name] || self_type_methods&.fetch(original.old_name, nil)

    unless original_method
      raise UnknownMethodAliasError.new(
        type_name: definition.type_name,
        original_name: original.old_name,
        aliased_name: original.new_name,
        location: original.location
      )
    end

    accessibility = special_accessibility(original.instance?, original.new_name) || original_method.accessibility

    method_definition = Definition::Method.new(
      super_method: existing_method,
      defs: original_method.defs.map do |defn|
        defn.update(defined_in: defined_in, implemented_in: implemented_in)
      end,
      accessibility: accessibility,
      alias_of: original_method,
      alias_member: original
    )

    method_definition.annotations.replace(original.annotations)
  when AST::Members::MethodDefinition
    if duplicated_method = methods[method.name]
      raise DuplicatedMethodDefinitionError.new(
        type: definition.self_type,
        method_name: method.name,
        members: [original, *duplicated_method.members]
      )
    end

    defs = original.overloads.map do |overload|
      Definition::Method::TypeDef.new(
        type: subst.empty? ? overload.method_type : overload.method_type.sub(subst),
        member: original,
        defined_in: defined_in,
        implemented_in: implemented_in
      ).tap do |type_def|
        # Keep the original annotations given to overloads.
        type_def.overload_annotations.replace(overload.annotations)
      end
    end

    # Respect the visibility of the original method definition.
    accessibility = original.visibility || special_accessibility(original.instance?, method.name) || method.accessibility

    # Skip setting up `super_method` if `implemented_in` is `nil`, that means the type doesn't have implementation.
    # This typically happens if the type is an interface.
    if implemented_in
      super_method = existing_method
    end

    method_definition = Definition::Method.new(
      super_method: super_method,
      defs: defs,
      accessibility: accessibility,
      alias_of: nil,
      alias_member: nil
    )

    method_definition.annotations.replace(original.annotations)
  when AST::Members::AttrReader, AST::Members::AttrWriter, AST::Members::AttrAccessor
    if duplicated_method = methods[method.name]
      raise DuplicatedMethodDefinitionError.new(
        type: definition.self_type,
        method_name: method.name,
        members: [*duplicated_method.members, original]
      )
    end

    attr_type = original.type.sub(subst)
    method_type =
      if method.name.to_s.end_with?("=")
        # setter
        MethodType.new(
          type_params: [],
          type: Types::Function.empty(attr_type).update(
            required_positionals: [
              Types::Function::Param.new(type: attr_type, name: original.name)
            ]
          ),
          block: nil,
          location: original.location
        )
      else
        # getter
        MethodType.new(
          type_params: [],
          type: Types::Function.empty(attr_type),
          block: nil,
          location: original.location
        )
      end

    if implemented_in
      super_method = existing_method
    end

    # Respect the visibility of the original method definition.
    accessibility = original.visibility || special_accessibility(original.kind == :instance, method.name) || method.accessibility

    method_definition = Definition::Method.new(
      super_method: super_method,
      defs: [
        Definition::Method::TypeDef.new(
          type: method_type,
          member: original,
          defined_in: defined_in,
          implemented_in: implemented_in
        )
      ],
      accessibility: accessibility,
      alias_of: nil,
      alias_member: nil
    )

    method_definition.annotations.replace(original.annotations)
  when AST::Ruby::Members::AttrReaderMember, AST::Ruby::Members::AttrWriterMember, AST::Ruby::Members::AttrAccessorMember
    if duplicated_method = methods[method.name]
      raise DuplicatedMethodDefinitionError.new(
        type: definition.self_type,
        method_name: method.name,
        members: [*duplicated_method.members, original]
      )
    end

    attr_type = original.type || Types::Bases::Any.new(location: nil)
    method_type =
      if method.name.to_s.end_with?("=")
        # setter
        MethodType.new(
          type_params: [],
          type: Types::Function.empty(attr_type).update(
            required_positionals: [
              Types::Function::Param.new(type: attr_type, name: method.name.to_s.chomp("=").to_sym)
            ]
          ),
          block: nil,
          location: original.location
        )
      else
        # getter
        MethodType.new(
          type_params: [],
          type: Types::Function.empty(attr_type),
          block: nil,
          location: original.location
        )
      end

    if implemented_in
      super_method = existing_method
    end

    method_definition = Definition::Method.new(
      super_method: super_method,
      defs: [
        Definition::Method::TypeDef.new(
          type: method_type,
          member: original,
          defined_in: defined_in,
          implemented_in: implemented_in
        )
      ],
      accessibility: method.accessibility,
      alias_of: nil,
      alias_member: nil
    )

    method_definition.annotations.replace([])
  when AST::Ruby::Members::DefMember
    if duplicated_method = methods[method.name]
      raise DuplicatedMethodDefinitionError.new(
        type: definition.self_type,
        method_name: method.name,
        members: [original, *duplicated_method.members]
      )
    end

    if original.method_type.empty? && existing_method
      # Unannotated method with a parent definition → inherit from the parent, like @rbs ...
      method_definition = Definition::Method.new(
        super_method: existing_method,
        defs: existing_method.defs.map { |defn| defn.update(implemented_in: implemented_in) },
        accessibility: :public,
        alias_of: existing_method.alias_of,
        alias_member: nil
      )

      method_definition.annotations.replace(existing_method.annotations)
    else
      defs = original.overloads.map do |overload|
        Definition::Method::TypeDef.new(
          type: subst.empty? ? overload.method_type : overload.method_type.sub(subst),
          member: original,
          defined_in: defined_in,
          implemented_in: implemented_in
        ).tap do |type_def|
          # Keep the original annotations given to overloads.
          type_def.overload_annotations.replace(overload.annotations)
        end
      end

      method_definition = Definition::Method.new(
        super_method: existing_method,
        defs: defs,
        accessibility: :public,
        alias_of: nil,
        alias_member: nil
      )

      method_definition.annotations.replace([])
    end

  when nil
    # Overloading method definition only

    case
    when methods.key?(method.name)
      # The method is defined in an interface
      super_method = methods[method.name].super_method
    when definition.methods.key?(method.name)
      # The method is defined in the super class
      super_method = existing_method
    else
      # Cannot find any non-overloading method
      raise InvalidOverloadMethodError.new(
        type_name: definition.type_name,
        method_name: method.name,
        kind: :instance,
        members: method.overloads
      )
    end

    method_definition = Definition::Method.new(
      super_method: super_method,
      defs: existing_method.defs.map do |defn|
        defn.update(implemented_in: implemented_in)
      end,
      accessibility: existing_method.accessibility,
      alias_of: existing_method.alias_of,
      alias_member: nil
    )

    method_definition.annotations.replace(existing_method.annotations)
  end

  method.overloads.each do |overloading_def|
    overloading_def.overloads.reverse_each do |overload|
      type_def = Definition::Method::TypeDef.new(
        type: subst.empty? ? overload.method_type : overload.method_type.sub(subst),
        member: overloading_def,
        defined_in: defined_in,
        implemented_in: implemented_in
      )

      type_def.overload_annotations.replace(overload.annotations)

      method_definition.defs.unshift(type_def)
    end

    method_definition.annotations.concat(overloading_def.annotations)
  end

  method_definition.defs.each do |type_def|
    type_def.member_annotations.replace(method_definition.annotations)
  end

  methods[method.name] = method_definition
end

#ensure_namespace!(namespace, location:) ⇒ void

This method returns an undefined value.

Validates the given Namespace and its ancestor namespaces exists

Raises NoTypeFoundError

Parameters:



36
37
38
39
40
41
42
# File 'sig/definition_builder.rbs', line 36

def ensure_namespace!(namespace, location:)
  namespace.ascend do |ns|
    unless ns.empty?
      NoTypeFoundError.check!(ns.to_type_name, env: env, location: location)
    end
  end
end

#expand_alias(type_name) ⇒ Types::t

Expand a type alias of given name without type arguments. Raises an error if the type alias requires arguments.

Assume type foo[T] = [T, T]:

expand_alias("::foo")   # => error

Parameters:

Returns:

  • (Types::t)


47
48
49
# File 'sig/definition_builder.rbs', line 47

def expand_alias(type_name)
  expand_alias2(type_name, [])
end

#expand_alias1(type_name) ⇒ Types::t

Expand a type alias of given name with arguments of untyped.

Assume type foo[T] = [T, T]:

expand_alias1("::foo")   # => [untyped, untyped]

Parameters:

Returns:

  • (Types::t)


57
58
59
60
61
62
# File 'sig/definition_builder.rbs', line 57

def expand_alias1(type_name)
  type_name = env.normalize_type_name(type_name)
  entry = env.type_alias_decls[type_name] or raise "Unknown alias name: #{type_name}"
  as = entry.decl.type_params.each.map { Types::Bases::Any.new(location: nil) }
  expand_alias2(type_name, as)
end

#expand_alias2(type_name, args) ⇒ Types::t

Expand a type alias of given name with args.

Assume type foo[T] = [T, T]:

expand_alias2("::foo", ["::Integer"])   # => [::Integer, ::Integer]

Parameters:

Returns:

  • (Types::t)


67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'sig/definition_builder.rbs', line 67

def expand_alias2(type_name, args)
  type_name = env.normalize_type_name(type_name)
  entry = env.type_alias_decls[type_name] or raise "Unknown alias name: #{type_name}"

  ensure_namespace!(type_name.namespace, location: entry.decl.location)
  params = entry.decl.type_params.each.map(&:name)

  unless params.size == args.size
    as = "[#{args.join(", ")}]" unless args.empty?
    ps = "[#{params.join(", ")}]" unless params.empty?

    raise "Invalid type application: type = #{type_name}#{as}, decl = #{type_name}#{ps}"
  end

  type = entry.decl.type

  unless params.empty?
    subst = Substitution.build(params, args)
    type = type.sub(subst)
  end

  type
end

#import_methods(definition, module_name, module_methods, interfaces_methods, subst, self_type_methods) ⇒ Object

Add method definitions from module_methods to definition

It also processes interface mixins, with duplication detection. It doesn't process module mixins.



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'sig/definition_builder.rbs', line 146

def import_methods(definition, module_name, module_methods, interfaces_methods, subst, self_type_methods)
  new_methods = {} #: Hash[Symbol, Definition::Method]
  interface_method_duplicates = Set[] #: Set[Symbol]

  interfaces_methods.each do |interface, (methods, member)|
    unless interface.args.empty?
      methods.type.is_a?(Types::Interface) or raise

      interface.args.each do |arg|
        validate_type_presence(arg)
      end

      type_params = env.interface_decls.fetch(interface.name).decl.type_params
      if s = AST::TypeParam.application(type_params, interface.args)
        subst_ = subst + s
      else
        subst_ = subst
      end
    else
      subst_ = subst
    end

    methods.each do |method|
      if interface_method_duplicates.include?(method.name)
        (member.is_a?(AST::Members::Include) || member.is_a?(AST::Members::Extend)) or raise

        raise DuplicatedInterfaceMethodDefinitionError.new(
          type: definition.self_type,
          method_name: method.name,
          member: member
        )
      end

      interface_method_duplicates << method.name
      define_method(
        new_methods,
        definition,
        method,
        subst_,
        nil,
        defined_in: interface.name,
        implemented_in: module_name
      )
    end
  end

  module_methods.each do |method|
    define_method(
      new_methods,
      definition,
      method,
      subst,
      self_type_methods,
      defined_in: module_name,
      implemented_in: module_name.interface? ? nil : module_name
    )
  end

  definition.methods.merge!(new_methods)
end

#insert_variable(type_name, variables, name:, type:, source:) ⇒ Object



583
584
585
586
587
588
589
590
591
592
# File 'lib/rbs/definition_builder.rb', line 583

def insert_variable(type_name, variables, name:, type:, source:)
  variables[name] = Definition::Variable.new(
    parent_variable: variables[name],
    type: type,
    declared_in: type_name,
    source: source
  )

  validate_variable(variables[name])
end

#interface_methods(interface_ancestors) ⇒ interface_methods

Returns interface_methods from given array of interface ancestors

Parameters:

Returns:



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'sig/definition_builder.rbs', line 98

def interface_methods(interface_ancestors)
  interface_methods = {} #: interface_methods

  interface_ancestors.each do |mod|
    source =
      case mod.source
      when AST::Members::Include, AST::Members::Extend
        mod.source
      else
        raise "Interface mixin must be include/extend: #{mod.source.inspect}"
      end

    methods = method_builder.build_interface(mod.name)

    interface_methods[mod] = [methods, source]
  end

  interface_methods
end

#source_location(source, decl) ⇒ Location[untyped, untyped]?

Parameters:

  • (Definition::Ancestor::Instance::source)
  • (AST::Declarations::t, AST::Ruby::Declarations::t)

Returns:



483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
# File 'lib/rbs/definition_builder.rb', line 483

def source_location(source, decl)
  case source
  when nil
    decl.location
  when :super
    case decl
    when AST::Declarations::Class
      decl.super_class&.location
    when AST::Ruby::Declarations::ClassDecl
      nil
    else
      raise "Unexpected `:super` source location with #{decl.class}"
    end
  else
    source.location
  end
end

#special_accessibility(is_instance, method_name) ⇒ Definition::accessibility?

Parameters:

  • (Boolean)
  • (Symbol)

Returns:

  • (Definition::accessibility, nil)


973
974
975
976
977
# File 'lib/rbs/definition_builder.rb', line 973

def special_accessibility(is_instance, method_name)
  if is_instance && [:initialize, :initialize_copy, :initialize_clone, :initialize_dup, :respond_to_missing?].include?(method_name)
    :private
  end
end

#tapp_subst(name, args) ⇒ Substitution

Returns a substitution that corresponds to type application

tapp_subst(`::Array`, [`::Integer`]) # => Substitution.build([:Elem], [`::Integer`])

Parameters:

Returns:



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'sig/definition_builder.rbs', line 179

def tapp_subst(name, args)
  params =
    case
    when name.interface?
      entry = env.interface_decls[name] or raise "Unknown interface name: #{name}"
      entry.decl.type_params
    when name.alias?
      entry = env.type_alias_decls[name] or raise "Unknown alias name: #{name}"
      entry.decl.type_params
    when name.class?
      entry = env.class_decls[name] or raise "Unknown module name: #{name}"
      entry.type_params
    else
      raise
    end

  AST::TypeParam.application(params, args) || Substitution.new()
end

#try_cache(type_name, cache:) { ... } ⇒ Definition

Parameters:

Yields:

Yield Returns:

Returns:



979
980
981
# File 'lib/rbs/definition_builder.rb', line 979

def try_cache(type_name, cache:)
  cache[type_name] ||= yield
end

#update(env:, except:, ancestor_builder:) ⇒ DefinitionBuilder

Returns a new DefinitionBuilder with updated Environment, AncestorBuilder, and exceptions

Parameters:

Returns:



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'sig/definition_builder.rbs', line 88

def update(env:, except:, ancestor_builder:)
  method_builder = self.method_builder.update(env: env, except: except)

  DefinitionBuilder.new(env: env, ancestor_builder: ancestor_builder, method_builder: method_builder).tap do |builder|
    builder.instance_cache.merge!(instance_cache)
    builder.singleton_cache.merge!(singleton_cache)
    builder.singleton0_cache.merge!(singleton0_cache)
    builder.interface_cache.merge!(interface_cache)

    except.each do |name|
      builder.instance_cache.delete(name)
      builder.singleton_cache.delete(name)
      builder.singleton0_cache.delete(name)
      builder.interface_cache.delete(name)
    end
  end
end

#validate_params_with(type_params, result:) {|arg0| ... } ⇒ void

This method returns an undefined value.

Parameters:

Yields:

Yield Parameters:

Yield Returns:

  • (void)


473
474
475
476
477
478
479
480
481
# File 'lib/rbs/definition_builder.rb', line 473

def validate_params_with(type_params, result:)
  type_params.each do |param|
    unless param.unchecked?
      unless result.compatible?(param.name, with_annotation: param.variance)
        yield param
      end
    end
  end
end

#validate_super_class!void

This method returns an undefined value.



92
# File 'sig/definition_builder.rbs', line 92

def validate_super_class!: (TypeName, Environment::ClassEntry) -> void

#validate_type_name(name, location) ⇒ void

This method returns an undefined value.

Validates presence of an absolute type name

Raises NoTypeFoundError in case of error.

Normalizes the given type name automatically.

Parameters:



84
85
86
87
88
89
# File 'sig/definition_builder.rbs', line 84

def validate_type_name(name, location)
  name = name.absolute! unless name.absolute?
  return if env.type_name?(env.normalize_type_name(name))

  raise NoTypeFoundError.new(type_name: name, location: location)
end

#validate_type_params(definition, ancestors:, methods:) ⇒ void

This method returns an undefined value.

Parameters:



501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
# File 'lib/rbs/definition_builder.rb', line 501

def validate_type_params(definition, ancestors:, methods:)
  type_params = definition.type_params_decl

  calculator = VarianceCalculator.new(builder: self)
  param_names = type_params.each.map(&:name)

  ancestors.each_ancestor do |ancestor|
    case ancestor
    when Definition::Ancestor::Instance
      result = calculator.in_inherit(name: ancestor.name, args: ancestor.args, variables: param_names)
      validate_params_with(type_params, result: result) do |param|
        decl = case entry = definition.entry
               when Environment::ModuleEntry, Environment::ClassEntry
                 entry.primary_decl
               when Environment::SingleEntry
                 entry.decl
               end

        raise InvalidVarianceAnnotationError.new(
          type_name: definition.type_name,
          param: param,
          location: source_location(ancestor.source, decl)
        )
      end
    end
  end

  methods.each do |defn|
    next if defn.name == :initialize

    method_types = case original = defn.original
                   when AST::Members::MethodDefinition
                     original.overloads.map(&:method_type)
                   when AST::Members::AttrWriter, AST::Members::AttrReader, AST::Members::AttrAccessor
                     if defn.name.to_s.end_with?("=")
                       [
                         MethodType.new(
                           type_params: [],
                           type: Types::Function.empty(original.type).update(
                             required_positionals: [
                               Types::Function::Param.new(type: original.type, name: original.name)
                             ]
                           ),
                           block: nil,
                           location: original.location
                         )
                       ]
                     else
                       [
                         MethodType.new(
                           type_params: [],
                           type: Types::Function.empty(original.type),
                           block: nil,
                           location: original.location
                         )
                       ]
                     end
                   when AST::Members::Alias
                     nil
                   when nil
                     nil
                   end

    if method_types
      method_types.each do |method_type|
        merged_params = type_params
          .reject {|param| method_type.type_param_names.include?(param.name) }
          .concat(method_type.type_params)

        result = calculator.in_method_type(method_type: method_type, variables: param_names)
        validate_params_with(merged_params, result: result) do |param|
          raise InvalidVarianceAnnotationError.new(
            type_name: definition.type_name,
            param: param,
            location: method_type.location
          )
        end
      end
    end
  end
end

#validate_type_presence(type) ⇒ void

This method returns an undefined value.

Validates presence of type names recursively

Assumes the type names are already resolved. Raises NoTypeFoundError in case of failure.

Normalizes type names in the given type automatically.

Parameters:

  • (Types::t)


76
77
78
79
80
81
82
83
84
85
# File 'sig/definition_builder.rbs', line 76

def validate_type_presence(type)
  case type
  when Types::ClassInstance, Types::ClassSingleton, Types::Interface, Types::Alias
    validate_type_name(type.name, type.location)
  end

  type.each_type do |type|
    validate_type_presence(type)
  end
end

#validate_variable(var) ⇒ void

This method returns an undefined value.

Parameters:



594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
# File 'lib/rbs/definition_builder.rb', line 594

def validate_variable(var)
  return unless var.parent_variable

  # Ignore attrs
  variables = [] #: Array[Definition::Variable]
  tmp_var = var
  while tmp_var
    case tmp_var.source
    when AST::Members::AttrReader, AST::Members::AttrWriter, AST::Members::AttrAccessor
      # nop
    when AST::Ruby::Members::AttrReaderMember, AST::Ruby::Members::AttrWriterMember, AST::Ruby::Members::AttrAccessorMember
      # nop
    else
      variables << tmp_var
    end

    tmp_var = tmp_var.parent_variable
  end

  # Duplicates should be eliminated, so there can't be more than 3.
  return unless variables.length == 2

  l, r = variables #: [Definition::Variable, Definition::Variable]

  case l.source
  when AST::Members::InstanceVariable
    if r.source.instance_of?(AST::Members::InstanceVariable) && l.declared_in == r.declared_in
      raise InstanceVariableDuplicationError.new(type_name: l.declared_in, variable_name: l.source.name, location: l.source.location)
    end
  when AST::Members::ClassInstanceVariable
    if r.source.instance_of?(AST::Members::ClassInstanceVariable) && l.declared_in == r.declared_in
      raise ClassInstanceVariableDuplicationError.new(type_name: l.declared_in, variable_name: l.source.name, location: l.source.location)
    end
  when AST::Ruby::Members::InstanceVariableMember
    if l.declared_in == r.declared_in
      raise InstanceVariableDuplicationError.new(type_name: l.declared_in, variable_name: l.source.name, location: l.source.location)
    end
  end
end