diff --git a/crates/hir-analysis/src/ty/diagnostics.rs b/crates/hir-analysis/src/ty/diagnostics.rs index 3483bd90f..6d36bd18e 100644 --- a/crates/hir-analysis/src/ty/diagnostics.rs +++ b/crates/hir-analysis/src/ty/diagnostics.rs @@ -542,26 +542,61 @@ impl DiagnosticVoucher for TyLowerDiag { pub enum BodyDiag { TypeMismatch(DynLazySpan, String, String), InfiniteOccurrence(DynLazySpan), + DuplicatedRestPat(DynLazySpan), + InvalidPathDomainInPat { primary: DynLazySpan, resolved: Option, }, + UnitVariantExpectedInPat { primary: DynLazySpan, pat_kind: &'static str, hint: Option, }, + TupleVariantExpectedInPat { primary: DynLazySpan, pat_kind: Option<&'static str>, hint: Option, }, + + RecordVariantExpectedInPat { + primary: DynLazySpan, + pat_kind: Option<&'static str>, + hint: Option, + }, + MismatchedFieldCount { primary: DynLazySpan, expected: usize, given: usize, }, + + DuplicatedRecordFieldBind { + primary: DynLazySpan, + first_use: DynLazySpan, + name: IdentId, + }, + + RecordFieldNotFound { + primary: DynLazySpan, + label: IdentId, + def_span: DynLazySpan, + def_name: IdentId, + }, + + ExplicitLabelExpectedInRecord { + primary: DynLazySpan, + hint: Option, + }, + + MissingRecordFields { + primary: DynLazySpan, + missing_fields: BTreeSet, + hint: Option, + }, } impl BodyDiag { @@ -608,6 +643,41 @@ impl BodyDiag { } } + pub(super) fn record_variant_expected_in_pat( + db: &dyn HirAnalysisDb, + primary: DynLazySpan, + data: Option, + ) -> Self { + let (pat_kind, hint) = if let Some(data) = data { + (Some(data.data_kind(db)), data.initializer_hint(db)) + } else { + (None, None) + }; + + Self::RecordVariantExpectedInPat { + primary, + pat_kind, + hint, + } + } + + pub(super) fn record_field_not_found( + db: &dyn HirAnalysisDb, + primary: DynLazySpan, + data: &ResolvedPathData, + label: IdentId, + ) -> Self { + let def_span = data.def_span(db); + let def_name = data.def_name(db); + + Self::RecordFieldNotFound { + primary, + label, + def_span, + def_name, + } + } + fn local_code(&self) -> u16 { match self { Self::TypeMismatch(_, _, _) => 0, @@ -616,7 +686,12 @@ impl BodyDiag { Self::InvalidPathDomainInPat { .. } => 3, Self::UnitVariantExpectedInPat { .. } => 4, Self::TupleVariantExpectedInPat { .. } => 5, - Self::MismatchedFieldCount { .. } => 6, + Self::RecordVariantExpectedInPat { .. } => 6, + Self::MismatchedFieldCount { .. } => 7, + Self::DuplicatedRecordFieldBind { .. } => 8, + Self::RecordFieldNotFound { .. } => 9, + Self::ExplicitLabelExpectedInRecord { .. } => 10, + Self::MissingRecordFields { .. } => 11, } } @@ -628,7 +703,12 @@ impl BodyDiag { Self::InvalidPathDomainInPat { .. } => "invalid item is given here".to_string(), Self::UnitVariantExpectedInPat { .. } => "expected unit variant".to_string(), Self::TupleVariantExpectedInPat { .. } => "expected tuple variant".to_string(), + Self::RecordVariantExpectedInPat { .. } => "expected record variant".to_string(), Self::MismatchedFieldCount { .. } => "field count mismatch".to_string(), + Self::DuplicatedRecordFieldBind { .. } => "duplicated record field binding".to_string(), + Self::RecordFieldNotFound { .. } => "specified field not found".to_string(), + Self::ExplicitLabelExpectedInRecord { .. } => "explicit label is required".to_string(), + Self::MissingRecordFields { .. } => "all fields are not given".to_string(), } } @@ -718,6 +798,36 @@ impl BodyDiag { diag } + Self::RecordVariantExpectedInPat { + primary, + pat_kind, + hint, + } => { + let mut diag = if let Some(pat_kind) = pat_kind { + vec![SubDiagnostic::new( + LabelStyle::Primary, + format!("expected record variant here, but found {}", pat_kind,), + primary.resolve(db), + )] + } else { + vec![SubDiagnostic::new( + LabelStyle::Primary, + "expected record variant here".to_string(), + primary.resolve(db), + )] + }; + + if let Some(hint) = hint { + diag.push(SubDiagnostic::new( + LabelStyle::Secondary, + format!("Consider using `{}` instead", hint), + primary.resolve(db), + )) + } + + diag + } + Self::MismatchedFieldCount { primary, expected, @@ -729,6 +839,91 @@ impl BodyDiag { primary.resolve(db), )] } + + Self::DuplicatedRecordFieldBind { + primary, + first_use, + name, + } => { + let name = name.data(db.as_hir_db()); + vec![ + SubDiagnostic::new( + LabelStyle::Primary, + format!("duplicated field binding `{}`", name), + primary.resolve(db), + ), + SubDiagnostic::new( + LabelStyle::Secondary, + format!("first use of `{}`", name), + first_use.resolve(db), + ), + ] + } + + Self::RecordFieldNotFound { + primary, + label, + def_span, + def_name, + } => { + let label = label.data(db.as_hir_db()); + let def_name = def_name.data(db.as_hir_db()); + + vec![ + SubDiagnostic::new( + LabelStyle::Primary, + format!("field `{}` not found", label), + primary.resolve(db), + ), + SubDiagnostic::new( + LabelStyle::Secondary, + format!("`{}` is defined here", def_name), + def_span.resolve(db), + ), + ] + } + + Self::ExplicitLabelExpectedInRecord { primary, hint } => { + let mut diag = vec![SubDiagnostic::new( + LabelStyle::Primary, + "explicit label is required".to_string(), + primary.resolve(db), + )]; + if let Some(hint) = hint { + diag.push(SubDiagnostic::new( + LabelStyle::Secondary, + format!("Consider using `{}` instead", hint), + primary.resolve(db), + )) + } + + diag + } + + Self::MissingRecordFields { + primary, + missing_fields, + hint, + } => { + let missing = missing_fields + .iter() + .map(|id| id.data(db.as_hir_db())) + .join(", "); + + let mut diag = vec![SubDiagnostic::new( + LabelStyle::Primary, + format! {"missing `{}`", missing}, + primary.resolve(db), + )]; + if let Some(hint) = hint { + diag.push(SubDiagnostic::new( + LabelStyle::Secondary, + format!("Consider using `{}` instead", hint), + primary.resolve(db), + )) + } + diag + } } } diff --git a/crates/hir-analysis/src/ty/ty_check/mod.rs b/crates/hir-analysis/src/ty/ty_check/mod.rs index d2d8ef632..3dc63de5f 100644 --- a/crates/hir-analysis/src/ty/ty_check/mod.rs +++ b/crates/hir-analysis/src/ty/ty_check/mod.rs @@ -6,16 +6,16 @@ mod stmt; use env::TyCheckEnv; use hir::{ hir_def::{ - Body, Enum, ExprId, Func, LitKind, PatId, PathId, TypeId as HirTyId, + Body, Enum, ExprId, Func, IdentId, LitKind, PatId, PathId, TypeId as HirTyId, VariantKind as HirVariantKind, }, span::DynLazySpan, }; -use rustc_hash::FxHashMap; +use rustc_hash::{FxHashMap, FxHashSet}; use super::{ diagnostics::{BodyDiag, FuncBodyDiagAccumulator}, - ty_def::{AdtDef, AdtRef, AdtRefId, InvalidCause, Kind, TyId, TyVarUniverse}, + ty_def::{AdtDef, AdtRef, AdtRefId, InvalidCause, Kind, Subst, TyId, TyVarUniverse}, ty_lower::{lower_adt, lower_hir_ty}, unify::{UnificationError, UnificationTable}, }; @@ -200,24 +200,77 @@ impl Typeable { } } -#[derive(Clone, Debug, Hash, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum ResolvedPathData { - Adt(AdtDef, PathId), - Variant(Enum, usize, PathId), + Adt { + adt: AdtDef, + args: FxHashMap, + path: PathId, + }, + + Variant { + enum_: Enum, + idx: usize, + args: FxHashMap, + path: PathId, + }, } impl ResolvedPathData { - pub(crate) fn adt_ref(&self, db: &dyn HirAnalysisDb) -> AdtRef { + fn new_adt( + db: &dyn HirAnalysisDb, + table: &mut UnificationTable, + adt: AdtDef, + path: PathId, + ) -> Self { + let args = adt + .params(db) + .iter() + .map(|param| (*param, table.new_var_from_param(*param))) + .collect(); + + Self::Adt { adt, args, path } + } + + fn new_variant( + db: &dyn HirAnalysisDb, + table: &mut UnificationTable, + enum_: Enum, + idx: usize, + path: PathId, + ) -> Self { + let args = lower_adt(db, AdtRefId::from_enum(db, enum_)) + .params(db) + .iter() + .map(|param| (*param, table.new_var_from_param(*param))) + .collect(); + + Self::Variant { + enum_, + idx, + args, + path, + } + } + + pub(crate) fn adt_ref(&self, db: &dyn HirAnalysisDb) -> AdtRefId { match self { - Self::Adt(adt, _) => adt.adt_ref(db).data(db), - Self::Variant(enum_, _, _) => AdtRef::Enum(*enum_), + Self::Adt { adt, .. } => adt.adt_ref(db), + Self::Variant { enum_, .. } => AdtRefId::from_enum(db, *enum_), + } + } + + pub(crate) fn adt_def(&self, db: &dyn HirAnalysisDb) -> AdtDef { + match self { + Self::Adt { adt, .. } => *adt, + Self::Variant { enum_, .. } => lower_adt(db, AdtRefId::from_enum(db, *enum_)), } } pub(crate) fn data_kind(&self, db: &dyn HirAnalysisDb) -> &'static str { match self { - Self::Adt(adt, _) => adt.adt_ref(db).kind_name(db), - Self::Variant(enum_, idx, _) => { + Self::Adt { adt, .. } => adt.adt_ref(db).kind_name(db), + Self::Variant { enum_, idx, .. } => { let hir_db = db.as_hir_db(); match enum_.variants(hir_db).data(hir_db)[*idx].kind { hir::hir_def::VariantKind::Unit => "unit variant", @@ -232,15 +285,15 @@ impl ResolvedPathData { let hir_db = db.as_hir_db(); let expected_sub_pat = match self { - Self::Adt(_, _) => { - let AdtRef::Struct(s) = self.adt_ref(db) else { + Self::Adt { .. } => { + let AdtRef::Struct(s) = self.adt_ref(db).data(db) else { return None; }; s.format_initializer_args(hir_db) } - Self::Variant(enum_, idx, _) => { + Self::Variant { enum_, idx, .. } => { enum_.variants(hir_db).data(hir_db)[*idx].format_initializer_args(hir_db) } }; @@ -249,31 +302,51 @@ impl ResolvedPathData { Some(format!("{}{}", path, expected_sub_pat)) } - fn path(&self) -> PathId { + pub(super) fn def_span(&self, db: &dyn HirAnalysisDb) -> DynLazySpan { match self { - Self::Adt(_, path) => *path, - Self::Variant(_, _, path) => *path, + Self::Adt { .. } => self.adt_ref(db).name_span(db), + Self::Variant { enum_, idx, .. } => { + enum_.lazy_span().variants_moved().variant(*idx).into() + } } } - fn ty(&self, db: &dyn HirAnalysisDb, table: &mut UnificationTable) -> TyId { - let adt = match self { - Self::Adt(adt, _) => *adt, + pub(super) fn def_name(&self, db: &dyn HirAnalysisDb) -> IdentId { + match self { + Self::Adt { adt, .. } => adt.adt_ref(db).name(db), + Self::Variant { enum_, idx, .. } => { + *enum_.variants(db.as_hir_db()).data(db.as_hir_db())[*idx] + .name + .unwrap() + } + } + } - Self::Variant(enum_, ..) => lower_adt(db, AdtRefId::from_enum(db, *enum_)), - }; + fn path(&self) -> PathId { + match self { + Self::Adt { path, .. } => *path, + Self::Variant { path, .. } => *path, + } + } - let adt_ty = TyId::adt(db, adt); - adt.params(db).iter().fold(adt_ty, |ty, param| { - let param_ty = table.new_var_from_param(*param); - TyId::app(db, ty, param_ty) - }) + fn is_record(&self, db: &dyn HirAnalysisDb) -> bool { + match self { + Self::Adt { adt, .. } => matches!( + adt.adt_ref(db).data(db), + AdtRef::Struct(_) | AdtRef::Contract(_) + ), + + Self::Variant { enum_, idx, .. } => matches!( + enum_.variants(db.as_hir_db()).data(db.as_hir_db())[*idx].kind, + HirVariantKind::Record(_) + ), + } } fn is_unit_variant(&self, db: &dyn HirAnalysisDb) -> bool { match self { - Self::Adt(_, _) => false, - Self::Variant(enum_, idx, _) => { + Self::Adt { .. } => false, + Self::Variant { enum_, idx, .. } => { let hir_db = db.as_hir_db(); matches!( enum_.variants(hir_db).data(hir_db)[*idx].kind, @@ -285,8 +358,8 @@ impl ResolvedPathData { fn is_tuple_variant(&self, db: &dyn HirAnalysisDb) -> bool { match self { - Self::Adt(_, _) => false, - Self::Variant(enum_, idx, _) => { + Self::Adt { .. } => false, + Self::Variant { enum_, idx, .. } => { let hir_db = db.as_hir_db(); matches!( enum_.variants(hir_db).data(hir_db)[*idx].kind, @@ -296,9 +369,60 @@ impl ResolvedPathData { } } - fn field_tys(&self, db: &dyn HirAnalysisDb) -> Vec { + fn ty(&self, db: &dyn HirAnalysisDb) -> TyId { + let adt = match self { + Self::Adt { adt, .. } => *adt, + + Self::Variant { enum_, .. } => lower_adt(db, AdtRefId::from_enum(db, *enum_)), + }; + + let adt_ty = TyId::adt(db, adt); + self.args().fold(adt_ty, |ty, arg| TyId::app(db, ty, *arg)) + } + + fn record_field_ty(&mut self, db: &dyn HirAnalysisDb, name: IdentId) -> Option { + let hir_db = db.as_hir_db(); + + let (hir_field_list, field_list) = match self { + Self::Adt { adt, .. } => match adt.adt_ref(db).data(db) { + AdtRef::Struct(s) => (s.fields(hir_db), &adt.fields(db)[0]), + AdtRef::Contract(c) => (c.fields(hir_db), &adt.fields(db)[0]), + + _ => return None, + }, + + Self::Variant { enum_, ref idx, .. } => { + match enum_.variants(hir_db).data(hir_db)[*idx].kind { + hir::hir_def::VariantKind::Record(fields) => { + (fields, &self.adt_def(db).fields(db)[*idx]) + } + + _ => return None, + } + } + }; + + let field_idx = hir_field_list.field_idx(hir_db, name)?; + let ty = field_list.ty(db, field_idx); + + Some(ty.apply_subst(db, self.subst())) + } + + fn args(&self) -> impl Iterator { + match self { + Self::Adt { args, .. } | Self::Variant { args, .. } => args.values(), + } + } + + fn subst(&mut self) -> &mut impl Subst { + match self { + Self::Adt { args, .. } | Self::Variant { args, .. } => args, + } + } + + fn field_tys(&mut self, db: &dyn HirAnalysisDb) -> Vec { let (adt, idx) = match self { - Self::Adt(adt, _) => { + Self::Adt { adt, .. } => { if matches!( adt.adt_ref(db).data(db), AdtRef::Struct(_) | AdtRef::Contract(_) @@ -308,12 +432,42 @@ impl ResolvedPathData { return vec![]; } } - Self::Variant(enum_, idx, _) => { + Self::Variant { enum_, idx, .. } => { let adt = lower_adt(db, AdtRefId::from_enum(db, *enum_)); (adt, *idx) } }; - adt.fields(db)[idx].iter_types(db).collect() + adt.fields(db)[idx] + .iter_types(db) + .map(|ty| ty.apply_subst(db, self.subst())) + .collect() + } + + fn record_labels(&mut self, db: &dyn HirAnalysisDb) -> FxHashSet { + let hir_db = db.as_hir_db(); + + let fields = match self { + Self::Adt { adt, .. } => match adt.adt_ref(db).data(db) { + AdtRef::Struct(s) => s.fields(hir_db), + AdtRef::Contract(c) => c.fields(hir_db), + + _ => return FxHashSet::default(), + }, + + Self::Variant { enum_, ref idx, .. } => { + match enum_.variants(hir_db).data(hir_db)[*idx].kind { + hir::hir_def::VariantKind::Record(fields) => fields, + + _ => return FxHashSet::default(), + } + } + }; + + fields + .data(hir_db) + .iter() + .filter_map(|field| field.name.to_opt()) + .collect() } } diff --git a/crates/hir-analysis/src/ty/ty_check/pat.rs b/crates/hir-analysis/src/ty/ty_check/pat.rs index 7e497e6a8..8efd45227 100644 --- a/crates/hir-analysis/src/ty/ty_check/pat.rs +++ b/crates/hir-analysis/src/ty/ty_check/pat.rs @@ -1,11 +1,15 @@ -use std::ops::Range; +use std::{ + collections::{hash_map::Entry, BTreeSet}, + ops::Range, +}; use hir::{ hir_def::{scope_graph::ScopeId, Enum, IdentId, ItemKind, Partial, Pat, PatId, PathId}, span::path::LazyPathSpan, }; +use rustc_hash::{FxHashMap, FxHashSet}; -use super::{env::TyCheckEnv, ResolvedPathData, TyChecker}; +use super::{ResolvedPathData, TyChecker}; use crate::{ name_resolution::{ resolve_path_early, resolve_segments_early, EarlyResolvedPath, NameDomain, NameRes, @@ -16,7 +20,6 @@ use crate::{ ty_def::{AdtRefId, InvalidCause, Kind, TyId, TyVarUniverse}, ty_lower::lower_adt, }, - HirAnalysisDb, }; impl<'db> TyChecker<'db> { @@ -37,7 +40,7 @@ impl<'db> TyChecker<'db> { Pat::Tuple(..) => self.check_tuple_pat(pat, pat_data, expected), Pat::Path(..) => self.check_path_pat(pat, pat_data), Pat::PathTuple(..) => self.check_path_tuple_pat(pat, pat_data), - Pat::Record(..) => todo!(), + Pat::Record(..) => self.check_record_pat(pat, pat_data), Pat::Or(lhs, rhs) => { self.check_pat(*lhs, expected); @@ -111,10 +114,10 @@ impl<'db> TyChecker<'db> { return TyId::invalid(self.db, InvalidCause::Other); }; - match resolve_path_in_pat(self.db, &self.env, *path, pat) { + match resolve_path_in_pat(self, *path, pat) { ResolvedPathInPat::Data(data) => { if data.is_unit_variant(self.db) { - data.ty(self.db, &mut self.table) + data.ty(self.db) } else { let diag = BodyDiag::unit_variant_expected_in_pat( self.db, @@ -142,21 +145,14 @@ impl<'db> TyChecker<'db> { } fn check_path_tuple_pat(&mut self, pat: PatId, pat_data: &Pat) -> TyId { - let Pat::PathTuple(path, args) = pat_data else { - unreachable!() - }; - - let Partial::Present(path) = path else { + let Pat::PathTuple(Partial::Present(path), args) = pat_data else { return TyId::invalid(self.db, InvalidCause::Other); }; - let (pat_ty, expected_fields) = match resolve_path_in_pat(self.db, &self.env, *path, pat) { + let mut path_data = match resolve_path_in_pat(self, *path, pat) { ResolvedPathInPat::Data(data) => { if data.is_tuple_variant(self.db) { - let ty = data.ty(self.db, &mut self.table); - let field_tys = data.field_tys(self.db); - - (ty, Some(field_tys)) + data } else { let diag = BodyDiag::tuple_variant_expected_in_pat( self.db, @@ -164,8 +160,7 @@ impl<'db> TyChecker<'db> { Some(data), ); FuncBodyDiagAccumulator::push(self.db, diag.into()); - - (TyId::invalid(self.db, InvalidCause::Other), None) + return TyId::invalid(self.db, InvalidCause::Other); } } @@ -177,26 +172,22 @@ impl<'db> TyChecker<'db> { ); FuncBodyDiagAccumulator::push(self.db, diag.into()); - (TyId::invalid(self.db, InvalidCause::Other), None) + return TyId::invalid(self.db, InvalidCause::Other); } ResolvedPathInPat::Diag(diag) => { FuncBodyDiagAccumulator::push(self.db, diag); - (TyId::invalid(self.db, InvalidCause::Other), None) + return TyId::invalid(self.db, InvalidCause::Other); } - ResolvedPathInPat::Invalid => (TyId::invalid(self.db, InvalidCause::Other), None), + ResolvedPathInPat::Invalid => { + return TyId::invalid(self.db, InvalidCause::Other); + } }; - let Some(expected_fields) = expected_fields else { - args.iter().for_each(|&pat| { - self.env - .type_pat(pat, TyId::invalid(self.db, InvalidCause::Other)); - }); - - return pat_ty; - }; + let pat_ty = path_data.ty(self.db); + let expected_fields = path_data.field_tys(self.db); let (actual_args, rest_range) = self.unpack_rest_pat(args, Some(expected_fields.len())); if actual_args.len() != expected_fields.len() { @@ -232,6 +223,139 @@ impl<'db> TyChecker<'db> { pat_ty } + fn check_record_pat(&mut self, pat: PatId, pat_data: &Pat) -> TyId { + let Pat::Record(Partial::Present(path), fields) = pat_data else { + return TyId::invalid(self.db, InvalidCause::Other); + }; + + let mut path_data = match resolve_path_in_pat(self, *path, pat) { + ResolvedPathInPat::Data(data) => { + if data.is_record(self.db) { + data + } else { + let diag = BodyDiag::record_variant_expected_in_pat( + self.db, + pat.lazy_span(self.body()).into(), + Some(data), + ); + FuncBodyDiagAccumulator::push(self.db, diag.into()); + + return TyId::invalid(self.db, InvalidCause::Other); + } + } + + ResolvedPathInPat::NewBinding(_) => { + let diag = BodyDiag::record_variant_expected_in_pat( + self.db, + pat.lazy_span(self.body()).into(), + None, + ); + FuncBodyDiagAccumulator::push(self.db, diag.into()); + + return TyId::invalid(self.db, InvalidCause::Other); + } + + ResolvedPathInPat::Diag(diag) => { + FuncBodyDiagAccumulator::push(self.db, diag); + + return TyId::invalid(self.db, InvalidCause::Other); + } + + ResolvedPathInPat::Invalid => return TyId::invalid(self.db, InvalidCause::Other), + }; + + let hir_db = self.db.as_hir_db(); + let mut seen_fields = FxHashMap::default(); + let mut contains_rest = false; + let mut contains_invalid_field = false; + + let pat_span = pat.lazy_span(self.body()).into_record_pat(); + + for (i, field_pat) in fields.iter().enumerate() { + let field_pat_span = pat_span.fields().field(i); + + if field_pat.pat.is_rest(hir_db, self.body()) { + if contains_rest { + let diag = + BodyDiag::DuplicatedRestPat(field_pat.pat.lazy_span(self.body()).into()); + FuncBodyDiagAccumulator::push(self.db, diag.into()); + continue; + } + + contains_rest = true; + continue; + } + + let expected_ty = match field_pat.label(hir_db, self.body()) { + Some(label) => match seen_fields.entry(label) { + Entry::Occupied(i) => { + let first_use = pat_span.fields().field(*i.get()).name(); + let diag = BodyDiag::DuplicatedRecordFieldBind { + primary: pat_span.clone().into(), + first_use: first_use.into(), + name: label, + }; + FuncBodyDiagAccumulator::push(self.db, diag.into()); + contains_invalid_field = true; + + TyId::invalid(self.db, InvalidCause::Other) + } + + Entry::Vacant(entry) => { + entry.insert(i); + if let Some(ty) = path_data.record_field_ty(self.db, label) { + ty + } else { + let diag = BodyDiag::record_field_not_found( + self.db, + field_pat_span.into(), + &path_data, + label, + ); + FuncBodyDiagAccumulator::push(self.db, diag.into()); + contains_invalid_field = true; + + TyId::invalid(self.db, InvalidCause::Other) + } + } + }, + + None => { + let diag = BodyDiag::ExplicitLabelExpectedInRecord { + primary: field_pat_span.into(), + hint: path_data.initializer_hint(self.db), + }; + FuncBodyDiagAccumulator::push(self.db, diag.into()); + contains_invalid_field = true; + + TyId::invalid(self.db, InvalidCause::Other) + } + }; + + self.check_pat(field_pat.pat, expected_ty); + } + + // Check for missing fields if no rest pat in the record. + if !contains_rest && !contains_invalid_field { + let expected_labels = path_data.record_labels(self.db); + let found = seen_fields.keys().copied().collect::>(); + let missing_fields: BTreeSet = + expected_labels.difference(&found).copied().collect(); + + if !missing_fields.is_empty() { + let diag = BodyDiag::MissingRecordFields { + primary: pat_span.into(), + missing_fields, + hint: path_data.initializer_hint(self.db), + }; + + FuncBodyDiagAccumulator::push(self.db, diag.into()); + } + } + + path_data.ty(self.db) + } + fn unpack_rest_pat( &mut self, pat_tup: &[PatId], @@ -271,33 +395,29 @@ impl<'db> TyChecker<'db> { } } -fn resolve_path_in_pat( - db: &dyn HirAnalysisDb, - env: &TyCheckEnv, - path: PathId, - pat: PatId, -) -> ResolvedPathInPat { - PathResolver { db, env, pat, path }.resolve_path() +fn resolve_path_in_pat(tc: &mut TyChecker, path: PathId, pat: PatId) -> ResolvedPathInPat { + PathResolver { tc, pat, path }.resolve_path() } -struct PathResolver<'db, 'env> { - db: &'db dyn HirAnalysisDb, - env: &'env TyCheckEnv<'db>, +struct PathResolver<'db, 'tc> { + tc: &'tc mut TyChecker<'db>, pat: PatId, path: PathId, } impl<'db, 'env> PathResolver<'db, 'env> { - fn resolve_path(&self) -> ResolvedPathInPat { - let early_resolved_path = resolve_path_early(self.db, self.path, self.env.scope()); + fn resolve_path(&mut self) -> ResolvedPathInPat { + let hir_db = self.tc.db.as_hir_db(); + + let early_resolved_path = resolve_path_early(self.tc.db, self.path, self.tc.env.scope()); let resolved = self.resolve_early_resolved_path(early_resolved_path); - if !self.path.is_ident(self.db.as_hir_db()) { + if !self.path.is_ident(hir_db) { resolved } else { match resolved { ResolvedPathInPat::Diag(_) | ResolvedPathInPat::Invalid => { - if let Some(ident) = self.path.last_segment(self.db.as_hir_db()).to_opt() { + if let Some(ident) = self.path.last_segment(hir_db).to_opt() { ResolvedPathInPat::NewBinding(ident) } else { resolved @@ -309,8 +429,8 @@ impl<'db, 'env> PathResolver<'db, 'env> { } } - fn resolve_early_resolved_path(&self, early: EarlyResolvedPath) -> ResolvedPathInPat { - let hir_db = self.db.as_hir_db(); + fn resolve_early_resolved_path(&mut self, early: EarlyResolvedPath) -> ResolvedPathInPat { + let hir_db = self.tc.db.as_hir_db(); // Try to resolve the partially resolved path as an enum variant. let early = match early { @@ -320,7 +440,7 @@ impl<'db, 'env> PathResolver<'db, 'env> { } if res.is_enum() && unresolved_from + 1 == self.path.len(hir_db) => { let segments = &self.path.segments(hir_db)[unresolved_from..]; let scope = res.scope().unwrap(); - resolve_segments_early(self.db, segments, scope) + resolve_segments_early(self.tc.db, segments, scope) } _ => early, @@ -337,7 +457,7 @@ impl<'db, 'env> PathResolver<'db, 'env> { } } - fn resolve_bucket(&self, bucket: NameResBucket) -> ResolvedPathInPat { + fn resolve_bucket(&mut self, bucket: NameResBucket) -> ResolvedPathInPat { if let Ok(res) = bucket.pick(NameDomain::Value) { let res = self.resolve_name_res(res); if !matches!(res, ResolvedPathInPat::Diag(_) | ResolvedPathInPat::Invalid) { @@ -351,16 +471,26 @@ impl<'db, 'env> PathResolver<'db, 'env> { } } - fn resolve_name_res(&self, res: &NameRes) -> ResolvedPathInPat { + fn resolve_name_res(&mut self, res: &NameRes) -> ResolvedPathInPat { match res.kind { NameResKind::Scope(ScopeId::Item(ItemKind::Struct(struct_))) => { - let adt = lower_adt(self.db, AdtRefId::from_struct(self.db, struct_)); - ResolvedPathInPat::Data(ResolvedPathData::Adt(adt, self.path)) + let adt = lower_adt(self.tc.db, AdtRefId::from_struct(self.tc.db, struct_)); + let data = + ResolvedPathData::new_adt(self.tc.db, &mut self.tc.table, adt, self.path); + + ResolvedPathInPat::Data(data) } NameResKind::Scope(ScopeId::Variant(parent, idx)) => { let enum_: Enum = parent.try_into().unwrap(); - let data = ResolvedPathData::Variant(enum_, idx, self.path); + let data = ResolvedPathData::new_variant( + self.tc.db, + &mut self.tc.table, + enum_, + idx, + self.path, + ); + ResolvedPathInPat::Data(data) } @@ -369,7 +499,7 @@ impl<'db, 'env> PathResolver<'db, 'env> { primary: self.path_span().into(), resolved: res .scope() - .and_then(|scope| scope.name_span(self.db.as_hir_db())), + .and_then(|scope| scope.name_span(self.tc.db.as_hir_db())), }; ResolvedPathInPat::Diag(diag.into()) @@ -378,11 +508,12 @@ impl<'db, 'env> PathResolver<'db, 'env> { } fn path_span(&self) -> LazyPathSpan { - let Partial::Present(pat_data) = self.pat.data(self.db.as_hir_db(), self.env.body()) else { + let Partial::Present(pat_data) = self.pat.data(self.tc.db.as_hir_db(), self.tc.env.body()) + else { unreachable!() }; - let pat_span = self.pat.lazy_span(self.env.body()); + let pat_span = self.pat.lazy_span(self.tc.env.body()); match pat_data { Pat::Path(_) => pat_span.into_path_pat().path(), diff --git a/crates/hir-analysis/src/ty/ty_def.rs b/crates/hir-analysis/src/ty/ty_def.rs index 19af386a1..ab1c37079 100644 --- a/crates/hir-analysis/src/ty/ty_def.rs +++ b/crates/hir-analysis/src/ty/ty_def.rs @@ -610,8 +610,6 @@ impl FuncDef { /// represents a variant. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct AdtFieldList { - name: Partial, - /// Fields of the variant. /// If the adt is an struct or contract, /// the length of the vector is always 1. @@ -640,8 +638,8 @@ impl AdtFieldList { self.tys.len() } - pub(super) fn new(name: Partial, tys: Vec>, scope: ScopeId) -> Self { - Self { name, tys, scope } + pub(super) fn new(tys: Vec>, scope: ScopeId) -> Self { + Self { tys, scope } } } diff --git a/crates/hir-analysis/src/ty/ty_lower.rs b/crates/hir-analysis/src/ty/ty_lower.rs index dc4dbbba8..61decce2b 100644 --- a/crates/hir-analysis/src/ty/ty_lower.rs +++ b/crates/hir-analysis/src/ty/ty_lower.rs @@ -498,10 +498,13 @@ impl<'db> AdtTyBuilder<'db> { fn collect_field_types(&mut self, fields: FieldDefListId) { let scope = self.adt.scope(self.db); - fields.data(self.db.as_hir_db()).iter().for_each(|field| { - let variant = AdtFieldList::new(field.name, vec![field.ty], scope); - self.variants.push(variant); - }) + let fields = fields + .data(self.db.as_hir_db()) + .iter() + .map(|field| field.ty) + .collect(); + + self.variants.push(AdtFieldList::new(fields, scope)); } fn collect_enum_variant_types(&mut self, variants: VariantDefListId) { @@ -524,7 +527,7 @@ impl<'db> AdtTyBuilder<'db> { VariantKind::Unit => vec![], }; - let variant = AdtFieldList::new(variant.name, tys, scope); + let variant = AdtFieldList::new(tys, scope); self.variants.push(variant) }) } diff --git a/crates/hir-analysis/test_files/ty_check/pat/record.fe b/crates/hir-analysis/test_files/ty_check/pat/record.fe new file mode 100644 index 000000000..b437f0fba --- /dev/null +++ b/crates/hir-analysis/test_files/ty_check/pat/record.fe @@ -0,0 +1,21 @@ +pub struct S { + x: i32, + y: T, +} + +pub enum E { + Variant{x: i32, y: T}, +} + +pub enum Unit { + U +} + +pub fn foo() { + let S {x, y}: S + let S {x, y: Unit::U} + let S {y: Unit::U, x} + let E::Variant {x, y}: E + let E::Variant {x, y: Unit::U} + let E::Variant {y: Unit::U, x} +} \ No newline at end of file diff --git a/crates/hir-analysis/test_files/ty_check/pat/record.snap b/crates/hir-analysis/test_files/ty_check/pat/record.snap new file mode 100644 index 000000000..4d9985b9e --- /dev/null +++ b/crates/hir-analysis/test_files/ty_check/pat/record.snap @@ -0,0 +1,127 @@ +--- +source: crates/hir-analysis/tests/ty_check.rs +expression: res +input_file: crates/hir-analysis/test_files/ty_check/pat/record.fe +--- +note: + ┌─ test_file.fe:14:14 + │ +14 │ pub fn foo() { + │ ╭──────────────^ +15 │ │ let S {x, y}: S +16 │ │ let S {x, y: Unit::U} +17 │ │ let S {y: Unit::U, x} + · │ +20 │ │ let E::Variant {y: Unit::U, x} +21 │ │ } + │ ╰─^ () + +note: + ┌─ test_file.fe:15:9 + │ +15 │ let S {x, y}: S + │ ^^^^^^^^ S + +note: + ┌─ test_file.fe:15:12 + │ +15 │ let S {x, y}: S + │ ^ i32 + +note: + ┌─ test_file.fe:15:15 + │ +15 │ let S {x, y}: S + │ ^ u32 + +note: + ┌─ test_file.fe:16:9 + │ +16 │ let S {x, y: Unit::U} + │ ^^^^^^^^^^^^^^^^^ S + +note: + ┌─ test_file.fe:16:12 + │ +16 │ let S {x, y: Unit::U} + │ ^ i32 + +note: + ┌─ test_file.fe:16:18 + │ +16 │ let S {x, y: Unit::U} + │ ^^^^^^^ Unit + +note: + ┌─ test_file.fe:17:9 + │ +17 │ let S {y: Unit::U, x} + │ ^^^^^^^^^^^^^^^^^ S + +note: + ┌─ test_file.fe:17:15 + │ +17 │ let S {y: Unit::U, x} + │ ^^^^^^^ Unit + +note: + ┌─ test_file.fe:17:24 + │ +17 │ let S {y: Unit::U, x} + │ ^ i32 + +note: + ┌─ test_file.fe:18:9 + │ +18 │ let E::Variant {x, y}: E + │ ^^^^^^^^^^^^^^^^^ E + +note: + ┌─ test_file.fe:18:21 + │ +18 │ let E::Variant {x, y}: E + │ ^ i32 + +note: + ┌─ test_file.fe:18:24 + │ +18 │ let E::Variant {x, y}: E + │ ^ u32 + +note: + ┌─ test_file.fe:19:9 + │ +19 │ let E::Variant {x, y: Unit::U} + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ E + +note: + ┌─ test_file.fe:19:21 + │ +19 │ let E::Variant {x, y: Unit::U} + │ ^ i32 + +note: + ┌─ test_file.fe:19:27 + │ +19 │ let E::Variant {x, y: Unit::U} + │ ^^^^^^^ Unit + +note: + ┌─ test_file.fe:20:9 + │ +20 │ let E::Variant {y: Unit::U, x} + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ E + +note: + ┌─ test_file.fe:20:24 + │ +20 │ let E::Variant {y: Unit::U, x} + │ ^^^^^^^ Unit + +note: + ┌─ test_file.fe:20:33 + │ +20 │ let E::Variant {y: Unit::U, x} + │ ^ i32 + + diff --git a/crates/hir/src/hir_def/item.rs b/crates/hir/src/hir_def/item.rs index 96155bd77..bf58ceaa6 100644 --- a/crates/hir/src/hir_def/item.rs +++ b/crates/hir/src/hir_def/item.rs @@ -979,6 +979,18 @@ pub struct FieldDefListId { } impl FieldDefListId { + pub fn get_field(self, db: &dyn HirDb, name: IdentId) -> Option<&FieldDef> { + self.data(db) + .iter() + .find(|field| field.name.to_opt() == Some(name)) + } + + pub fn field_idx(self, db: &dyn HirDb, name: IdentId) -> Option { + self.data(db) + .iter() + .position(|field| field.name.to_opt() == Some(name)) + } + fn format_initializer_args(self, db: &dyn HirDb) -> String { let args = self .data(db) diff --git a/crates/hir/src/hir_def/pat.rs b/crates/hir/src/hir_def/pat.rs index b46b50c65..5a5c2aff5 100644 --- a/crates/hir/src/hir_def/pat.rs +++ b/crates/hir/src/hir_def/pat.rs @@ -38,3 +38,18 @@ pub struct RecordPatField { pub label: Partial, pub pat: PatId, } + +impl RecordPatField { + pub fn label(&self, db: &dyn HirDb, body: Body) -> Option { + if let Partial::Present(label) = self.label { + return Some(label); + } + + match self.pat.data(db, body) { + Partial::Present(Pat::Path(Partial::Present(path))) if path.is_ident(db) => { + path.last_segment(db).to_opt() + } + _ => None, + } + } +} diff --git a/crates/uitest/fixtures/ty_check/pat/path_tuple.snap b/crates/uitest/fixtures/ty_check/pat/path_tuple.snap index 9687a9f9b..fe07c178c 100644 --- a/crates/uitest/fixtures/ty_check/pat/path_tuple.snap +++ b/crates/uitest/fixtures/ty_check/pat/path_tuple.snap @@ -21,25 +21,25 @@ error[8-0005]: expected tuple variant │ expected tuple variant here, but found unit variant │ Consider using `Foo::Variant2` instead -error[8-0006]: field count mismatch +error[8-0007]: field count mismatch ┌─ path_tuple.fe:7:9 │ 7 │ let Foo::Variant(x, y, z) │ ^^^^^^^^^^^^^^^^^^^^^ expected 2 fields here, but 3 given -error[8-0006]: field count mismatch +error[8-0007]: field count mismatch ┌─ path_tuple.fe:8:9 │ 8 │ let Foo::Variant(.., x, y, z) │ ^^^^^^^^^^^^^^^^^^^^^^^^^ expected 2 fields here, but 3 given -error[8-0006]: field count mismatch +error[8-0007]: field count mismatch ┌─ path_tuple.fe:9:9 │ 9 │ let Foo::Variant(x, .., y, z) │ ^^^^^^^^^^^^^^^^^^^^^^^^^ expected 2 fields here, but 3 given -error[8-0006]: field count mismatch +error[8-0007]: field count mismatch ┌─ path_tuple.fe:10:9 │ 10 │ let Foo::Variant(x, y, z, ..) diff --git a/crates/uitest/fixtures/ty_check/pat/record.fe b/crates/uitest/fixtures/ty_check/pat/record.fe new file mode 100644 index 000000000..68c7b915d --- /dev/null +++ b/crates/uitest/fixtures/ty_check/pat/record.fe @@ -0,0 +1,23 @@ +pub struct Foo { + x: i32, + y: u32, +} + +pub enum Bar { + Unit + Variant {x: i32, y: u32} +} + +fn foo() { + let Foo {x, x} + let Foo {x, .., y, ..} + let Foo {x, z} + let Foo {x, Bar::Unit} + let Foo {x} + + let Bar::Variant {x, x} + let Bar::Variant {x, .., y, ..} + let Bar::Variant {x, z} + let Bar::Variant {x, Bar::Unit} + let Bar::Variant {} +} \ No newline at end of file diff --git a/crates/uitest/fixtures/ty_check/pat/record.snap b/crates/uitest/fixtures/ty_check/pat/record.snap new file mode 100644 index 000000000..11d5fda8d --- /dev/null +++ b/crates/uitest/fixtures/ty_check/pat/record.snap @@ -0,0 +1,90 @@ +--- +source: crates/uitest/tests/ty_check.rs +expression: diags +input_file: crates/uitest/fixtures/ty_check/pat/record.fe +--- +error[1-0001]: expected comma after enum variant definition + ┌─ record.fe:7:9 + │ +7 │ Unit + │ ^ expected comma after enum variant definition + +error[8-0002]: duplicated `..` found + ┌─ record.fe:13:24 + │ +13 │ let Foo {x, .., y, ..} + │ ^^ `..` can be used only once + +error[8-0002]: duplicated `..` found + ┌─ record.fe:19:33 + │ +19 │ let Bar::Variant {x, .., y, ..} + │ ^^ `..` can be used only once + +error[8-0008]: duplicated record field binding + ┌─ record.fe:12:9 + │ +12 │ let Foo {x, x} + │ ^^^^^^^^^^ duplicated field binding `x` + +error[8-0008]: duplicated record field binding + ┌─ record.fe:18:9 + │ +18 │ let Bar::Variant {x, x} + │ ^^^^^^^^^^^^^^^^^^^ duplicated field binding `x` + +error[8-0009]: specified field not found + ┌─ record.fe:14:17 + │ + 1 │ pub struct Foo { + │ --- `Foo` is defined here + · +14 │ let Foo {x, z} + │ ^ field `z` not found + +error[8-0009]: specified field not found + ┌─ record.fe:20:26 + │ + 8 │ Variant {x: i32, y: u32} + │ ------------------------ `Variant` is defined here + · +20 │ let Bar::Variant {x, z} + │ ^ field `z` not found + +error[8-0010]: explicit label is required + ┌─ record.fe:15:17 + │ +15 │ let Foo {x, Bar::Unit} + │ ^^^^^^^^^ + │ │ + │ explicit label is required + │ Consider using `Foo { x, y }` instead + +error[8-0010]: explicit label is required + ┌─ record.fe:21:26 + │ +21 │ let Bar::Variant {x, Bar::Unit} + │ ^^^^^^^^^ + │ │ + │ explicit label is required + │ Consider using `Bar::Variant { x, y }` instead + +error[8-0011]: all fields are not given + ┌─ record.fe:16:9 + │ +16 │ let Foo {x} + │ ^^^^^^^ + │ │ + │ missing `y` + │ Consider using `Foo { x, y }` instead + +error[8-0011]: all fields are not given + ┌─ record.fe:22:9 + │ +22 │ let Bar::Variant {} + │ ^^^^^^^^^^^^^^^ + │ │ + │ missing `x, y` + │ Consider using `Bar::Variant { x, y }` instead + +