Skip to content

Commit 58c336e

Browse files
johnniwintherCommit Queue
authored andcommitted
[_fe_analyzer_shared] Avoid deconstruction of potentially nullable type variables
The exhaustiveness model relies on null being extracted from nullable, for instance modeling `int?` as `int|Null`. This failed on type variable that are potentially nullable but not explicitly nullable. For these `Null` should be extract from their bound and not from themselves. The logic in the getStaticType method now uses `isNullable` to determine both when to extract and to combine, so to avoid extracting but not combining `Null` for type variables and also not combine but not extract for instance for `void`. Closes #56998 Change-Id: I47c2d0d6535ca66fe00e6344b11550c4308a7388 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/392944 Reviewed-by: Chloe Stefantsova <[email protected]> Commit-Queue: Johnni Winther <[email protected]>
1 parent 297c90b commit 58c336e

File tree

3 files changed

+64
-17
lines changed

3 files changed

+64
-17
lines changed

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

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -185,49 +185,57 @@ class ExhaustivenessCache<
185185
}
186186

187187
StaticType staticType;
188-
Type nonNullable = typeOperations.getNonNullable(type);
189-
if (typeOperations.isBoolType(nonNullable)) {
188+
bool extractNull = typeOperations.isNullable(type);
189+
Type typeWithoutNull = type;
190+
if (extractNull) {
191+
// If [type] is nullable, we model the static type by creating the
192+
// non-nullable equivalent and then add `Null` afterwards.
193+
//
194+
// For instance we model `int?` as `int|Null`.
195+
typeWithoutNull = typeOperations.getNonNullable(type);
196+
}
197+
if (typeOperations.isBoolType(typeWithoutNull)) {
190198
staticType = _boolStaticType;
191-
} else if (typeOperations.isRecordType(nonNullable)) {
192-
staticType = new RecordStaticType(typeOperations, this, nonNullable);
199+
} else if (typeOperations.isRecordType(typeWithoutNull)) {
200+
staticType = new RecordStaticType(typeOperations, this, typeWithoutNull);
193201
} else {
194202
Type? futureOrTypeArgument =
195-
typeOperations.getFutureOrTypeArgument(nonNullable);
203+
typeOperations.getFutureOrTypeArgument(typeWithoutNull);
196204
if (futureOrTypeArgument != null) {
197205
StaticType typeArgument = getStaticType(futureOrTypeArgument);
198206
StaticType futureType = getStaticType(
199207
typeOperations.instantiateFuture(futureOrTypeArgument));
200208
bool isImplicitlyNullable =
201209
typeOperations.isNullable(futureOrTypeArgument);
202210
staticType = new FutureOrStaticType(
203-
typeOperations, this, nonNullable, typeArgument, futureType,
211+
typeOperations, this, typeWithoutNull, typeArgument, futureType,
204212
isImplicitlyNullable: isImplicitlyNullable);
205213
} else {
206-
EnumClass? enumClass = enumOperations.getEnumClass(nonNullable);
214+
EnumClass? enumClass = enumOperations.getEnumClass(typeWithoutNull);
207215
if (enumClass != null) {
208216
staticType = new EnumStaticType(
209-
typeOperations, this, nonNullable, _getEnumInfo(enumClass));
217+
typeOperations, this, typeWithoutNull, _getEnumInfo(enumClass));
210218
} else {
211219
Class? sealedClass =
212-
_sealedClassOperations.getSealedClass(nonNullable);
220+
_sealedClassOperations.getSealedClass(typeWithoutNull);
213221
if (sealedClass != null) {
214222
staticType = new SealedClassStaticType(
215223
typeOperations,
216224
this,
217-
nonNullable,
225+
typeWithoutNull,
218226
this,
219227
_sealedClassOperations,
220228
_getSealedClassInfo(sealedClass));
221229
} else {
222-
Type? listType = typeOperations.getListType(nonNullable);
230+
Type? listType = typeOperations.getListType(typeWithoutNull);
223231
if (listType != null) {
224232
staticType =
225-
new ListTypeStaticType(typeOperations, this, nonNullable);
233+
new ListTypeStaticType(typeOperations, this, typeWithoutNull);
226234
} else {
227235
bool isImplicitlyNullable =
228-
typeOperations.isNullable(nonNullable);
236+
typeOperations.isNullable(typeWithoutNull);
229237
staticType = new TypeBasedStaticType(
230-
typeOperations, this, nonNullable,
238+
typeOperations, this, typeWithoutNull,
231239
isImplicitlyNullable: isImplicitlyNullable);
232240
Type? bound = typeOperations.getTypeVariableBound(type);
233241
if (bound != null) {
@@ -239,7 +247,8 @@ class ExhaustivenessCache<
239247
}
240248
}
241249
}
242-
if (typeOperations.isNullable(type)) {
250+
if (extractNull) {
251+
// Include the `Null` which extracted from [type] into [typeWithoutNull`.
243252
staticType = staticType.nullable;
244253
}
245254
return staticType;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
int nonExhaustive1<T extends int?>(T value) =>
6+
/*
7+
checkingOrder={int?,int,Null},
8+
error=non-exhaustive:null,
9+
subtypes={int,Null},
10+
type=int?
11+
*/switch (value) { int() /*space=int*/ => value };
12+
13+
int nonExhaustive2<T extends int?>(
14+
T? value) => /*
15+
checkingOrder={int?,int,Null},
16+
error=non-exhaustive:null,
17+
subtypes={int,Null},
18+
type=int?
19+
*/
20+
switch (value) {
21+
int() /*space=int*/ => value
22+
};
23+
24+
int exhaustive<T extends int>(T value) => /*type=int*/
25+
switch (value) {
26+
int() /*space=int*/ => value
27+
};
28+
29+
int nonExhaustive3<T extends int>(
30+
T? value) => /*
31+
checkingOrder={int?,int,Null},
32+
error=non-exhaustive:null,
33+
subtypes={int,Null},
34+
type=int?
35+
*/
36+
switch (value) {
37+
int() /*space=int*/ => value
38+
};

pkg/_fe_analyzer_shared/test/exhaustiveness/data/private/main.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ exhaustiveC(C c) => /*
3333
type=C
3434
*/switch (c) { C(: num _a) /*space=C(_a: num)*/=> 0, }
3535

36-
nonExhaustiveA(C c) => /*analyzer.
36+
nonExhaustiveC(C c) => /*
3737
error=non-exhaustive:C(_a: double()),
3838
fields={_a:num},
3939
type=C
40-
*/switch (c) { C(: int _a) /*analyzer.space=C(_a: int)*/=> 0, }
40+
*/switch (c) { C(: int _a) /*space=C(_a: int)*/=> 0, }

0 commit comments

Comments
 (0)