diff --git a/crates/hir-def/src/item_tree/pretty.rs b/crates/hir-def/src/item_tree/pretty.rs index 0c5e3a3620..70bf2f13c8 100644 --- a/crates/hir-def/src/item_tree/pretty.rs +++ b/crates/hir-def/src/item_tree/pretty.rs @@ -648,9 +648,9 @@ impl Printer<'_> { let (target, bound) = match pred { WherePredicate::TypeBound { target, bound } => (target, bound), WherePredicate::Lifetime { target, bound } => { - wln!( + w!( this, - "{}: {},", + "{}: {}", target.name.display(self.db.upcast(), edition), bound.name.display(self.db.upcast(), edition) ); diff --git a/crates/hir-def/src/item_tree/tests.rs b/crates/hir-def/src/item_tree/tests.rs index 5c07369f4b..0f53969d6c 100644 --- a/crates/hir-def/src/item_tree/tests.rs +++ b/crates/hir-def/src/item_tree/tests.rs @@ -351,7 +351,8 @@ trait Tr<'a, T: 'a>: Super where Self: for<'a> Tr<'a, T> {} where T: Copy, T: 'a, - T: 'b + T: 'b, + 'b: 'a { pub(self) field: &'a &'b T, } @@ -370,7 +371,8 @@ trait Tr<'a, T: 'a>: Super where Self: for<'a> Tr<'a, T> {} where T: Copy, T: 'a, - T: 'b + T: 'b, + 'b: 'a { // AstId: 9 pub(self) fn f( diff --git a/crates/hir-ty/src/tests/traits.rs b/crates/hir-ty/src/tests/traits.rs index 624148cab2..b62672d21e 100644 --- a/crates/hir-ty/src/tests/traits.rs +++ b/crates/hir-ty/src/tests/traits.rs @@ -1630,6 +1630,29 @@ fn test<'lifetime>( ); } +#[test] +fn lifetime_bounds() { + check_infer( + r#" +//- minicore: sized, coerce_unsized +trait Trait<'a>: Sized { + fn f(&'a self) {} +} +fn test<'a, 'b: 'a>(it: impl Trait<'a>){ + it.f(); +} +"#, + expect![[r#" + 38..42 'self': &'a Self + 44..46 '{}': () + 69..71 'it': impl Trait<'a> + 88..103 '{ it.f(); }': () + 94..96 'it': impl Trait<'a> + 94..100 'it.f()': () + "#]], + ); +} + #[test] fn error_bound_chalk() { check_types( diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 65470d061b..0b09cf2792 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -2026,6 +2026,10 @@ impl SemanticsScope<'_> { ) } + pub fn generic_def(&self) -> Option { + self.resolver.generic_def().map(|id| id.into()) + } + pub fn extern_crates(&self) -> impl Iterator + '_ { self.resolver.extern_crates_in_scope().map(|(name, id)| (name, Module { id })) } diff --git a/crates/ide-completion/src/completions/lifetime.rs b/crates/ide-completion/src/completions/lifetime.rs index 9efc52428e..0692446381 100644 --- a/crates/ide-completion/src/completions/lifetime.rs +++ b/crates/ide-completion/src/completions/lifetime.rs @@ -8,7 +8,6 @@ //! show up for normal completions, or they won't show completions other than lifetimes depending //! on the fixture input. use hir::{sym, Name, ScopeDef}; -use syntax::{ast, ToSmolStr, TokenText}; use crate::{ completions::Completions, @@ -21,33 +20,24 @@ pub(crate) fn complete_lifetime( ctx: &CompletionContext<'_>, lifetime_ctx: &LifetimeContext, ) { - let (lp, lifetime) = match lifetime_ctx { - LifetimeContext { kind: LifetimeKind::Lifetime, lifetime } => (None, lifetime), - LifetimeContext { - kind: LifetimeKind::LifetimeParam { is_decl: false, param }, - lifetime, - } => (Some(param), lifetime), - _ => return, + let &LifetimeContext { kind: LifetimeKind::Lifetime { in_lifetime_param_bound, def }, .. } = + lifetime_ctx + else { + return; }; - let param_lifetime = match (lifetime, lp.and_then(|lp| lp.lifetime())) { - (Some(lt), Some(lp)) if lp == lt.clone() => return, - (Some(_), Some(lp)) => Some(lp), - _ => None, - }; - let param_lifetime = param_lifetime.as_ref().map(ast::Lifetime::text); - let param_lifetime = param_lifetime.as_ref().map(TokenText::as_str); ctx.process_all_names_raw(&mut |name, res| { - if matches!( - res, - ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_)) - if param_lifetime != Some(&*name.display_no_db(ctx.edition).to_smolstr()) - ) { + if matches!(res, ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_))) { acc.add_lifetime(ctx, name); } }); - if param_lifetime.is_none() { - acc.add_lifetime(ctx, Name::new_symbol_root(sym::tick_static.clone())); + acc.add_lifetime(ctx, Name::new_symbol_root(sym::tick_static.clone())); + if !in_lifetime_param_bound + && def.is_some_and(|def| { + !matches!(def, hir::GenericDef::Function(_) | hir::GenericDef::Impl(_)) + }) + { + acc.add_lifetime(ctx, Name::new_symbol_root(sym::tick_underscore.clone())); } } @@ -222,6 +212,8 @@ fn foo<'footime, 'lifetime: 'a$0>() {} "#, expect![[r#" lt 'footime + lt 'lifetime + lt 'static "#]], ); } diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index 5b8d1c30a2..3a66170633 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -290,15 +290,14 @@ pub(crate) struct ParamContext { /// The state of the lifetime we are completing. #[derive(Debug)] pub(crate) struct LifetimeContext { - pub(crate) lifetime: Option, pub(crate) kind: LifetimeKind, } /// The kind of lifetime we are completing. #[derive(Debug)] pub(crate) enum LifetimeKind { - LifetimeParam { is_decl: bool, param: ast::LifetimeParam }, - Lifetime, + LifetimeParam, + Lifetime { in_lifetime_param_bound: bool, def: Option }, LabelRef, LabelDef, } diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs index a4e018b180..4a678963b9 100644 --- a/crates/ide-completion/src/context/analysis.rs +++ b/crates/ide-completion/src/context/analysis.rs @@ -562,7 +562,7 @@ fn expected_type_and_name( } fn classify_lifetime( - _sema: &Semantics<'_, RootDatabase>, + sema: &Semantics<'_, RootDatabase>, original_file: &SyntaxNode, lifetime: ast::Lifetime, ) -> Option { @@ -571,21 +571,22 @@ fn classify_lifetime( return None; } + let lifetime = + find_node_at_offset::(original_file, lifetime.syntax().text_range().start()); let kind = match_ast! { match parent { - ast::LifetimeParam(param) => LifetimeKind::LifetimeParam { - is_decl: param.lifetime().as_ref() == Some(&lifetime), - param - }, + ast::LifetimeParam(_) => LifetimeKind::LifetimeParam, ast::BreakExpr(_) => LifetimeKind::LabelRef, ast::ContinueExpr(_) => LifetimeKind::LabelRef, ast::Label(_) => LifetimeKind::LabelDef, - _ => LifetimeKind::Lifetime, + _ => { + let def = lifetime.as_ref().and_then(|lt| sema.scope(lt.syntax())?.generic_def()); + LifetimeKind::Lifetime { in_lifetime_param_bound: ast::TypeBound::can_cast(parent.kind()), def } + }, } }; - let lifetime = find_node_at_offset(original_file, lifetime.syntax().text_range().start()); - Some(LifetimeContext { lifetime, kind }) + Some(LifetimeContext { kind }) } fn classify_name( diff --git a/crates/ide-db/src/defs.rs b/crates/ide-db/src/defs.rs index fdac4dd2ef..5eec33636b 100644 --- a/crates/ide-db/src/defs.rs +++ b/crates/ide-db/src/defs.rs @@ -772,16 +772,6 @@ impl NameRefClass { .map(GenericParam::LifetimeParam) .map(Definition::GenericParam) .map(NameRefClass::Definition), - // lifetime bounds, as in the 'b in 'a: 'b aren't wrapped in TypeBound nodes so we gotta check - // if our lifetime is in a LifetimeParam without being the constrained lifetime - _ if ast::LifetimeParam::cast(parent).and_then(|param| param.lifetime()).as_ref() - != Some(lifetime) => - { - sema.resolve_lifetime_param(lifetime) - .map(GenericParam::LifetimeParam) - .map(Definition::GenericParam) - .map(NameRefClass::Definition) - } _ => None, } } diff --git a/crates/intern/src/symbol/symbols.rs b/crates/intern/src/symbol/symbols.rs index 8f79cf2007..ebc9c14059 100644 --- a/crates/intern/src/symbol/symbols.rs +++ b/crates/intern/src/symbol/symbols.rs @@ -80,6 +80,7 @@ define_symbols! { self_ = "self", Self_ = "Self", tick_static = "'static", + tick_underscore = "'_", dollar_crate = "$crate", MISSING_NAME = "[missing name]", fn_ = "fn", diff --git a/crates/parser/src/grammar/generic_params.rs b/crates/parser/src/grammar/generic_params.rs index 08b23cd92a..9d4fdbfaf2 100644 --- a/crates/parser/src/grammar/generic_params.rs +++ b/crates/parser/src/grammar/generic_params.rs @@ -56,7 +56,7 @@ fn generic_param(p: &mut Parser<'_>, m: Marker) -> bool { fn lifetime_param(p: &mut Parser<'_>, m: Marker) { assert!(p.at(LIFETIME_IDENT)); lifetime(p); - if p.at(T![:]) { + if p.eat(T![:]) { lifetime_bounds(p); } m.complete(p, LIFETIME_PARAM); @@ -106,14 +106,19 @@ fn const_param(p: &mut Parser<'_>, m: Marker) { } fn lifetime_bounds(p: &mut Parser<'_>) { - assert!(p.at(T![:])); - p.bump(T![:]); - while p.at(LIFETIME_IDENT) { - lifetime(p); + let marker = p.start(); + while { + if !matches!(p.current(), LIFETIME_IDENT | T![>] | T![,]) { + p.error("expected lifetime"); + } + + type_bound(p) + } { if !p.eat(T![+]) { break; } } + marker.complete(p, TYPE_BOUND_LIST); } // test type_param_bounds diff --git a/crates/parser/test_data/parser/inline/ok/lifetime_param.rast b/crates/parser/test_data/parser/inline/ok/lifetime_param.rast index c595031f35..315200aca2 100644 --- a/crates/parser/test_data/parser/inline/ok/lifetime_param.rast +++ b/crates/parser/test_data/parser/inline/ok/lifetime_param.rast @@ -11,8 +11,10 @@ SOURCE_FILE LIFETIME_IDENT "'a" COLON ":" WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'b" + TYPE_BOUND_LIST + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'b" R_ANGLE ">" PARAM_LIST L_PAREN "(" diff --git a/crates/parser/test_data/parser/inline/ok/precise_capturing.rast b/crates/parser/test_data/parser/inline/ok/precise_capturing.rast index f9c0a245af..5a67cc2176 100644 --- a/crates/parser/test_data/parser/inline/ok/precise_capturing.rast +++ b/crates/parser/test_data/parser/inline/ok/precise_capturing.rast @@ -11,8 +11,10 @@ SOURCE_FILE LIFETIME_IDENT "'a" COLON ":" WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'a" + TYPE_BOUND_LIST + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'a" COMMA "," WHITESPACE " " LIFETIME_PARAM @@ -20,8 +22,10 @@ SOURCE_FILE LIFETIME_IDENT "'b" COLON ":" WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'b" + TYPE_BOUND_LIST + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'b" COMMA "," WHITESPACE " " TYPE_PARAM diff --git a/crates/parser/test_data/parser/ok/0018_struct_type_params.rast b/crates/parser/test_data/parser/ok/0018_struct_type_params.rast index 11ebc7efb9..1e4eb15609 100644 --- a/crates/parser/test_data/parser/ok/0018_struct_type_params.rast +++ b/crates/parser/test_data/parser/ok/0018_struct_type_params.rast @@ -96,6 +96,7 @@ SOURCE_FILE LIFETIME LIFETIME_IDENT "'a" COLON ":" + TYPE_BOUND_LIST R_ANGLE ">" SEMICOLON ";" WHITESPACE "\n" @@ -111,8 +112,10 @@ SOURCE_FILE LIFETIME_IDENT "'a" COLON ":" WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'b" + TYPE_BOUND_LIST + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'b" R_ANGLE ">" SEMICOLON ";" WHITESPACE "\n" @@ -128,10 +131,12 @@ SOURCE_FILE LIFETIME_IDENT "'a" COLON ":" WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'b" - WHITESPACE " " - PLUS "+" + TYPE_BOUND_LIST + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'b" + WHITESPACE " " + PLUS "+" WHITESPACE " " R_ANGLE ">" SEMICOLON ";" @@ -148,13 +153,16 @@ SOURCE_FILE LIFETIME_IDENT "'a" COLON ":" WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'b" - WHITESPACE " " - PLUS "+" - WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'c" + TYPE_BOUND_LIST + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'b" + WHITESPACE " " + PLUS "+" + WHITESPACE " " + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'c" R_ANGLE ">" SEMICOLON ";" WHITESPACE "\n" @@ -202,9 +210,11 @@ SOURCE_FILE LIFETIME_IDENT "'a" COLON ":" WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'b" - PLUS "+" + TYPE_BOUND_LIST + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'b" + PLUS "+" COMMA "," WHITESPACE " " LIFETIME_PARAM @@ -212,8 +222,10 @@ SOURCE_FILE LIFETIME_IDENT "'b" COLON ":" WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'c" + TYPE_BOUND_LIST + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'c" COMMA "," R_ANGLE ">" SEMICOLON ";" diff --git a/crates/parser/test_data/parser/ok/0020_type_param_bounds.rast b/crates/parser/test_data/parser/ok/0020_type_param_bounds.rast index 043a966ff9..448cf49446 100644 --- a/crates/parser/test_data/parser/ok/0020_type_param_bounds.rast +++ b/crates/parser/test_data/parser/ok/0020_type_param_bounds.rast @@ -237,8 +237,10 @@ SOURCE_FILE LIFETIME_IDENT "'a" COLON ":" WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'d" + TYPE_BOUND_LIST + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'d" COMMA "," WHITESPACE " " LIFETIME_PARAM @@ -246,13 +248,16 @@ SOURCE_FILE LIFETIME_IDENT "'d" COLON ":" WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'a" - WHITESPACE " " - PLUS "+" - WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'b" + TYPE_BOUND_LIST + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'a" + WHITESPACE " " + PLUS "+" + WHITESPACE " " + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'b" COMMA "," WHITESPACE " " TYPE_PARAM