Function references are not considered constant
- Dominant language
- C++
- Stars
- 25.7k
- Forks
- 6.2k
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 21
Description
## Description
The language allows constant state variables of function types, but they are currently unusable, because it is impossible to initialize them. That's because a reference you get when you refer to a function is never considered constant. I think we should make those references constant. Unfortunately, this gets a bit weird in corner cases.
### External function in a foreign contract
This is the most straightforward case. An external function pointer is represented with a contract address and a function selector. The latter is always constant as long as we're referring to a function directly (not via another pointer). The former can be constant if we make it so.
I think that in the following example `constCPtr` and `literalCPtr` could be allowed to compile without any complications:
```solidity
contract C {
function f() external {}
}
contract D {
C varC;
C constant constC = C(address(0));
function() external constant varCPtr = varC.f; // Error: Initial value for constant variable has to be compile-time constant.
function() external constant constCPtr = constC.f; // Error: Initial value for constant variable has to be compile-time constant.
function() external constant literalCPtr = C(address(0)).f; // Error: Initial value for constant variable has to be compile-time constant.
}
```
### External function in the current contract
This is a weird corner case. Since the address of the current contract is not known at compilation time, it can't be a compile-time constant. However, our `constant` state variables are actually runtime constants, i.e. are read-only, but evaluated at runtime. Allowing the code below to compiler would fit this model:
```solidity
contract C {
function f() external {}
function() external constant constPtr = this.f; // Error: Initial value for constant variable has to be compile-time constant.
function() external creationPtr = constPtr;
function() external runtimePtr;
function run() public {
runtimePtr = constPtr;
}
}
```
Note that this would have odd consequences like `creationPtr` and `runtimePtr` having different values despite both being initialized with the same constant. That's because the address is only known after deployment and `creationPtr` gets initialized before that, at creation time. Despite this, it would not be a new thing. We already have this problem with `string`/`bytes` constants (see #16188), which are allocated in memory and can point at different addresses in different transactions.
Allowing this could also make things harder for us if we wanted to switch to true compile-time `constant` state variables (it would become a breaking change).
### Internal function
This is a slightly different corner case. The value is always the same at runtime, but is still not known at compilation time. At least not during analysis, which is where it matters. In evmasm codegen they are pointers into bytecode, while in IR codegen they are global function IDs and both obviously depend on code generation.
```solidity
contract D {
function f() internal {}
function() internal constant constPtr = f; // Error: Initial value for constant variable has to be compile-time constant.
}
```
Contributor guide
Research direction
Start by tracing how constant state-variable expressions are handled during analysis, then compare the evmasm and IR codegen paths mentioned in the issue. Determine separate behavior for external, current-contract, and internal function references, including the creation-time and runtime consequences. Done means the supported cases are specified and consistently accepted or rejected, with the corner cases resolved.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, solidity
- Domain
- compilers
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100