-
Notifications
You must be signed in to change notification settings - Fork 12.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rewrite and refactor format_args!() builtin macro. #100996
Merged
Merged
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
9bec0de
Rewrite and refactor format_args!() builtin macro.
m-ou-se 3ffcb65
Update tests.
m-ou-se 8efc383
Move FormatArgs structure to its own module.
m-ou-se 0007492
Fix typo.
m-ou-se ae238ef
Prefer new_v1_formatted instead of new_v1 with duplicates.
m-ou-se e65c96e
Tweak comments.
m-ou-se df7fd11
Use if let chain.
m-ou-se 15754f5
Move enum definition closer to its usage.
m-ou-se 8d9a588
Flatten if-let and match into one.
m-ou-se c1c6e3a
Add clarifying comments.
m-ou-se 1406563
Update test.
m-ou-se cf53fef
Turn format arguments Vec into its own struct.
m-ou-se ba7bf1d
Update doc comments.
m-ou-se 20bb600
Remove confusing drop.
m-ou-se File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,240 @@ | ||
use rustc_ast::ptr::P; | ||
use rustc_ast::Expr; | ||
use rustc_data_structures::fx::FxHashMap; | ||
use rustc_span::symbol::{Ident, Symbol}; | ||
use rustc_span::Span; | ||
|
||
// Definitions: | ||
// | ||
// format_args!("hello {abc:.xyz$}!!", abc="world"); | ||
// └──────────────────────────────────────────────┘ | ||
// FormatArgs | ||
// | ||
// format_args!("hello {abc:.xyz$}!!", abc="world"); | ||
// └─────────┘ | ||
// argument | ||
// | ||
// format_args!("hello {abc:.xyz$}!!", abc="world"); | ||
// └───────────────────┘ | ||
// template | ||
// | ||
// format_args!("hello {abc:.xyz$}!!", abc="world"); | ||
// └────┘└─────────┘└┘ | ||
// pieces | ||
// | ||
// format_args!("hello {abc:.xyz$}!!", abc="world"); | ||
// └────┘ └┘ | ||
// literal pieces | ||
// | ||
// format_args!("hello {abc:.xyz$}!!", abc="world"); | ||
// └─────────┘ | ||
// placeholder | ||
// | ||
// format_args!("hello {abc:.xyz$}!!", abc="world"); | ||
// └─┘ └─┘ | ||
// positions (could be names, numbers, empty, or `*`) | ||
|
||
/// (Parsed) format args. | ||
/// | ||
/// Basically the "AST" for a complete `format_args!()`. | ||
/// | ||
/// E.g., `format_args!("hello {name}");`. | ||
#[derive(Clone, Debug)] | ||
pub struct FormatArgs { | ||
pub span: Span, | ||
pub template: Vec<FormatArgsPiece>, | ||
pub arguments: FormatArguments, | ||
} | ||
|
||
/// A piece of a format template string. | ||
/// | ||
/// E.g. "hello" or "{name}". | ||
#[derive(Clone, Debug)] | ||
pub enum FormatArgsPiece { | ||
Literal(Symbol), | ||
Placeholder(FormatPlaceholder), | ||
} | ||
|
||
/// The arguments to format_args!(). | ||
/// | ||
/// E.g. `1, 2, name="ferris", n=3`, | ||
/// but also implicit captured arguments like `x` in `format_args!("{x}")`. | ||
#[derive(Clone, Debug)] | ||
pub struct FormatArguments { | ||
arguments: Vec<FormatArgument>, | ||
num_unnamed_args: usize, | ||
num_explicit_args: usize, | ||
names: FxHashMap<Symbol, usize>, | ||
} | ||
|
||
impl FormatArguments { | ||
pub fn new() -> Self { | ||
Self { | ||
arguments: Vec::new(), | ||
names: FxHashMap::default(), | ||
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.insert(name.name, 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 explcit. | ||
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: Symbol) -> Option<(usize, &FormatArgument)> { | ||
let i = *self.names.get(&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 into_vec(self) -> Vec<FormatArgument> { | ||
self.arguments | ||
} | ||
} | ||
|
||
#[derive(Clone, Debug)] | ||
pub struct FormatArgument { | ||
pub kind: FormatArgumentKind, | ||
pub expr: P<Expr>, | ||
} | ||
|
||
#[derive(Clone, Debug)] | ||
pub enum FormatArgumentKind { | ||
/// `format_args(…, arg)` | ||
Normal, | ||
/// `format_args(…, arg = 1)` | ||
Named(Ident), | ||
/// `format_args("… {arg} …")` | ||
Captured(Ident), | ||
} | ||
|
||
impl FormatArgumentKind { | ||
pub fn ident(&self) -> Option<Ident> { | ||
match self { | ||
&Self::Normal => None, | ||
&Self::Named(id) => Some(id), | ||
&Self::Captured(id) => Some(id), | ||
} | ||
} | ||
} | ||
|
||
#[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<Span>, | ||
/// `{}`, `{:?}`, or `{:x}`, etc. | ||
pub format_trait: FormatTrait, | ||
/// `{}` or `{:.5}` or `{:-^20}`, etc. | ||
pub format_options: FormatOptions, | ||
} | ||
|
||
#[derive(Clone, Debug, 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<Span>, | ||
} | ||
|
||
#[derive(Copy, Clone, Debug, PartialEq, Eq)] | ||
pub enum FormatArgPositionKind { | ||
/// `{}` or `{:.*}` | ||
Implicit, | ||
/// `{1}` or `{:1$}` or `{:.1$}` | ||
Number, | ||
/// `{a}` or `{:a$}` or `{:.a$}` | ||
Named, | ||
} | ||
|
||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] | ||
pub enum FormatTrait { | ||
/// `{}` | ||
Display, | ||
/// `{:?}` | ||
Debug, | ||
/// `{:e}` | ||
LowerExp, | ||
/// `{:E}` | ||
UpperExp, | ||
/// `{:o}` | ||
Octal, | ||
/// `{:p}` | ||
Pointer, | ||
/// `{:b}` | ||
Binary, | ||
/// `{:x}` | ||
LowerHex, | ||
/// `{:X}` | ||
UpperHex, | ||
} | ||
|
||
#[derive(Clone, Debug, Default, 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 `+`, `-`, `0`, `#`, `x?` and `X?` flags. | ||
pub flags: u32, | ||
m-ou-se marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
#[derive(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), | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add a
Custom(String)
variant that we error out on, but that might be allowed in the future?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can always add that. Why would we add it now, if there's nothing that would create that variant?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For error propagation, but we can do that later, fair enough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What errors do you mean? Like, when someone does
{:z}
or something, to propagate that"z"
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly, but let's not deal with that now.