Module: Mirah::JVM::MethodLookup

Included in:
Compiler::JVMBytecode, Types::Type
Defined in:
lib/mirah/jvm/method_lookup.rb

Instance Method Summary collapse

Instance Method Details

#each_is_exact(incoming, target) ⇒ Object



269
270
271
# File 'lib/mirah/jvm/method_lookup.rb', line 269

def each_is_exact(incoming, target)
  incoming.zip(target).all? { |in_type, target_type| target_type == in_type }
end

#each_is_exact_or_subtype_or_convertible(incoming, target) ⇒ Object



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
# File 'lib/mirah/jvm/method_lookup.rb', line 273

def each_is_exact_or_subtype_or_convertible(incoming, target)
  incoming.zip(target).each do |in_type, target_type|

    # exact match
    next if target_type == in_type

    unless target_type.respond_to?(:primitive?) && in_type.respond_to?(:primitive?)
      puts "Huh?"
    end

    # primitive is safely convertible
    if target_type.primitive?
      if in_type.primitive?
        next if primitive_convertible? in_type, target_type
      end
      return false
    end

    # object type is assignable
    compatible = if target_type.respond_to?(:compatible?)
      target_type.compatible? in_type
    else
      target_type.assignable_from? in_type
    end
    return false unless compatible
  end
  return true
end

#field_lookup(mapped_params, mapped_type, meta, name, scope) ⇒ Object



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
# File 'lib/mirah/jvm/method_lookup.rb', line 219

def field_lookup(mapped_params, mapped_type, meta, name, scope)
  log("Attempting #{meta ? 'static' : 'instance'} field lookup for '#{name}' on class #{mapped_type}")
  # if we get to this point, the potentials do not match, so we ignore them


  # search for a field of the given name
  if name =~ /_set$/
    # setter
    setter = true
    name = name[0..-5]
    field = mapped_type.field_setter(name)
  else
    # getter
    setter = false

    # field accesses don't take arguments
    return if mapped_params.size > 0
    field = mapped_type.field_getter(name)
  end

  return nil unless field

  if (meta && !field.static?) ||
      (!meta && field.static?)
    return nil
  end

  # check accessibility
  # TODO: protected field access check appropriate to current type
  if setter
    if field.final?
      log "cannot set final field '#{name}' on class #{mapped_type}"
      return nil
    end
  end
  unless field.public?
    from = " from #{scope.selfType.resolve.name}" if scope
    log "cannot access field '#{name}' on class #{mapped_type}#{from}"
    return nil
  end

  field
end

#find_jls(mapped_type, name, mapped_params, macro_params, meta, constructor, scope = nil) ⇒ Object



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/mirah/jvm/method_lookup.rb', line 97

def find_jls(mapped_type, name, mapped_params, macro_params, meta, constructor, scope=nil)
  interfaces = []
  by_name = if constructor
    mapped_type.unmeta.declared_constructors
  elsif meta
    mapped_type.find_callable_static_methods(name)
  else
    mapped_type.find_callable_methods(name)
  end
  method = find_jls2(mapped_type, name, mapped_params, meta, by_name, true, scope)
  return method if (constructor || macro_params.nil?)
  macros = mapped_type.find_callable_macros(name)
  if macros.size != 0
    log "Found potential macro match for #{mapped_type.name}.#{name}(#{macro_params.map(&:full_name).join ', '})"
    macro = find_jls2(mapped_type, name, macro_params, meta, macros, false, scope)
  end
  if macro && method
    raise "Ambiguous targets invoking #{mapped_type}.#{name}:\n#{macro} and #{method}"
  end
  macro || method
end

#find_jls2(mapped_type, name, mapped_params, meta, by_name, include_fields = true, scope = nil) ⇒ Object



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
# File 'lib/mirah/jvm/method_lookup.rb', line 119

def find_jls2(mapped_type, name, mapped_params, meta, by_name, include_fields=true, scope=nil)
  return nil if mapped_params.any? {|p| p.nil? || p.isError}

  # filter by arity, varargs
  by_name_and_arity = by_name.select {|m| m.argument_types.size == mapped_params.size }

  phase1_methods = phase1(mapped_params, by_name_and_arity)

  if phase1_methods.size > 1
    method_list = phase1_methods.map do |m|
      case m.member
      when BiteScript::ASM::MethodMirror
        m.member.inspect
      else
        "#{m.name}(#{m.parameter_types.map(&:name).join(', ')})"
      end
    end.join("\n")
    raise "Ambiguous targets invoking #{mapped_type}.#{name}:\n#{method_list}"
  end

  phase1_methods[0] ||
    phase2(mapped_params, by_name) ||
    phase3(mapped_params, by_name)[0] ||
    (include_fields &&
      (field_lookup(mapped_params, mapped_type, meta, name, scope) ||
       inner_class(mapped_params, mapped_type, meta, name)))
end

#find_method(mapped_type, name, mapped_params, macro_params, meta, scope = nil, &block) ⇒ Object



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
# File 'lib/mirah/jvm/method_lookup.rb', line 31

def find_method(mapped_type, name, mapped_params, macro_params, meta, scope=nil, &block)
  if mapped_type.error?
    raise "WTF?!?"
  end

  if name == 'new' && meta # we're calling SomeClass.new
    name = "<init>"
    constructor = true
    mapped_type = mapped_type.unmeta
    meta = false
  elsif name == 'new' && !meta # we're calling some_instance.new
    constructor = false
  elsif name == '<init>' && !meta # how do we get here?
    constructor = true
  elsif name == 'initialize' && !meta && scope
    context = scope.context
    if context.kind_of?(ConstructorDefinition) || context.findAncesor(ConstructorDefinition.class)
      constructor = true
      name = '<init>'
    end
  end

  if block_given?
    if constructor
      mapped_type.add_method_listener('initialize') {block.call(find_method2(mapped_type.meta, 'new', mapped_params, macro_params, true))}
    else
      mapped_type.add_method_listener(name) {block.call(find_method2(mapped_type, name, mapped_params, macro_params, meta))}
    end
    block.call(find_method(mapped_type, name, mapped_params, macro_params, meta))
    return
  end

  begin
    unless mapped_params.any? {|p| p.nil? || p.isError}
      if constructor
        method = mapped_type.constructor(*mapped_params)
      else
        method = mapped_type.java_method(name, *mapped_params)
      end
    end
  rescue NameError => ex
    # TODO return nil instead of raising an exception if the method doesn't exist.
    raise ex unless ex.message =~ /#{Regexp.quote(mapped_type.name)}\.#{Regexp.quote(name)}|No constructor #{Regexp.quote(mapped_type.name)}/
  end

  macro = mapped_type.macro(name, macro_params)
  if method && macro
    method = nil  # Need full lookup to determine precedence.
  elsif method.nil? && macro
    method = macro
  elsif method.nil?
    # exact args failed, do a deeper search
    log "No exact match for #{mapped_type.name}.#{name}(#{mapped_params.map(&:name).join ', '})" if mapped_params.all?

    method = find_jls(mapped_type, name, mapped_params, macro_params, meta, constructor, scope)

    unless method
      log "Failed to locate method #{mapped_type.name}.#{name}(#{mapped_params.map(&:name).join ', '})" if mapped_params.all?
      return nil
    end
  end

  log "Found method #{method.declaring_class.name}.#{name}(#{method.argument_types.map(&:name).join ', '}) from #{mapped_type.name}" if method
  return method
end

#find_method2(mapped_type, name, mapped_params, macro_params, meta, scope = nil, &block) ⇒ Object



21
22
23
24
25
26
27
28
29
# File 'lib/mirah/jvm/method_lookup.rb', line 21

def find_method2(mapped_type, name, mapped_params, macro_params, meta, scope=nil, &block)
  find_method(mapped_type, name, mapped_params, macro_params, meta, scope, &block)
rescue NameError => ex
  raise ex unless ex.message.include?(name)
  if block_given?
    block.call(ex)
  end
  ex
end

#inner_class(params, type, meta, name) ⇒ Object



263
264
265
266
267
# File 'lib/mirah/jvm/method_lookup.rb', line 263

def inner_class(params, type, meta, name)
  return unless params.empty? && meta
  log("Attempting inner class lookup for '#{name}' on #{type}")
  type.inner_class_getter(name)
end

#is_more_specific?(potential, current) ⇒ Boolean

Returns:

  • (Boolean)


188
189
190
# File 'lib/mirah/jvm/method_lookup.rb', line 188

def is_more_specific?(potential, current)
  each_is_exact_or_subtype_or_convertible(potential, current)
end

#phase1(mapped_params, potentials) ⇒ Object



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
# File 'lib/mirah/jvm/method_lookup.rb', line 147

def phase1(mapped_params, potentials)
  log "Beginning JLS phase 1 search with params (#{mapped_params.map(&:name).join ', '})"

  # cycle through methods looking for more specific matches; gather matches of equal specificity
  methods = potentials.inject([]) do |currents, potential|
    method_params = potential.argument_types
    next currents unless method_params.all?

    # exact match always wins; duplicates not possible
    if each_is_exact(mapped_params, method_params)
      return [potential]
    end

    # otherwise, check for potential match and compare to current
    # TODO: missing ambiguity check; picks first method of equal specificity
    # Picking the first method means we prefer methods from the child class,
    # which is important if the parent class is not accessible (like AbstractStringBuilder).
    if each_is_exact_or_subtype_or_convertible(mapped_params, method_params)
      if !currents.empty?
        if is_more_specific?(currents[0].argument_types, potential.argument_types)
          # currents are better, try next potential
          #next
        elsif is_more_specific?(potential.argument_types, currents[0].argument_types)
          # potential is better, dump all currents
          currents = [potential]
        else
          # equal specificity, append to currents
          currents << potential
        end
      else
        # no previous matches, use potential
        currents = [potential]
      end
    end

    currents
  end

  methods
end

#phase2(mapped_params, potentials) ⇒ Object



192
193
194
# File 'lib/mirah/jvm/method_lookup.rb', line 192

def phase2(mapped_params, potentials)
  nil
end

#phase3(mapped_params, potentials) ⇒ Object



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/mirah/jvm/method_lookup.rb', line 196

def phase3(mapped_params, potentials)
  potential_varargs = potentials.select{|m| m.respond_to?(:varargs?) && m.varargs? }
  methods = potential_varargs.inject([]) do |currents, potential|
    method_params = potential.argument_types
    # match n-1 params of potential
    non_varargs_params, possible_varargs_params = mapped_params.partition.with_index{|param,i| i < method_params.size-1}
    
    vararg_types = possible_varargs_params.size.times.map{ method_params.last.component_type }
    
    if each_is_exact(non_varargs_params, method_params[0..-2]) &&
        each_is_exact(possible_varargs_params, vararg_types)
      return [potential]
    end
    
    if each_is_exact_or_subtype_or_convertible(non_varargs_params, method_params[0..-2]) &&
        each_is_exact_or_subtype_or_convertible(possible_varargs_params, vararg_types)
      currents << potential
    end

    currents
  end
end

#primitive_convertible?(in_type, target_type) ⇒ Boolean

Returns:

  • (Boolean)


302
303
304
# File 'lib/mirah/jvm/method_lookup.rb', line 302

def primitive_convertible?(in_type, target_type)
  in_type.convertible_to?(target_type)
end