From abe8f1ece416e5627fa1e5cd30b86110f3f7d91b Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 5 Sep 2023 19:06:15 +0200 Subject: [PATCH 1/7] Implement builtin#format_args, using rustc's format_args parser --- Cargo.lock | 1 + crates/hir-def/Cargo.toml | 1 + crates/hir-def/src/body/lower.rs | 59 +- crates/hir-def/src/body/pretty.rs | 5 + crates/hir-def/src/hir.rs | 14 +- crates/hir-def/src/hir/format_args.rs | 511 ++++++++ crates/hir-def/src/hir/format_args/parse.rs | 1023 +++++++++++++++++ crates/hir-expand/src/name.rs | 6 + crates/hir-ty/src/infer/closure.rs | 13 +- crates/hir-ty/src/infer/expr.rs | 22 +- crates/hir-ty/src/infer/mutability.rs | 12 +- crates/hir-ty/src/mir/lower.rs | 3 + crates/hir-ty/src/tests/simple.rs | 22 + crates/parser/src/grammar/expressions/atom.rs | 20 +- crates/parser/src/syntax_kind/generated.rs | 1 + .../parser/inline/ok/0207_builtin_expr.rs | 2 +- crates/syntax/rust.ungram | 8 +- crates/syntax/src/ast/generated/nodes.rs | 30 + crates/syntax/src/tests/ast_src.rs | 1 + 19 files changed, 1740 insertions(+), 14 deletions(-) create mode 100644 crates/hir-def/src/hir/format_args.rs create mode 100644 crates/hir-def/src/hir/format_args/parse.rs diff --git a/Cargo.lock b/Cargo.lock index f6900f883a15..c3d7b3f79b97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -541,6 +541,7 @@ dependencies = [ "mbe", "once_cell", "profile", + "ra-ap-rustc_lexer", "rustc-hash", "smallvec", "stdx", diff --git a/crates/hir-def/Cargo.toml b/crates/hir-def/Cargo.toml index 30307deb79b8..4640ee5140f4 100644 --- a/crates/hir-def/Cargo.toml +++ b/crates/hir-def/Cargo.toml @@ -33,6 +33,7 @@ triomphe.workspace = true 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_lexer = { version = "0.1.0", package = "ra-ap-rustc_lexer" } # local deps stdx.workspace = true diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs index 038032e05d8c..bb0127c9ef61 100644 --- a/crates/hir-def/src/body/lower.rs +++ b/crates/hir-def/src/body/lower.rs @@ -29,9 +29,13 @@ use crate::{ db::DefDatabase, expander::Expander, hir::{ - dummy_expr_id, Array, Binding, BindingAnnotation, BindingId, BindingProblems, CaptureBy, - ClosureKind, Expr, ExprId, InlineAsm, Label, LabelId, Literal, LiteralOrConst, MatchArm, - Movability, OffsetOf, Pat, PatId, RecordFieldPat, RecordLitField, Statement, + dummy_expr_id, + format_args::{ + 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, lang_item::LangItem, @@ -649,15 +653,58 @@ impl ExprCollector<'_> { } ast::Expr::UnderscoreExpr(_) => self.alloc_expr(Expr::Underscore, syntax_ptr), ast::Expr::AsmExpr(e) => { - let expr = Expr::InlineAsm(InlineAsm { e: self.collect_expr_opt(e.expr()) }); - self.alloc_expr(expr, syntax_ptr) + let e = self.collect_expr_opt(e.expr()); + self.alloc_expr(Expr::InlineAsm(InlineAsm { e }), syntax_ptr) } ast::Expr::OffsetOfExpr(e) => { let container = Interned::new(TypeRef::from_ast_opt(&self.ctx(), e.ty())); let fields = e.fields().map(|it| it.as_name()).collect(); 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, + ) + } }) } diff --git a/crates/hir-def/src/body/pretty.rs b/crates/hir-def/src/body/pretty.rs index 602a7983c7c8..b67ed2fb3857 100644 --- a/crates/hir-def/src/body/pretty.rs +++ b/crates/hir-def/src/body/pretty.rs @@ -156,6 +156,11 @@ impl Printer<'_> { Expr::Missing => w!(self, "�"), Expr::Underscore => w!(self, "_"), Expr::InlineAsm(_) => w!(self, "builtin#asm(_)"), + Expr::FormatArgs(_fmt_args) => { + w!(self, "builtin#format_args("); + // FIXME + w!(self, ")"); + } Expr::OffsetOf(offset_of) => { w!(self, "builtin#offset_of("); self.print_type_ref(&offset_of.container); diff --git a/crates/hir-def/src/hir.rs b/crates/hir-def/src/hir.rs index 1c86af456d17..98220de388e0 100644 --- a/crates/hir-def/src/hir.rs +++ b/crates/hir-def/src/hir.rs @@ -13,6 +13,7 @@ //! See also a neighboring `body` module. pub mod type_ref; +pub mod format_args; use std::fmt; @@ -24,6 +25,7 @@ use syntax::ast; use crate::{ builtin_type::{BuiltinFloat, BuiltinInt, BuiltinUint}, + hir::format_args::{FormatArgs, FormatArgumentKind}, path::{GenericArgs, Path}, type_ref::{Mutability, Rawness, TypeRef}, BlockId, ConstBlockId, @@ -117,7 +119,6 @@ impl From for Literal { fn from(ast_lit_kind: ast::LiteralKind) -> Self { use ast::LiteralKind; match ast_lit_kind { - // FIXME: these should have actual values filled in, but unsure on perf impact LiteralKind::IntNumber(lit) => { if let builtin @ Some(_) = lit.suffix().and_then(BuiltinFloat::from_suffix) { Literal::Float( @@ -283,6 +284,7 @@ pub enum Expr { Underscore, OffsetOf(OffsetOf), InlineAsm(InlineAsm), + FormatArgs(FormatArgs), } #[derive(Debug, Clone, PartialEq, Eq)] @@ -355,7 +357,15 @@ impl Expr { match self { Expr::Missing => {} 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 } => { f(*condition); f(*then_branch); diff --git a/crates/hir-def/src/hir/format_args.rs b/crates/hir-def/src/hir/format_args.rs new file mode 100644 index 000000000000..8fa8b7246bcf --- /dev/null +++ b/crates/hir-def/src/hir/format_args.rs @@ -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), + 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, + /// `{}`, `{:?}`, 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, + /// What kind of position this is. See [`FormatArgPositionKind`]. + pub kind: FormatArgPositionKind, + /// The span of the name or number. + pub span: Option, +} + +#[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, + /// The precision. E.g. `{:.5}` or `{:.precision$}`. + pub precision: Option, + /// The alignment. E.g. `{:>}` or `{:<}` or `{:^}`. + pub alignment: Option, + /// The fill character. E.g. the `.` in `{:.>10}`. + pub fill: Option, + /// The `+` or `-` flag. + pub sign: Option, + /// 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, +} +#[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), + Precision, + Width, +} +use PositionUsedAs::*; + +pub(crate) fn parse( + expr: ExprId, + s: &ast::String, + fmt_snippet: Option, + 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), + } + let mut lookup_arg = |arg: ArgRef<'_>, + span: Option, + 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::>(); + + 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, + 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 { + &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), + } + } +} diff --git a/crates/hir-def/src/hir/format_args/parse.rs b/crates/hir-def/src/hir/format_args/parse.rs new file mode 100644 index 000000000000..22efa3883d40 --- /dev/null +++ b/crates/hir-def/src/hir/format_args/parse.rs @@ -0,0 +1,1023 @@ +//! Macro support for format strings +//! +//! These structures are used when parsing format strings for the compiler. +//! Parsing does not happen at runtime: structures of `std::fmt::rt` are +//! generated instead. + +// This is a copy of +// https://github.com/Veykril/rust/blob/b89d7d6882532686fd90a89cec1a0fd386f0ade3/compiler/rustc_parse_format/src/lib.rs#L999-L1000 +// with the dependency of rustc-data-structures stripped out. + +// #![doc( +// html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/", +// html_playground_url = "https://play.rust-lang.org/", +// test(attr(deny(warnings))) +// )] +// #![deny(rustc::untranslatable_diagnostic)] +// #![deny(rustc::diagnostic_outside_of_impl)] +// We want to be able to build this crate with a stable compiler, so no +// `#![feature]` attributes should be added. +#![allow(dead_code, unreachable_pub)] + +use rustc_lexer::unescape; +pub use Alignment::*; +pub use Count::*; +pub use Piece::*; +pub use Position::*; + +use std::iter; +use std::str; +use std::string; + +// Note: copied from rustc_span +/// Range inside of a `Span` used for diagnostics when we only have access to relative positions. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct InnerSpan { + pub start: usize, + pub end: usize, +} + +impl InnerSpan { + pub fn new(start: usize, end: usize) -> InnerSpan { + InnerSpan { start, end } + } +} + +/// The location and before/after width of a character whose width has changed from its source code +/// representation +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct InnerWidthMapping { + /// Index of the character in the source + pub position: usize, + /// The inner width in characters + pub before: usize, + /// The transformed width in characters + pub after: usize, +} + +impl InnerWidthMapping { + pub fn new(position: usize, before: usize, after: usize) -> InnerWidthMapping { + InnerWidthMapping { position, before, after } + } +} + +/// Whether the input string is a literal. If yes, it contains the inner width mappings. +#[derive(Clone, PartialEq, Eq)] +enum InputStringKind { + NotALiteral, + Literal { width_mappings: Vec }, +} + +/// The type of format string that we are parsing. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum ParseMode { + /// A normal format string as per `format_args!`. + Format, + /// An inline assembly template string for `asm!`. + InlineAsm, +} + +#[derive(Copy, Clone)] +struct InnerOffset(usize); + +impl InnerOffset { + fn to(self, end: InnerOffset) -> InnerSpan { + InnerSpan::new(self.0, end.0) + } +} + +/// A piece is a portion of the format string which represents the next part +/// to emit. These are emitted as a stream by the `Parser` class. +#[derive(Clone, Debug, PartialEq)] +pub enum Piece<'a> { + /// A literal string which should directly be emitted + String(&'a str), + /// This describes that formatting should process the next argument (as + /// specified inside) for emission. + NextArgument(Box>), +} + +/// Representation of an argument specification. +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct Argument<'a> { + /// Where to find this argument + pub position: Position<'a>, + /// The span of the position indicator. Includes any whitespace in implicit + /// positions (`{ }`). + pub position_span: InnerSpan, + /// How to format the argument + pub format: FormatSpec<'a>, +} + +/// Specification for the formatting of an argument in the format string. +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct FormatSpec<'a> { + /// Optionally specified character to fill alignment with. + pub fill: Option, + /// Span of the optionally specified fill character. + pub fill_span: Option, + /// Optionally specified alignment. + pub align: Alignment, + /// The `+` or `-` flag. + pub sign: Option, + /// The `#` flag. + pub alternate: bool, + /// The `0` flag. + pub zero_pad: bool, + /// The `x` or `X` flag. (Only for `Debug`.) + pub debug_hex: Option, + /// The integer precision to use. + pub precision: Count<'a>, + /// The span of the precision formatting flag (for diagnostics). + pub precision_span: Option, + /// The string width requested for the resulting format. + pub width: Count<'a>, + /// The span of the width formatting flag (for diagnostics). + pub width_span: Option, + /// The descriptor string representing the name of the format desired for + /// this argument, this can be empty or any number of characters, although + /// it is required to be one word. + pub ty: &'a str, + /// The span of the descriptor string (for diagnostics). + pub ty_span: Option, +} + +/// Enum describing where an argument for a format can be located. +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Position<'a> { + /// The argument is implied to be located at an index + ArgumentImplicitlyIs(usize), + /// The argument is located at a specific index given in the format, + ArgumentIs(usize), + /// The argument has a name. + ArgumentNamed(&'a str), +} + +impl Position<'_> { + pub fn index(&self) -> Option { + match self { + ArgumentIs(i, ..) | ArgumentImplicitlyIs(i) => Some(*i), + _ => None, + } + } +} + +/// Enum of alignments which are supported. +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Alignment { + /// The value will be aligned to the left. + AlignLeft, + /// The value will be aligned to the right. + AlignRight, + /// The value will be aligned in the center. + AlignCenter, + /// The value will take on a default alignment. + AlignUnknown, +} + +/// Enum for the sign flags. +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Sign { + /// The `+` flag. + Plus, + /// The `-` flag. + Minus, +} + +/// Enum for the debug hex flags. +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum DebugHex { + /// The `x` flag in `{:x?}`. + Lower, + /// The `X` flag in `{:X?}`. + Upper, +} + +/// A count is used for the precision and width parameters of an integer, and +/// can reference either an argument or a literal integer. +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Count<'a> { + /// The count is specified explicitly. + CountIs(usize), + /// The count is specified by the argument with the given name. + CountIsName(&'a str, InnerSpan), + /// The count is specified by the argument at the given index. + CountIsParam(usize), + /// The count is specified by a star (like in `{:.*}`) that refers to the argument at the given index. + CountIsStar(usize), + /// The count is implied and cannot be explicitly specified. + CountImplied, +} + +pub struct ParseError { + pub description: string::String, + pub note: Option, + pub label: string::String, + pub span: InnerSpan, + pub secondary_label: Option<(string::String, InnerSpan)>, + pub should_be_replaced_with_positional_argument: bool, +} + +/// The parser structure for interpreting the input format string. This is +/// modeled as an iterator over `Piece` structures to form a stream of tokens +/// being output. +/// +/// This is a recursive-descent parser for the sake of simplicity, and if +/// necessary there's probably lots of room for improvement performance-wise. +pub struct Parser<'a> { + mode: ParseMode, + input: &'a str, + cur: iter::Peekable>, + /// Error messages accumulated during parsing + pub errors: Vec, + /// Current position of implicit positional argument pointer + pub curarg: usize, + /// `Some(raw count)` when the string is "raw", used to position spans correctly + style: Option, + /// Start and end byte offset of every successfully parsed argument + pub arg_places: Vec, + /// Characters whose length has been changed from their in-code representation + width_map: Vec, + /// Span of the last opening brace seen, used for error reporting + last_opening_brace: Option, + /// Whether the source string is comes from `println!` as opposed to `format!` or `print!` + append_newline: bool, + /// Whether this formatting string was written directly in the source. This controls whether we + /// can use spans to refer into it and give better error messages. + /// N.B: This does _not_ control whether implicit argument captures can be used. + pub is_source_literal: bool, + /// Start position of the current line. + cur_line_start: usize, + /// Start and end byte offset of every line of the format string. Excludes + /// newline characters and leading whitespace. + pub line_spans: Vec, +} + +impl<'a> Iterator for Parser<'a> { + type Item = Piece<'a>; + + fn next(&mut self) -> Option> { + if let Some(&(pos, c)) = self.cur.peek() { + match c { + '{' => { + let curr_last_brace = self.last_opening_brace; + let byte_pos = self.to_span_index(pos); + let lbrace_end = InnerOffset(byte_pos.0 + self.to_span_width(pos)); + self.last_opening_brace = Some(byte_pos.to(lbrace_end)); + self.cur.next(); + if self.consume('{') { + self.last_opening_brace = curr_last_brace; + + Some(String(self.string(pos + 1))) + } else { + let arg = self.argument(lbrace_end); + if let Some(rbrace_pos) = self.consume_closing_brace(&arg) { + if self.is_source_literal { + let lbrace_byte_pos = self.to_span_index(pos); + let rbrace_byte_pos = self.to_span_index(rbrace_pos); + + let width = self.to_span_width(rbrace_pos); + + self.arg_places.push( + lbrace_byte_pos.to(InnerOffset(rbrace_byte_pos.0 + width)), + ); + } + } else { + if let Some(&(_, maybe)) = self.cur.peek() { + if maybe == '?' { + self.suggest_format(); + } else { + self.suggest_positional_arg_instead_of_captured_arg(arg); + } + } + } + Some(NextArgument(Box::new(arg))) + } + } + '}' => { + self.cur.next(); + if self.consume('}') { + Some(String(self.string(pos + 1))) + } else { + let err_pos = self.to_span_index(pos); + self.err_with_note( + "unmatched `}` found", + "unmatched `}`", + "if you intended to print `}`, you can escape it using `}}`", + err_pos.to(err_pos), + ); + None + } + } + _ => Some(String(self.string(pos))), + } + } else { + if self.is_source_literal { + let span = self.span(self.cur_line_start, self.input.len()); + if self.line_spans.last() != Some(&span) { + self.line_spans.push(span); + } + } + None + } + } +} + +impl<'a> Parser<'a> { + /// Creates a new parser for the given format string + pub fn new( + s: &'a str, + style: Option, + snippet: Option, + append_newline: bool, + mode: ParseMode, + ) -> Parser<'a> { + let input_string_kind = find_width_map_from_snippet(s, snippet, style); + let (width_map, is_source_literal) = match input_string_kind { + InputStringKind::Literal { width_mappings } => (width_mappings, true), + InputStringKind::NotALiteral => (Vec::new(), false), + }; + + Parser { + mode, + input: s, + cur: s.char_indices().peekable(), + errors: vec![], + curarg: 0, + style, + arg_places: vec![], + width_map, + last_opening_brace: None, + append_newline, + is_source_literal, + cur_line_start: 0, + line_spans: vec![], + } + } + + /// Notifies of an error. The message doesn't actually need to be of type + /// String, but I think it does when this eventually uses conditions so it + /// might as well start using it now. + fn err, S2: Into>( + &mut self, + description: S1, + label: S2, + span: InnerSpan, + ) { + self.errors.push(ParseError { + description: description.into(), + note: None, + label: label.into(), + span, + secondary_label: None, + should_be_replaced_with_positional_argument: false, + }); + } + + /// Notifies of an error. The message doesn't actually need to be of type + /// String, but I think it does when this eventually uses conditions so it + /// might as well start using it now. + fn err_with_note< + S1: Into, + S2: Into, + S3: Into, + >( + &mut self, + description: S1, + label: S2, + note: S3, + span: InnerSpan, + ) { + self.errors.push(ParseError { + description: description.into(), + note: Some(note.into()), + label: label.into(), + span, + secondary_label: None, + should_be_replaced_with_positional_argument: false, + }); + } + + /// Optionally consumes the specified character. If the character is not at + /// the current position, then the current iterator isn't moved and `false` is + /// returned, otherwise the character is consumed and `true` is returned. + fn consume(&mut self, c: char) -> bool { + self.consume_pos(c).is_some() + } + + /// Optionally consumes the specified character. If the character is not at + /// the current position, then the current iterator isn't moved and `None` is + /// returned, otherwise the character is consumed and the current position is + /// returned. + fn consume_pos(&mut self, c: char) -> Option { + if let Some(&(pos, maybe)) = self.cur.peek() { + if c == maybe { + self.cur.next(); + return Some(pos); + } + } + None + } + + fn remap_pos(&self, mut pos: usize) -> InnerOffset { + for width in &self.width_map { + if pos > width.position { + pos += width.before - width.after; + } else if pos == width.position && width.after == 0 { + pos += width.before; + } else { + break; + } + } + + InnerOffset(pos) + } + + fn to_span_index(&self, pos: usize) -> InnerOffset { + // This handles the raw string case, the raw argument is the number of # + // in r###"..."### (we need to add one because of the `r`). + let raw = self.style.map_or(0, |raw| raw + 1); + let pos = self.remap_pos(pos); + InnerOffset(raw + pos.0 + 1) + } + + fn to_span_width(&self, pos: usize) -> usize { + let pos = self.remap_pos(pos); + match self.width_map.iter().find(|w| w.position == pos.0) { + Some(w) => w.before, + None => 1, + } + } + + fn span(&self, start_pos: usize, end_pos: usize) -> InnerSpan { + let start = self.to_span_index(start_pos); + let end = self.to_span_index(end_pos); + start.to(end) + } + + /// Forces consumption of the specified character. If the character is not + /// found, an error is emitted. + fn consume_closing_brace(&mut self, arg: &Argument<'_>) -> Option { + self.ws(); + + let pos; + let description; + + if let Some(&(peek_pos, maybe)) = self.cur.peek() { + if maybe == '}' { + self.cur.next(); + return Some(peek_pos); + } + + pos = peek_pos; + description = format!("expected `'}}'`, found `{maybe:?}`"); + } else { + description = "expected `'}'` but string was terminated".to_owned(); + // point at closing `"` + pos = self.input.len() - if self.append_newline { 1 } else { 0 }; + } + + let pos = self.to_span_index(pos); + + let label = "expected `'}'`".to_owned(); + let (note, secondary_label) = if arg.format.fill == Some('}') { + ( + Some("the character `'}'` is interpreted as a fill character because of the `:` that precedes it".to_owned()), + arg.format.fill_span.map(|sp| ("this is not interpreted as a formatting closing brace".to_owned(), sp)), + ) + } else { + ( + Some("if you intended to print `{`, you can escape it using `{{`".to_owned()), + self.last_opening_brace.map(|sp| ("because of this opening brace".to_owned(), sp)), + ) + }; + + self.errors.push(ParseError { + description, + note, + label, + span: pos.to(pos), + secondary_label, + should_be_replaced_with_positional_argument: false, + }); + + None + } + + /// Consumes all whitespace characters until the first non-whitespace character + fn ws(&mut self) { + while let Some(&(_, c)) = self.cur.peek() { + if c.is_whitespace() { + self.cur.next(); + } else { + break; + } + } + } + + /// Parses all of a string which is to be considered a "raw literal" in a + /// format string. This is everything outside of the braces. + fn string(&mut self, start: usize) -> &'a str { + // we may not consume the character, peek the iterator + while let Some(&(pos, c)) = self.cur.peek() { + match c { + '{' | '}' => { + return &self.input[start..pos]; + } + '\n' if self.is_source_literal => { + self.line_spans.push(self.span(self.cur_line_start, pos)); + self.cur_line_start = pos + 1; + self.cur.next(); + } + _ => { + if self.is_source_literal && pos == self.cur_line_start && c.is_whitespace() { + self.cur_line_start = pos + c.len_utf8(); + } + self.cur.next(); + } + } + } + &self.input[start..self.input.len()] + } + + /// Parses an `Argument` structure, or what's contained within braces inside the format string. + fn argument(&mut self, start: InnerOffset) -> Argument<'a> { + let pos = self.position(); + + let end = self + .cur + .clone() + .find(|(_, ch)| !ch.is_whitespace()) + .map_or(start, |(end, _)| self.to_span_index(end)); + let position_span = start.to(end); + + let format = match self.mode { + ParseMode::Format => self.format(), + ParseMode::InlineAsm => self.inline_asm(), + }; + + // Resolve position after parsing format spec. + let pos = match pos { + Some(position) => position, + None => { + let i = self.curarg; + self.curarg += 1; + ArgumentImplicitlyIs(i) + } + }; + + Argument { position: pos, position_span, format } + } + + /// Parses a positional argument for a format. This could either be an + /// integer index of an argument, a named argument, or a blank string. + /// Returns `Some(parsed_position)` if the position is not implicitly + /// consuming a macro argument, `None` if it's the case. + fn position(&mut self) -> Option> { + if let Some(i) = self.integer() { + Some(ArgumentIs(i)) + } else { + match self.cur.peek() { + Some(&(_, c)) if rustc_lexer::is_id_start(c) => Some(ArgumentNamed(self.word())), + + // This is an `ArgumentNext`. + // Record the fact and do the resolution after parsing the + // format spec, to make things like `{:.*}` work. + _ => None, + } + } + } + + fn current_pos(&mut self) -> usize { + if let Some(&(pos, _)) = self.cur.peek() { + pos + } else { + self.input.len() + } + } + + /// Parses a format specifier at the current position, returning all of the + /// relevant information in the `FormatSpec` struct. + fn format(&mut self) -> FormatSpec<'a> { + let mut spec = FormatSpec { + fill: None, + fill_span: None, + align: AlignUnknown, + sign: None, + alternate: false, + zero_pad: false, + debug_hex: None, + precision: CountImplied, + precision_span: None, + width: CountImplied, + width_span: None, + ty: &self.input[..0], + ty_span: None, + }; + if !self.consume(':') { + return spec; + } + + // fill character + if let Some(&(idx, c)) = self.cur.peek() { + if let Some((_, '>' | '<' | '^')) = self.cur.clone().nth(1) { + spec.fill = Some(c); + spec.fill_span = Some(self.span(idx, idx + 1)); + self.cur.next(); + } + } + // Alignment + if self.consume('<') { + spec.align = AlignLeft; + } else if self.consume('>') { + spec.align = AlignRight; + } else if self.consume('^') { + spec.align = AlignCenter; + } + // Sign flags + if self.consume('+') { + spec.sign = Some(Sign::Plus); + } else if self.consume('-') { + spec.sign = Some(Sign::Minus); + } + // Alternate marker + if self.consume('#') { + spec.alternate = true; + } + // Width and precision + let mut havewidth = false; + + if self.consume('0') { + // small ambiguity with '0$' as a format string. In theory this is a + // '0' flag and then an ill-formatted format string with just a '$' + // and no count, but this is better if we instead interpret this as + // no '0' flag and '0$' as the width instead. + if let Some(end) = self.consume_pos('$') { + spec.width = CountIsParam(0); + spec.width_span = Some(self.span(end - 1, end + 1)); + havewidth = true; + } else { + spec.zero_pad = true; + } + } + + if !havewidth { + let start = self.current_pos(); + spec.width = self.count(start); + if spec.width != CountImplied { + let end = self.current_pos(); + spec.width_span = Some(self.span(start, end)); + } + } + + if let Some(start) = self.consume_pos('.') { + if self.consume('*') { + // Resolve `CountIsNextParam`. + // We can do this immediately as `position` is resolved later. + let i = self.curarg; + self.curarg += 1; + spec.precision = CountIsStar(i); + } else { + spec.precision = self.count(start + 1); + } + let end = self.current_pos(); + spec.precision_span = Some(self.span(start, end)); + } + + let ty_span_start = self.current_pos(); + // Optional radix followed by the actual format specifier + if self.consume('x') { + if self.consume('?') { + spec.debug_hex = Some(DebugHex::Lower); + spec.ty = "?"; + } else { + spec.ty = "x"; + } + } else if self.consume('X') { + if self.consume('?') { + spec.debug_hex = Some(DebugHex::Upper); + spec.ty = "?"; + } else { + spec.ty = "X"; + } + } else if self.consume('?') { + spec.ty = "?"; + } else { + spec.ty = self.word(); + if !spec.ty.is_empty() { + let ty_span_end = self.current_pos(); + spec.ty_span = Some(self.span(ty_span_start, ty_span_end)); + } + } + spec + } + + /// Parses an inline assembly template modifier at the current position, returning the modifier + /// in the `ty` field of the `FormatSpec` struct. + fn inline_asm(&mut self) -> FormatSpec<'a> { + let mut spec = FormatSpec { + fill: None, + fill_span: None, + align: AlignUnknown, + sign: None, + alternate: false, + zero_pad: false, + debug_hex: None, + precision: CountImplied, + precision_span: None, + width: CountImplied, + width_span: None, + ty: &self.input[..0], + ty_span: None, + }; + if !self.consume(':') { + return spec; + } + + let ty_span_start = self.current_pos(); + spec.ty = self.word(); + if !spec.ty.is_empty() { + let ty_span_end = self.current_pos(); + spec.ty_span = Some(self.span(ty_span_start, ty_span_end)); + } + + spec + } + + /// Parses a `Count` parameter at the current position. This does not check + /// for 'CountIsNextParam' because that is only used in precision, not + /// width. + fn count(&mut self, start: usize) -> Count<'a> { + if let Some(i) = self.integer() { + if self.consume('$') { + CountIsParam(i) + } else { + CountIs(i) + } + } else { + let tmp = self.cur.clone(); + let word = self.word(); + if word.is_empty() { + self.cur = tmp; + CountImplied + } else if let Some(end) = self.consume_pos('$') { + let name_span = self.span(start, end); + CountIsName(word, name_span) + } else { + self.cur = tmp; + CountImplied + } + } + } + + /// Parses a word starting at the current position. A word is the same as + /// Rust identifier, except that it can't start with `_` character. + fn word(&mut self) -> &'a str { + let start = match self.cur.peek() { + Some(&(pos, c)) if rustc_lexer::is_id_start(c) => { + self.cur.next(); + pos + } + _ => { + return ""; + } + }; + let mut end = None; + while let Some(&(pos, c)) = self.cur.peek() { + if rustc_lexer::is_id_continue(c) { + self.cur.next(); + } else { + end = Some(pos); + break; + } + } + let end = end.unwrap_or(self.input.len()); + let word = &self.input[start..end]; + if word == "_" { + self.err_with_note( + "invalid argument name `_`", + "invalid argument name", + "argument name cannot be a single underscore", + self.span(start, end), + ); + } + word + } + + fn integer(&mut self) -> Option { + let mut cur: usize = 0; + let mut found = false; + let mut overflow = false; + let start = self.current_pos(); + while let Some(&(_, c)) = self.cur.peek() { + if let Some(i) = c.to_digit(10) { + let (tmp, mul_overflow) = cur.overflowing_mul(10); + let (tmp, add_overflow) = tmp.overflowing_add(i as usize); + if mul_overflow || add_overflow { + overflow = true; + } + cur = tmp; + found = true; + self.cur.next(); + } else { + break; + } + } + + if overflow { + let end = self.current_pos(); + let overflowed_int = &self.input[start..end]; + self.err( + format!( + "integer `{}` does not fit into the type `usize` whose range is `0..={}`", + overflowed_int, + usize::MAX + ), + "integer out of range for `usize`", + self.span(start, end), + ); + } + + found.then_some(cur) + } + + fn suggest_format(&mut self) { + if let (Some(pos), Some(_)) = (self.consume_pos('?'), self.consume_pos(':')) { + let word = self.word(); + let _end = self.current_pos(); + let pos = self.to_span_index(pos); + self.errors.insert( + 0, + ParseError { + description: "expected format parameter to occur after `:`".to_owned(), + note: Some(format!("`?` comes after `:`, try `{}:{}` instead", word, "?")), + label: "expected `?` to occur after `:`".to_owned(), + span: pos.to(pos), + secondary_label: None, + should_be_replaced_with_positional_argument: false, + }, + ); + } + } + + fn suggest_positional_arg_instead_of_captured_arg(&mut self, arg: Argument<'a>) { + if let Some(end) = self.consume_pos('.') { + let byte_pos = self.to_span_index(end); + let start = InnerOffset(byte_pos.0 + 1); + let field = self.argument(start); + // We can only parse `foo.bar` field access, any deeper nesting, + // or another type of expression, like method calls, are not supported + if !self.consume('}') { + return; + } + if let ArgumentNamed(_) = arg.position { + if let ArgumentNamed(_) = field.position { + self.errors.insert( + 0, + ParseError { + description: "field access isn't supported".to_string(), + note: None, + label: "not supported".to_string(), + span: InnerSpan::new(arg.position_span.start, field.position_span.end), + secondary_label: None, + should_be_replaced_with_positional_argument: true, + }, + ); + } + } + } + } +} + +/// Finds the indices of all characters that have been processed and differ between the actual +/// written code (code snippet) and the `InternedString` that gets processed in the `Parser` +/// in order to properly synthesise the intra-string `Span`s for error diagnostics. +fn find_width_map_from_snippet( + input: &str, + snippet: Option, + str_style: Option, +) -> InputStringKind { + let snippet = match snippet { + Some(ref s) if s.starts_with('"') || s.starts_with("r\"") || s.starts_with("r#") => s, + _ => return InputStringKind::NotALiteral, + }; + + if str_style.is_some() { + return InputStringKind::Literal { width_mappings: Vec::new() }; + } + + // Strip quotes. + let snippet = &snippet[1..snippet.len() - 1]; + + // Macros like `println` add a newline at the end. That technically doesn't make them "literals" anymore, but it's fine + // since we will never need to point our spans there, so we lie about it here by ignoring it. + // Since there might actually be newlines in the source code, we need to normalize away all trailing newlines. + // If we only trimmed it off the input, `format!("\n")` would cause a mismatch as here we they actually match up. + // Alternatively, we could just count the trailing newlines and only trim one from the input if they don't match up. + let input_no_nl = input.trim_end_matches('\n'); + let Some(unescaped) = unescape_string(snippet) else { + return InputStringKind::NotALiteral; + }; + + let unescaped_no_nl = unescaped.trim_end_matches('\n'); + + if unescaped_no_nl != input_no_nl { + // The source string that we're pointing at isn't our input, so spans pointing at it will be incorrect. + // This can for example happen with proc macros that respan generated literals. + return InputStringKind::NotALiteral; + } + + let mut s = snippet.char_indices(); + let mut width_mappings = vec![]; + while let Some((pos, c)) = s.next() { + match (c, s.clone().next()) { + // skip whitespace and empty lines ending in '\\' + ('\\', Some((_, '\n'))) => { + let _ = s.next(); + let mut width = 2; + + while let Some((_, c)) = s.clone().next() { + if matches!(c, ' ' | '\n' | '\t') { + width += 1; + let _ = s.next(); + } else { + break; + } + } + + width_mappings.push(InnerWidthMapping::new(pos, width, 0)); + } + ('\\', Some((_, 'n' | 't' | 'r' | '0' | '\\' | '\'' | '\"'))) => { + width_mappings.push(InnerWidthMapping::new(pos, 2, 1)); + let _ = s.next(); + } + ('\\', Some((_, 'x'))) => { + // consume `\xAB` literal + s.nth(2); + width_mappings.push(InnerWidthMapping::new(pos, 4, 1)); + } + ('\\', Some((_, 'u'))) => { + let mut width = 2; + let _ = s.next(); + + if let Some((_, next_c)) = s.next() { + if next_c == '{' { + // consume up to 6 hexanumeric chars + let digits_len = + s.clone().take(6).take_while(|(_, c)| c.is_digit(16)).count(); + + let len_utf8 = s + .as_str() + .get(..digits_len) + .and_then(|digits| u32::from_str_radix(digits, 16).ok()) + .and_then(char::from_u32) + .map_or(1, char::len_utf8); + + // Skip the digits, for chars that encode to more than 1 utf-8 byte + // exclude as many digits as it is greater than 1 byte + // + // So for a 3 byte character, exclude 2 digits + let required_skips = digits_len.saturating_sub(len_utf8.saturating_sub(1)); + + // skip '{' and '}' also + width += required_skips + 2; + + s.nth(digits_len); + } else if next_c.is_digit(16) { + width += 1; + + // We suggest adding `{` and `}` when appropriate, accept it here as if + // it were correct + let mut i = 0; // consume up to 6 hexanumeric chars + while let (Some((_, c)), _) = (s.next(), i < 6) { + if c.is_digit(16) { + width += 1; + } else { + break; + } + i += 1; + } + } + } + + width_mappings.push(InnerWidthMapping::new(pos, width, 1)); + } + _ => {} + } + } + + InputStringKind::Literal { width_mappings } +} + +fn unescape_string(string: &str) -> Option { + let mut buf = string::String::new(); + let mut ok = true; + unescape::unescape_literal(string, unescape::Mode::Str, &mut |_, unescaped_char| { + match unescaped_char { + Ok(c) => buf.push(c), + Err(_) => ok = false, + } + }); + + ok.then_some(buf) +} diff --git a/crates/hir-expand/src/name.rs b/crates/hir-expand/src/name.rs index 7c179c0cf959..5395b867c96c 100644 --- a/crates/hir-expand/src/name.rs +++ b/crates/hir-expand/src/name.rs @@ -54,6 +54,12 @@ impl Name { 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 { Name(Repr::TupleField(idx)) } diff --git a/crates/hir-ty/src/infer/closure.rs b/crates/hir-ty/src/infer/closure.rs index 1f040393f1f3..9431599ac791 100644 --- a/crates/hir-ty/src/infer/closure.rs +++ b/crates/hir-ty/src/infer/closure.rs @@ -9,7 +9,10 @@ use chalk_ir::{ }; use hir_def::{ 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, resolver::{resolver_for_expr, ResolveValueResult, ValueNs}, DefWithBodyId, FieldId, HasModule, VariantId, @@ -453,6 +456,14 @@ impl InferenceContext<'_> { fn walk_expr_without_adjust(&mut self, tgt_expr: ExprId) { match &self.body[tgt_expr] { 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::If { condition, then_branch, else_branch } => { self.consume_expr(*condition); diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 555a9fae48ec..06742f7b36d3 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -9,7 +9,8 @@ use chalk_ir::{cast::Cast, fold::Shift, DebruijnIndex, Mutability, TyVariableKin use hir_def::{ generics::TypeOrConstParamData, 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}, path::{GenericArg, GenericArgs}, @@ -848,6 +849,25 @@ impl InferenceContext<'_> { self.infer_expr_no_expect(it.e); 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 let ty = self.insert_type_vars_shallow(ty); diff --git a/crates/hir-ty/src/infer/mutability.rs b/crates/hir-ty/src/infer/mutability.rs index b8a1af96fba6..763f4ed2f960 100644 --- a/crates/hir-ty/src/infer/mutability.rs +++ b/crates/hir-ty/src/infer/mutability.rs @@ -3,7 +3,10 @@ use chalk_ir::Mutability; 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, }; use hir_expand::name; @@ -37,6 +40,13 @@ impl InferenceContext<'_> { Expr::Missing => (), Expr::InlineAsm(e) => self.infer_mut_expr_without_adjust(e.e, Mutability::Not), 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 } => { self.infer_mut_expr(condition, Mutability::Not); self.infer_mut_expr(then_branch, Mutability::Not); diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs index b6408cea502a..9cc98684bf34 100644 --- a/crates/hir-ty/src/mir/lower.rs +++ b/crates/hir-ty/src/mir/lower.rs @@ -376,6 +376,9 @@ impl<'ctx> MirLowerCtx<'ctx> { Expr::InlineAsm(_) => { not_supported!("builtin#asm") } + Expr::FormatArgs(_) => { + not_supported!("builtin#format_args") + } Expr::Missing => { if let DefWithBodyId::FunctionId(f) = self.owner { let assoc = f.lookup(self.db.upcast()); diff --git a/crates/hir-ty/src/tests/simple.rs b/crates/hir-ty/src/tests/simple.rs index e7cb7cd41746..0c73370a686c 100644 --- a/crates/hir-ty/src/tests/simple.rs +++ b/crates/hir-ty/src/tests/simple.rs @@ -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 + "#]], + ); +} diff --git a/crates/parser/src/grammar/expressions/atom.rs b/crates/parser/src/grammar/expressions/atom.rs index e13284d1b7ae..4197f248e0a9 100644 --- a/crates/parser/src/grammar/expressions/atom.rs +++ b/crates/parser/src/grammar/expressions/atom.rs @@ -219,7 +219,7 @@ fn tuple_expr(p: &mut Parser<'_>) -> CompletedMarker { // test builtin_expr // fn foo() { // builtin#asm(0); -// builtin#format_args(0); +// builtin#format_args("", 0, 1, a = 2 + 3, a + b); // builtin#offset_of(Foo, bar.baz.0); // } fn builtin_expr(p: &mut Parser<'_>) -> Option { @@ -249,6 +249,24 @@ fn builtin_expr(p: &mut Parser<'_>) -> Option { p.bump_remap(T![format_args]); p.expect(T!['(']); 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![')']); Some(m.complete(p, FORMAT_ARGS_EXPR)) } else if p.at_contextual_kw(T![asm]) { diff --git a/crates/parser/src/syntax_kind/generated.rs b/crates/parser/src/syntax_kind/generated.rs index 3e31e4628bd4..db5278f89d56 100644 --- a/crates/parser/src/syntax_kind/generated.rs +++ b/crates/parser/src/syntax_kind/generated.rs @@ -210,6 +210,7 @@ pub enum SyntaxKind { OFFSET_OF_EXPR, ASM_EXPR, FORMAT_ARGS_EXPR, + FORMAT_ARGS_ARG, CALL_EXPR, INDEX_EXPR, METHOD_CALL_EXPR, diff --git a/crates/parser/test_data/parser/inline/ok/0207_builtin_expr.rs b/crates/parser/test_data/parser/inline/ok/0207_builtin_expr.rs index dbad0a91df94..14431b0210ea 100644 --- a/crates/parser/test_data/parser/inline/ok/0207_builtin_expr.rs +++ b/crates/parser/test_data/parser/inline/ok/0207_builtin_expr.rs @@ -1,5 +1,5 @@ fn foo() { builtin#asm(0); - builtin#format_args(0); + builtin#format_args("", 0, 1, a = 2 + 3, a + b); builtin#offset_of(Foo, bar.baz.0); } diff --git a/crates/syntax/rust.ungram b/crates/syntax/rust.ungram index 2ce609b97a65..3603560d35c3 100644 --- a/crates/syntax/rust.ungram +++ b/crates/syntax/rust.ungram @@ -382,7 +382,13 @@ AsmExpr = Attr* 'builtin' '#' 'asm' '(' Expr ')' FormatArgsExpr = - Attr* 'builtin' '#' 'format_args' '(' ')' + Attr* 'builtin' '#' 'format_args' '(' + template:Expr + (',' args:(FormatArgsArg (',' FormatArgsArg)* ','?)? )? + ')' + +FormatArgsArg = + (Name '=')? Expr MacroExpr = MacroCall diff --git a/crates/syntax/src/ast/generated/nodes.rs b/crates/syntax/src/ast/generated/nodes.rs index 1c5e2282ecb1..7ba0d4dc656f 100644 --- a/crates/syntax/src/ast/generated/nodes.rs +++ b/crates/syntax/src/ast/generated/nodes.rs @@ -931,6 +931,9 @@ impl FormatArgsExpr { support::token(&self.syntax, T![format_args]) } pub fn l_paren_token(&self) -> Option { support::token(&self.syntax, T!['(']) } + pub fn template(&self) -> Option { support::child(&self.syntax) } + pub fn comma_token(&self) -> Option { support::token(&self.syntax, T![,]) } + pub fn args(&self) -> AstChildren { support::children(&self.syntax) } pub fn r_paren_token(&self) -> Option { support::token(&self.syntax, T![')']) } } @@ -1163,6 +1166,16 @@ impl UnderscoreExpr { pub fn underscore_token(&self) -> Option { 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 { support::token(&self.syntax, T![=]) } + pub fn expr(&self) -> Option { support::child(&self.syntax) } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct StmtList { pub(crate) syntax: SyntaxNode, @@ -2855,6 +2868,17 @@ impl AstNode for UnderscoreExpr { } 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 { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} impl AstNode for StmtList { fn can_cast(kind: SyntaxKind) -> bool { kind == STMT_LIST } fn cast(syntax: SyntaxNode) -> Option { @@ -4254,6 +4278,7 @@ impl AstNode for AnyHasName { | VARIANT | CONST_PARAM | TYPE_PARAM + | FORMAT_ARGS_ARG | IDENT_PAT ) } @@ -4860,6 +4885,11 @@ impl std::fmt::Display for UnderscoreExpr { 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 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) diff --git a/crates/syntax/src/tests/ast_src.rs b/crates/syntax/src/tests/ast_src.rs index 2c1d832d1e57..341bda892ba1 100644 --- a/crates/syntax/src/tests/ast_src.rs +++ b/crates/syntax/src/tests/ast_src.rs @@ -169,6 +169,7 @@ pub(crate) const KINDS_SRC: KindsSrc<'_> = KindsSrc { "OFFSET_OF_EXPR", "ASM_EXPR", "FORMAT_ARGS_EXPR", + "FORMAT_ARGS_ARG", // postfix "CALL_EXPR", "INDEX_EXPR", From e243a03da1cef50148d1c2da5e64afa1656e444a Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 6 Sep 2023 15:21:41 +0200 Subject: [PATCH 2/7] Desugar builtin#format_args --- crates/hir-def/src/body/lower.rs | 450 ++++++++++++++++++++++--- crates/hir-def/src/body/pretty.rs | 38 +-- crates/hir-def/src/body/tests.rs | 89 ++++- crates/hir-def/src/hir.rs | 10 - crates/hir-def/src/hir/format_args.rs | 17 +- crates/hir-def/src/item_tree.rs | 2 +- crates/hir-def/src/item_tree/pretty.rs | 64 ++-- crates/hir-def/src/lang_item.rs | 13 +- crates/hir-def/src/path.rs | 31 +- crates/hir-def/src/pretty.rs | 72 +++- crates/hir-def/src/resolver.rs | 45 ++- crates/hir-expand/src/name.rs | 19 ++ crates/hir-ty/src/infer/closure.rs | 13 +- crates/hir-ty/src/infer/expr.rs | 22 +- crates/hir-ty/src/infer/mutability.rs | 12 +- crates/hir-ty/src/infer/path.rs | 23 +- crates/hir-ty/src/mir/lower.rs | 3 - crates/hir-ty/src/tests/simple.rs | 17 +- crates/test-utils/src/minicore.rs | 86 ++++- 19 files changed, 783 insertions(+), 243 deletions(-) diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs index bb0127c9ef61..e6fdbba9cbee 100644 --- a/crates/hir-def/src/body/lower.rs +++ b/crates/hir-def/src/body/lower.rs @@ -25,13 +25,16 @@ use triomphe::Arc; use crate::{ body::{Body, BodyDiagnostic, BodySourceMap, ExprPtr, LabelPtr, PatPtr}, + builtin_type::BuiltinUint, data::adt::StructKind, db::DefDatabase, expander::Expander, hir::{ dummy_expr_id, format_args::{ - self, FormatArgs, FormatArgument, FormatArgumentKind, FormatArgumentsCollector, + self, FormatAlignment, FormatArgsPiece, FormatArgument, FormatArgumentKind, + FormatArgumentsCollector, FormatCount, FormatDebugHex, FormatOptions, + FormatPlaceholder, FormatSign, FormatTrait, }, Array, Binding, BindingAnnotation, BindingId, BindingProblems, CaptureBy, ClosureKind, Expr, ExprId, InlineAsm, Label, LabelId, Literal, LiteralOrConst, MatchArm, Movability, @@ -46,6 +49,8 @@ use crate::{ AdtId, BlockId, BlockLoc, ConstBlockLoc, DefWithBodyId, ModuleDefId, UnresolvedMacro, }; +type FxIndexSet = indexmap::IndexSet>; + pub(super) fn lower( db: &dyn DefDatabase, owner: DefWithBodyId, @@ -661,50 +666,10 @@ impl ExprCollector<'_> { let fields = e.fields().map(|it| it.as_name()).collect(); self.alloc_expr(Expr::OffsetOf(OffsetOf { container, fields }), syntax_ptr) } - 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, - ) - } + ast::Expr::FormatArgsExpr(f) => match self.collect_format_args(f, syntax_ptr) { + Ok(value) => value, + Err(value) => return value, + }, }) } @@ -1604,6 +1569,395 @@ impl ExprCollector<'_> { } } // endregion: labels + + // region: format + + fn collect_format_args( + &mut self, + f: ast::FormatArgsExpr, + syntax_ptr: AstPtr, + ) -> Result, Option>> { + 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); + + // FIXME: We shouldn't allocate this one, just resolve and expand the macros to fetch the + // string literal! + let expr = self.collect_expr_opt(template); + + let fmt = 'b: { + 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() { + break 'b format_args::parse( + expr, + &s, + fmt_snippet, + args, + is_direct_literal, + |name| self.alloc_expr_desugared(Expr::Path(Path::from(name))), + ); + } + } + } + todo!(); + }; + + // Create a list of all _unique_ (argument, format trait) combinations. + // E.g. "{0} {0:x} {0} {1}" -> [(0, Display), (0, LowerHex), (1, Display)] + let mut argmap = FxIndexSet::default(); + for piece in fmt.template.iter() { + let FormatArgsPiece::Placeholder(placeholder) = piece else { continue }; + if let Ok(index) = placeholder.argument.index { + argmap.insert((index, ArgumentType::Format(placeholder.format_trait))); + } + } + + let lit_pieces = + fmt.template + .iter() + .enumerate() + .filter_map(|(i, piece)| { + match piece { + FormatArgsPiece::Literal(s) => Some( + self.alloc_expr_desugared(Expr::Literal(Literal::String(s.clone()))), + ), + &FormatArgsPiece::Placeholder(_) => { + // Inject empty string before placeholders when not already preceded by a literal piece. + if i == 0 + || matches!(fmt.template[i - 1], FormatArgsPiece::Placeholder(_)) + { + Some(self.alloc_expr_desugared(Expr::Literal(Literal::String( + "".into(), + )))) + } else { + None + } + } + } + }) + .collect(); + let lit_pieces = self.alloc_expr_desugared(Expr::Array(Array::ElementList { + elements: lit_pieces, + is_assignee_expr: false, + })); + let lit_pieces = self.alloc_expr_desugared(Expr::Ref { + expr: lit_pieces, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }); + let format_options = { + // Generate: + // &[format_spec_0, format_spec_1, format_spec_2] + let elements = fmt + .template + .iter() + .filter_map(|piece| { + let FormatArgsPiece::Placeholder(placeholder) = piece else { return None }; + Some(self.make_format_spec(placeholder, &mut argmap)) + }) + .collect(); + let array = self.alloc_expr_desugared(Expr::Array(Array::ElementList { + elements, + is_assignee_expr: false, + })); + self.alloc_expr_desugared(Expr::Ref { + expr: array, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }) + }; + let arguments = &*fmt.arguments.arguments; + + let args = if arguments.is_empty() { + let expr = self.alloc_expr_desugared(Expr::Array(Array::ElementList { + elements: Box::default(), + is_assignee_expr: false, + })); + self.alloc_expr_desugared(Expr::Ref { + expr, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }) + } else { + // Generate: + // &match (&arg0, &arg1, &…) { + // args => [ + // ::new_display(args.0), + // ::new_lower_hex(args.1), + // ::new_debug(args.0), + // … + // ] + // } + let args = argmap + .iter() + .map(|&(arg_index, ty)| { + let arg = self.alloc_expr_desugared(Expr::Ref { + expr: arguments[arg_index].expr, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }); + self.make_argument(arg, ty) + }) + .collect(); + let array = self.alloc_expr_desugared(Expr::Array(Array::ElementList { + elements: args, + is_assignee_expr: false, + })); + self.alloc_expr_desugared(Expr::Ref { + expr: array, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }) + }; + + // Generate: + // ::new_v1_formatted( + // lit_pieces, + // args, + // format_options, + // unsafe { ::core::fmt::UnsafeArg::new() } + // ) + + let Some(new_v1_formatted) = + LangItem::FormatArguments.ty_rel_path(self.db, self.krate, name![new_v1_formatted]) + else { + todo!() + }; + let Some(unsafe_arg_new) = + LangItem::FormatUnsafeArg.ty_rel_path(self.db, self.krate, name![new]) + else { + todo!() + }; + let new_v1_formatted = self.alloc_expr_desugared(Expr::Path(new_v1_formatted)); + + let unsafe_arg_new = self.alloc_expr_desugared(Expr::Path(unsafe_arg_new)); + let unsafe_arg_new = self.alloc_expr_desugared(Expr::Call { + callee: unsafe_arg_new, + args: Box::default(), + is_assignee_expr: false, + }); + let unsafe_arg_new = self.alloc_expr_desugared(Expr::Unsafe { + id: None, + statements: Box::default(), + tail: Some(unsafe_arg_new), + }); + + Ok(self.alloc_expr( + Expr::Call { + callee: new_v1_formatted, + args: Box::new([lit_pieces, args, format_options, unsafe_arg_new]), + is_assignee_expr: false, + }, + syntax_ptr, + )) + } + + /// Generate a hir expression for a format_args placeholder specification. + /// + /// Generates + /// + /// ```text + /// ::…, // alignment + /// …u32, // flags + /// , // width + /// , // precision + /// ) + /// ``` + fn make_format_spec( + &mut self, + placeholder: &FormatPlaceholder, + argmap: &mut FxIndexSet<(usize, ArgumentType)>, + ) -> ExprId { + let position = match placeholder.argument.index { + Ok(arg_index) => { + let (i, _) = + argmap.insert_full((arg_index, ArgumentType::Format(placeholder.format_trait))); + self.alloc_expr_desugared(Expr::Literal(Literal::Uint( + i as u128, + Some(BuiltinUint::Usize), + ))) + } + Err(_) => self.missing_expr(), + }; + let &FormatOptions { + ref width, + ref precision, + alignment, + fill, + sign, + alternate, + zero_pad, + debug_hex, + } = &placeholder.format_options; + let fill = self.alloc_expr_desugared(Expr::Literal(Literal::Char(fill.unwrap_or(' ')))); + + let Some(align) = LangItem::FormatAlignment.ty_rel_path( + self.db, + self.krate, + match alignment { + Some(FormatAlignment::Left) => name![Left], + Some(FormatAlignment::Right) => name![Right], + Some(FormatAlignment::Center) => name![Center], + None => name![Unknown], + }, + ) else { + todo!() + }; + let align = self.alloc_expr_desugared(Expr::Path(align)); + // This needs to match `Flag` in library/core/src/fmt/rt.rs. + let flags: u32 = ((sign == Some(FormatSign::Plus)) as u32) + | ((sign == Some(FormatSign::Minus)) as u32) << 1 + | (alternate as u32) << 2 + | (zero_pad as u32) << 3 + | ((debug_hex == Some(FormatDebugHex::Lower)) as u32) << 4 + | ((debug_hex == Some(FormatDebugHex::Upper)) as u32) << 5; + let flags = self.alloc_expr_desugared(Expr::Literal(Literal::Uint( + flags as u128, + Some(BuiltinUint::U32), + ))); + let precision = self.make_count(&precision, argmap); + let width = self.make_count(&width, argmap); + let Some(format_placeholder_new) = + LangItem::FormatPlaceholder.ty_rel_path(self.db, self.krate, name![new]) + else { + todo!() + }; + let format_placeholder_new = self.alloc_expr_desugared(Expr::Path(format_placeholder_new)); + self.alloc_expr_desugared(Expr::Call { + callee: format_placeholder_new, + args: Box::new([position, fill, align, flags, precision, width]), + is_assignee_expr: false, + }) + } + + /// Generate a hir expression for a format_args Count. + /// + /// Generates: + /// + /// ```text + /// ::Is(…) + /// ``` + /// + /// or + /// + /// ```text + /// ::Param(…) + /// ``` + /// + /// or + /// + /// ```text + /// ::Implied + /// ``` + fn make_count( + &mut self, + count: &Option, + argmap: &mut FxIndexSet<(usize, ArgumentType)>, + ) -> ExprId { + match count { + Some(FormatCount::Literal(n)) => { + let Some(count_is) = + LangItem::FormatCount.ty_rel_path(self.db, self.krate, name![Is]) + else { + todo!() + }; + let count_is = self.alloc_expr_desugared(Expr::Path(count_is)); + let args = self.alloc_expr_desugared(Expr::Literal(Literal::Uint( + *n as u128, + Some(BuiltinUint::Usize), + ))); + self.alloc_expr_desugared(Expr::Call { + callee: count_is, + args: Box::new([args]), + is_assignee_expr: false, + }) + } + Some(FormatCount::Argument(arg)) => { + if let Ok(arg_index) = arg.index { + let (i, _) = argmap.insert_full((arg_index, ArgumentType::Usize)); + let Some(count_param) = + LangItem::FormatCount.ty_rel_path(self.db, self.krate, name![Param]) + else { + todo!() + }; + let count_param = self.alloc_expr_desugared(Expr::Path(count_param)); + let args = self.alloc_expr_desugared(Expr::Literal(Literal::Uint( + i as u128, + Some(BuiltinUint::Usize), + ))); + self.alloc_expr_desugared(Expr::Call { + callee: count_param, + args: Box::new([args]), + is_assignee_expr: false, + }) + } else { + self.missing_expr() + } + } + None => { + let Some(count_param) = + LangItem::FormatCount.ty_rel_path(self.db, self.krate, name![Implied]) + else { + todo!() + }; + self.alloc_expr_desugared(Expr::Path(count_param)) + } + } + } + + /// Generate a hir expression representing an argument to a format_args invocation. + /// + /// Generates: + /// + /// ```text + /// ::new_…(arg) + /// ``` + fn make_argument(&mut self, arg: ExprId, ty: ArgumentType) -> ExprId { + use ArgumentType::*; + use FormatTrait::*; + let Some(new_fn) = LangItem::FormatArgument.ty_rel_path( + self.db, + self.krate, + match ty { + Format(Display) => name![new_display], + Format(Debug) => name![new_debug], + Format(LowerExp) => name![new_lower_exp], + Format(UpperExp) => name![new_upper_exp], + Format(Octal) => name![new_octal], + Format(Pointer) => name![new_pointer], + Format(Binary) => name![new_binary], + Format(LowerHex) => name![new_lower_hex], + Format(UpperHex) => name![new_upper_hex], + Usize => name![from_usize], + }, + ) else { + todo!() + }; + let new_fn = self.alloc_expr_desugared(Expr::Path(new_fn)); + self.alloc_expr_desugared(Expr::Call { + callee: new_fn, + args: Box::new([arg]), + is_assignee_expr: false, + }) + } + // endregion: format } fn pat_literal_to_hir(lit: &ast::LiteralPat) -> Option<(Literal, ast::Literal)> { @@ -1679,3 +2033,9 @@ fn comma_follows_token(t: Option) -> bool { (|| syntax::algo::skip_trivia_token(t?.next_token()?, syntax::Direction::Next))() .map_or(false, |it| it.kind() == syntax::T![,]) } + +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +enum ArgumentType { + Format(FormatTrait), + Usize, +} diff --git a/crates/hir-def/src/body/pretty.rs b/crates/hir-def/src/body/pretty.rs index b67ed2fb3857..31c2e9c1fb98 100644 --- a/crates/hir-def/src/body/pretty.rs +++ b/crates/hir-def/src/body/pretty.rs @@ -2,7 +2,6 @@ use std::fmt::{self, Write}; -use hir_expand::db::ExpandDatabase; use itertools::Itertools; use syntax::ast::HasName; @@ -52,8 +51,7 @@ pub(super) fn print_body_hir(db: &dyn DefDatabase, body: &Body, owner: DefWithBo } }; - let mut p = - Printer { db: db.upcast(), body, buf: header, indent_level: 0, needs_indent: false }; + let mut p = Printer { db, body, buf: header, indent_level: 0, needs_indent: false }; if let DefWithBodyId::FunctionId(it) = owner { p.buf.push('('); body.params.iter().zip(&db.function_data(it).params).for_each(|(¶m, ty)| { @@ -77,8 +75,7 @@ pub(super) fn print_expr_hir( _owner: DefWithBodyId, expr: ExprId, ) -> String { - let mut p = - Printer { db: db.upcast(), body, buf: String::new(), indent_level: 0, needs_indent: false }; + let mut p = Printer { db, body, buf: String::new(), indent_level: 0, needs_indent: false }; p.print_expr(expr); p.buf } @@ -99,7 +96,7 @@ macro_rules! wln { } struct Printer<'a> { - db: &'a dyn ExpandDatabase, + db: &'a dyn DefDatabase, body: &'a Body, buf: String, indent_level: usize, @@ -156,18 +153,16 @@ impl Printer<'_> { Expr::Missing => w!(self, "�"), Expr::Underscore => w!(self, "_"), Expr::InlineAsm(_) => w!(self, "builtin#asm(_)"), - Expr::FormatArgs(_fmt_args) => { - w!(self, "builtin#format_args("); - // FIXME - w!(self, ")"); - } Expr::OffsetOf(offset_of) => { w!(self, "builtin#offset_of("); self.print_type_ref(&offset_of.container); w!( self, ", {})", - offset_of.fields.iter().format_with(".", |field, f| f(&field.display(self.db))) + offset_of + .fields + .iter() + .format_with(".", |field, f| f(&field.display(self.db.upcast()))) ); } Expr::Path(path) => self.print_path(path), @@ -189,7 +184,7 @@ impl Printer<'_> { } Expr::Loop { body, label } => { if let Some(lbl) = label { - w!(self, "{}: ", self.body[*lbl].name.display(self.db)); + w!(self, "{}: ", self.body[*lbl].name.display(self.db.upcast())); } w!(self, "loop "); self.print_expr(*body); @@ -209,7 +204,7 @@ impl Printer<'_> { } Expr::MethodCall { receiver, method_name, args, generic_args } => { self.print_expr(*receiver); - w!(self, ".{}", method_name.display(self.db)); + w!(self, ".{}", method_name.display(self.db.upcast())); if let Some(args) = generic_args { w!(self, "::<"); print_generic_args(self.db, args, self).unwrap(); @@ -247,13 +242,13 @@ impl Printer<'_> { Expr::Continue { label } => { w!(self, "continue"); if let Some(lbl) = label { - w!(self, " {}", self.body[*lbl].name.display(self.db)); + w!(self, " {}", self.body[*lbl].name.display(self.db.upcast())); } } Expr::Break { expr, label } => { w!(self, "break"); if let Some(lbl) = label { - w!(self, " {}", self.body[*lbl].name.display(self.db)); + w!(self, " {}", self.body[*lbl].name.display(self.db.upcast())); } if let Some(expr) = expr { self.whitespace(); @@ -292,7 +287,7 @@ impl Printer<'_> { w!(self, "{{"); self.indented(|p| { for field in &**fields { - w!(p, "{}: ", field.name.display(self.db)); + w!(p, "{}: ", field.name.display(self.db.upcast())); p.print_expr(field.expr); wln!(p, ","); } @@ -309,7 +304,7 @@ impl Printer<'_> { } Expr::Field { expr, name } => { self.print_expr(*expr); - w!(self, ".{}", name.display(self.db)); + w!(self, ".{}", name.display(self.db.upcast())); } Expr::Await { expr } => { self.print_expr(*expr); @@ -447,7 +442,8 @@ impl Printer<'_> { } Expr::Literal(lit) => self.print_literal(lit), Expr::Block { id: _, statements, tail, label } => { - let label = label.map(|lbl| format!("{}: ", self.body[lbl].name.display(self.db))); + let label = + label.map(|lbl| format!("{}: ", self.body[lbl].name.display(self.db.upcast()))); self.print_block(label.as_deref(), statements, tail); } Expr::Unsafe { id: _, statements, tail } => { @@ -523,7 +519,7 @@ impl Printer<'_> { w!(self, " {{"); self.indented(|p| { for arg in args.iter() { - w!(p, "{}: ", arg.name.display(self.db)); + w!(p, "{}: ", arg.name.display(self.db.upcast())); p.print_pat(arg.pat); wln!(p, ","); } @@ -682,6 +678,6 @@ impl Printer<'_> { BindingAnnotation::Ref => "ref ", BindingAnnotation::RefMut => "ref mut ", }; - w!(self, "{}{}", mode, name.display(self.db)); + w!(self, "{}{}", mode, name.display(self.db.upcast())); } } diff --git a/crates/hir-def/src/body/tests.rs b/crates/hir-def/src/body/tests.rs index d5582011645d..76880bf68ed7 100644 --- a/crates/hir-def/src/body/tests.rs +++ b/crates/hir-def/src/body/tests.rs @@ -1,13 +1,13 @@ mod block; use base_db::{fixture::WithFixture, SourceDatabase}; -use expect_test::Expect; +use expect_test::{expect, Expect}; use crate::{test_db::TestDB, ModuleDefId}; use super::*; -fn lower(ra_fixture: &str) -> Arc { +fn lower(ra_fixture: &str) -> (TestDB, Arc, DefWithBodyId) { let db = TestDB::with_files(ra_fixture); let krate = db.crate_graph().iter().next().unwrap(); @@ -21,8 +21,10 @@ fn lower(ra_fixture: &str) -> Arc { } } } + let fn_def = fn_def.unwrap().into(); - db.body(fn_def.unwrap().into()) + let body = db.body(fn_def); + (db, body, fn_def) } fn def_map_at(ra_fixture: &str) -> String { @@ -138,3 +140,84 @@ mod m { "#, ); } + +#[test] +fn desugar_builtin_format_args() { + // Regression test for a path resolution bug introduced with inner item handling. + let (db, body, def) = lower( + r#" +//- minicore: fmt +fn main() { + let are = "are"; + let count = 10; + builtin#format_args("hello {count:02} {} friends, we {are:?} {0}{last}", "fancy", last = "!"); +} +"#, + ); + + expect![[r#" + fn main() { + let are = "are"; + let count = 10; + builtin#lang(Arguments::new_v1_formatted)( + &[ + "\"hello ", " ", " friends, we ", " ", "", "\"", + ], + &[ + builtin#lang(Argument::new_display)( + &count, + ), builtin#lang(Argument::new_display)( + &"fancy", + ), builtin#lang(Argument::new_debug)( + &are, + ), builtin#lang(Argument::new_display)( + &"!", + ), + ], + &[ + builtin#lang(Placeholder::new)( + 0usize, + ' ', + builtin#lang(Alignment::Unknown), + 8u32, + builtin#lang(Count::Implied), + builtin#lang(Count::Is)( + 2usize, + ), + ), builtin#lang(Placeholder::new)( + 1usize, + ' ', + builtin#lang(Alignment::Unknown), + 0u32, + builtin#lang(Count::Implied), + builtin#lang(Count::Implied), + ), builtin#lang(Placeholder::new)( + 2usize, + ' ', + builtin#lang(Alignment::Unknown), + 0u32, + builtin#lang(Count::Implied), + builtin#lang(Count::Implied), + ), builtin#lang(Placeholder::new)( + 1usize, + ' ', + builtin#lang(Alignment::Unknown), + 0u32, + builtin#lang(Count::Implied), + builtin#lang(Count::Implied), + ), builtin#lang(Placeholder::new)( + 3usize, + ' ', + builtin#lang(Alignment::Unknown), + 0u32, + builtin#lang(Count::Implied), + builtin#lang(Count::Implied), + ), + ], + unsafe { + builtin#lang(UnsafeArg::new)() + }, + ); + }"#]] + .assert_eq(&body.pretty_print(&db, def)) +} diff --git a/crates/hir-def/src/hir.rs b/crates/hir-def/src/hir.rs index 98220de388e0..591ee77c70a4 100644 --- a/crates/hir-def/src/hir.rs +++ b/crates/hir-def/src/hir.rs @@ -25,7 +25,6 @@ use syntax::ast; use crate::{ builtin_type::{BuiltinFloat, BuiltinInt, BuiltinUint}, - hir::format_args::{FormatArgs, FormatArgumentKind}, path::{GenericArgs, Path}, type_ref::{Mutability, Rawness, TypeRef}, BlockId, ConstBlockId, @@ -284,7 +283,6 @@ pub enum Expr { Underscore, OffsetOf(OffsetOf), InlineAsm(InlineAsm), - FormatArgs(FormatArgs), } #[derive(Debug, Clone, PartialEq, Eq)] @@ -358,14 +356,6 @@ impl Expr { Expr::Missing => {} Expr::Path(_) | Expr::OffsetOf(_) => {} 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 } => { f(*condition); f(*then_branch); diff --git a/crates/hir-def/src/hir/format_args.rs b/crates/hir-def/src/hir/format_args.rs index 8fa8b7246bcf..6197ee164942 100644 --- a/crates/hir-def/src/hir/format_args.rs +++ b/crates/hir-def/src/hir/format_args.rs @@ -6,7 +6,7 @@ use syntax::{ AstToken, SmolStr, TextRange, }; -use crate::hir::{dummy_expr_id, ExprId}; +use crate::hir::ExprId; mod parse; @@ -31,7 +31,7 @@ pub enum FormatArgsPiece { Placeholder(FormatPlaceholder), } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Debug, Clone, PartialEq, Eq)] pub struct FormatPlaceholder { /// Index into [`FormatArgs::arguments`]. pub argument: FormatArgPosition, @@ -43,7 +43,7 @@ pub struct FormatPlaceholder { pub format_options: FormatOptions, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Copy, Debug, Clone, PartialEq, Eq)] pub struct FormatArgPosition { /// Which argument this position refers to (Ok), /// or would've referred to if it existed (Err). @@ -64,7 +64,7 @@ pub enum FormatArgPositionKind { Named, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] pub enum FormatTrait { /// `{}` Display, @@ -86,7 +86,7 @@ pub enum FormatTrait { UpperHex, } -#[derive(Clone, Default, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] pub struct FormatOptions { /// The width. E.g. `{:5}` or `{:width$}`. pub width: Option, @@ -131,7 +131,7 @@ pub enum FormatAlignment { Center, } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum FormatCount { /// `{:5}` or `{:.5}` Literal(usize), @@ -171,6 +171,7 @@ pub(crate) fn parse( fmt_snippet: Option, mut args: FormatArgumentsCollector, is_direct_literal: bool, + mut synth: impl FnMut(Name) -> ExprId, ) -> FormatArgs { let text = s.text(); let str_style = match s.quote_offsets() { @@ -252,10 +253,10 @@ pub(crate) fn parse( // FIXME: Diagnose } Ok(args.add(FormatArgument { - kind: FormatArgumentKind::Captured(name), + kind: FormatArgumentKind::Captured(name.clone()), // FIXME: This is problematic, we might want to synthesize a dummy // expression proper and/or desugar these. - expr: dummy_expr_id(), + expr: synth(name), })) } } diff --git a/crates/hir-def/src/item_tree.rs b/crates/hir-def/src/item_tree.rs index 3e1922750b9a..4c812b62a46a 100644 --- a/crates/hir-def/src/item_tree.rs +++ b/crates/hir-def/src/item_tree.rs @@ -177,7 +177,7 @@ impl ItemTree { } pub fn pretty_print(&self, db: &dyn DefDatabase) -> String { - pretty::print_item_tree(db.upcast(), self) + pretty::print_item_tree(db, self) } fn data(&self) -> &ItemTreeData { diff --git a/crates/hir-def/src/item_tree/pretty.rs b/crates/hir-def/src/item_tree/pretty.rs index 4b852dd613e7..417bd37c8a9b 100644 --- a/crates/hir-def/src/item_tree/pretty.rs +++ b/crates/hir-def/src/item_tree/pretty.rs @@ -2,8 +2,6 @@ use std::fmt::{self, Write}; -use hir_expand::db::ExpandDatabase; - use crate::{ generics::{TypeOrConstParamData, WherePredicate, WherePredicateTypeTarget}, pretty::{print_path, print_type_bounds, print_type_ref}, @@ -12,7 +10,7 @@ use crate::{ use super::*; -pub(super) fn print_item_tree(db: &dyn ExpandDatabase, tree: &ItemTree) -> String { +pub(super) fn print_item_tree(db: &dyn DefDatabase, tree: &ItemTree) -> String { let mut p = Printer { db, tree, buf: String::new(), indent_level: 0, needs_indent: true }; if let Some(attrs) = tree.attrs.get(&AttrOwner::TopLevel) { @@ -45,7 +43,7 @@ macro_rules! wln { } struct Printer<'a> { - db: &'a dyn ExpandDatabase, + db: &'a dyn DefDatabase, tree: &'a ItemTree, buf: String, indent_level: usize, @@ -91,7 +89,7 @@ impl Printer<'_> { self, "#{}[{}{}]{}", inner, - attr.path.display(self.db), + attr.path.display(self.db.upcast()), attr.input.as_ref().map(|it| it.to_string()).unwrap_or_default(), separated_by, ); @@ -106,7 +104,7 @@ impl Printer<'_> { fn print_visibility(&mut self, vis: RawVisibilityId) { match &self.tree[vis] { - RawVisibility::Module(path) => w!(self, "pub({}) ", path.display(self.db)), + RawVisibility::Module(path) => w!(self, "pub({}) ", path.display(self.db.upcast())), RawVisibility::Public => w!(self, "pub "), }; } @@ -121,7 +119,7 @@ impl Printer<'_> { let Field { visibility, name, type_ref, ast_id: _ } = &this.tree[field]; this.print_attrs_of(field, "\n"); this.print_visibility(*visibility); - w!(this, "{}: ", name.display(self.db)); + w!(this, "{}: ", name.display(self.db.upcast())); this.print_type_ref(type_ref); wln!(this, ","); } @@ -135,7 +133,7 @@ impl Printer<'_> { let Field { visibility, name, type_ref, ast_id: _ } = &this.tree[field]; this.print_attrs_of(field, "\n"); this.print_visibility(*visibility); - w!(this, "{}: ", name.display(self.db)); + w!(this, "{}: ", name.display(self.db.upcast())); this.print_type_ref(type_ref); wln!(this, ","); } @@ -168,20 +166,20 @@ impl Printer<'_> { fn print_use_tree(&mut self, use_tree: &UseTree) { match &use_tree.kind { UseTreeKind::Single { path, alias } => { - w!(self, "{}", path.display(self.db)); + w!(self, "{}", path.display(self.db.upcast())); if let Some(alias) = alias { w!(self, " as {}", alias); } } UseTreeKind::Glob { path } => { if let Some(path) = path { - w!(self, "{}::", path.display(self.db)); + w!(self, "{}::", path.display(self.db.upcast())); } w!(self, "*"); } UseTreeKind::Prefixed { prefix, list } => { if let Some(prefix) = prefix { - w!(self, "{}::", prefix.display(self.db)); + w!(self, "{}::", prefix.display(self.db.upcast())); } w!(self, "{{"); for (i, tree) in list.iter().enumerate() { @@ -209,7 +207,7 @@ impl Printer<'_> { ModItem::ExternCrate(it) => { let ExternCrate { name, alias, visibility, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - w!(self, "extern crate {}", name.display(self.db)); + w!(self, "extern crate {}", name.display(self.db.upcast())); if let Some(alias) = alias { w!(self, " as {}", alias); } @@ -256,7 +254,7 @@ impl Printer<'_> { if let Some(abi) = abi { w!(self, "extern \"{}\" ", abi); } - w!(self, "fn {}", name.display(self.db)); + w!(self, "fn {}", name.display(self.db.upcast())); self.print_generic_params(explicit_generic_params); w!(self, "("); if !params.is_empty() { @@ -290,7 +288,7 @@ impl Printer<'_> { ModItem::Struct(it) => { let Struct { visibility, name, fields, generic_params, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - w!(self, "struct {}", name.display(self.db)); + w!(self, "struct {}", name.display(self.db.upcast())); self.print_generic_params(generic_params); self.print_fields_and_where_clause(fields, generic_params); if matches!(fields, Fields::Record(_)) { @@ -302,7 +300,7 @@ impl Printer<'_> { ModItem::Union(it) => { let Union { name, visibility, fields, generic_params, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - w!(self, "union {}", name.display(self.db)); + w!(self, "union {}", name.display(self.db.upcast())); self.print_generic_params(generic_params); self.print_fields_and_where_clause(fields, generic_params); if matches!(fields, Fields::Record(_)) { @@ -314,14 +312,14 @@ impl Printer<'_> { ModItem::Enum(it) => { let Enum { name, visibility, variants, generic_params, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - w!(self, "enum {}", name.display(self.db)); + w!(self, "enum {}", name.display(self.db.upcast())); self.print_generic_params(generic_params); self.print_where_clause_and_opening_brace(generic_params); self.indented(|this| { for variant in variants.clone() { let Variant { name, fields, ast_id: _ } = &this.tree[variant]; this.print_attrs_of(variant, "\n"); - w!(this, "{}", name.display(self.db)); + w!(this, "{}", name.display(self.db.upcast())); this.print_fields(fields); wln!(this, ","); } @@ -333,7 +331,7 @@ impl Printer<'_> { self.print_visibility(*visibility); w!(self, "const "); match name { - Some(name) => w!(self, "{}", name.display(self.db)), + Some(name) => w!(self, "{}", name.display(self.db.upcast())), None => w!(self, "_"), } w!(self, ": "); @@ -347,7 +345,7 @@ impl Printer<'_> { if *mutable { w!(self, "mut "); } - w!(self, "{}: ", name.display(self.db)); + w!(self, "{}: ", name.display(self.db.upcast())); self.print_type_ref(type_ref); w!(self, " = _;"); wln!(self); @@ -369,7 +367,7 @@ impl Printer<'_> { if *is_auto { w!(self, "auto "); } - w!(self, "trait {}", name.display(self.db)); + w!(self, "trait {}", name.display(self.db.upcast())); self.print_generic_params(generic_params); self.print_where_clause_and_opening_brace(generic_params); self.indented(|this| { @@ -382,7 +380,7 @@ impl Printer<'_> { ModItem::TraitAlias(it) => { let TraitAlias { name, visibility, generic_params, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - w!(self, "trait {}", name.display(self.db)); + w!(self, "trait {}", name.display(self.db.upcast())); self.print_generic_params(generic_params); w!(self, " = "); self.print_where_clause(generic_params); @@ -415,7 +413,7 @@ impl Printer<'_> { let TypeAlias { name, visibility, bounds, type_ref, generic_params, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - w!(self, "type {}", name.display(self.db)); + w!(self, "type {}", name.display(self.db.upcast())); self.print_generic_params(generic_params); if !bounds.is_empty() { w!(self, ": "); @@ -432,7 +430,7 @@ impl Printer<'_> { ModItem::Mod(it) => { let Mod { name, visibility, kind, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - w!(self, "mod {}", name.display(self.db)); + w!(self, "mod {}", name.display(self.db.upcast())); match kind { ModKind::Inline { items } => { w!(self, " {{"); @@ -450,16 +448,16 @@ impl Printer<'_> { } ModItem::MacroCall(it) => { let MacroCall { path, ast_id: _, expand_to: _ } = &self.tree[it]; - wln!(self, "{}!(...);", path.display(self.db)); + wln!(self, "{}!(...);", path.display(self.db.upcast())); } ModItem::MacroRules(it) => { let MacroRules { name, ast_id: _ } = &self.tree[it]; - wln!(self, "macro_rules! {} {{ ... }}", name.display(self.db)); + wln!(self, "macro_rules! {} {{ ... }}", name.display(self.db.upcast())); } ModItem::MacroDef(it) => { let MacroDef { name, visibility, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - wln!(self, "macro {} {{ ... }}", name.display(self.db)); + wln!(self, "macro {} {{ ... }}", name.display(self.db.upcast())); } } @@ -491,7 +489,7 @@ impl Printer<'_> { } first = false; self.print_attrs_of(idx, " "); - w!(self, "{}", lt.name.display(self.db)); + w!(self, "{}", lt.name.display(self.db.upcast())); } for (idx, x) in params.type_or_consts.iter() { if !first { @@ -501,11 +499,11 @@ impl Printer<'_> { self.print_attrs_of(idx, " "); match x { TypeOrConstParamData::TypeParamData(ty) => match &ty.name { - Some(name) => w!(self, "{}", name.display(self.db)), + Some(name) => w!(self, "{}", name.display(self.db.upcast())), None => w!(self, "_anon_{}", idx.into_raw()), }, TypeOrConstParamData::ConstParamData(konst) => { - w!(self, "const {}: ", konst.name.display(self.db)); + w!(self, "const {}: ", konst.name.display(self.db.upcast())); self.print_type_ref(&konst.ty); } } @@ -540,8 +538,8 @@ impl Printer<'_> { wln!( this, "{}: {},", - target.name.display(self.db), - bound.name.display(self.db) + target.name.display(self.db.upcast()), + bound.name.display(self.db.upcast()) ); continue; } @@ -551,7 +549,7 @@ impl Printer<'_> { if i != 0 { w!(this, ", "); } - w!(this, "{}", lt.display(self.db)); + w!(this, "{}", lt.display(self.db.upcast())); } w!(this, "> "); (target, bound) @@ -562,7 +560,7 @@ impl Printer<'_> { WherePredicateTypeTarget::TypeRef(ty) => this.print_type_ref(ty), WherePredicateTypeTarget::TypeOrConstParam(id) => { match ¶ms.type_or_consts[*id].name() { - Some(name) => w!(this, "{}", name.display(self.db)), + Some(name) => w!(this, "{}", name.display(self.db.upcast())), None => w!(this, "_anon_{}", id.into_raw()), } } diff --git a/crates/hir-def/src/lang_item.rs b/crates/hir-def/src/lang_item.rs index 627479bb7c1b..1ae6bd4c9194 100644 --- a/crates/hir-def/src/lang_item.rs +++ b/crates/hir-def/src/lang_item.rs @@ -2,6 +2,7 @@ //! //! This attribute to tell the compiler about semi built-in std library //! features, such as Fn family of traits. +use hir_expand::name::Name; use rustc_hash::FxHashMap; use syntax::SmolStr; use triomphe::Arc; @@ -238,7 +239,17 @@ impl LangItem { pub fn path(&self, db: &dyn DefDatabase, start_crate: CrateId) -> Option { let t = db.lang_item(start_crate, *self)?; - Some(Path::LangItem(t)) + Some(Path::LangItem(t, None)) + } + + pub fn ty_rel_path( + &self, + db: &dyn DefDatabase, + start_crate: CrateId, + seg: Name, + ) -> Option { + let t = db.lang_item(start_crate, *self)?; + Some(Path::LangItem(t, Some(seg))) } } diff --git a/crates/hir-def/src/path.rs b/crates/hir-def/src/path.rs index 06530cc7ebd7..3894172a5ad8 100644 --- a/crates/hir-def/src/path.rs +++ b/crates/hir-def/src/path.rs @@ -47,7 +47,7 @@ pub enum Path { }, /// A link to a lang item. It is used in desugaring of things like `it?`. We can show these /// links via a normal path since they might be private and not accessible in the usage place. - LangItem(LangItemTarget), + LangItem(LangItemTarget, Option), } /// Generic arguments to a path segment (e.g. the `i32` in `Option`). This @@ -122,33 +122,40 @@ impl Path { pub fn kind(&self) -> &PathKind { match self { Path::Normal { mod_path, .. } => &mod_path.kind, - Path::LangItem(_) => &PathKind::Abs, + Path::LangItem(..) => &PathKind::Abs, } } pub fn type_anchor(&self) -> Option<&TypeRef> { match self { Path::Normal { type_anchor, .. } => type_anchor.as_deref(), - Path::LangItem(_) => None, + Path::LangItem(..) => None, } } pub fn segments(&self) -> PathSegments<'_> { - let Path::Normal { mod_path, generic_args, .. } = self else { - return PathSegments { segments: &[], generic_args: None }; - }; - let s = - PathSegments { segments: mod_path.segments(), generic_args: generic_args.as_deref() }; - if let Some(generic_args) = s.generic_args { - assert_eq!(s.segments.len(), generic_args.len()); + match self { + Path::Normal { mod_path, generic_args, .. } => { + let s = PathSegments { + segments: mod_path.segments(), + generic_args: generic_args.as_deref(), + }; + if let Some(generic_args) = s.generic_args { + assert_eq!(s.segments.len(), generic_args.len()); + } + s + } + Path::LangItem(_, seg) => PathSegments { + segments: seg.as_ref().map_or(&[], |seg| std::slice::from_ref(seg)), + generic_args: None, + }, } - s } pub fn mod_path(&self) -> Option<&ModPath> { match self { Path::Normal { mod_path, .. } => Some(&mod_path), - Path::LangItem(_) => None, + Path::LangItem(..) => None, } } diff --git a/crates/hir-def/src/pretty.rs b/crates/hir-def/src/pretty.rs index 11d58a6ba09c..f4f5541e3733 100644 --- a/crates/hir-def/src/pretty.rs +++ b/crates/hir-def/src/pretty.rs @@ -2,18 +2,54 @@ use std::fmt::{self, Write}; -use hir_expand::{db::ExpandDatabase, mod_path::PathKind}; +use hir_expand::mod_path::PathKind; use intern::Interned; use itertools::Itertools; use crate::{ + db::DefDatabase, + lang_item::LangItemTarget, path::{GenericArg, GenericArgs, Path}, type_ref::{Mutability, TraitBoundModifier, TypeBound, TypeRef}, }; -pub(crate) fn print_path(db: &dyn ExpandDatabase, path: &Path, buf: &mut dyn Write) -> fmt::Result { - if let Path::LangItem(it) = path { - return write!(buf, "$lang_item::{it:?}"); +pub(crate) fn print_path(db: &dyn DefDatabase, path: &Path, buf: &mut dyn Write) -> fmt::Result { + if let Path::LangItem(it, s) = path { + write!(buf, "builtin#lang(")?; + match *it { + LangItemTarget::ImplDef(it) => write!(buf, "{it:?}")?, + LangItemTarget::EnumId(it) => { + write!(buf, "{}", db.enum_data(it).name.display(db.upcast()))? + } + LangItemTarget::Function(it) => { + write!(buf, "{}", db.function_data(it).name.display(db.upcast()))? + } + LangItemTarget::Static(it) => { + write!(buf, "{}", db.static_data(it).name.display(db.upcast()))? + } + LangItemTarget::Struct(it) => { + write!(buf, "{}", db.struct_data(it).name.display(db.upcast()))? + } + LangItemTarget::Union(it) => { + write!(buf, "{}", db.union_data(it).name.display(db.upcast()))? + } + LangItemTarget::TypeAlias(it) => { + write!(buf, "{}", db.type_alias_data(it).name.display(db.upcast()))? + } + LangItemTarget::Trait(it) => { + write!(buf, "{}", db.trait_data(it).name.display(db.upcast()))? + } + LangItemTarget::EnumVariant(it) => write!( + buf, + "{}", + db.enum_data(it.parent).variants[it.local_id].name.display(db.upcast()) + )?, + } + + if let Some(s) = s { + write!(buf, "::{}", s.display(db.upcast()))?; + } + return write!(buf, ")"); } match path.type_anchor() { Some(anchor) => { @@ -44,7 +80,7 @@ pub(crate) fn print_path(db: &dyn ExpandDatabase, path: &Path, buf: &mut dyn Wri write!(buf, "::")?; } - write!(buf, "{}", segment.name.display(db))?; + write!(buf, "{}", segment.name.display(db.upcast()))?; if let Some(generics) = segment.args_and_bindings { write!(buf, "::<")?; print_generic_args(db, generics, buf)?; @@ -57,7 +93,7 @@ pub(crate) fn print_path(db: &dyn ExpandDatabase, path: &Path, buf: &mut dyn Wri } pub(crate) fn print_generic_args( - db: &dyn ExpandDatabase, + db: &dyn DefDatabase, generics: &GenericArgs, buf: &mut dyn Write, ) -> fmt::Result { @@ -83,7 +119,7 @@ pub(crate) fn print_generic_args( write!(buf, ", ")?; } first = false; - write!(buf, "{}", binding.name.display(db))?; + write!(buf, "{}", binding.name.display(db.upcast()))?; if !binding.bounds.is_empty() { write!(buf, ": ")?; print_type_bounds(db, &binding.bounds, buf)?; @@ -97,19 +133,19 @@ pub(crate) fn print_generic_args( } pub(crate) fn print_generic_arg( - db: &dyn ExpandDatabase, + db: &dyn DefDatabase, arg: &GenericArg, buf: &mut dyn Write, ) -> fmt::Result { match arg { GenericArg::Type(ty) => print_type_ref(db, ty, buf), - GenericArg::Const(c) => write!(buf, "{}", c.display(db)), - GenericArg::Lifetime(lt) => write!(buf, "{}", lt.name.display(db)), + GenericArg::Const(c) => write!(buf, "{}", c.display(db.upcast())), + GenericArg::Lifetime(lt) => write!(buf, "{}", lt.name.display(db.upcast())), } } pub(crate) fn print_type_ref( - db: &dyn ExpandDatabase, + db: &dyn DefDatabase, type_ref: &TypeRef, buf: &mut dyn Write, ) -> fmt::Result { @@ -143,7 +179,7 @@ pub(crate) fn print_type_ref( }; write!(buf, "&")?; if let Some(lt) = lt { - write!(buf, "{} ", lt.name.display(db))?; + write!(buf, "{} ", lt.name.display(db.upcast()))?; } write!(buf, "{mtbl}")?; print_type_ref(db, pointee, buf)?; @@ -151,7 +187,7 @@ pub(crate) fn print_type_ref( TypeRef::Array(elem, len) => { write!(buf, "[")?; print_type_ref(db, elem, buf)?; - write!(buf, "; {}]", len.display(db))?; + write!(buf, "; {}]", len.display(db.upcast()))?; } TypeRef::Slice(elem) => { write!(buf, "[")?; @@ -198,7 +234,7 @@ pub(crate) fn print_type_ref( } pub(crate) fn print_type_bounds( - db: &dyn ExpandDatabase, + db: &dyn DefDatabase, bounds: &[Interned], buf: &mut dyn Write, ) -> fmt::Result { @@ -216,10 +252,14 @@ pub(crate) fn print_type_bounds( print_path(db, path, buf)?; } TypeBound::ForLifetime(lifetimes, path) => { - write!(buf, "for<{}> ", lifetimes.iter().map(|it| it.display(db)).format(", "))?; + write!( + buf, + "for<{}> ", + lifetimes.iter().map(|it| it.display(db.upcast())).format(", ") + )?; print_path(db, path, buf)?; } - TypeBound::Lifetime(lt) => write!(buf, "{}", lt.name.display(db))?, + TypeBound::Lifetime(lt) => write!(buf, "{}", lt.name.display(db.upcast()))?, TypeBound::Error => write!(buf, "{{unknown}}")?, } } diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs index 2f9187009e2d..50da9ed06a0d 100644 --- a/crates/hir-def/src/resolver.rs +++ b/crates/hir-def/src/resolver.rs @@ -156,22 +156,19 @@ impl Resolver { ) -> Option<(TypeNs, Option, Option)> { let path = match path { Path::Normal { mod_path, .. } => mod_path, - Path::LangItem(l) => { - return Some(( - match *l { - LangItemTarget::Union(it) => TypeNs::AdtId(it.into()), - LangItemTarget::TypeAlias(it) => TypeNs::TypeAliasId(it), - LangItemTarget::Struct(it) => TypeNs::AdtId(it.into()), - LangItemTarget::EnumVariant(it) => TypeNs::EnumVariantId(it), - LangItemTarget::EnumId(it) => TypeNs::AdtId(it.into()), - LangItemTarget::Trait(it) => TypeNs::TraitId(it), - LangItemTarget::Function(_) - | LangItemTarget::ImplDef(_) - | LangItemTarget::Static(_) => return None, - }, - None, - None, - )) + Path::LangItem(l, seg) => { + let type_ns = match *l { + LangItemTarget::Union(it) => TypeNs::AdtId(it.into()), + LangItemTarget::TypeAlias(it) => TypeNs::TypeAliasId(it), + LangItemTarget::Struct(it) => TypeNs::AdtId(it.into()), + LangItemTarget::EnumVariant(it) => TypeNs::EnumVariantId(it), + LangItemTarget::EnumId(it) => TypeNs::AdtId(it.into()), + LangItemTarget::Trait(it) => TypeNs::TraitId(it), + LangItemTarget::Function(_) + | LangItemTarget::ImplDef(_) + | LangItemTarget::Static(_) => return None, + }; + return Some((type_ns, seg.as_ref().map(|_| 1), None)); } }; let first_name = path.segments().first()?; @@ -256,7 +253,7 @@ impl Resolver { ) -> Option { let path = match path { Path::Normal { mod_path, .. } => mod_path, - Path::LangItem(l) => { + Path::LangItem(l, None) => { return Some(ResolveValueResult::ValueNs( match *l { LangItemTarget::Function(it) => ValueNs::FunctionId(it), @@ -272,6 +269,20 @@ impl Resolver { None, )) } + Path::LangItem(l, Some(_)) => { + let type_ns = match *l { + LangItemTarget::Union(it) => TypeNs::AdtId(it.into()), + LangItemTarget::TypeAlias(it) => TypeNs::TypeAliasId(it), + LangItemTarget::Struct(it) => TypeNs::AdtId(it.into()), + LangItemTarget::EnumVariant(it) => TypeNs::EnumVariantId(it), + LangItemTarget::EnumId(it) => TypeNs::AdtId(it.into()), + LangItemTarget::Trait(it) => TypeNs::TraitId(it), + LangItemTarget::Function(_) + | LangItemTarget::ImplDef(_) + | LangItemTarget::Static(_) => return None, + }; + return Some(ResolveValueResult::Partial(type_ns, 1, None)); + } }; let n_segments = path.segments().len(); let tmp = name![self]; diff --git a/crates/hir-expand/src/name.rs b/crates/hir-expand/src/name.rs index 5395b867c96c..a876f48bda4e 100644 --- a/crates/hir-expand/src/name.rs +++ b/crates/hir-expand/src/name.rs @@ -308,6 +308,16 @@ pub mod known { rust_2018, rust_2021, v1, + new_display, + new_debug, + new_lower_exp, + new_upper_exp, + new_octal, + new_pointer, + new_binary, + new_lower_hex, + new_upper_hex, + from_usize, // Components of known path (type name) Iterator, IntoIterator, @@ -333,6 +343,13 @@ pub mod known { Not, None, Index, + Left, + Right, + Center, + Unknown, + Is, + Param, + Implied, // Components of known path (function name) filter_map, next, @@ -341,6 +358,8 @@ pub mod known { is_empty, as_str, new, + new_v1_formatted, + none, // Builtin macros asm, assert, diff --git a/crates/hir-ty/src/infer/closure.rs b/crates/hir-ty/src/infer/closure.rs index 9431599ac791..1f040393f1f3 100644 --- a/crates/hir-ty/src/infer/closure.rs +++ b/crates/hir-ty/src/infer/closure.rs @@ -9,10 +9,7 @@ use chalk_ir::{ }; use hir_def::{ data::adt::VariantData, - hir::{ - format_args::FormatArgumentKind, Array, BinaryOp, BindingId, CaptureBy, Expr, ExprId, Pat, - PatId, Statement, UnaryOp, - }, + hir::{Array, BinaryOp, BindingId, CaptureBy, Expr, ExprId, Pat, PatId, Statement, UnaryOp}, lang_item::LangItem, resolver::{resolver_for_expr, ResolveValueResult, ValueNs}, DefWithBodyId, FieldId, HasModule, VariantId, @@ -456,14 +453,6 @@ impl InferenceContext<'_> { fn walk_expr_without_adjust(&mut self, tgt_expr: ExprId) { match &self.body[tgt_expr] { 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::If { condition, then_branch, else_branch } => { self.consume_expr(*condition); diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 06742f7b36d3..555a9fae48ec 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -9,8 +9,7 @@ use chalk_ir::{cast::Cast, fold::Shift, DebruijnIndex, Mutability, TyVariableKin use hir_def::{ generics::TypeOrConstParamData, hir::{ - format_args::FormatArgumentKind, ArithOp, Array, BinaryOp, ClosureKind, Expr, ExprId, - LabelId, Literal, Statement, UnaryOp, + ArithOp, Array, BinaryOp, ClosureKind, Expr, ExprId, LabelId, Literal, Statement, UnaryOp, }, lang_item::{LangItem, LangItemTarget}, path::{GenericArg, GenericArgs}, @@ -849,25 +848,6 @@ impl InferenceContext<'_> { self.infer_expr_no_expect(it.e); 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 let ty = self.insert_type_vars_shallow(ty); diff --git a/crates/hir-ty/src/infer/mutability.rs b/crates/hir-ty/src/infer/mutability.rs index 763f4ed2f960..b8a1af96fba6 100644 --- a/crates/hir-ty/src/infer/mutability.rs +++ b/crates/hir-ty/src/infer/mutability.rs @@ -3,10 +3,7 @@ use chalk_ir::Mutability; use hir_def::{ - hir::{ - format_args::FormatArgumentKind, Array, BinaryOp, BindingAnnotation, Expr, ExprId, PatId, - Statement, UnaryOp, - }, + hir::{Array, BinaryOp, BindingAnnotation, Expr, ExprId, PatId, Statement, UnaryOp}, lang_item::LangItem, }; use hir_expand::name; @@ -40,13 +37,6 @@ impl InferenceContext<'_> { Expr::Missing => (), Expr::InlineAsm(e) => self.infer_mut_expr_without_adjust(e.e, Mutability::Not), 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 } => { self.infer_mut_expr(condition, Mutability::Not); self.infer_mut_expr(then_branch, Mutability::Not); diff --git a/crates/hir-ty/src/infer/path.rs b/crates/hir-ty/src/infer/path.rs index 2a51c84db3aa..81daf66ac162 100644 --- a/crates/hir-ty/src/infer/path.rs +++ b/crates/hir-ty/src/infer/path.rs @@ -178,13 +178,30 @@ impl InferenceContext<'_> { remaining_index: usize, id: ExprOrPatId, ) -> Option<(ValueNs, Substitution)> { - assert!(remaining_index < path.segments().len()); // there may be more intermediate segments between the resolved one and // the end. Only the last segment needs to be resolved to a value; from // the segments before that, we need to get either a type or a trait ref. - let resolved_segment = path.segments().get(remaining_index - 1).unwrap(); - let remaining_segments = path.segments().skip(remaining_index); + let _d; + let (resolved_segment, remaining_segments) = match path { + Path::Normal { .. } => { + assert!(remaining_index < path.segments().len()); + ( + path.segments().get(remaining_index - 1).unwrap(), + path.segments().skip(remaining_index), + ) + } + Path::LangItem(_, seg) => ( + PathSegment { + name: { + _d = hir_expand::name::known::Unknown; + &_d + }, + args_and_bindings: None, + }, + path.segments(), + ), + }; let is_before_last = remaining_segments.len() == 1; match (def, is_before_last) { diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs index 9cc98684bf34..b6408cea502a 100644 --- a/crates/hir-ty/src/mir/lower.rs +++ b/crates/hir-ty/src/mir/lower.rs @@ -376,9 +376,6 @@ impl<'ctx> MirLowerCtx<'ctx> { Expr::InlineAsm(_) => { not_supported!("builtin#asm") } - Expr::FormatArgs(_) => { - not_supported!("builtin#format_args") - } Expr::Missing => { if let DefWithBodyId::FunctionId(f) = self.owner { let assoc = f.lookup(self.db.upcast()); diff --git a/crates/hir-ty/src/tests/simple.rs b/crates/hir-ty/src/tests/simple.rs index 0c73370a686c..8140c4107b8d 100644 --- a/crates/hir-ty/src/tests/simple.rs +++ b/crates/hir-ty/src/tests/simple.rs @@ -3615,22 +3615,15 @@ fn main() { #[test] fn builtin_format_args() { - check_infer( + check( r#" -#[lang = "format_arguments"] -pub struct Arguments<'a>; +//- minicore: fmt fn main() { let are = "are"; - builtin#format_args("hello {} friends, we {are} {0}{last}", "fancy", last = "!"); + let count = 10; + builtin#format_args("hello {count:02} {} friends, we {are:?} {0}{last}", "fancy", last = "!"); + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type: Arguments<'_> } "#, - expect![[r#" - 65..175 '{ ...!"); }': () - 75..78 'are': &str - 81..86 '"are"': &str - 92..172 'builti...= "!")': Arguments<'_> - 152..159 '"fancy"': &str - 168..171 '"!"': &str - "#]], ); } diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs index c765f42447a3..0fc95635e665 100644 --- a/crates/test-utils/src/minicore.rs +++ b/crates/test-utils/src/minicore.rs @@ -899,32 +899,90 @@ pub mod fmt { fn fmt(&self, f: &mut Formatter<'_>) -> Result; } - extern "C" { - type Opaque; - } + mod rt { - #[lang = "format_argument"] - pub struct Argument<'a> { - value: &'a Opaque, - formatter: fn(&Opaque, &mut Formatter<'_>) -> Result, - } + extern "C" { + type Opaque; + } + + #[lang = "format_argument"] + pub struct Argument<'a> { + value: &'a Opaque, + formatter: fn(&Opaque, &mut Formatter<'_>) -> Result, + } + + impl<'a> Argument<'a> { + pub fn new<'b, T>(x: &'b T, f: fn(&T, &mut Formatter<'_>) -> Result) -> Argument<'b> { + use crate::mem::transmute; + unsafe { Argument { formatter: transmute(f), value: transmute(x) } } + } + } + + #[lang = "format_alignment"] + pub enum Alignment { + Left, + Right, + Center, + Unknown, + } - impl<'a> Argument<'a> { - pub fn new<'b, T>(x: &'b T, f: fn(&T, &mut Formatter<'_>) -> Result) -> Argument<'b> { - use crate::mem::transmute; - unsafe { Argument { formatter: transmute(f), value: transmute(x) } } + #[lang = "format_count"] + pub enum Count { + Is(usize), + Param(usize), + Implied, + } + + #[lang = "format_placeholder"] + pub struct Placeholder { + pub position: usize, + pub fill: char, + pub align: Alignment, + pub flags: u32, + pub precision: Count, + pub width: Count, + } + + impl Placeholder { + pub const fn new( + position: usize, + fill: char, + align: Alignment, + flags: u32, + precision: Count, + width: Count, + ) -> Self; + } + + #[lang = "format_unsafe_arg"] + pub struct UnsafeArg { + _private: (), + } + + impl UnsafeArg { + pub unsafe fn new() -> Self; } } #[lang = "format_arguments"] pub struct Arguments<'a> { pieces: &'a [&'static str], - args: &'a [Argument<'a>], + fmt: Option<&'a [rt::Placeholder]>, + args: &'a [rt::Argument<'a>], } impl<'a> Arguments<'a> { pub const fn new_v1(pieces: &'a [&'static str], args: &'a [Argument<'a>]) -> Arguments<'a> { - Arguments { pieces, args } + Arguments { pieces, fmt: None, args } + } + + pub fn new_v1_formatted( + pieces: &'a [&'static str], + args: &'a [rt::Argument<'a>], + fmt: &'a [rt::Placeholder], + _unsafe_arg: rt::UnsafeArg, + ) -> Arguments<'a> { + Arguments { pieces, fmt: Some(fmt), args } } } From 5fdd1e36e358019e2d58e64483717d533c04d7e7 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 6 Sep 2023 15:30:44 +0200 Subject: [PATCH 3/7] Remove `todo!()`s --- crates/hir-def/src/body/lower.rs | 153 ++++++++++++++++--------------- crates/hir-ty/src/infer/path.rs | 2 +- 2 files changed, 79 insertions(+), 76 deletions(-) diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs index e6fdbba9cbee..9e2b6f1a8644 100644 --- a/crates/hir-def/src/body/lower.rs +++ b/crates/hir-def/src/body/lower.rs @@ -666,10 +666,7 @@ impl ExprCollector<'_> { let fields = e.fields().map(|it| it.as_name()).collect(); self.alloc_expr(Expr::OffsetOf(OffsetOf { container, fields }), syntax_ptr) } - ast::Expr::FormatArgsExpr(f) => match self.collect_format_args(f, syntax_ptr) { - Ok(value) => value, - Err(value) => return value, - }, + ast::Expr::FormatArgsExpr(f) => self.collect_format_args(f, syntax_ptr), }) } @@ -1576,7 +1573,7 @@ impl ExprCollector<'_> { &mut self, f: ast::FormatArgsExpr, syntax_ptr: AstPtr, - ) -> Result, Option>> { + ) -> ExprId { let mut args = FormatArgumentsCollector::new(); f.args().for_each(|arg| { args.add(FormatArgument { @@ -1613,7 +1610,7 @@ impl ExprCollector<'_> { } } } - todo!(); + return self.missing_expr(); }; // Create a list of all _unique_ (argument, format trait) combinations. @@ -1735,12 +1732,12 @@ impl ExprCollector<'_> { let Some(new_v1_formatted) = LangItem::FormatArguments.ty_rel_path(self.db, self.krate, name![new_v1_formatted]) else { - todo!() + return self.missing_expr(); }; let Some(unsafe_arg_new) = LangItem::FormatUnsafeArg.ty_rel_path(self.db, self.krate, name![new]) else { - todo!() + return self.missing_expr(); }; let new_v1_formatted = self.alloc_expr_desugared(Expr::Path(new_v1_formatted)); @@ -1756,14 +1753,14 @@ impl ExprCollector<'_> { tail: Some(unsafe_arg_new), }); - Ok(self.alloc_expr( + self.alloc_expr( Expr::Call { callee: new_v1_formatted, args: Box::new([lit_pieces, args, format_options, unsafe_arg_new]), is_assignee_expr: false, }, syntax_ptr, - )) + ) } /// Generate a hir expression for a format_args placeholder specification. @@ -1808,19 +1805,22 @@ impl ExprCollector<'_> { } = &placeholder.format_options; let fill = self.alloc_expr_desugared(Expr::Literal(Literal::Char(fill.unwrap_or(' ')))); - let Some(align) = LangItem::FormatAlignment.ty_rel_path( - self.db, - self.krate, - match alignment { - Some(FormatAlignment::Left) => name![Left], - Some(FormatAlignment::Right) => name![Right], - Some(FormatAlignment::Center) => name![Center], - None => name![Unknown], - }, - ) else { - todo!() + let align = { + let align = LangItem::FormatAlignment.ty_rel_path( + self.db, + self.krate, + match alignment { + Some(FormatAlignment::Left) => name![Left], + Some(FormatAlignment::Right) => name![Right], + Some(FormatAlignment::Center) => name![Center], + None => name![Unknown], + }, + ); + match align { + Some(path) => self.alloc_expr_desugared(Expr::Path(path)), + None => self.missing_expr(), + } }; - let align = self.alloc_expr_desugared(Expr::Path(align)); // This needs to match `Flag` in library/core/src/fmt/rt.rs. let flags: u32 = ((sign == Some(FormatSign::Plus)) as u32) | ((sign == Some(FormatSign::Minus)) as u32) << 1 @@ -1834,12 +1834,16 @@ impl ExprCollector<'_> { ))); let precision = self.make_count(&precision, argmap); let width = self.make_count(&width, argmap); - let Some(format_placeholder_new) = - LangItem::FormatPlaceholder.ty_rel_path(self.db, self.krate, name![new]) - else { - todo!() + + let format_placeholder_new = { + let format_placeholder_new = + LangItem::FormatPlaceholder.ty_rel_path(self.db, self.krate, name![new]); + match format_placeholder_new { + Some(path) => self.alloc_expr_desugared(Expr::Path(path)), + None => self.missing_expr(), + } }; - let format_placeholder_new = self.alloc_expr_desugared(Expr::Path(format_placeholder_new)); + self.alloc_expr_desugared(Expr::Call { callee: format_placeholder_new, args: Box::new([position, fill, align, flags, precision, width]), @@ -1873,52 +1877,49 @@ impl ExprCollector<'_> { ) -> ExprId { match count { Some(FormatCount::Literal(n)) => { - let Some(count_is) = - LangItem::FormatCount.ty_rel_path(self.db, self.krate, name![Is]) - else { - todo!() - }; - let count_is = self.alloc_expr_desugared(Expr::Path(count_is)); - let args = self.alloc_expr_desugared(Expr::Literal(Literal::Uint( - *n as u128, - Some(BuiltinUint::Usize), - ))); - self.alloc_expr_desugared(Expr::Call { - callee: count_is, - args: Box::new([args]), - is_assignee_expr: false, - }) + match LangItem::FormatCount.ty_rel_path(self.db, self.krate, name![Is]) { + Some(count_is) => { + let count_is = self.alloc_expr_desugared(Expr::Path(count_is)); + let args = self.alloc_expr_desugared(Expr::Literal(Literal::Uint( + *n as u128, + Some(BuiltinUint::Usize), + ))); + self.alloc_expr_desugared(Expr::Call { + callee: count_is, + args: Box::new([args]), + is_assignee_expr: false, + }) + } + None => self.missing_expr(), + } } Some(FormatCount::Argument(arg)) => { if let Ok(arg_index) = arg.index { let (i, _) = argmap.insert_full((arg_index, ArgumentType::Usize)); - let Some(count_param) = - LangItem::FormatCount.ty_rel_path(self.db, self.krate, name![Param]) - else { - todo!() - }; - let count_param = self.alloc_expr_desugared(Expr::Path(count_param)); - let args = self.alloc_expr_desugared(Expr::Literal(Literal::Uint( - i as u128, - Some(BuiltinUint::Usize), - ))); - self.alloc_expr_desugared(Expr::Call { - callee: count_param, - args: Box::new([args]), - is_assignee_expr: false, - }) + + match LangItem::FormatCount.ty_rel_path(self.db, self.krate, name![Param]) { + Some(count_param) => { + let count_param = self.alloc_expr_desugared(Expr::Path(count_param)); + let args = self.alloc_expr_desugared(Expr::Literal(Literal::Uint( + i as u128, + Some(BuiltinUint::Usize), + ))); + self.alloc_expr_desugared(Expr::Call { + callee: count_param, + args: Box::new([args]), + is_assignee_expr: false, + }) + } + None => self.missing_expr(), + } } else { self.missing_expr() } } - None => { - let Some(count_param) = - LangItem::FormatCount.ty_rel_path(self.db, self.krate, name![Implied]) - else { - todo!() - }; - self.alloc_expr_desugared(Expr::Path(count_param)) - } + None => match LangItem::FormatCount.ty_rel_path(self.db, self.krate, name![Implied]) { + Some(count_param) => self.alloc_expr_desugared(Expr::Path(count_param)), + None => self.missing_expr(), + }, } } @@ -1932,7 +1933,7 @@ impl ExprCollector<'_> { fn make_argument(&mut self, arg: ExprId, ty: ArgumentType) -> ExprId { use ArgumentType::*; use FormatTrait::*; - let Some(new_fn) = LangItem::FormatArgument.ty_rel_path( + match LangItem::FormatArgument.ty_rel_path( self.db, self.krate, match ty { @@ -1947,15 +1948,17 @@ impl ExprCollector<'_> { Format(UpperHex) => name![new_upper_hex], Usize => name![from_usize], }, - ) else { - todo!() - }; - let new_fn = self.alloc_expr_desugared(Expr::Path(new_fn)); - self.alloc_expr_desugared(Expr::Call { - callee: new_fn, - args: Box::new([arg]), - is_assignee_expr: false, - }) + ) { + Some(new_fn) => { + let new_fn = self.alloc_expr_desugared(Expr::Path(new_fn)); + self.alloc_expr_desugared(Expr::Call { + callee: new_fn, + args: Box::new([arg]), + is_assignee_expr: false, + }) + } + None => self.missing_expr(), + } } // endregion: format } diff --git a/crates/hir-ty/src/infer/path.rs b/crates/hir-ty/src/infer/path.rs index 81daf66ac162..c6bbf2f61407 100644 --- a/crates/hir-ty/src/infer/path.rs +++ b/crates/hir-ty/src/infer/path.rs @@ -191,7 +191,7 @@ impl InferenceContext<'_> { path.segments().skip(remaining_index), ) } - Path::LangItem(_, seg) => ( + Path::LangItem(..) => ( PathSegment { name: { _d = hir_expand::name::known::Unknown; From c0e402637eeff7dea29082f983f26be4ef2e317c Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 6 Sep 2023 18:00:30 +0200 Subject: [PATCH 4/7] Emit builtin#format_args in builtin format_args expander --- .../macro_expansion_tests/builtin_fn_macro.rs | 39 +++-- .../hir-def/src/macro_expansion_tests/mbe.rs | 6 +- .../macro_expansion_tests/mbe/regression.rs | 12 +- crates/hir-expand/src/builtin_fn_macro.rs | 160 +----------------- .../extract_expressions_from_format_string.rs | 97 +++-------- crates/ide-assists/src/tests/generated.rs | 15 +- .../src/completions/format_string.rs | 22 +-- .../src/syntax_helpers/format_string.rs | 20 +-- crates/ide/src/hover/tests.rs | 20 +++ crates/ide/src/inlay_hints/chaining.rs | 12 +- crates/ide/src/syntax_highlighting/format.rs | 1 + .../ide/src/syntax_highlighting/highlight.rs | 1 + .../test_data/highlight_strings.html | 52 +++--- crates/ide/src/syntax_highlighting/tests.rs | 13 +- crates/test-utils/src/minicore.rs | 17 +- 15 files changed, 146 insertions(+), 341 deletions(-) diff --git a/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs b/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs index a350511c7a16..4aedb22c6bc9 100644 --- a/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs +++ b/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs @@ -240,7 +240,7 @@ macro_rules! format_args { } fn main() { - ::core::fmt::Arguments::new_v1(&["", " ", ], &[::core::fmt::ArgumentV1::new(&(arg1(a, b, c)), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(arg2), ::core::fmt::Debug::fmt), ]); + builtin #format_args ("{} {:?}", arg1(a, b, c), arg2); } "##]], ); @@ -258,10 +258,10 @@ macro_rules! format_args { fn main() { format_args!(x = 2); - format_args!(x =); - format_args!(x =, x = 2); - format_args!("{}", x =); - format_args!(=, "{}", x =); + format_args!/*+errors*/(x =); + format_args!/*+errors*/(x =, x = 2); + format_args!/*+errors*/("{}", x =); + format_args!/*+errors*/(=, "{}", x =); format_args!(x = 2, "{}", 5); } "#, @@ -273,12 +273,19 @@ macro_rules! format_args { } fn main() { - /* error: no rule matches input tokens */; - /* error: expected expression */; - /* error: expected expression, expected COMMA */; - /* error: expected expression */::core::fmt::Arguments::new_v1(&["", ], &[::core::fmt::ArgumentV1::new(&(), ::core::fmt::Display::fmt), ]); - /* error: expected expression, expected R_PAREN */; - ::core::fmt::Arguments::new_v1(&["", ], &[::core::fmt::ArgumentV1::new(&(5), ::core::fmt::Display::fmt), ]); + builtin #format_args (x = 2); + /* parse error: expected expression */ +builtin #format_args (x = ); + /* parse error: expected expression */ +/* parse error: expected R_PAREN */ +/* parse error: expected expression, item or let statement */ +builtin #format_args (x = , x = 2); + /* parse error: expected expression */ +builtin #format_args ("{}", x = ); + /* parse error: expected expression */ +/* parse error: expected expression */ +builtin #format_args ( = , "{}", x = ); + builtin #format_args (x = 2, "{}", 5); } "##]], ); @@ -306,7 +313,7 @@ macro_rules! format_args { } fn main() { - ::core::fmt::Arguments::new_v1(&["", " ", ], &[::core::fmt::ArgumentV1::new(&(a::()), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(b), ::core::fmt::Debug::fmt), ]); + builtin #format_args ("{} {:?}", a::(), b); } "##]], ); @@ -339,7 +346,7 @@ macro_rules! format_args { } fn main() { - ::core::fmt::Arguments::new_v1(&[r#""#, r#",mismatch,""#, r#"",""#, r#"""#, ], &[::core::fmt::ArgumentV1::new(&(location_csv_pat(db, &analysis, vfs, &sm, pat_id)), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(mismatch.expected.display(db)), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(mismatch.actual.display(db)), ::core::fmt::Display::fmt), ]); + builtin #format_args (r#"{},mismatch,"{}","{}""#, location_csv_pat(db, &analysis, vfs, &sm, pat_id), mismatch.expected.display(db), mismatch.actual.display(db)); } "##]], ); @@ -373,7 +380,7 @@ macro_rules! format_args { } fn main() { - ::core::fmt::Arguments::new_v1(&["xxx", "y", "zzz", ], &[::core::fmt::ArgumentV1::new(&(2), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(b), ::core::fmt::Debug::fmt), ]); + builtin #format_args (concat!("xxx{}y", "{:?}zzz"), 2, b); } "##]], ); @@ -403,8 +410,8 @@ macro_rules! format_args { fn main() { let _ = - /* error: expected field name or number *//* parse error: expected field name or number */ -::core::fmt::Arguments::new_v1(&["", " ", ], &[::core::fmt::ArgumentV1::new(&(a.), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(), ::core::fmt::Debug::fmt), ]); + /* parse error: expected field name or number */ +builtin #format_args ("{} {:?}", a.); } "##]], ); diff --git a/crates/hir-def/src/macro_expansion_tests/mbe.rs b/crates/hir-def/src/macro_expansion_tests/mbe.rs index 2170cadcf83b..d0906213243d 100644 --- a/crates/hir-def/src/macro_expansion_tests/mbe.rs +++ b/crates/hir-def/src/macro_expansion_tests/mbe.rs @@ -117,7 +117,7 @@ fn main(foo: ()) { macro_rules! format_args {} fn main(foo: ()) { - /* error: unresolved macro identity */::core::fmt::Arguments::new_v1(&["", " ", " ", ], &[::core::fmt::ArgumentV1::new(&(::core::fmt::Arguments::new_v1(&["", ], &[::core::fmt::ArgumentV1::new(&(0), ::core::fmt::Display::fmt), ])), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(foo), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(identity!(10)), ::core::fmt::Display::fmt), ]) + builtin #format_args ("{} {} {}", format_args!("{}", 0), foo, identity!(10), "bar") } "##]], ); @@ -150,8 +150,8 @@ macro_rules! identity { } fn main(foo: ()) { - // format_args/*+tokenids*/!("{} {} {}"#1,#3 format_args!("{}", 0#10),#12 foo#13,#14 identity!(10#18),#21 "bar"#22) -::core#4294967295::fmt#4294967295::Arguments#4294967295::new_v1#4294967295(�[#4294967295""#4294967295,#4294967295 " "#4294967295,#4294967295 " "#4294967295,#4294967295 ]#4294967295,#4294967295 �[::core#4294967295::fmt#4294967295::ArgumentV1#4294967295::new#4294967295(�(::core#4294967295::fmt#4294967295::Arguments#4294967295::new_v1#4294967295(�[#4294967295""#4294967295,#4294967295 ]#4294967295,#4294967295 �[::core#4294967295::fmt#4294967295::ArgumentV1#4294967295::new#4294967295(�(#42949672950#10)#4294967295,#4294967295 ::core#4294967295::fmt#4294967295::Display#4294967295::fmt#4294967295)#4294967295,#4294967295 ]#4294967295)#4294967295)#4294967295,#4294967295 ::core#4294967295::fmt#4294967295::Display#4294967295::fmt#4294967295)#4294967295,#4294967295 ::core#4294967295::fmt#4294967295::ArgumentV1#4294967295::new#4294967295(�(#4294967295foo#13)#4294967295,#4294967295 ::core#4294967295::fmt#4294967295::Display#4294967295::fmt#4294967295)#4294967295,#4294967295 ::core#4294967295::fmt#4294967295::ArgumentV1#4294967295::new#4294967295(�(#429496729510#18)#4294967295,#4294967295 ::core#4294967295::fmt#4294967295::Display#4294967295::fmt#4294967295)#4294967295,#4294967295 ]#4294967295)#4294967295 + // format_args/*+tokenids*/!("{} {} {}"#1,#2 format_args#3!#4("{}"#6,#7 0#8),#9 foo#10,#11 identity#12!#13(10#15),#16 "bar"#17) +builtin#4294967295 ##4294967295format_args#4294967295 (#0"{} {} {}"#1,#2 format_args#3!#4(#5"{}"#6,#7 0#8)#5,#9 foo#10,#11 identity#12!#13(#1410#15)#14,#16 "bar"#17)#0 } "##]], diff --git a/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs b/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs index 97554f93f1c1..b416f45ff208 100644 --- a/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs +++ b/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs @@ -929,8 +929,8 @@ fn main() { macro_rules! format_args {} fn main() { - /* error: expected field name or number *//* parse error: expected field name or number */ -::core::fmt::Arguments::new_v1(&["", ], &[::core::fmt::ArgumentV1::new(&(line.1.), ::core::fmt::Display::fmt), ]); + /* parse error: expected field name or number */ +builtin #format_args ("{}", line.1.); } "##]], @@ -956,19 +956,15 @@ fn main() { macro_rules! format_args {} fn main() { - /* error: expected COMMA, expected R_BRACK, expected COMMA, expected COMMA, expected expression, expected R_PAREN *//* parse error: expected COMMA */ + /* parse error: expected COMMA */ /* parse error: expected R_BRACK */ /* parse error: expected COMMA */ /* parse error: expected COMMA */ /* parse error: expected expression */ /* parse error: expected R_PAREN */ -/* parse error: expected R_PAREN */ -/* parse error: expected expression, item or let statement */ -/* parse error: expected expression, item or let statement */ -/* parse error: expected expression, item or let statement */ /* parse error: expected expression, item or let statement */ /* parse error: expected expression, item or let statement */ -::core::fmt::Arguments::new_v1(&["", ], &[::core::fmt::ArgumentV1::new(&(&[0 2]), ::core::fmt::Display::fmt), ]); +builtin #format_args ("{}", &[0 2]); } "##]], diff --git a/crates/hir-expand/src/builtin_fn_macro.rs b/crates/hir-expand/src/builtin_fn_macro.rs index 00ee5e8b9d01..30b19b6e51b8 100644 --- a/crates/hir-expand/src/builtin_fn_macro.rs +++ b/crates/hir-expand/src/builtin_fn_macro.rs @@ -1,13 +1,9 @@ //! Builtin macro -use std::mem; - -use ::tt::Ident; use base_db::{AnchoredPath, Edition, FileId}; use cfg::CfgExpr; use either::Either; use mbe::{parse_exprs_with_sep, parse_to_token_tree, TokenMap}; -use rustc_hash::FxHashMap; use syntax::{ ast::{self, AstToken}, SmolStr, @@ -97,11 +93,11 @@ register_builtin! { (unreachable, Unreachable) => unreachable_expand, (log_syntax, LogSyntax) => log_syntax_expand, (trace_macros, TraceMacros) => trace_macros_expand, - - EAGER: (format_args, FormatArgs) => format_args_expand, (const_format_args, ConstFormatArgs) => format_args_expand, (format_args_nl, FormatArgsNl) => format_args_nl_expand, + + EAGER: (compile_error, CompileError) => compile_error_expand, (concat, Concat) => concat_expand, (concat_idents, ConcatIdents) => concat_idents_expand, @@ -247,151 +243,15 @@ fn format_args_expand_general( _db: &dyn ExpandDatabase, _id: MacroCallId, tt: &tt::Subtree, - end_string: &str, + // FIXME: Make use of this so that mir interpretation works properly + _end_string: &str, ) -> ExpandResult { - let args = parse_exprs_with_sep(tt, ','); - - let expand_error = - ExpandResult::new(tt::Subtree::empty(), mbe::ExpandError::NoMatchingRule.into()); - - let mut key_args = FxHashMap::default(); - let mut args = args.into_iter().filter_map(|mut arg| { - // Remove `key =`. - if matches!(arg.token_trees.get(1), Some(tt::TokenTree::Leaf(tt::Leaf::Punct(p))) if p.char == '=') - { - // but not with `==` - if !matches!(arg.token_trees.get(2), Some(tt::TokenTree::Leaf(tt::Leaf::Punct(p))) if p.char == '=') - { - let key = arg.token_trees.drain(..2).next().unwrap(); - key_args.insert(key.to_string(), arg); - return None; - } - } - Some(arg) - }).collect::>().into_iter(); - // ^^^^^^^ we need this collect, to enforce the side effect of the filter_map closure (building the `key_args`) - let Some(format_subtree) = args.next() else { - return expand_error; - }; - let format_string = (|| { - let token_tree = format_subtree.token_trees.get(0)?; - match token_tree { - tt::TokenTree::Leaf(l) => match l { - tt::Leaf::Literal(l) => { - if let Some(mut text) = l.text.strip_prefix('r') { - let mut raw_sharps = String::new(); - while let Some(t) = text.strip_prefix('#') { - text = t; - raw_sharps.push('#'); - } - text = - text.strip_suffix(&raw_sharps)?.strip_prefix('"')?.strip_suffix('"')?; - Some((text, l.span, Some(raw_sharps))) - } else { - let text = l.text.strip_prefix('"')?.strip_suffix('"')?; - let span = l.span; - Some((text, span, None)) - } - } - _ => None, - }, - tt::TokenTree::Subtree(_) => None, - } - })(); - let Some((format_string, _format_string_span, raw_sharps)) = format_string else { - return expand_error; - }; - let mut format_iter = format_string.chars().peekable(); - let mut parts = vec![]; - let mut last_part = String::new(); - let mut arg_tts = vec![]; - let mut err = None; - while let Some(c) = format_iter.next() { - // Parsing the format string. See https://doc.rust-lang.org/std/fmt/index.html#syntax for the grammar and more info - match c { - '{' => { - if format_iter.peek() == Some(&'{') { - format_iter.next(); - last_part.push('{'); - continue; - } - let mut argument = String::new(); - while ![Some(&'}'), Some(&':')].contains(&format_iter.peek()) { - argument.push(match format_iter.next() { - Some(c) => c, - None => return expand_error, - }); - } - let format_spec = match format_iter.next().unwrap() { - '}' => "".to_owned(), - ':' => { - let mut s = String::new(); - while let Some(c) = format_iter.next() { - if c == '}' { - break; - } - s.push(c); - } - s - } - _ => unreachable!(), - }; - parts.push(mem::take(&mut last_part)); - let arg_tree = if argument.is_empty() { - match args.next() { - Some(it) => it, - None => { - err = Some(mbe::ExpandError::NoMatchingRule.into()); - tt::Subtree::empty() - } - } - } else if let Some(tree) = key_args.get(&argument) { - tree.clone() - } else { - // FIXME: we should pick the related substring of the `_format_string_span` as the span. You - // can use `.char_indices()` instead of `.char()` for `format_iter` to find the substring interval. - let ident = Ident::new(argument, tt::TokenId::unspecified()); - quote!(#ident) - }; - let formatter = match &*format_spec { - "?" => quote!(::core::fmt::Debug::fmt), - "" => quote!(::core::fmt::Display::fmt), - _ => { - // FIXME: implement the rest and return expand error here - quote!(::core::fmt::Display::fmt) - } - }; - arg_tts.push(quote! { ::core::fmt::ArgumentV1::new(&(#arg_tree), #formatter), }); - } - '}' => { - if format_iter.peek() == Some(&'}') { - format_iter.next(); - last_part.push('}'); - } else { - return expand_error; - } - } - _ => last_part.push(c), - } - } - last_part += end_string; - if !last_part.is_empty() { - parts.push(last_part); - } - let part_tts = parts.into_iter().map(|it| { - let text = if let Some(raw) = &raw_sharps { - format!("r{raw}\"{}\"{raw}", it).into() - } else { - format!("\"{}\"", it).into() - }; - let l = tt::Literal { span: tt::TokenId::unspecified(), text }; - quote!(#l ,) + let pound = quote! {@PUNCT '#'}; + let mut tt = tt.clone(); + tt.delimiter.kind = tt::DelimiterKind::Parenthesis; + return ExpandResult::ok(quote! { + builtin #pound format_args #tt }); - let arg_tts = arg_tts.into_iter().flat_map(|arg| arg.token_trees); - let expanded = quote! { - ::core::fmt::Arguments::new_v1(&[##part_tts], &[##arg_tts]) - }; - ExpandResult { value: expanded, err } } fn asm_expand( @@ -399,8 +259,6 @@ fn asm_expand( _id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult { - // FIXME: parse asm here - // We expand all assembly snippets to `format_args!` invocations to get format syntax // highlighting for them. diff --git a/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs b/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs index c3d925cb26c4..25c4b49c9450 100644 --- a/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs +++ b/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs @@ -15,26 +15,13 @@ use syntax::{ast, AstNode, AstToken, NodeOrToken, SyntaxKind::COMMA, TextRange}; // Move an expression out of a format string. // // ``` -// macro_rules! format_args { -// ($lit:literal $(tt:tt)*) => { 0 }, -// } -// macro_rules! print { -// ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*))); -// } -// +// # //- minicore: fmt // fn main() { // print!("{var} {x + 1}$0"); // } // ``` // -> // ``` -// macro_rules! format_args { -// ($lit:literal $(tt:tt)*) => { 0 }, -// } -// macro_rules! print { -// ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*))); -// } -// // fn main() { // print!("{var} {}"$0, x + 1); // } @@ -47,17 +34,23 @@ pub(crate) fn extract_expressions_from_format_string( let fmt_string = ctx.find_token_at_offset::()?; let tt = fmt_string.syntax().parent().and_then(ast::TokenTree::cast)?; + dbg!(); let expanded_t = ast::String::cast( ctx.sema.descend_into_macros_with_kind_preference(fmt_string.syntax().clone(), 0.into()), )?; + dbg!(); if !is_format_string(&expanded_t) { + dbg!(); return None; } + dbg!(); let (new_fmt, extracted_args) = parse_format_exprs(fmt_string.text()).ok()?; + dbg!(); if extracted_args.is_empty() { return None; } + dbg!(); acc.add( AssistId( @@ -158,37 +151,21 @@ mod tests { use super::*; use crate::tests::check_assist; - const MACRO_DECL: &'static str = r#" -macro_rules! format_args { - ($lit:literal $(tt:tt)*) => { 0 }, -} -macro_rules! print { - ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*))); -} -"#; - - fn add_macro_decl(s: &'static str) -> String { - MACRO_DECL.to_string() + s - } - #[test] fn multiple_middle_arg() { check_assist( extract_expressions_from_format_string, - &add_macro_decl( - r#" + r#" +//- minicore: fmt fn main() { print!("{} {x + 1:b} {}$0", y + 2, 2); } "#, - ), - &add_macro_decl( - r#" + r#" fn main() { print!("{} {:b} {}"$0, y + 2, x + 1, 2); } "#, - ), ); } @@ -196,20 +173,17 @@ fn main() { fn single_arg() { check_assist( extract_expressions_from_format_string, - &add_macro_decl( - r#" + r#" +//- minicore: fmt fn main() { print!("{obj.value:b}$0",); } "#, - ), - &add_macro_decl( - r#" + r#" fn main() { print!("{:b}"$0, obj.value); } "#, - ), ); } @@ -217,20 +191,17 @@ fn main() { fn multiple_middle_placeholders_arg() { check_assist( extract_expressions_from_format_string, - &add_macro_decl( - r#" + r#" +//- minicore: fmt fn main() { print!("{} {x + 1:b} {} {}$0", y + 2, 2); } "#, - ), - &add_macro_decl( - r#" + r#" fn main() { print!("{} {:b} {} {}"$0, y + 2, x + 1, 2, $1); } "#, - ), ); } @@ -238,20 +209,17 @@ fn main() { fn multiple_trailing_args() { check_assist( extract_expressions_from_format_string, - &add_macro_decl( - r#" + r#" +//- minicore: fmt fn main() { print!("{:b} {x + 1:b} {Struct(1, 2)}$0", 1); } "#, - ), - &add_macro_decl( - r#" + r#" fn main() { print!("{:b} {:b} {}"$0, 1, x + 1, Struct(1, 2)); } "#, - ), ); } @@ -259,20 +227,17 @@ fn main() { fn improper_commas() { check_assist( extract_expressions_from_format_string, - &add_macro_decl( - r#" + r#" +//- minicore: fmt fn main() { print!("{} {x + 1:b} {Struct(1, 2)}$0", 1,); } "#, - ), - &add_macro_decl( - r#" + r#" fn main() { print!("{} {:b} {}"$0, 1, x + 1, Struct(1, 2)); } "#, - ), ); } @@ -280,20 +245,17 @@ fn main() { fn nested_tt() { check_assist( extract_expressions_from_format_string, - &add_macro_decl( - r#" + r#" +//- minicore: fmt fn main() { print!("My name is {} {x$0 + x}", stringify!(Paperino)) } "#, - ), - &add_macro_decl( - r#" + r#" fn main() { print!("My name is {} {}"$0, stringify!(Paperino), x + x) } "#, - ), ); } @@ -301,22 +263,19 @@ fn main() { fn extract_only_expressions() { check_assist( extract_expressions_from_format_string, - &add_macro_decl( - r#" + r#" +//- minicore: fmt fn main() { let var = 1 + 1; print!("foobar {var} {var:?} {x$0 + x}") } "#, - ), - &add_macro_decl( - r#" + r#" fn main() { let var = 1 + 1; print!("foobar {var} {var:?} {}"$0, x + x) } "#, - ), ); } } diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index 6eadc3dbcbcc..8bd87e5f5dbe 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -694,25 +694,12 @@ fn doctest_extract_expressions_from_format_string() { check_doc_test( "extract_expressions_from_format_string", r#####" -macro_rules! format_args { - ($lit:literal $(tt:tt)*) => { 0 }, -} -macro_rules! print { - ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*))); -} - +//- minicore: fmt fn main() { print!("{var} {x + 1}$0"); } "#####, r#####" -macro_rules! format_args { - ($lit:literal $(tt:tt)*) => { 0 }, -} -macro_rules! print { - ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*))); -} - fn main() { print!("{var} {}"$0, x + 1); } diff --git a/crates/ide-completion/src/completions/format_string.rs b/crates/ide-completion/src/completions/format_string.rs index 8e904fd605a8..cecbe75391d1 100644 --- a/crates/ide-completion/src/completions/format_string.rs +++ b/crates/ide-completion/src/completions/format_string.rs @@ -51,9 +51,7 @@ mod tests { fn works_when_wrapped() { check( r#" -macro_rules! format_args { - ($lit:literal $(tt:tt)*) => { 0 }, -} +//- minicore: fmt macro_rules! print { ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*))); } @@ -70,9 +68,7 @@ fn main() { fn no_completion_without_brace() { check( r#" -macro_rules! format_args { - ($lit:literal $(tt:tt)*) => { 0 }, -} +//- minicore: fmt fn main() { let foobar = 1; format_args!("f$0"); @@ -87,18 +83,13 @@ fn main() { check_edit( "foobar", r#" -macro_rules! format_args { - ($lit:literal $(tt:tt)*) => { 0 }, -} +//- minicore: fmt fn main() { let foobar = 1; format_args!("{f$0"); } "#, r#" -macro_rules! format_args { - ($lit:literal $(tt:tt)*) => { 0 }, -} fn main() { let foobar = 1; format_args!("{foobar"); @@ -108,18 +99,13 @@ fn main() { check_edit( "foobar", r#" -macro_rules! format_args { - ($lit:literal $(tt:tt)*) => { 0 }, -} +//- minicore: fmt fn main() { let foobar = 1; format_args!("{$0"); } "#, r#" -macro_rules! format_args { - ($lit:literal $(tt:tt)*) => { 0 }, -} fn main() { let foobar = 1; format_args!("{foobar"); diff --git a/crates/ide-db/src/syntax_helpers/format_string.rs b/crates/ide-db/src/syntax_helpers/format_string.rs index acf0a67de4a1..8302b015ddaf 100644 --- a/crates/ide-db/src/syntax_helpers/format_string.rs +++ b/crates/ide-db/src/syntax_helpers/format_string.rs @@ -1,10 +1,10 @@ //! Tools to work with format string literals for the `format_args!` family of macros. -use crate::syntax_helpers::node_ext::macro_call_for_string_token; use syntax::{ ast::{self, IsString}, - TextRange, TextSize, + AstNode, AstToken, TextRange, TextSize, }; +// FIXME: This can probably be re-implemented via the HIR? pub fn is_format_string(string: &ast::String) -> bool { // Check if `string` is a format string argument of a macro invocation. // `string` is a string literal, mapped down into the innermost macro expansion. @@ -15,19 +15,9 @@ pub fn is_format_string(string: &ast::String) -> bool { // This setup lets us correctly highlight the components of `concat!("{}", "bla")` format // strings. It still fails for `concat!("{", "}")`, but that is rare. (|| { - let name = macro_call_for_string_token(string)?.path()?.segment()?.name_ref()?; - - if !matches!( - name.text().as_str(), - "format_args" | "format_args_nl" | "const_format_args" | "panic_2015" | "panic_2021" - ) { - return None; - } - - // NB: we match against `panic_2015`/`panic_2021` here because they have a special-cased arm for - // `"{}"`, which otherwise wouldn't get highlighted. - - Some(()) + let lit = string.syntax().parent().and_then(ast::Literal::cast)?; + let fa = lit.syntax().parent().and_then(ast::FormatArgsExpr::cast)?; + (fa.template()? == ast::Expr::Literal(lit)).then_some(|| ()) })() .is_some() } diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs index d0f9f7b0e16d..81d6db564ff5 100644 --- a/crates/ide/src/hover/tests.rs +++ b/crates/ide/src/hover/tests.rs @@ -6574,3 +6574,23 @@ fn test() { "#]], ); } + +#[test] +fn format_args_arg() { + check( + r#" +//- minicore: fmt +fn test() { + let foo = 0; + format_args!("{}", foo$0); +} +"#, + expect![[r#" + *foo* + + ```rust + let foo: i32 // size = 4, align = 4 + ``` + "#]], + ); +} diff --git a/crates/ide/src/inlay_hints/chaining.rs b/crates/ide/src/inlay_hints/chaining.rs index b621a8dda7ee..8207bec7e895 100644 --- a/crates/ide/src/inlay_hints/chaining.rs +++ b/crates/ide/src/inlay_hints/chaining.rs @@ -474,7 +474,7 @@ fn main() { file_id: FileId( 1, ), - range: 9289..9297, + range: 10739..10747, }, ), tooltip: "", @@ -487,7 +487,7 @@ fn main() { file_id: FileId( 1, ), - range: 9321..9325, + range: 10771..10775, }, ), tooltip: "", @@ -511,7 +511,7 @@ fn main() { file_id: FileId( 1, ), - range: 9289..9297, + range: 10739..10747, }, ), tooltip: "", @@ -524,7 +524,7 @@ fn main() { file_id: FileId( 1, ), - range: 9321..9325, + range: 10771..10775, }, ), tooltip: "", @@ -548,7 +548,7 @@ fn main() { file_id: FileId( 1, ), - range: 9289..9297, + range: 10739..10747, }, ), tooltip: "", @@ -561,7 +561,7 @@ fn main() { file_id: FileId( 1, ), - range: 9321..9325, + range: 10771..10775, }, ), tooltip: "", diff --git a/crates/ide/src/syntax_highlighting/format.rs b/crates/ide/src/syntax_highlighting/format.rs index 2ed57e201302..2ef1315945a0 100644 --- a/crates/ide/src/syntax_highlighting/format.rs +++ b/crates/ide/src/syntax_highlighting/format.rs @@ -17,6 +17,7 @@ pub(super) fn highlight_format_string( return; } + // FIXME: Replace this with the HIR info we have now. lex_format_specifiers(string, &mut |piece_range, kind| { if let Some(highlight) = highlight_format_specifier(kind) { stack.add(HlRange { diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs index 8e96bfa01ada..7d00282fc14b 100644 --- a/crates/ide/src/syntax_highlighting/highlight.rs +++ b/crates/ide/src/syntax_highlighting/highlight.rs @@ -617,6 +617,7 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight { CONST => SymbolKind::Const, STATIC => SymbolKind::Static, IDENT_PAT => SymbolKind::Local, + FORMAT_ARGS_ARG => SymbolKind::Local, _ => return default.into(), }; diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html index 9c5c6d50ea70..64e614cecd20 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html @@ -45,17 +45,11 @@
macro_rules! println {
     ($($arg:tt)*) => ({
-        $crate::io::_print($crate::format_args_nl!($($arg)*));
+        $crate::io::_print(format_args_nl!($($arg)*));
     })
 }
 #[rustc_builtin_macro]
 #[macro_export]
-macro_rules! format_args {}
-#[rustc_builtin_macro]
-#[macro_export]
-macro_rules! const_format_args {}
-#[rustc_builtin_macro]
-#[macro_export]
 macro_rules! format_args_nl {}
 
 mod panic {
@@ -75,7 +69,7 @@
             $crate::panicking::panic_display(&$arg)
         ),
         ($fmt:expr, $($arg:tt)+) => (
-            $crate::panicking::panic_fmt($crate::const_format_args!($fmt, $($arg)+))
+            $crate::panicking::panic_fmt(const_format_args!($fmt, $($arg)+))
         ),
     }
 }
@@ -92,7 +86,7 @@
 
 macro_rules! toho {
     () => ($crate::panic!("not yet implemented"));
-    ($($arg:tt)+) => ($crate::panic!("not yet implemented: {}", $crate::format_args!($($arg)+)));
+    ($($arg:tt)+) => ($crate::panic!("not yet implemented: {}", format_args!($($arg)+)));
 }
 
 fn main() {
@@ -114,18 +108,18 @@
     println!("Hello, {}!", "world");   // => "Hello, world!"
     println!("The number is {}", 1);   // => "The number is 1"
     println!("{:?}", (3, 4));          // => "(3, 4)"
-    println!("{value}", value=4);      // => "4"
+    println!("{value}", value=4);      // => "4"
     println!("{} {}", 1, 2);           // => "1 2"
     println!("{:04}", 42);             // => "0042" with leading zerosV
     println!("{1} {} {0} {}", 1, 2);   // => "2 1 1 2"
-    println!("{argument}", argument = "test");   // => "test"
-    println!("{name} {}", 1, name = 2);          // => "2 1"
-    println!("{a} {c} {b}", a="a", b='b', c=3);  // => "a 3 b"
+    println!("{argument}", argument = "test");   // => "test"
+    println!("{name} {}", 1, name = 2);          // => "2 1"
+    println!("{a} {c} {b}", a="a", b='b', c=3);  // => "a 3 b"
     println!("{{{}}}", 2);                       // => "{2}"
     println!("Hello {:5}!", "x");
     println!("Hello {:1$}!", "x", 5);
     println!("Hello {1:0$}!", 5, "x");
-    println!("Hello {:width$}!", "x", width = 5);
+    println!("Hello {:width$}!", "x", width = 5);
     println!("Hello {:<5}!", "x");
     println!("Hello {:-<5}!", "x");
     println!("Hello {:^5}!", "x");
@@ -140,10 +134,10 @@
     println!("Hello {0} is {2:.1$}", "x", 5, 0.01);
     println!("Hello {} is {:.*}",    "x", 5, 0.01);
     println!("Hello {} is {2:.*}",   "x", 5, 0.01);
-    println!("Hello {} is {number:.prec$}", "x", prec = 5, number = 0.01);
-    println!("{}, `{name:.*}` has 3 fractional digits", "Hello", 3, name=1234.56);
-    println!("{}, `{name:.*}` has 3 characters", "Hello", 3, name="1234.56");
-    println!("{}, `{name:>8.*}` has 3 right-aligned characters", "Hello", 3, name="1234.56");
+    println!("Hello {} is {number:.prec$}", "x", prec = 5, number = 0.01);
+    println!("{}, `{name:.*}` has 3 fractional digits", "Hello", 3, name=1234.56);
+    println!("{}, `{name:.*}` has 3 characters", "Hello", 3, name="1234.56");
+    println!("{}, `{name:>8.*}` has 3 right-aligned characters", "Hello", 3, name="1234.56");
 
     let _ = "{}"
     let _ = "{{}}";
@@ -167,24 +161,24 @@
     let _ = c"\u{FF}\xFF"; // valid bytes, valid unicodes
     let backslash = r"\\";
 
-    println!("{\x41}", A = 92);
-    println!("{ничоси}", ничоси = 92);
+    println!("{\x41}", A = 92);
+    println!("{ничоси}", ничоси = 92);
 
     println!("{:x?} {} ", thingy, n2);
-    panic!("{}", 0);
-    panic!("more {}", 1);
-    assert!(true, "{}", 1);
-    assert!(true, "{} asdasd", 1);
-    toho!("{}fmt", 0);
+    panic!("{}", 0);
+    panic!("more {}", 1);
+    assert!(true, "{}", 1);
+    assert!(true, "{} asdasd", 1);
+    toho!("{}fmt", 0);
     let i: u64 = 3;
     let o: u64;
     asm!(
-        "mov {0}, {1}",
-        "add {0}, 5",
+        "mov {0}, {1}",
+        "add {0}, 5",
         out(reg) o,
         in(reg) i,
     );
 
-    format_args!(concat!("{}"), "{}");
-    format_args!("{} {} {} {} {} {}", backslash, format_args!("{}", 0), foo, "bar", toho!(), backslash);
+    format_args!(concat!("{}"), "{}");
+    format_args!("{} {} {} {} {} {}", backslash, format_args!("{}", 0), foo, "bar", toho!(), backslash);
 }
\ No newline at end of file diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs index aacd57af5894..542d8992531f 100644 --- a/crates/ide/src/syntax_highlighting/tests.rs +++ b/crates/ide/src/syntax_highlighting/tests.rs @@ -401,19 +401,14 @@ fn test_string_highlighting() { // thus, we have to copy the macro definition from `std` check_highlighting( r#" +//- minicore: fmt macro_rules! println { ($($arg:tt)*) => ({ - $crate::io::_print($crate::format_args_nl!($($arg)*)); + $crate::io::_print(format_args_nl!($($arg)*)); }) } #[rustc_builtin_macro] #[macro_export] -macro_rules! format_args {} -#[rustc_builtin_macro] -#[macro_export] -macro_rules! const_format_args {} -#[rustc_builtin_macro] -#[macro_export] macro_rules! format_args_nl {} mod panic { @@ -433,7 +428,7 @@ mod panic { $crate::panicking::panic_display(&$arg) ), ($fmt:expr, $($arg:tt)+) => ( - $crate::panicking::panic_fmt($crate::const_format_args!($fmt, $($arg)+)) + $crate::panicking::panic_fmt(const_format_args!($fmt, $($arg)+)) ), } } @@ -450,7 +445,7 @@ macro_rules! concat {} macro_rules! toho { () => ($crate::panic!("not yet implemented")); - ($($arg:tt)+) => ($crate::panic!("not yet implemented: {}", $crate::format_args!($($arg)+))); + ($($arg:tt)+) => ($crate::panic!("not yet implemented: {}", format_args!($($arg)+))); } fn main() { diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs index 0fc95635e665..573f56b003af 100644 --- a/crates/test-utils/src/minicore.rs +++ b/crates/test-utils/src/minicore.rs @@ -1352,8 +1352,6 @@ mod macros { /* compiler built-in */ }; } - - pub(crate) use panic; // endregion:panic // region:fmt @@ -1364,7 +1362,20 @@ mod macros { ($fmt:expr, $($args:tt)*) => {{ /* compiler built-in */ }}; } - pub(crate) use const_format_args; + #[macro_export] + #[rustc_builtin_macro] + macro_rules! format_args { + ($fmt:expr) => {{ /* compiler built-in */ }}; + ($fmt:expr, $($args:tt)*) => {{ /* compiler built-in */ }}; + } + + #[macro_export] + macro_rules! print { + ($($arg:tt)*) => {{ + $crate::io::_print($crate::format_args!($($arg)*)); + }}; + } + // endregion:fmt // region:derive From 3fa0bf0dd3f9749e1d98a9d5a43decf1a03d0331 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 6 Sep 2023 18:22:35 +0200 Subject: [PATCH 5/7] Fix ide-diagnostics test fixture --- .../src/handlers/macro_error.rs | 6 +-- .../parser/inline/ok/0207_builtin_expr.rast | 45 ++++++++++++++++++- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/crates/ide-diagnostics/src/handlers/macro_error.rs b/crates/ide-diagnostics/src/handlers/macro_error.rs index f54cdd63bbb6..7ca0a0eab2b6 100644 --- a/crates/ide-diagnostics/src/handlers/macro_error.rs +++ b/crates/ide-diagnostics/src/handlers/macro_error.rs @@ -157,6 +157,7 @@ struct S; fn macro_diag_builtin() { check_diagnostics( r#" +//- minicore: fmt #[rustc_builtin_macro] macro_rules! env {} @@ -166,9 +167,6 @@ macro_rules! include {} #[rustc_builtin_macro] macro_rules! compile_error {} -#[rustc_builtin_macro] -macro_rules! format_args { () => {} } - fn main() { // Test a handful of built-in (eager) macros: @@ -189,7 +187,7 @@ fn main() { // Lazy: format_args!(); - //^^^^^^^^^^^ error: no rule matches input tokens + //^^^^^^^^^^^ error: Syntax Error in Expansion: expected expression } "#, ); diff --git a/crates/parser/test_data/parser/inline/ok/0207_builtin_expr.rast b/crates/parser/test_data/parser/inline/ok/0207_builtin_expr.rast index f127b3e8c890..361900b6d3e4 100644 --- a/crates/parser/test_data/parser/inline/ok/0207_builtin_expr.rast +++ b/crates/parser/test_data/parser/inline/ok/0207_builtin_expr.rast @@ -30,7 +30,50 @@ SOURCE_FILE FORMAT_ARGS_KW "format_args" L_PAREN "(" LITERAL - INT_NUMBER "0" + STRING "\"\"" + COMMA "," + WHITESPACE " " + FORMAT_ARGS_ARG + LITERAL + INT_NUMBER "0" + COMMA "," + WHITESPACE " " + FORMAT_ARGS_ARG + LITERAL + INT_NUMBER "1" + COMMA "," + WHITESPACE " " + FORMAT_ARGS_ARG + NAME + IDENT "a" + WHITESPACE " " + EQ "=" + WHITESPACE " " + BIN_EXPR + LITERAL + INT_NUMBER "2" + WHITESPACE " " + PLUS "+" + WHITESPACE " " + LITERAL + INT_NUMBER "3" + COMMA "," + WHITESPACE " " + FORMAT_ARGS_ARG + BIN_EXPR + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "a" + WHITESPACE " " + PLUS "+" + WHITESPACE " " + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "b" R_PAREN ")" SEMICOLON ";" WHITESPACE "\n " From 5046889f431e4eb94a1add6a23f023ae5e461a16 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 6 Sep 2023 18:52:12 +0200 Subject: [PATCH 6/7] Don't allocate the format_args template string as an expression --- crates/hir-def/src/body/lower.rs | 50 +++++++++---------- crates/hir-def/src/body/tests.rs | 6 +-- crates/hir-def/src/hir/format_args.rs | 15 ++---- .../extract_expressions_from_format_string.rs | 6 --- 4 files changed, 31 insertions(+), 46 deletions(-) diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs index 9e2b6f1a8644..38073ce3da59 100644 --- a/crates/hir-def/src/body/lower.rs +++ b/crates/hir-def/src/body/lower.rs @@ -32,7 +32,7 @@ use crate::{ hir::{ dummy_expr_id, format_args::{ - self, FormatAlignment, FormatArgsPiece, FormatArgument, FormatArgumentKind, + self, FormatAlignment, FormatArgs, FormatArgsPiece, FormatArgument, FormatArgumentKind, FormatArgumentsCollector, FormatCount, FormatDebugHex, FormatOptions, FormatPlaceholder, FormatSign, FormatTrait, }, @@ -1568,6 +1568,24 @@ impl ExprCollector<'_> { // endregion: labels // region: format + fn expand_macros_to_string(&mut self, expr: ast::Expr) -> Option<(ast::String, bool)> { + let m = match expr { + ast::Expr::MacroExpr(m) => m, + ast::Expr::Literal(l) => { + return match l.kind() { + ast::LiteralKind::String(s) => Some((s, true)), + _ => None, + } + } + _ => return None, + }; + let e = m.macro_call()?; + let macro_ptr = AstPtr::new(&e); + let (exp, _) = self.collect_macro_call(e, macro_ptr, true, |this, expansion| { + expansion.and_then(|it| this.expand_macros_to_string(it)) + })?; + Some((exp, false)) + } fn collect_format_args( &mut self, @@ -1586,31 +1604,13 @@ impl ExprCollector<'_> { }); let template = f.template(); let fmt_snippet = template.as_ref().map(ToString::to_string); - - // FIXME: We shouldn't allocate this one, just resolve and expand the macros to fetch the - // string literal! - let expr = self.collect_expr_opt(template); - - let fmt = 'b: { - 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() { - break 'b format_args::parse( - expr, - &s, - fmt_snippet, - args, - is_direct_literal, - |name| self.alloc_expr_desugared(Expr::Path(Path::from(name))), - ); - } - } + let fmt = match template.and_then(|it| self.expand_macros_to_string(it)) { + Some((s, is_direct_literal)) => { + format_args::parse(&s, fmt_snippet, args, is_direct_literal, |name| { + self.alloc_expr_desugared(Expr::Path(Path::from(name))) + }) } - return self.missing_expr(); + None => FormatArgs { template: Default::default(), arguments: args.finish() }, }; // Create a list of all _unique_ (argument, format trait) combinations. diff --git a/crates/hir-def/src/body/tests.rs b/crates/hir-def/src/body/tests.rs index 76880bf68ed7..1658757d2b6e 100644 --- a/crates/hir-def/src/body/tests.rs +++ b/crates/hir-def/src/body/tests.rs @@ -161,7 +161,7 @@ fn main() { let count = 10; builtin#lang(Arguments::new_v1_formatted)( &[ - "\"hello ", " ", " friends, we ", " ", "", "\"", + "\"hello ", " ", " friends, we ", " ", "", "\"", ], &[ builtin#lang(Argument::new_display)( @@ -172,7 +172,7 @@ fn main() { &are, ), builtin#lang(Argument::new_display)( &"!", - ), + ), ], &[ builtin#lang(Placeholder::new)( @@ -212,7 +212,7 @@ fn main() { 0u32, builtin#lang(Count::Implied), builtin#lang(Count::Implied), - ), + ), ], unsafe { builtin#lang(UnsafeArg::new)() diff --git a/crates/hir-def/src/hir/format_args.rs b/crates/hir-def/src/hir/format_args.rs index 6197ee164942..d8f8e6026a5a 100644 --- a/crates/hir-def/src/hir/format_args.rs +++ b/crates/hir-def/src/hir/format_args.rs @@ -1,3 +1,4 @@ +//! Parses `format_args` input. use std::mem; use hir_expand::name::Name; @@ -12,7 +13,6 @@ mod parse; #[derive(Debug, Clone, PartialEq, Eq)] pub struct FormatArgs { - pub template_expr: ExprId, pub template: Box<[FormatArgsPiece]>, pub arguments: FormatArguments, } @@ -166,7 +166,6 @@ enum PositionUsedAs { use PositionUsedAs::*; pub(crate) fn parse( - expr: ExprId, s: &ast::String, fmt_snippet: Option, mut args: FormatArgumentsCollector, @@ -195,11 +194,7 @@ pub(crate) fn parse( 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(), - }; + return FormatArgs { template: Default::default(), arguments: args.finish() }; } let to_span = |inner_span: parse::InnerSpan| { @@ -419,11 +414,7 @@ pub(crate) fn parse( // FIXME: Diagnose } - FormatArgs { - template_expr: expr, - template: template.into_boxed_slice(), - arguments: args.finish(), - } + FormatArgs { template: template.into_boxed_slice(), arguments: args.finish() } } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs b/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs index 25c4b49c9450..31a1ff496e13 100644 --- a/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs +++ b/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs @@ -34,23 +34,17 @@ pub(crate) fn extract_expressions_from_format_string( let fmt_string = ctx.find_token_at_offset::()?; let tt = fmt_string.syntax().parent().and_then(ast::TokenTree::cast)?; - dbg!(); let expanded_t = ast::String::cast( ctx.sema.descend_into_macros_with_kind_preference(fmt_string.syntax().clone(), 0.into()), )?; - dbg!(); if !is_format_string(&expanded_t) { - dbg!(); return None; } - dbg!(); let (new_fmt, extracted_args) = parse_format_exprs(fmt_string.text()).ok()?; - dbg!(); if extracted_args.is_empty() { return None; } - dbg!(); acc.add( AssistId( From 96f19231d3d241c41540570f6caee29666aa9384 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 6 Sep 2023 19:31:48 +0200 Subject: [PATCH 7/7] Fix hir pretty printing emitting trailing whitespace --- crates/hir-def/src/body/pretty.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/hir-def/src/body/pretty.rs b/crates/hir-def/src/body/pretty.rs index 31c2e9c1fb98..fad4d7a4da66 100644 --- a/crates/hir-def/src/body/pretty.rs +++ b/crates/hir-def/src/body/pretty.rs @@ -140,9 +140,14 @@ impl Printer<'_> { } fn newline(&mut self) { - match self.buf.chars().rev().find(|ch| *ch != ' ') { - Some('\n') | None => {} - _ => writeln!(self).unwrap(), + match self.buf.chars().rev().find_position(|ch| *ch != ' ') { + Some((_, '\n')) | None => {} + Some((idx, _)) => { + if idx != 0 { + self.buf.drain(self.buf.len() - idx..); + } + writeln!(self).unwrap() + } } }