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.
This commit is contained in:
Lukas Wirth 2024-12-05 14:37:38 +01:00
parent df7ab62a06
commit f3d7415bd6
14 changed files with 125 additions and 85 deletions

View file

@ -648,9 +648,9 @@ impl Printer<'_> {
let (target, bound) = match pred { let (target, bound) = match pred {
WherePredicate::TypeBound { target, bound } => (target, bound), WherePredicate::TypeBound { target, bound } => (target, bound),
WherePredicate::Lifetime { target, bound } => { WherePredicate::Lifetime { target, bound } => {
wln!( w!(
this, this,
"{}: {},", "{}: {}",
target.name.display(self.db.upcast(), edition), target.name.display(self.db.upcast(), edition),
bound.name.display(self.db.upcast(), edition) bound.name.display(self.db.upcast(), edition)
); );

View file

@ -351,7 +351,8 @@ trait Tr<'a, T: 'a>: Super where Self: for<'a> Tr<'a, T> {}
where where
T: Copy, T: Copy,
T: 'a, T: 'a,
T: 'b T: 'b,
'b: 'a
{ {
pub(self) field: &'a &'b T, pub(self) field: &'a &'b T,
} }
@ -370,7 +371,8 @@ trait Tr<'a, T: 'a>: Super where Self: for<'a> Tr<'a, T> {}
where where
T: Copy, T: Copy,
T: 'a, T: 'a,
T: 'b T: 'b,
'b: 'a
{ {
// AstId: 9 // AstId: 9
pub(self) fn f<G>( pub(self) fn f<G>(

View file

@ -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] #[test]
fn error_bound_chalk() { fn error_bound_chalk() {
check_types( check_types(

View file

@ -2026,6 +2026,10 @@ impl SemanticsScope<'_> {
) )
} }
pub fn generic_def(&self) -> Option<crate::GenericDef> {
self.resolver.generic_def().map(|id| id.into())
}
pub fn extern_crates(&self) -> impl Iterator<Item = (Name, Module)> + '_ { pub fn extern_crates(&self) -> impl Iterator<Item = (Name, Module)> + '_ {
self.resolver.extern_crates_in_scope().map(|(name, id)| (name, Module { id })) self.resolver.extern_crates_in_scope().map(|(name, id)| (name, Module { id }))
} }

View file

@ -8,7 +8,6 @@
//! show up for normal completions, or they won't show completions other than lifetimes depending //! show up for normal completions, or they won't show completions other than lifetimes depending
//! on the fixture input. //! on the fixture input.
use hir::{sym, Name, ScopeDef}; use hir::{sym, Name, ScopeDef};
use syntax::{ast, ToSmolStr, TokenText};
use crate::{ use crate::{
completions::Completions, completions::Completions,
@ -21,33 +20,24 @@ pub(crate) fn complete_lifetime(
ctx: &CompletionContext<'_>, ctx: &CompletionContext<'_>,
lifetime_ctx: &LifetimeContext, lifetime_ctx: &LifetimeContext,
) { ) {
let (lp, lifetime) = match lifetime_ctx { let &LifetimeContext { kind: LifetimeKind::Lifetime { in_lifetime_param_bound, def }, .. } =
LifetimeContext { kind: LifetimeKind::Lifetime, lifetime } => (None, lifetime), lifetime_ctx
LifetimeContext { else {
kind: LifetimeKind::LifetimeParam { is_decl: false, param }, return;
lifetime,
} => (Some(param), lifetime),
_ => 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| { ctx.process_all_names_raw(&mut |name, res| {
if matches!( if matches!(res, ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_))) {
res,
ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_))
if param_lifetime != Some(&*name.display_no_db(ctx.edition).to_smolstr())
) {
acc.add_lifetime(ctx, name); 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#" expect![[r#"
lt 'footime lt 'footime
lt 'lifetime
lt 'static
"#]], "#]],
); );
} }

View file

@ -290,15 +290,14 @@ pub(crate) struct ParamContext {
/// The state of the lifetime we are completing. /// The state of the lifetime we are completing.
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct LifetimeContext { pub(crate) struct LifetimeContext {
pub(crate) lifetime: Option<ast::Lifetime>,
pub(crate) kind: LifetimeKind, pub(crate) kind: LifetimeKind,
} }
/// The kind of lifetime we are completing. /// The kind of lifetime we are completing.
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum LifetimeKind { pub(crate) enum LifetimeKind {
LifetimeParam { is_decl: bool, param: ast::LifetimeParam }, LifetimeParam,
Lifetime, Lifetime { in_lifetime_param_bound: bool, def: Option<hir::GenericDef> },
LabelRef, LabelRef,
LabelDef, LabelDef,
} }

View file

@ -562,7 +562,7 @@ fn expected_type_and_name(
} }
fn classify_lifetime( fn classify_lifetime(
_sema: &Semantics<'_, RootDatabase>, sema: &Semantics<'_, RootDatabase>,
original_file: &SyntaxNode, original_file: &SyntaxNode,
lifetime: ast::Lifetime, lifetime: ast::Lifetime,
) -> Option<LifetimeContext> { ) -> Option<LifetimeContext> {
@ -571,21 +571,22 @@ fn classify_lifetime(
return None; return None;
} }
let lifetime =
find_node_at_offset::<ast::Lifetime>(original_file, lifetime.syntax().text_range().start());
let kind = match_ast! { let kind = match_ast! {
match parent { match parent {
ast::LifetimeParam(param) => LifetimeKind::LifetimeParam { ast::LifetimeParam(_) => LifetimeKind::LifetimeParam,
is_decl: param.lifetime().as_ref() == Some(&lifetime),
param
},
ast::BreakExpr(_) => LifetimeKind::LabelRef, ast::BreakExpr(_) => LifetimeKind::LabelRef,
ast::ContinueExpr(_) => LifetimeKind::LabelRef, ast::ContinueExpr(_) => LifetimeKind::LabelRef,
ast::Label(_) => LifetimeKind::LabelDef, 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( fn classify_name(

View file

@ -772,16 +772,6 @@ impl NameRefClass {
.map(GenericParam::LifetimeParam) .map(GenericParam::LifetimeParam)
.map(Definition::GenericParam) .map(Definition::GenericParam)
.map(NameRefClass::Definition), .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, _ => None,
} }
} }

View file

@ -80,6 +80,7 @@ define_symbols! {
self_ = "self", self_ = "self",
Self_ = "Self", Self_ = "Self",
tick_static = "'static", tick_static = "'static",
tick_underscore = "'_",
dollar_crate = "$crate", dollar_crate = "$crate",
MISSING_NAME = "[missing name]", MISSING_NAME = "[missing name]",
fn_ = "fn", fn_ = "fn",

View file

@ -56,7 +56,7 @@ fn generic_param(p: &mut Parser<'_>, m: Marker) -> bool {
fn lifetime_param(p: &mut Parser<'_>, m: Marker) { fn lifetime_param(p: &mut Parser<'_>, m: Marker) {
assert!(p.at(LIFETIME_IDENT)); assert!(p.at(LIFETIME_IDENT));
lifetime(p); lifetime(p);
if p.at(T![:]) { if p.eat(T![:]) {
lifetime_bounds(p); lifetime_bounds(p);
} }
m.complete(p, LIFETIME_PARAM); m.complete(p, LIFETIME_PARAM);
@ -106,14 +106,19 @@ fn const_param(p: &mut Parser<'_>, m: Marker) {
} }
fn lifetime_bounds(p: &mut Parser<'_>) { fn lifetime_bounds(p: &mut Parser<'_>) {
assert!(p.at(T![:])); let marker = p.start();
p.bump(T![:]); while {
while p.at(LIFETIME_IDENT) { if !matches!(p.current(), LIFETIME_IDENT | T![>] | T![,]) {
lifetime(p); p.error("expected lifetime");
}
type_bound(p)
} {
if !p.eat(T![+]) { if !p.eat(T![+]) {
break; break;
} }
} }
marker.complete(p, TYPE_BOUND_LIST);
} }
// test type_param_bounds // test type_param_bounds

View file

@ -11,8 +11,10 @@ SOURCE_FILE
LIFETIME_IDENT "'a" LIFETIME_IDENT "'a"
COLON ":" COLON ":"
WHITESPACE " " WHITESPACE " "
LIFETIME TYPE_BOUND_LIST
LIFETIME_IDENT "'b" TYPE_BOUND
LIFETIME
LIFETIME_IDENT "'b"
R_ANGLE ">" R_ANGLE ">"
PARAM_LIST PARAM_LIST
L_PAREN "(" L_PAREN "("

View file

@ -11,8 +11,10 @@ SOURCE_FILE
LIFETIME_IDENT "'a" LIFETIME_IDENT "'a"
COLON ":" COLON ":"
WHITESPACE " " WHITESPACE " "
LIFETIME TYPE_BOUND_LIST
LIFETIME_IDENT "'a" TYPE_BOUND
LIFETIME
LIFETIME_IDENT "'a"
COMMA "," COMMA ","
WHITESPACE " " WHITESPACE " "
LIFETIME_PARAM LIFETIME_PARAM
@ -20,8 +22,10 @@ SOURCE_FILE
LIFETIME_IDENT "'b" LIFETIME_IDENT "'b"
COLON ":" COLON ":"
WHITESPACE " " WHITESPACE " "
LIFETIME TYPE_BOUND_LIST
LIFETIME_IDENT "'b" TYPE_BOUND
LIFETIME
LIFETIME_IDENT "'b"
COMMA "," COMMA ","
WHITESPACE " " WHITESPACE " "
TYPE_PARAM TYPE_PARAM

View file

@ -96,6 +96,7 @@ SOURCE_FILE
LIFETIME LIFETIME
LIFETIME_IDENT "'a" LIFETIME_IDENT "'a"
COLON ":" COLON ":"
TYPE_BOUND_LIST
R_ANGLE ">" R_ANGLE ">"
SEMICOLON ";" SEMICOLON ";"
WHITESPACE "\n" WHITESPACE "\n"
@ -111,8 +112,10 @@ SOURCE_FILE
LIFETIME_IDENT "'a" LIFETIME_IDENT "'a"
COLON ":" COLON ":"
WHITESPACE " " WHITESPACE " "
LIFETIME TYPE_BOUND_LIST
LIFETIME_IDENT "'b" TYPE_BOUND
LIFETIME
LIFETIME_IDENT "'b"
R_ANGLE ">" R_ANGLE ">"
SEMICOLON ";" SEMICOLON ";"
WHITESPACE "\n" WHITESPACE "\n"
@ -128,10 +131,12 @@ SOURCE_FILE
LIFETIME_IDENT "'a" LIFETIME_IDENT "'a"
COLON ":" COLON ":"
WHITESPACE " " WHITESPACE " "
LIFETIME TYPE_BOUND_LIST
LIFETIME_IDENT "'b" TYPE_BOUND
WHITESPACE " " LIFETIME
PLUS "+" LIFETIME_IDENT "'b"
WHITESPACE " "
PLUS "+"
WHITESPACE " " WHITESPACE " "
R_ANGLE ">" R_ANGLE ">"
SEMICOLON ";" SEMICOLON ";"
@ -148,13 +153,16 @@ SOURCE_FILE
LIFETIME_IDENT "'a" LIFETIME_IDENT "'a"
COLON ":" COLON ":"
WHITESPACE " " WHITESPACE " "
LIFETIME TYPE_BOUND_LIST
LIFETIME_IDENT "'b" TYPE_BOUND
WHITESPACE " " LIFETIME
PLUS "+" LIFETIME_IDENT "'b"
WHITESPACE " " WHITESPACE " "
LIFETIME PLUS "+"
LIFETIME_IDENT "'c" WHITESPACE " "
TYPE_BOUND
LIFETIME
LIFETIME_IDENT "'c"
R_ANGLE ">" R_ANGLE ">"
SEMICOLON ";" SEMICOLON ";"
WHITESPACE "\n" WHITESPACE "\n"
@ -202,9 +210,11 @@ SOURCE_FILE
LIFETIME_IDENT "'a" LIFETIME_IDENT "'a"
COLON ":" COLON ":"
WHITESPACE " " WHITESPACE " "
LIFETIME TYPE_BOUND_LIST
LIFETIME_IDENT "'b" TYPE_BOUND
PLUS "+" LIFETIME
LIFETIME_IDENT "'b"
PLUS "+"
COMMA "," COMMA ","
WHITESPACE " " WHITESPACE " "
LIFETIME_PARAM LIFETIME_PARAM
@ -212,8 +222,10 @@ SOURCE_FILE
LIFETIME_IDENT "'b" LIFETIME_IDENT "'b"
COLON ":" COLON ":"
WHITESPACE " " WHITESPACE " "
LIFETIME TYPE_BOUND_LIST
LIFETIME_IDENT "'c" TYPE_BOUND
LIFETIME
LIFETIME_IDENT "'c"
COMMA "," COMMA ","
R_ANGLE ">" R_ANGLE ">"
SEMICOLON ";" SEMICOLON ";"

View file

@ -237,8 +237,10 @@ SOURCE_FILE
LIFETIME_IDENT "'a" LIFETIME_IDENT "'a"
COLON ":" COLON ":"
WHITESPACE " " WHITESPACE " "
LIFETIME TYPE_BOUND_LIST
LIFETIME_IDENT "'d" TYPE_BOUND
LIFETIME
LIFETIME_IDENT "'d"
COMMA "," COMMA ","
WHITESPACE " " WHITESPACE " "
LIFETIME_PARAM LIFETIME_PARAM
@ -246,13 +248,16 @@ SOURCE_FILE
LIFETIME_IDENT "'d" LIFETIME_IDENT "'d"
COLON ":" COLON ":"
WHITESPACE " " WHITESPACE " "
LIFETIME TYPE_BOUND_LIST
LIFETIME_IDENT "'a" TYPE_BOUND
WHITESPACE " " LIFETIME
PLUS "+" LIFETIME_IDENT "'a"
WHITESPACE " " WHITESPACE " "
LIFETIME PLUS "+"
LIFETIME_IDENT "'b" WHITESPACE " "
TYPE_BOUND
LIFETIME
LIFETIME_IDENT "'b"
COMMA "," COMMA ","
WHITESPACE " " WHITESPACE " "
TYPE_PARAM TYPE_PARAM