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/FunctionCallGraph.cpp b/libsolidity/analysis/FunctionCallGraph.cpp index 45da1b4f0..2c13422e8 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)) @@ -230,6 +232,16 @@ bool FunctionCallGraphBuilder::visit(NewExpression const& _newExpression) return true; } +bool FunctionCallGraphBuilder::visit(Literal const& _literal) +{ + if (auto const* identifierPath = get_if>(&_literal.suffix())) + functionReferenced( + dynamic_cast(*(*identifierPath)->annotation().referencedDeclaration) + ); + + return true; +} + void FunctionCallGraphBuilder::enqueueCallable(CallableDeclaration const& _callable) { if (!m_graph.edges.count(&_callable)) diff --git a/libsolidity/analysis/FunctionCallGraph.h b/libsolidity/analysis/FunctionCallGraph.h index 3ea1f5b9e..87fdf5b18 100644 --- a/libsolidity/analysis/FunctionCallGraph.h +++ b/libsolidity/analysis/FunctionCallGraph.h @@ -74,6 +74,7 @@ private: bool visit(MemberAccess const& _memberAccess) override; bool visit(ModifierInvocation const& _modifierInvocation) override; bool visit(NewExpression const& _newExpression) override; + bool visit(Literal const& _literal) override; void enqueueCallable(CallableDeclaration const& _callable); void processQueue(); diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index de3cdc847..0be3a63f6 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -3678,10 +3678,11 @@ void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr) void TypeChecker::endVisit(Literal const& _literal) { + Type const* type = 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(); + type = TypeProvider::address(); string msg; if (_literal.valueWithoutUnderscores().length() != 42) // "0x" + 40 hex digits @@ -3707,7 +3708,8 @@ void TypeChecker::endVisit(Literal const& _literal) ); } - if (_literal.isHexNumber() && _literal.subDenomination() != Literal::SubDenomination::None) + Literal::SubDenomination const* subDenomination = get_if(&_literal.suffix()); + if (_literal.isHexNumber() && subDenomination && *subDenomination != Literal::SubDenomination::None) m_errorReporter.fatalTypeError( 5145_error, _literal.location(), @@ -3715,20 +3717,83 @@ 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 (subDenomination && *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 (!type) + type = TypeProvider::forLiteral(_literal); - if (!_literal.annotation().type) + if (!type) m_errorReporter.fatalTypeError(2826_error, _literal.location(), "Invalid literal value."); - _literal.annotation().isPure = true; + // TODO at this point 'type' needs to be stored for code generation. + + bool isPure = true; + if (auto const* identifierPath = get_if>(&_literal.suffix())) + { + Declaration const* declaration = (*identifierPath)->annotation().referencedDeclaration; + if ( + !dynamic_cast(declaration) || + !dynamic_cast(declaration)->isFree() + ) + m_errorReporter.typeError( + 4438_error, + _literal.location(), + "The literal suffix needs to be a pre-defined suffix or a file-level function." + ); + else + { + FunctionType const& functionType = dynamic_cast(*declaration->type()); + if ( + dynamic_cast(type) && + dynamic_cast(type)->isFractional() + ) + { + auto&& [mantissa, exponent] = dynamic_cast(type)->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 the suffix function." + ); + else if ( + functionType.parameterTypes().size() != 2 || + !mantissa->isImplicitlyConvertibleTo(*functionType.parameterTypes().at(0)) || + !exponent->isImplicitlyConvertibleTo(*functionType.parameterTypes().at(1)) + ) + m_errorReporter.typeError( + 4778_error, + _literal.location(), + "TODO Fractional number, types to do match." + ); + } + else + if ( + functionType.parameterTypes().size() != 1 || + !type->isImplicitlyConvertibleTo(*functionType.parameterTypes().front()) + ) + m_errorReporter.typeError( + 8838_error, + _literal.location(), + "The type of the literal cannot be converted to the parameter of the suffix function." + ); + + isPure = functionType.isPure(); + if (functionType.returnParameterTypes().size() == 1) + type = functionType.returnParameterTypes().front(); + else + type = TypeProvider::tuple(functionType.returnParameterTypes()); + } + } + + _literal.annotation().isPure = isPure; + _literal.annotation().type = type; _literal.annotation().isLValue = false; _literal.annotation().isConstant = false; } diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 04c47a155..341b1712e 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -949,8 +949,10 @@ bool Literal::isHexNumber() const bool Literal::looksLikeAddress() const { - if (subDenomination() != SubDenomination::None) - return false; + // User suffixes are fine. + if (auto subDenomination = get_if(&suffix())) + if (*subDenomination != SubDenomination::None) + return false; if (!isHexNumber()) return false; diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index e688b66d1..0d975e8cc 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -2332,6 +2332,8 @@ private: /** * A literal string or number. @see ExpressionCompiler::endVisit() is used to actually parse its value. + * + * It can have a suffix that can lead to a function call. */ class Literal: public PrimaryExpression { @@ -2349,14 +2351,15 @@ public: Week = static_cast(Token::SubWeek), Year = static_cast(Token::SubYear) }; + using Suffix = std::variant>; Literal( int64_t _id, SourceLocation const& _location, Token _token, ASTPointer _value, - SubDenomination _sub = SubDenomination::None + Suffix _suffix = SubDenomination::None ): - PrimaryExpression(_id, _location), m_token(_token), m_value(std::move(_value)), m_subDenomination(_sub) {} + PrimaryExpression(_id, _location), m_token(_token), m_value(std::move(_value)), m_suffix(std::move(_suffix)) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -2366,7 +2369,8 @@ public: ASTString valueWithoutUnderscores() const; - SubDenomination subDenomination() const { return m_subDenomination; } + //SubDenomination subDenomination() const { return m_suffix; } + Suffix const& suffix() const { return m_suffix; } /// @returns true if this is a number with a hex prefix. bool isHexNumber() const; @@ -2381,7 +2385,7 @@ public: private: Token m_token; ASTPointer m_value; - SubDenomination m_subDenomination; + Suffix m_suffix; }; /// @} diff --git a/libsolidity/ast/ASTJsonExporter.cpp b/libsolidity/ast/ASTJsonExporter.cpp index 939fc79c6..9292b282e 100644 --- a/libsolidity/ast/ASTJsonExporter.cpp +++ b/libsolidity/ast/ASTJsonExporter.cpp @@ -962,17 +962,15 @@ bool ASTJsonExporter::visit(Literal const& _node) Json::Value value{_node.value()}; if (!util::validateUTF8(_node.value())) value = Json::nullValue; - Token subdenomination = Token(_node.subDenomination()); + Json::Value subdenomination = Json::nullValue; +// if (auto subden = get_if(&_node.suffix())) +// subdenomination = Json::Value{TokenTraits::toString(*subden)}; + // TODO suffix std::vector> attributes = { make_pair("kind", literalTokenKind(_node.token())), make_pair("value", value), make_pair("hexValue", util::toHex(util::asBytes(_node.value()))), - make_pair( - "subdenomination", - subdenomination == Token::Illegal ? - Json::nullValue : - Json::Value{TokenTraits::toString(subdenomination)} - ) + make_pair("subdenomination", subdenomination) }; appendExpressionAttributes(attributes, _node.annotation()); setJsonNode(_node, "Literal", std::move(attributes)); diff --git a/libsolidity/ast/AST_accept.h b/libsolidity/ast/AST_accept.h index 6dab08f00..8af515526 100644 --- a/libsolidity/ast/AST_accept.h +++ b/libsolidity/ast/AST_accept.h @@ -1014,13 +1014,17 @@ void ElementaryTypeNameExpression::accept(ASTConstVisitor& _visitor) const void Literal::accept(ASTVisitor& _visitor) { - _visitor.visit(*this); + if (_visitor.visit(*this)) + if (auto identifierPath = std::get_if>(&m_suffix)) + (*identifierPath)->accept(_visitor); _visitor.endVisit(*this); } void Literal::accept(ASTConstVisitor& _visitor) const { - _visitor.visit(*this); + if (_visitor.visit(*this)) + if (auto identifierPath = std::get_if>(&m_suffix)) + (*identifierPath)->accept(_visitor); _visitor.endVisit(*this); } diff --git a/libsolidity/ast/TypeProvider.h b/libsolidity/ast/TypeProvider.h index b089ff1ef..9f8ef9438 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 f88d1afce..fe59e76e3 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -925,8 +925,10 @@ tuple RationalNumberType::isValidLiteral(Literal const& _literal { return make_tuple(false, rational(0)); } - switch (_literal.subDenomination()) - { + + if (auto subDenomination = get_if(&_literal.suffix())) + switch (*subDenomination) + { case Literal::SubDenomination::None: case Literal::SubDenomination::Wei: case Literal::SubDenomination::Second: @@ -952,7 +954,7 @@ tuple RationalNumberType::isValidLiteral(Literal const& _literal case Literal::SubDenomination::Year: value *= bigint("31536000"); break; - } + } return make_tuple(true, value); @@ -1220,6 +1222,33 @@ FixedPointType const* RationalNumberType::fixedPointType() const ); } +pair RationalNumberType::mantissaExponent() const +{ + bool negative = (m_value < 0); + int exponent = 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.denominator() != 1) + { + value *= 10; + exponent--; + if ( + value > rational((bigint(1) << 256) - 1) || + value < rational(-(bigint(1) << 255)) || + exponent < -255 // TODO sane vale? + ) + return {nullptr, nullptr}; + } + + return { + TypeProvider::rationalNumber(value), + 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 553af6c7d..78bb806f7 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -587,6 +587,9 @@ public: /// @returns true if the value is not an integer. bool isFractional() const { return m_value.denominator() != 1; } + /// TODO document + std::pair mantissaExponent() const; + /// @returns true if the value is negative. bool isNegative() const { return m_value < 0; } diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 95087aa02..034329d9e 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -2270,19 +2270,62 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier) void ExpressionCompiler::endVisit(Literal const& _literal) { CompilerContext::LocationSetter locationSetter(m_context, _literal); - Type const* type = _literal.annotation().type; - switch (type->category()) + if (auto identifierPath = get_if>(&_literal.suffix())) { - case Type::Category::RationalNumber: - case Type::Category::Bool: - case Type::Category::Address: - m_context << type->literalValue(&_literal); - break; - case Type::Category::StringLiteral: - break; // will be done during conversion - default: - solUnimplemented("Only integer, boolean and string literals implemented for now."); + FunctionDefinition const& function = dynamic_cast( + *(*identifierPath)->annotation().referencedDeclaration + ); + FunctionType const& functionType = *function.functionType(true); + + evmasm::AssemblyItem returnLabel = m_context.pushNewTag(); + + // TODO this is actually not always the right one. + auto type = TypeProvider::forLiteral(_literal); + + if (dynamic_cast(type) && dynamic_cast(type)->isFractional()) + { + auto&& [mantissa, exponent] = dynamic_cast(type)->mantissaExponent(); + m_context << mantissa->literalValue(nullptr); + utils().convertType(*mantissa, *functionType.parameterTypes().at(0)); + m_context << exponent->literalValue(nullptr); + utils().convertType(*exponent, *functionType.parameterTypes().at(1)); + } + else + { + // TODO reuse the code below + if (type->category() != Type::Category::StringLiteral) + m_context << type->literalValue(&_literal); + utils().convertType(*type, *functionType.parameterTypes().at(0)); + } + m_context << m_context.functionEntryLabel(function).pushTag(); + m_context.appendJump(evmasm::AssemblyItem::JumpType::IntoFunction); + m_context << returnLabel; + + // callee adds return parameters, but removes arguments and return label + m_context.adjustStackOffset(static_cast( + CompilerUtils::sizeOnStack(functionType.returnParameterTypes()) - + CompilerUtils::sizeOnStack(functionType.parameterTypes()) - + 1 + )); + } + else + { + Type const* type = _literal.annotation().type; + + + switch (type->category()) + { + case Type::Category::RationalNumber: + case Type::Category::Bool: + case Type::Category::Address: + m_context << type->literalValue(&_literal); + break; + case Type::Category::StringLiteral: + break; // will be done during conversion + default: + solUnimplemented("Only integer, boolean and string literals implemented for now."); + } } } diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 5eaeed743..50d35a4da 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -2437,19 +2437,60 @@ void IRGeneratorForStatements::endVisit(Identifier const& _identifier) bool IRGeneratorForStatements::visit(Literal const& _literal) { setLocation(_literal); - Type const& literalType = type(_literal); - switch (literalType.category()) + if (auto identifierPath = get_if>(&_literal.suffix())) { - case Type::Category::RationalNumber: - case Type::Category::Bool: - case Type::Category::Address: - define(_literal) << toCompactHexWithPrefix(literalType.literalValue(&_literal)) << "\n"; - break; - case Type::Category::StringLiteral: - break; // will be done during conversion - default: - solUnimplemented("Only integer, boolean and string literals implemented for now."); + FunctionDefinition const& function = dynamic_cast( + *(*identifierPath)->annotation().referencedDeclaration + ); + FunctionType const& functionType = *function.functionType(true); + + // TODO this is actually not always the right one. + auto type = TypeProvider::forLiteral(_literal); + + vector args; + if (dynamic_cast(type) && dynamic_cast(type)->isFractional()) + { + auto&& [mantissa, exponent] = dynamic_cast(type)->mantissaExponent(); + IRVariable mantissaVar(m_context.newYulVariable(), *mantissa); + define(mantissaVar) << toCompactHexWithPrefix(mantissa->literalValue(&_literal)) << "\n"; + args = convert(mantissaVar, *functionType.parameterTypes().at(0)).stackSlots(); + + IRVariable exponentVar(m_context.newYulVariable(), *exponent); + define(exponentVar) << toCompactHexWithPrefix(exponent->literalValue(&_literal)) << "\n"; + args += convert(exponentVar, *functionType.parameterTypes().at(1)).stackSlots(); + } + else + { + // TODO reuse the code below + if (type->category() != Type::Category::StringLiteral) + { + IRVariable value(m_context.newYulVariable(), *type); + define(value) << toCompactHexWithPrefix(type->literalValue(&_literal)) << "\n"; + args = convert(value, *functionType.parameterTypes().at(0)).stackSlots(); + } + // TODO what about string? + } + define(_literal) << + m_context.enqueueFunctionForCodeGeneration(function) << + "(" + joinHumanReadable(args) + ")\n"; + } + else + { + Type const& literalType = type(_literal); + + switch (literalType.category()) + { + case Type::Category::RationalNumber: + case Type::Category::Bool: + case Type::Category::Address: + define(_literal) << toCompactHexWithPrefix(literalType.literalValue(&_literal)) << "\n"; + break; + case Type::Category::StringLiteral: + break; // will be done during conversion + default: + solUnimplemented("Only integer, boolean and string literals implemented for now."); + } } return false; } diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 9ccb1aca3..0e835ff51 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -1947,6 +1947,61 @@ ASTPointer Parser::parseLeftHandSideExpression( } } +ASTPointer Parser::parseLiteral() +{ + RecursionGuard recursionGuard(*this); + ASTNodeFactory nodeFactory(*this); + Token token = m_scanner->currentToken(); + ASTPointer value = make_shared(m_scanner->currentLiteral()); + Literal::Suffix suffix = Literal::SubDenomination::None; + + switch (token) + { + case Token::TrueLiteral: + case Token::FalseLiteral: + case Token::Number: + break; + case Token::StringLiteral: + case Token::UnicodeStringLiteral: + case Token::HexStringLiteral: + { + Token firstToken = token; + while (m_scanner->peekNextToken() == firstToken) + { + advance(); + *value += m_scanner->currentLiteral(); + } + nodeFactory.markEndPosition(); + advance(); + if (m_scanner->currentToken() == Token::Illegal) + fatalParserError(5428_error, to_string(m_scanner->currentError())); + break; + } + default: + solAssert(false); + break; + } + nodeFactory.markEndPosition(); + advance(); + + if (token == Token::Number && ( + TokenTraits::isEtherSubdenomination(m_scanner->currentToken()) || + TokenTraits::isTimeSubdenomination(m_scanner->currentToken()) + )) + { + nodeFactory.markEndPosition(); + suffix = static_cast(m_scanner->currentToken()); + advance(); + } + else if (m_scanner->currentToken() == Token::Identifier) + { + auto identifierPath = parseIdentifierPath(); + nodeFactory.setEndPositionFromNode(identifierPath); + suffix = move(identifierPath); + } + return nodeFactory.createNode(token, move(value), move(suffix)); +} + ASTPointer Parser::parsePrimaryExpression() { RecursionGuard recursionGuard(*this); @@ -1958,50 +2013,12 @@ ASTPointer Parser::parsePrimaryExpression() { case Token::TrueLiteral: case Token::FalseLiteral: - nodeFactory.markEndPosition(); - expression = nodeFactory.createNode(token, getLiteralAndAdvance()); - break; case Token::Number: - if (TokenTraits::isEtherSubdenomination(m_scanner->peekNextToken())) - { - ASTPointer literal = getLiteralAndAdvance(); - nodeFactory.markEndPosition(); - Literal::SubDenomination subdenomination = static_cast(m_scanner->currentToken()); - advance(); - expression = nodeFactory.createNode(token, literal, subdenomination); - } - else if (TokenTraits::isTimeSubdenomination(m_scanner->peekNextToken())) - { - ASTPointer literal = getLiteralAndAdvance(); - nodeFactory.markEndPosition(); - Literal::SubDenomination subdenomination = static_cast(m_scanner->currentToken()); - advance(); - expression = nodeFactory.createNode(token, literal, subdenomination); - } - else - { - nodeFactory.markEndPosition(); - expression = nodeFactory.createNode(token, getLiteralAndAdvance()); - } - break; case Token::StringLiteral: case Token::UnicodeStringLiteral: case Token::HexStringLiteral: - { - string literal = m_scanner->currentLiteral(); - Token firstToken = m_scanner->currentToken(); - while (m_scanner->peekNextToken() == firstToken) - { - advance(); - literal += m_scanner->currentLiteral(); - } - nodeFactory.markEndPosition(); - advance(); - if (m_scanner->currentToken() == Token::Illegal) - fatalParserError(5428_error, to_string(m_scanner->currentError())); - expression = nodeFactory.createNode(token, make_shared(literal)); + expression = parseLiteral(); break; - } case Token::Identifier: nodeFactory.markEndPosition(); expression = nodeFactory.createNode(getLiteralAndAdvance()); diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index 7a9b41318..c7d6146be 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -159,6 +159,7 @@ private: ASTPointer parseLeftHandSideExpression( ASTPointer const& _partiallyParsedExpression = ASTPointer() ); + ASTPointer parseLiteral(); ASTPointer parsePrimaryExpression(); std::vector> parseFunctionCallListArguments(); diff --git a/test/libsolidity/semanticTests/literalSuffixes/intermediate.sol b/test/libsolidity/semanticTests/literalSuffixes/intermediate.sol new file mode 100644 index 000000000..630ae9496 --- /dev/null +++ b/test/libsolidity/semanticTests/literalSuffixes/intermediate.sol @@ -0,0 +1,22 @@ +type Length is uint; + +function km(uint meters) pure returns (Length) { + return Length.wrap(meters * 1000); +} + +struct Float { + uint mantissa; + int exponent; +} + +function f(uint mantissa, int exponent) pure returns (Float memory) { + return Float(mantissa, exponent); +} + +contract C { + Length public length = 1000 km; + Float public factor = 1.23 f; +} +// ---- +// length() -> 1000000 +// factor() -> 0x7b, 2