mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-29 13:25:09 +00:00
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:
parent
df7ab62a06
commit
f3d7415bd6
14 changed files with 125 additions and 85 deletions
|
@ -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)
|
||||||
);
|
);
|
||||||
|
|
|
@ -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>(
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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 }))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
"#]],
|
"#]],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 "("
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 ";"
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue