Module: Mirah::JVM::MethodLookup

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

Instance Method Summary collapse

Instance Method Details

#each_is_exact(incoming, target) ⇒ Object



195
196
197
198
199
200
201
202
203
# File 'lib/mirah/jvm/method_lookup.rb', line 195

def each_is_exact(incoming, target)
  incoming.each_with_index do |in_type, i|
    target_type = target[i]

    # exact match
    return false unless target_type == in_type
  end
  return true
end

#each_is_exact_or_subtype_or_convertible(incoming, target) ⇒ Object



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

def each_is_exact_or_subtype_or_convertible(incoming, target)
  incoming.each_with_index do |in_type, i|
    target_type = target[i]

    # exact match
    next if target_type == in_type

    # 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) ⇒ Object



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

def field_lookup(mapped_params, mapped_type, meta, name)
  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?)
    field == nil
  end

  # check accessibility
  # TODO: protected field access check appropriate to current type
  if setter
    raise "cannot set final field '#{name}' on class #{mapped_type}" if field.final?
  end
  raise "cannot access field '#{name}' on class #{mapped_type}" unless field.public?

  field
end

#find_jls(mapped_type, name, mapped_params, meta, constructor) ⇒ Object



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

def find_jls(mapped_type, name, mapped_params, meta, constructor)
  interfaces = []
  if constructor
    by_name = mapped_type.unmeta.declared_constructors
  elsif meta
    by_name = mapped_type.declared_class_methods(name)
  else
    by_name = []
    cls = mapped_type
    while cls && !cls.error?
      by_name += cls.declared_instance_methods(name)
      interfaces.concat(cls.interfaces)
      cls = cls.superclass
    end
    if mapped_type.interface?  # TODO or abstract
      seen = {}
      until interfaces.empty?
        interface = interfaces.pop
        next if seen[interface] || interface.error?
        seen[interface] = true
        interfaces.concat(interface.interfaces)
        by_name += interface.declared_instance_methods(name)
      end
    end
  end
  # filter by arity
  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|
      
      "#{m.name}(#{m.parameter_types.map(&:name).join(', ')})"
    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) ||
    field_lookup(mapped_params, mapped_type, meta, name) ||
    inner_class(mapped_params, mapped_type, meta, name)
end

#find_method(mapped_type, name, mapped_params, meta) ⇒ Object

Raises:

  • (ArgumentError)


22
23
24
25
26
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
# File 'lib/mirah/jvm/method_lookup.rb', line 22

def find_method(mapped_type, name, mapped_params, meta)
  raise ArgumentError if mapped_params.any? {|p| p.nil?}

  if name == 'new'
    if meta
      name = "<init>"
      constructor = true
    else
      constructor = false
    end
  end

  begin
    if constructor
      method = mapped_type.constructor(*mapped_params)
    else
      method = mapped_type.java_method(name, *mapped_params)
    end
  rescue NameError
    # exact args failed, do a deeper search
    log "No exact match for #{mapped_type.name}.#{name}(#{mapped_params.map(&:name).join ', '})"

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

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

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

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



189
190
191
192
193
# File 'lib/mirah/jvm/method_lookup.rb', line 189

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

#is_more_specific?(potential, current) ⇒ Boolean

Returns:

  • (Boolean)


140
141
142
# File 'lib/mirah/jvm/method_lookup.rb', line 140

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

#log(msg) ⇒ Object

dummy log; it’s expected the inclusion target will have it



20
# File 'lib/mirah/jvm/method_lookup.rb', line 20

def log(msg); end

#phase1(mapped_params, potentials) ⇒ Object



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

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

  # cycle through methods looking for more specific matches; gather matches of equal specificity
  methods = potentials.inject([]) do |currents, potential|
    method_params = potential.argument_types
    raise "Bad arguments for method #{potential.declaring_class}.#{potential.name}" 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 last method of equal specificity
    if each_is_exact_or_subtype_or_convertible(mapped_params, method_params)
      if currents.size > 0
        if is_more_specific?(potential.argument_types, currents[0].argument_types)
          # potential is better, dump all currents
          currents = [potential]
        elsif is_more_specific?(currents[0].argument_types, potential.argument_types)
          # currents are better, try next potential
          #next
        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



144
145
146
# File 'lib/mirah/jvm/method_lookup.rb', line 144

def phase2(mapped_params, potentials)
  nil
end

#phase3(mapped_params, potentials) ⇒ Object



148
149
150
# File 'lib/mirah/jvm/method_lookup.rb', line 148

def phase3(mapped_params, potentials)
  nil
end

#primitive_convertible?(in_type, target_type) ⇒ Boolean

Returns:

  • (Boolean)


231
232
233
# File 'lib/mirah/jvm/method_lookup.rb', line 231

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