Skip to content
Draft
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
100 changes: 96 additions & 4 deletions crates/sema/src/typeck/checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ impl<'gcx> TypeChecker<'gcx> {
self.types[&expr.id]
}

fn is_compile_time_constant(&self, expr: &'gcx hir::Expr<'gcx>) -> bool {
let mut evaluator = crate::eval::ConstantEvaluator::new(self.gcx);
evaluator.try_eval(expr).is_ok()
}

#[must_use]
fn check_expr(&mut self, expr: &'gcx hir::Expr<'gcx>) -> Ty<'gcx> {
self.check_expr_with(expr, None)
Expand Down Expand Up @@ -493,8 +498,28 @@ impl<'gcx> TypeChecker<'gcx> {
}

fn check_assign(&self, ty: Ty<'gcx>, expr: &'gcx hir::Expr<'gcx>) {
// TODO: https://github.com/ethereum/solidity/blob/9d7cc42bc1c12bb43e9dccf8c6c36833fdfcbbca/libsolidity/analysis/TypeChecker.cpp#L1421
let _ = (ty, expr);
// Check if assigning to type containing mappings.
// Mappings are always in storage, so we only need to check has_mapping().
// Allow if the lvalue is a local/return variable (local storage pointers are OK).
// https://github.com/ethereum/solidity/blob/9d7cc42bc1c12bb43e9dccf8c6c36833fdfcbbca/libsolidity/analysis/TypeChecker.cpp#L1421
if ty.has_mapping() && !self.is_local_or_return_variable(expr) {
self.dcx()
.err("Types in storage containing (nested) mappings cannot be assigned to.")
.span(expr.span)
.emit();
}
}

/// Returns true if the expression refers to a local or return variable.
fn is_local_or_return_variable(&self, expr: &'gcx hir::Expr<'gcx>) -> bool {
if let hir::ExprKind::Ident(res_slice) = &expr.kind {
let res = self.resolve_overloads(res_slice, expr.span);
if let hir::Res::Item(hir::ItemId::Variable(var_id)) = res {
let var = self.gcx.hir.variable(var_id);
return var.is_local_or_return();
}
}
false
}

fn check_binop(
Expand Down Expand Up @@ -588,9 +613,76 @@ impl<'gcx> TypeChecker<'gcx> {
let var = self.gcx.hir.variable(id);
let _ = self.visit_ty(&var.ty);
let ty = self.gcx.type_of_item(id.into());

// Constants must have compile-time constant initializers
if var.is_constant() {
// Constants must be initialized
if var.initializer.is_none() {
self.dcx()
.err("uninitialized \"constant\" variable")
.span(var.span)
.emit();
return ty;
} else if let Some(init) = var.initializer {
// Constant initializers must be compile-time constants
if !self.is_compile_time_constant(init) {
self.dcx()
.err("initial value for constant variable has to be compile-time constant")
.span(init.span)
.emit();
return ty;
}
}
} else if var.is_immutable() {
// Immutable variables must be value types
if !ty.is_value_type() {
self.dcx()
.err("immutable variables cannot have a non-value type")
.span(var.span)
.emit();
return ty;
}
}

// State variables containing mappings cannot be initialized
if var.is_state_variable() && ty.has_mapping() && var.initializer.is_some() {
self.dcx()
.err("Types in storage containing (nested) mappings cannot be assigned to.")
.span(var.span)
.emit();
return ty;
}

// Non-state variables with mappings must be in storage
if !var.is_state_variable()
&& matches!(
var.data_location,
Some(DataLocation::Calldata) | Some(DataLocation::Memory)
)
&& ty.has_mapping()
{
self.dcx()
.err(format!(
"Type {} is only valid in storage because it contains a (nested) mapping.",
ty.display(self.gcx)
))
.span(var.span)
.emit();
return ty;
}

// Libraries cannot have non-constant state variables
if var.is_state_variable()
&& var.contract.is_some_and(|contract_id| {
let contract = self.gcx.hir.contract(contract_id);
contract.kind.is_library() && !var.is_constant()
})
{
self.dcx().err("library cannot have non-constant state variable").span(var.span).emit();
return ty;
}

if let Some(init) = var.initializer {
// TODO: might have different logic vs assignment
self.check_assign(ty, init);
if expect {
let _ = self.expect_ty(init, ty);
}
Expand Down
20 changes: 20 additions & 0 deletions tests/ui/typeck/mapping/assignment_in_function.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//@compile-flags: -Ztypeck

contract F {
mapping (uint => uint) a;
mapping (uint => uint) b;

function foo() public {
a = b; //~ ERROR: Types in storage containing (nested) mappings cannot be assigned to.
}
}

contract LocalStorageOK {
mapping (uint => uint) a;
mapping (uint => uint) b;

function foo() public view {
mapping (uint => uint) storage c = b; // OK - local storage pointer
b = c; //~ ERROR: Types in storage containing (nested) mappings cannot be assigned to.
}
}
14 changes: 14 additions & 0 deletions tests/ui/typeck/mapping/assignment_in_function.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error: Types in storage containing (nested) mappings cannot be assigned to.
╭▸ ROOT/tests/ui/typeck/mapping/assignment_in_function.sol:LL:CC
LL │ a = b;
╰╴ ━

error: Types in storage containing (nested) mappings cannot be assigned to.
╭▸ ROOT/tests/ui/typeck/mapping/assignment_in_function.sol:LL:CC
LL │ b = c;
╰╴ ━

error: aborting due to 2 previous errors

10 changes: 10 additions & 0 deletions tests/ui/typeck/mapping/assignment_state_var.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//@compile-flags: -Ztypeck

contract C {
mapping (uint => address payable []) public a = a; //~ ERROR: Types in storage containing (nested) mappings cannot be assigned to.
}

contract D {
mapping (uint => uint) a;
mapping (uint => uint) b = a; //~ ERROR: Types in storage containing (nested) mappings cannot be assigned to.
}
14 changes: 14 additions & 0 deletions tests/ui/typeck/mapping/assignment_state_var.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error: Types in storage containing (nested) mappings cannot be assigned to.
╭▸ ROOT/tests/ui/typeck/mapping/assignment_state_var.sol:LL:CC
LL │ mapping (uint => address payable []) public a = a;
╰╴ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

error: Types in storage containing (nested) mappings cannot be assigned to.
╭▸ ROOT/tests/ui/typeck/mapping/assignment_state_var.sol:LL:CC
LL │ mapping (uint => uint) b = a;
╰╴ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

error: aborting due to 2 previous errors

9 changes: 9 additions & 0 deletions tests/ui/typeck/mapping/assignment_struct.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//@compile-flags: -Ztypeck

contract H {
struct S { mapping (uint => uint) a; }

S x;
S y = x; //~ ERROR: Types in storage containing (nested) mappings cannot be assigned to.
S z = z; //~ ERROR: Types in storage containing (nested) mappings cannot be assigned to.
}
14 changes: 14 additions & 0 deletions tests/ui/typeck/mapping/assignment_struct.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error: Types in storage containing (nested) mappings cannot be assigned to.
╭▸ ROOT/tests/ui/typeck/mapping/assignment_struct.sol:LL:CC
LL │ S y = x;
╰╴ ━━━━━━━━

error: Types in storage containing (nested) mappings cannot be assigned to.
╭▸ ROOT/tests/ui/typeck/mapping/assignment_struct.sol:LL:CC
LL │ S z = z;
╰╴ ━━━━━━━━

error: aborting due to 2 previous errors

18 changes: 18 additions & 0 deletions tests/ui/typeck/mapping/nested_mapping_storage_only.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//@compile-flags: -Ztypeck

contract C {
struct S { mapping(uint => uint) m; }

// These should error - mappings in memory/calldata (use internal to avoid public function check)
function f1(S memory s) internal {} //~ ERROR: only valid in storage because it contains a (nested) mapping
function f3() internal {
S memory s; //~ ERROR: only valid in storage because it contains a (nested) mapping
}

// These should be OK - mappings in storage
S storageVar;
function f4(S storage s) internal {}
function f5() internal {
S storage s = storageVar;
}
}
14 changes: 14 additions & 0 deletions tests/ui/typeck/mapping/nested_mapping_storage_only.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error: Type struct C.S memory is only valid in storage because it contains a (nested) mapping.
╭▸ ROOT/tests/ui/typeck/mapping/nested_mapping_storage_only.sol:LL:CC
LL │ function f1(S memory s) internal {}
╰╴ ━━━━━━━━━━

error: Type struct C.S memory is only valid in storage because it contains a (nested) mapping.
╭▸ ROOT/tests/ui/typeck/mapping/nested_mapping_storage_only.sol:LL:CC
LL │ S memory s;
╰╴ ━━━━━━━━━━

error: aborting due to 2 previous errors

21 changes: 21 additions & 0 deletions tests/ui/typeck/mapping/var_init.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//@compile-flags: -Ztypeck

contract Helper {
mapping(uint => uint) m;
}

contract C {
mapping(uint => uint) m1; // OK - no initializer
mapping(uint => uint) m2; // OK - no initializer

struct S {
uint x;
}

S s1; // OK - no initializer
S s2; // OK - no initializer

// Note: Mappings cannot be initialized in Solidity syntax at all.
// The check is defensive - if a type contains mapping and has initializer,
// we catch it at the semantic level.
}
10 changes: 10 additions & 0 deletions tests/ui/typeck/var_constant.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//@compile-flags: -Ztypeck

// Test constant validation with compile-time constant evaluation
contract TestConstants {
// Constant with non-constant initializers - ERROR
uint nonConstant;
uint constant refNonConstant = nonConstant; //~ ERROR: initial value for constant variable has to be compile-time constant

uint constant blockTime = block.timestamp; //~ ERROR: initial value for constant variable has to be compile-time constant
}
14 changes: 14 additions & 0 deletions tests/ui/typeck/var_constant.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error: initial value for constant variable has to be compile-time constant
╭▸ ROOT/tests/ui/typeck/var_constant.sol:LL:CC
LL │ uint constant refNonConstant = nonConstant;
╰╴ ━━━━━━━━━━━

error: initial value for constant variable has to be compile-time constant
╭▸ ROOT/tests/ui/typeck/var_constant.sol:LL:CC
LL │ uint constant blockTime = block.timestamp;
╰╴ ━━━━━━━━━━━━━━━

error: aborting due to 2 previous errors

20 changes: 20 additions & 0 deletions tests/ui/typeck/var_immutable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//@compile-flags: -Ztypeck

contract C {
// Immutable value types - OK
uint immutable a;
address immutable b;
bool immutable c;
bytes32 immutable d;
int256 immutable e;

// Immutable reference types - ERROR
uint[] immutable f; //~ ERROR: immutable variables cannot have a non-value type
string immutable g; //~ ERROR: immutable variables cannot have a non-value type
bytes immutable h; //~ ERROR: immutable variables cannot have a non-value type

struct S { uint x; }
S immutable i; //~ ERROR: immutable variables cannot have a non-value type

mapping(uint => uint) immutable j; //~ ERROR: immutable variables cannot have a non-value type
}
32 changes: 32 additions & 0 deletions tests/ui/typeck/var_immutable.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
error: immutable variables cannot have a non-value type
╭▸ ROOT/tests/ui/typeck/var_immutable.sol:LL:CC
LL │ uint[] immutable f;
╰╴ ━━━━━━━━━━━━━━━━━━━

error: immutable variables cannot have a non-value type
╭▸ ROOT/tests/ui/typeck/var_immutable.sol:LL:CC
LL │ string immutable g;
╰╴ ━━━━━━━━━━━━━━━━━━━

error: immutable variables cannot have a non-value type
╭▸ ROOT/tests/ui/typeck/var_immutable.sol:LL:CC
LL │ bytes immutable h;
╰╴ ━━━━━━━━━━━━━━━━━━

error: immutable variables cannot have a non-value type
╭▸ ROOT/tests/ui/typeck/var_immutable.sol:LL:CC
LL │ S immutable i;
╰╴ ━━━━━━━━━━━━━━

error: immutable variables cannot have a non-value type
╭▸ ROOT/tests/ui/typeck/var_immutable.sol:LL:CC
LL │ mapping(uint => uint) immutable j;
╰╴ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

error: aborting due to 5 previous errors

13 changes: 13 additions & 0 deletions tests/ui/typeck/var_library.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//@compile-flags: -Ztypeck

library L {
uint x; //~ ERROR: library cannot have non-constant state variable
uint constant c = 1; // OK - constant
uint immutable i; //~ ERROR: library cannot have non-constant state variable
}

contract C {
uint x; // OK - not a library
uint constant c = 1; // OK
uint immutable i; // OK - not a library
}
14 changes: 14 additions & 0 deletions tests/ui/typeck/var_library.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error: library cannot have non-constant state variable
╭▸ ROOT/tests/ui/typeck/var_library.sol:LL:CC
LL │ uint x;
╰╴ ━━━━━━━

error: library cannot have non-constant state variable
╭▸ ROOT/tests/ui/typeck/var_library.sol:LL:CC
LL │ uint immutable i;
╰╴ ━━━━━━━━━━━━━━━━━

error: aborting due to 2 previous errors

Loading