/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.resolve.jvm.platform
import org.jetbrains.kotlin.load.java.lazy.types.RawTypeImpl
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.resolve.calls.checkers.AdditionalTypeChecker
import org.jetbrains.kotlin.resolve.calls.context.ResolutionContext
import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
import org.jetbrains.kotlin.types.checker.TypeCheckingProcedure
object JavaGenericVarianceViolationTypeChecker : AdditionalTypeChecker {
// Prohibits covariant type argument conversions `List<String> -> (MutableList<Any>..List<Any>)` when expected type's lower bound is invariant.
// It's needed to prevent accident unsafe covariant conversions of mutable collections.
//
// Example:
// class JavaClass { static void fillWithDefaultObjects(List<Object> list); // add Object's to list }
//
// val x: MutableList<String>
// JavaClass.fillWithDefaultObjects(x) // using `x` after this call may lead to CCE
override fun checkType(
expression: KtExpression,
expressionType: KotlinType,
expressionTypeWithSmartCast: KotlinType,
c: ResolutionContext<*>
) {
val expectedType = c.expectedType
if (TypeUtils.noExpectedType(expectedType) || ErrorUtils.containsErrorType(expectedType) || ErrorUtils.containsUninferredParameter(expectedType)) return
// optimization: if no arguments or flexibility, everything is OK
if (expectedType.arguments.isEmpty() || !expectedType.isFlexible()) return
val lowerBound = expectedType.asFlexibleType().lowerBound
val upperBound = expectedType.asFlexibleType().upperBound
// Use site variance projection is always the same for flexible types
if (lowerBound.constructor == upperBound.constructor) return
// Anything is acceptable for raw types
if (expectedType.unwrap() is RawTypeImpl) return
val correspondingSubType = TypeCheckingProcedure.findCorrespondingSupertype(expressionTypeWithSmartCast, lowerBound) ?: return
assert(lowerBound.arguments.size == upperBound.arguments.size) {
"Different arguments count in flexible bounds: " +
"($lowerBound(${lowerBound.arguments.size})..$upperBound(${upperBound.arguments.size})"
}
assert(lowerBound.arguments.size == correspondingSubType.arguments.size) {
"Different arguments count in corresponding subtype and supertype: " +
"($lowerBound(${lowerBound.arguments.size})..$correspondingSubType(${correspondingSubType.arguments.size})"
}
val lowerParameters = lowerBound.constructor.parameters
val upperParameters = upperBound.constructor.parameters
val lowerArguments = lowerBound.arguments
correspondingSubType.arguments.indices.forEach {
index ->
val lowerArgument = lowerArguments[index]
// Currently we don't have flexible types with different constructors with contravariant arguments
// So check just covariant case
if (lowerParameters[index].variance == Variance.INVARIANT
&& upperParameters[index].variance == Variance.OUT_VARIANCE
&& lowerArgument.projectionKind != Variance.OUT_VARIANCE
&& !KotlinTypeChecker.DEFAULT.equalTypes(correspondingSubType.arguments[index].type, lowerArgument.type)
) {
c.trace.report(ErrorsJvm.JAVA_TYPE_MISMATCH.on(expression, expressionTypeWithSmartCast, expectedType))
}
}
}
}