diff --git a/crates/parse/src/parser/ty.rs b/crates/parse/src/parser/ty.rs index fde9cc1e..c928d4ba 100644 --- a/crates/parse/src/parser/ty.rs +++ b/crates/parse/src/parser/ty.rs @@ -114,19 +114,30 @@ impl<'sess, 'ast> Parser<'sess, 'ast> { Ok(ty) } + /// Parses a mapping key type. + fn parse_mapping_key_type(&mut self) -> PResult<'sess, Type<'ast>> { + if self.check_elementary_type() { + self.parse_spanned(Self::parse_elementary_type) + .map(|(span, kind)| Type { span, kind: TypeKind::Elementary(kind) }) + } else if self.check_path() { + self.parse_spanned(Self::parse_path) + .map(|(span, path)| Type { span, kind: TypeKind::Custom(path) }) + } else { + self.unexpected() + } + } + /// Parses a mapping type. fn parse_mapping_type(&mut self) -> PResult<'sess, TypeMapping<'ast>> { self.expect(TokenKind::OpenDelim(Delimiter::Parenthesis))?; - let key = self.parse_type()?; - // TODO: Move to type checking. - if !key.is_elementary() && !key.is_custom() { - let msg = - "only elementary types or used-defined types can be used as key types in mappings"; - self.dcx().err(msg).span(key.span).emit(); - } + let key = self.parse_mapping_key_type()?; let key_name = self.parse_ident_opt()?; + // We are about to require a `=>`. Previous checks (e.g. state mutability on elementary + // types) may have added expectations like `payable`/`pure`/`view`. Clear them so the + // diagnostic only mentions `=>` here. + self.expected_tokens.clear(); self.expect(TokenKind::FatArrow)?; let value = self.parse_type()?; diff --git a/crates/sema/src/typeck/checker.rs b/crates/sema/src/typeck/checker.rs index 9ca6ae0b..14a616a7 100644 --- a/crates/sema/src/typeck/checker.rs +++ b/crates/sema/src/typeck/checker.rs @@ -625,6 +625,18 @@ impl<'gcx> TypeChecker<'gcx> { // TODO: checks from https://github.com/ethereum/solidity/blob/9d7cc42bc1c12bb43e9dccf8c6c36833fdfcbbca/libsolidity/analysis/TypeChecker.cpp#L1219 } + #[must_use] + fn check_mapping_key_type(&mut self, key: &'gcx hir::Type<'gcx>) -> Ty<'gcx> { + let ty = self.gcx.type_of_hir_ty(key); + if !matches!( + ty.kind, + TyKind::Elementary(_) | TyKind::Udvt(_, _) | TyKind::Contract(_) | TyKind::Enum(_) + ) { + self.dcx().err("only elementary types, user defined value types, contract types or enums are allowed as mapping keys.").span(key.span).emit(); + } + ty + } + #[must_use] fn require_lvalue(&mut self, expr: &'gcx hir::Expr<'gcx>) -> Ty<'gcx> { let prev = self.lvalue_context.replace(true); @@ -731,7 +743,6 @@ impl<'gcx> hir::Visit<'gcx> for TypeChecker<'gcx> { ControlFlow::Continue(()) } - #[expect(clippy::single_match)] fn visit_ty(&mut self, hir_ty: &'gcx hir::Type<'gcx>) -> ControlFlow { match hir_ty.kind { hir::TypeKind::Array(array) => { @@ -740,6 +751,10 @@ impl<'gcx> hir::Visit<'gcx> for TypeChecker<'gcx> { } return self.visit_ty(&array.element); } + hir::TypeKind::Mapping(mapping) => { + let _ = self.check_mapping_key_type(&mapping.key); + self.visit_ty(&mapping.value)?; + } // TODO: https://github.com/ethereum/solidity/blob/9d7cc42bc1c12bb43e9dccf8c6c36833fdfcbbca/libsolidity/analysis/TypeChecker.cpp#L713 // hir::TypeKind::Function(func) => { // if func.visibility == hir::Visibility::External { diff --git a/tests/ui/parser/mapping_key_type.sol b/tests/ui/parser/mapping_key_type.sol new file mode 100644 index 00000000..f4286b4f --- /dev/null +++ b/tests/ui/parser/mapping_key_type.sol @@ -0,0 +1,20 @@ +type U is int; +enum E { + A, + B +} + +library L{ + enum E1 { + A, + B + } +} + +contract C { + mapping(uint => uint) m0; + mapping(E => uint) m1; + mapping(U => uint) m2; + mapping(L.E1 => uint) m3; + mapping(uint[] => uint) m4; //~ ERROR: expected `=>`, found `[` +} diff --git a/tests/ui/parser/mapping_key_type.stderr b/tests/ui/parser/mapping_key_type.stderr new file mode 100644 index 00000000..0f8b04fb --- /dev/null +++ b/tests/ui/parser/mapping_key_type.stderr @@ -0,0 +1,8 @@ +error: expected `=>`, found `[` + --> ROOT/tests/ui/parser/mapping_key_type.sol:LL:CC + | +LL | mapping(uint[] => uint) m4; + | ^ expected `=>` + +error: aborting due to 1 previous error + diff --git a/tests/ui/typeck/mapping_key_type_check.sol b/tests/ui/typeck/mapping_key_type_check.sol new file mode 100644 index 00000000..9dd7d1ab --- /dev/null +++ b/tests/ui/typeck/mapping_key_type_check.sol @@ -0,0 +1,25 @@ +//@compile-flags: -Ztypeck +type U is int; +enum E { + A, + B +} + +library L{ + enum E1 { + A, + B + } + struct S1 { + int x; + } +} + +contract C { + mapping(uint => uint) m0; + mapping(string => uint) m1; + mapping(E => uint) m2; + mapping(U => uint) m3; + mapping(L.E1 => uint) m4; + mapping(L.S1 => uint) m5; //~ ERROR: only elementary types, user defined value types, contract types or enums are allowed as mapping keys. +} diff --git a/tests/ui/typeck/mapping_key_type_check.stderr b/tests/ui/typeck/mapping_key_type_check.stderr new file mode 100644 index 00000000..24f3df6e --- /dev/null +++ b/tests/ui/typeck/mapping_key_type_check.stderr @@ -0,0 +1,8 @@ +error: only elementary types, user defined value types, contract types or enums are allowed as mapping keys. + --> ROOT/tests/ui/typeck/mapping_key_type_check.sol:LL:CC + | +LL | mapping(L.S1 => uint) m5; + | ^^^^ + +error: aborting due to 1 previous error +