diff options
author | chriseth <c@ethdev.com> | 2016-05-12 18:30:10 +0800 |
---|---|---|
committer | chriseth <c@ethdev.com> | 2016-05-12 18:30:10 +0800 |
commit | 1ab0f25dffe19f2cc9e1a0e2fa03a1091679f5a0 (patch) | |
tree | 267d5df3091710f7a1c3b54ddb8fcac5ab794015 | |
parent | 9e36bdda8a9552f1885e0a63a85db588623b39b2 (diff) | |
parent | d4206b7cd0bb0b8a3364c29fe097db035f308388 (diff) | |
download | dexon-solidity-1ab0f25dffe19f2cc9e1a0e2fa03a1091679f5a0.tar dexon-solidity-1ab0f25dffe19f2cc9e1a0e2fa03a1091679f5a0.tar.gz dexon-solidity-1ab0f25dffe19f2cc9e1a0e2fa03a1091679f5a0.tar.bz2 dexon-solidity-1ab0f25dffe19f2cc9e1a0e2fa03a1091679f5a0.tar.lz dexon-solidity-1ab0f25dffe19f2cc9e1a0e2fa03a1091679f5a0.tar.xz dexon-solidity-1ab0f25dffe19f2cc9e1a0e2fa03a1091679f5a0.tar.zst dexon-solidity-1ab0f25dffe19f2cc9e1a0e2fa03a1091679f5a0.zip |
Merge pull request #402 from VoR0220/fixedDataType
Fixed Type initial PR
-rw-r--r-- | libsolidity/analysis/ConstantEvaluator.cpp | 6 | ||||
-rw-r--r-- | libsolidity/analysis/ReferencesResolver.cpp | 7 | ||||
-rw-r--r-- | libsolidity/analysis/TypeChecker.cpp | 113 | ||||
-rw-r--r-- | libsolidity/ast/Types.cpp | 477 | ||||
-rw-r--r-- | libsolidity/ast/Types.h | 76 | ||||
-rw-r--r-- | libsolidity/codegen/CompilerUtils.cpp | 26 | ||||
-rw-r--r-- | libsolidity/codegen/ExpressionCompiler.cpp | 15 | ||||
-rw-r--r-- | libsolidity/codegen/LValue.cpp | 6 | ||||
-rw-r--r-- | libsolidity/formal/Why3Translator.cpp | 12 | ||||
-rw-r--r-- | libsolidity/parsing/Token.cpp | 5 | ||||
-rw-r--r-- | test/libsolidity/SolidityEndToEndTest.cpp | 1 | ||||
-rw-r--r-- | test/libsolidity/SolidityNameAndTypeResolution.cpp | 467 | ||||
-rw-r--r-- | test/libsolidity/SolidityParser.cpp | 46 |
13 files changed, 1047 insertions, 210 deletions
diff --git a/libsolidity/analysis/ConstantEvaluator.cpp b/libsolidity/analysis/ConstantEvaluator.cpp index 6beb655e..bdd8f61e 100644 --- a/libsolidity/analysis/ConstantEvaluator.cpp +++ b/libsolidity/analysis/ConstantEvaluator.cpp @@ -31,7 +31,7 @@ using namespace dev::solidity; void ConstantEvaluator::endVisit(UnaryOperation const& _operation) { TypePointer const& subType = _operation.subExpression().annotation().type; - if (!dynamic_cast<IntegerConstantType const*>(subType.get())) + if (!dynamic_cast<RationalNumberType const*>(subType.get())) BOOST_THROW_EXCEPTION(_operation.subExpression().createTypeError("Invalid constant expression.")); TypePointer t = subType->unaryOperatorResult(_operation.getOperator()); _operation.annotation().type = t; @@ -41,9 +41,9 @@ void ConstantEvaluator::endVisit(BinaryOperation const& _operation) { TypePointer const& leftType = _operation.leftExpression().annotation().type; TypePointer const& rightType = _operation.rightExpression().annotation().type; - if (!dynamic_cast<IntegerConstantType const*>(leftType.get())) + if (!dynamic_cast<RationalNumberType const*>(leftType.get())) BOOST_THROW_EXCEPTION(_operation.leftExpression().createTypeError("Invalid constant expression.")); - if (!dynamic_cast<IntegerConstantType const*>(rightType.get())) + if (!dynamic_cast<RationalNumberType const*>(rightType.get())) BOOST_THROW_EXCEPTION(_operation.rightExpression().createTypeError("Invalid constant expression.")); TypePointer commonType = leftType->binaryOperatorResult(_operation.getOperator(), rightType); if (Token::isCompareOp(_operation.getOperator())) diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index d7542bf3..a7b9e8b8 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -103,10 +103,9 @@ void ReferencesResolver::endVisit(ArrayTypeName const& _typeName) { if (!length->annotation().type) ConstantEvaluator e(*length); - - auto const* lengthType = dynamic_cast<IntegerConstantType const*>(length->annotation().type.get()); - if (!lengthType) - fatalTypeError(length->location(), "Invalid array length."); + auto const* lengthType = dynamic_cast<RationalNumberType const*>(length->annotation().type.get()); + if (!lengthType || lengthType->isFractional()) + fatalTypeError(length->location(), "Invalid array length, expected integer literal."); else _typeName.annotation().type = make_shared<ArrayType>(DataLocation::Storage, baseType, lengthType->literalValue(nullptr)); } diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index bc342b58..5ae0443a 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -772,26 +772,51 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) { // Infer type from value. solAssert(!var.typeName(), ""); - if ( - valueComponentType->category() == Type::Category::IntegerConstant && - !dynamic_pointer_cast<IntegerConstantType const>(valueComponentType)->integerType() - ) - fatalTypeError(_statement.initialValue()->location(), "Invalid integer constant " + valueComponentType->toString() + "."); var.annotation().type = valueComponentType->mobileType(); + if (!var.annotation().type) + { + if (valueComponentType->category() == Type::Category::RationalNumber) + fatalTypeError( + _statement.initialValue()->location(), + "Invalid rational " + + valueComponentType->toString() + + " (absolute value too large or divison by zero)." + ); + else + solAssert(false, ""); + } var.accept(*this); } else { var.accept(*this); if (!valueComponentType->isImplicitlyConvertibleTo(*var.annotation().type)) - typeError( - _statement.location(), - "Type " + - valueComponentType->toString() + - " is not implicitly convertible to expected type " + - var.annotation().type->toString() + - "." - ); + { + if ( + valueComponentType->category() == Type::Category::RationalNumber && + dynamic_cast<RationalNumberType const&>(*valueComponentType).isFractional() && + valueComponentType->mobileType() + ) + typeError( + _statement.location(), + "Type " + + valueComponentType->toString() + + " is not implicitly convertible to expected type " + + var.annotation().type->toString() + + ". Try converting to type " + + valueComponentType->mobileType()->toString() + + " or use an explicit conversion." + ); + else + typeError( + _statement.location(), + "Type " + + valueComponentType->toString() + + " is not implicitly convertible to expected type " + + var.annotation().type->toString() + + "." + ); + } } } return false; @@ -799,9 +824,9 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) void TypeChecker::endVisit(ExpressionStatement const& _statement) { - if (type(_statement.expression())->category() == Type::Category::IntegerConstant) - if (!dynamic_pointer_cast<IntegerConstantType const>(type(_statement.expression()))->integerType()) - typeError(_statement.expression().location(), "Invalid integer constant."); + if (type(_statement.expression())->category() == Type::Category::RationalNumber) + if (!dynamic_cast<RationalNumberType const&>(*type(_statement.expression())).mobileType()) + typeError(_statement.expression().location(), "Invalid rational number."); } bool TypeChecker::visit(Conditional const& _conditional) @@ -1106,9 +1131,9 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) auto const& argType = type(*arguments[i]); if (functionType->takesArbitraryParameters()) { - if (auto t = dynamic_cast<IntegerConstantType const*>(argType.get())) - if (!t->integerType()) - typeError(arguments[i]->location(), "Integer constant too large."); + if (auto t = dynamic_cast<RationalNumberType const*>(argType.get())) + if (!t->mobileType()) + typeError(arguments[i]->location(), "Invalid rational number (too large or division by zero)."); } else if (!type(*arguments[i])->isImplicitlyConvertibleTo(*parameterTypes[i])) typeError( @@ -1341,9 +1366,12 @@ bool TypeChecker::visit(IndexAccess const& _access) else { expectType(*index, IntegerType(256)); - if (auto integerType = dynamic_cast<IntegerConstantType const*>(type(*index).get())) - if (!actualType.isDynamicallySized() && actualType.length() <= integerType->literalValue(nullptr)) - typeError(_access.location(), "Out of bounds array access."); + if (auto numberType = dynamic_cast<RationalNumberType const*>(type(*index).get())) + { + if (!numberType->isFractional()) // error is reported above + if (!actualType.isDynamicallySized() && actualType.length() <= numberType->literalValue(nullptr)) + typeError(_access.location(), "Out of bounds array access."); + } } resultType = actualType.baseType(); isLValue = actualType.location() != DataLocation::CallData; @@ -1367,8 +1395,8 @@ bool TypeChecker::visit(IndexAccess const& _access) resultType = make_shared<TypeType>(make_shared<ArrayType>(DataLocation::Memory, typeType.actualType())); else { - index->accept(*this); - if (auto length = dynamic_cast<IntegerConstantType const*>(type(*index).get())) + expectType(*index, IntegerType(256)); + if (auto length = dynamic_cast<RationalNumberType const*>(type(*index).get())) resultType = make_shared<TypeType>(make_shared<ArrayType>( DataLocation::Memory, typeType.actualType(), @@ -1387,7 +1415,7 @@ bool TypeChecker::visit(IndexAccess const& _access) else { expectType(*index, IntegerType(256)); - if (auto integerType = dynamic_cast<IntegerConstantType const*>(type(*index).get())) + if (auto integerType = dynamic_cast<RationalNumberType const*>(type(*index).get())) if (bytesType.numBytes() <= integerType->literalValue(nullptr)) typeError(_access.location(), "Out of bounds array access."); } @@ -1492,16 +1520,33 @@ Declaration const& TypeChecker::dereference(UserDefinedTypeName const& _typeName void TypeChecker::expectType(Expression const& _expression, Type const& _expectedType) { _expression.accept(*this); - if (!type(_expression)->isImplicitlyConvertibleTo(_expectedType)) - typeError( - _expression.location(), - "Type " + - type(_expression)->toString() + - " is not implicitly convertible to expected type " + - _expectedType.toString() + - "." - ); + { + if ( + type(_expression)->category() == Type::Category::RationalNumber && + dynamic_pointer_cast<RationalNumberType const>(type(_expression))->isFractional() && + type(_expression)->mobileType() + ) + typeError( + _expression.location(), + "Type " + + type(_expression)->toString() + + " is not implicitly convertible to expected type " + + _expectedType.toString() + + ". Try converting to type " + + type(_expression)->mobileType()->toString() + + " or use an explicit conversion." + ); + else + typeError( + _expression.location(), + "Type " + + type(_expression)->toString() + + " is not implicitly convertible to expected type " + + _expectedType.toString() + + "." + ); + } } void TypeChecker::requireLValue(Expression const& _expression) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index d541de23..0df68d3d 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -122,7 +122,8 @@ TypePointer Type::fromElementaryTypeName(ElementaryTypeNameToken const& _type) ); Token::Value token = _type.token(); - unsigned int m = _type.firstNumber(); + unsigned m = _type.firstNumber(); + unsigned n = _type.secondNumber(); switch (token) { @@ -132,10 +133,18 @@ TypePointer Type::fromElementaryTypeName(ElementaryTypeNameToken const& _type) return make_shared<IntegerType>(m, IntegerType::Modifier::Unsigned); case Token::BytesM: return make_shared<FixedBytesType>(m); + case Token::FixedMxN: + return make_shared<FixedPointType>(m, n, FixedPointType::Modifier::Signed); + case Token::UFixedMxN: + return make_shared<FixedPointType>(m, n, FixedPointType::Modifier::Unsigned); case Token::Int: return make_shared<IntegerType>(256, IntegerType::Modifier::Signed); case Token::UInt: return make_shared<IntegerType>(256, IntegerType::Modifier::Unsigned); + case Token::Fixed: + return make_shared<FixedPointType>(128, 128, FixedPointType::Modifier::Signed); + case Token::UFixed: + return make_shared<FixedPointType>(128, 128, FixedPointType::Modifier::Unsigned); case Token::Byte: return make_shared<FixedBytesType>(1); case Token::Address: @@ -171,13 +180,17 @@ TypePointer Type::forLiteral(Literal const& _literal) case Token::FalseLiteral: return make_shared<BoolType>(); case Token::Number: - if (!IntegerConstantType::isValidLiteral(_literal)) + { + tuple<bool, rational> validLiteral = RationalNumberType::isValidLiteral(_literal); + if (get<0>(validLiteral) == true) + return make_shared<RationalNumberType>(get<1>(validLiteral)); + else return TypePointer(); - return make_shared<IntegerConstantType>(_literal); + } case Token::StringLiteral: return make_shared<StringLiteralType>(_literal); default: - return shared_ptr<Type>(); + return TypePointer(); } } @@ -246,17 +259,30 @@ IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier): bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const { - if (_convertTo.category() != category()) - return false; - IntegerType const& convertTo = dynamic_cast<IntegerType const&>(_convertTo); - if (convertTo.m_bits < m_bits) - return false; - if (isAddress()) - return convertTo.isAddress(); - else if (isSigned()) - return convertTo.isSigned(); + if (_convertTo.category() == category()) + { + IntegerType const& convertTo = dynamic_cast<IntegerType const&>(_convertTo); + if (convertTo.m_bits < m_bits) + return false; + if (isAddress()) + return convertTo.isAddress(); + else if (isSigned()) + return convertTo.isSigned(); + else + return !convertTo.isSigned() || convertTo.m_bits > m_bits; + } + else if (_convertTo.category() == Category::FixedPoint) + { + FixedPointType const& convertTo = dynamic_cast<FixedPointType const&>(_convertTo); + if (convertTo.integerBits() < m_bits || isAddress()) + return false; + else if (isSigned()) + return convertTo.isSigned(); + else + return !convertTo.isSigned() || convertTo.integerBits() > m_bits; + } else - return !convertTo.isSigned() || convertTo.m_bits > m_bits; + return false; } bool IntegerType::isExplicitlyConvertibleTo(Type const& _convertTo) const @@ -302,7 +328,7 @@ string IntegerType::toString(bool) const TypePointer IntegerType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const { - if (_other->category() != Category::IntegerConstant && _other->category() != category()) + if (_other->category() != Category::RationalNumber && _other->category() != category()) return TypePointer(); auto commonType = dynamic_pointer_cast<IntegerType const>(Type::commonType(shared_from_this(), _other)); @@ -335,143 +361,287 @@ MemberList::MemberMap IntegerType::nativeMembers(ContractDefinition const*) cons return MemberList::MemberMap(); } -bool IntegerConstantType::isValidLiteral(const Literal& _literal) +FixedPointType::FixedPointType(int _integerBits, int _fractionalBits, FixedPointType::Modifier _modifier): + m_integerBits(_integerBits), m_fractionalBits(_fractionalBits), m_modifier(_modifier) { - try + solAssert( + m_integerBits + m_fractionalBits > 0 && + m_integerBits + m_fractionalBits <= 256 && + m_integerBits % 8 == 0 && + m_fractionalBits % 8 == 0, + "Invalid bit number(s) for fixed type: " + + dev::toString(_integerBits) + "x" + dev::toString(_fractionalBits) + ); +} + +bool FixedPointType::isImplicitlyConvertibleTo(Type const& _convertTo) const +{ + if (_convertTo.category() == category()) { - bigint x(_literal.value()); + FixedPointType const& convertTo = dynamic_cast<FixedPointType const&>(_convertTo); + if (convertTo.m_integerBits < m_integerBits || convertTo.m_fractionalBits < m_fractionalBits) + return false; + else if (isSigned()) + return convertTo.isSigned(); + else + return !convertTo.isSigned() || (convertTo.m_integerBits > m_integerBits); } - catch (...) - { + + return false; +} + +bool FixedPointType::isExplicitlyConvertibleTo(Type const& _convertTo) const +{ + return _convertTo.category() == category() || + _convertTo.category() == Category::Integer || + _convertTo.category() == Category::FixedBytes; +} + +TypePointer FixedPointType::unaryOperatorResult(Token::Value _operator) const +{ + // "delete" is ok for all fixed types + if (_operator == Token::Delete) + return make_shared<TupleType>(); + // for fixed, we allow +, -, ++ and -- + else if ( + _operator == Token::Add || + _operator == Token::Sub || + _operator == Token::Inc || + _operator == Token::Dec || + _operator == Token::After + ) + return shared_from_this(); + else + return TypePointer(); +} + +bool FixedPointType::operator==(Type const& _other) const +{ + if (_other.category() != category()) return false; - } - return true; + FixedPointType const& other = dynamic_cast<FixedPointType const&>(_other); + return other.m_integerBits == m_integerBits && other.m_fractionalBits == m_fractionalBits && other.m_modifier == m_modifier; +} + +string FixedPointType::toString(bool) const +{ + string prefix = isSigned() ? "fixed" : "ufixed"; + return prefix + dev::toString(m_integerBits) + "x" + dev::toString(m_fractionalBits); } -IntegerConstantType::IntegerConstantType(Literal const& _literal) +TypePointer FixedPointType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const { - m_value = bigint(_literal.value()); + if ( + _other->category() != Category::RationalNumber + && _other->category() != category() + && _other->category() != Category::Integer + ) + return TypePointer(); + auto commonType = dynamic_pointer_cast<FixedPointType const>(Type::commonType(shared_from_this(), _other)); + + if (!commonType) + return TypePointer(); + + // All fixed types can be compared + if (Token::isCompareOp(_operator)) + return commonType; + if (Token::isBitOp(_operator) || Token::isBooleanOp(_operator)) + return TypePointer(); + if (Token::Exp == _operator) + return TypePointer(); + return commonType; +} + +tuple<bool, rational> RationalNumberType::isValidLiteral(Literal const& _literal) +{ + rational x; + try + { + rational numerator; + rational denominator(1); + + auto radixPoint = find(_literal.value().begin(), _literal.value().end(), '.'); + if (radixPoint != _literal.value().end()) + { + if ( + !all_of(radixPoint + 1, _literal.value().end(), ::isdigit) || + !all_of(_literal.value().begin(), radixPoint, ::isdigit) + ) + throw; + //Only decimal notation allowed here, leading zeros would switch to octal. + auto fractionalBegin = find_if_not( + radixPoint + 1, + _literal.value().end(), + [](char const& a) { return a == '0'; } + ); + denominator = bigint(string(fractionalBegin, _literal.value().end())); + denominator /= boost::multiprecision::pow( + bigint(10), + distance(radixPoint + 1, _literal.value().end()) + ); + numerator = bigint(string(_literal.value().begin(), radixPoint)); + x = numerator + denominator; + } + else + x = bigint(_literal.value()); + } + catch (...) + { + return make_tuple(false, rational(0)); + } switch (_literal.subDenomination()) { - case Literal::SubDenomination::Wei: - case Literal::SubDenomination::Second: - case Literal::SubDenomination::None: - break; - case Literal::SubDenomination::Szabo: - m_value *= bigint("1000000000000"); - break; - case Literal::SubDenomination::Finney: - m_value *= bigint("1000000000000000"); - break; - case Literal::SubDenomination::Ether: - m_value *= bigint("1000000000000000000"); - break; - case Literal::SubDenomination::Minute: - m_value *= bigint("60"); - break; - case Literal::SubDenomination::Hour: - m_value *= bigint("3600"); - break; - case Literal::SubDenomination::Day: - m_value *= bigint("86400"); - break; - case Literal::SubDenomination::Week: - m_value *= bigint("604800"); - break; - case Literal::SubDenomination::Year: - m_value *= bigint("31536000"); - break; + case Literal::SubDenomination::None: + case Literal::SubDenomination::Wei: + case Literal::SubDenomination::Second: + break; + case Literal::SubDenomination::Szabo: + x *= bigint("1000000000000"); + break; + case Literal::SubDenomination::Finney: + x *= bigint("1000000000000000"); + break; + case Literal::SubDenomination::Ether: + x *= bigint("1000000000000000000"); + break; + case Literal::SubDenomination::Minute: + x *= bigint("60"); + break; + case Literal::SubDenomination::Hour: + x *= bigint("3600"); + break; + case Literal::SubDenomination::Day: + x *= bigint("86400"); + break; + case Literal::SubDenomination::Week: + x *= bigint("604800"); + break; + case Literal::SubDenomination::Year: + x *= bigint("31536000"); + break; } + + + return make_tuple(true, x); } -bool IntegerConstantType::isImplicitlyConvertibleTo(Type const& _convertTo) const +bool RationalNumberType::isImplicitlyConvertibleTo(Type const& _convertTo) const { - if (auto targetType = dynamic_cast<IntegerType const*>(&_convertTo)) + if (_convertTo.category() == Category::Integer) { + auto targetType = dynamic_cast<IntegerType const*>(&_convertTo); if (m_value == 0) return true; + if (isFractional()) + return false; int forSignBit = (targetType->isSigned() ? 1 : 0); if (m_value > 0) { - if (m_value <= (u256(-1) >> (256 - targetType->numBits() + forSignBit))) + if (m_value.numerator() <= (u256(-1) >> (256 - targetType->numBits() + forSignBit))) return true; } - else if (targetType->isSigned() && -m_value <= (u256(1) << (targetType->numBits() - forSignBit))) + else if (targetType->isSigned() && -m_value.numerator() <= (u256(1) << (targetType->numBits() - forSignBit))) return true; return false; } + else if (_convertTo.category() == Category::FixedPoint) + { + if (auto fixed = fixedPointType()) + { + // We disallow implicit conversion if we would have to truncate (fixedPointType() + // can return a type that requires truncation). + rational value = m_value * (bigint(1) << fixed->fractionalBits()); + return value.denominator() == 1 && fixed->isImplicitlyConvertibleTo(_convertTo); + } + return false; + } else if (_convertTo.category() == Category::FixedBytes) { FixedBytesType const& fixedBytes = dynamic_cast<FixedBytesType const&>(_convertTo); - return fixedBytes.numBytes() * 8 >= integerType()->numBits(); + if (!isFractional()) + return fixedBytes.numBytes() * 8 >= integerType()->numBits(); + else + return false; } - else - return false; + return false; } -bool IntegerConstantType::isExplicitlyConvertibleTo(Type const& _convertTo) const +bool RationalNumberType::isExplicitlyConvertibleTo(Type const& _convertTo) const { - TypePointer intType = integerType(); - return intType && intType->isExplicitlyConvertibleTo(_convertTo); + TypePointer mobType = mobileType(); + return mobType && mobType->isExplicitlyConvertibleTo(_convertTo); } -TypePointer IntegerConstantType::unaryOperatorResult(Token::Value _operator) const +TypePointer RationalNumberType::unaryOperatorResult(Token::Value _operator) const { - bigint value; + rational value; switch (_operator) { case Token::BitNot: - value = ~m_value; + if (isFractional()) + return TypePointer(); + value = ~m_value.numerator(); break; case Token::Add: - value = m_value; + value = +(m_value); break; case Token::Sub: - value = -m_value; + value = -(m_value); break; case Token::After: return shared_from_this(); default: return TypePointer(); } - return make_shared<IntegerConstantType>(value); + return make_shared<RationalNumberType>(value); } -TypePointer IntegerConstantType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const +TypePointer RationalNumberType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const { - if (_other->category() == Category::Integer) + if (_other->category() == Category::Integer || _other->category() == Category::FixedPoint) { - shared_ptr<IntegerType const> intType = integerType(); - if (!intType) + auto mobile = mobileType(); + if (!mobile) return TypePointer(); - return intType->binaryOperatorResult(_operator, _other); + return mobile->binaryOperatorResult(_operator, _other); } else if (_other->category() != category()) return TypePointer(); - IntegerConstantType const& other = dynamic_cast<IntegerConstantType const&>(*_other); + RationalNumberType const& other = dynamic_cast<RationalNumberType const&>(*_other); if (Token::isCompareOp(_operator)) { - shared_ptr<IntegerType const> thisIntegerType = integerType(); - shared_ptr<IntegerType const> otherIntegerType = other.integerType(); - if (!thisIntegerType || !otherIntegerType) + // Since we do not have a "BoolConstantType", we have to do the acutal comparison + // at runtime and convert to mobile typse first. Such a comparison is not a very common + // use-case and will be optimized away. + TypePointer thisMobile = mobileType(); + TypePointer otherMobile = other.mobileType(); + if (!thisMobile || !otherMobile) return TypePointer(); - return thisIntegerType->binaryOperatorResult(_operator, otherIntegerType); + return thisMobile->binaryOperatorResult(_operator, otherMobile); } else { - bigint value; + rational value; + bool fractional = isFractional() || other.isFractional(); switch (_operator) { + //bit operations will only be enabled for integers and fixed types that resemble integers case Token::BitOr: - value = m_value | other.m_value; + if (fractional) + return TypePointer(); + value = m_value.numerator() | other.m_value.numerator(); break; case Token::BitXor: - value = m_value ^ other.m_value; + if (fractional) + return TypePointer(); + value = m_value.numerator() ^ other.m_value.numerator(); break; case Token::BitAnd: - value = m_value & other.m_value; + if (fractional) + return TypePointer(); + value = m_value.numerator() & other.m_value.numerator(); break; case Token::Add: value = m_value + other.m_value; @@ -485,68 +655,104 @@ TypePointer IntegerConstantType::binaryOperatorResult(Token::Value _operator, Ty case Token::Div: if (other.m_value == 0) return TypePointer(); - value = m_value / other.m_value; + else + value = m_value / other.m_value; break; case Token::Mod: if (other.m_value == 0) return TypePointer(); - value = m_value % other.m_value; - break; + else if (fractional) + { + rational tempValue = m_value / other.m_value; + value = m_value - (tempValue.numerator() / tempValue.denominator()) * other.m_value; + } + else + value = m_value.numerator() % other.m_value.numerator(); + break; case Token::Exp: - if (other.m_value < 0) - return TypePointer(); - else if (other.m_value > numeric_limits<unsigned int>::max()) + { + using boost::multiprecision::pow; + if (other.isFractional()) return TypePointer(); + else if (abs(other.m_value) > numeric_limits<uint32_t>::max()) + return TypePointer(); // This will need too much memory to represent. + uint32_t exponent = abs(other.m_value).numerator().convert_to<uint32_t>(); + bigint numerator = pow(m_value.numerator(), exponent); + bigint denominator = pow(m_value.denominator(), exponent); + if (other.m_value >= 0) + value = rational(numerator, denominator); else - value = boost::multiprecision::pow(m_value, other.m_value.convert_to<unsigned int>()); + // invert + value = rational(denominator, numerator); break; + } default: return TypePointer(); } - return make_shared<IntegerConstantType>(value); + return make_shared<RationalNumberType>(value); } } -bool IntegerConstantType::operator==(Type const& _other) const +bool RationalNumberType::operator==(Type const& _other) const { if (_other.category() != category()) return false; - return m_value == dynamic_cast<IntegerConstantType const&>(_other).m_value; + RationalNumberType const& other = dynamic_cast<RationalNumberType const&>(_other); + return m_value == other.m_value; } -string IntegerConstantType::toString(bool) const +string RationalNumberType::toString(bool) const { - return "int_const " + m_value.str(); + if (!isFractional()) + return "int_const " + m_value.numerator().str(); + return "rational_const " + m_value.numerator().str() + '/' + m_value.denominator().str(); } -u256 IntegerConstantType::literalValue(Literal const*) const +u256 RationalNumberType::literalValue(Literal const*) const { + // We ignore the literal and hope that the type was correctly determined to represent + // its value. + u256 value; + bigint shiftedValue; + + if (!isFractional()) + shiftedValue = m_value.numerator(); + else + { + auto fixed = fixedPointType(); + solAssert(!!fixed, ""); + rational shifted = m_value * (bigint(1) << fixed->fractionalBits()); + // truncate + shiftedValue = shifted.numerator() / shifted.denominator(); + } + // we ignore the literal and hope that the type was correctly determined - solAssert(m_value <= u256(-1), "Integer constant too large."); - solAssert(m_value >= -(bigint(1) << 255), "Integer constant too small."); + solAssert(shiftedValue <= u256(-1), "Integer constant too large."); + solAssert(shiftedValue >= -(bigint(1) << 255), "Number constant too small."); if (m_value >= 0) - value = u256(m_value); + value = u256(shiftedValue); else - value = s2u(s256(m_value)); - + value = s2u(s256(shiftedValue)); return value; } -TypePointer IntegerConstantType::mobileType() const +TypePointer RationalNumberType::mobileType() const { - auto intType = integerType(); - solAssert(!!intType, "mobileType called with invalid integer constant " + toString(false)); - return intType; + if (!isFractional()) + return integerType(); + else + return fixedPointType(); } -shared_ptr<IntegerType const> IntegerConstantType::integerType() const +shared_ptr<IntegerType const> RationalNumberType::integerType() const { - bigint value = m_value; + solAssert(!isFractional(), "integerType() called for fractional number."); + bigint value = m_value.numerator(); bool negative = (value < 0); if (negative) // convert to positive number of same bit requirements - value = ((-value) - 1) << 1; + value = ((0 - value) - 1) << 1; if (value > u256(-1)) return shared_ptr<IntegerType const>(); else @@ -556,6 +762,58 @@ shared_ptr<IntegerType const> IntegerConstantType::integerType() const ); } +shared_ptr<FixedPointType const> RationalNumberType::fixedPointType() const +{ + bool negative = (m_value < 0); + unsigned fractionalBits = 0; + rational value = abs(m_value); // We care about the sign later. + rational maxValue = negative ? + rational(bigint(1) << 255, 1): + rational((bigint(1) << 256) - 1, 1); + + while (value * 0x100 <= maxValue && value.denominator() != 1 && fractionalBits < 256) + { + value *= 0x100; + fractionalBits += 8; + } + + if (value > maxValue) + return shared_ptr<FixedPointType const>(); + // u256(v) is the actual value that will be put on the stack + // From here on, very similar to integerType() + bigint v = value.numerator() / value.denominator(); + if (negative) + // modify value to satisfy bit requirements for negative numbers: + // add one bit for sign and decrement because negative numbers can be larger + v = (v - 1) << 1; + + if (v > u256(-1)) + return shared_ptr<FixedPointType const>(); + + unsigned totalBits = bytesRequired(v) * 8; + solAssert(totalBits <= 256, ""); + unsigned integerBits = totalBits >= fractionalBits ? totalBits - fractionalBits : 0; + // Special case: Numbers between -1 and 0 have their sign bit in the fractional part. + if (negative && abs(m_value) < 1 && totalBits > fractionalBits) + { + fractionalBits += 8; + integerBits = 0; + } + + if (integerBits > 256 || fractionalBits > 256 || fractionalBits + integerBits > 256) + return shared_ptr<FixedPointType const>(); + if (integerBits == 0 && fractionalBits == 0) + { + integerBits = 0; + fractionalBits = 8; + } + + return make_shared<FixedPointType>( + integerBits, fractionalBits, + negative ? FixedPointType::Modifier::Signed : FixedPointType::Modifier::Unsigned + ); +} + StringLiteralType::StringLiteralType(Literal const& _literal): m_value(_literal.value()) { @@ -609,6 +867,7 @@ bool FixedBytesType::isImplicitlyConvertibleTo(Type const& _convertTo) const bool FixedBytesType::isExplicitlyConvertibleTo(Type const& _convertTo) const { return _convertTo.category() == Category::Integer || + _convertTo.category() == Category::FixedPoint || _convertTo.category() == Category::Contract || _convertTo.category() == category(); } @@ -1277,9 +1536,9 @@ bool EnumType::isExplicitlyConvertibleTo(Type const& _convertTo) const return _convertTo.category() == category() || _convertTo.category() == Category::Integer; } -unsigned int EnumType::memberValue(ASTString const& _member) const +unsigned EnumType::memberValue(ASTString const& _member) const { - unsigned int index = 0; + unsigned index = 0; for (ASTPointer<EnumValue> const& decl: m_enum.members()) { if (decl->name() == _member) diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index d42bb5dd..0c437316 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -26,6 +26,7 @@ #include <string> #include <map> #include <boost/noncopyable.hpp> +#include <boost/rational.hpp> #include <libdevcore/Common.h> #include <libdevcore/CommonIO.h> #include <libsolidity/interface/Exceptions.h> @@ -43,6 +44,7 @@ class FunctionType; // forward using TypePointer = std::shared_ptr<Type const>; using FunctionTypePointer = std::shared_ptr<FunctionType const>; using TypePointers = std::vector<TypePointer>; +using rational = boost::rational<dev::bigint>; enum class DataLocation { Storage, CallData, Memory }; @@ -133,7 +135,7 @@ class Type: private boost::noncopyable, public std::enable_shared_from_this<Type public: enum class Category { - Integer, IntegerConstant, StringLiteral, Bool, Real, Array, + Integer, RationalNumber, StringLiteral, Bool, FixedPoint, Array, FixedBytes, Contract, Struct, Function, Enum, Tuple, Mapping, TypeType, Modifier, Magic, Module }; @@ -202,8 +204,9 @@ public: virtual bool isValueType() const { return false; } virtual unsigned sizeOnStack() const { return 1; } /// @returns the mobile (in contrast to static) type corresponding to the given type. - /// This returns the corresponding integer type for IntegerConstantTypes and the pointer type - /// for storage reference types. + /// This returns the corresponding IntegerType or FixedPointType for RationalNumberType + /// and the pointer type for storage reference types. + /// Might return a null pointer if there is no fitting type. virtual TypePointer mobileType() const { return shared_from_this(); } /// @returns true if this is a non-value type and the data of this type is stored at the /// given location. @@ -309,20 +312,63 @@ private: }; /** - * Integer constants either literals or computed. Example expressions: 2, 2+10, ~10. - * There is one distinct type per value. + * A fixed point type number (signed, unsigned). */ -class IntegerConstantType: public Type +class FixedPointType: public Type { public: - virtual Category category() const override { return Category::IntegerConstant; } + enum class Modifier + { + Unsigned, Signed + }; + virtual Category category() const override { return Category::FixedPoint; } - /// @returns true if the literal is a valid integer. - static bool isValidLiteral(Literal const& _literal); + explicit FixedPointType(int _integerBits, int _fractionalBits, Modifier _modifier = Modifier::Unsigned); + + virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; + virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; + virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; + virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override; + + virtual bool operator==(Type const& _other) const override; + + virtual unsigned calldataEncodedSize(bool _padded = true) const override { return _padded ? 32 : (m_integerBits + m_fractionalBits) / 8; } + virtual unsigned storageBytes() const override { return (m_integerBits + m_fractionalBits) / 8; } + virtual bool isValueType() const override { return true; } + + virtual std::string toString(bool _short) const override; + + virtual TypePointer encodingType() const override { return shared_from_this(); } + virtual TypePointer interfaceType(bool) const override { return shared_from_this(); } - explicit IntegerConstantType(Literal const& _literal); - explicit IntegerConstantType(bigint _value): m_value(_value) {} + int numBits() const { return m_integerBits + m_fractionalBits; } + int integerBits() const { return m_integerBits; } + int fractionalBits() const { return m_fractionalBits; } + bool isSigned() const { return m_modifier == Modifier::Signed; } +private: + int m_integerBits; + int m_fractionalBits; + Modifier m_modifier; +}; + +/** + * Integer and fixed point constants either literals or computed. + * Example expressions: 2, 3.14, 2+10.2, ~10. + * There is one distinct type per value. + */ +class RationalNumberType: public Type +{ +public: + + virtual Category category() const override { return Category::RationalNumber; } + + /// @returns true if the literal is a valid integer. + static std::tuple<bool, rational> isValidLiteral(Literal const& _literal); + + explicit RationalNumberType(rational const& _value): + m_value(_value) + {} virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; @@ -339,9 +385,15 @@ public: /// @returns the smallest integer type that can hold the value or an empty pointer if not possible. std::shared_ptr<IntegerType const> integerType() const; + /// @returns the smallest fixed type that can hold the value or incurs the least precision loss. + /// If the integer part does not fit, returns an empty pointer. + std::shared_ptr<FixedPointType const> fixedPointType() const; + + /// @returns true if the value is not an integer. + bool isFractional() const { return m_value.denominator() != 1; } private: - bigint m_value; + rational m_value; }; /** diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 36ed480e..e35f3374 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -343,12 +343,14 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp case Type::Category::Enum: solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Enum, ""); break; + case Type::Category::FixedPoint: + solAssert(false, "Not yet implemented - FixedPointType."); case Type::Category::Integer: case Type::Category::Contract: - case Type::Category::IntegerConstant: + case Type::Category::RationalNumber: if (targetTypeCategory == Type::Category::FixedBytes) { - solAssert(stackTypeCategory == Type::Category::Integer || stackTypeCategory == Type::Category::IntegerConstant, + solAssert(stackTypeCategory == Type::Category::Integer || stackTypeCategory == Type::Category::RationalNumber, "Invalid conversion to FixedBytesType requested."); // conversion from bytes to string. no need to clean the high bit // only to shift left because of opposite alignment @@ -361,17 +363,33 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp else if (targetTypeCategory == Type::Category::Enum) // just clean convertType(_typeOnStack, *_typeOnStack.mobileType(), true); + else if (targetTypeCategory == Type::Category::FixedPoint) + { + solAssert( + stackTypeCategory == Type::Category::Integer || + stackTypeCategory == Type::Category::RationalNumber || + stackTypeCategory == Type::Category::FixedPoint, + "Invalid conversion to FixedMxNType requested." + ); + //shift all integer bits onto the left side of the fixed type + FixedPointType const& targetFixedPointType = dynamic_cast<FixedPointType const&>(_targetType); + if (auto typeOnStack = dynamic_cast<IntegerType const*>(&_typeOnStack)) + if (targetFixedPointType.integerBits() > typeOnStack->numBits()) + cleanHigherOrderBits(*typeOnStack); + solAssert(false, "Not yet implemented - FixedPointType."); + } else { solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Contract, ""); IntegerType addressType(0, IntegerType::Modifier::Address); IntegerType const& targetType = targetTypeCategory == Type::Category::Integer ? dynamic_cast<IntegerType const&>(_targetType) : addressType; - if (stackTypeCategory == Type::Category::IntegerConstant) + if (stackTypeCategory == Type::Category::RationalNumber) { - IntegerConstantType const& constType = dynamic_cast<IntegerConstantType const&>(_typeOnStack); + RationalNumberType const& constType = dynamic_cast<RationalNumberType const&>(_typeOnStack); // We know that the stack is clean, we only have to clean for a narrowing conversion // where cleanup is forced. + solAssert(!constType.isFractional(), "Not yet implemented - FixedPointType."); if (targetType.numBits() < constType.integerType()->numBits() && _cleanupNeeded) cleanHigherOrderBits(targetType); } diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index ab02e0b5..a01e306e 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -281,11 +281,7 @@ bool ExpressionCompiler::visit(TupleExpression const& _tuple) bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) { CompilerContext::LocationSetter locationSetter(m_context, _unaryOperation); - //@todo type checking and creating code for an operator should be in the same place: - // the operator should know how to convert itself and to which types it applies, so - // put this code together with "Type::acceptsBinary/UnaryOperator" into a class that - // represents the operator - if (_unaryOperation.annotation().type->category() == Type::Category::IntegerConstant) + if (_unaryOperation.annotation().type->category() == Type::Category::RationalNumber) { m_context << _unaryOperation.annotation().type->literalValue(nullptr); return false; @@ -360,7 +356,7 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) if (c_op == Token::And || c_op == Token::Or) // special case: short-circuiting appendAndOrOperatorCode(_binaryOperation); - else if (commonType.category() == Type::Category::IntegerConstant) + else if (commonType.category() == Type::Category::RationalNumber) m_context << commonType.literalValue(nullptr); else { @@ -370,7 +366,7 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) // for commutative operators, push the literal as late as possible to allow improved optimization auto isLiteral = [](Expression const& _e) { - return dynamic_cast<Literal const*>(&_e) || _e.annotation().type->category() == Type::Category::IntegerConstant; + return dynamic_cast<Literal const*>(&_e) || _e.annotation().type->category() == Type::Category::RationalNumber; }; bool swap = m_optimize && Token::isCommutativeOp(c_op) && isLiteral(rightExpression) && !isLiteral(leftExpression); if (swap) @@ -1225,7 +1221,7 @@ void ExpressionCompiler::endVisit(Literal const& _literal) switch (type->category()) { - case Type::Category::IntegerConstant: + case Type::Category::RationalNumber: case Type::Category::Bool: m_context << type->literalValue(&_literal); break; @@ -1306,6 +1302,9 @@ void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Ty IntegerType const& type = dynamic_cast<IntegerType const&>(_type); bool const c_isSigned = type.isSigned(); + if (_type.category() == Type::Category::FixedPoint) + solAssert(false, "Not yet implemented - FixedPointType."); + switch (_operator) { case Token::Add: diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp index fcadd2ff..ea8bc1ba 100644 --- a/libsolidity/codegen/LValue.cpp +++ b/libsolidity/codegen/LValue.cpp @@ -179,6 +179,9 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const m_context << Instruction::SWAP1 << Instruction::SLOAD << Instruction::SWAP1 << u256(0x100) << Instruction::EXP << Instruction::SWAP1 << Instruction::DIV; + if (m_dataType->category() == Type::Category::FixedPoint) + // implementation should be very similar to the integer case. + solAssert(false, "Not yet implemented - FixedPointType."); if (m_dataType->category() == Type::Category::FixedBytes) m_context << (u256(0x1) << (256 - 8 * m_dataType->storageBytes())) << Instruction::MUL; else if ( @@ -239,6 +242,9 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc << Instruction::DUP2 << Instruction::MUL << Instruction::DIV; + else if (m_dataType->category() == Type::Category::FixedPoint) + // implementation should be very similar to the integer case. + solAssert(false, "Not yet implemented - FixedPointType."); m_context << Instruction::MUL << Instruction::OR; // stack: value storage_ref updated_value m_context << Instruction::SWAP1 << Instruction::SSTORE; diff --git a/libsolidity/formal/Why3Translator.cpp b/libsolidity/formal/Why3Translator.cpp index 24fbab13..c794cb24 100644 --- a/libsolidity/formal/Why3Translator.cpp +++ b/libsolidity/formal/Why3Translator.cpp @@ -428,8 +428,11 @@ bool Why3Translator::visit(BinaryOperation const& _binaryOperation) Type const& commonType = *_binaryOperation.annotation().commonType; Token::Value const c_op = _binaryOperation.getOperator(); - if (commonType.category() == Type::Category::IntegerConstant) + if (commonType.category() == Type::Category::RationalNumber) { + auto const& constantNumber = dynamic_cast<RationalNumberType const&>(commonType); + if (constantNumber.isFractional()) + error(_binaryOperation, "Fractional numbers not supported."); add("(of_int " + toString(commonType.literalValue(nullptr)) + ")"); return false; } @@ -589,9 +592,14 @@ bool Why3Translator::visit(Literal const& _literal) else add("true"); break; - case Type::Category::IntegerConstant: + case Type::Category::RationalNumber: + { + auto const& constantNumber = dynamic_cast<RationalNumberType const&>(*type); + if (constantNumber.isFractional()) + error(_literal, "Fractional numbers not supported."); add("(of_int " + toString(type->literalValue(&_literal)) + ")"); break; + } default: error(_literal, "Not supported."); } diff --git a/libsolidity/parsing/Token.cpp b/libsolidity/parsing/Token.cpp index c73368e5..0ab97988 100644 --- a/libsolidity/parsing/Token.cpp +++ b/libsolidity/parsing/Token.cpp @@ -109,6 +109,7 @@ char const Token::m_tokenType[] = { TOKEN_LIST(KT, KK) }; + int Token::parseSize(string::const_iterator _begin, string::const_iterator _end) { try @@ -121,6 +122,7 @@ int Token::parseSize(string::const_iterator _begin, string::const_iterator _end) return -1; } } + tuple<Token::Value, unsigned int, unsigned int> Token::fromIdentifierOrKeyword(string const& _literal) { auto positionM = find_if(_literal.begin(), _literal.end(), ::isdigit); @@ -156,7 +158,7 @@ tuple<Token::Value, unsigned int, unsigned int> Token::fromIdentifierOrKeyword(s int n = parseSize(positionX + 1, _literal.end()); if ( 0 <= m && m <= 256 && - 0 <= n && n <= 256 && + 8 <= n && n <= 256 && m + n > 0 && m + n <= 256 && m % 8 == 0 && @@ -171,6 +173,7 @@ tuple<Token::Value, unsigned int, unsigned int> Token::fromIdentifierOrKeyword(s } return make_tuple(Token::Identifier, 0, 0); } + return make_tuple(keywordByName(_literal), 0, 0); } Token::Value Token::keywordByName(string const& _name) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 9df64cdc..de428f96 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -6631,6 +6631,7 @@ BOOST_AUTO_TEST_CASE(delete_on_array_of_structs) // This code interprets x as an array length and thus will go out of gas. // neither of the two should throw due to out-of-bounds access BOOST_CHECK(callContractFunction("f()") == encodeArgs(true)); + } BOOST_AUTO_TEST_CASE(internal_library_function) diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 9c411781..d7f7961a 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -97,13 +97,20 @@ parseAnalyseAndReturnError(string const& _source, bool _reportWarnings = false) return make_pair(sourceUnit, std::make_shared<Error::Type const>(currentError->type())); } } + catch (InternalCompilerError const& _e) + { + string message("Internal compiler error"); + if (string const* description = boost::get_error_info<errinfo_comment>(_e)) + message += ": " + *description; + BOOST_FAIL(message); + } catch (Error const& _e) { return make_pair(sourceUnit, std::make_shared<Error::Type const>(_e.type())); } - catch (Exception const& /*_exception*/) + catch (...) { - return make_pair(sourceUnit, nullptr); + BOOST_FAIL("Unexpected exception."); } return make_pair(sourceUnit, nullptr); } @@ -1330,15 +1337,6 @@ BOOST_AUTO_TEST_CASE(overflow_caused_by_ether_units) BOOST_CHECK(expectError(sourceCode) == Error::Type::TypeError); } -BOOST_AUTO_TEST_CASE(exp_operator_negative_exponent) -{ - char const* sourceCode = R"( - contract test { - function f() returns(uint d) { return 2 ** -3; } - })"; - BOOST_CHECK(expectError(sourceCode) == Error::Type::TypeError); -} - BOOST_AUTO_TEST_CASE(exp_operator_exponent_too_big) { char const* sourceCode = R"( @@ -2229,18 +2227,6 @@ BOOST_AUTO_TEST_CASE(literal_strings) BOOST_CHECK(success(text)); } -BOOST_AUTO_TEST_CASE(invalid_integer_literal_fraction) -{ - char const* text = R"( - contract Foo { - function f() { - var x = 1.20; - } - } - )"; - BOOST_CHECK(expectError(text) == Error::Type::TypeError); -} - BOOST_AUTO_TEST_CASE(invalid_integer_literal_exp) { char const* text = R"( @@ -2792,8 +2778,8 @@ BOOST_AUTO_TEST_CASE(inline_array_declaration_and_passing_implicit_conversion) uint8 x = 7; uint16 y = 8; uint32 z = 9; - uint32[3] memory ending = [x, y, z]; - return (ending[1]); + uint32[3] memory ending = [x, y, z]; + return (ending[1]); } } )"; @@ -3230,27 +3216,44 @@ BOOST_AUTO_TEST_CASE(int10abc_is_identifier) BOOST_CHECK(success(text)); } -BOOST_AUTO_TEST_CASE(invalid_fixed_types) +BOOST_AUTO_TEST_CASE(library_functions_do_not_have_value) { char const* text = R"( + library L { function l() {} } contract test { function f() { - fixed0x7 a = .3; - fixed99999999999999999999999999999999999999x7 b = 9.5; + L.l.value; } } )"; BOOST_CHECK(!success(text)); } -BOOST_AUTO_TEST_CASE(library_functions_do_not_have_value) +BOOST_AUTO_TEST_CASE(invalid_fixed_types_0x7_mxn) { char const* text = R"( - library L { function l() {} } contract test { - function f() { - L.l.value; - } + fixed0x7 a = .3; + } + )"; + BOOST_CHECK(!success(text)); +} + +BOOST_AUTO_TEST_CASE(invalid_fixed_types_long_invalid_identifier) +{ + char const* text = R"( + contract test { + fixed99999999999999999999999999999999999999x7 b = 9.5; + } + )"; + BOOST_CHECK(!success(text)); +} + +BOOST_AUTO_TEST_CASE(invalid_fixed_types_7x8_mxn) +{ + char const* text = R"( + contract test { + fixed7x8 c = 3.12345678; } )"; BOOST_CHECK(!success(text)); @@ -3282,6 +3285,404 @@ BOOST_AUTO_TEST_CASE(invalid_fixed_type_long) BOOST_CHECK(!success(text)); } +BOOST_AUTO_TEST_CASE(fixed_type_int_conversion) +{ + char const* text = R"( + contract test { + function f() { + uint128 a = 3; + int128 b = 4; + fixed c = b; + ufixed d = a; + } + } + )"; + BOOST_CHECK(success(text)); +} + +BOOST_AUTO_TEST_CASE(fixed_type_rational_int_conversion) +{ + char const* text = R"( + contract test { + function f() { + fixed c = 3; + ufixed d = 4; + } + } + )"; + BOOST_CHECK(success(text)); +} + +BOOST_AUTO_TEST_CASE(fixed_type_rational_fraction_conversion) +{ + char const* text = R"( + contract test { + function f() { + fixed a = 4.5; + ufixed d = 2.5; + } + } + )"; + BOOST_CHECK(success(text)); +} + +BOOST_AUTO_TEST_CASE(invalid_int_implicit_conversion_from_fixed) +{ + char const* text = R"( + contract test { + function f() { + fixed a = 4.5; + int b = a; + } + } + )"; + BOOST_CHECK(!success(text)); +} + +BOOST_AUTO_TEST_CASE(rational_unary_operation) +{ + char const* text = R"( + contract test { + function f() { + ufixed8x16 a = +3.25; + fixed8x16 b = -3.25; + } + } + )"; + BOOST_CHECK(success(text)); +} + +BOOST_AUTO_TEST_CASE(leading_zero_rationals_convert) +{ + char const* text = R"( + contract A { + function f() { + ufixed0x8 a = 0.5; + ufixed0x56 b = 0.0000000000000006661338147750939242541790008544921875; + fixed0x8 c = -0.5; + fixed0x56 d = -0.0000000000000006661338147750939242541790008544921875; + } + } + )"; + BOOST_CHECK(success(text)); +} + +BOOST_AUTO_TEST_CASE(size_capabilities_of_fixed_point_types) +{ + char const* text = R"( + contract test { + function f() { + ufixed248x8 a = 123456781234567979695948382928485849359686494864095409282048094275023098123.5; + ufixed0x256 b = 0.920890746623327805482905058466021565416131529487595827354393978494366605267637829135688384325135165352082715782143655824815685807141335814463015972119819459298455224338812271036061391763384038070334798471324635050876128428143374549108557403087615966796875; + ufixed0x256 c = 0.0000000000015198847363997979984922685411315294875958273543939784943666052676464653042434787697605517039455161817147718251801220885263595179331845639229818863564267318422845592626219390573301877339317935702714669975697814319204326238832436501979827880859375; + fixed248x8 d = -123456781234567979695948382928485849359686494864095409282048094275023098123.5; + fixed0x256 e = -0.93322335481643744342575580035176794825198893968114429702091846411734101080123092162893656820177312738451291806995868682861328125; + fixed0x256 g = -0.00011788606643744342575580035176794825198893968114429702091846411734101080123092162893656820177312738451291806995868682861328125; + } + } + )"; + BOOST_CHECK(success(text)); +} + +BOOST_AUTO_TEST_CASE(fixed_type_invalid_implicit_conversion_size) +{ + char const* text = R"( + contract test { + function f() { + ufixed a = 11/4; + ufixed248x8 b = a; + } + } + )"; + BOOST_CHECK(!success(text)); +} + +BOOST_AUTO_TEST_CASE(fixed_type_invalid_implicit_conversion_lost_data) +{ + char const* text = R"( + contract test { + function f() { + ufixed0x256 a = 1/3; + } + } + )"; + BOOST_CHECK(!success(text)); +} + +BOOST_AUTO_TEST_CASE(fixed_type_valid_explicit_conversions) +{ + char const* text = R"( + contract test { + function f() { + ufixed0x256 a = ufixed0x256(1/3); + ufixed0x248 b = ufixed0x248(1/3); + ufixed0x8 c = ufixed0x8(1/3); + } + } + )"; + BOOST_CHECK(success(text)); +} + +BOOST_AUTO_TEST_CASE(invalid_array_declaration_with_rational) +{ + char const* text = R"( + contract test { + function f() { + uint[3.5] a; + } + } + )"; + BOOST_CHECK(!success(text)); +} + +BOOST_AUTO_TEST_CASE(invalid_array_declaration_with_fixed_type) +{ + char const* text = R"( + contract test { + function f() { + uint[fixed(3.5)] a; + } + } + )"; + BOOST_CHECK(!success(text)); +} + +BOOST_AUTO_TEST_CASE(rational_to_bytes_implicit_conversion) +{ + char const* text = R"( + contract test { + function f() { + bytes32 c = 3.2; + } + } + )"; + BOOST_CHECK(!success(text)); +} + +BOOST_AUTO_TEST_CASE(fixed_to_bytes_implicit_conversion) +{ + char const* text = R"( + contract test { + function f() { + fixed a = 3.2; + bytes32 c = a; + } + } + )"; + BOOST_CHECK(!success(text)); +} + +BOOST_AUTO_TEST_CASE(mapping_with_fixed_literal) +{ + char const* text = R"( + contract test { + mapping(ufixed8x248 => string) fixedString; + function f() { + fixedString[0.5] = "Half"; + } + } + )"; + BOOST_CHECK(success(text)); +} + +BOOST_AUTO_TEST_CASE(fixed_points_inside_structs) +{ + char const* text = R"( + contract test { + struct myStruct { + ufixed a; + int b; + } + myStruct a = myStruct(3.125, 3); + } + )"; + BOOST_CHECK(success(text)); +} + +BOOST_AUTO_TEST_CASE(inline_array_fixed_types) +{ + char const* text = R"( + contract test { + function f() { + fixed[3] memory a = [fixed(3.5), fixed(-4.25), fixed(967.125)]; + } + } + )"; + BOOST_CHECK(success(text)); +} + +BOOST_AUTO_TEST_CASE(inline_array_rationals) +{ + char const* text = R"( + contract test { + function f() { + ufixed8x8[4] memory a = [3.5, 4.125, 2.5, 4.0]; + } + } + )"; + BOOST_CHECK(success(text)); +} + +BOOST_AUTO_TEST_CASE(rational_index_access) +{ + char const* text = R"( + contract test { + function f() { + uint[] memory a; + a[.5]; + } + } + )"; + BOOST_CHECK(!success(text)); +} + +BOOST_AUTO_TEST_CASE(rational_to_fixed_literal_expression) +{ + char const* text = R"( + contract test { + function f() { + ufixed8x8 a = 3.5 * 3; + ufixed8x8 b = 4 - 2.5; + ufixed8x8 c = 11 / 4; + ufixed16x240 d = 599 + 0.21875; + ufixed8x248 e = ufixed8x248(35.245 % 12.9); + ufixed8x248 f = ufixed8x248(1.2 % 2); + fixed g = 2 ** -2; + } + } + )"; + BOOST_CHECK(success(text)); +} + +BOOST_AUTO_TEST_CASE(rational_as_exponent_value) +{ + char const* text = R"( + contract test { + function f() { + fixed g = 2 ** -2.2; + ufixed b = 3 ** 2.5; + ufixed24x24 b = 2 ** (1/2); + fixed40x40 c = 42 ** (-1/4); + } + } + )"; + BOOST_CHECK(!success(text)); +} + +BOOST_AUTO_TEST_CASE(fixed_point_casting_exponents) +{ + char const* text = R"( + contract test { + function f() { + ufixed a = 3 ** ufixed(1.5); + ufixed b = 2 ** ufixed(1/2); + fixed c = 42 ** fixed(-1/4); + fixed d = 16 ** fixed(-0.33); + } + } + )"; + BOOST_CHECK(!success(text)); +} + +BOOST_AUTO_TEST_CASE(var_capable_of_holding_constant_rationals) +{ + char const* text = R"( + contract test { + function f() { + var a = 0.12345678; + var b = 12345678.352; + var c = 0.00000009; + } + } + )"; + BOOST_CHECK(success(text)); +} + +BOOST_AUTO_TEST_CASE(var_and_rational_with_tuple) +{ + char const* text = R"( + contract test { + function f() { + var (a, b) = (.5, 1/3); + } + } + )"; + BOOST_CHECK(success(text)); +} + +BOOST_AUTO_TEST_CASE(var_handle_divided_integers) +{ + char const* text = R"( + contract test { + function f() { + var x = 1/3; + } + } + )"; + BOOST_CHECK(success(text)); +} + +BOOST_AUTO_TEST_CASE(rational_bitnot_unary_operation) +{ + char const* text = R"( + contract test { + function f() { + fixed a = ~3.56; + } + } + )"; + BOOST_CHECK(!success(text)); +} + +BOOST_AUTO_TEST_CASE(rational_bitor_binary_operation) +{ + char const* text = R"( + contract test { + function f() { + fixed a = 1.56 | 3; + } + } + )"; + BOOST_CHECK(!success(text)); +} + +BOOST_AUTO_TEST_CASE(rational_bitxor_binary_operation) +{ + char const* text = R"( + contract test { + function f() { + fixed a = 1.56 ^ 3; + } + } + )"; + BOOST_CHECK(!success(text)); +} + +BOOST_AUTO_TEST_CASE(rational_bitand_binary_operation) +{ + char const* text = R"( + contract test { + function f() { + fixed a = 1.56 & 3; + } + } + )"; + BOOST_CHECK(!success(text)); +} + +BOOST_AUTO_TEST_CASE(zero_handling) +{ + char const* text = R"( + contract test { + function f() { + fixed8x8 a = 0; + ufixed8x8 b = 0; + } + } + )"; + BOOST_CHECK(success(text)); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index e43b026c..909d18c9 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -1177,6 +1177,52 @@ BOOST_AUTO_TEST_CASE(conditional_with_assignment) BOOST_CHECK(successParse(text)); } +BOOST_AUTO_TEST_CASE(declaring_fixed_and_ufixed_variables) +{ + char const* text = R"( + contract A { + fixed40x40 storeMe; + function f(ufixed x, fixed32x32 y) { + ufixed8x8 a; + fixed b; + } + } + )"; + BOOST_CHECK(successParse(text)); +} + +BOOST_AUTO_TEST_CASE(declaring_fixed_literal_variables) +{ + char const* text = R"( + contract A { + fixed40x40 pi = 3.14; + } + )"; + BOOST_CHECK(successParse(text)); +} + +BOOST_AUTO_TEST_CASE(no_double_radix_in_fixed_literal) +{ + char const* text = R"( + contract A { + fixed40x40 pi = 3.14.15; + } + )"; + BOOST_CHECK(!successParse(text)); +} + +BOOST_AUTO_TEST_CASE(invalid_fixed_conversion_leading_zeroes_check) +{ + char const* text = R"( + contract test { + function f() { + fixed a = 1.0x2; + } + } + )"; + BOOST_CHECK(!successParse(text)); +} + BOOST_AUTO_TEST_SUITE_END() } |