From 99ce53b1d7dae328b93d9dbd2e8ce8dc83a2b1ba Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Sun, 30 Mar 2025 20:20:24 +0300 Subject: [PATCH] Add two new diagnostics: one for mismatch in generic arguments count, and another for mismatch in their kind Also known as E0747 and E0107. And by the way, rewrite how we lower generic arguments and deduplicate it between paths and method calls. The new version is taken almost straight from rustc. This commit also changes the binders of `generic_defaults()`, to only include the binders of the arguments up to (and not including) the current argument. This make it easier to handle it in the rewritten lowering of generic args. It's also how rustc does it. --- crates/hir-def/src/expr_store.rs | 4 +- crates/hir-def/src/expr_store/lower.rs | 32 + crates/hir-def/src/expr_store/lower/path.rs | 4 +- crates/hir-def/src/hir/generics.rs | 1 + crates/hir-ty/src/builder.rs | 39 +- crates/hir-ty/src/db.rs | 3 + crates/hir-ty/src/display.rs | 2 +- crates/hir-ty/src/generics.rs | 18 +- crates/hir-ty/src/infer.rs | 33 +- crates/hir-ty/src/infer/diagnostics.rs | 7 +- crates/hir-ty/src/infer/expr.rs | 218 ++++--- crates/hir-ty/src/infer/path.rs | 7 +- crates/hir-ty/src/lib.rs | 32 +- crates/hir-ty/src/lower.rs | 172 ++---- crates/hir-ty/src/lower/diagnostics.rs | 43 +- crates/hir-ty/src/lower/path.rs | 576 +++++++++++++----- crates/hir/src/diagnostics.rs | 147 ++++- crates/hir/src/lib.rs | 38 +- .../src/handlers/generic_args_prohibited.rs | 24 +- .../src/handlers/incorrect_generics_len.rs | 172 ++++++ .../src/handlers/incorrect_generics_order.rs | 80 +++ crates/ide-diagnostics/src/lib.rs | 4 + 22 files changed, 1247 insertions(+), 409 deletions(-) create mode 100644 crates/ide-diagnostics/src/handlers/incorrect_generics_len.rs create mode 100644 crates/ide-diagnostics/src/handlers/incorrect_generics_order.rs diff --git a/crates/hir-def/src/expr_store.rs b/crates/hir-def/src/expr_store.rs index aa26e8b3df..3141fceeb0 100644 --- a/crates/hir-def/src/expr_store.rs +++ b/crates/hir-def/src/expr_store.rs @@ -35,7 +35,9 @@ use crate::{ }; pub use self::body::{Body, BodySourceMap}; -pub use self::lower::hir_segment_to_ast_segment; +pub use self::lower::{ + hir_assoc_type_binding_to_ast, hir_generic_arg_to_ast, hir_segment_to_ast_segment, +}; /// A wrapper around [`span::SyntaxContextId`] that is intended only for comparisons. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] diff --git a/crates/hir-def/src/expr_store/lower.rs b/crates/hir-def/src/expr_store/lower.rs index e7f2247c5b..d49c283bee 100644 --- a/crates/hir-def/src/expr_store/lower.rs +++ b/crates/hir-def/src/expr_store/lower.rs @@ -788,6 +788,7 @@ impl ExprCollector<'_> { node: ast::GenericArgList, impl_trait_lower_fn: &mut impl FnMut(ThinVec) -> TypeRef, ) -> Option { + // This needs to be kept in sync with `hir_generic_arg_to_ast()`. let mut args = Vec::new(); let mut bindings = Vec::new(); for generic_arg in node.generic_args() { @@ -797,6 +798,7 @@ impl ExprCollector<'_> { args.push(GenericArg::Type(type_ref)); } ast::GenericArg::AssocTypeArg(assoc_type_arg) => { + // This needs to be kept in sync with `hir_assoc_type_binding_to_ast()`. if assoc_type_arg.param_list().is_some() { // We currently ignore associated return type bounds. continue; @@ -3228,3 +3230,33 @@ enum ArgumentType { Format(FormatTrait), Usize, } + +/// This function find the AST fragment that corresponds to an `AssociatedTypeBinding` in the HIR. +pub fn hir_assoc_type_binding_to_ast( + segment_args: &ast::GenericArgList, + binding_idx: u32, +) -> Option { + segment_args + .generic_args() + .filter_map(|arg| match arg { + ast::GenericArg::AssocTypeArg(it) => Some(it), + _ => None, + }) + .filter(|binding| binding.param_list().is_none() && binding.name_ref().is_some()) + .nth(binding_idx as usize) +} + +/// This function find the AST generic argument from the one in the HIR. Does not support the `Self` argument. +pub fn hir_generic_arg_to_ast( + args: &ast::GenericArgList, + arg_idx: u32, + has_self_arg: bool, +) -> Option { + args.generic_args() + .filter(|arg| match arg { + ast::GenericArg::AssocTypeArg(_) => false, + ast::GenericArg::LifetimeArg(arg) => arg.lifetime().is_some(), + ast::GenericArg::ConstArg(_) | ast::GenericArg::TypeArg(_) => true, + }) + .nth(arg_idx as usize - has_self_arg as usize) +} diff --git a/crates/hir-def/src/expr_store/lower/path.rs b/crates/hir-def/src/expr_store/lower/path.rs index 36b3d11d75..14b0d3abd4 100644 --- a/crates/hir-def/src/expr_store/lower/path.rs +++ b/crates/hir-def/src/expr_store/lower/path.rs @@ -152,10 +152,8 @@ pub(super) fn lower_path( args: iter::once(self_type) .chain(it.args.iter().cloned()) .collect(), - has_self_type: true, - bindings: it.bindings.clone(), - parenthesized: it.parenthesized, + ..it }, None => GenericArgs { args: Box::new([self_type]), diff --git a/crates/hir-def/src/hir/generics.rs b/crates/hir-def/src/hir/generics.rs index 890e7874a8..52c0c669ea 100644 --- a/crates/hir-def/src/hir/generics.rs +++ b/crates/hir-def/src/hir/generics.rs @@ -138,6 +138,7 @@ impl GenericParamData { impl_from!(TypeParamData, ConstParamData, LifetimeParamData for GenericParamData); +#[derive(Debug, Clone, Copy)] pub enum GenericParamDataRef<'a> { TypeParamData(&'a TypeParamData), ConstParamData(&'a ConstParamData), diff --git a/crates/hir-ty/src/builder.rs b/crates/hir-ty/src/builder.rs index 8c3665dfc8..77d15a73af 100644 --- a/crates/hir-ty/src/builder.rs +++ b/crates/hir-ty/src/builder.rs @@ -306,29 +306,28 @@ impl TyBuilder { // Note that we're building ADT, so we never have parent generic parameters. let defaults = db.generic_defaults(self.data.into()); - for default_ty in &defaults[self.vec.len()..] { - // NOTE(skip_binders): we only check if the arg type is error type. - if let Some(x) = default_ty.skip_binders().ty(Interner) { - if x.is_unknown() { - self.vec.push(fallback().cast(Interner)); - continue; + if let Some(defaults) = defaults.get(self.vec.len()..) { + for default_ty in defaults { + // NOTE(skip_binders): we only check if the arg type is error type. + if let Some(x) = default_ty.skip_binders().ty(Interner) { + if x.is_unknown() { + self.vec.push(fallback().cast(Interner)); + continue; + } } + // Each default can only depend on the previous parameters. + self.vec.push(default_ty.clone().substitute(Interner, &*self.vec).cast(Interner)); } - // Each default can only depend on the previous parameters. - let subst_so_far = Substitution::from_iter( - Interner, - self.vec - .iter() - .cloned() - .chain(self.param_kinds[self.vec.len()..].iter().map(|it| match it { - ParamKind::Type => TyKind::Error.intern(Interner).cast(Interner), - ParamKind::Lifetime => error_lifetime().cast(Interner), - ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()), - })) - .take(self.param_kinds.len()), - ); - self.vec.push(default_ty.clone().substitute(Interner, &subst_so_far).cast(Interner)); } + + // The defaults may be missing if no param has default, so fill that. + let filler = self.param_kinds[self.vec.len()..].iter().map(|x| match x { + ParamKind::Type => fallback().cast(Interner), + ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()), + ParamKind::Lifetime => error_lifetime().cast(Interner), + }); + self.vec.extend(filler.casted(Interner)); + self } diff --git a/crates/hir-ty/src/db.rs b/crates/hir-ty/src/db.rs index 563d3c7141..75a680b588 100644 --- a/crates/hir-ty/src/db.rs +++ b/crates/hir-ty/src/db.rs @@ -200,6 +200,9 @@ pub trait HirDatabase: DefDatabase + std::fmt::Debug { def: GenericDefId, ) -> (GenericDefaults, Diagnostics); + /// This returns an empty list if no parameter has default. + /// + /// The binders of the returned defaults are only up to (not including) this parameter. #[salsa::invoke(crate::lower::generic_defaults_query)] #[salsa::transparent] fn generic_defaults(&self, def: GenericDefId) -> GenericDefaults; diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs index a79b8cdf8e..39d6083b35 100644 --- a/crates/hir-ty/src/display.rs +++ b/crates/hir-ty/src/display.rs @@ -1640,7 +1640,7 @@ fn generic_args_sans_defaults<'ga>( Some(default_parameter) => { // !is_err(default_parameter.skip_binders()) // && - arg != &default_parameter.clone().substitute(Interner, ¶meters) + arg != &default_parameter.clone().substitute(Interner, ¶meters[..i]) } } }; diff --git a/crates/hir-ty/src/generics.rs b/crates/hir-ty/src/generics.rs index 8379636d5c..bb4aaf7889 100644 --- a/crates/hir-ty/src/generics.rs +++ b/crates/hir-ty/src/generics.rs @@ -21,7 +21,6 @@ use hir_def::{ }, }; use itertools::chain; -use stdx::TupleExt; use triomphe::Arc; use crate::{Interner, Substitution, db::HirDatabase, lt_to_placeholder_idx, to_placeholder_idx}; @@ -76,10 +75,13 @@ impl Generics { self.iter_parent().map(|(id, _)| id) } - pub(crate) fn iter_self_type_or_consts_id( + pub(crate) fn iter_self_type_or_consts( &self, - ) -> impl DoubleEndedIterator + '_ { - self.params.iter_type_or_consts().map(from_toc_id(self)).map(TupleExt::head) + ) -> impl DoubleEndedIterator + '_ + { + let mut toc = self.params.iter_type_or_consts(); + let trait_self_param = self.has_trait_self_param.then(|| toc.next()).flatten(); + chain!(trait_self_param, toc) } /// Iterate over the parent params followed by self params. @@ -107,7 +109,7 @@ impl Generics { } /// Iterator over types and const params of parent. - fn iter_parent( + pub(crate) fn iter_parent( &self, ) -> impl DoubleEndedIterator)> + '_ { self.parent_generics().into_iter().flat_map(|it| { @@ -129,6 +131,10 @@ impl Generics { self.params.len() } + pub(crate) fn len_lifetimes_self(&self) -> usize { + self.params.len_lifetimes() + } + /// (parent total, self param, type params, const params, impl trait list, lifetimes) pub(crate) fn provenance_split(&self) -> (usize, bool, usize, usize, usize, usize) { let mut self_param = false; @@ -144,7 +150,7 @@ impl Generics { TypeOrConstParamData::ConstParamData(_) => const_params += 1, }); - let lifetime_params = self.params.iter_lt().count(); + let lifetime_params = self.params.len_lifetimes(); let parent_len = self.parent_generics().map_or(0, Generics::len); (parent_len, self_param, type_params, const_params, impl_trait_params, lifetime_params) diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 24b1909b6d..cad2e3ce99 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -34,8 +34,8 @@ use chalk_ir::{ }; use either::Either; use hir_def::{ - AdtId, AssocItemId, DefWithBodyId, FieldId, FunctionId, GenericDefId, ImplId, ItemContainerId, - Lookup, TraitId, TupleFieldId, TupleId, TypeAliasId, VariantId, + AdtId, AssocItemId, DefWithBodyId, FieldId, FunctionId, GenericDefId, GenericParamId, ImplId, + ItemContainerId, Lookup, TraitId, TupleFieldId, TupleId, TypeAliasId, VariantId, builtin_type::{BuiltinInt, BuiltinType, BuiltinUint}, expr_store::{Body, ExpressionStore, HygieneId, path::Path}, hir::{BindingAnnotation, BindingId, ExprId, ExprOrPatId, LabelId, PatId}, @@ -55,8 +55,9 @@ use triomphe::Arc; use crate::{ AliasEq, AliasTy, Binders, ClosureId, Const, DomainGoal, GenericArg, Goal, ImplTraitId, - ImplTraitIdx, InEnvironment, Interner, Lifetime, OpaqueTyId, ParamLoweringMode, - PathLoweringDiagnostic, ProjectionTy, Substitution, TraitEnvironment, Ty, TyBuilder, TyExt, + ImplTraitIdx, InEnvironment, IncorrectGenericsLenKind, Interner, Lifetime, OpaqueTyId, + ParamLoweringMode, PathLoweringDiagnostic, ProjectionTy, Substitution, TraitEnvironment, Ty, + TyBuilder, TyExt, db::HirDatabase, fold_tys, generics::Generics, @@ -66,7 +67,7 @@ use crate::{ expr::ExprIsRead, unify::InferenceTable, }, - lower::{ImplTraitLoweringMode, diagnostics::TyLoweringDiagnostic}, + lower::{GenericArgsPosition, ImplTraitLoweringMode, diagnostics::TyLoweringDiagnostic}, mir::MirSpan, to_assoc_type_id, traits::FnTrait, @@ -275,6 +276,20 @@ pub enum InferenceDiagnostic { node: ExprOrPatId, diag: PathLoweringDiagnostic, }, + MethodCallIncorrectGenericsLen { + expr: ExprId, + provided_count: u32, + expected_count: u32, + kind: IncorrectGenericsLenKind, + def: GenericDefId, + }, + MethodCallIncorrectGenericsOrder { + expr: ExprId, + param_id: GenericParamId, + arg_idx: u32, + /// Whether the `GenericArgs` contains a `Self` arg. + has_self_arg: bool, + }, } /// A mismatch between an expected and an inferred type. @@ -909,6 +924,7 @@ impl<'a> InferenceContext<'a> { let mut param_tys = self.with_ty_lowering(&data.store, InferenceTyDiagnosticSource::Signature, |ctx| { ctx.type_param_mode(ParamLoweringMode::Placeholder); + ctx.in_fn_signature = true; data.params.iter().map(|&type_ref| ctx.lower_ty(type_ref)).collect::>() }); @@ -953,8 +969,9 @@ impl<'a> InferenceContext<'a> { InferenceTyDiagnosticSource::Signature, |ctx| { ctx.type_param_mode(ParamLoweringMode::Placeholder) - .impl_trait_mode(ImplTraitLoweringMode::Opaque) - .lower_ty(return_ty) + .impl_trait_mode(ImplTraitLoweringMode::Opaque); + ctx.in_fn_signature = true; + ctx.lower_ty(return_ty) }, ); let return_ty = self.insert_type_vars(return_ty); @@ -1513,7 +1530,7 @@ impl<'a> InferenceContext<'a> { InferenceTyDiagnosticSource::Body, self.generic_def, ); - let mut path_ctx = ctx.at_path(path, node); + let mut path_ctx = ctx.at_path(path, node, GenericArgsPosition::Value); let (resolution, unresolved) = if value_ns { let Some(res) = path_ctx.resolve_path_in_value_ns(HygieneId::ROOT) else { return (self.err_ty(), None); diff --git a/crates/hir-ty/src/infer/diagnostics.rs b/crates/hir-ty/src/infer/diagnostics.rs index f613e2f69f..2c633a03c5 100644 --- a/crates/hir-ty/src/infer/diagnostics.rs +++ b/crates/hir-ty/src/infer/diagnostics.rs @@ -12,6 +12,7 @@ use hir_def::expr_store::path::Path; use hir_def::{hir::ExprOrPatId, resolver::Resolver}; use la_arena::{Idx, RawIdx}; +use crate::lower::GenericArgsPosition; use crate::{ InferenceDiagnostic, InferenceTyDiagnosticSource, TyLoweringContext, TyLoweringDiagnostic, db::HirDatabase, @@ -74,6 +75,7 @@ impl<'a> InferenceTyLoweringContext<'a> { &'b mut self, path: &'b Path, node: ExprOrPatId, + position: GenericArgsPosition, ) -> PathLoweringContext<'b, 'a> { let on_diagnostic = PathDiagnosticCallback { data: Either::Right(PathDiagnosticCallbackData { diagnostics: self.diagnostics, node }), @@ -83,13 +85,14 @@ impl<'a> InferenceTyLoweringContext<'a> { .push(InferenceDiagnostic::PathDiagnostic { node: data.node, diag }); }, }; - PathLoweringContext::new(&mut self.ctx, on_diagnostic, path) + PathLoweringContext::new(&mut self.ctx, on_diagnostic, path, position) } #[inline] pub(super) fn at_path_forget_diagnostics<'b>( &'b mut self, path: &'b Path, + position: GenericArgsPosition, ) -> PathLoweringContext<'b, 'a> { let on_diagnostic = PathDiagnosticCallback { data: Either::Right(PathDiagnosticCallbackData { @@ -98,7 +101,7 @@ impl<'a> InferenceTyLoweringContext<'a> { }), callback: |_data, _, _diag| {}, }; - PathLoweringContext::new(&mut self.ctx, on_diagnostic, path) + PathLoweringContext::new(&mut self.ctx, on_diagnostic, path, position) } #[inline] diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 17cd322a22..2980549c23 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -12,7 +12,7 @@ use hir_def::{ expr_store::path::{GenericArg, GenericArgs, Path}, hir::{ ArithOp, Array, AsmOperand, AsmOptions, BinaryOp, Expr, ExprId, ExprOrPatId, LabelId, - Literal, Pat, PatId, Statement, UnaryOp, + Literal, Pat, PatId, Statement, UnaryOp, generics::GenericParamDataRef, }, lang_item::{LangItem, LangItemTarget}, resolver::ValueNs, @@ -24,11 +24,11 @@ use syntax::ast::RangeOp; use crate::{ Adjust, Adjustment, AdtId, AutoBorrow, Binders, CallableDefId, CallableSig, DeclContext, - DeclOrigin, Interner, Rawness, Scalar, Substitution, TraitEnvironment, TraitRef, Ty, TyBuilder, - TyExt, TyKind, + DeclOrigin, IncorrectGenericsLenKind, Interner, Rawness, Scalar, Substitution, + TraitEnvironment, TraitRef, Ty, TyBuilder, TyExt, TyKind, autoderef::{Autoderef, builtin_deref, deref_by_trait}, - consteval, error_lifetime, - generics::{Generics, generics}, + consteval, + generics::generics, infer::{ BreakableKind, coerce::{CoerceMany, CoerceNever, CoercionCause}, @@ -36,7 +36,10 @@ use crate::{ pat::contains_explicit_ref_binding, }, lang_items::lang_items_for_bin_op, - lower::{ParamLoweringMode, generic_arg_to_chalk, lower_to_chalk_mutability}, + lower::{ + GenericArgsPosition, ParamLoweringMode, lower_to_chalk_mutability, + path::{GenericArgsLowerer, TypeLikeConst, substs_from_args_and_bindings}, + }, mapping::{ToChalk, from_chalk}, method_resolution::{self, VisibleFromModule}, primitive::{self, UintTy}, @@ -1658,8 +1661,7 @@ impl InferenceContext<'_> { match resolved { Some((adjust, func, _)) => { let (ty, adjustments) = adjust.apply(&mut self.table, receiver_ty); - let generics = generics(self.db, func.into()); - let substs = self.substs_for_method_call(generics, None); + let substs = self.substs_for_method_call(tgt_expr, func.into(), None); self.write_expr_adj(receiver, adjustments); self.write_method_resolution(tgt_expr, func, substs.clone()); @@ -1809,8 +1811,7 @@ impl InferenceContext<'_> { let (ty, adjustments) = adjust.apply(&mut self.table, receiver_ty); self.write_expr_adj(receiver, adjustments); - let generics = generics(self.db, func.into()); - let substs = self.substs_for_method_call(generics, generic_args); + let substs = self.substs_for_method_call(tgt_expr, func.into(), generic_args); self.write_method_resolution(tgt_expr, func, substs.clone()); self.check_method_call( tgt_expr, @@ -1860,8 +1861,7 @@ impl InferenceContext<'_> { let recovered = match assoc_func_with_same_name { Some(f) => { - let generics = generics(self.db, f.into()); - let substs = self.substs_for_method_call(generics, generic_args); + let substs = self.substs_for_method_call(tgt_expr, f.into(), generic_args); let f = self .db .value_ty(f.into()) @@ -2051,83 +2051,129 @@ impl InferenceContext<'_> { fn substs_for_method_call( &mut self, - def_generics: Generics, + expr: ExprId, + def: GenericDefId, generic_args: Option<&GenericArgs>, ) -> Substitution { - let ( - parent_params, - has_self_param, - type_params, - const_params, - impl_trait_params, - lifetime_params, - ) = def_generics.provenance_split(); - assert!(!has_self_param); // method shouldn't have another Self param - let total_len = - parent_params + type_params + const_params + impl_trait_params + lifetime_params; - - let param_to_var = |id| match id { - GenericParamId::TypeParamId(_) => self.table.new_type_var().cast(Interner), - GenericParamId::ConstParamId(id) => { - self.table.new_const_var(self.db.const_param_ty(id)).cast(Interner) - } - GenericParamId::LifetimeParamId(_) => self.table.new_lifetime_var().cast(Interner), - }; - - let mut substs: Vec<_> = def_generics.iter_parent_id().map(param_to_var).collect(); - - // handle provided arguments - if let Some(generic_args) = generic_args { - // if args are provided, it should be all of them, but we can't rely on that - let self_params = type_params + const_params + lifetime_params; - - let mut args = generic_args.args.iter().peekable(); - for kind_id in def_generics.iter_self_id().take(self_params) { - let arg = args.peek(); - let arg = match (kind_id, arg) { - // Lifetimes can be inferred. - // Once we have implemented lifetime inference correctly, - // this should be handled in a proper way. - ( - GenericParamId::LifetimeParamId(_), - None | Some(GenericArg::Type(_) | GenericArg::Const(_)), - ) => error_lifetime().cast(Interner), - - // If we run out of `generic_args`, stop pushing substs - (_, None) => break, - - // Normal cases - (_, Some(_)) => generic_arg_to_chalk( - self.db, - kind_id, - args.next().unwrap(), // `peek()` is `Some(_)`, so guaranteed no panic - self, - &self.body.store, - |this, type_ref| this.make_body_ty(type_ref), - |this, c, ty| this.make_body_const(*c, ty), - |this, path, ty| this.make_path_as_body_const(path, ty), - |this, lt_ref| this.make_body_lifetime(lt_ref), - ), - }; - - substs.push(arg); - } - }; - - let mut param_to_var = |id| match id { - GenericParamId::TypeParamId(_) => self.table.new_type_var().cast(Interner), - GenericParamId::ConstParamId(id) => { - self.table.new_const_var(self.db.const_param_ty(id)).cast(Interner) - } - GenericParamId::LifetimeParamId(_) => self.table.new_lifetime_var().cast(Interner), - }; - - // Handle everything else as unknown. - for (id, _data) in def_generics.iter().skip(substs.len()) { - substs.push(param_to_var(id)); + struct LowererCtx<'a, 'b> { + ctx: &'a mut InferenceContext<'b>, + expr: ExprId, } - assert_eq!(substs.len(), total_len); - Substitution::from_iter(Interner, substs) + + impl GenericArgsLowerer for LowererCtx<'_, '_> { + fn report_len_mismatch( + &mut self, + def: GenericDefId, + provided_count: u32, + expected_count: u32, + kind: IncorrectGenericsLenKind, + ) { + self.ctx.push_diagnostic(InferenceDiagnostic::MethodCallIncorrectGenericsLen { + expr: self.expr, + provided_count, + expected_count, + kind, + def, + }); + } + + fn report_arg_mismatch( + &mut self, + param_id: GenericParamId, + arg_idx: u32, + has_self_arg: bool, + ) { + self.ctx.push_diagnostic(InferenceDiagnostic::MethodCallIncorrectGenericsOrder { + expr: self.expr, + param_id, + arg_idx, + has_self_arg, + }); + } + + fn provided_kind( + &mut self, + param_id: GenericParamId, + param: GenericParamDataRef<'_>, + arg: &GenericArg, + ) -> crate::GenericArg { + match (param, arg) { + (GenericParamDataRef::LifetimeParamData(_), GenericArg::Lifetime(lifetime)) => { + self.ctx.make_body_lifetime(lifetime).cast(Interner) + } + (GenericParamDataRef::TypeParamData(_), GenericArg::Type(type_ref)) => { + self.ctx.make_body_ty(*type_ref).cast(Interner) + } + (GenericParamDataRef::ConstParamData(_), GenericArg::Const(konst)) => { + let GenericParamId::ConstParamId(const_id) = param_id else { + unreachable!("non-const param ID for const param"); + }; + let const_ty = self.ctx.db.const_param_ty(const_id); + self.ctx.make_body_const(*konst, const_ty).cast(Interner) + } + _ => unreachable!("unmatching param kinds were passed to `provided_kind()`"), + } + } + + fn provided_type_like_const( + &mut self, + const_ty: Ty, + arg: TypeLikeConst<'_>, + ) -> crate::Const { + match arg { + TypeLikeConst::Path(path) => self.ctx.make_path_as_body_const(path, const_ty), + TypeLikeConst::Infer => self.ctx.table.new_const_var(const_ty), + } + } + + fn inferred_kind( + &mut self, + _def: GenericDefId, + param_id: GenericParamId, + _param: GenericParamDataRef<'_>, + _infer_args: bool, + _preceding_args: &[crate::GenericArg], + ) -> crate::GenericArg { + // Always create an inference var, even when `infer_args == false`. This helps with diagnostics, + // and I think it's also required in the presence of `impl Trait` (that must be inferred). + match param_id { + GenericParamId::TypeParamId(_) => self.ctx.table.new_type_var().cast(Interner), + GenericParamId::ConstParamId(const_id) => self + .ctx + .table + .new_const_var(self.ctx.db.const_param_ty(const_id)) + .cast(Interner), + GenericParamId::LifetimeParamId(_) => { + self.ctx.table.new_lifetime_var().cast(Interner) + } + } + } + + fn parent_arg(&mut self, param_id: GenericParamId) -> crate::GenericArg { + match param_id { + GenericParamId::TypeParamId(_) => self.ctx.table.new_type_var().cast(Interner), + GenericParamId::ConstParamId(const_id) => self + .ctx + .table + .new_const_var(self.ctx.db.const_param_ty(const_id)) + .cast(Interner), + GenericParamId::LifetimeParamId(_) => { + self.ctx.table.new_lifetime_var().cast(Interner) + } + } + } + } + + substs_from_args_and_bindings( + self.db, + self.body, + generic_args, + def, + true, + GenericArgsPosition::MethodCall, + None, + &mut LowererCtx { ctx: self, expr }, + ) } fn register_obligations_for_call(&mut self, callable_ty: &Ty) { diff --git a/crates/hir-ty/src/infer/path.rs b/crates/hir-ty/src/infer/path.rs index 461cea4f14..0e1d23b694 100644 --- a/crates/hir-ty/src/infer/path.rs +++ b/crates/hir-ty/src/infer/path.rs @@ -16,6 +16,7 @@ use crate::{ consteval, error_lifetime, generics::generics, infer::diagnostics::InferenceTyLoweringContext as TyLoweringContext, + lower::GenericArgsPosition, method_resolution::{self, VisibleFromModule}, to_chalk_trait_id, }; @@ -95,7 +96,7 @@ impl InferenceContext<'_> { }; let substs = self.with_body_ty_lowering(|ctx| { - let mut path_ctx = ctx.at_path(path, id); + let mut path_ctx = ctx.at_path(path, id, GenericArgsPosition::Value); let last_segment = path.segments().len().checked_sub(1); if let Some(last_segment) = last_segment { path_ctx.set_current_segment(last_segment) @@ -163,9 +164,9 @@ impl InferenceContext<'_> { self.generic_def, ); let mut path_ctx = if no_diagnostics { - ctx.at_path_forget_diagnostics(path) + ctx.at_path_forget_diagnostics(path, GenericArgsPosition::Value) } else { - ctx.at_path(path, id) + ctx.at_path(path, id, GenericArgsPosition::Value) }; let (value, self_subst) = if let Some(type_ref) = path.type_anchor() { let last = path.segments().last()?; diff --git a/crates/hir-ty/src/lib.rs b/crates/hir-ty/src/lib.rs index 4f60bb21b6..2cb977b634 100644 --- a/crates/hir-ty/src/lib.rs +++ b/crates/hir-ty/src/lib.rs @@ -347,20 +347,24 @@ pub(crate) fn make_binders>( generics: &Generics, value: T, ) -> Binders { - Binders::new( - VariableKinds::from_iter( - Interner, - generics.iter_id().map(|x| match x { - hir_def::GenericParamId::ConstParamId(id) => { - chalk_ir::VariableKind::Const(db.const_param_ty(id)) - } - hir_def::GenericParamId::TypeParamId(_) => { - chalk_ir::VariableKind::Ty(chalk_ir::TyVariableKind::General) - } - hir_def::GenericParamId::LifetimeParamId(_) => chalk_ir::VariableKind::Lifetime, - }), - ), - value, + Binders::new(variable_kinds_from_iter(db, generics.iter_id()), value) +} + +pub(crate) fn variable_kinds_from_iter( + db: &dyn HirDatabase, + iter: impl Iterator, +) -> VariableKinds { + VariableKinds::from_iter( + Interner, + iter.map(|x| match x { + hir_def::GenericParamId::ConstParamId(id) => { + chalk_ir::VariableKind::Const(db.const_param_ty(id)) + } + hir_def::GenericParamId::TypeParamId(_) => { + chalk_ir::VariableKind::Ty(chalk_ir::TyVariableKind::General) + } + hir_def::GenericParamId::LifetimeParamId(_) => chalk_ir::VariableKind::Lifetime, + }), ) } diff --git a/crates/hir-ty/src/lower.rs b/crates/hir-ty/src/lower.rs index 57849ff913..ea42c59296 100644 --- a/crates/hir-ty/src/lower.rs +++ b/crates/hir-ty/src/lower.rs @@ -28,10 +28,7 @@ use hir_def::{ FunctionId, GenericDefId, GenericParamId, HasModule, ImplId, LocalFieldId, Lookup, StaticId, StructId, TypeAliasId, TypeOrConstParamId, UnionId, VariantId, builtin_type::BuiltinType, - expr_store::{ - ExpressionStore, - path::{GenericArg, Path}, - }, + expr_store::{ExpressionStore, path::Path}, hir::generics::{ GenericParamDataRef, TypeOrConstParamData, WherePredicate, WherePredicateTypeTarget, }, @@ -55,9 +52,9 @@ use triomphe::{Arc, ThinArc}; use crate::{ AliasTy, Binders, BoundVar, CallableSig, Const, DebruijnIndex, DynTy, FnAbi, FnPointer, FnSig, FnSubst, ImplTrait, ImplTraitId, ImplTraits, Interner, Lifetime, LifetimeData, - LifetimeOutlives, ParamKind, PolyFnSig, ProgramClause, QuantifiedWhereClause, - QuantifiedWhereClauses, Substitution, TraitEnvironment, TraitRef, TraitRefExt, Ty, TyBuilder, - TyKind, WhereClause, all_super_traits, + LifetimeOutlives, PolyFnSig, ProgramClause, QuantifiedWhereClause, QuantifiedWhereClauses, + Substitution, TraitEnvironment, TraitRef, TraitRefExt, Ty, TyBuilder, TyKind, WhereClause, + all_super_traits, consteval::{intern_const_ref, path_to_const, unknown_const, unknown_const_as_generic}, db::HirDatabase, error_lifetime, @@ -70,6 +67,7 @@ use crate::{ mapping::{ToChalk, from_chalk_trait_id, lt_to_placeholder_idx}, static_lifetime, to_chalk_trait_id, to_placeholder_idx, utils::all_super_trait_refs, + variable_kinds_from_iter, }; #[derive(Debug, Default)] @@ -89,6 +87,22 @@ impl ImplTraitLoweringState { pub(crate) struct PathDiagnosticCallbackData(TypeRefId); +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum GenericArgsPosition { + Type, + /// E.g. functions. + Value, + MethodCall, + // FIXME: This is a temporary variant we need to work around the lack of lifetime elision. + // The reason for its existence is that in `check_generic_args_len()`, without this, we will + // not infer elide lifetimes. + // They indeed should not be inferred - they should be elided - but we won't elide them either, + // emitting an error instead. rustc elides them in late resolve, and the generics it passes + // to lowering already include them. We probably can't do that, but we will still need to + // account for them when we properly implement lifetime elision. + FnSignature, +} + #[derive(Debug)] pub struct TyLoweringContext<'a> { pub db: &'a dyn HirDatabase, @@ -106,6 +120,7 @@ pub struct TyLoweringContext<'a> { /// Tracks types with explicit `?Sized` bounds. pub(crate) unsized_types: FxHashSet, pub(crate) diagnostics: Vec, + pub(crate) in_fn_signature: bool, } impl<'a> TyLoweringContext<'a> { @@ -129,6 +144,7 @@ impl<'a> TyLoweringContext<'a> { type_param_mode, unsized_types: FxHashSet::default(), diagnostics: Vec::new(), + in_fn_signature: false, } } @@ -415,6 +431,11 @@ impl<'a> TyLoweringContext<'a> { self, Self::on_path_diagnostic_callback(path_id.type_ref()), &self.store[path_id], + if self.in_fn_signature { + GenericArgsPosition::FnSignature + } else { + GenericArgsPosition::Type + }, ) } @@ -1172,22 +1193,30 @@ pub(crate) fn generic_defaults_with_diagnostics_query( .with_impl_trait_mode(ImplTraitLoweringMode::Disallowed) .with_type_param_mode(ParamLoweringMode::Variable); let mut idx = 0; + let mut has_any_default = false; let mut defaults = generic_params - .iter_self() - .map(|(id, p)| { - let result = handle_generic_param(&mut ctx, idx, id, p, &generic_params); + .iter_parents_with_store() + .map(|((id, p), store)| { + ctx.store = store; + let (result, has_default) = handle_generic_param(&mut ctx, idx, id, p, &generic_params); + has_any_default |= has_default; idx += 1; result }) .collect::>(); - let diagnostics = create_diagnostics(mem::take(&mut ctx.diagnostics)); - defaults.extend(generic_params.iter_parents_with_store().map(|((id, p), store)| { - ctx.store = store; - let result = handle_generic_param(&mut ctx, idx, id, p, &generic_params); + ctx.diagnostics.clear(); // Don't include diagnostics from the parent. + defaults.extend(generic_params.iter_self().map(|(id, p)| { + let (result, has_default) = handle_generic_param(&mut ctx, idx, id, p, &generic_params); + has_any_default |= has_default; idx += 1; result })); - let defaults = GenericDefaults(Some(Arc::from_iter(defaults))); + let diagnostics = create_diagnostics(mem::take(&mut ctx.diagnostics)); + let defaults = if has_any_default { + GenericDefaults(Some(Arc::from_iter(defaults))) + } else { + GenericDefaults(None) + }; return (defaults, diagnostics); fn handle_generic_param( @@ -1196,16 +1225,20 @@ pub(crate) fn generic_defaults_with_diagnostics_query( id: GenericParamId, p: GenericParamDataRef<'_>, generic_params: &Generics, - ) -> Binders { + ) -> (Binders, bool) { + let binders = variable_kinds_from_iter(ctx.db, generic_params.iter_id().take(idx)); match p { GenericParamDataRef::TypeParamData(p) => { - let ty = p.default.as_ref().map_or(TyKind::Error.intern(Interner), |ty| { - // Each default can only refer to previous parameters. - // Type variable default referring to parameter coming - // after it is forbidden (FIXME: report diagnostic) - fallback_bound_vars(ctx.lower_ty(*ty), idx, 0) - }); - crate::make_binders(ctx.db, generic_params, ty.cast(Interner)) + let ty = p.default.as_ref().map_or_else( + || TyKind::Error.intern(Interner), + |ty| { + // Each default can only refer to previous parameters. + // Type variable default referring to parameter coming + // after it is forbidden (FIXME: report diagnostic) + fallback_bound_vars(ctx.lower_ty(*ty), idx) + }, + ); + (Binders::new(binders, ty.cast(Interner)), p.default.is_some()) } GenericParamDataRef::ConstParamData(p) => { let GenericParamId::ConstParamId(id) = id else { @@ -1221,36 +1254,22 @@ pub(crate) fn generic_defaults_with_diagnostics_query( }, ); // Each default can only refer to previous parameters, see above. - val = fallback_bound_vars(val, idx, 0); - make_binders(ctx.db, generic_params, val) + val = fallback_bound_vars(val, idx); + (Binders::new(binders, val), p.default.is_some()) } GenericParamDataRef::LifetimeParamData(_) => { - make_binders(ctx.db, generic_params, error_lifetime().cast(Interner)) + (Binders::new(binders, error_lifetime().cast(Interner)), false) } } } } pub(crate) fn generic_defaults_with_diagnostics_recover( - db: &dyn HirDatabase, + _db: &dyn HirDatabase, _cycle: &Cycle, - def: GenericDefId, + _def: GenericDefId, ) -> (GenericDefaults, Diagnostics) { - let generic_params = generics(db, def); - if generic_params.len() == 0 { - return (GenericDefaults(None), None); - } - // FIXME: this code is not covered in tests. - // we still need one default per parameter - let defaults = GenericDefaults(Some(Arc::from_iter(generic_params.iter_id().map(|id| { - let val = match id { - GenericParamId::TypeParamId(_) => TyKind::Error.intern(Interner).cast(Interner), - GenericParamId::ConstParamId(id) => unknown_const_as_generic(db.const_param_ty(id)), - GenericParamId::LifetimeParamId(_) => error_lifetime().cast(Interner), - }; - crate::make_binders(db, &generic_params, val) - })))); - (defaults, None) + (GenericDefaults(None), None) } fn fn_sig_for_fn(db: &dyn HirDatabase, def: FunctionId) -> PolyFnSig { @@ -1258,6 +1277,7 @@ fn fn_sig_for_fn(db: &dyn HirDatabase, def: FunctionId) -> PolyFnSig { let resolver = def.resolver(db); let mut ctx_params = TyLoweringContext::new(db, &resolver, &data.store, def.into()) .with_type_param_mode(ParamLoweringMode::Variable); + ctx_params.in_fn_signature = true; let params = data.params.iter().map(|&tr| ctx_params.lower_ty(tr)); let ret = match data.ret_type { @@ -1265,6 +1285,7 @@ fn fn_sig_for_fn(db: &dyn HirDatabase, def: FunctionId) -> PolyFnSig { let mut ctx_ret = TyLoweringContext::new(db, &resolver, &data.store, def.into()) .with_impl_trait_mode(ImplTraitLoweringMode::Opaque) .with_type_param_mode(ParamLoweringMode::Variable); + ctx_ret.in_fn_signature = true; ctx_ret.lower_ty(ret_type) } None => TyKind::Tuple(0, Substitution::empty(Interner)).intern(Interner), @@ -1612,73 +1633,14 @@ pub(crate) fn lower_to_chalk_mutability(m: hir_def::type_ref::Mutability) -> Mut } } -/// Checks if the provided generic arg matches its expected kind, then lower them via -/// provided closures. Use unknown if there was kind mismatch. -/// -pub(crate) fn generic_arg_to_chalk<'a, T>( - db: &dyn HirDatabase, - kind_id: GenericParamId, - arg: &'a GenericArg, - this: &mut T, - store: &ExpressionStore, - for_type: impl FnOnce(&mut T, TypeRefId) -> Ty + 'a, - for_const: impl FnOnce(&mut T, &ConstRef, Ty) -> Const + 'a, - for_const_ty_path_fallback: impl FnOnce(&mut T, &Path, Ty) -> Const + 'a, - for_lifetime: impl FnOnce(&mut T, &LifetimeRef) -> Lifetime + 'a, -) -> crate::GenericArg { - let kind = match kind_id { - GenericParamId::TypeParamId(_) => ParamKind::Type, - GenericParamId::ConstParamId(id) => { - let ty = db.const_param_ty(id); - ParamKind::Const(ty) - } - GenericParamId::LifetimeParamId(_) => ParamKind::Lifetime, - }; - match (arg, kind) { - (GenericArg::Type(type_ref), ParamKind::Type) => for_type(this, *type_ref).cast(Interner), - (GenericArg::Const(c), ParamKind::Const(c_ty)) => for_const(this, c, c_ty).cast(Interner), - (GenericArg::Lifetime(lifetime_ref), ParamKind::Lifetime) => { - for_lifetime(this, lifetime_ref).cast(Interner) - } - (GenericArg::Const(_), ParamKind::Type) => TyKind::Error.intern(Interner).cast(Interner), - (GenericArg::Lifetime(_), ParamKind::Type) => TyKind::Error.intern(Interner).cast(Interner), - (GenericArg::Type(t), ParamKind::Const(c_ty)) => match &store[*t] { - TypeRef::Path(p) => for_const_ty_path_fallback(this, p, c_ty).cast(Interner), - _ => unknown_const_as_generic(c_ty), - }, - (GenericArg::Lifetime(_), ParamKind::Const(c_ty)) => unknown_const_as_generic(c_ty), - (GenericArg::Type(_), ParamKind::Lifetime) => error_lifetime().cast(Interner), - (GenericArg::Const(_), ParamKind::Lifetime) => error_lifetime().cast(Interner), - } -} - /// Replaces any 'free' `BoundVar`s in `s` by `TyKind::Error` from the perspective of generic -/// parameter whose index is `param_index`. A `BoundVar` is free when it is or (syntactically) -/// appears after the generic parameter of `param_index`. +/// parameter whose index is `param_index`. A `BoundVar` is free when it appears after the +/// generic parameter of `param_index`. fn fallback_bound_vars + HasInterner>( s: T, param_index: usize, - parent_start: usize, ) -> T { - // Keep in mind that parent generic parameters, if any, come *after* those of the item in - // question. In the diagrams below, `c*` and `p*` represent generic parameters of the item and - // its parent respectively. - let is_allowed = |index| { - if param_index < parent_start { - // The parameter of `param_index` is one from the item in question. Any parent generic - // parameters or the item's generic parameters that come before `param_index` is - // allowed. - // [c1, .., cj, .., ck, p1, .., pl] where cj is `param_index` - // ^^^^^^ ^^^^^^^^^^ these are allowed - !(param_index..parent_start).contains(&index) - } else { - // The parameter of `param_index` is one from the parent generics. Only parent generic - // parameters that come before `param_index` are allowed. - // [c1, .., ck, p1, .., pj, .., pl] where pj is `param_index` - // ^^^^^^ these are allowed - (parent_start..param_index).contains(&index) - } - }; + let is_allowed = |index| (0..param_index).contains(&index); crate::fold_free_vars( s, diff --git a/crates/hir-ty/src/lower/diagnostics.rs b/crates/hir-ty/src/lower/diagnostics.rs index 5f299aca3a..a5a13d64e0 100644 --- a/crates/hir-ty/src/lower/diagnostics.rs +++ b/crates/hir-ty/src/lower/diagnostics.rs @@ -1,6 +1,7 @@ //! This files contains the declaration of diagnostics kinds for ty and path lowering. use hir_def::type_ref::TypeRefId; +use hir_def::{GenericDefId, GenericParamId}; #[derive(Debug, PartialEq, Eq, Clone)] pub struct TyLoweringDiagnostic { @@ -21,13 +22,51 @@ pub enum GenericArgsProhibitedReason { PrimitiveTy, Const, Static, + LocalVariable, /// When there is a generic enum, within the expression `Enum::Variant`, /// either `Enum` or `Variant` are allowed to have generic arguments, but not both. EnumVariant, } +/// A path can have many generic arguments: each segment may have one associated with the +/// segment, and in addition, each associated type binding may have generic arguments. This +/// enum abstracts over both. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PathGenericsSource { + /// Generic arguments directly on the segment. + Segment(u32), + /// Generic arguments on an associated type, e.g. `Foo = C>` or `Foo: Bound>`. + AssocType { segment: u32, assoc_type: u32 }, +} + #[derive(Debug, PartialEq, Eq, Clone)] pub enum PathLoweringDiagnostic { - GenericArgsProhibited { segment: u32, reason: GenericArgsProhibitedReason }, - ParenthesizedGenericArgsWithoutFnTrait { segment: u32 }, + GenericArgsProhibited { + segment: u32, + reason: GenericArgsProhibitedReason, + }, + ParenthesizedGenericArgsWithoutFnTrait { + segment: u32, + }, + /// The expected lifetimes & types and consts counts can be found by inspecting the `GenericDefId`. + IncorrectGenericsLen { + generics_source: PathGenericsSource, + provided_count: u32, + expected_count: u32, + kind: IncorrectGenericsLenKind, + def: GenericDefId, + }, + IncorrectGenericsOrder { + generics_source: PathGenericsSource, + param_id: GenericParamId, + arg_idx: u32, + /// Whether the `GenericArgs` contains a `Self` arg. + has_self_arg: bool, + }, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum IncorrectGenericsLenKind { + Lifetimes, + TypesAndConsts, } diff --git a/crates/hir-ty/src/lower/path.rs b/crates/hir-ty/src/lower/path.rs index a059d9df1c..be35816399 100644 --- a/crates/hir-ty/src/lower/path.rs +++ b/crates/hir-ty/src/lower/path.rs @@ -1,30 +1,33 @@ //! A wrapper around [`TyLoweringContext`] specifically for lowering paths. -use std::iter; - use chalk_ir::{BoundVar, cast::Cast, fold::Shift}; use either::Either; use hir_def::{ - GenericDefId, GenericParamId, ItemContainerId, Lookup, TraitId, + GenericDefId, GenericParamId, Lookup, TraitId, expr_store::{ - HygieneId, + ExpressionStore, HygieneId, path::{GenericArg, GenericArgs, GenericArgsParentheses, Path, PathSegment, PathSegments}, }, + hir::generics::{ + GenericParamDataRef, TypeOrConstParamData, TypeParamData, TypeParamProvenance, + }, resolver::{ResolveValueResult, TypeNs, ValueNs}, signatures::TraitFlags, - type_ref::TypeRef, + type_ref::{TypeRef, TypeRefId}, }; use smallvec::SmallVec; use stdx::never; use crate::{ - AliasEq, AliasTy, GenericArgsProhibitedReason, ImplTraitLoweringMode, Interner, - ParamLoweringMode, PathLoweringDiagnostic, ProjectionTy, QuantifiedWhereClause, Substitution, - TraitRef, Ty, TyBuilder, TyDefId, TyKind, TyLoweringContext, ValueTyDefId, WhereClause, - consteval::unknown_const_as_generic, + AliasEq, AliasTy, GenericArgsProhibitedReason, ImplTraitLoweringMode, IncorrectGenericsLenKind, + Interner, ParamLoweringMode, PathGenericsSource, PathLoweringDiagnostic, ProjectionTy, + QuantifiedWhereClause, Substitution, TraitRef, Ty, TyBuilder, TyDefId, TyKind, + TyLoweringContext, ValueTyDefId, WhereClause, + consteval::{unknown_const, unknown_const_as_generic}, + db::HirDatabase, error_lifetime, - generics::generics, - lower::{generic_arg_to_chalk, named_associated_type_shorthand_candidates}, + generics::{Generics, generics}, + lower::{GenericArgsPosition, named_associated_type_shorthand_candidates}, to_assoc_type_id, to_chalk_trait_id, to_placeholder_idx, utils::associated_type_by_name_including_super_traits, }; @@ -49,6 +52,7 @@ pub(crate) struct PathLoweringContext<'a, 'b> { current_segment_idx: usize, /// Contains the previous segment if `current_segment_idx == segments.len()` current_or_prev_segment: PathSegment<'a>, + position: GenericArgsPosition, } impl<'a, 'b> PathLoweringContext<'a, 'b> { @@ -57,6 +61,7 @@ impl<'a, 'b> PathLoweringContext<'a, 'b> { ctx: &'a mut TyLoweringContext<'b>, on_diagnostic: PathDiagnosticCallback<'a>, path: &'a Path, + position: GenericArgsPosition, ) -> Self { let segments = path.segments(); let first_segment = segments.first().unwrap_or(PathSegment::MISSING); @@ -67,6 +72,7 @@ impl<'a, 'b> PathLoweringContext<'a, 'b> { segments, current_segment_idx: 0, current_or_prev_segment: first_segment, + position, } } @@ -449,14 +455,19 @@ impl<'a, 'b> PathLoweringContext<'a, 'b> { // 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(_) => { + ValueNs::GenericParam(_) => { prohibit_generics_on_resolved(GenericArgsProhibitedReason::Const) } ValueNs::StaticId(_) => { prohibit_generics_on_resolved(GenericArgsProhibitedReason::Static) } - ValueNs::FunctionId(_) | ValueNs::StructId(_) | ValueNs::EnumVariantId(_) => {} - ValueNs::LocalBinding(_) => {} + ValueNs::LocalBinding(_) => { + prohibit_generics_on_resolved(GenericArgsProhibitedReason::LocalVariable) + } + ValueNs::FunctionId(_) + | ValueNs::StructId(_) + | ValueNs::EnumVariantId(_) + | ValueNs::ConstId(_) => {} } } ResolveValueResult::Partial(resolution, _, _) => { @@ -615,6 +626,7 @@ impl<'a, 'b> PathLoweringContext<'a, 'b> { def, infer_args, explicit_self_ty, + PathGenericsSource::Segment(self.current_segment_u32()), ) } @@ -624,144 +636,143 @@ impl<'a, 'b> PathLoweringContext<'a, 'b> { def: GenericDefId, infer_args: bool, explicit_self_ty: Option, + generics_source: PathGenericsSource, ) -> Substitution { - // Order is - // - Parent parameters - // - Optional Self parameter - // - Lifetime parameters - // - Type or Const parameters - let def_generics = generics(self.ctx.db, def); - let ( - parent_params, - self_param, - type_params, - const_params, - impl_trait_params, - lifetime_params, - ) = def_generics.provenance_split(); - let item_len = - self_param as usize + type_params + const_params + impl_trait_params + lifetime_params; - let total_len = parent_params + item_len; + struct LowererCtx<'a, 'b, 'c> { + ctx: &'a mut PathLoweringContext<'b, 'c>, + generics_source: PathGenericsSource, + } - let ty_error = || TyKind::Error.intern(Interner).cast(Interner); - let param_to_err = |id| match id { - GenericParamId::ConstParamId(x) => { - unknown_const_as_generic(self.ctx.db.const_param_ty(x)) + impl GenericArgsLowerer for LowererCtx<'_, '_, '_> { + fn report_len_mismatch( + &mut self, + def: GenericDefId, + provided_count: u32, + expected_count: u32, + kind: IncorrectGenericsLenKind, + ) { + self.ctx.on_diagnostic(PathLoweringDiagnostic::IncorrectGenericsLen { + generics_source: self.generics_source, + provided_count, + expected_count, + kind, + def, + }); } - GenericParamId::TypeParamId(_) => ty_error(), - GenericParamId::LifetimeParamId(_) => error_lifetime().cast(Interner), - }; - let mut substs: Vec<_> = def_generics.iter_parent_id().map(param_to_err).collect(); + fn report_arg_mismatch( + &mut self, + param_id: GenericParamId, + arg_idx: u32, + has_self_arg: bool, + ) { + self.ctx.on_diagnostic(PathLoweringDiagnostic::IncorrectGenericsOrder { + generics_source: self.generics_source, + param_id, + arg_idx, + has_self_arg, + }); + } - tracing::debug!(?substs, ?parent_params); - - // we need to iterate the lifetime and type/const params separately as our order of them - // differs from the supplied syntax - - let mut def_toc_iter = def_generics.iter_self_type_or_consts_id(); - let fill_self_param = || { - if self_param { - let self_ty = explicit_self_ty.map(|x| x.cast(Interner)).unwrap_or_else(ty_error); - - if let Some(id) = def_toc_iter.next() { - assert!(matches!(id, GenericParamId::TypeParamId(_))); - substs.push(self_ty); + fn provided_kind( + &mut self, + param_id: GenericParamId, + param: GenericParamDataRef<'_>, + arg: &GenericArg, + ) -> crate::GenericArg { + match (param, arg) { + (GenericParamDataRef::LifetimeParamData(_), GenericArg::Lifetime(lifetime)) => { + self.ctx.ctx.lower_lifetime(lifetime).cast(Interner) + } + (GenericParamDataRef::TypeParamData(_), GenericArg::Type(type_ref)) => { + self.ctx.ctx.lower_ty(*type_ref).cast(Interner) + } + (GenericParamDataRef::ConstParamData(_), GenericArg::Const(konst)) => { + let GenericParamId::ConstParamId(const_id) = param_id else { + unreachable!("non-const param ID for const param"); + }; + self.ctx + .ctx + .lower_const(konst, self.ctx.ctx.db.const_param_ty(const_id)) + .cast(Interner) + } + _ => unreachable!("unmatching param kinds were passed to `provided_kind()`"), } } - }; - let mut had_explicit_args = false; - if let Some(&GenericArgs { ref args, has_self_type, .. }) = args_and_bindings { - // Fill in the self param first - if has_self_type && self_param { - had_explicit_args = true; - if let Some(id) = def_toc_iter.next() { - assert!(matches!(id, GenericParamId::TypeParamId(_))); - had_explicit_args = true; - if let GenericArg::Type(ty) = &args[0] { - substs.push(self.ctx.lower_ty(*ty).cast(Interner)); + fn provided_type_like_const( + &mut self, + const_ty: Ty, + arg: TypeLikeConst<'_>, + ) -> crate::Const { + match arg { + TypeLikeConst::Path(path) => self.ctx.ctx.lower_path_as_const(path, const_ty), + TypeLikeConst::Infer => unknown_const(const_ty), + } + } + + fn inferred_kind( + &mut self, + def: GenericDefId, + param_id: GenericParamId, + param: GenericParamDataRef<'_>, + infer_args: bool, + preceding_args: &[crate::GenericArg], + ) -> crate::GenericArg { + let default = || { + self.ctx + .ctx + .db + .generic_defaults(def) + .get(preceding_args.len()) + .map(|default| default.clone().substitute(Interner, preceding_args)) + }; + match param { + GenericParamDataRef::LifetimeParamData(_) => error_lifetime().cast(Interner), + GenericParamDataRef::TypeParamData(param) => { + if !infer_args && param.default.is_some() { + if let Some(default) = default() { + return default; + } + } + TyKind::Error.intern(Interner).cast(Interner) + } + GenericParamDataRef::ConstParamData(param) => { + if !infer_args && param.default.is_some() { + if let Some(default) = default() { + return default; + } + } + let GenericParamId::ConstParamId(const_id) = param_id else { + unreachable!("non-const param ID for const param"); + }; + unknown_const_as_generic(self.ctx.ctx.db.const_param_ty(const_id)) + .cast(Interner) } } - } else { - fill_self_param() - }; - - // Then fill in the supplied lifetime args, or error lifetimes if there are too few - // (default lifetimes aren't a thing) - for arg in args - .iter() - .filter_map(|arg| match arg { - GenericArg::Lifetime(arg) => Some(self.ctx.lower_lifetime(arg)), - _ => None, - }) - .chain(iter::repeat(error_lifetime())) - .take(lifetime_params) - { - substs.push(arg.cast(Interner)); } - let skip = if has_self_type { 1 } else { 0 }; - // Fill in supplied type and const args - // Note if non-lifetime args are provided, it should be all of them, but we can't rely on that - for (arg, id) in args - .iter() - .filter(|arg| !matches!(arg, GenericArg::Lifetime(_))) - .skip(skip) - .take(type_params + const_params) - .zip(def_toc_iter) - { - had_explicit_args = true; - let arg = generic_arg_to_chalk( - self.ctx.db, - id, - arg, - self.ctx, - self.ctx.store, - |ctx, type_ref| ctx.lower_ty(type_ref), - |ctx, const_ref, ty| ctx.lower_const(const_ref, ty), - |ctx, path, ty| ctx.lower_path_as_const(path, ty), - |ctx, lifetime_ref| ctx.lower_lifetime(lifetime_ref), - ); - substs.push(arg); + fn parent_arg(&mut self, param_id: GenericParamId) -> crate::GenericArg { + match param_id { + GenericParamId::TypeParamId(_) => TyKind::Error.intern(Interner).cast(Interner), + GenericParamId::ConstParamId(const_id) => { + unknown_const_as_generic(self.ctx.ctx.db.const_param_ty(const_id)) + } + GenericParamId::LifetimeParamId(_) => error_lifetime().cast(Interner), + } } - } else { - fill_self_param(); } - // handle defaults. In expression or pattern path segments without - // explicitly specified type arguments, missing type arguments are inferred - // (i.e. defaults aren't used). - // Generic parameters for associated types are not supposed to have defaults, so we just - // ignore them. - let is_assoc_ty = || match def { - GenericDefId::TypeAliasId(id) => { - matches!(id.lookup(self.ctx.db).container, ItemContainerId::TraitId(_)) - } - _ => false, - }; - let fill_defaults = (!infer_args || had_explicit_args) && !is_assoc_ty(); - if fill_defaults { - let defaults = &*self.ctx.db.generic_defaults(def); - - let rem = - def_generics.iter_id().skip(substs.len()).map(param_to_err).collect::>(); - // Fill in defaults for type/const params - for (idx, default_ty) in defaults[substs.len()..].iter().enumerate() { - // each default can depend on the previous parameters - let substs_so_far = Substitution::from_iter( - Interner, - substs.iter().cloned().chain(rem[idx..].iter().cloned()), - ); - substs.push(default_ty.clone().substitute(Interner, &substs_so_far)); - } - } else { - // Fill in remaining def params and parent params - substs.extend(def_generics.iter_id().skip(substs.len()).map(param_to_err)); - } - - assert_eq!(substs.len(), total_len, "expected {} substs, got {}", total_len, substs.len()); - Substitution::from_iter(Interner, substs) + substs_from_args_and_bindings( + self.ctx.db, + self.ctx.store, + args_and_bindings, + def, + infer_args, + self.position, + explicit_self_ty, + &mut LowererCtx { ctx: self, generics_source }, + ) } pub(crate) fn lower_trait_ref_from_resolved_path( @@ -786,7 +797,7 @@ impl<'a, 'b> PathLoweringContext<'a, 'b> { trait_ref: TraitRef, ) -> Option + use<'a, 'b, 'c>> { self.current_or_prev_segment.args_and_bindings.map(|args_and_bindings| { - args_and_bindings.bindings.iter().flat_map(move |binding| { + args_and_bindings.bindings.iter().enumerate().flat_map(move |(binding_idx, binding)| { let found = associated_type_by_name_including_super_traits( self.ctx.db, trait_ref.clone(), @@ -805,6 +816,10 @@ impl<'a, 'b> PathLoweringContext<'a, 'b> { associated_ty.into(), false, // this is not relevant Some(super_trait_ref.self_type_parameter(Interner)), + PathGenericsSource::AssocType { + segment: self.current_segment_u32(), + assoc_type: binding_idx as u32, + }, ); let substitution = Substitution::from_iter( Interner, @@ -845,3 +860,288 @@ impl<'a, 'b> PathLoweringContext<'a, 'b> { }) } } + +/// A const that were parsed like a type. +pub(crate) enum TypeLikeConst<'a> { + Infer, + Path(&'a Path), +} + +pub(crate) trait GenericArgsLowerer { + fn report_len_mismatch( + &mut self, + def: GenericDefId, + provided_count: u32, + expected_count: u32, + kind: IncorrectGenericsLenKind, + ); + + fn report_arg_mismatch(&mut self, param_id: GenericParamId, arg_idx: u32, has_self_arg: bool); + + fn provided_kind( + &mut self, + param_id: GenericParamId, + param: GenericParamDataRef<'_>, + arg: &GenericArg, + ) -> crate::GenericArg; + + fn provided_type_like_const(&mut self, const_ty: Ty, arg: TypeLikeConst<'_>) -> crate::Const; + + fn inferred_kind( + &mut self, + def: GenericDefId, + param_id: GenericParamId, + param: GenericParamDataRef<'_>, + infer_args: bool, + preceding_args: &[crate::GenericArg], + ) -> crate::GenericArg; + + fn parent_arg(&mut self, param_id: GenericParamId) -> crate::GenericArg; +} + +/// Returns true if there was an error. +fn check_generic_args_len( + args_and_bindings: Option<&GenericArgs>, + def: GenericDefId, + def_generics: &Generics, + infer_args: bool, + position: GenericArgsPosition, + ctx: &mut impl GenericArgsLowerer, +) -> bool { + let mut had_error = false; + + let (mut provided_lifetimes_count, mut provided_types_and_consts_count) = (0usize, 0usize); + if let Some(args_and_bindings) = args_and_bindings { + let args_no_self = &args_and_bindings.args[usize::from(args_and_bindings.has_self_type)..]; + for arg in args_no_self { + match arg { + GenericArg::Lifetime(_) => provided_lifetimes_count += 1, + GenericArg::Type(_) | GenericArg::Const(_) => provided_types_and_consts_count += 1, + } + } + } + + let infer_lifetimes = + (position != GenericArgsPosition::Type || infer_args) && provided_lifetimes_count == 0; + + let min_expected_lifetime_args = + if infer_lifetimes { 0 } else { def_generics.len_lifetimes_self() }; + let max_expected_lifetime_args = def_generics.len_lifetimes_self(); + if !(min_expected_lifetime_args..=max_expected_lifetime_args) + .contains(&provided_lifetimes_count) + { + ctx.report_len_mismatch( + def, + provided_lifetimes_count as u32, + def_generics.len_lifetimes_self() as u32, + IncorrectGenericsLenKind::Lifetimes, + ); + had_error = true; + } + + let defaults_count = + def_generics.iter_self_type_or_consts().filter(|(_, param)| param.has_default()).count(); + let named_type_and_const_params_count = def_generics + .iter_self_type_or_consts() + .filter(|(_, param)| match param { + TypeOrConstParamData::TypeParamData(param) => { + param.provenance == TypeParamProvenance::TypeParamList + } + TypeOrConstParamData::ConstParamData(_) => true, + }) + .count(); + let expected_min = + if infer_args { 0 } else { named_type_and_const_params_count - defaults_count }; + let expected_max = named_type_and_const_params_count; + if !(expected_min..=expected_max).contains(&provided_types_and_consts_count) { + ctx.report_len_mismatch( + def, + provided_types_and_consts_count as u32, + named_type_and_const_params_count as u32, + IncorrectGenericsLenKind::TypesAndConsts, + ); + had_error = true; + } + + had_error +} + +pub(crate) fn substs_from_args_and_bindings( + db: &dyn HirDatabase, + store: &ExpressionStore, + args_and_bindings: Option<&GenericArgs>, + def: GenericDefId, + mut infer_args: bool, + position: GenericArgsPosition, + explicit_self_ty: Option, + ctx: &mut impl GenericArgsLowerer, +) -> Substitution { + // Order is + // - Parent parameters + // - Optional Self parameter + // - Lifetime parameters + // - Type or Const parameters + let def_generics = generics(db, def); + let args_slice = args_and_bindings.map(|it| &*it.args).unwrap_or_default(); + + // We do not allow inference if there are specified args, i.e. we do not allow partial inference. + let has_non_lifetime_args = + args_slice.iter().any(|arg| !matches!(arg, GenericArg::Lifetime(_))); + infer_args &= !has_non_lifetime_args; + + let had_count_error = + check_generic_args_len(args_and_bindings, def, &def_generics, infer_args, position, ctx); + + let mut substs = Vec::with_capacity(def_generics.len()); + + substs.extend(def_generics.iter_parent_id().map(|id| ctx.parent_arg(id))); + + let mut args = args_slice.iter().enumerate().peekable(); + let mut params = def_generics.iter_self().peekable(); + + // If we encounter a type or const when we expect a lifetime, we infer the lifetimes. + // If we later encounter a lifetime, we know that the arguments were provided in the + // wrong order. `force_infer_lt` records the type or const that forced lifetimes to be + // inferred, so we can use it for diagnostics later. + let mut force_infer_lt = None; + + let has_self_arg = args_and_bindings.is_some_and(|it| it.has_self_type); + // First, handle `Self` parameter. Consume it from the args if provided, otherwise from `explicit_self_ty`, + // and lastly infer it. + if let Some(&( + self_param_id, + self_param @ GenericParamDataRef::TypeParamData(TypeParamData { + provenance: TypeParamProvenance::TraitSelf, + .. + }), + )) = params.peek() + { + let self_ty = if has_self_arg { + let (_, self_ty) = args.next().expect("has_self_type=true, should have Self type"); + ctx.provided_kind(self_param_id, self_param, self_ty) + } else { + explicit_self_ty.map(|it| it.cast(Interner)).unwrap_or_else(|| { + ctx.inferred_kind(def, self_param_id, self_param, infer_args, &substs) + }) + }; + params.next(); + substs.push(self_ty); + } + + loop { + // We're going to iterate through the generic arguments that the user + // provided, matching them with the generic parameters we expect. + // Mismatches can occur as a result of elided lifetimes, or for malformed + // input. We try to handle both sensibly. + match (args.peek(), params.peek()) { + (Some(&(arg_idx, arg)), Some(&(param_id, param))) => match (arg, param) { + (GenericArg::Type(_), GenericParamDataRef::TypeParamData(type_param)) + if type_param.provenance == TypeParamProvenance::ArgumentImplTrait => + { + // Do not allow specifying `impl Trait` explicitly. We already err at that, but if we won't handle it here + // we will handle it as if it was specified, instead of inferring it. + substs.push(ctx.inferred_kind(def, param_id, param, infer_args, &substs)); + params.next(); + } + (GenericArg::Lifetime(_), GenericParamDataRef::LifetimeParamData(_)) + | (GenericArg::Type(_), GenericParamDataRef::TypeParamData(_)) + | (GenericArg::Const(_), GenericParamDataRef::ConstParamData(_)) => { + substs.push(ctx.provided_kind(param_id, param, arg)); + args.next(); + params.next(); + } + ( + GenericArg::Type(_) | GenericArg::Const(_), + GenericParamDataRef::LifetimeParamData(_), + ) => { + // We expected a lifetime argument, but got a type or const + // argument. That means we're inferring the lifetime. + substs.push(ctx.inferred_kind(def, param_id, param, infer_args, &substs)); + params.next(); + force_infer_lt = Some((arg_idx as u32, param_id)); + } + (GenericArg::Type(type_ref), GenericParamDataRef::ConstParamData(_)) => { + if let Some(konst) = type_looks_like_const(store, *type_ref) { + let GenericParamId::ConstParamId(param_id) = param_id else { + panic!("unmatching param kinds"); + }; + let const_ty = db.const_param_ty(param_id); + substs.push(ctx.provided_type_like_const(const_ty, konst).cast(Interner)); + args.next(); + params.next(); + } else { + // See the `_ => { ... }` branch. + if !had_count_error { + ctx.report_arg_mismatch(param_id, arg_idx as u32, has_self_arg); + } + while args.next().is_some() {} + } + } + _ => { + // We expected one kind of parameter, but the user provided + // another. This is an error. However, if we already know that + // the arguments don't match up with the parameters, we won't issue + // an additional error, as the user already knows what's wrong. + if !had_count_error { + ctx.report_arg_mismatch(param_id, arg_idx as u32, has_self_arg); + } + + // We've reported the error, but we want to make sure that this + // problem doesn't bubble down and create additional, irrelevant + // errors. In this case, we're simply going to ignore the argument + // and any following arguments. The rest of the parameters will be + // inferred. + while args.next().is_some() {} + } + }, + + (Some(&(_, arg)), None) => { + // We should never be able to reach this point with well-formed input. + // There are two situations in which we can encounter this issue. + // + // 1. The number of arguments is incorrect. In this case, an error + // will already have been emitted, and we can ignore it. + // 2. We've inferred some lifetimes, which have been provided later (i.e. + // after a type or const). We want to throw an error in this case. + if !had_count_error { + assert!( + matches!(arg, GenericArg::Lifetime(_)), + "the only possible situation here is incorrect lifetime order" + ); + let (provided_arg_idx, param_id) = + force_infer_lt.expect("lifetimes ought to have been inferred"); + ctx.report_arg_mismatch(param_id, provided_arg_idx, has_self_arg); + } + + break; + } + + (None, Some(&(param_id, param))) => { + // If there are fewer arguments than parameters, it means we're inferring the remaining arguments. + substs.push(ctx.inferred_kind(def, param_id, param, infer_args, &substs)); + params.next(); + } + + (None, None) => break, + } + } + + Substitution::from_iter(Interner, substs) +} + +fn type_looks_like_const( + store: &ExpressionStore, + type_ref: TypeRefId, +) -> Option> { + // A path/`_` const will be parsed as a type, instead of a const, because when parsing/lowering + // in hir-def we don't yet know the expected argument kind. rustc does this a bit differently, + // when lowering to HIR it resolves the path, and if it doesn't resolve to the type namespace + // it is lowered as a const. Our behavior could deviate from rustc when the value is resolvable + // in both the type and value namespaces, but I believe we only allow more code. + let type_ref = &store[type_ref]; + match type_ref { + TypeRef::Path(path) => Some(TypeLikeConst::Path(path)), + TypeRef::Placeholder => Some(TypeLikeConst::Infer), + _ => None, + } +} diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index 4eaa9fb501..d8bcaa0e74 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -6,14 +6,17 @@ use cfg::{CfgExpr, CfgOptions}; use either::Either; use hir_def::{ - DefWithBodyId, SyntheticSyntax, - expr_store::{ExprOrPatPtr, ExpressionStoreSourceMap, hir_segment_to_ast_segment}, + DefWithBodyId, GenericParamId, SyntheticSyntax, + expr_store::{ + ExprOrPatPtr, ExpressionStoreSourceMap, hir_assoc_type_binding_to_ast, + hir_generic_arg_to_ast, hir_segment_to_ast_segment, + }, hir::ExprOrPatId, }; use hir_expand::{HirFileId, InFile, mod_path::ModPath, name::Name}; use hir_ty::{ - CastError, InferenceDiagnostic, InferenceTyDiagnosticSource, PathLoweringDiagnostic, - TyLoweringDiagnostic, TyLoweringDiagnosticKind, + CastError, InferenceDiagnostic, InferenceTyDiagnosticSource, PathGenericsSource, + PathLoweringDiagnostic, TyLoweringDiagnostic, TyLoweringDiagnosticKind, db::HirDatabase, diagnostics::{BodyValidationDiagnostic, UnsafetyReason}, }; @@ -24,11 +27,11 @@ use syntax::{ }; use triomphe::Arc; -use crate::{AssocItem, Field, Function, Local, Trait, Type}; +use crate::{AssocItem, Field, Function, GenericDef, Local, Trait, Type}; pub use hir_def::VariantId; pub use hir_ty::{ - GenericArgsProhibitedReason, + GenericArgsProhibitedReason, IncorrectGenericsLenKind, diagnostics::{CaseType, IncorrectCase}, }; @@ -113,6 +116,8 @@ diagnostics![ GenericArgsProhibited, ParenthesizedGenericArgsWithoutFnTrait, BadRtn, + IncorrectGenericsLen, + IncorrectGenericsOrder, ]; #[derive(Debug)] @@ -425,6 +430,39 @@ pub struct BadRtn { pub rtn: InFile>, } +#[derive(Debug)] +pub struct IncorrectGenericsLen { + /// Points at the name if there are no generics. + pub generics_or_segment: InFile>>, + pub kind: IncorrectGenericsLenKind, + pub provided: u32, + pub expected: u32, + pub def: GenericDef, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum GenericArgKind { + Lifetime, + Type, + Const, +} + +impl GenericArgKind { + fn from_id(id: GenericParamId) -> Self { + match id { + GenericParamId::TypeParamId(_) => GenericArgKind::Type, + GenericParamId::ConstParamId(_) => GenericArgKind::Const, + GenericParamId::LifetimeParamId(_) => GenericArgKind::Lifetime, + } + } +} + +#[derive(Debug)] +pub struct IncorrectGenericsOrder { + pub provided_arg: InFile>, + pub expected_kind: GenericArgKind, +} + impl AnyDiagnostic { pub(crate) fn body_validation_diagnostic( db: &dyn HirDatabase, @@ -714,6 +752,47 @@ impl AnyDiagnostic { }; Self::path_diagnostic(diag, source.with_value(path))? } + &InferenceDiagnostic::MethodCallIncorrectGenericsLen { + expr, + provided_count, + expected_count, + kind, + def, + } => { + let syntax = expr_syntax(expr)?; + let file_id = syntax.file_id; + let syntax = + syntax.with_value(syntax.value.cast::()?).to_node(db); + let generics_or_name = syntax + .generic_arg_list() + .map(Either::Left) + .or_else(|| syntax.name_ref().map(Either::Right))?; + let generics_or_name = InFile::new(file_id, AstPtr::new(&generics_or_name)); + IncorrectGenericsLen { + generics_or_segment: generics_or_name, + kind, + provided: provided_count, + expected: expected_count, + def: def.into(), + } + .into() + } + &InferenceDiagnostic::MethodCallIncorrectGenericsOrder { + expr, + param_id, + arg_idx, + has_self_arg, + } => { + let syntax = expr_syntax(expr)?; + let file_id = syntax.file_id; + let syntax = + syntax.with_value(syntax.value.cast::()?).to_node(db); + let generic_args = syntax.generic_arg_list()?; + let provided_arg = hir_generic_arg_to_ast(&generic_args, arg_idx, has_self_arg)?; + let provided_arg = InFile::new(file_id, AstPtr::new(&provided_arg)); + let expected_kind = GenericArgKind::from_id(param_id); + IncorrectGenericsOrder { provided_arg, expected_kind }.into() + } }) } @@ -750,6 +829,38 @@ impl AnyDiagnostic { let args = path.with_value(args); ParenthesizedGenericArgsWithoutFnTrait { args }.into() } + PathLoweringDiagnostic::IncorrectGenericsLen { + generics_source, + provided_count, + expected_count, + kind, + def, + } => { + let generics_or_segment = + path_generics_source_to_ast(&path.value, generics_source)?; + let generics_or_segment = path.with_value(AstPtr::new(&generics_or_segment)); + IncorrectGenericsLen { + generics_or_segment, + kind, + provided: provided_count, + expected: expected_count, + def: def.into(), + } + .into() + } + PathLoweringDiagnostic::IncorrectGenericsOrder { + generics_source, + param_id, + arg_idx, + has_self_arg, + } => { + let generic_args = + path_generics_source_to_ast(&path.value, generics_source)?.left()?; + let provided_arg = hir_generic_arg_to_ast(&generic_args, arg_idx, has_self_arg)?; + let provided_arg = path.with_value(AstPtr::new(&provided_arg)); + let expected_kind = GenericArgKind::from_id(param_id); + IncorrectGenericsOrder { provided_arg, expected_kind }.into() + } }) } @@ -771,3 +882,27 @@ impl AnyDiagnostic { }) } } + +fn path_generics_source_to_ast( + path: &ast::Path, + generics_source: PathGenericsSource, +) -> Option> { + Some(match generics_source { + PathGenericsSource::Segment(segment) => { + let segment = hir_segment_to_ast_segment(path, segment)?; + segment + .generic_arg_list() + .map(Either::Left) + .or_else(|| segment.name_ref().map(Either::Right))? + } + PathGenericsSource::AssocType { segment, assoc_type } => { + let segment = hir_segment_to_ast_segment(path, segment)?; + let segment_args = segment.generic_arg_list()?; + let assoc = hir_assoc_type_binding_to_ast(&segment_args, assoc_type)?; + assoc + .generic_arg_list() + .map(Either::Left) + .or_else(|| assoc.name_ref().map(Either::Right))? + } + }) +} diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 37c213c2af..143c13069e 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -1709,10 +1709,11 @@ impl_from!(Struct, Union, Enum for Adt); impl Adt { pub fn has_non_default_type_params(self, db: &dyn HirDatabase) -> bool { let subst = db.generic_defaults(self.into()); - subst.iter().any(|ty| match ty.skip_binders().data(Interner) { - GenericArgData::Ty(it) => it.is_unknown(), - _ => false, - }) + (subst.is_empty() && db.generic_params(self.into()).len_type_or_consts() != 0) + || subst.iter().any(|ty| match ty.skip_binders().data(Interner) { + GenericArgData::Ty(it) => it.is_unknown(), + _ => false, + }) } pub fn layout(self, db: &dyn HirDatabase) -> Result { @@ -3000,10 +3001,11 @@ pub struct TypeAlias { impl TypeAlias { pub fn has_non_default_type_params(self, db: &dyn HirDatabase) -> bool { let subst = db.generic_defaults(self.id.into()); - subst.iter().any(|ty| match ty.skip_binders().data(Interner) { - GenericArgData::Ty(it) => it.is_unknown(), - _ => false, - }) + (subst.is_empty() && db.generic_params(self.id.into()).len_type_or_consts() != 0) + || subst.iter().any(|ty| match ty.skip_binders().data(Interner) { + GenericArgData::Ty(it) => it.is_unknown(), + _ => false, + }) } pub fn module(self, db: &dyn HirDatabase) -> Module { @@ -3732,6 +3734,23 @@ impl GenericDef { } } } + + /// Returns a string describing the kind of this type. + #[inline] + pub fn description(self) -> &'static str { + match self { + GenericDef::Function(_) => "function", + GenericDef::Adt(Adt::Struct(_)) => "struct", + GenericDef::Adt(Adt::Enum(_)) => "enum", + GenericDef::Adt(Adt::Union(_)) => "union", + GenericDef::Trait(_) => "trait", + GenericDef::TraitAlias(_) => "trait alias", + GenericDef::TypeAlias(_) => "type alias", + GenericDef::Impl(_) => "impl", + GenericDef::Const(_) => "constant", + GenericDef::Static(_) => "static", + } + } } // We cannot call this `Substitution` unfortunately... @@ -4276,7 +4295,8 @@ fn generic_arg_from_param(db: &dyn HirDatabase, id: TypeOrConstParamId) -> Optio let local_idx = hir_ty::param_idx(db, id)?; let defaults = db.generic_defaults(id.parent); let ty = defaults.get(local_idx)?.clone(); - let subst = TyBuilder::placeholder_subst(db, id.parent); + let full_subst = TyBuilder::placeholder_subst(db, id.parent); + let subst = &full_subst.as_slice(Interner)[..local_idx]; Some(ty.substitute(Interner, &subst)) } diff --git a/crates/ide-diagnostics/src/handlers/generic_args_prohibited.rs b/crates/ide-diagnostics/src/handlers/generic_args_prohibited.rs index b79894dd15..b617c09498 100644 --- a/crates/ide-diagnostics/src/handlers/generic_args_prohibited.rs +++ b/crates/ide-diagnostics/src/handlers/generic_args_prohibited.rs @@ -36,6 +36,7 @@ fn describe_reason(reason: GenericArgsProhibitedReason) -> String { } GenericArgsProhibitedReason::Const => "constants", GenericArgsProhibitedReason::Static => "statics", + GenericArgsProhibitedReason::LocalVariable => "local variables", }; format!("generic arguments are not allowed on {kind}") } @@ -320,7 +321,7 @@ trait E::Trait> // ^^^^^ 💡 error: generic arguments are not allowed on builtin types } -impl::Trait> E for () +impl::Trait> E<()> for () // ^^^^^^ 💡 error: generic arguments are not allowed on modules where bool: foo::Trait // ^^^^^ 💡 error: generic arguments are not allowed on builtin types @@ -518,14 +519,14 @@ fn baz() { } #[test] - fn const_and_static() { + fn const_param_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 +fn baz() { + let _ = CONST_PARAM::<()>; + // ^^^^^^ 💡 error: generic arguments are not allowed on constants let _ = STATIC::<()>; // ^^^^^^ 💡 error: generic arguments are not allowed on statics } @@ -533,6 +534,19 @@ fn baz() { ); } + #[test] + fn local_variable() { + check_diagnostics( + r#" +fn baz() { + let x = 1; + let _ = x::<()>; + // ^^^^^^ 💡 error: generic arguments are not allowed on local variables +} + "#, + ); + } + #[test] fn enum_variant() { check_diagnostics( diff --git a/crates/ide-diagnostics/src/handlers/incorrect_generics_len.rs b/crates/ide-diagnostics/src/handlers/incorrect_generics_len.rs new file mode 100644 index 0000000000..4fc894080a --- /dev/null +++ b/crates/ide-diagnostics/src/handlers/incorrect_generics_len.rs @@ -0,0 +1,172 @@ +use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext}; +use hir::IncorrectGenericsLenKind; + +// Diagnostic: incorrect-generics-len +// +// This diagnostic is triggered if the number of generic arguments does not match their declaration. +pub(crate) fn incorrect_generics_len( + ctx: &DiagnosticsContext<'_>, + d: &hir::IncorrectGenericsLen, +) -> Diagnostic { + let owner_description = d.def.description(); + let expected = d.expected; + let provided = d.provided; + let kind_description = match d.kind { + IncorrectGenericsLenKind::Lifetimes => "lifetime", + IncorrectGenericsLenKind::TypesAndConsts => "generic", + }; + let message = format!( + "this {owner_description} takes {expected} {kind_description} argument{} \ + but {provided} {kind_description} argument{} {} supplied", + if expected == 1 { "" } else { "s" }, + if provided == 1 { "" } else { "s" }, + if provided == 1 { "was" } else { "were" }, + ); + Diagnostic::new_with_syntax_node_ptr( + ctx, + DiagnosticCode::RustcHardError("E0107"), + message, + d.generics_or_segment.map(Into::into), + ) +} + +#[cfg(test)] +mod tests { + use crate::tests::check_diagnostics; + + #[test] + fn partially_specified_generics() { + check_diagnostics( + r#" +struct Bar(T, U); + +fn foo() { + let _ = Bar::<()>; + // ^^^^^^ error: this struct takes 2 generic arguments but 1 generic argument was supplied +} + + "#, + ); + } + + #[test] + fn enum_variant() { + check_diagnostics( + r#" +enum Enum { + Variant(T, U), +} + +fn foo() { + let _ = Enum::<()>::Variant; + // ^^^^^^ error: this enum takes 2 generic arguments but 1 generic argument was supplied + let _ = Enum::Variant::<()>; + // ^^^^^^ error: this enum takes 2 generic arguments but 1 generic argument was supplied +} + + "#, + ); + } + + #[test] + fn lifetimes() { + check_diagnostics( + r#" +struct Foo<'a, 'b>(&'a &'b ()); +struct Bar<'a>(&'a ()); + +fn foo() -> Foo { + let _ = Foo; + let _ = Foo::<>; + let _ = Foo::<'static>; + // ^^^^^^^^^^^ error: this struct takes 2 lifetime arguments but 1 lifetime argument was supplied + loop {} +} + +fn bar(_v: Bar) -> Foo { loop {} } + "#, + ); + } + + #[test] + fn no_error_for_elided_lifetimes() { + check_diagnostics( + r#" +struct Foo<'a>(&'a ()); + +fn foo(_v: &()) -> Foo { loop {} } + "#, + ); + } + + #[test] + fn errs_for_elided_lifetimes_if_lifetimes_are_explicitly_provided() { + check_diagnostics( + r#" +struct Foo<'a, 'b>(&'a &'b ()); + +fn foo(_v: Foo<'_> + // ^^^^ error: this struct takes 2 lifetime arguments but 1 lifetime argument was supplied +) -> Foo<'static> { loop {} } + // ^^^^^^^^^ error: this struct takes 2 lifetime arguments but 1 lifetime argument was supplied + "#, + ); + } + + #[test] + fn types_and_consts() { + check_diagnostics( + r#" +struct Foo<'a, T>(&'a T); +fn foo(_v: Foo) {} + // ^^^ error: this struct takes 1 generic argument but 0 generic arguments were supplied + +struct Bar(T); +fn bar() { + let _ = Bar::<()>; + // ^^^^^^ error: this struct takes 2 generic arguments but 1 generic argument was supplied +} + "#, + ); + } + + #[test] + fn respects_defaults() { + check_diagnostics( + r#" +struct Foo(T); +fn foo(_v: Foo) {} + +struct Bar(T); +fn bar(_v: Bar<()>) {} + "#, + ); + } + + #[test] + fn constant() { + check_diagnostics( + r#" +const CONST: i32 = 0; +fn baz() { + let _ = CONST::<()>; + // ^^^^^^ error: this constant takes 0 generic arguments but 1 generic argument was supplied +} + "#, + ); + } + + #[test] + fn assoc_type() { + check_diagnostics( + r#" +trait Trait { + type Assoc; +} + +fn foo = bool>>() {} + // ^^^^^ error: this type alias takes 0 generic arguments but 1 generic argument was supplied + "#, + ); + } +} diff --git a/crates/ide-diagnostics/src/handlers/incorrect_generics_order.rs b/crates/ide-diagnostics/src/handlers/incorrect_generics_order.rs new file mode 100644 index 0000000000..84496df2d7 --- /dev/null +++ b/crates/ide-diagnostics/src/handlers/incorrect_generics_order.rs @@ -0,0 +1,80 @@ +use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext}; +use hir::GenericArgKind; +use syntax::SyntaxKind; + +// Diagnostic: incorrect-generics-order +// +// This diagnostic is triggered the order of provided generic arguments does not match their declaration. +pub(crate) fn incorrect_generics_order( + ctx: &DiagnosticsContext<'_>, + d: &hir::IncorrectGenericsOrder, +) -> Diagnostic { + let provided_description = match d.provided_arg.value.kind() { + SyntaxKind::CONST_ARG => "constant", + SyntaxKind::LIFETIME_ARG => "lifetime", + SyntaxKind::TYPE_ARG => "type", + _ => panic!("non-generic-arg passed to `incorrect_generics_order()`"), + }; + let expected_description = match d.expected_kind { + GenericArgKind::Lifetime => "lifetime", + GenericArgKind::Type => "type", + GenericArgKind::Const => "constant", + }; + let message = + format!("{provided_description} provided when a {expected_description} was expected"); + Diagnostic::new_with_syntax_node_ptr( + ctx, + DiagnosticCode::RustcHardError("E0747"), + message, + d.provided_arg.map(Into::into), + ) +} + +#[cfg(test)] +mod tests { + use crate::tests::check_diagnostics; + + #[test] + fn lifetime_out_of_order() { + check_diagnostics( + r#" +struct Foo<'a, T>(&'a T); + +fn bar(_v: Foo<(), 'static>) {} + // ^^ error: type provided when a lifetime was expected + "#, + ); + } + + #[test] + fn types_and_consts() { + check_diagnostics( + r#" +struct Foo(T); +fn foo1(_v: Foo<1>) {} + // ^ error: constant provided when a type was expected +fn foo2(_v: Foo<{ (1, 2) }>) {} + // ^^^^^^^^^^ error: constant provided when a type was expected + +struct Bar; +fn bar(_v: Bar<()>) {} + // ^^ error: type provided when a constant was expected + +struct Baz(T); +fn baz(_v: Baz<1, ()>) {} + // ^ error: constant provided when a type was expected + "#, + ); + } + + #[test] + fn no_error_when_num_incorrect() { + check_diagnostics( + r#" +struct Baz(T, U); +fn baz(_v: Baz<1>) {} + // ^^^ error: this struct takes 2 generic arguments but 1 generic argument was supplied + "#, + ); + } +} diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs index 0d725b3563..ddaef57b53 100644 --- a/crates/ide-diagnostics/src/lib.rs +++ b/crates/ide-diagnostics/src/lib.rs @@ -32,6 +32,8 @@ mod handlers { pub(crate) mod inactive_code; pub(crate) mod incoherent_impl; pub(crate) mod incorrect_case; + pub(crate) mod incorrect_generics_len; + pub(crate) mod incorrect_generics_order; pub(crate) mod invalid_cast; pub(crate) mod invalid_derive_target; pub(crate) mod macro_error; @@ -499,6 +501,8 @@ pub fn semantic_diagnostics( handlers::parenthesized_generic_args_without_fn_trait::parenthesized_generic_args_without_fn_trait(&ctx, &d) } AnyDiagnostic::BadRtn(d) => handlers::bad_rtn::bad_rtn(&ctx, &d), + AnyDiagnostic::IncorrectGenericsLen(d) => handlers::incorrect_generics_len::incorrect_generics_len(&ctx, &d), + AnyDiagnostic::IncorrectGenericsOrder(d) => handlers::incorrect_generics_order::incorrect_generics_order(&ctx, &d), }; res.push(d) }