Conversion to Kotlin (step 2)
This commit is contained in:
+143
-181
@@ -14,229 +14,191 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.resolve.calls.smartcasts;
|
||||
package org.jetbrains.kotlin.resolve.calls.smartcasts
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import kotlin.collections.CollectionsKt;
|
||||
import kotlin.jvm.functions.Function1;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
|
||||
import org.jetbrains.kotlin.psi.KtExpression;
|
||||
import org.jetbrains.kotlin.resolve.BindingContext;
|
||||
import org.jetbrains.kotlin.resolve.BindingTrace;
|
||||
import org.jetbrains.kotlin.resolve.calls.ArgumentTypeResolver;
|
||||
import org.jetbrains.kotlin.resolve.calls.context.ResolutionContext;
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue;
|
||||
import org.jetbrains.kotlin.types.KotlinType;
|
||||
import org.jetbrains.kotlin.types.TypeIntersector;
|
||||
import org.jetbrains.kotlin.types.TypeUtils;
|
||||
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.jetbrains.kotlin.diagnostics.Errors.SMARTCAST_IMPOSSIBLE;
|
||||
import static org.jetbrains.kotlin.resolve.BindingContext.IMPLICIT_RECEIVER_SMARTCAST;
|
||||
import static org.jetbrains.kotlin.resolve.BindingContext.SMARTCAST;
|
||||
import com.google.common.collect.Lists
|
||||
import com.google.common.collect.Sets
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Errors.SMARTCAST_IMPOSSIBLE
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.BindingContext.IMPLICIT_RECEIVER_SMARTCAST
|
||||
import org.jetbrains.kotlin.resolve.BindingContext.SMARTCAST
|
||||
import org.jetbrains.kotlin.resolve.BindingTrace
|
||||
import org.jetbrains.kotlin.resolve.calls.ArgumentTypeResolver
|
||||
import org.jetbrains.kotlin.resolve.calls.context.ResolutionContext
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.TypeIntersector
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
|
||||
|
||||
// We do not want to make methods static to keep SmartCastManager as a component
|
||||
@SuppressWarnings("MethodMayBeStatic")
|
||||
public class SmartCastManager {
|
||||
class SmartCastManager {
|
||||
|
||||
@NotNull
|
||||
public List<KotlinType> getSmartCastVariants(
|
||||
@NotNull ReceiverValue receiverToCast,
|
||||
@NotNull ResolutionContext context
|
||||
) {
|
||||
return getSmartCastVariants(receiverToCast, context.trace.getBindingContext(), context.scope.getOwnerDescriptor(), context.dataFlowInfo);
|
||||
fun getSmartCastVariants(
|
||||
receiverToCast: ReceiverValue,
|
||||
context: ResolutionContext<*>): List<KotlinType> {
|
||||
return getSmartCastVariants(receiverToCast, context.trace.bindingContext, context.scope.ownerDescriptor, context.dataFlowInfo)
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<KotlinType> getSmartCastVariants(
|
||||
@NotNull ReceiverValue receiverToCast,
|
||||
@NotNull BindingContext bindingContext,
|
||||
@NotNull DeclarationDescriptor containingDeclarationOrModule,
|
||||
@NotNull DataFlowInfo dataFlowInfo
|
||||
) {
|
||||
List<KotlinType> variants = Lists.newArrayList();
|
||||
variants.add(receiverToCast.getType());
|
||||
variants.addAll(getSmartCastVariantsExcludingReceiver(bindingContext, containingDeclarationOrModule, dataFlowInfo, receiverToCast));
|
||||
return variants;
|
||||
fun getSmartCastVariants(
|
||||
receiverToCast: ReceiverValue,
|
||||
bindingContext: BindingContext,
|
||||
containingDeclarationOrModule: DeclarationDescriptor,
|
||||
dataFlowInfo: DataFlowInfo): List<KotlinType> {
|
||||
val variants = Lists.newArrayList<KotlinType>()
|
||||
variants.add(receiverToCast.type)
|
||||
variants.addAll(getSmartCastVariantsExcludingReceiver(bindingContext, containingDeclarationOrModule, dataFlowInfo, receiverToCast))
|
||||
return variants
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<KotlinType> getSmartCastVariantsWithLessSpecificExcluded(
|
||||
@NotNull ReceiverValue receiverToCast,
|
||||
@NotNull BindingContext bindingContext,
|
||||
@NotNull DeclarationDescriptor containingDeclarationOrModule,
|
||||
@NotNull DataFlowInfo dataFlowInfo
|
||||
) {
|
||||
final List<KotlinType> variants = CollectionsKt.distinct(
|
||||
getSmartCastVariants(receiverToCast, bindingContext, containingDeclarationOrModule, dataFlowInfo));
|
||||
return CollectionsKt.filter(variants, new Function1<KotlinType, Boolean>() {
|
||||
@Override
|
||||
public Boolean invoke(final KotlinType type) {
|
||||
return CollectionsKt.none(variants, new Function1<KotlinType, Boolean>() {
|
||||
@Override
|
||||
public Boolean invoke(KotlinType another) {
|
||||
return another != type && KotlinTypeChecker.DEFAULT.isSubtypeOf(another, type);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
fun getSmartCastVariantsWithLessSpecificExcluded(
|
||||
receiverToCast: ReceiverValue,
|
||||
bindingContext: BindingContext,
|
||||
containingDeclarationOrModule: DeclarationDescriptor,
|
||||
dataFlowInfo: DataFlowInfo): List<KotlinType> {
|
||||
val variants = getSmartCastVariants(receiverToCast, bindingContext, containingDeclarationOrModule, dataFlowInfo).distinct()
|
||||
return variants.filter { type -> variants.none { another -> another !== type && KotlinTypeChecker.DEFAULT.isSubtypeOf(another, type) } }
|
||||
}
|
||||
|
||||
/**
|
||||
* @return variants @param receiverToCast may be cast to according to context dataFlowInfo, receiverToCast itself is NOT included
|
||||
*/
|
||||
@NotNull
|
||||
public Collection<KotlinType> getSmartCastVariantsExcludingReceiver(
|
||||
@NotNull ResolutionContext context,
|
||||
@NotNull ReceiverValue receiverToCast
|
||||
) {
|
||||
return getSmartCastVariantsExcludingReceiver(context.trace.getBindingContext(),
|
||||
context.scope.getOwnerDescriptor(),
|
||||
fun getSmartCastVariantsExcludingReceiver(
|
||||
context: ResolutionContext<*>,
|
||||
receiverToCast: ReceiverValue): Collection<KotlinType> {
|
||||
return getSmartCastVariantsExcludingReceiver(context.trace.bindingContext,
|
||||
context.scope.ownerDescriptor,
|
||||
context.dataFlowInfo,
|
||||
receiverToCast);
|
||||
receiverToCast)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return variants @param receiverToCast may be cast to according to @param dataFlowInfo, @param receiverToCast itself is NOT included
|
||||
*/
|
||||
@NotNull
|
||||
public Collection<KotlinType> getSmartCastVariantsExcludingReceiver(
|
||||
@NotNull BindingContext bindingContext,
|
||||
@NotNull DeclarationDescriptor containingDeclarationOrModule,
|
||||
@NotNull DataFlowInfo dataFlowInfo,
|
||||
@NotNull ReceiverValue receiverToCast
|
||||
) {
|
||||
DataFlowValue dataFlowValue = DataFlowValueFactory.createDataFlowValue(
|
||||
receiverToCast, bindingContext, containingDeclarationOrModule
|
||||
);
|
||||
fun getSmartCastVariantsExcludingReceiver(
|
||||
bindingContext: BindingContext,
|
||||
containingDeclarationOrModule: DeclarationDescriptor,
|
||||
dataFlowInfo: DataFlowInfo,
|
||||
receiverToCast: ReceiverValue): Collection<KotlinType> {
|
||||
val dataFlowValue = DataFlowValueFactory.createDataFlowValue(
|
||||
receiverToCast, bindingContext, containingDeclarationOrModule)
|
||||
|
||||
return dataFlowInfo.getCollectedTypes(dataFlowValue);
|
||||
return dataFlowInfo.getCollectedTypes(dataFlowValue)
|
||||
}
|
||||
|
||||
public boolean isSubTypeBySmartCastIgnoringNullability(
|
||||
@NotNull ReceiverValue receiverArgument,
|
||||
@NotNull KotlinType receiverParameterType,
|
||||
@NotNull ResolutionContext context
|
||||
) {
|
||||
List<KotlinType> smartCastTypes = getSmartCastVariants(receiverArgument, context);
|
||||
return getSmartCastSubType(TypeUtils.makeNullable(receiverParameterType), smartCastTypes) != null;
|
||||
fun isSubTypeBySmartCastIgnoringNullability(
|
||||
receiverArgument: ReceiverValue,
|
||||
receiverParameterType: KotlinType,
|
||||
context: ResolutionContext<*>): Boolean {
|
||||
val smartCastTypes = getSmartCastVariants(receiverArgument, context)
|
||||
return getSmartCastSubType(TypeUtils.makeNullable(receiverParameterType), smartCastTypes) != null
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private KotlinType getSmartCastSubType(
|
||||
@NotNull KotlinType receiverParameterType,
|
||||
@NotNull Collection<KotlinType> smartCastTypes
|
||||
) {
|
||||
Set<KotlinType> subTypes = Sets.newHashSet();
|
||||
for (KotlinType smartCastType : smartCastTypes) {
|
||||
private fun getSmartCastSubType(
|
||||
receiverParameterType: KotlinType,
|
||||
smartCastTypes: Collection<KotlinType>): KotlinType? {
|
||||
val subTypes = Sets.newHashSet<KotlinType>()
|
||||
for (smartCastType in smartCastTypes) {
|
||||
if (ArgumentTypeResolver.isSubtypeOfForArgumentType(smartCastType, receiverParameterType)) {
|
||||
subTypes.add(smartCastType);
|
||||
subTypes.add(smartCastType)
|
||||
}
|
||||
}
|
||||
if (subTypes.isEmpty()) return null;
|
||||
if (subTypes.isEmpty()) return null
|
||||
|
||||
KotlinType intersection = TypeIntersector.intersectTypes(KotlinTypeChecker.DEFAULT, subTypes);
|
||||
if (intersection == null || !intersection.getConstructor().isDenotable()) {
|
||||
return receiverParameterType;
|
||||
val intersection = TypeIntersector.intersectTypes(KotlinTypeChecker.DEFAULT, subTypes)
|
||||
if (intersection == null || !intersection.constructor.isDenotable) {
|
||||
return receiverParameterType
|
||||
}
|
||||
return intersection;
|
||||
return intersection
|
||||
}
|
||||
|
||||
private static void recordCastOrError(
|
||||
@NotNull KtExpression expression,
|
||||
@NotNull KotlinType type,
|
||||
@NotNull BindingTrace trace,
|
||||
@NotNull DataFlowValue dataFlowValue,
|
||||
boolean recordExpressionType
|
||||
) {
|
||||
if (KotlinBuiltIns.isNullableNothing(type)) return;
|
||||
if (dataFlowValue.isPredictable()) {
|
||||
trace.record(SMARTCAST, expression, type);
|
||||
if (recordExpressionType) {
|
||||
//TODO
|
||||
//Why the expression type is rewritten for receivers and is not rewritten for arguments? Is it necessary?
|
||||
trace.recordType(expression, type);
|
||||
}
|
||||
}
|
||||
else {
|
||||
trace.report(SMARTCAST_IMPOSSIBLE.on(expression, type, expression.getText(), dataFlowValue.getKind().getDescription()));
|
||||
}
|
||||
}
|
||||
companion object {
|
||||
|
||||
@Nullable
|
||||
public static SmartCastResult checkAndRecordPossibleCast(
|
||||
@NotNull DataFlowValue dataFlowValue,
|
||||
@NotNull KotlinType expectedType,
|
||||
@Nullable KtExpression expression,
|
||||
@NotNull ResolutionContext c,
|
||||
@Nullable KtExpression calleeExpression,
|
||||
boolean recordExpressionType
|
||||
) {
|
||||
return checkAndRecordPossibleCast(
|
||||
dataFlowValue, expectedType, null, expression, c, calleeExpression, recordExpressionType);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static SmartCastResult checkAndRecordPossibleCast(
|
||||
@NotNull DataFlowValue dataFlowValue,
|
||||
@NotNull KotlinType expectedType,
|
||||
@Nullable Function1<KotlinType, Boolean> additionalPredicate,
|
||||
@Nullable KtExpression expression,
|
||||
@NotNull ResolutionContext c,
|
||||
@Nullable KtExpression calleeExpression,
|
||||
boolean recordExpressionType
|
||||
) {
|
||||
for (KotlinType possibleType : c.dataFlowInfo.getCollectedTypes(dataFlowValue)) {
|
||||
if (ArgumentTypeResolver.isSubtypeOfForArgumentType(possibleType, expectedType)
|
||||
&& (additionalPredicate == null || additionalPredicate.invoke(possibleType))) {
|
||||
if (expression != null) {
|
||||
recordCastOrError(expression, possibleType, c.trace, dataFlowValue, recordExpressionType);
|
||||
private fun recordCastOrError(
|
||||
expression: KtExpression,
|
||||
type: KotlinType,
|
||||
trace: BindingTrace,
|
||||
dataFlowValue: DataFlowValue,
|
||||
recordExpressionType: Boolean) {
|
||||
if (KotlinBuiltIns.isNullableNothing(type)) return
|
||||
if (dataFlowValue.isPredictable) {
|
||||
trace.record(SMARTCAST, expression, type)
|
||||
if (recordExpressionType) {
|
||||
//TODO
|
||||
//Why the expression type is rewritten for receivers and is not rewritten for arguments? Is it necessary?
|
||||
trace.recordType(expression, type)
|
||||
}
|
||||
else if (calleeExpression != null && dataFlowValue.isPredictable()) {
|
||||
c.trace.record(IMPLICIT_RECEIVER_SMARTCAST, calleeExpression, possibleType);
|
||||
}
|
||||
return new SmartCastResult(possibleType, dataFlowValue.isPredictable());
|
||||
}
|
||||
else {
|
||||
trace.report(SMARTCAST_IMPOSSIBLE.on(expression, type, expression.text, dataFlowValue.kind.description))
|
||||
}
|
||||
}
|
||||
|
||||
if (!c.dataFlowInfo.getCollectedNullability(dataFlowValue).canBeNull() && !expectedType.isMarkedNullable()) {
|
||||
// Handling cases like:
|
||||
// fun bar(x: Any) {}
|
||||
// fun <T : Any?> foo(x: T) {
|
||||
// if (x != null) {
|
||||
// bar(x) // Should be allowed with smart cast
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// It doesn't handled by lower code with getPossibleTypes because smart cast of T after `x != null` is still has same type T.
|
||||
// But at the same time we're sure that `x` can't be null and just check for such cases manually
|
||||
fun checkAndRecordPossibleCast(
|
||||
dataFlowValue: DataFlowValue,
|
||||
expectedType: KotlinType,
|
||||
expression: KtExpression?,
|
||||
c: ResolutionContext<*>,
|
||||
calleeExpression: KtExpression?,
|
||||
recordExpressionType: Boolean): SmartCastResult? {
|
||||
return checkAndRecordPossibleCast(
|
||||
dataFlowValue, expectedType, null, expression, c, calleeExpression, recordExpressionType)
|
||||
}
|
||||
|
||||
// E.g. in case x!! when x has type of T where T is type parameter with nullable upper bounds
|
||||
// x!! is immanently not null (see DataFlowValueFactory.createDataFlowValue for expression)
|
||||
boolean immanentlyNotNull = !dataFlowValue.getImmanentNullability().canBeNull();
|
||||
KotlinType nullableExpectedType = TypeUtils.makeNullable(expectedType);
|
||||
|
||||
if (ArgumentTypeResolver.isSubtypeOfForArgumentType(dataFlowValue.getType(), nullableExpectedType)
|
||||
&& (additionalPredicate == null || additionalPredicate.invoke(dataFlowValue.getType()))) {
|
||||
if (!immanentlyNotNull) {
|
||||
fun checkAndRecordPossibleCast(
|
||||
dataFlowValue: DataFlowValue,
|
||||
expectedType: KotlinType,
|
||||
additionalPredicate: Function1<KotlinType, Boolean>?,
|
||||
expression: KtExpression?,
|
||||
c: ResolutionContext<*>,
|
||||
calleeExpression: KtExpression?,
|
||||
recordExpressionType: Boolean): SmartCastResult? {
|
||||
for (possibleType in c.dataFlowInfo.getCollectedTypes(dataFlowValue)) {
|
||||
if (ArgumentTypeResolver.isSubtypeOfForArgumentType(possibleType, expectedType) && (additionalPredicate == null || additionalPredicate.invoke(possibleType))) {
|
||||
if (expression != null) {
|
||||
recordCastOrError(expression, dataFlowValue.getType(), c.trace, dataFlowValue, recordExpressionType);
|
||||
recordCastOrError(expression, possibleType, c.trace, dataFlowValue, recordExpressionType)
|
||||
}
|
||||
else if (calleeExpression != null && dataFlowValue.isPredictable) {
|
||||
c.trace.record(IMPLICIT_RECEIVER_SMARTCAST, calleeExpression, possibleType)
|
||||
}
|
||||
return SmartCastResult(possibleType, dataFlowValue.isPredictable)
|
||||
}
|
||||
|
||||
return new SmartCastResult(dataFlowValue.getType(), immanentlyNotNull || dataFlowValue.isPredictable());
|
||||
}
|
||||
return checkAndRecordPossibleCast(dataFlowValue, nullableExpectedType, expression, c, calleeExpression, recordExpressionType);
|
||||
}
|
||||
|
||||
return null;
|
||||
if (!c.dataFlowInfo.getCollectedNullability(dataFlowValue).canBeNull() && !expectedType.isMarkedNullable) {
|
||||
// Handling cases like:
|
||||
// fun bar(x: Any) {}
|
||||
// fun <T : Any?> foo(x: T) {
|
||||
// if (x != null) {
|
||||
// bar(x) // Should be allowed with smart cast
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// It doesn't handled by lower code with getPossibleTypes because smart cast of T after `x != null` is still has same type T.
|
||||
// But at the same time we're sure that `x` can't be null and just check for such cases manually
|
||||
|
||||
// E.g. in case x!! when x has type of T where T is type parameter with nullable upper bounds
|
||||
// x!! is immanently not null (see DataFlowValueFactory.createDataFlowValue for expression)
|
||||
val immanentlyNotNull = !dataFlowValue.immanentNullability.canBeNull()
|
||||
val nullableExpectedType = TypeUtils.makeNullable(expectedType)
|
||||
|
||||
if (ArgumentTypeResolver.isSubtypeOfForArgumentType(dataFlowValue.type, nullableExpectedType) && (additionalPredicate == null || additionalPredicate.invoke(dataFlowValue.type))) {
|
||||
if (!immanentlyNotNull) {
|
||||
if (expression != null) {
|
||||
recordCastOrError(expression, dataFlowValue.type, c.trace, dataFlowValue, recordExpressionType)
|
||||
}
|
||||
}
|
||||
|
||||
return SmartCastResult(dataFlowValue.type, immanentlyNotNull || dataFlowValue.isPredictable)
|
||||
}
|
||||
return checkAndRecordPossibleCast(dataFlowValue, nullableExpectedType, expression, c, calleeExpression, recordExpressionType)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,7 +304,7 @@ public class DataFlowAnalyzer {
|
||||
) {
|
||||
DataFlowValue dataFlowValue = DataFlowValueFactory.createDataFlowValue(expression, expressionType, c);
|
||||
|
||||
return SmartCastManager.checkAndRecordPossibleCast(dataFlowValue, c.expectedType, expression, c, null, false);
|
||||
return SmartCastManager.Companion.checkAndRecordPossibleCast(dataFlowValue, c.expectedType, expression, c, null, false);
|
||||
}
|
||||
|
||||
public void recordExpectedType(@NotNull BindingTrace trace, @NotNull KtExpression expression, @NotNull KotlinType expectedType) {
|
||||
|
||||
Reference in New Issue
Block a user