diff --git a/crates/hir-def/src/expr_store.rs b/crates/hir-def/src/expr_store.rs index aa26e8b3df25..3141fceeb0ff 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 e7f2247c5bc2..d49c283bee6c 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 36b3d11d75ff..14b0d3abd41c 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 890e7874a847..52c0c669ea42 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 8c3665dfc836..77d15a73af6f 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 563d3c714120..75a680b5885c 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 a79b8cdf8eec..39d6083b35c6 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 8379636d5c6d..bb4aaf788958 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 24b1909b6d18..cad2e3ce9932 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 f613e2f69f72..2c633a03c543 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 17cd322a22ea..2980549c2365 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), - }; + struct LowererCtx<'a, 'b> { + ctx: &'a mut InferenceContext<'b>, + expr: ExprId, + } - 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), - ), - }; + 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, + }); + } - substs.push(arg); + 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, + }); } - }; - 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) + 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), + } } - 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)); + 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) + } + } + } } - assert_eq!(substs.len(), total_len); - Substitution::from_iter(Interner, substs) + + 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 461cea4f1431..0e1d23b6949a 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 4f60bb21b6aa..2cb977b63402 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 57849ff9139f..ea42c5929627 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 5f299aca3ae4..a5a13d64e0d4 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 a059d9df1c21..be3581639918 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; - - 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)) - } - GenericParamId::TypeParamId(_) => ty_error(), - GenericParamId::LifetimeParamId(_) => error_lifetime().cast(Interner), - }; - - let mut substs: Vec<_> = def_generics.iter_parent_id().map(param_to_err).collect(); + struct LowererCtx<'a, 'b, 'c> { + ctx: &'a mut PathLoweringContext<'b, 'c>, + generics_source: PathGenericsSource, + } - tracing::debug!(?substs, ?parent_params); + 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, + }); + } - // we need to iterate the lifetime and type/const params separately as our order of them - // differs from the supplied syntax + 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, + }); + } - 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); + 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()`"), + } + } - if let Some(id) = def_toc_iter.next() { - assert!(matches!(id, GenericParamId::TypeParamId(_))); - substs.push(self_ty); + 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), } } - }; - 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 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); } - } 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)); + 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 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 4eaa9fb50142..d8bcaa0e74ee 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 37c213c2afaa..143c13069e42 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 b79894dd1579..b617c0949839 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 000000000000..4fc894080abc --- /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 000000000000..84496df2d7cf --- /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 0d725b35634e..ddaef57b537c 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) } diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs index 4a2896ab1486..1079dec04314 100644 --- a/crates/test-utils/src/minicore.rs +++ b/crates/test-utils/src/minicore.rs @@ -1063,7 +1063,7 @@ pub mod cmp { // region:fmt pub mod fmt { pub struct Error; - pub type Result = Result<(), Error>; + pub type Result = crate::result::Result<(), Error>; pub struct Formatter<'a>; pub struct DebugTuple; pub struct DebugStruct;