Skip to content

Commit 44dfe08

Browse files
natebiggsCommit Queue
authored andcommitted
[ddc] Add a covariant parameter check for optional nonnullable parameters with null initializer on lowered constructor tearoffs.
Change-Id: I77379ee43173ddbbdee25083bd641968eeeb787f Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/406500 Reviewed-by: Sigmund Cherem <[email protected]> Commit-Queue: Nate Biggs <[email protected]>
1 parent 63261a7 commit 44dfe08

File tree

4 files changed

+68
-8
lines changed

4 files changed

+68
-8
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@
88

99
- Added `Iterable.withIterator` constructor.
1010

11+
### Tools
12+
13+
#### Dart Development Compiler (dartdevc)
14+
15+
In order to align with dart2js semantics, DDC will now throw a runtime error
16+
when a redirecting factory is torn off and one of its optional non-nullable
17+
parameters is provided no value. The implicit null passed to the factory will
18+
not match the non-nullable type and this will now throw.
19+
20+
In the future this will likely be a compile-time error and will be entirely
21+
disallowed.
22+
1123
## 3.7.0
1224

1325
**Released on:** Unreleased

pkg/dev_compiler/lib/src/kernel/compiler.dart

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3857,7 +3857,8 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
38573857

38583858
_emitCovarianceBoundsCheck(f.typeParameters, body);
38593859

3860-
void initParameter(VariableDeclaration p, js_ast.Identifier jsParam) {
3860+
void initParameter(
3861+
VariableDeclaration p, js_ast.Identifier jsParam, bool isOptional) {
38613862
// When the parameter is covariant, insert the null check before the
38623863
// covariant cast to avoid a TypeError when testing equality with null.
38633864
if (name == '==') {
@@ -3870,7 +3871,19 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
38703871
// Eliminate it when possible.
38713872
body.add(js.statement('if (# == null) return false;', [jsParam]));
38723873
}
3873-
if (isCovariantParameter(p)) {
3874+
if (isCovariantParameter(p) ||
3875+
// TODO(52582): This should be unreachable once the CFE ensures that
3876+
// redirecting factories parameter types match the target constructor.
3877+
// Matches dart2js check semantics for redirecting factory tearoffs.
3878+
// If a non-nullable optional argument with a null initializer is
3879+
// detected, we add an additional covariant check at runtime.
3880+
(f.parent is Procedure &&
3881+
isOptional &&
3882+
isConstructorTearOffLowering(f.parent as Procedure) &&
3883+
!p.type.isPotentiallyNullable &&
3884+
!p.initializer!
3885+
.getStaticType(_staticTypeContext)
3886+
.isPotentiallyNonNullable)) {
38743887
var castExpr = _emitCast(jsParam, p.type);
38753888
if (!identical(castExpr, jsParam)) body.add(castExpr.toStatement());
38763889
}
@@ -3884,11 +3897,13 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
38843897
}
38853898
}
38863899

3900+
var counter = 0;
38873901
for (var p in f.positionalParameters) {
38883902
var jsParam = _emitVariableRef(p);
38893903
if (_checkParameters) {
3890-
initParameter(p, jsParam);
3904+
initParameter(p, jsParam, counter >= f.requiredParameterCount);
38913905
}
3906+
counter++;
38923907
}
38933908
for (var p in f.namedParameters) {
38943909
// Parameters will be passed using their real names, not the (possibly
@@ -3906,7 +3921,7 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
39063921
]));
39073922

39083923
if (_checkParameters) {
3909-
initParameter(p, jsParam);
3924+
initParameter(p, jsParam, !p.isRequired);
39103925
}
39113926
}
39123927

pkg/dev_compiler/lib/src/kernel/compiler_new.dart

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4256,7 +4256,8 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
42564256

42574257
_emitCovarianceBoundsCheck(f.typeParameters, body);
42584258

4259-
void initParameter(VariableDeclaration p, js_ast.Identifier jsParam) {
4259+
void initParameter(
4260+
VariableDeclaration p, js_ast.Identifier jsParam, bool isOptional) {
42604261
// When the parameter is covariant, insert the null check before the
42614262
// covariant cast to avoid a TypeError when testing equality with null.
42624263
if (name == '==') {
@@ -4269,7 +4270,19 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
42694270
// Eliminate it when possible.
42704271
body.add(js.statement('if (# == null) return false;', [jsParam]));
42714272
}
4272-
if (isCovariantParameter(p)) {
4273+
if (isCovariantParameter(p) ||
4274+
// TODO(52582): This should be unreachable once the CFE ensures that
4275+
// redirecting factories parameter types match the target constructor.
4276+
// Matches dart2js check semantics for redirecting factory tearoffs.
4277+
// If a non-nullable optional argument with a null initializer is
4278+
// detected, we add an additional covariant check at runtime.
4279+
(f.parent is Procedure &&
4280+
isOptional &&
4281+
isConstructorTearOffLowering(f.parent as Procedure) &&
4282+
!p.type.isPotentiallyNullable &&
4283+
!p.initializer!
4284+
.getStaticType(_staticTypeContext)
4285+
.isPotentiallyNonNullable)) {
42734286
var castExpr = _emitCast(jsParam, p.type);
42744287
if (!identical(castExpr, jsParam)) body.add(castExpr.toStatement());
42754288
}
@@ -4283,11 +4296,13 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
42834296
}
42844297
}
42854298

4299+
var counter = 0;
42864300
for (var p in f.positionalParameters) {
42874301
var jsParam = _emitVariableRef(p);
42884302
if (_checkParameters) {
4289-
initParameter(p, jsParam);
4303+
initParameter(p, jsParam, counter >= f.requiredParameterCount);
42904304
}
4305+
counter++;
42914306
}
42924307
for (var p in f.namedParameters) {
42934308
// Parameters will be passed using their real names, not the (possibly
@@ -4305,7 +4320,7 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
43054320
]));
43064321

43074322
if (_checkParameters) {
4308-
initParameter(p, jsParam);
4323+
initParameter(p, jsParam, !p.isRequired);
43094324
}
43104325
}
43114326

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) 2025, 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+
import 'package:expect/expect.dart';
6+
7+
class A {
8+
factory A.foo([int x]) = B.foo;
9+
}
10+
11+
class B implements A {
12+
B.foo([int? x]);
13+
}
14+
15+
void main() {
16+
final f = A.foo;
17+
Expect.throws(() => f(), (e) => e is TypeError);
18+
}

0 commit comments

Comments
 (0)