Lay the foundation for diagnostics in ty lowering, and implement a first diagnostic

The diagnostic implemented is a simple one (E0109). It serves as a test for the new foundation.

This commit only implements diagnostics for type in bodies and body-carrying signatures; the next commit will include diagnostics in the rest of the things.

Also fix one weird bug that was detected when implementing this that caused `Fn::(A, B) -> C` (which is a valid, if bizarre, alternative syntax to `Fn(A, B) -> C` to lower incorrectly.

And also fix a maybe-bug where parentheses were sneaked into a code string needlessly; this was not detected until now because the parentheses were removed (by the make-AST family API), but with a change in this commit they are now inserted. So fix that too.
This commit is contained in:
Chayim Refael Friedman 2024-11-20 23:05:48 +02:00
parent 4e475a3245
commit 5f25ae3d1b
19 changed files with 811 additions and 80 deletions

View file

@ -58,7 +58,7 @@ use crate::{
fold_tys,
generics::Generics,
infer::{coerce::CoerceMany, expr::ExprIsRead, unify::InferenceTable},
lower::ImplTraitLoweringMode,
lower::{ImplTraitLoweringMode, TyLoweringDiagnostic},
mir::MirSpan,
to_assoc_type_id,
traits::FnTrait,
@ -191,6 +191,14 @@ impl<T> InferOk<T> {
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum InferenceTyDiagnosticSource {
/// Diagnostics that come from types in the body.
Body,
/// Diagnostics that come from types in fn parameters/return type, or static & const types.
Signature,
}
#[derive(Debug)]
pub(crate) struct TypeError;
pub(crate) type InferResult<T> = Result<InferOk<T>, TypeError>;
@ -264,6 +272,10 @@ pub enum InferenceDiagnostic {
expr_ty: Ty,
cast_ty: Ty,
},
TyDiagnostic {
source: InferenceTyDiagnosticSource,
diag: TyLoweringDiagnostic,
},
}
/// A mismatch between an expected and an inferred type.
@ -858,7 +870,8 @@ impl<'a> InferenceContext<'a> {
}
fn collect_const(&mut self, data: &ConstData) {
let return_ty = self.make_ty(data.type_ref, &data.types_map);
let return_ty =
self.make_ty(data.type_ref, &data.types_map, InferenceTyDiagnosticSource::Signature);
// Constants might be defining usage sites of TAITs.
self.make_tait_coercion_table(iter::once(&return_ty));
@ -867,7 +880,8 @@ impl<'a> InferenceContext<'a> {
}
fn collect_static(&mut self, data: &StaticData) {
let return_ty = self.make_ty(data.type_ref, &data.types_map);
let return_ty =
self.make_ty(data.type_ref, &data.types_map, InferenceTyDiagnosticSource::Signature);
// Statics might be defining usage sites of TAITs.
self.make_tait_coercion_table(iter::once(&return_ty));
@ -877,11 +891,12 @@ impl<'a> InferenceContext<'a> {
fn collect_fn(&mut self, func: FunctionId) {
let data = self.db.function_data(func);
let mut param_tys = self.with_ty_lowering(&data.types_map, |ctx| {
ctx.type_param_mode(ParamLoweringMode::Placeholder)
.impl_trait_mode(ImplTraitLoweringMode::Param);
data.params.iter().map(|&type_ref| ctx.lower_ty(type_ref)).collect::<Vec<_>>()
});
let mut param_tys =
self.with_ty_lowering(&data.types_map, InferenceTyDiagnosticSource::Signature, |ctx| {
ctx.type_param_mode(ParamLoweringMode::Placeholder)
.impl_trait_mode(ImplTraitLoweringMode::Param);
data.params.iter().map(|&type_ref| ctx.lower_ty(type_ref)).collect::<Vec<_>>()
});
// Check if function contains a va_list, if it does then we append it to the parameter types
// that are collected from the function data
if data.is_varargs() {
@ -918,11 +933,12 @@ impl<'a> InferenceContext<'a> {
}
let return_ty = data.ret_type;
let return_ty = self.with_ty_lowering(&data.types_map, |ctx| {
ctx.type_param_mode(ParamLoweringMode::Placeholder)
.impl_trait_mode(ImplTraitLoweringMode::Opaque)
.lower_ty(return_ty)
});
let return_ty =
self.with_ty_lowering(&data.types_map, InferenceTyDiagnosticSource::Signature, |ctx| {
ctx.type_param_mode(ParamLoweringMode::Placeholder)
.impl_trait_mode(ImplTraitLoweringMode::Opaque)
.lower_ty(return_ty)
});
let return_ty = self.insert_type_vars(return_ty);
let return_ty = if let Some(rpits) = self.db.return_type_impl_traits(func) {
@ -1226,9 +1242,20 @@ impl<'a> InferenceContext<'a> {
self.result.diagnostics.push(diagnostic);
}
fn push_ty_diagnostics(
&mut self,
source: InferenceTyDiagnosticSource,
diagnostics: Vec<TyLoweringDiagnostic>,
) {
self.result.diagnostics.extend(
diagnostics.into_iter().map(|diag| InferenceDiagnostic::TyDiagnostic { source, diag }),
);
}
fn with_ty_lowering<R>(
&self,
&mut self,
types_map: &TypesMap,
types_source: InferenceTyDiagnosticSource,
f: impl FnOnce(&mut crate::lower::TyLoweringContext<'_>) -> R,
) -> R {
let mut ctx = crate::lower::TyLoweringContext::new(
@ -1237,32 +1264,41 @@ impl<'a> InferenceContext<'a> {
types_map,
self.owner.into(),
);
f(&mut ctx)
let result = f(&mut ctx);
self.push_ty_diagnostics(types_source, ctx.diagnostics);
result
}
fn with_body_ty_lowering<R>(
&self,
&mut self,
f: impl FnOnce(&mut crate::lower::TyLoweringContext<'_>) -> R,
) -> R {
self.with_ty_lowering(&self.body.types, f)
self.with_ty_lowering(&self.body.types, InferenceTyDiagnosticSource::Body, f)
}
fn make_ty(&mut self, type_ref: TypeRefId, types_map: &TypesMap) -> Ty {
let ty = self.with_ty_lowering(types_map, |ctx| ctx.lower_ty(type_ref));
fn make_ty(
&mut self,
type_ref: TypeRefId,
types_map: &TypesMap,
type_source: InferenceTyDiagnosticSource,
) -> Ty {
let ty = self.with_ty_lowering(types_map, type_source, |ctx| ctx.lower_ty(type_ref));
let ty = self.insert_type_vars(ty);
self.normalize_associated_types_in(ty)
}
fn make_body_ty(&mut self, type_ref: TypeRefId) -> Ty {
self.make_ty(type_ref, &self.body.types)
self.make_ty(type_ref, &self.body.types, InferenceTyDiagnosticSource::Body)
}
fn err_ty(&self) -> Ty {
self.result.standard_types.unknown.clone()
}
fn make_lifetime(&mut self, lifetime_ref: &LifetimeRef) -> Lifetime {
let lt = self.with_ty_lowering(TypesMap::EMPTY, |ctx| ctx.lower_lifetime(lifetime_ref));
fn make_body_lifetime(&mut self, lifetime_ref: &LifetimeRef) -> Lifetime {
let lt = self.with_ty_lowering(TypesMap::EMPTY, InferenceTyDiagnosticSource::Body, |ctx| {
ctx.lower_lifetime(lifetime_ref)
});
self.insert_type_vars(lt)
}
@ -1431,12 +1467,20 @@ impl<'a> InferenceContext<'a> {
Some(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,
);
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,
);
let ty = self.db.ty(strukt.into());
let ty = self.insert_type_vars(ty.substitute(Interner, &substs));
return (ty, Some(strukt.into()));
@ -1462,18 +1506,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);
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);
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);
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)
@ -1519,6 +1566,9 @@ impl<'a> InferenceContext<'a> {
resolved_segment,
current_segment,
false,
&mut |_, _reason| {
// FIXME: Report an error.
},
);
ty = self.table.insert_type_vars(ty);
@ -1532,6 +1582,7 @@ impl<'a> InferenceContext<'a> {
remaining_idx += 1;
remaining_segments = remaining_segments.skip(1);
}
self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics);
let variant = ty.as_adt().and_then(|(id, _)| match id {
AdtId::StructId(s) => Some(VariantId::StructId(s)),
@ -1550,6 +1601,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);
let ty = self.db.ty(it.into());
let ty = self.insert_type_vars(ty.substitute(Interner, &substs));

View file

@ -2155,7 +2155,7 @@ impl InferenceContext<'_> {
DebruijnIndex::INNERMOST,
)
},
|this, lt_ref| this.make_lifetime(lt_ref),
|this, lt_ref| this.make_body_lifetime(lt_ref),
),
};

View file

@ -19,7 +19,7 @@ use crate::{
TyBuilder, TyExt, TyKind, ValueTyDefId,
};
use super::{ExprOrPatId, InferenceContext};
use super::{ExprOrPatId, InferenceContext, InferenceTyDiagnosticSource};
impl InferenceContext<'_> {
pub(super) fn infer_path(&mut self, path: &Path, id: ExprOrPatId) -> Option<Ty> {
@ -163,6 +163,7 @@ impl InferenceContext<'_> {
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);
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)))?
@ -265,6 +266,9 @@ impl InferenceContext<'_> {
resolved_segment,
remaining_segments_for_ty,
true,
&mut |_, _reason| {
// FIXME: Report an error.
},
)
});
if ty.is_unknown() {

View file

@ -84,12 +84,13 @@ pub use infer::{
cast::CastError,
closure::{CaptureKind, CapturedItem},
could_coerce, could_unify, could_unify_deeply, Adjust, Adjustment, AutoBorrow, BindingMode,
InferenceDiagnostic, InferenceResult, OverloadedDeref, PointerCast,
InferenceDiagnostic, InferenceResult, InferenceTyDiagnosticSource, OverloadedDeref,
PointerCast,
};
pub use interner::Interner;
pub use lower::{
associated_type_shorthand_candidates, ImplTraitLoweringMode, ParamLoweringMode, TyDefId,
TyLoweringContext, ValueTyDefId,
associated_type_shorthand_candidates, GenericArgsProhibitedReason, ImplTraitLoweringMode,
ParamLoweringMode, TyDefId, TyLoweringContext, TyLoweringDiagnosticKind, ValueTyDefId,
};
pub use mapping::{
from_assoc_type_id, from_chalk_trait_id, from_foreign_def_id, from_placeholder_idx,

View file

@ -102,6 +102,31 @@ impl ImplTraitLoweringState {
}
}
type TypeSource = Either<TypeRefId, hir_def::type_ref::TypeSource>;
#[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,
@ -125,6 +150,7 @@ pub struct TyLoweringContext<'a> {
expander: Option<Expander>,
/// Tracks types with explicit `?Sized` bounds.
pub(crate) unsized_types: FxHashSet<Ty>,
pub(crate) diagnostics: Vec<TyLoweringDiagnostic>,
}
impl<'a> TyLoweringContext<'a> {
@ -159,6 +185,7 @@ impl<'a> TyLoweringContext<'a> {
type_param_mode,
expander: None,
unsized_types: FxHashSet::default(),
diagnostics: Vec::new(),
}
}
@ -198,6 +225,20 @@ impl<'a> TyLoweringContext<'a> {
self.type_param_mode = type_param_mode;
self
}
pub fn push_diagnostic(&mut self, type_ref: TypeRefId, kind: TyLoweringDiagnosticKind) {
let source = match self.types_source_map {
Some(source_map) => {
let Ok(source) = source_map.type_syntax(type_ref) else {
stdx::never!("error in synthetic type");
return;
};
Either::Right(source)
}
None => Either::Left(type_ref),
};
self.diagnostics.push(TyLoweringDiagnostic { source, kind });
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
@ -464,6 +505,7 @@ impl<'a> TyLoweringContext<'a> {
impl_trait_mode: mem::take(&mut self.impl_trait_mode),
expander: self.expander.take(),
unsized_types: mem::take(&mut self.unsized_types),
diagnostics: mem::take(&mut self.diagnostics),
};
let ty = inner_ctx.lower_ty(type_ref);
@ -471,6 +513,7 @@ impl<'a> TyLoweringContext<'a> {
self.impl_trait_mode = inner_ctx.impl_trait_mode;
self.expander = inner_ctx.expander;
self.unsized_types = inner_ctx.unsized_types;
self.diagnostics = inner_ctx.diagnostics;
self.expander.as_mut().unwrap().exit(mark);
Some(ty)
@ -542,6 +585,10 @@ impl<'a> TyLoweringContext<'a> {
resolved_segment: PathSegment<'_>,
remaining_segments: PathSegments<'_>,
infer_args: bool,
on_prohibited_generics_for_resolved_segment: &mut dyn FnMut(
&mut Self,
GenericArgsProhibitedReason,
),
) -> (Ty, Option<TypeNs>) {
let ty = match resolution {
TypeNs::TraitId(trait_) => {
@ -608,28 +655,44 @@ impl<'a> TyLoweringContext<'a> {
// FIXME(trait_alias): Implement trait alias.
return (TyKind::Error.intern(Interner), None);
}
TypeNs::GenericParam(param_id) => match self.type_param_mode {
ParamLoweringMode::Placeholder => {
TyKind::Placeholder(to_placeholder_idx(self.db, param_id.into()))
TypeNs::GenericParam(param_id) => {
if resolved_segment.args_and_bindings.is_some() {
on_prohibited_generics_for_resolved_segment(
self,
GenericArgsProhibitedReason::TyParam,
);
}
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))
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))
}
}
.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 {
@ -655,6 +718,13 @@ 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),
@ -667,6 +737,12 @@ 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) => {
@ -698,14 +774,39 @@ impl<'a> TyLoweringContext<'a> {
return (ty, None);
}
let (resolved_segment, remaining_segments) = match remaining_index {
None => (
path.segments().last().expect("resolved path has at least one element"),
PathSegments::EMPTY,
),
Some(i) => (path.segments().get(i - 1).unwrap(), path.segments().skip(i)),
};
self.lower_partly_resolved_path(resolution, resolved_segment, remaining_segments, false)
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);
self.lower_partly_resolved_path(
resolution,
resolved_segment,
remaining_segments,
false,
&mut |this, reason| {
this.push_diagnostic(
path_id.type_ref(),
TyLoweringDiagnosticKind::GenericArgsProhibited {
segment: resolved_segment_idx as u32,
reason,
},
)
},
)
}
fn select_associated_type(&mut self, res: Option<TypeNs>, segment: PathSegment<'_>) -> Ty {
@ -742,12 +843,8 @@ impl<'a> TyLoweringContext<'a> {
// generic params. It's inefficient to splice the `Substitution`s, so we may want
// that method to optionally take parent `Substitution` as we already know them at
// this point (`t.substitution`).
let substs = self.substs_from_path_segment(
segment.clone(),
Some(associated_ty.into()),
false,
None,
);
let substs =
self.substs_from_path_segment(segment, Some(associated_ty.into()), false, None);
let len_self =
crate::generics::generics(self.db.upcast(), associated_ty.into()).len_self();
@ -999,6 +1096,23 @@ impl<'a> TyLoweringContext<'a> {
TraitRef { trait_id: to_chalk_trait_id(resolved), substitution: substs }
}
fn prohibit_generics(
&mut self,
path_id: PathId,
idx: u32,
segments: PathSegments<'_>,
reason: GenericArgsProhibitedReason,
) {
segments.iter().zip(idx..).for_each(|(segment, idx)| {
if segment.args_and_bindings.is_some() {
self.push_diagnostic(
path_id.type_ref(),
TyLoweringDiagnosticKind::GenericArgsProhibited { segment: idx, reason },
);
}
});
}
fn lower_trait_ref_from_path(
&mut self,
path_id: PathId,
@ -1010,6 +1124,13 @@ impl<'a> TyLoweringContext<'a> {
TypeNs::TraitId(tr) => tr,
_ => return None,
};
// Do this after we verify it's indeed a trait to not confuse the user if they're not modules.
self.prohibit_generics(
path_id,
0,
path.segments().strip_last(),
GenericArgsProhibitedReason::Module,
);
let segment = path.segments().last().expect("path should have at least one segment");
Some(self.lower_trait_ref_from_resolved_path(resolved, segment, explicit_self_ty))
}
@ -1233,7 +1354,9 @@ impl<'a> TyLoweringContext<'a> {
}
_ => unreachable!(),
}
ext.lower_ty(type_ref)
let ty = ext.lower_ty(type_ref);
self.diagnostics.extend(ext.diagnostics);
ty
} else {
self.lower_ty(type_ref)
};