Add InlineArrayType to support literals conversion to statically and dynamically allocated arrays.

This commit is contained in:
wechman 2022-07-15 12:32:40 +02:00
parent 7bfec3ba70
commit 1ef2f60049
63 changed files with 1581 additions and 384 deletions

View File

@ -1,9 +1,13 @@
### 0.8.17 (unreleased)
Important Bugfixes:
* Array literals are convertible to both dynamically-sized and statically-sized arrays.
* String literals being part of an array literal can be implicitly converted into bytes.
Language Features:
* Array literals are convertible to both dynamically-sized and statically-sized arrays.
* String literals being part of an array literal can be implicitly converted into bytes.
Compiler Features:

View File

@ -237,96 +237,68 @@ Array Literals
^^^^^^^^^^^^^^
An array literal is a comma-separated list of one or more expressions, enclosed
in square brackets (``[...]``). For example ``[1, a, f(3)]``. The type of the
array literal is determined as follows:
It is always a statically-sized memory array whose length is the
number of expressions.
The base type of the array is the type of the first expression on the list such that all
other expressions can be implicitly converted to it. It is a type error
if this is not possible.
It is not enough that there is a type all the elements can be converted to. One of the elements
has to be of that type.
In the example below, the type of ``[1, 2, 3]`` is
``uint8[3] memory``, because the type of each of these constants is ``uint8``. If
you want the result to be a ``uint[3] memory`` type, you need to convert
the first element to ``uint``.
in square brackets (``[...]``). For example ``[1, a, f(3)]``. It can be converted to
statically and dynamically-sized array if all its expressions can be implicitly
converted to the base type of the array. In the example below, the conversion is impossible
because ``-1`` cannot be implicitly converted to ``uint8``.
.. code-block:: solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
pragma solidity >=0.8.16 <0.9.0;
// This will not compile
contract C {
function f() public pure {
g([uint(1), 2, 3]);
// The next line creates a type error because int_const -1
// cannot be converted to uint8 memory.
g([1, -1]);
}
function g(uint8[] memory) public pure {
// ...
}
}
A conversion into a statically-sized array can only be performed if the length of the array matches
the number of the expressions in the array literal. Therefore following is impossible:
.. code-block:: solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.16 <0.9.0;
// This will not compile
contract C {
function f() public pure {
// The next line creates a type error because inline_array(int_const 1, int_const 1)
// cannot be converted to uint[3] memory.
g([1, 2]);
}
function g(uint[3] memory) public pure {
// ...
}
}
The array literal ``[1, -1]`` is invalid because the type of the first expression
is ``uint8`` while the type of the second is ``int8`` and they cannot be implicitly
converted to each other. To make it work, you can use ``[int8(1), -1]``, for example.
Since fixed-size memory arrays of different type cannot be converted into each other
(even if the base types can), you always have to specify a common base type explicitly
if you want to use two-dimensional array literals:
Array literals can also be used to initialize multi-dimensional arrays:
.. code-block:: solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
pragma solidity >=0.8.16 <0.9.0;
contract C {
function f() public pure returns (uint24[2][4] memory) {
uint24[2][4] memory x = [[uint24(0x1), 1], [0xffffff, 2], [uint24(0xff), 3], [uint24(0xffff), 4]];
// The following does not work, because some of the inner arrays are not of the right type.
// uint[2][4] memory x = [[0x1, 1], [0xffffff, 2], [0xff, 3], [0xffff, 4]];
return x;
int256 [2][] a = [[1, 2], [3, 4], [5, 6]];
function f() public pure returns (uint8[][] memory) {
return [[1], [2, 3], [4, 5]];
}
}
Fixed size memory arrays cannot be assigned to dynamically-sized
memory arrays, i.e. the following is not possible:
.. code-block:: solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
// This will not compile.
contract C {
function f() public {
// The next line creates a type error because uint[3] memory
// cannot be converted to uint[] memory.
uint[] memory x = [uint(1), 3, 4];
}
}
It is planned to remove this restriction in the future, but it creates some
complications because of how arrays are passed in the ABI.
If you want to initialize dynamically-sized arrays, you have to assign the
individual elements:
.. code-block:: solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
contract C {
function f() public pure {
uint[] memory x = new uint[](3);
x[0] = 1;
x[1] = 3;
x[2] = 4;
}
}
.. note::
Until Solidity 0.8.15, array literals were always a statically-sized memory array whose length
was the number of expressions. The base type of the array was the type of the first expression
on the list such that all other expressions could be implicitly converted to it. It was a type
error if the conversion was not possible.
.. index:: ! array;length, length, push, pop, !array;push, !array;pop
@ -375,7 +347,7 @@ Array Members
.. code-block:: solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;
pragma solidity >=0.8.16 <0.9.0;
contract ArrayContract {
uint[2**20] aLotOfIntegers;
@ -457,12 +429,14 @@ Array Members
}
function createMemoryArray(uint size) public pure returns (bytes memory) {
// Dynamic memory arrays are created using `new`:
uint[2][] memory arrayOfPairs = new uint[2][](size);
// Dynamically-sized memory arrays are created using `new`:
uint[2][] memory arrayOfPairs1 = new uint[2][](size);
// Inline arrays are always statically-sized and if you only
// use literals, you have to provide at least one type.
arrayOfPairs[0] = [uint(1), 2];
// Dynamically-sized memory arrays can be created with array literals as well:
uint[2][] memory arrayOfPairs2 = [[1, 2], [3, 4], [5, 6]];
require(arrayOfPairs1.length == arrayOfPairs2.length);
require(arrayOfPairs2[1][1] == 4);
// Create a dynamic byte array:
bytes memory b = new bytes(200);

View File

@ -67,6 +67,13 @@ bool TypeChecker::typeSupportedByOldABIEncoder(Type const& _type, bool _isLibrar
if (!typeSupportedByOldABIEncoder(*base, _isLibraryCall) || (base->category() == Type::Category::Array && base->isDynamicallySized()))
return false;
}
if (_type.category() == Type::Category::InlineArray)
{
auto const& mobileType = dynamic_cast<InlineArrayType const&>(_type).mobileType();
if (!mobileType)
return false;
return typeSupportedByOldABIEncoder(*mobileType, _isLibraryCall);
}
return true;
}
@ -1647,7 +1654,6 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
else
{
bool isPure = true;
Type const* inlineArrayType = nullptr;
for (size_t i = 0; i < components.size(); ++i)
{
@ -1662,7 +1668,8 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
{
if (_tuple.isInlineArray())
m_errorReporter.fatalTypeError(5604_error, components[i]->location(), "Array component cannot be empty.");
m_errorReporter.typeError(6473_error, components[i]->location(), "Tuple component cannot be empty.");
else
m_errorReporter.typeError(6473_error, components[i]->location(), "Tuple component cannot be empty.");
}
// Note: code generation will visit each of the expression even if they are not assigned from.
@ -1670,48 +1677,37 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
if (!dynamic_cast<RationalNumberType const&>(*types[i]).mobileType())
m_errorReporter.fatalTypeError(3390_error, components[i]->location(), "Invalid rational number.");
if (_tuple.isInlineArray())
if (_tuple.isInlineArray() &&
types[i]->category() != Type::Category::InlineArray)
{
solAssert(!!types[i], "Inline array cannot have empty components");
if ((i == 0 || inlineArrayType) && !types[i]->mobileType())
Type const* mobileType = types[i]->mobileType();
if (!mobileType)
m_errorReporter.fatalTypeError(9563_error, components[i]->location(), "Invalid mobile type.");
if (i == 0)
inlineArrayType = types[i]->mobileType();
else if (inlineArrayType)
inlineArrayType = Type::commonType(inlineArrayType, types[i]);
else if (!mobileType->nameable())
m_errorReporter.fatalTypeError(
9656_error,
_tuple.location(),
"Unable to deduce nameable type for array elements. Try adding explicit type conversion for the first element."
);
else if (mobileType->containsNestedMapping())
m_errorReporter.fatalTypeError(
1545_error,
_tuple.location(),
"Type " + types[i]->humanReadableName() + " is only valid in storage."
);
}
if (!*components[i]->annotation().isPure)
isPure = false;
}
_tuple.annotation().isPure = isPure;
if (_tuple.isInlineArray())
{
if (!inlineArrayType)
m_errorReporter.fatalTypeError(6378_error, _tuple.location(), "Unable to deduce common type for array elements.");
else if (!inlineArrayType->nameable())
m_errorReporter.fatalTypeError(
9656_error,
_tuple.location(),
"Unable to deduce nameable type for array elements. Try adding explicit type conversion for the first element."
);
else if (inlineArrayType->containsNestedMapping())
m_errorReporter.fatalTypeError(
1545_error,
_tuple.location(),
"Type " + inlineArrayType->humanReadableName() + " is only valid in storage."
);
_tuple.annotation().type = TypeProvider::array(DataLocation::Memory, inlineArrayType, types.size());
}
if (_tuple.isInlineArray())
_tuple.annotation().type = TypeProvider::inlineArray(move(types));
else if (components.size() == 1)
_tuple.annotation().type = type(*components[0]);
else
{
if (components.size() == 1)
_tuple.annotation().type = type(*components[0]);
else
_tuple.annotation().type = TypeProvider::tuple(move(types));
}
_tuple.annotation().type = TypeProvider::tuple(move(types));
_tuple.annotation().isLValue = false;
}
@ -2105,10 +2101,9 @@ void TypeChecker::typeCheckABIEncodeFunctions(
}
// Check additional arguments for variadic functions
vector<ASTPointer<Expression const>> const& arguments = _functionCall.arguments();
for (size_t i = 0; i < arguments.size(); ++i)
for (auto const& argument: _functionCall.arguments())
{
auto const& argType = type(*arguments[i]);
Type const* argType = type(*argument);
if (argType->category() == Type::Category::RationalNumber)
{
@ -2117,7 +2112,7 @@ void TypeChecker::typeCheckABIEncodeFunctions(
{
m_errorReporter.typeError(
6090_error,
arguments[i]->location(),
argument->location(),
"Fractional numbers cannot yet be encoded."
);
continue;
@ -2126,7 +2121,7 @@ void TypeChecker::typeCheckABIEncodeFunctions(
{
m_errorReporter.typeError(
8009_error,
arguments[i]->location(),
argument->location(),
"Invalid rational number (too large or division by zero)."
);
continue;
@ -2135,7 +2130,7 @@ void TypeChecker::typeCheckABIEncodeFunctions(
{
m_errorReporter.typeError(
7279_error,
arguments[i]->location(),
argument->location(),
"Cannot perform packed encoding for a literal."
" Please convert it to an explicit type first."
);
@ -2147,7 +2142,7 @@ void TypeChecker::typeCheckABIEncodeFunctions(
{
m_errorReporter.typeError(
9578_error,
arguments[i]->location(),
argument->location(),
"Type not supported in packed mode."
);
continue;
@ -2156,7 +2151,7 @@ void TypeChecker::typeCheckABIEncodeFunctions(
if (!argType->fullEncodingType(false, abiEncoderV2, !_functionType->padArguments()))
m_errorReporter.typeError(
2056_error,
arguments[i]->location(),
argument->location(),
"This type cannot be encoded."
);
}
@ -3361,6 +3356,16 @@ bool TypeChecker::visit(IndexAccess const& _access)
isLValue = actualType.location() != DataLocation::CallData;
break;
}
case Type::Category::InlineArray:
{
if (!index)
m_errorReporter.typeError(5093_error, _access.location(), "Index expression cannot be omitted.");
else
expectType(*index, *TypeProvider::uint256());
resultType = dynamic_cast<InlineArrayType const&>(*baseType).componentsCommonMobileType();
break;
}
case Type::Category::Mapping:
{
MappingType const& actualType = dynamic_cast<MappingType const&>(*baseType);
@ -3469,6 +3474,8 @@ bool TypeChecker::visit(IndexRangeAccess const& _access)
ArrayType const* arrayType = nullptr;
if (auto const* arraySlice = dynamic_cast<ArraySliceType const*>(exprType))
arrayType = &arraySlice->arrayType();
else if (auto const* inlineArray = dynamic_cast<InlineArrayType const*>(exprType))
arrayType = TypeProvider::array(DataLocation::Memory, inlineArray->componentsCommonMobileType(), inlineArray->components().size());
else if (!(arrayType = dynamic_cast<ArrayType const*>(exprType)))
m_errorReporter.fatalTypeError(4781_error, _access.location(), "Index range access is only possible for arrays and array slices.");

View File

@ -407,6 +407,11 @@ TupleType const* TypeProvider::tuple(vector<Type const*> members)
return createAndGet<TupleType>(move(members));
}
InlineArrayType const* TypeProvider::inlineArray(vector<Type const*> _members)
{
return createAndGet<InlineArrayType>(move(_members));
}
ReferenceType const* TypeProvider::withLocation(ReferenceType const* _type, DataLocation _location, bool _isPointer)
{
if (_type->location() == _location && _type->isPointer() == _isPointer)

View File

@ -109,6 +109,8 @@ public:
static TupleType const* emptyTuple() noexcept { return &m_emptyTuple; }
static InlineArrayType const* inlineArray(std::vector<Type const*> members);
static ReferenceType const* withLocation(ReferenceType const* _type, DataLocation _location, bool _isPointer);
/// @returns a copy of @a _type having the same location as this (and is not a pointer type)

View File

@ -323,6 +323,7 @@ Type const* Type::fullEncodingType(bool _inLibraryCall, bool _encoderV2, bool) c
if (_inLibraryCall && encodingType && encodingType->dataStoredIn(DataLocation::Storage))
return encodingType;
Type const* baseType = encodingType;
while (auto const* arrayType = dynamic_cast<ArrayType const*>(baseType))
{
baseType = arrayType->baseType();
@ -2740,6 +2741,101 @@ Type const* TupleType::mobileType() const
return TypeProvider::tuple(move(mobiles));
}
BoolResult InlineArrayType::isImplicitlyConvertibleTo(Type const& _other) const
{
auto arrayType = dynamic_cast<ArrayType const*>(&_other);
if (!arrayType || arrayType->isByteArrayOrString())
return BoolResult::err("Array literal can not be converted to byte array or string.");
if (!arrayType->isDynamicallySized() && arrayType->length() != components().size())
return BoolResult::err(
"Number of components in array literal (" + to_string(components().size()) + ") " +
"does not match array size (" + to_string(arrayType->length().convert_to<unsigned>()) + ")."
);
for (Type const* c: components())
{
BoolResult result = c->isImplicitlyConvertibleTo(*arrayType->baseType());
if (!result)
return BoolResult::err(
"Invalid conversion from " + c->toString(false) +
" to " + arrayType->baseType()->toString(false) + "." +
(result.message().empty() ? "" : " ") + result.message()
);
}
return true;
}
string InlineArrayType::richIdentifier() const
{
return "t_inline_array" + identifierList(components());
}
bool InlineArrayType::operator==(Type const& _other) const
{
if (auto inlineArrayType = dynamic_cast<InlineArrayType const*>(&_other))
{
std::vector<Type const*> const& otherComponents = inlineArrayType->components();
if (m_components.size() != otherComponents.size())
return false;
for (unsigned i = 0; i < m_components.size(); ++i)
if (!(*m_components[i] == *otherComponents[i]))
return false;
return true;
}
else
return false;
}
string InlineArrayType::toString(bool _short) const
{
vector<string> result;
for (auto const& t: components())
result.push_back(t->toString(_short));
return "inline_array(" + util::joinHumanReadable(result) + ")";
}
u256 InlineArrayType::storageSize() const
{
solAssert(false, "Storage size of non-storable InlineArrayType type requested.");
}
Type const* InlineArrayType::mobileType() const
{
Type const* baseType = componentsCommonMobileType();
return
baseType ?
TypeProvider::array(DataLocation::Memory, baseType, components().size()) :
nullptr;
}
Type const* InlineArrayType::componentsCommonMobileType() const
{
solAssert(!m_components.empty(), "Empty array literal");
Type const* commonType = nullptr;
for (Type const* type: m_components)
commonType =
commonType ?
Type::commonType(commonType, type->mobileType()) :
type->mobileType();
return TypeProvider::withLocationIfReference(DataLocation::Memory, commonType);
}
vector<tuple<string, Type const*>> InlineArrayType::makeStackItems() const
{
vector<tuple<string, Type const*>> slots;
for (auto && [index, type]: components() | ranges::views::enumerate)
slots.emplace_back("component_" + std::to_string(index + 1), type);
return slots;
}
FunctionType::FunctionType(FunctionDefinition const& _function, Kind _kind):
m_kind(_kind),
m_stateMutability(_function.stateMutability()),

View File

@ -175,7 +175,7 @@ public:
enum class Category
{
Address, Integer, RationalNumber, StringLiteral, Bool, FixedPoint, Array, ArraySlice,
FixedBytes, Contract, Struct, Function, Enum, UserDefinedValueType, Tuple,
FixedBytes, Contract, Struct, Function, Enum, UserDefinedValueType, Tuple, InlineArray,
Mapping, TypeType, Modifier, Magic, Module,
InaccessibleDynamic
};
@ -1204,6 +1204,41 @@ private:
std::vector<Type const*> const m_components;
};
class InlineArrayType: public CompositeType
{
public:
explicit InlineArrayType(std::vector<Type const*> _types): m_components(std::move(_types)) {}
Category category() const override { return Category::InlineArray; }
BoolResult isImplicitlyConvertibleTo(Type const& _other) const override;
std::string richIdentifier() const override;
bool operator==(Type const& _other) const override;
TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; }
std::string toString(bool) const override;
bool canBeStored() const override { return false; }
u256 storageSize() const override;
bool hasSimpleZeroValueInMemory() const override { return false; }
Type const* mobileType() const override;
Type const* componentsCommonMobileType() const;
std::vector<Type const*> const& components() const { return m_components; }
protected:
std::vector<std::tuple<std::string, Type const*>> makeStackItems() const override;
std::vector<Type const*> decomposition() const override
{
// Currently calling InlineArrayType::decomposition() is not expected, because we cannot declare a variable of a InlineArrayType type.
// If that changes, before removing the solAssert, make sure the function does the right thing and is used properly.
// Note that different InlineArrayType members can have different data locations, so using decomposition() to check
// the tuple validity for a data location might require special care.
solUnimplemented("InlineArray decomposition is not expected.");
return m_components;
}
private:
std::vector<Type const*> const m_components;
};
/**
* The type of a function, identified by its (return) parameter types.
* @todo the return parameters should also have names, i.e. return parameters should be a struct

View File

@ -29,6 +29,7 @@
#include <libsolutil/StringUtils.h>
#include <boost/algorithm/string/join.hpp>
#include <range/v3/view/enumerate.hpp>
using namespace std;
using namespace solidity;
@ -286,6 +287,16 @@ string ABIFunctions::abiEncodingFunction(
if (_from.category() == Type::Category::StringLiteral)
return abiEncodingFunctionStringLiteral(_from, to, _options);
else if (_from.category() == Type::Category::InlineArray)
{
solAssert(_to.category() == Type::Category::Array);
return abiEncodingFunctionInlineArray(
dynamic_cast<InlineArrayType const&>(_from),
dynamic_cast<ArrayType const&>(_to),
_options
);
}
else if (auto toArray = dynamic_cast<ArrayType const*>(&to))
{
ArrayType const* fromArray = nullptr;
@ -632,6 +643,87 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
});
}
string ABIFunctions::abiEncodingFunctionInlineArray(
InlineArrayType const& _from,
ArrayType const& _to,
EncodingOptions const& _options
)
{
string functionName =
"abi_encode_" +
_from.identifier() +
"_to_" +
_to.identifier() +
_options.toFunctionNameSuffix();
return createFunction(functionName, [&]() {
bool dynamic = _to.isDynamicallyEncoded();
bool dynamicBase = _to.baseType()->isDynamicallyEncoded();
bool const usesTail = dynamicBase && !_options.dynamicInplace;
EncodingOptions subOptions(_options);
subOptions.encodeFunctionFromStack = true;
subOptions.padded = true;
vector<map<string, string>> memberSetValues;
unsigned stackItemIndex = 0;
for (auto const& type: _from.components())
{
memberSetValues.emplace_back();
memberSetValues.back()["setMember"] = Whiskers(R"(
<?usesTail>
mstore(pos, sub(tail, headStart))
tail := <encodeToMemoryFun>(<value>, tail)
pos := add(pos, 0x20)
<!usesTail>
pos := <encodeToMemoryFun>(<value>, pos)
</usesTail>
)")
("value", suffixedVariableNameList("var_", stackItemIndex, stackItemIndex + type->sizeOnStack()))
("usesTail", usesTail)
("encodeToMemoryFun", abiEncodeAndReturnUpdatedPosFunction(*type, *_to.baseType(), subOptions))
.render();
stackItemIndex += type->sizeOnStack();
}
return Whiskers(R"(
// <readableTypeNameFrom> -> <readableTypeNameTo>
function <functionName>(<values>, pos) <return> {
let length := <length>
pos := <storeLength>(pos, length)
<?usesTail>
let headStart := pos
let tail := add(pos, mul(length, 0x20))
<#member>
<setMember>
</member>
pos := tail
<!usesTail>
<#member>
<setMember>
</member>
</usesTail>
<assignEnd>
}
)")
("functionName", functionName)
("member", std::move(memberSetValues))
("length", to_string(_from.components().size()))
("readableTypeNameFrom", _from.toString(true))
("readableTypeNameTo", _to.toString(true))
("return", dynamic ? " -> end " : "")
("assignEnd", dynamic ? "end := pos" : "")
("storeLength", arrayStoreLengthForEncodingFunction(_to, _options))
("usesTail", usesTail)
("values", suffixedVariableNameList("var_", 0, _from.sizeOnStack()))
.render();
});
}
string ABIFunctions::abiEncodingFunctionMemoryByteArray(
ArrayType const& _from,
ArrayType const& _to,

View File

@ -190,6 +190,11 @@ private:
ArrayType const& _targetType,
EncodingOptions const& _options
);
std::string abiEncodingFunctionInlineArray(
InlineArrayType const& _givenType,
ArrayType const& _targetType,
EncodingOptions const& _options
);
std::string abiEncodingFunctionMemoryByteArray(
ArrayType const& _givenType,
ArrayType const& _targetType,

View File

@ -36,6 +36,9 @@
#include <libevmasm/Instruction.h>
#include <liblangutil/Exceptions.h>
#include <range/v3/view/reverse.hpp>
#include <range/v3/view/enumerate.hpp>
using namespace std;
using namespace solidity;
using namespace solidity::evmasm;
@ -268,20 +271,13 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
<< swapInstruction(1 + byteOffsetSize);
_context.appendJumpTo(copyLoopStart);
_context << copyLoopEnd;
if (haveByteOffsetTarget)
{
// clear elements that might be left over in the current slot in target
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end target_byte_offset [source_byte_offset]
_context << dupInstruction(byteOffsetSize) << Instruction::ISZERO;
evmasm::AssemblyItem copyCleanupLoopEnd = _context.appendConditionalJump();
_context << dupInstruction(2 + byteOffsetSize) << dupInstruction(1 + byteOffsetSize);
StorageItem(_context, *targetBaseType).setToZero(SourceLocation(), true);
utils.incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2);
_context.appendJumpTo(copyLoopEnd);
_context << copyCleanupLoopEnd;
utils.clearLeftoversInSlot(*targetBaseType, byteOffsetSize, 2 + byteOffsetSize);
_context << Instruction::POP; // might pop the source, but then target is popped next
}
if (haveByteOffsetSource)
_context << Instruction::POP;
_context << copyLoopEndWithoutByteOffset;
@ -298,6 +294,188 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
}
);
}
void ArrayUtils::clearLeftoversInSlot(Type const& _type, unsigned _byteOffsetPosition, unsigned _storageOffsetPosition) const
{
// clear elements that might be left over in the current slot in target
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end target_byte_offset [source_byte_offset]
auto cleanupLoopStart = m_context.newTag();
m_context << cleanupLoopStart;
m_context << dupInstruction(_byteOffsetPosition) << Instruction::ISZERO;
evmasm::AssemblyItem copyCleanupLoopEnd = m_context.appendConditionalJump();
m_context << dupInstruction(_storageOffsetPosition) << dupInstruction(_byteOffsetPosition + 1);
StorageItem(m_context, _type).setToZero(SourceLocation(), true);
incrementByteOffset(_type.storageBytes(), _byteOffsetPosition, _storageOffsetPosition);
m_context.appendJumpTo(cleanupLoopStart);
m_context << copyCleanupLoopEnd;
}
void ArrayUtils::moveInlineArrayToStorage(
ArrayType const& _targetType,
InlineArrayType const& _sourceType,
unsigned _sourcePosition) const
{
// stack: source... ... target_ref
solAssert(!_targetType.containsNestedMapping());
solAssert(!_targetType.isByteArrayOrString());
solAssert(_sourceType.components().size() > 0);
Type const* arrayBaseType = _targetType.baseType();
bool const hasByteOffset = arrayBaseType->storageBytes() <= 16;
if (_targetType.isDynamicallySized())
{
m_context << u256(_sourceType.components().size());
// stack: source... ... target_ref source_length
m_context << Instruction::DUP2 << Instruction::SSTORE;
// stack: source... ... target_ref
m_context << Instruction::DUP1;
CompilerUtils(m_context).computeHashStatic();
++_sourcePosition;
// stack: source... ... target_ref target_data_pos
}
for (auto&& [index, sourceComponentType]:
_sourceType.components() | ranges::views::enumerate | ranges::views::reverse)
{
solAssert(arrayBaseType->nameable(), "");
if (ArrayType const* targetType = dynamic_cast<ArrayType const*>(_targetType.baseType()))
{
// stack: source... ... target_ref target_data_pos
m_context
<< Instruction::DUP1
<< u256(targetType->storageSize() * index) << Instruction::ADD;
// stack: source... ... target_ref target_data_pos component_data_ref
if (StringLiteralType const* stringLiteralType = dynamic_cast<StringLiteralType const*>(sourceComponentType))
copyLiteralToStorage(*stringLiteralType);
else
{
InlineArrayType const* sourceType = dynamic_cast<InlineArrayType const*>(sourceComponentType);
solAssert(sourceType);
moveInlineArrayToStorage(*targetType, *sourceType, _sourcePosition + 1);
// stack: source... ... target_ref target_data_pos component_data_ref
}
m_context << Instruction::POP;
// stack: source... ... target_ref target_data_pos
}
else
{
// stack: source... ... target_ref target_data_pos
CompilerUtils(m_context).moveToStackTop(
_sourcePosition,
sourceComponentType->sizeOnStack()
);
// stack: source... ... target_ref target_data_pos value...
m_context << dupInstruction(1 + sourceComponentType->sizeOnStack());
Type const* stackType = sourceComponentType;
if (RationalNumberType const* rType = dynamic_cast<RationalNumberType const*>(sourceComponentType))
{
solUnimplementedAssert(!rType->isFractional(), "Not yet implemented - FixedPointType.");
stackType = rType->integerType();
CompilerUtils(m_context).convertType(*sourceComponentType, *stackType);
}
// stack: source... ... target_ref target_data_pos value... target_data_pos
computeStoragePosition(static_cast<unsigned>(index), arrayBaseType->storageBytes());
// stack: source... ... target_ref target_data_pos value... target_data_pos offset
if (index == _sourceType.components().size() && hasByteOffset)
{
m_context << Instruction::DUP2 << Instruction::DUP2;
clearLeftoversInSlot(*arrayBaseType, 1, 2);
m_context << Instruction::POP << Instruction::POP;
}
StorageItem(m_context, *arrayBaseType)
.storeValue(*stackType, SourceLocation(), true);
// stack: source... ... target_ref target_data_pos
}
}
// stack: ... target_ref target_data_pos
if (_targetType.isDynamicallySized())
m_context << Instruction::POP;
// stack: ... target_ref
}
void ArrayUtils::copyLiteralToStorage(StringLiteralType const& _sourceType) const
{
bytesConstRef data(_sourceType.value());
// stack: target_ref
if (data.empty())
m_context << u256(0) << Instruction::DUP2 << Instruction::SSTORE;
else if (data.size() < 32)
{
// stack: target_ref
m_context
<< u256(util::h256(data, util::h256::AlignLeft))
<< u256(2)
<< u256(data.size())
<< Instruction::MUL
<< Instruction::ADD;
// stack: target_ref value
m_context << Instruction::DUP2 << Instruction::SSTORE;
// stack: target_ref
}
else
{
// stack: target_ref
m_context << Instruction::DUP1;
// stack: target_ref target_ref
m_context
<< u256(1) << u256(2) << u256(data.size())
<< Instruction::MUL << Instruction::ADD;
// stack: target_ref target_ref 2*length+1
m_context << Instruction::DUP2 << Instruction::SSTORE;
// stack: target_ref target_ref
CompilerUtils(m_context).computeHashStatic();
// stack: target_ref target_data_pos
for (size_t index = 0; index <= data.size() / 32; ++index)
{
// stack: target_ref target_data_pos
size_t const chunk = min<size_t>(32, data.size() - index * 32);
m_context << u256(util::h256(data.cropped(index * 32, chunk), util::h256::AlignLeft));
// stack: target_ref target_data_pos value
m_context << Instruction::DUP2 << Instruction::SSTORE;
// stack: target_ref target_data_pos
m_context << u256(1) << Instruction::ADD;
// stack: target_ref target_data_pos+1
}
m_context << Instruction::POP;
// stack: target_ref
}
}
void ArrayUtils::computeStoragePosition(unsigned _index, unsigned _byteSize) const
{
// We do the following calculation:
// slot = element_index * element_byte_size / 32
// offset = element_index * element_byte_size % 32
// stack: slot
m_context << u256(_byteSize) << u256(_index) << Instruction::MUL;
// stack: slot byte_pos
m_context << Instruction::SWAP1;
// stack: byte_pos slot
m_context << u256(32) << Instruction::DUP3 << Instruction::DIV;
// stack: byte_pos slot slot_offset
m_context << Instruction::ADD << Instruction::SWAP1;
// stack: target_slot byte_pos
m_context << u256(32) << Instruction::SWAP1 << Instruction::MOD;
// stack: target_slot offset
}
void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWordBoundaries) const
{
@ -535,6 +713,52 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord
}
}
void ArrayUtils::moveInlineArrayToMemory(
InlineArrayType const& _sourceType,
ArrayType const& _targetType,
unsigned _sourcePosition,
bool _padToWordBoundaries) const
{
auto const& components = _sourceType.components();
u256 const memoryStride =
_padToWordBoundaries ?
(_targetType.memoryStride() + 31) / 32 * 32 :
_targetType.memoryStride();
// value... ... target_pos
m_context << u256(components.size() * memoryStride) << Instruction::ADD;
// value... ... target_pos_end
m_context << Instruction::DUP1;
// value... ... target_pos_end target_pos_end
CompilerUtils utils(m_context);
for (Type const* component: components | ranges::views::reverse)
{
solAssert(
component->category() != Type::Category::InlineArray &&
component->category() != Type::Category::Array &&
component->category() != Type::Category::ArraySlice);
// values... ... target_pos_end component_pos_end
m_context << memoryStride << Instruction::SWAP1 << Instruction::SUB;
// values... ... target_pos_end component_pos
m_context << Instruction::DUP1;
// values... ... target_pos_end component_pos component_pos
utils.moveToStackTop(_sourcePosition + 2, component->sizeOnStack());
// values... ... target_pos_end component_pos component_pos value
utils.convertType(*component, *_targetType.baseType());
// values... ... target_pos_end component_pos component_pos converted_value
utils.storeInMemoryDynamic(*_targetType.baseType());
// values... ... target_pos_end component_pos component_pos_end
m_context << Instruction::POP;
// values... ... target_pos_end component_pos
}
// ... target_pos_end component_pos
m_context << Instruction::POP;
// ... target_pos_end
}
void ArrayUtils::clearArray(ArrayType const& _typeIn) const
{
Type const* type = &_typeIn;

View File

@ -28,9 +28,11 @@
namespace solidity::frontend
{
class CompilerContext;
class Type;
class ArrayType;
class CompilerContext;
class InlineArrayType;
class StringLiteralType;
class Type;
/**
* Class that provides code generation for handling arrays.
@ -45,6 +47,16 @@ public:
/// Stack pre: source_reference [source_length] target_reference
/// Stack post: target_reference
void copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const;
/// Moves an inline array from the stack to the storage.
/// @param sourcePosition the stack offset of the source
/// Stack pre: source ... target_reference
/// Stack post: ... target_reference
void moveInlineArrayToStorage(
ArrayType const& _targetType,
InlineArrayType const& _sourceType,
unsigned _sourcePosition = 1) const;
/// Copies the data part of an array (which cannot be dynamically nested) from anywhere
/// to a given position in memory.
/// This always copies contained data as is (i.e. structs and fixed-size arrays are copied in
@ -53,6 +65,17 @@ public:
/// Stack pre: memory_offset source_item
/// Stack post: memory_offest + length(padded)
void copyArrayToMemory(ArrayType const& _sourceType, bool _padToWordBoundaries = true) const;
/// Moves inline array from the stack to a given position in memory.
/// @param sourcePosition the stack offset of the source
/// Stack pre: source ... target_reference
/// Stack post: ... target_reference + length(padded)
void moveInlineArrayToMemory(
InlineArrayType const& _sourceType,
ArrayType const& _targetType,
unsigned _sourcePosition,
bool _padToWordBoundaries = true) const;
/// Clears the given dynamic or static array.
/// Stack pre: storage_ref storage_byte_offset
/// Stack post:
@ -114,6 +137,29 @@ private:
/// @param storageOffsetPosition the stack offset of the storage slot offset
void incrementByteOffset(unsigned _byteSize, unsigned _byteOffsetPosition, unsigned _storageOffsetPosition) const;
/// Copy a string literal to the storage.
/// @param sourcePosition the stack offset of the source
/// Stack pre: target_reference
/// Stack post: target_reference
void copyLiteralToStorage(StringLiteralType const& _sourceType) const;
/// Appends code that computes a storage position of the array element.
/// @param index array element index
/// @param byteSize array element size in bytes
/// Stack pre: slot
/// Stack post: target_slot byte_offset
void computeStoragePosition(unsigned _index, unsigned _byteSize) const;
/// Appends code that set to zero all elements in slot starting at offset.
/// Slot and offset are updated to show next slot.
/// @param type element type
/// @param byteOffsetPosition the stack offset of the storage byte offset
/// @param storageOffsetPosition the stack offset of the storage slot offset
/// Stack pre: ... slot offset ...
/// Stack post: ... slot offset ...
void clearLeftoversInSlot(Type const& _type, unsigned _byteOffsetPosition, unsigned _storageOffsetPosition) const;
CompilerContext& m_context;
};

View File

@ -32,6 +32,7 @@
#include <libevmasm/Instruction.h>
#include <libsolutil/Whiskers.h>
#include <libsolutil/StackTooDeepString.h>
#include <range/v3/view/reverse.hpp>
using namespace std;
using namespace solidity;
@ -462,6 +463,7 @@ void CompilerUtils::encodeToMemory(
// store memory start pointer
m_context << Instruction::DUP1;
ArrayUtils utils(m_context);
unsigned argSize = CompilerUtils::sizeOnStack(_givenTypes);
unsigned stackPos = 0; // advances through the argument values
unsigned dynPointers = 0; // number of dynamic head pointers on the stack
@ -479,6 +481,14 @@ void CompilerUtils::encodeToMemory(
StackTooDeepError,
util::stackTooDeepString
);
stackPos += _givenTypes[i]->sizeOnStack();
}
else if (InlineArrayType const* inlineArrayType = dynamic_cast<InlineArrayType const*>(_givenTypes[i]))
{
ArrayType const* arrayType = dynamic_cast<ArrayType const*>(_targetTypes[i]);
unsigned const sourceStackPosition = argSize - stackPos + dynPointers - inlineArrayType->sizeOnStack() + 2;
utils.moveInlineArrayToMemory(*inlineArrayType, *arrayType, sourceStackPosition, _padToWordBoundaries);
argSize -= inlineArrayType->sizeOnStack();
}
else
{
@ -520,8 +530,10 @@ void CompilerUtils::encodeToMemory(
}
else
storeInMemoryDynamic(*type, _padToWordBoundaries, needCleanup);
stackPos += _givenTypes[i]->sizeOnStack();
}
stackPos += _givenTypes[i]->sizeOnStack();
}
// now copy the dynamic part
@ -545,7 +557,18 @@ void CompilerUtils::encodeToMemory(
m_context << dupInstruction(2 + dynPointers - thisDynPointer);
m_context << Instruction::MSTORE;
// stack: ... <end_of_mem>
if (_givenTypes[i]->category() == Type::Category::StringLiteral)
if (InlineArrayType const* inlineArrayType = dynamic_cast<InlineArrayType const*>(_givenTypes[i]))
{
ArrayType const* arrayType = dynamic_cast<ArrayType const*>(_targetTypes[i]);
m_context << u256(inlineArrayType->components().size());
storeInMemoryDynamic(*TypeProvider::uint256(), true);
unsigned const sourceStackPosition = argSize - stackPos + dynPointers - inlineArrayType->sizeOnStack() + 2;
utils.moveInlineArrayToMemory(*inlineArrayType, *arrayType, sourceStackPosition, _padToWordBoundaries);
argSize -= inlineArrayType->sizeOnStack();
}
else if (_givenTypes[i]->category() == Type::Category::StringLiteral)
{
auto const& strType = dynamic_cast<StringLiteralType const&>(*_givenTypes[i]);
auto const size = strType.value().size();
@ -1114,6 +1137,62 @@ void CompilerUtils::convertType(
}
break;
}
case Type::Category::InlineArray:
{
InlineArrayType const& inlineArray = dynamic_cast<InlineArrayType const&>(_typeOnStack);
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(_targetType);
solAssert(arrayType.location() == DataLocation::Memory);
auto const& components = inlineArray.components();
m_context << u256(components.size());
// stack: <source ref> <length>
ArrayUtils(m_context).convertLengthToSize(arrayType, true);
// stack: <source ref> <size>
if (arrayType.isDynamicallySized())
m_context << u256(0x20) << Instruction::ADD;
// <size> = <size> + 0x20
allocateMemory();
// stack: <source ref> <mem start>
m_context << Instruction::DUP1;
// stack: <source ref> <mem start> <mem start>
if (arrayType.isDynamicallySized())
{
m_context << u256(components.size());
// stack: <source ref> <mem start> <mem start> <length>
storeInMemoryDynamic(*TypeProvider::uint256());
// memory[<mem start>] = <length>
// stack: <source ref> <mem start> <mem data pos>
}
// stack: <source ref> <mem start> <mem data pos>
m_context << u256(components.size() * arrayType.baseType()->memoryHeadSize()) << Instruction::ADD;
// stack: <source ref> <mem start> <mem data end>
for (Type const* component: components | ranges::views::reverse)
{
// stack: <source ref> <mem start> <component end>
m_context << u256(arrayType.memoryStride()) << Instruction::SWAP1 << Instruction::SUB;
// stack: <source ref> <mem start> <component pos>
m_context << Instruction::DUP1;
// stack: <source ref> <mem start> <component pos> <component pos>
unsigned const componentSize = component->sizeOnStack();
moveToStackTop(3, componentSize);
// stack: <source ref> <mem start> <component pos> <component pos> <value>
convertType(*component, *arrayType.baseType());
// stack: <source ref> <mem start> <component pos> <component pos> <converted value>
storeInMemoryDynamic(*arrayType.baseType());
// stack: <source ref> <mem start> <component pos> <component end>
m_context << Instruction::POP;
// stack: <source ref> <mem start> <component pos>
}
// stack: <mem start> <mem data pos>
m_context << Instruction::POP;
// stack: <mem start>
break;
}
case Type::Category::ArraySlice:
{
auto& typeOnStack = dynamic_cast<ArraySliceType const&>(_typeOnStack);
@ -1287,6 +1366,7 @@ void CompilerUtils::convertType(
}
break;
}
case Type::Category::Bool:
solAssert(_targetType == _typeOnStack, "Invalid conversion for bool.");
if (_cleanupNeeded)

View File

@ -73,6 +73,23 @@ Type const* closestType(Type const* _type, Type const* _targetType, bool _isShif
}
return TypeProvider::tuple(move(tempComponents));
}
else if (auto const* inlineArrayType = dynamic_cast<InlineArrayType const*>(_type))
{
auto targetArray = dynamic_cast<ArrayType const*>(_targetType);
solAssert(targetArray);
if (targetArray->isDynamicallySized())
return TypeProvider::array(
DataLocation::Memory,
targetArray->baseType()
);
else
return TypeProvider::array(
DataLocation::Memory,
targetArray->baseType(),
inlineArrayType->components().size()
);
}
else
return _targetType->dataStoredIn(DataLocation::Storage) ? _type->mobileType() : _targetType;
}
@ -93,18 +110,21 @@ void ExpressionCompiler::appendStateVariableInitialization(VariableDeclaration c
CompilerContext::LocationSetter locationSetter(m_context, _varDecl);
_varDecl.value()->accept(*this);
if (_varDecl.annotation().type->dataStoredIn(DataLocation::Storage))
if (type->category() != Type::Category::InlineArray)
{
// reference type, only convert value to mobile type and do final conversion in storeValue.
auto mt = type->mobileType();
solAssert(mt, "");
utils().convertType(*type, *mt);
type = mt;
}
else
{
utils().convertType(*type, *_varDecl.annotation().type);
type = _varDecl.annotation().type;
if (_varDecl.annotation().type->dataStoredIn(DataLocation::Storage))
{
// reference type, only convert value to mobile type and do final conversion in storeValue.
auto mt = type->mobileType();
solAssert(mt, "");
utils().convertType(*type, *mt);
type = mt;
}
else
{
utils().convertType(*type, *_varDecl.annotation().type);
type = _varDecl.annotation().type;
}
}
if (_varDecl.immutable())
ImmutableItem(m_context, _varDecl).storeValue(*type, _varDecl.location(), true);
@ -365,44 +385,25 @@ bool ExpressionCompiler::visit(Assignment const& _assignment)
bool ExpressionCompiler::visit(TupleExpression const& _tuple)
{
if (_tuple.isInlineArray())
{
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(*_tuple.annotation().type);
solAssert(!arrayType.isDynamicallySized(), "Cannot create dynamically sized inline array.");
utils().allocateMemory(max(u256(32u), arrayType.memoryDataSize()));
m_context << Instruction::DUP1;
for (auto const& component: _tuple.components())
vector<unique_ptr<LValue>> lvalues;
for (auto const& component: _tuple.components())
if (component)
{
acceptAndConvert(*component, *arrayType.baseType(), true);
utils().storeInMemoryDynamic(*arrayType.baseType(), true);
}
m_context << Instruction::POP;
}
else
{
vector<unique_ptr<LValue>> lvalues;
for (auto const& component: _tuple.components())
if (component)
component->accept(*this);
if (_tuple.annotation().willBeWrittenTo)
{
component->accept(*this);
if (_tuple.annotation().willBeWrittenTo)
{
solAssert(!!m_currentLValue, "");
lvalues.push_back(move(m_currentLValue));
}
solAssert(!!m_currentLValue, "");
lvalues.push_back(move(m_currentLValue));
}
else if (_tuple.annotation().willBeWrittenTo)
lvalues.push_back(unique_ptr<LValue>());
if (_tuple.annotation().willBeWrittenTo)
{
if (_tuple.components().size() == 1)
m_currentLValue = move(lvalues[0]);
else
m_currentLValue = make_unique<TupleObject>(m_context, move(lvalues));
}
else if (_tuple.annotation().willBeWrittenTo)
lvalues.push_back(unique_ptr<LValue>());
if (_tuple.annotation().willBeWrittenTo)
{
if (_tuple.components().size() == 1)
m_currentLValue = move(lvalues[0]);
else
m_currentLValue = make_unique<TupleObject>(m_context, move(lvalues));
}
return false;
}
@ -2127,6 +2128,27 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
}
break;
}
case Type::Category::InlineArray:
{
InlineArrayType const& inlineArrayType = dynamic_cast<InlineArrayType const&>(baseType);
solAssert(_indexAccess.indexExpression(), "Index expression expected.");
ArrayType const* arrayType = TypeProvider::array(DataLocation::Memory, inlineArrayType.componentsCommonMobileType(), inlineArrayType.components().size());
// stack layout: <source ref> (variably sized)
acceptAndConvert(_indexAccess.baseExpression(), *arrayType, true);
utils().moveIntoStack(inlineArrayType.sizeOnStack());
utils().popStackSlots(inlineArrayType.sizeOnStack());
// stack layout: <array_ref> [<length>]
acceptAndConvert(*_indexAccess.indexExpression(), *TypeProvider::uint256(), true);
// stack layout: <array_ref> [<length>] <index>
ArrayUtils(m_context).accessIndex(*arrayType, true);
setLValue<MemoryItem>(_indexAccess, *arrayType->baseType());
break;
}
case Type::Category::FixedBytes:
{
FixedBytesType const& fixedBytesType = dynamic_cast<FixedBytesType const&>(baseType);

View File

@ -359,86 +359,100 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
}
else
{
solAssert(
_sourceType.category() == m_dataType->category(),
"Wrong type conversation for assignment."
);
if (m_dataType->category() == Type::Category::Array)
if (_sourceType.category() == Type::Category::InlineArray)
{
m_context << Instruction::POP; // remove byte offset
ArrayUtils(m_context).copyArrayToStorage(
ArrayUtils(m_context).moveInlineArrayToStorage(
dynamic_cast<ArrayType const&>(*m_dataType),
dynamic_cast<ArrayType const&>(_sourceType)
dynamic_cast<InlineArrayType const&>(_sourceType)
);
if (_move)
m_context << Instruction::POP;
}
else if (m_dataType->category() == Type::Category::Struct)
{
// stack layout: source_ref target_ref target_offset
// note that we have structs, so offset should be zero and are ignored
m_context << Instruction::POP;
auto const& structType = dynamic_cast<StructType const&>(*m_dataType);
auto const& sourceType = dynamic_cast<StructType const&>(_sourceType);
solAssert(
structType.structDefinition() == sourceType.structDefinition(),
"Struct assignment with conversion."
);
solAssert(!structType.containsNestedMapping(), "");
if (sourceType.location() == DataLocation::CallData)
{
solAssert(sourceType.sizeOnStack() == 1, "");
solAssert(structType.sizeOnStack() == 1, "");
m_context << Instruction::DUP2 << Instruction::DUP2;
m_context.callYulFunction(m_context.utilFunctions().updateStorageValueFunction(sourceType, structType, 0), 2, 0);
}
else
{
for (auto const& member: structType.members(nullptr))
{
// assign each member that can live outside of storage
Type const* memberType = member.type;
solAssert(memberType->nameable(), "");
Type const* sourceMemberType = sourceType.memberType(member.name);
if (sourceType.location() == DataLocation::Storage)
{
// stack layout: source_ref target_ref
pair<u256, unsigned> const& offsets = sourceType.storageOffsetsOfMember(member.name);
m_context << offsets.first << Instruction::DUP3 << Instruction::ADD;
m_context << u256(offsets.second);
// stack: source_ref target_ref source_member_ref source_member_off
StorageItem(m_context, *sourceMemberType).retrieveValue(_location, true);
// stack: source_ref target_ref source_value...
}
else
{
solAssert(sourceType.location() == DataLocation::Memory, "");
// stack layout: source_ref target_ref
m_context << sourceType.memoryOffsetOfMember(member.name);
m_context << Instruction::DUP3 << Instruction::ADD;
MemoryItem(m_context, *sourceMemberType).retrieveValue(_location, true);
// stack layout: source_ref target_ref source_value...
}
unsigned stackSize = sourceMemberType->sizeOnStack();
pair<u256, unsigned> const& offsets = structType.storageOffsetsOfMember(member.name);
m_context << dupInstruction(1 + stackSize) << offsets.first << Instruction::ADD;
m_context << u256(offsets.second);
// stack: source_ref target_ref target_off source_value... target_member_ref target_member_byte_off
StorageItem(m_context, *memberType).storeValue(*sourceMemberType, _location, true);
}
}
// stack layout: source_ref target_ref
solAssert(sourceType.sizeOnStack() == 1, "Unexpected source size.");
if (_move)
utils.popStackSlots(2);
else
m_context << Instruction::SWAP1 << Instruction::POP;
}
else
BOOST_THROW_EXCEPTION(
InternalCompilerError()
<< errinfo_sourceLocation(_location)
<< util::errinfo_comment("Invalid non-value type for assignment."));
{
solAssert(
_sourceType.category() == m_dataType->category(),
"Wrong type conversation for assignment."
);
if (m_dataType->category() == Type::Category::Array)
{
m_context << Instruction::POP; // remove byte offset
ArrayUtils(m_context).copyArrayToStorage(
dynamic_cast<ArrayType const&>(*m_dataType),
dynamic_cast<ArrayType const&>(_sourceType)
);
if (_move)
m_context << Instruction::POP;
}
else if (m_dataType->category() == Type::Category::Struct)
{
// stack layout: source_ref target_ref target_offset
// note that we have structs, so offset should be zero and are ignored
m_context << Instruction::POP;
auto const& structType = dynamic_cast<StructType const&>(*m_dataType);
auto const& sourceType = dynamic_cast<StructType const&>(_sourceType);
solAssert(
structType.structDefinition() == sourceType.structDefinition(),
"Struct assignment with conversion."
);
solAssert(!structType.containsNestedMapping(), "");
if (sourceType.location() == DataLocation::CallData)
{
solAssert(sourceType.sizeOnStack() == 1, "");
solAssert(structType.sizeOnStack() == 1, "");
m_context << Instruction::DUP2 << Instruction::DUP2;
m_context.callYulFunction(m_context.utilFunctions().updateStorageValueFunction(sourceType, structType, 0), 2, 0);
}
else
{
for (auto const& member: structType.members(nullptr))
{
// assign each member that can live outside of storage
Type const* memberType = member.type;
solAssert(memberType->nameable(), "");
Type const* sourceMemberType = sourceType.memberType(member.name);
if (sourceType.location() == DataLocation::Storage)
{
// stack layout: source_ref target_ref
pair<u256, unsigned> const& offsets = sourceType.storageOffsetsOfMember(member.name);
m_context << offsets.first << Instruction::DUP3 << Instruction::ADD;
m_context << u256(offsets.second);
// stack: source_ref target_ref source_member_ref source_member_off
StorageItem(m_context, *sourceMemberType).retrieveValue(_location, true);
// stack: source_ref target_ref source_value...
}
else
{
solAssert(sourceType.location() == DataLocation::Memory, "");
// stack layout: source_ref target_ref
m_context << sourceType.memoryOffsetOfMember(member.name);
m_context << Instruction::DUP3 << Instruction::ADD;
MemoryItem(m_context, *sourceMemberType).retrieveValue(_location, true);
// stack layout: source_ref target_ref source_value...
}
unsigned stackSize = sourceMemberType->sizeOnStack();
pair<u256, unsigned> const& offsets = structType.storageOffsetsOfMember(member.name);
m_context << dupInstruction(1 + stackSize) << offsets.first << Instruction::ADD;
m_context << u256(offsets.second);
// stack: source_ref target_ref target_off source_value... target_member_ref target_member_byte_off
StorageItem(m_context, *memberType).storeValue(*sourceMemberType, _location, true);
}
}
// stack layout: source_ref target_ref
solAssert(sourceType.sizeOnStack() == 1, "Unexpected source size.");
if (_move)
utils.popStackSlots(2);
else
m_context << Instruction::SWAP1 << Instruction::POP;
}
else
BOOST_THROW_EXCEPTION(
InternalCompilerError()
<< errinfo_sourceLocation(_location)
<< util::errinfo_comment("Invalid non-value type for assignment."));
}
}
}

View File

@ -31,6 +31,8 @@
#include <libsolutil/StringUtils.h>
#include <libsolidity/ast/TypeProvider.h>
#include <range/v3/view/enumerate.hpp>
using namespace std;
using namespace solidity;
using namespace solidity::util;
@ -1926,6 +1928,78 @@ string YulUtilFunctions::copyArrayToStorageFunction(ArrayType const& _fromType,
});
}
string YulUtilFunctions::copyInlineArrayToStorageFunction(InlineArrayType const& _fromType, ArrayType const& _toType)
{
if (!_toType.isDynamicallySized())
solAssert(_fromType.components().size() <= _toType.length(), "");
string const functionName = "copy_inline_array_to_storage_from_" + _fromType.identifier() + "_to_" + _toType.identifier();
return m_functionCollector.createFunction(functionName, [&](){
vector<map<string, string>> memberSetValues;
unsigned stackItemIndex = 0;
for (Type const* type: _fromType.components())
{
memberSetValues.emplace_back();
memberSetValues.back()["setMember"] = Whiskers(R"({
<updateStorageValue>(elementSlot, elementOffset<value>)
<?multipleItemsPerSlot>
elementOffset := add(elementOffset, <storageStride>)
if gt(elementOffset, sub(32, <storageStride>)) {
elementOffset := 0
elementSlot := add(elementSlot, 1)
}
<!multipleItemsPerSlot>
elementSlot := add(elementSlot, <storageSize>)
</multipleItemsPerSlot>
})")
("value", _fromType.sizeOnStack() ?
", " + suffixedVariableNameList("var_", stackItemIndex, stackItemIndex + type->sizeOnStack()) : "")
("multipleItemsPerSlot", _toType.storageStride() <= 16)
("storageStride", to_string(_toType.storageStride()))
("storageSize", _toType.baseType()->storageSize().str())
("updateStorageValue", updateStorageValueFunction(*type, *_toType.baseType()))
.render();
stackItemIndex += type->sizeOnStack();
}
Whiskers templ(R"(
function <functionName>(slot<values>) {
let length := <arrayLength>
<resizeArray>(slot, length)
let elementSlot := <dstDataLocation>(slot)
let elementOffset := 0
<#member>
<setMember>
</member>
<?multipleItemsPerSlot>
if gt(elementOffset, 0) {
<partialClearStorageSlotFunction>(elementSlot, elementOffset)
}
</multipleItemsPerSlot>
}
)");
if (_fromType.dataStoredIn(DataLocation::Storage))
solAssert(!_fromType.isValueType(), "");
templ("functionName", functionName);
templ("values", _fromType.sizeOnStack() ?
", " + suffixedVariableNameList("var_", 0, _fromType.sizeOnStack()) : "");
templ("arrayLength", to_string(_fromType.components().size()));
templ("resizeArray", resizeArrayFunction(_toType));
templ("dstDataLocation", arrayDataAreaFunction(_toType));
templ("member", move(memberSetValues));
templ("multipleItemsPerSlot", _toType.storageStride() <= 16);
templ("partialClearStorageSlotFunction", partialClearStorageSlotFunction());
return templ.render();
});
}
string YulUtilFunctions::copyByteArrayToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType)
{
@ -2820,6 +2894,31 @@ string YulUtilFunctions::updateStorageValueFunction(
auto const* fromReferenceType = dynamic_cast<ReferenceType const*>(&_fromType);
solAssert(toReferenceType, "");
Whiskers templ(R"(
function <functionName>(slot<?dynamicOffset>,offset </dynamicOffset><extraParams>) {
<?dynamicOffset>if offset { <panic>() }</dynamicOffset>
<copyToStorage>(slot<extraParams>)
}
)");
templ("functionName", functionName);
templ("dynamicOffset", !_offset.has_value());
templ("panic", panicFunction(PanicCode::Generic));
if (_fromType.category() == Type::Category::InlineArray)
{
solAssert(_toType.category() == Type::Category::Array, "");
solAssert(!dynamic_cast<ArrayType const&>(*toReferenceType).isByteArrayOrString(), "");
templ("extraParams", _fromType.sizeOnStack() ?
", " + suffixedVariableNameList("value_", 0, _fromType.sizeOnStack()) : "");
templ("copyToStorage", copyInlineArrayToStorageFunction(
dynamic_cast<InlineArrayType const&>(_fromType),
dynamic_cast<ArrayType const&>(_toType)
));
return templ.render();
}
if (!fromReferenceType)
{
solAssert(_fromType.category() == Type::Category::StringLiteral, "");
@ -2827,17 +2926,10 @@ string YulUtilFunctions::updateStorageValueFunction(
auto const& toArrayType = dynamic_cast<ArrayType const&>(*toReferenceType);
solAssert(toArrayType.isByteArrayOrString(), "");
return Whiskers(R"(
function <functionName>(slot<?dynamicOffset>, offset</dynamicOffset>) {
<?dynamicOffset>if offset { <panic>() }</dynamicOffset>
<copyToStorage>(slot)
}
)")
("functionName", functionName)
("dynamicOffset", !_offset.has_value())
("panic", panicFunction(PanicCode::Generic))
("copyToStorage", copyLiteralToStorageFunction(dynamic_cast<StringLiteralType const&>(_fromType).value()))
.render();
templ("extraParams", "");
templ("copyToStorage", copyLiteralToStorageFunction(dynamic_cast<StringLiteralType const&>(_fromType).value()));
return templ.render();
}
solAssert(*toReferenceType->copyForLocation(
@ -2851,16 +2943,7 @@ string YulUtilFunctions::updateStorageValueFunction(
solAssert(toReferenceType->category() == fromReferenceType->category(), "");
solAssert(_offset.value_or(0) == 0, "");
Whiskers templ(R"(
function <functionName>(slot, <?dynamicOffset>offset, </dynamicOffset><value>) {
<?dynamicOffset>if offset { <panic>() }</dynamicOffset>
<copyToStorage>(slot, <value>)
}
)");
templ("functionName", functionName);
templ("dynamicOffset", !_offset.has_value());
templ("panic", panicFunction(PanicCode::Generic));
templ("value", suffixedVariableNameList("value_", 0, _fromType.sizeOnStack()));
templ("extraParams", ", " + suffixedVariableNameList("value_", 0, _fromType.sizeOnStack()));
if (_fromType.category() == Type::Category::Array)
templ("copyToStorage", copyArrayToStorageFunction(
dynamic_cast<ArrayType const&>(_fromType),
@ -3332,6 +3415,11 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
solAssert(_to.category() == Type::Category::Array, "");
return arrayConversionFunction(fromArrayType, dynamic_cast<ArrayType const&>(_to));
}
else if (_from.category() == Type::Category::InlineArray)
{
solAssert(_to.category() == Type::Category::Array, "");
return inlineArrayConversionFunction(dynamic_cast<InlineArrayType const&>(_from), dynamic_cast<ArrayType const&>(_to));
}
if (_from.sizeOnStack() != 1 || _to.sizeOnStack() != 1)
return conversionFunctionSpecial(_from, _to);
@ -3781,6 +3869,64 @@ string YulUtilFunctions::arrayConversionFunction(ArrayType const& _from, ArrayTy
});
}
string YulUtilFunctions::inlineArrayConversionFunction(InlineArrayType const& _from, ArrayType const& _to)
{
if (_to.dataStoredIn(DataLocation::CallData))
solAssert(false);
if (!_to.isDynamicallySized())
solAssert(_to.length() == _from.components().size());
string functionName =
"convert_inline_array_" +
_from.identifier() +
"_to_" +
_to.identifier();
vector<map<string, string>> memberSetValues;
unsigned stackItemIndex = 0;
for (auto&& [index, type]: _from.components() | ranges::views::enumerate)
{
memberSetValues.emplace_back();
memberSetValues.back()["setMember"] = Whiskers(R"(
let <memberValues> := <conversionFunction>(<value>)
<writeToMemory>(add(mpos, <offset>), <memberValues>)
)")
("memberValues", suffixedVariableNameList("memberValue_", 0, _to.baseType()->stackItems().size()))
("offset", to_string(0x20 * index))
("value", suffixedVariableNameList("var_", stackItemIndex, stackItemIndex + type->sizeOnStack()))
("conversionFunction", conversionFunction(*type, *_to.baseType()))
("writeToMemory", writeToMemoryFunction(*_to.baseType()))
.render();
stackItemIndex += type->sizeOnStack();
}
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>(<values>) -> converted {
converted := <allocateArray>(<length>)
let mpos := converted
<?toDynamic>mpos := add(mpos, 0x20)</toDynamic>
<#member>
{
<setMember>
}
</member>
}
)");
templ("functionName", functionName);
templ("allocateArray", allocateMemoryArrayFunction(_to));
templ("length", toCompactHexWithPrefix(_from.components().size()));
templ("toDynamic", _to.isDynamicallySized());
templ("values", suffixedVariableNameList("var_", 0, _from.sizeOnStack()));
templ("member", move(memberSetValues));
return templ.render();
});
}
string YulUtilFunctions::cleanupFunction(Type const& _type)
{
if (auto userDefinedValueType = dynamic_cast<UserDefinedValueType const*>(&_type))

View File

@ -255,6 +255,9 @@ public:
/// signature (to_slot, from_ptr) ->
std::string copyArrayToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType);
std::string copyInlineArrayToStorageFunction(InlineArrayType const& _fromType, ArrayType const& _toType);
/// @returns the name of a function that will copy a byte array to storage
/// signature (to_slot, from_ptr) ->
std::string copyByteArrayToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType);
@ -538,6 +541,8 @@ private:
/// Special case of conversion functions - handles all array conversions.
std::string arrayConversionFunction(ArrayType const& _from, ArrayType const& _to);
std::string inlineArrayConversionFunction(InlineArrayType const& _from, ArrayType const& _to);
/// Special case of conversionFunction - handles everything that does not
/// use exactly one variable to hold the value.
std::string conversionFunctionSpecial(Type const& _from, Type const& _to);

View File

@ -47,6 +47,7 @@
#include <libsolutil/FunctionSelector.h>
#include <libsolutil/Visitor.h>
#include <range/v3/view/enumerate.hpp>
#include <range/v3/view/transform.hpp>
using namespace std;
@ -492,75 +493,49 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple)
{
setLocation(_tuple);
if (_tuple.isInlineArray())
bool willBeWrittenTo = _tuple.annotation().willBeWrittenTo;
if (willBeWrittenTo)
solAssert(!m_currentLValue);
if (!_tuple.isInlineArray() && _tuple.components().size() == 1)
{
auto const& arrayType = dynamic_cast<ArrayType const&>(*_tuple.annotation().type);
solAssert(!arrayType.isDynamicallySized(), "Cannot create dynamically sized inline array.");
define(_tuple) <<
m_utils.allocateMemoryArrayFunction(arrayType) <<
"(" <<
_tuple.components().size() <<
")\n";
string mpos = IRVariable(_tuple).part("mpos").name();
Type const& baseType = *arrayType.baseType();
for (size_t i = 0; i < _tuple.components().size(); i++)
{
Expression const& component = *_tuple.components()[i];
component.accept(*this);
setLocation(_tuple);
IRVariable converted = convert(component, baseType);
appendCode() <<
m_utils.writeToMemoryFunction(baseType) <<
"(" <<
("add(" + mpos + ", " + to_string(i * arrayType.memoryStride()) + ")") <<
", " <<
converted.commaSeparatedList() <<
")\n";
}
solAssert(_tuple.components().front());
_tuple.components().front()->accept(*this);
setLocation(_tuple);
if (willBeWrittenTo)
solAssert(!!m_currentLValue);
else
define(_tuple, *_tuple.components().front());
}
else
{
bool willBeWrittenTo = _tuple.annotation().willBeWrittenTo;
if (willBeWrittenTo)
solAssert(!m_currentLValue);
if (_tuple.components().size() == 1)
{
solAssert(_tuple.components().front());
_tuple.components().front()->accept(*this);
setLocation(_tuple);
if (willBeWrittenTo)
solAssert(!!m_currentLValue);
else
define(_tuple, *_tuple.components().front());
}
else
{
vector<optional<IRLValue>> lvalues;
for (size_t i = 0; i < _tuple.components().size(); ++i)
if (auto const& component = _tuple.components()[i])
{
component->accept(*this);
setLocation(_tuple);
if (willBeWrittenTo)
{
solAssert(!!m_currentLValue);
lvalues.emplace_back(std::move(m_currentLValue));
m_currentLValue.reset();
}
else
define(IRVariable(_tuple).tupleComponent(i), *component);
}
else if (willBeWrittenTo)
lvalues.emplace_back();
vector<optional<IRLValue>> lvalues;
if (_tuple.annotation().willBeWrittenTo)
m_currentLValue.emplace(IRLValue{
*_tuple.annotation().type,
IRLValue::Tuple{std::move(lvalues)}
});
for (auto&& [index, component]: _tuple.components() | ranges::views::enumerate)
{
if (component)
{
component->accept(*this);
setLocation(_tuple);
if (willBeWrittenTo)
{
solAssert(!!m_currentLValue);
lvalues.emplace_back(std::move(m_currentLValue));
m_currentLValue.reset();
}
else
define(IRVariable(_tuple).tupleComponent(index), *component);
}
else if (willBeWrittenTo)
lvalues.emplace_back();
}
if (_tuple.annotation().willBeWrittenTo)
m_currentLValue.emplace(IRLValue{
*_tuple.annotation().type,
IRLValue::Tuple{std::move(lvalues)}
});
}
return false;
}
@ -2284,6 +2259,29 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess)
}
}
}
else if (baseType.category() == Type::Category::InlineArray)
{
InlineArrayType const& inlineArrayType = dynamic_cast<InlineArrayType const&>(baseType);
ArrayType const* arrayType = dynamic_cast<ArrayType const*>(inlineArrayType.mobileType());
solAssert(arrayType);
IRVariable irArray = convert(IRVariable(_indexAccess.baseExpression()), *arrayType);
string const memAddress =
m_utils.memoryArrayIndexAccessFunction(*arrayType) +
"(" +
irArray.part("mpos").name() +
", " +
expressionAsType(*_indexAccess.indexExpression(), *TypeProvider::uint256()) +
")";
setLValue(_indexAccess, IRLValue{
*arrayType->baseType(),
IRLValue::Memory{memAddress}
});
}
else if (baseType.category() == Type::Category::FixedBytes)
{
auto const& fixedBytesType = dynamic_cast<FixedBytesType const&>(baseType);
@ -2532,7 +2530,7 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
}
// NOTE: When the expected size of returndata is static, we pass that in to the call opcode and it gets copied automatically.
// When it's dynamic, we get zero from estimatedReturnSize() instead and then we need an explicit returndatacopy().
// When it's dynamic, we get zero from estimatedReturnSize() instead and then we need an explicit returndatacopy().
Whiskers templ(R"(
<?checkExtcodesize>
if iszero(extcodesize(<address>)) { <revertNoCode>() }
@ -3024,6 +3022,12 @@ void IRGeneratorForStatements::writeToLValue(IRLValue const& _lvalue, IRVariable
m_utils.copyLiteralToMemoryFunction(literalType->value()) + "()" <<
")\n";
}
else if (dynamic_cast<InlineArrayType const*>(&_value.type()))
{
solAssert(dynamic_cast<ArrayType const*>(&_lvalue.type));
IRVariable value = convert(_value, _lvalue.type);
writeToLValue(_lvalue, value);
}
else
{
solAssert(_lvalue.type.sizeOnStack() == 1);

View File

@ -102,7 +102,7 @@ string IRVariable::name() const
IRVariable IRVariable::tupleComponent(size_t _i) const
{
solAssert(
m_type.category() == Type::Category::Tuple,
m_type.category() == Type::Category::Tuple || m_type.category() == Type::Category::InlineArray,
"Requested tuple component of non-tuple IR variable."
);
return part(IRNames::tupleComponent(_i));

View File

@ -78,6 +78,10 @@ SortPointer smtSort(frontend::Type const& _type)
solAssert(stringLitType, "");
array = make_shared<ArraySort>(SortProvider::uintSort, SortProvider::uintSort);
}
else if (auto const* inlineArrayType = dynamic_cast<InlineArrayType const*>(&_type))
{
array = make_shared<ArraySort>(SortProvider::uintSort, smtSortAbstractFunction(*inlineArrayType->componentsCommonMobileType()));
}
else
{
frontend::ArrayType const* arrayType = nullptr;
@ -93,8 +97,19 @@ SortPointer smtSort(frontend::Type const& _type)
}
string tupleName;
auto sliceArrayType = dynamic_cast<ArraySliceType const*>(&_type);
ArrayType const* arrayType = sliceArrayType ? &sliceArrayType->arrayType() : dynamic_cast<ArrayType const*>(&_type);
ArrayType const* arrayType = nullptr;
if (auto const* inlineArrayType = dynamic_cast<InlineArrayType const*>(&_type))
arrayType = TypeProvider::array(
DataLocation::Memory,
inlineArrayType->componentsCommonMobileType(),
inlineArrayType->components().size()
);
else if (auto const* sliceArrayType = dynamic_cast<ArraySliceType const*>(&_type))
arrayType = &sliceArrayType->arrayType();
else
arrayType = dynamic_cast<ArrayType const*>(&_type);
if (
(arrayType && arrayType->isByteArrayOrString()) ||
_type.category() == frontend::Type::Category::StringLiteral
@ -371,7 +386,8 @@ bool isArray(frontend::Type const& _type)
{
return _type.category() == frontend::Type::Category::Array ||
_type.category() == frontend::Type::Category::StringLiteral ||
_type.category() == frontend::Type::Category::ArraySlice;
_type.category() == frontend::Type::Category::ArraySlice ||
_type.category() == frontend::Type::Category::InlineArray;
}
bool isTuple(frontend::Type const& _type)
@ -475,6 +491,11 @@ smtutil::Expression zeroValue(frontend::Type const* _type)
if (!arrayType->isDynamicallySized())
length = bigint(arrayType->length());
}
else if (auto inlineArrayType = dynamic_cast<InlineArrayType const*>(_type))
{
zeroArray = smtutil::Expression::const_array(smtutil::Expression(sortSort), zeroValue(inlineArrayType->componentsCommonMobileType()));
length = bigint(inlineArrayType->components().size());
}
else if (auto mappingType = dynamic_cast<MappingType const*>(_type))
zeroArray = smtutil::Expression::const_array(smtutil::Expression(sortSort), zeroValue(mappingType->valueType()));
else

View File

@ -126,7 +126,9 @@ done < <(
grep -v -E 'license/license_hidden_unicode.sol' |
grep -v -E 'license/license_unicode.sol' |
# Skipping tests with 'something.address' as 'address' as the grammar fails on those
grep -v -E 'inlineAssembly/external_function_pointer_address.*.sol'
grep -v -E 'inlineAssembly/external_function_pointer_address.*.sol' |
# Skipping a test with an empty array as the grammar fails on it
grep -v -E 'array/inline_array_return_dynamic.sol'
)
YUL_FILES=()

View File

@ -30,6 +30,6 @@ contract C is B {
}
// ----
// test() -> 77
// gas irOptimized: 120170
// gas legacy: 155093
// gas legacyOptimized: 111550
// gas irOptimized: 115552
// gas legacy: 159144
// gas legacyOptimized: 110758

View File

@ -13,8 +13,8 @@ contract Test {
// ----
// set(uint24[3][]): 0x20, 0x06, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12 -> 0x06
// gas irOptimized: 186766
// gas legacy: 211149
// gas legacyOptimized: 206054
// gas legacy: 211155
// gas legacyOptimized: 206060
// data(uint256,uint256): 0x02, 0x02 -> 0x09
// data(uint256,uint256): 0x05, 0x01 -> 0x11
// data(uint256,uint256): 0x06, 0x00 -> FAILURE

View File

@ -21,6 +21,6 @@ contract c {
// ----
// store(uint256[9],uint8[3][]): 21, 22, 23, 24, 25, 26, 27, 28, 29, 0x140, 4, 1, 2, 3, 11, 12, 13, 21, 22, 23, 31, 32, 33 -> 32
// gas irOptimized: 648324
// gas legacy: 694515
// gas legacyOptimized: 694013
// gas legacy: 694519
// gas legacyOptimized: 694017
// retrieve() -> 9, 28, 9, 28, 4, 3, 32

View File

@ -47,5 +47,5 @@ contract C {
// ----
// f() -> true
// gas irOptimized: 146936
// gas legacy: 155961
// gas legacyOptimized: 153588
// gas legacy: 155962
// gas legacyOptimized: 153589

View File

@ -20,5 +20,5 @@ contract c {
// ----
// test() -> 0x01000000000000000000000000000000000000000000000000, 0x02000000000000000000000000000000000000000000000000, 0x03000000000000000000000000000000000000000000000000, 0x04000000000000000000000000000000000000000000000000, 0x05000000000000000000000000000000000000000000000000
// gas irOptimized: 208149
// gas legacy: 221856
// gas legacyOptimized: 220680
// gas legacy: 221858
// gas legacyOptimized: 220682

View File

@ -24,5 +24,5 @@ contract c {
// ----
// test() -> 3, 4
// gas irOptimized: 189690
// gas legacy: 195353
// gas legacyOptimized: 192441
// gas legacy: 195355
// gas legacyOptimized: 192443

View File

@ -21,4 +21,4 @@ contract c {
// test() -> 0xffffffff, 0x0000000000000000000000000a00090008000700060005000400030002000100, 0x0000000000000000000000000000000000000000000000000000000000000000
// gas irOptimized: 124910
// gas legacy: 187414
// gas legacyOptimized: 165659
// gas legacyOptimized: 165660

View File

@ -11,8 +11,8 @@ contract Test {
// ----
// set(uint24[]): 0x20, 18, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 -> 18
// gas irOptimized: 99616
// gas legacy: 103563
// gas legacyOptimized: 101397
// gas legacy: 103564
// gas legacyOptimized: 101398
// data(uint256): 7 -> 8
// data(uint256): 15 -> 16
// data(uint256): 18 -> FAILURE

View File

@ -16,7 +16,7 @@ contract C {
}
// ----
// constructor()
// gas irOptimized: 237351
// gas legacy: 221315
// gas legacyOptimized: 185247
// gas irOptimized: 192200
// gas legacy: 237108
// gas legacyOptimized: 190689
// f() -> 0

View File

@ -17,5 +17,5 @@ contract C {
// ----
// test() -> 7
// gas irOptimized: 122483
// gas legacy: 205196
// gas legacyOptimized: 204987
// gas legacy: 205197
// gas legacyOptimized: 204988

View File

@ -21,6 +21,6 @@ contract C {
// compileToEwasm: also
// ----
// one() -> 3
// gas legacy: 140260
// gas legacyOptimized: 140097
// gas legacy: 140261
// gas legacyOptimized: 140098
// two() -> FAILURE, hex"4e487b71", 0x51

View File

@ -0,0 +1,18 @@
contract C {
function f() public returns (uint8[3] memory) {
uint8[3][] memory x = new uint8[3][](1);
x[0] = [1, 2, 3];
return x[0];
}
function g() public returns (uint8[] memory) {
uint8[][] memory x = new uint8[][](1);
x[0] = [4, 5, 6];
return x[0];
}
}
// ====
// compileViaYul: also
// ----
// f() -> 1, 2, 3
// g() -> 0x20, 3, 4, 5, 6

View File

@ -0,0 +1,25 @@
contract C {
uint8[3][] x;
uint8[][] y;
constructor() {
x = new uint8[3][](1);
x[0] = [1, 2, 3];
y = new uint8[][](1);
y[0] = [4, 5, 6];
}
function f() public returns (uint8[3] memory) {
return x[0];
}
function g() public returns (uint8[] memory) {
return y[0];
}
}
// ====
// compileViaYul: also
// ----
// f() -> 1, 2, 3
// g() -> 0x20, 3, 4, 5, 6

View File

@ -0,0 +1,15 @@
pragma abicoder v2;
contract C {
string sString = "text2";
function f(string calldata cString) public returns (string[] memory) {
string memory mString = "text3";
return ["", "text1", sString, mString, cString];
}
}
// ====
// compileViaYul: true
// ----
// f(string): 0x20, 5, "text4" -> 0x20, 5, 0xa0, 0xc0, 0x0100, 0x0140, 0x0180, 0, 5, 52647573331277248417481526129911949709942841133814091230869433742303074189312, 5, 52647573331382560709150083316609867737626511566132986326269982853557385166848, 5, 52647573331487873000818640503307785765310181998451881421670531964811696144384, 5, 52647573331593185292487197690005703792993852430770776517071081076066007121920

View File

@ -0,0 +1,27 @@
pragma abicoder v2;
contract C {
function f() public returns (uint8[] memory) {
return [1, 2, 3, 4, 5];
}
function g() public returns (uint8[][] memory) {
return [[1, 2], [3], []];
}
function h() public returns (uint8[][3] memory) {
return [[1, 2], [3], []];
}
function i() public returns (uint8[2][] memory) {
return [[1, 2], [3,4], [5,6]];
}
}
// ====
// compileViaYul: true
// ----
// f() -> 0x20, 5, 1, 2, 3, 4, 5
// g() -> 0x20, 3, 0x60, 0xc0, 0x0100, 2, 1, 2, 1, 3, 0
// h() -> 0x20, 0x60, 0xc0, 0x0100, 2, 1, 2, 1, 3, 0
// i() -> 0x20, 3, 1, 2, 3, 4, 5, 6

View File

@ -0,0 +1,63 @@
pragma abicoder v2;
contract C {
struct CustomStruct{
uint16 index;
string text;
}
function statically_sized_uint_array() public returns (uint8[3] memory) {
uint8[3] memory a = [1, 2, 3];
return a;
}
function dynamically_sized_uint_array() public returns (uint8[] memory) {
uint8[] memory a = [1, 2, 3];
return a;
}
function statically_sized_int_array() public returns (int8[3] memory) {
int8[3] memory a = [-1, -2, -3];
return a;
}
function dynamically_sized_int_array() public returns (int8[] memory) {
int8[] memory a = [-1, -2, -3];
return a;
}
function statically_sized_string_array() public returns (string[2] memory) {
string[2] memory a = ["foo", "bar"];
return a;
}
function dynamically_sized_string_array() public returns (string[] memory) {
string[] memory a = ["foo", "bar"];
return a;
}
function statically_sized_struct_array() public returns (CustomStruct[2] memory) {
CustomStruct[2] memory s = [CustomStruct(1, "foo"), CustomStruct(2, "bar")];
return s;
}
function dynamically_sized_struct_array() public returns (CustomStruct[] memory) {
CustomStruct[] memory s = [CustomStruct(1, "foo"), CustomStruct(2, "bar")];
return s;
}
}
// ====
// EVMVersion: >=constantinople
// compileToEwasm: also
// compileViaYul: also
// ----
// statically_sized_uint_array() -> 1, 2, 3
// dynamically_sized_uint_array() -> 0x20, 3, 1, 2, 3
// statically_sized_int_array() -> -1, -2, -3
// dynamically_sized_int_array() -> 0x20, 3, -1, -2, -3
// statically_sized_string_array() -> 0x20, 0x40, 0x80, 3, "foo", 3, "bar"
// dynamically_sized_string_array() -> 0x20, 2, 0x40, 0x80, 3, 46332796673528066027243215619882264990369332300865266851730502456685210107904, 3, 44498830125527143464827115118378702402016761369235290884359940707316142178304
// statically_sized_struct_array() -> 0x20, 0x40, 0xc0, 1, 0x40, 3, 46332796673528066027243215619882264990369332300865266851730502456685210107904, 2, 0x40, 3, 44498830125527143464827115118378702402016761369235290884359940707316142178304
// dynamically_sized_struct_array() -> 0x20, 2, 0x40, 0xc0, 1, 0x40, 3, 46332796673528066027243215619882264990369332300865266851730502456685210107904, 2, 0x40, 3, 44498830125527143464827115118378702402016761369235290884359940707316142178304

View File

@ -0,0 +1,20 @@
contract C {
uint8[3] st = [1, 2, 3];
uint8[] public dt = [4, 5, 6];
function s() public returns (uint8[3] memory) {
return st;
}
function d() public returns (uint8[] memory) {
return dt;
}
}
// ====
// EVMVersion: >=constantinople
// compileToEwasm: also
// compileViaYul: also
// ----
// s() -> 1, 2, 3
// d() -> 0x20, 3, 4, 5, 6

View File

@ -0,0 +1,20 @@
pragma abicoder v2;
contract C {
uint8[3][2] st = [[1, 2, 3], [4, 5, 6]];
uint8[][] dt = [[1, 2], [3, 4, 5, 6]];
function s() public returns (uint8[3][2] memory) {
return st;
}
function d() public returns (uint8[][] memory) {
return dt;
}
}
// ====
// compileViaYul: true
// ----
// s() -> 1, 2, 3, 4, 5, 6
// d() -> 0x20, 2, 0x40, 0xa0, 2, 1, 2, 4, 3, 4, 5, 6

View File

@ -0,0 +1,11 @@
pragma abicoder v2;
contract C {
function f() external pure returns (string[2] memory rdatas) {
rdatas = [hex'74000001', hex'c4a40001'];
}
}
// ====
// compileViaYul: also
// ----
// f() -> 0x20, 0x40, 0x80, 4, "t\0\0\x01", 4, "\xc4\xa4\0\x01"

View File

@ -0,0 +1,12 @@
contract C {
bytes[2] public staticBytesArray = [hex"616263", hex"646566"];
bytes[] public dynamicBytesArray = [hex"616263", hex"646566"];
}
// ====
// compileViaYul: also
// ----
// staticBytesArray(uint256): 0 -> 0x20, 3, 0x6162630000000000000000000000000000000000000000000000000000000000
// staticBytesArray(uint256): 1 -> 0x20, 3, 0x6465660000000000000000000000000000000000000000000000000000000000
// dynamicBytesArray(uint256): 0 -> 0x20, 3, 0x6162630000000000000000000000000000000000000000000000000000000000
// dynamicBytesArray(uint256): 1 -> 0x20, 3, 0x6465660000000000000000000000000000000000000000000000000000000000

View File

@ -0,0 +1,16 @@
contract C {
string[4] public staticArray = ["foo", "", "a very long string that needs more than 32 bytes", "bar"];
string[] public dynamicArray = ["foo", "", "a very long string that needs more than 32 bytes", "bar"];
}
// ====
// compileViaYul: also
// ----
// staticArray(uint256): 0 -> 0x20, 3, "foo"
// staticArray(uint256): 1 -> 0x20, 0
// staticArray(uint256): 2 -> 0x20, 0x30, 0x612076657279206c6f6e6720737472696e672074686174206e65656473206d6f, 0x7265207468616E20333220627974657300000000000000000000000000000000
// staticArray(uint256): 3 -> 0x20, 3, "bar"
// dynamicArray(uint256): 0 -> 0x20, 3, "foo"
// dynamicArray(uint256): 1 -> 0x20, 0
// dynamicArray(uint256): 2 -> 0x20, 0x30, 0x612076657279206c6f6e6720737472696e672074686174206e65656473206d6f, 0x7265207468616E20333220627974657300000000000000000000000000000000
// dynamicArray(uint256): 3 -> 0x20, 3, "bar"

View File

@ -0,0 +1,39 @@
pragma abicoder v2;
contract C {
function test1() public returns (uint8[1][] memory) {
uint8[1][] memory a = [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12], [13], [14], [15], [16]];
return a;
}
function test2() public returns (int8[2][] memory) {
int8[2][] memory a = [[-1, -2], [-3, -4], [-5, -6]];
return a;
}
function test3() public returns (uint16[][2] memory) {
uint16[][2] memory a = [[1, 2, 3],[4, 5, 6, 7, 8, 9, 10, 11, 12, 13]];
return a;
}
function test4() public returns (uint8[][2][] memory) {
uint8[][2][] memory a = [[[1, 2, 3], [4, 5]], [[6], [7]], [[8, 9, 10, 11], [12, 13, 14, 15, 16]]];
return a;
}
function test5() public returns (string[2][] memory) {
string[2][] memory a = [["this", "is"], ["just", "a"], ["test", "array"]];
return a;
}
}
// ====
// compileViaYul: also
// ----
// test1() -> 0x20, 0x10, 0x01, 0x02, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0x10
// test2() -> 0x20, 0x03, -1, -2, -3, -4, -5, -6
// test3() -> 0x20, 0x40, 0xc0, 0x03, 1, 2, 3, 10, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13
// test4() -> 0x20, 0x03, 0x60, 0x0180, 0x0240, 0x40, 0xc0, 3, 1, 2, 3, 2, 4, 5, 0x40, 0x80, 1, 6, 1, 7, 0x40, 0xe0, 4, 8, 9, 10, 11, 5, 12, 13, 14, 15, 0x10
// test5() -> 0x20, 3, 0x60, 288, 0x01e0, 0x40, 0x80, 4, 52652770314156132753103522558211245866589054961607131434777992190478746910720, 2, 47696036513692484977101116032555085334762927796157333774492759403894270853120, 0x40, 0x80, 4, 48152679884589002438443377966044670264050608660991796074848385302132292583424, 1, 43874346312576839672212443538448152585028080127215369968075725190498334277632, 0x40, 0x80, 4, 52647538817385212172903286807934654968315727694643370704309751478220717293568, 5, 44076556304902723615564186688849689667362290371271333483270849954029028507648

View File

@ -0,0 +1,38 @@
pragma abicoder v2;
contract C {
uint8[1][] array1 = [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12], [13], [14], [15], [16]];
int8[2][] array2 = [[1, 2], [3, 4], [5, 6]];
uint16[][2] array3 = [[1, 2, 3],[4, 5, 6, 7, 8, 9, 10, 11, 12, 13]];
uint8[][2][] array4 = [[[1, 2, 3], [4, 5]], [[6], [7]], [[8, 9, 10, 11], [12, 13, 14, 15, 16]]];
string[2][] array5 = [["this", "is"], ["just", "a"], ["test", "array"]];
function test1() public returns (uint8[1][] memory) {
return array1;
}
function test2() public returns (int8[2][] memory) {
return array2;
}
function test3() public returns (uint16[][2] memory) {
return array3;
}
function test4() public returns (uint8[][2][] memory) {
return array4;
}
function test5() public returns (string[2][] memory) {
return array5;
}
}
// ====
// compileViaYul: also
// ----
// test1() -> 0x20, 0x10, 0x01, 0x02, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0x10
// test2() -> 0x20, 0x03, 0x01, 0x02, 3, 4, 5, 6
// test3() -> 0x20, 0x40, 0xc0, 0x03, 1, 2, 3, 10, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13
// test4() -> 0x20, 0x03, 0x60, 0x0180, 0x0240, 0x40, 0xc0, 3, 1, 2, 3, 2, 4, 5, 0x40, 0x80, 1, 6, 1, 7, 0x40, 0xe0, 4, 8, 9, 10, 11, 5, 12, 13, 14, 15, 0x10
// test5() -> 0x20, 3, 0x60, 288, 0x01e0, 0x40, 0x80, 4, 52652770314156132753103522558211245866589054961607131434777992190478746910720, 2, 47696036513692484977101116032555085334762927796157333774492759403894270853120, 0x40, 0x80, 4, 48152679884589002438443377966044670264050608660991796074848385302132292583424, 1, 43874346312576839672212443538448152585028080127215369968075725190498334277632, 0x40, 0x80, 4, 52647538817385212172903286807934654968315727694643370704309751478220717293568, 5, 44076556304902723615564186688849689667362290371271333483270849954029028507648

View File

@ -0,0 +1,29 @@
pragma abicoder v2;
contract C {
uint8[] array1 = [1,2,3,4,5,6,7,8,9,10,11,12,13, 14, 15, 16];
uint8[][] array2 = [[1, 2, 3],[4, 5],[6,7,8,9,10,11,12], [13,14,15,16]];
uint8[][][] array3 = [[[1, 2, 3],[4, 5],[6]], [[7, 8, 9, 10,11],[12, 13,14,15,16]]];
function test1() public returns (uint8[] memory) {
return array1;
}
function test2() public returns (uint8[][] memory) {
return array2;
}
function test3() public returns (uint8[][][] memory) {
return array3;
}
}
// ====
// EVMVersion: >=constantinople
// compileToEwasm: also
// compileViaYul: also
// ----
// test1() -> 0x20, 0x10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0x10
// test2() -> 0x20, 4, 0x80, 0x0100, 0x0160, 0x0260, 3, 1, 2, 3, 2, 4, 5, 7, 6, 7, 8, 9, 10, 11, 12, 4, 13, 14, 15, 0x10
// test3() -> 0x20, 0x02, 0x40, 0x01e0, 0x03, 0x60, 0xe0, 0x0140, 3, 1, 2, 3, 2, 4, 5, 1, 6, 2, 0x40, 0x0100, 5, 7, 8, 9, 10, 11, 5, 12, 13, 14, 15, 0x10

View File

@ -0,0 +1,25 @@
contract C {
uint16[1][16] array1 = [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12], [13], [14], [15], [16]];
uint16[4][2] array2 = [[1, 2, 3, 4], [5, 6, 7, 8]];
uint8[4][2][2] array3 = [[[1, 2, 3, 4], [5, 6, 7, 8]], [[9, 10, 11, 12], [13, 14, 15, 16]]];
function test1() public returns (uint16[1][16] memory) {
return array1;
}
function test2() public returns (uint16[4][2] memory) {
return array2;
}
function test3() public returns (uint8[4][2][2] memory) {
return array3;
}
}
// ====
// compileToEwasm: also
// compileViaYul: also
// ----
// test1() -> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
// test2() -> 1, 2, 3, 4, 5, 6, 7, 8
// test3() -> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16

View File

@ -13,5 +13,5 @@ contract C {
// ----
// f(uint120[]): 0x20, 3, 1, 2, 3 -> 1
// gas irOptimized: 112832
// gas legacy: 113686
// gas legacyOptimized: 113499
// gas legacy: 113687
// gas legacyOptimized: 113500

View File

@ -21,5 +21,5 @@ contract c {
// ----
// test() -> 2, 3, 4, 5
// gas irOptimized: 135204
// gas legacy: 147484
// gas legacyOptimized: 146456
// gas legacy: 147486
// gas legacyOptimized: 146458

View File

@ -20,6 +20,6 @@ contract C {
// ----
// g() -> 2, 6
// gas irOptimized: 178549
// gas irOptimized: 178177
// gas legacy: 180893
// gas legacyOptimized: 179394
// gas legacyOptimized: 179388

View File

@ -6,3 +6,4 @@ contract A{
// ====
// SMTEngine: all
// ----
// TypeError 4247: (50-57): Expression has to be an lvalue.

View File

@ -4,4 +4,6 @@ contract C {
}
}
// ----
// TypeError 6378: (60-62): Unable to deduce common type for array elements.
// TypeError 8015: (60-62): Invalid type for argument in the bytes.concat function call. bytes or fixed bytes type is required, but inline_array() provided.
// TypeError 8015: (64-66): Invalid type for argument in the bytes.concat function call. bytes or fixed bytes type is required, but inline_array() provided.
// TypeError 8015: (68-70): Invalid type for argument in the bytes.concat function call. bytes or fixed bytes type is required, but inline_array() provided.

View File

@ -50,7 +50,7 @@ contract C {
// TypeError 8015: (796-797): Invalid type for argument in the bytes.concat function call. bytes or fixed bytes type is required, but function () provided.
// TypeError 8015: (811-813): Invalid type for argument in the bytes.concat function call. bytes or fixed bytes type is required, but tuple() provided.
// TypeError 8015: (827-833): Invalid type for argument in the bytes.concat function call. bytes or fixed bytes type is required, but tuple(int_const 0,int_const 0) provided.
// TypeError 8015: (847-850): Invalid type for argument in the bytes.concat function call. bytes or fixed bytes type is required, but uint8[1] memory provided.
// TypeError 8015: (847-850): Invalid type for argument in the bytes.concat function call. bytes or fixed bytes type is required, but inline_array(int_const 0) provided.
// TypeError 8015: (864-870): Invalid type for argument in the bytes.concat function call. bytes or fixed bytes type is required, but uint8[1] memory slice provided.
// TypeError 8015: (884-890): Invalid type for argument in the bytes.concat function call. bytes or fixed bytes type is required, but uint8 provided.
// TypeError 8015: (904-911): Invalid type for argument in the bytes.concat function call. bytes or fixed bytes type is required, but contract C provided.

View File

@ -11,7 +11,6 @@ contract C {
function () external returns(uint)[1] memory externalDefaultArray;
function () internal returns(uint)[1] memory internalDefaultArray;
// This would work if we were assigning to storage rather than memory
externalDefaultArray = [this.externalView];
internalDefaultArray = [internalView];
@ -22,7 +21,6 @@ contract C {
function () external returns(uint)[1] memory externalDefaultArray;
function () internal returns(uint)[1] memory internalDefaultArray;
// This would work if we were assigning to storage rather than memory
externalDefaultArray = [this.externalPure];
internalDefaultArray = [internalPure];
@ -33,7 +31,6 @@ contract C {
function () external returns(uint)[1] memory externalViewArray;
function () internal returns(uint)[1] memory internalViewArray;
// This would work if we were assigning to storage rather than memory
externalViewArray = [this.externalPure];
internalViewArray = [internalPure];
@ -41,9 +38,7 @@ contract C {
}
}
// ----
// TypeError 7407: (760-779): Type function () view external returns (uint256)[1] memory is not implicitly convertible to expected type function () external returns (uint256)[1] memory.
// TypeError 7407: (812-826): Type function () view returns (uint256)[1] memory is not implicitly convertible to expected type function () returns (uint256)[1] memory.
// TypeError 7407: (1230-1249): Type function () pure external returns (uint256)[1] memory is not implicitly convertible to expected type function () external returns (uint256)[1] memory.
// TypeError 7407: (1282-1296): Type function () pure returns (uint256)[1] memory is not implicitly convertible to expected type function () returns (uint256)[1] memory.
// TypeError 7407: (1688-1707): Type function () pure external returns (uint256)[1] memory is not implicitly convertible to expected type function () external returns (uint256)[1] memory.
// TypeError 7407: (1737-1751): Type function () pure returns (uint256)[1] memory is not implicitly convertible to expected type function () returns (uint256)[1] memory.
// Warning 2018: (17-81): Function state mutability can be restricted to pure
// Warning 2018: (86-152): Function state mutability can be restricted to pure
// Warning 2018: (229-293): Function state mutability can be restricted to pure
// Warning 2018: (298-364): Function state mutability can be restricted to pure

View File

@ -1,4 +1,4 @@
pragma abicoder v2;
pragma abicoder v2;
contract C {
function f() public pure returns(string[5] calldata) {
@ -6,4 +6,4 @@ contract C {
}
}
// ----
// TypeError 6359: (122-147): Return argument type string[5] memory is not implicitly convertible to expected type (type of first return variable) string[5] calldata.
// TypeError 6359: (108-133): Return argument type inline_array(literal_string "h", literal_string "e", literal_string "l", literal_string "l", literal_string "o") is not implicitly convertible to expected type (type of first return variable) string[5] calldata. Invalid conversion from literal_string "h" to string calldata.

View File

@ -0,0 +1,7 @@
contract C {
function f() public {
uint i = [0, 1, 2][];
}
}
// ----
// TypeError 5093: (56-67): Index expression cannot be omitted.

View File

@ -0,0 +1,8 @@
contract C {
function f() external pure {
bytes[2] memory a1 = ['foo', 'bar'];
bytes[2] memory a2 = [hex'666f6f', hex'626172'];
require(keccak256(a1[0]) == keccak256(a2[0]));
require(keccak256(a1[1]) == keccak256(a2[1]));
}
}

View File

@ -0,0 +1,13 @@
contract C {
function f(uint256[] memory x) private {}
function f(uint256[3] memory x) private {}
function g() private {
f([1]);
f([1,2,3]);
f([1,2,3,4]);
}
}
// ----
// TypeError 4487: (158-159): No unique declaration found after argument-dependent lookup.

View File

@ -0,0 +1,9 @@
contract C {
function f() external pure {
string[2] memory a1 = [string(bytes(hex'74000001')), string(bytes(hex'c0a80101'))];
bytes[2] memory a2 = [bytes(hex'74000001'), bytes(hex'c0a80101')];
}
}
// ----
// Warning 2072: (54-73): Unused local variable.
// Warning 2072: (146-164): Unused local variable.

View File

@ -0,0 +1,11 @@
contract C {
function f() external pure {
string[2] memory a1 = [hex'74000001', hex'c0a80101'];
string[2] memory a2 = [bytes(hex'74000001'), bytes(hex'c0a80101')];
bytes[2] memory a3 = [hex'74000001', hex'c0a80101'];
bytes[2] memory a4 = ['foo', 'bar'];
}
}
// ----
// TypeError 9574: (54-106): Type inline_array(literal_string hex"74000001", literal_string hex"c0a80101") is not implicitly convertible to expected type string[2] memory. Invalid conversion from literal_string hex"c0a80101" to string memory. Contains invalid UTF-8 sequence at position 4.
// TypeError 9574: (116-182): Type inline_array(bytes memory, bytes memory) is not implicitly convertible to expected type string[2] memory. Invalid conversion from bytes memory to string memory.

View File

@ -0,0 +1,7 @@
contract C {
function f() public {
uint[3] memory x = [1, 2];
}
}
// ----
// TypeError 9574: (47-72): Type inline_array(int_const 1, int_const 2) is not implicitly convertible to expected type uint256[3] memory. Number of components in array literal (2) does not match array size (3).

View File

@ -4,4 +4,4 @@ contract C {
}
}
// ----
// TypeError 6378: (66-83): Unable to deduce common type for array elements.
// TypeError 9574: (47-83): Type inline_array(int_const 45, literal_string "foo", bool) is not implicitly convertible to expected type uint256[3] memory. Invalid conversion from literal_string "foo" to uint256.

View File

@ -24,4 +24,4 @@ contract C {
// TypeError 7788: (382-408): Expected 1 instead of 0 components for the tuple parameter.
// TypeError 6219: (489-511): Expected two arguments: a function pointer followed by a tuple.
// TypeError 7515: (597-628): Expected a tuple with 2 components instead of a single non-tuple parameter.
// TypeError 5407: (621-627): Cannot implicitly convert component at position 0 from "uint8[2] memory" to "int256".
// TypeError 5407: (621-627): Cannot implicitly convert component at position 0 from "inline_array(int_const 1, int_const 2)" to "int256": Array literal can not be converted to byte array or string.

View File

@ -4,4 +4,6 @@ contract C {
}
}
// ----
// TypeError 6378: (61-63): Unable to deduce common type for array elements.
// TypeError 9977: (61-63): Invalid type for argument in the string.concat function call. string type is required, but t_inline_array$__$ provided.
// TypeError 9977: (65-67): Invalid type for argument in the string.concat function call. string type is required, but t_inline_array$__$ provided.
// TypeError 9977: (69-71): Invalid type for argument in the string.concat function call. string type is required, but t_inline_array$__$ provided.

View File

@ -50,7 +50,7 @@ contract C {
// TypeError 9977: (797-798): Invalid type for argument in the string.concat function call. string type is required, but t_function_internal_nonpayable$__$returns$__$ provided.
// TypeError 9977: (812-814): Invalid type for argument in the string.concat function call. string type is required, but t_tuple$__$ provided.
// TypeError 9977: (828-834): Invalid type for argument in the string.concat function call. string type is required, but t_tuple$_t_rational_0_by_1_$_t_rational_0_by_1_$ provided.
// TypeError 9977: (848-851): Invalid type for argument in the string.concat function call. string type is required, but t_array$_t_uint8_$1_memory_ptr provided.
// TypeError 9977: (848-851): Invalid type for argument in the string.concat function call. string type is required, but t_inline_array$_t_rational_0_by_1_$ provided.
// TypeError 9977: (865-871): Invalid type for argument in the string.concat function call. string type is required, but t_array$_t_uint8_$1_memory_ptr_slice provided.
// TypeError 9977: (885-891): Invalid type for argument in the string.concat function call. string type is required, but t_uint8 provided.
// TypeError 9977: (905-912): Invalid type for argument in the string.concat function call. string type is required, but t_contract$_C_$61 provided.