| // 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; |
| } |
| } |