diff --git a/libsolidity/analysis/ConstantEvaluator.cpp b/libsolidity/analysis/ConstantEvaluator.cpp index 7fde36ec0..3bbf4fa3f 100644 --- a/libsolidity/analysis/ConstantEvaluator.cpp +++ b/libsolidity/analysis/ConstantEvaluator.cpp @@ -378,6 +378,7 @@ void ConstantEvaluator::endVisit(BinaryOperation const& _operation) void ConstantEvaluator::endVisit(Literal const& _literal) { + // TODO handle user suffix if (Type const* literalType = TypeProvider::forLiteral(_literal)) m_values[&_literal] = constantToTypedValue(*literalType); } diff --git a/libsolidity/analysis/DeclarationTypeChecker.cpp b/libsolidity/analysis/DeclarationTypeChecker.cpp index f17c854a7..9aabf5b34 100644 --- a/libsolidity/analysis/DeclarationTypeChecker.cpp +++ b/libsolidity/analysis/DeclarationTypeChecker.cpp @@ -30,6 +30,7 @@ #include using namespace std; +using namespace solidity::util; using namespace solidity::langutil; using namespace solidity::frontend; @@ -342,11 +343,16 @@ void DeclarationTypeChecker::endVisit(ArrayTypeName const& _typeName) else if (optional value = ConstantEvaluator::evaluate(m_errorReporter, *length)) lengthValue = value->value; - if (!lengthValue) + string suffixErrorMessage; + if (auto const* lengthLiteral = dynamic_cast(length)) + if (lengthLiteral->isSuffixed() && !lengthLiteral->hasSubDenomination()) + suffixErrorMessage = " A suffixed literal is not a constant expression unless the suffix is a denomination."; + + if (!lengthValue || !suffixErrorMessage.empty()) m_errorReporter.typeError( 5462_error, length->location(), - "Invalid array length, expected integer literal or constant expression." + "Invalid array length, expected integer literal or constant expression." + suffixErrorMessage ); else if (*lengthValue == 0) m_errorReporter.typeError(1406_error, length->location(), "Array with zero length specified."); diff --git a/libsolidity/analysis/FunctionCallGraph.cpp b/libsolidity/analysis/FunctionCallGraph.cpp index ce5c53934..184ae91d7 100644 --- a/libsolidity/analysis/FunctionCallGraph.cpp +++ b/libsolidity/analysis/FunctionCallGraph.cpp @@ -141,6 +141,8 @@ bool FunctionCallGraphBuilder::visit(EmitStatement const& _emitStatement) return true; } +// TODO why don't we handle IdentifierPath here? + bool FunctionCallGraphBuilder::visit(Identifier const& _identifier) { if (auto const* variable = dynamic_cast(_identifier.annotation().referencedDeclaration)) diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index b97280b2c..951fd2afc 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -47,6 +47,7 @@ #include #include #include +#include #include #include @@ -3707,6 +3708,10 @@ bool TypeChecker::visit(Identifier const& _identifier) { IdentifierAnnotation& annotation = _identifier.annotation(); + // NOTE: referencedDeclaration is already be set at this point if the reference resolver found + // exactly one candidate. We do not put this candidate through the filters below so make sure + // other parts of type checker actually validate these things too. + // E.g. it might be a non-suffix function used as a suffix. if (!annotation.referencedDeclaration) { annotation.overloadedDeclarations = cleanOverloadedDeclarations(_identifier, annotation.candidateDeclarations); @@ -3737,9 +3742,32 @@ bool TypeChecker::visit(Identifier const& _identifier) for (Declaration const* declaration: annotation.overloadedDeclarations) { - FunctionTypePointer functionType = declaration->functionType(true); + FunctionTypePointer functionType = declaration->functionType(true /* _internal */); solAssert(!!functionType, "Requested type not present."); - if (functionType->canTakeArguments(*annotation.arguments)) + + auto const* functionDefinition = dynamic_cast(declaration); + + bool argumentsMatch = false; + if (_identifier.annotation().suffixedLiteral && !(functionDefinition && functionDefinition->usableAsSuffix())) + argumentsMatch = false; + else if (functionType->canTakeArguments(*annotation.arguments)) + argumentsMatch = true; + else if (_identifier.annotation().suffixedLiteral && functionType->parameterTypes().size() == 2) + { + Type const* literalType = _identifier.annotation().suffixedLiteral->annotation().type; + auto const* literalRationalType = dynamic_cast(literalType); + if (literalRationalType) + { + auto&& [mantissa, exponent] = literalRationalType->mantissaExponent(); + // This was already validated in visit(Literal) but the error is not fatal. + if (!mantissa || !exponent) + solAssert(!m_errorReporter.errors().empty()); + else + argumentsMatch = functionType->canTakeArguments({{mantissa, exponent}, {}}); + } + } + + if (argumentsMatch) candidates.push_back(declaration); } if (candidates.size() == 1) @@ -3861,12 +3889,13 @@ void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr) _expr.annotation().isConstant = false; } -void TypeChecker::endVisit(Literal const& _literal) +bool TypeChecker::visit(Literal const& _literal) { + Type const* literalType = nullptr; if (_literal.looksLikeAddress()) { // Assign type here if it even looks like an address. This prevents double errors for invalid addresses - _literal.annotation().type = TypeProvider::address(); + literalType = TypeProvider::address(); string msg; if (_literal.valueWithoutUnderscores().length() != 42) // "0x" + 40 hex digits @@ -3892,7 +3921,7 @@ void TypeChecker::endVisit(Literal const& _literal) ); } - if (_literal.isHexNumber() && _literal.subDenomination() != Literal::SubDenomination::None) + if (_literal.isHexNumber() && _literal.hasSubDenomination()) m_errorReporter.fatalTypeError( 5145_error, _literal.location(), @@ -3900,22 +3929,132 @@ void TypeChecker::endVisit(Literal const& _literal) "You can use an expression of the form \"0x1234 * 1 day\" instead." ); - if (_literal.subDenomination() == Literal::SubDenomination::Year) + if (_literal.hasSubDenomination() && _literal.subDenomination() == Literal::SubDenomination::Year) m_errorReporter.typeError( 4820_error, _literal.location(), "Using \"years\" as a unit denomination is deprecated." ); - if (!_literal.annotation().type) - _literal.annotation().type = TypeProvider::forLiteral(_literal); + if (!literalType) + literalType = TypeProvider::forLiteral(_literal); - if (!_literal.annotation().type) + if (!literalType) m_errorReporter.fatalTypeError(2826_error, _literal.location(), "Invalid literal value."); - _literal.annotation().isPure = true; + std::visit(GenericVisitor{ + [&](ASTPointer const& _identifier) { + _identifier->annotation().suffixedLiteral = &_literal; + _identifier->annotation().arguments = {{literalType}, {}}; + _identifier->annotation().calledDirectly = true; + }, + [&](ASTPointer const& _memberAccess) { + _memberAccess->annotation().suffixedLiteral = &_literal; + _memberAccess->annotation().arguments = {{literalType}, {}}; + _memberAccess->annotation().calledDirectly = true; + }, + [&](Literal::SubDenomination) {}, + }, _literal.suffix()); + + auto const* literalRationalType = dynamic_cast(literalType); + if (_literal.isSuffixed() && !_literal.hasSubDenomination() && literalRationalType) + { + auto&& [mantissa, exponent] = literalRationalType->mantissaExponent(); + solAssert((mantissa && exponent) || (!mantissa && !exponent)); + if (!mantissa) + m_errorReporter.typeError( + 5503_error, + _literal.location(), + "This fractional number cannot be decomposed into a mantissa and decimal exponent " + "that fit the range of parameters of any possible suffix function." + ); + } + + // NOTE: For suffixed literals this is not the final type yet. We will update it in endVisit() + // when we know what the suffix function returns. + _literal.annotation().type = literalType; _literal.annotation().isLValue = false; _literal.annotation().isConstant = false; + + return true; +} + +void TypeChecker::endVisit(Literal const& _literal) +{ + bool isCompileTimeConstant = true; + if (_literal.isSuffixed() && !_literal.hasSubDenomination()) + { + FunctionType const* suffixFunctionType = _literal.suffixFunctionType(); + + if ( + !suffixFunctionType || // Rejects variables + !suffixFunctionType->hasDeclaration() || // Rejects function pointers + !dynamic_cast(&suffixFunctionType->declaration()) || // Rejects events and errors + suffixFunctionType->hasBoundFirstArgument() || + !_literal.suffixFunction()->usableAsSuffix() + ) + m_errorReporter.typeError( + 4438_error, + _literal.location(), + "The literal suffix must be either a subdenomination or a file-level suffix function." + ); + else + { + solAssert(_literal.suffixFunction()); + solAssert(!_literal.suffixFunction()->virtualSemantics()); + solAssert(!suffixFunctionType->takesArbitraryParameters()); + solAssert(suffixFunctionType->kind() == FunctionType::Kind::Internal); + + Type const* literalType = _literal.annotation().type; + auto const* literalRationalType = dynamic_cast(literalType); + + optional parameterTypeMessage; + if (suffixFunctionType->parameterTypes().size() == 2) + { + if (!literalRationalType) + m_errorReporter.typeError( + 2505_error, + _literal.location(), + "Functions that take 2 arguments can only be used as literal suffixes for rational numbers." + ); + else + { + auto&& [mantissa, exponent] = literalRationalType->mantissaExponent(); + // This was already validated in visit(Literal) but the error is not fatal. + if (!mantissa || !exponent) + solAssert(!m_errorReporter.errors().empty()); + else if ( + !mantissa->isImplicitlyConvertibleTo(*suffixFunctionType->parameterTypes().at(0)) || + !exponent->isImplicitlyConvertibleTo(*suffixFunctionType->parameterTypes().at(1)) + ) + // TODO: Is this triggered when the argument is out of range? Test. + parameterTypeMessage = "The type of the literal cannot be converted to the parameters of the suffix function."; + } + } + else if (suffixFunctionType->parameterTypes().size() == 1) + { + if (!literalType->isImplicitlyConvertibleTo(*suffixFunctionType->parameterTypes().at(0))) + parameterTypeMessage = "The type of the literal cannot be converted to the parameter of the suffix function."; + } + else + solAssert(m_errorReporter.hasErrors()); + + if (parameterTypeMessage.has_value()) + m_errorReporter.typeError(8838_error, _literal.location(), parameterTypeMessage.value()); + + if (suffixFunctionType->returnParameterTypes().size() == 1) + _literal.annotation().type = suffixFunctionType->returnParameterTypes().front(); + else + { + solAssert(m_errorReporter.hasErrors()); + _literal.annotation().type = TypeProvider::tuple(suffixFunctionType->returnParameterTypes()); + } + + isCompileTimeConstant = suffixFunctionType->isPure(); + } + } + + _literal.annotation().isPure = isCompileTimeConstant; } void TypeChecker::endVisit(UsingForDirective const& _usingFor) diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index 2ef6b818b..e5aefea0b 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -164,6 +164,7 @@ private: void endVisit(IdentifierPath const& _identifierPath) override; void endVisit(UserDefinedTypeName const& _userDefinedTypeName) override; void endVisit(ElementaryTypeNameExpression const& _expr) override; + bool visit(Literal const& _literal) override; void endVisit(Literal const& _literal) override; void endVisit(UsingForDirective const& _usingForDirective) override; diff --git a/libsolidity/ast/TypeProvider.h b/libsolidity/ast/TypeProvider.h index 02b9eb35d..f724dad3e 100644 --- a/libsolidity/ast/TypeProvider.h +++ b/libsolidity/ast/TypeProvider.h @@ -167,6 +167,7 @@ public: /// Auto-detect the proper type for a literal. @returns an empty pointer if the literal does /// not fit any type. + /// Ignores user-defined suffixes. static Type const* forLiteral(Literal const& _literal); static RationalNumberType const* rationalNumber(Literal const& _literal); diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 8fa330da4..32a25cbf3 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -968,8 +968,10 @@ tuple RationalNumberType::isValidLiteral(Literal const& _literal { return make_tuple(false, rational(0)); } - switch (_literal.subDenomination()) - { + + if (_literal.hasSubDenomination()) + switch (_literal.subDenomination()) + { case Literal::SubDenomination::None: case Literal::SubDenomination::Wei: case Literal::SubDenomination::Second: @@ -995,8 +997,8 @@ tuple RationalNumberType::isValidLiteral(Literal const& _literal case Literal::SubDenomination::Year: value *= bigint("31536000"); break; - } - + } + // TODO: Do we need to consider literal suffixes here? return make_tuple(true, value); } @@ -1263,6 +1265,36 @@ FixedPointType const* RationalNumberType::fixedPointType() const ); } +pair RationalNumberType::mantissaExponent() const +{ + rational const maxUint = rational((bigint(1) << 256) - 1); + rational const minInt = -rational(bigint(1) << 255); + + bool negative = (m_value < 0); + rational const maxMantissa = (negative ? -minInt : maxUint); + + rational exponent = 0; + rational unsignedMantissa = abs(m_value); + + if (unsignedMantissa > maxMantissa) + return {nullptr, nullptr}; + + while (unsignedMantissa.denominator() != 1) + { + unsignedMantissa *= 10; + --exponent; + + // FIXME: What happens when exponent in scientific notation is max uint? + if (unsignedMantissa > maxMantissa || exponent > maxUint) + return {nullptr, nullptr}; + } + + return { + TypeProvider::rationalNumber(unsignedMantissa), + TypeProvider::rationalNumber(-exponent), + }; +} + StringLiteralType::StringLiteralType(Literal const& _literal): m_value(_literal.value()) { diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index d7312aa1d..a7fd1cc16 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -602,6 +602,14 @@ public: /// @returns true if the value is not an integer. bool isFractional() const { return m_value.denominator() != 1; } + // TODO: Update if it turns out we do need to support negative numbers here. + /// Tries to decompose a positive rational number into two positive integers - a mantissa and a + /// base-10 exponent, such that the number is equal to `mantissa * 10**-exponent`. + /// @returns Pair of non-null pointers representing the types of the literals corresponding to + /// mantissa and exponent if the resulting mantissa and exponent both fit in 256 bits. + /// A pair of null pointers otherwise. + std::pair mantissaExponent() const; + /// @returns true if the value is negative. bool isNegative() const { return m_value < 0; }