blob: f4965b48ef6ecfe84b06ca5dd706f189ab0569ce [file] [log] [blame]
// 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.
// @dart = 2.9
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');