blob: 2203e77fe8cb1496d560b2899e6d529bb1c2f0de [file] [log] [blame]
// Copyright (c) 2022, 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.
import 'type_analyzer.dart';
import 'variable_bindings.dart';
/// Container for the result of running type analysis on an expression.
///
/// This class keeps track of a provisional type of the expression (prior to
/// resolving null shorting) as well as the information necessary to resolve
/// null shorting.
abstract class ExpressionTypeAnalysisResult<Type extends Object> {
/// Type of the expression before resolving null shorting.
///
/// For example, if `this` is the result of analyzing `(... as int?)?.isEven`,
/// [provisionalType] will be `bool`, because the `isEven` getter returns
/// `bool`, and it is not yet known (until looking at the surrounding code)
/// whether there will be additional selectors after `isEven` that should act
/// on the `bool` type.
Type get provisionalType;
/// Resolves any pending null shorting. For example, if `this` is the result
/// of analyzing `(... as int?)?.isEven`, then calling [resolveShorting] will
/// cause the `?.` to be desugared (if code generation is occurring) and will
/// return the type `bool?`.
///
/// TODO(paulberry): document what calls back to the client might be made by
/// invoking this method.
Type resolveShorting();
}
/// Container for the result of running type analysis on an integer literal.
class IntTypeAnalysisResult<Type extends Object>
extends SimpleTypeAnalysisResult<Type> {
/// Whether the integer literal was converted to a double.
final bool convertedToDouble;
IntTypeAnalysisResult({required super.type, required this.convertedToDouble});
}
/// Information about the code context surrounding a pattern match.
class MatchContext<Node extends Object, Expression extends Node> {
/// Simple case where the match context is non-final and there's nothing else
/// special going on.
static const MatchContext<Never, Never> simpleNonFinal =
const MatchContext(isFinal: false, topPattern: null);
/// If non-`null`, the match is being done in an irrefutable context, and this
/// is the surrounding AST node that establishes the irrefutable context.
final Node? irrefutableContext;
/// Indicates whether variables declared in the pattern should be `final`.
final bool isFinal;
/// Indicates whether variables declared in the pattern should be `late`.
final bool isLate;
/// The top level pattern in this pattern match.
final Node? topPattern;
/// The initializer being assigned to this pattern via a variable declaration
/// statement, or `null` if this pattern does not occur in a variable
/// declaration statement.
final Expression? _initializer;
/// The switch scrutinee, or `null` if this pattern does not occur in a switch
/// statement or switch expression.
final Expression? _switchScrutinee;
const MatchContext(
{Expression? initializer,
this.irrefutableContext,
required this.isFinal,
this.isLate = false,
Expression? switchScrutinee,
required this.topPattern})
: _initializer = initializer,
_switchScrutinee = switchScrutinee;
/// If the pattern [pattern] is the [topPattern] and there is a corresponding
/// initializer expression, returns it. Otherwise returns `null`.
///
/// Note: the type of [pattern] is `Object` to avoid a runtime covariance
/// check (which would fail if this method is called on [simpleNonFinal]).
Expression? getInitializer(Object pattern) =>
identical(pattern, topPattern) ? _initializer : null;
/// If the pattern [pattern] is the [topPattern] and there is a corresponding
/// switch scrutinee expression, returns it. Otherwise returns `null`.
///
/// Note: the type of [pattern] is `Object` to avoid a runtime covariance
/// check (which would fail if this method is called on [simpleNonFinal]).
Expression? getSwitchScrutinee(Object pattern) =>
identical(pattern, topPattern) ? _switchScrutinee : null;
/// Returns a modified version of `this`, with [irrefutableContext] set to
/// `null`. This is used to suppress cascading errors after reporting
/// [TypeAnalyzerErrors.refutablePatternInIrrefutableContext].
MatchContext<Node, Expression> makeRefutable() => irrefutableContext == null
? this
: new MatchContext(
initializer: _initializer,
isFinal: isFinal,
isLate: isLate,
switchScrutinee: _switchScrutinee,
topPattern: topPattern);
}
/// Data structure returned by the [TypeAnalyzer] `analyze` methods for
/// patterns.
abstract class PatternDispatchResult<Node extends Object,
Expression extends Node, Variable extends Object, Type extends Object> {
/// The AST node for this pattern.
Node get node;
/// The type schema for this pattern.
Type get typeSchema;
/// Called by the [TypeAnalyzer] when an attempt is made to match this
/// pattern.
///
/// [matchedType] is the type of the thing being matched (for a variable
/// declaration, this is the type of the initializer or substructure thereof;
/// for a switch statement this is the type of the scrutinee or substructure
/// thereof).
///
/// [bindings] is a data structure keeping track of the variable patterns seen
/// so far and their type information.
///
/// [isFinal] and [isLate] only apply to variable patterns, and indicate
/// whether the variable in question should be late and/or final.
///
/// [initializer] is only present if [node] is the principal pattern of a
/// variable declaration; it is the variable declaration's initializer
/// expression. This is used by flow analysis to track when the truth or
/// falsity of a boolean variable causes other variables to be promoted.
///
/// If the match is happening in an irrefutable context, [irrefutableContext]
/// should be the containing AST node that establishes the context as
/// irrefutable. Otherwise it should be `null`.
///
/// Stack effect (see [TypeAnalyzer] for explanation): pushes (Pattern).
void match(Type matchedType, VariableBindings<Node, Variable, Type> bindings,
MatchContext<Node, Expression> context);
}
/// Container for the result of running type analysis on an expression that does
/// not contain any null shorting.
class SimpleTypeAnalysisResult<Type extends Object>
implements ExpressionTypeAnalysisResult<Type> {
/// The static type of the expression.
final Type type;
SimpleTypeAnalysisResult({required this.type});
@override
Type get provisionalType => type;
@override
Type resolveShorting() => type;
}
/// Container for the result of running type analysis on an integer literal.
class SwitchStatementTypeAnalysisResult<Type> {
/// Whether the switch statement had a `default` clause.
final bool hasDefault;
/// Whether the switch statement was exhaustive.
final bool isExhaustive;
/// Whether the last case body in the switch statement terminated.
final bool lastCaseTerminates;
/// The number of case bodies in the switch statement (after merging cases
/// that share a body).
final int numExecutionPaths;
/// The static type of the scrutinee expression.
final Type scrutineeType;
SwitchStatementTypeAnalysisResult(
{required this.hasDefault,
required this.isExhaustive,
required this.lastCaseTerminates,
required this.numExecutionPaths,
required this.scrutineeType});
}