Class: Steep::TypeAssignability

Inherits:
Object
  • Object
show all
Defined in:
lib/steep/type_assignability.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeTypeAssignability

Returns a new instance of TypeAssignability.



6
7
8
9
10
11
12
13
14
15
16
# File 'lib/steep/type_assignability.rb', line 6

def initialize()
  @signatures = {}
  @klasses = []
  @instances = []
  @errors = []

  if block_given?
    yield self
    validate
  end
end

Instance Attribute Details

#errorsObject (readonly)

Returns the value of attribute errors.



4
5
6
# File 'lib/steep/type_assignability.rb', line 4

def errors
  @errors
end

#signaturesObject (readonly)

Returns the value of attribute signatures.



3
4
5
# File 'lib/steep/type_assignability.rb', line 3

def signatures
  @signatures
end

Instance Method Details

#add_signature(signature) ⇒ Object



35
36
37
38
# File 'lib/steep/type_assignability.rb', line 35

def add_signature(signature)
  raise "Signature Duplicated: #{signature.name}" if signatures.key?(signature.name)
  signatures[signature.name] = signature
end

#compact(types) ⇒ Object



336
337
338
339
340
341
342
343
344
# File 'lib/steep/type_assignability.rb', line 336

def compact(types)
  types = types.reject {|type| type.is_a?(Types::Any) }
  
  if types.empty?
    [Types::Any.new]
  else
    compact0(types)
  end
end

#compact0(types) ⇒ Object



346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# File 'lib/steep/type_assignability.rb', line 346

def compact0(types)
  if types.size == 1
    types
  else
    type, *types_ = types
    compacted = compact0(types_)
    compacted.flat_map do |type_|
      case
      when type == type_
        [type]
      when test(src: type_, dest: type)
        [type]
      when test(src: type, dest: type_)
        [type_]
      else
        [type, type_]
      end
    end.uniq
  end
end

#instanceObject



31
32
33
# File 'lib/steep/type_assignability.rb', line 31

def instance
  @instances.last
end

#klassObject



27
28
29
# File 'lib/steep/type_assignability.rb', line 27

def klass
  @klasses.last
end

#lookup_class_signature(type) ⇒ Object



259
260
261
262
263
264
265
266
267
268
# File 'lib/steep/type_assignability.rb', line 259

def lookup_class_signature(type)
  raise "#{self.class}#lookup_class_signature expects type name: #{type.inspect}" unless type.is_a?(Types::Name)
  raise "#{self.class}#lookup_class_signature expects instance name: #{type.name.inspect}" unless type.name.is_a?(TypeName::Instance)

  signature = signatures[type.name.name]

  raise "#{self.class}#lookup_super_class_signature expects class: #{signature.inspect}" unless signature.is_a?(Signature::Class)

  signature
end

#lookup_extensions(module_name) ⇒ Object



270
271
272
273
274
275
276
277
# File 'lib/steep/type_assignability.rb', line 270

def lookup_extensions(module_name)
  signatures.values.select do |signature|
    case signature
    when Signature::Extension
      signature.module_name == module_name
    end
  end
end

#lookup_included_signature(type) ⇒ Object



241
242
243
244
245
246
# File 'lib/steep/type_assignability.rb', line 241

def lookup_included_signature(type)
  raise "#{self.class}#lookup_included_signature expects type name: #{type.inspect}" unless type.is_a?(Types::Name)
  raise "#{self.class}#lookup_included_signature expects module instance name: #{type.name.inspect}" unless type.name.is_a?(TypeName::Instance)

  signatures[type.name.name]
end

#lookup_super_class_signature(type) ⇒ Object



248
249
250
251
252
253
254
255
256
257
# File 'lib/steep/type_assignability.rb', line 248

def lookup_super_class_signature(type)
  raise "#{self.class}#lookup_super_class_signature expects type name: #{type.inspect}" unless type.is_a?(Types::Name)
  raise "#{self.class}#lookup_super_class_signature expects module instance name: #{type.name.inspect}" unless type.name.is_a?(TypeName::Instance)

  signature = signatures[type.name.name]

  raise "#{self.class}#lookup_super_class_signature expects class: #{type.name.inspect}" unless signature.is_a?(Signature::Class)

  signature
end

#method_type(type, name) ⇒ Object



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
# File 'lib/steep/type_assignability.rb', line 279

def method_type(type, name)
  case type
  when Types::Any
    return type
  when Types::Merge
    methods = type.types.map {|t|
      resolve_interface(t.name, t.params, klass: Types::Var.new(name: :some_klass), instance: Types::Var.new(name: :some_instance))
    }.each.with_object({}) {|interface, methods|
      methods.merge! interface.methods
    }
    method = methods[name]
  when Types::Name
    constructor = type.name.is_a?(TypeName::Module) && type.name.constructor
    interface = resolve_interface(type.name, type.params, constructor: constructor)
    method = interface.methods[name]
  else
    raise "Unexpected type: #{type}"
  end

  if method
    yield(method) || Types::Any.new
  else
    yield(nil) || Types::Any.new
  end
end

#resolve_interface(name, params, klass: nil, instance: nil, constructor: nil) ⇒ Object



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/steep/type_assignability.rb', line 223

def resolve_interface(name, params, klass: nil, instance: nil, constructor: nil)
  klass ||= Types::Name.module(name: name.name, params: params)
  instance ||= Types::Name.instance(name: name.name, params: params)

  case name
  when TypeName::Interface
    signatures[name.name].to_interface(klass: klass, instance: instance, params: params)
  when TypeName::Instance
    methods = signatures[name.name].instance_methods(assignability: self, klass: klass, instance: instance, params: params)
    Interface.new(name: name, params: params, methods: methods.reject {|key, _| key == :initialize })
  when TypeName::Module
    methods = signatures[name.name].module_methods(assignability: self, klass: klass, instance: instance, params: params, constructor: constructor)
    Interface.new(name: name, params: params, methods: methods)
  else
    raise "Unexpected type name: #{name.inspect}"
  end
end

#test(src:, dest:, known_pairs: []) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/steep/type_assignability.rb', line 40

def test(src:, dest:, known_pairs: [])
  case
  when src.is_a?(Types::Any) || dest.is_a?(Types::Any)
    true
  when src == dest
    true
  when src.is_a?(Types::Union)
    src.types.all? do |type|
      test(src: type, dest: dest, known_pairs: known_pairs)
    end
  when dest.is_a?(Types::Union)
    dest.types.any? do |type|
      test(src: src, dest: type, known_pairs: known_pairs)
    end
  when src.is_a?(Types::Var) || dest.is_a?(Types::Var)
    known_pairs.include?([src, dest])
  when src.is_a?(Types::Name) && dest.is_a?(Types::Name)
    test_interface(resolve_interface(src.name, src.params), resolve_interface(dest.name, dest.params), known_pairs)
  else
    raise "Unexpected type: src=#{src.inspect}, dest=#{dest.inspect}, known_pairs=#{known_pairs.inspect}"
  end
end

#test_application(params:, argument:, index:) ⇒ Object



63
64
65
66
67
68
69
70
# File 'lib/steep/type_assignability.rb', line 63

def test_application(params:, argument:, index:)
  param_type = params.flat_unnamed_params[index]&.last
  if param_type
    unless test(src: argument, dest: param_type)
      yield param_type
    end
  end
end

#test_block(src, dest, known_pairs) ⇒ Object



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
# File 'lib/steep/type_assignability.rb', line 195

def test_block(src, dest, known_pairs)
  return true if !src && !dest
  return false if !src || !dest

  raise "Keyword args for block is not yet supported" unless src.params&.flat_keywords&.empty?
  raise "Keyword args for block is not yet supported" unless dest.params&.flat_keywords&.empty?

  ss = src.params.flat_unnamed_params
  ds = dest.params.flat_unnamed_params

  max = ss.size > ds.size ? ss.size : ds.size

  for i in 0...max
    s = ss[i]&.last || src.params.rest
    d = ds[i]&.last || dest.params.rest

    if s && d
      test(src: s, dest: d, known_pairs: known_pairs) or return false
    end
  end

  if src.params.rest && dest.params.rest
    test(src: src.params.rest, dest: dest.params.rest, known_pairs: known_pairs) or return false
  end

  test(src: dest.return_type, dest: src.return_type, known_pairs: known_pairs)
end

#test_interface(src, dest, known_pairs) ⇒ Object



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/steep/type_assignability.rb', line 72

def test_interface(src, dest, known_pairs)
  if src == dest
    return true
  end

  if known_pairs.include?([src, dest])
    return true
  end

  pairs = known_pairs + [[src, dest]]

  dest.methods.all? do |name, dest_methods|
    if src.methods.key?(name)
      src_methods = src.methods[name]

      dest_methods.types.all? do |dest_method|
        src_methods.types.any? do |src_method|
          test_method(src_method, dest_method, pairs)
        end
      end
    end
  end
end

#test_method(src, dest, known_pairs) ⇒ Object



96
97
98
99
100
# File 'lib/steep/type_assignability.rb', line 96

def test_method(src, dest, known_pairs)
  test_params(src.params, dest.params, known_pairs) &&
    test_block(src.block, dest.block, known_pairs) &&
    test(src: src.return_type, dest: dest.return_type, known_pairs: known_pairs)
end

#test_params(src, dest, known_pairs) ⇒ Object



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
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
# File 'lib/steep/type_assignability.rb', line 102

def test_params(src, dest, known_pairs)
  assigning_pairs = []

  src_flat = src.flat_unnamed_params
  dest_flat = dest.flat_unnamed_params

  case
  when dest.rest
    return false unless src.rest

    while src_flat.size > 0
      src_type = src_flat.shift
      dest_type = dest_flat.shift

      if dest_type
        assigning_pairs << [src_type.last, dest_type.last]
      else
        assigning_pairs << [src_type.last, dest.rest]
      end
    end

    if src.rest
      assigning_pairs << [src.rest, dest.rest]
    end
  when src.rest
    while src_flat.size > 0
      src_type = src_flat.shift
      dest_type = dest_flat.shift

      if dest_type
        assigning_pairs << [src_type.last, dest_type.last]
      else
        break
      end
    end

    if src.rest && !dest_flat.empty?
      dest_flat.each do |dest_type|
        assigning_pairs << [src.rest, dest_type.last]
      end
    end
  when src.required.size + src.optional.size >= dest.required.size + dest.optional.size
    while src_flat.size > 0
      src_type = src_flat.shift
      dest_type = dest_flat.shift

      if dest_type
        assigning_pairs << [src_type.last, dest_type.last]
      else
        if src_type.first == :required
          return false
        else
          break
        end
      end
    end
  else
    return false
  end

  src_flat_kws = src.flat_keywords
  dest_flat_kws = dest.flat_keywords

  dest_flat_kws.each do |name, _|
    if src_flat_kws.key?(name)
      assigning_pairs << [src_flat_kws[name], dest_flat_kws[name]]
    else
      if src.rest_keywords
        assigning_pairs << [src.rest_keywords, dest_flat_kws[name]]
      else
        return false
      end
    end
  end

  src.required_keywords.each do |name, _|
    unless dest.required_keywords.key?(name)
      return false
    end
  end

  if src.rest_keywords && dest.rest_keywords
    assigning_pairs << [src.rest_keywords, dest.rest_keywords]
  end

  assigning_pairs.all? do |pair|
    src_type = pair.first
    dest_type = pair.last

    test(src: dest_type, dest: src_type, known_pairs: known_pairs)
  end
end

#validateObject



305
306
307
308
309
# File 'lib/steep/type_assignability.rb', line 305

def validate
  signatures.each do |name, signature|
    signature.validate(self)
  end
end

#validate_method_compatibility(signature, method_name, method) ⇒ Object



319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/steep/type_assignability.rb', line 319

def validate_method_compatibility(signature, method_name, method)
  if method.super_method
    test = method.types.all? {|method_type|
      method.super_method.types.any? {|super_type|
        test_method(method_type, super_type, [])
      }
    }

    unless test
      errors << Signature::Errors::IncompatibleOverride.new(signature: signature,
                                                            method_name: method_name,
                                                            this_method: method.types,
                                                            super_method: method.super_method.types)
    end
  end
end

#validate_type_presence(signature, type) ⇒ Object



311
312
313
314
315
316
317
# File 'lib/steep/type_assignability.rb', line 311

def validate_type_presence(signature, type)
  if type.is_a?(Types::Name)
    unless signatures[type.name.name]
      errors << Signature::Errors::UnknownTypeName.new(signature: signature, type: type)
    end
  end
end

#with(klass: nil, instance: nil, &block) ⇒ Object



18
19
20
21
22
23
24
25
# File 'lib/steep/type_assignability.rb', line 18

def with(klass: nil, instance: nil, &block)
  @klasses.push(klass) if klass
  @instances.push(instance) if instance
  yield
ensure
  @klasses.pop if klass
  @instances.pop if instance
end