[_fe_analyzer_shared] Move analysis of await expressions to shared logic.
Moves the bulk of the business logic for type analyzing await expressions from the analyzer and front_end codebases into the shared `TypeAnalyzer` class. There is no functional change. This paves the way for fixing https://github.com/dart-lang/sdk/issues/62889 (Unsound type promotion in inner async/generator functions), which will require the type analysis of await expressions to be integrated more closely with flow analysis. Sharing the type analysis logic will avoid the need to do that integration twice. Change-Id: I9f8770ce8127a960b746a89ee3e370ed6a6a6964 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/489480 Reviewed-by: Chloe Stefantsova <cstefantsova@google.com> Commit-Queue: Paul Berry <paulberry@google.com>
This commit is contained in:
@@ -23,6 +23,19 @@ class AssignedVariablePatternResult<Error> extends PatternResult {
|
||||
});
|
||||
}
|
||||
|
||||
/// Result for analyzing an await expression in
|
||||
/// [TypeAnalyzer.analyzeSwitchExpression].
|
||||
class AwaitExpressionResult extends ExpressionTypeAnalysisResult {
|
||||
/// The static type of the operand.
|
||||
final SharedTypeView operandType;
|
||||
|
||||
AwaitExpressionResult({
|
||||
required super.type,
|
||||
super.flowAnalysisInfo,
|
||||
required this.operandType,
|
||||
});
|
||||
}
|
||||
|
||||
/// Result for analyzing a constant pattern in
|
||||
/// [TypeAnalyzer.analyzeConstantPattern].
|
||||
class ConstantPatternResult<Error> extends PatternResult {
|
||||
|
||||
@@ -380,6 +380,65 @@ mixin TypeAnalyzer<
|
||||
flow.promotedType(variable) ?? operations.variableType(variable),
|
||||
);
|
||||
|
||||
/// Analyzes an expression of the form `await operand`.
|
||||
///
|
||||
/// Returns an [AwaitExpressionResult] containing the static type of the await
|
||||
/// expression and its operand.
|
||||
///
|
||||
/// Stack effect: pushes the operand expression.
|
||||
AwaitExpressionResult analyzeAwaitExpression(
|
||||
Expression operand,
|
||||
SharedTypeSchemaView schema,
|
||||
) {
|
||||
// Stack: ()
|
||||
|
||||
// (Note: comments pulled from
|
||||
// https://github.com/dart-lang/language/blob/main/resources/type-system/inference.md)
|
||||
|
||||
// Expression inference of an await expression await e_1, in context K,
|
||||
// produces an elaborated expression m with static type T, where m and T are
|
||||
// determined as follows:
|
||||
SharedTypeSchemaView k = schema;
|
||||
|
||||
// Define K_1 as follows:
|
||||
// - If K is FutureOr<S> or FutureOr<S>? for some type schema S, then let
|
||||
// K_1 be K.
|
||||
// - Otherwise, if K is dynamic, then let K_1 be FutureOr<_>.
|
||||
// - Otherwise, let K_1 be FutureOr<K>.
|
||||
assert(
|
||||
schema is! SharedDynamicTypeSchemaView,
|
||||
'Caller should convert dynamic context to _',
|
||||
);
|
||||
SharedTypeSchemaView k1 = operations.matchTypeSchemaFutureOr(k) != null
|
||||
? k
|
||||
: operations.futureOrTypeSchema(k);
|
||||
|
||||
// Let m_1 be the result of performing expression inference on e_1, in
|
||||
// context K_1.
|
||||
ExpressionTypeAnalysisResult m1 = analyzeExpression(
|
||||
operand,
|
||||
k1,
|
||||
isVoidAllowed: false,
|
||||
);
|
||||
// Stack: (operand)
|
||||
|
||||
// Let T_1 be the static type of m_1.
|
||||
SharedTypeView t1 = m1.type;
|
||||
|
||||
// If T_1 is incompatible with await (as defined in the extension types
|
||||
// specification), then there is a compile-time error.
|
||||
// (Currently this error is detected by the analyzer and front_end clients,
|
||||
// not by shared code. TODO(paulberry): share this logic.)
|
||||
|
||||
// Let T_2 be flatten(T_1).
|
||||
SharedTypeView t2 = operations.flatten(t1);
|
||||
|
||||
// Let m_2 be @AWAIT_WITH_TYPE_CHECK(m_1), with static type Future<T_2>.
|
||||
|
||||
// Let T be T_2, and let m be `await m_2`.
|
||||
return new AwaitExpressionResult(type: t2, operandType: t1);
|
||||
}
|
||||
|
||||
/// Analyzes a cast pattern. [innerPattern] is the sub-pattern] and
|
||||
/// [requiredType] is the type to cast to.
|
||||
///
|
||||
|
||||
@@ -138,6 +138,57 @@ abstract interface class TypeAnalyzerOperations<
|
||||
required bool inferenceUsingBoundsIsEnabled,
|
||||
});
|
||||
|
||||
/// Computes `flatten` of [type].
|
||||
///
|
||||
/// See the `Function Expressions` section of
|
||||
/// https://storage.googleapis.com/dart-specification/DartLangSpecDraft.pdf.
|
||||
SharedTypeView flatten(SharedTypeView type);
|
||||
|
||||
/// Returns the type `FutureOr` with omitted nullability and type argument
|
||||
/// [argumentTypeSchema].
|
||||
///
|
||||
/// The concrete classes implementing [TypeAnalyzerOperations] should mix in
|
||||
/// [TypeAnalyzerOperationsMixin] and implement [futureOrTypeInternal] to
|
||||
/// receive a concrete implementation of [futureOrType] instead of
|
||||
/// implementing [futureOrType] directly.
|
||||
SharedTypeView futureOrType(SharedTypeView argumentTypeSchema);
|
||||
|
||||
/// [futureOrTypeInternal] should be implemented by concrete classes
|
||||
/// implementing [TypeAnalyzerOperations]. The implementations of
|
||||
/// [futureOrType] and [futureOrTypeSchema] are provided by mixing in
|
||||
/// [TypeAnalyzerOperationsMixin], which defines [futureOrType] and
|
||||
/// [futureOrTypeSchema] in terms of [futureOrTypeInternal].
|
||||
///
|
||||
/// The main purpose of this method is to avoid code duplication in the
|
||||
/// concrete classes implementing [TypeAnalyzerOperations], so they can
|
||||
/// implement only one member, in this case [futureOrTypeInternal], and
|
||||
/// receive the implementation of both [futureOrType] and [futureOrTypeSchema]
|
||||
/// from the mixin.
|
||||
///
|
||||
/// The auxiliary purpose of [futureOrTypeInternal] is to facilitate the
|
||||
/// development of the shared code at early stages. Sometimes the sharing of
|
||||
/// the code starts by unifying the implementations of some concrete members
|
||||
/// in the Analyzer and the CFE by bringing them in a form that looks
|
||||
/// syntactically very similar in both tools, and then continues by
|
||||
/// abstracting the two concrete members and using the shared abstracted one
|
||||
/// instead of the two concrete methods existing previously. During the early
|
||||
/// stages of unifying the two concrete members it can be beneficial to use
|
||||
/// [futureOrTypeInternal] instead of the tool-specific ways of constructing a
|
||||
/// future type, for the sake of uniformity, and to simplify the abstraction
|
||||
/// step too.
|
||||
SharedType futureOrTypeInternal(covariant SharedType typeStructure);
|
||||
|
||||
/// Returns the type schema `FutureOr` with omitted nullability and type
|
||||
/// argument [argumentTypeSchema].
|
||||
///
|
||||
/// The concrete classes implementing [TypeAnalyzerOperations] should mix in
|
||||
/// [TypeAnalyzerOperationsMixin] and implement [futureOrTypeInternal] to
|
||||
/// receive a concrete implementation of [futureOrTypeSchema] instead of
|
||||
/// implementing [futureOrTypeSchema] directly.
|
||||
SharedTypeSchemaView futureOrTypeSchema(
|
||||
SharedTypeSchemaView argumentTypeSchema,
|
||||
);
|
||||
|
||||
/// Returns the type `Future` with omitted nullability and type argument
|
||||
/// [argumentType].
|
||||
///
|
||||
@@ -1066,6 +1117,22 @@ mixin TypeAnalyzerOperationsMixin<
|
||||
return inferredTypes;
|
||||
}
|
||||
|
||||
@override
|
||||
SharedTypeView futureOrType(SharedTypeView argumentType) {
|
||||
return new SharedTypeView(
|
||||
futureOrTypeInternal(argumentType.unwrapTypeView()),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
SharedTypeSchemaView futureOrTypeSchema(
|
||||
SharedTypeSchemaView argumentTypeSchema,
|
||||
) {
|
||||
return new SharedTypeSchemaView(
|
||||
futureOrTypeInternal(argumentTypeSchema.unwrapTypeSchemaView()),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
SharedTypeView futureType(SharedTypeView argumentType) {
|
||||
return new SharedTypeView(
|
||||
|
||||
@@ -58,6 +58,14 @@ Statement assert_(ProtoExpression condition, [ProtoExpression? message]) {
|
||||
);
|
||||
}
|
||||
|
||||
Expression await_(ProtoExpression operand) {
|
||||
var location = computeLocation();
|
||||
return new AwaitExpression._(
|
||||
operand.asExpression(location: location),
|
||||
location: location,
|
||||
);
|
||||
}
|
||||
|
||||
Statement block(List<ProtoStatement> statements) =>
|
||||
new Block._(statements, location: computeLocation());
|
||||
|
||||
@@ -716,6 +724,32 @@ class Assert extends Statement {
|
||||
}
|
||||
}
|
||||
|
||||
class AwaitExpression extends Expression {
|
||||
final Expression operand;
|
||||
|
||||
AwaitExpression._(this.operand, {required super.location});
|
||||
|
||||
@override
|
||||
void preVisit(PreVisitor visitor) {
|
||||
operand.preVisit(visitor);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => 'await $operand';
|
||||
|
||||
@override
|
||||
ExpressionTypeAnalysisResult visit(Harness h, SharedTypeSchemaView schema) {
|
||||
var result = h.typeAnalyzer.analyzeAwaitExpression(operand, schema);
|
||||
h.irBuilder.apply(
|
||||
'awaitExpr',
|
||||
[Kind.expression],
|
||||
Kind.expression,
|
||||
location: location,
|
||||
);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class Block extends Statement {
|
||||
final List<Statement> statements;
|
||||
|
||||
@@ -3261,6 +3295,54 @@ class MiniAstOperations
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
SharedTypeView flatten(SharedTypeView type) {
|
||||
// (Note: comments below are pulled from the definition of the "flatten"
|
||||
// function in the "Function Expressions" section of the language spec.)
|
||||
|
||||
// We define the auxiliary function flatten(T) as follows, using the first
|
||||
// applicable case:
|
||||
var t = type.unwrapTypeView<Type>();
|
||||
|
||||
// - If T is X & S for some type variable X and type S then
|
||||
if (t case TypeParameterType(:var typeParameter, promotion: var s?)) {
|
||||
// - If S derives a future type U then flatten(T) ≜ flatten(U).
|
||||
if (_typeSystem.derivedFutureType(s) case var u?) {
|
||||
return flatten(u.wrapSharedTypeView());
|
||||
}
|
||||
|
||||
// - otherwise, flatten(T) ≜ flatten(X)
|
||||
return flatten(TypeParameterType(typeParameter).wrapSharedTypeView());
|
||||
}
|
||||
|
||||
// - If T derives a future type Future<S> or FutureOr<S> then
|
||||
// flatten(T) ≜ S.
|
||||
// - If T derives a future type Future<S>? or FutureOr<S>? then
|
||||
// flatten(T) ≜ S?.
|
||||
if (_typeSystem.derivedFutureType(t) case var f?) {
|
||||
var s = switch (f) {
|
||||
PrimaryType(nameInfo: TypeNameInfo(name: 'Future'), args: [var s]) ||
|
||||
FutureOrType(typeArgument: var s) => s,
|
||||
_ => fail(
|
||||
'Derived future type should always be Future<...> or FutureOr<...>',
|
||||
),
|
||||
};
|
||||
if (f.isQuestionType) {
|
||||
return s.asQuestionType(true).wrapSharedTypeView();
|
||||
} else {
|
||||
return s.wrapSharedTypeView();
|
||||
}
|
||||
}
|
||||
|
||||
// - Otherwise, flatten(T) ≜ T.
|
||||
return t.wrapSharedTypeView();
|
||||
}
|
||||
|
||||
@override
|
||||
Type futureOrTypeInternal(Type argumentType) {
|
||||
return FutureOrType(argumentType);
|
||||
}
|
||||
|
||||
@override
|
||||
Type futureTypeInternal(Type argumentType) {
|
||||
return PrimaryType(TypeRegistry.future, args: [argumentType]);
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'dart:core' hide Type;
|
||||
|
||||
import 'package:_fe_analyzer_shared/src/types/shared_type.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
/// Surrounds [s] with parentheses if [condition] is `true`, otherwise returns
|
||||
/// [s] unchanged.
|
||||
@@ -1246,6 +1247,50 @@ class TypeSystem {
|
||||
_superInterfaceTemplates[className] = template;
|
||||
}
|
||||
|
||||
/// If [t] derives a future type `F` (as defined in the "Function Expressions"
|
||||
/// section of the language spec), returns `F`. Otherwise returns `null`.
|
||||
Type? derivedFutureType(Type t) {
|
||||
// (Note: comments below are pulled from the definition of "derives a future
|
||||
// type" in the "Function Expressions" section of the language spec.)
|
||||
|
||||
// We say that a type T derives a future type F in the following cases,
|
||||
// using the first applicable case:
|
||||
// - If T is a type which is introduced by a class, mixin, or enum
|
||||
// declaration, and if T or a direct or indirect superinterface of T is
|
||||
// Future<U> for some U, then T derives the future type Future<U>.
|
||||
if (t case PrimaryType(isInterfaceType: true, isQuestionType: false)) {
|
||||
for (var f in [t, ..._getSuperInterfaces(t)]) {
|
||||
if (f case PrimaryType(nameInfo: TypeNameInfo(name: 'Future'))) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// - If T is the type FutureOr<U> for some U, then T derives the future type
|
||||
// FutureOr<U>.
|
||||
if (t is FutureOrType) {
|
||||
return t;
|
||||
}
|
||||
|
||||
// - If T is S? for some S, and S derives the future type F, then T derives
|
||||
// the future type F?.
|
||||
if (t.isQuestionType) {
|
||||
if (derivedFutureType(t.asQuestionType(false)) case var f?) {
|
||||
return f.asQuestionType(true);
|
||||
}
|
||||
}
|
||||
|
||||
// - If T is a type variable with bound B, and B derives the future type F,
|
||||
// then T derives the future type F.
|
||||
if (t case TypeParameterType(bound: var b)) {
|
||||
if (derivedFutureType(b) case var f?) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Type factor(Type t, Type s) {
|
||||
// If T <: S then Never
|
||||
if (isSubtype(t, s)) return NeverType.instance;
|
||||
@@ -1551,12 +1596,7 @@ class TypeSystem {
|
||||
// Super-Interface: T0 is an interface type with super-interfaces S0,...Sn
|
||||
bool isSuperInterfaceSubtype() {
|
||||
if (t0 is! PrimaryType) return false;
|
||||
var superInterfaceTemplate = _superInterfaceTemplates[t0.name];
|
||||
if (superInterfaceTemplate == null) {
|
||||
assert(false, 'Superinterfaces for $t0 not known');
|
||||
return false;
|
||||
}
|
||||
var superInterfaces = superInterfaceTemplate(t0.args);
|
||||
var superInterfaces = _getSuperInterfaces(t0);
|
||||
|
||||
// - and Si <: T1 for some i
|
||||
for (var superInterface in superInterfaces) {
|
||||
@@ -1728,6 +1768,14 @@ class TypeSystem {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<Type> _getSuperInterfaces(PrimaryType t) {
|
||||
var superInterfaceTemplate = _superInterfaceTemplates[t.name];
|
||||
if (superInterfaceTemplate == null) {
|
||||
fail('Superinterfaces for $t not known');
|
||||
}
|
||||
return superInterfaceTemplate(t.args);
|
||||
}
|
||||
|
||||
bool _isTop(Type t) {
|
||||
if (t is PrimaryType) {
|
||||
return t is DynamicType || t is InvalidType || t is VoidType;
|
||||
|
||||
@@ -294,6 +294,75 @@ main() {
|
||||
});
|
||||
|
||||
group('Expressions:', () {
|
||||
group('await:', () {
|
||||
group('AwaitExpressionResult:', () {
|
||||
test('operandType', () {
|
||||
h.run([
|
||||
await_(expr('int')).checkExpressionTypeAnalysisResult((result) {
|
||||
result as AwaitExpressionResult;
|
||||
expect(result.operandType.toString(), 'int');
|
||||
}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
group('Downward inference:', () {
|
||||
test('Schema is FutureOr<S>', () {
|
||||
h.run([
|
||||
await_(
|
||||
expr('int').checkSchema('FutureOr<int>'),
|
||||
).inTypeSchema('FutureOr<int>'),
|
||||
]);
|
||||
});
|
||||
|
||||
test('Schema is FutureOr<S>?', () {
|
||||
h.run([
|
||||
await_(
|
||||
expr('int').checkSchema('FutureOr<int>?'),
|
||||
).inTypeSchema('FutureOr<int>?'),
|
||||
]);
|
||||
});
|
||||
|
||||
test('Schema is dynamic', () {
|
||||
h.run([
|
||||
await_(
|
||||
expr('int').checkSchema('FutureOr<_>'),
|
||||
).inTypeSchema('dynamic'),
|
||||
]);
|
||||
});
|
||||
|
||||
test('Schema is other type', () {
|
||||
h.run([
|
||||
await_(
|
||||
expr('int').checkSchema('FutureOr<int>'),
|
||||
).inTypeSchema('int'),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
group('Upward inference:', () {
|
||||
test('Operand has type Future<S>', () {
|
||||
h.run([await_(expr('Future<int>')).checkType('int')]);
|
||||
});
|
||||
|
||||
test('Operand has type FutureOr<S>', () {
|
||||
h.run([await_(expr('FutureOr<int>')).checkType('int')]);
|
||||
});
|
||||
|
||||
test('Operand has type Future<S>?', () {
|
||||
h.run([await_(expr('Future<int>?')).checkType('int?')]);
|
||||
});
|
||||
|
||||
test('Operand has type FutureOr<S>?', () {
|
||||
h.run([await_(expr('FutureOr<int>?')).checkType('int?')]);
|
||||
});
|
||||
|
||||
test('Operand has other type', () {
|
||||
h.run([await_(expr('int')).checkType('int')]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('cascade:', () {
|
||||
group('IR:', () {
|
||||
test('not null-aware', () {
|
||||
|
||||
@@ -566,6 +566,16 @@ class TypeSystemOperations
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
SharedTypeView flatten(SharedTypeView type) {
|
||||
return typeSystem.flatten(type.unwrapTypeView()).wrapSharedTypeView();
|
||||
}
|
||||
|
||||
@override
|
||||
SharedType futureOrTypeInternal(TypeImpl argumentType) {
|
||||
return typeSystem.typeProvider.futureOrType(argumentType);
|
||||
}
|
||||
|
||||
@override
|
||||
TypeImpl futureTypeInternal(TypeImpl argumentType) {
|
||||
return typeSystem.typeProvider.futureType(argumentType);
|
||||
|
||||
@@ -2146,12 +2146,15 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
|
||||
}) {
|
||||
inferenceLogWriter?.enterExpression(node, contextType);
|
||||
checkUnreachableNode(node);
|
||||
analyzeExpression(
|
||||
var analysisResult = analyzeAwaitExpression(
|
||||
node.expression,
|
||||
SharedTypeSchemaView(_createFutureOr(contextType)),
|
||||
contextType.wrapSharedTypeSchemaView(),
|
||||
);
|
||||
node.expression = popRewrite()!;
|
||||
node.recordStaticType(
|
||||
analysisResult.type.unwrapTypeView<TypeImpl>(),
|
||||
resolver: this,
|
||||
);
|
||||
popRewrite();
|
||||
typeAnalyzer.visitAwaitExpression(node);
|
||||
_insertImplicitCallReference(
|
||||
insertGenericFunctionInstantiation(node, contextType: contextType),
|
||||
contextType: contextType,
|
||||
@@ -4541,15 +4544,6 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a union of `T | Future<T>`, unless `T` is already a
|
||||
/// future-union, in which case it simply returns `T`.
|
||||
TypeImpl _createFutureOr(TypeImpl type) {
|
||||
if (type.isDartAsyncFutureOr) {
|
||||
return type;
|
||||
}
|
||||
return typeProvider.futureOrType(type);
|
||||
}
|
||||
|
||||
/// Helper function used to print information to the console in debug mode.
|
||||
/// This method returns `true` so that it can be conveniently called inside of
|
||||
/// an `assert` statement.
|
||||
|
||||
@@ -58,16 +58,6 @@ class StaticTypeAnalyzer {
|
||||
node.recordStaticType(_getType(node.type), resolver: _resolver);
|
||||
}
|
||||
|
||||
/// The Dart Language Specification, 16.29 (Await Expressions):
|
||||
///
|
||||
/// The static type of [the expression "await e"] is flatten(T) where T is
|
||||
/// the static type of e.
|
||||
void visitAwaitExpression(covariant AwaitExpressionImpl node) {
|
||||
var resultType = node.expression.typeOrThrow;
|
||||
resultType = _typeSystem.flatten(resultType);
|
||||
node.recordStaticType(resultType, resolver: _resolver);
|
||||
}
|
||||
|
||||
/// The Dart Language Specification, 12.4: <blockquote>The static type of a boolean literal is
|
||||
/// bool.</blockquote>
|
||||
void visitBooleanLiteral(covariant BooleanLiteralImpl node) {
|
||||
|
||||
@@ -1213,17 +1213,16 @@ class InferenceVisitorImpl extends InferenceVisitorBase
|
||||
if (typeContext is DynamicType) {
|
||||
typeContext = const UnknownType();
|
||||
}
|
||||
typeContext = wrapFutureOrType(typeContext);
|
||||
ExpressionInferenceResult operandResult = inferExpression(
|
||||
AwaitExpressionResult analysisResult = analyzeAwaitExpression(
|
||||
node.operand,
|
||||
typeContext,
|
||||
isVoidAllowed: false,
|
||||
typeContext.wrapSharedTypeSchemaView(),
|
||||
);
|
||||
DartType operandType = operandResult.inferredType;
|
||||
DartType flattenType = typeSchemaEnvironment.flatten(operandType);
|
||||
Expression operandRewrite = popRewrite() as Expression;
|
||||
DartType operandType = analysisResult.operandType.unwrapTypeView();
|
||||
DartType flattenType = analysisResult.type.unwrapTypeView();
|
||||
if (_isIncompatibleWithAwait(operandType)) {
|
||||
Expression wrapped = operandResult.expression;
|
||||
node.operand = problemReporting.wrapInProblem(
|
||||
Expression wrapped = operandRewrite;
|
||||
operandRewrite = problemReporting.wrapInProblem(
|
||||
compilerContext: compilerContext,
|
||||
expression: wrapped,
|
||||
message: diag.awaitOfExtensionTypeNotFuture,
|
||||
@@ -1231,10 +1230,9 @@ class InferenceVisitorImpl extends InferenceVisitorBase
|
||||
fileOffset: wrapped.fileOffset,
|
||||
length: 1,
|
||||
);
|
||||
wrapped.parent = node.operand;
|
||||
} else {
|
||||
node.operand = operandResult.expression..parent = node;
|
||||
wrapped.parent = operandRewrite;
|
||||
}
|
||||
node.operand = operandRewrite..parent = node;
|
||||
DartType runtimeCheckType = new InterfaceType(
|
||||
coreTypes.futureClass,
|
||||
Nullability.nonNullable,
|
||||
|
||||
@@ -587,6 +587,11 @@ class OperationsCfe
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
SharedTypeView flatten(SharedTypeView type) {
|
||||
return new SharedTypeView(typeEnvironment.flatten(type.unwrapTypeView()));
|
||||
}
|
||||
|
||||
@override
|
||||
bool isAlwaysExhaustiveType(SharedTypeView type) {
|
||||
return computeIsAlwaysExhaustiveType(
|
||||
@@ -993,6 +998,11 @@ class OperationsCfe
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOrType futureOrTypeInternal(DartType argumentType) {
|
||||
return new FutureOrType(argumentType, Nullability.nonNullable);
|
||||
}
|
||||
|
||||
@override
|
||||
InterfaceType futureTypeInternal(DartType argumentType) {
|
||||
return new InterfaceType(
|
||||
|
||||
@@ -549,6 +549,7 @@ downloaded
|
||||
downloading
|
||||
dq
|
||||
dquote
|
||||
draft
|
||||
drawn
|
||||
ds
|
||||
dsdk
|
||||
@@ -579,6 +580,7 @@ efficiently
|
||||
ei
|
||||
eight
|
||||
eighth
|
||||
elaborated
|
||||
elected
|
||||
elem
|
||||
eliminating
|
||||
@@ -781,6 +783,7 @@ glyph
|
||||
gn
|
||||
gobble
|
||||
goldens
|
||||
googleapis
|
||||
googlesource
|
||||
goto
|
||||
gotos
|
||||
|
||||
Reference in New Issue
Block a user