Extended loop data flow analysis was implemented. #KT-6283 Fixed. #KT-6284 Fixed.

A local descendant of JetTypeInfo added to save separately current data flow info and jump point data flow info together with jump opportunity.
Now data flow analysis know about loop bodies that must be executed at least once (do...while, while(true) until the first break/continue).
A set of tests for smart casts in and after loops.
Existing DoWhile and WhileTrue resolve tests corrected in accordance.
This commit is contained in:
Mikhail Glukhikh
2015-04-06 17:52:37 +03:00
parent bb808b5620
commit 8184bccda1
73 changed files with 850 additions and 24 deletions
@@ -83,6 +83,7 @@ public interface BindingContext {
WritableSlice<JetTypeReference, JetType> TYPE = Slices.createSimpleSlice();
WritableSlice<JetExpression, JetType> EXPRESSION_TYPE = new BasicWritableSlice<JetExpression, JetType>(DO_NOTHING);
WritableSlice<JetExpression, Boolean> EXPRESSION_JUMP_OUT_POSSIBLE = new BasicWritableSlice<JetExpression, Boolean>(DO_NOTHING);
WritableSlice<JetExpression, JetType> EXPECTED_EXPRESSION_TYPE = new BasicWritableSlice<JetExpression, JetType>(DO_NOTHING);
WritableSlice<JetFunction, JetType> EXPECTED_RETURN_TYPE = new BasicWritableSlice<JetFunction, JetType>(DO_NOTHING);
WritableSlice<JetExpression, DataFlowInfo> EXPRESSION_DATA_FLOW_INFO = new BasicWritableSlice<JetExpression, DataFlowInfo>(DO_NOTHING);
@@ -25,8 +25,7 @@ import java.util.Map;
import java.util.Set;
public interface DataFlowInfo {
DataFlowInfo EMPTY = new DelegatingDataFlowInfo(null, ImmutableMap.<DataFlowValue, Nullability>of(),
DelegatingDataFlowInfo.newTypeInfo());
DataFlowInfo EMPTY = new DelegatingDataFlowInfo(null, ImmutableMap.<DataFlowValue, Nullability>of(), DelegatingDataFlowInfo.newTypeInfo());
@NotNull
Map<DataFlowValue, Nullability> getCompleteNullabilityInfo();
@@ -22,9 +22,11 @@ import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.JetNodeTypes;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.diagnostics.Errors;
import org.jetbrains.kotlin.lexer.JetTokens;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.resolve.*;
import org.jetbrains.kotlin.resolve.calls.model.MutableDataFlowInfoForArguments;
@@ -100,8 +102,11 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor {
if (elseBranch == null) {
if (thenBranch != null) {
return getTypeInfoWhenOnlyOneBranchIsPresent(
LoopTypeInfo result = getTypeInfoWhenOnlyOneBranchIsPresent(
thenBranch, thenScope, thenInfo, elseInfo, contextWithExpectedType, ifExpression, isStatement);
// If jump was possible, take condition check info as the jump info
return result.isJumpOutPossible() ?
new LoopTypeInfo(result.getType(), result.getDataFlowInfo(), true, conditionDataFlowInfo) : result;
}
return DataFlowUtils.checkImplicitCast(components.builtIns.getUnitType(), ifExpression, contextWithExpectedType,
isStatement, thenInfo.or(elseInfo));
@@ -121,8 +126,12 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor {
Lists.newArrayList(false, false),
contextWithExpectedType, dataFlowInfoForArguments);
JetTypeInfo thenTypeInfo = BindingContextUtils.getRecordedTypeInfo(thenBranch, context.trace.getBindingContext());
JetTypeInfo elseTypeInfo = BindingContextUtils.getRecordedTypeInfo(elseBranch, context.trace.getBindingContext());
BindingContext bindingContext = context.trace.getBindingContext();
JetTypeInfo thenTypeInfo = BindingContextUtils.getRecordedTypeInfo(thenBranch, bindingContext);
JetTypeInfo elseTypeInfo = BindingContextUtils.getRecordedTypeInfo(elseBranch, bindingContext);
Boolean thenLoopBreakContinuePossible = bindingContext.get(BindingContext.EXPRESSION_JUMP_OUT_POSSIBLE, thenBranch);
Boolean elseLoopBreakContinuePossible = bindingContext.get(BindingContext.EXPRESSION_JUMP_OUT_POSSIBLE, elseBranch);
boolean loopBreakContinuePossible = (thenLoopBreakContinuePossible == Boolean.TRUE || elseLoopBreakContinuePossible == Boolean.TRUE);
assert thenTypeInfo != null : "'Then' branch of if expression was not processed: " + ifExpression;
assert elseTypeInfo != null : "'Else' branch of if expression was not processed: " + ifExpression;
@@ -149,11 +158,13 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor {
}
JetType resultType = resolvedCall.getResultingDescriptor().getReturnType();
return DataFlowUtils.checkImplicitCast(resultType, ifExpression, contextWithExpectedType, isStatement, resultDataFlowInfo);
JetTypeInfo result = DataFlowUtils.checkImplicitCast(resultType, ifExpression, contextWithExpectedType, isStatement, resultDataFlowInfo);
// If break or continue was possible, take condition check info as the jump info
return new LoopTypeInfo(result.getType(), result.getDataFlowInfo(), loopBreakContinuePossible, conditionDataFlowInfo);
}
@NotNull
private JetTypeInfo getTypeInfoWhenOnlyOneBranchIsPresent(
private LoopTypeInfo getTypeInfoWhenOnlyOneBranchIsPresent(
@NotNull JetExpression presentBranch,
@NotNull WritableScopeImpl presentScope,
@NotNull DataFlowInfo presentInfo,
@@ -164,7 +175,7 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor {
) {
ExpressionTypingContext newContext = context.replaceDataFlowInfo(presentInfo).replaceExpectedType(NO_EXPECTED_TYPE)
.replaceContextDependency(INDEPENDENT);
JetTypeInfo typeInfo = components.expressionTypingServices.getBlockReturnedTypeWithWritableScope(
LoopTypeInfo typeInfo = components.expressionTypingServices.getBlockReturnedTypeWithWritableScope(
presentScope, Collections.singletonList(presentBranch), CoercionStrategy.NO_COERCION, newContext);
JetType type = typeInfo.getType();
DataFlowInfo dataFlowInfo;
@@ -174,7 +185,8 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor {
dataFlowInfo = typeInfo.getDataFlowInfo().or(otherInfo);
}
JetType typeForIfExpression = DataFlowUtils.checkType(components.builtIns.getUnitType(), ifExpression, context);
return DataFlowUtils.checkImplicitCast(typeForIfExpression, ifExpression, context, isStatement, dataFlowInfo);
JetTypeInfo result = DataFlowUtils.checkImplicitCast(typeForIfExpression, ifExpression, context, isStatement, dataFlowInfo);
return new LoopTypeInfo(result.getType(), result.getDataFlowInfo(), typeInfo.isJumpOutPossible(), typeInfo.getJumpFlowInfo());
}
@Override
@@ -182,26 +194,42 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor {
return visitWhileExpression(expression, context, false);
}
private static boolean isTrueConstant(JetExpression condition) {
return (condition != null && condition.getNode().getElementType() == JetNodeTypes.BOOLEAN_CONSTANT &&
condition.getNode().findChildByType(JetTokens.TRUE_KEYWORD) != null);
}
public JetTypeInfo visitWhileExpression(JetWhileExpression expression, ExpressionTypingContext contextWithExpectedType, boolean isStatement) {
if (!isStatement) return DataFlowUtils.illegalStatementType(expression, contextWithExpectedType, facade);
ExpressionTypingContext context = contextWithExpectedType.replaceExpectedType(NO_EXPECTED_TYPE).replaceContextDependency(
INDEPENDENT);
JetExpression condition = expression.getCondition();
// Extract data flow info from condition itself without taking value into account
DataFlowInfo dataFlowInfo = checkCondition(context.scope, condition, context);
JetExpression body = expression.getBody();
LoopTypeInfo bodyTypeInfo = null;
if (body != null) {
WritableScopeImpl scopeToExtend = newWritableScopeImpl(context, "Scope extended in while's condition");
DataFlowInfo conditionInfo = DataFlowUtils.extractDataFlowInfoFromCondition(condition, true, context).and(dataFlowInfo);
components.expressionTypingServices.getBlockReturnedTypeWithWritableScope(
bodyTypeInfo = components.expressionTypingServices.getBlockReturnedTypeWithWritableScope(
scopeToExtend, Collections.singletonList(body),
CoercionStrategy.NO_COERCION, context.replaceDataFlowInfo(conditionInfo));
}
// Condition is false at this point only if there is no jumps outside
if (!containsJumpOutOfLoop(expression, context)) {
dataFlowInfo = DataFlowUtils.extractDataFlowInfoFromCondition(condition, false, context).and(dataFlowInfo);
}
// Special case: while (true)
// In this case we must record data flow information at the nearest break / continue and
// .and it with entrance data flow information, because while body until break is executed at least once in this case
// See KT-6284
if (bodyTypeInfo != null && isTrueConstant(condition)) {
dataFlowInfo = dataFlowInfo.and(bodyTypeInfo.getJumpFlowInfo());
}
return DataFlowUtils.checkType(components.builtIns.getUnitType(), expression, contextWithExpectedType, dataFlowInfo);
}
@@ -248,6 +276,7 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor {
public JetTypeInfo visitDoWhileExpression(@NotNull JetDoWhileExpression expression, ExpressionTypingContext context) {
return visitDoWhileExpression(expression, context, false);
}
public JetTypeInfo visitDoWhileExpression(JetDoWhileExpression expression, ExpressionTypingContext contextWithExpectedType, boolean isStatement) {
if (!isStatement) return DataFlowUtils.illegalStatementType(expression, contextWithExpectedType, facade);
@@ -255,13 +284,14 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor {
contextWithExpectedType.replaceExpectedType(NO_EXPECTED_TYPE).replaceContextDependency(INDEPENDENT);
JetExpression body = expression.getBody();
JetScope conditionScope = context.scope;
LoopTypeInfo bodyTypeInfo = null;
if (body instanceof JetFunctionLiteralExpression) {
JetFunctionLiteralExpression function = (JetFunctionLiteralExpression) body;
JetFunctionLiteral functionLiteral = function.getFunctionLiteral();
if (!functionLiteral.hasParameterSpecification()) {
WritableScope writableScope = newWritableScopeImpl(context, "do..while body scope");
conditionScope = writableScope;
components.expressionTypingServices.getBlockReturnedTypeWithWritableScope(
bodyTypeInfo = components.expressionTypingServices.getBlockReturnedTypeWithWritableScope(
writableScope, functionLiteral.getBodyExpression().getStatements(), CoercionStrategy.NO_COERCION, context);
context.trace.record(BindingContext.BLOCK, function);
}
@@ -279,18 +309,25 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor {
else {
block = Collections.<JetElement>singletonList(body);
}
components.expressionTypingServices.getBlockReturnedTypeWithWritableScope(
bodyTypeInfo = components.expressionTypingServices.getBlockReturnedTypeWithWritableScope(
writableScope, block, CoercionStrategy.NO_COERCION, context);
}
JetExpression condition = expression.getCondition();
DataFlowInfo conditionDataFlowInfo = checkCondition(conditionScope, condition, context);
DataFlowInfo dataFlowInfo;
// Without jumps out, condition is entered and false, with jumps out, we know nothing about it
if (!containsJumpOutOfLoop(expression, context)) {
dataFlowInfo = DataFlowUtils.extractDataFlowInfoFromCondition(condition, false, context).and(conditionDataFlowInfo);
}
else {
dataFlowInfo = context.dataFlowInfo;
}
// Here we must record data flow information at the end of the body (or at the first jump, to be precise) and
// .and it with entrance data flow information, because do-while body is executed at least once
// See KT-6283
if (bodyTypeInfo != null) {
dataFlowInfo = dataFlowInfo.and(bodyTypeInfo.getJumpFlowInfo());
}
return DataFlowUtils.checkType(components.builtIns.getUnitType(), expression, contextWithExpectedType, dataFlowInfo);
}
@@ -510,15 +547,17 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor {
}
@Override
public JetTypeInfo visitBreakExpression(@NotNull JetBreakExpression expression, ExpressionTypingContext context) {
public LoopTypeInfo visitBreakExpression(@NotNull JetBreakExpression expression, ExpressionTypingContext context) {
LabelResolver.INSTANCE.resolveControlLabel(expression, context);
return DataFlowUtils.checkType(components.builtIns.getNothingType(), expression, context, context.dataFlowInfo);
JetTypeInfo result = DataFlowUtils.checkType(components.builtIns.getNothingType(), expression, context, context.dataFlowInfo);
return new LoopTypeInfo(result.getType(), result.getDataFlowInfo(), true, result.getDataFlowInfo());
}
@Override
public JetTypeInfo visitContinueExpression(@NotNull JetContinueExpression expression, ExpressionTypingContext context) {
public LoopTypeInfo visitContinueExpression(@NotNull JetContinueExpression expression, ExpressionTypingContext context) {
LabelResolver.INSTANCE.resolveControlLabel(expression, context);
return DataFlowUtils.checkType(components.builtIns.getNothingType(), expression, context, context.dataFlowInfo);
JetTypeInfo result = DataFlowUtils.checkType(components.builtIns.getNothingType(), expression, context, context.dataFlowInfo);
return new LoopTypeInfo(result.getType(), result.getDataFlowInfo(), true, result.getDataFlowInfo());
}
@NotNull
@@ -305,22 +305,26 @@ public class ExpressionTypingServices {
/**
* Visits block statements propagating data flow information from the first to the last.
* Determines block returned type and data flow information at the end of the block.
* Determines block returned type and data flow information at the end of the block AND
* at the nearest jump point from the block beginning.
*/
/*package*/ JetTypeInfo getBlockReturnedTypeWithWritableScope(
/*package*/ LoopTypeInfo getBlockReturnedTypeWithWritableScope(
@NotNull WritableScope scope,
@NotNull List<? extends JetElement> block,
@NotNull CoercionStrategy coercionStrategyForLastExpression,
@NotNull ExpressionTypingContext context
) {
if (block.isEmpty()) {
return JetTypeInfo.create(builtIns.getUnitType(), context.dataFlowInfo);
return new LoopTypeInfo(builtIns.getUnitType(), context.dataFlowInfo);
}
ExpressionTypingInternals blockLevelVisitor = ExpressionTypingVisitorDispatcher.createForBlock(expressionTypingComponents, scope);
ExpressionTypingContext newContext = context.replaceScope(scope).replaceExpectedType(NO_EXPECTED_TYPE);
JetTypeInfo result = JetTypeInfo.create(null, context.dataFlowInfo);
// Jump point data flow info
DataFlowInfo beforeJumpInfo = newContext.dataFlowInfo;
boolean jumpOutPossible = false;
for (Iterator<? extends JetElement> iterator = block.iterator(); iterator.hasNext(); ) {
JetElement statement = iterator.next();
if (!(statement instanceof JetExpression)) {
@@ -338,12 +342,24 @@ public class ExpressionTypingServices {
}
DataFlowInfo newDataFlowInfo = result.getDataFlowInfo();
// If jump is not possible, we take new data flow info before jump
if (!jumpOutPossible) {
if (result instanceof LoopTypeInfo) {
LoopTypeInfo loopTypeInfo = (LoopTypeInfo) result;
beforeJumpInfo = loopTypeInfo.getJumpFlowInfo();
jumpOutPossible = loopTypeInfo.isJumpOutPossible();
}
else {
beforeJumpInfo = newDataFlowInfo;
}
}
if (newDataFlowInfo != context.dataFlowInfo) {
newContext = newContext.replaceDataFlowInfo(newDataFlowInfo);
// We take current data flow info if jump there is not possible
}
blockLevelVisitor = ExpressionTypingVisitorDispatcher.createForBlock(expressionTypingComponents, scope);
}
return result;
return new LoopTypeInfo(result.getType(), result.getDataFlowInfo(), jumpOutPossible, beforeJumpInfo);
}
private JetTypeInfo getTypeOfLastExpressionInBlock(
@@ -28,6 +28,7 @@ import org.jetbrains.kotlin.resolve.BindingContextUtils;
import org.jetbrains.kotlin.resolve.scopes.WritableScope;
import org.jetbrains.kotlin.types.DeferredType;
import org.jetbrains.kotlin.types.ErrorUtils;
import org.jetbrains.kotlin.types.JetType;
import org.jetbrains.kotlin.types.JetTypeInfo;
import org.jetbrains.kotlin.util.ReenteringLazyValueComputationException;
import org.jetbrains.kotlin.utils.KotlinFrontEndException;
@@ -140,8 +141,16 @@ public class ExpressionTypingVisitorDispatcher extends JetVisitor<JetTypeInfo, E
result = expression.accept(visitor, context);
// Some recursive definitions (object expressions) must put their types in the cache manually:
if (context.trace.get(BindingContext.PROCESSED, expression)) {
return JetTypeInfo.create(context.trace.getBindingContext().get(BindingContext.EXPRESSION_TYPE, expression),
result.getDataFlowInfo());
JetType type = context.trace.getBindingContext().get(BindingContext.EXPRESSION_TYPE, expression);
if (result instanceof LoopTypeInfo) {
LoopTypeInfo loopTypeInfo = (LoopTypeInfo)result;
return new LoopTypeInfo(type,
loopTypeInfo.getDataFlowInfo(),
loopTypeInfo.isJumpOutPossible(),
loopTypeInfo.getJumpFlowInfo());
} else {
return JetTypeInfo.create(type, result.getDataFlowInfo());
}
}
if (result.getType() instanceof DeferredType) {
@@ -150,6 +159,12 @@ public class ExpressionTypingVisitorDispatcher extends JetVisitor<JetTypeInfo, E
if (result.getType() != null) {
context.trace.record(BindingContext.EXPRESSION_TYPE, expression, result.getType());
}
if (result instanceof LoopTypeInfo) {
LoopTypeInfo loopTypeInfo = (LoopTypeInfo)result;
if (loopTypeInfo.isJumpOutPossible()) {
context.trace.record(BindingContext.EXPRESSION_JUMP_OUT_POSSIBLE, expression, true);
}
}
}
catch (ReenteringLazyValueComputationException e) {
@@ -0,0 +1,67 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.types.expressions;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo;
import org.jetbrains.kotlin.types.JetType;
import org.jetbrains.kotlin.types.JetTypeInfo;
/**
* A local descendant of JetTypeInfo. Stores simultaneously current data flow info
* and jump point data flow info, together with information about possible jump inside. For example:
* do {
* x!!.foo()
* if (bar()) break;
* y!!.gav()
* } while (bis())
* At the end current data flow info is x != null && y != null, but jump data flow info is x != null only.
* Jump will be possible
*/
/*package*/ class LoopTypeInfo extends JetTypeInfo {
private final DataFlowInfo jumpFlowInfo;
private final boolean jumpOutPossible;
LoopTypeInfo(@Nullable JetType type, @NotNull DataFlowInfo dataFlowInfo) {
this(type, dataFlowInfo, false, dataFlowInfo);
}
LoopTypeInfo(
@Nullable JetType type, @NotNull DataFlowInfo dataFlowInfo,
boolean jumpOutPossible, @NotNull DataFlowInfo jumpFlowInfo
) {
super(type, dataFlowInfo);
this.jumpFlowInfo = jumpFlowInfo;
this.jumpOutPossible = jumpOutPossible;
}
@NotNull
DataFlowInfo getJumpFlowInfo() {
return jumpFlowInfo;
}
/**
* Returns true if jump to the end of the loop is possible inside the considered expression.
* Break and continue are both counted as possible jump to the end, but return is not.
*/
boolean isJumpOutPossible() {
return jumpOutPossible;
}
}
@@ -0,0 +1,11 @@
fun x(): Boolean { return true }
public fun foo(p: String?): Int {
// See KT-6283
do {
p!!.length()
} while (!x())
// Do-while loop is executed at least once, so
// p should be not null here
return <!DEBUG_INFO_SMARTCAST!>p<!>.length()
}
@@ -0,0 +1,4 @@
package
public fun foo(/*0*/ p: kotlin.String?): kotlin.Int
internal fun x(): kotlin.Boolean
@@ -0,0 +1,11 @@
fun x(): Boolean { return true }
public fun foo(p: String?): Int {
// See KT-6283
do {
p!!.length()
if (p == "abc") break
} while (!x())
// p should be smart casted despite of break
return <!DEBUG_INFO_SMARTCAST!>p<!>.length()
}
@@ -0,0 +1,4 @@
package
public fun foo(/*0*/ p: kotlin.String?): kotlin.Int
internal fun x(): kotlin.Boolean
@@ -0,0 +1,11 @@
fun x(): Boolean { return true }
public fun foo(p: String?): Int {
// See KT-6283
do {
p!!.length()
if (p == "abc") continue
} while (!x())
// p should be smart casted despite of continue
return <!DEBUG_INFO_SMARTCAST!>p<!>.length()
}
@@ -0,0 +1,4 @@
package
public fun foo(/*0*/ p: kotlin.String?): kotlin.Int
internal fun x(): kotlin.Boolean
@@ -0,0 +1,13 @@
fun x(): Boolean { return true }
fun y(): Boolean { return false }
public fun foo(p: String?): Int {
do {
if (y()) break
// We do not always reach this statement
p!!.length()
} while (!x())
// Here we have do while loop but p is still nullable due to break before
return p<!UNSAFE_CALL!>.<!>length()
}
@@ -0,0 +1,5 @@
package
public fun foo(/*0*/ p: kotlin.String?): kotlin.Int
internal fun x(): kotlin.Boolean
internal fun y(): kotlin.Boolean
@@ -0,0 +1,13 @@
fun x(): Boolean { return true }
fun y(): Boolean { return false }
public fun foo(p: String?): Int {
do {
if (y()) continue
// We do not always reach this statement
p!!.length()
} while (!x())
// Here we have do while loop but p is still nullable due to continue before
return p<!UNSAFE_CALL!>.<!>length()
}
@@ -0,0 +1,5 @@
package
public fun foo(/*0*/ p: kotlin.String?): kotlin.Int
internal fun x(): kotlin.Boolean
internal fun y(): kotlin.Boolean
@@ -0,0 +1,5 @@
fun foo(s: String?): Int {
do {
} while (s!!.length() > 0)
return <!DEBUG_INFO_SMARTCAST!>s<!>.length()
}
@@ -0,0 +1,3 @@
package
internal fun foo(/*0*/ s: kotlin.String?): kotlin.Int
@@ -0,0 +1,9 @@
fun bar(): Boolean { return true }
fun foo(s: String?): Int {
do {
if (bar()) break
} while (s!!.length() > 0)
// This call is unsafe due to break
return s<!UNSAFE_CALL!>.<!>length()
}
@@ -0,0 +1,4 @@
package
internal fun bar(): kotlin.Boolean
internal fun foo(/*0*/ s: kotlin.String?): kotlin.Int
@@ -0,0 +1,10 @@
fun x(): Boolean { return true }
public fun foo(p: String?): Int {
// Exotic variant with unused literal
do <!UNUSED_FUNCTION_LITERAL!>{ ->
p!!.length()
}<!> while (!x())
// Literal is not called so p.length() is unsafe
return p<!UNSAFE_CALL!>.<!>length()
}
@@ -0,0 +1,4 @@
package
public fun foo(/*0*/ p: kotlin.String?): kotlin.Int
internal fun x(): kotlin.Boolean
@@ -0,0 +1,10 @@
fun x(): Boolean { return true }
public fun foo(p: String?): Int {
// See KT-6283
do {
if (p != null) break
} while (!x())
// p can be null despite of the break
return p<!UNSAFE_CALL!>.<!>length()
}
@@ -0,0 +1,4 @@
package
public fun foo(/*0*/ p: kotlin.String?): kotlin.Int
internal fun x(): kotlin.Boolean
@@ -0,0 +1,5 @@
fun foo(s: String?): Int {
do {
} while (s==null)
return <!DEBUG_INFO_SMARTCAST!>s<!>.length()
}
@@ -0,0 +1,3 @@
package
internal fun foo(/*0*/ s: kotlin.String?): kotlin.Int
@@ -0,0 +1,9 @@
fun bar(): Boolean { return true }
fun foo(s: String?): Int {
do {
if (bar()) break
} while (s==null)
// This call is unsafe due to break
return s<!UNSAFE_CALL!>.<!>length()
}
@@ -0,0 +1,4 @@
package
internal fun bar(): kotlin.Boolean
internal fun foo(/*0*/ s: kotlin.String?): kotlin.Int
@@ -0,0 +1,12 @@
public fun foo(p: String?, y: String?): Int {
do {
// After the check, smart cast should work
if (y == null) {
"null".toString()
break
}
<!DEBUG_INFO_SMARTCAST!>y<!>.length()
p!!.length()
} while (true)
return y?.length() ?: -1
}
@@ -0,0 +1,3 @@
package
public fun foo(/*0*/ p: kotlin.String?, /*1*/ y: kotlin.String?): kotlin.Int
@@ -0,0 +1,15 @@
public fun foo(x: String?, y: String?): Int {
do {
// After the check, smart cast should work
if (x != null) {
if (x == "abc") break
y!!.length()
} else {
y!!.length()
}
// y!! in both branches
<!DEBUG_INFO_SMARTCAST!>y<!>.length()
} while (true)
// break is possible before so !! is necessary
return y!!.length()
}
@@ -0,0 +1,3 @@
package
public fun foo(/*0*/ x: kotlin.String?, /*1*/ y: kotlin.String?): kotlin.Int
@@ -0,0 +1,9 @@
public fun foo(p: String?, y: String?): Int {
do {
// After the check, smart cast should work
if (y == null) break
<!DEBUG_INFO_SMARTCAST!>y<!>.length()
p!!.length()
} while (true)
return y?.length() ?: -1
}
@@ -0,0 +1,3 @@
package
public fun foo(/*0*/ p: kotlin.String?, /*1*/ y: kotlin.String?): kotlin.Int
@@ -0,0 +1,13 @@
fun x(): Boolean { return true }
public fun foo(p: String?, r: String?): Int {
do {
do {
p!!.length()
} while (r == null)
} while (!x())
// Auto cast possible
<!DEBUG_INFO_SMARTCAST!>r<!>.length()
// Auto cast possible
return <!DEBUG_INFO_SMARTCAST!>p<!>.length()
}
@@ -0,0 +1,4 @@
package
public fun foo(/*0*/ p: kotlin.String?, /*1*/ r: kotlin.String?): kotlin.Int
internal fun x(): kotlin.Boolean
@@ -0,0 +1,14 @@
fun x(): Boolean { return true }
public fun foo(p: String?, r: String?): Int {
@outer do {
do {
p!!.length()
if (!x()) continue@outer
} while (r == null)
} while (!x())
// Auto cast NOT possible due to long continue
r<!UNSAFE_CALL!>.<!>length()
// Auto cast possible
return <!DEBUG_INFO_SMARTCAST!>p<!>.length()
}
@@ -0,0 +1,4 @@
package
public fun foo(/*0*/ p: kotlin.String?, /*1*/ r: kotlin.String?): kotlin.Int
internal fun x(): kotlin.Boolean
@@ -0,0 +1,17 @@
fun x(): Boolean { return true }
public fun foo(p: String?, r: String?, q: String?): Int {
while(true) {
q!!.length()
do {
do {
p!!.length()
} while (!x())
} while (r == null)
if (!x()) break
}
// Smart cast is possible everywhere
<!DEBUG_INFO_SMARTCAST!>r<!>.length()
<!DEBUG_INFO_SMARTCAST!>q<!>.length()
return <!DEBUG_INFO_SMARTCAST!>p<!>.length()
}
@@ -0,0 +1,4 @@
package
public fun foo(/*0*/ p: kotlin.String?, /*1*/ r: kotlin.String?, /*2*/ q: kotlin.String?): kotlin.Int
internal fun x(): kotlin.Boolean
@@ -0,0 +1,15 @@
fun x(): Boolean { return true }
public fun foo(p: String?, r: String?, q: String?): Int {
while(true) {
q!!.length()
do {
p!!.length()
} while (r == null)
if (!x()) break
}
// Smart cast is possible everywhere
<!DEBUG_INFO_SMARTCAST!>r<!>.length()
<!DEBUG_INFO_SMARTCAST!>q<!>.length()
return <!DEBUG_INFO_SMARTCAST!>p<!>.length()
}
@@ -0,0 +1,4 @@
package
public fun foo(/*0*/ p: kotlin.String?, /*1*/ r: kotlin.String?, /*2*/ q: kotlin.String?): kotlin.Int
internal fun x(): kotlin.Boolean
@@ -0,0 +1,18 @@
fun x(): Boolean { return true }
public fun foo(p: String?, r: String?, q: String?): Int {
while(true) {
q!!.length()
do {
while(true) {
p!!.length()
if (x()) break
}
} while (r == null)
if (!x()) break
}
// Smart cast is possible everywhere
<!DEBUG_INFO_SMARTCAST!>r<!>.length()
<!DEBUG_INFO_SMARTCAST!>q<!>.length()
return <!DEBUG_INFO_SMARTCAST!>p<!>.length()
}
@@ -0,0 +1,4 @@
package
public fun foo(/*0*/ p: kotlin.String?, /*1*/ r: kotlin.String?, /*2*/ q: kotlin.String?): kotlin.Int
internal fun x(): kotlin.Boolean
@@ -0,0 +1,20 @@
fun x(p: String): Boolean { return p == "abc" }
public fun foo(p: String?, r: String?, q: String?): Int {
while(true) {
q!!.length()
@loop do {
while(true) {
p!!.length()
if (x(<!DEBUG_INFO_SMARTCAST!>p<!>)) break@loop
if (x(<!DEBUG_INFO_SMARTCAST!>q<!>)) break
}
} while (r == null)
if (!x(<!DEBUG_INFO_SMARTCAST!>p<!>)) break
}
// Long break allows r == null
r<!UNSAFE_CALL!>.<!>length()
// Smart cast is possible
<!DEBUG_INFO_SMARTCAST!>q<!>.length()
return <!DEBUG_INFO_SMARTCAST!>p<!>.length()
}
@@ -0,0 +1,4 @@
package
public fun foo(/*0*/ p: kotlin.String?, /*1*/ r: kotlin.String?, /*2*/ q: kotlin.String?): kotlin.Int
internal fun x(/*0*/ p: kotlin.String): kotlin.Boolean
@@ -0,0 +1,19 @@
fun x(): Boolean { return true }
public fun foo(p: String?, r: String?, q: String?): Int {
@outer while(true) {
q!!.length()
do {
if (x()) continue@outer
do {
p!!.length()
} while (!x())
} while (r == null)
if (!x()) break
}
// Smart cast is possible only for q
<!DEBUG_INFO_SMARTCAST!>q<!>.length()
// But not possible for the others
r<!UNSAFE_CALL!>.<!>length()
return p<!UNSAFE_CALL!>.<!>length()
}
@@ -0,0 +1,4 @@
package
public fun foo(/*0*/ p: kotlin.String?, /*1*/ r: kotlin.String?, /*2*/ q: kotlin.String?): kotlin.Int
internal fun x(): kotlin.Boolean
@@ -0,0 +1,9 @@
public fun foo(p: String?, y: String?): Int {
do {
// After this !!, y. should be smartcasted in loop as well as outside
y!!.length()
if (p == null) break
<!DEBUG_INFO_SMARTCAST!>y<!>.length()
} while (true)
return <!DEBUG_INFO_SMARTCAST!>y<!>.length()
}
@@ -0,0 +1,3 @@
package
public fun foo(/*0*/ p: kotlin.String?, /*1*/ y: kotlin.String?): kotlin.Int
@@ -0,0 +1,6 @@
fun foo(s: String?): Int {
while (s!!.length() > 0) {
<!DEBUG_INFO_SMARTCAST!>s<!>.length()
}
return <!DEBUG_INFO_SMARTCAST!>s<!>.length()
}
@@ -0,0 +1,3 @@
package
internal fun foo(/*0*/ s: kotlin.String?): kotlin.Int
@@ -0,0 +1,9 @@
fun bar(): Boolean { return true }
fun foo(s: String?): Int {
while (s!!.length() > 0) {
<!DEBUG_INFO_SMARTCAST!>s<!>.length()
if (bar()) break
}
return <!DEBUG_INFO_SMARTCAST!>s<!>.length()
}
@@ -0,0 +1,4 @@
package
internal fun bar(): kotlin.Boolean
internal fun foo(/*0*/ s: kotlin.String?): kotlin.Int
@@ -0,0 +1,5 @@
fun foo(s: String?): Int {
while (s==null) {
}
return <!DEBUG_INFO_SMARTCAST!>s<!>.length()
}
@@ -0,0 +1,3 @@
package
internal fun foo(/*0*/ s: kotlin.String?): kotlin.Int
@@ -0,0 +1,9 @@
fun bar(): Boolean { return true }
fun foo(s: String?): Int {
while (s==null) {
if (bar()) break
}
// Call is unsafe due to break
return s<!UNSAFE_CALL!>.<!>length()
}
@@ -0,0 +1,4 @@
package
internal fun bar(): kotlin.Boolean
internal fun foo(/*0*/ s: kotlin.String?): kotlin.Int
@@ -0,0 +1,10 @@
fun x(): Boolean { return true }
public fun foo(p: String?): Int {
while(x()) {
p!!.length()
if (x()) break
}
// p is nullable because it's possible loop body is not executed at all
return p<!UNSAFE_CALL!>.<!>length()
}
@@ -0,0 +1,4 @@
package
public fun foo(/*0*/ p: kotlin.String?): kotlin.Int
internal fun x(): kotlin.Boolean
@@ -0,0 +1,11 @@
fun x(): Boolean { return true }
public fun foo(p: String?): Int {
// Like whileTrue but 2 == 2 is in use
while(2 == 2) {
p!!.length()
if (x()) break
}
// Smart cast should not work in this case, see KT-6284
return p<!UNSAFE_CALL!>.<!>length()
}
@@ -0,0 +1,4 @@
package
public fun foo(/*0*/ p: kotlin.String?): kotlin.Int
internal fun x(): kotlin.Boolean
@@ -0,0 +1,12 @@
fun x(): Boolean { return true }
public fun foo(p: String?): Int {
// KT-6284
while(true) {
p!!.length()
if (x()) break
}
// while (true) loop body is executed at least once
// so p is not null here
return <!DEBUG_INFO_SMARTCAST!>p<!>.length()
}
@@ -0,0 +1,4 @@
package
public fun foo(/*0*/ p: kotlin.String?): kotlin.Int
internal fun x(): kotlin.Boolean
@@ -0,0 +1,12 @@
fun x(): Boolean { return true }
public fun foo(p: String?): Int {
while(true) {
if (x()) break
if (p==null) return -1
// p is not null
<!DEBUG_INFO_SMARTCAST!>p<!>.length()
}
// p can be null because break is earlier than return
return p<!UNSAFE_CALL!>.<!>length()
}
@@ -0,0 +1,4 @@
package
public fun foo(/*0*/ p: kotlin.String?): kotlin.Int
internal fun x(): kotlin.Boolean
@@ -0,0 +1,11 @@
fun x(): Boolean { return true }
public fun foo(p: String?): Int {
while(true) {
if (x()) break
// We do not always reach this statement
p!!.length()
}
// Here we have while (true) loop but p is nullable due to break before
return p<!UNSAFE_CALL!>.<!>length()
}
@@ -0,0 +1,4 @@
package
public fun foo(/*0*/ p: kotlin.String?): kotlin.Int
internal fun x(): kotlin.Boolean
@@ -0,0 +1,13 @@
fun x(): Boolean { return true }
public fun foo(p: String?): Int {
while(true) {
if (p==null) return -1
if (x()) break
// p is not null
<!DEBUG_INFO_SMARTCAST!>p<!>.length()
}
// while (true) loop body with return is executed at least once
// so p is not null here
return <!DEBUG_INFO_SMARTCAST!>p<!>.length()
}
@@ -0,0 +1,4 @@
package
public fun foo(/*0*/ p: kotlin.String?): kotlin.Int
internal fun x(): kotlin.Boolean
@@ -11016,6 +11016,207 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest {
}
}
@TestMetadata("compiler/testData/diagnostics/tests/smartCasts/loops")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Loops extends AbstractJetDiagnosticsTest {
public void testAllFilesPresentInLoops() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/smartCasts/loops"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("doWhile.kt")
public void testDoWhile() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/doWhile.kt");
doTest(fileName);
}
@TestMetadata("doWhileBreak.kt")
public void testDoWhileBreak() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/doWhileBreak.kt");
doTest(fileName);
}
@TestMetadata("doWhileContinue.kt")
public void testDoWhileContinue() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/doWhileContinue.kt");
doTest(fileName);
}
@TestMetadata("doWhileEarlyBreak.kt")
public void testDoWhileEarlyBreak() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/doWhileEarlyBreak.kt");
doTest(fileName);
}
@TestMetadata("doWhileEarlyContinue.kt")
public void testDoWhileEarlyContinue() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/doWhileEarlyContinue.kt");
doTest(fileName);
}
@TestMetadata("doWhileInCondition.kt")
public void testDoWhileInCondition() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/doWhileInCondition.kt");
doTest(fileName);
}
@TestMetadata("doWhileInConditionWithBreak.kt")
public void testDoWhileInConditionWithBreak() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/doWhileInConditionWithBreak.kt");
doTest(fileName);
}
@TestMetadata("doWhileLiteral.kt")
public void testDoWhileLiteral() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/doWhileLiteral.kt");
doTest(fileName);
}
@TestMetadata("doWhileNotNullBreak.kt")
public void testDoWhileNotNullBreak() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/doWhileNotNullBreak.kt");
doTest(fileName);
}
@TestMetadata("doWhileNull.kt")
public void testDoWhileNull() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/doWhileNull.kt");
doTest(fileName);
}
@TestMetadata("doWhileNullWithBreak.kt")
public void testDoWhileNullWithBreak() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/doWhileNullWithBreak.kt");
doTest(fileName);
}
@TestMetadata("ifBlockInsideDoWhile.kt")
public void testIfBlockInsideDoWhile() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/ifBlockInsideDoWhile.kt");
doTest(fileName);
}
@TestMetadata("ifElseBlockInsideDoWhile.kt")
public void testIfElseBlockInsideDoWhile() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/ifElseBlockInsideDoWhile.kt");
doTest(fileName);
}
@TestMetadata("ifInsideDoWhile.kt")
public void testIfInsideDoWhile() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/ifInsideDoWhile.kt");
doTest(fileName);
}
@TestMetadata("nestedDoWhile.kt")
public void testNestedDoWhile() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/nestedDoWhile.kt");
doTest(fileName);
}
@TestMetadata("nestedDoWhileWithLongContinue.kt")
public void testNestedDoWhileWithLongContinue() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/nestedDoWhileWithLongContinue.kt");
doTest(fileName);
}
@TestMetadata("nestedLoops.kt")
public void testNestedLoops() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/nestedLoops.kt");
doTest(fileName);
}
@TestMetadata("nestedLoopsShort.kt")
public void testNestedLoopsShort() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/nestedLoopsShort.kt");
doTest(fileName);
}
@TestMetadata("nestedLoopsWithBreak.kt")
public void testNestedLoopsWithBreak() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/nestedLoopsWithBreak.kt");
doTest(fileName);
}
@TestMetadata("nestedLoopsWithLongBreak.kt")
public void testNestedLoopsWithLongBreak() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/nestedLoopsWithLongBreak.kt");
doTest(fileName);
}
@TestMetadata("nestedLoopsWithLongContinue.kt")
public void testNestedLoopsWithLongContinue() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/nestedLoopsWithLongContinue.kt");
doTest(fileName);
}
@TestMetadata("useInsideDoWhile.kt")
public void testUseInsideDoWhile() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/useInsideDoWhile.kt");
doTest(fileName);
}
@TestMetadata("whileInCondition.kt")
public void testWhileInCondition() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/whileInCondition.kt");
doTest(fileName);
}
@TestMetadata("whileInConditionWithBreak.kt")
public void testWhileInConditionWithBreak() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/whileInConditionWithBreak.kt");
doTest(fileName);
}
@TestMetadata("whileNull.kt")
public void testWhileNull() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/whileNull.kt");
doTest(fileName);
}
@TestMetadata("whileNullWithBreak.kt")
public void testWhileNullWithBreak() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/whileNullWithBreak.kt");
doTest(fileName);
}
@TestMetadata("whileSimple.kt")
public void testWhileSimple() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/whileSimple.kt");
doTest(fileName);
}
@TestMetadata("whileTrivial.kt")
public void testWhileTrivial() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/whileTrivial.kt");
doTest(fileName);
}
@TestMetadata("whileTrue.kt")
public void testWhileTrue() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/whileTrue.kt");
doTest(fileName);
}
@TestMetadata("whileTrueBreakReturn.kt")
public void testWhileTrueBreakReturn() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/whileTrueBreakReturn.kt");
doTest(fileName);
}
@TestMetadata("whileTrueEarlyBreak.kt")
public void testWhileTrueEarlyBreak() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/whileTrueEarlyBreak.kt");
doTest(fileName);
}
@TestMetadata("whileTrueReturn.kt")
public void testWhileTrueReturn() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/loops/whileTrueReturn.kt");
doTest(fileName);
}
}
@TestMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -1,4 +1,4 @@
Resolve target: value-parameter val p: kotlin.Any?
Resolve target: value-parameter val p: kotlin.Any? smart-cast to kotlin.Any
----------------------------------------------
fun foo(p: Any?) {
do {
@@ -1,4 +1,4 @@
Resolve target: value-parameter val p: kotlin.Any?
Resolve target: value-parameter val p: kotlin.Any? smart-cast to kotlin.Any
----------------------------------------------
fun x(): Boolean{}