| // 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 'ast.dart'; |
| |
| typedef GetFieldInitializer = |
| Expression? Function(FieldReference fieldReference); |
| |
| /// Evaluates an [expression] based on the semantics that can be deduced from |
| /// the syntax. |
| /// |
| /// If [getFieldInitializer] is provided, it is used to get constant initializer |
| /// expressions of const [StaticGet]s in [expression]. The evaluated constant |
| /// initializer expressions are used as the evaluation result of the |
| /// [StaticGet]s. |
| /// |
| /// If [dereferences] is provided, the field references of [StaticGet]s, for |
| /// which [getFieldInitializer] has provided a constant initializer |
| /// [Expression], are mapped to there corresponding in constant initializer |
| /// [Expression]s in [dereferences]. |
| Expression evaluateExpression( |
| Expression expression, { |
| GetFieldInitializer? getFieldInitializer, |
| Map<FieldReference, Expression>? dereferences, |
| }) { |
| return new Evaluator( |
| getFieldInitializer: getFieldInitializer, |
| dereferences: dereferences, |
| ).evaluate(expression); |
| } |
| |
| class Evaluator { |
| final GetFieldInitializer? _getFieldInitializer; |
| final Map<FieldReference, Expression>? _dereferences; |
| |
| Evaluator({ |
| required GetFieldInitializer? getFieldInitializer, |
| required Map<FieldReference, Expression>? dereferences, |
| }) : _getFieldInitializer = getFieldInitializer, |
| _dereferences = dereferences; |
| |
| Expression evaluate(Expression expression) { |
| return _visitExpression(expression); |
| } |
| |
| Expression _visitExpression(Expression expression) { |
| switch (expression) { |
| case StaticGet(): |
| if (_getFieldInitializer != null) { |
| Expression? result = _getFieldInitializer(expression.reference); |
| if (result != null) { |
| if (_dereferences != null) { |
| _dereferences[expression.reference] = result; |
| } |
| return _visitExpression(result); |
| } |
| } |
| return expression; |
| case InvalidExpression(): |
| case FunctionTearOff(): |
| case ConstructorTearOff(): |
| case IntegerLiteral(): |
| case DoubleLiteral(): |
| case BooleanLiteral(): |
| case NullLiteral(): |
| case SymbolLiteral(): |
| return expression; |
| case ConstructorInvocation(): |
| return new ConstructorInvocation( |
| expression.type, |
| expression.constructor, |
| _visitArguments(expression.arguments), |
| ); |
| case StringLiteral(): |
| return _visitStringLiteral(expression); |
| case AdjacentStringLiterals(): |
| return _visitAdjacentStringLiterals(expression); |
| case ImplicitInvocation(): |
| return new ImplicitInvocation( |
| _visitExpression(expression.receiver), |
| expression.typeArguments, |
| _visitArguments(expression.arguments), |
| ); |
| case StaticInvocation(): |
| return new StaticInvocation( |
| expression.function, |
| expression.typeArguments, |
| _visitArguments(expression.arguments), |
| ); |
| case Instantiation(): |
| return new Instantiation( |
| _visitExpression(expression.receiver), |
| expression.typeArguments, |
| ); |
| case MethodInvocation(): |
| return new MethodInvocation( |
| _visitExpression(expression.receiver), |
| expression.name, |
| expression.typeArguments, |
| _visitArguments(expression.arguments), |
| ); |
| case PropertyGet(): |
| Expression receiver = _visitExpression(expression.receiver); |
| if (expression.name == 'length') { |
| if (receiver case StringLiteral(parts: [StringPart(:String text)])) { |
| return new IntegerLiteral.fromValue(text.length); |
| } |
| } |
| return new PropertyGet(receiver, expression.name); |
| case NullAwarePropertyGet(): |
| Expression receiver = _visitExpression(expression.receiver); |
| return switch (_isNull(receiver)) { |
| NullValue.isNull => new NullLiteral(), |
| NullValue.isNonNull => _visitExpression( |
| new PropertyGet(receiver, expression.name), |
| ), |
| NullValue.unknown => new NullAwarePropertyGet( |
| receiver, |
| expression.name, |
| ), |
| }; |
| case TypeLiteral(): |
| return expression; |
| case ParenthesizedExpression(): |
| return _visitExpression(expression.expression); |
| case ConditionalExpression(): |
| Expression condition = _visitExpression(expression.condition); |
| return switch (condition) { |
| BooleanLiteral(value: true) => _visitExpression(expression.then), |
| BooleanLiteral(value: false) => _visitExpression( |
| expression.otherwise, |
| ), |
| _ => new ConditionalExpression( |
| condition, |
| _visitExpression(expression.then), |
| _visitExpression(expression.otherwise), |
| ), |
| }; |
| case ListLiteral(): |
| return new ListLiteral( |
| expression.typeArguments, |
| _visitElements(expression.elements), |
| ); |
| case SetOrMapLiteral(): |
| return new SetOrMapLiteral( |
| expression.typeArguments, |
| _visitElements(expression.elements), |
| ); |
| case RecordLiteral(): |
| return new RecordLiteral(_visitRecordFields(expression.fields)); |
| case IfNull(): |
| Expression left = _visitExpression(expression.left); |
| return switch (_isNull(left)) { |
| NullValue.isNull => _visitExpression(expression.right), |
| NullValue.isNonNull => left, |
| NullValue.unknown => new IfNull( |
| left, |
| _visitExpression(expression.right), |
| ), |
| }; |
| case LogicalExpression(): |
| return _visitLogicalExpression(expression); |
| case EqualityExpression(): |
| return _visitEqualityExpression(expression); |
| case BinaryExpression(): |
| return _visitBinaryExpression(expression); |
| case UnaryExpression(): |
| return _visitUnaryExpression(expression); |
| case IsTest(): |
| return new IsTest( |
| _visitExpression(expression.expression), |
| expression.type, |
| isNot: expression.isNot, |
| ); |
| case AsExpression(): |
| return new AsExpression( |
| _visitExpression(expression.expression), |
| expression.type, |
| ); |
| case NullCheck(): |
| Expression operand = _visitExpression(expression.expression); |
| return switch (_isNull(operand)) { |
| // This is known to fail but we have no way to represent failure. |
| NullValue.isNull => new NullCheck(operand), |
| NullValue.isNonNull => operand, |
| NullValue.unknown => new NullCheck(operand), |
| }; |
| |
| case UnresolvedExpression(): |
| return expression; |
| } |
| } |
| |
| Expression _visitStringLiteral(StringLiteral expression) { |
| if (expression.parts.length == 1 && expression.parts.single is StringPart) { |
| return expression; |
| } |
| List<StringLiteralPart> evaluatedParts = []; |
| StringBuffer? stringBuffer; |
| |
| void flush() { |
| if (stringBuffer != null) { |
| String text = stringBuffer.toString(); |
| if (text.isNotEmpty) { |
| evaluatedParts.add(new StringPart(stringBuffer.toString())); |
| } |
| stringBuffer = null; |
| } |
| } |
| |
| for (StringLiteralPart part in expression.parts) { |
| for (StringLiteralPart evaluatedPart in _visitStringLiteralPart(part)) { |
| switch (evaluatedPart) { |
| case StringPart(): |
| (stringBuffer ??= new StringBuffer()).write(evaluatedPart.text); |
| case InterpolationPart(): |
| flush(); |
| evaluatedParts.add(evaluatedPart); |
| } |
| } |
| } |
| flush(); |
| |
| if (evaluatedParts.isEmpty) { |
| evaluatedParts.add(new StringPart(stringBuffer.toString())); |
| } |
| return new StringLiteral(evaluatedParts); |
| } |
| |
| Expression _visitAdjacentStringLiterals(AdjacentStringLiterals expression) { |
| List<Expression> evaluatedParts = []; |
| List<StringLiteralPart>? stringLiteralParts; |
| |
| void flush() { |
| if (stringLiteralParts != null) { |
| StringLiteral stringLiteral = new StringLiteral(stringLiteralParts); |
| evaluatedParts.add(_visitExpression(stringLiteral)); |
| } |
| } |
| |
| for (Expression part in expression.expressions) { |
| Expression evaluatedPart = _visitExpression(part); |
| switch (evaluatedPart) { |
| case StringLiteral(:List<StringLiteralPart> parts): |
| (stringLiteralParts ??= []).addAll(parts); |
| default: |
| flush(); |
| evaluatedParts.add(evaluatedPart); |
| } |
| } |
| |
| flush(); |
| if (evaluatedParts.length == 1) { |
| return evaluatedParts.single; |
| } |
| return new AdjacentStringLiterals(evaluatedParts); |
| } |
| |
| Expression _visitLogicalExpression(LogicalExpression expression) { |
| Expression left = _visitExpression(expression.left); |
| return switch (left) { |
| BooleanLiteral(value: true) => switch (expression.operator) { |
| LogicalOperator.and => _visitExpression(expression.right), |
| LogicalOperator.or => left, |
| }, |
| BooleanLiteral(value: false) => switch (expression.operator) { |
| LogicalOperator.and => left, |
| LogicalOperator.or => _visitExpression(expression.right), |
| }, |
| _ => new LogicalExpression( |
| left, |
| expression.operator, |
| _visitExpression(expression.right), |
| ), |
| }; |
| } |
| |
| Expression _visitEqualityExpression(EqualityExpression expression) { |
| Expression leftExpression = _visitExpression(expression.left); |
| Expression rightExpression = _visitExpression(expression.right); |
| switch ((leftExpression, rightExpression)) { |
| case (NullLiteral(), NullLiteral()): |
| return new BooleanLiteral(!expression.isNotEquals); |
| case (IntegerLiteral(value: int left), IntegerLiteral(value: int right)): |
| return new BooleanLiteral( |
| expression.isNotEquals ? left != right : left == right, |
| ); |
| default: |
| // TODO(johnniwinther): Support all cases. |
| return new EqualityExpression( |
| leftExpression, |
| rightExpression, |
| isNotEquals: expression.isNotEquals, |
| ); |
| } |
| } |
| |
| Expression? _visitBinaryIntExpression( |
| int left, |
| BinaryOperator operator, |
| int right, |
| ) { |
| switch (operator) { |
| case BinaryOperator.lessThan: |
| return new BooleanLiteral(left < right); |
| case BinaryOperator.lessThanOrEqual: |
| return new BooleanLiteral(left <= right); |
| case BinaryOperator.greaterThan: |
| return new BooleanLiteral(left > right); |
| case BinaryOperator.greaterThanOrEqual: |
| return new BooleanLiteral(left >= right); |
| case BinaryOperator.bitwiseOr: |
| return new IntegerLiteral.fromValue(left | right); |
| case BinaryOperator.bitwiseXor: |
| return new IntegerLiteral.fromValue(left ^ right); |
| case BinaryOperator.bitwiseAnd: |
| return new IntegerLiteral.fromValue(left & right); |
| case BinaryOperator.shiftLeft: |
| return new IntegerLiteral.fromValue(left << right); |
| case BinaryOperator.unsignedShiftRight: |
| return new IntegerLiteral.fromValue(left >> right); |
| case BinaryOperator.signedShiftRight: |
| return new IntegerLiteral.fromValue(left >>> right); |
| case BinaryOperator.plus: |
| return new IntegerLiteral.fromValue(left + right); |
| case BinaryOperator.minus: |
| return new IntegerLiteral.fromValue(left - right); |
| case BinaryOperator.times: |
| return new IntegerLiteral.fromValue(left * right); |
| case BinaryOperator.divide: |
| if (right != 0) { |
| double value = left / right; |
| return new DoubleLiteral('$value', value); |
| } |
| case BinaryOperator.modulo: |
| if (right != 0) { |
| return new IntegerLiteral.fromValue(left % right); |
| } |
| case BinaryOperator.integerDivide: |
| if (right != 0) { |
| return new IntegerLiteral.fromValue(left ~/ right); |
| } |
| } |
| return null; |
| } |
| |
| Expression _visitBinaryExpression(BinaryExpression expression) { |
| Expression leftExpression = _visitExpression(expression.left); |
| Expression rightExpression = _visitExpression(expression.right); |
| |
| switch ((leftExpression, rightExpression)) { |
| case (IntegerLiteral(value: int left), IntegerLiteral(value: int right)): |
| return _visitBinaryIntExpression(left, expression.operator, right) ?? |
| new BinaryExpression( |
| leftExpression, |
| expression.operator, |
| rightExpression, |
| ); |
| default: |
| // TODO(johnniwinther): Support more cases. |
| return new BinaryExpression( |
| leftExpression, |
| expression.operator, |
| rightExpression, |
| ); |
| } |
| } |
| |
| Expression _visitUnaryExpression(UnaryExpression expression) { |
| Expression operand = _visitExpression(expression.expression); |
| switch ((expression.operator, operand)) { |
| case (UnaryOperator.minus, IntegerLiteral(:int value)): |
| return new IntegerLiteral.fromValue(-value); |
| case (UnaryOperator.bang, BooleanLiteral(:bool value)): |
| return new BooleanLiteral(!value); |
| case (UnaryOperator.tilde, IntegerLiteral(:int value)): |
| return new IntegerLiteral.fromValue(~value); |
| default: |
| // TODO(johnniwinther): Support more cases. |
| return new UnaryExpression(expression.operator, operand); |
| } |
| } |
| |
| List<StringLiteralPart> _visitStringLiteralPart(StringLiteralPart part) { |
| switch (part) { |
| case StringPart(): |
| return [part]; |
| case InterpolationPart(): |
| Expression expression = _visitExpression(part.expression); |
| return switch (expression) { |
| StringLiteral() => expression.parts, |
| NullLiteral() => [new StringPart('null')], |
| BooleanLiteral(:bool value) => [new StringPart('$value')], |
| IntegerLiteral(:int value) => [new StringPart('$value')], |
| DoubleLiteral(:double value) => [new StringPart('$value')], |
| _ => [new InterpolationPart(expression)], |
| }; |
| } |
| } |
| |
| List<Argument> _visitArguments(List<Argument> arguments) { |
| List<Argument> list = []; |
| for (Argument argument in arguments) { |
| switch (argument) { |
| case PositionalArgument(): |
| list.add( |
| new PositionalArgument(_visitExpression(argument.expression)), |
| ); |
| case NamedArgument(): |
| list.add( |
| new NamedArgument( |
| argument.name, |
| _visitExpression(argument.expression), |
| ), |
| ); |
| } |
| } |
| return list; |
| } |
| |
| List<RecordField> _visitRecordFields(List<RecordField> fields) { |
| List<RecordField> list = []; |
| for (RecordField field in fields) { |
| switch (field) { |
| case RecordNamedField(): |
| list.add( |
| new RecordNamedField( |
| field.name, |
| _visitExpression(field.expression), |
| ), |
| ); |
| case RecordPositionalField(): |
| list.add( |
| new RecordPositionalField(_visitExpression(field.expression)), |
| ); |
| } |
| } |
| return list; |
| } |
| |
| List<Element> _visitElements(List<Element> elements) { |
| List<Element> list = []; |
| for (Element element in elements) { |
| switch (element) { |
| case ExpressionElement(): |
| Expression expression = _visitExpression(element.expression); |
| bool isNullAware = element.isNullAware; |
| if (isNullAware) { |
| switch (_isNull(expression)) { |
| case NullValue.isNull: |
| // Skip element. |
| continue; |
| case NullValue.isNonNull: |
| isNullAware = false; |
| case NullValue.unknown: |
| } |
| } |
| list.add(new ExpressionElement(expression, isNullAware: isNullAware)); |
| case MapEntryElement(): |
| Expression key = _visitExpression(element.key); |
| Expression value = _visitExpression(element.value); |
| bool isNullAwareKey = element.isNullAwareKey; |
| bool isNullAwareValue = element.isNullAwareValue; |
| if (isNullAwareKey) { |
| switch (_isNull(key)) { |
| case NullValue.isNull: |
| // Skip entry. |
| continue; |
| case NullValue.isNonNull: |
| isNullAwareKey = false; |
| case NullValue.unknown: |
| } |
| } |
| if (isNullAwareValue) { |
| switch (_isNull(value)) { |
| case NullValue.isNull: |
| // Skip entry. |
| continue; |
| case NullValue.isNonNull: |
| isNullAwareValue = false; |
| case NullValue.unknown: |
| } |
| } |
| list.add( |
| new MapEntryElement( |
| key, |
| value, |
| isNullAwareKey: isNullAwareKey, |
| isNullAwareValue: isNullAwareValue, |
| ), |
| ); |
| case SpreadElement(): |
| Expression expression = _visitExpression(element.expression); |
| bool isNullAware = element.isNullAware; |
| if (isNullAware) { |
| switch (_isNull(expression)) { |
| case NullValue.isNull: |
| // Skip element. |
| continue; |
| case NullValue.isNonNull: |
| isNullAware = false; |
| case NullValue.unknown: |
| } |
| } |
| if (isNullAware) { |
| list.add(new SpreadElement(expression, isNullAware: true)); |
| } else { |
| switch (expression) { |
| case ListLiteral(): |
| list.addAll(_visitElements(expression.elements)); |
| case SetOrMapLiteral(): |
| list.addAll(_visitElements(expression.elements)); |
| default: |
| list.add(new SpreadElement(expression, isNullAware: false)); |
| } |
| } |
| case IfElement(): |
| Expression condition = _visitExpression(element.condition); |
| switch (condition) { |
| case BooleanLiteral(value: true): |
| list.addAll(_visitElements([element.then])); |
| case BooleanLiteral(value: false): |
| if (element.otherwise != null) { |
| list.addAll(_visitElements([element.otherwise!])); |
| } |
| default: |
| Element? then = _visitElement(element.then); |
| Element? otherwise = |
| element.otherwise != null |
| ? _visitElement(element.otherwise!) |
| : null; |
| if (then != null) { |
| list.add(new IfElement(condition, then, otherwise)); |
| } else if (otherwise != null) { |
| list.add( |
| new IfElement( |
| new UnaryExpression(UnaryOperator.bang, condition), |
| otherwise, |
| ), |
| ); |
| } else { |
| // Skip element. |
| continue; |
| } |
| } |
| } |
| } |
| return list; |
| } |
| |
| Element? _visitElement(Element element) { |
| switch (element) { |
| case ExpressionElement(): |
| Expression expression = _visitExpression(element.expression); |
| bool isNullAware = element.isNullAware; |
| if (isNullAware) { |
| switch (_isNull(expression)) { |
| case NullValue.isNull: |
| // Skip element. |
| return null; |
| case NullValue.isNonNull: |
| isNullAware = false; |
| case NullValue.unknown: |
| } |
| } |
| return new ExpressionElement( |
| expression, |
| isNullAware: element.isNullAware, |
| ); |
| case MapEntryElement(): |
| Expression key = _visitExpression(element.key); |
| Expression value = _visitExpression(element.value); |
| bool isNullAwareKey = element.isNullAwareKey; |
| bool isNullAwareValue = element.isNullAwareValue; |
| if (isNullAwareKey) { |
| switch (_isNull(key)) { |
| case NullValue.isNull: |
| // Skip entry. |
| return null; |
| case NullValue.isNonNull: |
| isNullAwareKey = false; |
| case NullValue.unknown: |
| } |
| } |
| if (isNullAwareValue) { |
| switch (_isNull(value)) { |
| case NullValue.isNull: |
| // Skip entry. |
| return null; |
| case NullValue.isNonNull: |
| isNullAwareValue = false; |
| case NullValue.unknown: |
| } |
| } |
| return new MapEntryElement( |
| key, |
| value, |
| isNullAwareKey: isNullAwareKey, |
| isNullAwareValue: isNullAwareValue, |
| ); |
| case SpreadElement(): |
| Expression expression = _visitExpression(element.expression); |
| bool isNullAware = element.isNullAware; |
| if (isNullAware) { |
| switch (_isNull(expression)) { |
| case NullValue.isNull: |
| // Skip element. |
| return null; |
| case NullValue.isNonNull: |
| isNullAware = false; |
| case NullValue.unknown: |
| } |
| } |
| if (isNullAware) { |
| return new SpreadElement(expression, isNullAware: true); |
| } else { |
| switch (expression) { |
| case ListLiteral(elements: []): |
| case SetOrMapLiteral(elements: []): |
| // Empty spread. |
| return null; |
| default: |
| return new SpreadElement(expression, isNullAware: false); |
| } |
| } |
| case IfElement(): |
| Expression condition = _visitExpression(element.condition); |
| switch (condition) { |
| case BooleanLiteral(value: true): |
| return _visitElement(element.then); |
| case BooleanLiteral(value: false): |
| if (element.otherwise != null) { |
| return _visitElement(element.otherwise!); |
| } else { |
| return null; |
| } |
| default: |
| Element? then = _visitElement(element.then); |
| Element? otherwise = |
| element.otherwise != null |
| ? _visitElement(element.otherwise!) |
| : null; |
| if (then != null) { |
| return new IfElement(condition, then, otherwise); |
| } else if (otherwise != null) { |
| return new IfElement( |
| new UnaryExpression(UnaryOperator.bang, condition), |
| otherwise, |
| ); |
| } else { |
| // Skip element. |
| return null; |
| } |
| } |
| } |
| } |
| |
| NullValue _isNull(Expression expression) { |
| return switch (expression) { |
| NullLiteral() => NullValue.isNull, |
| BooleanLiteral() => NullValue.isNonNull, |
| IntegerLiteral() => NullValue.isNonNull, |
| DoubleLiteral() => NullValue.isNonNull, |
| StringLiteral() => NullValue.isNonNull, |
| SymbolLiteral() => NullValue.isNonNull, |
| AdjacentStringLiterals() => NullValue.isNonNull, |
| EqualityExpression() => NullValue.isNonNull, |
| FunctionTearOff() => NullValue.isNonNull, |
| IsTest() => NullValue.isNonNull, |
| ListLiteral() => NullValue.isNonNull, |
| SetOrMapLiteral() => NullValue.isNonNull, |
| LogicalExpression() => NullValue.isNonNull, |
| ConstructorTearOff() => NullValue.isNonNull, |
| ConstructorInvocation() => NullValue.isNonNull, |
| Instantiation() => NullValue.isNonNull, |
| TypeLiteral() => NullValue.isNonNull, |
| RecordLiteral() => NullValue.isNonNull, |
| NullCheck() => NullValue.isNonNull, |
| |
| // TODO(johnniwinther): Should the subexpressions be visited? |
| ParenthesizedExpression() => NullValue.unknown, |
| ConditionalExpression() => NullValue.unknown, |
| IfNull() => NullValue.unknown, |
| BinaryExpression() => NullValue.unknown, |
| UnaryExpression() => NullValue.unknown, |
| PropertyGet() => NullValue.unknown, |
| NullAwarePropertyGet() => NullValue.unknown, |
| StaticGet() => NullValue.unknown, |
| StaticInvocation() => NullValue.unknown, |
| InvalidExpression() => NullValue.unknown, |
| ImplicitInvocation() => NullValue.unknown, |
| MethodInvocation() => NullValue.unknown, |
| AsExpression() => NullValue.unknown, |
| UnresolvedExpression() => NullValue.unknown, |
| }; |
| } |
| } |
| |
| enum NullValue { isNull, isNonNull, unknown } |