| // Copyright (c) 2021, 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 'dart:convert'; | |
| import "package:expect/expect.dart"; | |
| /// Helper class for determining when no argument is passed to a function. | |
| class Absent { | |
| const Absent(); | |
| } | |
| const absent = Absent(); | |
| /// Returns an approximate representation of the syntax that was used to | |
| /// construct [x]. Extra parentheses are used around unary and binary | |
| /// expressions to disambiguate precedence. | |
| String syntax(Object? x) { | |
| var knownSyntax = SyntaxTracker.known[x]; | |
| if (knownSyntax != null) return knownSyntax; | |
| if (x is SyntaxTracker || x is num) { | |
| return x.toString(); | |
| } else if (x is List) { | |
| return '[${x.map(syntax).join(', ')}]'; | |
| } else if (x is Set) { | |
| if (x.isEmpty) return 'Set()'; | |
| return '{${x.map(syntax).join(', ')}}'; | |
| } else if (x is Map) { | |
| if (x.isEmpty) return '{}'; | |
| var entries = x.entries.map( | |
| (entry) => '${syntax(entry.key)}: ${syntax(entry.value)}', | |
| ); | |
| return '{ ${entries.join(', ')} }'; | |
| } else if (x is String) { | |
| return json.encode(x); | |
| } else { | |
| throw UnimplementedError( | |
| 'Unknown syntax for $x. ' | |
| 'Consider adding to `SyntaxTracker.known`.', | |
| ); | |
| } | |
| } | |
| /// Instances of this class record the syntax of operations performed on them. | |
| class SyntaxTracker { | |
| final String _syntax; | |
| SyntaxTracker(this._syntax); | |
| String toString() => _syntax; | |
| static String args([Object? x = absent, Object? y = absent]) => | |
| '(${[x, y].where((v) => v is! Absent).join(', ')})'; | |
| static String typeArgs(Type T, Type U) => | |
| T == dynamic && U == dynamic ? '' : '<${syntax(T)}, ${syntax(U)}>'; | |
| /// Simple objects with known syntactic representations. Tests can add to | |
| /// this map. | |
| static Map<Object?, String> known = { | |
| true: 'true', | |
| false: 'false', | |
| null: 'null', | |
| }; | |
| } | |
| /// Extension allowing us to detect the syntax of most operations performed on | |
| /// arbitrary types. | |
| extension SyntaxTrackingExtension on Object? { | |
| Object? method<T, U>([ | |
| Object? x = absent, | |
| Object? y = absent, | |
| ]) => SyntaxTracker( | |
| '${syntax(this)}.method${SyntaxTracker.typeArgs(T, U)}${SyntaxTracker.args(x, y)}', | |
| ); | |
| Object? call<T, U>([Object? x = absent, Object? y = absent]) => SyntaxTracker( | |
| '${syntax(this)}${SyntaxTracker.typeArgs(T, U)}${SyntaxTracker.args(x, y)}', | |
| ); | |
| Object? get getter => SyntaxTracker('${syntax(this)}.getter'); | |
| Object? operator [](Object? index) => | |
| SyntaxTracker('${syntax(this)}[${syntax(index)}]'); | |
| Object? operator -() => SyntaxTracker('(-${syntax(this)})'); | |
| Object? operator ~() => SyntaxTracker('(~${syntax(this)})'); | |
| Object? operator *(Object? other) => | |
| SyntaxTracker('(${syntax(this)} * ${syntax(other)})'); | |
| Object? operator /(Object? other) => | |
| SyntaxTracker('(${syntax(this)} / ${syntax(other)})'); | |
| Object? operator ~/(Object? other) => | |
| SyntaxTracker('(${syntax(this)} ~/ ${syntax(other)})'); | |
| Object? operator %(Object? other) => | |
| SyntaxTracker('(${syntax(this)} % ${syntax(other)})'); | |
| Object? operator +(Object? other) => | |
| SyntaxTracker('(${syntax(this)} + ${syntax(other)})'); | |
| Object? operator -(Object? other) => | |
| SyntaxTracker('(${syntax(this)} - ${syntax(other)})'); | |
| Object? operator <<(Object? other) => | |
| SyntaxTracker('(${syntax(this)} << ${syntax(other)})'); | |
| Object? operator >>(Object? other) => | |
| SyntaxTracker('(${syntax(this)} >> ${syntax(other)})'); | |
| Object? operator &(Object? other) => | |
| SyntaxTracker('(${syntax(this)} & ${syntax(other)})'); | |
| Object? operator ^(Object? other) => | |
| SyntaxTracker('(${syntax(this)} ^ ${syntax(other)})'); | |
| Object? operator |(Object? other) => | |
| SyntaxTracker('(${syntax(this)} | ${syntax(other)})'); | |
| Object? operator <(Object? other) => | |
| SyntaxTracker('(${syntax(this)} < ${syntax(other)})'); | |
| Object? operator >(Object? other) => | |
| SyntaxTracker('(${syntax(this)} > ${syntax(other)})'); | |
| Object? operator <=(Object? other) => | |
| SyntaxTracker('(${syntax(this)} <= ${syntax(other)})'); | |
| Object? operator >=(Object? other) => | |
| SyntaxTracker('(${syntax(this)} >= ${syntax(other)})'); | |
| } | |
| void checkSyntax(Object? x, String expectedSyntax) { | |
| Expect.equals(expectedSyntax, syntax(x)); | |
| } | |
| Object? f<T, U>([Object? x = absent, Object? y = absent]) => SyntaxTracker( | |
| 'f${SyntaxTracker.typeArgs(T, U)}${SyntaxTracker.args(x, y)}', | |
| ); | |
| Object? x = SyntaxTracker('x'); |