Skip to content

Commit 1210c3e

Browse files
authored
Merge pull request #12628 from nishant-sachdeva/adding_functiontype_string_and_allow_string_concat_operations
Added support for FunctionType::Kind::StringConcat
2 parents f1be7e1 + 276851f commit 1210c3e

24 files changed

+391
-38
lines changed

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Bugfixes:
1717
* Code Generator: Fix ICE when accessing the members of external functions occupying more than two stack slots.
1818
* Code Generator: Fix ICE when doing an explicit conversion from ``string calldata`` to ``bytes``.
1919
* Control Flow Graph: Perform proper virtual lookup for modifiers for uninitialized variable and unreachable code analysis.
20+
* General: ``string.concat`` now properly takes strings as arguments and returns ``string memory``. It was accidentally introduced as a copy of ``bytes.concat`` before.
2021
* Immutables: Fix wrong error when the constructor of a base contract uses ``return`` and the derived contract contains immutable variables.
2122
* IR Generator: Add missing cleanup during the conversion of fixed bytes types to smaller fixed bytes types.
2223
* IR Generator: Add missing cleanup for indexed event arguments of value type.

docs/cheatsheet.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ Global Variables
8686
to ``abi.encodeWithSelector(bytes4(keccak256(bytes(signature)), ...)``
8787
- ``bytes.concat(...) returns (bytes memory)``: :ref:`Concatenates variable number of
8888
arguments to one byte array<bytes-concat>`
89+
- ``string.concat(...) returns (string memory)``: :ref:`Concatenates variable number of
90+
arguments to one string array<string-concat>`
8991
- ``block.basefee`` (``uint``): current block's base fee (`EIP-3198 <https://eips.ethereum.org/EIPS/eip-3198>`_ and `EIP-1559 <https://eips.ethereum.org/EIPS/eip-1559>`_)
9092
- ``block.chainid`` (``uint``): current chain id
9193
- ``block.coinbase`` (``address payable``): current block miner's address

docs/types/reference-types.rst

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ length or index access.
150150
Solidity does not have string manipulation functions, but there are
151151
third-party string libraries. You can also compare two strings by their keccak256-hash using
152152
``keccak256(abi.encodePacked(s1)) == keccak256(abi.encodePacked(s2))`` and
153-
concatenate two strings using ``bytes.concat(bytes(s1), bytes(s2))``.
153+
concatenate two strings using ``string.concat(s1, s2)``.
154154

155155
You should use ``bytes`` over ``bytes1[]`` because it is cheaper,
156156
since using ``bytes1[]`` in ``memory`` adds 31 padding bytes between the elements. Note that in ``storage``, the
@@ -165,31 +165,40 @@ always use one of the value types ``bytes1`` to ``bytes32`` because they are muc
165165
that you are accessing the low-level bytes of the UTF-8 representation,
166166
and not the individual characters.
167167

168-
.. index:: ! bytes-concat
168+
.. index:: ! bytes-concat, ! string-concat
169169

170170
.. _bytes-concat:
171+
.. _string-concat:
171172

172-
``bytes.concat`` function
173-
^^^^^^^^^^^^^^^^^^^^^^^^^
173+
The functions ``bytes.concat`` and ``string.concat``
174+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
174175

175-
You can concatenate a variable number of ``bytes`` or ``bytes1 ... bytes32`` using ``bytes.concat``.
176+
You can concatenate an arbitrary number of ``string`` values using ``string.concat``.
177+
The function returns a single ``string memory`` array that contains the contents of the arguments without padding.
178+
If you want to use parameters of other types that are not implicitly convertible to ``string``, you need to convert them to ``string`` first.
179+
180+
Analogously, the ``bytes.concat`` function can concatenate an arbitrary number of ``bytes`` or ``bytes1 ... bytes32`` values.
176181
The function returns a single ``bytes memory`` array that contains the contents of the arguments without padding.
177-
If you want to use string parameters or other types, you need to convert them to ``bytes`` or ``bytes1``/.../``bytes32`` first.
182+
If you want to use string parameters or other types that are not implicitly convertible to ``bytes``, you need to convert them to ``bytes`` or ``bytes1``/.../``bytes32`` first.
183+
178184

179185
.. code-block:: solidity
180186
181187
// SPDX-License-Identifier: GPL-3.0
182-
pragma solidity ^0.8.4;
188+
pragma solidity ^0.8.12;
183189
184190
contract C {
185-
bytes s = "Storage";
186-
function f(bytes calldata c, string memory m, bytes16 b) public view {
187-
bytes memory a = bytes.concat(s, c, c[:2], "Literal", bytes(m), b);
188-
assert((s.length + c.length + 2 + 7 + bytes(m).length + 16) == a.length);
191+
string s = "Storage";
192+
function f(bytes calldata bc, string memory sm, bytes16 b) public view {
193+
string memory concat_string = string.concat(s, string(bc), "Literal", sm);
194+
assert((bytes(s).length + bc.length + 7 + bytes(sm).length) == bytes(concat_string).length);
195+
196+
bytes memory concat_bytes = bytes.concat(bytes(s), bc, bc[:2], "Literal", bytes(sm), b);
197+
assert((bytes(s).length + bc.length + 2 + 7 + bytes(sm).length + b.length) == concat_bytes.length);
189198
}
190199
}
191200
192-
If you call ``bytes.concat`` without arguments it will return an empty ``bytes`` array.
201+
If you call ``string.concat`` or ``bytes.concat`` without arguments they return an empty array.
193202

194203
.. index:: ! array;allocating, new
195204

docs/units-and-global-variables.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,14 @@ Members of bytes
154154

155155
- ``bytes.concat(...) returns (bytes memory)``: :ref:`Concatenates variable number of bytes and bytes1, ..., bytes32 arguments to one byte array<bytes-concat>`
156156

157+
.. index:: string members
158+
159+
Members of string
160+
-----------------
161+
162+
- ``string.concat(...) returns (string memory)``: :ref:`Concatenates variable number of string arguments to one string array<string-concat>`
163+
164+
157165
.. index:: assert, revert, require
158166

159167
Error Handling

libsolidity/analysis/TypeChecker.cpp

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2207,14 +2207,42 @@ void TypeChecker::typeCheckABIEncodeCallFunction(FunctionCall const& _functionCa
22072207
}
22082208
}
22092209

2210+
2211+
void TypeChecker::typeCheckStringConcatFunction(
2212+
FunctionCall const& _functionCall,
2213+
FunctionType const* _functionType
2214+
)
2215+
{
2216+
solAssert(_functionType);
2217+
solAssert(_functionType->kind() == FunctionType::Kind::StringConcat);
2218+
solAssert(_functionCall.names().empty());
2219+
2220+
typeCheckFunctionGeneralChecks(_functionCall, _functionType);
2221+
2222+
for (shared_ptr<Expression const> const& argument: _functionCall.arguments())
2223+
{
2224+
Type const* argumentType = type(*argument);
2225+
bool notConvertibleToString = !argumentType->isImplicitlyConvertibleTo(*TypeProvider::stringMemory());
2226+
2227+
if (notConvertibleToString)
2228+
m_errorReporter.typeError(
2229+
9977_error,
2230+
argument->location(),
2231+
"Invalid type for argument in the string.concat function call. "
2232+
"string type is required, but " +
2233+
argumentType->identifier() + " provided."
2234+
);
2235+
}
2236+
}
2237+
22102238
void TypeChecker::typeCheckBytesConcatFunction(
22112239
FunctionCall const& _functionCall,
22122240
FunctionType const* _functionType
22132241
)
22142242
{
2215-
solAssert(_functionType, "");
2216-
solAssert(_functionType->kind() == FunctionType::Kind::BytesConcat, "");
2217-
solAssert(_functionCall.names().empty(), "");
2243+
solAssert(_functionType);
2244+
solAssert(_functionType->kind() == FunctionType::Kind::BytesConcat);
2245+
solAssert(_functionCall.names().empty());
22182246

22192247
typeCheckFunctionGeneralChecks(_functionCall, _functionType);
22202248

@@ -2651,6 +2679,12 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
26512679
returnTypes = functionType->returnParameterTypes();
26522680
break;
26532681
}
2682+
case FunctionType::Kind::StringConcat:
2683+
{
2684+
typeCheckStringConcatFunction(_functionCall, functionType);
2685+
returnTypes = functionType->returnParameterTypes();
2686+
break;
2687+
}
26542688
case FunctionType::Kind::Wrap:
26552689
case FunctionType::Kind::Unwrap:
26562690
{

libsolidity/analysis/TypeChecker.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ class TypeChecker: private ASTConstVisitor
113113
/// Performs checks specific to the ABI encode functions of type ABIEncodeCall
114114
void typeCheckABIEncodeCallFunction(FunctionCall const& _functionCall);
115115

116+
/// Performs general checks and checks specific to string concat function call
117+
void typeCheckStringConcatFunction(
118+
FunctionCall const& _functionCall,
119+
FunctionType const* _functionType
120+
);
121+
116122
/// Performs general checks and checks specific to bytes concat function call
117123
void typeCheckBytesConcatFunction(
118124
FunctionCall const& _functionCall,

libsolidity/ast/Types.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2928,6 +2928,7 @@ string FunctionType::richIdentifier() const
29282928
case Kind::ArrayPush: id += "arraypush"; break;
29292929
case Kind::ArrayPop: id += "arraypop"; break;
29302930
case Kind::BytesConcat: id += "bytesconcat"; break;
2931+
case Kind::StringConcat: id += "stringconcat"; break;
29312932
case Kind::ObjectCreation: id += "objectcreation"; break;
29322933
case Kind::Assert: id += "assert"; break;
29332934
case Kind::Require: id += "require"; break;
@@ -3817,15 +3818,14 @@ MemberList::MemberMap TypeType::nativeMembers(ASTNode const* _currentScope) cons
38173818
)
38183819
members.emplace_back("concat", TypeProvider::function(
38193820
TypePointers{},
3820-
TypePointers{TypeProvider::bytesMemory()},
3821+
TypePointers{arrayType->isString() ? TypeProvider::stringMemory() : TypeProvider::bytesMemory()},
38213822
strings{},
3822-
strings{string()},
3823-
FunctionType::Kind::BytesConcat,
3823+
strings{string{}},
3824+
arrayType->isString() ? FunctionType::Kind::StringConcat : FunctionType::Kind::BytesConcat,
38243825
StateMutability::Pure,
38253826
nullptr,
38263827
FunctionType::Options::withArbitraryParameters()
38273828
));
3828-
38293829
return members;
38303830
}
38313831

libsolidity/ast/Types.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1228,6 +1228,7 @@ class FunctionType: public Type
12281228
ArrayPush, ///< .push() to a dynamically sized array in storage
12291229
ArrayPop, ///< .pop() from a dynamically sized array in storage
12301230
BytesConcat, ///< .concat() on bytes (type type)
1231+
StringConcat, ///< .concat() on string (type type)
12311232
ObjectCreation, ///< array creation using new
12321233
Assert, ///< assert()
12331234
Require, ///< require()

libsolidity/codegen/ExpressionCompiler.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,6 +1101,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
11011101
ArrayUtils(m_context).popStorageArrayElement(*arrayType);
11021102
break;
11031103
}
1104+
case FunctionType::Kind::StringConcat:
11041105
case FunctionType::Kind::BytesConcat:
11051106
{
11061107
_functionCall.expression().accept(*this);
@@ -1121,8 +1122,16 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
11211122
else
11221123
{
11231124
solAssert(!dynamic_cast<RationalNumberType const*>(argument->annotation().type), "");
1124-
solAssert(argument->annotation().type->isImplicitlyConvertibleTo(*TypeProvider::bytesMemory()), "");
1125-
targetTypes.emplace_back(TypeProvider::bytesMemory());
1125+
if (function.kind() == FunctionType::Kind::StringConcat)
1126+
{
1127+
solAssert(argument->annotation().type->isImplicitlyConvertibleTo(*TypeProvider::stringMemory()), "");
1128+
targetTypes.emplace_back(TypeProvider::stringMemory());
1129+
}
1130+
else if (function.kind() == FunctionType::Kind::BytesConcat)
1131+
{
1132+
solAssert(argument->annotation().type->isImplicitlyConvertibleTo(*TypeProvider::bytesMemory()), "");
1133+
targetTypes.emplace_back(TypeProvider::bytesMemory());
1134+
}
11261135
}
11271136
}
11281137
utils().fetchFreeMemoryPointer();

libsolidity/codegen/YulUtilFunctions.cpp

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2475,18 +2475,26 @@ string YulUtilFunctions::copyArrayFromStorageToMemoryFunction(ArrayType const& _
24752475
});
24762476
}
24772477

2478-
string YulUtilFunctions::bytesConcatFunction(vector<Type const*> const& _argumentTypes)
2478+
string YulUtilFunctions::bytesOrStringConcatFunction(
2479+
vector<Type const*> const& _argumentTypes,
2480+
FunctionType::Kind _functionTypeKind
2481+
)
24792482
{
2480-
string functionName = "bytes_concat";
2483+
solAssert(_functionTypeKind == FunctionType::Kind::BytesConcat || _functionTypeKind == FunctionType::Kind::StringConcat);
2484+
std::string functionName = (_functionTypeKind == FunctionType::Kind::StringConcat) ? "string_concat" : "bytes_concat";
24812485
size_t totalParams = 0;
24822486
vector<Type const*> targetTypes;
2487+
24832488
for (Type const* argumentType: _argumentTypes)
24842489
{
2485-
solAssert(
2486-
argumentType->isImplicitlyConvertibleTo(*TypeProvider::bytesMemory()) ||
2487-
argumentType->isImplicitlyConvertibleTo(*TypeProvider::fixedBytes(32)),
2488-
""
2489-
);
2490+
if (_functionTypeKind == FunctionType::Kind::StringConcat)
2491+
solAssert(argumentType->isImplicitlyConvertibleTo(*TypeProvider::stringMemory()));
2492+
else if (_functionTypeKind == FunctionType::Kind::BytesConcat)
2493+
solAssert(
2494+
argumentType->isImplicitlyConvertibleTo(*TypeProvider::bytesMemory()) ||
2495+
argumentType->isImplicitlyConvertibleTo(*TypeProvider::fixedBytes(32))
2496+
);
2497+
24902498
if (argumentType->category() == Type::Category::FixedBytes)
24912499
targetTypes.emplace_back(argumentType);
24922500
else if (
@@ -2496,15 +2504,16 @@ string YulUtilFunctions::bytesConcatFunction(vector<Type const*> const& _argumen
24962504
targetTypes.emplace_back(TypeProvider::fixedBytes(static_cast<unsigned>(literalType->value().size())));
24972505
else
24982506
{
2499-
solAssert(!dynamic_cast<RationalNumberType const*>(argumentType), "");
2500-
solAssert(argumentType->isImplicitlyConvertibleTo(*TypeProvider::bytesMemory()), "");
2501-
targetTypes.emplace_back(TypeProvider::bytesMemory());
2507+
solAssert(!dynamic_cast<RationalNumberType const*>(argumentType));
2508+
targetTypes.emplace_back(
2509+
_functionTypeKind == FunctionType::Kind::StringConcat ?
2510+
TypeProvider::stringMemory() :
2511+
TypeProvider::bytesMemory()
2512+
);
25022513
}
2503-
25042514
totalParams += argumentType->sizeOnStack();
25052515
functionName += "_" + argumentType->identifier();
25062516
}
2507-
25082517
return m_functionCollector.createFunction(functionName, [&]() {
25092518
Whiskers templ(R"(
25102519
function <functionName>(<parameters>) -> outPtr {

0 commit comments

Comments
 (0)