Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions libsolidity/analysis/DeclarationTypeChecker.cpp
Copy link
Copy Markdown
Collaborator

@cameel cameel Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I doubt that implementation for this will be this short. There are many corner cases around function types, so if you allow them to masquerade as UDVTs, we have to basically replicate all their analysis checks in all of the UDVT cases. Otherwise some safeguards could be bypassed.

  • Internal functions are not allowed in the ABI. UDVTs are.
  • Same for immutables.
  • Storing internal function pointers is complicated. It differs between the IR and evmasm codegen and in case of evmasm requires combining two versions (creation and deployed) into a single slot. Not sure if this PR handles that properly or not, but I at least don't see any coverage for it.
  • There are some limitations and design question around constants of function types that we're currently working to resolve (Function references are not considered constant #16339). It's still not certain that we'll allow constant internal function pointers. This PR likely enables them, because UDVTs can be constant.
    • Even if we decide that we want them (which is likely), the fact that they weren't allowed so far means we have zero test coverage for internal function constants. The PR will have to add it.
  • Codegen relies on detecting function pointers to build the internal dispatch and assign them IDs. It may work as is since you have to assign the function to the UDVT somewhere anyway, but needs some coverage.
  • In library ABI user-defined types are handled specially (Fix ICE for user-defined value type in external library function #16509). This may or may not have to be addressed here.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @cameel! Thanks for your thorough analysis of the impact. I’m curious if we could apply the first two checks to canonical types? It would be great if we could achieve constant arrays of function pointers for O(1) dispatch tables.

Original file line number Diff line number Diff line change
Expand Up @@ -143,17 +143,35 @@ void DeclarationTypeChecker::endVisit(UserDefinedValueTypeDefinition const& _use
{
TypeName const* typeName = _userDefined.underlyingType();
solAssert(typeName, "");
if (!dynamic_cast<ElementaryTypeName const*>(typeName))

if (
!dynamic_cast<ElementaryTypeName const*>(typeName) &&
!dynamic_cast<FunctionTypeName const*>(typeName)
)
m_errorReporter.fatalTypeError(
8657_error,
typeName->location(),
"The underlying type for a user defined value type has to be an elementary value type."
"The underlying type for a user defined value type has to be an elementary value type or an internal function type."
);

Type const* type = typeName->annotation().type;
solAssert(type, "");
solAssert(!dynamic_cast<UserDefinedValueType const*>(type), "");
if (!type->isValueType())

if (auto const* funType = dynamic_cast<FunctionType const*>(type))
{
if (funType->kind() != FunctionType::Kind::Internal)
m_errorReporter.fatalTypeError(
8657_error,
typeName->location(),
"The underlying type of the user defined value type \"" +
_userDefined.name() +
"\" has to be an internal function type, but \"" +
type->toString(true) +
"\" is an external function type."
);
}
else if (!type->isValueType())
m_errorReporter.typeError(
8129_error,
_userDefined.location(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
library Hooks {
type Callback is function(uint256) returns (uint256);
}

contract C {
Hooks.Callback cb;

function double(uint256 x) internal pure returns (uint256) {
return x * 2;
}

function triple(uint256 x) internal pure returns (uint256) {
return x * 3;
}

function setCb(Hooks.Callback _cb) internal {
cb = _cb;
}

function setup() public {
setCb(Hooks.Callback.wrap(double));
}

function callCb(uint256 x) public returns (uint256) {
function(uint256) returns (uint256) fn = Hooks.Callback.unwrap(cb);
return fn(x);
}

function wrapAndCall(uint256 x) public returns (uint256) {
Hooks.Callback wrapped = Hooks.Callback.wrap(triple);
function(uint256) returns (uint256) fn = Hooks.Callback.unwrap(wrapped);
return fn(x);
}
}
// ----
// setup() ->
// callCb(uint256): 5 -> 10
// wrapAndCall(uint256): 7 -> 21
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ type MyUfixed is ufixed;
type MyFixedBytes32 is bytes32;
type MyFixedBytes1 is bytes1;
type MyBool is bool;
type MyCallback is function(uint256) returns (bool);
type MyPureFunction is function(uint256, uint256) pure returns (uint256);
/// test to see if having NatSpec causes issues
type redundantNatSpec is bytes2;
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
function f(MyIntB x) pure {}
type MyIntB is MyIntB;
// ----
// TypeError 8657: (44-50): The underlying type for a user defined value type has to be an elementary value type.
// TypeError 8657: (44-50): The underlying type for a user defined value type has to be an elementary value type or an internal function type.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
type Callback is function(uint256) returns (bool);

library Hooks {
type BeforeSwap is function(uint256) returns (bool);
}

contract C {
Hooks.BeforeSwap cb;

function set(Hooks.BeforeSwap _cb) internal {
cb = _cb;
}

function wrapUnwrap() internal pure {
function(uint256) returns (bool) raw;
Callback wrapped = Callback.wrap(raw);
function(uint256) returns (bool) unwrapped = Callback.unwrap(wrapped);
unwrapped;
}

function wrapUnwrapLib() internal pure {
function(uint256) returns (bool) raw;
Hooks.BeforeSwap wrapped = Hooks.BeforeSwap.wrap(raw);
function(uint256) returns (bool) unwrapped = Hooks.BeforeSwap.unwrap(wrapped);
unwrapped;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
contract C {}
type MyContract is C;
// ----
// TypeError 8657: (33-34): The underlying type for a user defined value type has to be an elementary value type.
// TypeError 8657: (33-34): The underlying type for a user defined value type has to be an elementary value type or an internal function type.
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ enum E {A, B, C}

type MyType is E;
// ----
// TypeError 8657: (33-34): The underlying type for a user defined value type has to be an elementary value type.
// TypeError 8657: (33-34): The underlying type for a user defined value type has to be an elementary value type or an internal function type.
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
type MyFunction is function(uint) returns (uint);
type MyFunction is function(uint) external returns (uint);
// ----
// TypeError 8657: (19-49): The underlying type for a user defined value type has to be an elementary value type.
// TypeError 8657: (19-58): The underlying type of the user defined value type "MyFunction" has to be an internal function type, but "function (uint256) external returns (uint256)" is an external function type.
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
type MyInt is mapping(uint => uint);
// ----
// TypeError 8657: (14-35): The underlying type for a user defined value type has to be an elementary value type.
// TypeError 8657: (14-35): The underlying type for a user defined value type has to be an elementary value type or an internal function type.
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ contract C {
type MyType is S;
}
// ----
// TypeError 8657: (52-53): The underlying type for a user defined value type has to be an elementary value type.
// TypeError 8657: (52-53): The underlying type for a user defined value type has to be an elementary value type or an internal function type.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
type MyInt1 is MyInt2;
type MyInt2 is MyInt1;
// ----
// TypeError 8657: (15-21): The underlying type for a user defined value type has to be an elementary value type.
// TypeError 8657: (15-21): The underlying type for a user defined value type has to be an elementary value type or an internal function type.
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
type MyFunction is function(MyFunction) external returns(MyFunction);
// ----
// TypeError 8657: (19-69): The underlying type for a user defined value type has to be an elementary value type.
// TypeError 8657: (19-69): The underlying type of the user defined value type "MyFunction" has to be an internal function type, but "function (MyFunction) external returns (MyFunction)" is an external function type.
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
type MyInt is MyInt;
// ----
// TypeError 8657: (14-19): The underlying type for a user defined value type has to be an elementary value type.
// TypeError 8657: (14-19): The underlying type for a user defined value type has to be an elementary value type or an internal function type.