User-defined literal suffixes.

This commit is contained in:
chriseth 2022-02-10 19:16:10 +01:00 committed by Kamil Śliwak
parent 3c0a7355d0
commit 216df46ff4
16 changed files with 330 additions and 86 deletions

View File

@ -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);
}

View File

@ -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<VariableDeclaration const*>(_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<ASTPointer<IdentifierPath>>(&_literal.suffix()))
functionReferenced(
dynamic_cast<FunctionDefinition const&>(*(*identifierPath)->annotation().referencedDeclaration)
);
return true;
}
void FunctionCallGraphBuilder::enqueueCallable(CallableDeclaration const& _callable)
{
if (!m_graph.edges.count(&_callable))

View File

@ -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();

View File

@ -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::SubDenomination>(&_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<ASTPointer<IdentifierPath>>(&_literal.suffix()))
{
Declaration const* declaration = (*identifierPath)->annotation().referencedDeclaration;
if (
!dynamic_cast<FunctionDefinition const*>(declaration) ||
!dynamic_cast<FunctionDefinition const*>(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<FunctionType const&>(*declaration->type());
if (
dynamic_cast<RationalNumberType const*>(type) &&
dynamic_cast<RationalNumberType const*>(type)->isFractional()
)
{
auto&& [mantissa, exponent] = dynamic_cast<RationalNumberType const*>(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;
}

View File

@ -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<Literal::SubDenomination>(&suffix()))
if (*subDenomination != SubDenomination::None)
return false;
if (!isHexNumber())
return false;

View File

@ -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<int>(Token::SubWeek),
Year = static_cast<int>(Token::SubYear)
};
using Suffix = std::variant<SubDenomination, ASTPointer<IdentifierPath>>;
Literal(
int64_t _id,
SourceLocation const& _location,
Token _token,
ASTPointer<ASTString> _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<ASTString> m_value;
SubDenomination m_subDenomination;
Suffix m_suffix;
};
/// @}

View File

@ -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<Literal::SubDenomination>(&_node.suffix()))
// subdenomination = Json::Value{TokenTraits::toString(*subden)};
// TODO suffix
std::vector<pair<string, Json::Value>> 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));

View File

@ -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<ASTPointer<IdentifierPath>>(&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<ASTPointer<IdentifierPath>>(&m_suffix))
(*identifierPath)->accept(_visitor);
_visitor.endVisit(*this);
}

View File

@ -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);

View File

@ -925,8 +925,10 @@ tuple<bool, rational> RationalNumberType::isValidLiteral(Literal const& _literal
{
return make_tuple(false, rational(0));
}
switch (_literal.subDenomination())
{
if (auto subDenomination = get_if<Literal::SubDenomination>(&_literal.suffix()))
switch (*subDenomination)
{
case Literal::SubDenomination::None:
case Literal::SubDenomination::Wei:
case Literal::SubDenomination::Second:
@ -952,7 +954,7 @@ tuple<bool, rational> 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 const*, RationalNumberType const*> 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())
{

View File

@ -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<RationalNumberType const*, RationalNumberType const*> mantissaExponent() const;
/// @returns true if the value is negative.
bool isNegative() const { return m_value < 0; }

View File

@ -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<ASTPointer<IdentifierPath>>(&_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<FunctionDefinition const&>(
*(*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<RationalNumberType const*>(type) && dynamic_cast<RationalNumberType const*>(type)->isFractional())
{
auto&& [mantissa, exponent] = dynamic_cast<RationalNumberType const*>(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<int>(
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.");
}
}
}

View File

@ -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<ASTPointer<IdentifierPath>>(&_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<FunctionDefinition const&>(
*(*identifierPath)->annotation().referencedDeclaration
);
FunctionType const& functionType = *function.functionType(true);
// TODO this is actually not always the right one.
auto type = TypeProvider::forLiteral(_literal);
vector<string> args;
if (dynamic_cast<RationalNumberType const*>(type) && dynamic_cast<RationalNumberType const*>(type)->isFractional())
{
auto&& [mantissa, exponent] = dynamic_cast<RationalNumberType const*>(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;
}

View File

@ -1947,6 +1947,61 @@ ASTPointer<Expression> Parser::parseLeftHandSideExpression(
}
}
ASTPointer<Expression> Parser::parseLiteral()
{
RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
Token token = m_scanner->currentToken();
ASTPointer<ASTString> value = make_shared<string>(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<Literal::SubDenomination>(m_scanner->currentToken());
advance();
}
else if (m_scanner->currentToken() == Token::Identifier)
{
auto identifierPath = parseIdentifierPath();
nodeFactory.setEndPositionFromNode(identifierPath);
suffix = move(identifierPath);
}
return nodeFactory.createNode<Literal>(token, move(value), move(suffix));
}
ASTPointer<Expression> Parser::parsePrimaryExpression()
{
RecursionGuard recursionGuard(*this);
@ -1958,50 +2013,12 @@ ASTPointer<Expression> Parser::parsePrimaryExpression()
{
case Token::TrueLiteral:
case Token::FalseLiteral:
nodeFactory.markEndPosition();
expression = nodeFactory.createNode<Literal>(token, getLiteralAndAdvance());
break;
case Token::Number:
if (TokenTraits::isEtherSubdenomination(m_scanner->peekNextToken()))
{
ASTPointer<ASTString> literal = getLiteralAndAdvance();
nodeFactory.markEndPosition();
Literal::SubDenomination subdenomination = static_cast<Literal::SubDenomination>(m_scanner->currentToken());
advance();
expression = nodeFactory.createNode<Literal>(token, literal, subdenomination);
}
else if (TokenTraits::isTimeSubdenomination(m_scanner->peekNextToken()))
{
ASTPointer<ASTString> literal = getLiteralAndAdvance();
nodeFactory.markEndPosition();
Literal::SubDenomination subdenomination = static_cast<Literal::SubDenomination>(m_scanner->currentToken());
advance();
expression = nodeFactory.createNode<Literal>(token, literal, subdenomination);
}
else
{
nodeFactory.markEndPosition();
expression = nodeFactory.createNode<Literal>(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<Literal>(token, make_shared<ASTString>(literal));
expression = parseLiteral();
break;
}
case Token::Identifier:
nodeFactory.markEndPosition();
expression = nodeFactory.createNode<Identifier>(getLiteralAndAdvance());

View File

@ -159,6 +159,7 @@ private:
ASTPointer<Expression> parseLeftHandSideExpression(
ASTPointer<Expression> const& _partiallyParsedExpression = ASTPointer<Expression>()
);
ASTPointer<Expression> parseLiteral();
ASTPointer<Expression> parsePrimaryExpression();
std::vector<ASTPointer<Expression>> parseFunctionCallListArguments();

View File

@ -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