private List<TypeProjection> substituteTypeArguments( List<TypeParameterDescriptor> typeParameters, List<TypeProjection> typeArguments, int recursionDepth) throws SubstitutionException { List<TypeProjection> substitutedArguments = new ArrayList<TypeProjection>(typeParameters.size()); for (int i = 0; i < typeParameters.size(); i++) { TypeParameterDescriptor typeParameter = typeParameters.get(i); TypeProjection typeArgument = typeArguments.get(i); TypeProjection substitutedTypeArgument = unsafeSubstitute(typeArgument, recursionDepth + 1); switch (conflictType( typeParameter.getVariance(), substitutedTypeArgument.getProjectionKind())) { case NO_CONFLICT: // if the corresponding type parameter is already co/contra-variant, there's not need for // an explicit projection if (typeParameter.getVariance() != Variance.INVARIANT && !substitutedTypeArgument.isStarProjection()) { substitutedTypeArgument = new TypeProjectionImpl(Variance.INVARIANT, substitutedTypeArgument.getType()); } break; case OUT_IN_IN_POSITION: case IN_IN_OUT_POSITION: substitutedTypeArgument = TypeUtils.makeStarProjection(typeParameter); break; } substitutedArguments.add(substitutedTypeArgument); } return substitutedArguments; }
private void appendTypeProjections( @NotNull List<TypeProjection> typeProjections, @NotNull StringBuilder builder) { for (Iterator<TypeProjection> iterator = typeProjections.iterator(); iterator.hasNext(); ) { TypeProjection typeProjection = iterator.next(); if (typeProjection.getProjectionKind() != Variance.INVARIANT) { builder.append(typeProjection.getProjectionKind()).append(" "); } builder.append(renderType(typeProjection.getType())); if (iterator.hasNext()) { builder.append(", "); } } }
private Variance calculateArgumentProjectionKindFromSuper( @NotNull TypeProjection argument, @NotNull List<TypeProjectionAndVariance> projectionsFromSuper) { Set<Variance> projectionKindsInSuper = Sets.newLinkedHashSet(); for (TypeProjectionAndVariance projectionAndVariance : projectionsFromSuper) { projectionKindsInSuper.add(projectionAndVariance.typeProjection.getProjectionKind()); } Variance defaultProjectionKind = argument.getProjectionKind(); if (projectionKindsInSuper.size() == 0) { return defaultProjectionKind; } else if (projectionKindsInSuper.size() == 1) { Variance projectionKindInSuper = projectionKindsInSuper.iterator().next(); if (defaultProjectionKind == INVARIANT || defaultProjectionKind == projectionKindInSuper) { return projectionKindInSuper; } else { reportError( "Incompatible projection kinds in type arguments of super methods' return types: " + projectionsFromSuper + ", defined in current: " + argument); return defaultProjectionKind; } } else { reportError( "Incompatible projection kinds in type arguments of super methods' return types: " + projectionsFromSuper); return defaultProjectionKind; } }
/** * Determines what constraint (supertype, subtype or equal) should be generated for type parameter * {@code T} in a constraint like (in this example subtype one): <br> * {@code MyClass<in/out/- A> <: MyClass<in/out/- B>}, where {@code MyClass<in/out/- T>} is * declared. <br> * The parameters description is given according to the example above. * * @param typeParameterVariance declared variance of T * @param subjectTypeProjection {@code in/out/- A} * @param constrainingTypeProjection {@code in/out/- B} * @param upperConstraintKind kind of the constraint {@code MyClass<...A> <: MyClass<...B>} * (subtype in this example). * @return kind of constraint to be generated: {@code A <: B} (subtype), {@code A >: B} * (supertype) or {@code A = B} (equal). */ @NotNull private static ConstraintKind getTypeParameterConstraintKind( @NotNull Variance typeParameterVariance, @NotNull TypeProjection subjectTypeProjection, @NotNull TypeProjection constrainingTypeProjection, @NotNull ConstraintKind upperConstraintKind) { // If variance of type parameter is non-trivial, it should be taken into consideration to infer // result constraint type. // Otherwise when type parameter declared as INVARIANT, there might be non-trivial use-site // variance of a supertype. // // Example: Let class MyClass<T> is declared. // // If super type has 'out' projection: // MyClass<A> <: MyClass<out B>, // then constraint A <: B can be generated. // // If super type has 'in' projection: // MyClass<A> <: MyClass<in B>, // then constraint A >: B can be generated. // // Otherwise constraint A = B should be generated. Variance varianceForTypeParameter; if (typeParameterVariance != INVARIANT) { varianceForTypeParameter = typeParameterVariance; } else if (upperConstraintKind == SUPER_TYPE) { varianceForTypeParameter = constrainingTypeProjection.getProjectionKind(); } else if (upperConstraintKind == SUB_TYPE) { varianceForTypeParameter = subjectTypeProjection.getProjectionKind(); } else { varianceForTypeParameter = INVARIANT; } return getTypeParameterConstraintKind(varianceForTypeParameter, upperConstraintKind); }
private TypeProjection substituteCompoundType( TypeProjection originalProjection, int recursionDepth) throws SubstitutionException { final JetType type = originalProjection.getType(); Variance projectionKind = originalProjection.getProjectionKind(); if (type.getConstructor().getDeclarationDescriptor() instanceof TypeParameterDescriptor) { // substitution can't change type parameter // todo substitute bounds return originalProjection; } List<TypeProjection> substitutedArguments = substituteTypeArguments( type.getConstructor().getParameters(), type.getArguments(), recursionDepth); // Only type parameters of the corresponding class (or captured type parameters of outer // declaration) are substituted // e.g. for return type Foo of 'add(..)' in 'class Foo { fun <R> add(bar: Bar<R>): Foo }' R // shouldn't be substituted in the scope TypeSubstitution substitutionFilteringTypeParameters = new TypeSubstitution() { private final Collection<TypeConstructor> containedOrCapturedTypeParameters = TypeUtilsKt.getContainedAndCapturedTypeParameterConstructors(type); @Nullable @Override public TypeProjection get(@NotNull JetType key) { return containedOrCapturedTypeParameters.contains(key.getConstructor()) ? substitution.get(key) : null; } @Override public boolean isEmpty() { return substitution.isEmpty(); } }; JetType substitutedType = JetTypeImpl.create( type.getAnnotations(), // Old annotations. This is questionable type.getConstructor(), // The same constructor type.isMarkedNullable(), // Same nullability substitutedArguments, substitutionFilteringTypeParameters, new SubstitutingScope( type.getMemberScope(), create(substitutionFilteringTypeParameters)), type.getCapabilities()); return new TypeProjectionImpl(projectionKind, substitutedType); }
private static void doUnify( TypeProjection knownProjection, TypeProjection projectWithVariables, Predicate<TypeConstructor> isVariable, UnificationResultImpl result) { JetType known = knownProjection.getType(); JetType withVariables = projectWithVariables.getType(); // in Foo ~ in X => Foo ~ X Variance knownProjectionKind = knownProjection.getProjectionKind(); Variance withVariablesProjectionKind = projectWithVariables.getProjectionKind(); if (knownProjectionKind == withVariablesProjectionKind && knownProjectionKind != Variance.INVARIANT) { doUnify(new TypeProjection(known), new TypeProjection(withVariables), isVariable, result); return; } // Foo? ~ X? => Foo ~ X if (known.isNullable() && withVariables.isNullable()) { doUnify( new TypeProjection(knownProjectionKind, TypeUtils.makeNotNullable(known)), new TypeProjection(withVariablesProjectionKind, TypeUtils.makeNotNullable(withVariables)), isVariable, result); return; } // in Foo ~ out X => fail // in Foo ~ X => may be OK if (knownProjectionKind != withVariablesProjectionKind && withVariablesProjectionKind != Variance.INVARIANT) { result.fail(); return; } // Foo ~ X? => fail if (!known.isNullable() && withVariables.isNullable()) { result.fail(); return; } // Foo ~ X => x |-> Foo TypeConstructor maybeVariable = withVariables.getConstructor(); if (isVariable.apply(maybeVariable)) { result.put(maybeVariable, new TypeProjection(knownProjectionKind, known)); return; } // Foo? ~ Foo || in Foo ~ Foo || Foo ~ Bar boolean structuralMismatch = known.isNullable() != withVariables.isNullable() || knownProjectionKind != withVariablesProjectionKind || !known.getConstructor().equals(withVariables.getConstructor()); if (structuralMismatch) { result.fail(); return; } // Foo<A> ~ Foo<B, C> if (known.getArguments().size() != withVariables.getArguments().size()) { result.fail(); return; } // Foo ~ Foo if (known.getArguments().isEmpty()) { return; } // Foo<...> ~ Foo<...> List<TypeProjection> knownArguments = known.getArguments(); List<TypeProjection> withVariablesArguments = withVariables.getArguments(); for (int i = 0; i < knownArguments.size(); i++) { TypeProjection knownArg = knownArguments.get(i); TypeProjection withVariablesArg = withVariablesArguments.get(i); doUnify(knownArg, withVariablesArg, isVariable, result); } }
@NotNull private TypeProjection unsafeSubstitute( @NotNull TypeProjection originalProjection, int recursionDepth) throws SubstitutionException { assertRecursionDepth(recursionDepth, originalProjection, substitution); if (originalProjection.isStarProjection()) return originalProjection; // The type is within the substitution range, i.e. T or T? JetType type = originalProjection.getType(); TypeProjection replacement = substitution.get(type); Variance originalProjectionKind = originalProjection.getProjectionKind(); if (replacement == null && FlexibleTypesKt.isFlexible(type) && !TypeCapabilitiesKt.isCustomTypeVariable(type)) { Flexibility flexibility = FlexibleTypesKt.flexibility(type); TypeProjection substitutedLower = unsafeSubstitute( new TypeProjectionImpl(originalProjectionKind, flexibility.getLowerBound()), recursionDepth + 1); TypeProjection substitutedUpper = unsafeSubstitute( new TypeProjectionImpl(originalProjectionKind, flexibility.getUpperBound()), recursionDepth + 1); Variance substitutedProjectionKind = substitutedLower.getProjectionKind(); assert (substitutedProjectionKind == substitutedUpper.getProjectionKind()) && originalProjectionKind == Variance.INVARIANT || originalProjectionKind == substitutedProjectionKind : "Unexpected substituted projection kind: " + substitutedProjectionKind + "; original: " + originalProjectionKind; JetType substitutedFlexibleType = DelegatingFlexibleType.create( substitutedLower.getType(), substitutedUpper.getType(), flexibility.getExtraCapabilities()); return new TypeProjectionImpl(substitutedProjectionKind, substitutedFlexibleType); } if (KotlinBuiltIns.isNothing(type) || type.isError()) return originalProjection; if (replacement != null) { VarianceConflictType varianceConflict = conflictType(originalProjectionKind, replacement.getProjectionKind()); // Captured type might be substituted in an opposite projection: // out 'Captured (in Int)' = out Int // in 'Captured (out Int)' = in Int boolean allowVarianceConflict = CapturedTypeConstructorKt.isCaptured(type); if (!allowVarianceConflict) { //noinspection EnumSwitchStatementWhichMissesCases switch (varianceConflict) { case OUT_IN_IN_POSITION: throw new SubstitutionException("Out-projection in in-position"); case IN_IN_OUT_POSITION: // todo use the right type parameter variance and upper bound return new TypeProjectionImpl( Variance.OUT_VARIANCE, type.getConstructor().getBuiltIns().getNullableAnyType()); } } JetType substitutedType; CustomTypeVariable typeVariable = TypeCapabilitiesKt.getCustomTypeVariable(type); if (replacement.isStarProjection()) { return replacement; } else if (typeVariable != null) { substitutedType = typeVariable.substitutionResult(replacement.getType()); } else { // this is a simple type T or T?: if it's T, we should just take replacement, if T? - we // make replacement nullable substitutedType = TypeUtils.makeNullableIfNeeded(replacement.getType(), type.isMarkedNullable()); } // substitutionType.annotations = replacement.annotations ++ type.annotations if (!type.getAnnotations().isEmpty()) { Annotations typeAnnotations = filterOutUnsafeVariance(type.getAnnotations()); substitutedType = TypeUtilsKt.replaceAnnotations( substitutedType, new CompositeAnnotations(substitutedType.getAnnotations(), typeAnnotations)); } Variance resultingProjectionKind = varianceConflict == VarianceConflictType.NO_CONFLICT ? combine(originalProjectionKind, replacement.getProjectionKind()) : originalProjectionKind; return new TypeProjectionImpl(resultingProjectionKind, substitutedType); } // The type is not within the substitution range, i.e. Foo, Bar<T> etc. return substituteCompoundType(originalProjection, recursionDepth); }
public static boolean canHaveSubtypes(KotlinTypeChecker typeChecker, @NotNull KotlinType type) { if (type.isMarkedNullable()) { return true; } if (!type.getConstructor().isFinal()) { return true; } List<TypeParameterDescriptor> parameters = type.getConstructor().getParameters(); List<TypeProjection> arguments = type.getArguments(); for (int i = 0, parametersSize = parameters.size(); i < parametersSize; i++) { TypeParameterDescriptor parameterDescriptor = parameters.get(i); TypeProjection typeProjection = arguments.get(i); if (typeProjection.isStarProjection()) return true; Variance projectionKind = typeProjection.getProjectionKind(); KotlinType argument = typeProjection.getType(); switch (parameterDescriptor.getVariance()) { case INVARIANT: switch (projectionKind) { case INVARIANT: if (lowerThanBound(typeChecker, argument, parameterDescriptor) || canHaveSubtypes(typeChecker, argument)) { return true; } break; case IN_VARIANCE: if (lowerThanBound(typeChecker, argument, parameterDescriptor)) { return true; } break; case OUT_VARIANCE: if (canHaveSubtypes(typeChecker, argument)) { return true; } break; } break; case IN_VARIANCE: if (projectionKind != Variance.OUT_VARIANCE) { if (lowerThanBound(typeChecker, argument, parameterDescriptor)) { return true; } } else { if (canHaveSubtypes(typeChecker, argument)) { return true; } } break; case OUT_VARIANCE: if (projectionKind != Variance.IN_VARIANCE) { if (canHaveSubtypes(typeChecker, argument)) { return true; } } else { if (lowerThanBound(typeChecker, argument, parameterDescriptor)) { return true; } } break; } } return false; }