blob: 586edc0ddd8a549add3ccba62b6925fee212b227 [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.
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');