| // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| /// This file declares a "shadow hierarchy" of concrete classes which extend |
| /// the kernel class hierarchy, adding methods and fields needed by the |
| /// BodyBuilder. |
| /// |
| /// Instances of these classes may be created using the factory methods in |
| /// `ast_factory.dart`. |
| /// |
| /// Note that these classes represent the Dart language prior to desugaring. |
| /// When a single Dart construct desugars to a tree containing multiple kernel |
| /// AST nodes, the shadow class extends the kernel object at the top of the |
| /// desugared tree. |
| /// |
| /// This means that in some cases multiple shadow classes may extend the same |
| /// kernel class, because multiple constructs in Dart may desugar to a tree |
| /// with the same kind of root node. |
| import 'package:kernel/ast.dart'; |
| import 'package:kernel/clone.dart'; |
| import 'package:kernel/src/bounds_checks.dart'; |
| import 'package:kernel/src/printer.dart'; |
| import 'package:kernel/text/ast_to_text.dart' show Precedence, Printer; |
| import 'package:kernel/type_environment.dart'; |
| |
| import 'package:_fe_analyzer_shared/src/type_inference/type_analysis_result.dart' |
| as shared; |
| |
| import '../builder/type_alias_builder.dart'; |
| import '../fasta_codes.dart'; |
| import '../names.dart'; |
| import '../problems.dart' show unsupported; |
| import '../type_inference/inference_visitor.dart'; |
| import '../type_inference/inference_visitor_base.dart'; |
| import '../type_inference/inference_results.dart'; |
| import '../type_inference/object_access_target.dart'; |
| import '../type_inference/type_schema.dart' show UnknownType; |
| |
| typedef SharedMatchContext = shared |
| .MatchContext<Node, Expression, Pattern, DartType, VariableDeclaration>; |
| |
| int getExtensionTypeParameterCount(Arguments arguments) { |
| if (arguments is ArgumentsImpl) { |
| return arguments._extensionTypeParameterCount; |
| } else { |
| // TODO(johnniwinther): Remove this path or assert why it is accepted. |
| return 0; |
| } |
| } |
| |
| int getExtensionTypeArgumentCount(Arguments arguments) { |
| if (arguments is ArgumentsImpl) { |
| return arguments._explicitExtensionTypeArgumentCount; |
| } else { |
| // TODO(johnniwinther): Remove this path or assert why it is accepted. |
| return 0; |
| } |
| } |
| |
| List<DartType>? getExplicitExtensionTypeArguments(Arguments arguments) { |
| if (arguments is ArgumentsImpl) { |
| if (arguments._explicitExtensionTypeArgumentCount == 0) { |
| return null; |
| } else { |
| return arguments.types |
| .take(arguments._explicitExtensionTypeArgumentCount) |
| .toList(); |
| } |
| } else { |
| // TODO(johnniwinther): Remove this path or assert why it is accepted. |
| return null; |
| } |
| } |
| |
| /// Information about explicit/implicit type arguments used for error |
| /// reporting. |
| abstract class TypeArgumentsInfo { |
| const TypeArgumentsInfo(); |
| |
| /// Returns `true` if the [index]th type argument was inferred. |
| bool isInferred(int index); |
| |
| /// Returns the offset to use when reporting an error on the [index]th type |
| /// arguments, using [offset] as the default offset. |
| int getOffsetForIndex(int index, int offset) => offset; |
| } |
| |
| class AllInferredTypeArgumentsInfo extends TypeArgumentsInfo { |
| const AllInferredTypeArgumentsInfo(); |
| |
| @override |
| bool isInferred(int index) => true; |
| } |
| |
| class NoneInferredTypeArgumentsInfo extends TypeArgumentsInfo { |
| const NoneInferredTypeArgumentsInfo(); |
| |
| @override |
| bool isInferred(int index) => false; |
| } |
| |
| class ExtensionMethodTypeArgumentsInfo implements TypeArgumentsInfo { |
| final ArgumentsImpl arguments; |
| |
| ExtensionMethodTypeArgumentsInfo(this.arguments); |
| |
| @override |
| bool isInferred(int index) { |
| if (index < arguments._extensionTypeParameterCount) { |
| // The index refers to a type argument for a type parameter declared on |
| // the extension. Check whether we have enough explicit extension type |
| // arguments. |
| return index >= arguments._explicitExtensionTypeArgumentCount; |
| } |
| // The index refers to a type argument for a type parameter declared on |
| // the method. Check whether we have enough explicit regular type arguments. |
| return index - arguments._extensionTypeParameterCount >= |
| arguments._explicitTypeArgumentCount; |
| } |
| |
| @override |
| int getOffsetForIndex(int index, int offset) { |
| if (index < arguments._extensionTypeParameterCount) { |
| return arguments._extensionTypeArgumentOffset ?? offset; |
| } |
| return offset; |
| } |
| } |
| |
| TypeArgumentsInfo getTypeArgumentsInfo(Arguments arguments) { |
| if (arguments is ArgumentsImpl) { |
| if (arguments._extensionTypeParameterCount == 0) { |
| return arguments._explicitTypeArgumentCount == 0 |
| ? const AllInferredTypeArgumentsInfo() |
| : const NoneInferredTypeArgumentsInfo(); |
| } else { |
| return new ExtensionMethodTypeArgumentsInfo(arguments); |
| } |
| } else { |
| // This code path should only be taken in situations where there are no |
| // type arguments at all, e.g. calling a user-definable operator. |
| assert(arguments.types.isEmpty); |
| return const NoneInferredTypeArgumentsInfo(); |
| } |
| } |
| |
| List<DartType>? getExplicitTypeArguments(Arguments arguments) { |
| if (arguments is ArgumentsImpl) { |
| if (arguments._explicitTypeArgumentCount == 0) { |
| return null; |
| } else if (arguments._extensionTypeParameterCount == 0) { |
| return arguments.types; |
| } else { |
| return arguments.types |
| .skip(arguments._extensionTypeParameterCount) |
| .toList(); |
| } |
| } else { |
| // This code path should only be taken in situations where there are no |
| // type arguments at all, e.g. calling a user-definable operator. |
| assert(arguments.types.isEmpty); |
| return null; |
| } |
| } |
| |
| bool hasExplicitTypeArguments(Arguments arguments) { |
| return getExplicitTypeArguments(arguments) != null; |
| } |
| |
| mixin InternalTreeNode implements TreeNode { |
| @override |
| void replaceChild(TreeNode child, TreeNode replacement) { |
| // Do nothing. The node should not be part of the resulting AST, anyway. |
| } |
| |
| @override |
| R accept<R>(TreeVisitor<R> visitor) { |
| if (visitor is Printer || visitor is Precedence || visitor is Transformer) { |
| // Allow visitors needed for toString and replaceWith. |
| return visitor.defaultTreeNode(this); |
| } |
| return unsupported( |
| "${runtimeType}.accept on ${visitor.runtimeType}", -1, null); |
| } |
| |
| @override |
| R accept1<R, A>(TreeVisitor1<R, A> visitor, A arg) { |
| return unsupported( |
| "${runtimeType}.accept1 on ${visitor.runtimeType}", -1, null); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| unsupported( |
| "${runtimeType}.transformChildren on ${v.runtimeType}", -1, null); |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| unsupported("${runtimeType}.transformOrRemoveChildren on ${v.runtimeType}", |
| -1, null); |
| } |
| |
| @override |
| void visitChildren(Visitor v) { |
| unsupported("${runtimeType}.visitChildren on ${v.runtimeType}", -1, null); |
| } |
| } |
| |
| /// Common base class for internal statements. |
| abstract class InternalStatement extends Statement { |
| @override |
| void replaceChild(TreeNode child, TreeNode replacement) { |
| // Do nothing. The node should not be part of the resulting AST, anyway. |
| } |
| |
| @override |
| R accept<R>(StatementVisitor<R> visitor) { |
| if (visitor is Printer || visitor is Precedence) { |
| // Allow visitors needed for toString. |
| return visitor.defaultStatement(this); |
| } |
| return unsupported("${runtimeType}.accept", -1, null); |
| } |
| |
| @override |
| R accept1<R, A>(StatementVisitor1<R, A> visitor, A arg) => |
| unsupported("${runtimeType}.accept1", -1, null); |
| |
| @override |
| void transformChildren(Transformer v) => unsupported( |
| "${runtimeType}.transformChildren on ${v.runtimeType}", -1, null); |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) => unsupported( |
| "${runtimeType}.transformOrRemoveChildren on ${v.runtimeType}", -1, null); |
| |
| @override |
| void visitChildren(Visitor v) => |
| unsupported("${runtimeType}.visitChildren on ${v.runtimeType}", -1, null); |
| |
| StatementInferenceResult acceptInference(InferenceVisitorImpl visitor); |
| } |
| |
| class ForInStatementWithSynthesizedVariable extends InternalStatement { |
| VariableDeclaration? variable; |
| Expression iterable; |
| Expression? syntheticAssignment; |
| Statement? expressionEffects; |
| Statement body; |
| final bool isAsync; |
| final bool hasProblem; |
| int bodyOffset = TreeNode.noOffset; |
| |
| ForInStatementWithSynthesizedVariable(this.variable, this.iterable, |
| this.syntheticAssignment, this.expressionEffects, this.body, |
| {required this.isAsync, required this.hasProblem}) |
| // ignore: unnecessary_null_comparison |
| : assert(isAsync != null), |
| // ignore: unnecessary_null_comparison |
| assert(hasProblem != null) { |
| variable?.parent = this; |
| iterable.parent = this; |
| syntheticAssignment?.parent = this; |
| expressionEffects?.parent = this; |
| body.parent = this; |
| } |
| |
| @override |
| StatementInferenceResult acceptInference(InferenceVisitorImpl visitor) { |
| return visitor.visitForInStatementWithSynthesizedVariable(this); |
| } |
| |
| @override |
| String toString() { |
| return "ForInStatementWithSynthesizedVariable(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter state) { |
| // TODO(johnniwinther): Implement this. |
| } |
| } |
| |
| class TryStatement extends InternalStatement { |
| Statement tryBlock; |
| List<Catch> catchBlocks; |
| Statement? finallyBlock; |
| |
| TryStatement(this.tryBlock, this.catchBlocks, this.finallyBlock) |
| // ignore: unnecessary_null_comparison |
| : assert(tryBlock != null), |
| // ignore: unnecessary_null_comparison |
| assert(catchBlocks != null) { |
| tryBlock.parent = this; |
| setParents(catchBlocks, this); |
| finallyBlock?.parent = this; |
| } |
| |
| @override |
| StatementInferenceResult acceptInference(InferenceVisitorImpl visitor) { |
| return visitor.visitTryStatement(this); |
| } |
| |
| @override |
| String toString() { |
| return "TryStatement(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write('try '); |
| printer.writeStatement(tryBlock); |
| for (Catch catchBlock in catchBlocks) { |
| printer.write(' '); |
| printer.writeCatch(catchBlock); |
| } |
| if (finallyBlock != null) { |
| printer.write(' finally '); |
| printer.writeStatement(finallyBlock!); |
| } |
| } |
| } |
| |
| class SwitchCaseImpl extends SwitchCase { |
| final bool hasLabel; |
| |
| SwitchCaseImpl( |
| List<Expression> expressions, List<int> expressionOffsets, Statement body, |
| {bool isDefault = false, required this.hasLabel}) |
| // ignore: unnecessary_null_comparison |
| : assert(hasLabel != null), |
| super(expressions, expressionOffsets, body, isDefault: isDefault); |
| |
| @override |
| String toString() { |
| return "SwitchCaseImpl(${toStringInternal()})"; |
| } |
| } |
| |
| final PatternGuard dummyPatternGuard = new PatternGuard(dummyPattern); |
| |
| /// A [Pattern] with an optional guard [Expression]. |
| class PatternGuard extends TreeNode with InternalTreeNode { |
| Pattern pattern; |
| Expression? guard; |
| |
| PatternGuard(this.pattern, [this.guard]) { |
| pattern.parent = this; |
| guard?.parent = this; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| pattern.toTextInternal(printer); |
| if (guard != null) { |
| printer.write(' when '); |
| printer.writeExpression(guard!); |
| } |
| } |
| |
| @override |
| String toString() => 'PatternGuard(${toStringInternal()})'; |
| } |
| |
| class PatternSwitchCase extends TreeNode |
| with InternalTreeNode |
| implements SwitchCase { |
| final List<PatternGuard> patternGuards; |
| @override |
| Statement body; |
| |
| @override |
| bool isDefault; |
| |
| final bool hasLabel; |
| |
| PatternSwitchCase(int fileOffset, this.patternGuards, this.body, |
| {required this.isDefault, required this.hasLabel}) { |
| this.fileOffset = fileOffset; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| for (int index = 0; index < patternGuards.length; index++) { |
| if (index > 0) { |
| printer.newLine(); |
| } |
| printer.write('case '); |
| patternGuards[index].toTextInternal(printer); |
| printer.write(':'); |
| } |
| if (isDefault) { |
| if (patternGuards.isNotEmpty) { |
| printer.newLine(); |
| } |
| printer.write('default:'); |
| } |
| printer.incIndentation(); |
| Statement? block = body; |
| if (block is Block) { |
| for (Statement statement in block.statements) { |
| printer.newLine(); |
| printer.writeStatement(statement); |
| } |
| } else { |
| printer.write(' '); |
| printer.writeStatement(body); |
| } |
| printer.decIndentation(); |
| } |
| |
| @override |
| String toString() { |
| return "PatternSwitchCase(${toStringInternal()})"; |
| } |
| |
| @override |
| List<Expression> get expressions => |
| throw new UnimplementedError('PatternSwitchCase.expressions'); |
| |
| @override |
| List<int> get expressionOffsets => |
| throw new UnimplementedError('PatternSwitchCase.expressionOffsets'); |
| } |
| |
| class PatternSwitchStatement extends InternalStatement |
| implements SwitchStatement { |
| @override |
| Expression expression; |
| |
| @override |
| final List<SwitchCase> cases; |
| |
| @override |
| bool isExplicitlyExhaustive = false; |
| |
| /// Whether the switch has a `default` case. |
| @override |
| bool get hasDefault { |
| assert(cases.every((c) => c == cases.last || !c.isDefault)); |
| return cases.isNotEmpty && cases.last.isDefault; |
| } |
| |
| @override |
| bool get isExhaustive => throw new UnimplementedError(); |
| |
| PatternSwitchStatement(int fileOffset, this.expression, this.cases) { |
| this.fileOffset = fileOffset; |
| expression.parent = this; |
| setParents(cases, this); |
| } |
| |
| @override |
| StatementInferenceResult acceptInference(InferenceVisitorImpl visitor) { |
| return visitor.visitPatternSwitchStatement(this); |
| } |
| |
| @override |
| String toString() { |
| return "PatternSwitchStatement(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write('switch ('); |
| printer.writeExpression(expression); |
| printer.write(') {'); |
| printer.incIndentation(); |
| for (SwitchCase switchCase in cases) { |
| printer.newLine(); |
| printer.writeSwitchCase(switchCase); |
| } |
| printer.decIndentation(); |
| printer.newLine(); |
| printer.write('}'); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| throw new UnsupportedError('PatternSwitchStatement.transformChildren'); |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| throw new UnsupportedError( |
| 'PatternSwitchStatement.transformOrRemoveChildren'); |
| } |
| |
| @override |
| void visitChildren(Visitor v) { |
| throw new UnsupportedError('PatternSwitchStatement.visitChildren'); |
| } |
| } |
| |
| final SwitchExpressionCase dummySwitchExpressionCase = new SwitchExpressionCase( |
| TreeNode.noOffset, dummyPatternGuard, dummyExpression); |
| |
| class SwitchExpressionCase extends TreeNode with InternalTreeNode { |
| PatternGuard patternGuard; |
| Expression expression; |
| |
| SwitchExpressionCase(int fileOffset, this.patternGuard, this.expression) { |
| this.fileOffset = fileOffset; |
| patternGuard.parent = this; |
| expression.parent = this; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write('case '); |
| patternGuard.toTextInternal(printer); |
| printer.write(' => '); |
| printer.writeExpression(expression); |
| } |
| |
| @override |
| String toString() { |
| return 'SwitchExpressionCase(${toStringInternal()})'; |
| } |
| } |
| |
| class SwitchExpression extends InternalExpression { |
| Expression expression; |
| final List<SwitchExpressionCase> cases; |
| |
| SwitchExpression(int fileOffset, this.expression, this.cases) { |
| this.fileOffset = fileOffset; |
| expression.parent = this; |
| setParents(cases, this); |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitSwitchExpression(this, typeContext); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| throw new UnsupportedError('SwitchExpression.transformChildren'); |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| throw new UnsupportedError('SwitchExpression.transformOrRemoveChildren'); |
| } |
| |
| @override |
| void visitChildren(Visitor v) { |
| throw new UnsupportedError('SwitchExpression.visitChildren'); |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write('switch ('); |
| printer.writeExpression(expression); |
| printer.write(') {'); |
| String comma = ' '; |
| for (SwitchExpressionCase switchCase in cases) { |
| printer.write(comma); |
| switchCase.toTextInternal(printer); |
| comma = ', '; |
| } |
| printer.write(' }'); |
| } |
| |
| @override |
| String toString() => 'SwitchExpression(${toStringInternal()})'; |
| } |
| |
| class BreakStatementImpl extends BreakStatement { |
| Statement? targetStatement; |
| final bool isContinue; |
| |
| BreakStatementImpl({required this.isContinue}) |
| // ignore: unnecessary_null_comparison |
| : assert(isContinue != null), |
| super(dummyLabeledStatement); |
| |
| @override |
| String toString() { |
| return "BreakStatementImpl(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| if (isContinue) { |
| printer.write('continue '); |
| } else { |
| printer.write('break '); |
| } |
| printer.write(printer.getLabelName(target)); |
| printer.write(';'); |
| } |
| } |
| |
| /// Common base class for internal expressions. |
| abstract class InternalExpression extends Expression { |
| @override |
| void replaceChild(TreeNode child, TreeNode replacement) { |
| // Do nothing. The node should not be part of the resulting AST, anyway. |
| } |
| |
| @override |
| R accept<R>(ExpressionVisitor<R> visitor) { |
| if (visitor is Printer || |
| visitor is Precedence /* || visitor is Transformer*/) { |
| // Allow visitors needed for toString and replaceWith. |
| return visitor.defaultExpression(this); |
| } |
| return unsupported( |
| "${runtimeType}.accept on ${visitor.runtimeType}", -1, null); |
| } |
| |
| @override |
| R accept1<R, A>(ExpressionVisitor1<R, A> visitor, A arg) { |
| return unsupported( |
| "${runtimeType}.accept1 on ${visitor.runtimeType}", -1, null); |
| } |
| |
| @override |
| DartType getStaticType(StaticTypeContext context) => |
| unsupported("${runtimeType}.getStaticType", -1, null); |
| |
| @override |
| DartType getStaticTypeInternal(StaticTypeContext context) => |
| unsupported("${runtimeType}.getStaticType", -1, null); |
| |
| @override |
| void visitChildren(Visitor<dynamic> v) => |
| unsupported("${runtimeType}.visitChildren", -1, null); |
| |
| @override |
| void transformChildren(Transformer v) => |
| unsupported("${runtimeType}.transformChildren", -1, null); |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) => |
| unsupported("${runtimeType}.transformOrRemoveChildren", -1, null); |
| |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext); |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| // TODO(johnniwinther): Implement this. |
| } |
| } |
| |
| /// Front end specific implementation of [Argument]. |
| class ArgumentsImpl extends Arguments { |
| // TODO(johnniwinther): Move this to the static invocation instead. |
| final int _extensionTypeParameterCount; |
| |
| final int _explicitExtensionTypeArgumentCount; |
| |
| final int? _extensionTypeArgumentOffset; |
| |
| int _explicitTypeArgumentCount; |
| |
| List<Object?>? argumentsOriginalOrder; |
| |
| /// True if the arguments are passed to the super-constructor in a |
| /// super-initializer, and the positional parameters are super-initializer |
| /// parameters. It is true that either all of the positional parameters are |
| /// super-initializer parameters or none of them, so a simple boolean |
| /// accurately reflects the state. |
| bool positionalAreSuperParameters = false; |
| |
| /// Names of the named positional parameters. If none of the parameters are |
| /// super-positional, the field is null. |
| Set<String>? namedSuperParameterNames; |
| |
| ArgumentsImpl.internal( |
| {required List<Expression> positional, |
| required List<DartType>? types, |
| required List<NamedExpression>? named, |
| required int extensionTypeParameterCount, |
| required int explicitExtensionTypeArgumentCount, |
| required int? extensionTypeArgumentOffset, |
| required int explicitTypeArgumentCount}) |
| : this._extensionTypeParameterCount = extensionTypeParameterCount, |
| this._explicitExtensionTypeArgumentCount = |
| explicitExtensionTypeArgumentCount, |
| this._extensionTypeArgumentOffset = extensionTypeArgumentOffset, |
| this._explicitTypeArgumentCount = explicitTypeArgumentCount, |
| this.argumentsOriginalOrder = null, |
| super(positional, types: types, named: named); |
| |
| ArgumentsImpl(List<Expression> positional, |
| {List<DartType>? types, |
| List<NamedExpression>? named, |
| this.argumentsOriginalOrder}) |
| : _explicitTypeArgumentCount = types?.length ?? 0, |
| _extensionTypeParameterCount = 0, |
| _explicitExtensionTypeArgumentCount = 0, |
| // The offset is unused in this case. |
| _extensionTypeArgumentOffset = null, |
| super(positional, types: types, named: named); |
| |
| ArgumentsImpl.forExtensionMethod(int extensionTypeParameterCount, |
| int typeParameterCount, Expression receiver, |
| {List<DartType> extensionTypeArguments = const <DartType>[], |
| int? extensionTypeArgumentOffset, |
| List<DartType> typeArguments = const <DartType>[], |
| List<Expression> positionalArguments = const <Expression>[], |
| List<NamedExpression> namedArguments = const <NamedExpression>[], |
| this.argumentsOriginalOrder}) |
| : _extensionTypeParameterCount = extensionTypeParameterCount, |
| _explicitExtensionTypeArgumentCount = extensionTypeArguments.length, |
| _explicitTypeArgumentCount = typeArguments.length, |
| _extensionTypeArgumentOffset = extensionTypeArgumentOffset, |
| assert( |
| extensionTypeArguments.isEmpty || |
| extensionTypeArguments.length == extensionTypeParameterCount, |
| "Extension type arguments must be empty or complete."), |
| super(<Expression>[receiver]..addAll(positionalArguments), |
| named: namedArguments, |
| types: <DartType>[] |
| ..addAll(_normalizeTypeArguments( |
| extensionTypeParameterCount, extensionTypeArguments)) |
| ..addAll( |
| _normalizeTypeArguments(typeParameterCount, typeArguments))); |
| |
| static ArgumentsImpl clone(ArgumentsImpl node, List<Expression> positional, |
| List<NamedExpression> named, List<DartType> types) { |
| return new ArgumentsImpl.internal( |
| positional: positional, |
| named: named, |
| types: types, |
| extensionTypeParameterCount: node._extensionTypeParameterCount, |
| explicitExtensionTypeArgumentCount: |
| node._explicitExtensionTypeArgumentCount, |
| explicitTypeArgumentCount: node._explicitTypeArgumentCount, |
| extensionTypeArgumentOffset: node._extensionTypeArgumentOffset); |
| } |
| |
| static List<DartType> _normalizeTypeArguments( |
| int length, List<DartType> arguments) { |
| if (arguments.isEmpty && length > 0) { |
| return new List<DartType>.filled(length, const UnknownType()); |
| } |
| return arguments; |
| } |
| |
| static void setNonInferrableArgumentTypes( |
| ArgumentsImpl arguments, List<DartType> types) { |
| arguments.types.clear(); |
| arguments.types.addAll(types); |
| arguments._explicitTypeArgumentCount = types.length; |
| } |
| |
| static void removeNonInferrableArgumentTypes(ArgumentsImpl arguments) { |
| arguments.types.clear(); |
| arguments._explicitTypeArgumentCount = 0; |
| } |
| |
| @override |
| String toString() { |
| return "ArgumentsImpl(${toStringInternal()})"; |
| } |
| } |
| |
| /// Internal expression representing a cascade expression. |
| /// |
| /// A cascade expression of the form `a..b()..c()` is represented as the kernel |
| /// expression: |
| /// |
| /// let v = a in |
| /// let _ = v.b() in |
| /// let _ = v.c() in |
| /// v |
| /// |
| /// In the documentation that follows, `v` is referred to as the "cascade |
| /// variable"--this is the variable that remembers the value of the expression |
| /// preceding the first `..` while the cascades are being evaluated. |
| class Cascade extends InternalExpression { |
| /// The temporary variable holding the cascade receiver expression in its |
| /// initializer; |
| VariableDeclaration variable; |
| |
| final bool isNullAware; |
| |
| /// The expressions performed on [variable]. |
| final List<Expression> expressions = <Expression>[]; |
| |
| /// Creates a [Cascade] using [variable] as the cascade |
| /// variable. Caller is responsible for ensuring that [variable]'s |
| /// initializer is the expression preceding the first `..` of the cascade |
| /// expression. |
| Cascade(this.variable, {required this.isNullAware}) |
| // ignore: unnecessary_null_comparison |
| : assert(variable != null), |
| // ignore: unnecessary_null_comparison |
| assert(isNullAware != null) { |
| variable.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitCascade(this, typeContext); |
| } |
| |
| /// Adds [expression] to the list of [expressions] performed on [variable]. |
| void addCascadeExpression(Expression expression) { |
| expressions.add(expression); |
| expression.parent = this; |
| } |
| |
| @override |
| String toString() { |
| return "Cascade(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write('let '); |
| printer.writeVariableDeclaration(variable); |
| printer.write(' in cascade {'); |
| printer.incIndentation(); |
| for (Expression expression in expressions) { |
| printer.newLine(); |
| printer.writeExpression(expression); |
| printer.write(';'); |
| } |
| printer.decIndentation(); |
| if (expressions.isNotEmpty) { |
| printer.newLine(); |
| } |
| printer.write('} => '); |
| printer.write(printer.getVariableName(variable)); |
| } |
| } |
| |
| /// Internal expression representing a deferred check. |
| // TODO(johnniwinther): Change the representation to be direct and perform |
| // the [Let] encoding in the replacement. |
| class DeferredCheck extends InternalExpression { |
| VariableDeclaration variable; |
| Expression expression; |
| |
| DeferredCheck(this.variable, this.expression) |
| // ignore: unnecessary_null_comparison |
| : assert(variable != null), |
| // ignore: unnecessary_null_comparison |
| assert(expression != null) { |
| variable.parent = this; |
| expression.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitDeferredCheck(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "DeferredCheck(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write('let '); |
| printer.writeVariableDeclaration(variable); |
| printer.write(' in '); |
| printer.writeExpression(expression); |
| } |
| } |
| |
| /// Common base class for shadow objects representing expressions in kernel |
| /// form. |
| abstract class ExpressionJudgment extends Expression { |
| /// Calls back to [inferrer] to perform type inference for whatever concrete |
| /// type of [Expression] this is. |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext); |
| } |
| |
| /// Shadow object for [StaticInvocation] when the procedure being invoked is a |
| /// factory constructor. |
| class FactoryConstructorInvocation extends StaticInvocation |
| implements ExpressionJudgment { |
| bool hasBeenInferred = false; |
| |
| FactoryConstructorInvocation(Procedure target, Arguments arguments, |
| {bool isConst = false}) |
| : super(target, arguments, isConst: isConst); |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitFactoryConstructorInvocation(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "FactoryConstructorInvocation(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| if (isConst) { |
| printer.write('const '); |
| } else { |
| printer.write('new '); |
| } |
| printer.writeClassName(target.enclosingClass!.reference); |
| printer.writeTypeArguments(arguments.types); |
| if (target.name.text.isNotEmpty) { |
| printer.write('.'); |
| printer.write(target.name.text); |
| } |
| printer.writeArguments(arguments, includeTypeArguments: false); |
| } |
| } |
| |
| /// Shadow object for [ConstructorInvocation] when the procedure being invoked |
| /// is a type aliased constructor. |
| class TypeAliasedConstructorInvocation extends ConstructorInvocation |
| implements ExpressionJudgment { |
| bool hasBeenInferred = false; |
| final TypeAliasBuilder typeAliasBuilder; |
| |
| TypeAliasedConstructorInvocation( |
| this.typeAliasBuilder, Constructor target, Arguments arguments, |
| {bool isConst = false}) |
| : super(target, arguments, isConst: isConst); |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitTypeAliasedConstructorInvocation(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "TypeAliasedConstructorInvocation(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| if (isConst) { |
| printer.write('const '); |
| } else { |
| printer.write('new '); |
| } |
| printer.writeTypedefName(typeAliasBuilder.typedef.reference); |
| printer.writeTypeArguments(arguments.types); |
| if (target.name.text.isNotEmpty) { |
| printer.write('.'); |
| printer.write(target.name.text); |
| } |
| printer.writeArguments(arguments, includeTypeArguments: false); |
| } |
| } |
| |
| /// Shadow object for [StaticInvocation] when the procedure being invoked is a |
| /// type aliased factory constructor. |
| class TypeAliasedFactoryInvocation extends StaticInvocation |
| implements ExpressionJudgment { |
| bool hasBeenInferred = false; |
| final TypeAliasBuilder typeAliasBuilder; |
| |
| TypeAliasedFactoryInvocation( |
| this.typeAliasBuilder, Procedure target, Arguments arguments, |
| {bool isConst = false}) |
| : super(target, arguments, isConst: isConst); |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitTypeAliasedFactoryInvocation(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "TypeAliasedConstructorInvocation(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| if (isConst) { |
| printer.write('const '); |
| } else { |
| printer.write('new '); |
| } |
| printer.writeTypedefName(typeAliasBuilder.typedef.reference); |
| printer.writeTypeArguments(arguments.types); |
| if (target.name.text.isNotEmpty) { |
| printer.write('.'); |
| printer.write(target.name.text); |
| } |
| printer.writeArguments(arguments, includeTypeArguments: false); |
| } |
| } |
| |
| /// Front end specific implementation of [FunctionDeclaration]. |
| class FunctionDeclarationImpl extends FunctionDeclaration { |
| bool hasImplicitReturnType = false; |
| |
| FunctionDeclarationImpl(VariableDeclaration variable, FunctionNode function) |
| : super(variable, function); |
| |
| static void setHasImplicitReturnType( |
| FunctionDeclarationImpl declaration, bool hasImplicitReturnType) { |
| declaration.hasImplicitReturnType = hasImplicitReturnType; |
| } |
| |
| @override |
| String toString() { |
| return "FunctionDeclarationImpl(${toStringInternal()})"; |
| } |
| } |
| |
| /// Concrete shadow object representing a super initializer in kernel form. |
| class InvalidSuperInitializerJudgment extends LocalInitializer |
| implements InitializerJudgment { |
| final Constructor target; |
| final ArgumentsImpl argumentsJudgment; |
| |
| InvalidSuperInitializerJudgment( |
| this.target, this.argumentsJudgment, VariableDeclaration variable) |
| : super(variable); |
| |
| @override |
| InitializerInferenceResult acceptInference(InferenceVisitorImpl visitor) { |
| return visitor.visitInvalidSuperInitializerJudgment(this); |
| } |
| |
| @override |
| String toString() { |
| return "InvalidSuperInitializerJudgment(${toStringInternal()})"; |
| } |
| } |
| |
| /// Internal expression representing an if-null expression. |
| /// |
| /// An if-null expression of the form `a ?? b` is encoded as: |
| /// |
| /// let v = a in v == null ? b : v |
| /// |
| class IfNullExpression extends InternalExpression { |
| Expression left; |
| Expression right; |
| |
| IfNullExpression(this.left, this.right) |
| // ignore: unnecessary_null_comparison |
| : assert(left != null), |
| // ignore: unnecessary_null_comparison |
| assert(right != null) { |
| left.parent = this; |
| right.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitIfNullExpression(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "IfNullExpression(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.writeExpression(left, minimumPrecedence: Precedence.CONDITIONAL); |
| printer.write(' ?? '); |
| printer.writeExpression(right, |
| minimumPrecedence: Precedence.CONDITIONAL + 1); |
| } |
| } |
| |
| /// Common base class for shadow objects representing initializers in kernel |
| /// form. |
| abstract class InitializerJudgment implements Initializer { |
| /// Performs type inference for whatever concrete type of |
| /// [InitializerJudgment] this is. |
| InitializerInferenceResult acceptInference(InferenceVisitorImpl visitor); |
| } |
| |
| /// Concrete shadow object representing an integer literal in kernel form. |
| class IntJudgment extends IntLiteral implements ExpressionJudgment { |
| final String? literal; |
| |
| IntJudgment(int value, this.literal) : super(value); |
| |
| double? asDouble({bool negated = false}) { |
| if (value == 0 && negated) { |
| return -0.0; |
| } |
| BigInt intValue = new BigInt.from(negated ? -value : value); |
| double doubleValue = intValue.toDouble(); |
| return intValue == new BigInt.from(doubleValue) ? doubleValue : null; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitIntJudgment(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "IntJudgment(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| if (literal == null) { |
| printer.write('$value'); |
| } else { |
| printer.write(literal!); |
| } |
| } |
| } |
| |
| class ShadowLargeIntLiteral extends IntLiteral implements ExpressionJudgment { |
| final String literal; |
| @override |
| final int fileOffset; |
| bool isParenthesized = false; |
| |
| ShadowLargeIntLiteral(this.literal, this.fileOffset) : super(0); |
| |
| double? asDouble({bool negated = false}) { |
| BigInt? intValue = BigInt.tryParse(negated ? '-${literal}' : literal); |
| if (intValue == null) { |
| return null; |
| } |
| double doubleValue = intValue.toDouble(); |
| return !doubleValue.isNaN && |
| !doubleValue.isInfinite && |
| intValue == new BigInt.from(doubleValue) |
| ? doubleValue |
| : null; |
| } |
| |
| int? asInt64({bool negated = false}) { |
| return int.tryParse(negated ? '-${literal}' : literal); |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitShadowLargeIntLiteral(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "ShadowLargeIntLiteral(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write(literal); |
| } |
| } |
| |
| /// Concrete shadow object representing an invalid initializer in kernel form. |
| class ShadowInvalidInitializer extends LocalInitializer |
| implements InitializerJudgment { |
| ShadowInvalidInitializer(VariableDeclaration variable) : super(variable); |
| |
| @override |
| InitializerInferenceResult acceptInference(InferenceVisitorImpl visitor) { |
| return visitor.visitShadowInvalidInitializer(this); |
| } |
| |
| @override |
| String toString() { |
| return "ShadowInvalidInitializer(${toStringInternal()})"; |
| } |
| } |
| |
| /// Concrete shadow object representing an invalid initializer in kernel form. |
| class ShadowInvalidFieldInitializer extends LocalInitializer |
| implements InitializerJudgment { |
| Field field; |
| Expression value; |
| |
| ShadowInvalidFieldInitializer( |
| this.field, this.value, VariableDeclaration variable) |
| // ignore: unnecessary_null_comparison |
| : assert(value != null), |
| super(variable) { |
| value.parent = this; |
| } |
| |
| @override |
| InitializerInferenceResult acceptInference(InferenceVisitorImpl visitor) { |
| return visitor.visitShadowInvalidFieldInitializer(this); |
| } |
| |
| @override |
| String toString() { |
| return "ShadowInvalidFieldInitializer(${toStringInternal()})"; |
| } |
| } |
| |
| class ExpressionInvocation extends InternalExpression { |
| Expression expression; |
| Arguments arguments; |
| |
| ExpressionInvocation(this.expression, this.arguments) |
| // ignore: unnecessary_null_comparison |
| : assert(expression != null), |
| // ignore: unnecessary_null_comparison |
| assert(arguments != null) { |
| expression.parent = this; |
| arguments.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitExpressionInvocation(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "ExpressionInvocation(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.writeExpression(expression); |
| printer.writeArguments(arguments); |
| } |
| } |
| |
| /// Internal expression representing a null-aware method invocation. |
| /// |
| /// A null-aware method invocation of the form `a?.b(...)` is encoded as: |
| /// |
| /// let v = a in v == null ? null : v.b(...) |
| /// |
| class NullAwareMethodInvocation extends InternalExpression { |
| /// The synthetic variable whose initializer hold the receiver. |
| VariableDeclarationImpl variable; |
| |
| /// The expression that invokes the method on [variable]. |
| Expression invocation; |
| |
| NullAwareMethodInvocation(this.variable, this.invocation) |
| // ignore: unnecessary_null_comparison |
| : assert(variable != null), |
| // ignore: unnecessary_null_comparison |
| assert(invocation != null) { |
| variable.parent = this; |
| invocation.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitNullAwareMethodInvocation(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "NullAwareMethodInvocation(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| Expression methodInvocation = invocation; |
| if (methodInvocation is InstanceInvocation) { |
| Expression receiver = methodInvocation.receiver; |
| if (receiver is VariableGet && receiver.variable == variable) { |
| // Special-case the usual use of this node. |
| printer.writeExpression(variable.initializer!); |
| printer.write('?.'); |
| printer.writeInterfaceMemberName( |
| methodInvocation.interfaceTargetReference, methodInvocation.name); |
| printer.writeArguments(methodInvocation.arguments); |
| return; |
| } |
| } else if (methodInvocation is DynamicInvocation) { |
| Expression receiver = methodInvocation.receiver; |
| if (receiver is VariableGet && receiver.variable == variable) { |
| // Special-case the usual use of this node. |
| printer.writeExpression(variable.initializer!); |
| printer.write('?.'); |
| printer.writeName(methodInvocation.name); |
| printer.writeArguments(methodInvocation.arguments); |
| return; |
| } |
| } |
| printer.write('let '); |
| printer.writeVariableDeclaration(variable); |
| printer.write(' in null-aware '); |
| printer.writeExpression(methodInvocation); |
| } |
| } |
| |
| /// Internal expression representing a null-aware read from a property. |
| /// |
| /// A null-aware property get of the form `a?.b` is encoded as: |
| /// |
| /// let v = a in v == null ? null : v.b |
| /// |
| class NullAwarePropertyGet extends InternalExpression { |
| /// The synthetic variable whose initializer hold the receiver. |
| VariableDeclarationImpl variable; |
| |
| /// The expression that reads the property from [variable]. |
| Expression read; |
| |
| NullAwarePropertyGet(this.variable, this.read) |
| // ignore: unnecessary_null_comparison |
| : assert(variable != null), |
| // ignore: unnecessary_null_comparison |
| assert(read != null) { |
| variable.parent = this; |
| read.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitNullAwarePropertyGet(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "NullAwarePropertyGet(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| Expression propertyGet = read; |
| if (propertyGet is PropertyGet) { |
| Expression receiver = propertyGet.receiver; |
| if (receiver is VariableGet && receiver.variable == variable) { |
| // Special-case the usual use of this node. |
| printer.writeExpression(variable.initializer!); |
| printer.write('?.'); |
| printer.writeName(propertyGet.name); |
| return; |
| } |
| } |
| printer.write('let '); |
| printer.writeVariableDeclaration(variable); |
| printer.write(' in null-aware '); |
| printer.writeExpression(propertyGet); |
| } |
| } |
| |
| /// Internal expression representing a null-aware read from a property. |
| /// |
| /// A null-aware property get of the form `a?.b = c` is encoded as: |
| /// |
| /// let v = a in v == null ? null : v.b = c |
| /// |
| class NullAwarePropertySet extends InternalExpression { |
| /// The synthetic variable whose initializer hold the receiver. |
| VariableDeclarationImpl variable; |
| |
| /// The expression that writes the value to the property in [variable]. |
| Expression write; |
| |
| NullAwarePropertySet(this.variable, this.write) |
| // ignore: unnecessary_null_comparison |
| : assert(variable != null), |
| // ignore: unnecessary_null_comparison |
| assert(write != null) { |
| variable.parent = this; |
| write.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitNullAwarePropertySet(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "NullAwarePropertySet(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| Expression propertySet = write; |
| if (propertySet is InstanceSet) { |
| Expression receiver = propertySet.receiver; |
| if (receiver is VariableGet && receiver.variable == variable) { |
| // Special-case the usual use of this node. |
| printer.writeExpression(variable.initializer!); |
| printer.write('?.'); |
| printer.writeInterfaceMemberName( |
| propertySet.interfaceTargetReference, propertySet.name); |
| printer.write(' = '); |
| printer.writeExpression(propertySet.value); |
| return; |
| } |
| } else if (propertySet is DynamicSet) { |
| Expression receiver = propertySet.receiver; |
| if (receiver is VariableGet && receiver.variable == variable) { |
| // Special-case the usual use of this node. |
| printer.writeExpression(variable.initializer!); |
| printer.write('?.'); |
| printer.writeName(propertySet.name); |
| printer.write(' = '); |
| printer.writeExpression(propertySet.value); |
| return; |
| } |
| } |
| printer.write('let '); |
| printer.writeVariableDeclaration(variable); |
| printer.write(' in null-aware '); |
| printer.writeExpression(propertySet); |
| } |
| } |
| |
| /// Front end specific implementation of [ReturnStatement]. |
| class ReturnStatementImpl extends ReturnStatement { |
| final bool isArrow; |
| |
| ReturnStatementImpl(this.isArrow, [Expression? expression]) |
| : super(expression); |
| |
| @override |
| String toString() { |
| return "ReturnStatementImpl(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| if (isArrow) { |
| printer.write('=>'); |
| } else { |
| printer.write('return'); |
| } |
| if (expression != null) { |
| printer.write(' '); |
| printer.writeExpression(expression!); |
| } |
| printer.write(';'); |
| } |
| } |
| |
| /// Front end specific implementation of [VariableDeclaration]. |
| class VariableDeclarationImpl extends VariableDeclaration { |
| final bool forSyntheticToken; |
| |
| /// Determine whether the given [VariableDeclarationImpl] had an implicit |
| /// type. |
| /// |
| /// This is static to avoid introducing a method that would be visible to |
| /// the kernel. |
| final bool isImplicitlyTyped; |
| |
| // TODO(ahe): Remove this field. It's only used locally when compiling a |
| // method, and this can thus be tracked in a [Set] (actually, tracking this |
| // information in a [List] is probably even faster as the average size will |
| // be close to zero). |
| bool mutatedInClosure = false; |
| |
| /// Determines whether the given [VariableDeclarationImpl] represents a |
| /// local function. |
| /// |
| /// This is static to avoid introducing a method that would be visible to the |
| /// kernel. |
| // TODO(ahe): Investigate if this can be removed. |
| final bool isLocalFunction; |
| |
| /// Whether the variable is final with no initializer in a null safe library. |
| /// |
| /// Such variables behave similar to those declared with the `late` keyword, |
| /// except that the don't have lazy evaluation semantics, and it is statically |
| /// verified by the front end that they are always assigned before they are |
| /// used. |
| bool isStaticLate; |
| |
| VariableDeclarationImpl(String? name, |
| {this.forSyntheticToken = false, |
| bool hasDeclaredInitializer = false, |
| Expression? initializer, |
| DartType? type, |
| bool isFinal = false, |
| bool isConst = false, |
| bool isInitializingFormal = false, |
| bool isCovariantByDeclaration = false, |
| bool isLocalFunction = false, |
| bool isLate = false, |
| bool isRequired = false, |
| bool isLowered = false, |
| this.isStaticLate = false}) |
| : isImplicitlyTyped = type == null, |
| isLocalFunction = isLocalFunction, |
| super(name, |
| initializer: initializer, |
| type: type ?? const DynamicType(), |
| isFinal: isFinal, |
| isConst: isConst, |
| isInitializingFormal: isInitializingFormal, |
| isCovariantByDeclaration: isCovariantByDeclaration, |
| isLate: isLate, |
| isRequired: isRequired, |
| isLowered: isLowered, |
| hasDeclaredInitializer: hasDeclaredInitializer); |
| |
| VariableDeclarationImpl.forEffect(Expression initializer) |
| : forSyntheticToken = false, |
| isImplicitlyTyped = false, |
| isLocalFunction = false, |
| isStaticLate = false, |
| super.forValue(initializer); |
| |
| VariableDeclarationImpl.forValue(Expression initializer) |
| : forSyntheticToken = false, |
| isImplicitlyTyped = true, |
| isLocalFunction = false, |
| isStaticLate = false, |
| super.forValue(initializer); |
| |
| // The synthesized local getter function for a lowered late variable. |
| // |
| // This is set in `InferenceVisitor.visitVariableDeclaration` when late |
| // lowering is enabled. |
| VariableDeclaration? lateGetter; |
| |
| // The synthesized local setter function for an assignable lowered late |
| // variable. |
| // |
| // This is set in `InferenceVisitor.visitVariableDeclaration` when late |
| // lowering is enabled. |
| VariableDeclaration? lateSetter; |
| |
| // Is `true` if this a lowered late final variable without an initializer. |
| // |
| // This is set in `InferenceVisitor.visitVariableDeclaration` when late |
| // lowering is enabled. |
| bool isLateFinalWithoutInitializer = false; |
| |
| // The original type (declared or inferred) of a lowered late variable. |
| // |
| // This is set in `InferenceVisitor.visitVariableDeclaration` when late |
| // lowering is enabled. |
| DartType? lateType; |
| |
| // The original name of a lowered late variable. |
| // |
| // This is set in `InferenceVisitor.visitVariableDeclaration` when late |
| // lowering is enabled. |
| String? lateName; |
| |
| @override |
| bool get isAssignable { |
| if (isStaticLate) return true; |
| return super.isAssignable; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.writeVariableDeclaration(this, |
| isLate: isLate || lateGetter != null, type: lateType ?? type); |
| printer.write(';'); |
| } |
| |
| @override |
| String toString() { |
| return "VariableDeclarationImpl(${toStringInternal()})"; |
| } |
| } |
| |
| /// Front end specific implementation of [VariableGet]. |
| class VariableGetImpl extends VariableGet { |
| // TODO(johnniwinther): Remove the need for this by encoding all null aware |
| // expressions explicitly. |
| final bool forNullGuardedAccess; |
| |
| VariableGetImpl(VariableDeclaration variable, |
| {required this.forNullGuardedAccess}) |
| // ignore: unnecessary_null_comparison |
| : assert(forNullGuardedAccess != null), |
| super(variable); |
| |
| @override |
| String toString() { |
| return "VariableGetImpl(${toStringInternal()})"; |
| } |
| } |
| |
| /// Front end specific implementation of [LoadLibrary]. |
| class LoadLibraryImpl extends LoadLibrary { |
| final Arguments? arguments; |
| |
| LoadLibraryImpl(LibraryDependency import, this.arguments) : super(import); |
| |
| @override |
| String toString() { |
| return "LoadLibraryImpl(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write(import.name!); |
| printer.write('.loadLibrary'); |
| if (arguments != null) { |
| printer.writeArguments(arguments!); |
| } else { |
| printer.write('()'); |
| } |
| } |
| } |
| |
| /// Internal expression representing a tear-off of a `loadLibrary` function. |
| class LoadLibraryTearOff extends InternalExpression { |
| LibraryDependency import; |
| Procedure target; |
| |
| LoadLibraryTearOff(this.import, this.target); |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitLoadLibraryTearOff(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "LoadLibraryTearOff(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write(import.name!); |
| printer.write('.loadLibrary'); |
| } |
| } |
| |
| /// Internal expression representing an if-null property set. |
| /// |
| /// An if-null property set of the form `o.a ??= b` is, if used for value, |
| /// encoded as the expression: |
| /// |
| /// let v1 = o in let v2 = v1.a in v2 == null ? v1.a = b : v2 |
| /// |
| /// and, if used for effect, encoded as the expression: |
| /// |
| /// let v1 = o in v1.a == null ? v1.a = b : null |
| /// |
| class IfNullPropertySet extends InternalExpression { |
| /// The receiver used for the read/write operations. |
| Expression receiver; |
| |
| /// Name of the property. |
| Name propertyName; |
| |
| /// The right-hand side of the binary operation. |
| Expression rhs; |
| |
| /// If `true`, the expression is only need for effect and not for its value. |
| final bool forEffect; |
| |
| /// The file offset for the read operation. |
| final int readOffset; |
| |
| /// The file offset for the write operation. |
| final int writeOffset; |
| |
| IfNullPropertySet(this.receiver, this.propertyName, this.rhs, |
| {required this.forEffect, |
| required this.readOffset, |
| required this.writeOffset}) |
| // ignore: unnecessary_null_comparison |
| : assert(receiver != null), |
| // ignore: unnecessary_null_comparison |
| assert(rhs != null), |
| // ignore: unnecessary_null_comparison |
| assert(forEffect != null), |
| // ignore: unnecessary_null_comparison |
| assert(readOffset != null), |
| // ignore: unnecessary_null_comparison |
| assert(writeOffset != null) { |
| receiver.parent = this; |
| rhs.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitIfNullPropertySet(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "IfNullPropertySet(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.writeExpression(receiver); |
| printer.write('.'); |
| printer.writeName(propertyName); |
| printer.write(' ??= '); |
| printer.writeExpression(rhs); |
| } |
| } |
| |
| /// Internal expression representing an if-null assignment. |
| /// |
| /// An if-null assignment of the form `a ??= b` is, if used for value, |
| /// encoded as the expression: |
| /// |
| /// let v1 = a in v1 == null ? a = b : v1 |
| /// |
| /// and, if used for effect, encoded as the expression: |
| /// |
| /// a == null ? a = b : null |
| /// |
| class IfNullSet extends InternalExpression { |
| /// The expression that reads the property from [variable]. |
| Expression read; |
| |
| /// The expression that writes the value to the property on [variable]. |
| Expression write; |
| |
| /// If `true`, the expression is only need for effect and not for its value. |
| final bool forEffect; |
| |
| IfNullSet(this.read, this.write, {required this.forEffect}) |
| // ignore: unnecessary_null_comparison |
| : assert(read != null), |
| // ignore: unnecessary_null_comparison |
| assert(write != null), |
| // ignore: unnecessary_null_comparison |
| assert(forEffect != null) { |
| read.parent = this; |
| write.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitIfNullSet(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "IfNullSet(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.writeExpression(read); |
| printer.write(' ?? '); |
| printer.writeExpression(write); |
| } |
| } |
| |
| /// Internal expression representing an compound extension assignment. |
| /// |
| /// An compound extension assignment of the form |
| /// |
| /// Extension(receiver).propertyName += rhs |
| /// |
| /// is, if used for value, encoded as the expression: |
| /// |
| /// let receiverVariable = receiver in |
| /// let valueVariable = |
| /// Extension|get#propertyName(receiverVariable) + rhs) in |
| /// let writeVariable = |
| /// Extension|set#propertyName(receiverVariable, valueVariable) in |
| /// valueVariable |
| /// |
| /// and if used for effect as: |
| /// |
| /// let receiverVariable = receiver in |
| /// Extension|set#propertyName(receiverVariable, |
| /// Extension|get#propertyName(receiverVariable) + rhs) |
| /// |
| /// If [readOnlyReceiver] is `true` the [receiverVariable] is not created |
| /// and the [receiver] is used directly. |
| class CompoundExtensionSet extends InternalExpression { |
| /// The extension in which the [setter] is declared. |
| final Extension extension; |
| |
| /// The explicit type arguments for the type parameters declared in |
| /// [extension]. |
| final List<DartType>? explicitTypeArguments; |
| |
| /// The receiver used for the read/write operations. |
| Expression receiver; |
| |
| /// The name of the property accessed by the read/write operations. |
| final Name propertyName; |
| |
| /// The member used for the read operation. |
| final Member? getter; |
| |
| /// The binary operation performed on the getter result and [rhs]. |
| final Name binaryName; |
| |
| /// The right-hand side of the binary operation. |
| Expression rhs; |
| |
| /// The member used for the write operation. |
| final Member? setter; |
| |
| /// If `true`, the expression is only need for effect and not for its value. |
| final bool forEffect; |
| |
| /// The file offset for the read operation. |
| final int readOffset; |
| |
| /// The file offset for the binary operation. |
| final int binaryOffset; |
| |
| /// The file offset for the write operation. |
| final int writeOffset; |
| |
| CompoundExtensionSet( |
| this.extension, |
| this.explicitTypeArguments, |
| this.receiver, |
| this.propertyName, |
| this.getter, |
| this.binaryName, |
| this.rhs, |
| this.setter, |
| {required this.forEffect, |
| required this.readOffset, |
| required this.binaryOffset, |
| required this.writeOffset}) |
| // ignore: unnecessary_null_comparison |
| : assert(receiver != null), |
| // ignore: unnecessary_null_comparison |
| assert(rhs != null), |
| // ignore: unnecessary_null_comparison |
| assert(forEffect != null), |
| // ignore: unnecessary_null_comparison |
| assert(readOffset != null), |
| // ignore: unnecessary_null_comparison |
| assert(binaryOffset != null), |
| // ignore: unnecessary_null_comparison |
| assert(writeOffset != null) { |
| receiver.parent = this; |
| rhs.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitCompoundExtensionSet(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "CompoundExtensionSet(${toStringInternal()})"; |
| } |
| } |
| |
| /// Internal expression representing an compound property assignment. |
| /// |
| /// An compound property assignment of the form |
| /// |
| /// receiver.propertyName += rhs |
| /// |
| /// is encoded as the expression: |
| /// |
| /// let receiverVariable = receiver in |
| /// receiverVariable.propertyName = receiverVariable.propertyName + rhs |
| /// |
| class CompoundPropertySet extends InternalExpression { |
| /// The receiver used for the read/write operations. |
| Expression receiver; |
| |
| /// The name of the property accessed by the read/write operations. |
| final Name propertyName; |
| |
| /// The binary operation performed on the getter result and [rhs]. |
| final Name binaryName; |
| |
| /// The right-hand side of the binary operation. |
| Expression rhs; |
| |
| /// If `true`, the expression is only need for effect and not for its value. |
| final bool forEffect; |
| |
| /// The file offset for the read operation. |
| final int readOffset; |
| |
| /// The file offset for the binary operation. |
| final int binaryOffset; |
| |
| /// The file offset for the write operation. |
| final int writeOffset; |
| |
| CompoundPropertySet( |
| this.receiver, this.propertyName, this.binaryName, this.rhs, |
| {required this.forEffect, |
| required this.readOffset, |
| required this.binaryOffset, |
| required this.writeOffset}) |
| // ignore: unnecessary_null_comparison |
| : assert(receiver != null), |
| // ignore: unnecessary_null_comparison |
| assert(rhs != null), |
| // ignore: unnecessary_null_comparison |
| assert(forEffect != null), |
| // ignore: unnecessary_null_comparison |
| assert(readOffset != null), |
| // ignore: unnecessary_null_comparison |
| assert(binaryOffset != null), |
| // ignore: unnecessary_null_comparison |
| assert(writeOffset != null) { |
| receiver.parent = this; |
| rhs.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitCompoundPropertySet(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "CompoundPropertySet(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.writeExpression(receiver); |
| printer.write('.'); |
| printer.writeName(propertyName); |
| printer.write(' '); |
| printer.writeName(binaryName); |
| printer.write('= '); |
| printer.writeExpression(rhs); |
| } |
| } |
| |
| /// Internal expression representing an compound property assignment. |
| /// |
| /// An compound property assignment of the form `o.a++` is encoded as the |
| /// expression: |
| /// |
| /// let v1 = o in let v2 = v1.a in let v3 = v1.a = v2 + 1 in v2 |
| /// |
| class PropertyPostIncDec extends InternalExpression { |
| /// The synthetic variable whose initializer hold the receiver. |
| /// |
| /// This is `null` if the receiver is read-only and therefore does not need to |
| /// be stored in a temporary variable. |
| VariableDeclarationImpl? variable; |
| |
| /// The expression that reads the property on [variable]. |
| VariableDeclarationImpl read; |
| |
| /// The expression that writes the result of the binary operation to the |
| /// property on [variable]. |
| VariableDeclarationImpl write; |
| |
| PropertyPostIncDec(this.variable, this.read, this.write) |
| // ignore: unnecessary_null_comparison |
| : assert(read != null), |
| // ignore: unnecessary_null_comparison |
| assert(write != null) { |
| variable?.parent = this; |
| read.parent = this; |
| write.parent = this; |
| } |
| |
| PropertyPostIncDec.onReadOnly( |
| VariableDeclarationImpl read, VariableDeclarationImpl write) |
| : this(null, read, write); |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitPropertyPostIncDec(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "PropertyPostIncDec(${toStringInternal()})"; |
| } |
| } |
| |
| /// Internal expression representing an local variable post inc/dec expression. |
| /// |
| /// An local variable post inc/dec expression of the form `a++` is encoded as |
| /// the expression: |
| /// |
| /// let v1 = a in let v2 = a = v1 + 1 in v1 |
| /// |
| class LocalPostIncDec extends InternalExpression { |
| /// The expression that reads the local variable. |
| VariableDeclarationImpl read; |
| |
| /// The expression that writes the result of the binary operation to the |
| /// local variable. |
| VariableDeclarationImpl write; |
| |
| LocalPostIncDec(this.read, this.write) |
| // ignore: unnecessary_null_comparison |
| : assert(read != null), |
| // ignore: unnecessary_null_comparison |
| assert(write != null) { |
| read.parent = this; |
| write.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitLocalPostIncDec(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "LocalPostIncDec(${toStringInternal()})"; |
| } |
| } |
| |
| /// Internal expression representing an static member post inc/dec expression. |
| /// |
| /// An local variable post inc/dec expression of the form `a++` is encoded as |
| /// the expression: |
| /// |
| /// let v1 = a in let v2 = a = v1 + 1 in v1 |
| /// |
| class StaticPostIncDec extends InternalExpression { |
| /// The expression that reads the static member. |
| VariableDeclarationImpl read; |
| |
| /// The expression that writes the result of the binary operation to the |
| /// static member. |
| VariableDeclarationImpl write; |
| |
| StaticPostIncDec(this.read, this.write) |
| // ignore: unnecessary_null_comparison |
| : assert(read != null), |
| // ignore: unnecessary_null_comparison |
| assert(write != null) { |
| read.parent = this; |
| write.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitStaticPostIncDec(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "StaticPostIncDec(${toStringInternal()})"; |
| } |
| } |
| |
| /// Internal expression representing an static member post inc/dec expression. |
| /// |
| /// An local variable post inc/dec expression of the form `super.a++` is encoded |
| /// as the expression: |
| /// |
| /// let v1 = super.a in let v2 = super.a = v1 + 1 in v1 |
| /// |
| class SuperPostIncDec extends InternalExpression { |
| /// The expression that reads the static member. |
| VariableDeclarationImpl read; |
| |
| /// The expression that writes the result of the binary operation to the |
| /// static member. |
| VariableDeclarationImpl write; |
| |
| SuperPostIncDec(this.read, this.write) |
| // ignore: unnecessary_null_comparison |
| : assert(read != null), |
| // ignore: unnecessary_null_comparison |
| assert(write != null) { |
| read.parent = this; |
| write.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitSuperPostIncDec(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "SuperPostIncDec(${toStringInternal()})"; |
| } |
| } |
| |
| /// Internal expression representing an index get expression. |
| class IndexGet extends InternalExpression { |
| /// The receiver on which the index set operation is performed. |
| Expression receiver; |
| |
| /// The index expression of the operation. |
| Expression index; |
| |
| IndexGet(this.receiver, this.index) |
| // ignore: unnecessary_null_comparison |
| : assert(receiver != null), |
| // ignore: unnecessary_null_comparison |
| assert(index != null) { |
| receiver.parent = this; |
| index.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitIndexGet(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "IndexGet(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.writeExpression(receiver); |
| printer.write('['); |
| printer.writeExpression(index); |
| printer.write(']'); |
| } |
| } |
| |
| /// Internal expression representing an index set expression. |
| /// |
| /// An index set expression of the form `o[a] = b` used for value is encoded as |
| /// the expression: |
| /// |
| /// let v1 = o in let v2 = a in let v3 = b in let _ = o.[]=(v2, v3) in v3 |
| /// |
| /// An index set expression used for effect is encoded as |
| /// |
| /// o.[]=(a, b) |
| /// |
| /// using [MethodInvocationImpl]. |
| /// |
| class IndexSet extends InternalExpression { |
| /// The receiver on which the index set operation is performed. |
| Expression receiver; |
| |
| /// The index expression of the operation. |
| Expression index; |
| |
| /// The value expression of the operation. |
| Expression value; |
| |
| final bool forEffect; |
| |
| IndexSet(this.receiver, this.index, this.value, {required this.forEffect}) |
| // ignore: unnecessary_null_comparison |
| : assert(receiver != null), |
| // ignore: unnecessary_null_comparison |
| assert(index != null), |
| // ignore: unnecessary_null_comparison |
| assert(value != null), |
| // ignore: unnecessary_null_comparison |
| assert(forEffect != null) { |
| receiver.parent = this; |
| index.parent = this; |
| value.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitIndexSet(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "IndexSet(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.writeExpression(receiver); |
| printer.write('['); |
| printer.writeExpression(index); |
| printer.write('] = '); |
| printer.writeExpression(value); |
| } |
| } |
| |
| /// Internal expression representing a super index set expression. |
| /// |
| /// A super index set expression of the form `super[a] = b` used for value is |
| /// encoded as the expression: |
| /// |
| /// let v1 = a in let v2 = b in let _ = super.[]=(v1, v2) in v2 |
| /// |
| /// An index set expression used for effect is encoded as |
| /// |
| /// super.[]=(a, b) |
| /// |
| /// using [SuperMethodInvocation]. |
| /// |
| class SuperIndexSet extends InternalExpression { |
| /// The []= member. |
| Member setter; |
| |
| /// The index expression of the operation. |
| Expression index; |
| |
| /// The value expression of the operation. |
| Expression value; |
| |
| SuperIndexSet(this.setter, this.index, this.value) |
| // ignore: unnecessary_null_comparison |
| : assert(index != null), |
| // ignore: unnecessary_null_comparison |
| assert(value != null) { |
| index.parent = this; |
| value.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitSuperIndexSet(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "SuperIndexSet(${toStringInternal()})"; |
| } |
| } |
| |
| /// Internal expression representing an extension index set expression. |
| /// |
| /// An extension index set expression of the form `Extension(o)[a] = b` used |
| /// for value is encoded as the expression: |
| /// |
| /// let receiverVariable = o |
| /// let indexVariable = a in |
| /// let valueVariable = b in ' |
| /// let writeVariable = |
| /// receiverVariable.[]=(indexVariable, valueVariable) in |
| /// valueVariable |
| /// |
| /// An extension index set expression used for effect is encoded as |
| /// |
| /// o.[]=(a, b) |
| /// |
| /// using [StaticInvocation]. |
| /// |
| class ExtensionIndexSet extends InternalExpression { |
| /// The extension in which the [setter] is declared. |
| final Extension extension; |
| |
| /// The explicit type arguments for the type parameters declared in |
| /// [extension]. |
| final List<DartType>? explicitTypeArguments; |
| |
| /// The receiver of the extension access. |
| Expression receiver; |
| |
| /// The []= member. |
| Member setter; |
| |
| /// The index expression of the operation. |
| Expression index; |
| |
| /// The value expression of the operation. |
| Expression value; |
| |
| ExtensionIndexSet(this.extension, this.explicitTypeArguments, this.receiver, |
| this.setter, this.index, this.value) |
| : assert(explicitTypeArguments == null || |
| explicitTypeArguments.length == extension.typeParameters.length), |
| // ignore: unnecessary_null_comparison |
| assert(receiver != null), |
| // ignore: unnecessary_null_comparison |
| assert(index != null), |
| // ignore: unnecessary_null_comparison |
| assert(value != null) { |
| receiver.parent = this; |
| index.parent = this; |
| value.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitExtensionIndexSet(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "ExtensionIndexSet(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write(extension.name); |
| if (explicitTypeArguments != null) { |
| printer.writeTypeArguments(explicitTypeArguments!); |
| } |
| printer.write('('); |
| printer.writeExpression(receiver); |
| printer.write(')['); |
| printer.writeExpression(index); |
| printer.write('] = '); |
| printer.writeExpression(value); |
| } |
| } |
| |
| /// Internal expression representing an if-null index assignment. |
| /// |
| /// An if-null index assignment of the form `o[a] ??= b` is, if used for value, |
| /// encoded as the expression: |
| /// |
| /// let v1 = o in |
| /// let v2 = a in |
| /// let v3 = v1[v2] in |
| /// v3 == null |
| /// ? (let v4 = b in |
| /// let _ = v1.[]=(v2, v4) in |
| /// v4) |
| /// : v3 |
| /// |
| /// and, if used for effect, encoded as the expression: |
| /// |
| /// let v1 = o in |
| /// let v2 = a in |
| /// let v3 = v1[v2] in |
| /// v3 == null ? v1.[]=(v2, b) : null |
| /// |
| /// If the [readOnlyReceiver] is true, no temporary variable is created for the |
| /// receiver and its use is inlined. |
| class IfNullIndexSet extends InternalExpression { |
| /// The receiver on which the index set operation is performed. |
| Expression receiver; |
| |
| /// The index expression of the operation. |
| Expression index; |
| |
| /// The value expression of the operation. |
| Expression value; |
| |
| /// The file offset for the [] operation. |
| final int readOffset; |
| |
| /// The file offset for the == operation. |
| final int testOffset; |
| |
| /// The file offset for the []= operation. |
| final int writeOffset; |
| |
| /// If `true`, the expression is only need for effect and not for its value. |
| final bool forEffect; |
| |
| IfNullIndexSet(this.receiver, this.index, this.value, |
| {required this.readOffset, |
| required this.testOffset, |
| required this.writeOffset, |
| required this.forEffect}) |
| // ignore: unnecessary_null_comparison |
| : assert(receiver != null), |
| // ignore: unnecessary_null_comparison |
| assert(index != null), |
| // ignore: unnecessary_null_comparison |
| assert(value != null), |
| // ignore: unnecessary_null_comparison |
| assert(readOffset != null), |
| // ignore: unnecessary_null_comparison |
| assert(testOffset != null), |
| // ignore: unnecessary_null_comparison |
| assert(writeOffset != null), |
| // ignore: unnecessary_null_comparison |
| assert(forEffect != null) { |
| receiver.parent = this; |
| index.parent = this; |
| value.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitIfNullIndexSet(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "IfNullIndexSet(${toStringInternal()})"; |
| } |
| } |
| |
| /// Internal expression representing an if-null super index set expression. |
| /// |
| /// An if-null super index set expression of the form `super[a] ??= b` is, if |
| /// used for value, encoded as the expression: |
| /// |
| /// let v1 = a in |
| /// let v2 = super.[](v1) in |
| /// v2 == null |
| /// ? (let v3 = b in |
| /// let _ = super.[]=(v1, v3) in |
| /// v3) |
| /// : v2 |
| /// |
| /// and, if used for effect, encoded as the expression: |
| /// |
| /// let v1 = a in |
| /// let v2 = super.[](v1) in |
| /// v2 == null ? super.[]=(v1, b) : null |
| /// |
| class IfNullSuperIndexSet extends InternalExpression { |
| /// The [] member; |
| Member? getter; |
| |
| /// The []= member; |
| Member? setter; |
| |
| /// The index expression of the operation. |
| Expression index; |
| |
| /// The value expression of the operation. |
| Expression value; |
| |
| /// The file offset for the [] operation. |
| final int readOffset; |
| |
| /// The file offset for the == operation. |
| final int testOffset; |
| |
| /// The file offset for the []= operation. |
| final int writeOffset; |
| |
| /// If `true`, the expression is only need for effect and not for its value. |
| final bool forEffect; |
| |
| IfNullSuperIndexSet(this.getter, this.setter, this.index, this.value, |
| {required this.readOffset, |
| required this.testOffset, |
| required this.writeOffset, |
| required this.forEffect}) |
| // ignore: unnecessary_null_comparison |
| : assert(index != null), |
| // ignore: unnecessary_null_comparison |
| assert(value != null), |
| // ignore: unnecessary_null_comparison |
| assert(readOffset != null), |
| // ignore: unnecessary_null_comparison |
| assert(testOffset != null), |
| // ignore: unnecessary_null_comparison |
| assert(writeOffset != null), |
| // ignore: unnecessary_null_comparison |
| assert(forEffect != null) { |
| index.parent = this; |
| value.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitIfNullSuperIndexSet(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "IfNullSuperIndexSet(${toStringInternal()})"; |
| } |
| } |
| |
| /// Internal expression representing an if-null super index set expression. |
| /// |
| /// An if-null super index set expression of the form `super[a] ??= b` is, if |
| /// used for value, encoded as the expression: |
| /// |
| /// let v1 = a in |
| /// let v2 = super.[](v1) in |
| /// v2 == null |
| /// ? (let v3 = b in |
| /// let _ = super.[]=(v1, v3) in |
| /// v3) |
| /// : v2 |
| /// |
| /// and, if used for effect, encoded as the expression: |
| /// |
| /// let v1 = a in |
| /// let v2 = super.[](v1) in |
| /// v2 == null ? super.[]=(v1, b) : null |
| /// |
| class IfNullExtensionIndexSet extends InternalExpression { |
| final Extension extension; |
| |
| final List<DartType>? explicitTypeArguments; |
| |
| /// The extension receiver; |
| Expression receiver; |
| |
| /// The [] member; |
| Member? getter; |
| |
| /// The []= member; |
| Member? setter; |
| |
| /// The index expression of the operation. |
| Expression index; |
| |
| /// The value expression of the operation. |
| Expression value; |
| |
| /// The file offset for the [] operation. |
| final int readOffset; |
| |
| /// The file offset for the == operation. |
| final int testOffset; |
| |
| /// The file offset for the []= operation. |
| final int writeOffset; |
| |
| /// If `true`, the expression is only need for effect and not for its value. |
| final bool forEffect; |
| |
| IfNullExtensionIndexSet(this.extension, this.explicitTypeArguments, |
| this.receiver, this.getter, this.setter, this.index, this.value, |
| {required this.readOffset, |
| required this.testOffset, |
| required this.writeOffset, |
| required this.forEffect}) |
| : assert(explicitTypeArguments == null || |
| explicitTypeArguments.length == extension.typeParameters.length), |
| // ignore: unnecessary_null_comparison |
| assert(receiver != null), |
| // ignore: unnecessary_null_comparison |
| assert(index != null), |
| // ignore: unnecessary_null_comparison |
| assert(value != null), |
| // ignore: unnecessary_null_comparison |
| assert(readOffset != null), |
| // ignore: unnecessary_null_comparison |
| assert(testOffset != null), |
| // ignore: unnecessary_null_comparison |
| assert(writeOffset != null), |
| // ignore: unnecessary_null_comparison |
| assert(forEffect != null) { |
| receiver.parent = this; |
| index.parent = this; |
| value.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitIfNullExtensionIndexSet(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "IfNullExtensionIndexSet(${toStringInternal()})"; |
| } |
| } |
| |
| /// Internal expression representing a compound index assignment. |
| /// |
| /// An if-null index assignment of the form `o[a] += b` is, if used for value, |
| /// encoded as the expression: |
| /// |
| /// let v1 = o in |
| /// let v2 = a in |
| /// let v3 = v1.[](v2) + b |
| /// let v4 = v1.[]=(v2, c3) in v3 |
| /// |
| /// and, if used for effect, encoded as the expression: |
| /// |
| /// let v1 = o in let v2 = a in v1.[]=(v2, v1.[](v2) + b) |
| /// |
| class CompoundIndexSet extends InternalExpression { |
| /// The receiver on which the index set operation is performed. |
| Expression receiver; |
| |
| /// The index expression of the operation. |
| Expression index; |
| |
| /// The name of the binary operation. |
| Name binaryName; |
| |
| /// The right-hand side of the binary expression. |
| Expression rhs; |
| |
| /// The file offset for the [] operation. |
| final int readOffset; |
| |
| /// The file offset for the []= operation. |
| final int writeOffset; |
| |
| /// The file offset for the binary operation. |
| final int binaryOffset; |
| |
| /// If `true`, the expression is only need for effect and not for its value. |
| final bool forEffect; |
| |
| /// If `true`, the expression is a post-fix inc/dec expression. |
| final bool forPostIncDec; |
| |
| CompoundIndexSet(this.receiver, this.index, this.binaryName, this.rhs, |
| {required this.readOffset, |
| required this.binaryOffset, |
| required this.writeOffset, |
| required this.forEffect, |
| required this.forPostIncDec}) |
| // ignore: unnecessary_null_comparison |
| : assert(receiver != null), |
| // ignore: unnecessary_null_comparison |
| assert(index != null), |
| // ignore: unnecessary_null_comparison |
| assert(rhs != null), |
| // ignore: unnecessary_null_comparison |
| assert(readOffset != null), |
| // ignore: unnecessary_null_comparison |
| assert(binaryOffset != null), |
| // ignore: unnecessary_null_comparison |
| assert(writeOffset != null), |
| // ignore: unnecessary_null_comparison |
| assert(forEffect != null), |
| // ignore: unnecessary_null_comparison |
| assert(forPostIncDec != null) { |
| receiver.parent = this; |
| index.parent = this; |
| rhs.parent = this; |
| fileOffset = binaryOffset; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitCompoundIndexSet(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "CompoundIndexSet(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.writeExpression(receiver); |
| printer.write('['); |
| printer.writeExpression(index); |
| printer.write(']'); |
| if (forPostIncDec && |
| (binaryName.text == '+' || binaryName.text == '-') && |
| rhs is IntLiteral && |
| (rhs as IntLiteral).value == 1) { |
| if (binaryName.text == '+') { |
| printer.write('++'); |
| } else { |
| printer.write('--'); |
| } |
| } else { |
| printer.write(' '); |
| printer.write(binaryName.text); |
| printer.write('= '); |
| printer.writeExpression(rhs); |
| } |
| } |
| } |
| |
| /// Internal expression representing a null-aware compound assignment. |
| /// |
| /// A null-aware compound assignment of the form |
| /// |
| /// receiver?.property binaryName= rhs |
| /// |
| /// is, if used for value as a normal compound or prefix operation, encoded as |
| /// the expression: |
| /// |
| /// let receiverVariable = receiver in |
| /// receiverVariable == null ? null : |
| /// let leftVariable = receiverVariable.propertyName in |
| /// let valueVariable = leftVariable binaryName rhs in |
| /// let writeVariable = |
| /// receiverVariable.propertyName = valueVariable in |
| /// valueVariable |
| /// |
| /// and, if used for value as a postfix operation, encoded as |
| /// |
| /// let receiverVariable = receiver in |
| /// receiverVariable == null ? null : |
| /// let leftVariable = receiverVariable.propertyName in |
| /// let writeVariable = |
| /// receiverVariable.propertyName = |
| /// leftVariable binaryName rhs in |
| /// leftVariable |
| /// |
| /// and, if used for effect, encoded as: |
| /// |
| /// let receiverVariable = receiver in |
| /// receiverVariable == null ? null : |
| /// receiverVariable.propertyName = receiverVariable.propertyName + rhs |
| /// |
| class NullAwareCompoundSet extends InternalExpression { |
| /// The receiver on which the null aware operation is performed. |
| Expression receiver; |
| |
| /// The name of the null-aware property. |
| Name propertyName; |
| |
| /// The name of the binary operation. |
| Name binaryName; |
| |
| /// The right-hand side of the binary expression. |
| Expression rhs; |
| |
| /// The file offset for the read operation. |
| final int readOffset; |
| |
| /// The file offset for the write operation. |
| final int writeOffset; |
| |
| /// The file offset for the binary operation. |
| final int binaryOffset; |
| |
| /// If `true`, the expression is only need for effect and not for its value. |
| final bool forEffect; |
| |
| /// If `true`, the expression is a postfix inc/dec expression. |
| final bool forPostIncDec; |
| |
| NullAwareCompoundSet( |
| this.receiver, this.propertyName, this.binaryName, this.rhs, |
| {required this.readOffset, |
| required this.binaryOffset, |
| required this.writeOffset, |
| required this.forEffect, |
| required this.forPostIncDec}) |
| // ignore: unnecessary_null_comparison |
| : assert(receiver != null), |
| // ignore: unnecessary_null_comparison |
| assert(rhs != null), |
| // ignore: unnecessary_null_comparison |
| assert(readOffset != null), |
| // ignore: unnecessary_null_comparison |
| assert(binaryOffset != null), |
| // ignore: unnecessary_null_comparison |
| assert(writeOffset != null), |
| // ignore: unnecessary_null_comparison |
| assert(forEffect != null), |
| // ignore: unnecessary_null_comparison |
| assert(forPostIncDec != null) { |
| receiver.parent = this; |
| rhs.parent = this; |
| fileOffset = binaryOffset; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitNullAwareCompoundSet(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "NullAwareCompoundSet(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.writeExpression(receiver); |
| printer.write('?.'); |
| printer.writeName(propertyName); |
| if (forPostIncDec && |
| rhs is IntLiteral && |
| (rhs as IntLiteral).value == 1 && |
| (binaryName == plusName || binaryName == minusName)) { |
| if (binaryName == plusName) { |
| printer.write('++'); |
| } else { |
| printer.write('--'); |
| } |
| } else { |
| printer.write(' '); |
| printer.writeName(binaryName); |
| printer.write('= '); |
| printer.writeExpression(rhs); |
| } |
| } |
| } |
| |
| /// Internal expression representing an null-aware if-null property set. |
| /// |
| /// A null-aware if-null property set of the form |
| /// |
| /// receiver?.name ??= value |
| /// |
| /// is, if used for value, encoded as the expression: |
| /// |
| /// let receiverVariable = receiver in |
| /// receiverVariable == null ? null : |
| /// (let readVariable = receiverVariable.name in |
| /// readVariable == null ? |
| /// receiverVariable.name = value : readVariable) |
| /// |
| /// and, if used for effect, encoded as the expression: |
| /// |
| /// let receiverVariable = receiver in |
| /// receiverVariable == null ? null : |
| /// (receiverVariable.name == null ? |
| /// receiverVariable.name = value : null) |
| /// |
| /// |
| class NullAwareIfNullSet extends InternalExpression { |
| /// The synthetic variable whose initializer hold the receiver. |
| Expression receiver; |
| |
| /// The expression that reads the property from [variable]. |
| Name name; |
| |
| /// The expression that writes the value to the property on [variable]. |
| Expression value; |
| |
| /// The file offset for the read operation. |
| final int readOffset; |
| |
| /// The file offset for the write operation. |
| final int writeOffset; |
| |
| /// The file offset for the == operation. |
| final int testOffset; |
| |
| /// If `true`, the expression is only need for effect and not for its value. |
| final bool forEffect; |
| |
| NullAwareIfNullSet(this.receiver, this.name, this.value, |
| {required this.readOffset, |
| required this.writeOffset, |
| required this.testOffset, |
| required this.forEffect}) |
| // ignore: unnecessary_null_comparison |
| : assert(receiver != null), |
| // ignore: unnecessary_null_comparison |
| assert(value != null), |
| // ignore: unnecessary_null_comparison |
| assert(readOffset != null), |
| // ignore: unnecessary_null_comparison |
| assert(writeOffset != null), |
| // ignore: unnecessary_null_comparison |
| assert(testOffset != null), |
| // ignore: unnecessary_null_comparison |
| assert(forEffect != null) { |
| receiver.parent = this; |
| value.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitNullAwareIfNullSet(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "NullAwareIfNullSet(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.writeExpression(receiver); |
| printer.write('?.'); |
| printer.writeName(name); |
| printer.write(' ??= '); |
| printer.writeExpression(value); |
| } |
| } |
| |
| /// Internal expression representing a compound super index assignment. |
| /// |
| /// An if-null index assignment of the form `super[a] += b` is, if used for |
| /// value, encoded as the expression: |
| /// |
| /// let v1 = a in |
| /// let v2 = super.[](v1) + b |
| /// let v3 = super.[]=(v1, v2) in v2 |
| /// |
| /// and, if used for effect, encoded as the expression: |
| /// |
| /// let v1 = a in super.[]=(v2, super.[](v2) + b) |
| /// |
| class CompoundSuperIndexSet extends InternalExpression { |
| /// The [] member. |
| Member getter; |
| |
| /// The []= member. |
| Member setter; |
| |
| /// The index expression of the operation. |
| Expression index; |
| |
| /// The name of the binary operation. |
| Name binaryName; |
| |
| /// The right-hand side of the binary expression. |
| Expression rhs; |
| |
| /// The file offset for the [] operation. |
| final int readOffset; |
| |
| /// The file offset for the []= operation. |
| final int writeOffset; |
| |
| /// The file offset for the binary operation. |
| final int binaryOffset; |
| |
| /// If `true`, the expression is only need for effect and not for its value. |
| final bool forEffect; |
| |
| /// If `true`, the expression is a post-fix inc/dec expression. |
| final bool forPostIncDec; |
| |
| CompoundSuperIndexSet( |
| this.getter, this.setter, this.index, this.binaryName, this.rhs, |
| {required this.readOffset, |
| required this.binaryOffset, |
| required this.writeOffset, |
| required this.forEffect, |
| required this.forPostIncDec}) |
| // ignore: unnecessary_null_comparison |
| : assert(index != null), |
| // ignore: unnecessary_null_comparison |
| assert(rhs != null), |
| // ignore: unnecessary_null_comparison |
| assert(readOffset != null), |
| // ignore: unnecessary_null_comparison |
| assert(binaryOffset != null), |
| // ignore: unnecessary_null_comparison |
| assert(writeOffset != null), |
| // ignore: unnecessary_null_comparison |
| assert(forEffect != null), |
| // ignore: unnecessary_null_comparison |
| assert(forPostIncDec != null) { |
| index.parent = this; |
| rhs.parent = this; |
| fileOffset = binaryOffset; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitCompoundSuperIndexSet(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "CompoundSuperIndexSet(${toStringInternal()})"; |
| } |
| } |
| |
| /// Internal expression representing a compound extension index assignment. |
| /// |
| /// An compound extension index assignment of the form `Extension(o)[a] += b` |
| /// is, if used for value, encoded as the expression: |
| /// |
| /// let receiverVariable = o; |
| /// let indexVariable = a in |
| /// let valueVariable = receiverVariable.[](indexVariable) + b |
| /// let writeVariable = |
| /// receiverVariable.[]=(indexVariable, valueVariable) in |
| /// valueVariable |
| /// |
| /// and, if used for effect, encoded as the expression: |
| /// |
| /// let receiverVariable = o; |
| /// let indexVariable = a in |
| /// receiverVariable.[]=(indexVariable, |
| /// receiverVariable.[](indexVariable) + b) |
| /// |
| class CompoundExtensionIndexSet extends InternalExpression { |
| final Extension extension; |
| |
| final List<DartType>? explicitTypeArguments; |
| |
| Expression receiver; |
| |
| /// The [] member. |
| Member? getter; |
| |
| /// The []= member. |
| Member? setter; |
| |
| /// The index expression of the operation. |
| Expression index; |
| |
| /// The name of the binary operation. |
| Name binaryName; |
| |
| /// The right-hand side of the binary expression. |
| Expression rhs; |
| |
| /// The file offset for the [] operation. |
| final int readOffset; |
| |
| /// The file offset for the []= operation. |
| final int writeOffset; |
| |
| /// The file offset for the binary operation. |
| final int binaryOffset; |
| |
| /// If `true`, the expression is only need for effect and not for its value. |
| final bool forEffect; |
| |
| /// If `true`, the expression is a post-fix inc/dec expression. |
| final bool forPostIncDec; |
| |
| CompoundExtensionIndexSet( |
| this.extension, |
| this.explicitTypeArguments, |
| this.receiver, |
| this.getter, |
| this.setter, |
| this.index, |
| this.binaryName, |
| this.rhs, |
| {required this.readOffset, |
| required this.binaryOffset, |
| required this.writeOffset, |
| required this.forEffect, |
| required this.forPostIncDec}) |
| : assert(explicitTypeArguments == null || |
| explicitTypeArguments.length == extension.typeParameters.length), |
| // ignore: unnecessary_null_comparison |
| assert(receiver != null), |
| // ignore: unnecessary_null_comparison |
| assert(index != null), |
| // ignore: unnecessary_null_comparison |
| assert(rhs != null), |
| // ignore: unnecessary_null_comparison |
| assert(readOffset != null), |
| // ignore: unnecessary_null_comparison |
| assert(binaryOffset != null), |
| // ignore: unnecessary_null_comparison |
| assert(writeOffset != null), |
| // ignore: unnecessary_null_comparison |
| assert(forEffect != null), |
| // ignore: unnecessary_null_comparison |
| assert(forPostIncDec != null) { |
| receiver.parent = this; |
| index.parent = this; |
| rhs.parent = this; |
| fileOffset = binaryOffset; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitCompoundExtensionIndexSet(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "CompoundExtensionIndexSet(${toStringInternal()})"; |
| } |
| } |
| |
| /// Internal expression representing an assignment to an extension setter. |
| /// |
| /// An extension set of the form `receiver.target = value` is, if used for |
| /// value, encoded as the expression: |
| /// |
| /// let receiverVariable = receiver in |
| /// let valueVariable = value in |
| /// let writeVariable = target(receiverVariable, valueVariable) in |
| /// valueVariable |
| /// |
| /// or if the receiver is read-only, like `this` or a final variable, |
| /// |
| /// let valueVariable = value in |
| /// let writeVariable = target(receiver, valueVariable) in |
| /// valueVariable |
| /// |
| /// and, if used for effect, encoded as a [StaticInvocation]: |
| /// |
| /// target(receiver, value) |
| /// |
| // TODO(johnniwinther): Rename read-only to side-effect-free. |
| class ExtensionSet extends InternalExpression { |
| final Extension extension; |
| |
| final List<DartType>? explicitTypeArguments; |
| |
| /// The receiver for the assignment. |
| Expression receiver; |
| |
| /// The extension member called for the assignment. |
| Procedure target; |
| |
| /// The right-hand side value of the assignment. |
| Expression value; |
| |
| /// If `true` the assignment is only needed for effect and not its result |
| /// value. |
| final bool forEffect; |
| |
| ExtensionSet(this.extension, this.explicitTypeArguments, this.receiver, |
| this.target, this.value, |
| {required this.forEffect}) |
| : assert(explicitTypeArguments == null || |
| explicitTypeArguments.length == extension.typeParameters.length), |
| // ignore: unnecessary_null_comparison |
| assert(receiver != null), |
| // ignore: unnecessary_null_comparison |
| assert(value != null), |
| // ignore: unnecessary_null_comparison |
| assert(forEffect != null) { |
| receiver.parent = this; |
| value.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitExtensionSet(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "ExtensionSet(${toStringInternal()})"; |
| } |
| } |
| |
| /// Internal expression representing an null-aware extension expression. |
| /// |
| /// An null-aware extension expression of the form `Extension(receiver)?.target` |
| /// is encoded as the expression: |
| /// |
| /// let variable = receiver in |
| /// variable == null ? null : expression |
| /// |
| /// where `expression` is an encoding of `receiverVariable.target`. |
| class NullAwareExtension extends InternalExpression { |
| VariableDeclarationImpl variable; |
| Expression expression; |
| |
| NullAwareExtension(this.variable, this.expression) |
| // ignore: unnecessary_null_comparison |
| : assert(variable != null), |
| // ignore: unnecessary_null_comparison |
| assert(expression != null) { |
| variable.parent = this; |
| expression.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitNullAwareExtension(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "NullAwareExtension(${toStringInternal()})"; |
| } |
| } |
| |
| /// Internal representation of a read of an extension instance member. |
| /// |
| /// A read of an extension instance member `o.foo` is encoded as the |
| /// [StaticInvocation] |
| /// |
| /// extension|foo(o) |
| /// |
| /// where `extension|foo` is the top level method created for reading the |
| /// `foo` member. If `foo` is an extension instance method, then `extension|foo` |
| /// the special tear-off function created for extension instance methods. |
| /// Otherwise `extension|foo` is the top level method corresponding to the |
| /// extension instance getter being read. |
| class ExtensionTearOff extends InternalExpression { |
| /// The top-level method that is that target for the read operation. |
| Procedure target; |
| |
| /// The arguments provided to the top-level method. |
| Arguments arguments; |
| |
| ExtensionTearOff(this.target, this.arguments) |
| // ignore: unnecessary_null_comparison |
| : assert(arguments != null) { |
| arguments.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitExtensionTearOff(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "ExtensionTearOff(${toStringInternal()})"; |
| } |
| } |
| |
| /// Internal expression for an equals or not-equals expression. |
| class EqualsExpression extends InternalExpression { |
| Expression left; |
| Expression right; |
| bool isNot; |
| |
| EqualsExpression(this.left, this.right, {required this.isNot}) |
| // ignore: unnecessary_null_comparison |
| : assert(left != null), |
| // ignore: unnecessary_null_comparison |
| assert(right != null), |
| // ignore: unnecessary_null_comparison |
| assert(isNot != null) { |
| left.parent = this; |
| right.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitEquals(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "EqualsExpression(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.writeExpression(left, minimumPrecedence: Precedence.EQUALITY); |
| if (isNot) { |
| printer.write(' != '); |
| } else { |
| printer.write(' == '); |
| } |
| printer.writeExpression(right, minimumPrecedence: Precedence.EQUALITY + 1); |
| } |
| } |
| |
| /// Internal expression for a binary expression. |
| class BinaryExpression extends InternalExpression { |
| Expression left; |
| Name binaryName; |
| Expression right; |
| |
| BinaryExpression(this.left, this.binaryName, this.right) |
| // ignore: unnecessary_null_comparison |
| : assert(left != null), |
| // ignore: unnecessary_null_comparison |
| assert(right != null) { |
| left.parent = this; |
| right.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitBinary(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "BinaryExpression(${toStringInternal()})"; |
| } |
| |
| @override |
| int get precedence => Precedence.binaryPrecedence[binaryName.text]!; |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.writeExpression(left, minimumPrecedence: precedence); |
| printer.write(' ${binaryName.text} '); |
| printer.writeExpression(right, minimumPrecedence: precedence); |
| } |
| } |
| |
| /// Internal expression for a unary expression. |
| class UnaryExpression extends InternalExpression { |
| Name unaryName; |
| Expression expression; |
| |
| UnaryExpression(this.unaryName, this.expression) |
| // ignore: unnecessary_null_comparison |
| : assert(expression != null) { |
| expression.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitUnary(this, typeContext); |
| } |
| |
| @override |
| int get precedence => Precedence.PREFIX; |
| |
| @override |
| String toString() { |
| return "UnaryExpression(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| if (unaryName == unaryMinusName) { |
| printer.write('-'); |
| } else { |
| printer.write('${unaryName.text}'); |
| } |
| printer.writeExpression(expression, minimumPrecedence: precedence); |
| } |
| } |
| |
| /// Internal expression for a parenthesized expression. |
| class ParenthesizedExpression extends InternalExpression { |
| Expression expression; |
| |
| ParenthesizedExpression(this.expression) |
| // ignore: unnecessary_null_comparison |
| : assert(expression != null) { |
| expression.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitParenthesized(this, typeContext); |
| } |
| |
| @override |
| int get precedence => Precedence.CALLEE; |
| |
| @override |
| String toString() { |
| return "ParenthesizedExpression(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write('('); |
| printer.writeExpression(expression); |
| printer.write(')'); |
| } |
| } |
| |
| /// Creates a [Let] of [variable] with the given [body] using |
| /// `variable.fileOffset` as the file offset for the let. |
| /// |
| /// This is useful for create let expressions in replacement code. |
| Let createLet(VariableDeclaration variable, Expression body) { |
| return new Let(variable, body)..fileOffset = variable.fileOffset; |
| } |
| |
| /// Creates a [VariableDeclaration] for [expression] with the static [type] |
| /// using `expression.fileOffset` as the file offset for the declaration. |
| /// |
| /// This is useful for creating let variables for expressions in replacement |
| /// code. |
| VariableDeclaration createVariable(Expression expression, DartType type) { |
| assert(expression is! ThisExpression); |
| return new VariableDeclaration.forValue(expression, type: type) |
| ..fileOffset = expression.fileOffset; |
| } |
| |
| /// Creates a [VariableDeclaration] for the expression inference [result] |
| /// using `result.expression.fileOffset` as the file offset for the declaration. |
| /// |
| /// This is useful for creating let variables for expressions in replacement |
| /// code. |
| VariableDeclaration createVariableForResult(ExpressionInferenceResult result) { |
| return createVariable(result.expression, result.inferredType); |
| } |
| |
| /// Creates a [VariableGet] of [variable] using `variable.fileOffset` as the |
| /// file offset for the expression. |
| /// |
| /// This is useful for referencing let variables for expressions in replacement |
| /// code. |
| VariableGet createVariableGet(VariableDeclaration variable) { |
| return new VariableGet(variable)..fileOffset = variable.fileOffset; |
| } |
| |
| ExpressionStatement createExpressionStatement(Expression expression) { |
| return new ExpressionStatement(expression) |
| ..fileOffset = expression.fileOffset; |
| } |
| |
| /// Returns `true` if [node] is a pure expression. |
| /// |
| /// A pure expression is an expression that is deterministic and side effect |
| /// free, such as `this` or a variable get of a final variable. |
| bool isPureExpression(Expression node) { |
| if (node is ThisExpression) { |
| return true; |
| } else if (node is VariableGet) { |
| return node.variable.isFinal && !node.variable.isLate; |
| } |
| return false; |
| } |
| |
| /// Returns a clone of [node]. |
| /// |
| /// This assumes that `isPureExpression(node)` is `true`. |
| Expression clonePureExpression(Expression node) { |
| if (node is ThisExpression) { |
| return new ThisExpression()..fileOffset = node.fileOffset; |
| } else if (node is VariableGet) { |
| assert( |
| node.variable.isFinal && !node.variable.isLate, |
| "Trying to clone VariableGet of non-final variable" |
| " ${node.variable}."); |
| return new VariableGet(node.variable, node.promotedType) |
| ..fileOffset = node.fileOffset; |
| } |
| throw new UnsupportedError("Clone not supported for ${node.runtimeType}."); |
| } |
| |
| /// A dynamically bound method invocation of the form `o.foo()`. |
| /// |
| /// This will be transformed into an [InstanceInvocation], [DynamicInvocation], |
| /// [FunctionInvocation] or [StaticInvocation] (for implicit extension method |
| /// invocation) after type inference. |
| class MethodInvocation extends InternalExpression { |
| Expression receiver; |
| |
| Name name; |
| |
| Arguments arguments; |
| |
| MethodInvocation(this.receiver, this.name, this.arguments) |
| // ignore: unnecessary_null_comparison |
| : assert(receiver != null), |
| // ignore: unnecessary_null_comparison |
| assert(arguments != null) { |
| receiver.parent = this; |
| arguments.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitMethodInvocation(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "MethodInvocation(${toStringInternal()})"; |
| } |
| |
| @override |
| int get precedence => Precedence.PRIMARY; |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.writeExpression(receiver, minimumPrecedence: Precedence.PRIMARY); |
| printer.write('.'); |
| printer.writeName(name); |
| printer.writeArguments(arguments); |
| } |
| } |
| |
| /// A dynamically bound property read of the form `o.foo`. |
| /// |
| /// This will be transformed into an [InstanceGet], [InstanceTearOff], |
| /// [DynamicGet], [FunctionTearOff] or [StaticInvocation] (for implicit |
| /// extension member access) after type inference. |
| class PropertyGet extends InternalExpression { |
| Expression receiver; |
| |
| Name name; |
| |
| PropertyGet(this.receiver, this.name) |
| // ignore: unnecessary_null_comparison |
| : assert(receiver != null) { |
| receiver.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitPropertyGet(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "PropertyGet(${toStringInternal()})"; |
| } |
| |
| @override |
| int get precedence => Precedence.PRIMARY; |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.writeExpression(receiver, minimumPrecedence: Precedence.PRIMARY); |
| printer.write('.'); |
| printer.writeName(name); |
| } |
| } |
| |
| /// A dynamically bound property write of the form `o.foo = e`. |
| /// |
| /// This will be transformed into an [InstanceSet], [DynamicSet], or |
| /// [StaticInvocation] (for implicit extension member access) after type |
| /// inference. |
| class PropertySet extends InternalExpression { |
| Expression receiver; |
| Name name; |
| Expression value; |
| |
| /// If `true` the assignment is need for its effect and not for its value. |
| final bool forEffect; |
| |
| /// If `true` the receiver can be cloned and doesn't need a temporary variable |
| /// for multiple reads. |
| final bool readOnlyReceiver; |
| |
| PropertySet(this.receiver, this.name, this.value, |
| {required this.forEffect, required this.readOnlyReceiver}) |
| // ignore: unnecessary_null_comparison |
| : assert(receiver != null), |
| // ignore: unnecessary_null_comparison |
| assert(value != null), |
| // ignore: unnecessary_null_comparison |
| assert(forEffect != null), |
| // ignore: unnecessary_null_comparison |
| assert(readOnlyReceiver != null) { |
| receiver.parent = this; |
| value.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitPropertySet(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "PropertySet(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.writeExpression(receiver, minimumPrecedence: Precedence.PRIMARY); |
| printer.write('.'); |
| printer.writeName(name); |
| printer.write(' = '); |
| printer.writeExpression(value); |
| } |
| } |
| |
| /// An augment super invocation of the form `augment super()`. |
| /// |
| /// This will be transformed into an [InstanceInvocation], [InstanceGet] plus |
| /// [FunctionInvocation], or [StaticInvocation] after type inference. |
| class AugmentSuperInvocation extends InternalExpression { |
| final Member target; |
| |
| Arguments arguments; |
| |
| AugmentSuperInvocation(this.target, this.arguments, |
| {required int fileOffset}) { |
| arguments.parent = this; |
| this.fileOffset = fileOffset; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitAugmentSuperInvocation(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "AugmentSuperInvocation(${toStringInternal()})"; |
| } |
| |
| @override |
| int get precedence => Precedence.PRIMARY; |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write('augment super'); |
| printer.writeArguments(arguments); |
| } |
| } |
| |
| /// An augment super read of the form `augment super`. |
| /// |
| /// This will be transformed into an [InstanceGet], [InstanceTearOff], |
| /// [DynamicGet], [FunctionTearOff] or [StaticInvocation] (for implicit |
| /// extension member access) after type inference. |
| class AugmentSuperGet extends InternalExpression { |
| final Member target; |
| |
| AugmentSuperGet(this.target, {required int fileOffset}) { |
| this.fileOffset = fileOffset; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitAugmentSuperGet(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "AugmentSuperGet(${toStringInternal()})"; |
| } |
| |
| @override |
| int get precedence => Precedence.PRIMARY; |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write('augment super'); |
| } |
| } |
| |
| /// An augment super write of the form `augment super = e`. |
| /// |
| /// This will be transformed into an [InstanceSet], or [StaticSet] after type |
| /// inference. |
| class AugmentSuperSet extends InternalExpression { |
| final Member target; |
| |
| Expression value; |
| |
| /// If `true` the assignment is need for its effect and not for its value. |
| final bool forEffect; |
| |
| AugmentSuperSet(this.target, this.value, |
| {required this.forEffect, required int fileOffset}) { |
| value.parent = this; |
| this.fileOffset = fileOffset; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitAugmentSuperSet(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "AugmentSuperSet(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write('augment super = '); |
| printer.writeExpression(value); |
| } |
| } |
| |
| class InternalRecordLiteral extends InternalExpression { |
| final List<Expression> positional; |
| final List<NamedExpression> named; |
| final Map<String, NamedExpression>? namedElements; |
| final List<Object /*Expression|NamedExpression*/ > originalElementOrder; |
| final bool isConst; |
| |
| InternalRecordLiteral(this.positional, this.named, this.namedElements, |
| this.originalElementOrder, |
| {required this.isConst, required int offset}) { |
| fileOffset = offset; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitInternalRecordLiteral(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "InternalRecordLiteral(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| if (isConst) { |
| printer.write('const '); |
| } |
| printer.write('('); |
| String comma = ''; |
| for (Object element in originalElementOrder) { |
| printer.write(comma); |
| if (element is NamedExpression) { |
| printer.write(element.name); |
| printer.write(': '); |
| printer.writeExpression(element.value); |
| } else { |
| printer.writeExpression(element as Expression); |
| } |
| comma = ', '; |
| } |
| printer.write(')'); |
| } |
| } |
| |
| abstract class Pattern extends TreeNode with InternalTreeNode { |
| Pattern(int fileOffset) { |
| this.fileOffset = fileOffset; |
| } |
| |
| /// Variable declarations induced by nested variable patterns. |
| /// |
| /// These variables are initialized to the values captured by the variable |
| /// patterns nested in the pattern. |
| List<VariableDeclaration> get declaredVariables; |
| |
| void acceptInference( |
| InferenceVisitorImpl visitor, { |
| required SharedMatchContext context, |
| }); |
| |
| /// Transforms a pattern into a series of if-statements and local variables |
| /// |
| /// [matchedExpression] is the expression that evaluates to the object being |
| /// matched against the pattern at runtime. [matchedType] is the static type |
| /// of [matchedExpression]. [variableInitializingContext] evaluates to the |
| /// same runtime objects as [matchedExpression], but can be accessed without |
| /// causing additional side effects. It is the responsibility of the caller to |
| /// ensure the absence of those side effects, which can be done, for example, |
| /// via caching of the value to match in a local variable. |
| PatternTransformationResult transform(InferenceVisitorBase inferenceVisitor, |
| {required Expression matchedExpression, |
| required DartType matchedType, |
| required Expression variableInitializingContext}); |
| } |
| |
| class DummyPattern extends Pattern { |
| DummyPattern(int fileOffset) : super(fileOffset); |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write('<dummy-pattern>'); |
| } |
| |
| @override |
| List<VariableDeclaration> get declaredVariables => const []; |
| |
| @override |
| void acceptInference( |
| InferenceVisitorImpl visitor, { |
| required SharedMatchContext context, |
| }) { |
| visitor.visitDummyPattern(this, context: context); |
| } |
| |
| @override |
| PatternTransformationResult transform(InferenceVisitorBase inferenceVisitor, |
| {required Expression matchedExpression, |
| required DartType matchedType, |
| required Expression variableInitializingContext}) { |
| return new PatternTransformationResult([ |
| new PatternTransformationElement( |
| kind: PatternTransformationElementKind.regular, |
| condition: null, |
| variableInitializers: <Statement>[]) |
| ]); |
| } |
| |
| @override |
| String toString() { |
| return "DummyPattern(${toStringInternal()})"; |
| } |
| } |
| |
| /// A [Pattern] based on an [Expression]. This corresponds to a constant |
| /// pattern in the specification. |
| class ExpressionPattern extends Pattern { |
| Expression expression; |
| |
| ExpressionPattern(this.expression) : super(expression.fileOffset) { |
| expression.parent = this; |
| } |
| |
| @override |
| List<VariableDeclaration> get declaredVariables => const []; |
| |
| @override |
| void acceptInference( |
| InferenceVisitorImpl visitor, { |
| required SharedMatchContext context, |
| }) { |
| visitor.visitExpressionPattern(this, context: context); |
| } |
| |
| @override |
| PatternTransformationResult transform(InferenceVisitorBase inferenceVisitor, |
| {required Expression matchedExpression, |
| required DartType matchedType, |
| required Expression variableInitializingContext}) { |
| ObjectAccessTarget target = inferenceVisitor.findInterfaceMember( |
| matchedType, equalsName, fileOffset, |
| callSiteAccessKind: CallSiteAccessKind.operatorInvocation); |
| Expression result = inferenceVisitor.engine.forest.createEqualsCall( |
| fileOffset, |
| matchedExpression, |
| expression, |
| target.getFunctionType(inferenceVisitor), |
| target.member as Procedure); |
| return new PatternTransformationResult([ |
| new PatternTransformationElement( |
| kind: PatternTransformationElementKind.regular, |
| condition: result, |
| variableInitializers: []) |
| ]); |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| expression.toTextInternal(printer); |
| } |
| |
| @override |
| String toString() { |
| return "ExpressionPattern(${toStringInternal()})"; |
| } |
| } |
| |
| /// A [Pattern] for `pattern & pattern`. |
| class AndPattern extends Pattern { |
| Pattern left; |
| Pattern right; |
| |
| @override |
| List<VariableDeclaration> get declaredVariables => |
| [...left.declaredVariables, ...right.declaredVariables]; |
| |
| AndPattern(this.left, this.right, int fileOffset) : super(fileOffset) { |
| left.parent = this; |
| right.parent = this; |
| } |
| |
| @override |
| void acceptInference( |
| InferenceVisitorImpl visitor, { |
| required SharedMatchContext context, |
| }) { |
| visitor.visitAndPattern(this, context: context); |
| } |
| |
| @override |
| PatternTransformationResult transform(InferenceVisitorBase inferenceVisitor, |
| {required Expression matchedExpression, |
| required DartType matchedType, |
| required Expression variableInitializingContext}) { |
| // intermediateVariable: `matchedType` VAR = `matchedExpression` |
| VariableDeclaration intermediateVariable = inferenceVisitor.engine.forest |
| .createVariableDeclarationForValue(matchedExpression, |
| type: matchedType); |
| |
| PatternTransformationResult transformationResult = left.transform( |
| inferenceVisitor, |
| matchedExpression: inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, intermediateVariable), |
| matchedType: matchedType, |
| variableInitializingContext: inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, intermediateVariable)); |
| |
| transformationResult = transformationResult.combine( |
| right.transform(inferenceVisitor, |
| matchedExpression: inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, intermediateVariable), |
| matchedType: matchedType, |
| variableInitializingContext: inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, intermediateVariable)), |
| inferenceVisitor); |
| |
| transformationResult = transformationResult.prependElement( |
| new PatternTransformationElement( |
| kind: PatternTransformationElementKind.regular, |
| condition: null, |
| variableInitializers: [intermediateVariable]), |
| inferenceVisitor); |
| |
| return transformationResult; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| left.toTextInternal(printer); |
| printer.write(' & '); |
| right.toTextInternal(printer); |
| } |
| |
| @override |
| String toString() { |
| return "BinaryPattern(${toStringInternal()})"; |
| } |
| } |
| |
| /// A [Pattern] for `pattern | pattern`. |
| class OrPattern extends Pattern { |
| Pattern left; |
| Pattern right; |
| List<VariableDeclaration> _orPatternJointVariables; |
| |
| @override |
| List<VariableDeclaration> get declaredVariables => _orPatternJointVariables; |
| |
| OrPattern(this.left, this.right, int fileOffset, |
| {required List<VariableDeclaration> orPatternJointVariables}) |
| : _orPatternJointVariables = orPatternJointVariables, |
| super(fileOffset) { |
| left.parent = this; |
| right.parent = this; |
| } |
| |
| @override |
| void acceptInference( |
| InferenceVisitorImpl visitor, { |
| required SharedMatchContext context, |
| }) { |
| visitor.visitOrPattern(this, context: context); |
| } |
| |
| @override |
| PatternTransformationResult transform(InferenceVisitorBase inferenceVisitor, |
| {required Expression matchedExpression, |
| required DartType matchedType, |
| required Expression variableInitializingContext}) { |
| // intermediateVariable: `matchedType` VAR = `matchedExpression` |
| VariableDeclaration intermediateVariable = inferenceVisitor.engine.forest |
| .createVariableDeclarationForValue(matchedExpression, |
| type: matchedType); |
| |
| // leftConditionIsTrue: bool LVAR = false; |
| VariableDeclaration leftConditionIsTrue = inferenceVisitor.engine.forest |
| .createVariableDeclarationForValue( |
| inferenceVisitor.engine.forest.createBoolLiteral(fileOffset, false), |
| type: inferenceVisitor.coreTypes.boolNonNullableRawType); |
| |
| Map<String, VariableDeclaration> leftVariablesByName = { |
| for (VariableDeclaration variable in left.declaredVariables) |
| variable.name!: variable |
| }; |
| Map<String, VariableDeclaration> rightVariablesByName = { |
| for (VariableDeclaration variable in right.declaredVariables) |
| variable.name!: variable |
| }; |
| List<VariableDeclaration> declaredVariables = this.declaredVariables; |
| for (VariableDeclaration variable in declaredVariables) { |
| VariableDeclaration? leftVariable = leftVariablesByName[variable.name!]; |
| VariableDeclaration? rightVariable = rightVariablesByName[variable.name!]; |
| |
| if (leftVariable == null || rightVariable == null) { |
| // TODO(cstefantsova): Make sure an error is reported. |
| continue; |
| } |
| |
| variable.initializer = inferenceVisitor.engine.forest |
| .createConditionalExpression( |
| fileOffset, |
| inferenceVisitor.engine.forest.createVariableGet( |
| fileOffset, leftConditionIsTrue), |
| inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, leftVariable) |
| ..promotedType = leftVariable.type, |
| inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, rightVariable) |
| ..promotedType = rightVariable.type) |
| ..staticType = inferenceVisitor.typeSchemaEnvironment |
| .getStandardUpperBound(leftVariable.type, rightVariable.type, |
| isNonNullableByDefault: true) |
| ..parent = variable; |
| } |
| |
| // setLeftConditionIsTrue: `leftConditionIsTrue` = true; |
| // ==> VAR = true; |
| Statement setLeftConditionIsTrue = inferenceVisitor.engine.forest |
| .createExpressionStatement( |
| fileOffset, |
| inferenceVisitor.engine.forest.createVariableSet( |
| fileOffset, |
| leftConditionIsTrue, |
| inferenceVisitor.engine.forest |
| .createBoolLiteral(fileOffset, true))); |
| |
| PatternTransformationResult leftTransformationResult = left.transform( |
| inferenceVisitor, |
| matchedExpression: inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, intermediateVariable), |
| matchedType: matchedType, |
| variableInitializingContext: inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, intermediateVariable)); |
| leftTransformationResult = leftTransformationResult.prependElement( |
| new PatternTransformationElement( |
| kind: PatternTransformationElementKind.logicalOrPatternLeftBegin, |
| condition: null, |
| variableInitializers: []), |
| inferenceVisitor); |
| // Initialize variables to values captured by [left]. |
| leftTransformationResult = leftTransformationResult.combine( |
| new PatternTransformationResult([ |
| new PatternTransformationElement( |
| kind: PatternTransformationElementKind.regular, |
| condition: null, |
| variableInitializers: [ |
| setLeftConditionIsTrue, |
| for (VariableDeclaration variable in left.declaredVariables) |
| inferenceVisitor.engine.forest.createExpressionStatement( |
| fileOffset, |
| inferenceVisitor.engine.forest.createVariableSet( |
| fileOffset, variable, variable.initializer!)) |
| ]) |
| ]), |
| inferenceVisitor); |
| for (VariableDeclaration variable in left.declaredVariables) { |
| variable.name = null; |
| variable.initializer = null; |
| variable.type = const DynamicType(); |
| } |
| |
| // rightConditionIsTrue: bool RVAR = false; |
| VariableDeclaration rightConditionIsTrue = inferenceVisitor.engine.forest |
| .createVariableDeclarationForValue( |
| inferenceVisitor.engine.forest.createBoolLiteral(fileOffset, false), |
| type: inferenceVisitor.coreTypes.boolNonNullableRawType); |
| |
| // setRightConditionIsTrue: `rightConditionIsTrue` = true; |
| // ==> VAR = true; |
| Statement setRightConditionIsTrue = inferenceVisitor.engine.forest |
| .createExpressionStatement( |
| fileOffset, |
| inferenceVisitor.engine.forest.createVariableSet( |
| fileOffset, |
| rightConditionIsTrue, |
| inferenceVisitor.engine.forest |
| .createBoolLiteral(fileOffset, true))); |
| |
| PatternTransformationResult rightTransformationResult = right.transform( |
| inferenceVisitor, |
| matchedExpression: inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, intermediateVariable), |
| matchedType: matchedType, |
| variableInitializingContext: inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, intermediateVariable)); |
| rightTransformationResult = rightTransformationResult.prependElement( |
| new PatternTransformationElement( |
| kind: PatternTransformationElementKind.regular, |
| // condition: !`leftConditionIsTrue` |
| condition: inferenceVisitor.engine.forest.createNot( |
| fileOffset, |
| inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, leftConditionIsTrue)), |
| variableInitializers: []), |
| inferenceVisitor); |
| rightTransformationResult = rightTransformationResult.prependElement( |
| new PatternTransformationElement( |
| kind: PatternTransformationElementKind.logicalOrPatternRightBegin, |
| condition: null, |
| variableInitializers: []), |
| inferenceVisitor); |
| // Initialize variables to values captured by [right]. |
| rightTransformationResult = rightTransformationResult.combine( |
| new PatternTransformationResult([ |
| new PatternTransformationElement( |
| kind: PatternTransformationElementKind.regular, |
| condition: null, |
| variableInitializers: [ |
| setRightConditionIsTrue, |
| for (VariableDeclaration variable in right.declaredVariables) |
| inferenceVisitor.engine.forest.createExpressionStatement( |
| fileOffset, |
| inferenceVisitor.engine.forest.createVariableSet( |
| fileOffset, variable, variable.initializer!)) |
| ]) |
| ]), |
| inferenceVisitor); |
| for (VariableDeclaration variable in right.declaredVariables) { |
| variable.name = null; |
| variable.initializer = null; |
| variable.type = const DynamicType(); |
| } |
| |
| PatternTransformationResult transformationResult = leftTransformationResult |
| .combine(rightTransformationResult, inferenceVisitor) |
| .combine( |
| new PatternTransformationResult([ |
| new PatternTransformationElement( |
| kind: PatternTransformationElementKind.logicalOrPatternEnd, |
| condition: null, |
| variableInitializers: []), |
| new PatternTransformationElement( |
| kind: PatternTransformationElementKind.regular, |
| // condition: |
| // `leftConditionIsTrue` || `rightConditionIsTrue` |
| condition: inferenceVisitor.engine.forest |
| .createLogicalExpression( |
| fileOffset, |
| inferenceVisitor.engine.forest.createVariableGet( |
| fileOffset, leftConditionIsTrue), |
| doubleBarName.text, |
| inferenceVisitor.engine.forest.createVariableGet( |
| fileOffset, rightConditionIsTrue)), |
| variableInitializers: []) |
| ]), |
| inferenceVisitor); |
| |
| transformationResult = transformationResult.prependElement( |
| new PatternTransformationElement( |
| kind: PatternTransformationElementKind.regular, |
| condition: null, |
| variableInitializers: [ |
| intermediateVariable, |
| leftConditionIsTrue, |
| rightConditionIsTrue, |
| ...left.declaredVariables, |
| ...right.declaredVariables |
| ]), |
| inferenceVisitor); |
| |
| return transformationResult; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| left.toTextInternal(printer); |
| printer.write(' | '); |
| right.toTextInternal(printer); |
| } |
| |
| @override |
| String toString() { |
| return "BinaryPattern(${toStringInternal()})"; |
| } |
| } |
| |
| /// A [Pattern] for `pattern as type`. |
| class CastPattern extends Pattern { |
| Pattern pattern; |
| DartType type; |
| |
| CastPattern(this.pattern, this.type, int fileOffset) : super(fileOffset) { |
| pattern.parent = this; |
| } |
| |
| @override |
| List<VariableDeclaration> get declaredVariables => pattern.declaredVariables; |
| |
| @override |
| void acceptInference( |
| InferenceVisitorImpl visitor, { |
| required SharedMatchContext context, |
| }) { |
| visitor.visitCastPattern(this, context: context); |
| } |
| |
| @override |
| PatternTransformationResult transform(InferenceVisitorBase inferenceVisitor, |
| {required Expression matchedExpression, |
| required DartType matchedType, |
| required Expression variableInitializingContext}) { |
| // castExpression: `matchedExpression` as `type` |
| Expression castExpression = inferenceVisitor.engine.forest |
| .createAsExpression(fileOffset, matchedExpression, type, |
| forNonNullableByDefault: inferenceVisitor.isNonNullableByDefault); |
| |
| // intermediateVariable: `type` VAR = `castExpression`; |
| // ==> `type` VAR = `matchedExpression` as `type`; |
| VariableDeclaration intermediateVariable = inferenceVisitor.engine.forest |
| .createVariableDeclarationForValue(castExpression, type: type); |
| |
| PatternTransformationResult transformationResult = pattern.transform( |
| inferenceVisitor, |
| matchedExpression: inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, intermediateVariable), |
| matchedType: type, |
| variableInitializingContext: inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, intermediateVariable)); |
| |
| transformationResult = transformationResult.prependElement( |
| new PatternTransformationElement( |
| kind: PatternTransformationElementKind.regular, |
| condition: null, |
| variableInitializers: [intermediateVariable]), |
| inferenceVisitor); |
| |
| return transformationResult; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| pattern.toTextInternal(printer); |
| printer.write(' as '); |
| printer.writeType(type); |
| } |
| |
| @override |
| String toString() { |
| return "CastPattern(${toStringInternal()})"; |
| } |
| } |
| |
| /// A [Pattern] for `pattern!`. |
| class NullAssertPattern extends Pattern { |
| Pattern pattern; |
| |
| NullAssertPattern(this.pattern, int fileOffset) : super(fileOffset) { |
| pattern.parent = this; |
| } |
| |
| @override |
| List<VariableDeclaration> get declaredVariables => pattern.declaredVariables; |
| |
| @override |
| void acceptInference( |
| InferenceVisitorImpl visitor, { |
| required SharedMatchContext context, |
| }) { |
| visitor.visitNullAssertPattern(this, context: context); |
| } |
| |
| @override |
| PatternTransformationResult transform(InferenceVisitorBase inferenceVisitor, |
| {required Expression matchedExpression, |
| required DartType matchedType, |
| required Expression variableInitializingContext}) { |
| // nullCheckCondition: `matchedExpression`! |
| Expression nullCheckExpression = inferenceVisitor.engine.forest |
| .createNullCheck(fileOffset, matchedExpression); |
| |
| DartType typeWithoutNullabilityMarkers = matchedType.toNonNull(); |
| |
| // intermediateVariable: `typeWithoutNullabilityMarkers` VAR = |
| // `nullCheckExpression`; |
| // ==> `typeWithoutNullabilityMarkers` VAR = `matchedExpression`!; |
| VariableDeclaration intermediateVariable = inferenceVisitor.engine.forest |
| .createVariableDeclarationForValue(nullCheckExpression, |
| type: typeWithoutNullabilityMarkers); |
| |
| PatternTransformationResult transformationResult = pattern.transform( |
| inferenceVisitor, |
| matchedExpression: inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, intermediateVariable), |
| matchedType: typeWithoutNullabilityMarkers, |
| variableInitializingContext: inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, intermediateVariable)); |
| |
| transformationResult = transformationResult.prependElement( |
| new PatternTransformationElement( |
| kind: PatternTransformationElementKind.regular, |
| condition: null, |
| variableInitializers: [intermediateVariable]), |
| inferenceVisitor); |
| |
| return transformationResult; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| pattern.toTextInternal(printer); |
| printer.write('!'); |
| } |
| |
| @override |
| String toString() { |
| return "NullAssertPattern(${toStringInternal()})"; |
| } |
| } |
| |
| /// A [Pattern] for `pattern?`. |
| class NullCheckPattern extends Pattern { |
| Pattern pattern; |
| |
| NullCheckPattern(this.pattern, int fileOffset) : super(fileOffset) { |
| pattern.parent = this; |
| } |
| |
| @override |
| List<VariableDeclaration> get declaredVariables => pattern.declaredVariables; |
| |
| @override |
| void acceptInference( |
| InferenceVisitorImpl visitor, { |
| required SharedMatchContext context, |
| }) { |
| visitor.visitNullCheckPattern(this, context: context); |
| } |
| |
| @override |
| PatternTransformationResult transform(InferenceVisitorBase inferenceVisitor, |
| {required Expression matchedExpression, |
| required DartType matchedType, |
| required Expression variableInitializingContext}) { |
| // intermediateVariable: `matchedType` VAR = `matchedExpression` |
| VariableDeclaration intermediateVariable = inferenceVisitor.engine.forest |
| .createVariableDeclarationForValue(matchedExpression, |
| type: matchedType); |
| |
| // nullCheckCondition: !(`intermediateVariable` == null) |
| Expression nullCheckCondition = inferenceVisitor.engine.forest.createNot( |
| fileOffset, |
| inferenceVisitor.engine.forest.createEqualsNull( |
| fileOffset, |
| inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, intermediateVariable))); |
| |
| DartType promotedType = matchedType.toNonNull(); |
| PatternTransformationResult transformationResult = pattern.transform( |
| inferenceVisitor, |
| matchedExpression: inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, intermediateVariable) |
| ..promotedType = matchedType != promotedType ? promotedType : null, |
| matchedType: promotedType, |
| variableInitializingContext: inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, intermediateVariable)); |
| |
| // This needs to be added to the transformation elements since we need to |
| // create the [intermediateVariable] unconditionally before applying the |
| // [nullCheckCondition], as opposed to passing [nullCheckCondition] as the |
| // condition in the last transformation element. |
| transformationResult = transformationResult.prependElement( |
| new PatternTransformationElement( |
| kind: PatternTransformationElementKind.regular, |
| condition: nullCheckCondition, |
| variableInitializers: []), |
| inferenceVisitor); |
| transformationResult = transformationResult.prependElement( |
| new PatternTransformationElement( |
| kind: PatternTransformationElementKind.regular, |
| condition: null, |
| variableInitializers: [intermediateVariable]), |
| inferenceVisitor); |
| |
| return transformationResult; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| pattern.toTextInternal(printer); |
| printer.write('?'); |
| } |
| |
| @override |
| String toString() { |
| return "NullCheckPattern(${toStringInternal()})"; |
| } |
| } |
| |
| /// A [Pattern] for `<typeArgument>[pattern0, ... patternN]`. |
| class ListPattern extends Pattern { |
| DartType? typeArgument; |
| List<Pattern> patterns; |
| |
| @override |
| List<VariableDeclaration> get declaredVariables => |
| [for (Pattern pattern in patterns) ...pattern.declaredVariables]; |
| |
| ListPattern(this.typeArgument, this.patterns, int fileOffset) |
| : super(fileOffset) { |
| setParents(patterns, this); |
| } |
| |
| @override |
| void acceptInference( |
| InferenceVisitorImpl visitor, { |
| required SharedMatchContext context, |
| }) { |
| visitor.visitListPattern(this, context: context); |
| } |
| |
| @override |
| PatternTransformationResult transform(InferenceVisitorBase inferenceVisitor, |
| {required Expression matchedExpression, |
| required DartType matchedType, |
| required Expression variableInitializingContext}) { |
| // targetListType: List<`typeArgument`> |
| DartType typeArgument = this.typeArgument ?? const DynamicType(); |
| DartType targetListType = new InterfaceType( |
| inferenceVisitor.coreTypes.listClass, |
| Nullability.nonNullable, |
| <DartType>[typeArgument]); |
| |
| ObjectAccessTarget lengthTarget = inferenceVisitor.findInterfaceMember( |
| targetListType, lengthName, fileOffset, |
| callSiteAccessKind: CallSiteAccessKind.getterInvocation); |
| bool typeCheckForTargetListNeeded = |
| !inferenceVisitor.isAssignable(targetListType, matchedType) || |
| matchedType is DynamicType; |
| |
| // listVariable: `matchedType` LVAR = `matchedExpression` |
| VariableDeclaration listVariable = inferenceVisitor.engine.forest |
| .createVariableDeclarationForValue(matchedExpression, |
| type: matchedType); |
| |
| // lengthGet: `listVariable`.length |
| // ==> LVAR.length |
| Expression lengthGet = new InstanceGet( |
| InstanceAccessKind.Instance, |
| inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, listVariable) |
| ..promotedType = typeCheckForTargetListNeeded ? targetListType : null, |
| lengthName, |
| resultType: lengthTarget.getGetterType(inferenceVisitor), |
| interfaceTarget: lengthTarget.member as Procedure) |
| ..fileOffset = fileOffset; |
| |
| ObjectAccessTarget greaterThanOrEqualsTarget = |
| inferenceVisitor.findInterfaceMember( |
| inferenceVisitor.coreTypes.intNonNullableRawType, |
| greaterThanOrEqualsName, |
| fileOffset, |
| callSiteAccessKind: CallSiteAccessKind.operatorInvocation); |
| ObjectAccessTarget equalsTarget = inferenceVisitor.findInterfaceMember( |
| inferenceVisitor.coreTypes.intNonNullableRawType, |
| equalsName, |
| fileOffset, |
| callSiteAccessKind: CallSiteAccessKind.operatorInvocation); |
| |
| Expression lengthCheck; |
| bool hasRestPattern = false; |
| for (Pattern pattern in patterns) { |
| if (pattern is RestPattern) { |
| hasRestPattern = true; |
| break; |
| } |
| } |
| if (hasRestPattern) { |
| // lengthCheck: `lengthGet` >= `patterns.length - 1` |
| // ==> LVAR.length >= `patterns.length - 1` |
| lengthCheck = new InstanceInvocation( |
| InstanceAccessKind.Instance, |
| lengthGet, |
| greaterThanOrEqualsName, |
| inferenceVisitor.engine.forest.createArguments(fileOffset, [ |
| inferenceVisitor.engine.forest |
| .createIntLiteral(fileOffset, patterns.length - 1) |
| ]), |
| functionType: |
| greaterThanOrEqualsTarget.getFunctionType(inferenceVisitor), |
| interfaceTarget: greaterThanOrEqualsTarget.member as Procedure) |
| ..fileOffset = fileOffset; |
| } else { |
| // lengthCheck: `lengthGet` == `patterns.length` |
| lengthCheck = new InstanceInvocation( |
| InstanceAccessKind.Instance, |
| lengthGet, |
| equalsName, |
| inferenceVisitor.engine.forest.createArguments(fileOffset, [ |
| inferenceVisitor.engine.forest |
| .createIntLiteral(fileOffset, patterns.length) |
| ]), |
| functionType: equalsTarget.getFunctionType(inferenceVisitor), |
| interfaceTarget: equalsTarget.member as Procedure) |
| ..fileOffset = fileOffset; |
| } |
| |
| // typeAndLengthCheck: `listVariable` is `targetListType` |
| // && `greaterThanOrEqualsInvocation` |
| // ==> [LVAR is List<`typeArgument`> &&]? |
| // LVAR.length >= `patterns.length` |
| Expression typeAndLengthCheck; |
| if (typeCheckForTargetListNeeded) { |
| typeAndLengthCheck = inferenceVisitor.engine.forest |
| .createLogicalExpression( |
| fileOffset, |
| inferenceVisitor.engine.forest.createIsExpression( |
| fileOffset, |
| inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, listVariable), |
| targetListType, |
| forNonNullableByDefault: false), |
| doubleAmpersandName.text, |
| lengthCheck); |
| } else { |
| typeAndLengthCheck = lengthCheck; |
| } |
| |
| ObjectAccessTarget intSubtraction = inferenceVisitor.findInterfaceMember( |
| inferenceVisitor.coreTypes.intNonNullableRawType, minusName, fileOffset, |
| callSiteAccessKind: CallSiteAccessKind.operatorInvocation); |
| |
| ObjectAccessTarget elementAccess = inferenceVisitor.findInterfaceMember( |
| targetListType, indexGetName, fileOffset, |
| callSiteAccessKind: CallSiteAccessKind.operatorInvocation); |
| FunctionType elementAccessFunctionType = |
| elementAccess.getFunctionType(inferenceVisitor); |
| PatternTransformationResult transformationResult = |
| new PatternTransformationResult([]); |
| List<VariableDeclaration> elementAccessVariables = []; |
| bool hasSeenRestPattern = false; |
| for (int i = 0; i < patterns.length; i++) { |
| if (patterns[i] is RestPattern) { |
| hasSeenRestPattern = true; |
| continue; |
| } |
| |
| Expression elementIndex; |
| if (!hasSeenRestPattern) { |
| // elementIndex: `i` |
| elementIndex = |
| inferenceVisitor.engine.forest.createIntLiteral(fileOffset, i); |
| } else { |
| // elementIndex: `listVariable`.length - `patterns.length - i` |
| // ==> LVAR.length - `patterns.length - i` |
| |
| // lengthGet: `listVariable`.length ==> LVAR.length |
| Expression lengthGet = new InstanceGet( |
| InstanceAccessKind.Instance, |
| inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, listVariable) |
| ..promotedType = |
| typeCheckForTargetListNeeded ? targetListType : null, |
| lengthName, |
| resultType: lengthTarget.getGetterType(inferenceVisitor), |
| interfaceTarget: lengthTarget.member as Procedure) |
| ..fileOffset = fileOffset; |
| |
| // elementIndex: `lengthGet` - `patterns.length - i` |
| // ==> LVAR.length - `patterns.length - i` |
| elementIndex = new InstanceInvocation( |
| InstanceAccessKind.Instance, |
| lengthGet, |
| minusName, |
| inferenceVisitor.engine.forest.createArguments(fileOffset, [ |
| inferenceVisitor.engine.forest |
| .createIntLiteral(fileOffset, patterns.length - i) |
| ]), |
| interfaceTarget: intSubtraction.member as Procedure, |
| functionType: intSubtraction.getFunctionType(inferenceVisitor)); |
| } |
| |
| // listElement: `listVariable`[`elementIndex`] |
| // ==> LVAR[`elementIndex`] |
| Expression listElement = new InstanceInvocation( |
| InstanceAccessKind.Instance, |
| inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, listVariable) |
| ..promotedType = targetListType, |
| indexGetName, |
| inferenceVisitor.engine.forest |
| .createArguments(fileOffset, [elementIndex]), |
| functionType: elementAccessFunctionType, |
| interfaceTarget: elementAccess.member as Procedure); |
| |
| // listElementVariable: `typeArgument` EVAR = `listElement` |
| // ==> `typeArgument` EVAR = LVAR[`i`] |
| VariableDeclaration listElementVariable = inferenceVisitor.engine.forest |
| .createVariableDeclarationForValue(listElement, type: typeArgument); |
| |
| PatternTransformationResult subpatternTransformationResult = patterns[i] |
| .transform(inferenceVisitor, |
| matchedExpression: inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, listElementVariable), |
| matchedType: typeArgument, |
| variableInitializingContext: inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, listElementVariable)); |
| |
| // If the sub-pattern transformation doesn't declare captured variables |
| // and consists of a single empty element, it means that it simply |
| // doesn't have a place where it could refer to the element expression. |
| // In that case we can avoid creating the intermediary variable for the |
| // element expression. |
| // |
| // An example of such sub-pattern is in the following: |
| // |
| // if (x case [var _]) { /* ... */ } |
| if (patterns[i].declaredVariables.isNotEmpty || |
| !(subpatternTransformationResult.elements.length == 1 && |
| subpatternTransformationResult.elements.single.isEmpty)) { |
| elementAccessVariables.add(listElementVariable); |
| transformationResult = transformationResult.combine( |
| subpatternTransformationResult, inferenceVisitor); |
| } |
| } |
| |
| transformationResult = transformationResult.prependElement( |
| new PatternTransformationElement( |
| kind: PatternTransformationElementKind.regular, |
| condition: typeAndLengthCheck, |
| variableInitializers: elementAccessVariables), |
| inferenceVisitor); |
| |
| transformationResult = transformationResult.prependElement( |
| new PatternTransformationElement( |
| kind: PatternTransformationElementKind.regular, |
| condition: null, |
| variableInitializers: [listVariable]), |
| inferenceVisitor); |
| |
| return transformationResult; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| if (typeArgument != null) { |
| printer.write('<'); |
| printer.writeType(typeArgument!); |
| printer.write('>'); |
| } |
| printer.write('['); |
| String comma = ''; |
| for (Pattern pattern in patterns) { |
| printer.write(comma); |
| pattern.toTextInternal(printer); |
| comma = ', '; |
| } |
| printer.write(']'); |
| } |
| |
| @override |
| String toString() { |
| return "ListPattern(${toStringInternal()})"; |
| } |
| } |
| |
| class ObjectPattern extends Pattern { |
| final Reference classReference; |
| final List<NamedPattern> fields; |
| final List<DartType>? typeArguments; |
| |
| Class get classNode => classReference.asClass; |
| |
| ObjectPattern( |
| this.classReference, this.fields, this.typeArguments, int fileOffset) |
| : super(fileOffset) { |
| setParents(fields, this); |
| } |
| |
| @override |
| void acceptInference(InferenceVisitorImpl visitor, |
| {required SharedMatchContext context}) { |
| visitor.visitObjectPattern(this, context: context); |
| } |
| |
| @override |
| List<VariableDeclaration> get declaredVariables { |
| return [for (NamedPattern field in fields) ...field.declaredVariables]; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write('${classNode.name}'); |
| if (typeArguments != null) { |
| printer.write('<'); |
| printer.writeTypes(typeArguments!); |
| printer.write('>'); |
| } |
| printer.write('('); |
| String comma = ''; |
| for (Pattern field in fields) { |
| printer.write(comma); |
| field.toTextInternal(printer); |
| comma = ', '; |
| } |
| printer.write(')'); |
| } |
| |
| @override |
| String toString() { |
| return "ObjectPattern(${toStringInternal()})"; |
| } |
| |
| @override |
| PatternTransformationResult transform(InferenceVisitorBase inferenceVisitor, |
| {required Expression matchedExpression, |
| required DartType matchedType, |
| required Expression variableInitializingContext}) { |
| // targetObjectType: `classNode`<`typeArguments`> |
| DartType targetObjectType; |
| if (typeArguments != null && |
| typeArguments!.length == classNode.typeParameters.length) { |
| targetObjectType = |
| new InterfaceType(classNode, Nullability.nonNullable, typeArguments); |
| } else { |
| if (typeArguments != null) { |
| // TODO(cstefantsova): Report an error. |
| } |
| targetObjectType = new InterfaceType( |
| classNode, |
| Nullability.nonNullable, |
| calculateBounds( |
| classNode.typeParameters, |
| inferenceVisitor.coreTypes.objectClass, |
| inferenceVisitor.libraryBuilder.library)); |
| } |
| |
| bool typeCheckForTargetNeeded = |
| !inferenceVisitor.isAssignable(targetObjectType, matchedType) || |
| matchedType is DynamicType; |
| |
| // objectVariable: `matchedType` OVAR = `matchedExpression` |
| VariableDeclaration objectVariable = inferenceVisitor.engine.forest |
| .createVariableDeclarationForValue(matchedExpression, |
| type: matchedType); |
| |
| // typeCheck: `objectVariable` is `targetObjectType` |
| // ==> OVAR is `classNode`<`typeArguments`> |
| Expression? typeCheck; |
| if (typeCheckForTargetNeeded) { |
| typeCheck = inferenceVisitor.engine.forest.createIsExpression( |
| fileOffset, |
| inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, objectVariable), |
| targetObjectType, |
| forNonNullableByDefault: false); |
| } |
| |
| List<VariableDeclaration> elementAccessVariables = []; |
| PatternTransformationResult transformationResult = |
| new PatternTransformationResult([]); |
| for (NamedPattern field in fields) { |
| String? fieldNameString; |
| if (field.name.isNotEmpty) { |
| fieldNameString = field.name; |
| } else { |
| // The name is defined by the nested variable pattern. |
| Pattern nestedPattern = field.pattern; |
| if (nestedPattern is VariablePattern) { |
| fieldNameString = nestedPattern.name; |
| } |
| } |
| |
| Expression objectElement; |
| DartType fieldType; |
| if (fieldNameString != null) { |
| Name fieldName = new Name(fieldNameString); |
| |
| ObjectAccessTarget fieldAccessTarget = inferenceVisitor |
| .findInterfaceMember(targetObjectType, fieldName, fileOffset, |
| callSiteAccessKind: CallSiteAccessKind.getterInvocation); |
| |
| if (fieldAccessTarget.member != null) { |
| fieldType = fieldAccessTarget.getGetterType(inferenceVisitor); |
| |
| // objectElement: `objectVariable`.`fieldName` |
| // ==> OVAR.`fieldName` |
| objectElement = new InstanceGet( |
| InstanceAccessKind.Instance, |
| inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, objectVariable) |
| ..promotedType = targetObjectType, |
| fieldName, |
| resultType: fieldType, |
| interfaceTarget: fieldAccessTarget.member!) |
| ..fileOffset = fileOffset; |
| } else { |
| objectElement = inferenceVisitor.helper.buildProblem( |
| templateUndefinedGetter.withArguments(fieldNameString, |
| targetObjectType, inferenceVisitor.isNonNullableByDefault), |
| fileOffset, |
| noLength); |
| fieldType = const InvalidType(); |
| } |
| } else { |
| objectElement = inferenceVisitor.helper.buildProblem( |
| messageUnspecifiedGetterNameInObjectPattern, fileOffset, noLength); |
| fieldType = const InvalidType(); |
| } |
| |
| // objectElementVariable: `fieldType` EVAR = `objectElement` |
| // ==> `fieldType` EVAR = OVAR.`fieldName` |
| VariableDeclaration objectElementVariable = inferenceVisitor.engine.forest |
| .createVariableDeclarationForValue(objectElement, type: fieldType); |
| |
| PatternTransformationResult subpatternTransformationResult = field.pattern |
| .transform(inferenceVisitor, |
| matchedExpression: inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, objectElementVariable), |
| matchedType: fieldType, |
| variableInitializingContext: inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, objectElementVariable)); |
| |
| // If the sub-pattern transformation doesn't declare captured variables |
| // and consists of a single empty element, it means that it simply |
| // doesn't have a place where it could refer to the element expression. |
| // In that case we can avoid creating the intermediary variable for the |
| // element expression. |
| // |
| // An example of such sub-pattern is in the following: |
| // |
| // if (x case A(foo: var _) { /* ... */ } |
| if (field.declaredVariables.isNotEmpty || |
| !(subpatternTransformationResult.elements.length == 1 && |
| subpatternTransformationResult.elements.single.isEmpty)) { |
| elementAccessVariables.add(objectElementVariable); |
| transformationResult = transformationResult.combine( |
| subpatternTransformationResult, inferenceVisitor); |
| } |
| } |
| |
| transformationResult = transformationResult.prependElement( |
| new PatternTransformationElement( |
| kind: PatternTransformationElementKind.regular, |
| condition: null, |
| variableInitializers: elementAccessVariables), |
| inferenceVisitor); |
| |
| if (typeCheck != null) { |
| transformationResult = transformationResult.prependElement( |
| new PatternTransformationElement( |
| kind: PatternTransformationElementKind.regular, |
| condition: typeCheck, |
| variableInitializers: []), |
| inferenceVisitor); |
| } |
| |
| transformationResult = transformationResult.prependElement( |
| new PatternTransformationElement( |
| kind: PatternTransformationElementKind.regular, |
| condition: null, |
| variableInitializers: [objectVariable]), |
| inferenceVisitor); |
| |
| return transformationResult; |
| } |
| } |
| |
| enum RelationalPatternKind { |
| equals, |
| notEquals, |
| lessThan, |
| lessThanEqual, |
| greaterThan, |
| greaterThanEqual, |
| } |
| |
| /// A [Pattern] for `operator expression` where `operator is either ==, !=, |
| /// <, <=, >, or >=. |
| class RelationalPattern extends Pattern { |
| final RelationalPatternKind kind; |
| Expression expression; |
| |
| RelationalPattern(this.kind, this.expression, int fileOffset) |
| : super(fileOffset) { |
| expression.parent = this; |
| } |
| |
| @override |
| List<VariableDeclaration> get declaredVariables => const []; |
| |
| @override |
| void acceptInference( |
| InferenceVisitorImpl visitor, { |
| required SharedMatchContext context, |
| }) { |
| visitor.visitRelationalPattern(this, context: context); |
| } |
| |
| @override |
| PatternTransformationResult transform(InferenceVisitorBase inferenceVisitor, |
| {required Expression matchedExpression, |
| required DartType matchedType, |
| required Expression variableInitializingContext}) { |
| Expression? condition; |
| Name? name; |
| switch (kind) { |
| case RelationalPatternKind.equals: |
| case RelationalPatternKind.notEquals: |
| if (expression is NullLiteral || expression is NullConstant) { |
| condition = inferenceVisitor.engine.forest |
| .createEqualsNull(fileOffset, matchedExpression); |
| } else { |
| ObjectAccessTarget target = inferenceVisitor.findInterfaceMember( |
| matchedType, equalsName, fileOffset, |
| callSiteAccessKind: CallSiteAccessKind.operatorInvocation); |
| condition = inferenceVisitor.engine.forest.createEqualsCall( |
| fileOffset, |
| matchedExpression, |
| expression, |
| target.getFunctionType(inferenceVisitor), |
| target.member as Procedure); |
| } |
| if (kind == RelationalPatternKind.notEquals) { |
| condition = |
| inferenceVisitor.engine.forest.createNot(fileOffset, condition); |
| } |
| break; |
| case RelationalPatternKind.lessThan: |
| name = lessThanName; |
| break; |
| case RelationalPatternKind.lessThanEqual: |
| name = lessThanOrEqualsName; |
| break; |
| case RelationalPatternKind.greaterThan: |
| name = greaterThanName; |
| break; |
| case RelationalPatternKind.greaterThanEqual: |
| name = greaterThanOrEqualsName; |
| break; |
| } |
| if (condition == null) { |
| ObjectAccessTarget target = inferenceVisitor.findInterfaceMember( |
| matchedType, name!, fileOffset, |
| callSiteAccessKind: CallSiteAccessKind.operatorInvocation); |
| if (target.kind == ObjectAccessTargetKind.dynamic) { |
| condition = new DynamicInvocation( |
| DynamicAccessKind.Dynamic, |
| matchedExpression, |
| name, |
| inferenceVisitor.engine.forest |
| .createArguments(fileOffset, [expression])); |
| } else if (target.member is! Procedure) { |
| inferenceVisitor.helper.addProblem( |
| templateUndefinedOperator.withArguments(name.text, matchedType, |
| inferenceVisitor.isNonNullableByDefault), |
| fileOffset, |
| noLength); |
| condition = null; |
| } else { |
| condition = new InstanceInvocation( |
| InstanceAccessKind.Instance, |
| matchedExpression, |
| name, |
| inferenceVisitor.engine.forest |
| .createArguments(fileOffset, [expression]), |
| functionType: target.getFunctionType(inferenceVisitor), |
| interfaceTarget: target.member as Procedure) |
| ..fileOffset = fileOffset; |
| } |
| } |
| return new PatternTransformationResult([ |
| new PatternTransformationElement( |
| kind: PatternTransformationElementKind.regular, |
| condition: condition, |
| variableInitializers: []) |
| ]); |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| switch (kind) { |
| case RelationalPatternKind.equals: |
| printer.write('== '); |
| break; |
| case RelationalPatternKind.notEquals: |
| printer.write('!= '); |
| break; |
| case RelationalPatternKind.lessThan: |
| printer.write('< '); |
| break; |
| case RelationalPatternKind.lessThanEqual: |
| printer.write('<= '); |
| break; |
| case RelationalPatternKind.greaterThan: |
| printer.write('> '); |
| break; |
| case RelationalPatternKind.greaterThanEqual: |
| printer.write('>= '); |
| break; |
| } |
| printer.writeExpression(expression); |
| } |
| |
| @override |
| String toString() { |
| return "RelationalPattern(${toStringInternal()})"; |
| } |
| } |
| |
| class WildcardPattern extends Pattern { |
| final DartType? type; |
| |
| WildcardPattern(this.type, int fileOffset) : super(fileOffset); |
| |
| @override |
| List<VariableDeclaration> get declaredVariables => const []; |
| |
| @override |
| void acceptInference( |
| InferenceVisitorImpl visitor, { |
| required SharedMatchContext context, |
| }) { |
| visitor.visitWildcardBinder(this, context: context); |
| } |
| |
| @override |
| PatternTransformationResult transform(InferenceVisitorBase inferenceVisitor, |
| {required Expression matchedExpression, |
| required DartType matchedType, |
| required Expression variableInitializingContext}) { |
| Expression? condition; |
| if (type != null) { |
| condition = inferenceVisitor.engine.forest.createIsExpression( |
| fileOffset, matchedExpression, type!, |
| forNonNullableByDefault: false); |
| } else { |
| condition = null; |
| } |
| return new PatternTransformationResult([ |
| new PatternTransformationElement( |
| kind: PatternTransformationElementKind.regular, |
| condition: condition, |
| variableInitializers: []) |
| ]); |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| if (type != null) { |
| type!.toTextInternal(printer); |
| printer.write(" "); |
| } |
| printer.write("_"); |
| } |
| |
| @override |
| String toString() { |
| return "WildcardBinder(${toStringInternal()})"; |
| } |
| } |
| |
| class PatternVariableDeclaration extends InternalStatement { |
| final Pattern pattern; |
| final Expression initializer; |
| final bool isFinal; |
| |
| PatternVariableDeclaration(this.pattern, this.initializer, |
| {required int fileOffset, required this.isFinal}) { |
| super.fileOffset = fileOffset; |
| } |
| |
| @override |
| StatementInferenceResult acceptInference(InferenceVisitorImpl visitor) { |
| return visitor.visitPatternVariableDeclaration(this); |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| if (isFinal) { |
| printer.write('final '); |
| } else { |
| printer.write('var '); |
| } |
| pattern.toTextInternal(printer); |
| printer.write(" = "); |
| printer.writeExpression(initializer); |
| printer.write(';'); |
| } |
| |
| @override |
| String toString() { |
| return "PatternVariableDeclaration(${toStringInternal()})"; |
| } |
| } |
| |
| class PatternAssignment extends InternalExpression { |
| final Pattern pattern; |
| final Expression expression; |
| |
| PatternAssignment(this.pattern, this.expression, {required int fileOffset}) { |
| super.fileOffset = fileOffset; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| return visitor.visitPatternAssignment(this, typeContext); |
| } |
| |
| @override |
| String toString() { |
| return "PatternAssignment(${toStringInternal()})"; |
| } |
| } |
| |
| class AssignedVariablePattern extends Pattern { |
| final VariableDeclaration variable; |
| |
| AssignedVariablePattern(this.variable, {required int offset}) : super(offset); |
| |
| @override |
| void acceptInference(InferenceVisitorImpl visitor, |
| {required SharedMatchContext context}) { |
| visitor.visitAssignedVariablePattern(this, context: context); |
| } |
| |
| @override |
| List<VariableDeclaration> get declaredVariables => [variable]; |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write(variable.name!); |
| } |
| |
| @override |
| String toString() { |
| return "AssignedVariablePattern(${toStringInternal()})"; |
| } |
| |
| @override |
| PatternTransformationResult transform(InferenceVisitorBase inferenceVisitor, |
| {required Expression matchedExpression, |
| required DartType matchedType, |
| required Expression variableInitializingContext}) { |
| // condition: let _ = `variable` = `matchedExpression` in true |
| return new PatternTransformationResult([ |
| new PatternTransformationElement( |
| kind: PatternTransformationElementKind.regular, |
| condition: inferenceVisitor.engine.forest.createLet( |
| inferenceVisitor.engine.forest.createVariableDeclarationForValue( |
| inferenceVisitor.engine.forest.createVariableSet( |
| fileOffset, variable, matchedExpression)), |
| inferenceVisitor.engine.forest |
| .createBoolLiteral(fileOffset, true)) |
| ..fileOffset = fileOffset, |
| variableInitializers: []) |
| ]); |
| } |
| } |
| |
| final Pattern dummyPattern = new ExpressionPattern(dummyExpression); |
| |
| /// Internal statement for a if-case statements: |
| /// |
| /// if (expression case pattern) then |
| /// if (expression case pattern) then else otherwise |
| /// if (expression case pattern when guard) then |
| /// if (expression case pattern when guard) then else otherwise |
| /// |
| class IfCaseStatement extends InternalStatement { |
| Expression expression; |
| PatternGuard patternGuard; |
| Statement then; |
| Statement? otherwise; |
| |
| IfCaseStatement(this.expression, this.patternGuard, this.then, this.otherwise, |
| int fileOffset) { |
| this.fileOffset = fileOffset; |
| expression.parent = this; |
| patternGuard.parent = this; |
| then.parent = this; |
| otherwise?.parent = this; |
| } |
| |
| @override |
| StatementInferenceResult acceptInference(InferenceVisitorImpl visitor) { |
| return visitor.visitIfCaseStatement(this); |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write('if ('); |
| printer.writeExpression(expression); |
| printer.write(' case '); |
| patternGuard.toTextInternal(printer); |
| printer.write(') '); |
| printer.writeStatement(then); |
| if (otherwise != null) { |
| printer.write(' else '); |
| printer.writeStatement(otherwise!); |
| } |
| } |
| |
| @override |
| String toString() { |
| return "IfCaseStatement(${toStringInternal()})"; |
| } |
| } |
| |
| final MapPatternEntry dummyMapPatternEntry = |
| new MapPatternEntry(dummyPattern, dummyPattern, TreeNode.noOffset); |
| |
| class MapPatternEntry extends TreeNode with InternalTreeNode { |
| final Pattern key; |
| final Pattern value; |
| |
| @override |
| final int fileOffset; |
| |
| MapPatternEntry(this.key, this.value, this.fileOffset) { |
| key.parent = this; |
| value.parent = this; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| key.toTextInternal(printer); |
| printer.write(': '); |
| value.toTextInternal(printer); |
| } |
| |
| @override |
| String toString() { |
| return 'MapMatcherEntry(${toStringInternal()})'; |
| } |
| } |
| |
| class MapPattern extends Pattern { |
| DartType? keyType; |
| DartType? valueType; |
| final List<MapPatternEntry> entries; |
| |
| @override |
| List<VariableDeclaration> get declaredVariables => |
| [for (MapPatternEntry entry in entries) ...entry.value.declaredVariables]; |
| |
| MapPattern(this.keyType, this.valueType, this.entries, int fileOffset) |
| : assert((keyType == null) == (valueType == null)), |
| super(fileOffset); |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| if (keyType != null && valueType != null) { |
| printer.writeTypeArguments([keyType!, valueType!]); |
| } |
| printer.write('{'); |
| String comma = ''; |
| for (MapPatternEntry entry in entries) { |
| printer.write(comma); |
| entry.toTextInternal(printer); |
| comma = ', '; |
| } |
| printer.write('}'); |
| } |
| |
| @override |
| String toString() { |
| return 'MapPattern(${toStringInternal()})'; |
| } |
| |
| @override |
| void acceptInference( |
| InferenceVisitorImpl visitor, { |
| required SharedMatchContext context, |
| }) { |
| visitor.visitMapPattern(this, context: context); |
| } |
| |
| @override |
| PatternTransformationResult transform(InferenceVisitorBase inferenceVisitor, |
| {required Expression matchedExpression, |
| required DartType matchedType, |
| required Expression variableInitializingContext}) { |
| DartType keyType = this.keyType ?? const DynamicType(); |
| DartType valueType = this.valueType ?? const DynamicType(); |
| DartType targetMapType = new InterfaceType( |
| inferenceVisitor.coreTypes.mapClass, |
| Nullability.nonNullable, |
| [keyType, valueType]); |
| |
| ObjectAccessTarget containsKeyTarget = inferenceVisitor.findInterfaceMember( |
| targetMapType, containsKeyName, fileOffset, |
| callSiteAccessKind: CallSiteAccessKind.methodInvocation); |
| bool typeCheckForTargetMapNeeded = |
| !inferenceVisitor.isAssignable(targetMapType, matchedType) || |
| matchedType is DynamicType; |
| |
| // mapVariable: `matchedType` MVAR = `matchedExpression` |
| VariableDeclaration mapVariable = inferenceVisitor.engine.forest |
| .createVariableDeclarationForValue(matchedExpression, |
| type: matchedType); |
| |
| Expression? keysCheck; |
| for (int i = entries.length - 1; i >= 0; i--) { |
| MapPatternEntry entry = entries[i]; |
| ExpressionPattern keyPattern = entry.key as ExpressionPattern; |
| |
| // containsKeyCheck: `mapVariable`.containsKey(`keyPattern.expression`) |
| // ==> MVAR.containsKey(`keyPattern.expression`) |
| Expression containsKeyCheck = new InstanceInvocation( |
| InstanceAccessKind.Instance, |
| inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, mapVariable) |
| ..promotedType = typeCheckForTargetMapNeeded ? targetMapType : null, |
| containsKeyName, |
| inferenceVisitor.engine.forest |
| .createArguments(fileOffset, [keyPattern.expression]), |
| functionType: containsKeyTarget.getFunctionType(inferenceVisitor), |
| interfaceTarget: containsKeyTarget.member as Procedure) |
| ..fileOffset = fileOffset; |
| |
| if (keysCheck == null) { |
| // keyCheck: `containsKeyCheck` |
| keysCheck = containsKeyCheck; |
| } else { |
| // keyCheck: `containsKeyCheck` && `keyCheck` |
| keysCheck = inferenceVisitor.engine.forest.createLogicalExpression( |
| fileOffset, containsKeyCheck, doubleAmpersandName.text, keysCheck); |
| } |
| } |
| |
| Expression? typeCheck; |
| if (typeCheckForTargetMapNeeded) { |
| // typeCheck: `mapVariable` is `targetMapType` |
| // ==> MVAR is Map<`keyType`, `valueType`> |
| typeCheck = inferenceVisitor.engine.forest.createIsExpression( |
| fileOffset, |
| inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, mapVariable), |
| targetMapType, |
| forNonNullableByDefault: inferenceVisitor.isNonNullableByDefault); |
| } |
| |
| Expression? typeAndKeysCheck; |
| if (typeCheck != null && keysCheck != null) { |
| // typeAndKeysCheck: `typeCheck` && `keysCheck` |
| typeAndKeysCheck = inferenceVisitor.engine.forest.createLogicalExpression( |
| fileOffset, typeCheck, doubleAmpersandName.text, keysCheck); |
| } else if (typeCheck != null && keysCheck == null) { |
| typeAndKeysCheck = typeCheck; |
| } else if (typeCheck == null && keysCheck != null) { |
| typeAndKeysCheck = keysCheck; |
| } else { |
| typeAndKeysCheck = null; |
| } |
| |
| ObjectAccessTarget valueAccess = inferenceVisitor.findInterfaceMember( |
| targetMapType, indexGetName, fileOffset, |
| callSiteAccessKind: CallSiteAccessKind.operatorInvocation); |
| FunctionType valueAccessFunctionType = |
| valueAccess.getFunctionType(inferenceVisitor); |
| PatternTransformationResult transformationResult = |
| new PatternTransformationResult([]); |
| List<VariableDeclaration> valueAccessVariables = []; |
| CloneVisitorNotMembers cloner = new CloneVisitorNotMembers(); |
| for (MapPatternEntry entry in entries) { |
| ExpressionPattern keyPattern = entry.key as ExpressionPattern; |
| |
| // [keyPattern.expression] can be cloned without caching because it's a |
| // const expression according to the spec, and the constant |
| // canonicalization will eliminate the duplicated code. |
| // |
| // mapValue: `mapVariable`[`keyPattern.expression`] |
| // ==> MVAR[`keyPattern.expression`] |
| Expression mapValue = new InstanceInvocation( |
| InstanceAccessKind.Instance, |
| inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, mapVariable) |
| ..promotedType = typeCheckForTargetMapNeeded ? targetMapType : null, |
| indexGetName, |
| inferenceVisitor.engine.forest.createArguments( |
| fileOffset, [cloner.clone(keyPattern.expression)]), |
| functionType: valueAccessFunctionType, |
| interfaceTarget: valueAccess.member as Procedure); |
| |
| // mapValueVariable: `valueType` VVAR = `mapValue` |
| // ==> `valueType` VVAR = MVAR[`keyPattern.expression`] |
| VariableDeclaration mapValueVariable = inferenceVisitor.engine.forest |
| .createVariableDeclarationForValue(mapValue, type: valueType); |
| |
| PatternTransformationResult subpatternTransformationResult = entry.value |
| .transform(inferenceVisitor, |
| matchedExpression: inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, mapValueVariable), |
| matchedType: valueType, |
| variableInitializingContext: inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, mapValueVariable)); |
| |
| // If the sub-pattern transformation doesn't declare captured variables |
| // and consists of a single empty element, it means that it simply |
| // doesn't have a place where it could refer to the element expression. |
| // In that case we can avoid creating the intermediary variable for the |
| // element expression. |
| // |
| // An example of such sub-pattern is in the following: |
| // |
| // if (x case {"key": var _}) { /* ... */ } |
| if (entry.value.declaredVariables.isNotEmpty || |
| !(subpatternTransformationResult.elements.length == 1 && |
| subpatternTransformationResult.elements.single.isEmpty)) { |
| valueAccessVariables.add(mapValueVariable); |
| transformationResult = transformationResult.combine( |
| subpatternTransformationResult, inferenceVisitor); |
| } |
| } |
| |
| transformationResult = transformationResult.prependElement( |
| new PatternTransformationElement( |
| kind: PatternTransformationElementKind.regular, |
| condition: typeAndKeysCheck, |
| variableInitializers: valueAccessVariables), |
| inferenceVisitor); |
| |
| transformationResult = transformationResult.prependElement( |
| new PatternTransformationElement( |
| kind: PatternTransformationElementKind.regular, |
| condition: null, |
| variableInitializers: [mapVariable]), |
| inferenceVisitor); |
| |
| return transformationResult; |
| } |
| } |
| |
| class NamedPattern extends Pattern { |
| final String name; |
| Pattern pattern; |
| |
| @override |
| List<VariableDeclaration> get declaredVariables => pattern.declaredVariables; |
| |
| NamedPattern(this.name, this.pattern, int fileOffset) : super(fileOffset) { |
| pattern.parent = this; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write(name); |
| printer.write(': '); |
| pattern.toTextInternal(printer); |
| } |
| |
| @override |
| String toString() { |
| return 'NamedPattern(${toStringInternal()})'; |
| } |
| |
| @override |
| void acceptInference( |
| InferenceVisitorImpl visitor, { |
| required SharedMatchContext context, |
| }) { |
| visitor.visitNamedPattern(this, context: context); |
| } |
| |
| @override |
| PatternTransformationResult transform(InferenceVisitorBase inferenceVisitor, |
| {required Expression matchedExpression, |
| required DartType matchedType, |
| required Expression variableInitializingContext}) { |
| return new PatternTransformationResult([ |
| new PatternTransformationElement( |
| kind: PatternTransformationElementKind.regular, |
| condition: |
| new InvalidExpression("Unimplemented NamedPattern.transform"), |
| variableInitializers: []) |
| ]); |
| } |
| } |
| |
| class RecordPattern extends Pattern { |
| final List<Pattern> patterns; |
| late final RecordType type; |
| |
| @override |
| List<VariableDeclaration> get declaredVariables => |
| [for (Pattern pattern in patterns) ...pattern.declaredVariables]; |
| |
| RecordPattern(this.patterns, int fileOffset) : super(fileOffset) { |
| setParents(patterns, this); |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write('('); |
| String comma = ''; |
| for (Pattern pattern in patterns) { |
| printer.write(comma); |
| pattern.toTextInternal(printer); |
| comma = ', '; |
| } |
| printer.write(')'); |
| } |
| |
| @override |
| String toString() { |
| return 'RecordPattern(${toStringInternal()})'; |
| } |
| |
| @override |
| void acceptInference( |
| InferenceVisitorImpl visitor, { |
| required SharedMatchContext context, |
| }) { |
| visitor.visitRecordPattern(this, context: context); |
| } |
| |
| @override |
| PatternTransformationResult transform(InferenceVisitorBase inferenceVisitor, |
| {required Expression matchedExpression, |
| required DartType matchedType, |
| required Expression variableInitializingContext}) { |
| bool typeCheckNeeded = !inferenceVisitor.isAssignable(type, matchedType) || |
| matchedType is DynamicType; |
| |
| // recordVariable: `matchedType` RVAR = `matchedExpression` |
| VariableDeclaration recordVariable = inferenceVisitor.engine.forest |
| .createVariableDeclarationForValue(matchedExpression, |
| type: matchedType); |
| |
| PatternTransformationResult transformationResult = |
| new PatternTransformationResult([]); |
| int recordFieldIndex = 0; |
| List<VariableDeclaration> fieldAccessVariables = []; |
| for (Pattern fieldPattern in patterns) { |
| Expression recordField; |
| DartType fieldType; |
| Pattern subpattern; |
| if (fieldPattern is NamedPattern) { |
| // recordField: `recordVariable`[`fieldPattern.name`] |
| // ==> RVAR[`fieldPattern.name`] |
| recordField = new RecordNameGet( |
| inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, recordVariable) |
| ..promotedType = typeCheckNeeded ? type : null, |
| type, |
| fieldPattern.name); |
| |
| // [type] is computed by the CFE, so the absence of the named field is |
| // an internal error, and we check the condition with an assert rather |
| // than reporting a compile-time error. |
| assert(type.named.any((named) => named.name == fieldPattern.name)); |
| fieldType = type.named |
| .firstWhere((named) => named.name == fieldPattern.name) |
| .type; |
| |
| subpattern = fieldPattern.pattern; |
| } else { |
| // recordField: `recordVariable`[`recordFieldIndex`] |
| // ==> RVAR[`recordFieldIndex`] |
| recordField = new RecordIndexGet( |
| inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, recordVariable) |
| ..promotedType = typeCheckNeeded ? type : null, |
| type, |
| recordFieldIndex); |
| |
| // [type] is computed by the CFE, so the field index out of range is an |
| // internal error, and we check the condition with an assert rather than |
| // reporting a compile-time error. |
| assert(recordFieldIndex < type.positional.length); |
| fieldType = type.positional[recordFieldIndex]; |
| |
| subpattern = fieldPattern; |
| recordFieldIndex++; |
| } |
| |
| // recordFieldIndex: `fieldType` FVAR = `recordField` |
| VariableDeclaration recordFieldVariable = inferenceVisitor.engine.forest |
| .createVariableDeclarationForValue(recordField, type: fieldType); |
| |
| PatternTransformationResult subpatternTransformationResult = subpattern |
| .transform(inferenceVisitor, |
| matchedExpression: inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, recordFieldVariable), |
| matchedType: fieldType, |
| variableInitializingContext: inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, recordFieldVariable)); |
| |
| // If the sub-pattern transformation doesn't declare captured variables |
| // and consists of a single empty element, it means that it simply |
| // doesn't have a place where it could refer to the element expression. |
| // In that case we can avoid creating the intermediary variable for the |
| // element expression. |
| // |
| // An example of such sub-pattern is in the following: |
| // |
| // if (x case (var _,)) { /* ... */ } |
| if (subpattern.declaredVariables.isNotEmpty || |
| !(subpatternTransformationResult.elements.length == 1 && |
| subpatternTransformationResult.elements.single.isEmpty)) { |
| fieldAccessVariables.add(recordFieldVariable); |
| transformationResult = transformationResult.combine( |
| subpatternTransformationResult, inferenceVisitor); |
| } |
| } |
| |
| // condition: [`recordVariable` is `type`]? |
| // ==> [RVAR is `type`]? |
| transformationResult = transformationResult.prependElement( |
| new PatternTransformationElement( |
| kind: PatternTransformationElementKind.regular, |
| condition: !typeCheckNeeded |
| ? null |
| : inferenceVisitor.engine.forest.createIsExpression( |
| fileOffset, |
| inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, recordVariable), |
| type, |
| forNonNullableByDefault: |
| inferenceVisitor.isNonNullableByDefault), |
| variableInitializers: fieldAccessVariables), |
| inferenceVisitor); |
| |
| transformationResult = transformationResult.prependElement( |
| new PatternTransformationElement( |
| kind: PatternTransformationElementKind.regular, |
| condition: null, |
| variableInitializers: [recordVariable]), |
| inferenceVisitor); |
| |
| return transformationResult; |
| } |
| } |
| |
| class VariablePattern extends Pattern { |
| final DartType? type; |
| String name; |
| VariableDeclaration variable; |
| |
| @override |
| List<VariableDeclaration> get declaredVariables => [variable]; |
| |
| VariablePattern(this.type, this.name, this.variable, int fileOffset) |
| : super(fileOffset); |
| |
| @override |
| void acceptInference( |
| InferenceVisitorImpl visitor, { |
| required SharedMatchContext context, |
| }) { |
| visitor.visitVariablePattern(this, context: context); |
| } |
| |
| @override |
| PatternTransformationResult transform(InferenceVisitorBase inferenceVisitor, |
| {required Expression matchedExpression, |
| required DartType matchedType, |
| required Expression variableInitializingContext}) { |
| PatternTransformationResult transformationResult; |
| |
| if (type != null) { |
| VariableDeclaration? matchedExpressionVariable; |
| matchedExpressionVariable = inferenceVisitor.engine.forest |
| .createVariableDeclarationForValue(matchedExpression, |
| type: matchedType); |
| Expression condition = inferenceVisitor.engine.forest.createIsExpression( |
| variable.fileOffset, |
| inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, matchedExpressionVariable), |
| type!, |
| forNonNullableByDefault: false); |
| transformationResult = new PatternTransformationResult([ |
| new PatternTransformationElement( |
| kind: PatternTransformationElementKind.regular, |
| condition: null, |
| variableInitializers: [matchedExpressionVariable]), |
| new PatternTransformationElement( |
| kind: PatternTransformationElementKind.regular, |
| condition: condition, |
| variableInitializers: []) |
| ]); |
| variable.initializer = inferenceVisitor.engine.forest |
| .createVariableGet(fileOffset, matchedExpressionVariable) |
| ..promotedType = type! |
| ..parent = variable; |
| } else { |
| transformationResult = new PatternTransformationResult([]); |
| variable.initializer = matchedExpression..parent = variable; |
| } |
| |
| return transformationResult; |
| } |
| |
| @override |
| R accept<R>(TreeVisitor<R> visitor) { |
| if (visitor is Printer || visitor is Precedence || visitor is Transformer) { |
| // Allow visitors needed for toString and replaceWith. |
| return visitor.defaultTreeNode(this); |
| } |
| return unsupported( |
| "${runtimeType}.accept on ${visitor.runtimeType}", -1, null); |
| } |
| |
| @override |
| R accept1<R, A>(TreeVisitor1<R, A> visitor, A arg) { |
| return unsupported( |
| "${runtimeType}.accept1 on ${visitor.runtimeType}", -1, null); |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| if (type != null) { |
| type!.toTextInternal(printer); |
| printer.write(" "); |
| } else { |
| printer.write("var "); |
| } |
| printer.write(name); |
| } |
| |
| @override |
| String toString() { |
| return "VariablePattern(${toStringInternal()})"; |
| } |
| } |
| |
| class RestPattern extends Pattern { |
| RestPattern(int fileOffset) : super(fileOffset); |
| |
| @override |
| void acceptInference(InferenceVisitorImpl visitor, |
| {required SharedMatchContext context}) { |
| visitor.visitRestPattern(this, context: context); |
| } |
| |
| @override |
| List<VariableDeclaration> get declaredVariables => const []; |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write('...'); |
| } |
| |
| @override |
| String toString() { |
| return "RestPattern(${toStringInternal()}"; |
| } |
| |
| @override |
| PatternTransformationResult transform(InferenceVisitorBase inferenceVisitor, |
| {required Expression matchedExpression, |
| required DartType matchedType, |
| required Expression variableInitializingContext}) { |
| return unsupported( |
| "RestPattern.transform", fileOffset, inferenceVisitor.helper.uri); |
| } |
| } |
| |
| enum PatternTransformationElementKind { |
| regular, |
| logicalOrPatternLeftBegin, |
| logicalOrPatternRightBegin, |
| logicalOrPatternEnd |
| } |
| |
| class PatternTransformationElement { |
| /// Part of a matching condition of a pattern |
| /// |
| /// The desugared condition for matching a pattern is broken into several |
| /// elements. This is needed in order to declare the intermediate variables |
| /// for storing values that should be computed only once. |
| final Expression? condition; |
| |
| /// Declaration and initialization of captured and intermediate variables |
| /// |
| /// These are the statements that needs to present in the desugared code in |
| /// order to properly initialize [declaredVariables] and any intermediate |
| /// variables. |
| final List<Statement> variableInitializers; |
| |
| final PatternTransformationResult? otherwise; |
| |
| final PatternTransformationElementKind kind; |
| |
| PatternTransformationElement( |
| {required this.condition, |
| required this.variableInitializers, |
| required this.kind, |
| this.otherwise}); |
| |
| bool get isEmpty => condition == null && variableInitializers.isEmpty; |
| } |
| |
| class PatternTransformationResult { |
| final List<PatternTransformationElement> elements; |
| |
| PatternTransformationResult(this.elements); |
| |
| /// Combines the results of two pattern transformations into a single result |
| /// |
| /// The typical use case of [combine] is to combine the results of |
| /// transforming two sub-patterns of the same pattern into a single result |
| /// that can later be combined with the results of transforming of other |
| /// sub-patterns as well. So the overall result of transforming a pattern is |
| /// a combination of the results of transforming of all of the sub-patterns. |
| /// |
| /// [combine] uses [prependElement] on [other] for the purpose of optimization |
| /// and simplification. |
| PatternTransformationResult combine(PatternTransformationResult other, |
| InferenceVisitorBase inferenceVisitor) { |
| if (elements.isEmpty) { |
| return other; |
| } else if (other.elements.isEmpty) { |
| return this; |
| } else { |
| // TODO(cstefantsova): Does it make sense to use [prependElement] on each |
| // element from [elements], last to the first, prepending them to the |
| // accumulated result? |
| return new PatternTransformationResult([ |
| ...elements.sublist(0, elements.length - 1), |
| ...other.prependElement(elements.last, inferenceVisitor).elements |
| ]); |
| } |
| } |
| |
| /// Adds [element] to the beginning of the transformation result |
| /// |
| /// The condition and the intermediate variables declared by the [element] are |
| /// assumed to affect the scope of the transformation result they are |
| /// prepended to. Some optimizations are performed to minimize the count of |
| /// the elements in the overall result. The optimizations include combining |
| /// conditions via logical `&&` operation and concatenating lists of variable |
| /// declarations. |
| PatternTransformationResult prependElement( |
| PatternTransformationElement element, |
| InferenceVisitorBase inferenceVisitor) { |
| if (elements.isEmpty) { |
| return new PatternTransformationResult([element]); |
| } |
| if (element.kind != PatternTransformationElementKind.regular || |
| elements.first.kind != PatternTransformationElementKind.regular) { |
| return new PatternTransformationResult([element, ...elements]); |
| } |
| PatternTransformationElement outermost = elements.first; |
| Expression? elementCondition = element.condition; |
| Expression? outermostCondition = outermost.condition; |
| if (outermostCondition == null) { |
| elements[0] = new PatternTransformationElement( |
| kind: PatternTransformationElementKind.regular, |
| condition: elementCondition, |
| variableInitializers: [ |
| ...element.variableInitializers, |
| ...outermost.variableInitializers |
| ]); |
| return this; |
| } else if (element.variableInitializers.isEmpty) { |
| if (elementCondition == null) { |
| // Trivial case: [element] has empty components. |
| return this; |
| } else { |
| elements[0] = new PatternTransformationElement( |
| kind: PatternTransformationElementKind.regular, |
| condition: inferenceVisitor.engine.forest.createLogicalExpression( |
| elementCondition.fileOffset, |
| elementCondition, |
| doubleAmpersandName.text, |
| outermostCondition), |
| variableInitializers: outermost.variableInitializers); |
| return this; |
| } |
| } else { |
| return new PatternTransformationResult([element, ...elements]); |
| } |
| } |
| } |
| |
| class ContinuationStackElement { |
| List<Statement> statements; |
| |
| ContinuationStackElement(this.statements); |
| } |