blob: 54ec92e181b21d32b3c090a53d4f7b05a49d32c3 [file] [edit]
// Copyright (c) 2025, 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.
part of '../control.dart';
/// A `switch` case used in either a [SwitchStatement] or a [SwitchExpression].
///
/// The case type is determined by the generic parameter [T], which defines
/// the type of [body] required.
///
/// For `switch` *statements*, [T] should be [Code] (e.g. a [Block]).
/// Bodies may be multi-line, and may also be left `null` to use case
/// fall-through. Additionally, labels are supported via [label].
///
/// ```dart
/// case pattern:
/// case pattern when guard:
/// body;
///
/// label:
/// case pattern:
/// body;
/// body;
/// ```
///
/// For `switch` *expressions*, [T] must be a non-null [Expression].
/// Fall-though is not supported in `switch` expressions, nor are labels.
/// Attempting to use fall-through by leaving [body] `null` will throw an
/// [ArgumentError], and setting [label] will have no effect.
///
/// ```dart
/// pattern => body,
/// pattern when guard => body,
/// ```
///
/// {@category controlFlow}
abstract class Case<T> implements Built<Case<T>, CaseBuilder<T>> {
Case._();
/// Build a new [Case].
factory Case([void Function(CaseBuilder<T>) updates]) = _$Case<T>;
/// Create a catch-all case, either `default` or wildcard (`_`).
///
/// For [SwitchStatement], the `default` keyword will be used. [label] will be
/// respected, if provided. To force use of the wildcard expression
/// instead, use [Case.new] with [Expression.wildcard] as the pattern.
///
/// For [SwitchExpression], a wildcard case will be created, as `switch`
/// expressions don't support `default`. [label] will be ignored.
factory Case.any(T body, {String? label}) = DefaultCase._;
/// The pattern to match.
Expression get pattern;
/// The optional guard (`when`) clause.
Expression? get guard;
/// An optional label for this case.
///
/// **NOTE:** Only `switch` *statements* ([SwitchStatement]) support labels.
/// Setting [label] with `switch` *expressions* ([SwitchExpression]) has no
/// effect; the label will be silently ignored.
String? get label;
/// Whether or not to use the `default` keyword
bool get _default => false;
/// The body of this case.
///
/// May be left null when used in a [SwitchStatement] to use case
/// fall-through.
///
/// May **not** be left null in a [SwitchExpression], as they do not support
/// fall-through. Will throw an [ArgumentError] if left unset.
//* T must be nullable, otherwise built_value will perform a check that it was
//* actually set, causing an error when left null to use fallthrough. Instead,
//* it is up to the buildable case implementations (see below) to validate
//* this value.
T? get body;
}
/// **INTERNAL**
/// Case with `default` keyword
@internal
class DefaultCase<T> extends _$Case<T> {
DefaultCase._(T body, {super.label})
: super._(body: body, pattern: Expression.wildcard);
@override
bool get _default => true;
}
/// **INTERNAL**
/// Buildable case statement
@internal
class CaseStatement extends _$Case<Code?> implements Code {
final Case<Code?> item;
CaseStatement._(this.item)
: super._(
pattern: item.pattern,
body: item.body,
guard: item.guard,
label: item.label,
);
@override
bool get _default => item._default;
@override
R accept<R>(covariant ControlBlockVisitor<R> visitor, [R? context]) =>
visitor._visitCaseStatement(this, context);
}
/// **INTERNAL**
/// Buildable case expression
@internal
class CaseExpression extends _$Case<Expression> implements Code {
// no need to store item, as _default functionality is not needed
// (case/switch expressions don't support the `default` keyword)
CaseExpression._(Case<Expression> item)
: super._(
pattern: item.pattern,
body:
item.body ??
(throw ArgumentError(
'Cases in `switch` expressions must provide '
'a non-null body.',
'body',
)),
guard: item.guard,
);
@override
R accept<R>(covariant ControlBlockVisitor<R> visitor, [R? context]) =>
visitor._visitCaseExpression(this, context);
}
/// **INTERNAL**
/// Base class for `switch` types.
@internal
@optionalTypeArgs
@BuiltValue(instantiable: false)
abstract class Switch<T> implements Code, Spec {
/// The value being matched against.
///
/// ```dart
/// switch (value) {
/// ...
/// }
/// ```
Expression get value;
/// The cases in the `switch` body.
BuiltList<Case<T>> get cases;
/// Convert generic [Case] into an implementation-specific buildable
/// subtype.
//* ignore from test coverage as there is no way to call this.
//* it can't be abstract, otherwise built_value will treat it
//* as a field on the class and throw an error because it's private.
// coverage:ignore-start
Iterable<Code> get _cases =>
throw UnsupportedError('Must be implemented by subclasses');
// coverage:ignore-end
}
/// **INTERNAL**
/// Base class for `switch` builders
@internal
@optionalTypeArgs
abstract class SwitchBuilder<T> {
/// The value being matched against.
///
/// ```dart
/// switch (value) {
/// ...
/// }
/// ```
Expression? value;
/// The cases in the `switch` body.
ListBuilder<Case<T>> cases = ListBuilder();
}
/// **INTERNAL**
///
/// A buildable switch class. [Switch] subtypes are converted into
/// this by [ControlBlockVisitor.visitSwitch] in order to be built.
///
@internal
@optionalTypeArgs
class BuildableSwitch<T> with ControlBlock {
final Expression value;
final Iterable<Code> cases;
BuildableSwitch({required this.value, required this.cases});
@override
ControlExpression get _expression => ControlExpression.switchStatement(value);
@override
Block get body => Block.of(cases);
}
/// Represents a `switch` statement.
///
/// `switch` *statements* are standalone `switch` blocks that
/// use the `case` keyword and execute the case body when matched. Unlike
/// `switch` *expressions*, they do not return any value. See
/// https://dart.dev/language/branches#switch-statements.
///
/// ```dart
/// switch (value) {
/// case value:
/// case otherValue:
/// body;
///
/// case value when guard:
/// body;
/// continue label;
///
/// label:
/// default:
/// body;
/// }
/// ```
/// [Case]s used in a [SwitchStatement] may leave their [Case.body] `null` in
/// order to use case fall-through. They can also specify labels ([Case.label])
/// and guard clauses ([Case.guard]).
///
abstract class SwitchStatement
implements Switch<Code?>, Built<SwitchStatement, SwitchStatementBuilder> {
SwitchStatement._();
/// Build a [SwitchStatement].
factory SwitchStatement([void Function(SwitchStatementBuilder) updates]) =
_$SwitchStatement;
@override
Iterable<Code> get _cases => cases.map(CaseStatement._);
@override
T accept<T>(covariant ControlBlockVisitor<T> visitor, [T? context]) =>
visitor.visitSwitch(this, context);
}
/// Represents a `switch` expression.
///
/// `switch` *expressions* are `switch` blocks that return the body of the
/// matched case and use the arrow (`=>`) syntax. Unlike `switch` statements,
/// they do not use the `case` keyword, and case bodies can only consist of
/// a single expression. See
/// https://dart.dev/language/branches#switch-expressions.
///
/// ```dart
/// final variable = switch (value) {
/// value => body,
/// value when guard => body,
/// _ => body,
/// };
/// ```
///
/// [Case]s used in a [SwitchExpression] must have a non-`null` body. They may
/// contain guard clauses ([Case.guard]) but not labels ([Case.label]). If a
/// label is specified, it will be ignored.
///
abstract class SwitchExpression extends Expression
implements
Switch<Expression>,
Built<SwitchExpression, SwitchExpressionBuilder> {
SwitchExpression._();
/// Build a [SwitchExpression].
factory SwitchExpression([void Function(SwitchExpressionBuilder) updates]) =
_$SwitchExpression;
@override
T accept<T>(covariant ControlBlockVisitor<T> visitor, [T? context]) =>
visitor.visitSwitch(this, context);
@override
Iterable<Code> get _cases => cases.map(CaseExpression._);
}