From 5d2dfcd48f5bf89ba8d5cb436914169a65b1628e Mon Sep 17 00:00:00 2001 From: Stepan Koltsov Date: Wed, 23 Nov 2011 18:15:37 +0400 Subject: [PATCH] more check for casts KT-445 (Don't allow deep instanceof for erased parameters) and more see new tests in compiler/testData/checkerWithErrorTypes/full/cast --- .../jet/lang/diagnostics/Errors.java | 6 ++- .../BasicExpressionTypingVisitor.java | 50 +++++++++++++++++++ .../PatternMatchingTypingVisitor.java | 32 +++++++++++- .../full/cast/AsErasedError.jet | 4 ++ .../full/cast/AsErasedFine.jet | 4 ++ .../full/cast/AsErasedStar.jet | 4 ++ .../full/cast/AsErasedWarning.jet | 3 ++ .../full/cast/IsErasedAllow.jet | 5 ++ .../cast/IsErasedAllowParameterSubtype.jet | 9 ++++ .../full/cast/IsErasedDisallowFromAny.jet | 3 ++ .../cast/IsErasedDisallowFromParameter.jet | 5 ++ .../full/cast/IsErasedStar.jet | 3 ++ .../full/cast/IsReified.jet | 3 ++ .../full/cast/IsReifiedDisallow.jet | 3 ++ .../full/cast/IsTraits.jet | 4 ++ .../full/cast/WhenErasedDisallowFromAny.jet | 6 +++ .../testData/codegen/patternMatching/is.jet | 4 +- 17 files changed, 143 insertions(+), 5 deletions(-) create mode 100644 compiler/testData/checkerWithErrorTypes/full/cast/AsErasedError.jet create mode 100644 compiler/testData/checkerWithErrorTypes/full/cast/AsErasedFine.jet create mode 100644 compiler/testData/checkerWithErrorTypes/full/cast/AsErasedStar.jet create mode 100644 compiler/testData/checkerWithErrorTypes/full/cast/AsErasedWarning.jet create mode 100644 compiler/testData/checkerWithErrorTypes/full/cast/IsErasedAllow.jet create mode 100644 compiler/testData/checkerWithErrorTypes/full/cast/IsErasedAllowParameterSubtype.jet create mode 100644 compiler/testData/checkerWithErrorTypes/full/cast/IsErasedDisallowFromAny.jet create mode 100644 compiler/testData/checkerWithErrorTypes/full/cast/IsErasedDisallowFromParameter.jet create mode 100644 compiler/testData/checkerWithErrorTypes/full/cast/IsErasedStar.jet create mode 100644 compiler/testData/checkerWithErrorTypes/full/cast/IsReified.jet create mode 100644 compiler/testData/checkerWithErrorTypes/full/cast/IsReifiedDisallow.jet create mode 100644 compiler/testData/checkerWithErrorTypes/full/cast/IsTraits.jet create mode 100644 compiler/testData/checkerWithErrorTypes/full/cast/WhenErasedDisallowFromAny.jet diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/Errors.java index da7edd9d6fe..785e9abbae6 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/Errors.java @@ -16,6 +16,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Collection; import java.util.Iterator; +import java.util.List; import static org.jetbrains.jet.lang.diagnostics.Severity.ERROR; import static org.jetbrains.jet.lang.diagnostics.Severity.WARNING; @@ -275,7 +276,10 @@ public interface Errors { ParameterizedDiagnosticFactory2 TYPE_MISMATCH_IN_TUPLE_PATTERN = ParameterizedDiagnosticFactory2.create(ERROR, "Type mismatch: subject is of type {0} but the pattern is of type Tuple{1}"); // TODO: message ParameterizedDiagnosticFactory2 TYPE_MISMATCH_IN_BINDING_PATTERN = ParameterizedDiagnosticFactory2.create(ERROR, "{0} must be a supertype of {1}. Use 'is' to match against {0}"); ParameterizedDiagnosticFactory2 INCOMPATIBLE_TYPES = ParameterizedDiagnosticFactory2.create(ERROR, "Incompatible types: {0} and {1}"); - + + ParameterizedDiagnosticFactory1 CANNOT_CHECK_FOR_ERASED = ParameterizedDiagnosticFactory1.create(ERROR, "Cannot check for instance of erased type: {0}"); + ParameterizedDiagnosticFactory2 UNCHECKED_CAST = ParameterizedDiagnosticFactory2.create(WARNING, "Unchecked cast: {0} to {1}"); + ParameterizedDiagnosticFactory3> INCONSISTENT_TYPE_PARAMETER_VALUES = new ParameterizedDiagnosticFactory3>(ERROR, "Type parameter {0} of {1} has inconsistent values: {2}") { @Override protected String makeMessageForA(@NotNull TypeParameterDescriptor typeParameterDescriptor) { diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/BasicExpressionTypingVisitor.java b/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/BasicExpressionTypingVisitor.java index 0446105d479..10c3ca8cae5 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/BasicExpressionTypingVisitor.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/BasicExpressionTypingVisitor.java @@ -1,6 +1,7 @@ package org.jetbrains.jet.lang.types.expressions; import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; import com.intellij.lang.ASTNode; import com.intellij.psi.PsiElement; import com.intellij.psi.tree.IElementType; @@ -8,6 +9,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.JetNodeTypes; import org.jetbrains.jet.lang.descriptors.*; +import org.jetbrains.jet.lang.diagnostics.Errors; import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.resolve.*; import org.jetbrains.jet.lang.resolve.calls.CallMaker; @@ -245,10 +247,58 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { else { if (typeChecker.isSubtypeOf(actualType, targetType)) { context.trace.report(USELESS_CAST.on(expression, expression.getOperationSign())); + } else { + if (isCastErased(actualType, targetType)) { + context.trace.report(Errors.UNCHECKED_CAST.on(expression, actualType, targetType)); + } } } } + /** + * Check if assignment from ActualType to TargetType is erased. + * It is an error in "is" statement and warning in "as". + */ + public static boolean isCastErased(JetType actualType, JetType targetType) { + + JetType targetTypeClerared = TypeUtils.makeUnsubstitutedType( + (ClassDescriptor) targetType.getConstructor().getDeclarationDescriptor(), null); + + Multimap clearTypeSubstitutionMap = + TypeUtils.buildDeepSubstitutionMultimap(targetTypeClerared); + + Set clearSubstituted = new HashSet(); + + for (int i = 0; i < actualType.getConstructor().getParameters().size(); ++i) { + TypeParameterDescriptor subjectTypeParameterDescriptor = actualType.getConstructor().getParameters().get(i); + + Collection subst = clearTypeSubstitutionMap.get(subjectTypeParameterDescriptor.getTypeConstructor()); + for (TypeProjection proj : subst) { + clearSubstituted.add(proj.getType()); + } + } + + for (int i = 0; i < targetType.getConstructor().getParameters().size(); ++i) { + TypeParameterDescriptor typeParameter = targetType.getConstructor().getParameters().get(i); + TypeProjection typeProjection = targetType.getArguments().get(i); + + if (typeParameter.isReified()) { + continue; + } + + // "is List<*>" + if (typeProjection.equals(TypeUtils.makeStarProjection(typeParameter))) { + continue; + } + + // if parameter is mapped to nothing then it is erased + if (!clearSubstituted.contains(typeParameter.getDefaultType())) { + return true; + } + } + return false; + } + @Override public JetType visitTupleExpression(JetTupleExpression expression, ExpressionTypingContext context) { List entries = expression.getEntries(); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/PatternMatchingTypingVisitor.java b/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/PatternMatchingTypingVisitor.java index 6b8b8d0630c..86bc4e82677 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/PatternMatchingTypingVisitor.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/PatternMatchingTypingVisitor.java @@ -1,11 +1,14 @@ package org.jetbrains.jet.lang.types.expressions; +import com.google.common.collect.Multimap; import com.google.common.collect.Sets; import com.intellij.lang.ASTNode; import com.intellij.openapi.util.Ref; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor; import org.jetbrains.jet.lang.descriptors.VariableDescriptor; +import org.jetbrains.jet.lang.diagnostics.Errors; import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.resolve.calls.autocasts.DataFlowInfo; import org.jetbrains.jet.lang.resolve.calls.autocasts.DataFlowValue; @@ -17,8 +20,7 @@ import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverDescriptor; import org.jetbrains.jet.lang.resolve.scopes.receivers.TransientReceiver; import org.jetbrains.jet.lang.types.*; -import java.util.List; -import java.util.Set; +import java.util.*; import static org.jetbrains.jet.lang.diagnostics.Errors.*; import static org.jetbrains.jet.lang.types.expressions.ExpressionTypingUtils.ensureBooleanResultWithCustomSubject; @@ -245,6 +247,9 @@ public class PatternMatchingTypingVisitor extends ExpressionTypingVisitor { } } + /* + * (a: SubjectType) is Type + */ private void checkTypeCompatibility(@Nullable JetType type, @NotNull JetType subjectType, @NotNull JetElement reportErrorOn) { // TODO : Take auto casts into account? if (type == null) { @@ -253,6 +258,29 @@ public class PatternMatchingTypingVisitor extends ExpressionTypingVisitor { if (TypeUtils.intersect(context.semanticServices.getTypeChecker(), Sets.newHashSet(type, subjectType)) == null) { // context.trace.getErrorHandler().genericError(reportErrorOn.getNode(), "Incompatible types: " + type + " and " + subjectType); context.trace.report(INCOMPATIBLE_TYPES.on(reportErrorOn, type, subjectType)); + return; + } + + { + // check parameters compatible + Multimap typeSubstritutionMap = TypeUtils.buildDeepSubstitutionMultimap(type); + + for (int i = 0; i < subjectType.getConstructor().getParameters().size(); ++i) { + TypeParameterDescriptor subjectTypeParameterDescriptor = subjectType.getConstructor().getParameters().get(i); + TypeProjection subjectParameterType = subjectType.getArguments().get(i); + + Collection xxy = typeSubstritutionMap.get(subjectTypeParameterDescriptor.getTypeConstructor()); + for (TypeProjection proj : xxy) { + if (!context.semanticServices.getTypeChecker().isSubtypeOf(subjectParameterType.getType(), proj.getType())) { + context.trace.report(INCOMPATIBLE_TYPES.on(reportErrorOn, type, subjectType)); + continue; + } + } + } + } + + if (BasicExpressionTypingVisitor.isCastErased(subjectType, type)) { + context.trace.report(Errors.CANNOT_CHECK_FOR_ERASED.on(reportErrorOn, type)); } } diff --git a/compiler/testData/checkerWithErrorTypes/full/cast/AsErasedError.jet b/compiler/testData/checkerWithErrorTypes/full/cast/AsErasedError.jet new file mode 100644 index 00000000000..bff067d20ee --- /dev/null +++ b/compiler/testData/checkerWithErrorTypes/full/cast/AsErasedError.jet @@ -0,0 +1,4 @@ +import java.util.List; +import java.util.Collection; + +fun ff(c: Collection) = c as List diff --git a/compiler/testData/checkerWithErrorTypes/full/cast/AsErasedFine.jet b/compiler/testData/checkerWithErrorTypes/full/cast/AsErasedFine.jet new file mode 100644 index 00000000000..341f370d108 --- /dev/null +++ b/compiler/testData/checkerWithErrorTypes/full/cast/AsErasedFine.jet @@ -0,0 +1,4 @@ +import java.util.List; +import java.util.Collection; + +fun ff(c: Collection) = c as List diff --git a/compiler/testData/checkerWithErrorTypes/full/cast/AsErasedStar.jet b/compiler/testData/checkerWithErrorTypes/full/cast/AsErasedStar.jet new file mode 100644 index 00000000000..b9c2faf0d9a --- /dev/null +++ b/compiler/testData/checkerWithErrorTypes/full/cast/AsErasedStar.jet @@ -0,0 +1,4 @@ +import java.util.List; + +fun ff(l: Any) = l as List<*> + diff --git a/compiler/testData/checkerWithErrorTypes/full/cast/AsErasedWarning.jet b/compiler/testData/checkerWithErrorTypes/full/cast/AsErasedWarning.jet new file mode 100644 index 00000000000..a55f6b7d58b --- /dev/null +++ b/compiler/testData/checkerWithErrorTypes/full/cast/AsErasedWarning.jet @@ -0,0 +1,3 @@ +import java.util.List; + +fun ff(a: Any) = a as List diff --git a/compiler/testData/checkerWithErrorTypes/full/cast/IsErasedAllow.jet b/compiler/testData/checkerWithErrorTypes/full/cast/IsErasedAllow.jet new file mode 100644 index 00000000000..7f32b79eba2 --- /dev/null +++ b/compiler/testData/checkerWithErrorTypes/full/cast/IsErasedAllow.jet @@ -0,0 +1,5 @@ +import java.util.Collection; +import java.util.List; + +fun ff(l: Collection) = l is List + diff --git a/compiler/testData/checkerWithErrorTypes/full/cast/IsErasedAllowParameterSubtype.jet b/compiler/testData/checkerWithErrorTypes/full/cast/IsErasedAllowParameterSubtype.jet new file mode 100644 index 00000000000..d1694229dc6 --- /dev/null +++ b/compiler/testData/checkerWithErrorTypes/full/cast/IsErasedAllowParameterSubtype.jet @@ -0,0 +1,9 @@ +import java.util.Collection; +import java.util.List; + +open class A + +class B : A + +fun ff(l: Collection) = l is List + diff --git a/compiler/testData/checkerWithErrorTypes/full/cast/IsErasedDisallowFromAny.jet b/compiler/testData/checkerWithErrorTypes/full/cast/IsErasedDisallowFromAny.jet new file mode 100644 index 00000000000..eb3b402ccc7 --- /dev/null +++ b/compiler/testData/checkerWithErrorTypes/full/cast/IsErasedDisallowFromAny.jet @@ -0,0 +1,3 @@ +import java.util.List; + +fun ff(l: Any) = l is List diff --git a/compiler/testData/checkerWithErrorTypes/full/cast/IsErasedDisallowFromParameter.jet b/compiler/testData/checkerWithErrorTypes/full/cast/IsErasedDisallowFromParameter.jet new file mode 100644 index 00000000000..a70b5932999 --- /dev/null +++ b/compiler/testData/checkerWithErrorTypes/full/cast/IsErasedDisallowFromParameter.jet @@ -0,0 +1,5 @@ +import java.util.List; +import java.util.Collection; + +fun ff(l: Collection) = l is List + diff --git a/compiler/testData/checkerWithErrorTypes/full/cast/IsErasedStar.jet b/compiler/testData/checkerWithErrorTypes/full/cast/IsErasedStar.jet new file mode 100644 index 00000000000..404d8f8f3b0 --- /dev/null +++ b/compiler/testData/checkerWithErrorTypes/full/cast/IsErasedStar.jet @@ -0,0 +1,3 @@ +import java.util.List; + +fun ff(l: Any) = l is List<*> diff --git a/compiler/testData/checkerWithErrorTypes/full/cast/IsReified.jet b/compiler/testData/checkerWithErrorTypes/full/cast/IsReified.jet new file mode 100644 index 00000000000..247196d6e84 --- /dev/null +++ b/compiler/testData/checkerWithErrorTypes/full/cast/IsReified.jet @@ -0,0 +1,3 @@ +class MyList + +fun ff(a: Any) = a is MyList diff --git a/compiler/testData/checkerWithErrorTypes/full/cast/IsReifiedDisallow.jet b/compiler/testData/checkerWithErrorTypes/full/cast/IsReifiedDisallow.jet new file mode 100644 index 00000000000..9abceb5f1e6 --- /dev/null +++ b/compiler/testData/checkerWithErrorTypes/full/cast/IsReifiedDisallow.jet @@ -0,0 +1,3 @@ +class MyList + +fun ff(l: MyList) = l is MyList diff --git a/compiler/testData/checkerWithErrorTypes/full/cast/IsTraits.jet b/compiler/testData/checkerWithErrorTypes/full/cast/IsTraits.jet new file mode 100644 index 00000000000..99656c2ab24 --- /dev/null +++ b/compiler/testData/checkerWithErrorTypes/full/cast/IsTraits.jet @@ -0,0 +1,4 @@ +trait Aaa +trait Bbb + +fun f(a: Aaa) = a is Bbb diff --git a/compiler/testData/checkerWithErrorTypes/full/cast/WhenErasedDisallowFromAny.jet b/compiler/testData/checkerWithErrorTypes/full/cast/WhenErasedDisallowFromAny.jet new file mode 100644 index 00000000000..f38a4b14fbe --- /dev/null +++ b/compiler/testData/checkerWithErrorTypes/full/cast/WhenErasedDisallowFromAny.jet @@ -0,0 +1,6 @@ +import java.util.List; + +fun ff(l: Any) = when(l) { + is List => 1 + else 2 +} diff --git a/compiler/testData/codegen/patternMatching/is.jet b/compiler/testData/codegen/patternMatching/is.jet index a5b5f33195f..9fac50c586b 100644 --- a/compiler/testData/codegen/patternMatching/is.jet +++ b/compiler/testData/codegen/patternMatching/is.jet @@ -1,6 +1,6 @@ fun typeName(a: Any?) : String { return when(a) { - is java.util.ArrayList => "array list" + is java.util.ArrayList<*> => "array list" else => "no idea" } } @@ -8,4 +8,4 @@ fun typeName(a: Any?) : String { fun box() : String { if(typeName(java.util.ArrayList ()) != "array list") return "array list failed" return "OK" -} \ No newline at end of file +}