| // Copyright (c) 2020, 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 implements the AST of a Dart-like language suitable for testing |
| /// flow analysis. Callers may use the top level methods in this file to create |
| /// AST nodes and then feed them to [Harness.run] to run them through flow |
| /// analysis testing. |
| library; |
| |
| import 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis.dart' |
| show |
| CascadePropertyTarget, |
| ExpressionInfo, |
| ExpressionPropertyTarget, |
| FlowAnalysis, |
| PropertyTarget, |
| SuperPropertyTarget, |
| ThisPropertyTarget; |
| import 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis_operations.dart'; |
| import 'package:_fe_analyzer_shared/src/type_inference/assigned_variables.dart'; |
| import 'package:_fe_analyzer_shared/src/type_inference/nullability_suffix.dart'; |
| import 'package:_fe_analyzer_shared/src/type_inference/type_analysis_result.dart' |
| as shared; |
| import 'package:_fe_analyzer_shared/src/type_inference/type_analysis_result.dart'; |
| import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer.dart' |
| as shared; |
| import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer.dart' |
| hide MapPatternEntry, RecordPatternField; |
| import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer_operations.dart'; |
| import 'package:_fe_analyzer_shared/src/type_inference/variable_bindings.dart'; |
| import 'package:_fe_analyzer_shared/src/types/shared_type.dart'; |
| import 'package:test/test.dart'; |
| |
| import 'mini_ir.dart'; |
| import 'mini_types.dart'; |
| |
| final RegExp _locationRegExp = |
| RegExp('(file:)?[a-zA-Z0-9_./]+.dart:[0-9]+:[0-9]+'); |
| |
| SwitchHeadDefault get default_ => |
| SwitchHeadDefault._(location: computeLocation()); |
| |
| ConstExpression get nullLiteral => |
| new NullLiteral._(location: computeLocation()); |
| |
| Expression get this_ => new This._(location: computeLocation()); |
| |
| Statement assert_(ProtoExpression condition, [ProtoExpression? message]) { |
| var location = computeLocation(); |
| return new Assert._(condition.asExpression(location: location), |
| message?.asExpression(location: location), |
| location: location); |
| } |
| |
| Statement block(List<ProtoStatement> statements) => |
| new Block._(statements, location: computeLocation()); |
| |
| Expression booleanLiteral(bool value) => |
| BooleanLiteral._(value, location: computeLocation()); |
| |
| Statement break_([Label? target]) => |
| new Break(target, location: computeLocation()); |
| |
| /// Creates a pseudo-expression whose function is to verify that flow analysis |
| /// considers [variable]'s assigned state to be [expectedAssignedState]. |
| Expression checkAssigned(Var variable, bool expectedAssignedState) => |
| new CheckAssigned._(variable, expectedAssignedState, |
| location: computeLocation()); |
| |
| /// Creates a pseudo-expression whose function is to verify that flow analysis |
| /// considers [promotable] to be un-promoted. |
| Expression checkNotPromoted(Promotable promotable) => |
| new CheckPromoted._(promotable, null, location: computeLocation()); |
| |
| /// Creates a pseudo-expression whose function is to verify that flow analysis |
| /// considers [promotable]'s assigned state to be promoted to [expectedTypeStr]. |
| Expression checkPromoted(Promotable promotable, String? expectedTypeStr) => |
| new CheckPromoted._(promotable, expectedTypeStr, |
| location: computeLocation()); |
| |
| /// Creates a pseudo-expression whose function is to verify that flow analysis |
| /// considers the current location's reachability state to be |
| /// [expectedReachable]. |
| Expression checkReachable(bool expectedReachable) => |
| new CheckReachable(expectedReachable, location: computeLocation()); |
| |
| /// Creates a pseudo-expression whose function is to verify that flow analysis |
| /// considers [variable]'s unassigned state to be [expectedUnassignedState]. |
| Expression checkUnassigned(Var variable, bool expectedUnassignedState) => |
| new CheckUnassigned._(variable, expectedUnassignedState, |
| location: computeLocation()); |
| |
| /// Computes a "location" string using `StackTrace.current` to find the source |
| /// location of the caller's caller. |
| /// |
| /// Note: this is highly dependent on the behavior of VM stack traces. This |
| /// won't work in code compiled with dart2js for example. That's fine, though, |
| /// since we only run these tests under the VM. |
| String computeLocation() { |
| var callStack = StackTrace.current.toString().split('\n'); |
| assert(callStack[0].contains('mini_ast.dart')); |
| assert(callStack[1].contains('mini_ast.dart')); |
| |
| String stackLine; |
| if (callStack[3].contains('joinPatternVariables')) { |
| stackLine = callStack[3]; |
| } else { |
| stackLine = callStack[2]; |
| assert( |
| stackLine.contains('type_inference_test.dart') || |
| stackLine.contains('flow_analysis_test.dart'), |
| 'Unexpected file: $stackLine'); |
| } |
| |
| var match = _locationRegExp.firstMatch(stackLine); |
| if (match == null) { |
| throw AssertionError( |
| '_locationRegExp failed to match $stackLine in $callStack'); |
| } |
| return match.group(0)!; |
| } |
| |
| Statement continue_([Label? target]) => |
| new Continue._(target, location: computeLocation()); |
| |
| Statement declare(Var variable, |
| {bool isLate = false, |
| bool isFinal = false, |
| String? type, |
| ProtoExpression? initializer, |
| String? expectInferredType}) { |
| var location = computeLocation(); |
| return new Declare._( |
| new VariablePattern._( |
| type == null ? null : Type(type), variable, expectInferredType, |
| location: location), |
| initializer?.asExpression(location: location), |
| isLate: isLate, |
| isFinal: isFinal, |
| location: location); |
| } |
| |
| Statement do_(List<ProtoStatement> body, ProtoExpression condition) { |
| var location = computeLocation(); |
| return Do._(Block._(body, location: location), |
| condition.asExpression(location: location), |
| location: location); |
| } |
| |
| /// Creates a pseudo-expression having type [typeStr] that otherwise has no |
| /// effect on flow analysis. |
| ConstExpression expr(String typeStr) => |
| new PlaceholderExpression._(new Type(typeStr), location: computeLocation()); |
| |
| /// Creates a conventional `for` statement. Optional boolean [forCollection] |
| /// indicates that this `for` statement is actually a collection element, so |
| /// `null` should be passed to [FlowAnalysis.for_bodyBegin]. |
| Statement for_(ProtoStatement? initializer, ProtoExpression? condition, |
| ProtoExpression? updater, List<ProtoStatement> body, |
| {bool forCollection = false}) { |
| var location = computeLocation(); |
| return new For._( |
| initializer?.asStatement(location: location), |
| condition?.asExpression(location: location), |
| updater?.asExpression(location: location), |
| Block._(body, location: location), |
| forCollection, |
| location: location); |
| } |
| |
| /// Creates a "for each" statement where the identifier being assigned to by the |
| /// iteration is not a local variable. |
| /// |
| /// This models code like: |
| /// var x; // Top level variable |
| /// f(Iterable iterable) { |
| /// for (x in iterable) { ... } |
| /// } |
| Statement forEachWithNonVariable( |
| ProtoExpression iterable, List<ProtoStatement> body) { |
| var location = computeLocation(); |
| return new ForEach._(null, iterable.asExpression(location: location), |
| Block._(body, location: location), false, |
| location: location); |
| } |
| |
| /// Creates a "for each" statement where the identifier being assigned to by the |
| /// iteration is a variable that is being declared by the "for each" statement. |
| /// |
| /// This models code like: |
| /// f(Iterable iterable) { |
| /// for (var x in iterable) { ... } |
| /// } |
| Statement forEachWithVariableDecl( |
| Var variable, ProtoExpression iterable, List<ProtoStatement> body) { |
| var location = computeLocation(); |
| return new ForEach._( |
| variable, iterable.asExpression(location: location), block(body), true, |
| location: location); |
| } |
| |
| /// Creates a "for each" statement where the identifier being assigned to by the |
| /// iteration is a local variable that is declared elsewhere in the function. |
| /// |
| /// This models code like: |
| /// f(Iterable iterable) { |
| /// var x; |
| /// for (x in iterable) { ... } |
| /// } |
| Statement forEachWithVariableSet( |
| Var variable, ProtoExpression iterable, List<ProtoStatement> body) { |
| var location = computeLocation(); |
| return new ForEach._(variable, iterable.asExpression(location: location), |
| Block._(body, location: location), false, |
| location: location); |
| } |
| |
| Statement if_(ProtoExpression condition, List<ProtoStatement> ifTrue, |
| [List<ProtoStatement>? ifFalse]) { |
| var location = computeLocation(); |
| return new If._( |
| condition.asExpression(location: location), |
| Block._(ifTrue, location: location), |
| ifFalse == null ? null : Block._(ifFalse, location: location), |
| location: location); |
| } |
| |
| Statement ifCase(ProtoExpression expression, PossiblyGuardedPattern pattern, |
| List<ProtoStatement> ifTrue, |
| [List<ProtoStatement>? ifFalse]) { |
| var location = computeLocation(); |
| var guardedPattern = pattern._asGuardedPattern; |
| return IfCase( |
| expression.asExpression(location: location), |
| guardedPattern.pattern, |
| guardedPattern.guard, |
| Block._(ifTrue, location: location), |
| ifFalse != null ? Block._(ifFalse, location: location) : null, |
| location: location, |
| ); |
| } |
| |
| CollectionElement ifCaseElement( |
| ProtoExpression expression, |
| PossiblyGuardedPattern pattern, |
| ProtoCollectionElement ifTrue, [ |
| ProtoCollectionElement? ifFalse, |
| ]) { |
| var location = computeLocation(); |
| var guardedPattern = pattern._asGuardedPattern; |
| return new IfCaseElement( |
| expression.asExpression(location: location), |
| guardedPattern.pattern, |
| guardedPattern.guard, |
| ifTrue.asCollectionElement(location: location), |
| ifFalse?.asCollectionElement(location: location), |
| location: location, |
| ); |
| } |
| |
| CollectionElement ifElement( |
| ProtoExpression condition, ProtoCollectionElement ifTrue, |
| [ProtoCollectionElement? ifFalse]) { |
| var location = computeLocation(); |
| return new IfElement._( |
| condition.asExpression(location: location), |
| ifTrue.asCollectionElement(location: location), |
| ifFalse?.asCollectionElement(location: location), |
| location: location); |
| } |
| |
| ConstExpression intLiteral(int value, {bool? expectConversionToDouble}) => |
| new IntLiteral(value, |
| expectConversionToDouble: expectConversionToDouble, |
| location: computeLocation()); |
| |
| /// Creates a list literal containing the given [elements]. |
| /// |
| /// [elementType] is the explicit type argument of the list literal. |
| /// TODO(paulberry): support list literals with an inferred type argument. |
| Expression listLiteral(List<ProtoCollectionElement> elements, |
| {required String elementType}) { |
| var location = computeLocation(); |
| return ListLiteral._([ |
| for (var element in elements) |
| element.asCollectionElement(location: location) |
| ], Type(elementType), location: location); |
| } |
| |
| Pattern listPattern(List<ListPatternElement> elements, {String? elementType}) => |
| ListPattern._(elementType == null ? null : Type(elementType), elements, |
| location: computeLocation()); |
| |
| Expression localFunction(List<ProtoStatement> body) { |
| var location = computeLocation(); |
| return LocalFunction._(Block._(body, location: location), location: location); |
| } |
| |
| /// Creates a map entry containing the given [key] and [value] subexpressions. |
| CollectionElement mapEntry(ProtoExpression key, ProtoExpression value) { |
| var location = computeLocation(); |
| return MapEntry._(key.asExpression(location: location), |
| value.asExpression(location: location), |
| location: location); |
| } |
| |
| /// Creates a map literal containing the given [elements]. |
| /// |
| /// [keyType] and [valueType] are the explicit type arguments of the map |
| /// literal. TODO(paulberry): support map literals with inferred type arguments. |
| Expression mapLiteral(List<ProtoCollectionElement> elements, |
| {required String keyType, required String valueType}) { |
| var location = computeLocation(); |
| return MapLiteral._([ |
| for (var element in elements) |
| element.asCollectionElement(location: location) |
| ], Type(keyType), Type(valueType), location: location); |
| } |
| |
| Pattern mapPattern(List<MapPatternElement> elements, |
| {String? keyType, String? valueType}) { |
| var location = computeLocation(); |
| return MapPattern._( |
| keyType == null && valueType == null |
| ? null |
| : (keyType: Type(keyType!), valueType: Type(valueType!)), |
| elements, |
| location: location); |
| } |
| |
| MapPatternElement mapPatternEntry(ProtoExpression key, Pattern value) { |
| var location = computeLocation(); |
| return MapPatternEntry._(key.asExpression(location: location), value, |
| location: location); |
| } |
| |
| Pattern mapPatternWithTypeArguments({ |
| required String keyType, |
| required String valueType, |
| required List<MapPatternElement> elements, |
| }) { |
| var location = computeLocation(); |
| return MapPattern._( |
| ( |
| keyType: Type(keyType), |
| valueType: Type(valueType), |
| ), |
| elements, |
| location: location, |
| ); |
| } |
| |
| Statement match(Pattern pattern, ProtoExpression initializer, |
| {bool isLate = false, bool isFinal = false}) { |
| var location = computeLocation(); |
| return new Declare._(pattern, initializer.asExpression(location: location), |
| isLate: isLate, isFinal: isFinal, location: location); |
| } |
| |
| Pattern objectPattern({ |
| required String requiredType, |
| required List<RecordPatternField> fields, |
| }) { |
| var parsedType = Type(requiredType); |
| if (parsedType is! PrimaryType || |
| parsedType.nullabilitySuffix != NullabilitySuffix.none) { |
| fail('Expected a primary type, got $parsedType'); |
| } |
| return ObjectPattern._( |
| requiredType: parsedType, |
| fields: fields, |
| location: computeLocation(), |
| ); |
| } |
| |
| /// Creates a "pattern-for-in" statement. |
| /// |
| /// This models code like: |
| /// void f(Iterable<(int, String)> iterable) { |
| /// for (var (a, b) in iterable) { ... } |
| /// } |
| Statement patternForIn( |
| Pattern pattern, |
| ProtoExpression expression, |
| List<ProtoStatement> body, { |
| bool hasAwait = false, |
| }) { |
| var location = computeLocation(); |
| return new PatternForIn(pattern, expression.asExpression(location: location), |
| Block._(body, location: location), |
| hasAwait: hasAwait, location: location); |
| } |
| |
| /// Creates a "pattern-for-in" element. |
| /// |
| /// This models code like: |
| /// void f(Iterable<(int, String)> iterable) { |
| /// [for (var (a, b) in iterable) '$a $b'] |
| /// } |
| CollectionElement patternForInElement( |
| Pattern pattern, |
| ProtoExpression expression, |
| ProtoCollectionElement body, { |
| bool hasAwait = false, |
| }) { |
| var location = computeLocation(); |
| return new PatternForInElement( |
| pattern, |
| expression.asExpression(location: location), |
| body.asCollectionElement(location: location), |
| hasAwait: hasAwait, |
| location: location); |
| } |
| |
| Pattern recordPattern(List<RecordPatternField> fields) => |
| RecordPattern._(fields, location: computeLocation()); |
| |
| Pattern relationalPattern(String operator, ProtoExpression operand, |
| {String? errorId}) { |
| var location = computeLocation(); |
| var result = RelationalPattern._( |
| operator, operand.asExpression(location: location), |
| location: location); |
| if (errorId != null) { |
| result.errorId = errorId; |
| } |
| return result; |
| } |
| |
| /// Creates a "rest" pattern with optional [subPattern], for use in a list |
| /// pattern. |
| /// |
| /// Although using a rest pattern inside a map pattern is an error, it's allowed |
| /// syntactically (since this leads to better error recovery). To facilitate |
| /// testing of the error recovery logic, the returned type ([RestPattern]) may |
| /// be used were a [MapPatternElement] is expected. |
| RestPattern restPattern([Pattern? subPattern]) => |
| RestPattern._(subPattern, location: computeLocation()); |
| |
| Statement return_() => new Return._(location: computeLocation()); |
| |
| /// Models a call to a generic Dart function that takes two arguments and |
| /// returns the second argument; in other words, a function defined this way: |
| /// |
| /// T second(dynamic x, T y) => y; |
| /// |
| /// This can be useful in situations where a test needs to verify certain |
| /// properties, or establish certain preconditions, before the analysis reaches |
| /// a certain subexpression. |
| Expression second(ProtoExpression first, ProtoExpression second) { |
| var location = computeLocation(); |
| return Second._(first.asExpression(location: location), |
| second.asExpression(location: location), |
| location: location); |
| } |
| |
| PromotableLValue superProperty(String name) => new ThisOrSuperProperty._(name, |
| location: computeLocation(), isSuperAccess: true); |
| |
| Statement switch_(ProtoExpression expression, List<SwitchStatementMember> cases, |
| {bool? isLegacyExhaustive, |
| bool? expectHasDefault, |
| bool? expectIsExhaustive, |
| bool? expectLastCaseTerminates, |
| bool? expectRequiresExhaustivenessValidation, |
| String? expectScrutineeType}) { |
| var location = computeLocation(); |
| return new SwitchStatement( |
| expression.asExpression(location: location), cases, isLegacyExhaustive, |
| location: location, |
| expectHasDefault: expectHasDefault, |
| expectIsExhaustive: expectIsExhaustive, |
| expectLastCaseTerminates: expectLastCaseTerminates, |
| expectRequiresExhaustivenessValidation: |
| expectRequiresExhaustivenessValidation, |
| expectScrutineeType: expectScrutineeType); |
| } |
| |
| Expression switchExpr(ProtoExpression expression, List<ExpressionCase> cases) { |
| var location = computeLocation(); |
| return new SwitchExpression._( |
| expression.asExpression(location: location), cases, |
| location: location); |
| } |
| |
| SwitchStatementMember switchStatementMember( |
| List<ProtoSwitchHead> cases, |
| List<ProtoStatement> body, { |
| bool hasLabels = false, |
| }) { |
| var location = computeLocation(); |
| return SwitchStatementMember._( |
| [for (var case_ in cases) case_.asSwitchHead], |
| Block._(body, location: location), |
| hasLabels: hasLabels, |
| location: computeLocation(), |
| ); |
| } |
| |
| PromotableLValue thisProperty(String name) => new ThisOrSuperProperty._(name, |
| location: computeLocation(), isSuperAccess: false); |
| |
| Expression throw_(ProtoExpression operand) { |
| var location = computeLocation(); |
| return new Throw._(operand.asExpression(location: location), |
| location: location); |
| } |
| |
| TryBuilder try_(List<ProtoStatement> body) { |
| var location = computeLocation(); |
| return new TryStatementImpl(Block._(body, location: location), [], null, |
| location: location); |
| } |
| |
| Statement while_(ProtoExpression condition, List<ProtoStatement> body) { |
| var location = computeLocation(); |
| return new While._(condition.asExpression(location: location), |
| Block._(body, location: location), |
| location: location); |
| } |
| |
| Pattern wildcard({String? type, String? expectInferredType}) { |
| return WildcardPattern._( |
| declaredType: type == null ? null : Type(type), |
| expectInferredType: expectInferredType, |
| location: computeLocation(), |
| ); |
| } |
| |
| typedef SharedMatchContext |
| = shared.MatchContext<Node, Expression, Pattern, SharedTypeView<Type>, Var>; |
| |
| typedef SharedRecordPatternField = shared.RecordPatternField<Node, Pattern>; |
| |
| class As extends Expression { |
| final Expression target; |
| final Type type; |
| |
| As._(this.target, this.type, {required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| target.preVisit(visitor); |
| } |
| |
| @override |
| String toString() => '$target as $type'; |
| |
| @override |
| ExpressionTypeAnalysisResult<Type> visit( |
| Harness h, SharedTypeSchemaView<Type> schema) { |
| return h.typeAnalyzer.analyzeTypeCast(this, target, type); |
| } |
| } |
| |
| class Assert extends Statement { |
| final Expression condition; |
| final Expression? message; |
| |
| Assert._(this.condition, this.message, {required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| condition.preVisit(visitor); |
| message?.preVisit(visitor); |
| } |
| |
| @override |
| String toString() => |
| 'assert($condition${message == null ? '' : ', $message'});'; |
| |
| @override |
| void visit(Harness h) { |
| h.typeAnalyzer.analyzeAssertStatement(this, condition, message); |
| h.irBuilder.apply( |
| 'assert', [Kind.expression, Kind.expression], Kind.statement, |
| location: location); |
| } |
| } |
| |
| class Block extends Statement { |
| final List<Statement> statements; |
| |
| Block._(List<ProtoStatement> statements, {required super.location}) |
| : statements = [ |
| for (var s in statements) s.asStatement(location: location) |
| ]; |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| for (var statement in statements) { |
| statement.preVisit(visitor); |
| } |
| } |
| |
| @override |
| String toString() => |
| statements.isEmpty ? '{}' : '{ ${statements.join(' ')} }'; |
| |
| @override |
| void visit(Harness h) { |
| h.typeAnalyzer.analyzeBlock(statements); |
| h.irBuilder.apply( |
| 'block', List.filled(statements.length, Kind.statement), Kind.statement, |
| location: location); |
| } |
| } |
| |
| class BooleanLiteral extends Expression { |
| final bool value; |
| |
| BooleanLiteral._(this.value, {required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) {} |
| |
| @override |
| String toString() => '$value'; |
| |
| @override |
| ExpressionTypeAnalysisResult<Type> visit( |
| Harness h, SharedTypeSchemaView<Type> schema) { |
| var type = h.typeAnalyzer.analyzeBoolLiteral(this, value); |
| h.irBuilder.atom('$value', Kind.expression, location: location); |
| return new SimpleTypeAnalysisResult<Type>(type: SharedTypeView(type)); |
| } |
| } |
| |
| /// Normal implementation of [Label]. |
| class BoundLabel extends Label { |
| final String name; |
| |
| Statement? _binding; |
| |
| BoundLabel._(this.name) : super._(location: computeLocation()); |
| |
| @override |
| Statement thenStmt(Statement statement) { |
| if (statement is! LabeledStatement) { |
| statement = LabeledStatement._(statement, location: computeLocation()); |
| } |
| statement.labels.insert(0, this); |
| _binding = statement; |
| return statement; |
| } |
| |
| @override |
| String toString() => name; |
| |
| @override |
| Statement? _getBinding() { |
| var binding = _binding; |
| if (binding == null) { |
| fail("Unbound label $name"); |
| } |
| return binding; |
| } |
| } |
| |
| class Break extends Statement { |
| final Label? target; |
| |
| Break(this.target, {required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) {} |
| |
| @override |
| String toString() => 'break;'; |
| |
| @override |
| void visit(Harness h) { |
| var target = this.target; |
| h.typeAnalyzer.analyzeBreakStatement(target == null |
| ? h.typeAnalyzer._currentBreakTarget |
| : target._getBinding()); |
| h.irBuilder.apply('break', [], Kind.statement, location: location); |
| } |
| } |
| |
| /// Representation of a cascade expression in the pseudo-Dart language used for |
| /// flow analysis testing. |
| class Cascade extends Expression { |
| /// The expression appearing before the first `..` (or `?..`). |
| final Expression target; |
| |
| /// List of the cascade sections. Each cascade section is an ordinary |
| /// expression, built around a [Property] or [InvokeMethod] expression whose |
| /// target is a [CascadePlaceholder]. See [CascadePlaceholder] for more |
| /// information. |
| final List<Expression> sections; |
| |
| /// Indicates whether the cascade is null-aware (i.e. its first section is |
| /// preceded by `?..` instead of `..`). |
| final bool isNullAware; |
| |
| Cascade._(this.target, this.sections, |
| {required this.isNullAware, required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| target.preVisit(visitor); |
| for (var section in sections) { |
| section.preVisit(visitor); |
| } |
| } |
| |
| @override |
| String toString() { |
| return [target, if (isNullAware) '?', ...sections].join(''); |
| } |
| |
| @override |
| ExpressionTypeAnalysisResult<Type> visit( |
| Harness h, SharedTypeSchemaView<Type> schema) { |
| // Form the IR for evaluating the LHS |
| var targetType = |
| h.typeAnalyzer.dispatchExpression(target, schema).resolveShorting(); |
| var previousCascadeTargetIR = h.typeAnalyzer._currentCascadeTargetIR; |
| var previousCascadeType = h.typeAnalyzer._currentCascadeTargetType; |
| // Create a let-variable that will be initialized to the value of the LHS |
| var targetTmp = |
| h.typeAnalyzer._currentCascadeTargetIR = h.irBuilder.allocateTmp(); |
| h.typeAnalyzer._currentCascadeTargetType = h.flow |
| .cascadeExpression_afterTarget(target, targetType, |
| isNullAware: isNullAware) |
| .unwrapTypeView(); |
| if (isNullAware) { |
| h.flow.nullAwareAccess_rightBegin(target, targetType); |
| // Push `targetTmp == null` and `targetTmp` on the IR builder stack, |
| // because they'll be needed later to form the conditional expression that |
| // does the null-aware guarding. |
| h.irBuilder.readTmp(targetTmp, location: location); |
| h.irBuilder.atom('null', Kind.expression, location: location); |
| h.irBuilder.apply( |
| '==', [Kind.expression, Kind.expression], Kind.expression, |
| location: location); |
| h.irBuilder.readTmp(targetTmp, location: location); |
| } |
| // Form the IR for evaluating each section |
| List<MiniIRTmp> sectionTmps = []; |
| for (var section in sections) { |
| h.typeAnalyzer.dispatchExpression(section, h.operations.unknownType); |
| // Create a let-variable that will be initialized to the value of the |
| // section (which will be discarded) |
| sectionTmps.add(h.irBuilder.allocateTmp()); |
| } |
| // For the final IR, `let targetTmp = target in let section1Tmp = section1 |
| // in section2Tmp = section2 ... in targetTmp`, or, for null-aware cascades, |
| // `let targetTmp = target in targetTmp == null ? targetTmp : let |
| // section1Tmp = section1 in section2Tmp = section2 ... in targetTmp`. |
| h.irBuilder.readTmp(targetTmp, location: location); |
| for (int i = sectionTmps.length; i-- > 0;) { |
| h.irBuilder.let(sectionTmps[i], location: location); |
| } |
| if (isNullAware) { |
| h.irBuilder.apply('if', |
| [Kind.expression, Kind.expression, Kind.expression], Kind.expression, |
| location: location); |
| h.flow.nullAwareAccess_end(); |
| } |
| h.irBuilder.let(targetTmp, location: location); |
| h.flow.cascadeExpression_end(this); |
| h.typeAnalyzer._currentCascadeTargetIR = previousCascadeTargetIR; |
| h.typeAnalyzer._currentCascadeTargetType = previousCascadeType; |
| return SimpleTypeAnalysisResult(type: targetType); |
| } |
| } |
| |
| /// Representation of the implicit reference to a cascade target in a cascade |
| /// section, in the pseudo-Dart language used for flow analysis testing. |
| /// |
| /// For example, in the cascade expression `x..f()`, the cascade section `..f()` |
| /// is represented as an [InvokeMethod] expression whose `target` is a |
| /// [CascadePlaceholder]. |
| class CascadePlaceholder extends Expression { |
| CascadePlaceholder._({required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) {} |
| |
| @override |
| String toString() { |
| // We use an empty string as the string representation of a cascade |
| // placeholder. This ensures that in a cascade expression like `x..f()`, the |
| // cascade section will have the string representation `..f()`. |
| return '.'; |
| } |
| |
| @override |
| ExpressionTypeAnalysisResult<Type> visit( |
| Harness h, SharedTypeSchemaView<Type> schema) { |
| h.irBuilder |
| .readTmp(h.typeAnalyzer._currentCascadeTargetIR!, location: location); |
| return SimpleTypeAnalysisResult( |
| type: SharedTypeView(h.typeAnalyzer._currentCascadeTargetType!)); |
| } |
| } |
| |
| class CastPattern extends Pattern { |
| final Pattern inner; |
| |
| final Type type; |
| |
| CastPattern(this.inner, this.type, {required super.location}) : super._(); |
| |
| @override |
| SharedTypeSchemaView<Type> computeSchema(Harness h) => |
| h.typeAnalyzer.analyzeCastPatternSchema(); |
| |
| @override |
| void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder, |
| {required bool isInAssignment}) { |
| inner.preVisit(visitor, variableBinder, isInAssignment: isInAssignment); |
| } |
| |
| @override |
| PatternResult<Type> visit(Harness h, SharedMatchContext context) { |
| var analysisResult = h.typeAnalyzer.analyzeCastPattern( |
| context: context, |
| pattern: this, |
| innerPattern: inner, |
| requiredType: SharedTypeView(type), |
| ); |
| var matchedType = analysisResult.matchedValueType.unwrapTypeView(); |
| h.irBuilder.atom(type.type, Kind.type, location: location); |
| h.irBuilder.atom(matchedType.type, Kind.type, location: location); |
| h.irBuilder.apply( |
| 'castPattern', [Kind.pattern, Kind.type, Kind.type], Kind.pattern, |
| names: ['matchedType'], location: location); |
| return analysisResult; |
| } |
| |
| @override |
| String _debugString({required bool needsKeywordOrType}) => |
| '${inner._debugString(needsKeywordOrType: needsKeywordOrType)} as ' |
| '${type.type}'; |
| } |
| |
| /// Representation of a single catch clause in a try/catch statement. Use |
| /// [TryBuilder.catch_] to create instances of this class. |
| class CatchClause { |
| final Statement body; |
| final Var? exception; |
| final Var? stackTrace; |
| |
| CatchClause._(this.body, this.exception, this.stackTrace); |
| |
| @override |
| String toString() { |
| String initialPart; |
| if (stackTrace != null) { |
| initialPart = 'catch (${exception!.name}, ${stackTrace!.name})'; |
| } else if (exception != null) { |
| initialPart = 'catch (${exception!.name})'; |
| } else { |
| initialPart = 'on ...'; |
| } |
| return '$initialPart $body'; |
| } |
| |
| void _preVisit(PreVisitor visitor) { |
| body.preVisit(visitor); |
| } |
| } |
| |
| class CheckAssigned extends Expression { |
| final Var variable; |
| final bool expectedAssignedState; |
| |
| CheckAssigned._(this.variable, this.expectedAssignedState, |
| {required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) {} |
| |
| @override |
| String toString() { |
| var verb = expectedAssignedState ? 'is' : 'is not'; |
| return 'check $variable $verb definitely assigned;'; |
| } |
| |
| @override |
| ExpressionTypeAnalysisResult<Type> visit( |
| Harness h, SharedTypeSchemaView<Type> schema) { |
| expect(h.flow.isAssigned(variable), expectedAssignedState, |
| reason: 'at $location'); |
| h.irBuilder.atom('null', Kind.expression, location: location); |
| return SimpleTypeAnalysisResult( |
| type: SharedTypeView(h.typeAnalyzer.nullType)); |
| } |
| } |
| |
| class CheckCollectionElementIR extends CollectionElement { |
| final CollectionElement inner; |
| |
| final String expectedIR; |
| |
| CheckCollectionElementIR._(this.inner, this.expectedIR, |
| {required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| inner.preVisit(visitor); |
| } |
| |
| @override |
| String toString() => '$inner (should produce IR $expectedIR)'; |
| |
| @override |
| void visit(Harness h, CollectionElementContext context) { |
| h.typeAnalyzer.dispatchCollectionElement(inner, context); |
| h.irBuilder.check(expectedIR, Kind.collectionElement, location: location); |
| } |
| } |
| |
| class CheckExpressionIR extends Expression { |
| final Expression inner; |
| |
| final String expectedIR; |
| |
| CheckExpressionIR._(this.inner, this.expectedIR, {required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| inner.preVisit(visitor); |
| } |
| |
| @override |
| String toString() => '$inner (should produce IR $expectedIR)'; |
| |
| @override |
| ExpressionTypeAnalysisResult<Type> visit( |
| Harness h, SharedTypeSchemaView<Type> schema) { |
| var result = |
| h.typeAnalyzer.analyzeParenthesizedExpression(this, inner, schema); |
| h.irBuilder.check(expectedIR, Kind.expression, location: location); |
| return result; |
| } |
| } |
| |
| class CheckExpressionSchema extends Expression { |
| final Expression inner; |
| |
| final String expectedSchema; |
| |
| CheckExpressionSchema._(this.inner, this.expectedSchema, |
| {required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| inner.preVisit(visitor); |
| } |
| |
| @override |
| String toString() => '$inner (should be in schema $expectedSchema)'; |
| |
| @override |
| ExpressionTypeAnalysisResult<Type> visit( |
| Harness h, SharedTypeSchemaView<Type> schema) { |
| expect(schema.unwrapTypeSchemaView().type, expectedSchema); |
| var result = |
| h.typeAnalyzer.analyzeParenthesizedExpression(this, inner, schema); |
| return result; |
| } |
| } |
| |
| class CheckExpressionType extends Expression { |
| final Expression target; |
| final String expectedType; |
| |
| CheckExpressionType(this.target, this.expectedType, |
| {required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| target.preVisit(visitor); |
| } |
| |
| @override |
| String toString() => '$target (expected type: $expectedType)'; |
| |
| @override |
| ExpressionTypeAnalysisResult<Type> visit( |
| Harness h, SharedTypeSchemaView<Type> schema) { |
| var result = |
| h.typeAnalyzer.analyzeParenthesizedExpression(this, target, schema); |
| expect(result.type.unwrapTypeView().type, expectedType, |
| reason: 'at $location'); |
| return result; |
| } |
| } |
| |
| class CheckPromoted extends Expression { |
| final Promotable promotable; |
| final String? expectedTypeStr; |
| |
| CheckPromoted._(this.promotable, this.expectedTypeStr, |
| {required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| promotable.preVisit(visitor); |
| } |
| |
| @override |
| String toString() { |
| var predicate = expectedTypeStr == null |
| ? 'not promoted' |
| : 'promoted to $expectedTypeStr'; |
| return 'check $promotable $predicate;'; |
| } |
| |
| @override |
| ExpressionTypeAnalysisResult<Type> visit( |
| Harness h, SharedTypeSchemaView<Type> schema) { |
| var promotedType = promotable._getPromotedType(h); |
| expect(promotedType?.type, expectedTypeStr, reason: 'at $location'); |
| return SimpleTypeAnalysisResult(type: SharedTypeView(NullType.instance)); |
| } |
| } |
| |
| class CheckReachable extends Expression { |
| final bool expectedReachable; |
| |
| CheckReachable(this.expectedReachable, {required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) {} |
| |
| @override |
| String toString() => 'check reachable'; |
| |
| @override |
| ExpressionTypeAnalysisResult<Type> visit( |
| Harness h, SharedTypeSchemaView<Type> schema) { |
| expect(h.flow.isReachable, expectedReachable, reason: 'at $location'); |
| h.irBuilder.atom('null', Kind.expression, location: location); |
| return new SimpleTypeAnalysisResult( |
| type: SharedTypeView(NullType.instance)); |
| } |
| } |
| |
| class CheckStatementIR extends Statement { |
| final Statement inner; |
| |
| final String expectedIR; |
| |
| CheckStatementIR._(this.inner, this.expectedIR, {required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| inner.preVisit(visitor); |
| } |
| |
| @override |
| String toString() => '$inner (should produce IR $expectedIR)'; |
| |
| @override |
| void visit(Harness h) { |
| h.typeAnalyzer.dispatchStatement(inner); |
| h.irBuilder.check(expectedIR, Kind.statement, location: location); |
| } |
| } |
| |
| class CheckUnassigned extends Expression { |
| final Var variable; |
| final bool expectedUnassignedState; |
| |
| CheckUnassigned._(this.variable, this.expectedUnassignedState, |
| {required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) {} |
| |
| @override |
| String toString() { |
| var verb = expectedUnassignedState ? 'is' : 'is not'; |
| return 'check $variable $verb definitely unassigned;'; |
| } |
| |
| @override |
| ExpressionTypeAnalysisResult<Type> visit( |
| Harness h, SharedTypeSchemaView<Type> schema) { |
| expect(h.flow.isUnassigned(variable), expectedUnassignedState, |
| reason: 'at $location'); |
| h.irBuilder.atom('null', Kind.expression, location: location); |
| return SimpleTypeAnalysisResult( |
| type: SharedTypeView(h.typeAnalyzer.nullType)); |
| } |
| } |
| |
| /// Representation of a collection element in the pseudo-Dart language used for |
| /// type analysis testing. |
| abstract class CollectionElement extends Node |
| with ProtoCollectionElement<CollectionElement> { |
| CollectionElement({required super.location}) : super._(); |
| |
| @override |
| CollectionElement asCollectionElement({required String location}) => this; |
| |
| @override |
| CollectionElement checkIR(String expectedIR) { |
| var location = computeLocation(); |
| return CheckCollectionElementIR._( |
| asCollectionElement(location: location), expectedIR, |
| location: location); |
| } |
| |
| void preVisit(PreVisitor visitor); |
| |
| void visit(Harness h, CollectionElementContext context); |
| } |
| |
| abstract class CollectionElementContext {} |
| |
| class CollectionElementContextMapEntry extends CollectionElementContext { |
| final Type keyType; |
| final Type valueType; |
| |
| CollectionElementContextMapEntry._(this.keyType, this.valueType); |
| } |
| |
| class CollectionElementContextType extends CollectionElementContext { |
| final SharedTypeSchemaView<Type> elementTypeSchema; |
| |
| CollectionElementContextType._(this.elementTypeSchema); |
| } |
| |
| class Conditional extends Expression { |
| final Expression condition; |
| final Expression ifTrue; |
| final Expression ifFalse; |
| |
| Conditional._(this.condition, this.ifTrue, this.ifFalse, |
| {required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| condition.preVisit(visitor); |
| visitor._assignedVariables.beginNode(); |
| ifTrue.preVisit(visitor); |
| visitor._assignedVariables.endNode(this); |
| ifFalse.preVisit(visitor); |
| } |
| |
| @override |
| String toString() => '$condition ? $ifTrue : $ifFalse'; |
| |
| @override |
| ExpressionTypeAnalysisResult<Type> visit( |
| Harness h, SharedTypeSchemaView<Type> schema) { |
| var result = h.typeAnalyzer |
| .analyzeConditionalExpression(this, condition, ifTrue, ifFalse); |
| h.irBuilder.apply('if', [Kind.expression, Kind.expression, Kind.expression], |
| Kind.expression, |
| location: location); |
| return result; |
| } |
| } |
| |
| class ConstantPattern extends Pattern { |
| final Expression constant; |
| |
| ConstantPattern(this.constant, {required super.location}) : super._(); |
| |
| @override |
| SharedTypeSchemaView<Type> computeSchema(Harness h) => |
| h.typeAnalyzer.analyzeConstantPatternSchema(); |
| |
| @override |
| void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder, |
| {required bool isInAssignment}) { |
| constant.preVisit(visitor); |
| } |
| |
| @override |
| PatternResult<Type> visit(Harness h, SharedMatchContext context) { |
| var analysisResult = |
| h.typeAnalyzer.analyzeConstantPattern(context, this, constant); |
| var matchedType = analysisResult.matchedValueType.unwrapTypeView(); |
| h.irBuilder.atom(matchedType.type, Kind.type, location: location); |
| h.irBuilder.apply('const', [Kind.expression, Kind.type], Kind.pattern, |
| names: ['matchedType'], location: location); |
| return analysisResult; |
| } |
| |
| @override |
| _debugString({required bool needsKeywordOrType}) => constant.toString(); |
| } |
| |
| /// Common interface shared by constructs that represent constant expressions, |
| /// in the pseudo-Dart language used for flow analysis testing. |
| abstract class ConstExpression extends Expression { |
| ConstExpression._({required super.location}); |
| |
| /// Converts this expression into a constant pattern. |
| Pattern get pattern => ConstantPattern(this, location: computeLocation()); |
| } |
| |
| class Continue extends Statement { |
| final Label? target; |
| |
| Continue._(this.target, {required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) {} |
| |
| @override |
| String toString() => 'continue;'; |
| |
| @override |
| void visit(Harness h) { |
| var target = this.target; |
| h.typeAnalyzer.analyzeContinueStatement(target == null |
| ? h.typeAnalyzer._currentContinueTarget |
| : target._getBinding()); |
| h.irBuilder.apply('continue', [], Kind.statement, location: location); |
| } |
| } |
| |
| class Declare extends Statement { |
| final bool isLate; |
| final bool isFinal; |
| final Pattern pattern; |
| final Expression? initializer; |
| |
| Declare._(this.pattern, this.initializer, |
| {required this.isLate, required this.isFinal, required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| var variableBinder = _VariableBinder(visitor); |
| variableBinder.casePatternStart(); |
| pattern.preVisit(visitor, variableBinder, isInAssignment: false); |
| variableBinder.casePatternFinish(); |
| variableBinder.finish(); |
| if (isLate) { |
| visitor._assignedVariables.beginNode(); |
| } |
| initializer?.preVisit(visitor); |
| if (isLate) { |
| visitor._assignedVariables.endNode(this); |
| } |
| } |
| |
| @override |
| String toString() { |
| var parts = <String>[ |
| if (isLate) 'late', |
| if (isFinal) 'final', |
| pattern._debugString(needsKeywordOrType: !isFinal), |
| if (initializer != null) '= $initializer' |
| ]; |
| return '${parts.join(' ')};'; |
| } |
| |
| @override |
| void visit(Harness h) { |
| String irName; |
| List<Kind> argKinds; |
| List<String> names = const []; |
| var initializer = this.initializer; |
| if (isLate) { |
| // Late declarations are not allowed using patterns, so interpret the |
| // declaration as an old-fashioned variable declaration. |
| var pattern = this.pattern as VariablePattern; |
| var variable = pattern.variable; |
| h.irBuilder.atom(variable.name, Kind.variable, location: location); |
| var declaredType = pattern.declaredType; |
| Type staticType; |
| if (initializer == null) { |
| // Use the shared logic for analyzing uninitialized variable |
| // declarations. |
| staticType = h.typeAnalyzer |
| .analyzeUninitializedVariableDeclaration( |
| this, pattern.variable, declaredType?.wrapSharedTypeView(), |
| isFinal: isFinal) |
| .unwrapTypeView(); |
| irName = 'declare'; |
| argKinds = [Kind.variable]; |
| } else { |
| // There's no shared logic for analyzing initialized late variable |
| // declarations, so analyze the declaration directly. |
| h.flow.lateInitializer_begin(this); |
| var initializerType = h.typeAnalyzer |
| .analyzeExpression( |
| initializer, |
| declaredType?.wrapSharedTypeSchemaView() ?? |
| h.operations.unknownType) |
| .unwrapTypeView(); |
| h.flow.lateInitializer_end(); |
| staticType = variable.type = declaredType ?? initializerType; |
| h.flow.declare(variable, SharedTypeView(staticType), initialized: true); |
| h.flow.initialize( |
| variable, SharedTypeView(initializerType), initializer, |
| isFinal: isFinal, |
| isLate: true, |
| isImplicitlyTyped: declaredType == null); |
| h.irBuilder.atom(initializerType.type, Kind.type, location: location); |
| h.irBuilder.atom(staticType.type, Kind.type, location: location); |
| irName = 'declare'; |
| argKinds = [Kind.variable, Kind.expression, Kind.type, Kind.type]; |
| names = (['initializerType', 'staticType']); |
| } |
| // Finally, double check the inferred variable type, if necessary for the |
| // test. |
| var expectInferredType = pattern.expectInferredType; |
| if (expectInferredType != null) { |
| expect(staticType, expectInferredType); |
| } |
| } else if (initializer == null) { |
| var pattern = this.pattern as VariablePattern; |
| var declaredType = pattern.declaredType; |
| var staticType = h.typeAnalyzer |
| .analyzeUninitializedVariableDeclaration( |
| this, pattern.variable, declaredType?.wrapSharedTypeView(), |
| isFinal: isFinal) |
| .unwrapTypeView(); |
| h.typeAnalyzer.handleDeclaredVariablePattern(pattern, |
| matchedType: staticType, staticType: staticType); |
| irName = 'declare'; |
| argKinds = [Kind.pattern]; |
| } else { |
| h.typeAnalyzer.analyzePatternVariableDeclaration( |
| this, pattern, initializer, |
| isFinal: isFinal); |
| irName = 'match'; |
| argKinds = [Kind.expression, Kind.pattern]; |
| } |
| h.irBuilder.apply( |
| [irName, if (isLate) 'late', if (isFinal) 'final'].join('_'), |
| argKinds, |
| Kind.statement, |
| location: location, |
| names: names); |
| } |
| } |
| |
| class Do extends Statement { |
| final Statement body; |
| final Expression condition; |
| |
| Do._(this.body, this.condition, {required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| visitor._assignedVariables.beginNode(); |
| body.preVisit(visitor); |
| condition.preVisit(visitor); |
| visitor._assignedVariables.endNode(this); |
| } |
| |
| @override |
| String toString() => 'do $body while ($condition);'; |
| |
| @override |
| void visit(Harness h) { |
| h.typeAnalyzer.analyzeDoLoop(this, body, condition); |
| h.irBuilder.apply('do', [Kind.statement, Kind.expression], Kind.statement, |
| location: location); |
| } |
| } |
| |
| class Equal extends Expression { |
| final Expression lhs; |
| final Expression rhs; |
| final bool isInverted; |
| |
| Equal._(this.lhs, this.rhs, this.isInverted, {required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| lhs.preVisit(visitor); |
| rhs.preVisit(visitor); |
| } |
| |
| @override |
| String toString() => '$lhs ${isInverted ? '!=' : '=='} $rhs'; |
| |
| @override |
| ExpressionTypeAnalysisResult<Type> visit( |
| Harness h, SharedTypeSchemaView<Type> schema) { |
| var operatorName = isInverted ? '!=' : '=='; |
| var result = |
| h.typeAnalyzer.analyzeBinaryExpression(this, lhs, operatorName, rhs); |
| h.irBuilder.apply( |
| operatorName, [Kind.expression, Kind.expression], Kind.expression, |
| location: location); |
| return result; |
| } |
| } |
| |
| /// Representation of an expression in the pseudo-Dart language used for flow |
| /// analysis testing. Methods in this class may be used to create more complex |
| /// expressions based on this one. |
| abstract class Expression extends Node |
| with |
| ProtoStatement<Expression>, |
| ProtoCollectionElement<Expression>, |
| ProtoExpression { |
| Expression({required super.location}) : super._(); |
| |
| @override |
| Expression asExpression({required String location}) => this; |
| |
| void preVisit(PreVisitor visitor); |
| |
| ExpressionTypeAnalysisResult<Type> visit( |
| Harness h, SharedTypeSchemaView<Type> schema); |
| } |
| |
| /// Representation of a single case clause in a switch expression. Use |
| /// [PossiblyGuardedPattern.thenExpr] or [SwitchHead.thenExpr] to create |
| /// instances of this class. |
| class ExpressionCase extends Node { |
| final GuardedPattern? guardedPattern; |
| final Expression expression; |
| |
| ExpressionCase._(this.guardedPattern, this.expression, |
| {required super.location}) |
| : super._(); |
| |
| @override |
| String toString() => [ |
| guardedPattern == null ? 'default' : 'case $guardedPattern', |
| ': $expression' |
| ].join(''); |
| |
| void _preVisit(PreVisitor visitor) { |
| final guardedPattern = this.guardedPattern; |
| if (guardedPattern != null) { |
| var variableBinder = _VariableBinder(visitor); |
| variableBinder.casePatternStart(); |
| guardedPattern.pattern |
| .preVisit(visitor, variableBinder, isInAssignment: false); |
| guardedPattern.variables = variableBinder.casePatternFinish(); |
| variableBinder.finish(); |
| } |
| expression.preVisit(visitor); |
| } |
| } |
| |
| class ExpressionCollectionElement extends CollectionElement { |
| final Expression expression; |
| |
| ExpressionCollectionElement(this.expression, {required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| expression.preVisit(visitor); |
| } |
| |
| @override |
| String toString() => '$expression;'; |
| |
| @override |
| void visit(Harness h, CollectionElementContext context) { |
| SharedTypeSchemaView<Type> typeSchema = |
| context is CollectionElementContextType |
| ? context.elementTypeSchema |
| : h.operations.unknownType; |
| h.typeAnalyzer.dispatchExpression(expression, typeSchema); |
| h.irBuilder.apply('celt', [Kind.expression], Kind.collectionElement, |
| location: location); |
| } |
| } |
| |
| class ExpressionInTypeSchema extends Statement { |
| final Expression expr; |
| |
| final SharedTypeSchemaView<Type> typeSchema; |
| |
| ExpressionInTypeSchema._(this.expr, this.typeSchema, |
| {required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| expr.preVisit(visitor); |
| } |
| |
| @override |
| String toString() => '$expr (in type schema $typeSchema);'; |
| |
| @override |
| void visit(Harness h) { |
| h.typeAnalyzer.analyzeExpression(expr, typeSchema); |
| h.irBuilder |
| .apply('stmt', [Kind.expression], Kind.statement, location: location); |
| } |
| } |
| |
| class ExpressionStatement extends Statement { |
| final Expression expr; |
| |
| ExpressionStatement._(this.expr, {required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| expr.preVisit(visitor); |
| } |
| |
| @override |
| String toString() => '$expr;'; |
| |
| @override |
| void visit(Harness h) { |
| h.typeAnalyzer.analyzeExpressionStatement(expr); |
| h.irBuilder |
| .apply('stmt', [Kind.expression], Kind.statement, location: location); |
| } |
| } |
| |
| class For extends Statement { |
| final Statement? initializer; |
| final Expression? condition; |
| final Expression? updater; |
| final Statement body; |
| final bool forCollection; |
| |
| For._(this.initializer, this.condition, this.updater, this.body, |
| this.forCollection, |
| {required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| initializer?.preVisit(visitor); |
| visitor._assignedVariables.beginNode(); |
| condition?.preVisit(visitor); |
| body.preVisit(visitor); |
| updater?.preVisit(visitor); |
| visitor._assignedVariables.endNode(this); |
| } |
| |
| @override |
| String toString() { |
| var buffer = StringBuffer('for ('); |
| if (initializer == null) { |
| buffer.write(';'); |
| } else { |
| buffer.write(initializer); |
| } |
| if (condition == null) { |
| buffer.write(';'); |
| } else { |
| buffer.write(' $condition;'); |
| } |
| if (updater != null) { |
| buffer.write(' $updater'); |
| } |
| buffer.write(') $body'); |
| return buffer.toString(); |
| } |
| |
| @override |
| void visit(Harness h) { |
| if (initializer != null) { |
| h.typeAnalyzer.dispatchStatement(initializer!); |
| } else { |
| h.typeAnalyzer.handleNoInitializer(this); |
| } |
| h.flow.for_conditionBegin(this); |
| if (condition != null) { |
| h.typeAnalyzer.analyzeExpression(condition!, h.operations.unknownType); |
| } else { |
| h.typeAnalyzer.handleNoCondition(this); |
| } |
| h.flow.for_bodyBegin(forCollection ? null : this, condition); |
| h.typeAnalyzer._visitLoopBody(this, body); |
| h.flow.for_updaterBegin(); |
| if (updater != null) { |
| h.typeAnalyzer.analyzeExpression(updater!, h.operations.unknownType); |
| } else { |
| h.typeAnalyzer.handleNoCondition(this); |
| } |
| h.flow.for_end(); |
| h.irBuilder.apply( |
| 'for', |
| [Kind.statement, Kind.expression, Kind.statement, Kind.expression], |
| Kind.statement, |
| location: location); |
| } |
| } |
| |
| class ForEach extends Statement { |
| final Var? variable; |
| final Expression iterable; |
| final Statement body; |
| final bool declaresVariable; |
| |
| ForEach._(this.variable, this.iterable, this.body, this.declaresVariable, |
| {required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| iterable.preVisit(visitor); |
| if (variable != null) { |
| if (declaresVariable) { |
| visitor._assignedVariables.declare(variable!); |
| } else { |
| visitor._assignedVariables.write(variable!); |
| } |
| } |
| visitor._assignedVariables.beginNode(); |
| body.preVisit(visitor); |
| visitor._assignedVariables.endNode(this); |
| } |
| |
| @override |
| String toString() { |
| String declarationPart; |
| if (variable == null) { |
| declarationPart = '<identifier>'; |
| } else if (declaresVariable) { |
| declarationPart = variable.toString(); |
| } else { |
| declarationPart = variable!.name; |
| } |
| return 'for ($declarationPart in $iterable) $body'; |
| } |
| |
| @override |
| void visit(Harness h) { |
| var iteratedType = h._getIteratedType(h.typeAnalyzer |
| .analyzeExpression(iterable, h.operations.unknownType) |
| .unwrapTypeView()); |
| h.flow.forEach_bodyBegin(this); |
| var variable = this.variable; |
| if (variable != null && !declaresVariable) { |
| h.flow.write(this, variable, SharedTypeView(iteratedType), null); |
| } |
| h.typeAnalyzer._visitLoopBody(this, body); |
| h.flow.forEach_end(); |
| h.irBuilder.apply( |
| 'forEach', [Kind.expression, Kind.statement], Kind.statement, |
| location: location); |
| } |
| } |
| |
| class GuardedPattern extends Node with PossiblyGuardedPattern { |
| final Pattern pattern; |
| late final Map<String, Var> variables; |
| final Expression? guard; |
| |
| GuardedPattern._({ |
| required this.pattern, |
| required this.guard, |
| required super.location, |
| }) : super._(); |
| |
| @override |
| GuardedPattern get _asGuardedPattern => this; |
| } |
| |
| class Harness { |
| static Map<String, Type> _coreMemberTypes = { |
| 'int.<': Type('bool Function(num)'), |
| 'int.<=': Type('bool Function(num)'), |
| 'int.>': Type('bool Function(num)'), |
| 'int.>=': Type('bool Function(num)'), |
| 'num.sign': Type('num'), |
| 'Object.toString': Type('String Function()'), |
| }; |
| |
| final MiniAstOperations operations = MiniAstOperations(); |
| |
| bool _started = false; |
| |
| late final FlowAnalysis<Node, Statement, Expression, Var, |
| SharedTypeView<Type>> flow; |
| |
| bool? _inferenceUpdate3Enabled; |
| |
| bool? _patternsEnabled; |
| |
| Type? _thisType; |
| |
| late final Map<String, _PropertyElement?> _members = { |
| for (var entry in _coreMemberTypes.entries) |
| entry.key: _PropertyElement(entry.value, |
| isPromotable: false, whyNotPromotable: null) |
| }; |
| |
| late final typeAnalyzer = _MiniAstTypeAnalyzer( |
| this, |
| TypeAnalyzerOptions( |
| nullSafetyEnabled: !operations.legacy, |
| patternsEnabled: patternsEnabled, |
| inferenceUpdate3Enabled: inferenceUpdate3Enabled)); |
| |
| /// Indicates whether initializers of implicitly typed variables should be |
| /// accounted for by SSA analysis. (In an ideal world, they always would be, |
| /// but due to https://github.com/dart-lang/language/issues/1785, they weren't |
| /// always, and we need to be able to replicate the old behavior when |
| /// analyzing old language versions). |
| bool _respectImplicitlyTypedVarInitializers = true; |
| |
| bool _fieldPromotionEnabled = true; |
| |
| bool get inferenceUpdate3Enabled => |
| _inferenceUpdate3Enabled ?? !operations.legacy; |
| |
| MiniIRBuilder get irBuilder => typeAnalyzer._irBuilder; |
| |
| bool get patternsEnabled => _patternsEnabled ?? !operations.legacy; |
| |
| set thisType(String type) { |
| assert(!_started); |
| _thisType = Type(type); |
| } |
| |
| /// Updates the harness with a new result for |
| /// [MiniAstOperations.downwardInfer]. |
| void addDownwardInfer({ |
| required String name, |
| required String context, |
| required String result, |
| }) { |
| operations.addDownwardInfer( |
| name: name, |
| context: context, |
| result: result, |
| ); |
| } |
| |
| /// Updates the harness so that when an |
| /// [TypeAnalyzerOperations.isAlwaysExhaustiveType] query is invoked on type |
| /// [type], [isExhaustive] will be returned. |
| void addExhaustiveness(String type, bool isExhaustive) { |
| operations.addExhaustiveness(type, isExhaustive); |
| } |
| |
| /// Updates the harness so that when an extension type erasure query is |
| /// invoked on type [type], [representation] will be returned. |
| void addExtensionTypeErasure(String type, String representation) { |
| operations.addExtensionTypeErasure(type, representation); |
| } |
| |
| void addLub(String type1, String type2, String resultType) { |
| operations.addLub(type1, type2, resultType); |
| } |
| |
| /// Updates the harness so that when member [memberName] is looked up on type |
| /// [targetType], a member is found having the given [type]. |
| /// |
| /// If [type] is `null`, then an attempt to look up [memberName] on type |
| /// [targetType] should result in `null` (no such member) rather than a test |
| /// failure. |
| void addMember(String targetType, String memberName, String? type, |
| {bool promotable = false, |
| PropertyNonPromotabilityReason? whyNotPromotable}) { |
| if (promotable) { |
| assert(whyNotPromotable == null); |
| } |
| var query = '$targetType.$memberName'; |
| if (type == null) { |
| if (promotable) { |
| fail("It doesn't make sense to specify `promotable: true` " |
| "when the type is `null`"); |
| } |
| _members[query] = null; |
| return; |
| } |
| _members[query] = _PropertyElement(Type(type), |
| isPromotable: promotable, whyNotPromotable: whyNotPromotable); |
| } |
| |
| void addPromotionException(String from, String to, String result) { |
| operations.addPromotionException(from, to, result); |
| } |
| |
| void addSuperInterfaces( |
| String className, List<Type> Function(List<Type>) template) { |
| operations.addSuperInterfaces(className, template); |
| } |
| |
| void addTypeVariable(String name, {String? bound}) { |
| operations.addTypeVariable(name, bound: bound); |
| } |
| |
| void disableFieldPromotion() { |
| assert(!_started); |
| _fieldPromotionEnabled = false; |
| } |
| |
| void disableInferenceUpdate3() { |
| assert(!_started); |
| _inferenceUpdate3Enabled = false; |
| } |
| |
| void disablePatterns() { |
| assert(!_started); |
| _patternsEnabled = false; |
| } |
| |
| void disableRespectImplicitlyTypedVarInitializers() { |
| assert(!_started); |
| _respectImplicitlyTypedVarInitializers = false; |
| } |
| |
| void enableLegacy() { |
| assert(!_started); |
| operations.legacy = true; |
| } |
| |
| /// Attempts to look up a member named [memberName] in the given [type]. If |
| /// a member is found, returns its [_PropertyElement] object; otherwise `null` |
| /// is returned. |
| /// |
| /// If test hasn't been configured in such a way that the result of the query |
| /// is known, the test fails. |
| _PropertyElement? getMember(Type type, String memberName) { |
| var query = '$type.$memberName'; |
| var member = _members[query]; |
| // If an explicit map entry was found for this member, return the associated |
| // value (even if it is `null`; `null` means the test has been explicitly |
| // configured so that the member lookup is supposed to find nothing). |
| if (member != null || _members.containsKey(query)) return member; |
| switch (memberName) { |
| case 'toString': |
| // Assume that all types implement `Object.toString`. |
| return _members['Object.$memberName']!; |
| default: |
| // It's legal to look up any member on the type `dynamic`. |
| if (type is DynamicType) { |
| return null; |
| } |
| // But an attempt to look up an unknown member on any other type |
| // results in a test failure. This is to catch mistakes in unit tests; |
| // if the unit test is deliberately trying to exercise a member lookup |
| // that should find nothing, please use `addMember` to store an |
| // explicit `null` value in the `_members` map. |
| fail('Unknown member query: $query'); |
| } |
| } |
| |
| /// See [TypeAnalyzer.resolveRelationalPatternOperator]. |
| RelationalOperatorResolution<Type>? resolveRelationalPatternOperator( |
| Type matchedValueType, String operator) { |
| if (operator == '==' || operator == '!=') { |
| return RelationalOperatorResolution( |
| kind: operator == '==' |
| ? RelationalOperatorKind.equals |
| : RelationalOperatorKind.notEquals, |
| parameterType: SharedTypeView(Type('Object')), |
| returnType: SharedTypeView(Type('bool'))); |
| } |
| var member = getMember(matchedValueType, operator); |
| if (member == null) return null; |
| var memberType = member._type; |
| if (memberType is! FunctionType || |
| memberType.nullabilitySuffix != NullabilitySuffix.none) { |
| fail('$matchedValueType.operator$operator has type $memberType; ' |
| 'must be a function type'); |
| } |
| if (memberType.positionalParameters.isEmpty) { |
| fail('$matchedValueType.operator$operator has type $memberType; ' |
| 'must accept a parameter'); |
| } |
| return RelationalOperatorResolution( |
| kind: RelationalOperatorKind.other, |
| parameterType: SharedTypeView(memberType.positionalParameters[0]), |
| returnType: SharedTypeView(memberType.returnType)); |
| } |
| |
| /// Runs the given [statements] through flow analysis, checking any assertions |
| /// they contain. |
| void run(List<ProtoStatement> statements, |
| {bool errorRecoveryOK = false, Set<String> expectedErrors = const {}}) { |
| try { |
| _started = true; |
| if (operations.legacy && patternsEnabled) { |
| fail('Patterns cannot be enabled in legacy mode'); |
| } |
| var visitor = PreVisitor(typeAnalyzer.errors); |
| var b = Block._(statements, location: computeLocation()); |
| b.preVisit(visitor); |
| flow = operations.legacy |
| ? FlowAnalysis<Node, Statement, Expression, Var, |
| SharedTypeView<Type>>.legacy( |
| operations, visitor._assignedVariables) |
| : FlowAnalysis<Node, Statement, Expression, Var, |
| SharedTypeView<Type>>(operations, visitor._assignedVariables, |
| respectImplicitlyTypedVarInitializers: |
| _respectImplicitlyTypedVarInitializers, |
| fieldPromotionEnabled: _fieldPromotionEnabled); |
| typeAnalyzer.dispatchStatement(b); |
| typeAnalyzer.finish(); |
| expect(typeAnalyzer.errors._accumulatedErrors, expectedErrors); |
| var assertInErrorRecoveryStack = |
| typeAnalyzer.errors._assertInErrorRecoveryStack; |
| if (!errorRecoveryOK && assertInErrorRecoveryStack != null) { |
| fail('assertInErrorRecovery called but no errors reported: ' |
| '$assertInErrorRecoveryStack'); |
| } |
| if (Node._nodesWithUnusedErrorIds.isNotEmpty) { |
| var ids = [ |
| for (var node in Node._nodesWithUnusedErrorIds) node._errorId |
| ].join(', '); |
| fail('Unused error ids: $ids'); |
| } |
| } finally { |
| Node._nodesWithUnusedErrorIds.clear(); |
| } |
| } |
| |
| Type _getIteratedType(Type iterableType) { |
| var typeStr = iterableType.type; |
| if (typeStr.startsWith('List<') && typeStr.endsWith('>')) { |
| return Type(typeStr.substring(5, typeStr.length - 1)); |
| } else { |
| throw UnimplementedError('TODO(paulberry): getIteratedType($typeStr)'); |
| } |
| } |
| } |
| |
| class If extends IfBase { |
| final Expression condition; |
| |
| If._(this.condition, super.ifTrue, super.ifFalse, {required super.location}) |
| : super._(); |
| |
| @override |
| String get _conditionPartString => condition.toString(); |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| condition.preVisit(visitor); |
| super.preVisit(visitor); |
| } |
| |
| @override |
| void visit(Harness h) { |
| h.typeAnalyzer.analyzeIfStatement(this, condition, ifTrue, ifFalse); |
| h.irBuilder.apply( |
| 'if', [Kind.expression, Kind.statement, Kind.statement], Kind.statement, |
| location: location); |
| } |
| } |
| |
| abstract class IfBase extends Statement { |
| final Statement ifTrue; |
| final Statement? ifFalse; |
| |
| IfBase._(this.ifTrue, this.ifFalse, {required super.location}); |
| |
| String get _conditionPartString; |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| visitor._assignedVariables.beginNode(); |
| ifTrue.preVisit(visitor); |
| visitor._assignedVariables.endNode(this); |
| ifFalse?.preVisit(visitor); |
| } |
| |
| @override |
| String toString() => |
| 'if ($_conditionPartString) $ifTrue' + |
| (ifFalse == null ? '' : 'else $ifFalse'); |
| } |
| |
| class IfCase extends IfBase { |
| final Expression expression; |
| final Pattern pattern; |
| final Expression? guard; |
| |
| /// These variables are set during pre-visit, and some of them are joins of |
| /// pattern variable declarations. We don't know their types until we do |
| /// type analysis. So, some of these variables might become unavailable. |
| late final Map<String, Var> _candidateVariables; |
| |
| IfCase(this.expression, this.pattern, this.guard, super.ifTrue, super.ifFalse, |
| {required super.location}) |
| : super._(); |
| |
| @override |
| String get _conditionPartString => '$expression case $pattern'; |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| expression.preVisit(visitor); |
| var variableBinder = _VariableBinder(visitor); |
| variableBinder.casePatternStart(); |
| pattern.preVisit(visitor, variableBinder, isInAssignment: false); |
| _candidateVariables = variableBinder.casePatternFinish(); |
| variableBinder.finish(); |
| guard?.preVisit(visitor); |
| super.preVisit(visitor); |
| } |
| |
| @override |
| void visit(Harness h) { |
| h.typeAnalyzer.analyzeIfCaseStatement( |
| this, expression, pattern, guard, ifTrue, ifFalse, _candidateVariables); |
| h.irBuilder.apply( |
| 'ifCase', |
| [ |
| Kind.expression, |
| Kind.pattern, |
| Kind.variables, |
| Kind.expression, |
| Kind.statement, |
| Kind.statement, |
| ], |
| Kind.statement, |
| location: location, |
| ); |
| } |
| } |
| |
| class IfCaseElement extends IfElementBase { |
| final Expression expression; |
| final Pattern pattern; |
| final Expression? guard; |
| late final Map<String, Var> _variables; |
| |
| IfCaseElement( |
| this.expression, this.pattern, this.guard, super.ifTrue, super.ifFalse, |
| {required super.location}) |
| : super._(); |
| |
| @override |
| String get _conditionPartString => '$expression case $pattern'; |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| expression.preVisit(visitor); |
| var variableBinder = _VariableBinder(visitor); |
| variableBinder.casePatternStart(); |
| pattern.preVisit(visitor, variableBinder, isInAssignment: false); |
| _variables = variableBinder.casePatternFinish(); |
| variableBinder.finish(); |
| guard?.preVisit(visitor); |
| super.preVisit(visitor); |
| } |
| |
| @override |
| void visit(Harness h, Object context) { |
| h.typeAnalyzer.analyzeIfCaseElement( |
| node: this, |
| expression: expression, |
| pattern: pattern, |
| variables: _variables, |
| guard: guard, |
| ifTrue: ifTrue, |
| ifFalse: ifFalse, |
| context: context, |
| ); |
| h.irBuilder.apply( |
| 'if', |
| [ |
| Kind.expression, |
| Kind.pattern, |
| Kind.expression, |
| Kind.collectionElement, |
| Kind.collectionElement, |
| ], |
| Kind.collectionElement, |
| names: ['expression', 'pattern', 'guard', 'ifTrue', 'ifFalse'], |
| location: location, |
| ); |
| } |
| } |
| |
| class IfElement extends IfElementBase { |
| final Expression condition; |
| |
| IfElement._(this.condition, super.ifTrue, super.ifFalse, |
| {required super.location}) |
| : super._(); |
| |
| @override |
| String get _conditionPartString => condition.toString(); |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| condition.preVisit(visitor); |
| super.preVisit(visitor); |
| } |
| |
| @override |
| void visit(Harness h, Object context) { |
| h.typeAnalyzer.analyzeIfElement( |
| node: this, |
| condition: condition, |
| ifTrue: ifTrue, |
| ifFalse: ifFalse, |
| context: context, |
| ); |
| h.irBuilder.apply( |
| 'if', |
| [Kind.expression, Kind.collectionElement, Kind.collectionElement], |
| Kind.collectionElement, |
| location: location, |
| ); |
| } |
| } |
| |
| abstract class IfElementBase extends CollectionElement { |
| final CollectionElement ifTrue; |
| final CollectionElement? ifFalse; |
| |
| IfElementBase._(this.ifTrue, this.ifFalse, {required super.location}); |
| |
| String get _conditionPartString; |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| visitor._assignedVariables.beginNode(); |
| ifTrue.preVisit(visitor); |
| visitor._assignedVariables.endNode(this); |
| ifFalse?.preVisit(visitor); |
| } |
| |
| @override |
| String toString() => |
| 'if ($_conditionPartString) $ifTrue' + |
| (ifFalse == null ? '' : 'else $ifFalse'); |
| } |
| |
| class IfNull extends Expression { |
| final Expression lhs; |
| final Expression rhs; |
| |
| IfNull._(this.lhs, this.rhs, {required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| lhs.preVisit(visitor); |
| rhs.preVisit(visitor); |
| } |
| |
| @override |
| String toString() => '$lhs ?? $rhs'; |
| |
| @override |
| ExpressionTypeAnalysisResult<Type> visit( |
| Harness h, SharedTypeSchemaView<Type> schema) { |
| var result = h.typeAnalyzer.analyzeIfNullExpression(this, lhs, rhs); |
| h.irBuilder.apply( |
| 'ifNull', [Kind.expression, Kind.expression], Kind.expression, |
| location: location); |
| return result; |
| } |
| } |
| |
| class IntLiteral extends ConstExpression { |
| final int value; |
| |
| /// `true` or `false` if we should assert that int->double conversion either |
| /// does, or does not, happen. `null` if no assertion should be done. |
| final bool? expectConversionToDouble; |
| |
| IntLiteral(this.value, |
| {this.expectConversionToDouble, required super.location}) |
| : super._(); |
| |
| @override |
| void preVisit(PreVisitor visitor) {} |
| |
| @override |
| String toString() => '$value'; |
| |
| @override |
| ExpressionTypeAnalysisResult<Type> visit( |
| Harness h, SharedTypeSchemaView<Type> schema) { |
| var result = h.typeAnalyzer.analyzeIntLiteral(schema); |
| if (expectConversionToDouble != null) { |
| expect(result.convertedToDouble, expectConversionToDouble); |
| } |
| h.irBuilder.atom( |
| result.convertedToDouble ? '${value.toDouble()}f' : '$value', |
| Kind.expression, |
| location: location); |
| return result; |
| } |
| } |
| |
| /// Representation of a method invocation in the pseudo-Dart language used for |
| /// flow analysis testing. |
| class InvokeMethod extends Expression { |
| // The expression appering before the `.`. |
| final Expression target; |
| |
| // The name of the method being invoked. |
| final String methodName; |
| |
| // The arguments being passed to the invocation. |
| final List<Expression> arguments; |
| |
| InvokeMethod._(this.target, this.methodName, this.arguments, |
| {required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| target.preVisit(visitor); |
| for (var argument in arguments) { |
| argument.preVisit(visitor); |
| } |
| } |
| |
| @override |
| String toString() => |
| '$target.$methodName(${[for (var arg in arguments) arg].join(', ')})'; |
| |
| @override |
| ExpressionTypeAnalysisResult<Type> visit( |
| Harness h, SharedTypeSchemaView<Type> schema) { |
| return h.typeAnalyzer.analyzeMethodInvocation(this, |
| target is CascadePlaceholder ? null : target, methodName, arguments); |
| } |
| } |
| |
| class Is extends Expression { |
| final Expression target; |
| final Type type; |
| final bool isInverted; |
| |
| Is._(this.target, this.type, this.isInverted, {required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| target.preVisit(visitor); |
| } |
| |
| @override |
| String toString() => '$target is${isInverted ? '!' : ''} $type'; |
| |
| @override |
| ExpressionTypeAnalysisResult<Type> visit( |
| Harness h, SharedTypeSchemaView<Type> schema) { |
| return h.typeAnalyzer |
| .analyzeTypeTest(this, target, type, isInverted: isInverted); |
| } |
| } |
| |
| abstract class Label extends Node { |
| factory Label(String name) = BoundLabel._; |
| |
| factory Label.unbound() = UnboundLabel._; |
| |
| Label._({required super.location}) : super._(); |
| |
| Statement thenStmt(Statement statement); |
| |
| /// Returns the statement this label has been bound to, or `null` for labels |
| /// constructed with [Label.unbound]. |
| Statement? _getBinding(); |
| } |
| |
| class LabeledStatement extends Statement { |
| final List<Label> labels = []; |
| |
| final Statement body; |
| |
| LabeledStatement._(this.body, {required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| body.preVisit(visitor); |
| } |
| |
| @override |
| String toString() => [...labels, body].join(': '); |
| |
| @override |
| void visit(Harness h) { |
| h.typeAnalyzer.analyzeLabeledStatement(this, body); |
| } |
| } |
| |
| /// Representation of a list literal in the pseudo-Dart language used for flow |
| /// analysis testing. |
| class ListLiteral extends Expression { |
| final List<CollectionElement> elements; |
| final Type elementType; |
| |
| ListLiteral._(this.elements, this.elementType, {required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| for (var element in elements) { |
| element.preVisit(visitor); |
| } |
| } |
| |
| @override |
| ExpressionTypeAnalysisResult<Type> visit( |
| Harness h, SharedTypeSchemaView<Type> schema) { |
| for (var element in elements) { |
| element.visit( |
| h, CollectionElementContextType._(SharedTypeSchemaView(elementType))); |
| } |
| h.irBuilder.apply('list', [for (var _ in elements) Kind.collectionElement], |
| Kind.expression, |
| location: location); |
| return SimpleTypeAnalysisResult( |
| type: h.operations.listType(SharedTypeView(elementType))); |
| } |
| } |
| |
| abstract class ListOrMapPatternElement implements Node { |
| ListOrMapPatternElement._(); |
| |
| void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder, |
| {required bool isInAssignment}); |
| |
| String _debugString({required bool needsKeywordOrType}); |
| } |
| |
| class ListPattern extends Pattern { |
| final Type? elementType; |
| |
| final List<ListPatternElement> elements; |
| |
| ListPattern._(this.elementType, this.elements, {required super.location}) |
| : super._(); |
| |
| @override |
| SharedTypeSchemaView<Type> computeSchema(Harness h) => |
| h.typeAnalyzer.analyzeListPatternSchema( |
| elementType: elementType?.wrapSharedTypeView(), elements: elements); |
| |
| @override |
| void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder, |
| {required bool isInAssignment}) { |
| for (var element in elements) { |
| element.preVisit(visitor, variableBinder, isInAssignment: isInAssignment); |
| } |
| } |
| |
| @override |
| PatternResult<Type> visit(Harness h, SharedMatchContext context) { |
| var listPatternResult = h.typeAnalyzer.analyzeListPattern(context, this, |
| elementType: elementType?.wrapSharedTypeView(), elements: elements); |
| var matchedType = listPatternResult.matchedValueType.unwrapTypeView(); |
| var requiredType = listPatternResult.requiredType.unwrapTypeView(); |
| h.irBuilder.atom(matchedType.type, Kind.type, location: location); |
| h.irBuilder.atom(requiredType.type, Kind.type, location: location); |
| h.irBuilder.apply( |
| 'listPattern', |
| [...List.filled(elements.length, Kind.pattern), Kind.type, Kind.type], |
| Kind.pattern, |
| names: ['matchedType', 'requiredType'], |
| location: location); |
| return listPatternResult; |
| } |
| |
| @override |
| String _debugString({required bool needsKeywordOrType}) { |
| var elements = [ |
| for (var element in this.elements) |
| element._debugString(needsKeywordOrType: needsKeywordOrType) |
| ]; |
| return '[${elements.join(', ')}]'; |
| } |
| } |
| |
| abstract class ListPatternElement implements ListOrMapPatternElement {} |
| |
| class LocalFunction extends Expression { |
| final Statement body; |
| final Type type; |
| |
| LocalFunction._(this.body, {String? type, required super.location}) |
| : type = Type(type ?? 'void Function()'); |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| visitor._assignedVariables.beginNode(); |
| body.preVisit(visitor); |
| visitor._assignedVariables |
| .endNode(this, isClosureOrLateVariableInitializer: true); |
| } |
| |
| @override |
| String toString() => '() $body'; |
| |
| @override |
| ExpressionTypeAnalysisResult<Type> visit( |
| Harness h, SharedTypeSchemaView<Type> schema) { |
| h.flow.functionExpression_begin(this); |
| h.typeAnalyzer.dispatchStatement(body); |
| h.flow.functionExpression_end(); |
| h.irBuilder.apply('localFunction', [Kind.statement], Kind.expression, |
| location: location); |
| return SimpleTypeAnalysisResult(type: SharedTypeView(type)); |
| } |
| } |
| |
| class Logical extends Expression { |
| final Expression lhs; |
| final Expression rhs; |
| final bool isAnd; |
| |
| Logical._(this.lhs, this.rhs, {required this.isAnd, required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| lhs.preVisit(visitor); |
| visitor._assignedVariables.beginNode(); |
| rhs.preVisit(visitor); |
| visitor._assignedVariables.endNode(this); |
| } |
| |
| @override |
| String toString() => '$lhs ${isAnd ? '&&' : '||'} $rhs'; |
| |
| @override |
| ExpressionTypeAnalysisResult<Type> visit( |
| Harness h, SharedTypeSchemaView<Type> schema) { |
| var operatorName = isAnd ? '&&' : '||'; |
| var result = |
| h.typeAnalyzer.analyzeBinaryExpression(this, lhs, operatorName, rhs); |
| h.irBuilder.apply( |
| operatorName, [Kind.expression, Kind.expression], Kind.expression, |
| location: location); |
| return result; |
| } |
| } |
| |
| class LogicalAndPattern extends Pattern { |
| final Pattern lhs; |
| |
| final Pattern rhs; |
| |
| LogicalAndPattern._(this.lhs, this.rhs, {required super.location}) |
| : super._(); |
| |
| @override |
| SharedTypeSchemaView<Type> computeSchema(Harness h) => |
| h.typeAnalyzer.analyzeLogicalAndPatternSchema(lhs, rhs); |
| |
| @override |
| void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder, |
| {required bool isInAssignment}) { |
| lhs.preVisit(visitor, variableBinder, isInAssignment: isInAssignment); |
| rhs.preVisit(visitor, variableBinder, isInAssignment: isInAssignment); |
| } |
| |
| @override |
| PatternResult<Type> visit(Harness h, SharedMatchContext context) { |
| var analysisResult = |
| h.typeAnalyzer.analyzeLogicalAndPattern(context, this, lhs, rhs); |
| var matchedType = analysisResult.matchedValueType.unwrapTypeView(); |
| h.irBuilder.atom(matchedType.type, Kind.type, location: location); |
| h.irBuilder.apply('logicalAndPattern', |
| [Kind.pattern, Kind.pattern, Kind.type], Kind.pattern, |
| names: ['matchedType'], location: location); |
| return analysisResult; |
| } |
| |
| @override |
| _debugString({required bool needsKeywordOrType}) => [ |
| lhs._debugString(needsKeywordOrType: false), |
| '&&', |
| rhs._debugString(needsKeywordOrType: false) |
| ].join(' '); |
| } |
| |
| class LogicalOrPattern extends Pattern { |
| final Pattern lhs; |
| |
| final Pattern rhs; |
| |
| LogicalOrPattern(this.lhs, this.rhs, {required super.location}) : super._(); |
| |
| @override |
| SharedTypeSchemaView<Type> computeSchema(Harness h) => |
| h.typeAnalyzer.analyzeLogicalOrPatternSchema(lhs, rhs); |
| |
| @override |
| void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder, |
| {required bool isInAssignment}) { |
| variableBinder.logicalOrPatternStart(); |
| lhs.preVisit(visitor, variableBinder, isInAssignment: isInAssignment); |
| variableBinder.logicalOrPatternFinishLeft(); |
| rhs.preVisit(visitor, variableBinder, isInAssignment: isInAssignment); |
| variableBinder.logicalOrPatternFinish(this); |
| } |
| |
| @override |
| PatternResult<Type> visit(Harness h, SharedMatchContext context) { |
| var analysisResult = |
| h.typeAnalyzer.analyzeLogicalOrPattern(context, this, lhs, rhs); |
| var matchedType = analysisResult.matchedValueType.unwrapTypeView(); |
| h.irBuilder.atom(matchedType.type, Kind.type, location: location); |
| h.irBuilder.apply('logicalOrPattern', |
| [Kind.pattern, Kind.pattern, Kind.type], Kind.pattern, |
| names: ['matchedType'], location: location); |
| return analysisResult; |
| } |
| |
| @override |
| _debugString({required bool needsKeywordOrType}) => [ |
| lhs._debugString(needsKeywordOrType: false), |
| '||', |
| rhs._debugString(needsKeywordOrType: false) |
| ].join(' '); |
| } |
| |
| /// Representation of an expression that can appear on the left hand side of an |
| /// assignment (or as the target of `++` or `--`). Methods in this class may be |
| /// used to create more complex expressions based on this one. |
| abstract class LValue extends Expression { |
| LValue._({required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor, {_LValueDisposition disposition}); |
| |
| /// Creates an expression representing a write to this L-value. |
| Expression write(ProtoExpression? value) { |
| var location = computeLocation(); |
| return new Write(this, value?.asExpression(location: location), |
| location: location); |
| } |
| |
| void _visitWrite(Harness h, Expression assignmentExpression, Type writtenType, |
| Expression? rhs); |
| } |
| |
| /// Representation of a map entry in the pseudo-Dart language used for flow |
| /// analysis testing. |
| class MapEntry extends CollectionElement { |
| final Expression key; |
| final Expression value; |
| |
| MapEntry._(this.key, this.value, {required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| key.preVisit(visitor); |
| value.preVisit(visitor); |
| } |
| |
| @override |
| String toString() => '$key: $value'; |
| |
| @override |
| void visit(Harness h, CollectionElementContext context) { |
| SharedTypeSchemaView<Type> keySchema; |
| SharedTypeSchemaView<Type> valueSchema; |
| switch (context) { |
| case CollectionElementContextMapEntry(:var keyType, :var valueType): |
| keySchema = SharedTypeSchemaView(keyType); |
| valueSchema = SharedTypeSchemaView(valueType); |
| default: |
| keySchema = valueSchema = h.operations.unknownType; |
| } |
| h.typeAnalyzer.analyzeExpression(key, keySchema); |
| h.typeAnalyzer.analyzeExpression(value, valueSchema); |
| h.irBuilder.apply( |
| 'mapEntry', [Kind.expression, Kind.expression], Kind.collectionElement, |
| location: location); |
| } |
| } |
| |
| /// Representation of a list literal in the pseudo-Dart language used for flow |
| /// analysis testing. |
| class MapLiteral extends Expression { |
| final List<CollectionElement> elements; |
| final Type keyType; |
| final Type valueType; |
| |
| MapLiteral._(this.elements, this.keyType, this.valueType, |
| {required super.location}); |
| |
| @override |
| void preVisit(PreVisitor visitor) { |
| for (var element in elements) { |
| element.preVisit(visitor); |
| } |
| } |
| |
| @override |
| ExpressionTypeAnalysisResult<Type> visit( |
| Harness h, SharedTypeSchemaView<Type> schema) { |
| var context = CollectionElementContextMapEntry._(keyType, valueType); |
| for (var element in elements) { |
| element.visit(h, context); |
| } |
| h.irBuilder.apply('map', [for (var _ in elements) Kind.collectionElement], |
| Kind.expression, |
| location: location); |
| return SimpleTypeAnalysisResult( |
| type: h.operations.mapType( |
| keyType: SharedTypeView(keyType), |
| valueType: SharedTypeView(valueType))); |
| } |
| } |
| |
| class MapPattern extends Pattern { |
| final ({Type keyType, Type valueType})? typeArguments; |
| |
| final List<MapPatternElement> elements; |
| |
| MapPattern._(this.typeArguments, this.elements, {required super.location}) |
| : super._(); |
| |
| @override |
| SharedTypeSchemaView<Type> computeSchema(Harness h) => |
| h.typeAnalyzer.analyzeMapPatternSchema( |
| typeArguments: typeArguments?.wrapSharedTypeMapEntryView(), |
| elements: elements); |
| |
| @override |
| void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder, |
| {required bool isInAssignment}) { |
| for (var element in elements) { |
| element.preVisit(visitor, variableBinder, isInAssignment: isInAssignment); |
| } |
| } |
| |
| @override |
| PatternResult<Type> visit(Harness h, SharedMatchContext context) { |
| var mapPatternResult = h.typeAnalyzer.analyzeMapPattern(context, this, |
| typeArguments: typeArguments?.wrapSharedTypeMapEntryView(), |
| elements: elements); |
| var matchedType = mapPatternResult.matchedValueType.unwrapTypeView(); |
| var requiredType = mapPatternResult.requiredType.unwrapTypeView(); |
| h.irBuilder.atom(matchedType.type, Kind.type, location: location); |
| h.irBuilder.atom(requiredType.type, Kind.type, location: location); |
| h.irBuilder.apply( |
| 'mapPattern', |
| [ |
| ...List.filled(elements.length, Kind.mapPatternElement), |
| Kind.type, |
| Kind.type, |
| ], |
| Kind.pattern, |
| names: ['matchedType', 'requiredType'], |
| location: location, |
| ); |
| return mapPatternResult; |
| } |
| |
| @override |
| String _debugString({required bool needsKeywordOrType}) { |
| var elements = [ |
| for (var element in this.elements) |
| element._debugString(needsKeywordOrType: needsKeywordOrType) |
| ]; |
| return '[${elements.join(', ')}]'; |
| } |
| } |
| |
| abstract class MapPatternElement implements ListOrMapPatternElement {} |
| |
| class MapPatternEntry extends Node implements MapPatternElement { |
| final Expression key; |
| final Pattern value; |
| |
| MapPatternEntry._(this.key, this.value, {required super.location}) |
| : super._(); |
| |
| @override |
| void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder, |
| {required bool isInAssignment}) { |
| value.preVisit(visitor, variableBinder, isInAssignment: isInAssignment); |
| } |
| |
| @override |
| String _debugString({required bool needsKeywordOrType}) { |
| return '$key: $value'; |
| } |
| } |
| |
| class MiniAstOperations |
| with |
| TypeAnalyzerOperationsMixin<Type, Var, PromotedTypeVariableType, Type, |
| String> |
| implements |
| TypeAnalyzerOperations<Type, Var, PromotedTypeVariableType, Type, |
| String> { |
| static const Map<String, bool> _coreExhaustiveness = const { |
| '()': true, |
| '(int, int?)': false, |
| 'bool': true, |
| 'dynamic': false, |
| 'int': false, |
| 'int?': false, |
| 'List<int>': false, |
| 'Never': false, |
| 'num': false, |
| 'num?': false, |
| 'Object': false, |
| 'Object?': false, |
| 'String': false, |
| 'String?': false, |
| }; |
| |
| static final Map<String, Type> _coreGlbs = { |
| '_, int': Type('int'), |
| '(int,), _': Type('(int,)'), |
| '(num,), _': Type('(num,)'), |
| 'Object?, double': Type('double'), |
| 'Object?, int': Type('int'), |
| 'double, int': Type('Never'), |
| 'double?, int?': Type('Null'), |
| 'int?, num': Type('int'), |
| 'Null, int': Type('Never'), |
| }; |
| |
| static final Map<String, Type> _coreLubs = { |
| 'double, int': Type('num'), |
| 'double?, int?': Type('num?'), |
| 'int, num': Type('num'), |
| 'Null, int': Type('int?'), |
| 'Null, Object': Type('Object?'), |
| 'int, _': Type('int'), |
| 'List<_>, _': Type('List<_>'), |
| 'Null, _': Type('Null'), |
| }; |
| |
| static final Map<String, Type> _coreDownwardInferenceResults = { |
| 'bool <: bool': Type('bool'), |
| 'dynamic <: int': Type('dynamic'), |
| 'error <: int': Type('error'), |
| 'error <: num': Type('error'), |
| 'int <: dynamic': Type('int'), |
| 'int <: int': Type('int'), |
| 'int <: num': Type('int'), |
| 'int <: Object': Type('int'), |
| 'int <: Object?': Type('int'), |
| 'List <: Iterable<int>': Type('List<int>'), |
| 'Never <: int': Type('Never'), |
| 'num <: int': Type('num'), |
| 'num <: Object': Type('num'), |
| 'Object <: num': Type('Object'), |
| 'String <: num': Type('String'), |
| }; |
| |
| static final Map<String, Type> _coreNormalizeResults = { |
| 'Object': Type('Object'), |
| 'FutureOr<Object>': Type('Object'), |
| 'double': Type('double'), |
| 'int': Type('int'), |
| 'int?': Type('int?'), |
| 'num': Type('num'), |
| 'String?': Type('String?'), |
| 'List<int>': Type('List<int>'), |
| }; |
| |
| @override |
| late final SharedTypeView<Type> objectQuestionType = |
| SharedTypeView(Type('Object?')); |
| |
| @override |
| late final SharedTypeView<Type> objectType = SharedTypeView(Type('Object')); |
| |
| @override |
| late final SharedTypeSchemaView<Type> unknownType = |
| SharedTypeSchemaView(Type('_')); |
| |
| @override |
| late final SharedTypeView<Type> intType = SharedTypeView(Type('int')); |
| |
| @override |
| late final SharedTypeView<Type> doubleType = SharedTypeView(Type('double')); |
| |
| bool? _legacy; |
| |
| final Map<String, bool> _exhaustiveness = Map.of(_coreExhaustiveness); |
| |
| final Map<String, Type> _extensionTypeErasure = {}; |
| |
| final Map<String, Type> _glbs = Map.of(_coreGlbs); |
| |
| final Map<String, Type> _lubs = Map.of(_coreLubs); |
| |
| final Map<String, Type> _downwardInferenceResults = |
| Map.of(_coreDownwardInferenceResults); |
| |
| Map<String, Map<String, String>> _promotionExceptions = {}; |
| |
| Map<String, Type> _normalizeResults = Map.of(_coreNormalizeResults); |
| |
| final TypeSystem _typeSystem = TypeSystem(); |
| |
| @override |
| final SharedTypeView<Type> boolType = SharedTypeView(Type('bool')); |
| |
| @override |
| SharedTypeView<Type> get dynamicType => SharedTypeView(DynamicType.instance); |
| |
| @override |
| SharedTypeView<Type> get errorType => SharedTypeView(InvalidType.instance); |
| |
| bool get legacy => _legacy ?? false; |
| |
| set legacy(bool value) { |
| _legacy = value; |
| } |
| |
| @override |
| SharedTypeView<Type> get neverType => SharedTypeView(NeverType.instance); |
| |
| @override |
| SharedTypeView<Type> get nullType => SharedTypeView(NullType.instance); |
| |
| /// Updates the harness with a new result for [downwardInfer]. |
| void addDownwardInfer({ |
| required String name, |
| required String context, |
| required String result, |
| }) { |
| var query = '$name <: $context'; |
| _downwardInferenceResults[query] = Type(result); |
| } |
| |
| /// Updates the harness so that when an [isAlwaysExhaustiveType] query is |
| /// invoked on type [type], [isExhaustive] will be returned. |
| void addExhaustiveness(String type, bool isExhaustive) { |
| _exhaustiveness[type] = isExhaustive; |
| } |
| |
| /// Updates the harness so that when an extension type erasure query is |
| /// invoked on type [type], [representation] will be returned. |
| void addExtensionTypeErasure(String type, String representation) { |
| _extensionTypeErasure[type] = Type(representation); |
| } |
| |
| void addLub(String type1, String type2, String resultType) { |
| _lubs['$type1, $type2'] = Type(resultType); |
| } |
| |
| void addPromotionException(String from, String to, String result) { |
| (_promotionExceptions[from] ??= {})[to] = result; |
| } |
| |
| void addSuperInterfaces( |
| String className, List<Type> Function(List<Type>) template) { |
| _typeSystem.addSuperInterfaces(className, template); |
| } |
| |
| void addTypeVariable(String name, {String? bound}) { |
| _typeSystem.addTypeVariable(name, bound: bound); |
| } |
| |
| @override |
| TypeClassification classifyType(SharedTypeView<Type> type) { |
| if (isSubtypeOfInternal(type.unwrapTypeView(), Type('Object'))) { |
| return TypeClassification.nonNullable; |
| } else if (isSubtypeOfInternal(type.unwrapTypeView(), NullType.instance)) { |
| return TypeClassification.nullOrEquivalent; |
| } else { |
| return TypeClassification.potentiallyNullable; |
| } |
| } |
| |
| /// Returns the downward inference result of a type with the given [name], |
| /// in the [context]. For example infer `List<int>` from `Iterable<int>`. |
| Type downwardInfer(String name, Type context) { |
| var query = '$name <: $context'; |
| return _downwardInferenceResults[query] ?? |
| fail('Unknown downward inference query: $query'); |
| } |
| |
| @override |
| SharedTypeView<Type> extensionTypeErasure(SharedTypeView<Type> type) { |
| var query = '${type.unwrapTypeView()}'; |
| return SharedTypeView( |
| _extensionTypeErasure[query] ?? type.unwrapTypeView()); |
| } |
| |
| @override |
| SharedTypeView<Type> factor( |
| SharedTypeView<Type> from, SharedTypeView<Type> what) { |
| return SharedTypeView( |
| _typeSystem.factor(from.unwrapTypeView(), what.unwrapTypeView())); |
| } |
| |
| @override |
| Type futureTypeInternal(Type argumentType) { |
| return PrimaryType('Future', args: [argumentType]); |
| } |
| |
| @override |
| TypeDeclarationKind? getTypeDeclarationKindInternal(Type type) { |
| if (isInterfaceType(SharedTypeView(type))) { |
| return TypeDeclarationKind.interfaceDeclaration; |
| } else if (isExtensionType(SharedTypeView(type))) { |
| return TypeDeclarationKind.extensionTypeDeclaration; |
| } else { |
| return null; |
| } |
| } |
| |
| @override |
| Variance getTypeParameterVariance( |
| String typeDeclaration, int parameterIndex) { |
| // TODO(cstefantsova): Support variance of type parameters in Mini AST. |
| return Variance.covariant; |
| } |
| |
| @override |
| Type glbInternal(Type type1, Type type2) { |
| if (type1.type == type2.type) return type1; |
| var typeNames = [type1.type, type2.type]; |
| typeNames.sort(); |
| var query = typeNames.join(', '); |
| return _glbs[query] ?? fail('Unknown glb query: $query'); |
| } |
| |
| @override |
| SharedTypeView<Type> greatestClosure(SharedTypeSchemaView<Type> schema) { |
| return SharedTypeView(schema |
| .unwrapTypeSchemaView() |
| .closureWithRespectToUnknown(covariant: true) ?? |
| schema.unwrapTypeSchemaView()); |
| } |
| |
| @override |
| bool isAlwaysExhaustiveType(SharedTypeView<Type> type) { |
| var query = type.unwrapTypeView().type; |
| return _exhaustiveness[query] ?? |
| fail('Unknown exhaustiveness query: $query'); |
| } |
| |
| @override |
| bool isAssignableTo( |
| SharedTypeView<Type> fromType, SharedTypeView<Type> toType) { |
| if (legacy && |
| isSubtypeOfInternal( |
| toType.unwrapTypeView(), fromType.unwrapTypeView())) { |
| return true; |
| } |
| if (fromType is DynamicType) return true; |
| if (fromType is InvalidType) return true; |
| return isSubtypeOfInternal( |
| fromType.unwrapTypeView(), toType.unwrapTypeView()); |
| } |
| |
| @override |
| bool isDartCoreFunction(SharedTypeView<Type> type) { |
| Type unwrappedType = type.unwrapTypeView(); |
| return unwrappedType is PrimaryType && |
| unwrappedType.nullabilitySuffix == NullabilitySuffix.none && |
| unwrappedType.name == 'Function' && |
| unwrappedType.args.isEmpty; |
| } |
| |
| @override |
| bool isExtensionType(SharedTypeView<Type> type) { |
| // TODO(cstefantsova): Add the support for extension types in the mini ast |
| // testing framework. |
| return false; |
| } |
| |
| @override |
| bool isFunctionType(SharedTypeView<Type> type) { |
| return type.unwrapTypeView() is FunctionType; |
| } |
| |
| @override |
| bool isInterfaceType(SharedTypeView<Type> type) { |
| Type unwrappedType = type.unwrapTypeView(); |
| return unwrappedType is PrimaryType && unwrappedType.isInterfaceType; |
| } |
| |
| @override |
| bool isNever(SharedTypeView<Type> type) { |
| Type unwrappedType = type.unwrapTypeView(); |
| return unwrappedType is NeverType && |
| unwrappedType.nullabilitySuffix == NullabilitySuffix.none; |
| } |
| |
| @override |
| bool isNonNullable(SharedTypeSchemaView<Type> type) { |
| Type unwrappedType = type.unwrapTypeSchemaView(); |
| if (unwrappedType is DynamicType || |
| unwrappedType is SharedUnknownTypeStructure || |
| unwrappedType is VoidType || |
| unwrappedType is NullType) { |
| return false; |
| } else if (unwrappedType is PromotedTypeVariableType && |
| unwrappedType.nullabilitySuffix == NullabilitySuffix.none) { |
| return isNonNullable(SharedTypeSchemaView(unwrappedType.promotion)); |
| } else if (type.nullabilitySuffix == NullabilitySuffix.question) { |
| return false; |
| } else |