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 {
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)
);

View file

@ -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<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]
fn error_bound_chalk() {
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)> + '_ {
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
//! 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
"#]],
);
}

View file

@ -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<ast::Lifetime>,
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<hir::GenericDef> },
LabelRef,
LabelDef,
}

View file

@ -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<LifetimeContext> {
@ -571,21 +571,22 @@ fn classify_lifetime(
return None;
}
let lifetime =
find_node_at_offset::<ast::Lifetime>(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(

View file

@ -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,
}
}

View file

@ -80,6 +80,7 @@ define_symbols! {
self_ = "self",
Self_ = "Self",
tick_static = "'static",
tick_underscore = "'_",
dollar_crate = "$crate",
MISSING_NAME = "[missing name]",
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) {
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

View file

@ -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 "("

View file

@ -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

View file

@ -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 ";"

View file

@ -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