Tmp
diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/LazyTopDownAnalyzer.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/LazyTopDownAnalyzer.kt
index b0c8e67..a45deaf 100644
--- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/LazyTopDownAnalyzer.kt
+++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/LazyTopDownAnalyzer.kt
@@ -52,9 +52,14 @@
private val classifierUsageCheckers: Iterable<ClassifierUsageChecker>
) {
fun analyzeDeclarations(
- topDownAnalysisMode: TopDownAnalysisMode,
- declarations: Collection<PsiElement>,
- outerDataFlowInfo: DataFlowInfo = DataFlowInfo.EMPTY
+ topDownAnalysisMode: TopDownAnalysisMode,
+ declarations: Collection<PsiElement>,
+ /**
+ * DFA
+ *
+ * This parameter is actually used in LocalClassifierAnalyzer
+ */
+ outerDataFlowInfo: DataFlowInfo = DataFlowInfo.EMPTY
): TopDownAnalysisContext {
val c = TopDownAnalysisContext(topDownAnalysisMode, outerDataFlowInfo, declarationScopeProvider)
@@ -198,6 +203,15 @@
declaration.accept(visitor)
}
+ /**
+ * DFA
+ *
+ * Here we need DFI because we may have to resolve function body
+ * And if it is local function, then DFI matters
+ * And it *can* be local function, because LocalClassifierAnalyzer re-uses LazyTopDownAnalyzer
+ *
+ * Same for properties ofc
+ */
createFunctionDescriptors(c, functions)
createPropertyDescriptors(c, topLevelFqNames, properties)
@@ -219,6 +233,9 @@
overloadResolver.checkOverloads(c)
+ /**
+ * DFA
+ */
bodyResolver.resolveBodies(c)
resolveImportsInAllFiles(c)
diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.kt
index b8aa75f..18a1b74 100644
--- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.kt
+++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.kt
@@ -370,28 +370,41 @@
if (type == null || (type.isError && !type.isFunctionPlaceholder)) {
matchStatus = ArgumentMatchStatus.ARGUMENT_HAS_NO_TYPE
} else if (!noExpectedType(expectedType)) {
+ // argument has expected type
if (!ArgumentTypeResolver.isSubtypeOfForArgumentType(type, expectedType)) {
+ // and we have raw type mismatch. But wait, maybe we can get a smartcast!
val smartCast = smartCastValueArgumentTypeIfPossible(expression, newContext.expectedType, type, newContext)
if (smartCast == null) {
+ // No smartcast too :(
+ // It's already mismatch for sure, but let's check if we're passing nullable argument to non-nullable
+ // parameter to provide better diagnostic
resultStatus = tryNotNullableArgument(type, expectedType) ?: OTHER_ERROR
matchStatus = ArgumentMatchStatus.TYPE_MISMATCH
} else {
+ // Nice, we got smartcast, lets use it
resultingType = smartCast
}
} else if (ErrorUtils.containsUninferredParameter(expectedType)) {
+ // Nice, type of argument is subtype of expected type
+ // However, ArgumentTypeResolver.isSubtypeOfForArgumentType could return true if some types were
+ // not inferred. Let's check this and if there are some, use appropriate match status
matchStatus = ArgumentMatchStatus.MATCH_MODULO_UNINFERRED_TYPES
}
val spreadElement = argument.getSpreadElement()
if (spreadElement != null && !type.isFlexible() && type.isMarkedNullable) {
+ // Oh shi~, we're using spread on the nullable argument. Usually this is forbidden,
+ // but what if we have a smartcast around...
val dataFlowValue = DataFlowValueFactory.createDataFlowValue(expression, type, context)
val smartCastResult = SmartCastManager.checkAndRecordPossibleCast(
dataFlowValue, expectedType, expression, context,
call = null, recordExpressionType = false
)
if (smartCastResult == null || !smartCastResult.isCorrect) {
+ // Nope, we don't, so report error
context.trace.report(Errors.SPREAD_OF_NULLABLE.on(spreadElement))
}
+ // Otherwise, we do have suitable smartcast, so everything is OK
}
}
argumentTypes.add(resultingType)
@@ -511,8 +524,13 @@
val safeAccess = isExplicitReceiver && !implicitInvokeCheck && call.isSemanticallyEquivalentToSafeCall
val expectedReceiverParameterType = if (safeAccess) TypeUtils.makeNullable(receiverParameter.type) else receiverParameter.type
+ // Inside getSmartCastReceiverResult:
+ // 1. Create DFV from receiverArgument
+ // 2. Pull DFI from context
+ // 3. Ask DFI about .getCollectedTypes()
val smartCastSubtypingResult = smartCastManager.getSmartCastReceiverResult(receiverArgument, expectedReceiverParameterType, this)
if (smartCastSubtypingResult == null) {
+ // Ok, we can't smartcast, give up
tracing.wrongReceiverType(
trace, receiverParameter, receiverArgument,
this.replaceCallPosition(CallPosition.ExtensionReceiverPosition(candidateCall))
@@ -520,9 +538,12 @@
return OTHER_ERROR
}
+ // Here we have smartcast: either to type or to right nullability
+
val notNullReceiverExpected = smartCastSubtypingResult != SmartCastManager.ReceiverSmartCastResult.OK
val smartCastNeeded =
- notNullReceiverExpected || !isCandidateVisibleOrExtensionReceiver(receiverArgument, null, isDispatchReceiver)
+ // TODO: Ask how the extension receiver is related here
+ notNullReceiverExpected || !isCandidateVisibleOrExtensionReceiver(receiverArgument, null, isDispatchReceiver)
var reportUnsafeCall = false
var nullableImplicitInvokeReceiver = false
@@ -539,19 +560,26 @@
}
}
+ // Again traverse all DataFlowInfo
val dataFlowValue = DataFlowValueFactory.createDataFlowValue(receiverArgument, this)
val nullability = dataFlowInfo.getStableNullability(dataFlowValue)
+
val expression = (receiverArgument as? ExpressionReceiver)?.expression
- if (nullability.canBeNull() && !nullability.canBeNonNull()) {
+ if (nullability.canBeNull() && !nullability.canBeNonNull()) { // I.e. receiver is definitely null
if (!TypeUtils.isNullableType(expectedReceiverParameterType)) {
reportUnsafeCall = true
}
+
+ // This 'if' can be false for 'Nothing?' (i.e. for 'null' constant)
if (dataFlowValue.immanentNullability.canBeNonNull()) {
expression?.let { trace.record(BindingContext.SMARTCAST_NULL, it) }
}
} else if (!nullableImplicitInvokeReceiver && smartCastNeeded) {
+ // !nullability.canBeNull() || nullability.canBeNotNull() <=> canBeNotNull
// Look if smart cast has some useful nullability info
+ // Why the hell we go and ask for smartcasts second time? We've already asked above
+ // Reminder: dataFlowValue was manually created from receiverArgument
val smartCastResult = SmartCastManager.checkAndRecordPossibleCast(
dataFlowValue, expectedReceiverParameterType,
{ possibleSmartCast -> isCandidateVisibleOrExtensionReceiver(receiverArgument, possibleSmartCast, isDispatchReceiver) },
@@ -559,10 +587,13 @@
)
if (smartCastResult == null) {
+ // Sigh, no smartcast
if (notNullReceiverExpected) {
+ // But dude, we wanted not null! This is unsafe, bruh
reportUnsafeCall = true
}
} else {
+ // Ok we have some smartcast
if (isDispatchReceiver) {
candidateCall.setSmartCastDispatchReceiverType(smartCastResult.resultType)
} else {
diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueFactory.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueFactory.kt
index 22a32a1..385e788 100644
--- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueFactory.kt
+++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueFactory.kt
@@ -70,6 +70,34 @@
}
@JvmStatic
+ /*
+ 1. [Special case] 'null', 'Nothing?': special DFV
+ 2. [Special case] error-type: special DFV
+ 3. [Special case] '!!': force non-null DFV to prevent issues with generics
+ 4. Block, if, when, elvis: stable DFV for complex expression
+ 5. Qualified expression:
+ - selector info, when receiver is package or class
+ - NO, when receiver is NO
+ - QualifiedInfo otherwise. Stability = min(stability of receiver, stability of selector)
+ 6. 'is', 'as': NO
+ 7. 'as?': SafeCastInfo. stability <=> stability of subject
+ 8. '++', '--': PostfixInfo. stability <=> stability of argument
+ 9. 'this': Receiver(target)
+ 10. Property.
+ Check if this property is either: 'var', has open or custom getter, declared in other module.
+ If so, then it is unstable.
+ Otherwise it is stable.
+ 11. Local variable/function parameter.
+ - 'val': stable value
+ - 'var': generally, unstable, but can be relaxed for some cases
+ - if no one writes in this 'var', then it is considered stable
+ - if inside closure: generally, unstable, but can be relaxed for some cases:
+ - if all writers are reliably "before" closured access, then considered stable
+ - if not inside closure, but has closure writers: generally, unstable, but can be relaxed for some cases:
+ - if all closure writers are provably "after" access, then considered stable
+ NB. Also consider implicit receivers, and create QualifiedInfo, if it is present
+
+ */
fun createDataFlowValue(
expression: KtExpression,
type: KotlinType,
@@ -92,6 +120,8 @@
//
// But there are some problem with types built on type parameters, e.g.
// fun <T : Any?> foo(x: T) = x!!.hashCode() // there no way in type system to denote that `x!!` is not nullable
+ //
+ // This is closely connected with 'immanentlyNotNull' checks in the second part of 'SmartCastManager.checkAndRecordPossibleCast()'
return DataFlowValue(
ExpressionIdentifierInfo(expression),
type,
diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DelegatingDataFlowInfo.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DelegatingDataFlowInfo.kt
index 42988fc..926a4e5 100644
--- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DelegatingDataFlowInfo.kt
+++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DelegatingDataFlowInfo.kt
@@ -102,8 +102,7 @@
}
val identifierInfo = value.identifierInfo
- if (affectReceiver && !nullability.canBeNull() &&
- languageVersionSettings.supportsFeature(LanguageFeature.SafeCallBoundSmartCasts)) {
+ if (affectReceiver && !nullability.canBeNull() && languageVersionSettings.supportsFeature(LanguageFeature.SafeCallBoundSmartCasts)) {
when (identifierInfo) {
is IdentifierInfo.Qualified -> {
val receiverType = identifierInfo.receiverType
diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/SmartCastManager.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/SmartCastManager.kt
index 2419a68..b4b2913 100644
--- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/SmartCastManager.kt
+++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/SmartCastManager.kt
@@ -82,18 +82,27 @@
return dataFlowInfo.getCollectedTypes(dataFlowValue, languageVersionSettings)
}
+ // Checks if `receiverArgument` can be cast to `receiverParameterType` with smartcasts taken into consideration
fun getSmartCastReceiverResult(
receiverArgument: ReceiverValue,
receiverParameterType: KotlinType,
context: ResolutionContext<*>
): ReceiverSmartCastResult? {
+ // Ok, let's just check if some cast is known with exact types
getSmartCastReceiverResultWithGivenNullability(receiverArgument, receiverParameterType, context)?.let {
+ // We have some cast with exact type
+ // Noe that it also returns for plain subtypes
return it
}
+ // No luck here, but maybe we can find suitable cast for nullable reciever?
val nullableParameterType = TypeUtils.makeNullable(receiverParameterType)
+
return when {
+ // Still no luck, return 'null'
getSmartCastReceiverResultWithGivenNullability(receiverArgument, nullableParameterType, context) == null -> null
+
+ // Found cast, but note that we've expanded nullability, so no matters what we've found, it's still "NOT_NULL_EXPECTED"
else -> ReceiverSmartCastResult.SMARTCAST_NEEDED_OR_NOT_NULL_EXPECTED
}
}
@@ -115,6 +124,7 @@
enum class ReceiverSmartCastResult {
OK,
+ // Fun fact -- it is used only in SmartCastManager
SMARTCAST_NEEDED_OR_NOT_NULL_EXPECTED
}
@@ -130,14 +140,21 @@
) {
if (KotlinBuiltIns.isNullableNothing(type)) return
if (dataFlowValue.isStable) {
+ // Smartcast is possible, lets try to do this!
+
val oldSmartCasts = trace[SMARTCAST, expression]
val newSmartCast = SingleSmartCast(call, type)
if (oldSmartCasts != null) {
+ // There already was some smartcast on that expression
+
+ // Let's check if this SMARTCAST slice (for the same expression!) already contains cast for this particular 'call'
val oldType = oldSmartCasts.type(call)
if (oldType != null && oldType != type) {
+ // Oops, this slice already contains smartcast for this particular 'call'
throw AssertionError("Rewriting key $call for smart cast on ${expression.text}")
}
}
+ // Ok, either there was no SMARTCAST slice at all for this 'expression', or it didn't contain a cast for this particular call
trace.record(SMARTCAST, expression, oldSmartCasts?.let { it + newSmartCast } ?: newSmartCast)
if (recordExpressionType) {
//TODO
@@ -145,6 +162,7 @@
trace.recordType(expression, type)
}
} else {
+ // Unstable data
trace.report(SMARTCAST_IMPOSSIBLE.on(expression, type, expression.text, dataFlowValue.kind.description))
}
}
@@ -160,6 +178,16 @@
return checkAndRecordPossibleCast(dataFlowValue, expectedType, null, expression, c, call, recordExpressionType)
}
+ /*
+ Semantics of this method:
+ 1. Take 'dataFlowValue'
+ 2. Pull 'dataFlowInfo' from 'c' and get all types for that 'dataFlowValue'
+ 3. If some of that types matches 'expectedType' and 'additionalPredicate' (if any), then record smartcast/error (i.e. diagnostic) with stability taken into consideration
+ (can also involve some additional hoops for implicit receiver)
+
+ IF NO TYPES WERE MATCHED ON PREVIOUS STEP, THEN:
+ 4.
+ */
fun checkAndRecordPossibleCast(
dataFlowValue: DataFlowValue,
expectedType: KotlinType,
@@ -171,14 +199,24 @@
): SmartCastResult? {
val calleeExpression = call?.calleeExpression
for (possibleType in c.dataFlowInfo.getCollectedTypes(dataFlowValue, c.languageVersionSettings)) {
+ // Check another type from smartcast
if (ArgumentTypeResolver.isSubtypeOfForArgumentType(possibleType, expectedType) &&
(additionalPredicate == null || additionalPredicate(possibleType))
) {
+ // This type suits us!
if (expression != null) {
+ // records cast if DFV is stable (with rewrite checks) OR records SMARTCAST_IMPOSSIBLE if DFV is unstable
recordCastOrError(expression, possibleType, c.trace, dataFlowValue, call, recordExpressionType)
} else if (calleeExpression != null && dataFlowValue.isStable) {
+ // This is the case when we have implicit receiver (e.g. in cases like 'with(a) { if (this is String) ... }'
+ // or in the body of extension function)
+ //
+ // Here we have to invent additional diagnostic on call itself
val receiver = (dataFlowValue.identifierInfo as? IdentifierInfo.Receiver)?.value
if (receiver is ImplicitReceiver) {
+ // This is the case #2
+
+ // dat logic duplication tho (see above)
val oldSmartCasts = c.trace[IMPLICIT_RECEIVER_SMARTCAST, calleeExpression]
val newSmartCasts = ImplicitSmartCasts(receiver, possibleType)
if (oldSmartCasts != null) {
@@ -192,14 +230,17 @@
}
c.trace.record(IMPLICIT_RECEIVER_SMARTCAST, calleeExpression,
oldSmartCasts?.let { it + newSmartCasts } ?: newSmartCasts)
-
}
}
+
+ // ...but who the fuck is going to report this smartcast? :(
return SmartCastResult(possibleType, dataFlowValue.isStable)
}
}
if (!c.dataFlowInfo.getCollectedNullability(dataFlowValue).canBeNull() && !expectedType.isMarkedNullable) {
+ // Ok, this value can't be null, and we expect not-null
+
// Handling cases like:
// fun bar(x: Any) {}
// fun <T : Any?> foo(x: T) {
@@ -208,23 +249,45 @@
// }
// }
//
- // It doesn't handled by lower code with getPossibleTypes because smart cast of T after `x != null` is still has same type T.
+ // It doesn't handled by upper code with getCollectedTypes 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)
+
+
+ // Segment below is much easier to understand if considered without 'immanentlyNotNull' flag
+
+ // Essentially, this is fast path for cases when 'dataFlowValue.type' can be casted to 'expected type'
+ // with the help of smartcasts.
val immanentlyNotNull = !dataFlowValue.immanentNullability.canBeNull()
val nullableExpectedType = TypeUtils.makeNullable(expectedType)
+ // Let's check if types are suitable modulo nullability smartcast
if (ArgumentTypeResolver.isSubtypeOfForArgumentType(dataFlowValue.type, nullableExpectedType) &&
(additionalPredicate == null || additionalPredicate(dataFlowValue.type))
) {
+ // Ok, dataFlowValue.type <: expectedType?
+ // AND
+ // we know what dataFlowValue can be casted to not-null, and we expect not-null
+ // =>
+ // let's record smartcast!
if (!immanentlyNotNull && expression != null) {
+ // Let's record smartcast!
recordCastOrError(expression, dataFlowValue.type, c.trace, dataFlowValue, call, recordExpressionType)
}
return SmartCastResult(dataFlowValue.type, immanentlyNotNull || dataFlowValue.isStable)
}
+ // Now, why do we even have that 'immanentlyNotNull' flag?
+ // Because it indicates that we're doing '!!'-assertion on generic type with nullable upper bound, which should be
+ // handled separately if it is in call chain (i.e. in cases like 'x!!.foo) because we have no other place to
+ // handle this case (compare that with cases like 'x!!; x.length' - here first statement provides DFI that x != null,
+ // and we already see it in the second statement)
+
+
+ // Ok, so we can't transform 'dataFlowValue' to 'expectedType' via nullability smartcast
+ // We say here: let's try again, but we will try to find cast to the nullable expected type
return checkAndRecordPossibleCast(dataFlowValue, nullableExpectedType, expression, c, call, recordExpressionType)
}
diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java
index f26cd4b..e978ff5 100644
--- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java
+++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java
@@ -173,6 +173,9 @@
return components.dataFlowAnalyzer.checkType(typeInfo, expression, context); // TODO : Extensions to this
}
+ /*
+ Veeeeeery suspicious
+ */
@Override
public KotlinTypeInfo visitParenthesizedExpression(@NotNull KtParenthesizedExpression expression, ExpressionTypingContext context) {
KtExpression innerExpression = expression.getExpression();
@@ -190,6 +193,10 @@
return result;
}
+
+ /*
+ Returns the same data-flow info, as in the context
+ */
@Override
public KotlinTypeInfo visitConstantExpression(@NotNull KtConstantExpression expression, ExpressionTypingContext context) {
IElementType elementType = expression.getNode().getElementType();
@@ -293,6 +300,9 @@
}
}
+ /*
+ Returns the same info + maybe enhances it with "as"-cast info
+ */
@Override
public KotlinTypeInfo visitBinaryWithTypeRHSExpression(
@NotNull KtBinaryExpressionWithTypeRHS expression,
@@ -658,6 +668,9 @@
return components.doubleColonExpressionResolver.visitClassLiteralExpression(expression, c);
}
+ /*
+ Seems like no extra data-flow hoops, but have to re-check
+ */
@Override
public KotlinTypeInfo visitCallableReferenceExpression(@NotNull KtCallableReferenceExpression expression, ExpressionTypingContext c) {
return components.doubleColonExpressionResolver.visitCallableReferenceExpression(expression, c);
@@ -819,6 +832,9 @@
contextWithExpectedType.replaceDataFlowInfo(typeInfo.getDataFlowInfo()));
}
+ /*
+ Practically no non-trivial data-flow manipulations except for casually forcing non-nullability of argument.
+ */
private KotlinTypeInfo visitExclExclExpression(@NotNull KtUnaryExpression expression, @NotNull ExpressionTypingContext context) {
KtExpression baseExpression = expression.getBaseExpression();
assert baseExpression != null;
@@ -847,6 +863,7 @@
assert baseTypeInfo != null : "Base expression was not processed: " + expression;
KotlinType baseType = baseTypeInfo.getType();
if (baseType == null) {
+ // Can get here in case of esoteric red code, like "java.lang.System!!"
return baseTypeInfo;
}
DataFlowInfo dataFlowInfo = baseTypeInfo.getDataFlowInfo();
@@ -1254,6 +1271,8 @@
KotlinTypeInfo leftTypeInfo = BindingContextUtils.getRecordedTypeInfo(left, context.trace.getBindingContext());
boolean isLeftFunctionLiteral = ArgumentTypeResolver.isFunctionLiteralArgument(left, context);
boolean isLeftCallableReference = ArgumentTypeResolver.isCallableReferenceArgument(left, context);
+
+ // Hoops with function literals and callable references
if (leftTypeInfo == null && (isLeftFunctionLiteral || isLeftCallableReference)) {
DiagnosticFactory0<PsiElement> diagnosticFactory =
isLeftFunctionLiteral ? USELESS_ELVIS_ON_LAMBDA_EXPRESSION : USELESS_ELVIS_ON_CALLABLE_REFERENCE;
@@ -1261,6 +1280,8 @@
return TypeInfoFactoryKt.noTypeInfo(context);
}
assert leftTypeInfo != null : "Left expression was not processed: " + expression;
+
+ // USELESS_ELVIS diagnostics
KotlinType leftType = leftTypeInfo.getType();
if (isKnownToBeNotNull(left, leftType, context)) {
context.trace.report(USELESS_ELVIS.on(expression, leftType));
@@ -1268,45 +1289,60 @@
else if (KtPsiUtil.isNullConstant(right) && leftType != null && !FlexibleTypesKt.isNullabilityFlexible(leftType)) {
context.trace.report(USELESS_ELVIS_RIGHT_IS_NULL.on(expression));
}
+
+ // Hoops with function literals and callable references
KotlinTypeInfo rightTypeInfo = BindingContextUtils.getRecordedTypeInfo(right, context.trace.getBindingContext());
if (rightTypeInfo == null && ArgumentTypeResolver.isFunctionLiteralOrCallableReference(right, context)) {
// the type is computed later in call completer according to the '?:' semantics as a function
return TypeInfoFactoryKt.noTypeInfo(context);
}
+
assert rightTypeInfo != null : "Right expression was not processed: " + expression;
- boolean loopBreakContinuePossible = leftTypeInfo.getJumpOutPossible() || rightTypeInfo.getJumpOutPossible();
KotlinType rightType = rightTypeInfo.getType();
+ // DataFlowShit starts here
+ boolean loopBreakContinuePossible = leftTypeInfo.getJumpOutPossible() || rightTypeInfo.getJumpOutPossible();
+
// Only left argument DFA is taken into account here: we cannot be sure that right argument is joined
// (we merge it with right DFA if right argument contains no jump outside)
- DataFlowInfo dataFlowInfo = resolvedCall.getDataFlowInfoForArguments().getInfo(call.getValueArguments().get(1));
+ DataFlowInfo resultDataFlowInfo = resolvedCall.getDataFlowInfoForArguments().getInfo(call.getValueArguments().get(1));
+
KotlinType type = resolvedCall.getResultingDescriptor().getReturnType();
if (type == null ||
rightType == null ||
- leftType == null && KotlinBuiltIns.isNothing(rightType)) return TypeInfoFactoryKt.noTypeInfo(dataFlowInfo);
+ leftType == null && KotlinBuiltIns.isNothing(rightType)
+ ) {
+ return TypeInfoFactoryKt.noTypeInfo(resultDataFlowInfo);
+ }
if (leftType != null) {
+ // This one is about logic "if right value is somehow breaks flow, then left is non-null"
DataFlowValue leftValue = createDataFlowValue(left, leftType, context);
DataFlowInfo rightDataFlowInfo = resolvedCall.getDataFlowInfoForArguments().getResultInfo();
boolean jumpInRight = KotlinBuiltIns.isNothing(rightType);
DataFlowValue nullValue = DataFlowValue.nullValue(components.builtIns);
+
// left argument is considered not-null if it's not-null also in right part or if we have jump in right part
if (jumpInRight || !rightDataFlowInfo.getStableNullability(leftValue).canBeNull()) {
- dataFlowInfo = dataFlowInfo.disequate(leftValue, nullValue, components.languageVersionSettings);
+ resultDataFlowInfo = resultDataFlowInfo.disequate(leftValue, nullValue, components.languageVersionSettings);
+
+ // Here we cover cases like "(x as? Foo) ?: return". Since 1.2, we have SafeCastCheckBoundSmartCasts language feature,
+ // which cover such cases more generally
if (left instanceof KtBinaryExpressionWithTypeRHS) {
- dataFlowInfo = establishSubtypingForTypeRHS((KtBinaryExpressionWithTypeRHS) left, dataFlowInfo, context,
+ resultDataFlowInfo = establishSubtypingForTypeRHS((KtBinaryExpressionWithTypeRHS) left, resultDataFlowInfo, context,
components.languageVersionSettings);
}
}
+
DataFlowValue resultValue = DataFlowValueFactory.createDataFlowValue(expression, type, context);
- dataFlowInfo =
- dataFlowInfo.assign(resultValue, leftValue, components.languageVersionSettings)
+ resultDataFlowInfo =
+ resultDataFlowInfo.assign(resultValue, leftValue, components.languageVersionSettings)
.disequate(resultValue, nullValue, components.languageVersionSettings);
if (!jumpInRight) {
DataFlowValue rightValue = DataFlowValueFactory.createDataFlowValue(right, rightType, context);
rightDataFlowInfo = rightDataFlowInfo.assign(resultValue, rightValue, components.languageVersionSettings);
- dataFlowInfo = dataFlowInfo.or(rightDataFlowInfo);
+ resultDataFlowInfo = resultDataFlowInfo.or(rightDataFlowInfo);
}
}
@@ -1316,16 +1352,24 @@
type = TypeUtils.makeNotNullable(type);
}
if (context.contextDependency == DEPENDENT) {
- return TypeInfoFactoryKt.createTypeInfo(type, dataFlowInfo);
+ return TypeInfoFactoryKt.createTypeInfo(type, resultDataFlowInfo);
}
// If break or continue was possible, take condition check info as the jump info
+ // components.dataFlowAnalyzer.createCheckedTypeInfo(type, contextWithExpectedType, expression);
+ // <=> checkType(TypeInfoFactoryKt.createTypeInfo(type, context), expression, context)
+ // <=> TypeInfoFactoryKt.createTypeInfo(type, context).replaceType(checkType(type), expression, context)
+ // <=> TypeInfoFactoryKT.createTypeInfo(checkType(type), expression, context
+
return TypeInfoFactoryKt.createTypeInfo(components.dataFlowAnalyzer.checkType(type, expression, contextWithExpectedType),
- dataFlowInfo,
+ resultDataFlowInfo,
loopBreakContinuePossible,
context.dataFlowInfo);
}
+ /*
+ Obsolete, see comment at use-site
+ */
@NotNull
private static DataFlowInfo establishSubtypingForTypeRHS(
@NotNull KtBinaryExpressionWithTypeRHS left,
@@ -1348,10 +1392,24 @@
return dataFlowInfo;
}
+ /*
+ Nothing too special, just regular linear data-flow juggling, but note that order is different:
+ 1. Resolve *right* expression in basic context, get data flow info after right ("dataFlowInfo")
+
+ 2. Resolve *whole* call in the context of right-expression ("contextWithDataFlow")
+ Note that it will compute data-flow info for left-expression too, as part of resolving arguments of desugared call
+
+ 3. Get data flow info for *left* expression in the "contextWithDataFlow" (will just take already computed data-flow info from trace), "flowForLeft"
+
+ 4. Return 'and'-merged flow for left and flow for right (orly??) <=> dataFlowInfo && flowForLeft
+ Note that implementation doesn't create new TypeInfo and instead mutates 'rightTypeInfo', which is quite confusing tbh.
+ Also note that 'and'-merging flow for left and right is most probably not correct (similar to 'and'-merging in DataFlowInfoForArguments),
+ but luckily we don't have (yet) constructions with non-trivial enough flow to break this.
+ */
@NotNull
public KotlinTypeInfo checkInExpression(
@NotNull KtElement callElement,
- @NotNull KtSimpleNameExpression operationSign,
+ @NotNull KtSimpleNameExpression operationSign, // either "in" or "!in"
@NotNull ValueArgument leftArgument,
@Nullable KtExpression right,
@NotNull ExpressionTypingContext context
@@ -1378,8 +1436,8 @@
ensureBooleanResult(operationSign, OperatorNameConventions.CONTAINS, containsType, context);
if (left != null) {
- dataFlowInfo = facade.getTypeInfo(left, contextWithDataFlow).getDataFlowInfo().and(dataFlowInfo);
- rightTypeInfo = rightTypeInfo.replaceDataFlowInfo(dataFlowInfo);
+ DataFlowInfo flowForLeft = facade.getTypeInfo(left, contextWithDataFlow).getDataFlowInfo();
+ rightTypeInfo = rightTypeInfo.replaceDataFlowInfo(flowForLeft);
}
if (resolutionResult.isSuccess()) {
@@ -1490,6 +1548,12 @@
return declarationInIllegalContext(property, context);
}
+ /*
+ Very trivial manipulations:
+ - resolve lhs in basic context
+ - resolve rhs in context with info from lhs
+ - use rhs resulting info (resolvedCall.dataFlowInfoForArguments.resultInfo) as result
+ */
@NotNull
private KotlinTypeInfo getTypeInfoForBinaryCall(
@NotNull Name name,
@@ -1676,6 +1740,13 @@
return resolveArrayAccessSpecialMethod(arrayAccessExpression, null, context, context.trace, true, false);
}
+ /*
+ 1. Take data flow for array call (e.g. for "foo()" in "foo()[bar(), baz(), bak()]", "arrayTypeInfo"
+ 2. If there are any indices, take data flow for all indices computation (as usual, this is resolved as call, and indices are arguments, so resulting info is
+ bound to the last argument)
+ 3. If there are rightHandSide (for 'set'-method), then take data flow for it instead (it's not obvious, but it *will* contain info about indices, because both
+ indices and RHS are analyzed during synthethic-call resolution as arguments, and dataFlow for them is already recorded)
+ */
@NotNull
private KotlinTypeInfo resolveArrayAccessSpecialMethod(
@NotNull KtArrayAccessExpression arrayAccessExpression,
@@ -1700,6 +1771,9 @@
Call call = isGet
? CallMaker.makeArrayGetCall(receiver, arrayAccessExpression, Call.CallType.ARRAY_GET_METHOD)
: CallMaker.makeArraySetCall(receiver, arrayAccessExpression, rightHandSide, Call.CallType.ARRAY_SET_METHOD);
+
+ // As a side effect, this call will record typeinfo for rightHandSide
+ // Quite logical, if you think about it, but totally not obvious from a first glance
OverloadResolutionResults<FunctionDescriptor> functionResults = components.callResolver.resolveCallWithGivenName(
context, call, arrayAccessExpression, isGet ? OperatorNameConventions.GET : OperatorNameConventions.SET);
@@ -1711,6 +1785,11 @@
}
if (!isGet) {
+ // Surprise-surprise: though that looks like we're saying here:
+ // "Take context info(which is empty atm), and resolve 'rightHandSide' in that context, and then *OVERWRITE* current info with it"
+ // And one could (rightfully) think that this is wrong, because it obviously won't contain data-flow information about indices
+ // But no!
+ // In face, getTypeInfo will take out cached type info, calculated by someone else, which apparently *does* contain info about indices
resultTypeInfo = facade.getTypeInfo(rightHandSide, context);
}
diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ControlStructureTypingVisitor.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ControlStructureTypingVisitor.java
index 0a9341a..850d601 100644
--- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ControlStructureTypingVisitor.java
+++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ControlStructureTypingVisitor.java
@@ -251,6 +251,33 @@
return visitWhileExpression(expression, context, false);
}
+ /*
+ 0. Check if it is used as statement, exit with reporting, if so
+ 1. Launch PreliminaryLoopVisitor to clear DFI for all variables assigned in loop
+ 2. Extract type info from body:
+ - If body is present, then resolve its body in context with DFI = "(condition == true) && (condition finished)"
+ - Otherwise, use empty type info
+ 3. Infer outer info:
+ Step a. Initialize "result: DataFlowInfo" with context data flow info (remember, it has already all assigned variables cleared)
+ Step b. Update "result &= dfi(condition finished)".
+ Step c. If there is no jumpout in body, then update "result &= dfi(condition == true)"
+ Step d. If it is 'while(true)', then further update 'result &= jumpOutInfo' <=> 'result &= dfi(first break in loop body) - assignedVariablesInfo'
+
+ Possible higher-level logic:
+ 0. Check if it is used as statement, exit with reporting
+ 1. dataFlowResults = analyzer.analyzeWhileLoop(whileExpression, context)
+ - launches PreliminiaryLoopVisitor, clears DFI for all variables assigned in loop
+ - extracts "condition == true"
+ - extracts "condition finished"
+ - ? returns some lightweight view, maybe even lazy one
+ 2. contextForBody = dataFlowResults.getContextForBodyResultion()
+ - returns context with "(condition == true) && (condition finished)"
+ 3. Resolve body in contextForBody. This will return "dataFlowInfoForBlock"
+ 4. analyzer.feed(dataFlowInfoForBlock) // submit info about body into analyzer, analyzer will decide what should be done about it
+ 5. return analyzer.loopResultInfo
+
+ */
+
public KotlinTypeInfo visitWhileExpression(KtWhileExpression expression, ExpressionTypingContext contextWithExpectedType, boolean isStatement) {
if (!isStatement) return components.dataFlowAnalyzer.illegalStatementType(expression, contextWithExpectedType, facade);
@@ -344,6 +371,22 @@
return visitDoWhileExpression(expression, context, false);
}
+ /*
+ 0. Check if it is used as statement, returning with diagnostic if so
+ 1. Launch PreliminaryVisitor to clear all info about variables assigned in loop
+ 2. Extract body info:
+ - if body is present, then resolve it in current context (remember, it has already all asigned variables cleared)
+ - otherwise, no type info
+ 3. Resolve condition:
+ - use context DFI (*NOT* a body DFI!)
+ - use *BODY* scope (hello, nice "feature" of using val's, declared in do-while body, in condition)
+ 4. Infer outer info:
+ Step a.
+ - If no jumpouts in body, then we initialize "result" to "dfi(condition == true) && dfi(condition finished)"
+ - If there are jumpouts in body, then initialize "result" to context dfi (dfi at the enter of loop - all assigned variables)
+ Step b. Update "result" with "result &= jumpOutInfo" <=> "result &= dataFlowInfoAtFirstBreak - allAssignedVariables"
+
+ */
public KotlinTypeInfo visitDoWhileExpression(KtDoWhileExpression expression, ExpressionTypingContext contextWithExpectedType, boolean isStatement) {
if (!isStatement) return components.dataFlowAnalyzer.illegalStatementType(expression, contextWithExpectedType, facade);
@@ -411,6 +454,17 @@
return visitForExpression(expression, context, false);
}
+ /*
+ 0. If used as statement, return with diagnostic
+ 1. Launch loopVisitor to clear all info for all assigned variables, producing context'
+ 2. Extract loopRangeInfo
+ - if loopRangeExpression is present, then resolve it in context'
+ - otherwise, use empty type info
+ 3. Extract body type info:
+ - if body is present, then resolve it in context of loopRangeInfo
+ - otherwise, use empty type info
+ 4. Use loopRangeInfo as outer info
+ */
public KotlinTypeInfo visitForExpression(KtForExpression expression, ExpressionTypingContext contextWithExpectedType, boolean isStatement) {
if (!isStatement) return components.dataFlowAnalyzer.illegalStatementType(expression, contextWithExpectedType, facade);
@@ -503,6 +557,22 @@
return variableDescriptor;
}
+ /*
+ 0. Let context' = current contexxt with no context dependency
+ 1. Extract info from try-block in context'
+ 2. Infer info at the end of try-catch, "tryOutputContext":
+ Step a. initialize to context'.dataFlowInfo
+ Step b. If it is possible to fall-through at least one catch-clause (i.e. not every catch-clause ends with jumpout),
+ then launch PreliminaryVisitor and clear info for all assigned variables in 'try'
+ 3. If there is a finally clause, then resolve it in "tryOutputContext" context
+ 4. Infer info at the end of the whole try-expression (with all catches and finaly-clause, if any), "result":
+ Step a. Initialize "result" to "tryOutputContext", i.e. very conservative info (it is <= context info)
+ Step b. If finally is present, then we're sure that it is executed.
+ Update "result" to the finally's info
+ Step c. If finally is not present, but fall-through is impossible, then we're sure that try-block is executed.
+ Update "result" to the info at the end of the try-block, "tryResult".
+ 5. Return "result"
+ */
@Override
public KotlinTypeInfo visitTryExpression(@NotNull KtTryExpression expression, ExpressionTypingContext typingContext) {
ExpressionTypingContext context = typingContext.replaceContextDependency(INDEPENDENT);
@@ -565,6 +635,7 @@
if (type != null) {
types.add(type);
}
+
if (types.isEmpty()) {
return result.clearType();
}
@@ -607,6 +678,10 @@
}
@Override
+ /*
+ No data-flow manipulations whatsoever, in the end it returns context.dataFlowInfo, though that's highly obscure and
+ non-obvious at the first glance
+ */
public KotlinTypeInfo visitReturnExpression(@NotNull KtReturnExpression expression, ExpressionTypingContext context) {
KtElement labelTargetElement = LabelResolver.INSTANCE.resolveControlLabel(expression, context);
diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DataFlowAnalyzer.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DataFlowAnalyzer.java
index ffe01d5..f501955 100644
--- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DataFlowAnalyzer.java
+++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DataFlowAnalyzer.java
@@ -122,6 +122,37 @@
return !typeHasOverriddenEquals(type, lookupElement);
}
+ /*
+ Visits PSI-tree which contains some boolean condition, and extracts
+ data flow info, given that it is known that this 'condition' is equal to 'conditionValue'.
+
+ 1. For 'is'-expression: just take recorded DFI from trace
+ Intricacies:
+ - check that it indeed brings us subtype infromation. Case chasing: "if operator isn't inverted
+ and 'conditionValue' == true, then record subtyping; if operator is inverted and 'conditionValue == false',
+ then record subtyping; otherwise do not record anything (we don't have a notion of "not-subtype-of" in compiler)
+
+ 2. For binary operator: do recursive calls on arguments and merge DFI
+ Intricacies:
+ - short-circuit logic
+ - choosing right operator for merging (case chasing: if operator is '&&' and 'conditionValue == true', then
+ merge with 'AND'; if operator is '||' and 'conditionValue == false', then merge with 'OR'; ...)
+
+ 3. For equals operator: equate 'left' and 'right'
+ Intricacies:
+ - Identity equals
+ - choosing right operator (equate vs. disequate + inversion in case of 'conditionValue == false')
+
+ 4. For elvis: lower
+ 'E ?: false' -> 'E == true',
+ 'E ?: true' -> 'E == false'
+ where "E" is some expression of nullable type
+
+ 5. For unary negation: recruse with inverted 'conditionValue'
+
+ 6. For everything else: just take recorded DFI from trace
+
+ */
@NotNull
public DataFlowInfo extractDataFlowInfoFromCondition(
@Nullable KtExpression condition,
@@ -270,6 +301,20 @@
}
@NotNull
+ /*
+ If expected type is not present, trivial, or not denotable -- then do not check anything.
+ Also omit trivial checks (if expression is already subtype of expected type)
+
+ Then:
+ - for constant expressions, check its type via CompileTimeConstantChecker in light mode
+ - for when expression, do nothing
+ - for all the rest, try to get smartcast, and return, if suitable is found.
+
+ Otherwise, there's type mismatch and we have to report it properly:
+ - Special case: type mismatch was because of type projections
+ - Special case: type mismatch was because of Scala-like function syntax
+ - Otherwise, just report generic "Expected X but got Y"
+ */
private KotlinType checkTypeInternal(
@NotNull KotlinType expressionType,
@NotNull KtExpression expression,
@@ -307,6 +352,12 @@
return expressionType;
}
+ /*
+ 1. Record expected type
+ - Needed for IDE, and ControlFlowAnalyzer
+ 2. Call into cehckTypeInternal
+ 3. Run additional type checkers
+ */
@Nullable
public KotlinType checkType(
@Nullable KotlinType expressionType,
@@ -405,6 +456,9 @@
}
@NotNull
+ /*
+ Returns the same dataflow, as in the passed context
+ */
public KotlinTypeInfo createCompileTimeConstantTypeInfo(
@NotNull CompileTimeConstant<?> value,
@NotNull KtExpression expression,
diff --git a/compiler/testData/diagnostics/tests/smartCasts/foobar.kt b/compiler/testData/diagnostics/tests/smartCasts/foobar.kt
new file mode 100644
index 0000000..6213d0a
--- /dev/null
+++ b/compiler/testData/diagnostics/tests/smartCasts/foobar.kt
@@ -0,0 +1,11 @@
+fun bar(x: Array<Int>?, y: Int?) {
+ if (y!! in x!!) {
+ y.inc()
+ x.size
+ } else {
+ y.inc()
+ x.size
+ }
+ y.inc()
+ x.size
+}
\ No newline at end of file
diff --git a/compiler/testData/diagnostics/tests/smartCasts/foobar.txt b/compiler/testData/diagnostics/tests/smartCasts/foobar.txt
new file mode 100644
index 0000000..e0de512
--- /dev/null
+++ b/compiler/testData/diagnostics/tests/smartCasts/foobar.txt
@@ -0,0 +1,11 @@
+package
+
+public fun </*0*/ T : Foo?> text(/*0*/ x: T): kotlin.Unit
+public fun kotlin.Int.bar(): kotlin.Int
+
+public interface Foo {
+ public abstract val length: kotlin.Int
+ public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
+ public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
+ public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
+}
diff --git a/compiler/testData/diagnostics/testsWithStdLib/dontCommitTmp.kt b/compiler/testData/diagnostics/testsWithStdLib/dontCommitTmp.kt
new file mode 100644
index 0000000..ab48e6a
--- /dev/null
+++ b/compiler/testData/diagnostics/testsWithStdLib/dontCommitTmp.kt
@@ -0,0 +1,13 @@
+val flag: Boolean = false
+
+fun test1(x: Int?) {
+ var z: Int? = null
+
+ z = x ?: if (flag) 42 else 239
+
+ val y = x ?: 42
+
+ z.inc()
+ y.inc()
+
+}
\ No newline at end of file
diff --git a/compiler/testData/diagnostics/testsWithStdLib/dontCommitTmp.txt b/compiler/testData/diagnostics/testsWithStdLib/dontCommitTmp.txt
new file mode 100644
index 0000000..8072ce3
--- /dev/null
+++ b/compiler/testData/diagnostics/testsWithStdLib/dontCommitTmp.txt
@@ -0,0 +1,12 @@
+package
+
+public fun test(/*0*/ foo: Foo): kotlin.Unit
+public fun kotlin.Int.bar(): kotlin.Int
+
+public interface Foo {
+ public abstract val length: kotlin.Int
+ public abstract val list: kotlin.collections.List<kotlin.Int>
+ public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
+ public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
+ public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
+}
diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java
index 23b359b..a5d328f 100644
--- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java
+++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java
@@ -20585,6 +20585,12 @@
doTest(fileName);
}
+ @TestMetadata("foobar.kt")
+ public void testFoobar() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/foobar.kt");
+ doTest(fileName);
+ }
+
@TestMetadata("genericIntersection.kt")
public void testGenericIntersection() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/genericIntersection.kt");
diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java
index 2f197b4..440021e 100644
--- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java
+++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java
@@ -55,6 +55,12 @@
doTest(fileName);
}
+ @TestMetadata("dontCommitTmp.kt")
+ public void testDontCommitTmp() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/dontCommitTmp.kt");
+ doTest(fileName);
+ }
+
@TestMetadata("elvisOnJavaList.kt")
public void testElvisOnJavaList() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/elvisOnJavaList.kt");
diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java
index 76cf622..a583904 100644
--- a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java
+++ b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java
@@ -55,6 +55,12 @@
doTest(fileName);
}
+ @TestMetadata("dontCommitTmp.kt")
+ public void testDontCommitTmp() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/dontCommitTmp.kt");
+ doTest(fileName);
+ }
+
@TestMetadata("elvisOnJavaList.kt")
public void testElvisOnJavaList() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/elvisOnJavaList.kt");
diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java
index 1e6c167..80290ce 100644
--- a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java
+++ b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java
@@ -20585,6 +20585,12 @@
doTest(fileName);
}
+ @TestMetadata("foobar.kt")
+ public void testFoobar() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/foobar.kt");
+ doTest(fileName);
+ }
+
@TestMetadata("genericIntersection.kt")
public void testGenericIntersection() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/genericIntersection.kt");