Skip to content

Commit 0096dfb

Browse files
committed
Make constants data location more powerful
1 parent e4a5b77 commit 0096dfb

File tree

79 files changed

+1310
-121
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+1310
-121
lines changed

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
Language Features:
44
* General: Add a builtin that computes the base slot of a storage namespace using the `erc7201` formula from ERC-7201.
5+
* General: Allow ``constant`` keyword for composite types (structs, arrays, function pointers) and as a data location for internal function parameters.
56

67
Compiler Features:
78
* Commandline Interface: Disallow selecting the deprecated assembly input mode that was only accessible via `--assemble` instead of treating it as equivalent to `--strict-assembly`.

docs/contracts/constant-state-variables.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ can sometimes be cheaper than immutable values.
2828
Not all types for constants and immutables are implemented at this time. The only supported types are
2929
:ref:`strings <strings>` (only for constants) and :ref:`value types <value-types>`.
3030

31+
When using the IR pipeline (``--via-ir``), ``constant`` also supports composite types:
32+
structs, fixed-size arrays (including nested), arrays of strings, structs with string fields,
33+
enum arrays, and arrays of internal function pointers.
34+
Dynamic arrays (``uint256[] constant``) are supported with slicing (``arr[s:e]``).
35+
36+
Additionally, ``constant`` can be used as a data location specifier for parameters of
37+
``internal`` and ``private`` functions, enabling zero-copy passing of constant references.
38+
3139
.. code-block:: solidity
3240
3341
// SPDX-License-Identifier: GPL-3.0

docs/grammar/SolidityParser.g4

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ locals [boolean visibilitySet = false, boolean mutabilitySet = false]
371371
* The declaration of a single variable.
372372
*/
373373
variableDeclaration: type=typeName location=dataLocation? name=identifier;
374-
dataLocation: Memory | Storage | Calldata;
374+
dataLocation: Memory | Storage | Calldata | Constant;
375375

376376
/**
377377
* Complex expression.

libsolidity/analysis/DeclarationTypeChecker.cpp

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
418418
case Location::Storage: return "\"storage\"";
419419
case Location::Transient: return "\"transient\"";
420420
case Location::CallData: return "\"calldata\"";
421+
case Location::Constant: return "\"constant\"";
421422
case Location::Unspecified: return "none";
422423
}
423424
return {};
@@ -514,6 +515,9 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
514515
case Location::Transient:
515516
solUnimplemented("Transient data location cannot be used in this kind of variable or parameter declaration.");
516517
break;
518+
case Location::Constant:
519+
typeLoc = DataLocation::Constant;
520+
break;
517521
case Location::Unspecified:
518522
solAssert(!_variable.hasReferenceOrMappingType(), "Data location not properly set.");
519523
}
@@ -528,10 +532,35 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
528532
if (_variable.isConstant() && !type->isValueType())
529533
{
530534
bool allowed = false;
535+
bool isByteArrayOrString = false;
531536
if (auto arrayType = dynamic_cast<ArrayType const*>(type))
532-
allowed = arrayType->isByteArrayOrString();
537+
{
538+
isByteArrayOrString = arrayType->isByteArrayOrString();
539+
if (isByteArrayOrString)
540+
allowed = true;
541+
else
542+
allowed = !arrayType->containsNestedMapping();
543+
}
544+
else if (auto structType = dynamic_cast<StructType const*>(type))
545+
{
546+
bool membersResolved = true;
547+
for (auto const& member: structType->structDefinition().members())
548+
if (!member->annotation().type)
549+
{
550+
membersResolved = false;
551+
break;
552+
}
553+
allowed = !membersResolved || !structType->containsNestedMapping();
554+
}
555+
else if (dynamic_cast<MappingType const*>(type))
556+
allowed = false;
533557
if (!allowed)
534-
m_errorReporter.fatalTypeError(9259_error, _variable.location(), "Only constants of value type and byte array type are implemented.");
558+
m_errorReporter.fatalTypeError(9259_error, _variable.location(), "Constants of this type are not supported. Only value types, arrays, structs, and byte/string types without mappings are allowed.");
559+
if (!isByteArrayOrString)
560+
{
561+
if (auto ref = dynamic_cast<ReferenceType const*>(type))
562+
type = TypeProvider::withLocation(ref, DataLocation::Constant, false);
563+
}
535564
}
536565

537566
if (!type->isValueType())

libsolidity/analysis/TypeChecker.cpp

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,30 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
472472
return false;
473473
}
474474

475+
namespace
476+
{
477+
bool isCompileTimeConstantExpression(Expression const& _expr)
478+
{
479+
if (*_expr.annotation().isPure)
480+
return true;
481+
if (auto const* ident = dynamic_cast<Identifier const*>(&_expr))
482+
{
483+
if (dynamic_cast<FunctionDefinition const*>(ident->annotation().referencedDeclaration))
484+
if (auto const* fType = dynamic_cast<FunctionType const*>(ident->annotation().type))
485+
if (fType->kind() == FunctionType::Kind::Internal)
486+
return true;
487+
}
488+
if (auto const* tuple = dynamic_cast<TupleExpression const*>(&_expr))
489+
{
490+
for (auto const& comp: tuple->components())
491+
if (comp && !isCompileTimeConstantExpression(*comp))
492+
return false;
493+
return true;
494+
}
495+
return false;
496+
}
497+
}
498+
475499
bool TypeChecker::visit(VariableDeclaration const& _variable)
476500
{
477501
_variable.typeName().accept(*this);
@@ -499,7 +523,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
499523
{
500524
if (!_variable.value())
501525
m_errorReporter.typeError(4266_error, _variable.location(), "Uninitialized \"constant\" variable.");
502-
else if (!*_variable.value()->annotation().isPure)
526+
else if (!*_variable.value()->annotation().isPure && !isCompileTimeConstantExpression(*_variable.value()))
503527
m_errorReporter.typeError(
504528
8349_error,
505529
_variable.value()->location(),
@@ -3458,8 +3482,8 @@ bool TypeChecker::visit(IndexAccess const& _access)
34583482
case Type::Category::ArraySlice:
34593483
{
34603484
auto const& arrayType = dynamic_cast<ArraySliceType const&>(*baseType).arrayType();
3461-
if (arrayType.location() != DataLocation::CallData || !arrayType.isDynamicallySized())
3462-
m_errorReporter.typeError(4802_error, _access.location(), "Index access is only implemented for slices of dynamic calldata arrays.");
3485+
if ((arrayType.location() != DataLocation::CallData && arrayType.location() != DataLocation::Constant) || !arrayType.isDynamicallySized())
3486+
m_errorReporter.typeError(4802_error, _access.location(), "Index access is only implemented for slices of dynamic calldata or constant arrays.");
34633487
baseType = &arrayType;
34643488
[[fallthrough]];
34653489
}
@@ -3599,8 +3623,11 @@ bool TypeChecker::visit(IndexRangeAccess const& _access)
35993623
else if (!(arrayType = dynamic_cast<ArrayType const*>(exprType)))
36003624
m_errorReporter.fatalTypeError(4781_error, _access.location(), "Index range access is only possible for arrays and array slices.");
36013625

3602-
if (arrayType->location() != DataLocation::CallData || !arrayType->isDynamicallySized())
3603-
m_errorReporter.typeError(1227_error, _access.location(), "Index range access is only supported for dynamic calldata arrays.");
3626+
if (
3627+
(arrayType->location() != DataLocation::CallData && arrayType->location() != DataLocation::Constant) ||
3628+
!arrayType->isDynamicallySized()
3629+
)
3630+
m_errorReporter.typeError(1227_error, _access.location(), "Index range access is only supported for dynamic calldata or constant arrays.");
36043631
else if (arrayType->baseType()->isDynamicallyEncoded())
36053632
m_errorReporter.typeError(2148_error, _access.location(), "Index range access is not supported for arrays with dynamically encoded base types.");
36063633
_access.annotation().type = TypeProvider::arraySlice(*arrayType);

libsolidity/ast/AST.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -852,6 +852,8 @@ std::set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocation
852852
locations.insert(Location::Storage);
853853
if (!isTryCatchParameter() && !isConstructorParameter())
854854
locations.insert(Location::CallData);
855+
if (!isConstructorParameter() && isInternalCallableParameter())
856+
locations.insert(Location::Constant);
855857

856858
return locations;
857859
}

libsolidity/ast/AST.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1091,7 +1091,7 @@ class FunctionDefinition: public CallableDeclaration, public StructurallyDocumen
10911091
class VariableDeclaration: public Declaration, public StructurallyDocumented
10921092
{
10931093
public:
1094-
enum Location { Unspecified, Storage, Transient, Memory, CallData };
1094+
enum Location { Unspecified, Storage, Transient, Memory, CallData, Constant };
10951095
enum class Mutability { Mutable, Immutable, Constant };
10961096
static std::string mutabilityToString(Mutability _mutability)
10971097
{

libsolidity/ast/ASTJsonExporter.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,6 +1086,8 @@ std::string ASTJsonExporter::location(VariableDeclaration::Location _location)
10861086
return "calldata";
10871087
case VariableDeclaration::Location::Transient:
10881088
return "transient";
1089+
case VariableDeclaration::Location::Constant:
1090+
return "constant";
10891091
}
10901092
// To make the compiler happy
10911093
return {};

libsolidity/ast/ASTJsonImporter.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1218,6 +1218,8 @@ VariableDeclaration::Location ASTJsonImporter::location(Json const& _node)
12181218
return VariableDeclaration::Location::CallData;
12191219
else if (storageLocStr == "transient")
12201220
return VariableDeclaration::Location::Transient;
1221+
else if (storageLocStr == "constant")
1222+
return VariableDeclaration::Location::Constant;
12211223
else
12221224
astAssert(false, "Unknown location declaration");
12231225

libsolidity/ast/Types.cpp

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1552,6 +1552,8 @@ TypeResult ReferenceType::unaryOperatorResult(Token _operator) const
15521552
return isPointer() ? nullptr : TypeProvider::emptyTuple();
15531553
case DataLocation::Transient:
15541554
solUnimplemented("Transient data location is only supported for value types.");
1555+
case DataLocation::Constant:
1556+
return nullptr;
15551557
}
15561558
return nullptr;
15571559
}
@@ -1582,6 +1584,8 @@ std::string ReferenceType::stringForReferencePart() const
15821584
case DataLocation::Transient:
15831585
solUnimplemented("Transient data location is only supported for value types.");
15841586
break;
1587+
case DataLocation::Constant:
1588+
return "constant";
15851589
}
15861590
solAssert(false, "");
15871591
return "";
@@ -1604,6 +1608,9 @@ std::string ReferenceType::identifierLocationSuffix() const
16041608
case DataLocation::CallData:
16051609
id += "_calldata";
16061610
break;
1611+
case DataLocation::Constant:
1612+
id += "_constant";
1613+
break;
16071614
}
16081615
if (isPointer())
16091616
id += "_ptr";
@@ -1646,6 +1653,14 @@ BoolResult ArrayType::isImplicitlyConvertibleTo(Type const& _convertTo) const
16461653
return true;
16471654
return !isDynamicallySized() && convertTo.length() >= length();
16481655
}
1656+
else if (convertTo.location() == DataLocation::Constant)
1657+
{
1658+
if (!baseType()->isImplicitlyConvertibleTo(*convertTo.baseType()))
1659+
return false;
1660+
if (convertTo.isDynamicallySized())
1661+
return true;
1662+
return !isDynamicallySized() && convertTo.length() >= length();
1663+
}
16491664
else
16501665
{
16511666
// Conversion to storage pointer or to memory, we do not copy element-for-element here, so
@@ -1769,6 +1784,28 @@ BoolResult ArrayType::validForLocation(DataLocation _loc) const
17691784
case DataLocation::Transient:
17701785
solUnimplemented("Transient data location is only supported for value types.");
17711786
break;
1787+
case DataLocation::Constant:
1788+
{
1789+
bigint size = bigint(length());
1790+
auto type = m_baseType;
1791+
while (auto arrayType = dynamic_cast<ArrayType const*>(type))
1792+
{
1793+
if (arrayType->isDynamicallySized())
1794+
break;
1795+
else
1796+
{
1797+
size *= arrayType->length();
1798+
type = arrayType->baseType();
1799+
}
1800+
}
1801+
if (type->isDynamicallySized())
1802+
size *= type->memoryHeadSize();
1803+
else
1804+
size *= type->memoryDataSize();
1805+
if (size >= std::numeric_limits<unsigned>::max())
1806+
return BoolResult::err("Type too large for constant data.");
1807+
break;
1808+
}
17721809
}
17731810
return true;
17741811
}
@@ -1852,6 +1889,11 @@ std::vector<std::tuple<std::string, Type const*>> ArrayType::makeStackItems() co
18521889
case DataLocation::Transient:
18531890
solUnimplemented("Transient data location is only supported for value types.");
18541891
break;
1892+
case DataLocation::Constant:
1893+
if (isDynamicallySized() && !isByteArrayOrString())
1894+
return {std::make_tuple("codeOffset", TypeProvider::uint256()), std::make_tuple("length", TypeProvider::uint256())};
1895+
else
1896+
return {std::make_tuple("codeOffset", TypeProvider::uint256())};
18551897
}
18561898
solAssert(false, "");
18571899
}
@@ -2050,7 +2092,7 @@ BoolResult ArraySliceType::isImplicitlyConvertibleTo(Type const& _other) const
20502092
return
20512093
(*this) == _other ||
20522094
(
2053-
m_arrayType.dataStoredIn(DataLocation::CallData) &&
2095+
(m_arrayType.dataStoredIn(DataLocation::CallData) || m_arrayType.dataStoredIn(DataLocation::Constant)) &&
20542096
m_arrayType.isDynamicallySized() &&
20552097
m_arrayType.isImplicitlyConvertibleTo(_other)
20562098
);
@@ -2609,6 +2651,8 @@ std::vector<std::tuple<std::string, Type const*>> StructType::makeStackItems() c
26092651
case DataLocation::Transient:
26102652
solUnimplemented("Transient data location is only supported for value types.");
26112653
break;
2654+
case DataLocation::Constant:
2655+
return {std::make_tuple("codeOffset", TypeProvider::uint256())};
26122656
}
26132657
solAssert(false, "");
26142658
}

0 commit comments

Comments
 (0)