Skip to content

Commit 08c6045

Browse files
stereotype441Commit Bot
authored and
Commit Bot
committed
Defer analysis of closure arguments when inference-update-1 is enabled.
In order to address dart-lang/language#731 (improved type inference for `fold` etc.) we're going to need to sometimes defer analysis of invocation arguments that are closures, so that closure parameters can have their types inferred based on other parameters. To avoid annoying the user with inconsistent behaviors, we defer analysis of closures in all circumstances, even if it's not necessary to do so for type inference purposes. This has a minor user-visible effect: if an invocation contains some closures and some non-closures, any demotions that happen due to write captures in the closures are postponed until the end of the invocation; this means that the write-captured variables remain promoted for other invocation arguments, even if those arguments appear after the closure. This is safe because there is no way for the closure to be called until after all of the other invocation arguments are evaluated. See the language tests in tests/language/inference_update_1/write_capture_deferral_enabled_test.dart for details. Note that this change only has an effect when the experimental feature `inference-update-1` is enabled. Change-Id: If7bb38e361755180c033ecb2108fc4fffa7570b1 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/241864 Reviewed-by: Chloe Stefantsova <[email protected]> Commit-Queue: Paul Berry <[email protected]>
1 parent edb7fb4 commit 08c6045

12 files changed

+444
-51
lines changed

pkg/front_end/lib/src/fasta/source/source_library_builder.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,11 @@ class SourceLibraryBuilder extends LibraryBuilderImpl {
463463
return type;
464464
}
465465

466+
bool get isInferenceUpdate1Enabled =>
467+
libraryFeatures.inferenceUpdate1.isSupported &&
468+
languageVersion.version >=
469+
libraryFeatures.inferenceUpdate1.enabledVersion;
470+
466471
bool? _isNonNullableByDefault;
467472

468473
@override

pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart

Lines changed: 137 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,9 @@ class TypeInferrerImpl implements TypeInferrer {
250250

251251
CoreTypes get coreTypes => engine.coreTypes;
252252

253+
bool get isInferenceUpdate1Enabled =>
254+
libraryBuilder.isInferenceUpdate1Enabled;
255+
253256
bool get isNonNullableByDefault => libraryBuilder.isNonNullableByDefault;
254257

255258
NnbdMode get nnbdMode => libraryBuilder.loader.nnbdMode;
@@ -2395,10 +2398,41 @@ class TypeInferrerImpl implements TypeInferrer {
23952398
hoistingEndIndex = 0;
23962399
}
23972400

2401+
ExpressionInferenceResult inferArgument(
2402+
DartType formalType, Expression argumentExpression,
2403+
{required bool isNamed}) {
2404+
DartType inferredFormalType = substitution != null
2405+
? substitution.substituteType(formalType)
2406+
: formalType;
2407+
if (!isNamed) {
2408+
if (isSpecialCasedBinaryOperator) {
2409+
inferredFormalType =
2410+
typeSchemaEnvironment.getContextTypeOfSpecialCasedBinaryOperator(
2411+
typeContext, receiverType!, inferredFormalType,
2412+
isNonNullableByDefault: isNonNullableByDefault);
2413+
} else if (isSpecialCasedTernaryOperator) {
2414+
inferredFormalType =
2415+
typeSchemaEnvironment.getContextTypeOfSpecialCasedTernaryOperator(
2416+
typeContext, receiverType!, inferredFormalType,
2417+
isNonNullableByDefault: isNonNullableByDefault);
2418+
}
2419+
}
2420+
return inferExpression(
2421+
argumentExpression,
2422+
isNonNullableByDefault
2423+
? inferredFormalType
2424+
: legacyErasure(inferredFormalType),
2425+
inferenceNeeded ||
2426+
isSpecialCasedBinaryOperator ||
2427+
isSpecialCasedTernaryOperator ||
2428+
typeChecksNeeded);
2429+
}
2430+
23982431
List<EqualityInfo<VariableDeclaration, DartType>?>? identicalInfo =
23992432
isIdentical && arguments.positional.length == 2 ? [] : null;
24002433
int positionalIndex = 0;
24012434
int namedIndex = 0;
2435+
List<_DeferredParamInfo>? deferredFunctionLiterals;
24022436
for (int evaluationOrderIndex = 0;
24032437
evaluationOrderIndex < argumentsEvaluationOrder.length;
24042438
evaluationOrderIndex++) {
@@ -2421,60 +2455,77 @@ class TypeInferrerImpl implements TypeInferrer {
24212455
formalType = getNamedParameterType(calleeType, namedArgument.name);
24222456
argumentExpression = namedArgument.value;
24232457
}
2424-
DartType inferredFormalType = substitution != null
2425-
? substitution.substituteType(formalType)
2426-
: formalType;
2427-
if (isExpression) {
2428-
if (isImplicitExtensionMember && index == 0) {
2429-
assert(
2430-
receiverType != null,
2431-
"No receiver type provided for implicit extension member "
2432-
"invocation.");
2433-
continue;
2434-
}
2435-
if (isSpecialCasedBinaryOperator) {
2436-
inferredFormalType =
2437-
typeSchemaEnvironment.getContextTypeOfSpecialCasedBinaryOperator(
2438-
typeContext, receiverType!, inferredFormalType,
2439-
isNonNullableByDefault: isNonNullableByDefault);
2440-
} else if (isSpecialCasedTernaryOperator) {
2441-
inferredFormalType =
2442-
typeSchemaEnvironment.getContextTypeOfSpecialCasedTernaryOperator(
2443-
typeContext, receiverType!, inferredFormalType,
2444-
isNonNullableByDefault: isNonNullableByDefault);
2445-
}
2446-
}
2447-
ExpressionInferenceResult result = inferExpression(
2448-
argumentExpression,
2449-
isNonNullableByDefault
2450-
? inferredFormalType
2451-
: legacyErasure(inferredFormalType),
2452-
inferenceNeeded ||
2453-
isSpecialCasedBinaryOperator ||
2454-
isSpecialCasedTernaryOperator ||
2455-
typeChecksNeeded);
2456-
DartType inferredType = identical(result.inferredType, noInferredType) ||
2457-
isNonNullableByDefault
2458-
? result.inferredType
2459-
: legacyErasure(result.inferredType);
2460-
if (localHoistedExpressions != null &&
2461-
evaluationOrderIndex >= hoistingEndIndex) {
2462-
hoistedExpressions = null;
2458+
if (isExpression && isImplicitExtensionMember && index == 0) {
2459+
assert(
2460+
receiverType != null,
2461+
"No receiver type provided for implicit extension member "
2462+
"invocation.");
2463+
continue;
24632464
}
2464-
Expression expression =
2465-
_hoist(result.expression, inferredType, hoistedExpressions);
2466-
identicalInfo
2467-
?.add(flowAnalysis.equalityOperand_end(expression, inferredType));
2468-
if (isExpression) {
2469-
arguments.positional[index] = expression..parent = arguments;
2465+
if (isInferenceUpdate1Enabled &&
2466+
argumentExpression is FunctionExpression) {
2467+
(deferredFunctionLiterals ??= []).add(new _DeferredParamInfo(
2468+
formalType: formalType,
2469+
argumentExpression: argumentExpression,
2470+
isNamed: !isExpression,
2471+
evaluationOrderIndex: evaluationOrderIndex,
2472+
index: index));
2473+
// We don't have `identical` info yet, so fill it in with `null` for
2474+
// now. Later, when we visit the function literal, we'll replace it.
2475+
identicalInfo?.add(null);
2476+
if (useFormalAndActualTypes) {
2477+
formalTypes!.add(formalType);
2478+
// We don't have an inferred type yet, so fill it in with UnknownType
2479+
// for now. Later, when we infer a type, we'll replace it.
2480+
actualTypes!.add(const UnknownType());
2481+
}
24702482
} else {
2471-
NamedExpression namedArgument = arguments.named[index];
2472-
namedArgument.value = expression..parent = namedArgument;
2483+
ExpressionInferenceResult result = inferArgument(
2484+
formalType, argumentExpression,
2485+
isNamed: !isExpression);
2486+
DartType inferredType = _computeInferredType(result);
2487+
if (localHoistedExpressions != null &&
2488+
evaluationOrderIndex >= hoistingEndIndex) {
2489+
hoistedExpressions = null;
2490+
}
2491+
Expression expression =
2492+
_hoist(result.expression, inferredType, hoistedExpressions);
2493+
identicalInfo
2494+
?.add(flowAnalysis.equalityOperand_end(expression, inferredType));
2495+
if (isExpression) {
2496+
arguments.positional[index] = expression..parent = arguments;
2497+
} else {
2498+
NamedExpression namedArgument = arguments.named[index];
2499+
namedArgument.value = expression..parent = namedArgument;
2500+
}
2501+
gatherer?.tryConstrainLower(formalType, inferredType);
2502+
if (useFormalAndActualTypes) {
2503+
formalTypes!.add(formalType);
2504+
actualTypes!.add(inferredType);
2505+
}
24732506
}
2474-
gatherer?.tryConstrainLower(formalType, inferredType);
2475-
if (useFormalAndActualTypes) {
2476-
formalTypes!.add(formalType);
2477-
actualTypes!.add(inferredType);
2507+
}
2508+
if (deferredFunctionLiterals != null) {
2509+
for (_DeferredParamInfo deferredArgument in deferredFunctionLiterals) {
2510+
ExpressionInferenceResult result = inferArgument(
2511+
deferredArgument.formalType, deferredArgument.argumentExpression,
2512+
isNamed: deferredArgument.isNamed);
2513+
DartType inferredType = _computeInferredType(result);
2514+
Expression expression = result.expression;
2515+
identicalInfo?[deferredArgument.evaluationOrderIndex] =
2516+
flowAnalysis.equalityOperand_end(expression, inferredType);
2517+
if (deferredArgument.isNamed) {
2518+
NamedExpression namedArgument =
2519+
arguments.named[deferredArgument.index];
2520+
namedArgument.value = expression..parent = namedArgument;
2521+
} else {
2522+
arguments.positional[deferredArgument.index] = expression
2523+
..parent = arguments;
2524+
}
2525+
gatherer?.tryConstrainLower(deferredArgument.formalType, inferredType);
2526+
if (useFormalAndActualTypes) {
2527+
actualTypes![deferredArgument.evaluationOrderIndex] = inferredType;
2528+
}
24782529
}
24792530
}
24802531
if (identicalInfo != null) {
@@ -4718,6 +4769,11 @@ class TypeInferrerImpl implements TypeInferrer {
47184769
Expression createEqualsNull(int fileOffset, Expression left) {
47194770
return new EqualsNull(left)..fileOffset = fileOffset;
47204771
}
4772+
4773+
DartType _computeInferredType(ExpressionInferenceResult result) =>
4774+
identical(result.inferredType, noInferredType) || isNonNullableByDefault
4775+
? result.inferredType
4776+
: legacyErasure(result.inferredType);
47214777
}
47224778

47234779
class TypeInferrerImplBenchmarked implements TypeInferrer {
@@ -5877,3 +5933,33 @@ class ImplicitInstantiation {
58775933
ImplicitInstantiation(
58785934
this.typeArguments, this.functionType, this.instantiatedType);
58795935
}
5936+
5937+
/// Information about an invocation argument that needs to be resolved later due
5938+
/// to the fact that it's a function literal and the `inference-update-1`
5939+
/// feature is enabled.
5940+
class _DeferredParamInfo {
5941+
/// The (unsubstituted) type of the formal parameter corresponding to this
5942+
/// argument.
5943+
final DartType formalType;
5944+
5945+
/// The function literal expression.
5946+
final FunctionExpression argumentExpression;
5947+
5948+
/// Indicates whether this is a named argument.
5949+
final bool isNamed;
5950+
5951+
/// The index into the full argument list (considering both named and unnamed
5952+
/// arguments) of the function literal expression.
5953+
final int evaluationOrderIndex;
5954+
5955+
/// The index into either [Arguments.named] or [Arguments.positional] of the
5956+
/// function literal expression (depending upon the value of [isNamed]).
5957+
final int index;
5958+
5959+
_DeferredParamInfo(
5960+
{required this.formalType,
5961+
required this.argumentExpression,
5962+
required this.isNamed,
5963+
required this.evaluationOrderIndex,
5964+
required this.index});
5965+
}

pkg/front_end/test/spell_checking_list_code.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ configs
248248
configured
249249
connectivity
250250
consideration
251+
considering
251252
constness
252253
constraining
253254
consult
@@ -1441,6 +1442,7 @@ unset
14411442
unshadowed
14421443
unsortable
14431444
unsound
1445+
unsubstituted
14441446
untouched
14451447
unwrapper
14461448
unwraps

pkg/front_end/test/spell_checking_list_tests.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@ implemen
341341
implementor
342342
implementors
343343
imprecision
344+
improvement
344345
inclosure
345346
inconsistencies
346347
increasing
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--enable-experiment=inference-update-1
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright (c) 2022, 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+
// Tests that when the feature is enabled, if an invocation argument is a
6+
// closure, write captures made by that closure do not take effect until after
7+
// the invocation. This is a minor improvement to flow analysis that falls
8+
// naturally out of the fact that closures are analyzed last (so that their
9+
// types can depend on the types of other arguments).
10+
11+
withUnnamedArguments(int? i, void Function(void Function(), Object?) f) {
12+
if (i != null) {
13+
f(() {
14+
i = null;
15+
}, i);
16+
i;
17+
}
18+
}
19+
20+
withNamedArguments(
21+
int? i, void Function({required void Function() g, Object? x}) f) {
22+
if (i != null) {
23+
f(
24+
g: () {
25+
i = null;
26+
},
27+
x: i);
28+
i;
29+
}
30+
}
31+
32+
withIdentical_lhs(int? i) {
33+
if (i != null) {
34+
i;
35+
identical(() {
36+
i = null;
37+
}, i);
38+
i;
39+
}
40+
}
41+
42+
withIdentical_rhs(int? i) {
43+
if (i != null) {
44+
identical(i, () {
45+
i = null;
46+
});
47+
i;
48+
}
49+
}
50+
51+
class B {
52+
B(Object? x, void Function() g, Object? y);
53+
B.redirectingConstructorInvocation(int? i)
54+
: this(i!, () {
55+
i = null;
56+
}, i);
57+
}
58+
59+
class C extends B {
60+
C.superConstructorInvocation(int? i)
61+
: super(i!, () {
62+
i = null;
63+
}, i);
64+
}
65+
66+
main() {}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
withUnnamedArguments(int? i, void Function(void Function(), Object?) f) {}
2+
withNamedArguments(
3+
int? i, void Function({required void Function() g, Object? x}) f) {}
4+
withIdentical_lhs(int? i) {}
5+
withIdentical_rhs(int? i) {}
6+
7+
class B {
8+
B(Object? x, void Function() g, Object? y);
9+
B.redirectingConstructorInvocation(int? i)
10+
: this(i!, () {
11+
i = null;
12+
}, i);
13+
}
14+
15+
class C extends B {
16+
C.superConstructorInvocation(int? i)
17+
: super(i!, () {
18+
i = null;
19+
}, i);
20+
}
21+
22+
main() {}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
class B {
2+
B(Object? x, void Function() g, Object? y);
3+
B.redirectingConstructorInvocation(int? i)
4+
: this(i!, () {
5+
i = null;
6+
}, i);
7+
}
8+
9+
class C extends B {
10+
C.superConstructorInvocation(int? i)
11+
: super(i!, () {
12+
i = null;
13+
}, i);
14+
}
15+
16+
main() {}
17+
withIdentical_lhs(int? i) {}
18+
withIdentical_rhs(int? i) {}
19+
withNamedArguments(
20+
int? i, void Function({required void Function() g, Object? x}) f) {}
21+
withUnnamedArguments(int? i, void Function(void Function(), Object?) f) {}

0 commit comments

Comments
 (0)