@@ -6,7 +6,7 @@ use crate::{
66use alloy_primitives:: U256 ;
77use solar_ast:: { DataLocation , ElementaryType , Span } ;
88use solar_data_structures:: { Never , map:: FxHashMap , pluralize, smallvec:: SmallVec } ;
9- use solar_interface:: diagnostics:: DiagCtxt ;
9+ use solar_interface:: { diagnostics:: DiagCtxt , sym } ;
1010use std:: ops:: ControlFlow ;
1111
1212pub ( super ) fn check ( gcx : Gcx < ' _ > , source : hir:: SourceId ) {
@@ -21,7 +21,18 @@ struct TypeChecker<'gcx> {
2121
2222 types : FxHashMap < hir:: ExprId , Ty < ' gcx > > ,
2323
24- lvalue_context : Option < bool > ,
24+ lvalue_context : Option < Result < ( ) , NotLvalueReason > > ,
25+ }
26+
27+ #[ derive( Clone , Copy ) ]
28+ enum NotLvalueReason {
29+ Constant ,
30+ Immutable ,
31+ CalldataArray ,
32+ CalldataStruct ,
33+ FixedBytesIndex ,
34+ ArrayLength ,
35+ Generic ,
2536}
2637
2738impl < ' gcx > TypeChecker < ' gcx > {
@@ -164,7 +175,7 @@ impl<'gcx> TypeChecker<'gcx> {
164175 } ;
165176
166177 if !is_array_push {
167- self . not_lvalue ( ) ;
178+ self . try_set_not_lvalue ( NotLvalueReason :: Generic ) ;
168179 }
169180
170181 ty
@@ -181,8 +192,8 @@ impl<'gcx> TypeChecker<'gcx> {
181192 }
182193 hir:: ExprKind :: Ident ( res) => {
183194 let res = self . resolve_overloads ( res, expr. span ) ;
184- if ! res_is_lvalue ( self . gcx , res) {
185- self . not_lvalue ( ) ;
195+ if let Some ( reason ) = res_not_lvalue_reason ( self . gcx , res) {
196+ self . try_set_not_lvalue ( reason ) ;
186197 }
187198 self . type_of_res ( res)
188199 }
@@ -192,7 +203,11 @@ impl<'gcx> TypeChecker<'gcx> {
192203 return ty;
193204 }
194205 if ty. loc ( ) == Some ( DataLocation :: Calldata ) {
195- self . not_lvalue ( ) ;
206+ self . try_set_not_lvalue ( NotLvalueReason :: CalldataArray ) ;
207+ }
208+ if matches ! ( ty. peel_refs( ) . kind, TyKind :: Elementary ( ElementaryType :: FixedBytes ( _) ) )
209+ {
210+ self . try_set_not_lvalue ( NotLvalueReason :: FixedBytesIndex ) ;
196211 }
197212 if let Some ( ( index_ty, result_ty) ) = self . index_types ( ty) {
198213 // Index expression.
@@ -283,18 +298,36 @@ impl<'gcx> TypeChecker<'gcx> {
283298 } ;
284299
285300 // Validate lvalue.
286- match expr_ty. kind {
287- TyKind :: Ref ( _, d) if d. is_calldata ( ) => self . not_lvalue ( ) ,
301+ let not_lvalue_reason = match expr_ty. kind {
302+ _ if matches ! (
303+ expr_ty. peel_refs( ) . kind,
304+ TyKind :: Array ( ..) | TyKind :: DynArray ( _)
305+ ) && possible_members. len ( ) == 1
306+ && possible_members[ 0 ] . name == sym:: length =>
307+ {
308+ Some ( NotLvalueReason :: ArrayLength )
309+ }
310+ TyKind :: Ref ( inner, d) if d. is_calldata ( ) => {
311+ let reason = if matches ! ( inner. kind, TyKind :: Struct ( _) ) {
312+ NotLvalueReason :: CalldataStruct
313+ } else {
314+ NotLvalueReason :: CalldataArray
315+ } ;
316+ Some ( reason)
317+ }
288318 TyKind :: Type ( ty)
289319 if matches ! ( ty. kind, TyKind :: Contract ( _) )
290320 && possible_members. len ( ) == 1
291- && ! possible_members[ 0 ]
292- . res
293- . is_some_and ( |res| res_is_lvalue ( self . gcx , res ) ) =>
321+ && possible_members[ 0 ] . res . is_some_and ( |res| {
322+ res_not_lvalue_reason ( self . gcx , res) . is_some ( )
323+ } ) =>
294324 {
295- self . not_lvalue ( ) ;
325+ Some ( NotLvalueReason :: Generic )
296326 }
297- _ => { }
327+ _ => None ,
328+ } ;
329+ if let Some ( reason) = not_lvalue_reason {
330+ self . try_set_not_lvalue ( reason) ;
298331 }
299332
300333 ty
@@ -636,26 +669,36 @@ impl<'gcx> TypeChecker<'gcx> {
636669
637670 #[ must_use]
638671 fn require_lvalue ( & mut self , expr : & ' gcx hir:: Expr < ' gcx > ) -> Ty < ' gcx > {
639- let prev = self . lvalue_context . replace ( true ) ;
672+ let prev = self . lvalue_context . replace ( Ok ( ( ) ) ) ;
640673 let ty = self . check_expr ( expr) ;
641- let ctx = self . lvalue_context ;
642- debug_assert ! ( ctx. is_some( ) ) ;
674+ let result = self . lvalue_context . unwrap ( ) ;
643675 self . lvalue_context = prev;
644- // TODO: check ctx
645- if is_syntactic_lvalue ( expr) {
676+
677+ if result . is_ok ( ) && is_syntactic_lvalue ( expr) {
646678 return ty;
647679 }
648680
649- // TODO: better error message https://github.com/ethereum/solidity/blob/9d7cc42bc1c12bb43e9dccf8c6c36833fdfcbbca/libsolidity/analysis/TypeChecker.cpp#L4143
650-
651- self . dcx ( ) . err ( "expected lvalue" ) . span ( expr. span ) . emit ( ) ;
681+ let msg = match result {
682+ Err ( NotLvalueReason :: Constant ) => "cannot assign to a constant variable" ,
683+ Err ( NotLvalueReason :: Immutable ) => "cannot assign to an immutable variable" ,
684+ Err ( NotLvalueReason :: CalldataArray ) => "calldata arrays are read-only" ,
685+ Err ( NotLvalueReason :: CalldataStruct ) => "calldata structs are read-only" ,
686+ Err ( NotLvalueReason :: FixedBytesIndex ) => {
687+ "single bytes in fixed bytes arrays cannot be modified"
688+ }
689+ Err ( NotLvalueReason :: ArrayLength ) => {
690+ "member `length` is read-only and cannot be used to resize arrays"
691+ }
692+ Err ( NotLvalueReason :: Generic ) | Ok ( ( ) ) => "expression has to be an lvalue" ,
693+ } ;
694+ self . dcx ( ) . err ( msg) . span ( expr. span ) . emit ( ) ;
652695
653696 ty
654697 }
655698
656- fn not_lvalue ( & mut self ) {
657- if let Some ( v ) = & mut self . lvalue_context {
658- * v = false ;
699+ fn try_set_not_lvalue ( & mut self , reason : NotLvalueReason ) {
700+ if let Some ( Ok ( ( ) ) ) = self . lvalue_context {
701+ self . lvalue_context = Some ( Err ( reason ) ) ;
659702 }
660703 }
661704
@@ -915,11 +958,18 @@ impl<T> FromIterator<T> for WantOne<T> {
915958 }
916959}
917960
918- fn res_is_lvalue ( gcx : Gcx < ' _ > , res : hir:: Res ) -> bool {
961+ fn res_not_lvalue_reason ( gcx : Gcx < ' _ > , res : hir:: Res ) -> Option < NotLvalueReason > {
919962 match res {
920- hir:: Res :: Item ( hir:: ItemId :: Variable ( var) ) => !gcx. hir . variable ( var) . is_constant ( ) ,
921- hir:: Res :: Err ( _) => true ,
922- _ => false ,
963+ hir:: Res :: Item ( hir:: ItemId :: Variable ( var) ) => {
964+ let var = gcx. hir . variable ( var) ;
965+ match var. mutability {
966+ Some ( m) if m. is_constant ( ) => Some ( NotLvalueReason :: Constant ) ,
967+ Some ( m) if m. is_immutable ( ) => Some ( NotLvalueReason :: Immutable ) ,
968+ _ => None ,
969+ }
970+ }
971+ hir:: Res :: Err ( _) => None ,
972+ _ => Some ( NotLvalueReason :: Generic ) ,
923973 }
924974}
925975
0 commit comments