Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit bc165fd

Browse files
FMorschelCommit Queue
authored and
Commit Queue
committed
[analyzer] Better exhaustiveness error message for private enum cases
Bug: #61873 Change-Id: Icbe14451e419614fd621cefb8c9324333118e074 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/459780 Reviewed-by: Kallen Tu <kallentu@google.com> Auto-Submit: Felipe Morschel <git@fmorschel.dev> Reviewed-by: Paul Berry <paulberry@google.com> Commit-Queue: Johnni Winther <johnniwinther@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
1 parent ee951bc commit bc165fd

File tree

15 files changed

+540
-57
lines changed

15 files changed

+540
-57
lines changed

‎pkg/_fe_analyzer_shared/lib/src/exhaustiveness/shared.dart‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ abstract class TypeOperations<Type extends Object> {
102102
/// Returns the bound of [type] if is a type variable or a promoted type
103103
/// variable. Otherwise returns `null`.
104104
Type? getTypeVariableBound(Type type);
105+
106+
/// Returns `true` if [type] is an enum type.
107+
bool isEnum(Type type);
108+
109+
/// Returns the library URI for [type].
110+
Uri? libraryUri(Type type);
105111
}
106112

107113
/// Interface for looking up fields and their corresponding [StaticType]s of

‎pkg/_fe_analyzer_shared/lib/src/exhaustiveness/static_type.dart‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,21 @@ abstract class StaticType {
6666
/// one of those two types*.
6767
bool get isSealed;
6868

69+
/// Returns `true` if this is a private type.
70+
///
71+
/// This is used to determine whether a missing pattern part is not accessible
72+
/// due to being private.
73+
bool get isPrivate;
74+
75+
/// Returns `true` if this is a subtype of `Enum`.
76+
///
77+
/// This is used along with [isPrivate] to determine whether we should suggest
78+
/// enum values as missing pattern parts or only a default case.
79+
bool get isEnumSubtype;
80+
81+
/// Returns the library URI for this type, or `null`.
82+
Uri? get libraryUri;
83+
6984
/// Returns `true` if this is a record type.
7085
///
7186
/// This is only used for print the type as part of a [Witness].
@@ -255,6 +270,15 @@ class _NonNullableObject extends _BaseStaticType with _ObjectFieldMixin {
255270
void typeToDart(DartTemplateBuffer buffer) {
256271
buffer.writeCoreType(name);
257272
}
273+
274+
@override
275+
bool get isEnumSubtype => false;
276+
277+
@override
278+
Uri? get libraryUri => null;
279+
280+
@override
281+
bool get isPrivate => false;
258282
}
259283

260284
class _NeverType extends _BaseStaticType with _ObjectFieldMixin {
@@ -285,6 +309,15 @@ class _NeverType extends _BaseStaticType with _ObjectFieldMixin {
285309
void typeToDart(DartTemplateBuffer buffer) {
286310
buffer.writeCoreType(name);
287311
}
312+
313+
@override
314+
bool get isEnumSubtype => false;
315+
316+
@override
317+
Uri? get libraryUri => null;
318+
319+
@override
320+
bool get isPrivate => false;
288321
}
289322

290323
class _NullType extends NullableStaticType with _ObjectFieldMixin {
@@ -367,6 +400,15 @@ class NullableStaticType extends _BaseStaticType with _ObjectFieldMixin {
367400
buffer.write('?');
368401
}
369402
}
403+
404+
@override
405+
bool get isEnumSubtype => underlying.isEnumSubtype;
406+
407+
@override
408+
Uri? get libraryUri => underlying.libraryUri;
409+
410+
@override
411+
bool get isPrivate => name.startsWith('_');
370412
}
371413

372414
abstract class NonNullableStaticType extends _BaseStaticType {
@@ -510,6 +552,15 @@ class WrappedStaticType extends _BaseStaticType {
510552
void typeToDart(DartTemplateBuffer buffer) {
511553
wrappedType.typeToDart(buffer);
512554
}
555+
556+
@override
557+
bool get isEnumSubtype => wrappedType.isEnumSubtype;
558+
559+
@override
560+
Uri? get libraryUri => wrappedType.libraryUri;
561+
562+
@override
563+
bool get isPrivate => wrappedType.isPrivate;
513564
}
514565

515566
/// Interface for accessing the members defined on `Object`.

‎pkg/_fe_analyzer_shared/lib/src/exhaustiveness/types.dart‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,15 @@ class TypeBasedStaticType<Type extends Object> extends NonNullableStaticType {
119119
void typeToDart(DartTemplateBuffer buffer) {
120120
buffer.writeGeneralType(_type, name);
121121
}
122+
123+
@override
124+
bool get isEnumSubtype => _typeOperations.isEnum(_type);
125+
126+
@override
127+
Uri? get libraryUri => _typeOperations.libraryUri(_type);
128+
129+
@override
130+
bool get isPrivate => name.startsWith('_');
122131
}
123132

124133
/// [StaticType] for an object restricted by its [restriction].

‎pkg/_fe_analyzer_shared/test/exhaustiveness/env.dart‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,20 @@ class _TypeOperations implements TypeOperations<_Type> {
456456
// TODO(johnniwinther): Support extension types in testing.
457457
return type;
458458
}
459+
460+
@override
461+
bool isEnum(_Type type) {
462+
if (type is _InterfaceType) {
463+
return type.cls is _EnumClass;
464+
}
465+
return false;
466+
}
467+
468+
@override
469+
Uri? libraryUri(_Type type) {
470+
// TODO(FMorschel): Support library URIs in testing.
471+
return null;
472+
}
459473
}
460474

461475
class _EnumOperations

‎pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1256,8 +1256,12 @@ CompileTimeErrorCode.NON_CONSTANT_SET_ELEMENT:
12561256
: status: noFix
12571257
CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH_EXPRESSION:
12581258
status: hasFix
1259+
CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH_EXPRESSION_PRIVATE:
1260+
status: hasFix
12591261
CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH_STATEMENT:
12601262
status: hasFix
1263+
CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH_STATEMENT_PRIVATE:
1264+
status: hasFix
12611265
CompileTimeErrorCode.NON_FINAL_FIELD_IN_ENUM:
12621266
status: hasFix
12631267
since: 2.17

‎pkg/analysis_server/lib/src/services/correction/fix_internal.dart‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,7 +688,9 @@ final _builtInNonLintGenerators = <DiagnosticCode, List<ProducerGenerator>>{
688688
diag.nonConstantRelationalPatternExpression: [AddConst.new],
689689
diag.nonConstantSetElement: [RemoveConst.new],
690690
diag.nonExhaustiveSwitchExpression: [AddMissingSwitchCases.new],
691+
diag.nonExhaustiveSwitchExpressionPrivate: [AddMissingSwitchCases.new],
691692
diag.nonExhaustiveSwitchStatement: [AddMissingSwitchCases.new],
693+
diag.nonExhaustiveSwitchStatementPrivate: [AddMissingSwitchCases.new],
692694
diag.nonFinalFieldInEnum: [MakeFinal.new],
693695
diag.notAType: [ChangeTo.classOrMixin],
694696
diag.notInitializedNonNullableInstanceField: [AddLate.new],

‎pkg/analysis_server/test/src/services/correction/fix/add_missing_switch_cases_test.dart‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,34 @@ int g(E e) {
407407
''');
408408
}
409409

410+
Future<void> test_privateEnumConstant_privateMissing() async {
411+
newFile('$testPackageLibPath/enum.dart', r'''
412+
enum E { first, second, _unknown }
413+
''');
414+
await resolveTestCode('''
415+
import 'enum.dart';
416+
417+
int g(E e) {
418+
return switch (e) {
419+
E.first => 0,
420+
E.second => 1,
421+
};
422+
}
423+
''');
424+
await assertHasFix('''
425+
import 'enum.dart';
426+
427+
int g(E e) {
428+
return switch (e) {
429+
E.first => 0,
430+
E.second => 1,
431+
// TODO: Handle this case.
432+
_ => throw UnimplementedError(),
433+
};
434+
}
435+
''');
436+
}
437+
410438
Future<void> test_sealed_impl() async {
411439
var otherRoot = getFolder('$packagesRootPath/other');
412440
newFile('$otherRoot/lib/src/private.dart', '''
@@ -986,6 +1014,37 @@ int g(E e) {
9861014
''');
9871015
}
9881016

1017+
Future<void> test_privateEnumConstant_privateMissing() async {
1018+
newFile('$testPackageLibPath/enum.dart', r'''
1019+
enum E { first, second, _unknown }
1020+
''');
1021+
await resolveTestCode('''
1022+
import 'enum.dart';
1023+
1024+
int g(E e) {
1025+
switch (e) {
1026+
case E.first:
1027+
case E.second:
1028+
return 0;
1029+
}
1030+
}
1031+
''');
1032+
await assertHasFix('''
1033+
import 'enum.dart';
1034+
1035+
int g(E e) {
1036+
switch (e) {
1037+
case E.first:
1038+
case E.second:
1039+
return 0;
1040+
default:
1041+
// TODO: Handle this case.
1042+
throw UnimplementedError();
1043+
}
1044+
}
1045+
''');
1046+
}
1047+
9891048
Future<void> test_static() async {
9901049
await resolveTestCode('''
9911050
enum E {

‎pkg/analyzer/lib/src/dart/constant/constant_verifier.dart‎

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,17 +1004,35 @@ class ConstantVerifier extends RecursiveAstVisitor<void> {
10041004
correctionData.add(correctionDataBuffer.parts);
10051005
}
10061006
}
1007-
var diagnostic = _diagnosticReporter.atToken(
1008-
switchKeyword,
1009-
isSwitchExpression
1010-
? diag.nonExhaustiveSwitchExpression
1011-
: diag.nonExhaustiveSwitchStatement,
1012-
arguments: [
1013-
scrutineeType,
1014-
errorBuffer.toString(),
1015-
correctionTextBuffer.toString(),
1016-
],
1017-
);
1007+
1008+
Diagnostic diagnostic;
1009+
_currentLibrary;
1010+
if (nonExhaustiveness.valueType.isEnumSubtype &&
1011+
nonExhaustiveness.valueType.libraryUri != _currentLibrary.uri &&
1012+
(nonExhaustiveness.valueType.isPrivate ||
1013+
nonExhaustiveness.witnesses.every(
1014+
(witness) => witness.asWitness.contains('._'),
1015+
))) {
1016+
diagnostic = _diagnosticReporter.atToken(
1017+
switchKeyword,
1018+
isSwitchExpression
1019+
? diag.nonExhaustiveSwitchExpressionPrivate
1020+
: diag.nonExhaustiveSwitchStatementPrivate,
1021+
arguments: [scrutineeType],
1022+
);
1023+
} else {
1024+
diagnostic = _diagnosticReporter.atToken(
1025+
switchKeyword,
1026+
isSwitchExpression
1027+
? diag.nonExhaustiveSwitchExpression
1028+
: diag.nonExhaustiveSwitchStatement,
1029+
arguments: [
1030+
scrutineeType,
1031+
errorBuffer.toString(),
1032+
correctionTextBuffer.toString(),
1033+
],
1034+
);
1035+
}
10181036
if (correctionData.isNotEmpty) {
10191037
MissingPatternPart.byDiagnostic[diagnostic] = correctionData;
10201038
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /