mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-27 05:23:24 +00:00
Implement builtin#format_args, using rustc's format_args parser
This commit is contained in:
parent
3431d586e5
commit
abe8f1ece4
19 changed files with 1740 additions and 14 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -541,6 +541,7 @@ dependencies = [
|
||||||
"mbe",
|
"mbe",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"profile",
|
"profile",
|
||||||
|
"ra-ap-rustc_lexer",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"stdx",
|
"stdx",
|
||||||
|
|
|
@ -33,6 +33,7 @@ triomphe.workspace = true
|
||||||
|
|
||||||
rustc_abi = { version = "0.0.20221221", package = "hkalbasi-rustc-ap-rustc_abi", default-features = false }
|
rustc_abi = { version = "0.0.20221221", package = "hkalbasi-rustc-ap-rustc_abi", default-features = false }
|
||||||
rustc_index = { version = "0.0.20221221", package = "hkalbasi-rustc-ap-rustc_index", default-features = false }
|
rustc_index = { version = "0.0.20221221", package = "hkalbasi-rustc-ap-rustc_index", default-features = false }
|
||||||
|
rustc_lexer = { version = "0.1.0", package = "ra-ap-rustc_lexer" }
|
||||||
|
|
||||||
# local deps
|
# local deps
|
||||||
stdx.workspace = true
|
stdx.workspace = true
|
||||||
|
|
|
@ -29,9 +29,13 @@ use crate::{
|
||||||
db::DefDatabase,
|
db::DefDatabase,
|
||||||
expander::Expander,
|
expander::Expander,
|
||||||
hir::{
|
hir::{
|
||||||
dummy_expr_id, Array, Binding, BindingAnnotation, BindingId, BindingProblems, CaptureBy,
|
dummy_expr_id,
|
||||||
ClosureKind, Expr, ExprId, InlineAsm, Label, LabelId, Literal, LiteralOrConst, MatchArm,
|
format_args::{
|
||||||
Movability, OffsetOf, Pat, PatId, RecordFieldPat, RecordLitField, Statement,
|
self, FormatArgs, FormatArgument, FormatArgumentKind, FormatArgumentsCollector,
|
||||||
|
},
|
||||||
|
Array, Binding, BindingAnnotation, BindingId, BindingProblems, CaptureBy, ClosureKind,
|
||||||
|
Expr, ExprId, InlineAsm, Label, LabelId, Literal, LiteralOrConst, MatchArm, Movability,
|
||||||
|
OffsetOf, Pat, PatId, RecordFieldPat, RecordLitField, Statement,
|
||||||
},
|
},
|
||||||
item_scope::BuiltinShadowMode,
|
item_scope::BuiltinShadowMode,
|
||||||
lang_item::LangItem,
|
lang_item::LangItem,
|
||||||
|
@ -649,15 +653,58 @@ impl ExprCollector<'_> {
|
||||||
}
|
}
|
||||||
ast::Expr::UnderscoreExpr(_) => self.alloc_expr(Expr::Underscore, syntax_ptr),
|
ast::Expr::UnderscoreExpr(_) => self.alloc_expr(Expr::Underscore, syntax_ptr),
|
||||||
ast::Expr::AsmExpr(e) => {
|
ast::Expr::AsmExpr(e) => {
|
||||||
let expr = Expr::InlineAsm(InlineAsm { e: self.collect_expr_opt(e.expr()) });
|
let e = self.collect_expr_opt(e.expr());
|
||||||
self.alloc_expr(expr, syntax_ptr)
|
self.alloc_expr(Expr::InlineAsm(InlineAsm { e }), syntax_ptr)
|
||||||
}
|
}
|
||||||
ast::Expr::OffsetOfExpr(e) => {
|
ast::Expr::OffsetOfExpr(e) => {
|
||||||
let container = Interned::new(TypeRef::from_ast_opt(&self.ctx(), e.ty()));
|
let container = Interned::new(TypeRef::from_ast_opt(&self.ctx(), e.ty()));
|
||||||
let fields = e.fields().map(|it| it.as_name()).collect();
|
let fields = e.fields().map(|it| it.as_name()).collect();
|
||||||
self.alloc_expr(Expr::OffsetOf(OffsetOf { container, fields }), syntax_ptr)
|
self.alloc_expr(Expr::OffsetOf(OffsetOf { container, fields }), syntax_ptr)
|
||||||
}
|
}
|
||||||
ast::Expr::FormatArgsExpr(_) => self.missing_expr(),
|
ast::Expr::FormatArgsExpr(f) => {
|
||||||
|
let mut args = FormatArgumentsCollector::new();
|
||||||
|
f.args().for_each(|arg| {
|
||||||
|
args.add(FormatArgument {
|
||||||
|
kind: match arg.name() {
|
||||||
|
Some(name) => FormatArgumentKind::Named(name.as_name()),
|
||||||
|
None => FormatArgumentKind::Normal,
|
||||||
|
},
|
||||||
|
expr: self.collect_expr_opt(arg.expr()),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
let template = f.template();
|
||||||
|
let fmt_snippet = template.as_ref().map(ToString::to_string);
|
||||||
|
let expr = self.collect_expr_opt(f.template());
|
||||||
|
if let Expr::Literal(Literal::String(_)) = self.body[expr] {
|
||||||
|
let source = self.source_map.expr_map_back[expr].clone();
|
||||||
|
let is_direct_literal = source.file_id == self.expander.current_file_id;
|
||||||
|
if let ast::Expr::Literal(l) =
|
||||||
|
source.value.to_node(&self.db.parse_or_expand(source.file_id))
|
||||||
|
{
|
||||||
|
if let ast::LiteralKind::String(s) = l.kind() {
|
||||||
|
return Some(self.alloc_expr(
|
||||||
|
Expr::FormatArgs(format_args::parse(
|
||||||
|
expr,
|
||||||
|
&s,
|
||||||
|
fmt_snippet,
|
||||||
|
args,
|
||||||
|
is_direct_literal,
|
||||||
|
)),
|
||||||
|
syntax_ptr,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.alloc_expr(
|
||||||
|
Expr::FormatArgs(FormatArgs {
|
||||||
|
template_expr: expr,
|
||||||
|
template: Default::default(),
|
||||||
|
arguments: args.finish(),
|
||||||
|
}),
|
||||||
|
syntax_ptr,
|
||||||
|
)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -156,6 +156,11 @@ impl Printer<'_> {
|
||||||
Expr::Missing => w!(self, "<EFBFBD>"),
|
Expr::Missing => w!(self, "<EFBFBD>"),
|
||||||
Expr::Underscore => w!(self, "_"),
|
Expr::Underscore => w!(self, "_"),
|
||||||
Expr::InlineAsm(_) => w!(self, "builtin#asm(_)"),
|
Expr::InlineAsm(_) => w!(self, "builtin#asm(_)"),
|
||||||
|
Expr::FormatArgs(_fmt_args) => {
|
||||||
|
w!(self, "builtin#format_args(");
|
||||||
|
// FIXME
|
||||||
|
w!(self, ")");
|
||||||
|
}
|
||||||
Expr::OffsetOf(offset_of) => {
|
Expr::OffsetOf(offset_of) => {
|
||||||
w!(self, "builtin#offset_of(");
|
w!(self, "builtin#offset_of(");
|
||||||
self.print_type_ref(&offset_of.container);
|
self.print_type_ref(&offset_of.container);
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
//! See also a neighboring `body` module.
|
//! See also a neighboring `body` module.
|
||||||
|
|
||||||
pub mod type_ref;
|
pub mod type_ref;
|
||||||
|
pub mod format_args;
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
@ -24,6 +25,7 @@ use syntax::ast;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
builtin_type::{BuiltinFloat, BuiltinInt, BuiltinUint},
|
builtin_type::{BuiltinFloat, BuiltinInt, BuiltinUint},
|
||||||
|
hir::format_args::{FormatArgs, FormatArgumentKind},
|
||||||
path::{GenericArgs, Path},
|
path::{GenericArgs, Path},
|
||||||
type_ref::{Mutability, Rawness, TypeRef},
|
type_ref::{Mutability, Rawness, TypeRef},
|
||||||
BlockId, ConstBlockId,
|
BlockId, ConstBlockId,
|
||||||
|
@ -117,7 +119,6 @@ impl From<ast::LiteralKind> for Literal {
|
||||||
fn from(ast_lit_kind: ast::LiteralKind) -> Self {
|
fn from(ast_lit_kind: ast::LiteralKind) -> Self {
|
||||||
use ast::LiteralKind;
|
use ast::LiteralKind;
|
||||||
match ast_lit_kind {
|
match ast_lit_kind {
|
||||||
// FIXME: these should have actual values filled in, but unsure on perf impact
|
|
||||||
LiteralKind::IntNumber(lit) => {
|
LiteralKind::IntNumber(lit) => {
|
||||||
if let builtin @ Some(_) = lit.suffix().and_then(BuiltinFloat::from_suffix) {
|
if let builtin @ Some(_) = lit.suffix().and_then(BuiltinFloat::from_suffix) {
|
||||||
Literal::Float(
|
Literal::Float(
|
||||||
|
@ -283,6 +284,7 @@ pub enum Expr {
|
||||||
Underscore,
|
Underscore,
|
||||||
OffsetOf(OffsetOf),
|
OffsetOf(OffsetOf),
|
||||||
InlineAsm(InlineAsm),
|
InlineAsm(InlineAsm),
|
||||||
|
FormatArgs(FormatArgs),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
@ -355,7 +357,15 @@ impl Expr {
|
||||||
match self {
|
match self {
|
||||||
Expr::Missing => {}
|
Expr::Missing => {}
|
||||||
Expr::Path(_) | Expr::OffsetOf(_) => {}
|
Expr::Path(_) | Expr::OffsetOf(_) => {}
|
||||||
Expr::InlineAsm(e) => f(e.e),
|
Expr::InlineAsm(it) => f(it.e),
|
||||||
|
Expr::FormatArgs(it) => {
|
||||||
|
f(it.template_expr);
|
||||||
|
it.arguments
|
||||||
|
.arguments
|
||||||
|
.iter()
|
||||||
|
.filter(|it| !matches!(it.kind, FormatArgumentKind::Captured(_)))
|
||||||
|
.for_each(|it| f(it.expr));
|
||||||
|
}
|
||||||
Expr::If { condition, then_branch, else_branch } => {
|
Expr::If { condition, then_branch, else_branch } => {
|
||||||
f(*condition);
|
f(*condition);
|
||||||
f(*then_branch);
|
f(*then_branch);
|
||||||
|
|
511
crates/hir-def/src/hir/format_args.rs
Normal file
511
crates/hir-def/src/hir/format_args.rs
Normal file
|
@ -0,0 +1,511 @@
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
use hir_expand::name::Name;
|
||||||
|
use syntax::{
|
||||||
|
ast::{self, IsString},
|
||||||
|
AstToken, SmolStr, TextRange,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::hir::{dummy_expr_id, ExprId};
|
||||||
|
|
||||||
|
mod parse;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct FormatArgs {
|
||||||
|
pub template_expr: ExprId,
|
||||||
|
pub template: Box<[FormatArgsPiece]>,
|
||||||
|
pub arguments: FormatArguments,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct FormatArguments {
|
||||||
|
pub arguments: Box<[FormatArgument]>,
|
||||||
|
pub num_unnamed_args: usize,
|
||||||
|
pub num_explicit_args: usize,
|
||||||
|
pub names: Box<[(Name, usize)]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum FormatArgsPiece {
|
||||||
|
Literal(Box<str>),
|
||||||
|
Placeholder(FormatPlaceholder),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct FormatPlaceholder {
|
||||||
|
/// Index into [`FormatArgs::arguments`].
|
||||||
|
pub argument: FormatArgPosition,
|
||||||
|
/// The span inside the format string for the full `{…}` placeholder.
|
||||||
|
pub span: Option<TextRange>,
|
||||||
|
/// `{}`, `{:?}`, or `{:x}`, etc.
|
||||||
|
pub format_trait: FormatTrait,
|
||||||
|
/// `{}` or `{:.5}` or `{:-^20}`, etc.
|
||||||
|
pub format_options: FormatOptions,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct FormatArgPosition {
|
||||||
|
/// Which argument this position refers to (Ok),
|
||||||
|
/// or would've referred to if it existed (Err).
|
||||||
|
pub index: Result<usize, usize>,
|
||||||
|
/// What kind of position this is. See [`FormatArgPositionKind`].
|
||||||
|
pub kind: FormatArgPositionKind,
|
||||||
|
/// The span of the name or number.
|
||||||
|
pub span: Option<TextRange>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum FormatArgPositionKind {
|
||||||
|
/// `{}` or `{:.*}`
|
||||||
|
Implicit,
|
||||||
|
/// `{1}` or `{:1$}` or `{:.1$}`
|
||||||
|
Number,
|
||||||
|
/// `{a}` or `{:a$}` or `{:.a$}`
|
||||||
|
Named,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum FormatTrait {
|
||||||
|
/// `{}`
|
||||||
|
Display,
|
||||||
|
/// `{:?}`
|
||||||
|
Debug,
|
||||||
|
/// `{:e}`
|
||||||
|
LowerExp,
|
||||||
|
/// `{:E}`
|
||||||
|
UpperExp,
|
||||||
|
/// `{:o}`
|
||||||
|
Octal,
|
||||||
|
/// `{:p}`
|
||||||
|
Pointer,
|
||||||
|
/// `{:b}`
|
||||||
|
Binary,
|
||||||
|
/// `{:x}`
|
||||||
|
LowerHex,
|
||||||
|
/// `{:X}`
|
||||||
|
UpperHex,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default, Debug, PartialEq, Eq)]
|
||||||
|
pub struct FormatOptions {
|
||||||
|
/// The width. E.g. `{:5}` or `{:width$}`.
|
||||||
|
pub width: Option<FormatCount>,
|
||||||
|
/// The precision. E.g. `{:.5}` or `{:.precision$}`.
|
||||||
|
pub precision: Option<FormatCount>,
|
||||||
|
/// The alignment. E.g. `{:>}` or `{:<}` or `{:^}`.
|
||||||
|
pub alignment: Option<FormatAlignment>,
|
||||||
|
/// The fill character. E.g. the `.` in `{:.>10}`.
|
||||||
|
pub fill: Option<char>,
|
||||||
|
/// The `+` or `-` flag.
|
||||||
|
pub sign: Option<FormatSign>,
|
||||||
|
/// The `#` flag.
|
||||||
|
pub alternate: bool,
|
||||||
|
/// The `0` flag. E.g. the `0` in `{:02x}`.
|
||||||
|
pub zero_pad: bool,
|
||||||
|
/// The `x` or `X` flag (for `Debug` only). E.g. the `x` in `{:x?}`.
|
||||||
|
pub debug_hex: Option<FormatDebugHex>,
|
||||||
|
}
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum FormatSign {
|
||||||
|
/// The `+` flag.
|
||||||
|
Plus,
|
||||||
|
/// The `-` flag.
|
||||||
|
Minus,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum FormatDebugHex {
|
||||||
|
/// The `x` flag in `{:x?}`.
|
||||||
|
Lower,
|
||||||
|
/// The `X` flag in `{:X?}`.
|
||||||
|
Upper,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum FormatAlignment {
|
||||||
|
/// `{:<}`
|
||||||
|
Left,
|
||||||
|
/// `{:>}`
|
||||||
|
Right,
|
||||||
|
/// `{:^}`
|
||||||
|
Center,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum FormatCount {
|
||||||
|
/// `{:5}` or `{:.5}`
|
||||||
|
Literal(usize),
|
||||||
|
/// `{:.*}`, `{:.5$}`, or `{:a$}`, etc.
|
||||||
|
Argument(FormatArgPosition),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct FormatArgument {
|
||||||
|
pub kind: FormatArgumentKind,
|
||||||
|
pub expr: ExprId,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub enum FormatArgumentKind {
|
||||||
|
/// `format_args(…, arg)`
|
||||||
|
Normal,
|
||||||
|
/// `format_args(…, arg = 1)`
|
||||||
|
Named(Name),
|
||||||
|
/// `format_args("… {arg} …")`
|
||||||
|
Captured(Name),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only used in parse_args and report_invalid_references,
|
||||||
|
// to indicate how a referred argument was used.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
enum PositionUsedAs {
|
||||||
|
Placeholder(Option<TextRange>),
|
||||||
|
Precision,
|
||||||
|
Width,
|
||||||
|
}
|
||||||
|
use PositionUsedAs::*;
|
||||||
|
|
||||||
|
pub(crate) fn parse(
|
||||||
|
expr: ExprId,
|
||||||
|
s: &ast::String,
|
||||||
|
fmt_snippet: Option<String>,
|
||||||
|
mut args: FormatArgumentsCollector,
|
||||||
|
is_direct_literal: bool,
|
||||||
|
) -> FormatArgs {
|
||||||
|
let text = s.text();
|
||||||
|
let str_style = match s.quote_offsets() {
|
||||||
|
Some(offsets) => {
|
||||||
|
let raw = u32::from(offsets.quotes.0.len()) - 1;
|
||||||
|
(raw != 0).then_some(raw as usize)
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
let mut parser =
|
||||||
|
parse::Parser::new(text, str_style, fmt_snippet, false, parse::ParseMode::Format);
|
||||||
|
|
||||||
|
let mut pieces = Vec::new();
|
||||||
|
while let Some(piece) = parser.next() {
|
||||||
|
if !parser.errors.is_empty() {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
pieces.push(piece);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let is_source_literal = parser.is_source_literal;
|
||||||
|
if !parser.errors.is_empty() {
|
||||||
|
// FIXME: Diagnose
|
||||||
|
return FormatArgs {
|
||||||
|
template_expr: expr,
|
||||||
|
template: Default::default(),
|
||||||
|
arguments: args.finish(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let to_span = |inner_span: parse::InnerSpan| {
|
||||||
|
is_source_literal.then(|| {
|
||||||
|
TextRange::new(inner_span.start.try_into().unwrap(), inner_span.end.try_into().unwrap())
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut used = vec![false; args.explicit_args().len()];
|
||||||
|
let mut invalid_refs = Vec::new();
|
||||||
|
let mut numeric_refences_to_named_arg = Vec::new();
|
||||||
|
|
||||||
|
enum ArgRef<'a> {
|
||||||
|
Index(usize),
|
||||||
|
Name(&'a str, Option<TextRange>),
|
||||||
|
}
|
||||||
|
let mut lookup_arg = |arg: ArgRef<'_>,
|
||||||
|
span: Option<TextRange>,
|
||||||
|
used_as: PositionUsedAs,
|
||||||
|
kind: FormatArgPositionKind|
|
||||||
|
-> FormatArgPosition {
|
||||||
|
let index = match arg {
|
||||||
|
ArgRef::Index(index) => {
|
||||||
|
if let Some(arg) = args.by_index(index) {
|
||||||
|
used[index] = true;
|
||||||
|
if arg.kind.ident().is_some() {
|
||||||
|
// This was a named argument, but it was used as a positional argument.
|
||||||
|
numeric_refences_to_named_arg.push((index, span, used_as));
|
||||||
|
}
|
||||||
|
Ok(index)
|
||||||
|
} else {
|
||||||
|
// Doesn't exist as an explicit argument.
|
||||||
|
invalid_refs.push((index, span, used_as, kind));
|
||||||
|
Err(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ArgRef::Name(name, _span) => {
|
||||||
|
let name = Name::new_text_dont_use(SmolStr::new(name));
|
||||||
|
if let Some((index, _)) = args.by_name(&name) {
|
||||||
|
// Name found in `args`, so we resolve it to its index.
|
||||||
|
if index < args.explicit_args().len() {
|
||||||
|
// Mark it as used, if it was an explicit argument.
|
||||||
|
used[index] = true;
|
||||||
|
}
|
||||||
|
Ok(index)
|
||||||
|
} else {
|
||||||
|
// Name not found in `args`, so we add it as an implicitly captured argument.
|
||||||
|
if !is_direct_literal {
|
||||||
|
// For the moment capturing variables from format strings expanded from macros is
|
||||||
|
// disabled (see RFC #2795)
|
||||||
|
// FIXME: Diagnose
|
||||||
|
}
|
||||||
|
Ok(args.add(FormatArgument {
|
||||||
|
kind: FormatArgumentKind::Captured(name),
|
||||||
|
// FIXME: This is problematic, we might want to synthesize a dummy
|
||||||
|
// expression proper and/or desugar these.
|
||||||
|
expr: dummy_expr_id(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
FormatArgPosition { index, kind, span }
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut template = Vec::new();
|
||||||
|
let mut unfinished_literal = String::new();
|
||||||
|
let mut placeholder_index = 0;
|
||||||
|
|
||||||
|
for piece in pieces {
|
||||||
|
match piece {
|
||||||
|
parse::Piece::String(s) => {
|
||||||
|
unfinished_literal.push_str(s);
|
||||||
|
}
|
||||||
|
parse::Piece::NextArgument(arg) => {
|
||||||
|
let parse::Argument { position, position_span, format } = *arg;
|
||||||
|
if !unfinished_literal.is_empty() {
|
||||||
|
template.push(FormatArgsPiece::Literal(
|
||||||
|
mem::take(&mut unfinished_literal).into_boxed_str(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let span = parser.arg_places.get(placeholder_index).and_then(|&s| to_span(s));
|
||||||
|
placeholder_index += 1;
|
||||||
|
|
||||||
|
let position_span = to_span(position_span);
|
||||||
|
let argument = match position {
|
||||||
|
parse::ArgumentImplicitlyIs(i) => lookup_arg(
|
||||||
|
ArgRef::Index(i),
|
||||||
|
position_span,
|
||||||
|
Placeholder(span),
|
||||||
|
FormatArgPositionKind::Implicit,
|
||||||
|
),
|
||||||
|
parse::ArgumentIs(i) => lookup_arg(
|
||||||
|
ArgRef::Index(i),
|
||||||
|
position_span,
|
||||||
|
Placeholder(span),
|
||||||
|
FormatArgPositionKind::Number,
|
||||||
|
),
|
||||||
|
parse::ArgumentNamed(name) => lookup_arg(
|
||||||
|
ArgRef::Name(name, position_span),
|
||||||
|
position_span,
|
||||||
|
Placeholder(span),
|
||||||
|
FormatArgPositionKind::Named,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
let alignment = match format.align {
|
||||||
|
parse::AlignUnknown => None,
|
||||||
|
parse::AlignLeft => Some(FormatAlignment::Left),
|
||||||
|
parse::AlignRight => Some(FormatAlignment::Right),
|
||||||
|
parse::AlignCenter => Some(FormatAlignment::Center),
|
||||||
|
};
|
||||||
|
|
||||||
|
let format_trait = match format.ty {
|
||||||
|
"" => FormatTrait::Display,
|
||||||
|
"?" => FormatTrait::Debug,
|
||||||
|
"e" => FormatTrait::LowerExp,
|
||||||
|
"E" => FormatTrait::UpperExp,
|
||||||
|
"o" => FormatTrait::Octal,
|
||||||
|
"p" => FormatTrait::Pointer,
|
||||||
|
"b" => FormatTrait::Binary,
|
||||||
|
"x" => FormatTrait::LowerHex,
|
||||||
|
"X" => FormatTrait::UpperHex,
|
||||||
|
_ => {
|
||||||
|
// FIXME: Diagnose
|
||||||
|
FormatTrait::Display
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let precision_span = format.precision_span.and_then(to_span);
|
||||||
|
let precision = match format.precision {
|
||||||
|
parse::CountIs(n) => Some(FormatCount::Literal(n)),
|
||||||
|
parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg(
|
||||||
|
ArgRef::Name(name, to_span(name_span)),
|
||||||
|
precision_span,
|
||||||
|
Precision,
|
||||||
|
FormatArgPositionKind::Named,
|
||||||
|
))),
|
||||||
|
parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg(
|
||||||
|
ArgRef::Index(i),
|
||||||
|
precision_span,
|
||||||
|
Precision,
|
||||||
|
FormatArgPositionKind::Number,
|
||||||
|
))),
|
||||||
|
parse::CountIsStar(i) => Some(FormatCount::Argument(lookup_arg(
|
||||||
|
ArgRef::Index(i),
|
||||||
|
precision_span,
|
||||||
|
Precision,
|
||||||
|
FormatArgPositionKind::Implicit,
|
||||||
|
))),
|
||||||
|
parse::CountImplied => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let width_span = format.width_span.and_then(to_span);
|
||||||
|
let width = match format.width {
|
||||||
|
parse::CountIs(n) => Some(FormatCount::Literal(n)),
|
||||||
|
parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg(
|
||||||
|
ArgRef::Name(name, to_span(name_span)),
|
||||||
|
width_span,
|
||||||
|
Width,
|
||||||
|
FormatArgPositionKind::Named,
|
||||||
|
))),
|
||||||
|
parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg(
|
||||||
|
ArgRef::Index(i),
|
||||||
|
width_span,
|
||||||
|
Width,
|
||||||
|
FormatArgPositionKind::Number,
|
||||||
|
))),
|
||||||
|
parse::CountIsStar(_) => unreachable!(),
|
||||||
|
parse::CountImplied => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
template.push(FormatArgsPiece::Placeholder(FormatPlaceholder {
|
||||||
|
argument,
|
||||||
|
span,
|
||||||
|
format_trait,
|
||||||
|
format_options: FormatOptions {
|
||||||
|
fill: format.fill,
|
||||||
|
alignment,
|
||||||
|
sign: format.sign.map(|s| match s {
|
||||||
|
parse::Sign::Plus => FormatSign::Plus,
|
||||||
|
parse::Sign::Minus => FormatSign::Minus,
|
||||||
|
}),
|
||||||
|
alternate: format.alternate,
|
||||||
|
zero_pad: format.zero_pad,
|
||||||
|
debug_hex: format.debug_hex.map(|s| match s {
|
||||||
|
parse::DebugHex::Lower => FormatDebugHex::Lower,
|
||||||
|
parse::DebugHex::Upper => FormatDebugHex::Upper,
|
||||||
|
}),
|
||||||
|
precision,
|
||||||
|
width,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !unfinished_literal.is_empty() {
|
||||||
|
template.push(FormatArgsPiece::Literal(unfinished_literal.into_boxed_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !invalid_refs.is_empty() {
|
||||||
|
// FIXME: Diagnose
|
||||||
|
}
|
||||||
|
|
||||||
|
let unused = used
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|&(_, used)| !used)
|
||||||
|
.map(|(i, _)| {
|
||||||
|
let named = matches!(args.explicit_args()[i].kind, FormatArgumentKind::Named(_));
|
||||||
|
(args.explicit_args()[i].expr, named)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if !unused.is_empty() {
|
||||||
|
// FIXME: Diagnose
|
||||||
|
}
|
||||||
|
|
||||||
|
FormatArgs {
|
||||||
|
template_expr: expr,
|
||||||
|
template: template.into_boxed_slice(),
|
||||||
|
arguments: args.finish(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct FormatArgumentsCollector {
|
||||||
|
arguments: Vec<FormatArgument>,
|
||||||
|
num_unnamed_args: usize,
|
||||||
|
num_explicit_args: usize,
|
||||||
|
names: Vec<(Name, usize)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FormatArgumentsCollector {
|
||||||
|
pub(crate) fn finish(self) -> FormatArguments {
|
||||||
|
FormatArguments {
|
||||||
|
arguments: self.arguments.into_boxed_slice(),
|
||||||
|
num_unnamed_args: self.num_unnamed_args,
|
||||||
|
num_explicit_args: self.num_explicit_args,
|
||||||
|
names: self.names.into_boxed_slice(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { arguments: vec![], names: vec![], num_unnamed_args: 0, num_explicit_args: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(&mut self, arg: FormatArgument) -> usize {
|
||||||
|
let index = self.arguments.len();
|
||||||
|
if let Some(name) = arg.kind.ident() {
|
||||||
|
self.names.push((name.clone(), index));
|
||||||
|
} else if self.names.is_empty() {
|
||||||
|
// Only count the unnamed args before the first named arg.
|
||||||
|
// (Any later ones are errors.)
|
||||||
|
self.num_unnamed_args += 1;
|
||||||
|
}
|
||||||
|
if !matches!(arg.kind, FormatArgumentKind::Captured(..)) {
|
||||||
|
// This is an explicit argument.
|
||||||
|
// Make sure that all arguments so far are explicit.
|
||||||
|
assert_eq!(
|
||||||
|
self.num_explicit_args,
|
||||||
|
self.arguments.len(),
|
||||||
|
"captured arguments must be added last"
|
||||||
|
);
|
||||||
|
self.num_explicit_args += 1;
|
||||||
|
}
|
||||||
|
self.arguments.push(arg);
|
||||||
|
index
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn by_name(&self, name: &Name) -> Option<(usize, &FormatArgument)> {
|
||||||
|
let &(_, i) = self.names.iter().find(|(n, _)| n == name)?;
|
||||||
|
Some((i, &self.arguments[i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn by_index(&self, i: usize) -> Option<&FormatArgument> {
|
||||||
|
(i < self.num_explicit_args).then(|| &self.arguments[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unnamed_args(&self) -> &[FormatArgument] {
|
||||||
|
&self.arguments[..self.num_unnamed_args]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn named_args(&self) -> &[FormatArgument] {
|
||||||
|
&self.arguments[self.num_unnamed_args..self.num_explicit_args]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn explicit_args(&self) -> &[FormatArgument] {
|
||||||
|
&self.arguments[..self.num_explicit_args]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn all_args(&self) -> &[FormatArgument] {
|
||||||
|
&self.arguments[..]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn all_args_mut(&mut self) -> &mut Vec<FormatArgument> {
|
||||||
|
&mut self.arguments
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FormatArgumentKind {
|
||||||
|
pub fn ident(&self) -> Option<&Name> {
|
||||||
|
match self {
|
||||||
|
Self::Normal => None,
|
||||||
|
Self::Named(id) => Some(id),
|
||||||
|
Self::Captured(id) => Some(id),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1023
crates/hir-def/src/hir/format_args/parse.rs
Normal file
1023
crates/hir-def/src/hir/format_args/parse.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -54,6 +54,12 @@ impl Name {
|
||||||
Name(Repr::Text(text))
|
Name(Repr::Text(text))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: See above, unfortunately some places really need this right now
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub const fn new_text_dont_use(text: SmolStr) -> Name {
|
||||||
|
Name(Repr::Text(text))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new_tuple_field(idx: usize) -> Name {
|
pub fn new_tuple_field(idx: usize) -> Name {
|
||||||
Name(Repr::TupleField(idx))
|
Name(Repr::TupleField(idx))
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,10 @@ use chalk_ir::{
|
||||||
};
|
};
|
||||||
use hir_def::{
|
use hir_def::{
|
||||||
data::adt::VariantData,
|
data::adt::VariantData,
|
||||||
hir::{Array, BinaryOp, BindingId, CaptureBy, Expr, ExprId, Pat, PatId, Statement, UnaryOp},
|
hir::{
|
||||||
|
format_args::FormatArgumentKind, Array, BinaryOp, BindingId, CaptureBy, Expr, ExprId, Pat,
|
||||||
|
PatId, Statement, UnaryOp,
|
||||||
|
},
|
||||||
lang_item::LangItem,
|
lang_item::LangItem,
|
||||||
resolver::{resolver_for_expr, ResolveValueResult, ValueNs},
|
resolver::{resolver_for_expr, ResolveValueResult, ValueNs},
|
||||||
DefWithBodyId, FieldId, HasModule, VariantId,
|
DefWithBodyId, FieldId, HasModule, VariantId,
|
||||||
|
@ -453,6 +456,14 @@ impl InferenceContext<'_> {
|
||||||
fn walk_expr_without_adjust(&mut self, tgt_expr: ExprId) {
|
fn walk_expr_without_adjust(&mut self, tgt_expr: ExprId) {
|
||||||
match &self.body[tgt_expr] {
|
match &self.body[tgt_expr] {
|
||||||
Expr::OffsetOf(_) => (),
|
Expr::OffsetOf(_) => (),
|
||||||
|
Expr::FormatArgs(fa) => {
|
||||||
|
self.walk_expr_without_adjust(fa.template_expr);
|
||||||
|
fa.arguments
|
||||||
|
.arguments
|
||||||
|
.iter()
|
||||||
|
.filter(|it| !matches!(it.kind, FormatArgumentKind::Captured(_)))
|
||||||
|
.for_each(|it| self.walk_expr_without_adjust(it.expr));
|
||||||
|
}
|
||||||
Expr::InlineAsm(e) => self.walk_expr_without_adjust(e.e),
|
Expr::InlineAsm(e) => self.walk_expr_without_adjust(e.e),
|
||||||
Expr::If { condition, then_branch, else_branch } => {
|
Expr::If { condition, then_branch, else_branch } => {
|
||||||
self.consume_expr(*condition);
|
self.consume_expr(*condition);
|
||||||
|
|
|
@ -9,7 +9,8 @@ use chalk_ir::{cast::Cast, fold::Shift, DebruijnIndex, Mutability, TyVariableKin
|
||||||
use hir_def::{
|
use hir_def::{
|
||||||
generics::TypeOrConstParamData,
|
generics::TypeOrConstParamData,
|
||||||
hir::{
|
hir::{
|
||||||
ArithOp, Array, BinaryOp, ClosureKind, Expr, ExprId, LabelId, Literal, Statement, UnaryOp,
|
format_args::FormatArgumentKind, ArithOp, Array, BinaryOp, ClosureKind, Expr, ExprId,
|
||||||
|
LabelId, Literal, Statement, UnaryOp,
|
||||||
},
|
},
|
||||||
lang_item::{LangItem, LangItemTarget},
|
lang_item::{LangItem, LangItemTarget},
|
||||||
path::{GenericArg, GenericArgs},
|
path::{GenericArg, GenericArgs},
|
||||||
|
@ -848,6 +849,25 @@ impl InferenceContext<'_> {
|
||||||
self.infer_expr_no_expect(it.e);
|
self.infer_expr_no_expect(it.e);
|
||||||
self.result.standard_types.unit.clone()
|
self.result.standard_types.unit.clone()
|
||||||
}
|
}
|
||||||
|
Expr::FormatArgs(fa) => {
|
||||||
|
fa.arguments
|
||||||
|
.arguments
|
||||||
|
.iter()
|
||||||
|
.filter(|it| !matches!(it.kind, FormatArgumentKind::Captured(_)))
|
||||||
|
.for_each(|it| _ = self.infer_expr_no_expect(it.expr));
|
||||||
|
|
||||||
|
match self
|
||||||
|
.resolve_lang_item(LangItem::FormatArguments)
|
||||||
|
.and_then(|it| it.as_struct())
|
||||||
|
{
|
||||||
|
Some(s) => {
|
||||||
|
// NOTE: This struct has a lifetime parameter, but we don't currently emit
|
||||||
|
// those to chalk
|
||||||
|
TyKind::Adt(AdtId(s.into()), Substitution::empty(Interner)).intern(Interner)
|
||||||
|
}
|
||||||
|
None => self.err_ty(),
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
// use a new type variable if we got unknown here
|
// use a new type variable if we got unknown here
|
||||||
let ty = self.insert_type_vars_shallow(ty);
|
let ty = self.insert_type_vars_shallow(ty);
|
||||||
|
|
|
@ -3,7 +3,10 @@
|
||||||
|
|
||||||
use chalk_ir::Mutability;
|
use chalk_ir::Mutability;
|
||||||
use hir_def::{
|
use hir_def::{
|
||||||
hir::{Array, BinaryOp, BindingAnnotation, Expr, ExprId, PatId, Statement, UnaryOp},
|
hir::{
|
||||||
|
format_args::FormatArgumentKind, Array, BinaryOp, BindingAnnotation, Expr, ExprId, PatId,
|
||||||
|
Statement, UnaryOp,
|
||||||
|
},
|
||||||
lang_item::LangItem,
|
lang_item::LangItem,
|
||||||
};
|
};
|
||||||
use hir_expand::name;
|
use hir_expand::name;
|
||||||
|
@ -37,6 +40,13 @@ impl InferenceContext<'_> {
|
||||||
Expr::Missing => (),
|
Expr::Missing => (),
|
||||||
Expr::InlineAsm(e) => self.infer_mut_expr_without_adjust(e.e, Mutability::Not),
|
Expr::InlineAsm(e) => self.infer_mut_expr_without_adjust(e.e, Mutability::Not),
|
||||||
Expr::OffsetOf(_) => (),
|
Expr::OffsetOf(_) => (),
|
||||||
|
Expr::FormatArgs(fa) => {
|
||||||
|
fa.arguments
|
||||||
|
.arguments
|
||||||
|
.iter()
|
||||||
|
.filter(|it| !matches!(it.kind, FormatArgumentKind::Captured(_)))
|
||||||
|
.for_each(|arg| self.infer_mut_expr_without_adjust(arg.expr, Mutability::Not));
|
||||||
|
}
|
||||||
&Expr::If { condition, then_branch, else_branch } => {
|
&Expr::If { condition, then_branch, else_branch } => {
|
||||||
self.infer_mut_expr(condition, Mutability::Not);
|
self.infer_mut_expr(condition, Mutability::Not);
|
||||||
self.infer_mut_expr(then_branch, Mutability::Not);
|
self.infer_mut_expr(then_branch, Mutability::Not);
|
||||||
|
|
|
@ -376,6 +376,9 @@ impl<'ctx> MirLowerCtx<'ctx> {
|
||||||
Expr::InlineAsm(_) => {
|
Expr::InlineAsm(_) => {
|
||||||
not_supported!("builtin#asm")
|
not_supported!("builtin#asm")
|
||||||
}
|
}
|
||||||
|
Expr::FormatArgs(_) => {
|
||||||
|
not_supported!("builtin#format_args")
|
||||||
|
}
|
||||||
Expr::Missing => {
|
Expr::Missing => {
|
||||||
if let DefWithBodyId::FunctionId(f) = self.owner {
|
if let DefWithBodyId::FunctionId(f) = self.owner {
|
||||||
let assoc = f.lookup(self.db.upcast());
|
let assoc = f.lookup(self.db.upcast());
|
||||||
|
|
|
@ -3612,3 +3612,25 @@ fn main() {
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn builtin_format_args() {
|
||||||
|
check_infer(
|
||||||
|
r#"
|
||||||
|
#[lang = "format_arguments"]
|
||||||
|
pub struct Arguments<'a>;
|
||||||
|
fn main() {
|
||||||
|
let are = "are";
|
||||||
|
builtin#format_args("hello {} friends, we {are} {0}{last}", "fancy", last = "!");
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
65..175 '{ ...!"); }': ()
|
||||||
|
75..78 'are': &str
|
||||||
|
81..86 '"are"': &str
|
||||||
|
92..172 'builti...= "!")': Arguments<'_>
|
||||||
|
152..159 '"fancy"': &str
|
||||||
|
168..171 '"!"': &str
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -219,7 +219,7 @@ fn tuple_expr(p: &mut Parser<'_>) -> CompletedMarker {
|
||||||
// test builtin_expr
|
// test builtin_expr
|
||||||
// fn foo() {
|
// fn foo() {
|
||||||
// builtin#asm(0);
|
// builtin#asm(0);
|
||||||
// builtin#format_args(0);
|
// builtin#format_args("", 0, 1, a = 2 + 3, a + b);
|
||||||
// builtin#offset_of(Foo, bar.baz.0);
|
// builtin#offset_of(Foo, bar.baz.0);
|
||||||
// }
|
// }
|
||||||
fn builtin_expr(p: &mut Parser<'_>) -> Option<CompletedMarker> {
|
fn builtin_expr(p: &mut Parser<'_>) -> Option<CompletedMarker> {
|
||||||
|
@ -249,6 +249,24 @@ fn builtin_expr(p: &mut Parser<'_>) -> Option<CompletedMarker> {
|
||||||
p.bump_remap(T![format_args]);
|
p.bump_remap(T![format_args]);
|
||||||
p.expect(T!['(']);
|
p.expect(T!['(']);
|
||||||
expr(p);
|
expr(p);
|
||||||
|
if p.eat(T![,]) {
|
||||||
|
while !p.at(EOF) && !p.at(T![')']) {
|
||||||
|
let m = p.start();
|
||||||
|
if p.at(IDENT) && p.nth_at(1, T![=]) {
|
||||||
|
name(p);
|
||||||
|
p.bump(T![=]);
|
||||||
|
}
|
||||||
|
if expr(p).is_none() {
|
||||||
|
m.abandon(p);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
m.complete(p, FORMAT_ARGS_ARG);
|
||||||
|
|
||||||
|
if !p.at(T![')']) {
|
||||||
|
p.expect(T![,]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
p.expect(T![')']);
|
p.expect(T![')']);
|
||||||
Some(m.complete(p, FORMAT_ARGS_EXPR))
|
Some(m.complete(p, FORMAT_ARGS_EXPR))
|
||||||
} else if p.at_contextual_kw(T![asm]) {
|
} else if p.at_contextual_kw(T![asm]) {
|
||||||
|
|
|
@ -210,6 +210,7 @@ pub enum SyntaxKind {
|
||||||
OFFSET_OF_EXPR,
|
OFFSET_OF_EXPR,
|
||||||
ASM_EXPR,
|
ASM_EXPR,
|
||||||
FORMAT_ARGS_EXPR,
|
FORMAT_ARGS_EXPR,
|
||||||
|
FORMAT_ARGS_ARG,
|
||||||
CALL_EXPR,
|
CALL_EXPR,
|
||||||
INDEX_EXPR,
|
INDEX_EXPR,
|
||||||
METHOD_CALL_EXPR,
|
METHOD_CALL_EXPR,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
fn foo() {
|
fn foo() {
|
||||||
builtin#asm(0);
|
builtin#asm(0);
|
||||||
builtin#format_args(0);
|
builtin#format_args("", 0, 1, a = 2 + 3, a + b);
|
||||||
builtin#offset_of(Foo, bar.baz.0);
|
builtin#offset_of(Foo, bar.baz.0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -382,7 +382,13 @@ AsmExpr =
|
||||||
Attr* 'builtin' '#' 'asm' '(' Expr ')'
|
Attr* 'builtin' '#' 'asm' '(' Expr ')'
|
||||||
|
|
||||||
FormatArgsExpr =
|
FormatArgsExpr =
|
||||||
Attr* 'builtin' '#' 'format_args' '(' ')'
|
Attr* 'builtin' '#' 'format_args' '('
|
||||||
|
template:Expr
|
||||||
|
(',' args:(FormatArgsArg (',' FormatArgsArg)* ','?)? )?
|
||||||
|
')'
|
||||||
|
|
||||||
|
FormatArgsArg =
|
||||||
|
(Name '=')? Expr
|
||||||
|
|
||||||
MacroExpr =
|
MacroExpr =
|
||||||
MacroCall
|
MacroCall
|
||||||
|
|
|
@ -931,6 +931,9 @@ impl FormatArgsExpr {
|
||||||
support::token(&self.syntax, T![format_args])
|
support::token(&self.syntax, T![format_args])
|
||||||
}
|
}
|
||||||
pub fn l_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['(']) }
|
pub fn l_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['(']) }
|
||||||
|
pub fn template(&self) -> Option<Expr> { support::child(&self.syntax) }
|
||||||
|
pub fn comma_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![,]) }
|
||||||
|
pub fn args(&self) -> AstChildren<FormatArgsArg> { support::children(&self.syntax) }
|
||||||
pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) }
|
pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1163,6 +1166,16 @@ impl UnderscoreExpr {
|
||||||
pub fn underscore_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![_]) }
|
pub fn underscore_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![_]) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct FormatArgsArg {
|
||||||
|
pub(crate) syntax: SyntaxNode,
|
||||||
|
}
|
||||||
|
impl ast::HasName for FormatArgsArg {}
|
||||||
|
impl FormatArgsArg {
|
||||||
|
pub fn eq_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![=]) }
|
||||||
|
pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct StmtList {
|
pub struct StmtList {
|
||||||
pub(crate) syntax: SyntaxNode,
|
pub(crate) syntax: SyntaxNode,
|
||||||
|
@ -2855,6 +2868,17 @@ impl AstNode for UnderscoreExpr {
|
||||||
}
|
}
|
||||||
fn syntax(&self) -> &SyntaxNode { &self.syntax }
|
fn syntax(&self) -> &SyntaxNode { &self.syntax }
|
||||||
}
|
}
|
||||||
|
impl AstNode for FormatArgsArg {
|
||||||
|
fn can_cast(kind: SyntaxKind) -> bool { kind == FORMAT_ARGS_ARG }
|
||||||
|
fn cast(syntax: SyntaxNode) -> Option<Self> {
|
||||||
|
if Self::can_cast(syntax.kind()) {
|
||||||
|
Some(Self { syntax })
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn syntax(&self) -> &SyntaxNode { &self.syntax }
|
||||||
|
}
|
||||||
impl AstNode for StmtList {
|
impl AstNode for StmtList {
|
||||||
fn can_cast(kind: SyntaxKind) -> bool { kind == STMT_LIST }
|
fn can_cast(kind: SyntaxKind) -> bool { kind == STMT_LIST }
|
||||||
fn cast(syntax: SyntaxNode) -> Option<Self> {
|
fn cast(syntax: SyntaxNode) -> Option<Self> {
|
||||||
|
@ -4254,6 +4278,7 @@ impl AstNode for AnyHasName {
|
||||||
| VARIANT
|
| VARIANT
|
||||||
| CONST_PARAM
|
| CONST_PARAM
|
||||||
| TYPE_PARAM
|
| TYPE_PARAM
|
||||||
|
| FORMAT_ARGS_ARG
|
||||||
| IDENT_PAT
|
| IDENT_PAT
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -4860,6 +4885,11 @@ impl std::fmt::Display for UnderscoreExpr {
|
||||||
std::fmt::Display::fmt(self.syntax(), f)
|
std::fmt::Display::fmt(self.syntax(), f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl std::fmt::Display for FormatArgsArg {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
std::fmt::Display::fmt(self.syntax(), f)
|
||||||
|
}
|
||||||
|
}
|
||||||
impl std::fmt::Display for StmtList {
|
impl std::fmt::Display for StmtList {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
std::fmt::Display::fmt(self.syntax(), f)
|
std::fmt::Display::fmt(self.syntax(), f)
|
||||||
|
|
|
@ -169,6 +169,7 @@ pub(crate) const KINDS_SRC: KindsSrc<'_> = KindsSrc {
|
||||||
"OFFSET_OF_EXPR",
|
"OFFSET_OF_EXPR",
|
||||||
"ASM_EXPR",
|
"ASM_EXPR",
|
||||||
"FORMAT_ARGS_EXPR",
|
"FORMAT_ARGS_EXPR",
|
||||||
|
"FORMAT_ARGS_ARG",
|
||||||
// postfix
|
// postfix
|
||||||
"CALL_EXPR",
|
"CALL_EXPR",
|
||||||
"INDEX_EXPR",
|
"INDEX_EXPR",
|
||||||
|
|
Loading…
Reference in a new issue