Class OperatorTargetMethodResolver

java.lang.Object
org.ek9lang.compiler.phase7.calls.OperatorTargetMethodResolver

public final class OperatorTargetMethodResolver extends Object
THE shared "resolve the callee method on its target type" step for phase-7 synthesised call sites. Use this - do not re-derive a variant. It carries two pieces of hard-won knowledge that an inline aggregate.resolveMember(new MethodSymbolSearch(name)) silently gets wrong (see the dual-glyph note below), and every phase-7 site that needs a resolved callee - whether to read its defining type or to read its CallMetaDataDetails - must go through here.

Uses resolveMember with the argument types so SymbolMatcher finds the correct overload - including coercible (promotable) matches, e.g. Float._addAss(Float) for f += celsius where Celsius promotes to Float. The symbol table keys operator methods by EK9 name (":=:"), so the JVM method name ("_copy") is reverse-mapped for the search. A non-operator method name (e.g. iterator, hasNext) is searched as given.

Negate is dual-glyph. The JVM method _negate is aliased by TWO EK9 operators: ~ (declared by Boolean/Bits/List/Colour as operator ~) and the zero-arg unary - (declared by the numeric types as operator -, split from binary _sub by arity in phase 2's ValidOperatorOrError.minusOperatorOrError). OperatorMap.getBackward("_negate") only yields ~ (the - key is taken by binary _sub), so a ~ search misses on a numeric type whose negate is stored under -. This mirrors, in reverse, the one-way -/not -> ~ normalisation that UnaryOperationGenerator does on the way in: on a ~ miss for a zero-arg call, retry the unary -. A zero-parameter search matches ONLY the unary - (SymbolMatcher rejects arity mismatch), never binary _sub. Without this the dispatch/defining type silently falls back to the static receiver - correct for the on-receiver common case, but a wrong, never-emitted symbol for an INHERITED numeric negate on the eagerly-linked native back-end (the cea4d156c defining-type remedy otherwise no-ops here).

Not-equals is dual-glyph as well. <> and != both map to _neq; OperatorMap.getBackward("_neq") yields != (registered last), but types DECLARE operator <>, so a != miss retries <>. Without it every synthesised _neq fell back to the static receiver - a never-emitted symbol for an INHERITED <> natively.

A call resolved on a generic's bare T is resolved again on the type argument. Phase 3 resolves an operator in a generic body against the conceptual T, whose operators are synthesised as T._cmp(T). Inside a monomorphisation T stands for a real type, and the member that serves the call is that type's - possibly INHERITED from a super (Base._cmp(Base) for a Kid that declares none, D53) or declared on a TRAIT, which needs an interface call (D54). See onTypeArgument(ScopedSymbol, UnaryOperator).

See Also:
  • Constructor Details

    • OperatorTargetMethodResolver

      public OperatorTargetMethodResolver()
  • Method Details

    • apply

      public Optional<ScopedSymbol> apply(ISymbol targetType, String methodName, List<ISymbol> argumentTypes)
      Resolve a method/operator on the target type, returning the resolved symbol itself (not just type names), so callers can read its defining type, return type and metadata rather than re-deriving them.
      Parameters:
      targetType - the type the call is made ON (a non-aggregate never resolves)
      methodName - the JVM method name (_copy) or the EK9 name (:=:) or a plain method name (iterator)
      argumentTypes - the resolved argument types, driving overload selection
      Returns:
      the resolved method, or empty when the target is not an aggregate or declares no such member
    • declaredParameterTypes

      public Optional<List<ISymbol>> declaredParameterTypes(ISymbol targetType, String methodName, List<ISymbol> argumentTypes)
      THE declared parameter types of the operator/method that actually serves a call - read off the RESOLVED member, never copied from the arguments. Use this - do not re-derive it. An operator may take a WIDER type than the argument (its trait, a super, Any), and the call must name the declared descriptor: naming the argument's type asks for a method that does not exist - a NoSuchMethodError C2._cmp(C3) on the JVM for c2 >? c3 over a trait's <=> (B18 prerequisite P2), an undefined symbol natively. Shared by the synthesised comparisons, the Scenario-2 wrapper equality and comparison coalescing.
      Parameters:
      targetType - the type the call is made ON
      methodName - the JVM or EK9 name, as for apply(ISymbol, String, List)
      argumentTypes - the resolved argument types
      Returns:
      the declared parameter types, or empty when nothing resolves
    • declaredParameterTypes

      public Optional<List<ISymbol>> declaredParameterTypes(ISymbol targetType, String methodName, List<ISymbol> argumentTypes, UnaryOperator<ISymbol> typeArgumentOf)
      declaredParameterTypes(ISymbol, String, List) inside a monomorphisation: the parameter types the member that SERVES the call declares (servingMember(ISymbol, String, List, UnaryOperator)), so a call a generic body makes on its bare T names the type argument's operator, never the template's T._cmp(T) (D53).
      Parameters:
      typeArgumentOf - the substitution of the monomorphisation in progress (IRGenerationContext::resolveType); the identity outside one
    • servingMember

      public Optional<ScopedSymbol> servingMember(ISymbol targetType, String methodName, List<ISymbol> argumentTypes, UnaryOperator<ISymbol> typeArgumentOf)
      apply(ISymbol, String, List) followed by onTypeArgument(ScopedSymbol, UnaryOperator): the member that serves the call, read off the type argument when the call is resolved on a generic's bare T inside a monomorphisation. A site that names a call's owner or parameter types itself (rather than through CallDetailsBuilder) uses this.
      Parameters:
      typeArgumentOf - the substitution of the monomorphisation in progress (IRGenerationContext::resolveType); the identity outside one
    • onTypeArgument

      public Optional<ScopedSymbol> onTypeArgument(ScopedSymbol templateMember, UnaryOperator<ISymbol> typeArgumentOf)
      THE member that serves, inside a monomorphisation, a call phase 3 resolved on a generic's bare T. Use this - do not re-derive it. The generic body is generated once per parameterisation from ONE template-typed AST, so the recorded callee is the conceptual T's synthesised operator, declared ON T and TAKING T. Substituting the type argument into that template signature names a method that need not exist: compared() of Kid called Kid._cmp(Kid) where the operator Kid has is Base._cmp(Base) (a NoSuchMethodError, D53), and compared() of Sized called a trait's operator as a class method (an IncompatibleClassChangeError, D54). The descriptor - owner, declared parameter types, return type, and whether the owner is an interface - must be read off the member that ACTUALLY serves it, found here by resolving the same operator on the type argument.
      Parameters:
      templateMember - the callee phase 3 recorded (possibly a member of a bare T)
      typeArgumentOf - the substitution of the monomorphisation in progress (IRGenerationContext::resolveType); the identity outside one
      Returns:
      the member of the type argument, or empty when the callee is not a member of a bare T, the substitution leaves T as it is, or nothing resolves (a function type argument, say)