From f3d7415bd6a7641a92b14735dd1bab32c967d231 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 5 Dec 2024 14:37:38 +0100 Subject: [PATCH] Parse lifetime bounds in lifetime param into TypeBoundList This mainly aids in error recovery but also makes it a bit easier to handle lifetime resolution. While doing so it also came apparent that we were not actually lowering lifetime outlives relationships within lifetime parameter declaration bounds, so this fixes that. --- crates/hir-def/src/item_tree/pretty.rs | 4 +- crates/hir-def/src/item_tree/tests.rs | 6 ++- crates/hir-ty/src/tests/traits.rs | 23 +++++++++ crates/hir/src/semantics.rs | 4 ++ .../src/completions/lifetime.rs | 36 ++++++-------- crates/ide-completion/src/context.rs | 5 +- crates/ide-completion/src/context/analysis.rs | 17 +++---- crates/ide-db/src/defs.rs | 10 ---- crates/intern/src/symbol/symbols.rs | 1 + crates/parser/src/grammar/generic_params.rs | 15 ++++-- .../parser/inline/ok/lifetime_param.rast | 6 ++- .../parser/inline/ok/precise_capturing.rast | 12 +++-- .../parser/ok/0018_struct_type_params.rast | 48 ++++++++++++------- .../parser/ok/0020_type_param_bounds.rast | 23 +++++---- 14 files changed, 125 insertions(+), 85 deletions(-) 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