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
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Language Features:
Compiler Features:

Bugfixes:
* TypeChecker: Allow assignment of error and event selectors to constant variables.


### 0.8.31 (2025-12-03)
Expand Down
27 changes: 20 additions & 7 deletions libsolidity/analysis/TypeChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3280,18 +3280,31 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
auto const* functionType = dynamic_cast<FunctionType const*>(exprType);
functionType &&
functionType->hasDeclaration() &&
dynamic_cast<FunctionDefinition const*>(&functionType->declaration()) &&
memberName == "selector"
)
if (auto const* parentAccess = dynamic_cast<MemberAccess const*>(&_memberAccess.expression()))
{
if (dynamic_cast<FunctionDefinition const*>(&functionType->declaration()))
{
bool isPure = *parentAccess->expression().annotation().isPure;
if (auto const* exprInt = dynamic_cast<Identifier const*>(&parentAccess->expression()))
if (exprInt->name() == "this" || exprInt->name() == "super")
isPure = true;
if (auto const* parentAccess = dynamic_cast<MemberAccess const*>(&_memberAccess.expression()))
{
bool isPure = *parentAccess->expression().annotation().isPure;
// Accessing a function selector using `super|this.f.selector`.
if (auto const* exprInt = dynamic_cast<Identifier const*>(&parentAccess->expression()))
if (exprInt->name() == "this" || exprInt->name() == "super")
isPure = true;
Comment on lines +3290 to +3294
Copy link
Collaborator

@cameel cameel Dec 8, 2025

Choose a reason for hiding this comment

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

This piece of code looked fishy and while poking around it I found another bug with selector constness. I reported it as #16338.

Copy link
Collaborator

@cameel cameel Dec 8, 2025

Choose a reason for hiding this comment

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

I guess the underlying cause is that the functions themselves are not considered constant, which I also reported as #16339. I'm not sure if we should consider that one a bug though. It might have been intentional, as there are cases where it's straightforward to decide whether a function reference should be const or not as well as some corner cases where it's not so clear.


annotation.isPure = isPure;
annotation.isPure = isPure;
}
}
// In case of event or error definition the selector is always compile-time constant, as it can be
// a keccak256 hash of the event signature or a function selector in case of an error.
else if (
dynamic_cast<EventDefinition const*>(&functionType->declaration()) ||
dynamic_cast<ErrorDefinition const*>(&functionType->declaration())
)
annotation.isPure = true;
}

if (
auto const* varDecl = dynamic_cast<VariableDeclaration const*>(annotation.referencedDeclaration);
!annotation.isPure.set() &&
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
event EvExt();
error ErExt();

bytes32 constant eventExtSelectorGlobal = EvExt.selector;
bytes4 constant errorExtSelectorGlobal = ErExt.selector;

contract C {
event Ev();
error Er();

bytes4 constant errorExtSelector = ErExt.selector;
bytes32 constant eventExtSelector = EvExt.selector;

bytes4 constant errorSelector = Er.selector;
bytes32 constant eventSelector = Ev.selector;

bytes4 constant errorSelectorC = C.Er.selector;
bytes32 constant eventSelectorC = C.Ev.selector;
}
// ----