User-defined literal suffixes: Analysis

This commit is contained in:
Kamil Śliwak 2023-02-24 19:52:14 +01:00
parent fda86c21f8
commit 143722c705
8 changed files with 206 additions and 16 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

@ -30,6 +30,7 @@
#include <range/v3/view/transform.hpp>
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<ConstantEvaluator::TypedRational> value = ConstantEvaluator::evaluate(m_errorReporter, *length))
lengthValue = value->value;
if (!lengthValue)
string suffixErrorMessage;
if (auto const* lengthLiteral = dynamic_cast<Literal const*>(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.");

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

View File

@ -47,6 +47,7 @@
#include <range/v3/view/drop_exactly.hpp>
#include <range/v3/view/enumerate.hpp>
#include <range/v3/view/zip.hpp>
#include <range/v3/algorithm/any_of.hpp>
#include <memory>
#include <vector>
@ -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<FunctionDefinition const*>(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<RationalNumberType const*>(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<Identifier> const& _identifier) {
_identifier->annotation().suffixedLiteral = &_literal;
_identifier->annotation().arguments = {{literalType}, {}};
_identifier->annotation().calledDirectly = true;
},
[&](ASTPointer<MemberAccess> const& _memberAccess) {
_memberAccess->annotation().suffixedLiteral = &_literal;
_memberAccess->annotation().arguments = {{literalType}, {}};
_memberAccess->annotation().calledDirectly = true;
},
[&](Literal::SubDenomination) {},
}, _literal.suffix());
auto const* literalRationalType = dynamic_cast<RationalNumberType const*>(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<FunctionDefinition const*>(&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<RationalNumberType const*>(literalType);
optional<string> 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)

View File

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

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

@ -968,8 +968,10 @@ tuple<bool, rational> 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<bool, rational> 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 const*, RationalNumberType const*> 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())
{

View File

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