Skip to content

Commit

Permalink
[cfe] Implement new rules around types of case expressions
Browse files Browse the repository at this point in the history
Bug: http://dartbug.com/40425
Change-Id: I1f6e93bde663ab6f1a6a63e91aef4133af6e5253
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/136186
Commit-Queue: Dmitry Stefantsov <dmitryas@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
  • Loading branch information
Dmitry Stefantsov authored and commit-bot@chromium.org committed Feb 19, 2020
1 parent 17d8b6b commit 0f141be
Show file tree
Hide file tree
Showing 11 changed files with 460 additions and 17 deletions.
37 changes: 37 additions & 0 deletions pkg/front_end/lib/src/fasta/fasta_codes_cfe_generated.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3577,6 +3577,43 @@ Message _withArgumentsSwitchExpressionNotAssignable(
arguments: {'type': _type, 'type2': _type2});
}

// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Template<
Message Function(
DartType _type, DartType _type2, bool isNonNullableByDefault)>
templateSwitchExpressionNotSubtype = const Template<
Message Function(
DartType _type, DartType _type2, bool isNonNullableByDefault)>(
messageTemplate:
r"""Type '#type' of the case expression is not a subtype of type '#type2' of this switch expression.""",
withArguments: _withArgumentsSwitchExpressionNotSubtype);

// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<
Message Function(
DartType _type, DartType _type2, bool isNonNullableByDefault)>
codeSwitchExpressionNotSubtype = const Code<
Message Function(
DartType _type, DartType _type2, bool isNonNullableByDefault)>(
"SwitchExpressionNotSubtype",
templateSwitchExpressionNotSubtype,
);

// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
Message _withArgumentsSwitchExpressionNotSubtype(
DartType _type, DartType _type2, bool isNonNullableByDefault) {
TypeLabeler labeler = new TypeLabeler(isNonNullableByDefault);
List<Object> typeParts = labeler.labelType(_type);
List<Object> type2Parts = labeler.labelType(_type2);
String type = typeParts.join();
String type2 = type2Parts.join();
return new Message(codeSwitchExpressionNotSubtype,
message:
"""Type '${type}' of the case expression is not a subtype of type '${type2}' of this switch expression.""" +
labeler.originMessages,
arguments: {'type': _type, 'type2': _type2});
}

// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Template<Message Function(DartType _type, bool isNonNullableByDefault)>
templateThrowingNotAssignableToObjectError = const Template<
Expand Down
53 changes: 37 additions & 16 deletions pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5154,22 +5154,43 @@ class InferenceVisitor
switchCase.expressions[index] = caseExpression..parent = switchCase;
DartType caseExpressionType = caseExpressionResult.inferredType;

// Check whether the expression type is assignable to the case
// expression type.
if (!inferrer.isAssignable(expressionType, caseExpressionType)) {
inferrer.helper.addProblem(
templateSwitchExpressionNotAssignable.withArguments(
expressionType,
caseExpressionType,
inferrer.isNonNullableByDefault),
caseExpression.fileOffset,
noLength,
context: [
messageSwitchExpressionNotAssignableCause.withLocation(
inferrer.uriForInstrumentation,
node.expression.fileOffset,
noLength)
]);
if (inferrer.library.isNonNullableByDefault) {
if (inferrer.performNnbdChecks) {
if (!inferrer.typeSchemaEnvironment.isSubtypeOf(caseExpressionType,
expressionType, SubtypeCheckMode.withNullabilities)) {
inferrer.helper.addProblem(
templateSwitchExpressionNotSubtype.withArguments(
caseExpressionType,
expressionType,
inferrer.isNonNullableByDefault),
caseExpression.fileOffset,
noLength,
context: [
messageSwitchExpressionNotAssignableCause.withLocation(
inferrer.uriForInstrumentation,
node.expression.fileOffset,
noLength)
]);
}
}
} else {
// Check whether the expression type is assignable to the case
// expression type.
if (!inferrer.isAssignable(expressionType, caseExpressionType)) {
inferrer.helper.addProblem(
templateSwitchExpressionNotAssignable.withArguments(
expressionType,
caseExpressionType,
inferrer.isNonNullableByDefault),
caseExpression.fileOffset,
noLength,
context: [
messageSwitchExpressionNotAssignableCause.withLocation(
inferrer.uriForInstrumentation,
node.expression.fileOffset,
noLength)
]);
}
}
}
StatementInferenceResult bodyResult =
Expand Down
4 changes: 4 additions & 0 deletions pkg/front_end/messages.status
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,10 @@ SupertypeIsFunction/example: Fail
SupertypeIsIllegal/example: Fail
SupertypeIsTypeVariable/example: Fail
SwitchCaseFallThrough/example: Fail
SwitchExpressionNotSubtype/analyzerCode: Fail
SwitchExpressionNotSubtype/example: Fail
SwitchExpressionUserDefinedEquals/analyzerCode: Fail
SwitchExpressionUserDefinedEquals/example: Fail
SyntheticToken/example: Fail # Can't be tested, used to recover from other errors.
ThisAccessInFieldInitializer/example: Fail
ThisAsIdentifier/example: Fail
Expand Down
3 changes: 3 additions & 0 deletions pkg/front_end/messages.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2615,6 +2615,9 @@ SwitchExpressionNotAssignableCause:
template: "The switch expression is here."
severity: CONTEXT

SwitchExpressionNotSubtype:
template: "Type '#type' of the case expression is not a subtype of type '#type2' of this switch expression."

SwitchHasCaseAfterDefault:
index: 16
template: "The default case should be the last case in a switch statement."
Expand Down
42 changes: 42 additions & 0 deletions pkg/front_end/testcases/nnbd/switch_redesign_types.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

// The test checks the constraints for types of the case expressions. The rules
// can be found at the following link:
// https://github.com/dart-lang/language/blob/master/accepted/future-releases/nnbd/feature-specification.md#errors-and-warnings

class A {
final int foo;
const A(this.foo);
}

class B extends A {
const B(int foo) : super(foo);
}

class C extends B {
const C(int foo) : super(foo);
}

class D extends B {
const D(int foo) : super(foo);

bool operator ==(dynamic other) => identical(this, other);
}

bar(B b) {
switch (b) {
case const B(42):
break;
case const C(42):
break;
case const A(42): // Error: not a subtype of B.
break;
case const D(42): // Error: D has custom operator ==.
break;
default:
}
}

main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
library /*isNonNullableByDefault*/;
import self as self;
import "dart:core" as core;

class A extends core::Object /*hasConstConstructor*/ {
final field core::int foo;
const constructor •(core::int foo) → self::A
: self::A::foo = foo, super core::Object::•()
;
}
class B extends self::A /*hasConstConstructor*/ {
const constructor •(core::int foo) → self::B
: super self::A::•(foo)
;
}
class C extends self::B /*hasConstConstructor*/ {
const constructor •(core::int foo) → self::C
: super self::B::•(foo)
;
}
class D extends self::B /*hasConstConstructor*/ {
const constructor •(core::int foo) → self::D
: super self::B::•(foo)
;
operator ==(dynamic other) → core::bool
;
}
static method bar(self::B b) → dynamic
;
static method main() → dynamic
;
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
library /*isNonNullableByDefault*/;
//
// Problems in library:
//
// pkg/front_end/testcases/nnbd/switch_redesign_types.dart:34:16: Error: Type 'A' of the case expression is not a subtype of type 'B' of this switch expression.
// - 'A' is from 'pkg/front_end/testcases/nnbd/switch_redesign_types.dart'.
// - 'B' is from 'pkg/front_end/testcases/nnbd/switch_redesign_types.dart'.
// case const A(42): // Error: not a subtype of B.
// ^
// pkg/front_end/testcases/nnbd/switch_redesign_types.dart:29:11: Context: The switch expression is here.
// switch (b) {
// ^
//
import self as self;
import "dart:core" as core;

class A extends core::Object /*hasConstConstructor*/ {
final field core::int foo;
const constructor •(core::int foo) → self::A
: self::A::foo = foo, super core::Object::•()
;
}
class B extends self::A /*hasConstConstructor*/ {
const constructor •(core::int foo) → self::B
: super self::A::•(foo)
;
}
class C extends self::B /*hasConstConstructor*/ {
const constructor •(core::int foo) → self::C
: super self::B::•(foo)
;
}
class D extends self::B /*hasConstConstructor*/ {
const constructor •(core::int foo) → self::D
: super self::B::•(foo)
;
operator ==(dynamic other) → core::bool
return core::identical(this, other);
}
static method bar(self::B b) → dynamic {
#L1:
switch(b) {
#L2:
case #C2:
{
break #L1;
}
#L3:
case #C3:
{
break #L1;
}
#L4:
case #C4:
{
break #L1;
}
#L5:
case #C5:
{
break #L1;
}
#L6:
default:
{}
}
}
static method main() → dynamic {}

constants {
#C1 = 42
#C2 = self::B {foo:#C1}
#C3 = self::C {foo:#C1}
#C4 = self::A {foo:#C1}
#C5 = self::D {foo:#C1}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
library /*isNonNullableByDefault*/;
//
// Problems in library:
//
// pkg/front_end/testcases/nnbd/switch_redesign_types.dart:34:16: Error: Type 'A' of the case expression is not a subtype of type 'B' of this switch expression.
// - 'A' is from 'pkg/front_end/testcases/nnbd/switch_redesign_types.dart'.
// - 'B' is from 'pkg/front_end/testcases/nnbd/switch_redesign_types.dart'.
// case const A(42): // Error: not a subtype of B.
// ^
// pkg/front_end/testcases/nnbd/switch_redesign_types.dart:29:11: Context: The switch expression is here.
// switch (b) {
// ^
//
import self as self;
import "dart:core" as core;

class A extends core::Object /*hasConstConstructor*/ {
final field core::int foo;
const constructor •(core::int foo) → self::A
: self::A::foo = foo, super core::Object::•()
;
}
class B extends self::A /*hasConstConstructor*/ {
const constructor •(core::int foo) → self::B
: super self::A::•(foo)
;
}
class C extends self::B /*hasConstConstructor*/ {
const constructor •(core::int foo) → self::C
: super self::B::•(foo)
;
}
class D extends self::B /*hasConstConstructor*/ {
const constructor •(core::int foo) → self::D
: super self::B::•(foo)
;
operator ==(dynamic other) → core::bool
return core::identical(this, other);
}
static method bar(self::B b) → dynamic {
#L1:
switch(b) {
#L2:
case #C2:
{
break #L1;
}
#L3:
case #C3:
{
break #L1;
}
#L4:
case #C4:
{
break #L1;
}
#L5:
case #C5:
{
break #L1;
}
#L6:
default:
{}
}
}
static method main() → dynamic {}

constants {
#C1 = 42
#C2 = self::B {foo:#C1}
#C3 = self::C {foo:#C1}
#C4 = self::A {foo:#C1}
#C5 = self::D {foo:#C1}
}
Loading

0 comments on commit 0f141be

Please sign in to comment.