use either::Either;
use hir_expand::name::Name;
use intern::Symbol;
use rustc_parse_format as parse;
use span::SyntaxContextId;
use stdx::TupleExt;
use syntax::{
ast::{self, IsString},
TextRange,
};
use crate::hir::ExprId;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FormatArgs {
pub template: Box<[FormatArgsPiece]>,
pub arguments: FormatArguments,
pub orphans: Vec<ExprId>,
}
#[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(Symbol),
Placeholder(FormatPlaceholder),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FormatPlaceholder {
pub argument: FormatArgPosition,
pub span: Option<TextRange>,
pub format_trait: FormatTrait,
pub format_options: FormatOptions,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FormatArgPosition {
pub index: Result<usize, Either<usize, Name>>,
pub kind: FormatArgPositionKind,
pub span: Option<TextRange>,
}
#[derive(Copy, Debug, Clone, PartialEq, Eq)]
pub enum FormatArgPositionKind {
Implicit,
Number,
Named,
}
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub enum FormatTrait {
Display,
Debug,
LowerExp,
UpperExp,
Octal,
Pointer,
Binary,
LowerHex,
UpperHex,
}
#[derive(Clone, Default, Debug, PartialEq, Eq)]
pub struct FormatOptions {
pub width: Option<FormatCount>,
pub precision: Option<FormatCount>,
pub alignment: Option<FormatAlignment>,
pub fill: Option<char>,
pub sign: Option<FormatSign>,
pub alternate: bool,
pub zero_pad: bool,
pub debug_hex: Option<FormatDebugHex>,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum FormatSign {
Plus,
Minus,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum FormatDebugHex {
Lower,
Upper,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum FormatAlignment {
Left,
Right,
Center,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FormatCount {
Literal(usize),
Argument(FormatArgPosition),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FormatArgument {
pub kind: FormatArgumentKind,
pub expr: ExprId,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum FormatArgumentKind {
Normal,
Named(Name),
Captured(Name),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum PositionUsedAs {
Placeholder(Option<TextRange>),
Precision,
Width,
}
use PositionUsedAs::*;
#[allow(clippy::unnecessary_lazy_evaluations)]
pub(crate) fn parse(
s: &ast::String,
fmt_snippet: Option<String>,
mut args: FormatArgumentsCollector,
is_direct_literal: bool,
mut synth: impl FnMut(Name, Option<TextRange>) -> ExprId,
mut record_usage: impl FnMut(Name, Option<TextRange>),
call_ctx: SyntaxContextId,
) -> FormatArgs {
let Ok(text) = s.value() else {
return FormatArgs {
template: Default::default(),
arguments: args.finish(),
orphans: vec![],
};
};
let str_style = match s.quote_offsets() {
Some(offsets) => {
let raw = usize::from(offsets.quotes.0.len()) - 1;
(raw != 0).then(|| raw - 1)
}
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() {
return FormatArgs {
template: Default::default(),
arguments: args.finish(),
orphans: vec![],
};
}
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_references_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() {
numeric_references_to_named_arg.push((index, span, used_as));
}
Ok(index)
} else {
invalid_refs.push((Either::Left(index), span, used_as, kind));
Err(Either::Left(index))
}
}
ArgRef::Name(name, span) => {
let name = Name::new(name, call_ctx);
if let Some((index, _)) = args.by_name(&name) {
record_usage(name, span);
if index < args.explicit_args().len() {
used[index] = true;
}
Ok(index)
} else {
if !is_direct_literal {
invalid_refs.push((Either::Right(name.clone()), span, used_as, kind));
Err(Either::Right(name))
} else {
record_usage(name.clone(), span);
Ok(args.add(FormatArgument {
kind: FormatArgumentKind::Captured(name.clone()),
expr: synth(name, span),
}))
}
}
}
};
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(Symbol::intern(&unfinished_literal)));
unfinished_literal.clear();
}
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,
_ => {
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(Symbol::intern(&unfinished_literal)));
}
if !invalid_refs.is_empty() {
}
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() {
}
FormatArgs {
template: template.into_boxed_slice(),
arguments: args.finish(),
orphans: unused.into_iter().map(TupleExt::head).collect(),
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
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 {
Default::default()
}
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() {
self.num_unnamed_args += 1;
}
if !matches!(arg.kind, FormatArgumentKind::Captured(..)) {
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),
}
}
}