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

@ -33,6 +33,14 @@ syntax.workspace = true
tt.workspace = true
span.workspace = true
[dev-dependencies]
expect-test.workspace = true
# local deps
test-utils.workspace = true
test-fixture.workspace = true
syntax-bridge.workspace = true
[features]
in-rust-tree = ["hir-expand/in-rust-tree"]

View file

@ -3,23 +3,34 @@
//!
//! This probably isn't the best way to do this -- ideally, diagnostics should
//! be expressed in terms of hir types themselves.
pub use hir_ty::diagnostics::{CaseType, IncorrectCase};
use cfg::{CfgExpr, CfgOptions};
use either::Either;
use hir_def::{
hir::ExprOrPatId,
path::{hir_segment_to_ast_segment, ModPath},
type_ref::TypesSourceMap,
AssocItemId, DefWithBodyId, SyntheticSyntax,
};
use hir_expand::{name::Name, HirFileId, InFile};
use hir_ty::{
db::HirDatabase,
diagnostics::{BodyValidationDiagnostic, UnsafetyReason},
CastError, InferenceDiagnostic,
CastError, InferenceDiagnostic, InferenceTyDiagnosticSource, TyLoweringDiagnosticKind,
};
use syntax::{
ast::{self, HasGenericArgs},
AstPtr, SyntaxError, SyntaxNodePtr, TextRange,
};
use cfg::{CfgExpr, CfgOptions};
use either::Either;
pub use hir_def::VariantId;
use hir_def::{hir::ExprOrPatId, path::ModPath, AssocItemId, DefWithBodyId, SyntheticSyntax};
use hir_expand::{name::Name, HirFileId, InFile};
use syntax::{ast, AstPtr, SyntaxError, SyntaxNodePtr, TextRange};
use triomphe::Arc;
use crate::{AssocItem, Field, Local, Trait, Type};
pub use hir_def::VariantId;
pub use hir_ty::{
diagnostics::{CaseType, IncorrectCase},
GenericArgsProhibitedReason,
};
macro_rules! diagnostics {
($($diag:ident,)*) => {
#[derive(Debug)]
@ -98,6 +109,7 @@ diagnostics![
UnresolvedIdent,
UnusedMut,
UnusedVariable,
GenericArgsProhibited,
];
#[derive(Debug)]
@ -388,6 +400,12 @@ pub struct InvalidCast {
pub cast_ty: Type,
}
#[derive(Debug)]
pub struct GenericArgsProhibited {
pub args: InFile<AstPtr<Either<ast::GenericArgList, ast::ParamList>>>,
pub reason: GenericArgsProhibitedReason,
}
impl AnyDiagnostic {
pub(crate) fn body_validation_diagnostic(
db: &dyn HirDatabase,
@ -527,6 +545,7 @@ impl AnyDiagnostic {
db: &dyn HirDatabase,
def: DefWithBodyId,
d: &InferenceDiagnostic,
outer_types_source_map: &TypesSourceMap,
source_map: &hir_def::body::BodySourceMap,
) -> Option<AnyDiagnostic> {
let expr_syntax = |expr| {
@ -640,6 +659,36 @@ impl AnyDiagnostic {
let cast_ty = Type::new(db, def, cast_ty.clone());
InvalidCast { expr, error: *error, expr_ty, cast_ty }.into()
}
InferenceDiagnostic::TyDiagnostic { source, diag } => {
let source_map = match source {
InferenceTyDiagnosticSource::Body => &source_map.types,
InferenceTyDiagnosticSource::Signature => outer_types_source_map,
};
let source = match diag.source {
Either::Left(type_ref_id) => {
let Ok(source) = source_map.type_syntax(type_ref_id) else {
stdx::never!("error on synthetic type syntax");
return None;
};
source
}
Either::Right(source) => source,
};
let syntax = || source.value.to_node(&db.parse_or_expand(source.file_id));
match diag.kind {
TyLoweringDiagnosticKind::GenericArgsProhibited { segment, reason } => {
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.param_list()?).wrap_right()
};
let args = source.with_value(args);
GenericArgsProhibited { args, reason }.into()
}
}
}
})
}
}

View file

@ -20,12 +20,11 @@
#![cfg_attr(feature = "in-rust-tree", feature(rustc_private))]
#![recursion_limit = "512"]
mod semantics;
mod source_analyzer;
mod attrs;
mod from_id;
mod has_source;
mod semantics;
mod source_analyzer;
pub mod db;
pub mod diagnostics;
@ -54,6 +53,7 @@ use hir_def::{
path::ImportAlias,
per_ns::PerNs,
resolver::{HasResolver, Resolver},
type_ref::TypesSourceMap,
AssocItemId, AssocItemLoc, AttrDefId, CallableDefId, ConstId, ConstParamId, CrateRootModuleId,
DefWithBodyId, EnumId, EnumVariantId, ExternCrateId, FunctionId, GenericDefId, GenericParamId,
HasModule, ImplId, InTypeConstId, ItemContainerId, LifetimeParamId, LocalFieldId, Lookup,
@ -1802,6 +1802,25 @@ impl DefWithBody {
let krate = self.module(db).id.krate();
let (body, source_map) = db.body_with_source_map(self.into());
let item_tree_source_maps;
let outer_types_source_map = match self {
DefWithBody::Function(function) => {
let function = function.id.lookup(db.upcast()).id;
item_tree_source_maps = function.item_tree_with_source_map(db.upcast()).1;
item_tree_source_maps.function(function.value).item()
}
DefWithBody::Static(statik) => {
let statik = statik.id.lookup(db.upcast()).id;
item_tree_source_maps = statik.item_tree_with_source_map(db.upcast()).1;
item_tree_source_maps.statik(statik.value)
}
DefWithBody::Const(konst) => {
let konst = konst.id.lookup(db.upcast()).id;
item_tree_source_maps = konst.item_tree_with_source_map(db.upcast()).1;
item_tree_source_maps.konst(konst.value)
}
DefWithBody::Variant(_) | DefWithBody::InTypeConst(_) => &TypesSourceMap::EMPTY,
};
for (_, def_map) in body.blocks(db.upcast()) {
Module { id: def_map.module_id(DefMap::ROOT) }.diagnostics(db, acc, style_lints);
@ -1861,7 +1880,13 @@ impl DefWithBody {
let infer = db.infer(self.into());
for d in &infer.diagnostics {
acc.extend(AnyDiagnostic::inference_diagnostic(db, self.into(), d, &source_map));
acc.extend(AnyDiagnostic::inference_diagnostic(
db,
self.into(),
d,
outer_types_source_map,
&source_map,
));
}
for (pat_or_expr, mismatch) in infer.type_mismatches() {