Limit "always null" scope: only for !!, is and dot; senseless comparison rolled back; "smart constant" information for nulls #KT-10029 Fixed

This commit is contained in:
Mikhail Glukhikh
2015-11-23 14:50:20 +03:00
parent db42941586
commit 89e56093a2
41 changed files with 323 additions and 151 deletions
@@ -80,9 +80,9 @@ public class CheckerTestUtil {
private static final String SHOULD_BE_ESCAPED = "\\)\\(;";
private static final String DIAGNOSTIC_PARAMETER = "(?:(?:\\\\[" + SHOULD_BE_ESCAPED + "])|[^" + SHOULD_BE_ESCAPED + "])+";
private static final String INDIVIDUAL_DIAGNOSTIC = "(\\w+)(\\(" + DIAGNOSTIC_PARAMETER + "(;\\s*" + DIAGNOSTIC_PARAMETER + ")*\\))?";
private static final Pattern RANGE_START_OR_END_PATTERN = Pattern.compile("(<!"+
INDIVIDUAL_DIAGNOSTIC +"(,\\s*"+
INDIVIDUAL_DIAGNOSTIC +")*!>)|(<!>)");
private static final Pattern RANGE_START_OR_END_PATTERN = Pattern.compile("(<!" +
INDIVIDUAL_DIAGNOSTIC + "(,\\s*" +
INDIVIDUAL_DIAGNOSTIC + ")*!>)|(<!>)");
private static final Pattern INDIVIDUAL_DIAGNOSTIC_PATTERN = Pattern.compile(INDIVIDUAL_DIAGNOSTIC);
private static final Pattern INDIVIDUAL_PARAMETER_PATTERN = Pattern.compile(DIAGNOSTIC_PARAMETER);
@@ -98,7 +98,7 @@ public class CheckerTestUtil {
new Predicate<Diagnostic>() {
@Override
public boolean apply(Diagnostic diagnostic) {
return PsiTreeUtil.isAncestor(root, diagnostic.getPsiElement(), false);
return PsiTreeUtil.isAncestor(root, diagnostic.getPsiElement(), false);
}
}));
for (PsiErrorElement errorElement : AnalyzingUtils.getSyntaxErrorRanges(root)) {
@@ -109,6 +109,7 @@ public class CheckerTestUtil {
return diagnostics;
}
@SuppressWarnings("TestOnlyProblems")
@NotNull
private static List<Diagnostic> getDebugInfoDiagnostics(
@NotNull PsiElement root,
@@ -149,24 +150,29 @@ public class CheckerTestUtil {
}
});
// this code is used in tests and in internal action 'copy current file as diagnostic test'
//noinspection TestOnlyProblems
for (KtExpression expression : bindingContext.getSliceContents(BindingContext.SMARTCAST).keySet()) {
if (PsiTreeUtil.isAncestor(root, expression, false)) {
debugAnnotations.add(new DebugInfoDiagnostic(expression, DebugInfoDiagnosticFactory.SMARTCAST));
}
}
//noinspection TestOnlyProblems
for (KtExpression expression : bindingContext.getSliceContents(BindingContext.IMPLICIT_RECEIVER_SMARTCAST).keySet()) {
if (PsiTreeUtil.isAncestor(root, expression, false)) {
debugAnnotations.add(new DebugInfoDiagnostic(expression, DebugInfoDiagnosticFactory.IMPLICIT_RECEIVER_SMARTCAST));
}
}
for (KtExpression expression : bindingContext.getSliceContents(BindingContext.SMARTCAST_NULL).keySet()) {
if (PsiTreeUtil.isAncestor(root, expression, false)) {
debugAnnotations.add(new DebugInfoDiagnostic(expression, DebugInfoDiagnosticFactory.CONSTANT));
}
}
return debugAnnotations;
}
public interface DiagnosticDiffCallbacks {
void missingDiagnostic(TextDiagnostic diagnostic, int expectedStart, int expectedEnd);
void wrongParametersDiagnostic(TextDiagnostic expectedDiagnostic, TextDiagnostic actualDiagnostic, int start, int end);
void unexpectedDiagnostic(TextDiagnostic diagnostic, int actualStart, int actualEnd);
}
@@ -298,7 +304,8 @@ public class CheckerTestUtil {
for (Diagnostic diagnostic : actual) {
List<TextRange> textRanges = diagnostic.getTextRanges();
for (TextRange textRange : textRanges) {
callbacks.unexpectedDiagnostic(TextDiagnostic.asTextDiagnostic(diagnostic), textRange.getStartOffset(), textRange.getEndOffset());
callbacks.unexpectedDiagnostic(TextDiagnostic.asTextDiagnostic(diagnostic), textRange.getStartOffset(),
textRange.getEndOffset());
}
}
}
@@ -343,14 +350,15 @@ public class CheckerTestUtil {
matcher.reset();
return matcher.replaceAll("");
}
public static StringBuffer addDiagnosticMarkersToText(@NotNull PsiFile psiFile, @NotNull Collection<Diagnostic> diagnostics) {
return addDiagnosticMarkersToText(psiFile, diagnostics, Collections.<Diagnostic, TextDiagnostic>emptyMap(), new Function<PsiFile, String>() {
@Override
public String fun(PsiFile file) {
return file.getText();
}
});
return addDiagnosticMarkersToText(psiFile, diagnostics, Collections.<Diagnostic, TextDiagnostic>emptyMap(),
new Function<PsiFile, String>() {
@Override
public String fun(PsiFile file) {
return file.getText();
}
});
}
public static StringBuffer addDiagnosticMarkersToText(
@@ -397,21 +405,20 @@ public class CheckerTestUtil {
}
result.append(c);
}
if (currentDescriptor != null) {
assert currentDescriptor.start == text.length();
assert currentDescriptor.end == text.length();
openDiagnosticsString(result, currentDescriptor, diagnosticToExpectedDiagnostic);
opened.push(currentDescriptor);
}
while (!opened.isEmpty() && text.length() == opened.peek().end) {
closeDiagnosticString(result);
opened.pop();
}
assert opened.isEmpty() : "Stack is not empty: " + opened;
}
else {
result.append(text);
@@ -432,10 +439,12 @@ public class CheckerTestUtil {
TextDiagnostic actualTextDiagnostic = TextDiagnostic.asTextDiagnostic(diagnostic);
if (compareTextDiagnostic(expectedDiagnostic, actualTextDiagnostic)) {
result.append(expectedDiagnostic.asString());
} else {
}
else {
result.append(actualTextDiagnostic.asString());
}
} else {
}
else {
result.append(diagnostic.getFactory().getName());
}
if (iterator.hasNext()) {
@@ -517,12 +526,14 @@ public class CheckerTestUtil {
public static class DebugInfoDiagnosticFactory extends DiagnosticFactory<DebugInfoDiagnostic> {
public static final DebugInfoDiagnosticFactory SMARTCAST = new DebugInfoDiagnosticFactory("SMARTCAST");
public static final DebugInfoDiagnosticFactory IMPLICIT_RECEIVER_SMARTCAST = new DebugInfoDiagnosticFactory("IMPLICIT_RECEIVER_SMARTCAST");
public static final DebugInfoDiagnosticFactory CONSTANT = new DebugInfoDiagnosticFactory("CONSTANT");
public static final DebugInfoDiagnosticFactory ELEMENT_WITH_ERROR_TYPE = new DebugInfoDiagnosticFactory("ELEMENT_WITH_ERROR_TYPE");
public static final DebugInfoDiagnosticFactory UNRESOLVED_WITH_TARGET = new DebugInfoDiagnosticFactory("UNRESOLVED_WITH_TARGET");
public static final DebugInfoDiagnosticFactory MISSING_UNRESOLVED = new DebugInfoDiagnosticFactory("MISSING_UNRESOLVED");
public static final DebugInfoDiagnosticFactory DYNAMIC = new DebugInfoDiagnosticFactory("DYNAMIC");
private final String name;
private DebugInfoDiagnosticFactory(String name, Severity severity) {
super(severity);
this.name = name;
@@ -729,7 +740,7 @@ public class CheckerTestUtil {
public void setEnd(int end) {
this.end = end;
}
public void addDiagnostic(String diagnostic) {
diagnostics.add(TextDiagnostic.parseDiagnostic(diagnostic));
}
@@ -131,6 +131,7 @@ public interface BindingContext {
WritableSlice<KtExpression, ResolvedCall<FunctionDescriptor>> INDEXED_LVALUE_SET = Slices.createSimpleSlice();
WritableSlice<KtExpression, KotlinType> SMARTCAST = Slices.createSimpleSlice();
WritableSlice<KtExpression, Boolean> SMARTCAST_NULL = Slices.createSimpleSlice();
WritableSlice<KtExpression, KotlinType> IMPLICIT_RECEIVER_SMARTCAST = Slices.createSimpleSlice();
WritableSlice<KtWhenExpression, Boolean> EXHAUSTIVE_WHEN = Slices.createSimpleSlice();
@@ -106,19 +106,55 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
return PsiTreeUtil.isAncestor(binaryExpression.getLeft(), expression, false);
}
private static boolean isDangerousWithNull(@NotNull KtSimpleNameExpression expression, @NotNull ExpressionTypingContext context) {
PsiElement parent = PsiTreeUtil.skipParentsOfType(expression, KtParenthesizedExpression.class);
if (parent instanceof KtUnaryExpression) {
// Unary: !! only
KtUnaryExpression unaryExpression = (KtUnaryExpression) parent;
return unaryExpression.getOperationToken() == KtTokens.EXCLEXCL;
}
if (parent instanceof KtBinaryExpressionWithTypeRHS) {
// Binary: unsafe as only
KtBinaryExpressionWithTypeRHS binaryExpression = (KtBinaryExpressionWithTypeRHS) parent;
KotlinType type = context.trace.get(TYPE, binaryExpression.getRight());
return type != null && !type.isMarkedNullable() &&
binaryExpression.getOperationReference().getReferencedNameElementType() == KtTokens.AS_KEYWORD;
}
// . is also sensitive, but at receiver only and not for extension functions with nullable receiver
if (parent instanceof KtQualifiedExpression) {
KtQualifiedExpression qualifiedExpression = (KtQualifiedExpression) parent;
return qualifiedExpression.getOperationSign() == KtTokens.DOT &&
qualifiedExpression.getReceiverExpression() == KtPsiUtil.deparenthesize(expression);
}
return false;
}
private static void checkNull(
@NotNull KtSimpleNameExpression expression,
@NotNull ExpressionTypingContext context,
@Nullable KotlinType type
) {
if (type != null && !type.isError() && !isLValue(expression)) {
DataFlowValue dataFlowValue = DataFlowValueFactory.createDataFlowValue(expression, type, context);
Nullability nullability = context.dataFlowInfo.getPredictableNullability(dataFlowValue);
if (!nullability.canBeNonNull() && nullability.canBeNull()) {
if (isDangerousWithNull(expression, context)) {
context.trace.report(ALWAYS_NULL.on(expression));
}
else {
context.trace.record(SMARTCAST_NULL, expression);
}
}
}
}
@Override
public KotlinTypeInfo visitSimpleNameExpression(@NotNull KtSimpleNameExpression expression, ExpressionTypingContext context) {
// TODO : other members
// TODO : type substitutions???
CallExpressionResolver callExpressionResolver = components.callExpressionResolver;
KotlinTypeInfo typeInfo = callExpressionResolver.getSimpleNameExpressionTypeInfo(expression, NO_RECEIVER, null, context);
if (typeInfo.getType() != null && !typeInfo.getType().isError() && !isLValue(expression)) {
DataFlowValue dataFlowValue = DataFlowValueFactory.createDataFlowValue(expression, typeInfo.getType(), context);
Nullability nullability = context.dataFlowInfo.getPredictableNullability(dataFlowValue);
if (!nullability.canBeNonNull() && nullability.canBeNull()) {
context.trace.report(ALWAYS_NULL.on(expression));
}
}
checkNull(expression, context, typeInfo.getType());
return components.dataFlowAnalyzer.checkType(typeInfo, expression, context); // TODO : Extensions to this
}
@@ -144,7 +180,8 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
if (!(compileTimeConstant instanceof IntegerValueTypeConstant)) {
CompileTimeConstantChecker constantChecker = new CompileTimeConstantChecker(context.trace, components.builtIns, false);
ConstantValue constantValue = compileTimeConstant != null ? ((TypedCompileTimeConstant) compileTimeConstant).getConstantValue() : null;
ConstantValue constantValue =
compileTimeConstant != null ? ((TypedCompileTimeConstant) compileTimeConstant).getConstantValue() : null;
boolean hasError = constantChecker.checkConstantExpressionType(constantValue, expression, context.expectedType);
if (hasError) {
IElementType elementType = expression.getNode().getElementType();
@@ -152,7 +189,9 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
}
}
assert compileTimeConstant != null : "CompileTimeConstant should be evaluated for constant expression or an error should be recorded " + expression.getText();
assert compileTimeConstant != null :
"CompileTimeConstant should be evaluated for constant expression or an error should be recorded " +
expression.getText();
return components.dataFlowAnalyzer.createCompileTimeConstantTypeInfo(compileTimeConstant, expression, context);
}
@@ -180,7 +219,10 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
}
@Override
public KotlinTypeInfo visitBinaryWithTypeRHSExpression(@NotNull KtBinaryExpressionWithTypeRHS expression, ExpressionTypingContext context) {
public KotlinTypeInfo visitBinaryWithTypeRHSExpression(
@NotNull KtBinaryExpressionWithTypeRHS expression,
ExpressionTypingContext context
) {
ExpressionTypingContext contextWithNoExpectedType =
context.replaceExpectedType(NO_EXPECTED_TYPE).replaceContextDependency(INDEPENDENT);
KtExpression left = expression.getLeft();
@@ -425,8 +467,8 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
else {
// supertypes may be empty when all the supertypes are error types (are not resolved, for example)
KotlinType type = supertypes.isEmpty()
? components.builtIns.getAnyType()
: supertypes.iterator().next();
? components.builtIns.getAnyType()
: supertypes.iterator().next();
result = substitutor.substitute(type, Variance.INVARIANT);
}
}
@@ -437,7 +479,8 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
}
}
context.trace.recordType(expression.getInstanceReference(), result);
context.trace.record(BindingContext.REFERENCE_TARGET, expression.getInstanceReference(), result.getConstructor().getDeclarationDescriptor());
context.trace.record(BindingContext.REFERENCE_TARGET, expression.getInstanceReference(),
result.getConstructor().getDeclarationDescriptor());
}
BindingContextUtilsKt.recordScope(context.trace, context.scope, superTypeQualifier);
@@ -506,7 +549,8 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
trace.record(RESOLVED_CALL, call, resolvedCall);
trace.record(CALL, expression, call);
BasicCallResolutionContext resolutionContext = BasicCallResolutionContext.create(context, call, CheckArgumentTypesMode.CHECK_CALLABLE_TYPE);
BasicCallResolutionContext resolutionContext =
BasicCallResolutionContext.create(context, call, CheckArgumentTypesMode.CHECK_CALLABLE_TYPE);
resolutionContext.performContextDependentCallChecks(resolvedCall);
for (CallChecker checker : components.callCheckers) {
checker.check(resolvedCall, resolutionContext);
@@ -651,38 +695,47 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
}
@Override
public KotlinTypeInfo visitObjectLiteralExpression(@NotNull final KtObjectLiteralExpression expression, final ExpressionTypingContext context) {
public KotlinTypeInfo visitObjectLiteralExpression(
@NotNull final KtObjectLiteralExpression expression,
final ExpressionTypingContext context
) {
final KotlinType[] result = new KotlinType[1];
TemporaryBindingTrace temporaryTrace = TemporaryBindingTrace.create(context.trace,
"trace to resolve object literal expression", expression);
ObservableBindingTrace.RecordHandler<PsiElement, ClassDescriptor> handler = new ObservableBindingTrace.RecordHandler<PsiElement, ClassDescriptor>() {
ObservableBindingTrace.RecordHandler<PsiElement, ClassDescriptor> handler =
new ObservableBindingTrace.RecordHandler<PsiElement, ClassDescriptor>() {
@Override
public void handleRecord(WritableSlice<PsiElement, ClassDescriptor> slice, PsiElement declaration, final ClassDescriptor descriptor) {
if (slice == CLASS && declaration == expression.getObjectDeclaration()) {
KotlinType defaultType = DeferredType.createRecursionIntolerant(components.globalContext.getStorageManager(),
context.trace,
new Function0<KotlinType>() {
@Override
public KotlinType invoke() {
return descriptor.getDefaultType();
}
});
result[0] = defaultType;
}
}
};
@Override
public void handleRecord(
WritableSlice<PsiElement, ClassDescriptor> slice,
PsiElement declaration,
final ClassDescriptor descriptor
) {
if (slice == CLASS && declaration == expression.getObjectDeclaration()) {
KotlinType defaultType = DeferredType.createRecursionIntolerant(components.globalContext.getStorageManager(),
context.trace,
new Function0<KotlinType>() {
@Override
public KotlinType invoke() {
return descriptor.getDefaultType();
}
});
result[0] = defaultType;
}
}
};
ObservableBindingTrace traceAdapter = new ObservableBindingTrace(temporaryTrace);
traceAdapter.addHandler(CLASS, handler);
components.localClassifierAnalyzer.processClassOrObject(null, // don't need to add classifier of object literal to any scope
context.replaceBindingTrace(traceAdapter).replaceContextDependency(INDEPENDENT),
context.replaceBindingTrace(traceAdapter)
.replaceContextDependency(INDEPENDENT),
context.scope.getOwnerDescriptor(),
expression.getObjectDeclaration());
temporaryTrace.commit();
DataFlowInfo resultFlowInfo = context.dataFlowInfo;
for (KtDelegationSpecifier specifier: expression.getObjectDeclaration().getDelegationSpecifiers()) {
for (KtDelegationSpecifier specifier : expression.getObjectDeclaration().getDelegationSpecifiers()) {
if (specifier instanceof KtDelegatorToSuperCall) {
KtDelegatorToSuperCall delegator = (KtDelegatorToSuperCall)specifier;
KtDelegatorToSuperCall delegator = (KtDelegatorToSuperCall) specifier;
KotlinTypeInfo delegatorTypeInfo = context.trace.get(EXPRESSION_TYPE_INFO, delegator.getCalleeExpression());
if (delegatorTypeInfo != null) {
resultFlowInfo = resultFlowInfo.and(delegatorTypeInfo.getDataFlowInfo());
@@ -749,8 +802,9 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
@Override
public KotlinTypeInfo visitUnaryExpression(@NotNull KtUnaryExpression expression, ExpressionTypingContext contextWithExpectedType) {
ExpressionTypingContext context = isUnaryExpressionDependentOnExpectedType(expression)
? contextWithExpectedType
: contextWithExpectedType.replaceContextDependency(INDEPENDENT).replaceExpectedType(NO_EXPECTED_TYPE);
? contextWithExpectedType
: contextWithExpectedType.replaceContextDependency(INDEPENDENT)
.replaceExpectedType(NO_EXPECTED_TYPE);
KtExpression baseExpression = expression.getBaseExpression();
if (baseExpression == null) return TypeInfoFactoryKt.noTypeInfo(context);
@@ -779,11 +833,12 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
}
// a[i]++/-- takes special treatment because it is actually let j = i, arr = a in arr.set(j, a.get(j).inc())
if ((operationType == KtTokens.PLUSPLUS || operationType == KtTokens.MINUSMINUS) && baseExpression instanceof KtArrayAccessExpression) {
KtExpression
stubExpression = ExpressionTypingUtils.createFakeExpressionOfType(baseExpression.getProject(), context.trace, "e", type);
TemporaryBindingTrace temporaryBindingTrace = TemporaryBindingTrace
.create(context.trace, "trace to resolve array access set method for unary expression", expression);
if ((operationType == KtTokens.PLUSPLUS || operationType == KtTokens.MINUSMINUS) &&
baseExpression instanceof KtArrayAccessExpression) {
KtExpression stubExpression = ExpressionTypingUtils.createFakeExpressionOfType(
baseExpression.getProject(), context.trace, "e", type);
TemporaryBindingTrace temporaryBindingTrace = TemporaryBindingTrace.create(
context.trace, "trace to resolve array access set method for unary expression", expression);
ExpressionTypingContext newContext = context.replaceBindingTrace(temporaryBindingTrace);
resolveArrayAccessSetMethod((KtArrayAccessExpression) baseExpression, stubExpression, newContext, context.trace);
}
@@ -812,11 +867,13 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
}
else {
context.trace.record(BindingContext.VARIABLE_REASSIGNMENT, expression);
KtExpression stubExpression = ExpressionTypingUtils.createFakeExpressionOfType(baseExpression.getProject(), context.trace, "e", type);
KtExpression stubExpression = ExpressionTypingUtils.createFakeExpressionOfType(
baseExpression.getProject(), context.trace, "e", type);
checkLValue(context.trace, context, baseExpression, stubExpression);
}
// x++ type is x type, but ++x type is x.inc() type
DataFlowValue receiverValue = DataFlowValueFactory.createDataFlowValue((ReceiverValue) call.getExplicitReceiver(), contextWithExpectedType);
DataFlowValue receiverValue = DataFlowValueFactory.createDataFlowValue(
(ReceiverValue) call.getExplicitReceiver(), contextWithExpectedType);
if (expression instanceof KtPrefixExpression) {
result = returnType;
}
@@ -840,8 +897,8 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
}
return components.dataFlowAnalyzer.checkType(typeInfo.replaceType(result),
expression,
contextWithExpectedType.replaceDataFlowInfo(typeInfo.getDataFlowInfo()));
expression,
contextWithExpectedType.replaceDataFlowInfo(typeInfo.getDataFlowInfo()));
}
private KotlinTypeInfo visitExclExclExpression(@NotNull KtUnaryExpression expression, @NotNull ExpressionTypingContext context) {
@@ -852,7 +909,8 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
// TODO: something must be done for not to lose safe call chain information here
// See also CallExpressionResolver.getSimpleNameExpressionTypeInfo, .getQualifiedExpressionTypeInfo
Call call = createCallForSpecialConstruction(expression, expression.getOperationReference(), Collections.singletonList(baseExpression));
Call call = createCallForSpecialConstruction(
expression, expression.getOperationReference(), Collections.singletonList(baseExpression));
components.controlStructureTypingUtils.resolveSpecialConstructionAsCall(
call, ResolveConstruct.EXCL_EXCL, Collections.singletonList("baseExpr"), Collections.singletonList(true), context, null);
KotlinTypeInfo baseTypeInfo = BindingContextUtils.getRecordedTypeInfo(baseExpression, context.trace.getBindingContext());
@@ -880,7 +938,8 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
}
// The call to checkType() is only needed here to execute additionalTypeCheckers, hence the NO_EXPECTED_TYPE
return components.dataFlowAnalyzer.checkType(baseTypeInfo.replaceType(resultingType), expression, context.replaceExpectedType(NO_EXPECTED_TYPE));
return components.dataFlowAnalyzer.checkType(
baseTypeInfo.replaceType(resultingType), expression, context.replaceExpectedType(NO_EXPECTED_TYPE));
}
@Override
@@ -966,7 +1025,8 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
if (propertyDescriptor.isSetterProjectedOut()) {
trace.report(SETTER_PROJECTED_OUT.on(reportOn, propertyDescriptor));
result = false;
} else {
}
else {
if (setter != null) {
components.symbolUsageValidator.validateCall(null, setter, trace, reportOn);
}
@@ -987,8 +1047,9 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
@Override
public KotlinTypeInfo visitBinaryExpression(@NotNull KtBinaryExpression expression, ExpressionTypingContext contextWithExpectedType) {
ExpressionTypingContext context = isBinaryExpressionDependentOnExpectedType(expression)
? contextWithExpectedType
: contextWithExpectedType.replaceContextDependency(INDEPENDENT).replaceExpectedType(NO_EXPECTED_TYPE);
? contextWithExpectedType
: contextWithExpectedType.replaceContextDependency(INDEPENDENT)
.replaceExpectedType(NO_EXPECTED_TYPE);
KtSimpleNameExpression operationSign = expression.getOperationReference();
KtExpression left = expression.getLeft();
@@ -1110,8 +1171,7 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
if (resolutionResults.isSuccess()) {
FunctionDescriptor equals = resolutionResults.getResultingCall().getResultingDescriptor();
if (ensureBooleanResult(operationSign, OperatorNameConventions.EQUALS, equals.getReturnType(),
context)) {
if (ensureBooleanResult(operationSign, OperatorNameConventions.EQUALS, equals.getReturnType(), context)) {
ensureNonemptyIntersectionOfOperandTypes(expression, context);
}
}
@@ -1187,7 +1247,8 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
Call call = createCallForSpecialConstruction(expression, expression.getOperationReference(), Lists.newArrayList(left, right));
ResolvedCall<FunctionDescriptor> resolvedCall = components.controlStructureTypingUtils.resolveSpecialConstructionAsCall(
call, ResolveConstruct.ELVIS, Lists.newArrayList("left", "right"), Lists.newArrayList(true, false), contextWithExpectedType, null);
call, ResolveConstruct.ELVIS, Lists.newArrayList("left", "right"),
Lists.newArrayList(true, false), contextWithExpectedType, null);
KotlinTypeInfo leftTypeInfo = BindingContextUtils.getRecordedTypeInfo(left, context.trace.getBindingContext());
if (ArgumentTypeResolver.isFunctionLiteralArgument(left, context)) {
context.trace.report(USELESS_ELVIS_ON_FUNCTION_LITERAL.on(expression.getOperationReference()));
@@ -1284,7 +1345,12 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
return ensureBooleanResultWithCustomSubject(operationSign, resultType, "'" + name + "'", context);
}
private boolean ensureBooleanResultWithCustomSubject(KtExpression operationSign, KotlinType resultType, String subjectName, ExpressionTypingContext context) {
private boolean ensureBooleanResultWithCustomSubject(
KtExpression operationSign,
KotlinType resultType,
String subjectName,
ExpressionTypingContext context
) {
if (resultType != null) {
// TODO : Relax?
if (!components.builtIns.isBooleanOrSubtype(resultType)) {
@@ -1361,7 +1427,8 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
if (left != null) {
//left here is a receiver, so it doesn't depend on expected type
typeInfo = facade.getTypeInfo(left, context.replaceContextDependency(INDEPENDENT).replaceExpectedType(NO_EXPECTED_TYPE));
} else {
}
else {
typeInfo = TypeInfoFactoryKt.noTypeInfo(context);
}
ExpressionTypingContext contextWithDataFlow = context.replaceDataFlowInfo(typeInfo.getDataFlowInfo());
@@ -1400,8 +1467,12 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
}
@Override
public KotlinTypeInfo visitStringTemplateExpression(@NotNull KtStringTemplateExpression expression, ExpressionTypingContext contextWithExpectedType) {
final ExpressionTypingContext context = contextWithExpectedType.replaceExpectedType(NO_EXPECTED_TYPE).replaceContextDependency(INDEPENDENT);
public KotlinTypeInfo visitStringTemplateExpression(
@NotNull KtStringTemplateExpression expression,
ExpressionTypingContext contextWithExpectedType
) {
final ExpressionTypingContext context = contextWithExpectedType.replaceExpectedType(NO_EXPECTED_TYPE)
.replaceContextDependency(INDEPENDENT);
checkStringPrefixAndSuffix(expression, context);
@@ -1418,7 +1489,8 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
@Override
public void visitEscapeStringTemplateEntry(@NotNull KtEscapeStringTemplateEntry entry) {
CompileTimeConstantChecker.CharacterWithDiagnostic value = CompileTimeConstantChecker.escapedStringToCharacter(entry.getText(), entry);
CompileTimeConstantChecker.CharacterWithDiagnostic value =
CompileTimeConstantChecker.escapedStringToCharacter(entry.getText(), entry);
Diagnostic diagnostic = value.getDiagnostic();
if (diagnostic != null) {
context.trace.report(diagnostic);
@@ -1431,8 +1503,8 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
}
components.constantExpressionEvaluator.evaluateExpression(expression, context.trace, contextWithExpectedType.expectedType);
return components.dataFlowAnalyzer.checkType(visitor.typeInfo.replaceType(components.builtIns.getStringType()),
expression,
contextWithExpectedType);
expression,
contextWithExpectedType);
}
private static void checkStringPrefixAndSuffix(@NotNull PsiElement expression, ExpressionTypingContext context) {
@@ -1478,21 +1550,31 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
}
@NotNull
/*package*/ KotlinTypeInfo resolveArrayAccessSetMethod(@NotNull KtArrayAccessExpression arrayAccessExpression, @NotNull KtExpression rightHandSide, @NotNull ExpressionTypingContext context, @NotNull BindingTrace traceForResolveResult) {
/*package*/ KotlinTypeInfo resolveArrayAccessSetMethod(
@NotNull KtArrayAccessExpression arrayAccessExpression,
@NotNull KtExpression rightHandSide,
@NotNull ExpressionTypingContext context,
@NotNull BindingTrace traceForResolveResult
) {
return resolveArrayAccessSpecialMethod(arrayAccessExpression, rightHandSide, context, traceForResolveResult, false);
}
@NotNull
/*package*/ KotlinTypeInfo resolveArrayAccessGetMethod(@NotNull KtArrayAccessExpression arrayAccessExpression, @NotNull ExpressionTypingContext context) {
/*package*/ KotlinTypeInfo resolveArrayAccessGetMethod(
@NotNull KtArrayAccessExpression arrayAccessExpression,
@NotNull ExpressionTypingContext context
) {
return resolveArrayAccessSpecialMethod(arrayAccessExpression, null, context, context.trace, true);
}
@NotNull
private KotlinTypeInfo resolveArrayAccessSpecialMethod(@NotNull KtArrayAccessExpression arrayAccessExpression,
@Nullable KtExpression rightHandSide, //only for 'set' method
@NotNull ExpressionTypingContext oldContext,
@NotNull BindingTrace traceForResolveResult,
boolean isGet) {
private KotlinTypeInfo resolveArrayAccessSpecialMethod(
@NotNull KtArrayAccessExpression arrayAccessExpression,
@Nullable KtExpression rightHandSide, //only for 'set' method
@NotNull ExpressionTypingContext oldContext,
@NotNull BindingTrace traceForResolveResult,
boolean isGet
) {
KtExpression arrayExpression = arrayAccessExpression.getArrayExpression();
if (arrayExpression == null) return TypeInfoFactoryKt.noTypeInfo(oldContext);
@@ -50,7 +50,7 @@ object SenselessComparisonChecker {
val nullability = getNullability(value)
val expressionIsAlways =
if (nullability == Nullability.NULL) return
if (nullability == Nullability.NULL) equality
else if (nullability == Nullability.NOT_NULL) !equality
else if (nullability == Nullability.IMPOSSIBLE) false
else return
@@ -5,24 +5,24 @@ class A() {
fun f(): Unit {
var x: Int? = <!VARIABLE_WITH_REDUNDANT_INITIALIZER!>1<!>
x = null
<!ALWAYS_NULL!>x<!> <!UNSAFE_INFIX_CALL!>+<!> 1
<!ALWAYS_NULL!>x<!> <!UNSAFE_INFIX_CALL, INFIX_MODIFIER_REQUIRED!>plus<!> 1
<!ALWAYS_NULL!>x<!> <!UNSAFE_INFIX_CALL!><<!> 1
<!DEBUG_INFO_CONSTANT!>x<!> <!UNSAFE_INFIX_CALL!>+<!> 1
<!DEBUG_INFO_CONSTANT!>x<!> <!UNSAFE_INFIX_CALL, INFIX_MODIFIER_REQUIRED!>plus<!> 1
<!DEBUG_INFO_CONSTANT!>x<!> <!UNSAFE_INFIX_CALL!><<!> 1
x <!UNSAFE_INFIX_CALL!>+=<!> 1
<!ALWAYS_NULL!>x<!> == 1
<!ALWAYS_NULL!>x<!> != 1
<!DEBUG_INFO_CONSTANT!>x<!> == 1
<!DEBUG_INFO_CONSTANT!>x<!> != 1
<!EQUALITY_NOT_APPLICABLE!>A() == 1<!>
<!EQUALITY_NOT_APPLICABLE!><!ALWAYS_NULL!>x<!> === "1"<!>
<!EQUALITY_NOT_APPLICABLE!><!ALWAYS_NULL!>x<!> !== "1"<!>
<!EQUALITY_NOT_APPLICABLE!><!DEBUG_INFO_CONSTANT!>x<!> === "1"<!>
<!EQUALITY_NOT_APPLICABLE!><!DEBUG_INFO_CONSTANT!>x<!> !== "1"<!>
<!ALWAYS_NULL!>x<!> === 1
<!ALWAYS_NULL!>x<!> !== 1
<!DEBUG_INFO_CONSTANT!>x<!> === 1
<!DEBUG_INFO_CONSTANT!>x<!> !== 1
<!ALWAYS_NULL!>x<!><!UNSAFE_INFIX_CALL!>..<!>2
<!ALWAYS_NULL, TYPE_MISMATCH!>x<!> in 1..2
<!DEBUG_INFO_CONSTANT!>x<!><!UNSAFE_INFIX_CALL!>..<!>2
<!TYPE_MISMATCH, DEBUG_INFO_CONSTANT!>x<!> in 1..2
val y : Boolean? = true
<!UNUSED_EXPRESSION!>false || <!TYPE_MISMATCH!>y<!><!>
+8 -8
View File
@@ -4,7 +4,7 @@ fun test() {
<!DEBUG_INFO_SMARTCAST!>a<!>.plus(1)
}
else {
<!ALWAYS_NULL!>a<!>?.plus(1)
<!DEBUG_INFO_CONSTANT!>a<!>?.plus(1)
}
val out : java.io.PrintStream? = null
@@ -28,7 +28,7 @@ fun test() {
}
if (out == null) {
<!ALWAYS_NULL!>out<!>?.println()
<!DEBUG_INFO_CONSTANT!>out<!>?.println()
} else {
<!DEBUG_INFO_SMARTCAST!>out<!>.println()
}
@@ -88,7 +88,7 @@ fun test() {
}
if (out == null) {
<!ALWAYS_NULL!>out<!>?.println()
<!DEBUG_INFO_CONSTANT!>out<!>?.println()
} else {
<!DEBUG_INFO_SMARTCAST!>out<!>.println()
}
@@ -140,12 +140,12 @@ fun test() {
while (out != null) {
<!DEBUG_INFO_SMARTCAST!>out<!>.println();
}
<!ALWAYS_NULL!>out<!>?.println();
<!DEBUG_INFO_CONSTANT!>out<!>?.println();
val out2 : java.io.PrintStream? = null
while (out2 == null) {
<!ALWAYS_NULL!>out2<!>?.println();
<!DEBUG_INFO_CONSTANT!>out2<!>?.println();
<!ALWAYS_NULL!>out2<!><!UNSAFE_CALL!>.<!>println();
}
<!DEBUG_INFO_SMARTCAST!>out2<!>.println()
@@ -235,7 +235,7 @@ fun f7(s : String?, t : String?) {
}
s?.get(0)
if (!(s != null)) {
<!ALWAYS_NULL!>s<!>?.get(0)
<!DEBUG_INFO_CONSTANT!>s<!>?.get(0)
}
else {
<!DEBUG_INFO_SMARTCAST!>s<!>.get(0)
@@ -245,7 +245,7 @@ fun f7(s : String?, t : String?) {
<!DEBUG_INFO_SMARTCAST!>s<!>.get(0)
}
else {
<!ALWAYS_NULL!>s<!>?.get(0)
<!DEBUG_INFO_CONSTANT!>s<!>?.get(0)
}
s?.get(0)
t?.get(0)
@@ -264,7 +264,7 @@ fun f7(s : String?, t : String?) {
t?.get(0)
}
else {
<!ALWAYS_NULL!>s<!>?.get(0)
<!DEBUG_INFO_CONSTANT!>s<!>?.get(0)
t?.get(0)
}
}
@@ -7,7 +7,7 @@ fun foo() {
val x: Int? = null
val a = Array<Int>(3, {0})
if (x != null) bar(a[<!DEBUG_INFO_SMARTCAST!>x<!>]) else bar(a[<!ALWAYS_NULL, TYPE_MISMATCH!>x<!>])
if (x != null) bar(a[<!DEBUG_INFO_SMARTCAST!>x<!>]) else bar(a[<!TYPE_MISMATCH, DEBUG_INFO_CONSTANT!>x<!>])
bar(a[if (x == null) 0 else <!DEBUG_INFO_SMARTCAST!>x<!>])
bar(a[<!TYPE_MISMATCH!>x<!>])
@@ -5,6 +5,6 @@ fun foo() {
val x: Int? = null
bar(1 + (if (x == null) 0 else x))
bar(if (x == null) <!ALWAYS_NULL, TYPE_MISMATCH!>x<!> else x)
bar(if (x == null) <!TYPE_MISMATCH, DEBUG_INFO_CONSTANT!>x<!> else x)
if (x != null) bar(x + x/(x-x*x))
}
@@ -16,15 +16,15 @@ fun foo() {
}
if (<!SENSELESS_COMPARISON!>x == null<!>) bar(x) else bar(x)
bar(bar(x))
} else if (<!ALWAYS_NULL!>x<!> == null) {
bar(<!ALWAYS_NULL, TYPE_MISMATCH!>x<!>)
if (<!ALWAYS_NULL!>x<!> != null) {
} else if (<!SENSELESS_COMPARISON!><!DEBUG_INFO_CONSTANT!>x<!> == null<!>) {
bar(<!TYPE_MISMATCH, DEBUG_INFO_CONSTANT!>x<!>)
if (<!SENSELESS_COMPARISON!><!DEBUG_INFO_CONSTANT!>x<!> != null<!>) {
bar(x)
if (<!SENSELESS_COMPARISON!>x == null<!>) bar(x)
if (<!SENSELESS_COMPARISON!>x == null<!>) bar(x) else bar(x)
bar(bar(x) + bar(x))
} else if (<!ALWAYS_NULL!>x<!> == null) {
bar(<!ALWAYS_NULL, TYPE_MISMATCH!>x<!>)
} else if (<!SENSELESS_COMPARISON!><!DEBUG_INFO_CONSTANT!>x<!> == null<!>) {
bar(<!TYPE_MISMATCH, DEBUG_INFO_CONSTANT!>x<!>)
}
}
@@ -12,5 +12,5 @@ fun foo() {
do {
bar(<!TYPE_MISMATCH!>y<!>)
} while (y != null)
bar(<!ALWAYS_NULL, TYPE_MISMATCH!>y<!>)
bar(<!TYPE_MISMATCH, DEBUG_INFO_CONSTANT!>y<!>)
}
@@ -6,7 +6,7 @@ fun foo() {
bar(if (x == null) 0 else <!DEBUG_INFO_SMARTCAST!>x<!>)
if (x == null) {
bar(<!ALWAYS_NULL, TYPE_MISMATCH!>x<!>)
bar(<!TYPE_MISMATCH, DEBUG_INFO_CONSTANT!>x<!>)
return
} else {
bar(<!DEBUG_INFO_SMARTCAST!>x<!>)
@@ -5,6 +5,6 @@ fun foo() {
val <!UNUSED_VARIABLE!>a<!> = object {
fun baz() = bar(if (x == null) 0 else <!DEBUG_INFO_SMARTCAST!>x<!>)
fun quux(): Int = if (x == null) <!ALWAYS_NULL, TYPE_MISMATCH!>x<!> else <!DEBUG_INFO_SMARTCAST!>x<!>
fun quux(): Int = if (x == null) <!TYPE_MISMATCH, DEBUG_INFO_CONSTANT!>x<!> else <!DEBUG_INFO_SMARTCAST!>x<!>
}
}
@@ -8,10 +8,10 @@ fun foo(): Int {
if (x != null) return x
val y: Int? = null
if (y == null) return if (<!ALWAYS_NULL!>y<!> != null) y else <!ALWAYS_NULL, TYPE_MISMATCH!>y<!>
if (y == null) return if (<!SENSELESS_COMPARISON!><!DEBUG_INFO_CONSTANT!>y<!> != null<!>) y else <!TYPE_MISMATCH, DEBUG_INFO_CONSTANT!>y<!>
val z: Int? = null
if (z != null) return if (<!SENSELESS_COMPARISON!>z == null<!>) z else z
return <!ALWAYS_NULL, TYPE_MISMATCH!>z<!>
return <!TYPE_MISMATCH, DEBUG_INFO_CONSTANT!>z<!>
}
@@ -16,6 +16,6 @@ class Derived : Base() {
val y: Int? = null
if (y != null) super.bar(this.baz(<!DEBUG_INFO_SMARTCAST!>y<!>))
else this.baz(super.bar(<!ALWAYS_NULL, TYPE_MISMATCH!>y<!>))
else this.baz(super.bar(<!TYPE_MISMATCH, DEBUG_INFO_CONSTANT!>y<!>))
}
}
@@ -3,7 +3,7 @@ fun bar(x: Int): RuntimeException = RuntimeException(x.toString())
fun foo() {
val x: Int? = null
if (x == null) throw bar(<!ALWAYS_NULL, TYPE_MISMATCH!>x<!>)
if (x == null) throw bar(<!TYPE_MISMATCH, DEBUG_INFO_CONSTANT!>x<!>)
throw bar(<!DEBUG_INFO_SMARTCAST!>x<!>)
<!UNREACHABLE_CODE!>throw bar(<!DEBUG_INFO_SMARTCAST!>x<!>)<!>
}
@@ -3,7 +3,7 @@ fun bar(x: Int): Int = x + 1
fun foo() {
val x: Int? = null
while (x == null) {
bar(<!ALWAYS_NULL, TYPE_MISMATCH!>x<!>)
bar(<!TYPE_MISMATCH, DEBUG_INFO_CONSTANT!>x<!>)
}
bar(<!DEBUG_INFO_SMARTCAST!>x<!>)
@@ -11,11 +11,11 @@ fun foo() {
while (y != null) {
bar(<!DEBUG_INFO_SMARTCAST!>y<!>)
}
bar(<!ALWAYS_NULL, TYPE_MISMATCH!>y<!>)
bar(<!TYPE_MISMATCH, DEBUG_INFO_CONSTANT!>y<!>)
val z: Int? = null
while (z == null) {
bar(<!ALWAYS_NULL, TYPE_MISMATCH!>z<!>)
bar(<!TYPE_MISMATCH, DEBUG_INFO_CONSTANT!>z<!>)
break
}
bar(<!TYPE_MISMATCH!>z<!>)
@@ -6,7 +6,7 @@ interface A<T>
fun <T> infer(<!UNUSED_PARAMETER!>a<!>: A<T>) : T {<!NO_RETURN_IN_FUNCTION_WITH_BLOCK_BODY!>}<!>
fun test(nothing: Nothing?) {
val <!UNUSED_VARIABLE!>i<!> = <!TYPE_INFERENCE_INCORPORATION_ERROR!>infer<!>(<!ALWAYS_NULL, TYPE_MISMATCH!>nothing<!>)
val <!UNUSED_VARIABLE!>i<!> = <!TYPE_INFERENCE_INCORPORATION_ERROR!>infer<!>(<!TYPE_MISMATCH, DEBUG_INFO_CONSTANT!>nothing<!>)
}
fun sum(<!UNUSED_PARAMETER!>a<!> : IntArray) : Int {
@@ -1,8 +1,8 @@
fun test() {
val out : Int? = null
val x : Nothing? = null
if (out != <!ALWAYS_NULL!>x<!>)
if (out != <!DEBUG_INFO_CONSTANT!>x<!>)
<!DEBUG_INFO_SMARTCAST!>out<!>.plus(1)
if (out == <!ALWAYS_NULL!>x<!>) return
if (out == <!DEBUG_INFO_CONSTANT!>x<!>) return
<!DEBUG_INFO_SMARTCAST!>out<!>.plus(1)
}
@@ -25,7 +25,7 @@ fun f3(s: Int?): Int {
fun f4(s: Int?): Int {
return when {
s == 4 -> <!DEBUG_INFO_SMARTCAST!>s<!>
s == null -> <!ALWAYS_NULL, TYPE_MISMATCH!>s<!>
s == null -> <!TYPE_MISMATCH, DEBUG_INFO_CONSTANT!>s<!>
else -> <!DEBUG_INFO_SMARTCAST!>s<!>
}
}
@@ -21,7 +21,7 @@ fun main(args : Array<String>) {
foo(x<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>)
foo(<!DEBUG_INFO_SMARTCAST!>x<!>)
} else {
foo(<!ALWAYS_NULL, TYPE_MISMATCH!>x<!>)
foo(<!TYPE_MISMATCH, DEBUG_INFO_CONSTANT!>x<!>)
<!UNREACHABLE_CODE!>foo(<!><!ALWAYS_NULL!>x<!>!!<!UNREACHABLE_CODE!>)<!>
<!UNREACHABLE_CODE!>foo(<!DEBUG_INFO_SMARTCAST!>x<!>)<!>
}
@@ -2,7 +2,7 @@ fun <T> test(t: T): String? {
if (t != null) {
return t<!UNNECESSARY_SAFE_CALL!>?.<!>toString()
}
return <!ALWAYS_NULL!>t<!>?.toString()
return <!DEBUG_INFO_CONSTANT!>t<!>?.toString()
}
fun <T> T.testThis(): String? {
@@ -33,10 +33,10 @@ fun test() {
if (platformN != null) {}
if (platformN == null) {}
if (a == null && platformN == <!ALWAYS_NULL!>a<!>) {}
if (a == null && platformN == <!DEBUG_INFO_CONSTANT!>a<!>) {}
if (platformJ != null) {}
if (platformJ == null) {}
if (a == null && platformJ == <!ALWAYS_NULL!>a<!>) {}
if (a == null && platformJ == <!DEBUG_INFO_CONSTANT!>a<!>) {}
}
@@ -31,10 +31,10 @@ fun test() {
if (platformN !== null) {}
if (platformN === null) {}
if (a === null && platformN === <!ALWAYS_NULL!>a<!>) {}
if (a === null && platformN === <!DEBUG_INFO_CONSTANT!>a<!>) {}
if (platformJ !== null) {}
if (platformJ === null) {}
if (a === null && platformJ === <!ALWAYS_NULL!>a<!>) {}
if (a === null && platformJ === <!DEBUG_INFO_CONSTANT!>a<!>) {}
}
@@ -1,11 +1,23 @@
fun foo(): String {
var s: String?
s = null
<!ALWAYS_NULL!>s<!>?.length
if (<!ALWAYS_NULL!>s<!> == null) s = "z"
<!DEBUG_INFO_CONSTANT!>s<!>?.length
<!ALWAYS_NULL!>s<!><!UNSAFE_CALL!>.<!>length
if (<!SENSELESS_COMPARISON!><!DEBUG_INFO_CONSTANT!>s<!> == null<!>) return <!ALWAYS_NULL!>s<!>!!
var t: String? = "y"
if (t == null) t = "x"
var x: Int? = null
if (x == null) <!TYPE_MISMATCH!>x += null<!>
return <!DEBUG_INFO_SMARTCAST!>t<!> + s
}
fun String?.gav() {}
fun bar(s: String?) {
if (s != null) return
// Ideally we should have DEBUG_INFO_CONSTANT instead
<!ALWAYS_NULL!>s<!>.gav()
<!DEBUG_INFO_CONSTANT!>s<!> as? String
<!DEBUG_INFO_CONSTANT!>s<!> <!USELESS_CAST!>as String?<!>
<!ALWAYS_NULL!>s<!> as String
}
@@ -1,3 +1,5 @@
package
public fun bar(/*0*/ s: kotlin.String?): kotlin.Unit
public fun foo(): kotlin.String
public fun kotlin.String?.gav(): kotlin.Unit
@@ -0,0 +1,16 @@
// FILE: My.java
public class My {
static public My create() { return new My(); }
public void foo() {}
}
// FILE: Test.kt
fun test() {
val my = My.create()
if (my == null) {
<!ALWAYS_NULL!>my<!>.foo()
}
}
@@ -0,0 +1,14 @@
package
public fun test(): kotlin.Unit
public open class My {
public constructor My()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open fun foo(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
// Static members
public open fun create(): My!
}
@@ -8,7 +8,7 @@ fun foo(x : String?, y : String?) {
x<!UNSAFE_CALL!>.<!>length
y<!UNSAFE_CALL!>.<!>length
}
if (y != null || x == <!ALWAYS_NULL!>y<!>) {
if (y != null || x == <!DEBUG_INFO_CONSTANT!>y<!>) {
x<!UNSAFE_CALL!>.<!>length
y<!UNSAFE_CALL!>.<!>length
}
@@ -17,7 +17,7 @@ fun foo(x : String?, y : String?) {
<!DEBUG_INFO_SMARTCAST!>x<!>.length
<!ALWAYS_NULL!>y<!><!UNSAFE_CALL!>.<!>length
}
if (y == null && x != <!ALWAYS_NULL!>y<!>) {
if (y == null && x != <!DEBUG_INFO_CONSTANT!>y<!>) {
// y == null but x != y
<!DEBUG_INFO_SMARTCAST!>x<!>.length
<!ALWAYS_NULL!>y<!><!UNSAFE_CALL!>.<!>length
@@ -3,7 +3,7 @@ fun foo(x: String?, y: String?, z: String?, w: String?) {
<!DEBUG_INFO_SMARTCAST!>z<!>.length
else
z<!UNSAFE_CALL!>.<!>length
if (x != null || y != null || (<!ALWAYS_NULL!>x<!> != z && <!ALWAYS_NULL!>y<!> != z))
if (x != null || y != null || (<!DEBUG_INFO_CONSTANT!>x<!> != z && <!DEBUG_INFO_CONSTANT!>y<!> != z))
z<!UNSAFE_CALL!>.<!>length
else
<!ALWAYS_NULL!>z<!><!UNSAFE_CALL!>.<!>length
@@ -1,6 +1,6 @@
fun bar(x: Int?): Int {
if (x != null) return -1
if (<!ALWAYS_NULL!>x<!> == null) return -2
if (<!SENSELESS_COMPARISON!><!DEBUG_INFO_CONSTANT!>x<!> == null<!>) return -2
// Should be unreachable
return 2 + 2
}
@@ -1,5 +1,5 @@
fun foo(): Int {
var i: Int? = <!VARIABLE_WITH_REDUNDANT_INITIALIZER!>42<!>
i = null
return <!ALWAYS_NULL!>i<!> <!UNSAFE_INFIX_CALL!>+<!> 1
return <!DEBUG_INFO_CONSTANT!>i<!> <!UNSAFE_INFIX_CALL!>+<!> 1
}
@@ -6,6 +6,6 @@ fun foo(e: E, something: Any?): Int {
return when (e) {
E.A -> 1
E.B -> 2
<!ALWAYS_NULL!>something<!> -> 3
<!DEBUG_INFO_CONSTANT!>something<!> -> 3
}
}
@@ -14580,6 +14580,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
doTest(fileName);
}
@TestMetadata("alwaysNullWithJava.kt")
public void testAlwaysNullWithJava() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/alwaysNullWithJava.kt");
doTest(fileName);
}
@TestMetadata("classObjectMember.kt")
public void testClassObjectMember() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/classObjectMember.kt");
@@ -134,6 +134,7 @@ options.kotlin.attribute.descriptor.constructor.call=Constructor call
options.kotlin.attribute.descriptor.variable.as.function.call=Variable as function call
options.kotlin.attribute.descriptor.variable.as.function.like.call=Variable as function-like call
options.kotlin.attribute.descriptor.smart.cast=Smart-cast value
options.kotlin.attribute.descriptor.smart.constant=Smart constant
options.kotlin.attribute.descriptor.smart.cast.receiver=Smart-cast implicit receiver
options.kotlin.attribute.descriptor.label=Label
change.to.function.invocation=Change to function invocation
@@ -83,6 +83,7 @@ public class KotlinHighlightingColors {
// other
public static final TextAttributesKey BAD_CHARACTER = createTextAttributesKey("KOTLIN_BAD_CHARACTER", HighlighterColors.BAD_CHARACTER);
public static final TextAttributesKey SMART_CAST_VALUE = createTextAttributesKey("KOTLIN_SMART_CAST_VALUE");
public static final TextAttributesKey SMART_CONSTANT = createTextAttributesKey("KOTLIN_SMART_CONSTANT");
public static final TextAttributesKey SMART_CAST_RECEIVER = createTextAttributesKey("KOTLIN_SMART_CAST_RECEIVER");
public static final TextAttributesKey LABEL = createTextAttributesKey("KOTLIN_LABEL");
public static final TextAttributesKey DEBUG_INFO = createTextAttributesKey("KOTLIN_DEBUG_INFO");
@@ -76,6 +76,12 @@ class VariablesHighlightingVisitor extends AfterAnalysisHighlightingVisitor {
.setTextAttributes(KotlinHighlightingColors.SMART_CAST_RECEIVER);
}
boolean nullSmartCast = bindingContext.get(SMARTCAST_NULL, expression) == Boolean.TRUE;
if (nullSmartCast) {
holder.createInfoAnnotation(expression, "Always null")
.setTextAttributes(KotlinHighlightingColors.SMART_CONSTANT);
}
KotlinType smartCast = bindingContext.get(SMARTCAST, expression);
if (smartCast != null) {
holder.createInfoAnnotation(expression, "Smart cast to " +
@@ -30,6 +30,11 @@
<option name="BACKGROUND" value="223c23" />
</value>
</option>
<option name="KOTLIN_SMART_CONSTANT">
<value>
<option name="BACKGROUND" value="223c23" />
</value>
</option>
<option name="KOTLIN_SMART_CAST_RECEIVER">
<value>
<option name="BACKGROUND" value="223c23" />
@@ -30,6 +30,11 @@
<option name="BACKGROUND" value="dbffdb" />
</value>
</option>
<option name="KOTLIN_SMART_CONSTANT">
<value>
<option name="BACKGROUND" value="dbffdb" />
</value>
</option>
<option name="KOTLIN_SMART_CAST_RECEIVER">
<value>
<option name="BACKGROUND" value="dbffdb" />
@@ -67,6 +67,9 @@ fun Int?.bar() {
if (this != null) {
println(<SMART_CAST_RECEIVER>toString</SMART_CAST_RECEIVER>())
}
else {
println(<SMART_CONSTANT>this</SMART_CONSTANT>.toString())
}
}
var <PROPERTY_WITH_BACKING_FIELD><PACKAGE_PROPERTY><MUTABLE_VARIABLE>globalCounter</MUTABLE_VARIABLE></PACKAGE_PROPERTY></PROPERTY_WITH_BACKING_FIELD> : Int = 5
@@ -152,6 +155,7 @@ var <PROPERTY_WITH_BACKING_FIELD><PACKAGE_PROPERTY><MUTABLE_VARIABLE>globalCount
KotlinBundle.message("options.kotlin.attribute.descriptor.variable.as.function.like.call") to KotlinHighlightingColors.VARIABLE_AS_FUNCTION_LIKE_CALL,
OptionsBundle.message("options.java.attribute.descriptor.bad.character") to KotlinHighlightingColors.BAD_CHARACTER,
KotlinBundle.message("options.kotlin.attribute.descriptor.smart.cast") to KotlinHighlightingColors.SMART_CAST_VALUE,
KotlinBundle.message("options.kotlin.attribute.descriptor.smart.constant") to KotlinHighlightingColors.SMART_CONSTANT,
KotlinBundle.message("options.kotlin.attribute.descriptor.smart.cast.receiver") to KotlinHighlightingColors.SMART_CAST_RECEIVER,
KotlinBundle.message("options.kotlin.attribute.descriptor.label") to KotlinHighlightingColors.LABEL)
}
+8 -8
View File
@@ -4,7 +4,7 @@ fun test() {
a.plus(1)
}
else {
<warning>a</warning>?.plus(1)
a?.plus(1)
}
val out : java.io.PrintStream? = null
@@ -28,7 +28,7 @@ fun test() {
}
if (out == null) {
<warning>out</warning>?.println()
out?.println()
} else {
out.println()
}
@@ -84,7 +84,7 @@ fun test() {
}
if (out == null) {
<warning>out</warning>?.println()
out?.println()
} else {
out.println()
}
@@ -131,12 +131,12 @@ fun test() {
while (out != null) {
out.println();
}
<warning>out</warning>?.println();
out?.println();
val out2 : java.io.PrintStream? = null
while (out2 == null) {
<warning>out2</warning>?.println();
out2?.println();
}
out2.println()
@@ -224,7 +224,7 @@ fun f7(s : String?, t : String?) {
}
s?.get(0)
if (!(s != null)) {
<warning>s</warning>?.get(0)
s?.get(0)
}
else {
s.get(0)
@@ -234,7 +234,7 @@ fun f7(s : String?, t : String?) {
s.get(0)
}
else {
<warning>s</warning>?.get(0)
s?.get(0)
}
s?.get(0)
t?.get(0)
@@ -253,7 +253,7 @@ fun f7(s : String?, t : String?) {
t?.get(0)
}
else {
<warning>s</warning>?.get(0)
s?.get(0)
t?.get(0)
}
}
+7 -1
View File
@@ -1,6 +1,12 @@
class <info textAttributesKey="KOTLIN_CLASS">My</info>(val <info textAttributesKey="KOTLIN_INSTANCE_PROPERTY"><info textAttributesKey="KOTLIN_PARAMETER"><info textAttributesKey="KOTLIN_PROPERTY_WITH_BACKING_FIELD">x</info></info></info>: <info textAttributesKey="KOTLIN_CLASS">Int</info>?)
fun <info textAttributesKey="KOTLIN_CLASS">My</info>?.<info textAttributesKey="KOTLIN_FUNCTION_DECLARATION">foo</info>(): <info textAttributesKey="KOTLIN_CLASS">Int</info> {
if (this == null || <info textAttributesKey="KOTLIN_INSTANCE_PROPERTY"><info textAttributesKey="KOTLIN_SMART_CAST_RECEIVER">x</info></info> == null) return 42
if (this == null) return 42
if (<info textAttributesKey="KOTLIN_INSTANCE_PROPERTY"><info textAttributesKey="KOTLIN_SMART_CAST_RECEIVER">x</info></info> == null) {
if (<warning textAttributesKey="WARNING_ATTRIBUTES"><info textAttributesKey="KOTLIN_INSTANCE_PROPERTY"><info textAttributesKey="KOTLIN_SMART_CONSTANT"><info textAttributesKey="KOTLIN_SMART_CAST_RECEIVER">x</info></info></info> != null</warning>) {
<warning textAttributesKey="WARNING_ATTRIBUTES">return</warning> <info textAttributesKey="KOTLIN_INSTANCE_PROPERTY"><info textAttributesKey="KOTLIN_SMART_CAST_RECEIVER"><info textAttributesKey="KOTLIN_SMART_CAST_VALUE">x</info></info></info>
}
return 13
}
return <info textAttributesKey="KOTLIN_INSTANCE_PROPERTY"><info textAttributesKey="KOTLIN_SMART_CAST_RECEIVER"><info textAttributesKey="KOTLIN_SMART_CAST_VALUE">x</info></info></info>
}