// 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 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([Object? x = absent, Object? y = absent]) => SyntaxTracker( '${syntax(this)}.method${SyntaxTracker.typeArgs(T, U)}${SyntaxTracker.args(x, y)}'); Object? call([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([Object? x = absent, Object? y = absent]) => SyntaxTracker( 'f${SyntaxTracker.typeArgs(T, U)}${SyntaxTracker.args(x, y)}'); Object? x = SyntaxTracker('x');