blob: 25a66def11d8140653f81a84dbcc7b5a01d7c874 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2011 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Technical University Berlin - extended API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.lookup;
import org.eclipse.jdt.internal.compiler.ast.MessageSend;
import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.AnchorMapping;
import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.DependentTypeBinding;
import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.ITeamAnchor;
import org.eclipse.objectteams.otdt.internal.core.compiler.statemachine.transformer.RoleMigrationImplementor;
/**
* OTDT changes:
* What: consider enhanced signature of callin method
*
* --------
* Binding denoting a generic method after type parameter substitutions got performed.
* On parameterized type bindings, all methods got substituted, regardless whether
* their signature did involve generics or not, so as to get the proper declaringClass for
* these methods.
*/
public class ParameterizedGenericMethodBinding extends ParameterizedMethodBinding implements Substitution {
public TypeBinding[] typeArguments;
private LookupEnvironment environment;
public boolean inferredReturnType;
public boolean wasInferred; // only set to true for instances resulting from method invocation inferrence
public boolean isRaw; // set to true for method behaving as raw for substitution purpose
private MethodBinding tiebreakMethod;
/**
* Perform inference of generic method type parameters and/or expected type
*/
public static MethodBinding computeCompatibleMethod(MethodBinding originalMethod, TypeBinding[] arguments, Scope scope, InvocationSite invocationSite) {
ParameterizedGenericMethodBinding methodSubstitute;
TypeVariableBinding[] typeVariables = originalMethod.typeVariables;
TypeBinding[] substitutes = invocationSite.genericTypeArguments();
TypeBinding[] uncheckedArguments = null;
//{ObjectTeams: different substitution rules for migrateToTeam()/migrateToBase() methods:
MethodBinding migrateMethod = RoleMigrationImplementor.getMigrateMethodSubstitute(originalMethod, arguments, substitutes, scope, invocationSite);
if (migrateMethod != null)
return migrateMethod;
// SH}
//{ObjectTeams: first substitute type anchors:
arguments = AnchorMapping.instantiateParameters(scope, arguments, originalMethod);
//SH}
computeSubstitutes: {
if (substitutes != null) {
// explicit type arguments got supplied
if (substitutes.length != typeVariables.length) {
// incompatible due to wrong arity
return new ProblemMethodBinding(originalMethod, originalMethod.selector, substitutes, ProblemReasons.TypeParameterArityMismatch);
}
methodSubstitute = scope.environment().createParameterizedGenericMethod(originalMethod, substitutes);
break computeSubstitutes;
}
// perform type argument inference (15.12.2.7)
// initializes the map of substitutes (var --> type[][]{ equal, extends, super}
TypeBinding[] parameters = originalMethod.parameters;
InferenceContext inferenceContext = new InferenceContext(originalMethod);
methodSubstitute = inferFromArgumentTypes(scope, originalMethod, arguments, parameters, inferenceContext);
if (methodSubstitute == null)
return null;
// substitutes may hold null to denote unresolved vars, but null arguments got replaced with respective original variable in param method
// 15.12.2.8 - inferring unresolved type arguments
if (inferenceContext.hasUnresolvedTypeArgument()) {
if (inferenceContext.isUnchecked) { // only remember unchecked status post 15.12.2.7
int length = inferenceContext.substitutes.length;
System.arraycopy(inferenceContext.substitutes, 0, uncheckedArguments = new TypeBinding[length], 0, length);
}
if (methodSubstitute.returnType != TypeBinding.VOID) {
TypeBinding expectedType = invocationSite.expectedType();
if (expectedType != null) {
// record it was explicit from context, as opposed to assumed by default (see below)
inferenceContext.hasExplicitExpectedType = true;
} else {
expectedType = scope.getJavaLangObject(); // assume Object by default
}
inferenceContext.expectedType = expectedType;
}
methodSubstitute = methodSubstitute.inferFromExpectedType(scope, inferenceContext);
if (methodSubstitute == null)
return null;
}
}
// bounds check
for (int i = 0, length = typeVariables.length; i < length; i++) {
TypeVariableBinding typeVariable = typeVariables[i];
TypeBinding substitute = methodSubstitute.typeArguments[i];
if (uncheckedArguments != null && uncheckedArguments[i] == null) continue; // only bound check if inferred through 15.12.2.6
//{ObjectTeams: methods with generic declared lifting need to be checked in knowledge of the actual receiver type:
ReferenceBinding actualReceiverRefType = null;
if (invocationSite instanceof MessageSend) {
TypeBinding actualReceiverType = ((MessageSend)invocationSite).actualReceiverType;
if (actualReceiverType instanceof ReferenceBinding)
actualReceiverRefType = (ReferenceBinding) actualReceiverType;
}
switch (typeVariable.boundCheck(methodSubstitute, substitute, actualReceiverRefType)) {
/* orig:
switch (typeVariable.boundCheck(methodSubstitute, substitute)) {
:giro */
// SH}
case TypeConstants.MISMATCH :
// incompatible due to bound check
int argLength = arguments.length;
TypeBinding[] augmentedArguments = new TypeBinding[argLength + 2]; // append offending substitute and typeVariable
System.arraycopy(arguments, 0, augmentedArguments, 0, argLength);
augmentedArguments[argLength] = substitute;
augmentedArguments[argLength+1] = typeVariable;
return new ProblemMethodBinding(methodSubstitute, originalMethod.selector, augmentedArguments, ProblemReasons.ParameterBoundMismatch);
case TypeConstants.UNCHECKED :
// tolerate unchecked bounds
methodSubstitute.tagBits |= TagBits.HasUncheckedTypeArgumentForBoundCheck;
break;
}
}
// check presence of unchecked argument conversion a posteriori (15.12.2.6)
return methodSubstitute;
}
/**
* Collect argument type mapping, handling varargs
*/
private static ParameterizedGenericMethodBinding inferFromArgumentTypes(Scope scope, MethodBinding originalMethod, TypeBinding[] arguments, TypeBinding[] parameters, InferenceContext inferenceContext) {
if (originalMethod.isVarargs()) {
int paramLength = parameters.length;
int minArgLength = paramLength - 1;
int argLength = arguments.length;
// process mandatory arguments
for (int i = 0; i < minArgLength; i++) {
parameters[i].collectSubstitutes(scope, arguments[i], inferenceContext, TypeConstants.CONSTRAINT_EXTENDS);
if (inferenceContext.status == InferenceContext.FAILED) return null; // impossible substitution
}
// process optional arguments
if (minArgLength < argLength) {
TypeBinding varargType = parameters[minArgLength]; // last arg type - as is ?
TypeBinding lastArgument = arguments[minArgLength];
checkVarargDimension: {
if (paramLength == argLength) {
if (lastArgument == TypeBinding.NULL) break checkVarargDimension;
switch (lastArgument.dimensions()) {
case 0 :
break; // will remove one dim
case 1 :
if (!lastArgument.leafComponentType().isBaseType()) break checkVarargDimension;
break; // will remove one dim
default :
break checkVarargDimension;
}
}
// eliminate one array dimension
varargType = ((ArrayBinding)varargType).elementsType();
}
for (int i = minArgLength; i < argLength; i++) {
varargType.collectSubstitutes(scope, arguments[i], inferenceContext, TypeConstants.CONSTRAINT_EXTENDS);
if (inferenceContext.status == InferenceContext.FAILED) return null; // impossible substitution
}
}
} else {
int paramLength = parameters.length;
for (int i = 0; i < paramLength; i++) {
parameters[i].collectSubstitutes(scope, arguments[i], inferenceContext, TypeConstants.CONSTRAINT_EXTENDS);
if (inferenceContext.status == InferenceContext.FAILED) return null; // impossible substitution
}
}
TypeVariableBinding[] originalVariables = originalMethod.typeVariables;
if (!resolveSubstituteConstraints(scope, originalVariables , inferenceContext, false/*ignore Ti<:Uk*/))
return null; // impossible substitution
// apply inferred variable substitutions - replacing unresolved variable with original ones in param method
TypeBinding[] inferredSustitutes = inferenceContext.substitutes;
TypeBinding[] actualSubstitutes = inferredSustitutes;
for (int i = 0, varLength = originalVariables.length; i < varLength; i++) {
if (inferredSustitutes[i] == null) {
if (actualSubstitutes == inferredSustitutes) {
System.arraycopy(inferredSustitutes, 0, actualSubstitutes = new TypeBinding[varLength], 0, i); // clone to replace null with original variable in param method
}
actualSubstitutes[i] = originalVariables[i];
} else if (actualSubstitutes != inferredSustitutes) {
actualSubstitutes[i] = inferredSustitutes[i];
}
}
ParameterizedGenericMethodBinding paramMethod = scope.environment().createParameterizedGenericMethod(originalMethod, actualSubstitutes);
return paramMethod;
}
private static boolean resolveSubstituteConstraints(Scope scope, TypeVariableBinding[] typeVariables, InferenceContext inferenceContext, boolean considerEXTENDSConstraints) {
TypeBinding[] substitutes = inferenceContext.substitutes;
int varLength = typeVariables.length;
// check Tj=U constraints
nextTypeParameter:
for (int i = 0; i < varLength; i++) {
TypeVariableBinding current = typeVariables[i];
TypeBinding substitute = substitutes[i];
if (substitute != null) continue nextTypeParameter; // already inferred previously
TypeBinding [] equalSubstitutes = inferenceContext.getSubstitutes(current, TypeConstants.CONSTRAINT_EQUAL);
if (equalSubstitutes != null) {
nextConstraint:
for (int j = 0, equalLength = equalSubstitutes.length; j < equalLength; j++) {
TypeBinding equalSubstitute = equalSubstitutes[j];
if (equalSubstitute == null) continue nextConstraint;
if (equalSubstitute == current) {
// try to find a better different match if any in subsequent equal candidates
for (int k = j+1; k < equalLength; k++) {
equalSubstitute = equalSubstitutes[k];
if (equalSubstitute != current && equalSubstitute != null) {
substitutes[i] = equalSubstitute;
continue nextTypeParameter;
}
}
substitutes[i] = current;
continue nextTypeParameter;
}
// if (equalSubstitute.isTypeVariable()) {
// TypeVariableBinding variable = (TypeVariableBinding) equalSubstitute;
// // substituted by a variable of the same method, ignore
// if (variable.rank < varLength && typeVariables[variable.rank] == variable) {
// // TODO (philippe) rewrite all other constraints to use current instead.
// continue nextConstraint;
// }
// }
substitutes[i] = equalSubstitute;
continue nextTypeParameter; // pick first match, applicability check will rule out invalid scenario where others were present
}
}
}
if (inferenceContext.hasUnresolvedTypeArgument()) {
// check Tj>:U constraints
nextTypeParameter:
for (int i = 0; i < varLength; i++) {
TypeVariableBinding current = typeVariables[i];
TypeBinding substitute = substitutes[i];
if (substitute != null) continue nextTypeParameter; // already inferred previously
TypeBinding [] bounds = inferenceContext.getSubstitutes(current, TypeConstants.CONSTRAINT_SUPER);
if (bounds == null) continue nextTypeParameter;
TypeBinding mostSpecificSubstitute = scope.lowerUpperBound(bounds);
if (mostSpecificSubstitute == null) {
return false; // incompatible
}
if (mostSpecificSubstitute != TypeBinding.VOID) {
substitutes[i] = mostSpecificSubstitute;
}
}
}
if (considerEXTENDSConstraints && inferenceContext.hasUnresolvedTypeArgument()) {
// check Tj<:U constraints
nextTypeParameter:
for (int i = 0; i < varLength; i++) {
TypeVariableBinding current = typeVariables[i];
TypeBinding substitute = substitutes[i];
if (substitute != null) continue nextTypeParameter; // already inferred previously
TypeBinding [] bounds = inferenceContext.getSubstitutes(current, TypeConstants.CONSTRAINT_EXTENDS);
if (bounds == null) continue nextTypeParameter;
TypeBinding[] glb = Scope.greaterLowerBound(bounds);
TypeBinding mostSpecificSubstitute = null;
if (glb != null) mostSpecificSubstitute = glb[0]; // TODO (philippe) need to improve
//TypeBinding mostSpecificSubstitute = scope.greaterLowerBound(bounds);
if (mostSpecificSubstitute != null) {
substitutes[i] = mostSpecificSubstitute;
}
}
}
return true;
}
/**
* Create raw generic method for raw type (double substitution from type vars with raw type arguments, and erasure of method variables)
* Only invoked for non-static generic methods of raw type
*/
public ParameterizedGenericMethodBinding(MethodBinding originalMethod, RawTypeBinding rawType, LookupEnvironment environment) {
TypeVariableBinding[] originalVariables = originalMethod.typeVariables;
int length = originalVariables.length;
TypeBinding[] rawArguments = new TypeBinding[length];
for (int i = 0; i < length; i++) {
rawArguments[i] = environment.convertToRawType(originalVariables[i].erasure(), false /*do not force conversion of enclosing types*/);
}
this.isRaw = true;
this.tagBits = originalMethod.tagBits;
this.environment = environment;
this.modifiers = originalMethod.modifiers;
this.selector = originalMethod.selector;
this.declaringClass = rawType == null ? originalMethod.declaringClass : rawType;
this.typeVariables = Binding.NO_TYPE_VARIABLES;
this.typeArguments = rawArguments;
this.originalMethod = originalMethod;
boolean ignoreRawTypeSubstitution = rawType == null || originalMethod.isStatic();
this.parameters = Scope.substitute(this, ignoreRawTypeSubstitution
? originalMethod.parameters // no substitution if original was static
: Scope.substitute(rawType, originalMethod.parameters));
this.thrownExceptions = Scope.substitute(this, ignoreRawTypeSubstitution
? originalMethod.thrownExceptions // no substitution if original was static
: Scope.substitute(rawType, originalMethod.thrownExceptions));
// error case where exception type variable would have been substituted by a non-reference type (207573)
if (this.thrownExceptions == null) this.thrownExceptions = Binding.NO_EXCEPTIONS;
this.returnType = Scope.substitute(this, ignoreRawTypeSubstitution
? originalMethod.returnType // no substitution if original was static
: Scope.substitute(rawType, originalMethod.returnType));
this.wasInferred = false; // not resulting from method invocation inferrence
}
/**
* Create method of parameterized type, substituting original parameters with type arguments.
*/
public ParameterizedGenericMethodBinding(MethodBinding originalMethod, TypeBinding[] typeArguments, LookupEnvironment environment) {
this.environment = environment;
this.modifiers = originalMethod.modifiers;
this.selector = originalMethod.selector;
this.declaringClass = originalMethod.declaringClass;
this.typeVariables = Binding.NO_TYPE_VARIABLES;
this.typeArguments = typeArguments;
this.isRaw = false;
this.tagBits = originalMethod.tagBits;
this.originalMethod = originalMethod;
this.parameters = Scope.substitute(this, originalMethod.parameters);
// error case where exception type variable would have been substituted by a non-reference type (207573)
this.returnType = Scope.substitute(this, originalMethod.returnType);
this.thrownExceptions = Scope.substitute(this, originalMethod.thrownExceptions);
if (this.thrownExceptions == null) this.thrownExceptions = Binding.NO_EXCEPTIONS;
checkMissingType: {
if ((this.tagBits & TagBits.HasMissingType) != 0)
break checkMissingType;
if ((this.returnType.tagBits & TagBits.HasMissingType) != 0) {
this.tagBits |= TagBits.HasMissingType;
break checkMissingType;
}
for (int i = 0, max = this.parameters.length; i < max; i++) {
if ((this.parameters[i].tagBits & TagBits.HasMissingType) != 0) {
this.tagBits |= TagBits.HasMissingType;
break checkMissingType;
}
}
for (int i = 0, max = this.thrownExceptions.length; i < max; i++) {
if ((this.thrownExceptions[i].tagBits & TagBits.HasMissingType) != 0) {
this.tagBits |= TagBits.HasMissingType;
break checkMissingType;
}
}
}
//{ObjectTeams: for callin methods:
if (originalMethod.switchCount > 0) {
this.parameters = Scope.substitute(this, originalMethod.enhancedParameters);
this.returnType = Scope.substitute(this, originalMethod.generalizedReturnType);
}
// share method model:
this.model= originalMethod.model;
// SH}
this.wasInferred = true;// resulting from method invocation inferrence
}
/*
* parameterizedDeclaringUniqueKey dot selector originalMethodGenericSignature percent typeArguments
* p.X<U> { <T> void bar(T t, U u) { new X<String>().bar(this, "") } } --> Lp/X<Ljava/lang/String;>;.bar<T:Ljava/lang/Object;>(TT;Ljava/lang/String;)V%<Lp/X;>
*/
public char[] computeUniqueKey(boolean isLeaf) {
StringBuffer buffer = new StringBuffer();
buffer.append(this.originalMethod.computeUniqueKey(false/*not a leaf*/));
buffer.append('%');
buffer.append('<');
if (!this.isRaw) {
int length = this.typeArguments.length;
for (int i = 0; i < length; i++) {
TypeBinding typeArgument = this.typeArguments[i];
buffer.append(typeArgument.computeUniqueKey(false/*not a leaf*/));
}
}
buffer.append('>');
int resultLength = buffer.length();
char[] result = new char[resultLength];
buffer.getChars(0, resultLength, result, 0);
return result;
}
/**
* @see org.eclipse.jdt.internal.compiler.lookup.Substitution#environment()
*/
public LookupEnvironment environment() {
return this.environment;
}
/**
* Returns true if some parameters got substituted.
* NOTE: generic method invocation delegates to its declaring method (could be a parameterized one)
*/
public boolean hasSubstitutedParameters() {
// generic parameterized method can represent either an invocation or a raw generic method
if (this.wasInferred)
return this.originalMethod.hasSubstitutedParameters();
return super.hasSubstitutedParameters();
}
/**
* Returns true if the return type got substituted.
* NOTE: generic method invocation delegates to its declaring method (could be a parameterized one)
*/
public boolean hasSubstitutedReturnType() {
if (this.inferredReturnType)
return this.originalMethod.hasSubstitutedReturnType();
return super.hasSubstitutedReturnType();
}
/**
* Given some type expectation, and type variable bounds, perform some inference.
* Returns true if still had unresolved type variable at the end of the operation
*/
private ParameterizedGenericMethodBinding inferFromExpectedType(Scope scope, InferenceContext inferenceContext) {
TypeVariableBinding[] originalVariables = this.originalMethod.typeVariables; // immediate parent (could be a parameterized method)
int varLength = originalVariables.length;
// infer from expected return type
if (inferenceContext.expectedType != null) {
this.returnType.collectSubstitutes(scope, inferenceContext.expectedType, inferenceContext, TypeConstants.CONSTRAINT_SUPER);
if (inferenceContext.status == InferenceContext.FAILED) return null; // impossible substitution
}
// infer from bounds of type parameters
for (int i = 0; i < varLength; i++) {
TypeVariableBinding originalVariable = originalVariables[i];
TypeBinding argument = this.typeArguments[i];
boolean argAlreadyInferred = argument != originalVariable;
if (originalVariable.firstBound == originalVariable.superclass) {
TypeBinding substitutedBound = Scope.substitute(this, originalVariable.superclass);
argument.collectSubstitutes(scope, substitutedBound, inferenceContext, TypeConstants.CONSTRAINT_SUPER);
if (inferenceContext.status == InferenceContext.FAILED) return null; // impossible substitution
// JLS 15.12.2.8 claims reverse inference shouldn't occur, however it improves inference
// e.g. given: <E extends Object, S extends Collection<E>> S test1(S param)
// invocation: test1(new Vector<String>()) will infer: S=Vector<String> and with code below: E=String
if (argAlreadyInferred) {
substitutedBound.collectSubstitutes(scope, argument, inferenceContext, TypeConstants.CONSTRAINT_EXTENDS);
if (inferenceContext.status == InferenceContext.FAILED) return null; // impossible substitution
}
}
for (int j = 0, max = originalVariable.superInterfaces.length; j < max; j++) {
TypeBinding substitutedBound = Scope.substitute(this, originalVariable.superInterfaces[j]);
argument.collectSubstitutes(scope, substitutedBound, inferenceContext, TypeConstants.CONSTRAINT_SUPER);
if (inferenceContext.status == InferenceContext.FAILED) return null; // impossible substitution
// JLS 15.12.2.8 claims reverse inference shouldn't occur, however it improves inference
if (argAlreadyInferred) {
substitutedBound.collectSubstitutes(scope, argument, inferenceContext, TypeConstants.CONSTRAINT_EXTENDS);
if (inferenceContext.status == InferenceContext.FAILED) return null; // impossible substitution
}
}
}
if (!resolveSubstituteConstraints(scope, originalVariables, inferenceContext, true/*consider Ti<:Uk*/))
return null; // incompatible
// this.typeArguments = substitutes; - no op since side effects got performed during #resolveSubstituteConstraints
for (int i = 0; i < varLength; i++) {
TypeBinding substitute = inferenceContext.substitutes[i];
if (substitute != null) {
this.typeArguments[i] = inferenceContext.substitutes[i];
} else {
// remaining unresolved variable are considered to be Object (or their bound actually)
this.typeArguments[i] = originalVariables[i].upperBound();
}
}
// may still need an extra substitution at the end (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=121369)
// to properly substitute a remaining unresolved variable which also appear in a formal bound
this.typeArguments = Scope.substitute(this, this.typeArguments);
// adjust method types to reflect latest inference
TypeBinding oldReturnType = this.returnType;
this.returnType = Scope.substitute(this, this.returnType);
this.inferredReturnType = inferenceContext.hasExplicitExpectedType && this.returnType != oldReturnType;
this.parameters = Scope.substitute(this, this.parameters);
this.thrownExceptions = Scope.substitute(this, this.thrownExceptions);
// error case where exception type variable would have been substituted by a non-reference type (207573)
if (this.thrownExceptions == null) this.thrownExceptions = Binding.NO_EXCEPTIONS;
checkMissingType: {
if ((this.tagBits & TagBits.HasMissingType) != 0)
break checkMissingType;
if ((this.returnType.tagBits & TagBits.HasMissingType) != 0) {
this.tagBits |= TagBits.HasMissingType;
break checkMissingType;
}
for (int i = 0, max = this.parameters.length; i < max; i++) {
if ((this.parameters[i].tagBits & TagBits.HasMissingType) != 0) {
this.tagBits |= TagBits.HasMissingType;
break checkMissingType;
}
}
for (int i = 0, max = this.thrownExceptions.length; i < max; i++) {
if ((this.thrownExceptions[i].tagBits & TagBits.HasMissingType) != 0) {
this.tagBits |= TagBits.HasMissingType;
break checkMissingType;
}
}
}
return this;
}
/**
* @see org.eclipse.jdt.internal.compiler.lookup.Substitution#isRawSubstitution()
*/
public boolean isRawSubstitution() {
return this.isRaw;
}
/**
* @see org.eclipse.jdt.internal.compiler.lookup.Substitution#substitute(org.eclipse.jdt.internal.compiler.lookup.TypeVariableBinding)
*/
public TypeBinding substitute(TypeVariableBinding originalVariable) {
TypeVariableBinding[] variables = this.originalMethod.typeVariables;
int length = variables.length;
// check this variable can be substituted given parameterized type
if (originalVariable.rank < length && variables[originalVariable.rank] == originalVariable) {
return this.typeArguments[originalVariable.rank];
}
return originalVariable;
}
//{ObjectTeams: more substitution:
/**
* Check if the given variable was the result of substituting a variable from the original method.
* If so answer the corresponding variable of the original method.
* @param specificVariable a type variable from this method's scope.
* @return either a type variable from the original method or the input specificVariable
*/
public TypeVariableBinding reverseSubstitute(TypeVariableBinding specificVariable) {
int length = this.typeArguments.length;
if (specificVariable.rank < length && this.typeArguments[specificVariable.rank] == specificVariable)
return this.originalMethod.typeVariables[specificVariable.rank];
return specificVariable;
}
public ITeamAnchor substituteAnchor(ITeamAnchor anchor, int rank) {
TypeBinding typeArgument = this.typeArguments[rank];
if (DependentTypeBinding.isDependentType(typeArgument))
return ((DependentTypeBinding)typeArgument)._teamAnchor;
return null;
}
// SH}
/**
* @see org.eclipse.jdt.internal.compiler.lookup.MethodBinding#tiebreakMethod()
*/
public MethodBinding tiebreakMethod() {
if (this.tiebreakMethod == null)
this.tiebreakMethod = this.originalMethod.asRawMethod(this.environment);
return this.tiebreakMethod;
}
}