blob: f6c90a4127049288c5844fe478783dce60615fb4 [file] [log] [blame]
// Copyright (c) 2024, 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:_fe_analyzer_shared/src/metadata/ast.dart';
import '../metadata/evaluate.dart';
/// Returns the arguments expression, if [expression] is of the form
/// `Helper(foo)`, and [expression] otherwise.
///
/// This is used to simplify testing of metadata expression where most can
/// only occur as arguments to a constant constructor invocation. To avoid the
/// clutter, these tests use a `Helper` class which is omitted in the test
/// output.
Expression unwrap(Expression expression) {
if (expression case ConstructorInvocation(
type: NamedTypeAnnotation(reference: ClassReference(name: 'Helper')),
constructor: ConstructorReference(name: 'new'),
arguments: [PositionalArgument(expression: Expression argument)],
)) {
return argument;
}
return expression;
}
/// Creates a list containing structured and readable textual representation of
/// the [resolved] expression and the result of evaluating [resolved].
///
/// If [getFieldInitializer] is provided, it is used to dereference constant
/// field references during evaluation.
List<String> evaluationToText(
Expression resolved, {
GetFieldInitializer? getFieldInitializer,
}) {
List<String> list = [];
Expression unwrappedResolved = unwrap(resolved);
list.add('resolved=${expressionToText(unwrappedResolved)}');
Map<FieldReference, Expression> dereferences = {};
Expression evaluated = evaluateExpression(
unwrappedResolved,
getFieldInitializer: getFieldInitializer,
dereferences: dereferences,
);
list.add('evaluate=${expressionToText(evaluated)}');
for (MapEntry<FieldReference, Expression> entry in dereferences.entries) {
list.add('${entry.key.name}=${expressionToText(entry.value)}');
}
return list;
}
/// Creates a list containing structured and readable textual representation of
/// the [unresolved] and [resolved] expressions.
List<String> expressionsToText({
required Expression unresolved,
required Expression resolved,
}) {
List<String> list = [];
list.add('unresolved=${expressionToText(unwrap(unresolved))}');
// The identifiers in [expression] haven't been resolved, so
// we call [Expression.resolve] to convert the expression into
// its resolved equivalent.
Expression lateResolved = unresolved.resolve() ?? unresolved;
Expression unwrappedResolved = unwrap(resolved);
String earlyResolvedText = expressionToText(unwrappedResolved);
String lateResolvedText = expressionToText(unwrap(lateResolved));
// These should always be the same. If not we include both to
// signal the error.
if (earlyResolvedText == lateResolvedText) {
list.add('resolved=${earlyResolvedText}');
} else {
list.add('early-resolved=${earlyResolvedText}');
list.add('late-resolved=${lateResolvedText}');
}
return list;
}
/// Creates a structured and readable textual representation of [expression].
String expressionToText(Expression expression) {
Writer writer = new Writer();
return writer.expressionToText(expression);
}
/// Helper class for creating a structured and readable textual representation
/// of an [Expression] node.
class Writer {
StringBuffer sb = new StringBuffer();
bool _lastWasNewLine = true;
int _indentLevel = 0;
String expressionToText(Expression expression) {
sb.clear();
_expressionToText(expression);
return sb.toString();
}
void _incIndent({int amount = 1, required bool addNewLine}) {
_indentLevel += amount;
if (addNewLine) {
_addNewLine();
}
}
void _decIndent({int amount = 1, bool addNewLine = false}) {
_indentLevel -= amount;
if (addNewLine) {
_addNewLine();
}
}
void _addNewLine() {
sb.write('\n');
_lastWasNewLine = true;
}
void _write(String value) {
if (_lastWasNewLine) {
sb.write(' ' * _indentLevel);
}
sb.write(value);
_lastWasNewLine = false;
}
void _expressionToText(Expression expression) {
switch (expression) {
case InvalidExpression():
_write('InvalidExpression()');
case StaticGet():
_write('StaticGet(');
_referenceToText(expression.reference);
_write(')');
case FunctionTearOff():
_write('FunctionTearOff(');
_referenceToText(expression.reference);
_write(')');
case ConstructorTearOff():
_write('ConstructorTearOff(');
_typeAnnotationToText(expression.type);
_write('.');
_referenceToText(expression.reference);
_write(')');
case ConstructorInvocation():
_write('ConstructorInvocation(');
_incIndent(addNewLine: true);
_typeAnnotationToText(expression.type);
_write('.');
_referenceToText(expression.constructor);
_argumentsToText(expression.arguments);
_decIndent();
_write(')');
case IntegerLiteral():
_write('IntegerLiteral(');
if (expression.text != null) {
_write(expression.text!);
} else {
_write('value=${expression.value}');
}
_write(')');
case DoubleLiteral():
_write('DoubleLiteral(');
_write(expression.text);
_write(')');
case BooleanLiteral():
_write('BooleanLiteral(');
_write('${expression.value}');
_write(')');
case NullLiteral():
_write('NullLiteral()');
case SymbolLiteral():
_write('SymbolLiteral(');
_write(expression.parts.join());
_write(')');
case StringLiteral():
_write('StringLiteral(');
_stringPartsToText(expression.parts);
_write(')');
case AdjacentStringLiterals():
_write('AdjacentStringLiterals(');
_incIndent(addNewLine: false);
_expressionsToText(expression.expressions, delimiter: '');
_decIndent();
_write(')');
case ImplicitInvocation():
_write('ImplicitInvocation(');
_incIndent(addNewLine: true);
_expressionToText(expression.receiver);
_typeArgumentsToText(expression.typeArguments);
_argumentsToText(expression.arguments);
_decIndent();
_write(')');
case StaticInvocation():
_write('StaticInvocation(');
_incIndent(addNewLine: true);
_referenceToText(expression.function);
_typeArgumentsToText(expression.typeArguments);
_argumentsToText(expression.arguments);
_decIndent();
_write(')');
case Instantiation():
_write('Instantiation(');
_expressionToText(expression.receiver);
_typeArgumentsToText(expression.typeArguments);
_write(')');
case MethodInvocation():
_write('MethodInvocation(');
_incIndent(addNewLine: true);
_expressionToText(expression.receiver);
_write('.');
_write(expression.name);
_typeArgumentsToText(expression.typeArguments);
_argumentsToText(expression.arguments);
_decIndent();
_write(')');
case PropertyGet():
_write('PropertyGet(');
_expressionToText(expression.receiver);
_write('.');
_write(expression.name);
_write(')');
case NullAwarePropertyGet():
_write('NullAwarePropertyGet(');
_expressionToText(expression.receiver);
_write('?.');
_write(expression.name);
_write(')');
case TypeLiteral():
_write('TypeLiteral(');
_typeAnnotationToText(expression.typeAnnotation);
_write(')');
case ParenthesizedExpression():
_write('ParenthesizedExpression(');
_expressionToText(expression.expression);
_write(')');
case ConditionalExpression():
_write('ConditionalExpression(');
_incIndent(addNewLine: true);
_expressionToText(expression.condition);
_incIndent(addNewLine: true);
_write('? ');
_incIndent(addNewLine: false);
_expressionToText(expression.then);
_decIndent(addNewLine: true);
_write(': ');
_incIndent(addNewLine: false);
_expressionToText(expression.otherwise);
_decIndent(amount: 3);
_write(')');
case ListLiteral():
_write('ListLiteral(');
_typeArgumentsToText(expression.typeArguments);
_write('[');
_elementsToText(expression.elements);
_write('])');
case SetOrMapLiteral():
_write('SetOrMapLiteral(');
_typeArgumentsToText(expression.typeArguments);
_write('{');
_elementsToText(expression.elements);
_write('})');
case RecordLiteral():
_write('RecordLiteral(');
_recordFieldsToText(expression.fields);
_write(')');
break;
case IfNull():
_write('IfNull(');
_incIndent(addNewLine: true);
_expressionToText(expression.left);
_addNewLine();
_write(' ?? ');
_addNewLine();
_expressionToText(expression.right);
_decIndent(addNewLine: true);
_write(')');
case LogicalExpression():
_write('LogicalExpression(');
_expressionToText(expression.left);
_write(' ');
_write(expression.operator.text);
_write(' ');
_expressionToText(expression.right);
_write(')');
case EqualityExpression():
_write('EqualityExpression(');
_expressionToText(expression.left);
_write(expression.isNotEquals ? ' != ' : ' == ');
_expressionToText(expression.right);
_write(')');
case BinaryExpression():
_write('BinaryExpression(');
_expressionToText(expression.left);
_write(' ');
_write(expression.operator.text);
_write(' ');
_expressionToText(expression.right);
_write(')');
case UnaryExpression():
_write('UnaryExpression(');
_write(expression.operator.text);
_expressionToText(expression.expression);
_write(')');
case IsTest():
_write('IsTest(');
_expressionToText(expression.expression);
_write(' is');
if (expression.isNot) {
_write('!');
}
_write(' ');
_typeAnnotationToText(expression.type);
_write(')');
case AsExpression():
_write('AsExpression(');
_expressionToText(expression.expression);
_write(' as ');
_typeAnnotationToText(expression.type);
_write(')');
case NullCheck():
_write('NullCheck(');
_expressionToText(expression.expression);
_write(')');
case UnresolvedExpression():
_write('UnresolvedExpression(');
_unresolvedToText(expression.unresolved);
_write(')');
}
}
void _referenceToText(Reference reference) {
switch (reference) {
case FieldReference():
_write(reference.name);
case FunctionReference():
_write(reference.name);
case ConstructorReference():
_write(reference.name);
case TypeReference():
_write(reference.name);
case ClassReference():
_write(reference.name);
case TypedefReference():
_write(reference.name);
case ExtensionReference():
_write(reference.name);
case ExtensionTypeReference():
_write(reference.name);
case EnumReference():
_write(reference.name);
case MixinReference():
_write(reference.name);
case FunctionTypeParameterReference():
_write(reference.name);
}
}
void _typeAnnotationToText(TypeAnnotation typeAnnotation) {
switch (typeAnnotation) {
case NamedTypeAnnotation():
_referenceToText(typeAnnotation.reference);
_typeArgumentsToText(typeAnnotation.typeArguments);
case NullableTypeAnnotation():
_typeAnnotationToText(typeAnnotation.typeAnnotation);
_write('?');
case VoidTypeAnnotation():
_write('void');
case DynamicTypeAnnotation():
_write('dynamic');
case InvalidTypeAnnotation():
_write('{invalid-type-annotation}');
case UnresolvedTypeAnnotation():
_write('{unresolved-type-annotation:');
_unresolvedToText(typeAnnotation.unresolved);
_write('}');
case FunctionTypeAnnotation(:TypeAnnotation? returnType):
if (returnType != null) {
_typeAnnotationToText(returnType);
_write(' ');
}
_write('Function');
if (typeAnnotation.typeParameters.isNotEmpty) {
_write('<');
}
_formalParametersToText(typeAnnotation.formalParameters);
case FunctionTypeParameterType():
_write(typeAnnotation.functionTypeParameter.name);
case RecordTypeAnnotation():
_write('(');
String comma = '';
for (RecordTypeEntry entry in typeAnnotation.positional) {
_write(comma);
_metadataToText(entry.metadata);
_typeAnnotationToText(entry.typeAnnotation);
if (entry.name != null) {
_write(' ');
_write(entry.name!);
}
comma = ', ';
}
if (typeAnnotation.named.isNotEmpty) {
_write(comma);
sb.write('{');
comma = '';
for (RecordTypeEntry entry in typeAnnotation.named) {
_write(comma);
_metadataToText(entry.metadata);
_typeAnnotationToText(entry.typeAnnotation);
if (entry.name != null) {
_write(' ');
_write(entry.name!);
}
comma = ', ';
}
sb.write('}');
} else if (typeAnnotation.positional.length == 1) {
sb.write(',');
}
_write(')');
}
}
void _typeArgumentsToText(List<TypeAnnotation> typeArguments) {
if (typeArguments.isNotEmpty) {
_write('<');
String comma = '';
for (TypeAnnotation typeArgument in typeArguments) {
_write(comma);
_typeAnnotationToText(typeArgument);
comma = ',';
}
_write('>');
}
}
void _formalParametersToText(List<FormalParameter> formalParameters) {
_write('(');
String comma = '';
bool inNamed = false;
for (FormalParameter formalParameter in formalParameters) {
_write(comma);
if (!inNamed && formalParameter.isNamed) {
_write('{');
inNamed = true;
}
if (formalParameter.metadata.isNotEmpty) {
_metadataToText(formalParameter.metadata);
}
if (inNamed && formalParameter.isRequired) {
_write('required ');
}
if (formalParameter.typeAnnotation != null) {
_typeAnnotationToText(formalParameter.typeAnnotation!);
if (formalParameter.name != null) {
_write(' ');
}
}
if (formalParameter.name != null) {
_write(formalParameter.name!);
}
if (formalParameter.defaultValue != null) {
_write(' = ');
_expressionToText(formalParameter.defaultValue!);
}
comma = ', ';
}
if (inNamed) {
_write('}');
}
_write(')');
}
void _argumentToText(Argument argument) {
switch (argument) {
case PositionalArgument():
_expressionToText(argument.expression);
case NamedArgument():
_write(argument.name);
_write(': ');
_expressionToText(argument.expression);
}
}
static final int _newLine = '\n'.codeUnitAt(0);
static final int _backSlash = r'\'.codeUnitAt(0);
static final int _dollar = r'$'.codeUnitAt(0);
static final int _quote = "'".codeUnitAt(0);
String _escape(String text) {
StringBuffer sb = new StringBuffer();
for (int c in text.codeUnits) {
if (c == _newLine) {
sb.write(r'\n');
} else if (c == _backSlash) {
sb.write(r'\\');
} else if (c == _dollar) {
sb.write(r'\$');
} else if (c == _quote) {
sb.write(r"\'");
} else if (c < 0x20 || c >= 0x80) {
String hex = c.toRadixString(16);
hex = ("0" * (4 - hex.length)) + hex;
sb.write('\\u$hex');
} else {
sb.writeCharCode(c);
}
}
return sb.toString();
}
void _stringPartsToText(List<StringLiteralPart> parts) {
_write("'");
for (StringLiteralPart part in parts) {
switch (part) {
case StringPart():
_write(_escape(part.text));
case InterpolationPart():
_write(r'${');
_expressionToText(part.expression);
_write('}');
}
}
_write("'");
}
void _listToText<E>(
List<E> list,
void Function(E) elementToText, {
required String delimiter,
bool useNewLine = false,
String prefix = '',
}) {
if (list.isNotEmpty) {
bool hasNewLine = useNewLine && list.length > 1;
if (hasNewLine) {
_incIndent(addNewLine: false);
}
String comma = '';
for (E element in list) {
_write(comma);
if (hasNewLine) {
_addNewLine();
}
_write(prefix);
elementToText(element);
comma = delimiter;
}
if (hasNewLine) {
_decIndent();
}
}
}
void _argumentsToText(List<Argument> arguments) {
_write('(');
_listToText(arguments, _argumentToText, delimiter: ', ', useNewLine: true);
_write(')');
}
void _expressionsToText(
List<Expression> expressions, {
required String delimiter,
String prefix = '',
}) {
_listToText(
expressions,
_expressionToText,
delimiter: delimiter,
useNewLine: true,
);
}
void _metadataToText(List<Expression> metadata) {
if (metadata.isNotEmpty) {
_expressionsToText(metadata, delimiter: ' ', prefix: '@');
_write(' ');
}
}
void _elementToText(Element element) {
switch (element) {
case ExpressionElement():
_write('ExpressionElement(');
if (element.isNullAware) {
_write('?');
}
_expressionToText(element.expression);
_write(')');
case MapEntryElement():
_write('MapEntryElement(');
if (element.isNullAwareKey) {
_write('?');
}
_expressionToText(element.key);
_write(':');
if (element.isNullAwareValue) {
_write('?');
}
_expressionToText(element.value);
_write(')');
case SpreadElement():
_write('SpreadElement(');
if (element.isNullAware) {
_write('?');
}
_write('...');
_expressionToText(element.expression);
_write(')');
case IfElement():
_write('IfElement(');
_incIndent(addNewLine: true);
_expressionToText(element.condition);
_write(',');
_addNewLine();
_elementToText(element.then);
if (element.otherwise != null) {
_write(',');
_addNewLine();
_elementToText(element.otherwise!);
}
_decIndent();
_write(')');
}
}
void _elementsToText(List<Element> elements) {
_listToText(elements, _elementToText, delimiter: ', ', useNewLine: true);
}
void _recordFieldToText(RecordField field) {
switch (field) {
case RecordNamedField():
_write(field.name);
_write(': ');
_expressionToText(field.expression);
case RecordPositionalField():
_expressionToText(field.expression);
}
}
void _recordFieldsToText(List<RecordField> fields) {
_listToText(fields, _recordFieldToText, delimiter: ', ');
}
void _unresolvedToText(Unresolved unresolved) {
switch (unresolved) {
case UnresolvedIdentifier():
_write('UnresolvedIdentifier(');
_write(unresolved.name);
_write(')');
case UnresolvedAccess():
_write('UnresolvedAccess(');
_incIndent(addNewLine: true);
_protoToText(unresolved.prefix);
_write('.');
_write(unresolved.name);
_decIndent();
_write(')');
case UnresolvedInstantiate():
_write('UnresolvedInstantiate(');
_incIndent(addNewLine: true);
_protoToText(unresolved.prefix);
_typeArgumentsToText(unresolved.typeArguments);
_decIndent();
_write(')');
case UnresolvedInvoke():
_write('UnresolvedInvoke(');
_incIndent(addNewLine: true);
_protoToText(unresolved.prefix);
_addNewLine();
_argumentsToText(unresolved.arguments);
_decIndent();
_write(')');
}
}
void _protoToText(Proto proto) {
switch (proto) {
case UnresolvedIdentifier():
_unresolvedToText(proto);
case UnresolvedAccess():
_unresolvedToText(proto);
case UnresolvedInstantiate():
_unresolvedToText(proto);
case UnresolvedInvoke():
_unresolvedToText(proto);
case ClassProto():
_write('ClassProto(');
_referenceToText(proto.reference);
_write(')');
case GenericClassProto():
_write('GenericClassProto(');
_referenceToText(proto.reference);
_typeArgumentsToText(proto.typeArguments);
_write(')');
case EnumProto():
_write('EnumProto(');
_referenceToText(proto.reference);
_write(')');
case GenericEnumProto():
_write('GenericEnumProto(');
_referenceToText(proto.reference);
_typeArgumentsToText(proto.typeArguments);
_write(')');
case MixinProto():
_write('MixinProto(');
_referenceToText(proto.reference);
_write(')');
case GenericMixinProto():
_write('GenericMixinProto(');
_referenceToText(proto.reference);
_typeArgumentsToText(proto.typeArguments);
_write(')');
case ExtensionProto():
_write('ExtensionProto(');
_referenceToText(proto.reference);
_write(')');
case ExtensionTypeProto():
_write('ExtensionTypeProto(');
_referenceToText(proto.reference);
_write(')');
case GenericExtensionTypeProto():
_write('GenericExtensionTypeProto(');
_referenceToText(proto.reference);
_typeArgumentsToText(proto.typeArguments);
_write(')');
case TypedefProto():
_write('TypedefProto(');
_referenceToText(proto.reference);
_write(')');
case GenericTypedefProto():
_write('GenericTypedefProto(');
_referenceToText(proto.reference);
_typeArgumentsToText(proto.typeArguments);
_write(')');
case VoidProto():
_write('VoidProto(');
_referenceToText(proto.reference);
_write(')');
case DynamicProto():
_write('DynamicProto(');
_referenceToText(proto.reference);
_write(')');
case ConstructorProto():
_write('ConstructorProto(');
_referenceToText(proto.type);
_referenceToText(proto.reference);
_typeArgumentsToText(proto.typeArguments);
_write(')');
case FieldProto():
_write('FieldProto(');
_referenceToText(proto.reference);
_write(')');
case FunctionProto():
_write('FunctionProto(');
_referenceToText(proto.reference);
_write(')');
case FunctionInstantiationProto():
_write('FunctionInstantiationProto(');
_referenceToText(proto.reference);
_typeArgumentsToText(proto.typeArguments);
_write(')');
case PrefixProto():
_write('PrefixProto(');
_write(proto.prefix);
_write(')');
case IdentifierProto():
_write('IdentifierProto(');
_write(proto.text);
if (proto.typeArguments != null) {
_typeArgumentsToText(proto.typeArguments!);
}
if (proto.arguments != null) {
_argumentsToText(proto.arguments!);
}
_write(')');
case InstanceAccessProto():
_write('InstanceAccessProto(');
_protoToText(proto.receiver);
if (proto.isNullAware) {
_write('?.');
} else {
_write('.');
}
_write(proto.text);
_write(')');
case ExpressionInstantiationProto():
_write('ExpressionInstantiationProto(');
_protoToText(proto.receiver);
_typeArgumentsToText(proto.typeArguments);
_write(')');
case InstanceInvocationProto():
_write('InstanceInvocationProto(');
_protoToText(proto.receiver);
_typeArgumentsToText(proto.typeArguments);
_argumentsToText(proto.arguments);
_write(')');
case InvalidAccessProto():
_write('InvalidAccessProto(');
_protoToText(proto.receiver);
_write(proto.text);
_write(')');
case InvalidInstantiationProto():
_write('InvalidInstantiationProto(');
_protoToText(proto.receiver);
_typeArgumentsToText(proto.typeArguments);
_write(')');
case InvalidInvocationProto():
_write('InvalidInvocationProto(');
_protoToText(proto.receiver);
_typeArgumentsToText(proto.typeArguments);
_argumentsToText(proto.arguments);
_write(')');
case ExpressionProto():
_write('ExpressionProto(');
_expressionToText(proto.expression);
_write(')');
case FunctionTypeParameterProto():
_write('FunctionTypeParameterProto(');
_write(proto.functionTypeParameter.name);
_write(')');
}
}
}