* case we want the refined decl to be the one with the non-erased type.
* - The third special case is when we implement a declaration via multiple super types, without having any refining
* declarations in those supertypes, simply by instantiating a common super type with different type parameters
*/
private ProducedTypedReference getRefinedDeclaration(ProducedTypedReference typedReference, ProducedType currentType) {
TypedDeclaration decl = typedReference.getDeclaration();
TypedDeclaration modelRefinedDecl = (TypedDeclaration)decl.getRefinedDeclaration();
ProducedType referenceQualifyingType = typedReference.getQualifyingType();
boolean forMixinMethod =
currentType != null
&& decl.getContainer() instanceof ClassOrInterface
&& referenceQualifyingType != null
&& !Decl.equal(referenceQualifyingType.getDeclaration(), currentType.getDeclaration());
// quick exit
if (Decl.equal(decl, modelRefinedDecl) && !forMixinMethod)
return null;
// modelRefinedDecl exists, but perhaps it's the toplevel refinement and not the one Java will look at
if(!forMixinMethod)
modelRefinedDecl = getFirstRefinedDeclaration(decl);
TypeDeclaration qualifyingDeclaration = currentType.getDeclaration();
if(qualifyingDeclaration instanceof ClassOrInterface){
// if both are returning unboxed void we're good
if(Decl.isUnboxedVoid(decl)
&& Decl.isUnboxedVoid(modelRefinedDecl))
return null;
// only try to find better if we're erasing to Object and we're not returning a type param
if(willEraseToObject(typedReference.getType())
|| isWideningTypeArguments(decl.getType(), modelRefinedDecl.getType(), true)
&& !isTypeParameter(typedReference.getType())){
ClassOrInterface declaringType = (ClassOrInterface) qualifyingDeclaration;
Set<TypedDeclaration> refinedMembers = getRefinedMembers(declaringType, decl.getName(),
com.redhat.ceylon.compiler.typechecker.model.Util.getSignature(decl), false);
// now we must select a different refined declaration if we refine it more than once
if(refinedMembers.size() > (forMixinMethod ? 0 : 1)){
// first case
for(TypedDeclaration refinedDecl : refinedMembers){
// get the type reference to see if any eventual type param is instantiated in our inheritance of this type/method
ProducedTypedReference refinedTypedReference = getRefinedTypedReference(typedReference, refinedDecl);
// if it is not instantiated, that's the one we're looking for
if(isTypeParameter(refinedTypedReference.getType()))
return refinedTypedReference;
}
// second case
for(TypedDeclaration refinedDecl : refinedMembers){
// get the type reference to see if any eventual type param is instantiated in our inheritance of this type/method
ProducedTypedReference refinedTypedReference = getRefinedTypedReference(typedReference, refinedDecl);
// if we're not erasing this one to Object let's select it
if(!willEraseToObject(refinedTypedReference.getType()) && !isWideningTypeArguments(refinedDecl.getType(), modelRefinedDecl.getType(), true))
return refinedTypedReference;
}
// third case
if(isTypeParameter(modelRefinedDecl.getType())){
// it can happen that we have inherited a method twice from a single refined declaration
// via different supertype instantiations, without having ever refined them in supertypes
// so we try each super type to see if we already have a matching typed reference
// first super type
ProducedType extendedType = declaringType.getExtendedType();
if(extendedType != null){
ProducedTypedReference refinedTypedReference = getRefinedTypedReference(extendedType, modelRefinedDecl);
ProducedType refinedType = refinedTypedReference.getType();
if(!isTypeParameter(refinedType)
&& !willEraseToObject(refinedType))
return refinedTypedReference;
}
// then satisfied interfaces
for(ProducedType satisfiedType : declaringType.getSatisfiedTypes()){
ProducedTypedReference refinedTypedReference = getRefinedTypedReference(satisfiedType, modelRefinedDecl);
ProducedType refinedType = refinedTypedReference.getType();
if(!isTypeParameter(refinedType)
&& !willEraseToObject(refinedType))
return refinedTypedReference;
}
}
}
}
/*
* Now there's another crazy case:
*
* interface Top<out Element> {
* Top<Element> ret => nothing;
* }
* interface Left satisfies Top<Integer> {}
* interface Right satisfies Top<String> {}
* class Bottom() satisfies Left&Right {}
*
* Where Bottom.ret does not exist and is typed as returning Integer&String which is Nothing, erased to Object,
* and we look at what it refines and find only a single definition Top.ret typed as returning Integer&String (Nothing),
* so we think there's no widening, but Java will only see Top<Integer>.ret from Left, and that's the one we want
* to use for determining widening.
* See https://github.com/ceylon/ceylon-compiler/issues/1765
*/
ProducedType firstInstantiation = isInheritedWithDifferentTypeArguments(modelRefinedDecl.getContainer(), currentType);
if(firstInstantiation != null){
ProducedTypedReference firstInstantiationTypedReference = getRefinedTypedReference(firstInstantiation, modelRefinedDecl);
ProducedType firstInstantiationType = firstInstantiationTypedReference.getType();
if(isWidening(decl.getType(), firstInstantiationType)
|| isWideningTypeArguments(decl.getType(), firstInstantiationType, true))