[linter] Adds new `switch_on_type` lint
Fixes: https://github.com/dart-lang/sdk/issues/59546
Change-Id: Ib78714e03c1487376c961214910d10c24ec9f8ff
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/413600
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Auto-Submit: Felipe Morschel <git@fmorschel.dev>
diff --git a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
index 3fc573b..8b7d89e 100644
--- a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
+++ b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
@@ -2394,6 +2394,8 @@
Conceivably for variable declarations we can search some space (like the
library or the compilation unit) for what values are assigned, and suggest
the LUB of those value(s)' type(s).
+LintCode.switch_on_type:
+ status: noFix
LintCode.test_types_in_equals:
status: noFix
LintCode.throw_in_finally:
diff --git a/pkg/linter/example/all.yaml b/pkg/linter/example/all.yaml
index 4d3b069..5c4c4ce 100644
--- a/pkg/linter/example/all.yaml
+++ b/pkg/linter/example/all.yaml
@@ -172,6 +172,7 @@
- specify_nonobvious_local_variable_types
- specify_nonobvious_property_types
- strict_top_level_inference
+ - switch_on_type
- test_types_in_equals
- throw_in_finally
- tighten_type_of_initializing_formals
diff --git a/pkg/linter/lib/src/lint_codes.g.dart b/pkg/linter/lib/src/lint_codes.g.dart
index 2af30eb..2e162dd 100644
--- a/pkg/linter/lib/src/lint_codes.g.dart
+++ b/pkg/linter/lib/src/lint_codes.g.dart
@@ -1542,6 +1542,12 @@
uniqueName: 'strict_top_level_inference_split_to_types',
);
+ static const LintCode switch_on_type = LinterLintCode(
+ LintNames.switch_on_type,
+ "Avoid switch statements on a 'Type'.",
+ correctionMessage: "Try using pattern matching on a variable instead.",
+ );
+
static const LintCode test_types_in_equals = LinterLintCode(
LintNames.test_types_in_equals,
"Missing type test for '{0}' in '=='.",
diff --git a/pkg/linter/lib/src/lint_names.g.dart b/pkg/linter/lib/src/lint_names.g.dart
index e490063..8c3b14e 100644
--- a/pkg/linter/lib/src/lint_names.g.dart
+++ b/pkg/linter/lib/src/lint_names.g.dart
@@ -461,6 +461,8 @@
static const String super_goes_last = 'super_goes_last';
+ static const String switch_on_type = 'switch_on_type';
+
static const String test_types_in_equals = 'test_types_in_equals';
static const String throw_in_finally = 'throw_in_finally';
diff --git a/pkg/linter/lib/src/rules.dart b/pkg/linter/lib/src/rules.dart
index 2190433..9aceb959 100644
--- a/pkg/linter/lib/src/rules.dart
+++ b/pkg/linter/lib/src/rules.dart
@@ -190,6 +190,7 @@
import 'rules/specify_nonobvious_property_types.dart';
import 'rules/strict_top_level_inference.dart';
import 'rules/super_goes_last.dart';
+import 'rules/switch_on_type.dart';
import 'rules/test_types_in_equals.dart';
import 'rules/throw_in_finally.dart';
import 'rules/tighten_type_of_initializing_formals.dart';
@@ -442,6 +443,7 @@
..registerLintRule(SpecifyNonObviousLocalVariableTypes())
..registerLintRule(SpecifyNonObviousPropertyTypes())
..registerLintRule(StrictTopLevelInference())
+ ..registerLintRule(SwitchOnType())
..registerLintRule(TestTypesInEquals())
..registerLintRule(ThrowInFinally())
..registerLintRule(TightenTypeOfInitializingFormals())
diff --git a/pkg/linter/lib/src/rules/switch_on_type.dart b/pkg/linter/lib/src/rules/switch_on_type.dart
new file mode 100644
index 0000000..c9f4b74
--- /dev/null
+++ b/pkg/linter/lib/src/rules/switch_on_type.dart
@@ -0,0 +1,158 @@
+// Copyright (c) 2025, 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.
+
+import 'package:analyzer/dart/analysis/features.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/error/error.dart';
+
+import '../analyzer.dart';
+import '../extensions.dart';
+
+const _desc = "Avoid switch statements on a 'Type'.";
+
+const _objectToStringName = 'toString';
+
+class SwitchOnType extends LintRule {
+ SwitchOnType() : super(name: LintNames.switch_on_type, description: _desc);
+
+ @override
+ DiagnosticCode get diagnosticCode => LinterLintCode.switch_on_type;
+
+ @override
+ LintCode get lintCode => LinterLintCode.switch_on_type;
+
+ @override
+ void registerNodeProcessors(
+ NodeLintRegistry registry,
+ LinterContext context,
+ ) {
+ if (!context.isEnabled(Feature.patterns)) return;
+ var visitor = _Visitor(this, context);
+ registry.addSwitchExpression(this, visitor);
+ registry.addSwitchStatement(this, visitor);
+ }
+}
+
+class _Visitor extends SimpleAstVisitor<void> {
+ final LintRule rule;
+
+ final LinterContext context;
+
+ /// The node where the lint will be reported.
+ late AstNode node;
+
+ _Visitor(this.rule, this.context);
+
+ /// A reference to the [Type] type.
+ ///
+ /// This is used to check if the type of the expression is assignable to
+ /// [Type].
+ ///
+ /// This shortens the code and avoids multiple calls to
+ /// `context.typeProvider.typeType`.
+ InterfaceType get _typeType => context.typeProvider.typeType;
+
+ @override
+ void visitSwitchExpression(SwitchExpression node) {
+ this.node = node.expression;
+ _processExpression(node.expression);
+ }
+
+ @override
+ void visitSwitchStatement(SwitchStatement node) {
+ this.node = node.expression;
+ _processExpression(node.expression);
+ }
+
+ /// Returns `true` if the [type] is assignable to [Type].
+ bool _isAssignableToType(DartType? type) {
+ if (type == null) return false;
+ return context.typeSystem.isAssignableTo(type, _typeType);
+ }
+
+ /// Processes the [expression] of a [SwitchStatement] or [SwitchExpression].
+ ///
+ /// Returns `true` if the lint was reportred and `false` otherwise.
+ bool _processExpression(Expression expression) {
+ if (expression case StringInterpolation(:var elements)) {
+ return _processInterpolation(elements);
+ }
+ if (expression case ConditionalExpression(
+ :var thenExpression,
+ :var elseExpression,
+ )) {
+ return _processExpression(thenExpression) ||
+ _processExpression(elseExpression);
+ }
+ if (expression case SwitchExpression(:var cases)) {
+ for (var caseClause in cases) {
+ if (_processExpression(caseClause.expression)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ if (expression case BinaryExpression(
+ :var leftOperand,
+ :var rightOperand,
+ :var operator,
+ ) when operator.lexeme == TokenType.PLUS.lexeme) {
+ return _processExpression(leftOperand) ||
+ _processExpression(rightOperand);
+ }
+ var type = switch (expression) {
+ PrefixedIdentifier(:var identifier) => identifier.staticType,
+ PropertyAccess(:var propertyName) => propertyName.staticType,
+ SimpleIdentifier(:var staticType) => staticType,
+ MethodInvocation(:var methodName, :var realTarget?) =>
+ methodName.element.isToStringMethod ? realTarget.staticType : null,
+ _ => null,
+ };
+ return _reportIfAssignableToType(type);
+ }
+
+ /// Processes the [elements] of an [InterpolationExpression].
+ ///
+ /// Returns `true` if the lint was reported and `false` otherwise.
+ bool _processInterpolation(NodeList<InterpolationElement> elements) {
+ for (var element in elements) {
+ switch (element) {
+ case InterpolationExpression(:var expression):
+ var reported = _processExpression(expression);
+
+ // This return is necessary to avoid multiple reporting of the lint
+ if (reported) {
+ return true;
+ }
+ case InterpolationString():
+ break;
+ }
+ }
+ return false;
+ }
+
+ /// Reports the lint if the [type] is assignable to [Type].
+ ///
+ /// Returns `true` if the lint was reported and `false` otherwise.
+ bool _reportIfAssignableToType(DartType? type) {
+ var reported = false;
+ if (_isAssignableToType(type)) {
+ rule.reportAtNode(node);
+ reported = true;
+ }
+ return reported;
+ }
+}
+
+extension on Element? {
+ /// Returns `true` if this element is the `toString` method.
+ bool get isToStringMethod {
+ var self = this;
+ return self is MethodElement && self.name3 == _objectToStringName;
+ }
+}
diff --git a/pkg/linter/messages.yaml b/pkg/linter/messages.yaml
index e642ca1..28e74ea 100644
--- a/pkg/linter/messages.yaml
+++ b/pkg/linter/messages.yaml
@@ -11344,6 +11344,82 @@
: _children = children,
super(style) {
```
+ switch_on_type:
+ problemMessage: "Avoid switch statements on a 'Type'."
+ correctionMessage: "Try using pattern matching on a variable instead."
+ state:
+ stable: "3.0"
+ categories: [unintentional, style, languageFeatureUsage, errorProne]
+ hasPublishedDocs: false
+ documentation: |-
+ #### Description
+
+ The analyzer produces this diagnostic when a switch statement or switch
+ expression is used on either the value of a `Type` or a `toString` call
+ on a `Type`.
+
+ #### Example
+
+ The following code produces this diagnostic because the switch statement
+ is used on a `Type`:
+
+ ```dart
+ void f(Object o) {
+ switch ([!o.runtimeType!]) {
+ case const (int):
+ print('int');
+ case const (String):
+ print('String');
+ }
+ }
+ ```
+
+ #### Common fixes
+
+ Use pattern matching on the variable instead:
+
+ ```dart
+ void f(Object o) {
+ switch (o) {
+ case int():
+ print('int');
+ case String():
+ print('String');
+ }
+ }
+ ```
+ deprecatedDetails: |-
+ **AVOID** using switch on `Type`.
+
+ Switching on `Type` is not type-safe and can lead to bugs if the
+ class hierarchy changes. Prefer to use pattern matching on the variable
+ instead.
+
+ **BAD:**
+ ```dart
+ void f(Object o) {
+ switch (o.runtimeType) {
+ case int:
+ print('int');
+ case String:
+ print('String');
+ }
+ }
+ ```
+
+ **GOOD:**
+ ```dart
+ void f(Object o) {
+ switch(o) {
+ case int():
+ print('int');
+ case String _:
+ print('String');
+ default:
+ print('other');
+ }
+ }
+ ```
test_types_in_equals:
problemMessage: "Missing type test for '{0}' in '=='."
correctionMessage: "Try testing the type of '{0}'."
diff --git a/pkg/linter/test/rules/all.dart b/pkg/linter/test/rules/all.dart
index a243789..fb2cd1e 100644
--- a/pkg/linter/test/rules/all.dart
+++ b/pkg/linter/test/rules/all.dart
@@ -242,6 +242,7 @@
import 'specify_nonobvious_property_types_test.dart'
as specify_nonobvious_property_types;
import 'strict_top_level_inference_test.dart' as strict_top_level_inference;
+import 'switch_on_type_test.dart' as switch_on_type;
import 'test_types_in_equals_test.dart' as test_types_in_equals;
import 'throw_in_finally_test.dart' as throw_in_finally;
import 'tighten_type_of_initializing_formals_test.dart'
@@ -502,6 +503,7 @@
specify_nonobvious_local_variable_types.main();
specify_nonobvious_property_types.main();
strict_top_level_inference.main();
+ switch_on_type.main();
test_types_in_equals.main();
throw_in_finally.main();
tighten_type_of_initializing_formals.main();
diff --git a/pkg/linter/test/rules/switch_on_type_test.dart b/pkg/linter/test/rules/switch_on_type_test.dart
new file mode 100644
index 0000000..6791a06
--- /dev/null
+++ b/pkg/linter/test/rules/switch_on_type_test.dart
@@ -0,0 +1,892 @@
+// Copyright (c) 2025, 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.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+void main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(SwitchExpressionOnTypeTest);
+ defineReflectiveTests(SwitchStatementOnTypeTest);
+ });
+}
+
+@reflectiveTest
+class SwitchExpressionOnTypeTest extends LintRuleTest {
+ @override
+ String get lintRule => LintNames.switch_on_type;
+
+ Future<void> test_binaryExpression() async {
+ await assertDiagnostics(
+ r'''
+void f(Type t) {
+ (switch ('' + '$t') {
+ 'type: int' => null,
+ _ => null,
+ });
+}
+''',
+ [lint(28, 9)],
+ );
+ }
+
+ Future<void> test_conditionalBoth() async {
+ await assertDiagnostics(
+ r'''
+void f(Type t) {
+ (switch (1 == 1 ? t : '$t') {
+ 'type: int' => null,
+ _ => null,
+ });
+}
+''',
+ [lint(28, 17)],
+ );
+ }
+
+ Future<void> test_conditionalElse() async {
+ await assertDiagnostics(
+ r'''
+void f(Type t) {
+ (switch (1 == 1 ? 'other' : '$t') {
+ 'type: int' => null,
+ _ => null,
+ });
+}
+''',
+ [lint(28, 23)],
+ );
+ }
+
+ Future<void> test_functionToString() async {
+ await assertNoDiagnostics('''
+void f() {
+ (switch (toString()) {
+ 'int' => null,
+ _ => null,
+ });
+}
+
+String toString() => '';
+''');
+ }
+
+ Future<void> test_functionToString_prefixed() async {
+ await assertNoDiagnostics('''
+import '' as self;
+
+void f() {
+ (switch (self.toString()) {
+ 'int' => null,
+ _ => null,
+ });
+}
+
+String toString() => '';
+''');
+ }
+
+ Future<void> test_insideClass_implicitThis() async {
+ await assertDiagnostics(
+ '''
+class A {
+ void m() {
+ (switch (runtimeType) {
+ const (A) => null,
+ _ => null,
+ });
+ }
+}
+''',
+ [lint(36, 11)],
+ );
+ }
+
+ Future<void> test_insideClass_withThis() async {
+ await assertDiagnostics(
+ '''
+class A {
+ void m() {
+ (switch (this.runtimeType) {
+ const (A) => null,
+ _ => null,
+ });
+ }
+}
+''',
+ [lint(36, 16)],
+ );
+ }
+
+ Future<void> test_nestedSwitch() async {
+ await assertDiagnostics(
+ r'''
+void f(Type t, Object o) {
+ (switch (switch(o) {_ => t}) {
+ const (int) => null,
+ _ => null,
+ });
+}
+''',
+ [lint(38, 18)],
+ );
+ }
+
+ Future<void> test_other() async {
+ await assertNoDiagnostics('''
+void f(num i) {
+ (switch (i) {
+ int _ => null,
+ double _ => null,
+ });
+}
+''');
+ }
+
+ Future<void> test_override() async {
+ await assertDiagnostics(
+ '''
+void f(MyClass i) {
+ (switch (i.runtimeType) {
+ const (MyClass) => null,
+ _ => null,
+ });
+}
+
+class MyClass {
+ @override
+ Type get runtimeType => int;
+}
+''',
+ [lint(31, 13)],
+ );
+ }
+
+ Future<void> test_runtimeType() async {
+ await assertDiagnostics(
+ '''
+void f(num i) {
+ (switch (i.runtimeType) {
+ const (int) => null,
+ const (double) => null,
+ _ => null,
+ });
+}
+''',
+ [lint(27, 13)],
+ );
+ }
+
+ Future<void> test_runtimeTypeToString() async {
+ await assertDiagnostics(
+ '''
+void f(num n) {
+ (switch (n.runtimeType.toString()) {
+ 'int' => null,
+ _ => null,
+ });
+}
+''',
+ [lint(27, 24)],
+ );
+ }
+
+ Future<void> test_runtimeTypeToString_insideClass() async {
+ await assertDiagnostics(
+ '''
+class A {
+ void m() {
+ (switch (runtimeType.toString()) {
+ 'A' => null,
+ _ => null,
+ });
+ }
+}
+''',
+ [lint(36, 22)],
+ );
+ }
+
+ Future<void> test_runtimeTypeToString_insideClass_override() async {
+ await assertDiagnostics(
+ '''
+class A {
+ void m() {
+ (switch (runtimeType.toString()) {
+ 'A' => null,
+ _ => null,
+ });
+ }
+
+ @override
+ MyType get runtimeType => const MyType();
+}
+
+class MyType implements Type {
+ const MyType();
+
+ @override
+ String toString() {
+ return 'MyType';
+ }
+}
+''',
+ [lint(36, 22)],
+ );
+ }
+
+ Future<void> test_runtimeTypeToString_noCall() async {
+ await assertNoDiagnostics('''
+void f(num n) {
+ (switch (n.runtimeType.toString) {
+ function => null,
+ _ => null,
+ });
+}
+
+void function() {}
+''');
+ }
+
+ Future<void> test_runtimeTypeToString_noCall_insideClass() async {
+ await assertNoDiagnostics('''
+class A {
+ void m() {
+ (switch (runtimeType.toString) {
+ function => null,
+ _ => null,
+ });
+ }
+}
+
+void function() {}
+''');
+ }
+
+ Future<void> test_stringAddition() async {
+ await assertDiagnostics(
+ r'''
+void f(Type t) {
+ (switch ('type: ' + t.toString()) {
+ 'type: int' => null,
+ _ => null,
+ });
+}
+''',
+ [lint(28, 23)],
+ );
+ }
+
+ Future<void> test_stringInterpolation() async {
+ await assertDiagnostics(
+ r'''
+void f(Type t) {
+ (switch ('type: $t') {
+ 'type: int' => null,
+ _ => null,
+ });
+}
+''',
+ [lint(28, 10)],
+ );
+ }
+
+ Future<void> test_stringInterpolation_innerConditionalResult() async {
+ await assertDiagnostics(
+ r'''
+void f(Type t) {
+ (switch ('type: ${1 == 1 ? '$t' : 'other'}') {
+ 'type: int' => null,
+ _ => null,
+ });
+}
+''',
+ [lint(28, 34)],
+ );
+ }
+
+ Future<void> test_stringInterpolation_innerInterpolation() async {
+ await assertDiagnostics(
+ r'''
+void f(Type t) {
+ (switch ('type: ${'inner string $t'}') {
+ 'type: int' => null,
+ _ => null,
+ });
+}
+''',
+ [lint(28, 28)],
+ );
+ }
+
+ Future<void> test_stringInterpolation_innerSwitchResult() async {
+ await assertDiagnostics(
+ r'''
+void f(Type t) {
+ (switch ('type: ${switch (1) {_ => '$t',}}') {
+ 'type: int' => null,
+ _ => null,
+ });
+}
+''',
+ [lint(28, 34)],
+ );
+ }
+
+ Future<void> test_stringInterpolation_innerTest() async {
+ await assertDiagnostics(
+ r'''
+void f(Type t) {
+ (switch ('type: ${t == int ? 'int' : '$t'}') {
+ 'type: int' => null,
+ _ => null,
+ });
+}
+''',
+ [lint(28, 34)],
+ );
+ }
+
+ Future<void> test_toString_insideClass_implicitThis() async {
+ await assertNoDiagnostics('''
+class A {
+ void m() {
+ (switch (toString()) {
+ 'A' => null,
+ _ => null,
+ });
+ }
+}
+''');
+ }
+
+ Future<void> test_toString_insideClass_withThis() async {
+ await assertNoDiagnostics('''
+class A {
+ void m() {
+ (switch (this.toString()) {
+ 'A' => null,
+ _ => null,
+ });
+ }
+}
+''');
+ }
+
+ Future<void> test_typeParameter() async {
+ await assertDiagnostics(
+ '''
+void f<T>() {
+ (switch (T) {
+ const (int) => null,
+ _ => null,
+ });
+}
+''',
+ [lint(25, 1)],
+ );
+ }
+
+ Future<void> test_variable_typeToString() async {
+ await assertNoDiagnostics(r'''
+void f(Object? o) {
+ final type = o.runtimeType.toString();
+ (switch (type) {
+ 'int' => null,
+ _ => null,
+ });
+}
+''');
+ }
+
+ Future<void> test_variableType() async {
+ await assertDiagnostics(
+ '''
+void f(Type t) {
+ (switch (t) {
+ const (int) => null,
+ _ => null,
+ });
+}
+''',
+ [lint(28, 1)],
+ );
+ }
+}
+
+@reflectiveTest
+class SwitchStatementOnTypeTest extends LintRuleTest {
+ @override
+ String get lintRule => LintNames.switch_on_type;
+
+ Future<void> test_binaryExpression() async {
+ await assertDiagnostics(
+ r'''
+void f(Type t) {
+ switch ('' + '$t') {
+ case 'type: int':
+ break;
+ default:
+ break;
+ }
+}
+''',
+ [lint(27, 9)],
+ );
+ }
+
+ Future<void> test_conditionalBoth() async {
+ await assertDiagnostics(
+ r'''
+void f(Type t) {
+ switch (1 == 1 ? t : '$t') {
+ case 'type: int':
+ break;
+ default:
+ break;
+ }
+}
+''',
+ [lint(27, 17)],
+ );
+ }
+
+ Future<void> test_conditionalElse() async {
+ await assertDiagnostics(
+ r'''
+void f(Type t) {
+ switch (1 == 1 ? 'other' : '$t') {
+ case 'type: int':
+ break;
+ default:
+ break;
+ }
+}
+''',
+ [lint(27, 23)],
+ );
+ }
+
+ Future<void> test_functionToString() async {
+ await assertNoDiagnostics('''
+void f() {
+ switch (toString()) {
+ case 'int':
+ break;
+ default:
+ break;
+ }
+}
+
+String toString() => '';
+''');
+ }
+
+ Future<void> test_functionToString_prefixed() async {
+ await assertNoDiagnostics('''
+import '' as self;
+
+void f() {
+ switch (self.toString()) {
+ case 'int':
+ break;
+ default:
+ break;
+ }
+}
+
+String toString() => '';
+''');
+ }
+
+ Future<void> test_insideClass_implicitThis() async {
+ await assertDiagnostics(
+ '''
+class A {
+ void m() {
+ switch (runtimeType) {
+ case const (A):
+ break;
+ default:
+ break;
+ }
+ }
+}
+''',
+ [lint(35, 11)],
+ );
+ }
+
+ Future<void> test_insideClass_withThis() async {
+ await assertDiagnostics(
+ '''
+class A {
+ void m() {
+ switch (this.runtimeType) {
+ case const (A):
+ break;
+ default:
+ break;
+ }
+ }
+}
+''',
+ [lint(35, 16)],
+ );
+ }
+
+ Future<void> test_nestedSwitch() async {
+ await assertDiagnostics(
+ r'''
+void f(Type t, Object o) {
+ switch (switch(o) {_ => t}) {
+ case const (int):
+ break;
+ default:
+ break;
+ }
+}
+''',
+ [lint(37, 18)],
+ );
+ }
+
+ Future<void> test_other() async {
+ await assertNoDiagnostics('''
+void f(num i) {
+ switch (i) {
+ case int _:
+ break;
+ case double _:
+ break;
+ }
+}
+''');
+ }
+
+ Future<void> test_override() async {
+ await assertDiagnostics(
+ '''
+void f(MyClass i) {
+ switch (i.runtimeType) {
+ case const (MyClass):
+ break;
+ default:
+ break;
+ }
+}
+
+class MyClass {
+ @override
+ Type get runtimeType => int;
+}
+''',
+ [lint(30, 13)],
+ );
+ }
+
+ Future<void> test_prePatterns() async {
+ await assertNoDiagnostics('''
+// @dart = 2.19
+
+void f(num i) {
+ switch (i.runtimeType) {
+ case int:
+ break;
+ case double:
+ break;
+ default:
+ break;
+ }
+}
+''');
+ }
+
+ Future<void> test_runtimeType() async {
+ await assertDiagnostics(
+ '''
+void f(num i) {
+ switch (i.runtimeType) {
+ case const (int):
+ break;
+ case const (double):
+ break;
+ default:
+ break;
+ }
+}
+''',
+ [lint(26, 13)],
+ );
+ }
+
+ Future<void> test_runtimeTypeToString() async {
+ await assertDiagnostics(
+ '''
+void f(num n) {
+ switch (n.runtimeType.toString()) {
+ case 'int':
+ break;
+ default:
+ break;
+ }
+}
+''',
+ [lint(26, 24)],
+ );
+ }
+
+ Future<void> test_runtimeTypeToString_insideClass() async {
+ await assertDiagnostics(
+ '''
+class A {
+ void m() {
+ switch (runtimeType.toString()) {
+ case 'A':
+ break;
+ default:
+ break;
+ }
+ }
+}
+''',
+ [lint(35, 22)],
+ );
+ }
+
+ Future<void> test_runtimeTypeToString_insideClass_override() async {
+ await assertDiagnostics(
+ '''
+class A {
+ void m() {
+ switch (runtimeType.toString()) {
+ case 'A':
+ break;
+ default:
+ break;
+ }
+ }
+
+ @override
+ MyType get runtimeType => const MyType();
+}
+
+class MyType implements Type {
+ const MyType();
+
+ @override
+ String toString() {
+ return 'MyType';
+ }
+}
+''',
+ [lint(35, 22)],
+ );
+ }
+
+ Future<void> test_runtimeTypeToString_noCall() async {
+ await assertNoDiagnostics('''
+void f(num n) {
+ switch (n.runtimeType.toString) {
+ case function:
+ break;
+ default:
+ break;
+ }
+}
+
+void function() {}
+''');
+ }
+
+ Future<void> test_runtimeTypeToString_noCall_insideClass() async {
+ await assertNoDiagnostics('''
+class A {
+ void m() {
+ switch (runtimeType.toString) {
+ case function:
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void function() {}
+''');
+ }
+
+ Future<void> test_stringAddition() async {
+ await assertDiagnostics(
+ r'''
+void f(Type t) {
+ switch ('type: ' + t.toString()) {
+ case 'type: int':
+ break;
+ default:
+ break;
+ }
+}
+''',
+ [lint(27, 23)],
+ );
+ }
+
+ Future<void> test_stringInterpolation() async {
+ await assertDiagnostics(
+ r'''
+void f(Type t) {
+ switch ('type: $t') {
+ case 'type: int':
+ break;
+ default:
+ break;
+ }
+}
+''',
+ [lint(27, 10)],
+ );
+ }
+
+ Future<void> test_stringInterpolation_innerConditionalResult() async {
+ await assertDiagnostics(
+ r'''
+void f(Type t) {
+ switch ('type: ${1 == 1 ? '$t' : 'other'}') {
+ case 'type: int':
+ break;
+ default:
+ break;
+ }
+}
+''',
+ [lint(27, 34)],
+ );
+ }
+
+ Future<void> test_stringInterpolation_innerInterpolation() async {
+ await assertDiagnostics(
+ r'''
+void f(Type t) {
+ switch ('type: ${'inner string $t'}') {
+ case 'type: int':
+ break;
+ default:
+ break;
+ }
+}
+''',
+ [lint(27, 28)],
+ );
+ }
+
+ Future<void> test_stringInterpolation_innerSwitchResult() async {
+ await assertDiagnostics(
+ r'''
+void f(Type t) {
+ switch ('type: ${switch (1) {_ => '$t',}}') {
+ case 'type: int':
+ break;
+ default:
+ break;
+ }
+}
+''',
+ [lint(27, 34)],
+ );
+ }
+
+ Future<void> test_stringInterpolation_innerTest() async {
+ await assertDiagnostics(
+ r'''
+void f(Type t) {
+ switch ('type: ${t == int ? 'int' : '$t'}') {
+ case 'type: int':
+ break;
+ default:
+ break;
+ }
+}
+''',
+ [lint(27, 34)],
+ );
+ }
+
+ Future<void> test_toString_insideClass_implicitThis() async {
+ await assertNoDiagnostics('''
+class A {
+ void m() {
+ switch (toString()) {
+ case 'A':
+ break;
+ default:
+ break;
+ }
+ }
+}
+''');
+ }
+
+ Future<void> test_toString_insideClass_withThis() async {
+ await assertNoDiagnostics('''
+class A {
+ void m() {
+ switch (this.toString()) {
+ case 'A':
+ break;
+ default:
+ break;
+ }
+ }
+}
+''');
+ }
+
+ Future<void> test_typeParameter() async {
+ await assertDiagnostics(
+ '''
+void f<T>() {
+ switch (T) {
+ case const (int):
+ break;
+ default:
+ break;
+ }
+}
+''',
+ [lint(24, 1)],
+ );
+ }
+
+ Future<void> test_variable_typeToString() async {
+ await assertNoDiagnostics(r'''
+void f(Object? o) {
+ final type = o.runtimeType.toString();
+ switch (type) {
+ case 'int':
+ break;
+ default:
+ break;
+ }
+}
+''');
+ }
+
+ Future<void> test_variableType() async {
+ await assertDiagnostics(
+ '''
+void f(Type t) {
+ switch (t) {
+ case const (int):
+ break;
+ default:
+ break;
+ }
+}
+''',
+ [lint(27, 1)],
+ );
+ }
+}