Skip to content

Commit e6ecb6c

Browse files
natebiggsCommit Queue
authored andcommitted
[dart2wasm] Fix dry run 'is' checks subtyping nullabilities.
Running on a Flutter app turned up this nullability inconsistency which produced a couple false positives. Added tests that match the 2 Flutter examples. Change-Id: Ieb75c67086c0cd8b4e20312abf4840b5a4cad628 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/438780 Reviewed-by: Martin Kustermann <[email protected]> Commit-Queue: Nate Biggs <[email protected]>
1 parent 84e279d commit e6ecb6c

File tree

3 files changed

+35
-9
lines changed

3 files changed

+35
-9
lines changed

pkg/dart2wasm/lib/dry_run.dart

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ class _AnalysisVisitor extends RecursiveVisitor {
161161
: _typeEnvironment = TypeEnvironment(coreTypes, hierarchy),
162162
_jsAnyType = ExtensionType(
163163
coreTypes.index.getExtensionType('dart:js_interop', 'JSAny'),
164-
Nullability.nullable);
164+
Nullability.nonNullable);
165165

166166
@override
167167
void visitLibrary(Library node) {
@@ -181,15 +181,19 @@ class _AnalysisVisitor extends RecursiveVisitor {
181181
@override
182182
void visitIsExpression(IsExpression node) {
183183
final operandStaticType = node.operand.getStaticType(_context);
184-
if (_typeEnvironment.isSubtypeOf(operandStaticType, _jsAnyType)) {
184+
if (_typeEnvironment.isSubtypeOf(
185+
operandStaticType.withDeclaredNullability(Nullability.nonNullable),
186+
_jsAnyType)) {
185187
errors.add(_DryRunError(
186188
_DryRunErrorCode.isTestValueError,
187189
'Should not perform an `is` test on a JS value. Use `isA` with a JS '
188190
'value type instead.',
189191
errorSourceUri: _enclosingLibrary?.importUri,
190192
errorLocation: node.location));
191193
}
192-
if (_typeEnvironment.isSubtypeOf(node.type, _jsAnyType)) {
194+
if (_typeEnvironment.isSubtypeOf(
195+
node.type.withDeclaredNullability(Nullability.nonNullable),
196+
_jsAnyType)) {
193197
errors.add(_DryRunError(
194198
_DryRunErrorCode.isTestTypeError,
195199
'Should not perform an `is` test against a JS value type. '
@@ -211,13 +215,15 @@ class _AnalysisVisitor extends RecursiveVisitor {
211215
// Check InterfaceType and ExtensionType
212216
if (type is TypeDeclarationType) {
213217
final arguments = type.typeArguments;
214-
if (arguments.any((e) => _typeEnvironment.isSubtypeOf(e, _jsAnyType))) {
218+
if (arguments.any((e) => _typeEnvironment.isSubtypeOf(
219+
e.withDeclaredNullability(Nullability.nonNullable), _jsAnyType))) {
215220
return true;
216221
}
217222
return arguments.any(_hasJsTypeArguments);
218223
} else if (type is RecordType) {
219224
final fields = type.positional.followedBy(type.named.map((t) => t.type));
220-
if (fields.any((e) => _typeEnvironment.isSubtypeOf(e, _jsAnyType))) {
225+
if (fields.any((e) => _typeEnvironment.isSubtypeOf(
226+
e.withDeclaredNullability(Nullability.nonNullable), _jsAnyType))) {
221227
return true;
222228
}
223229
return fields.any(_hasJsTypeArguments);

pkg/dart2wasm/test/dry_run/dry_run_test.dart

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,15 @@ class TestExpectation {
3838
}
3939

4040
class TestFinding {
41+
final String rawString;
4142
final int errorCode;
4243
final String problemMessage;
4344
final Uri? errorSourceUri;
4445
final int? errorLine;
4546
final int? errorColumn;
4647

47-
TestFinding(this.errorCode, this.problemMessage, this.errorSourceUri,
48-
this.errorLine, this.errorColumn);
48+
TestFinding(this.rawString, this.errorCode, this.problemMessage,
49+
this.errorSourceUri, this.errorLine, this.errorColumn);
4950

5051
factory TestFinding.fromLine(String line) {
5152
final parts = line.split(' - ');
@@ -60,6 +61,7 @@ class TestFinding {
6061
final errorCode =
6162
int.parse(problemMessage.substring(errorCodeStart + 1, errorCodeEnd));
6263
return TestFinding(
64+
line,
6365
errorCode,
6466
problemMessage.substring(0, errorCodeStart).trim(),
6567
Uri.parse(uri),
@@ -140,7 +142,11 @@ Future<TestResults> runTest(TestCase testCase) async {
140142
timer.stop();
141143
try {
142144
final exitCode = result.exitCode;
143-
Expect.equals(testCase.expectations.isEmpty ? 0 : 254, exitCode);
145+
if (testCase.expectations.isEmpty) {
146+
Expect.equals(0, exitCode, 'Unexpected findings:\n${result.stdout}');
147+
} else {
148+
Expect.equals(254, exitCode, 'Expected findings but found none.');
149+
}
144150
final findings = _parseTestFindings(result.stdout);
145151
_checkFindings(findings, testCase.expectations);
146152
} catch (e, s) {
@@ -236,7 +242,8 @@ void _checkFindings(
236242
findings.length,
237243
expectations.length,
238244
'Incorrect number of findings. '
239-
'Expected: ${expectations.length}, Actual: ${findings.length}');
245+
'Expected: ${expectations.length}, Actual: ${findings.length}\n'
246+
'Findings:\n${findings.map((e) => e.rawString).join('\n')}}');
240247
for (final expectation in expectations) {
241248
final lineNumber = expectation.lineNumber;
242249
final lineFindings =

pkg/dart2wasm/test/dry_run/testcases/valid_interop_import.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,24 @@
44

55
import 'dart:js_interop';
66

7+
class A<B> {
8+
void foo() {
9+
if (null is! B) {
10+
print('');
11+
}
12+
}
13+
}
14+
715
void main() {
816
JSAny? jsValue;
917
if (jsValue.isA<JSString>()) {
1018
print(jsValue);
1119
} else if (jsValue.isA<JSArray>()) {
1220
print(jsValue);
1321
}
22+
23+
Object? dartObject;
24+
if (dartObject case {'foo': final String foo}) {
25+
print(foo);
26+
}
1427
}

0 commit comments

Comments
 (0)