From 82896b2cc408f9761a74216b75f27eb9f573aac2 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Wed, 4 Dec 2024 15:52:15 +0200 Subject: [PATCH 1/2] Move ty lowering diagnostic definitions into a separate module To keep them organized. --- crates/hir-ty/src/infer.rs | 2 +- crates/hir-ty/src/lib.rs | 6 +++--- crates/hir-ty/src/lower.rs | 28 +++----------------------- crates/hir-ty/src/lower/diagnostics.rs | 27 +++++++++++++++++++++++++ 4 files changed, 34 insertions(+), 29 deletions(-) create mode 100644 crates/hir-ty/src/lower/diagnostics.rs diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index dbee5a1a91..5720539d34 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -58,7 +58,7 @@ use crate::{ fold_tys, generics::Generics, infer::{coerce::CoerceMany, expr::ExprIsRead, unify::InferenceTable}, - lower::{ImplTraitLoweringMode, TyLoweringDiagnostic}, + lower::{diagnostics::TyLoweringDiagnostic, ImplTraitLoweringMode}, mir::MirSpan, to_assoc_type_id, traits::FnTrait, diff --git a/crates/hir-ty/src/lib.rs b/crates/hir-ty/src/lib.rs index 8bb90ca31e..224fcf313a 100644 --- a/crates/hir-ty/src/lib.rs +++ b/crates/hir-ty/src/lib.rs @@ -88,10 +88,10 @@ pub use infer::{ PointerCast, }; pub use interner::Interner; +pub use lower::diagnostics::*; pub use lower::{ - associated_type_shorthand_candidates, GenericArgsProhibitedReason, ImplTraitLoweringMode, - ParamLoweringMode, TyDefId, TyLoweringContext, TyLoweringDiagnostic, TyLoweringDiagnosticKind, - ValueTyDefId, + associated_type_shorthand_candidates, ImplTraitLoweringMode, ParamLoweringMode, TyDefId, + TyLoweringContext, ValueTyDefId, }; pub use mapping::{ from_assoc_type_id, from_chalk_trait_id, from_foreign_def_id, from_placeholder_idx, diff --git a/crates/hir-ty/src/lower.rs b/crates/hir-ty/src/lower.rs index b23f2749ab..2610bb704e 100644 --- a/crates/hir-ty/src/lower.rs +++ b/crates/hir-ty/src/lower.rs @@ -5,6 +5,8 @@ //! - Building the type for an item: This happens through the `ty` query. //! //! This usually involves resolving names, collecting generic arguments etc. +pub(crate) mod diagnostics; + use std::{ cell::OnceCell, iter, mem, @@ -59,6 +61,7 @@ use crate::{ db::HirDatabase, error_lifetime, generics::{generics, trait_self_param_idx, Generics}, + lower::diagnostics::*, make_binders, mapping::{from_chalk_trait_id, lt_to_placeholder_idx, ToChalk}, static_lifetime, to_assoc_type_id, to_chalk_trait_id, to_placeholder_idx, @@ -102,31 +105,6 @@ impl ImplTraitLoweringState { } } -type TypeSource = Either; - -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct TyLoweringDiagnostic { - pub source: TypeSource, - pub kind: TyLoweringDiagnosticKind, -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum TyLoweringDiagnosticKind { - GenericArgsProhibited { segment: u32, reason: GenericArgsProhibitedReason }, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum GenericArgsProhibitedReason { - Module, - TyParam, - SelfTy, - PrimitiveTy, - /// When there is a generic enum, within the expression `Enum::Variant`, - /// either `Enum` or `Variant` are allowed to have generic arguments, but not both. - // FIXME: This is not used now but it should be. - EnumVariant, -} - #[derive(Debug)] pub struct TyLoweringContext<'a> { pub db: &'a dyn HirDatabase, diff --git a/crates/hir-ty/src/lower/diagnostics.rs b/crates/hir-ty/src/lower/diagnostics.rs new file mode 100644 index 0000000000..61fedc8c3a --- /dev/null +++ b/crates/hir-ty/src/lower/diagnostics.rs @@ -0,0 +1,27 @@ +use either::Either; +use hir_def::type_ref::TypeRefId; + +type TypeSource = Either; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct TyLoweringDiagnostic { + pub source: TypeSource, + pub kind: TyLoweringDiagnosticKind, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum TyLoweringDiagnosticKind { + GenericArgsProhibited { segment: u32, reason: GenericArgsProhibitedReason }, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum GenericArgsProhibitedReason { + Module, + TyParam, + SelfTy, + PrimitiveTy, + /// When there is a generic enum, within the expression `Enum::Variant`, + /// either `Enum` or `Variant` are allowed to have generic arguments, but not both. + // FIXME: This is not used now but it should be. + EnumVariant, +} From cc11e1a796787e89b5091cb05e94dbbd6cb20559 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Sun, 22 Dec 2024 18:07:27 +0200 Subject: [PATCH 2/2] Unify handling of path diagnostics in hir-ty Because it was a mess. Previously, pretty much you had to handle all path diagnostics manually: remember to check for them and handle them. Now, we wrap the resolver in `TyLoweringContext` and ensure proper error reporting. This means that you don't have to worry about them: most of the things are handled automatically, and things that cannot will create a compile-time error (forcing you top `drop(ty_lowering_context);`) if forgotten, instead of silently dropping the diagnostics. The real place for error reporting is in the hir-def resolver, because there are other things resolving, both in hir-ty and in hir-def, and they all need to ensure proper diagnostics. But this is a good start, and future compatible. This commit also ensures proper path diagnostics for value/pattern paths, which is why it's marked "feat". --- crates/hir-def/src/nameres.rs | 8 +- crates/hir-def/src/nameres/collector.rs | 4 +- crates/hir-def/src/nameres/path_resolution.rs | 79 +++- crates/hir-def/src/path.rs | 15 + crates/hir-def/src/resolver.rs | 103 +++-- crates/hir-ty/src/infer.rs | 127 ++++--- crates/hir-ty/src/infer/diagnostics.rs | 128 +++++++ crates/hir-ty/src/infer/expr.rs | 15 +- crates/hir-ty/src/infer/pat.rs | 4 +- crates/hir-ty/src/infer/path.rs | 54 +-- crates/hir-ty/src/lower.rs | 355 +++++++++++++----- crates/hir-ty/src/lower/diagnostics.rs | 11 +- crates/hir/src/diagnostics.rs | 52 ++- .../src/handlers/generic_args_prohibited.rs | 140 +++++++ 14 files changed, 848 insertions(+), 247 deletions(-) create mode 100644 crates/hir-ty/src/infer/diagnostics.rs diff --git a/crates/hir-def/src/nameres.rs b/crates/hir-def/src/nameres.rs index 9bd7d38f0a..39d383f015 100644 --- a/crates/hir-def/src/nameres.rs +++ b/crates/hir-def/src/nameres.rs @@ -85,6 +85,8 @@ use crate::{ FxIndexMap, LocalModuleId, Lookup, MacroExpander, MacroId, ModuleId, ProcMacroId, UseId, }; +pub use self::path_resolution::ResolvePathResultPrefixInfo; + const PREDEFINED_TOOLS: &[SmolStr] = &[ SmolStr::new_static("clippy"), SmolStr::new_static("rustfmt"), @@ -615,13 +617,15 @@ impl DefMap { (res.resolved_def, res.segment_index) } + /// The first `Option` points at the `Enum` segment in case of `Enum::Variant`, the second + /// points at the unresolved segments. pub(crate) fn resolve_path_locally( &self, db: &dyn DefDatabase, original_module: LocalModuleId, path: &ModPath, shadow: BuiltinShadowMode, - ) -> (PerNs, Option) { + ) -> (PerNs, Option, ResolvePathResultPrefixInfo) { let res = self.resolve_path_fp_with_macro_single( db, ResolveMode::Other, @@ -630,7 +634,7 @@ impl DefMap { shadow, None, // Currently this function isn't used for macro resolution. ); - (res.resolved_def, res.segment_index) + (res.resolved_def, res.segment_index, res.prefix_info) } /// Ascends the `DefMap` hierarchy and calls `f` with every `DefMap` and containing module. diff --git a/crates/hir-def/src/nameres/collector.rs b/crates/hir-def/src/nameres/collector.rs index f391cc41c1..318a8d5aa1 100644 --- a/crates/hir-def/src/nameres/collector.rs +++ b/crates/hir-def/src/nameres/collector.rs @@ -38,7 +38,7 @@ use crate::{ attr_resolution::{attr_macro_as_call_id, derive_macro_as_call_id}, diagnostics::DefDiagnostic, mod_resolution::ModDir, - path_resolution::ReachedFixedPoint, + path_resolution::{ReachedFixedPoint, ResolvePathResultPrefixInfo}, proc_macro::{parse_macro_name_and_helper_attrs, ProcMacroDef, ProcMacroKind}, sub_namespace_match, BuiltinShadowMode, DefMap, MacroSubNs, ModuleData, ModuleOrigin, ResolveMode, @@ -797,7 +797,7 @@ impl DefCollector<'_> { return PartialResolvedImport::Unresolved; } - if res.from_differing_crate { + if let ResolvePathResultPrefixInfo::DifferingCrate = res.prefix_info { return PartialResolvedImport::Resolved( def.filter_visibility(|v| matches!(v, Visibility::Public)), ); diff --git a/crates/hir-def/src/nameres/path_resolution.rs b/crates/hir-def/src/nameres/path_resolution.rs index 8eb195680d..9573697a87 100644 --- a/crates/hir-def/src/nameres/path_resolution.rs +++ b/crates/hir-def/src/nameres/path_resolution.rs @@ -43,21 +43,34 @@ pub(super) struct ResolvePathResult { pub(super) resolved_def: PerNs, pub(super) segment_index: Option, pub(super) reached_fixedpoint: ReachedFixedPoint, - pub(super) from_differing_crate: bool, + pub(super) prefix_info: ResolvePathResultPrefixInfo, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ResolvePathResultPrefixInfo { + None, + DifferingCrate, + /// Path of the form `Enum::Variant` (and not `Variant` alone). + Enum, } impl ResolvePathResult { fn empty(reached_fixedpoint: ReachedFixedPoint) -> ResolvePathResult { - ResolvePathResult::new(PerNs::none(), reached_fixedpoint, None, false) + ResolvePathResult::new( + PerNs::none(), + reached_fixedpoint, + None, + ResolvePathResultPrefixInfo::None, + ) } fn new( resolved_def: PerNs, reached_fixedpoint: ReachedFixedPoint, segment_index: Option, - from_differing_crate: bool, + prefix_info: ResolvePathResultPrefixInfo, ) -> ResolvePathResult { - ResolvePathResult { resolved_def, segment_index, reached_fixedpoint, from_differing_crate } + ResolvePathResult { resolved_def, segment_index, reached_fixedpoint, prefix_info } } } @@ -157,7 +170,17 @@ impl DefMap { if result.reached_fixedpoint == ReachedFixedPoint::No { result.reached_fixedpoint = new.reached_fixedpoint; } - result.from_differing_crate |= new.from_differing_crate; + result.prefix_info = match (result.prefix_info, new.prefix_info) { + (ResolvePathResultPrefixInfo::None, it) => it, + (ResolvePathResultPrefixInfo::DifferingCrate, _) => { + ResolvePathResultPrefixInfo::DifferingCrate + } + ( + ResolvePathResultPrefixInfo::Enum, + ResolvePathResultPrefixInfo::DifferingCrate, + ) => ResolvePathResultPrefixInfo::DifferingCrate, + (ResolvePathResultPrefixInfo::Enum, _) => ResolvePathResultPrefixInfo::Enum, + }; result.segment_index = match (result.segment_index, new.segment_index) { (Some(idx), None) => Some(idx), (Some(old), Some(new)) => Some(old.max(new)), @@ -403,14 +426,14 @@ impl DefMap { fn resolve_remaining_segments<'a>( &self, - segments: impl Iterator, + mut segments: impl Iterator, mut curr_per_ns: PerNs, path: &ModPath, db: &dyn DefDatabase, shadow: BuiltinShadowMode, original_module: LocalModuleId, ) -> ResolvePathResult { - for (i, segment) in segments { + while let Some((i, segment)) = segments.next() { let curr = match curr_per_ns.take_types_full() { Some(r) => r, None => { @@ -443,7 +466,7 @@ impl DefMap { def, ReachedFixedPoint::Yes, s.map(|s| s + i), - true, + ResolvePathResultPrefixInfo::DifferingCrate, ); } @@ -488,17 +511,28 @@ impl DefMap { ), }) }); - match res { - Some(res) => res, - None => { - return ResolvePathResult::new( - PerNs::types(e.into(), curr.vis, curr.import), - ReachedFixedPoint::Yes, - Some(i), - false, - ) + // FIXME: Need to filter visibility here and below? Not sure. + return match res { + Some(res) => { + if segments.next().is_some() { + // Enum variants are in value namespace, segments left => no resolution. + ResolvePathResult::empty(ReachedFixedPoint::No) + } else { + ResolvePathResult::new( + res, + ReachedFixedPoint::Yes, + None, + ResolvePathResultPrefixInfo::Enum, + ) + } } - } + None => ResolvePathResult::new( + PerNs::types(e.into(), curr.vis, curr.import), + ReachedFixedPoint::Yes, + Some(i), + ResolvePathResultPrefixInfo::None, + ), + }; } s => { // could be an inherent method call in UFCS form @@ -513,7 +547,7 @@ impl DefMap { PerNs::types(s, curr.vis, curr.import), ReachedFixedPoint::Yes, Some(i), - false, + ResolvePathResultPrefixInfo::None, ); } }; @@ -522,7 +556,12 @@ impl DefMap { .filter_visibility(|vis| vis.is_visible_from_def_map(db, self, original_module)); } - ResolvePathResult::new(curr_per_ns, ReachedFixedPoint::Yes, None, false) + ResolvePathResult::new( + curr_per_ns, + ReachedFixedPoint::Yes, + None, + ResolvePathResultPrefixInfo::None, + ) } fn resolve_name_in_module( diff --git a/crates/hir-def/src/path.rs b/crates/hir-def/src/path.rs index 44e132061a..e59c37104d 100644 --- a/crates/hir-def/src/path.rs +++ b/crates/hir-def/src/path.rs @@ -240,6 +240,7 @@ pub struct PathSegment<'a> { pub args_and_bindings: Option<&'a GenericArgs>, } +#[derive(Debug, Clone, Copy)] pub struct PathSegments<'a> { segments: &'a [Name], generic_args: Option<&'a [Option]>, @@ -259,6 +260,7 @@ impl<'a> PathSegments<'a> { pub fn last(&self) -> Option> { self.get(self.len().checked_sub(1)?) } + pub fn get(&self, idx: usize) -> Option> { let res = PathSegment { name: self.segments.get(idx)?, @@ -266,24 +268,37 @@ impl<'a> PathSegments<'a> { }; Some(res) } + pub fn skip(&self, len: usize) -> PathSegments<'a> { PathSegments { segments: self.segments.get(len..).unwrap_or(&[]), generic_args: self.generic_args.and_then(|it| it.get(len..)), } } + pub fn take(&self, len: usize) -> PathSegments<'a> { PathSegments { segments: self.segments.get(..len).unwrap_or(self.segments), generic_args: self.generic_args.map(|it| it.get(..len).unwrap_or(it)), } } + pub fn strip_last(&self) -> PathSegments<'a> { PathSegments { segments: self.segments.split_last().map_or(&[], |it| it.1), generic_args: self.generic_args.map(|it| it.split_last().map_or(&[][..], |it| it.1)), } } + + pub fn strip_last_two(&self) -> PathSegments<'a> { + PathSegments { + segments: self.segments.get(..self.segments.len().saturating_sub(2)).unwrap_or(&[]), + generic_args: self + .generic_args + .map(|it| it.get(..it.len().saturating_sub(2)).unwrap_or(&[])), + } + } + pub fn iter(&self) -> impl Iterator> { self.segments .iter() diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs index f4dfd42a30..4f15c8091c 100644 --- a/crates/hir-def/src/resolver.rs +++ b/crates/hir-def/src/resolver.rs @@ -21,7 +21,7 @@ use crate::{ hir::{BindingId, ExprId, LabelId}, item_scope::{BuiltinShadowMode, ImportId, ImportOrExternCrate, BUILTIN_SCOPE}, lang_item::LangItemTarget, - nameres::{DefMap, MacroSubNs}, + nameres::{DefMap, MacroSubNs, ResolvePathResultPrefixInfo}, path::{ModPath, Path, PathKind}, per_ns::PerNs, type_ref::{LifetimeRef, TypesMap}, @@ -263,25 +263,37 @@ impl Resolver { &self, db: &dyn DefDatabase, path: &Path, - mut hygiene_id: HygieneId, + hygiene_id: HygieneId, ) -> Option { + self.resolve_path_in_value_ns_with_prefix_info(db, path, hygiene_id).map(|(it, _)| it) + } + + pub fn resolve_path_in_value_ns_with_prefix_info( + &self, + db: &dyn DefDatabase, + path: &Path, + mut hygiene_id: HygieneId, + ) -> Option<(ResolveValueResult, ResolvePathResultPrefixInfo)> { let path = match path { Path::BarePath(mod_path) => mod_path, Path::Normal(it) => it.mod_path(), Path::LangItem(l, None) => { - return Some(ResolveValueResult::ValueNs( - match *l { - LangItemTarget::Function(it) => ValueNs::FunctionId(it), - LangItemTarget::Static(it) => ValueNs::StaticId(it), - LangItemTarget::Struct(it) => ValueNs::StructId(it), - LangItemTarget::EnumVariant(it) => ValueNs::EnumVariantId(it), - LangItemTarget::Union(_) - | LangItemTarget::ImplDef(_) - | LangItemTarget::TypeAlias(_) - | LangItemTarget::Trait(_) - | LangItemTarget::EnumId(_) => return None, - }, - None, + return Some(( + ResolveValueResult::ValueNs( + match *l { + LangItemTarget::Function(it) => ValueNs::FunctionId(it), + LangItemTarget::Static(it) => ValueNs::StaticId(it), + LangItemTarget::Struct(it) => ValueNs::StructId(it), + LangItemTarget::EnumVariant(it) => ValueNs::EnumVariantId(it), + LangItemTarget::Union(_) + | LangItemTarget::ImplDef(_) + | LangItemTarget::TypeAlias(_) + | LangItemTarget::Trait(_) + | LangItemTarget::EnumId(_) => return None, + }, + None, + ), + ResolvePathResultPrefixInfo::None, )) } Path::LangItem(l, Some(_)) => { @@ -296,7 +308,10 @@ impl Resolver { | LangItemTarget::ImplDef(_) | LangItemTarget::Static(_) => return None, }; - return Some(ResolveValueResult::Partial(type_ns, 1, None)); + return Some(( + ResolveValueResult::Partial(type_ns, 1, None), + ResolvePathResultPrefixInfo::None, + )); } }; let n_segments = path.segments().len(); @@ -326,9 +341,12 @@ impl Resolver { }); if let Some(e) = entry { - return Some(ResolveValueResult::ValueNs( - ValueNs::LocalBinding(e.binding()), - None, + return Some(( + ResolveValueResult::ValueNs( + ValueNs::LocalBinding(e.binding()), + None, + ), + ResolvePathResultPrefixInfo::None, )); } } @@ -350,14 +368,17 @@ impl Resolver { Scope::GenericParams { params, def } => { if let Some(id) = params.find_const_by_name(first_name, *def) { let val = ValueNs::GenericParam(id); - return Some(ResolveValueResult::ValueNs(val, None)); + return Some(( + ResolveValueResult::ValueNs(val, None), + ResolvePathResultPrefixInfo::None, + )); } } &Scope::ImplDefScope(impl_) => { if *first_name == sym::Self_.clone() { - return Some(ResolveValueResult::ValueNs( - ValueNs::ImplSelf(impl_), - None, + return Some(( + ResolveValueResult::ValueNs(ValueNs::ImplSelf(impl_), None), + ResolvePathResultPrefixInfo::None, )); } } @@ -377,22 +398,27 @@ impl Resolver { Scope::GenericParams { params, def } => { if let Some(id) = params.find_type_by_name(first_name, *def) { let ty = TypeNs::GenericParam(id); - return Some(ResolveValueResult::Partial(ty, 1, None)); + return Some(( + ResolveValueResult::Partial(ty, 1, None), + ResolvePathResultPrefixInfo::None, + )); } } &Scope::ImplDefScope(impl_) => { if *first_name == sym::Self_.clone() { - return Some(ResolveValueResult::Partial( - TypeNs::SelfType(impl_), - 1, - None, + return Some(( + ResolveValueResult::Partial(TypeNs::SelfType(impl_), 1, None), + ResolvePathResultPrefixInfo::None, )); } } Scope::AdtScope(adt) => { if *first_name == sym::Self_.clone() { let ty = TypeNs::AdtSelfType(*adt); - return Some(ResolveValueResult::Partial(ty, 1, None)); + return Some(( + ResolveValueResult::Partial(ty, 1, None), + ResolvePathResultPrefixInfo::None, + )); } } Scope::BlockScope(m) => { @@ -413,7 +439,10 @@ impl Resolver { // `use core::u16;`. if path.kind == PathKind::Plain && n_segments > 1 { if let Some(builtin) = BuiltinType::by_name(first_name) { - return Some(ResolveValueResult::Partial(TypeNs::BuiltinType(builtin), 1, None)); + return Some(( + ResolveValueResult::Partial(TypeNs::BuiltinType(builtin), 1, None), + ResolvePathResultPrefixInfo::None, + )); } } @@ -924,15 +953,15 @@ impl ModuleItemMap { &self, db: &dyn DefDatabase, path: &ModPath, - ) -> Option { - let (module_def, idx) = + ) -> Option<(ResolveValueResult, ResolvePathResultPrefixInfo)> { + let (module_def, unresolved_idx, prefix_info) = self.def_map.resolve_path_locally(db, self.module_id, path, BuiltinShadowMode::Other); - match idx { + match unresolved_idx { None => { let (value, import) = to_value_ns(module_def)?; - Some(ResolveValueResult::ValueNs(value, import)) + Some((ResolveValueResult::ValueNs(value, import), prefix_info)) } - Some(idx) => { + Some(unresolved_idx) => { let def = module_def.take_types_full()?; let ty = match def.def { ModuleDefId::AdtId(it) => TypeNs::AdtId(it), @@ -948,7 +977,7 @@ impl ModuleItemMap { | ModuleDefId::MacroId(_) | ModuleDefId::StaticId(_) => return None, }; - Some(ResolveValueResult::Partial(ty, idx, def.import)) + Some((ResolveValueResult::Partial(ty, unresolved_idx, def.import), prefix_info)) } } } @@ -958,7 +987,7 @@ impl ModuleItemMap { db: &dyn DefDatabase, path: &ModPath, ) -> Option<(TypeNs, Option, Option)> { - let (module_def, idx) = + let (module_def, idx, _) = self.def_map.resolve_path_locally(db, self.module_id, path, BuiltinShadowMode::Other); let (res, import) = to_type_ns(module_def)?; Some((res, idx, import)) diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 5720539d34..25bb3a76de 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -16,6 +16,7 @@ pub(crate) mod cast; pub(crate) mod closure; mod coerce; +mod diagnostics; mod expr; mod mutability; mod pat; @@ -57,15 +58,20 @@ use crate::{ db::HirDatabase, fold_tys, generics::Generics, - infer::{coerce::CoerceMany, expr::ExprIsRead, unify::InferenceTable}, + infer::{ + coerce::CoerceMany, + diagnostics::{Diagnostics, InferenceTyLoweringContext as TyLoweringContext}, + expr::ExprIsRead, + unify::InferenceTable, + }, lower::{diagnostics::TyLoweringDiagnostic, ImplTraitLoweringMode}, mir::MirSpan, to_assoc_type_id, traits::FnTrait, utils::{InTypeConstIdMetadata, UnevaluatedConstEvaluatorFolder}, AliasEq, AliasTy, Binders, ClosureId, Const, DomainGoal, GenericArg, Goal, ImplTraitId, - ImplTraitIdx, InEnvironment, Interner, Lifetime, OpaqueTyId, ParamLoweringMode, ProjectionTy, - Substitution, TraitEnvironment, Ty, TyBuilder, TyExt, + ImplTraitIdx, InEnvironment, Interner, Lifetime, OpaqueTyId, ParamLoweringMode, + PathLoweringDiagnostic, ProjectionTy, Substitution, TraitEnvironment, Ty, TyBuilder, TyExt, }; // This lint has a false positive here. See the link below for details. @@ -276,6 +282,10 @@ pub enum InferenceDiagnostic { source: InferenceTyDiagnosticSource, diag: TyLoweringDiagnostic, }, + PathDiagnostic { + node: ExprOrPatId, + diag: PathLoweringDiagnostic, + }, } /// A mismatch between an expected and an inferred type. @@ -442,6 +452,7 @@ pub struct InferenceResult { /// [`InferenceContext`] and store the tuples substitution there. This map is the reverse of /// that which allows us to resolve a [`TupleFieldId`]s type. pub tuple_field_access_types: FxHashMap, + /// During inference this field is empty and [`InferenceContext::diagnostics`] is filled instead. pub diagnostics: Vec, pub type_of_expr: ArenaMap, /// For each pattern record the type it resolves to. @@ -579,6 +590,8 @@ pub(crate) struct InferenceContext<'a> { pub(crate) db: &'a dyn HirDatabase, pub(crate) owner: DefWithBodyId, pub(crate) body: &'a Body, + /// Generally you should not resolve things via this resolver. Instead create a TyLoweringContext + /// and resolve the path via its methods. This will ensure proper error reporting. pub(crate) resolver: Resolver, generics: OnceCell>, table: unify::InferenceTable<'a>, @@ -620,6 +633,8 @@ pub(crate) struct InferenceContext<'a> { /// comment on `InferenceContext::sort_closures` closure_dependencies: FxHashMap>, deferred_closures: FxHashMap, ExprId)>>, + + diagnostics: Diagnostics, } #[derive(Clone, Debug)] @@ -701,6 +716,7 @@ impl<'a> InferenceContext<'a> { deferred_closures: FxHashMap::default(), closure_dependencies: FxHashMap::default(), inside_assignment: false, + diagnostics: Diagnostics::default(), } } @@ -724,8 +740,10 @@ impl<'a> InferenceContext<'a> { mut result, mut deferred_cast_checks, tuple_field_accesses_rev, + diagnostics, .. } = self; + let mut diagnostics = diagnostics.finish(); // Destructure every single field so whenever new fields are added to `InferenceResult` we // don't forget to handle them here. let InferenceResult { @@ -733,7 +751,6 @@ impl<'a> InferenceContext<'a> { field_resolutions: _, variant_resolutions: _, assoc_resolutions, - diagnostics, type_of_expr, type_of_pat, type_of_binding, @@ -752,6 +769,7 @@ impl<'a> InferenceContext<'a> { mutated_bindings_in_closure: _, tuple_field_access_types: _, coercion_casts, + diagnostics: _, } = &mut result; table.fallback_if_possible(); @@ -866,6 +884,9 @@ impl<'a> InferenceContext<'a> { *has_errors || subst.type_parameters(Interner).any(|ty| ty.contains_unknown()); }) .collect(); + + result.diagnostics = diagnostics; + result } @@ -1238,41 +1259,28 @@ impl<'a> InferenceContext<'a> { self.result.type_of_binding.insert(id, ty); } - fn push_diagnostic(&mut self, diagnostic: InferenceDiagnostic) { - self.result.diagnostics.push(diagnostic); - } - - fn push_ty_diagnostics( - &mut self, - source: InferenceTyDiagnosticSource, - diagnostics: Vec, - ) { - self.result.diagnostics.extend( - diagnostics.into_iter().map(|diag| InferenceDiagnostic::TyDiagnostic { source, diag }), - ); + fn push_diagnostic(&self, diagnostic: InferenceDiagnostic) { + self.diagnostics.push(diagnostic); } fn with_ty_lowering( &mut self, types_map: &TypesMap, types_source: InferenceTyDiagnosticSource, - f: impl FnOnce(&mut crate::lower::TyLoweringContext<'_>) -> R, + f: impl FnOnce(&mut TyLoweringContext<'_>) -> R, ) -> R { - let mut ctx = crate::lower::TyLoweringContext::new( + let mut ctx = TyLoweringContext::new( self.db, &self.resolver, types_map, self.owner.into(), + &self.diagnostics, + types_source, ); - let result = f(&mut ctx); - self.push_ty_diagnostics(types_source, ctx.diagnostics); - result + f(&mut ctx) } - fn with_body_ty_lowering( - &mut self, - f: impl FnOnce(&mut crate::lower::TyLoweringContext<'_>) -> R, - ) -> R { + fn with_body_ty_lowering(&mut self, f: impl FnOnce(&mut TyLoweringContext<'_>) -> R) -> R { self.with_ty_lowering(&self.body.types, InferenceTyDiagnosticSource::Body, f) } @@ -1451,51 +1459,55 @@ impl<'a> InferenceContext<'a> { } } - fn resolve_variant(&mut self, path: Option<&Path>, value_ns: bool) -> (Ty, Option) { + fn resolve_variant( + &mut self, + node: ExprOrPatId, + path: Option<&Path>, + value_ns: bool, + ) -> (Ty, Option) { let path = match path { Some(path) => path, None => return (self.err_ty(), None), }; - let mut ctx = crate::lower::TyLoweringContext::new( + let mut ctx = TyLoweringContext::new( self.db, &self.resolver, &self.body.types, self.owner.into(), + &self.diagnostics, + InferenceTyDiagnosticSource::Body, ); let (resolution, unresolved) = if value_ns { - match self.resolver.resolve_path_in_value_ns(self.db.upcast(), path, HygieneId::ROOT) { - Some(ResolveValueResult::ValueNs(value, _)) => match value { + let Some(res) = ctx.resolve_path_in_value_ns(path, node, HygieneId::ROOT) else { + return (self.err_ty(), None); + }; + match res { + ResolveValueResult::ValueNs(value, _) => match value { ValueNs::EnumVariantId(var) => { let substs = ctx.substs_from_path(path, var.into(), true); - self.push_ty_diagnostics( - InferenceTyDiagnosticSource::Body, - ctx.diagnostics, - ); + drop(ctx); let ty = self.db.ty(var.lookup(self.db.upcast()).parent.into()); let ty = self.insert_type_vars(ty.substitute(Interner, &substs)); return (ty, Some(var.into())); } ValueNs::StructId(strukt) => { let substs = ctx.substs_from_path(path, strukt.into(), true); - self.push_ty_diagnostics( - InferenceTyDiagnosticSource::Body, - ctx.diagnostics, - ); + drop(ctx); let ty = self.db.ty(strukt.into()); let ty = self.insert_type_vars(ty.substitute(Interner, &substs)); return (ty, Some(strukt.into())); } ValueNs::ImplSelf(impl_id) => (TypeNs::SelfType(impl_id), None), - _ => return (self.err_ty(), None), + _ => { + drop(ctx); + return (self.err_ty(), None); + } }, - Some(ResolveValueResult::Partial(typens, unresolved, _)) => { - (typens, Some(unresolved)) - } - None => return (self.err_ty(), None), + ResolveValueResult::Partial(typens, unresolved, _) => (typens, Some(unresolved)), } } else { - match self.resolver.resolve_path_in_type_ns(self.db.upcast(), path) { - Some((it, idx, _)) => (it, idx), + match ctx.resolve_path_in_type_ns(path, node) { + Some((it, idx)) => (it, idx), None => return (self.err_ty(), None), } }; @@ -1506,21 +1518,21 @@ impl<'a> InferenceContext<'a> { return match resolution { TypeNs::AdtId(AdtId::StructId(strukt)) => { let substs = ctx.substs_from_path(path, strukt.into(), true); - self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics); + drop(ctx); let ty = self.db.ty(strukt.into()); let ty = self.insert_type_vars(ty.substitute(Interner, &substs)); forbid_unresolved_segments((ty, Some(strukt.into())), unresolved) } TypeNs::AdtId(AdtId::UnionId(u)) => { let substs = ctx.substs_from_path(path, u.into(), true); - self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics); + drop(ctx); let ty = self.db.ty(u.into()); let ty = self.insert_type_vars(ty.substitute(Interner, &substs)); forbid_unresolved_segments((ty, Some(u.into())), unresolved) } TypeNs::EnumVariantId(var) => { let substs = ctx.substs_from_path(path, var.into(), true); - self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics); + drop(ctx); let ty = self.db.ty(var.lookup(self.db.upcast()).parent.into()); let ty = self.insert_type_vars(ty.substitute(Interner, &substs)); forbid_unresolved_segments((ty, Some(var.into())), unresolved) @@ -1531,6 +1543,7 @@ impl<'a> InferenceContext<'a> { let mut ty = self.db.impl_self_ty(impl_id).substitute(Interner, &substs); let Some(mut remaining_idx) = unresolved else { + drop(ctx); return self.resolve_variant_on_alias(ty, None, mod_path); }; @@ -1538,6 +1551,7 @@ impl<'a> InferenceContext<'a> { // We need to try resolving unresolved segments one by one because each may resolve // to a projection, which `TyLoweringContext` cannot handle on its own. + let mut tried_resolving_once = false; while !remaining_segments.is_empty() { let resolved_segment = path.segments().get(remaining_idx - 1).unwrap(); let current_segment = remaining_segments.take(1); @@ -1558,18 +1572,27 @@ impl<'a> InferenceContext<'a> { } } + if tried_resolving_once { + // FIXME: with `inherent_associated_types` this is allowed, but our `lower_partly_resolved_path()` + // will need to be updated to err at the correct segment. + // + // We need to stop here because otherwise the segment index passed to `lower_partly_resolved_path()` + // will be incorrect, and that can mess up error reporting. + break; + } + // `lower_partly_resolved_path()` returns `None` as type namespace unless // `remaining_segments` is empty, which is never the case here. We don't know // which namespace the new `ty` is in until normalized anyway. (ty, _) = ctx.lower_partly_resolved_path( + node, resolution, resolved_segment, current_segment, + (remaining_idx - 1) as u32, false, - &mut |_, _reason| { - // FIXME: Report an error. - }, ); + tried_resolving_once = true; ty = self.table.insert_type_vars(ty); ty = self.table.normalize_associated_types_in(ty); @@ -1582,7 +1605,7 @@ impl<'a> InferenceContext<'a> { remaining_idx += 1; remaining_segments = remaining_segments.skip(1); } - self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics); + drop(ctx); let variant = ty.as_adt().and_then(|(id, _)| match id { AdtId::StructId(s) => Some(VariantId::StructId(s)), @@ -1601,7 +1624,7 @@ impl<'a> InferenceContext<'a> { }; let substs = ctx.substs_from_path_segment(resolved_seg, Some(it.into()), true, None); - self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics); + drop(ctx); let ty = self.db.ty(it.into()); let ty = self.insert_type_vars(ty.substitute(Interner, &substs)); diff --git a/crates/hir-ty/src/infer/diagnostics.rs b/crates/hir-ty/src/infer/diagnostics.rs new file mode 100644 index 0000000000..032dc37899 --- /dev/null +++ b/crates/hir-ty/src/infer/diagnostics.rs @@ -0,0 +1,128 @@ +//! This file contains the [`Diagnostics`] type used during inference, +//! and a wrapper around [`TyLoweringContext`] ([`InferenceTyLoweringContext`]) that replaces +//! it and takes care of diagnostics in inference. + +use std::cell::RefCell; +use std::ops::{Deref, DerefMut}; + +use hir_def::body::HygieneId; +use hir_def::hir::ExprOrPatId; +use hir_def::path::{Path, PathSegment, PathSegments}; +use hir_def::resolver::{ResolveValueResult, Resolver, TypeNs}; +use hir_def::type_ref::TypesMap; +use hir_def::TypeOwnerId; + +use crate::db::HirDatabase; +use crate::{ + InferenceDiagnostic, InferenceTyDiagnosticSource, Ty, TyLoweringContext, TyLoweringDiagnostic, +}; + +// Unfortunately, this struct needs to use interior mutability (but we encapsulate it) +// because when lowering types and paths we hold a `TyLoweringContext` that holds a reference +// to our resolver and so we cannot have mutable reference, but we really want to have +// ability to dispatch diagnostics during this work otherwise the code becomes a complete mess. +#[derive(Debug, Default, Clone)] +pub(super) struct Diagnostics(RefCell>); + +impl Diagnostics { + pub(super) fn push(&self, diagnostic: InferenceDiagnostic) { + self.0.borrow_mut().push(diagnostic); + } + + fn push_ty_diagnostics( + &self, + source: InferenceTyDiagnosticSource, + diagnostics: Vec, + ) { + self.0.borrow_mut().extend( + diagnostics.into_iter().map(|diag| InferenceDiagnostic::TyDiagnostic { source, diag }), + ); + } + + pub(super) fn finish(self) -> Vec { + self.0.into_inner() + } +} + +pub(super) struct InferenceTyLoweringContext<'a> { + ctx: TyLoweringContext<'a>, + diagnostics: &'a Diagnostics, + source: InferenceTyDiagnosticSource, +} + +impl<'a> InferenceTyLoweringContext<'a> { + pub(super) fn new( + db: &'a dyn HirDatabase, + resolver: &'a Resolver, + types_map: &'a TypesMap, + owner: TypeOwnerId, + diagnostics: &'a Diagnostics, + source: InferenceTyDiagnosticSource, + ) -> Self { + Self { ctx: TyLoweringContext::new(db, resolver, types_map, owner), diagnostics, source } + } + + pub(super) fn resolve_path_in_type_ns( + &mut self, + path: &Path, + node: ExprOrPatId, + ) -> Option<(TypeNs, Option)> { + let diagnostics = self.diagnostics; + self.ctx.resolve_path_in_type_ns(path, &mut |_, diag| { + diagnostics.push(InferenceDiagnostic::PathDiagnostic { node, diag }) + }) + } + + pub(super) fn resolve_path_in_value_ns( + &mut self, + path: &Path, + node: ExprOrPatId, + hygiene_id: HygieneId, + ) -> Option { + let diagnostics = self.diagnostics; + self.ctx.resolve_path_in_value_ns(path, hygiene_id, &mut |_, diag| { + diagnostics.push(InferenceDiagnostic::PathDiagnostic { node, diag }) + }) + } + + pub(super) fn lower_partly_resolved_path( + &mut self, + node: ExprOrPatId, + resolution: TypeNs, + resolved_segment: PathSegment<'_>, + remaining_segments: PathSegments<'_>, + resolved_segment_idx: u32, + infer_args: bool, + ) -> (Ty, Option) { + let diagnostics = self.diagnostics; + self.ctx.lower_partly_resolved_path( + resolution, + resolved_segment, + remaining_segments, + resolved_segment_idx, + infer_args, + &mut |_, diag| diagnostics.push(InferenceDiagnostic::PathDiagnostic { node, diag }), + ) + } +} + +impl<'a> Deref for InferenceTyLoweringContext<'a> { + type Target = TyLoweringContext<'a>; + + fn deref(&self) -> &Self::Target { + &self.ctx + } +} + +impl DerefMut for InferenceTyLoweringContext<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.ctx + } +} + +impl Drop for InferenceTyLoweringContext<'_> { + fn drop(&mut self) { + self.diagnostics + .push_ty_diagnostics(self.source, std::mem::take(&mut self.ctx.diagnostics)); + } +} diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index a13541be69..3dccd536be 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -531,7 +531,7 @@ impl InferenceContext<'_> { (params, ret_ty) } None => { - self.result.diagnostics.push(InferenceDiagnostic::ExpectedFunction { + self.push_diagnostic(InferenceDiagnostic::ExpectedFunction { call_expr: tgt_expr, found: callee_ty.clone(), }); @@ -707,7 +707,7 @@ impl InferenceContext<'_> { self.result.standard_types.never.clone() } Expr::RecordLit { path, fields, spread, .. } => { - let (ty, def_id) = self.resolve_variant(path.as_deref(), false); + let (ty, def_id) = self.resolve_variant(tgt_expr.into(), path.as_deref(), false); if let Some(t) = expected.only_has_type(&mut self.table) { self.unify(&ty, &t); @@ -1816,9 +1816,10 @@ impl InferenceContext<'_> { if !is_public { if let Either::Left(field) = field_id { // FIXME: Merge this diagnostic into UnresolvedField? - self.result - .diagnostics - .push(InferenceDiagnostic::PrivateField { expr: tgt_expr, field }); + self.push_diagnostic(InferenceDiagnostic::PrivateField { + expr: tgt_expr, + field, + }); } } ty @@ -1835,7 +1836,7 @@ impl InferenceContext<'_> { VisibleFromModule::Filter(self.resolver.module()), name, ); - self.result.diagnostics.push(InferenceDiagnostic::UnresolvedField { + self.push_diagnostic(InferenceDiagnostic::UnresolvedField { expr: tgt_expr, receiver: receiver_ty.clone(), name: name.clone(), @@ -1927,7 +1928,7 @@ impl InferenceContext<'_> { }, ); - self.result.diagnostics.push(InferenceDiagnostic::UnresolvedMethodCall { + self.push_diagnostic(InferenceDiagnostic::UnresolvedMethodCall { expr: tgt_expr, receiver: receiver_ty.clone(), name: method_name.clone(), diff --git a/crates/hir-ty/src/infer/pat.rs b/crates/hir-ty/src/infer/pat.rs index 50e761196e..00398f019d 100644 --- a/crates/hir-ty/src/infer/pat.rs +++ b/crates/hir-ty/src/infer/pat.rs @@ -35,7 +35,7 @@ impl InferenceContext<'_> { ellipsis: Option, subs: &[PatId], ) -> Ty { - let (ty, def) = self.resolve_variant(path, true); + let (ty, def) = self.resolve_variant(id.into(), path, true); let var_data = def.map(|it| it.variant_data(self.db.upcast())); if let Some(variant) = def { self.write_variant_resolution(id.into(), variant); @@ -115,7 +115,7 @@ impl InferenceContext<'_> { id: PatId, subs: impl ExactSizeIterator, ) -> Ty { - let (ty, def) = self.resolve_variant(path, false); + let (ty, def) = self.resolve_variant(id.into(), path, false); if let Some(variant) = def { self.write_variant_resolution(id.into(), variant); } diff --git a/crates/hir-ty/src/infer/path.rs b/crates/hir-ty/src/infer/path.rs index a6296c3af2..d79bf9a05e 100644 --- a/crates/hir-ty/src/infer/path.rs +++ b/crates/hir-ty/src/infer/path.rs @@ -14,6 +14,7 @@ use crate::{ builder::ParamKind, consteval, error_lifetime, generics::generics, + infer::diagnostics::InferenceTyLoweringContext as TyLoweringContext, method_resolution::{self, VisibleFromModule}, to_chalk_trait_id, InferenceDiagnostic, Interner, Substitution, TraitRef, TraitRefExt, Ty, TyBuilder, TyExt, TyKind, ValueTyDefId, @@ -147,36 +148,38 @@ impl InferenceContext<'_> { path: &Path, id: ExprOrPatId, ) -> Option<(ValueNs, Option>)> { + // Don't use `self.make_ty()` here as we need `orig_ns`. + let mut ctx = TyLoweringContext::new( + self.db, + &self.resolver, + &self.body.types, + self.owner.into(), + &self.diagnostics, + InferenceTyDiagnosticSource::Body, + ); let (value, self_subst) = if let Some(type_ref) = path.type_anchor() { let last = path.segments().last()?; - // Don't use `self.make_ty()` here as we need `orig_ns`. - let mut ctx = crate::lower::TyLoweringContext::new( - self.db, - &self.resolver, - &self.body.types, - self.owner.into(), - ); let (ty, orig_ns) = ctx.lower_ty_ext(type_ref); let ty = self.table.insert_type_vars(ty); let ty = self.table.normalize_associated_types_in(ty); let remaining_segments_for_ty = path.segments().take(path.segments().len() - 1); let (ty, _) = ctx.lower_ty_relative_path(ty, orig_ns, remaining_segments_for_ty); - self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics); + drop(ctx); let ty = self.table.insert_type_vars(ty); let ty = self.table.normalize_associated_types_in(ty); self.resolve_ty_assoc_item(ty, last.name, id).map(|(it, substs)| (it, Some(substs)))? } else { let hygiene = self.body.expr_or_pat_path_hygiene(id); // FIXME: report error, unresolved first path segment - let value_or_partial = - self.resolver.resolve_path_in_value_ns(self.db.upcast(), path, hygiene)?; + let value_or_partial = ctx.resolve_path_in_value_ns(path, id, hygiene)?; + drop(ctx); match value_or_partial { ResolveValueResult::ValueNs(it, _) => (it, None), ResolveValueResult::Partial(def, remaining_index, _) => self - .resolve_assoc_item(def, path, remaining_index, id) + .resolve_assoc_item(id, def, path, remaining_index, id) .map(|(it, substs)| (it, Some(substs)))?, } }; @@ -212,6 +215,7 @@ impl InferenceContext<'_> { fn resolve_assoc_item( &mut self, + node: ExprOrPatId, def: TypeNs, path: &Path, remaining_index: usize, @@ -260,17 +264,23 @@ impl InferenceContext<'_> { // as Iterator>::Item::default`) let remaining_segments_for_ty = remaining_segments.take(remaining_segments.len() - 1); - let (ty, _) = self.with_body_ty_lowering(|ctx| { - ctx.lower_partly_resolved_path( - def, - resolved_segment, - remaining_segments_for_ty, - true, - &mut |_, _reason| { - // FIXME: Report an error. - }, - ) - }); + let mut ctx = TyLoweringContext::new( + self.db, + &self.resolver, + &self.body.types, + self.owner.into(), + &self.diagnostics, + InferenceTyDiagnosticSource::Body, + ); + let (ty, _) = ctx.lower_partly_resolved_path( + node, + def, + resolved_segment, + remaining_segments_for_ty, + (remaining_index - 1) as u32, + true, + ); + drop(ctx); if ty.is_unknown() { return None; } diff --git a/crates/hir-ty/src/lower.rs b/crates/hir-ty/src/lower.rs index 2610bb704e..4bfa51c849 100644 --- a/crates/hir-ty/src/lower.rs +++ b/crates/hir-ty/src/lower.rs @@ -23,6 +23,7 @@ use chalk_ir::{ use either::Either; use hir_def::{ + body::HygieneId, builtin_type::BuiltinType, data::adt::StructKind, expander::Expander, @@ -31,9 +32,9 @@ use hir_def::{ WherePredicateTypeTarget, }, lang_item::LangItem, - nameres::MacroSubNs, + nameres::{MacroSubNs, ResolvePathResultPrefixInfo}, path::{GenericArg, GenericArgs, ModPath, Path, PathKind, PathSegment, PathSegments}, - resolver::{HasResolver, LifetimeNs, Resolver, TypeNs}, + resolver::{HasResolver, LifetimeNs, ResolveValueResult, Resolver, TypeNs, ValueNs}, type_ref::{ ConstRef, LifetimeRef, PathId, TraitBoundModifier, TraitRef as HirTraitRef, TypeBound, TypeRef, TypeRefId, TypesMap, TypesSourceMap, @@ -514,8 +515,8 @@ impl<'a> TyLoweringContext<'a> { /// This is only for `generic_predicates_for_param`, where we can't just /// lower the self types of the predicates since that could lead to cycles. /// So we just check here if the `type_ref` resolves to a generic param, and which. - fn lower_ty_only_param(&self, type_ref: TypeRefId) -> Option { - let type_ref = &self.types_map[type_ref]; + fn lower_ty_only_param(&mut self, type_ref_id: TypeRefId) -> Option { + let type_ref = &self.types_map[type_ref_id]; let path = match type_ref { TypeRef::Path(path) => path, _ => return None, @@ -526,8 +527,10 @@ impl<'a> TyLoweringContext<'a> { if path.segments().len() > 1 { return None; } - let resolution = match self.resolver.resolve_path_in_type_ns(self.db.upcast(), path) { - Some((it, None, _)) => it, + let resolution = match self + .resolve_path_in_type_ns(path, &mut Self::on_path_diagnostic_callback(type_ref_id)) + { + Some((it, None)) => it, _ => return None, }; match resolution { @@ -562,11 +565,9 @@ impl<'a> TyLoweringContext<'a> { resolution: TypeNs, resolved_segment: PathSegment<'_>, remaining_segments: PathSegments<'_>, + _resolved_segment_idx: u32, infer_args: bool, - on_prohibited_generics_for_resolved_segment: &mut dyn FnMut( - &mut Self, - GenericArgsProhibitedReason, - ), + _on_diagnostic: &mut dyn FnMut(&mut Self, PathLoweringDiagnostic), ) -> (Ty, Option) { let ty = match resolution { TypeNs::TraitId(trait_) => { @@ -633,44 +634,28 @@ impl<'a> TyLoweringContext<'a> { // FIXME(trait_alias): Implement trait alias. return (TyKind::Error.intern(Interner), None); } - TypeNs::GenericParam(param_id) => { - if resolved_segment.args_and_bindings.is_some() { - on_prohibited_generics_for_resolved_segment( - self, - GenericArgsProhibitedReason::TyParam, - ); + TypeNs::GenericParam(param_id) => match self.type_param_mode { + ParamLoweringMode::Placeholder => { + TyKind::Placeholder(to_placeholder_idx(self.db, param_id.into())) } + ParamLoweringMode::Variable => { + let idx = match self + .generics() + .expect("generics in scope") + .type_or_const_param_idx(param_id.into()) + { + None => { + never!("no matching generics"); + return (TyKind::Error.intern(Interner), None); + } + Some(idx) => idx, + }; - match self.type_param_mode { - ParamLoweringMode::Placeholder => { - TyKind::Placeholder(to_placeholder_idx(self.db, param_id.into())) - } - ParamLoweringMode::Variable => { - let idx = match self - .generics() - .expect("generics in scope") - .type_or_const_param_idx(param_id.into()) - { - None => { - never!("no matching generics"); - return (TyKind::Error.intern(Interner), None); - } - Some(idx) => idx, - }; - - TyKind::BoundVar(BoundVar::new(self.in_binders, idx)) - } + TyKind::BoundVar(BoundVar::new(self.in_binders, idx)) } - .intern(Interner) } + .intern(Interner), TypeNs::SelfType(impl_id) => { - if resolved_segment.args_and_bindings.is_some() { - on_prohibited_generics_for_resolved_segment( - self, - GenericArgsProhibitedReason::SelfTy, - ); - } - let generics = self.generics().expect("impl should have generic param scope"); match self.type_param_mode { @@ -696,13 +681,6 @@ impl<'a> TyLoweringContext<'a> { } } TypeNs::AdtSelfType(adt) => { - if resolved_segment.args_and_bindings.is_some() { - on_prohibited_generics_for_resolved_segment( - self, - GenericArgsProhibitedReason::SelfTy, - ); - } - let generics = generics(self.db.upcast(), adt.into()); let substs = match self.type_param_mode { ParamLoweringMode::Placeholder => generics.placeholder_subst(self.db), @@ -715,12 +693,6 @@ impl<'a> TyLoweringContext<'a> { TypeNs::AdtId(it) => self.lower_path_inner(resolved_segment, it.into(), infer_args), TypeNs::BuiltinType(it) => { - if resolved_segment.args_and_bindings.is_some() { - on_prohibited_generics_for_resolved_segment( - self, - GenericArgsProhibitedReason::PrimitiveTy, - ); - } self.lower_path_inner(resolved_segment, it.into(), infer_args) } TypeNs::TypeAliasId(it) => { @@ -732,6 +704,220 @@ impl<'a> TyLoweringContext<'a> { self.lower_ty_relative_path(ty, Some(resolution), remaining_segments) } + fn handle_type_ns_resolution( + &mut self, + resolution: &TypeNs, + resolved_segment: PathSegment<'_>, + resolved_segment_idx: usize, + on_diagnostic: &mut dyn FnMut(&mut Self, PathLoweringDiagnostic), + ) { + let mut prohibit_generics_on_resolved = |reason| { + if resolved_segment.args_and_bindings.is_some() { + on_diagnostic( + self, + PathLoweringDiagnostic::GenericArgsProhibited { + segment: resolved_segment_idx as u32, + reason, + }, + ); + } + }; + + match resolution { + TypeNs::SelfType(_) => { + prohibit_generics_on_resolved(GenericArgsProhibitedReason::SelfTy) + } + TypeNs::GenericParam(_) => { + prohibit_generics_on_resolved(GenericArgsProhibitedReason::TyParam) + } + TypeNs::AdtSelfType(_) => { + prohibit_generics_on_resolved(GenericArgsProhibitedReason::SelfTy) + } + TypeNs::BuiltinType(_) => { + prohibit_generics_on_resolved(GenericArgsProhibitedReason::PrimitiveTy) + } + TypeNs::AdtId(_) + | TypeNs::EnumVariantId(_) + | TypeNs::TypeAliasId(_) + | TypeNs::TraitId(_) + | TypeNs::TraitAliasId(_) => {} + } + } + + pub(crate) fn resolve_path_in_type_ns_fully( + &mut self, + path: &Path, + on_diagnostic: &mut dyn FnMut(&mut Self, PathLoweringDiagnostic), + ) -> Option { + let (res, unresolved) = self.resolve_path_in_type_ns(path, on_diagnostic)?; + if unresolved.is_some() { + return None; + } + Some(res) + } + + pub(crate) fn resolve_path_in_type_ns( + &mut self, + path: &Path, + on_diagnostic: &mut dyn FnMut(&mut Self, PathLoweringDiagnostic), + ) -> Option<(TypeNs, Option)> { + let (resolution, remaining_index, _) = + self.resolver.resolve_path_in_type_ns(self.db.upcast(), path)?; + let segments = path.segments(); + + match path { + // `segments.is_empty()` can occur with `self`. + Path::Normal(..) if !segments.is_empty() => (), + _ => return Some((resolution, remaining_index)), + }; + + let (module_segments, resolved_segment_idx, resolved_segment) = match remaining_index { + None => ( + segments.strip_last(), + segments.len() - 1, + segments.last().expect("resolved path has at least one element"), + ), + Some(i) => (segments.take(i - 1), i - 1, segments.get(i - 1).unwrap()), + }; + + for (i, mod_segment) in module_segments.iter().enumerate() { + if mod_segment.args_and_bindings.is_some() { + on_diagnostic( + self, + PathLoweringDiagnostic::GenericArgsProhibited { + segment: i as u32, + reason: GenericArgsProhibitedReason::Module, + }, + ); + } + } + + self.handle_type_ns_resolution( + &resolution, + resolved_segment, + resolved_segment_idx, + on_diagnostic, + ); + + Some((resolution, remaining_index)) + } + + pub(crate) fn resolve_path_in_value_ns( + &mut self, + path: &Path, + hygiene_id: HygieneId, + on_diagnostic: &mut dyn FnMut(&mut Self, PathLoweringDiagnostic), + ) -> Option { + let (res, prefix_info) = self.resolver.resolve_path_in_value_ns_with_prefix_info( + self.db.upcast(), + path, + hygiene_id, + )?; + + let segments = path.segments(); + match path { + // `segments.is_empty()` can occur with `self`. + Path::Normal(..) if !segments.is_empty() => (), + _ => return Some(res), + }; + + let (mod_segments, enum_segment) = match res { + ResolveValueResult::Partial(_, unresolved_segment, _) => { + (segments.take(unresolved_segment - 1), None) + } + ResolveValueResult::ValueNs(ValueNs::EnumVariantId(_), _) + if prefix_info == ResolvePathResultPrefixInfo::Enum => + { + (segments.strip_last_two(), segments.len().checked_sub(2)) + } + ResolveValueResult::ValueNs(..) => (segments.strip_last(), None), + }; + for (i, mod_segment) in mod_segments.iter().enumerate() { + if mod_segment.args_and_bindings.is_some() { + on_diagnostic( + self, + PathLoweringDiagnostic::GenericArgsProhibited { + segment: i as u32, + reason: GenericArgsProhibitedReason::Module, + }, + ); + } + } + + if let Some(enum_segment) = enum_segment { + if segments.get(enum_segment).is_some_and(|it| it.args_and_bindings.is_some()) + && segments.get(enum_segment + 1).is_some_and(|it| it.args_and_bindings.is_some()) + { + on_diagnostic( + self, + PathLoweringDiagnostic::GenericArgsProhibited { + segment: (enum_segment + 1) as u32, + reason: GenericArgsProhibitedReason::EnumVariant, + }, + ); + } + } + + match &res { + ResolveValueResult::ValueNs(resolution, _) => { + let resolved_segment_idx = + segments.len().checked_sub(1).unwrap_or_else(|| panic!("{path:?}")); + let resolved_segment = segments.last().unwrap(); + + let mut prohibit_generics_on_resolved = |reason| { + if resolved_segment.args_and_bindings.is_some() { + on_diagnostic( + self, + PathLoweringDiagnostic::GenericArgsProhibited { + segment: resolved_segment_idx as u32, + reason, + }, + ); + } + }; + + match resolution { + ValueNs::ImplSelf(_) => { + prohibit_generics_on_resolved(GenericArgsProhibitedReason::SelfTy) + } + // FIXME: rustc generates E0107 (incorrect number of generic arguments) and not + // E0109 (generic arguments provided for a type that doesn't accept them) for + // consts and statics, presumably as a defense against future in which consts + // and statics can be generic, or just because it was easier for rustc implementors. + // That means we'll show the wrong error code. Because of us it's easier to do it + // this way :) + ValueNs::GenericParam(_) | ValueNs::ConstId(_) => { + prohibit_generics_on_resolved(GenericArgsProhibitedReason::Const) + } + ValueNs::StaticId(_) => { + prohibit_generics_on_resolved(GenericArgsProhibitedReason::Static) + } + ValueNs::FunctionId(_) | ValueNs::StructId(_) | ValueNs::EnumVariantId(_) => {} + ValueNs::LocalBinding(_) => {} + } + } + ResolveValueResult::Partial(resolution, unresolved_idx, _) => { + let resolved_segment_idx = unresolved_idx - 1; + let resolved_segment = segments.get(resolved_segment_idx).unwrap(); + self.handle_type_ns_resolution( + resolution, + resolved_segment, + resolved_segment_idx, + on_diagnostic, + ); + } + }; + Some(res) + } + + fn on_path_diagnostic_callback( + type_ref: TypeRefId, + ) -> impl FnMut(&mut Self, PathLoweringDiagnostic) { + move |this, diag| { + this.push_diagnostic(type_ref, TyLoweringDiagnosticKind::PathDiagnostic(diag)) + } + } + pub(crate) fn lower_path(&mut self, path: &Path, path_id: PathId) -> (Ty, Option) { // Resolve the path (in type namespace) if let Some(type_ref) = path.type_anchor() { @@ -739,11 +925,13 @@ impl<'a> TyLoweringContext<'a> { return self.lower_ty_relative_path(ty, res, path.segments()); } - let (resolution, remaining_index, _) = - match self.resolver.resolve_path_in_type_ns(self.db.upcast(), path) { - Some(it) => it, - None => return (TyKind::Error.intern(Interner), None), - }; + let (resolution, remaining_index) = match self.resolve_path_in_type_ns( + path, + &mut Self::on_path_diagnostic_callback(path_id.type_ref()), + ) { + Some(it) => it, + None => return (TyKind::Error.intern(Interner), None), + }; if matches!(resolution, TypeNs::TraitId(_)) && remaining_index.is_none() { // trait object type without dyn @@ -752,38 +940,22 @@ impl<'a> TyLoweringContext<'a> { return (ty, None); } - let (module_segments, resolved_segment_idx, resolved_segment, remaining_segments) = - match remaining_index { - None => ( - path.segments().strip_last(), - path.segments().len() - 1, - path.segments().last().expect("resolved path has at least one element"), - PathSegments::EMPTY, - ), - Some(i) => ( - path.segments().take(i - 1), - i - 1, - path.segments().get(i - 1).unwrap(), - path.segments().skip(i), - ), - }; - - self.prohibit_generics(path_id, 0, module_segments, GenericArgsProhibitedReason::Module); + let (resolved_segment_idx, resolved_segment, remaining_segments) = match remaining_index { + None => ( + path.segments().len() - 1, + path.segments().last().expect("resolved path has at least one element"), + PathSegments::EMPTY, + ), + Some(i) => (i - 1, path.segments().get(i - 1).unwrap(), path.segments().skip(i)), + }; self.lower_partly_resolved_path( resolution, resolved_segment, remaining_segments, + resolved_segment_idx as u32, false, - &mut |this, reason| { - this.push_diagnostic( - path_id.type_ref(), - TyLoweringDiagnosticKind::GenericArgsProhibited { - segment: resolved_segment_idx as u32, - reason, - }, - ) - }, + &mut Self::on_path_diagnostic_callback(path_id.type_ref()), ) } @@ -1085,7 +1257,9 @@ impl<'a> TyLoweringContext<'a> { if segment.args_and_bindings.is_some() { self.push_diagnostic( path_id.type_ref(), - TyLoweringDiagnosticKind::GenericArgsProhibited { segment: idx, reason }, + TyLoweringDiagnosticKind::PathDiagnostic( + PathLoweringDiagnostic::GenericArgsProhibited { segment: idx, reason }, + ), ); } }); @@ -1097,7 +1271,10 @@ impl<'a> TyLoweringContext<'a> { explicit_self_ty: Ty, ) -> Option { let path = &self.types_map[path_id]; - let resolved = match self.resolver.resolve_path_in_type_ns_fully(self.db.upcast(), path)? { + let resolved = match self.resolve_path_in_type_ns_fully( + path, + &mut Self::on_path_diagnostic_callback(path_id.type_ref()), + )? { // FIXME(trait_alias): We need to handle trait alias here. TypeNs::TraitId(tr) => tr, _ => return None, diff --git a/crates/hir-ty/src/lower/diagnostics.rs b/crates/hir-ty/src/lower/diagnostics.rs index 61fedc8c3a..7fe196cdbb 100644 --- a/crates/hir-ty/src/lower/diagnostics.rs +++ b/crates/hir-ty/src/lower/diagnostics.rs @@ -1,3 +1,5 @@ +//! This files contains the declaration of diagnostics kinds for ty and path lowering. + use either::Either; use hir_def::type_ref::TypeRefId; @@ -11,7 +13,7 @@ pub struct TyLoweringDiagnostic { #[derive(Debug, PartialEq, Eq, Clone)] pub enum TyLoweringDiagnosticKind { - GenericArgsProhibited { segment: u32, reason: GenericArgsProhibitedReason }, + PathDiagnostic(PathLoweringDiagnostic), } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -20,8 +22,15 @@ pub enum GenericArgsProhibitedReason { TyParam, SelfTy, PrimitiveTy, + Const, + Static, /// When there is a generic enum, within the expression `Enum::Variant`, /// either `Enum` or `Variant` are allowed to have generic arguments, but not both. // FIXME: This is not used now but it should be. EnumVariant, } + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum PathLoweringDiagnostic { + GenericArgsProhibited { segment: u32, reason: GenericArgsProhibitedReason }, +} diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index cbb1ed95ed..fc77d1889c 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -15,12 +15,12 @@ use hir_expand::{name::Name, HirFileId, InFile}; use hir_ty::{ db::HirDatabase, diagnostics::{BodyValidationDiagnostic, UnsafetyReason}, - CastError, InferenceDiagnostic, InferenceTyDiagnosticSource, TyLoweringDiagnostic, - TyLoweringDiagnosticKind, + CastError, InferenceDiagnostic, InferenceTyDiagnosticSource, PathLoweringDiagnostic, + TyLoweringDiagnostic, TyLoweringDiagnosticKind, }; use syntax::{ ast::{self, HasGenericArgs}, - AstPtr, SyntaxError, SyntaxNodePtr, TextRange, + match_ast, AstNode, AstPtr, SyntaxError, SyntaxNodePtr, TextRange, }; use triomphe::Arc; @@ -674,6 +674,39 @@ impl AnyDiagnostic { }; Self::ty_diagnostic(diag, source_map, db)? } + InferenceDiagnostic::PathDiagnostic { node, diag } => { + let source = expr_or_pat_syntax(*node)?; + let syntax = source.value.to_node(&db.parse_or_expand(source.file_id)); + let path = match_ast! { + match (syntax.syntax()) { + ast::RecordExpr(it) => it.path()?, + ast::RecordPat(it) => it.path()?, + ast::TupleStructPat(it) => it.path()?, + ast::PathExpr(it) => it.path()?, + ast::PathPat(it) => it.path()?, + _ => return None, + } + }; + Self::path_diagnostic(diag, source.with_value(path))? + } + }) + } + + fn path_diagnostic( + diag: &PathLoweringDiagnostic, + path: InFile, + ) -> Option { + Some(match diag { + &PathLoweringDiagnostic::GenericArgsProhibited { segment, reason } => { + let segment = hir_segment_to_ast_segment(&path.value, segment)?; + let args = if let Some(generics) = segment.generic_arg_list() { + AstPtr::new(&generics).wrap_left() + } else { + AstPtr::new(&segment.parenthesized_arg_list()?).wrap_right() + }; + let args = path.with_value(args); + GenericArgsProhibited { args, reason }.into() + } }) } @@ -693,17 +726,10 @@ impl AnyDiagnostic { Either::Right(source) => source, }; let syntax = || source.value.to_node(&db.parse_or_expand(source.file_id)); - Some(match diag.kind { - TyLoweringDiagnosticKind::GenericArgsProhibited { segment, reason } => { + Some(match &diag.kind { + TyLoweringDiagnosticKind::PathDiagnostic(diag) => { let ast::Type::PathType(syntax) = syntax() else { return None }; - let segment = hir_segment_to_ast_segment(&syntax.path()?, segment)?; - let args = if let Some(generics) = segment.generic_arg_list() { - AstPtr::new(&generics).wrap_left() - } else { - AstPtr::new(&segment.parenthesized_arg_list()?).wrap_right() - }; - let args = source.with_value(args); - GenericArgsProhibited { args, reason }.into() + Self::path_diagnostic(diag, source.with_value(syntax.path()?))? } }) } diff --git a/crates/ide-diagnostics/src/handlers/generic_args_prohibited.rs b/crates/ide-diagnostics/src/handlers/generic_args_prohibited.rs index a319a0bcf6..a6d2ed3223 100644 --- a/crates/ide-diagnostics/src/handlers/generic_args_prohibited.rs +++ b/crates/ide-diagnostics/src/handlers/generic_args_prohibited.rs @@ -34,6 +34,8 @@ fn describe_reason(reason: GenericArgsProhibitedReason) -> String { return "you can specify generic arguments on either the enum or the variant, but not both" .to_owned(); } + GenericArgsProhibitedReason::Const => "constants", + GenericArgsProhibitedReason::Static => "statics", }; format!("generic arguments are not allowed on {kind}") } @@ -435,6 +437,144 @@ type T = bool; impl Trait for () { type Assoc = i32; // ^^^^^^ 💡 error: generic arguments are not allowed on builtin types +} + "#, + ); + } + + #[test] + fn in_record_expr() { + check_diagnostics( + r#" +mod foo { + pub struct Bar { pub field: i32 } +} +fn baz() { + let _ = foo::<()>::Bar { field: 0 }; + // ^^^^^^ 💡 error: generic arguments are not allowed on modules +} + "#, + ); + } + + #[test] + fn in_record_pat() { + check_diagnostics( + r#" +mod foo { + pub struct Bar { field: i32 } +} +fn baz(v: foo::Bar) { + let foo::<()>::Bar { .. } = v; + // ^^^^^^ 💡 error: generic arguments are not allowed on modules +} + "#, + ); + } + + #[test] + fn in_tuple_struct_pat() { + check_diagnostics( + r#" +mod foo { + pub struct Bar(i32); +} +fn baz(v: foo::Bar) { + let foo::<()>::Bar(..) = v; + // ^^^^^^ 💡 error: generic arguments are not allowed on modules +} + "#, + ); + } + + #[test] + fn in_path_pat() { + check_diagnostics( + r#" +mod foo { + pub struct Bar; +} +fn baz(v: foo::Bar) { + let foo::<()>::Bar = v; + // ^^^^^^ 💡 error: generic arguments are not allowed on modules +} + "#, + ); + } + + #[test] + fn in_path_expr() { + check_diagnostics( + r#" +mod foo { + pub struct Bar; +} +fn baz() { + let _ = foo::<()>::Bar; + // ^^^^^^ 💡 error: generic arguments are not allowed on modules +} + "#, + ); + } + + #[test] + fn const_and_static() { + check_diagnostics( + r#" +const CONST: i32 = 0; +static STATIC: i32 = 0; +fn baz() { + let _ = CONST::<()>; + // ^^^^^^ 💡 error: generic arguments are not allowed on constants + let _ = STATIC::<()>; + // ^^^^^^ 💡 error: generic arguments are not allowed on statics +} + "#, + ); + } + + #[test] + fn enum_variant() { + check_diagnostics( + r#" +enum Enum { + Variant(A), +} +mod enum_ { + pub(super) use super::Enum::Variant as V; +} +fn baz() { + let v = Enum::<()>::Variant::<()>(()); + // ^^^^^^ 💡 error: you can specify generic arguments on either the enum or the variant, but not both + let Enum::<()>::Variant::<()>(..) = v; + // ^^^^^^ 💡 error: you can specify generic arguments on either the enum or the variant, but not both + let _ = Enum::<()>::Variant(()); + let _ = Enum::Variant::<()>(()); +} +fn foo() { + use Enum::Variant; + let _ = Variant::<()>(()); + let _ = enum_::V::<()>(()); + let _ = enum_::<()>::V::<()>(()); + // ^^^^^^ 💡 error: generic arguments are not allowed on modules +} + "#, + ); + } + + #[test] + fn dyn_trait() { + check_diagnostics( + r#" +mod foo { + pub trait Trait {} +} + +fn bar() { + let _: &dyn foo::<()>::Trait; + // ^^^^^^ 💡 error: generic arguments are not allowed on modules + let _: &foo::<()>::Trait; + // ^^^^^^ 💡 error: generic arguments are not allowed on modules } "#, );