Add support local return from lambda with implicit unit type.

This commit is contained in:
Stanislav Erokhin
2015-02-17 17:08:08 +03:00
parent 57fd5cc84c
commit c6f8a1cfcc
15 changed files with 188 additions and 43 deletions
@@ -303,54 +303,65 @@ public class ClosureExpressionsTypingVisitor extends ExpressionTypingVisitor {
: (expectedReturnType != null ? expectedReturnType : NO_EXPECTED_TYPE);
ExpressionTypingContext newContext = context.replaceScope(functionInnerScope).replaceExpectedType(expectedType);
context.trace.record(EXPECTED_RETURN_TYPE, functionLiteral, expectedType);
JetType typeOfBodyExpression = // needed for error reporting
components.expressionTypingServices.getBlockReturnedType(bodyExpression, COERCION_TO_UNIT, newContext).getType();
JetType typeOfBodyExpression = components.expressionTypingServices.getBlockReturnedType(bodyExpression, COERCION_TO_UNIT, newContext).getType();
if (declaredReturnType != null) {
return declaredReturnType;
}
else {
return computeReturnTypeBasedOnReturnExpressions(functionLiteral, context, typeOfBodyExpression);
}
}
@Nullable
private JetType computeReturnTypeBasedOnReturnExpressions(
@NotNull JetFunctionLiteral functionLiteral,
@NotNull ExpressionTypingContext context,
@Nullable JetType typeOfBodyExpression
) {
List<JetType> returnedExpressionTypes = Lists.newArrayList();
boolean hasEmptyReturn = false;
Collection<JetReturnExpression> returnExpressions = collectReturns(functionLiteral, context.trace);
for (JetReturnExpression returnExpression : returnExpressions) {
JetExpression returnedExpression = returnExpression.getReturnedExpression();
if (returnedExpression == null) {
hasEmptyReturn = true;
}
else {
// the type should have been computed by getBlockReturnedType() above, but can be null, if returnExpression contains some error
ContainerUtil.addIfNotNull(returnedExpressionTypes, context.trace.get(EXPRESSION_TYPE, returnedExpression));
}
}
if (hasEmptyReturn) {
for (JetReturnExpression returnExpression : returnExpressions) {
JetExpression returnedExpression = returnExpression.getReturnedExpression();
if (returnedExpression != null) {
JetType type = context.trace.get(EXPRESSION_TYPE, returnedExpression);
if (type == null || !KotlinBuiltIns.isUnit(type)) {
context.trace.report(RETURN_TYPE_MISMATCH.on(returnedExpression, components.builtIns.getUnitType()));
}
}
}
return components.builtIns.getUnitType();
}
List<JetType> returnedExpressionTypes = Lists.newArrayList(getTypesOfLocallyReturnedExpressions(
functionLiteral, context.trace, collectReturns(bodyExpression)));
ContainerUtil.addIfNotNull(returnedExpressionTypes, typeOfBodyExpression);
if (declaredReturnType != null) return declaredReturnType;
if (returnedExpressionTypes.isEmpty()) return null;
return CommonSupertypes.commonSupertype(returnedExpressionTypes);
}
private List<JetType> getTypesOfLocallyReturnedExpressions(
final JetFunctionLiteral functionLiteral,
final BindingTrace trace,
Collection<JetReturnExpression> returnExpressions
private static Collection<JetReturnExpression> collectReturns(
@NotNull final JetFunctionLiteral functionLiteral,
@NotNull final BindingTrace trace
) {
return ContainerUtil.mapNotNull(returnExpressions, new Function<JetReturnExpression, JetType>() {
@Override
public JetType fun(JetReturnExpression returnExpression) {
JetSimpleNameExpression label = returnExpression.getTargetLabel();
if (label == null) {
// No label => non-local return
return null;
}
PsiElement labelTarget = trace.get(BindingContext.LABEL_TARGET, label);
if (labelTarget != functionLiteral) {
// Either a local return of inner lambda/function or a non-local return
return null;
}
JetExpression returnedExpression = returnExpression.getReturnedExpression();
if (returnedExpression == null) {
return components.builtIns.getUnitType();
}
JetType returnedType = trace.get(EXPRESSION_TYPE, returnedExpression);
assert returnedType != null : "No type for returned expression: " + returnedExpression + ",\n" +
"the type should have been computed by getBlockReturnedType() above\n" +
JetPsiUtil.getElementTextWithContext(returnedExpression);
return returnedType;
}
});
}
public static Collection<JetReturnExpression> collectReturns(@NotNull JetExpression expression) {
Collection<JetReturnExpression> result = Lists.newArrayList();
expression.accept(
JetBlockExpression bodyExpression = functionLiteral.getBodyExpression();
assert bodyExpression != null;
bodyExpression.accept(
new JetTreeVisitor<Collection<JetReturnExpression>>() {
@Override
public Void visitReturnExpression(
@@ -362,6 +373,22 @@ public class ClosureExpressionsTypingVisitor extends ExpressionTypingVisitor {
},
result
);
return result;
return ContainerUtil.mapNotNull(result, new Function<JetReturnExpression, JetReturnExpression>() {
@Override
public JetReturnExpression fun(@NotNull JetReturnExpression returnExpression) {
JetSimpleNameExpression label = returnExpression.getTargetLabel();
if (label == null) {
// No label => non-local return
return null;
}
PsiElement labelTarget = trace.get(BindingContext.LABEL_TARGET, label);
if (labelTarget != functionLiteral) {
// Either a local return of inner lambda/function or a non-local return
return null;
}
return returnExpression;
}
});
}
}
@@ -47,6 +47,7 @@ import static org.jetbrains.kotlin.psi.PsiPackage.JetPsiFactory;
import static org.jetbrains.kotlin.resolve.BindingContext.*;
import static org.jetbrains.kotlin.resolve.calls.context.ContextDependency.INDEPENDENT;
import static org.jetbrains.kotlin.types.TypeUtils.NO_EXPECTED_TYPE;
import static org.jetbrains.kotlin.types.TypeUtils.isDontCarePlaceholder;
import static org.jetbrains.kotlin.types.TypeUtils.noExpectedType;
import static org.jetbrains.kotlin.types.expressions.ControlStructureTypingUtils.createCallForSpecialConstruction;
import static org.jetbrains.kotlin.types.expressions.ControlStructureTypingUtils.createDataFlowInfoForArgumentsForIfCall;
@@ -492,7 +493,11 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor {
.replaceContextDependency(INDEPENDENT));
}
else {
if (expectedType != null && !noExpectedType(expectedType) && !KotlinBuiltIns.isUnit(expectedType)) {
if (expectedType != null &&
!noExpectedType(expectedType) &&
!KotlinBuiltIns.isUnit(expectedType) &&
!isDontCarePlaceholder(expectedType)) // for lambda with implicit return type Unit
{
context.trace.report(RETURN_TYPE_MISMATCH.on(expression, expectedType));
}
}
@@ -1,7 +1,7 @@
fun test(a: Int) {
run @f{
if (a > 0) <!RETURN_TYPE_MISMATCH!>return@f<!>
else return@f 1
if (a > 0) return@f
else return@f <!RETURN_TYPE_MISMATCH!>1<!>
}
}
@@ -0,0 +1,6 @@
val flag = true
val a = @b {
if (flag) return@b <!RETURN_TYPE_MISMATCH!>4<!>
return@b
}
@@ -0,0 +1,4 @@
package
internal val a: () -> kotlin.Unit
internal val flag: kotlin.Boolean = true
@@ -1,6 +1,6 @@
fun test(a: Int) {
val x = run @f{
if (a > 0) <!RETURN_TYPE_MISMATCH!>return@f<!>
if (a > 0) return@f
else return@f Unit
}
x: Unit
@@ -0,0 +1,20 @@
val flag = true
fun run<T>(f: () -> T): T { return f() }
// type of a was checked by txt
val a = run { // () -> Unit
return@run
}
// Unit
val b = run {
if (flag) return@run
<!UNUSED_EXPRESSION!>5<!>
}
// Unit
val c = run {
if (flag) return@run
return@run <!RETURN_TYPE_MISMATCH!>4<!>
}
@@ -0,0 +1,7 @@
package
internal val a: kotlin.Unit
internal val b: kotlin.Unit
internal val c: kotlin.Unit
internal val flag: kotlin.Boolean = true
internal fun </*0*/ T> run(/*0*/ f: () -> T): T
@@ -0,0 +1,7 @@
val flag = true
// type of lambda was checked by txt
val a = @b { // () -> Unit
if (flag) return@b
else <!UNUSED_EXPRESSION!>54<!>
}
@@ -0,0 +1,4 @@
package
internal val a: () -> kotlin.Unit
internal val flag: kotlin.Boolean = true
@@ -0,0 +1,16 @@
val flag = true
// type of lambda was checked by txt
val a = @l { // () -> Any
if (flag) return@l 4
return@l Unit
}
val b = @l { // () -> Any
if (flag) return@l Unit
5
}
val c = @l { // () -> Unit
if (flag) return@l Unit
}
@@ -0,0 +1,6 @@
package
internal val a: () -> kotlin.Any
internal val b: () -> kotlin.Any
internal val c: () -> kotlin.Unit
internal val flag: kotlin.Boolean = true
@@ -0,0 +1,9 @@
val a = @l {
return@l <!UNRESOLVED_REFERENCE!>r<!>
}
val b = @l {
if ("" == "OK") return@l
return@l <!UNRESOLVED_REFERENCE, RETURN_TYPE_MISMATCH!>r<!>
}
@@ -0,0 +1,4 @@
package
internal val a: () -> kotlin.Nothing
internal val b: () -> kotlin.Unit
@@ -4697,18 +4697,42 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest {
doTest(fileName);
}
@TestMetadata("LocalReturnSecondUnit.kt")
public void testLocalReturnSecondUnit() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnSecondUnit.kt");
doTest(fileName);
}
@TestMetadata("LocalReturnUnit.kt")
public void testLocalReturnUnit() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnUnit.kt");
doTest(fileName);
}
@TestMetadata("LocalReturnUnitAndDontCareType.kt")
public void testLocalReturnUnitAndDontCareType() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnUnitAndDontCareType.kt");
doTest(fileName);
}
@TestMetadata("LocalReturnUnitWithBodyExpression.kt")
public void testLocalReturnUnitWithBodyExpression() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnUnitWithBodyExpression.kt");
doTest(fileName);
}
@TestMetadata("LocalReturnWithExpectedType.kt")
public void testLocalReturnWithExpectedType() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnWithExpectedType.kt");
doTest(fileName);
}
@TestMetadata("LocalReturnWithExplicitUnit.kt")
public void testLocalReturnWithExplicitUnit() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnWithExplicitUnit.kt");
doTest(fileName);
}
@TestMetadata("LocalReturnsWithExplicitReturnType.kt")
public void testLocalReturnsWithExplicitReturnType() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/functionLiterals/return/LocalReturnsWithExplicitReturnType.kt");
@@ -4738,6 +4762,12 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/functionLiterals/return/SmartCastWithExplicitType.kt");
doTest(fileName);
}
@TestMetadata("unresolvedReferenceInReturnBlock.kt")
public void testUnresolvedReferenceInReturnBlock() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/functionLiterals/return/unresolvedReferenceInReturnBlock.kt");
doTest(fileName);
}
}
}