diff --git a/pkg/_fe_analyzer_shared/lib/src/exhaustiveness/shared.dart b/pkg/_fe_analyzer_shared/lib/src/exhaustiveness/shared.dart index c418f57b9c0e..eadbed359340 100644 --- a/pkg/_fe_analyzer_shared/lib/src/exhaustiveness/shared.dart +++ b/pkg/_fe_analyzer_shared/lib/src/exhaustiveness/shared.dart @@ -185,14 +185,22 @@ class ExhaustivenessCache< } StaticType staticType; - Type nonNullable = typeOperations.getNonNullable(type); - if (typeOperations.isBoolType(nonNullable)) { + bool extractNull = typeOperations.isNullable(type); + Type typeWithoutNull = type; + if (extractNull) { + // If [type] is nullable, we model the static type by creating the + // non-nullable equivalent and then add `Null` afterwards. + // + // For instance we model `int?` as `int|Null`. + typeWithoutNull = typeOperations.getNonNullable(type); + } + if (typeOperations.isBoolType(typeWithoutNull)) { staticType = _boolStaticType; - } else if (typeOperations.isRecordType(nonNullable)) { - staticType = new RecordStaticType(typeOperations, this, nonNullable); + } else if (typeOperations.isRecordType(typeWithoutNull)) { + staticType = new RecordStaticType(typeOperations, this, typeWithoutNull); } else { Type? futureOrTypeArgument = - typeOperations.getFutureOrTypeArgument(nonNullable); + typeOperations.getFutureOrTypeArgument(typeWithoutNull); if (futureOrTypeArgument != null) { StaticType typeArgument = getStaticType(futureOrTypeArgument); StaticType futureType = getStaticType( @@ -200,34 +208,34 @@ class ExhaustivenessCache< bool isImplicitlyNullable = typeOperations.isNullable(futureOrTypeArgument); staticType = new FutureOrStaticType( - typeOperations, this, nonNullable, typeArgument, futureType, + typeOperations, this, typeWithoutNull, typeArgument, futureType, isImplicitlyNullable: isImplicitlyNullable); } else { - EnumClass? enumClass = enumOperations.getEnumClass(nonNullable); + EnumClass? enumClass = enumOperations.getEnumClass(typeWithoutNull); if (enumClass != null) { staticType = new EnumStaticType( - typeOperations, this, nonNullable, _getEnumInfo(enumClass)); + typeOperations, this, typeWithoutNull, _getEnumInfo(enumClass)); } else { Class? sealedClass = - _sealedClassOperations.getSealedClass(nonNullable); + _sealedClassOperations.getSealedClass(typeWithoutNull); if (sealedClass != null) { staticType = new SealedClassStaticType( typeOperations, this, - nonNullable, + typeWithoutNull, this, _sealedClassOperations, _getSealedClassInfo(sealedClass)); } else { - Type? listType = typeOperations.getListType(nonNullable); + Type? listType = typeOperations.getListType(typeWithoutNull); if (listType != null) { staticType = - new ListTypeStaticType(typeOperations, this, nonNullable); + new ListTypeStaticType(typeOperations, this, typeWithoutNull); } else { bool isImplicitlyNullable = - typeOperations.isNullable(nonNullable); + typeOperations.isNullable(typeWithoutNull); staticType = new TypeBasedStaticType( - typeOperations, this, nonNullable, + typeOperations, this, typeWithoutNull, isImplicitlyNullable: isImplicitlyNullable); Type? bound = typeOperations.getTypeVariableBound(type); if (bound != null) { @@ -239,7 +247,8 @@ class ExhaustivenessCache< } } } - if (typeOperations.isNullable(type)) { + if (extractNull) { + // Include the `Null` which extracted from [type] into [typeWithoutNull`. staticType = staticType.nullable; } return staticType; diff --git a/pkg/_fe_analyzer_shared/test/exhaustiveness/data/issue56998.dart b/pkg/_fe_analyzer_shared/test/exhaustiveness/data/issue56998.dart new file mode 100644 index 000000000000..ae9790e5e35d --- /dev/null +++ b/pkg/_fe_analyzer_shared/test/exhaustiveness/data/issue56998.dart @@ -0,0 +1,38 @@ +// Copyright (c) 2024, 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. + +int nonExhaustive1(T value) => + /* + checkingOrder={int?,int,Null}, + error=non-exhaustive:null, + subtypes={int,Null}, + type=int? + */switch (value) { int() /*space=int*/ => value }; + +int nonExhaustive2( + T? value) => /* + checkingOrder={int?,int,Null}, + error=non-exhaustive:null, + subtypes={int,Null}, + type=int? +*/ + switch (value) { + int() /*space=int*/ => value + }; + +int exhaustive(T value) => /*type=int*/ + switch (value) { + int() /*space=int*/ => value + }; + +int nonExhaustive3( + T? value) => /* + checkingOrder={int?,int,Null}, + error=non-exhaustive:null, + subtypes={int,Null}, + type=int? +*/ + switch (value) { + int() /*space=int*/ => value + }; diff --git a/pkg/_fe_analyzer_shared/test/exhaustiveness/data/private/main.dart b/pkg/_fe_analyzer_shared/test/exhaustiveness/data/private/main.dart index 31be986e20aa..4d1ce2b8b2ca 100644 --- a/pkg/_fe_analyzer_shared/test/exhaustiveness/data/private/main.dart +++ b/pkg/_fe_analyzer_shared/test/exhaustiveness/data/private/main.dart @@ -33,8 +33,8 @@ exhaustiveC(C c) => /* type=C */switch (c) { C(: num _a) /*space=C(_a: num)*/=> 0, } -nonExhaustiveA(C c) => /*analyzer. +nonExhaustiveC(C c) => /* error=non-exhaustive:C(_a: double()), fields={_a:num}, type=C -*/switch (c) { C(: int _a) /*analyzer.space=C(_a: int)*/=> 0, } +*/switch (c) { C(: int _a) /*space=C(_a: int)*/=> 0, }