Skip to content

Commit 5e3c017

Browse files
stereotype441Commit Queue
authored andcommitted
[cfe] Fix inference order for local function parameters with defaults.
When a function expression has a parameter that: - is optional, - has an explicitly specified default value, and - has an inferred type, it's important that the type of the parameter be inferred before recursively visiting the default value. Otherwise, the default value will receive the wrong type inference context, and the default value won't be type checked (which can lead to unsound behavior). Fixes #60908. Bug: #60908 Change-Id: I866468599c244c6117c05d86ce23bc0e9031aa9a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/434123 Reviewed-by: Chloe Stefantsova <[email protected]> Reviewed-by: Johnni Winther <[email protected]> Commit-Queue: Paul Berry <[email protected]>
1 parent a6f19e5 commit 5e3c017

File tree

3 files changed

+224
-35
lines changed

3 files changed

+224
-35
lines changed

pkg/front_end/lib/src/type_inference/inference_visitor_base.dart

Lines changed: 26 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2424,41 +2424,6 @@ abstract class InferenceVisitorBase implements InferenceVisitor {
24242424
hasImplicitReturnType = true;
24252425
returnContext = const UnknownType();
24262426
}
2427-
List<VariableDeclaration> positionalParameters =
2428-
function.positionalParameters;
2429-
for (int i = 0; i < positionalParameters.length; i++) {
2430-
VariableDeclaration parameter = positionalParameters[i];
2431-
flowAnalysis.declare(
2432-
parameter,
2433-
new SharedTypeView(parameter.type),
2434-
initialized: true,
2435-
);
2436-
inferMetadata(visitor, parameter, parameter.annotations);
2437-
if (parameter.initializer != null) {
2438-
ExpressionInferenceResult initializerResult = visitor.inferExpression(
2439-
parameter.initializer!,
2440-
parameter.type,
2441-
);
2442-
parameter.initializer = initializerResult.expression
2443-
..parent = parameter;
2444-
}
2445-
}
2446-
for (VariableDeclaration parameter in function.namedParameters) {
2447-
flowAnalysis.declare(
2448-
parameter,
2449-
new SharedTypeView(parameter.type),
2450-
initialized: true,
2451-
);
2452-
inferMetadata(visitor, parameter, parameter.annotations);
2453-
if (parameter.initializer != null) {
2454-
ExpressionInferenceResult initializerResult = visitor.inferExpression(
2455-
parameter.initializer!,
2456-
parameter.type,
2457-
);
2458-
parameter.initializer = initializerResult.expression
2459-
..parent = parameter;
2460-
}
2461-
}
24622427

24632428
// Let `<T0, ..., Tn>` be the set of type parameters of the closure (with
24642429
// `n`=0 if there are no type parameters).
@@ -2578,6 +2543,32 @@ abstract class InferenceVisitorBase implements InferenceVisitor {
25782543
}
25792544
}
25802545

2546+
List<VariableDeclaration> positionalParameters =
2547+
function.positionalParameters;
2548+
for (int i = 0; i < positionalParameters.length; i++) {
2549+
VariableDeclaration parameter = positionalParameters[i];
2550+
flowAnalysis.declare(parameter, new SharedTypeView(parameter.type),
2551+
initialized: true);
2552+
inferMetadata(visitor, parameter, parameter.annotations);
2553+
if (parameter.initializer != null) {
2554+
ExpressionInferenceResult initializerResult =
2555+
visitor.inferExpression(parameter.initializer!, parameter.type);
2556+
parameter.initializer = initializerResult.expression
2557+
..parent = parameter;
2558+
}
2559+
}
2560+
for (VariableDeclaration parameter in function.namedParameters) {
2561+
flowAnalysis.declare(parameter, new SharedTypeView(parameter.type),
2562+
initialized: true);
2563+
inferMetadata(visitor, parameter, parameter.annotations);
2564+
if (parameter.initializer != null) {
2565+
ExpressionInferenceResult initializerResult =
2566+
visitor.inferExpression(parameter.initializer!, parameter.type);
2567+
parameter.initializer = initializerResult.expression
2568+
..parent = parameter;
2569+
}
2570+
}
2571+
25812572
for (VariableDeclaration parameter in function.namedParameters) {
25822573
VariableDeclarationImpl formal = parameter as VariableDeclarationImpl;
25832574
// Required named parameters shouldn't have initializers.
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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+
// These tests verify that type checking is properly applied to default values
6+
// of closure parameters.
7+
8+
// If the closure has an optional positional parameter with an explicit type:
9+
//
10+
// - The explicit parameter type will be used as the type inference context for
11+
// the default value of the closure parameter
12+
void testPositionalExplicit() {
13+
var f = ([List<int> x = const ['foo']]) {};
14+
// ^^^^^
15+
// [analyzer] COMPILE_TIME_ERROR.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE
16+
// [cfe] A value of type 'String' can't be assigned to a variable of type 'int'.
17+
}
18+
19+
// If the closure has an optional positional parameter with an implicit type,
20+
// and the closure's context is a function type with a required positional
21+
// parameter:
22+
//
23+
// - The type of the closure's parameter will be inferred from the
24+
// corresponding required parameter in the context type
25+
// - And that type in turn will be used as the type inference context for the
26+
// default value of the closure parameter.
27+
void testPositionalInferredFromRequired() {
28+
void Function(List<int>) f = ([x = const ['foo']]) {};
29+
// ^^^^^
30+
// [analyzer] COMPILE_TIME_ERROR.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE
31+
// [cfe] A value of type 'String' can't be assigned to a variable of type 'int'.
32+
}
33+
34+
// If the closure has an optional positional parameter with an implicit type,
35+
// and the closure's context is a function type with an optional positional
36+
// parameter:
37+
//
38+
// - The type of the closure's parameter will be inferred from the
39+
// corresponding optional parameter in the context type
40+
// - And that type in turn will be used as the type inference context for the
41+
// default value of the closure parameter.
42+
void testPositionalInferredFromOptional() {
43+
void Function([List<int>]) f = ([x = const ['foo']]) {};
44+
// ^^^^^
45+
// [analyzer] COMPILE_TIME_ERROR.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE
46+
// [cfe] A value of type 'String' can't be assigned to a variable of type 'int'.
47+
}
48+
49+
// If the closure has an optional named parameter with an explicit type:
50+
//
51+
// - The explicit parameter type will be used as the type inference context for
52+
// the default value of the closure parameter
53+
void testNamedExplicit() {
54+
var f = ({List<int> x = const ['foo']}) {};
55+
// ^^^^^
56+
// [analyzer] COMPILE_TIME_ERROR.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE
57+
// [cfe] A value of type 'String' can't be assigned to a variable of type 'int'.
58+
}
59+
60+
// If the closure has an optional named parameter with an implicit type, and the
61+
// closure's context is a function type with a required named parameter:
62+
//
63+
// - The type of the closure's parameter will be inferred from the
64+
// corresponding required parameter in the context type
65+
// - And that type in turn will be used as the type inference context for the
66+
// default value of the closure parameter.
67+
void testNamedInferredFromRequired() {
68+
void Function({required List<int> x}) f = ({x = const ['foo']}) {};
69+
// ^^^^^
70+
// [analyzer] COMPILE_TIME_ERROR.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE
71+
// [cfe] A value of type 'String' can't be assigned to a variable of type 'int'.
72+
}
73+
74+
// If the closure has an optional named parameter with an implicit type, and the
75+
// closure's context is a function type with an optional named parameter:
76+
//
77+
// - The type of the closure's parameter will be inferred from the
78+
// corresponding optional parameter in the context type
79+
// - And that type in turn will be used as the type inference context for the
80+
// default value of the closure parameter.
81+
void testNamedInferredFromOptional() {
82+
void Function({List<int> x}) f = ({x = const ['foo']}) {};
83+
// ^^^^^
84+
// [analyzer] COMPILE_TIME_ERROR.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE
85+
// [cfe] A value of type 'String' can't be assigned to a variable of type 'int'.
86+
}
87+
88+
main() {}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
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+
// These tests verify that the proper context type is applied to default values
6+
// of closure parameters.
7+
8+
import 'package:expect/expect.dart';
9+
import '../static_type_helper.dart';
10+
11+
// If the closure has an optional positional parameter with an explicit type:
12+
//
13+
// - The explicit parameter type will be used as the type inference context for
14+
// the default value of the closure parameter
15+
void testPositionalExplicit() {
16+
var f = ([List<num> x = const []]) {
17+
x.expectStaticType<Exactly<List<num>>>();
18+
Expect.type<List<num>>(x);
19+
Expect.identical(x, const <num>[]);
20+
};
21+
f();
22+
}
23+
24+
// If the closure has an optional positional parameter with an implicit type,
25+
// and the closure's context is a function type with a required positional
26+
// parameter:
27+
//
28+
// - The type of the closure's parameter will be inferred from the
29+
// corresponding required parameter in the context type
30+
// - And that type in turn will be used as the type inference context for the
31+
// default value of the closure parameter.
32+
void testPositionalInferredFromRequired() {
33+
void Function(List<int>) f = ([x = const []]) {
34+
x.expectStaticType<Exactly<List<int>>>();
35+
Expect.type<List<int>>(x);
36+
Expect.identical(x, const <int>[]);
37+
};
38+
(f as void Function([List<int>]))();
39+
}
40+
41+
// If the closure has an optional positional parameter with an implicit type,
42+
// and the closure's context is a function type with an optional positional
43+
// parameter:
44+
//
45+
// - The type of the closure's parameter will be inferred from the
46+
// corresponding optional parameter in the context type
47+
// - And that type in turn will be used as the type inference context for the
48+
// default value of the closure parameter.
49+
void testPositionalInferredFromOptional() {
50+
void Function([List<int>]) f = ([x = const []]) {
51+
x.expectStaticType<Exactly<List<int>>>();
52+
Expect.type<List<int>>(x);
53+
Expect.identical(x, const <int>[]);
54+
};
55+
f();
56+
}
57+
58+
// If the closure has an optional named parameter with an explicit type:
59+
//
60+
// - The explicit parameter type will be used as the type inference context for
61+
// the default value of the closure parameter
62+
void testNamedExplicit() {
63+
var f = ({List<num> x = const []}) {
64+
x.expectStaticType<Exactly<List<num>>>();
65+
Expect.type<List<num>>(x);
66+
Expect.identical(x, const <num>[]);
67+
};
68+
f();
69+
}
70+
71+
// If the closure has an optional named parameter with an implicit type, and the
72+
// closure's context is a function type with a required named parameter:
73+
//
74+
// - The type of the closure's parameter will be inferred from the
75+
// corresponding required parameter in the context type
76+
// - And that type in turn will be used as the type inference context for the
77+
// default value of the closure parameter.
78+
void testNamedInferredFromRequired() {
79+
void Function({required List<int> x}) f = ({x = const []}) {
80+
x.expectStaticType<Exactly<List<int>>>();
81+
Expect.type<List<int>>(x);
82+
Expect.identical(x, const <int>[]);
83+
};
84+
(f as void Function({List<int> x}))();
85+
}
86+
87+
// If the closure has an optional named parameter with an implicit type, and the
88+
// closure's context is a function type with an optional named parameter:
89+
//
90+
// - The type of the closure's parameter will be inferred from the
91+
// corresponding optional parameter in the context type
92+
// - And that type in turn will be used as the type inference context for the
93+
// default value of the closure parameter.
94+
void testNamedInferredFromOptional() {
95+
void Function({List<int> x}) f = ({x = const []}) {
96+
x.expectStaticType<Exactly<List<int>>>();
97+
Expect.type<List<int>>(x);
98+
Expect.identical(x, const <int>[]);
99+
};
100+
f();
101+
}
102+
103+
main() {
104+
testPositionalExplicit();
105+
testPositionalInferredFromRequired();
106+
testPositionalInferredFromOptional();
107+
testNamedExplicit();
108+
testNamedInferredFromRequired();
109+
testNamedInferredFromOptional();
110+
}

0 commit comments

Comments
 (0)