| // Copyright (c) 2012, 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. | |
| /// Constant system following the semantics for Dart code that has been | |
| /// compiled to JavaScript. | |
| library; | |
| import '../common/elements.dart' show CommonElements; | |
| import '../elements/entities.dart'; | |
| import '../elements/types.dart'; | |
| import 'values.dart'; | |
| const add = AddOperation(); | |
| const bitAnd = BitAndOperation(); | |
| const bitNot = BitNotOperation(); | |
| const bitOr = BitOrOperation(); | |
| const bitXor = BitXorOperation(); | |
| const booleanAnd = BooleanAndOperation(); | |
| const booleanOr = BooleanOrOperation(); | |
| const divide = DivideOperation(); | |
| const equal = EqualsOperation(); | |
| const greaterEqual = GreaterEqualOperation(); | |
| const greater = GreaterOperation(); | |
| const identity = IdentityOperation(); | |
| const ifNull = IfNullOperation(); | |
| const index = _IndexOperation(); | |
| const lessEqual = LessEqualOperation(); | |
| const less = LessOperation(); | |
| const modulo = ModuloOperation(); | |
| const multiply = MultiplyOperation(); | |
| const negate = NegateOperation(); | |
| const not = NotOperation(); | |
| const remainder = RemainderOperation(); | |
| const shiftLeft = ShiftLeftOperation(); | |
| const shiftRight = ShiftRightOperation(); | |
| const shiftRightUnsigned = ShiftRightUnsignedOperation(); | |
| const subtract = SubtractOperation(); | |
| const truncatingDivide = TruncatingDivideOperation(); | |
| const codeUnitAt = CodeUnitAtOperation(); | |
| const round = RoundOperation(); | |
| const toInt = ToIntOperation(); | |
| const abs = UnfoldedUnaryOperation('abs'); | |
| /// Returns true if [value] will turn into NaN or infinity | |
| /// at runtime. | |
| bool _integerBecomesNanOrInfinity(BigInt value) { | |
| double doubleValue = value.toDouble(); | |
| return doubleValue.isNaN || doubleValue.isInfinite; | |
| } | |
| NumConstantValue _convertToJavaScriptConstant(NumConstantValue constant) { | |
| if (constant is IntConstantValue) { | |
| BigInt intValue = constant.intValue; | |
| if (_integerBecomesNanOrInfinity(intValue)) { | |
| return DoubleConstantValue(intValue.toDouble()); | |
| } | |
| // If the integer loses precision with JavaScript numbers, use | |
| // the floored value JavaScript will use. | |
| BigInt floorValue = BigInt.from(intValue.toDouble()); | |
| if (floorValue != intValue) { | |
| return IntConstantValue(floorValue); | |
| } | |
| } else if (constant is DoubleConstantValue) { | |
| double doubleValue = constant.doubleValue; | |
| if (!doubleValue.isInfinite && | |
| !doubleValue.isNaN && | |
| !constant.isMinusZero) { | |
| double truncated = doubleValue.truncateToDouble(); | |
| if (truncated == doubleValue) { | |
| return IntConstantValue(BigInt.from(truncated)); | |
| } | |
| } | |
| } | |
| return constant; | |
| } | |
| NumConstantValue createInt(BigInt i) => | |
| _convertToJavaScriptConstant(IntConstantValue(i)); | |
| NumConstantValue createIntFromInt(int i) => createInt(BigInt.from(i)); | |
| IntConstantValue _createInt32(BigInt i) => IntConstantValue(i.toUnsigned(32)); | |
| NumConstantValue createDouble(double d) => | |
| _convertToJavaScriptConstant(DoubleConstantValue(d)); | |
| StringConstantValue createString(String string) => StringConstantValue(string); | |
| BoolConstantValue createBool(bool value) => BoolConstantValue(value); | |
| NullConstantValue createNull() => NullConstantValue(); | |
| ListConstantValue createList( | |
| CommonElements commonElements, | |
| InterfaceType sourceType, | |
| List<ConstantValue> values, | |
| ) { | |
| InterfaceType type = commonElements.getConstantListTypeFor(sourceType); | |
| return ListConstantValue(type, values); | |
| } | |
| TypeConstantValue createType(CommonElements commonElements, DartType type) { | |
| InterfaceType instanceType = commonElements.typeLiteralType; | |
| return TypeConstantValue(type, instanceType); | |
| } | |
| /// Returns true if the [constant] is an integer at runtime. | |
| /// | |
| /// Integer checks report true for -0.0, INFINITY, and -INFINITY. At | |
| /// runtime an 'X is int' check is implemented as: | |
| /// | |
| /// typeof(X) === "number" && Math.floor(X) === X | |
| /// | |
| /// We consistently match that runtime semantics at compile time as well. | |
| bool isInt(ConstantValue constant) { | |
| constant = _unwrap(constant); | |
| return constant is IntConstantValue || | |
| constant.isMinusZero || | |
| constant.isPositiveInfinity || | |
| constant.isNegativeInfinity; | |
| } | |
| /// Returns true if the [constant] is a double at runtime. | |
| bool isDouble(ConstantValue constant) { | |
| constant = _unwrap(constant); | |
| return constant is DoubleConstantValue && !constant.isMinusZero; | |
| } | |
| /// Returns true if the [constant] is a string at runtime. | |
| bool isString(ConstantValue constant) => | |
| _unwrap(constant) is StringConstantValue; | |
| /// Returns true if the [constant] is a boolean at runtime. | |
| bool isBool(ConstantValue constant) => _unwrap(constant) is BoolConstantValue; | |
| /// Returns true if the [constant] is null at runtime. | |
| bool isNull(ConstantValue constant) => _unwrap(constant) is NullConstantValue; | |
| bool isSubtype(DartTypes types, DartType s, DartType t) { | |
| // At runtime, an integer is both an integer and a double: the | |
| // integer type check is Math.floor, which will return true only | |
| // for real integers, and our double type check is 'typeof number' | |
| // which will return true for both integers and doubles. | |
| if (s == types.commonElements.intType && | |
| t == types.commonElements.doubleType) { | |
| return true; | |
| } | |
| return types.isSubtype(s, t); | |
| } | |
| SetConstantValue createSet( | |
| CommonElements commonElements, | |
| InterfaceType sourceType, | |
| List<ConstantValue> values, | |
| ) { | |
| JavaScriptObjectConstantValue? indexObject = _makeStringIndex(values); | |
| InterfaceType type = commonElements.getConstantSetTypeFor( | |
| sourceType, | |
| onlyStringKeys: indexObject != null, | |
| ); | |
| return JavaScriptSetConstant(type, values, indexObject); | |
| } | |
| MapConstantValue createMap( | |
| CommonElements commonElements, | |
| InterfaceType sourceType, | |
| List<ConstantValue> keys, | |
| List<ConstantValue> values, | |
| ) { | |
| final JavaScriptObjectConstantValue? indexObject = _makeStringIndex(keys); | |
| final onlyStringKeys = indexObject != null; | |
| InterfaceType keysType = commonElements.listType( | |
| sourceType.typeArguments.first, | |
| ); | |
| InterfaceType valuesType = commonElements.listType( | |
| sourceType.typeArguments.last, | |
| ); | |
| ListConstantValue keysList = createList(commonElements, keysType, keys); | |
| ListConstantValue valuesList = createList(commonElements, valuesType, values); | |
| InterfaceType type = commonElements.getConstantMapTypeFor( | |
| sourceType, | |
| onlyStringKeys: onlyStringKeys, | |
| ); | |
| return JavaScriptMapConstant( | |
| type, | |
| keysList, | |
| valuesList, | |
| onlyStringKeys, | |
| indexObject, | |
| ); | |
| } | |
| JavaScriptObjectConstantValue? _makeStringIndex(List<ConstantValue> keys) { | |
| for (final key in keys) { | |
| if (key is! StringConstantValue) return null; | |
| if (key.stringValue == JavaScriptMapConstant.protoProperty) return null; | |
| } | |
| // If we generate a JavaScript Object initializer with the keys in order, are | |
| // the properties of the Object in the same order? If so, we can generate the | |
| // keys of the map using `Object.keys`, otherwise we need to provide the key | |
| // ordering explicitly, or sort by position at runtime, or have a Map/Set | |
| // subclass for the case where sorting is necessary. For now we use the | |
| // general constant Map/Set for the occasional case where the order is wrong. | |
| if (!_valuesInObjectPropertyOrder(keys)) return null; | |
| return JavaScriptObjectConstantValue( | |
| keys, | |
| List.generate(keys.length, createIntFromInt), | |
| ); | |
| } | |
| final _numberRegExp = RegExp(r'^0$|^[1-9][0-9]*$'); | |
| /// If the values are the keys of a JavaScript Object initializer, is the result | |
| /// of `Object.keys` in the same order as [keys]? This method may conservatively | |
| /// return `false`. | |
| /// | |
| /// Object keys are split into 'indexes' and 'names', with all the 'indexes' | |
| /// before the 'names'. 'indexes' are strings that have the value of | |
| /// `i.toString()` for some `i` from 0 up to some limit. The indexes are ordered | |
| /// by their integer value. 'names' are in insertion order. The literal | |
| /// `{"a":1,"10":2,"2":3,"b":4}` has `Object.keys` of `["2","10","a","b"]` | |
| /// because `10` and `2` come before `a` and `b`. | |
| bool _valuesInObjectPropertyOrder(List<ConstantValue> keys) { | |
| int lastNumber = -1; | |
| bool seenNonNumber = false; | |
| for (final key in keys) { | |
| if (key is! StringConstantValue) return false; | |
| final string = key.stringValue; | |
| if (_numberRegExp.hasMatch(string)) { | |
| // This index would move before the non-number. | |
| if (seenNonNumber) return false; | |
| // Sufficiently large digit strings are not considered to be indexes. It | |
| // is not clear where the cutoff is or whether it is consistent between | |
| // JavaScript implementations. | |
| if (string.length > 8) return false; | |
| final value = int.parse(string); | |
| // Adjacent indexes must be in increasing numerical order. | |
| if (value <= lastNumber) return false; | |
| lastNumber = value; | |
| } else { | |
| seenNonNumber = true; | |
| } | |
| } | |
| return true; | |
| } | |
| ConstructedConstantValue createSymbol( | |
| CommonElements commonElements, | |
| String text, [ | |
| Uri? libraryUri, | |
| ]) { | |
| InterfaceType type = commonElements.symbolImplementationType; | |
| FieldEntity field = commonElements.symbolField; | |
| String name = libraryUri != null ? '$text@$libraryUri' : text; | |
| ConstantValue argument = createString(name); | |
| var fields = <FieldEntity, ConstantValue>{field: argument}; | |
| return ConstructedConstantValue(type, fields); | |
| } | |
| abstract class Operation { | |
| String get name; | |
| } | |
| abstract class UnaryOperation extends Operation { | |
| /// Returns [:null:] if it was unable to fold the operation. | |
| ConstantValue? fold(ConstantValue constant); | |
| } | |
| abstract class BinaryOperation extends Operation { | |
| /// Returns [:null:] if it was unable to fold the operation. | |
| ConstantValue? fold(ConstantValue left, ConstantValue right); | |
| } | |
| class BitNotOperation implements UnaryOperation { | |
| @override | |
| final String name = '~'; | |
| const BitNotOperation(); | |
| @override | |
| IntConstantValue? fold(ConstantValue constant) { | |
| constant = _unwrap(constant); | |
| if (isInt(constant)) { | |
| // In JavaScript we don't check for -0 and treat it as if it was zero. | |
| if (constant.isMinusZero) { | |
| constant = createInt(BigInt.zero); | |
| } | |
| if (constant is IntConstantValue) { | |
| // Bit-operations yield 32-bit unsigned integers. | |
| return _createInt32(~constant.intValue); | |
| } | |
| } | |
| return null; | |
| } | |
| } | |
| class NegateOperation implements UnaryOperation { | |
| @override | |
| final String name = 'negate'; | |
| const NegateOperation(); | |
| @override | |
| NumConstantValue? fold(ConstantValue constant) { | |
| constant = _unwrap(constant); | |
| NumConstantValue? fold(ConstantValue constant) { | |
| if (constant is IntConstantValue) { | |
| return createInt(-constant.intValue); | |
| } | |
| if (constant is DoubleConstantValue) { | |
| return createDouble(-constant.doubleValue); | |
| } | |
| return null; | |
| } | |
| if (constant is IntConstantValue) { | |
| if (constant.intValue == BigInt.zero) { | |
| return createDouble(-0.0); | |
| } | |
| } | |
| return fold(constant); | |
| } | |
| } | |
| class NotOperation implements UnaryOperation { | |
| @override | |
| final String name = '!'; | |
| const NotOperation(); | |
| @override | |
| BoolConstantValue? fold(ConstantValue constant) { | |
| constant = _unwrap(constant); | |
| if (constant is BoolConstantValue) { | |
| return createBool(!constant.boolValue); | |
| } | |
| return null; | |
| } | |
| } | |
| /// Operations that only work if both arguments are integers. | |
| abstract class BinaryBitOperation implements BinaryOperation { | |
| const BinaryBitOperation(); | |
| @override | |
| IntConstantValue? fold(ConstantValue left, ConstantValue right) { | |
| left = _unwrap(left); | |
| right = _unwrap(right); | |
| IntConstantValue? fold(ConstantValue left, ConstantValue right) { | |
| if (left is IntConstantValue && right is IntConstantValue) { | |
| BigInt? resultValue = foldInts(left.intValue, right.intValue); | |
| if (resultValue == null) return null; | |
| return createInt(resultValue) as IntConstantValue; | |
| } | |
| return null; | |
| } | |
| // In JavaScript we don't check for -0 and treat it as if it was zero. | |
| if (left.isMinusZero) { | |
| left = createInt(BigInt.zero); | |
| } | |
| if (right.isMinusZero) { | |
| right = createInt(BigInt.zero); | |
| } | |
| IntConstantValue? result = fold(left, right); | |
| if (result != null) { | |
| // We convert the result of bit-operations to 32 bit unsigned integers. | |
| return _createInt32(result.intValue); | |
| } | |
| return result; | |
| } | |
| BigInt? foldInts(BigInt left, BigInt right); | |
| } | |
| class BitAndOperation extends BinaryBitOperation { | |
| @override | |
| final String name = '&'; | |
| const BitAndOperation(); | |
| @override | |
| BigInt foldInts(BigInt left, BigInt right) => left & right; | |
| } | |
| class BitOrOperation extends BinaryBitOperation { | |
| @override | |
| final String name = '|'; | |
| const BitOrOperation(); | |
| @override | |
| BigInt foldInts(BigInt left, BigInt right) => left | right; | |
| } | |
| class BitXorOperation extends BinaryBitOperation { | |
| @override | |
| final String name = '^'; | |
| const BitXorOperation(); | |
| @override | |
| BigInt foldInts(BigInt left, BigInt right) => left ^ right; | |
| } | |
| class ShiftLeftOperation extends BinaryBitOperation { | |
| @override | |
| final String name = '<<'; | |
| const ShiftLeftOperation(); | |
| @override | |
| BigInt? foldInts(BigInt left, BigInt right) { | |
| // TODO(floitsch): find a better way to guard against excessive shifts to | |
| // the left. | |
| if (right > BigInt.from(100) || right < BigInt.zero) return null; | |
| return left << right.toInt(); | |
| } | |
| } | |
| class ShiftRightOperation extends BinaryBitOperation { | |
| @override | |
| final String name = '>>'; | |
| const ShiftRightOperation(); | |
| @override | |
| IntConstantValue? fold(ConstantValue left, ConstantValue right) { | |
| // Truncate the input value to 32 bits. The web implementation of '>>' is a | |
| // signed shift for negative values, and an unsigned for shift for | |
| // non-negative values. | |
| ConstantValue adjustedLeft = left; | |
| if (left is IntConstantValue) { | |
| BigInt value = left.intValue; | |
| BigInt truncated = value.isNegative | |
| ? value.toSigned(32) | |
| : value.toUnsigned(32); | |
| if (value != truncated) { | |
| adjustedLeft = createInt(truncated); | |
| } | |
| } | |
| return super.fold(adjustedLeft, right); | |
| } | |
| @override | |
| BigInt? foldInts(BigInt left, BigInt right) { | |
| if (right < BigInt.zero) return null; | |
| return left >> right.toInt(); | |
| } | |
| } | |
| class ShiftRightUnsignedOperation extends BinaryBitOperation { | |
| @override | |
| final String name = '>>>'; | |
| const ShiftRightUnsignedOperation(); | |
| @override | |
| BigInt? foldInts(BigInt left, BigInt right) { | |
| if (right < BigInt.zero) return null; | |
| return left.toUnsigned(32) >> right.toInt(); | |
| } | |
| } | |
| abstract class BinaryBoolOperation implements BinaryOperation { | |
| const BinaryBoolOperation(); | |
| @override | |
| BoolConstantValue? fold(ConstantValue left, ConstantValue right) { | |
| if (left is BoolConstantValue && right is BoolConstantValue) { | |
| bool resultValue = foldBools(left.boolValue, right.boolValue); | |
| return createBool(resultValue); | |
| } | |
| return null; | |
| } | |
| bool foldBools(bool left, bool right); | |
| } | |
| class BooleanAndOperation extends BinaryBoolOperation { | |
| @override | |
| final String name = '&&'; | |
| const BooleanAndOperation(); | |
| @override | |
| bool foldBools(bool left, bool right) => left && right; | |
| } | |
| class BooleanOrOperation extends BinaryBoolOperation { | |
| @override | |
| final String name = '||'; | |
| const BooleanOrOperation(); | |
| @override | |
| bool foldBools(bool left, bool right) => left || right; | |
| } | |
| abstract class ArithmeticNumOperation implements BinaryOperation { | |
| const ArithmeticNumOperation(); | |
| @override | |
| NumConstantValue? fold(ConstantValue left, ConstantValue right) { | |
| left = _unwrap(left); | |
| right = _unwrap(right); | |
| NumConstantValue? fold(ConstantValue left, ConstantValue right) { | |
| if (left is NumConstantValue && right is NumConstantValue) { | |
| Object? foldedValue; | |
| if (left is IntConstantValue && right is IntConstantValue) { | |
| foldedValue = foldInts(left.intValue, right.intValue); | |
| } else { | |
| foldedValue = foldNums(left.doubleValue, right.doubleValue); | |
| } | |
| // A division by 0 means that we might not have a folded value. | |
| if (foldedValue == null) return null; | |
| if (left is IntConstantValue && | |
| right is IntConstantValue && | |
| !isDivide() || | |
| isTruncatingDivide()) { | |
| return createInt(foldedValue as BigInt); | |
| } else { | |
| return createDouble(foldedValue as double); | |
| } | |
| } | |
| return null; | |
| } | |
| NumConstantValue? result = fold(left, right); | |
| if (result == null) return result; | |
| return _convertToJavaScriptConstant(result); | |
| } | |
| bool isDivide() => false; | |
| bool isTruncatingDivide() => false; | |
| Object? foldInts(BigInt left, BigInt right); | |
| Object? foldNums(num left, num right); | |
| } | |
| class SubtractOperation extends ArithmeticNumOperation { | |
| @override | |
| final String name = '-'; | |
| const SubtractOperation(); | |
| @override | |
| BigInt foldInts(BigInt left, BigInt right) => left - right; | |
| @override | |
| num foldNums(num left, num right) => left - right; | |
| } | |
| class MultiplyOperation extends ArithmeticNumOperation { | |
| @override | |
| final String name = '*'; | |
| const MultiplyOperation(); | |
| @override | |
| BigInt foldInts(BigInt left, BigInt right) => left * right; | |
| @override | |
| num foldNums(num left, num right) => left * right; | |
| } | |
| class ModuloOperation extends ArithmeticNumOperation { | |
| @override | |
| final String name = '%'; | |
| const ModuloOperation(); | |
| @override | |
| BigInt? foldInts(BigInt left, BigInt right) { | |
| if (right == BigInt.zero) return null; | |
| return left % right; | |
| } | |
| @override | |
| num foldNums(num left, num right) => left % right; | |
| } | |
| class RemainderOperation extends ArithmeticNumOperation { | |
| @override | |
| final String name = 'remainder'; | |
| const RemainderOperation(); | |
| @override | |
| BigInt? foldInts(BigInt left, BigInt right) { | |
| if (right == BigInt.zero) return null; | |
| return left.remainder(right); | |
| } | |
| @override | |
| num foldNums(num left, num right) => left.remainder(right); | |
| } | |
| class TruncatingDivideOperation extends ArithmeticNumOperation { | |
| @override | |
| final String name = '~/'; | |
| const TruncatingDivideOperation(); | |
| @override | |
| BigInt? foldInts(BigInt left, BigInt right) { | |
| if (right == BigInt.zero) return null; | |
| return left ~/ right; | |
| } | |
| @override | |
| BigInt? foldNums(num left, num right) { | |
| num ratio = left / right; | |
| if (ratio.isNaN || ratio.isInfinite) return null; | |
| return BigInt.from(ratio.truncateToDouble()); | |
| } | |
| @override | |
| bool isTruncatingDivide() => true; | |
| } | |
| class DivideOperation extends ArithmeticNumOperation { | |
| @override | |
| final String name = '/'; | |
| const DivideOperation(); | |
| @override | |
| double foldInts(BigInt left, BigInt right) => left / right; | |
| @override | |
| num foldNums(num left, num right) => left / right; | |
| @override | |
| bool isDivide() => true; | |
| } | |
| class AddOperation implements BinaryOperation { | |
| @override | |
| final String name = '+'; | |
| const AddOperation(); | |
| @override | |
| ConstantValue? fold(ConstantValue left, ConstantValue right) { | |
| left = _unwrap(left); | |
| right = _unwrap(right); | |
| ConstantValue? fold(ConstantValue left, ConstantValue right) { | |
| if (left is IntConstantValue && right is IntConstantValue) { | |
| BigInt result = left.intValue + right.intValue; | |
| return createInt(result); | |
| } else if (left is NumConstantValue && right is NumConstantValue) { | |
| double result = left.doubleValue + right.doubleValue; | |
| return createDouble(result); | |
| } else if (left is StringConstantValue && right is StringConstantValue) { | |
| String result = left.stringValue + right.stringValue; | |
| return createString(result); | |
| } else { | |
| return null; | |
| } | |
| } | |
| ConstantValue? result = fold(left, right); | |
| if (result is NumConstantValue) { | |
| return _convertToJavaScriptConstant(result); | |
| } | |
| return result; | |
| } | |
| } | |
| abstract class RelationalNumOperation implements BinaryOperation { | |
| const RelationalNumOperation(); | |
| @override | |
| BoolConstantValue? fold(ConstantValue left, ConstantValue right) { | |
| left = _unwrap(left); | |
| right = _unwrap(right); | |
| if (left is NumConstantValue && right is NumConstantValue) { | |
| bool foldedValue; | |
| if (left is IntConstantValue && right is IntConstantValue) { | |
| foldedValue = foldInts(left.intValue, right.intValue); | |
| } else { | |
| foldedValue = foldNums(left.doubleValue, right.doubleValue); | |
| } | |
| return createBool(foldedValue); | |
| } | |
| return null; | |
| } | |
| bool foldInts(BigInt left, BigInt right); | |
| bool foldNums(num left, num right); | |
| } | |
| class LessOperation extends RelationalNumOperation { | |
| @override | |
| final String name = '<'; | |
| const LessOperation(); | |
| @override | |
| bool foldInts(BigInt left, BigInt right) => left < right; | |
| @override | |
| bool foldNums(num left, num right) => left < right; | |
| } | |
| class LessEqualOperation extends RelationalNumOperation { | |
| @override | |
| final String name = '<='; | |
| const LessEqualOperation(); | |
| @override | |
| bool foldInts(BigInt left, BigInt right) => left <= right; | |
| @override | |
| bool foldNums(num left, num right) => left <= right; | |
| } | |
| class GreaterOperation extends RelationalNumOperation { | |
| @override | |
| final String name = '>'; | |
| const GreaterOperation(); | |
| @override | |
| bool foldInts(BigInt left, BigInt right) => left > right; | |
| @override | |
| bool foldNums(num left, num right) => left > right; | |
| } | |
| class GreaterEqualOperation extends RelationalNumOperation { | |
| @override | |
| final String name = '>='; | |
| const GreaterEqualOperation(); | |
| @override | |
| bool foldInts(BigInt left, BigInt right) => left >= right; | |
| @override | |
| bool foldNums(num left, num right) => left >= right; | |
| } | |
| class EqualsOperation implements BinaryOperation { | |
| @override | |
| final String name = '=='; | |
| const EqualsOperation(); | |
| @override | |
| BoolConstantValue? fold(ConstantValue left, ConstantValue right) { | |
| left = _unwrap(left); | |
| right = _unwrap(right); | |
| // Numbers need to be treated specially because: NaN != NaN, -0.0 == 0.0, | |
| // and 1 == 1.0. | |
| if (left is IntConstantValue && right is IntConstantValue) { | |
| bool result = left.intValue == right.intValue; | |
| return createBool(result); | |
| } | |
| if (left is NumConstantValue && right is NumConstantValue) { | |
| bool result = left.doubleValue == right.doubleValue; | |
| return createBool(result); | |
| } | |
| if (left is ConstructedConstantValue) { | |
| if (right is NullConstantValue) { | |
| return createBool(false); | |
| } | |
| // Unless we know that the user-defined object does not implement the | |
| // equality operator we cannot fold here. | |
| return null; | |
| } | |
| return createBool(left == right); | |
| } | |
| } | |
| class IdentityOperation implements BinaryOperation { | |
| @override | |
| final String name = '==='; | |
| const IdentityOperation(); | |
| @override | |
| BoolConstantValue fold(ConstantValue left, ConstantValue right) { | |
| left = _unwrap(left); | |
| right = _unwrap(right); | |
| // NaNs are not identical to anything. This is a web platform departure from | |
| // standard Dart. If we make `identical(double.nan, double.nan)` be `true`, | |
| // this constant folding will be incorrect. TODOs below for cross-reference. | |
| // TODO(11551): Keep constant-folding consistent with `identical`. | |
| // TODO(42224): Keep constant-folding consistent with `identical`. | |
| if (left.isNaN || right.isNaN) return FalseConstantValue(); | |
| // In JavaScript -0.0 === 0 and all doubles are equal to their integer | |
| // values. | |
| if (left is IntConstantValue && right is IntConstantValue) { | |
| return createBool(left.intValue == right.intValue); | |
| } | |
| if (left is NumConstantValue && right is NumConstantValue) { | |
| return createBool(left.doubleValue == right.doubleValue); | |
| } | |
| // For the remaining constants, if they are the same constant, they are | |
| // identical, otherwise not. | |
| return createBool(left == right); | |
| } | |
| } | |
| class IfNullOperation implements BinaryOperation { | |
| @override | |
| final String name = '??'; | |
| const IfNullOperation(); | |
| @override | |
| ConstantValue fold(ConstantValue left, ConstantValue right) { | |
| if (_unwrap(left) is NullConstantValue) { | |
| return right; | |
| } | |
| return left; | |
| } | |
| } | |
| class CodeUnitAtOperation implements BinaryOperation { | |
| @override | |
| final String name = 'charCodeAt'; | |
| const CodeUnitAtOperation(); | |
| @override | |
| NumConstantValue? fold(ConstantValue left, ConstantValue right) { | |
| left = _unwrap(left); | |
| right = _unwrap(right); | |
| if (left is StringConstantValue && right is IntConstantValue) { | |
| String string = left.stringValue; | |
| int index = right.intValue.toInt(); | |
| if (index < 0 || index >= string.length) return null; | |
| int value = string.codeUnitAt(index); | |
| return createIntFromInt(value); | |
| } | |
| return null; | |
| } | |
| } | |
| class RoundOperation implements UnaryOperation { | |
| @override | |
| final String name = 'round'; | |
| const RoundOperation(); | |
| @override | |
| NumConstantValue? fold(ConstantValue constant) { | |
| constant = _unwrap(constant); | |
| // Be careful to round() only values that do not throw on either the host or | |
| // target platform. | |
| NumConstantValue? tryToRound(double value) { | |
| // Due to differences between browsers, only 'round' easy cases. Avoid | |
| // cases where nudging the value up or down changes the answer. | |
| // 13 digits is safely within the ~15 digit precision of doubles. | |
| const severalULP = 0.0000000000001; | |
| // Use 'roundToDouble()' to avoid exceptions on rounding the nudged value. | |
| double rounded = value.roundToDouble(); | |
| double rounded1 = (value * (1.0 + severalULP)).roundToDouble(); | |
| double rounded2 = (value * (1.0 - severalULP)).roundToDouble(); | |
| if (rounded != rounded1 || rounded != rounded2) return null; | |
| return _convertToJavaScriptConstant( | |
| IntConstantValue(BigInt.from(value.round())), | |
| ); | |
| } | |
| if (constant is IntConstantValue) { | |
| double value = constant.intValue.toDouble(); | |
| if (value >= -double.maxFinite && value <= double.maxFinite) { | |
| return tryToRound(value); | |
| } | |
| } | |
| if (constant is DoubleConstantValue) { | |
| double value = constant.doubleValue; | |
| // NaN and infinities will throw. | |
| if (value.isNaN) return null; | |
| if (value.isInfinite) return null; | |
| return tryToRound(value); | |
| } | |
| return null; | |
| } | |
| } | |
| class ToIntOperation implements UnaryOperation { | |
| @override | |
| final String name = 'toInt'; | |
| const ToIntOperation(); | |
| @override | |
| NumConstantValue? fold(ConstantValue constant) { | |
| constant = _unwrap(constant); | |
| if (constant is IntConstantValue) { | |
| double value = constant.doubleValue; | |
| // The code below is written to work for any `double`, even though | |
| // IntConstantValue uses `BigInt`. | |
| // TODO(sra): IntConstantValue should wrap a `double` since we consider | |
| // infinities and negative zero to be `is int`. | |
| if (!value.isFinite) return null; | |
| // Ensure `(-0.0).toInt()` --> `0`. | |
| if (value == 0) return createIntFromInt(0); | |
| return constant; | |
| } | |
| // TODO(sra): Handle doubles. Note that integral-valued doubles are | |
| // canonicalized to IntConstantValue, so we are only missing `toInt()` | |
| // operations that truncate. | |
| return null; | |
| } | |
| } | |
| class _IndexOperation implements BinaryOperation { | |
| @override | |
| final String name = '[]'; | |
| const _IndexOperation(); | |
| @override | |
| ConstantValue? fold(ConstantValue left, ConstantValue right) { | |
| left = _unwrap(left); | |
| right = _unwrap(right); | |
| if (left is ListConstantValue) { | |
| if (right is IntConstantValue) { | |
| List<ConstantValue> entries = left.entries; | |
| if (right.isUInt32()) { | |
| int index = right.intValue.toInt(); | |
| if (index >= 0 && index < entries.length) { | |
| return entries[index]; | |
| } | |
| } | |
| } | |
| } | |
| if (left is MapConstantValue) { | |
| ConstantValue? value = left.lookup(right); | |
| if (value != null) return value; | |
| return const NullConstantValue(); | |
| } | |
| return null; | |
| } | |
| } | |
| class UnfoldedUnaryOperation implements UnaryOperation { | |
| @override | |
| final String name; | |
| const UnfoldedUnaryOperation(this.name); | |
| @override | |
| ConstantValue? fold(ConstantValue constant) { | |
| return null; | |
| } | |
| } | |
| class JavaScriptSetConstant extends SetConstantValue { | |
| static const String dartStringClass = "ConstantStringSet"; | |
| static const String dartGeneralClass = "GeneralConstantSet"; | |
| /// Index for all-string Sets. | |
| final JavaScriptObjectConstantValue? indexObject; | |
| JavaScriptSetConstant(super.type, super.elements, this.indexObject); | |
| @override | |
| List<ConstantValue> getDependencies() { | |
| if (indexObject == null) { | |
| // For a general constant Set the values are emitted as a literal array. | |
| return [...values]; | |
| } else { | |
| // For a ConstantStringSet, the index contains the elements. | |
| return [indexObject!]; | |
| } | |
| } | |
| } | |
| class JavaScriptMapConstant extends MapConstantValue { | |
| /// The [protoProperty] must not be used as normal property in any JavaScript | |
| /// object. It would change the prototype chain. | |
| static const String protoProperty = "__proto__"; | |
| /// The dart class implementing constant map literals. | |
| static const String dartClass = "ConstantMap"; | |
| static const String dartStringClass = "ConstantStringMap"; | |
| static const String dartGeneralClass = "GeneralConstantMap"; | |
| static const String lengthName = "_length"; | |
| static const String jsObjectName = "_jsObject"; | |
| static const String keysName = "_keys"; | |
| static const String jsDataName = "_jsData"; | |
| static const String jsIndexName = '_jsIndex'; | |
| static const String valuesName = '_values'; | |
| final ListConstantValue keyList; | |
| final ListConstantValue valueList; | |
| final bool onlyStringKeys; | |
| final JavaScriptObjectConstantValue? indexObject; | |
| JavaScriptMapConstant( | |
| InterfaceType type, | |
| this.keyList, | |
| this.valueList, | |
| this.onlyStringKeys, | |
| this.indexObject, | |
| ) : super(type, keyList.entries, valueList.entries); | |
| @override | |
| List<ConstantValue> getDependencies() { | |
| if (onlyStringKeys) { | |
| // TODO(25230): If we use `valueList` instead of `...values`, that creates | |
| // a constant list that has a name in the constant pool and the list has | |
| // Dart type attached. The Map constant has a reference to the list. If we | |
| // knew that the `valueList` was the only reference to the list, we could | |
| // generate the array in-place and omit the type. See [here][1] for more | |
| // on the idea of building constants with unnamed subexpressions. | |
| // | |
| // [1]: https://github.com/dart-lang/sdk/issues/25230 | |
| // | |
| // For now the values are generated in a fresh Array, so add the values. | |
| return [indexObject!, ...values]; | |
| } else { | |
| // The general representation uses a list of key/value pairs, so add the | |
| // keys and values individually to avoid generating an unused list | |
| // constant for the keys and values. | |
| return [...keys, ...values]; | |
| } | |
| } | |
| } | |
| ConstantValue _unwrap(ConstantValue value) { | |
| return value is DeferredGlobalConstantValue ? value.referenced : value; | |
| } |