protected void checkReturnCompatibility(MethodSpec methodSpec) { Config.requireTypeAdjustment(); // reset flags // Note(SH): non-replace mappings have no return-dataflow (except for explicitly mapping // 'result' in after) if (isReplaceCallin()) // return for after/before is ignored. checkResultForReplace(methodSpec); }
/** Check whether the baseSpec has a result compatible via replace. */ public void checkResultForReplace(MethodSpec baseSpec) { boolean typeIdentityRequired = true; // default unless return is type variable // covariant return requires a fresh type parameter for the role's return type: if (baseSpec.covariantReturn && this.roleMethodSpec.returnType != null) { TypeBinding resolvedRoleReturn = this.roleMethodSpec.returnType.resolvedType; if (resolvedRoleReturn != null) { if (!resolvedRoleReturn.isTypeVariable()) { this.scope .problemReporter() .covariantReturnRequiresTypeParameter(this.roleMethodSpec.returnType); this.binding.tagBits |= TagBits.HasMappingIncompatibility; } else { // is the type parameter "fresh"? for (Argument arg : this.roleMethodSpec.arguments) { if (typeUsesTypeVariable( arg.type.resolvedType.leafComponentType(), resolvedRoleReturn)) { this.scope .problemReporter() .duplicateUseOfTypeVariableInCallin( this.roleMethodSpec.returnType, resolvedRoleReturn); this.binding.tagBits |= TagBits.HasMappingIncompatibility; break; } } } } } TypeVariableBinding returnVariable = MethodModel.checkedGetReturnTypeVariable(this.roleMethodSpec.resolvedMethod); if (returnVariable != null) { // unbounded type variable always matches: if (returnVariable.firstBound == null) return; // in case of type variable only one-way compatibility is needed even for replace: typeIdentityRequired = false; } // now go for the actual type checking: TypeBinding baseReturn = baseSpec.resolvedMethod.returnType; TypeBinding roleReturn = MethodModel.getReturnType(this.roleMethodSpec.resolvedMethod); TypeBinding roleReturnLeaf = roleReturn != null ? roleReturn.leafComponentType() : null; if (roleReturnLeaf instanceof ReferenceBinding && ((ReferenceBinding) roleReturnLeaf).isRole()) { // strengthen: roleReturnLeaf = TeamModel.strengthenRoleType(this.scope.enclosingSourceType(), roleReturnLeaf); if (roleReturnLeaf == null) { // FIXME(SH): testcase and better handling String roleReturnName = roleReturn != null ? new String(roleReturn.readableName()) : "null return type"; //$NON-NLS-1$ throw new InternalCompilerError( "role strengthening for " + roleReturnName + " -> null"); // $NON-NLS-1$ //$NON-NLS-2$ } // bound roles use their topmost bound super: if (((ReferenceBinding) roleReturnLeaf).baseclass() != null) roleReturnLeaf = RoleModel.getTopmostBoundRole(this.scope, (ReferenceBinding) roleReturnLeaf); // need the RTB: if (!(roleReturnLeaf instanceof DependentTypeBinding)) roleReturnLeaf = RoleTypeCreator.maybeWrapUnqualifiedRoleType( roleReturnLeaf, this.scope.enclosingSourceType()); // array? int dims = roleReturn != null ? roleReturn.dimensions() : 0; if (dims == 0) { roleReturn = roleReturnLeaf; this.realRoleReturn = roleReturnLeaf; } else { roleReturn = ((DependentTypeBinding) roleReturnLeaf).getArrayType(dims); this.realRoleReturn = ((DependentTypeBinding) roleReturnLeaf).getArrayType(dims); } } if (baseReturn == null || baseReturn == TypeBinding.VOID) { // OTJLD 4.4(b): "A callin method bound with replace // to a base method returning void // must not declare a non-void result." if (!(roleReturn == null || roleReturn == TypeBinding.VOID)) { this.scope.problemReporter().callinIllegalRoleReturnReturn(baseSpec, this.roleMethodSpec); this.binding.tagBits |= TagBits.HasMappingIncompatibility; } } else { if (roleReturn == null || roleReturn == TypeBinding.VOID) { this.baseMethodNeedingResultFromBasecall = baseSpec; // will be reported in checkBaseResult(). return; } TypeBinding baseLeaf = baseReturn.leafComponentType(); if (baseLeaf instanceof DependentTypeBinding) { // instantiate relative to Role._OT$base: ReferenceBinding enclosingRole = this.scope.enclosingSourceType(); FieldBinding baseField = enclosingRole.getField(IOTConstants._OT_BASE, true); if (baseField != null && baseField.isValidBinding()) baseReturn = baseField.getRoleTypeBinding((ReferenceBinding) baseLeaf, baseReturn.dimensions()); } // check auto(un)boxing: if (this.scope.isBoxingCompatibleWith(roleReturn, baseReturn)) return; Config oldConfig = Config.createOrResetConfig(this); try { if (!roleReturn.isCompatibleWith(baseReturn)) { if (typeIdentityRequired) { this.scope .problemReporter() .callinIncompatibleReturnType(baseSpec, this.roleMethodSpec); this.binding.tagBits |= TagBits.HasMappingIncompatibility; return; } // else we still needed the lowering test } // callin replace requires two way compatibility: baseSpec.returnNeedsTranslation = Config.getLoweringRequired(); } finally { Config.removeOrRestore(oldConfig, this); } // from now on don't bother with arrays any more (dimensions have been checked): roleReturn = roleReturn.leafComponentType(); baseReturn = baseReturn.leafComponentType(); TypeBinding translatedReturn = baseSpec.returnNeedsTranslation ? ((ReferenceBinding) roleReturn).baseclass() : roleReturn; if (translatedReturn.isTypeVariable()) { TypeBinding firstBound = ((TypeVariableBinding) translatedReturn).firstBound; if (firstBound != null) translatedReturn = firstBound; } if (!baseReturn.isCompatibleWith(translatedReturn)) { this.scope .problemReporter() .callinIncompatibleReturnTypeBaseCall(baseSpec, this.roleMethodSpec); this.binding.tagBits |= TagBits.HasMappingIncompatibility; } } }