blob: 5be1b935271e0014180720ac180b31081c7a9bbc [file] [edit]
// Copyright (c) 2026, 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 'package:meta/meta.dart';
import '../allocator.dart';
import '../base.dart';
import '../visitors.dart';
import 'expression.dart';
import 'expression.dart' as expr;
import 'reference.dart';
import 'reference.dart' as ref;
part 'pattern/binary.dart';
part 'pattern/constant.dart';
part 'pattern/destructuring.dart';
part 'pattern/relational.dart';
part 'pattern/unary.dart';
part 'pattern/variable.dart';
/// A Dart pattern for pattern matching and destructuring.
///
/// See https://dart.dev/language/patterns and the Dart 3 Patterns specification.
abstract class Pattern implements Spec {
const Pattern();
@override
R accept<R>(covariant PatternVisitor<R> visitor, [R? context]);
/// Logical-or pattern: `this || other`.
Pattern or(Pattern other) => BinaryPattern._(this, other, '||');
/// Logical-and pattern: `this && other`.
Pattern and(Pattern other) => BinaryPattern._(this, other, '&&');
/// Cast pattern: `this as type`.
Pattern asA(Reference type) => CastPattern._(this, type);
/// Null-check pattern: `this?`.
Pattern get nullChecked => UnaryPattern._(this, '?');
/// Null-assert pattern: `this!`.
Pattern get nullAsserted => UnaryPattern._(this, '!');
/// Parenthesized pattern: `(this)`.
Pattern get parenthesized => ParenthesizedPattern._(this);
/// Wildcard pattern: `_`.
static const wildcard = WildcardPattern._();
/// Variable pattern: `var name` or `type name`.
static VariablePattern declareVar(String name, {Reference? type}) =>
VariablePattern._(name, type: type);
/// Variable pattern: `final name` or `final type name`.
static VariablePattern declareFinal(String name, {Reference? type}) =>
VariablePattern._(name, type: type, isFinal: true);
/// Typed variable pattern: `type name`.
static VariablePattern typed(String name, Reference type) =>
VariablePattern._(name, type: type);
/// Variable pattern: `var name` or `final name` or `Type name`.
static VariablePattern variable(
String name, {
Reference? type,
bool isFinal = false,
}) => VariablePattern._(name, type: type, isFinal: isFinal);
/// Constant pattern from [expression].
static ConstantPattern constant(
Expression expression, {
bool isConst = false,
}) => ConstantPattern._(expression, isConst: isConst);
/// Constant pattern from a literal [value].
static ConstantPattern literal(Object? value) =>
ConstantPattern._(expr.literal(value));
/// Constant pattern referencing [name].
static ConstantPattern refer(String name, [String? url]) =>
ConstantPattern._(ref.refer(name, url));
/// Relational pattern with [operator] and [operand].
static RelationalPattern relational(String operator, Expression operand) =>
RelationalPattern._(operator, operand);
/// Equality pattern: `== operand`.
static RelationalPattern equalTo(Expression operand) =>
RelationalPattern._('==', operand);
/// Inequality pattern: `!= operand`.
static RelationalPattern notEqualTo(Expression operand) =>
RelationalPattern._('!=', operand);
/// Greater-than pattern: `> operand`.
static RelationalPattern greaterThan(Expression operand) =>
RelationalPattern._('>', operand);
/// Less-than pattern: `< operand`.
static RelationalPattern lessThan(Expression operand) =>
RelationalPattern._('<', operand);
/// Greater-or-equal pattern: `>= operand`.
static RelationalPattern greaterOrEqualTo(Expression operand) =>
RelationalPattern._('>=', operand);
/// Less-or-equal pattern: `<= operand`.
static RelationalPattern lessOrEqualTo(Expression operand) =>
RelationalPattern._('<=', operand);
/// List pattern: `[p1, p2, ...rest]`.
///
/// Pass [rest] to match the rest of the list elements (e.g. `...rest` or
/// `..._`). If [restIndex] is omitted, [rest] defaults to appearing at the
/// end of [elements]. If [restIndex] is provided without [rest], an anonymous
/// `...` rest element is emitted at that index.
///
/// ```dart
/// .list(
/// [.declareVar('a'), .declareVar('b')],
/// rest: .declareVar('rest'),
/// restIndex: 1,
/// )
/// // Emits: [var a, ...var rest, var b]
/// ```
static ListPattern list(
Iterable<Pattern> elements, {
Reference? type,
Pattern? rest,
int? restIndex,
}) {
final elementsList = elements.toList();
final resolvedRestIndex =
restIndex ?? (rest != null ? elementsList.length : null);
if (resolvedRestIndex != null) {
RangeError.checkValidIndex(
resolvedRestIndex,
elementsList,
'restIndex',
elementsList.length + 1,
);
}
return ListPattern._(
elementsList,
type: type,
rest: rest,
restIndex: resolvedRestIndex,
);
}
/// Map pattern: `{'key': pattern}`.
static MapPattern map(
Map<Expression, Pattern> entries, {
(Reference, Reference)? type,
}) => MapPattern._(
entries.entries.map((e) => MapPatternEntry(e.key, e.value)).toList(),
type: type,
);
/// Record pattern: `(p1, name: p2)`.
static RecordPattern record({
Iterable<Pattern> positional = const [],
Map<String, Pattern> named = const {},
}) => RecordPattern._(positional: positional.toList(), named: named);
/// Object pattern: `SomeClass(p1, name: p2)`.
static ObjectPattern object(
Reference type, {
Iterable<Pattern> positional = const [],
Map<String, Pattern> named = const {},
}) => ObjectPattern._(type, positional: positional.toList(), named: named);
}
/// Knowledge of different types of patterns in Dart.
abstract class PatternVisitor<T>
implements SpecVisitor<T>, ExpressionVisitor<T> {
T visitBinaryPattern(BinaryPattern pattern, [T? context]);
T visitUnaryPattern(UnaryPattern pattern, [T? context]);
T visitCastPattern(CastPattern pattern, [T? context]);
T visitRelationalPattern(RelationalPattern pattern, [T? context]);
T visitConstantPattern(ConstantPattern pattern, [T? context]);
T visitVariablePattern(VariablePattern pattern, [T? context]);
T visitWildcardPattern(WildcardPattern pattern, [T? context]);
T visitParenthesizedPattern(ParenthesizedPattern pattern, [T? context]);
T visitListPattern(ListPattern pattern, [T? context]);
T visitMapPattern(MapPattern pattern, [T? context]);
T visitRecordPattern(RecordPattern pattern, [T? context]);
T visitObjectPattern(ObjectPattern pattern, [T? context]);
}
/// Knowledge of how to write valid Dart code from [PatternVisitor].
abstract mixin class PatternEmitter implements PatternVisitor<StringSink> {
@protected
Allocator get allocator;
@override
StringSink visitBinaryPattern(BinaryPattern pattern, [StringSink? output]) {
output ??= StringBuffer();
pattern.left.accept(this, output);
output.write(' ${pattern.operator} ');
pattern.right.accept(this, output);
return output;
}
@override
StringSink visitUnaryPattern(UnaryPattern pattern, [StringSink? output]) {
output ??= StringBuffer();
pattern.pattern.accept(this, output);
output.write(pattern.operator);
return output;
}
@override
StringSink visitCastPattern(CastPattern pattern, [StringSink? output]) {
output ??= StringBuffer();
pattern.pattern.accept(this, output);
output.write(' as ');
pattern.type.accept(this, output);
return output;
}
@override
StringSink visitRelationalPattern(
RelationalPattern pattern, [
StringSink? output,
]) {
output ??= StringBuffer();
output.write('${pattern.operator} ');
pattern.operand.accept(this, output);
return output;
}
@override
StringSink visitConstantPattern(
ConstantPattern pattern, [
StringSink? output,
]) {
output ??= StringBuffer();
if (pattern.isConst) {
output.write('const (');
pattern.expression.accept(this, output);
output.write(')');
} else {
pattern.expression.accept(this, output);
}
return output;
}
@override
StringSink visitVariablePattern(
VariablePattern pattern, [
StringSink? output,
]) {
output ??= StringBuffer();
if (pattern.isFinal) {
output.write('final ');
} else if (pattern.type == null) {
output.write('var ');
}
if (pattern.type != null) {
pattern.type!.accept(this, output);
output.write(' ');
}
output.write(pattern.name);
return output;
}
@override
StringSink visitWildcardPattern(
WildcardPattern pattern, [
StringSink? output,
]) {
output ??= StringBuffer();
output.write('_');
return output;
}
@override
StringSink visitParenthesizedPattern(
ParenthesizedPattern pattern, [
StringSink? output,
]) {
output ??= StringBuffer();
output.write('(');
pattern.pattern.accept(this, output);
output.write(')');
return output;
}
@override
StringSink visitListPattern(ListPattern pattern, [StringSink? output]) {
output ??= StringBuffer();
if (pattern.type != null) {
output.write('<');
pattern.type!.accept(this, output);
output.write('>');
}
output.write('[');
final elements = pattern.elements;
final restIndex = pattern.restIndex;
final hasRest = pattern.hasRest;
final count = elements.length + (hasRest ? 1 : 0);
var elementIdx = 0;
for (var i = 0; i < count; i++) {
if (i > 0) output.write(', ');
if (hasRest && i == restIndex) {
output.write('...');
if (pattern.rest != null) {
pattern.rest!.accept(this, output);
}
} else {
elements[elementIdx++].accept(this, output);
}
}
output.write(']');
return output;
}
@override
StringSink visitMapPattern(MapPattern pattern, [StringSink? output]) {
output ??= StringBuffer();
if (pattern.type != null) {
output.write('<');
pattern.type!.$1.accept(this, output);
output.write(', ');
pattern.type!.$2.accept(this, output);
output.write('>');
}
output.write('{');
for (var i = 0; i < pattern.entries.length; i++) {
if (i > 0) output.write(', ');
final entry = pattern.entries[i];
entry.key.accept(this, output);
output.write(': ');
entry.value.accept(this, output);
}
output.write('}');
return output;
}
@override
StringSink visitRecordPattern(RecordPattern pattern, [StringSink? output]) {
output ??= StringBuffer();
output.write('(');
var first = true;
for (final p in pattern.positional) {
if (!first) output.write(', ');
p.accept(this, output);
first = false;
}
if (pattern.positional.length == 1 && pattern.named.isEmpty) {
output.write(',');
}
for (final entry in pattern.named.entries) {
if (!first) output.write(', ');
output.write('${entry.key}: ');
entry.value.accept(this, output);
first = false;
}
output.write(')');
return output;
}
@override
StringSink visitObjectPattern(ObjectPattern pattern, [StringSink? output]) {
output ??= StringBuffer();
pattern.type.accept(this, output);
output.write('(');
var first = true;
for (final p in pattern.positional) {
if (!first) output.write(', ');
p.accept(this, output);
first = false;
}
for (final entry in pattern.named.entries) {
if (!first) output.write(', ');
output.write('${entry.key}: ');
entry.value.accept(this, output);
first = false;
}
output.write(')');
return output;
}
}