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".
This commit is contained in:
Chayim Refael Friedman 2024-12-22 18:07:27 +02:00
parent 82896b2cc4
commit cc11e1a796
14 changed files with 848 additions and 247 deletions

View file

@ -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<TypeOrConstParamId> {
let type_ref = &self.types_map[type_ref];
fn lower_ty_only_param(&mut self, type_ref_id: TypeRefId) -> Option<TypeOrConstParamId> {
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<TypeNs>) {
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<TypeNs> {
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<usize>)> {
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<ResolveValueResult> {
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<TypeNs>) {
// 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<TraitRef> {
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,