From e2030405d5936911d99ef9854d9626122bf03a02 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Thu, 23 Jul 2020 17:23:01 +0200 Subject: Rename modules --- crates/ra_syntax/src/ast.rs | 14 +- crates/ra_syntax/src/ast/expr_ext.rs | 417 +++++++++++++++++++++ crates/ra_syntax/src/ast/expr_extensions.rs | 417 --------------------- crates/ra_syntax/src/ast/extensions.rs | 474 ------------------------ crates/ra_syntax/src/ast/node_ext.rs | 474 ++++++++++++++++++++++++ crates/ra_syntax/src/ast/token_ext.rs | 538 ++++++++++++++++++++++++++++ crates/ra_syntax/src/ast/tokens.rs | 537 --------------------------- 7 files changed, 1436 insertions(+), 1435 deletions(-) create mode 100644 crates/ra_syntax/src/ast/expr_ext.rs delete mode 100644 crates/ra_syntax/src/ast/expr_extensions.rs delete mode 100644 crates/ra_syntax/src/ast/extensions.rs create mode 100644 crates/ra_syntax/src/ast/node_ext.rs create mode 100644 crates/ra_syntax/src/ast/token_ext.rs delete mode 100644 crates/ra_syntax/src/ast/tokens.rs (limited to 'crates') diff --git a/crates/ra_syntax/src/ast.rs b/crates/ra_syntax/src/ast.rs index 9d02aeef3..c65c485cb 100644 --- a/crates/ra_syntax/src/ast.rs +++ b/crates/ra_syntax/src/ast.rs @@ -2,9 +2,9 @@ mod generated; mod traits; -mod tokens; -mod extensions; -mod expr_extensions; +mod token_ext; +mod node_ext; +mod expr_ext; pub mod edit; pub mod make; @@ -16,13 +16,13 @@ use crate::{ }; pub use self::{ - expr_extensions::{ArrayExprKind, BinOp, Effect, ElseBranch, LiteralKind, PrefixOp, RangeOp}, - extensions::{ + expr_ext::{ArrayExprKind, BinOp, Effect, ElseBranch, LiteralKind, PrefixOp, RangeOp}, + generated::{nodes::*, tokens::*}, + node_ext::{ AttrKind, FieldKind, NameOrNameRef, PathSegmentKind, SelfParamKind, SlicePatComponents, StructKind, TypeBoundKind, VisibilityKind, }, - generated::{nodes::*, tokens::*}, - tokens::*, + token_ext::*, traits::*, }; diff --git a/crates/ra_syntax/src/ast/expr_ext.rs b/crates/ra_syntax/src/ast/expr_ext.rs new file mode 100644 index 000000000..db5438d68 --- /dev/null +++ b/crates/ra_syntax/src/ast/expr_ext.rs @@ -0,0 +1,417 @@ +//! Various extension methods to ast Expr Nodes, which are hard to code-generate. + +use crate::{ + ast::{self, support, AstChildren, AstNode}, + SmolStr, + SyntaxKind::*, + SyntaxToken, T, +}; + +impl ast::Expr { + pub fn is_block_like(&self) -> bool { + match self { + ast::Expr::IfExpr(_) + | ast::Expr::LoopExpr(_) + | ast::Expr::ForExpr(_) + | ast::Expr::WhileExpr(_) + | ast::Expr::BlockExpr(_) + | ast::Expr::MatchExpr(_) + | ast::Expr::EffectExpr(_) => true, + _ => false, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ElseBranch { + Block(ast::BlockExpr), + IfExpr(ast::IfExpr), +} + +impl ast::IfExpr { + pub fn then_branch(&self) -> Option { + self.blocks().next() + } + pub fn else_branch(&self) -> Option { + let res = match self.blocks().nth(1) { + Some(block) => ElseBranch::Block(block), + None => { + let elif: ast::IfExpr = support::child(self.syntax())?; + ElseBranch::IfExpr(elif) + } + }; + Some(res) + } + + pub fn blocks(&self) -> AstChildren { + support::children(self.syntax()) + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum PrefixOp { + /// The `*` operator for dereferencing + Deref, + /// The `!` operator for logical inversion + Not, + /// The `-` operator for negation + Neg, +} + +impl ast::PrefixExpr { + pub fn op_kind(&self) -> Option { + match self.op_token()?.kind() { + T![*] => Some(PrefixOp::Deref), + T![!] => Some(PrefixOp::Not), + T![-] => Some(PrefixOp::Neg), + _ => None, + } + } + + pub fn op_token(&self) -> Option { + self.syntax().first_child_or_token()?.into_token() + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum BinOp { + /// The `||` operator for boolean OR + BooleanOr, + /// The `&&` operator for boolean AND + BooleanAnd, + /// The `==` operator for equality testing + EqualityTest, + /// The `!=` operator for equality testing + NegatedEqualityTest, + /// The `<=` operator for lesser-equal testing + LesserEqualTest, + /// The `>=` operator for greater-equal testing + GreaterEqualTest, + /// The `<` operator for comparison + LesserTest, + /// The `>` operator for comparison + GreaterTest, + /// The `+` operator for addition + Addition, + /// The `*` operator for multiplication + Multiplication, + /// The `-` operator for subtraction + Subtraction, + /// The `/` operator for division + Division, + /// The `%` operator for remainder after division + Remainder, + /// The `<<` operator for left shift + LeftShift, + /// The `>>` operator for right shift + RightShift, + /// The `^` operator for bitwise XOR + BitwiseXor, + /// The `|` operator for bitwise OR + BitwiseOr, + /// The `&` operator for bitwise AND + BitwiseAnd, + /// The `=` operator for assignment + Assignment, + /// The `+=` operator for assignment after addition + AddAssign, + /// The `/=` operator for assignment after division + DivAssign, + /// The `*=` operator for assignment after multiplication + MulAssign, + /// The `%=` operator for assignment after remainders + RemAssign, + /// The `>>=` operator for assignment after shifting right + ShrAssign, + /// The `<<=` operator for assignment after shifting left + ShlAssign, + /// The `-=` operator for assignment after subtraction + SubAssign, + /// The `|=` operator for assignment after bitwise OR + BitOrAssign, + /// The `&=` operator for assignment after bitwise AND + BitAndAssign, + /// The `^=` operator for assignment after bitwise XOR + BitXorAssign, +} + +impl BinOp { + pub fn is_assignment(self) -> bool { + match self { + BinOp::Assignment + | BinOp::AddAssign + | BinOp::DivAssign + | BinOp::MulAssign + | BinOp::RemAssign + | BinOp::ShrAssign + | BinOp::ShlAssign + | BinOp::SubAssign + | BinOp::BitOrAssign + | BinOp::BitAndAssign + | BinOp::BitXorAssign => true, + _ => false, + } + } +} + +impl ast::BinExpr { + pub fn op_details(&self) -> Option<(SyntaxToken, BinOp)> { + self.syntax().children_with_tokens().filter_map(|it| it.into_token()).find_map(|c| { + let bin_op = match c.kind() { + T![||] => BinOp::BooleanOr, + T![&&] => BinOp::BooleanAnd, + T![==] => BinOp::EqualityTest, + T![!=] => BinOp::NegatedEqualityTest, + T![<=] => BinOp::LesserEqualTest, + T![>=] => BinOp::GreaterEqualTest, + T![<] => BinOp::LesserTest, + T![>] => BinOp::GreaterTest, + T![+] => BinOp::Addition, + T![*] => BinOp::Multiplication, + T![-] => BinOp::Subtraction, + T![/] => BinOp::Division, + T![%] => BinOp::Remainder, + T![<<] => BinOp::LeftShift, + T![>>] => BinOp::RightShift, + T![^] => BinOp::BitwiseXor, + T![|] => BinOp::BitwiseOr, + T![&] => BinOp::BitwiseAnd, + T![=] => BinOp::Assignment, + T![+=] => BinOp::AddAssign, + T![/=] => BinOp::DivAssign, + T![*=] => BinOp::MulAssign, + T![%=] => BinOp::RemAssign, + T![>>=] => BinOp::ShrAssign, + T![<<=] => BinOp::ShlAssign, + T![-=] => BinOp::SubAssign, + T![|=] => BinOp::BitOrAssign, + T![&=] => BinOp::BitAndAssign, + T![^=] => BinOp::BitXorAssign, + _ => return None, + }; + Some((c, bin_op)) + }) + } + + pub fn op_kind(&self) -> Option { + self.op_details().map(|t| t.1) + } + + pub fn op_token(&self) -> Option { + self.op_details().map(|t| t.0) + } + + pub fn lhs(&self) -> Option { + support::children(self.syntax()).next() + } + + pub fn rhs(&self) -> Option { + support::children(self.syntax()).nth(1) + } + + pub fn sub_exprs(&self) -> (Option, Option) { + let mut children = support::children(self.syntax()); + let first = children.next(); + let second = children.next(); + (first, second) + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum RangeOp { + /// `..` + Exclusive, + /// `..=` + Inclusive, +} + +impl ast::RangeExpr { + fn op_details(&self) -> Option<(usize, SyntaxToken, RangeOp)> { + self.syntax().children_with_tokens().enumerate().find_map(|(ix, child)| { + let token = child.into_token()?; + let bin_op = match token.kind() { + T![..] => RangeOp::Exclusive, + T![..=] => RangeOp::Inclusive, + _ => return None, + }; + Some((ix, token, bin_op)) + }) + } + + pub fn op_kind(&self) -> Option { + self.op_details().map(|t| t.2) + } + + pub fn op_token(&self) -> Option { + self.op_details().map(|t| t.1) + } + + pub fn start(&self) -> Option { + let op_ix = self.op_details()?.0; + self.syntax() + .children_with_tokens() + .take(op_ix) + .find_map(|it| ast::Expr::cast(it.into_node()?)) + } + + pub fn end(&self) -> Option { + let op_ix = self.op_details()?.0; + self.syntax() + .children_with_tokens() + .skip(op_ix + 1) + .find_map(|it| ast::Expr::cast(it.into_node()?)) + } +} + +impl ast::IndexExpr { + pub fn base(&self) -> Option { + support::children(self.syntax()).next() + } + pub fn index(&self) -> Option { + support::children(self.syntax()).nth(1) + } +} + +pub enum ArrayExprKind { + Repeat { initializer: Option, repeat: Option }, + ElementList(AstChildren), +} + +impl ast::ArrayExpr { + pub fn kind(&self) -> ArrayExprKind { + if self.is_repeat() { + ArrayExprKind::Repeat { + initializer: support::children(self.syntax()).next(), + repeat: support::children(self.syntax()).nth(1), + } + } else { + ArrayExprKind::ElementList(support::children(self.syntax())) + } + } + + fn is_repeat(&self) -> bool { + self.syntax().children_with_tokens().any(|it| it.kind() == T![;]) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum LiteralKind { + String, + ByteString, + Char, + Byte, + IntNumber { suffix: Option }, + FloatNumber { suffix: Option }, + Bool(bool), +} + +impl ast::Literal { + pub fn token(&self) -> SyntaxToken { + self.syntax() + .children_with_tokens() + .find(|e| e.kind() != ATTR && !e.kind().is_trivia()) + .and_then(|e| e.into_token()) + .unwrap() + } + + fn find_suffix(text: &str, possible_suffixes: &[&str]) -> Option { + possible_suffixes + .iter() + .find(|&suffix| text.ends_with(suffix)) + .map(|&suffix| SmolStr::new(suffix)) + } + + pub fn kind(&self) -> LiteralKind { + const INT_SUFFIXES: [&str; 12] = [ + "u64", "u32", "u16", "u8", "usize", "isize", "i64", "i32", "i16", "i8", "u128", "i128", + ]; + const FLOAT_SUFFIXES: [&str; 2] = ["f32", "f64"]; + + let token = self.token(); + + match token.kind() { + INT_NUMBER => { + // FYI: there was a bug here previously, thus an if statement bellow is necessary. + // The lexer treats e.g. `1f64` as an integer literal. See + // https://github.com/rust-analyzer/rust-analyzer/issues/1592 + // and the comments on the linked PR. + + let text = token.text(); + + if let suffix @ Some(_) = Self::find_suffix(&text, &FLOAT_SUFFIXES) { + LiteralKind::FloatNumber { suffix } + } else { + LiteralKind::IntNumber { suffix: Self::find_suffix(&text, &INT_SUFFIXES) } + } + } + FLOAT_NUMBER => { + let text = token.text(); + LiteralKind::FloatNumber { suffix: Self::find_suffix(&text, &FLOAT_SUFFIXES) } + } + STRING | RAW_STRING => LiteralKind::String, + T![true] => LiteralKind::Bool(true), + T![false] => LiteralKind::Bool(false), + BYTE_STRING | RAW_BYTE_STRING => LiteralKind::ByteString, + CHAR => LiteralKind::Char, + BYTE => LiteralKind::Byte, + _ => unreachable!(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Effect { + Async(SyntaxToken), + Unsafe(SyntaxToken), + Try(SyntaxToken), + // Very much not an effect, but we stuff it into this node anyway + Label(ast::Label), +} + +impl ast::EffectExpr { + pub fn effect(&self) -> Effect { + if let Some(token) = self.async_token() { + return Effect::Async(token); + } + if let Some(token) = self.unsafe_token() { + return Effect::Unsafe(token); + } + if let Some(token) = self.try_token() { + return Effect::Try(token); + } + if let Some(label) = self.label() { + return Effect::Label(label); + } + unreachable!("ast::EffectExpr without Effect") + } +} + +impl ast::BlockExpr { + /// false if the block is an intrinsic part of the syntax and can't be + /// replaced with arbitrary expression. + /// + /// ```not_rust + /// fn foo() { not_stand_alone } + /// const FOO: () = { stand_alone }; + /// ``` + pub fn is_standalone(&self) -> bool { + let parent = match self.syntax().parent() { + Some(it) => it, + None => return true, + }; + !matches!(parent.kind(), FN_DEF | IF_EXPR | WHILE_EXPR | LOOP_EXPR | EFFECT_EXPR) + } +} + +#[test] +fn test_literal_with_attr() { + let parse = ast::SourceFile::parse(r#"const _: &str = { #[attr] "Hello" };"#); + let lit = parse.tree().syntax().descendants().find_map(ast::Literal::cast).unwrap(); + assert_eq!(lit.token().text(), r#""Hello""#); +} + +impl ast::RecordField { + pub fn parent_record_lit(&self) -> ast::RecordLit { + self.syntax().ancestors().find_map(ast::RecordLit::cast).unwrap() + } +} diff --git a/crates/ra_syntax/src/ast/expr_extensions.rs b/crates/ra_syntax/src/ast/expr_extensions.rs deleted file mode 100644 index db5438d68..000000000 --- a/crates/ra_syntax/src/ast/expr_extensions.rs +++ /dev/null @@ -1,417 +0,0 @@ -//! Various extension methods to ast Expr Nodes, which are hard to code-generate. - -use crate::{ - ast::{self, support, AstChildren, AstNode}, - SmolStr, - SyntaxKind::*, - SyntaxToken, T, -}; - -impl ast::Expr { - pub fn is_block_like(&self) -> bool { - match self { - ast::Expr::IfExpr(_) - | ast::Expr::LoopExpr(_) - | ast::Expr::ForExpr(_) - | ast::Expr::WhileExpr(_) - | ast::Expr::BlockExpr(_) - | ast::Expr::MatchExpr(_) - | ast::Expr::EffectExpr(_) => true, - _ => false, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ElseBranch { - Block(ast::BlockExpr), - IfExpr(ast::IfExpr), -} - -impl ast::IfExpr { - pub fn then_branch(&self) -> Option { - self.blocks().next() - } - pub fn else_branch(&self) -> Option { - let res = match self.blocks().nth(1) { - Some(block) => ElseBranch::Block(block), - None => { - let elif: ast::IfExpr = support::child(self.syntax())?; - ElseBranch::IfExpr(elif) - } - }; - Some(res) - } - - pub fn blocks(&self) -> AstChildren { - support::children(self.syntax()) - } -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub enum PrefixOp { - /// The `*` operator for dereferencing - Deref, - /// The `!` operator for logical inversion - Not, - /// The `-` operator for negation - Neg, -} - -impl ast::PrefixExpr { - pub fn op_kind(&self) -> Option { - match self.op_token()?.kind() { - T![*] => Some(PrefixOp::Deref), - T![!] => Some(PrefixOp::Not), - T![-] => Some(PrefixOp::Neg), - _ => None, - } - } - - pub fn op_token(&self) -> Option { - self.syntax().first_child_or_token()?.into_token() - } -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub enum BinOp { - /// The `||` operator for boolean OR - BooleanOr, - /// The `&&` operator for boolean AND - BooleanAnd, - /// The `==` operator for equality testing - EqualityTest, - /// The `!=` operator for equality testing - NegatedEqualityTest, - /// The `<=` operator for lesser-equal testing - LesserEqualTest, - /// The `>=` operator for greater-equal testing - GreaterEqualTest, - /// The `<` operator for comparison - LesserTest, - /// The `>` operator for comparison - GreaterTest, - /// The `+` operator for addition - Addition, - /// The `*` operator for multiplication - Multiplication, - /// The `-` operator for subtraction - Subtraction, - /// The `/` operator for division - Division, - /// The `%` operator for remainder after division - Remainder, - /// The `<<` operator for left shift - LeftShift, - /// The `>>` operator for right shift - RightShift, - /// The `^` operator for bitwise XOR - BitwiseXor, - /// The `|` operator for bitwise OR - BitwiseOr, - /// The `&` operator for bitwise AND - BitwiseAnd, - /// The `=` operator for assignment - Assignment, - /// The `+=` operator for assignment after addition - AddAssign, - /// The `/=` operator for assignment after division - DivAssign, - /// The `*=` operator for assignment after multiplication - MulAssign, - /// The `%=` operator for assignment after remainders - RemAssign, - /// The `>>=` operator for assignment after shifting right - ShrAssign, - /// The `<<=` operator for assignment after shifting left - ShlAssign, - /// The `-=` operator for assignment after subtraction - SubAssign, - /// The `|=` operator for assignment after bitwise OR - BitOrAssign, - /// The `&=` operator for assignment after bitwise AND - BitAndAssign, - /// The `^=` operator for assignment after bitwise XOR - BitXorAssign, -} - -impl BinOp { - pub fn is_assignment(self) -> bool { - match self { - BinOp::Assignment - | BinOp::AddAssign - | BinOp::DivAssign - | BinOp::MulAssign - | BinOp::RemAssign - | BinOp::ShrAssign - | BinOp::ShlAssign - | BinOp::SubAssign - | BinOp::BitOrAssign - | BinOp::BitAndAssign - | BinOp::BitXorAssign => true, - _ => false, - } - } -} - -impl ast::BinExpr { - pub fn op_details(&self) -> Option<(SyntaxToken, BinOp)> { - self.syntax().children_with_tokens().filter_map(|it| it.into_token()).find_map(|c| { - let bin_op = match c.kind() { - T![||] => BinOp::BooleanOr, - T![&&] => BinOp::BooleanAnd, - T![==] => BinOp::EqualityTest, - T![!=] => BinOp::NegatedEqualityTest, - T![<=] => BinOp::LesserEqualTest, - T![>=] => BinOp::GreaterEqualTest, - T![<] => BinOp::LesserTest, - T![>] => BinOp::GreaterTest, - T![+] => BinOp::Addition, - T![*] => BinOp::Multiplication, - T![-] => BinOp::Subtraction, - T![/] => BinOp::Division, - T![%] => BinOp::Remainder, - T![<<] => BinOp::LeftShift, - T![>>] => BinOp::RightShift, - T![^] => BinOp::BitwiseXor, - T![|] => BinOp::BitwiseOr, - T![&] => BinOp::BitwiseAnd, - T![=] => BinOp::Assignment, - T![+=] => BinOp::AddAssign, - T![/=] => BinOp::DivAssign, - T![*=] => BinOp::MulAssign, - T![%=] => BinOp::RemAssign, - T![>>=] => BinOp::ShrAssign, - T![<<=] => BinOp::ShlAssign, - T![-=] => BinOp::SubAssign, - T![|=] => BinOp::BitOrAssign, - T![&=] => BinOp::BitAndAssign, - T![^=] => BinOp::BitXorAssign, - _ => return None, - }; - Some((c, bin_op)) - }) - } - - pub fn op_kind(&self) -> Option { - self.op_details().map(|t| t.1) - } - - pub fn op_token(&self) -> Option { - self.op_details().map(|t| t.0) - } - - pub fn lhs(&self) -> Option { - support::children(self.syntax()).next() - } - - pub fn rhs(&self) -> Option { - support::children(self.syntax()).nth(1) - } - - pub fn sub_exprs(&self) -> (Option, Option) { - let mut children = support::children(self.syntax()); - let first = children.next(); - let second = children.next(); - (first, second) - } -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub enum RangeOp { - /// `..` - Exclusive, - /// `..=` - Inclusive, -} - -impl ast::RangeExpr { - fn op_details(&self) -> Option<(usize, SyntaxToken, RangeOp)> { - self.syntax().children_with_tokens().enumerate().find_map(|(ix, child)| { - let token = child.into_token()?; - let bin_op = match token.kind() { - T![..] => RangeOp::Exclusive, - T![..=] => RangeOp::Inclusive, - _ => return None, - }; - Some((ix, token, bin_op)) - }) - } - - pub fn op_kind(&self) -> Option { - self.op_details().map(|t| t.2) - } - - pub fn op_token(&self) -> Option { - self.op_details().map(|t| t.1) - } - - pub fn start(&self) -> Option { - let op_ix = self.op_details()?.0; - self.syntax() - .children_with_tokens() - .take(op_ix) - .find_map(|it| ast::Expr::cast(it.into_node()?)) - } - - pub fn end(&self) -> Option { - let op_ix = self.op_details()?.0; - self.syntax() - .children_with_tokens() - .skip(op_ix + 1) - .find_map(|it| ast::Expr::cast(it.into_node()?)) - } -} - -impl ast::IndexExpr { - pub fn base(&self) -> Option { - support::children(self.syntax()).next() - } - pub fn index(&self) -> Option { - support::children(self.syntax()).nth(1) - } -} - -pub enum ArrayExprKind { - Repeat { initializer: Option, repeat: Option }, - ElementList(AstChildren), -} - -impl ast::ArrayExpr { - pub fn kind(&self) -> ArrayExprKind { - if self.is_repeat() { - ArrayExprKind::Repeat { - initializer: support::children(self.syntax()).next(), - repeat: support::children(self.syntax()).nth(1), - } - } else { - ArrayExprKind::ElementList(support::children(self.syntax())) - } - } - - fn is_repeat(&self) -> bool { - self.syntax().children_with_tokens().any(|it| it.kind() == T![;]) - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub enum LiteralKind { - String, - ByteString, - Char, - Byte, - IntNumber { suffix: Option }, - FloatNumber { suffix: Option }, - Bool(bool), -} - -impl ast::Literal { - pub fn token(&self) -> SyntaxToken { - self.syntax() - .children_with_tokens() - .find(|e| e.kind() != ATTR && !e.kind().is_trivia()) - .and_then(|e| e.into_token()) - .unwrap() - } - - fn find_suffix(text: &str, possible_suffixes: &[&str]) -> Option { - possible_suffixes - .iter() - .find(|&suffix| text.ends_with(suffix)) - .map(|&suffix| SmolStr::new(suffix)) - } - - pub fn kind(&self) -> LiteralKind { - const INT_SUFFIXES: [&str; 12] = [ - "u64", "u32", "u16", "u8", "usize", "isize", "i64", "i32", "i16", "i8", "u128", "i128", - ]; - const FLOAT_SUFFIXES: [&str; 2] = ["f32", "f64"]; - - let token = self.token(); - - match token.kind() { - INT_NUMBER => { - // FYI: there was a bug here previously, thus an if statement bellow is necessary. - // The lexer treats e.g. `1f64` as an integer literal. See - // https://github.com/rust-analyzer/rust-analyzer/issues/1592 - // and the comments on the linked PR. - - let text = token.text(); - - if let suffix @ Some(_) = Self::find_suffix(&text, &FLOAT_SUFFIXES) { - LiteralKind::FloatNumber { suffix } - } else { - LiteralKind::IntNumber { suffix: Self::find_suffix(&text, &INT_SUFFIXES) } - } - } - FLOAT_NUMBER => { - let text = token.text(); - LiteralKind::FloatNumber { suffix: Self::find_suffix(&text, &FLOAT_SUFFIXES) } - } - STRING | RAW_STRING => LiteralKind::String, - T![true] => LiteralKind::Bool(true), - T![false] => LiteralKind::Bool(false), - BYTE_STRING | RAW_BYTE_STRING => LiteralKind::ByteString, - CHAR => LiteralKind::Char, - BYTE => LiteralKind::Byte, - _ => unreachable!(), - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Effect { - Async(SyntaxToken), - Unsafe(SyntaxToken), - Try(SyntaxToken), - // Very much not an effect, but we stuff it into this node anyway - Label(ast::Label), -} - -impl ast::EffectExpr { - pub fn effect(&self) -> Effect { - if let Some(token) = self.async_token() { - return Effect::Async(token); - } - if let Some(token) = self.unsafe_token() { - return Effect::Unsafe(token); - } - if let Some(token) = self.try_token() { - return Effect::Try(token); - } - if let Some(label) = self.label() { - return Effect::Label(label); - } - unreachable!("ast::EffectExpr without Effect") - } -} - -impl ast::BlockExpr { - /// false if the block is an intrinsic part of the syntax and can't be - /// replaced with arbitrary expression. - /// - /// ```not_rust - /// fn foo() { not_stand_alone } - /// const FOO: () = { stand_alone }; - /// ``` - pub fn is_standalone(&self) -> bool { - let parent = match self.syntax().parent() { - Some(it) => it, - None => return true, - }; - !matches!(parent.kind(), FN_DEF | IF_EXPR | WHILE_EXPR | LOOP_EXPR | EFFECT_EXPR) - } -} - -#[test] -fn test_literal_with_attr() { - let parse = ast::SourceFile::parse(r#"const _: &str = { #[attr] "Hello" };"#); - let lit = parse.tree().syntax().descendants().find_map(ast::Literal::cast).unwrap(); - assert_eq!(lit.token().text(), r#""Hello""#); -} - -impl ast::RecordField { - pub fn parent_record_lit(&self) -> ast::RecordLit { - self.syntax().ancestors().find_map(ast::RecordLit::cast).unwrap() - } -} diff --git a/crates/ra_syntax/src/ast/extensions.rs b/crates/ra_syntax/src/ast/extensions.rs deleted file mode 100644 index 662c6f73e..000000000 --- a/crates/ra_syntax/src/ast/extensions.rs +++ /dev/null @@ -1,474 +0,0 @@ -//! Various extension methods to ast Nodes, which are hard to code-generate. -//! Extensions for various expressions live in a sibling `expr_extensions` module. - -use std::fmt; - -use itertools::Itertools; -use ra_parser::SyntaxKind; - -use crate::{ - ast::{self, support, AstNode, AttrInput, NameOwner, SyntaxNode}, - SmolStr, SyntaxElement, SyntaxToken, T, -}; - -impl ast::Name { - pub fn text(&self) -> &SmolStr { - text_of_first_token(self.syntax()) - } -} - -impl ast::NameRef { - pub fn text(&self) -> &SmolStr { - text_of_first_token(self.syntax()) - } - - pub fn as_tuple_field(&self) -> Option { - self.text().parse().ok() - } -} - -fn text_of_first_token(node: &SyntaxNode) -> &SmolStr { - node.green().children().next().and_then(|it| it.into_token()).unwrap().text() -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum AttrKind { - Inner, - Outer, -} - -impl ast::Attr { - pub fn as_simple_atom(&self) -> Option { - match self.input() { - None => self.simple_name(), - Some(_) => None, - } - } - - pub fn as_simple_call(&self) -> Option<(SmolStr, ast::TokenTree)> { - match self.input() { - Some(AttrInput::TokenTree(tt)) => Some((self.simple_name()?, tt)), - _ => None, - } - } - - pub fn as_simple_key_value(&self) -> Option<(SmolStr, SmolStr)> { - match self.input() { - Some(AttrInput::Literal(lit)) => { - let key = self.simple_name()?; - // FIXME: escape? raw string? - let value = lit.syntax().first_token()?.text().trim_matches('"').into(); - Some((key, value)) - } - _ => None, - } - } - - pub fn simple_name(&self) -> Option { - let path = self.path()?; - match (path.segment(), path.qualifier()) { - (Some(segment), None) => Some(segment.syntax().first_token()?.text().clone()), - _ => None, - } - } - - pub fn kind(&self) -> AttrKind { - let first_token = self.syntax().first_token(); - let first_token_kind = first_token.as_ref().map(SyntaxToken::kind); - let second_token_kind = - first_token.and_then(|token| token.next_token()).as_ref().map(SyntaxToken::kind); - - match (first_token_kind, second_token_kind) { - (Some(SyntaxKind::POUND), Some(T![!])) => AttrKind::Inner, - _ => AttrKind::Outer, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum PathSegmentKind { - Name(ast::NameRef), - Type { type_ref: Option, trait_ref: Option }, - SelfKw, - SuperKw, - CrateKw, -} - -impl ast::PathSegment { - pub fn parent_path(&self) -> ast::Path { - self.syntax() - .parent() - .and_then(ast::Path::cast) - .expect("segments are always nested in paths") - } - - pub fn kind(&self) -> Option { - let res = if let Some(name_ref) = self.name_ref() { - PathSegmentKind::Name(name_ref) - } else { - match self.syntax().first_child_or_token()?.kind() { - T![self] => PathSegmentKind::SelfKw, - T![super] => PathSegmentKind::SuperKw, - T![crate] => PathSegmentKind::CrateKw, - T![<] => { - // or - // T is any TypeRef, Trait has to be a PathType - let mut type_refs = - self.syntax().children().filter(|node| ast::TypeRef::can_cast(node.kind())); - let type_ref = type_refs.next().and_then(ast::TypeRef::cast); - let trait_ref = type_refs.next().and_then(ast::PathType::cast); - PathSegmentKind::Type { type_ref, trait_ref } - } - _ => return None, - } - }; - Some(res) - } -} - -impl ast::Path { - pub fn parent_path(&self) -> Option { - self.syntax().parent().and_then(ast::Path::cast) - } -} - -impl ast::UseTreeList { - pub fn parent_use_tree(&self) -> ast::UseTree { - self.syntax() - .parent() - .and_then(ast::UseTree::cast) - .expect("UseTreeLists are always nested in UseTrees") - } -} - -impl ast::ImplDef { - pub fn target_type(&self) -> Option { - match self.target() { - (Some(t), None) | (_, Some(t)) => Some(t), - _ => None, - } - } - - pub fn target_trait(&self) -> Option { - match self.target() { - (Some(t), Some(_)) => Some(t), - _ => None, - } - } - - fn target(&self) -> (Option, Option) { - let mut types = support::children(self.syntax()); - let first = types.next(); - let second = types.next(); - (first, second) - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum StructKind { - Record(ast::RecordFieldDefList), - Tuple(ast::TupleFieldDefList), - Unit, -} - -impl StructKind { - fn from_node(node: &N) -> StructKind { - if let Some(nfdl) = support::child::(node.syntax()) { - StructKind::Record(nfdl) - } else if let Some(pfl) = support::child::(node.syntax()) { - StructKind::Tuple(pfl) - } else { - StructKind::Unit - } - } -} - -impl ast::StructDef { - pub fn kind(&self) -> StructKind { - StructKind::from_node(self) - } -} - -impl ast::RecordField { - pub fn for_field_name(field_name: &ast::NameRef) -> Option { - let candidate = - field_name.syntax().parent().and_then(ast::RecordField::cast).or_else(|| { - field_name.syntax().ancestors().nth(4).and_then(ast::RecordField::cast) - })?; - if candidate.field_name().as_ref() == Some(field_name) { - Some(candidate) - } else { - None - } - } - - /// Deals with field init shorthand - pub fn field_name(&self) -> Option { - if let Some(name_ref) = self.name_ref() { - return Some(name_ref); - } - if let Some(ast::Expr::PathExpr(expr)) = self.expr() { - let path = expr.path()?; - let segment = path.segment()?; - let name_ref = segment.name_ref()?; - if path.qualifier().is_none() { - return Some(name_ref); - } - } - None - } -} - -pub enum NameOrNameRef { - Name(ast::Name), - NameRef(ast::NameRef), -} - -impl fmt::Display for NameOrNameRef { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - NameOrNameRef::Name(it) => fmt::Display::fmt(it, f), - NameOrNameRef::NameRef(it) => fmt::Display::fmt(it, f), - } - } -} - -impl ast::RecordFieldPat { - /// Deals with field init shorthand - pub fn field_name(&self) -> Option { - if let Some(name_ref) = self.name_ref() { - return Some(NameOrNameRef::NameRef(name_ref)); - } - if let Some(ast::Pat::BindPat(pat)) = self.pat() { - let name = pat.name()?; - return Some(NameOrNameRef::Name(name)); - } - None - } -} - -impl ast::EnumVariant { - pub fn parent_enum(&self) -> ast::EnumDef { - self.syntax() - .parent() - .and_then(|it| it.parent()) - .and_then(ast::EnumDef::cast) - .expect("EnumVariants are always nested in Enums") - } - pub fn kind(&self) -> StructKind { - StructKind::from_node(self) - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum FieldKind { - Name(ast::NameRef), - Index(SyntaxToken), -} - -impl ast::FieldExpr { - pub fn index_token(&self) -> Option { - self.syntax - .children_with_tokens() - // FIXME: Accepting floats here to reject them in validation later - .find(|c| c.kind() == SyntaxKind::INT_NUMBER || c.kind() == SyntaxKind::FLOAT_NUMBER) - .as_ref() - .and_then(SyntaxElement::as_token) - .cloned() - } - - pub fn field_access(&self) -> Option { - if let Some(nr) = self.name_ref() { - Some(FieldKind::Name(nr)) - } else if let Some(tok) = self.index_token() { - Some(FieldKind::Index(tok)) - } else { - None - } - } -} - -pub struct SlicePatComponents { - pub prefix: Vec, - pub slice: Option, - pub suffix: Vec, -} - -impl ast::SlicePat { - pub fn components(&self) -> SlicePatComponents { - let mut args = self.args().peekable(); - let prefix = args - .peeking_take_while(|p| match p { - ast::Pat::DotDotPat(_) => false, - ast::Pat::BindPat(bp) => match bp.pat() { - Some(ast::Pat::DotDotPat(_)) => false, - _ => true, - }, - ast::Pat::RefPat(rp) => match rp.pat() { - Some(ast::Pat::DotDotPat(_)) => false, - Some(ast::Pat::BindPat(bp)) => match bp.pat() { - Some(ast::Pat::DotDotPat(_)) => false, - _ => true, - }, - _ => true, - }, - _ => true, - }) - .collect(); - let slice = args.next(); - let suffix = args.collect(); - - SlicePatComponents { prefix, slice, suffix } - } -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub enum SelfParamKind { - /// self - Owned, - /// &self - Ref, - /// &mut self - MutRef, -} - -impl ast::SelfParam { - pub fn kind(&self) -> SelfParamKind { - if self.amp_token().is_some() { - if self.mut_token().is_some() { - SelfParamKind::MutRef - } else { - SelfParamKind::Ref - } - } else { - SelfParamKind::Owned - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub enum TypeBoundKind { - /// Trait - PathType(ast::PathType), - /// for<'a> ... - ForType(ast::ForType), - /// 'a - Lifetime(SyntaxToken), -} - -impl ast::TypeBound { - pub fn kind(&self) -> TypeBoundKind { - if let Some(path_type) = support::children(self.syntax()).next() { - TypeBoundKind::PathType(path_type) - } else if let Some(for_type) = support::children(self.syntax()).next() { - TypeBoundKind::ForType(for_type) - } else if let Some(lifetime) = self.lifetime_token() { - TypeBoundKind::Lifetime(lifetime) - } else { - unreachable!() - } - } - - pub fn const_question_token(&self) -> Option { - self.syntax() - .children_with_tokens() - .filter_map(|it| it.into_token()) - .take_while(|it| it.kind() != T![const]) - .find(|it| it.kind() == T![?]) - } - - pub fn question_token(&self) -> Option { - if self.const_token().is_some() { - self.syntax() - .children_with_tokens() - .filter_map(|it| it.into_token()) - .skip_while(|it| it.kind() != T![const]) - .find(|it| it.kind() == T![?]) - } else { - support::token(&self.syntax, T![?]) - } - } -} - -pub enum VisibilityKind { - In(ast::Path), - PubCrate, - PubSuper, - PubSelf, - Pub, -} - -impl ast::Visibility { - pub fn kind(&self) -> VisibilityKind { - if let Some(path) = support::children(self.syntax()).next() { - VisibilityKind::In(path) - } else if self.crate_token().is_some() { - VisibilityKind::PubCrate - } else if self.super_token().is_some() { - VisibilityKind::PubSuper - } else if self.self_token().is_some() { - VisibilityKind::PubSelf - } else { - VisibilityKind::Pub - } - } -} - -impl ast::MacroCall { - pub fn is_macro_rules(&self) -> Option { - let name_ref = self.path()?.segment()?.name_ref()?; - if name_ref.text() == "macro_rules" { - self.name() - } else { - None - } - } - - pub fn is_bang(&self) -> bool { - self.is_macro_rules().is_none() - } -} - -impl ast::LifetimeParam { - pub fn lifetime_bounds(&self) -> impl Iterator { - self.syntax() - .children_with_tokens() - .filter_map(|it| it.into_token()) - .skip_while(|x| x.kind() != T![:]) - .filter(|it| it.kind() == T![lifetime]) - } -} - -impl ast::RangePat { - pub fn start(&self) -> Option { - self.syntax() - .children_with_tokens() - .take_while(|it| !(it.kind() == T![..] || it.kind() == T![..=])) - .filter_map(|it| it.into_node()) - .find_map(ast::Pat::cast) - } - - pub fn end(&self) -> Option { - self.syntax() - .children_with_tokens() - .skip_while(|it| !(it.kind() == T![..] || it.kind() == T![..=])) - .filter_map(|it| it.into_node()) - .find_map(ast::Pat::cast) - } -} - -impl ast::TokenTree { - pub fn left_delimiter_token(&self) -> Option { - self.syntax() - .first_child_or_token()? - .into_token() - .filter(|it| matches!(it.kind(), T!['{'] | T!['('] | T!['['])) - } - - pub fn right_delimiter_token(&self) -> Option { - self.syntax() - .last_child_or_token()? - .into_token() - .filter(|it| matches!(it.kind(), T!['}'] | T![')'] | T![']'])) - } -} diff --git a/crates/ra_syntax/src/ast/node_ext.rs b/crates/ra_syntax/src/ast/node_ext.rs new file mode 100644 index 000000000..662c6f73e --- /dev/null +++ b/crates/ra_syntax/src/ast/node_ext.rs @@ -0,0 +1,474 @@ +//! Various extension methods to ast Nodes, which are hard to code-generate. +//! Extensions for various expressions live in a sibling `expr_extensions` module. + +use std::fmt; + +use itertools::Itertools; +use ra_parser::SyntaxKind; + +use crate::{ + ast::{self, support, AstNode, AttrInput, NameOwner, SyntaxNode}, + SmolStr, SyntaxElement, SyntaxToken, T, +}; + +impl ast::Name { + pub fn text(&self) -> &SmolStr { + text_of_first_token(self.syntax()) + } +} + +impl ast::NameRef { + pub fn text(&self) -> &SmolStr { + text_of_first_token(self.syntax()) + } + + pub fn as_tuple_field(&self) -> Option { + self.text().parse().ok() + } +} + +fn text_of_first_token(node: &SyntaxNode) -> &SmolStr { + node.green().children().next().and_then(|it| it.into_token()).unwrap().text() +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum AttrKind { + Inner, + Outer, +} + +impl ast::Attr { + pub fn as_simple_atom(&self) -> Option { + match self.input() { + None => self.simple_name(), + Some(_) => None, + } + } + + pub fn as_simple_call(&self) -> Option<(SmolStr, ast::TokenTree)> { + match self.input() { + Some(AttrInput::TokenTree(tt)) => Some((self.simple_name()?, tt)), + _ => None, + } + } + + pub fn as_simple_key_value(&self) -> Option<(SmolStr, SmolStr)> { + match self.input() { + Some(AttrInput::Literal(lit)) => { + let key = self.simple_name()?; + // FIXME: escape? raw string? + let value = lit.syntax().first_token()?.text().trim_matches('"').into(); + Some((key, value)) + } + _ => None, + } + } + + pub fn simple_name(&self) -> Option { + let path = self.path()?; + match (path.segment(), path.qualifier()) { + (Some(segment), None) => Some(segment.syntax().first_token()?.text().clone()), + _ => None, + } + } + + pub fn kind(&self) -> AttrKind { + let first_token = self.syntax().first_token(); + let first_token_kind = first_token.as_ref().map(SyntaxToken::kind); + let second_token_kind = + first_token.and_then(|token| token.next_token()).as_ref().map(SyntaxToken::kind); + + match (first_token_kind, second_token_kind) { + (Some(SyntaxKind::POUND), Some(T![!])) => AttrKind::Inner, + _ => AttrKind::Outer, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum PathSegmentKind { + Name(ast::NameRef), + Type { type_ref: Option, trait_ref: Option }, + SelfKw, + SuperKw, + CrateKw, +} + +impl ast::PathSegment { + pub fn parent_path(&self) -> ast::Path { + self.syntax() + .parent() + .and_then(ast::Path::cast) + .expect("segments are always nested in paths") + } + + pub fn kind(&self) -> Option { + let res = if let Some(name_ref) = self.name_ref() { + PathSegmentKind::Name(name_ref) + } else { + match self.syntax().first_child_or_token()?.kind() { + T![self] => PathSegmentKind::SelfKw, + T![super] => PathSegmentKind::SuperKw, + T![crate] => PathSegmentKind::CrateKw, + T![<] => { + // or + // T is any TypeRef, Trait has to be a PathType + let mut type_refs = + self.syntax().children().filter(|node| ast::TypeRef::can_cast(node.kind())); + let type_ref = type_refs.next().and_then(ast::TypeRef::cast); + let trait_ref = type_refs.next().and_then(ast::PathType::cast); + PathSegmentKind::Type { type_ref, trait_ref } + } + _ => return None, + } + }; + Some(res) + } +} + +impl ast::Path { + pub fn parent_path(&self) -> Option { + self.syntax().parent().and_then(ast::Path::cast) + } +} + +impl ast::UseTreeList { + pub fn parent_use_tree(&self) -> ast::UseTree { + self.syntax() + .parent() + .and_then(ast::UseTree::cast) + .expect("UseTreeLists are always nested in UseTrees") + } +} + +impl ast::ImplDef { + pub fn target_type(&self) -> Option { + match self.target() { + (Some(t), None) | (_, Some(t)) => Some(t), + _ => None, + } + } + + pub fn target_trait(&self) -> Option { + match self.target() { + (Some(t), Some(_)) => Some(t), + _ => None, + } + } + + fn target(&self) -> (Option, Option) { + let mut types = support::children(self.syntax()); + let first = types.next(); + let second = types.next(); + (first, second) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum StructKind { + Record(ast::RecordFieldDefList), + Tuple(ast::TupleFieldDefList), + Unit, +} + +impl StructKind { + fn from_node(node: &N) -> StructKind { + if let Some(nfdl) = support::child::(node.syntax()) { + StructKind::Record(nfdl) + } else if let Some(pfl) = support::child::(node.syntax()) { + StructKind::Tuple(pfl) + } else { + StructKind::Unit + } + } +} + +impl ast::StructDef { + pub fn kind(&self) -> StructKind { + StructKind::from_node(self) + } +} + +impl ast::RecordField { + pub fn for_field_name(field_name: &ast::NameRef) -> Option { + let candidate = + field_name.syntax().parent().and_then(ast::RecordField::cast).or_else(|| { + field_name.syntax().ancestors().nth(4).and_then(ast::RecordField::cast) + })?; + if candidate.field_name().as_ref() == Some(field_name) { + Some(candidate) + } else { + None + } + } + + /// Deals with field init shorthand + pub fn field_name(&self) -> Option { + if let Some(name_ref) = self.name_ref() { + return Some(name_ref); + } + if let Some(ast::Expr::PathExpr(expr)) = self.expr() { + let path = expr.path()?; + let segment = path.segment()?; + let name_ref = segment.name_ref()?; + if path.qualifier().is_none() { + return Some(name_ref); + } + } + None + } +} + +pub enum NameOrNameRef { + Name(ast::Name), + NameRef(ast::NameRef), +} + +impl fmt::Display for NameOrNameRef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + NameOrNameRef::Name(it) => fmt::Display::fmt(it, f), + NameOrNameRef::NameRef(it) => fmt::Display::fmt(it, f), + } + } +} + +impl ast::RecordFieldPat { + /// Deals with field init shorthand + pub fn field_name(&self) -> Option { + if let Some(name_ref) = self.name_ref() { + return Some(NameOrNameRef::NameRef(name_ref)); + } + if let Some(ast::Pat::BindPat(pat)) = self.pat() { + let name = pat.name()?; + return Some(NameOrNameRef::Name(name)); + } + None + } +} + +impl ast::EnumVariant { + pub fn parent_enum(&self) -> ast::EnumDef { + self.syntax() + .parent() + .and_then(|it| it.parent()) + .and_then(ast::EnumDef::cast) + .expect("EnumVariants are always nested in Enums") + } + pub fn kind(&self) -> StructKind { + StructKind::from_node(self) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum FieldKind { + Name(ast::NameRef), + Index(SyntaxToken), +} + +impl ast::FieldExpr { + pub fn index_token(&self) -> Option { + self.syntax + .children_with_tokens() + // FIXME: Accepting floats here to reject them in validation later + .find(|c| c.kind() == SyntaxKind::INT_NUMBER || c.kind() == SyntaxKind::FLOAT_NUMBER) + .as_ref() + .and_then(SyntaxElement::as_token) + .cloned() + } + + pub fn field_access(&self) -> Option { + if let Some(nr) = self.name_ref() { + Some(FieldKind::Name(nr)) + } else if let Some(tok) = self.index_token() { + Some(FieldKind::Index(tok)) + } else { + None + } + } +} + +pub struct SlicePatComponents { + pub prefix: Vec, + pub slice: Option, + pub suffix: Vec, +} + +impl ast::SlicePat { + pub fn components(&self) -> SlicePatComponents { + let mut args = self.args().peekable(); + let prefix = args + .peeking_take_while(|p| match p { + ast::Pat::DotDotPat(_) => false, + ast::Pat::BindPat(bp) => match bp.pat() { + Some(ast::Pat::DotDotPat(_)) => false, + _ => true, + }, + ast::Pat::RefPat(rp) => match rp.pat() { + Some(ast::Pat::DotDotPat(_)) => false, + Some(ast::Pat::BindPat(bp)) => match bp.pat() { + Some(ast::Pat::DotDotPat(_)) => false, + _ => true, + }, + _ => true, + }, + _ => true, + }) + .collect(); + let slice = args.next(); + let suffix = args.collect(); + + SlicePatComponents { prefix, slice, suffix } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum SelfParamKind { + /// self + Owned, + /// &self + Ref, + /// &mut self + MutRef, +} + +impl ast::SelfParam { + pub fn kind(&self) -> SelfParamKind { + if self.amp_token().is_some() { + if self.mut_token().is_some() { + SelfParamKind::MutRef + } else { + SelfParamKind::Ref + } + } else { + SelfParamKind::Owned + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum TypeBoundKind { + /// Trait + PathType(ast::PathType), + /// for<'a> ... + ForType(ast::ForType), + /// 'a + Lifetime(SyntaxToken), +} + +impl ast::TypeBound { + pub fn kind(&self) -> TypeBoundKind { + if let Some(path_type) = support::children(self.syntax()).next() { + TypeBoundKind::PathType(path_type) + } else if let Some(for_type) = support::children(self.syntax()).next() { + TypeBoundKind::ForType(for_type) + } else if let Some(lifetime) = self.lifetime_token() { + TypeBoundKind::Lifetime(lifetime) + } else { + unreachable!() + } + } + + pub fn const_question_token(&self) -> Option { + self.syntax() + .children_with_tokens() + .filter_map(|it| it.into_token()) + .take_while(|it| it.kind() != T![const]) + .find(|it| it.kind() == T![?]) + } + + pub fn question_token(&self) -> Option { + if self.const_token().is_some() { + self.syntax() + .children_with_tokens() + .filter_map(|it| it.into_token()) + .skip_while(|it| it.kind() != T![const]) + .find(|it| it.kind() == T![?]) + } else { + support::token(&self.syntax, T![?]) + } + } +} + +pub enum VisibilityKind { + In(ast::Path), + PubCrate, + PubSuper, + PubSelf, + Pub, +} + +impl ast::Visibility { + pub fn kind(&self) -> VisibilityKind { + if let Some(path) = support::children(self.syntax()).next() { + VisibilityKind::In(path) + } else if self.crate_token().is_some() { + VisibilityKind::PubCrate + } else if self.super_token().is_some() { + VisibilityKind::PubSuper + } else if self.self_token().is_some() { + VisibilityKind::PubSelf + } else { + VisibilityKind::Pub + } + } +} + +impl ast::MacroCall { + pub fn is_macro_rules(&self) -> Option { + let name_ref = self.path()?.segment()?.name_ref()?; + if name_ref.text() == "macro_rules" { + self.name() + } else { + None + } + } + + pub fn is_bang(&self) -> bool { + self.is_macro_rules().is_none() + } +} + +impl ast::LifetimeParam { + pub fn lifetime_bounds(&self) -> impl Iterator { + self.syntax() + .children_with_tokens() + .filter_map(|it| it.into_token()) + .skip_while(|x| x.kind() != T![:]) + .filter(|it| it.kind() == T![lifetime]) + } +} + +impl ast::RangePat { + pub fn start(&self) -> Option { + self.syntax() + .children_with_tokens() + .take_while(|it| !(it.kind() == T![..] || it.kind() == T![..=])) + .filter_map(|it| it.into_node()) + .find_map(ast::Pat::cast) + } + + pub fn end(&self) -> Option { + self.syntax() + .children_with_tokens() + .skip_while(|it| !(it.kind() == T![..] || it.kind() == T![..=])) + .filter_map(|it| it.into_node()) + .find_map(ast::Pat::cast) + } +} + +impl ast::TokenTree { + pub fn left_delimiter_token(&self) -> Option { + self.syntax() + .first_child_or_token()? + .into_token() + .filter(|it| matches!(it.kind(), T!['{'] | T!['('] | T!['['])) + } + + pub fn right_delimiter_token(&self) -> Option { + self.syntax() + .last_child_or_token()? + .into_token() + .filter(|it| matches!(it.kind(), T!['}'] | T![')'] | T![']'])) + } +} diff --git a/crates/ra_syntax/src/ast/token_ext.rs b/crates/ra_syntax/src/ast/token_ext.rs new file mode 100644 index 000000000..c5ef92733 --- /dev/null +++ b/crates/ra_syntax/src/ast/token_ext.rs @@ -0,0 +1,538 @@ +//! There are many AstNodes, but only a few tokens, so we hand-write them here. + +use std::{ + borrow::Cow, + convert::{TryFrom, TryInto}, +}; + +use rustc_lexer::unescape::{unescape_literal, Mode}; + +use crate::{ + ast::{AstToken, Comment, RawString, String, Whitespace}, + TextRange, TextSize, +}; + +impl Comment { + pub fn kind(&self) -> CommentKind { + kind_by_prefix(self.text()) + } + + pub fn prefix(&self) -> &'static str { + for (prefix, k) in COMMENT_PREFIX_TO_KIND.iter() { + if *k == self.kind() && self.text().starts_with(prefix) { + return prefix; + } + } + unreachable!() + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct CommentKind { + pub shape: CommentShape, + pub doc: Option, +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum CommentShape { + Line, + Block, +} + +impl CommentShape { + pub fn is_line(self) -> bool { + self == CommentShape::Line + } + + pub fn is_block(self) -> bool { + self == CommentShape::Block + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum CommentPlacement { + Inner, + Outer, +} + +const COMMENT_PREFIX_TO_KIND: &[(&str, CommentKind)] = { + use {CommentPlacement::*, CommentShape::*}; + &[ + ("////", CommentKind { shape: Line, doc: None }), + ("///", CommentKind { shape: Line, doc: Some(Outer) }), + ("//!", CommentKind { shape: Line, doc: Some(Inner) }), + ("/**", CommentKind { shape: Block, doc: Some(Outer) }), + ("/*!", CommentKind { shape: Block, doc: Some(Inner) }), + ("//", CommentKind { shape: Line, doc: None }), + ("/*", CommentKind { shape: Block, doc: None }), + ] +}; + +fn kind_by_prefix(text: &str) -> CommentKind { + if text == "/**/" { + return CommentKind { shape: CommentShape::Block, doc: None }; + } + for (prefix, kind) in COMMENT_PREFIX_TO_KIND.iter() { + if text.starts_with(prefix) { + return *kind; + } + } + panic!("bad comment text: {:?}", text) +} + +impl Whitespace { + pub fn spans_multiple_lines(&self) -> bool { + let text = self.text(); + text.find('\n').map_or(false, |idx| text[idx + 1..].contains('\n')) + } +} + +pub struct QuoteOffsets { + pub quotes: (TextRange, TextRange), + pub contents: TextRange, +} + +impl QuoteOffsets { + fn new(literal: &str) -> Option { + let left_quote = literal.find('"')?; + let right_quote = literal.rfind('"')?; + if left_quote == right_quote { + // `literal` only contains one quote + return None; + } + + let start = TextSize::from(0); + let left_quote = TextSize::try_from(left_quote).unwrap() + TextSize::of('"'); + let right_quote = TextSize::try_from(right_quote).unwrap(); + let end = TextSize::of(literal); + + let res = QuoteOffsets { + quotes: (TextRange::new(start, left_quote), TextRange::new(right_quote, end)), + contents: TextRange::new(left_quote, right_quote), + }; + Some(res) + } +} + +pub trait HasQuotes: AstToken { + fn quote_offsets(&self) -> Option { + let text = self.text().as_str(); + let offsets = QuoteOffsets::new(text)?; + let o = self.syntax().text_range().start(); + let offsets = QuoteOffsets { + quotes: (offsets.quotes.0 + o, offsets.quotes.1 + o), + contents: offsets.contents + o, + }; + Some(offsets) + } + fn open_quote_text_range(&self) -> Option { + self.quote_offsets().map(|it| it.quotes.0) + } + + fn close_quote_text_range(&self) -> Option { + self.quote_offsets().map(|it| it.quotes.1) + } + + fn text_range_between_quotes(&self) -> Option { + self.quote_offsets().map(|it| it.contents) + } +} + +impl HasQuotes for String {} +impl HasQuotes for RawString {} + +pub trait HasStringValue: HasQuotes { + fn value(&self) -> Option>; +} + +impl HasStringValue for String { + fn value(&self) -> Option> { + let text = self.text().as_str(); + let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()]; + + let mut buf = std::string::String::with_capacity(text.len()); + let mut has_error = false; + unescape_literal(text, Mode::Str, &mut |_, unescaped_char| match unescaped_char { + Ok(c) => buf.push(c), + Err(_) => has_error = true, + }); + + if has_error { + return None; + } + // FIXME: don't actually allocate for borrowed case + let res = if buf == text { Cow::Borrowed(text) } else { Cow::Owned(buf) }; + Some(res) + } +} + +impl HasStringValue for RawString { + fn value(&self) -> Option> { + let text = self.text().as_str(); + let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()]; + Some(Cow::Borrowed(text)) + } +} + +impl RawString { + pub fn map_range_up(&self, range: TextRange) -> Option { + let contents_range = self.text_range_between_quotes()?; + assert!(TextRange::up_to(contents_range.len()).contains_range(range)); + Some(range + contents_range.start()) + } +} + +#[derive(Debug)] +pub enum FormatSpecifier { + Open, + Close, + Integer, + Identifier, + Colon, + Fill, + Align, + Sign, + NumberSign, + Zero, + DollarSign, + Dot, + Asterisk, + QuestionMark, +} + +pub trait HasFormatSpecifier: AstToken { + fn char_ranges( + &self, + ) -> Option)>>; + + fn lex_format_specifier(&self, mut callback: F) + where + F: FnMut(TextRange, FormatSpecifier), + { + let char_ranges = if let Some(char_ranges) = self.char_ranges() { + char_ranges + } else { + return; + }; + let mut chars = char_ranges.iter().peekable(); + + while let Some((range, first_char)) = chars.next() { + match first_char { + Ok('{') => { + // Format specifier, see syntax at https://doc.rust-lang.org/std/fmt/index.html#syntax + if let Some((_, Ok('{'))) = chars.peek() { + // Escaped format specifier, `{{` + chars.next(); + continue; + } + + callback(*range, FormatSpecifier::Open); + + // check for integer/identifier + match chars + .peek() + .and_then(|next| next.1.as_ref().ok()) + .copied() + .unwrap_or_default() + { + '0'..='9' => { + // integer + read_integer(&mut chars, &mut callback); + } + c if c == '_' || c.is_alphabetic() => { + // identifier + read_identifier(&mut chars, &mut callback); + } + _ => {} + } + + if let Some((_, Ok(':'))) = chars.peek() { + skip_char_and_emit(&mut chars, FormatSpecifier::Colon, &mut callback); + + // check for fill/align + let mut cloned = chars.clone().take(2); + let first = cloned + .next() + .and_then(|next| next.1.as_ref().ok()) + .copied() + .unwrap_or_default(); + let second = cloned + .next() + .and_then(|next| next.1.as_ref().ok()) + .copied() + .unwrap_or_default(); + match second { + '<' | '^' | '>' => { + // alignment specifier, first char specifies fillment + skip_char_and_emit( + &mut chars, + FormatSpecifier::Fill, + &mut callback, + ); + skip_char_and_emit( + &mut chars, + FormatSpecifier::Align, + &mut callback, + ); + } + _ => match first { + '<' | '^' | '>' => { + skip_char_and_emit( + &mut chars, + FormatSpecifier::Align, + &mut callback, + ); + } + _ => {} + }, + } + + // check for sign + match chars + .peek() + .and_then(|next| next.1.as_ref().ok()) + .copied() + .unwrap_or_default() + { + '+' | '-' => { + skip_char_and_emit( + &mut chars, + FormatSpecifier::Sign, + &mut callback, + ); + } + _ => {} + } + + // check for `#` + if let Some((_, Ok('#'))) = chars.peek() { + skip_char_and_emit( + &mut chars, + FormatSpecifier::NumberSign, + &mut callback, + ); + } + + // check for `0` + let mut cloned = chars.clone().take(2); + let first = cloned.next().and_then(|next| next.1.as_ref().ok()).copied(); + let second = cloned.next().and_then(|next| next.1.as_ref().ok()).copied(); + + if first == Some('0') && second != Some('$') { + skip_char_and_emit(&mut chars, FormatSpecifier::Zero, &mut callback); + } + + // width + match chars + .peek() + .and_then(|next| next.1.as_ref().ok()) + .copied() + .unwrap_or_default() + { + '0'..='9' => { + read_integer(&mut chars, &mut callback); + if let Some((_, Ok('$'))) = chars.peek() { + skip_char_and_emit( + &mut chars, + FormatSpecifier::DollarSign, + &mut callback, + ); + } + } + c if c == '_' || c.is_alphabetic() => { + read_identifier(&mut chars, &mut callback); + // can be either width (indicated by dollar sign, or type in which case + // the next sign has to be `}`) + let next = + chars.peek().and_then(|next| next.1.as_ref().ok()).copied(); + match next { + Some('$') => skip_char_and_emit( + &mut chars, + FormatSpecifier::DollarSign, + &mut callback, + ), + Some('}') => { + skip_char_and_emit( + &mut chars, + FormatSpecifier::Close, + &mut callback, + ); + continue; + } + _ => continue, + }; + } + _ => {} + } + + // precision + if let Some((_, Ok('.'))) = chars.peek() { + skip_char_and_emit(&mut chars, FormatSpecifier::Dot, &mut callback); + + match chars + .peek() + .and_then(|next| next.1.as_ref().ok()) + .copied() + .unwrap_or_default() + { + '*' => { + skip_char_and_emit( + &mut chars, + FormatSpecifier::Asterisk, + &mut callback, + ); + } + '0'..='9' => { + read_integer(&mut chars, &mut callback); + if let Some((_, Ok('$'))) = chars.peek() { + skip_char_and_emit( + &mut chars, + FormatSpecifier::DollarSign, + &mut callback, + ); + } + } + c if c == '_' || c.is_alphabetic() => { + read_identifier(&mut chars, &mut callback); + if chars.peek().and_then(|next| next.1.as_ref().ok()).copied() + != Some('$') + { + continue; + } + skip_char_and_emit( + &mut chars, + FormatSpecifier::DollarSign, + &mut callback, + ); + } + _ => { + continue; + } + } + } + + // type + match chars + .peek() + .and_then(|next| next.1.as_ref().ok()) + .copied() + .unwrap_or_default() + { + '?' => { + skip_char_and_emit( + &mut chars, + FormatSpecifier::QuestionMark, + &mut callback, + ); + } + c if c == '_' || c.is_alphabetic() => { + read_identifier(&mut chars, &mut callback); + } + _ => {} + } + } + + if let Some((_, Ok('}'))) = chars.peek() { + skip_char_and_emit(&mut chars, FormatSpecifier::Close, &mut callback); + } else { + continue; + } + } + _ => { + while let Some((_, Ok(next_char))) = chars.peek() { + match next_char { + '{' => break, + _ => {} + } + chars.next(); + } + } + }; + } + + fn skip_char_and_emit<'a, I, F>( + chars: &mut std::iter::Peekable, + emit: FormatSpecifier, + callback: &mut F, + ) where + I: Iterator)>, + F: FnMut(TextRange, FormatSpecifier), + { + let (range, _) = chars.next().unwrap(); + callback(*range, emit); + } + + fn read_integer<'a, I, F>(chars: &mut std::iter::Peekable, callback: &mut F) + where + I: Iterator)>, + F: FnMut(TextRange, FormatSpecifier), + { + let (mut range, c) = chars.next().unwrap(); + assert!(c.as_ref().unwrap().is_ascii_digit()); + while let Some((r, Ok(next_char))) = chars.peek() { + if next_char.is_ascii_digit() { + chars.next(); + range = range.cover(*r); + } else { + break; + } + } + callback(range, FormatSpecifier::Integer); + } + + fn read_identifier<'a, I, F>(chars: &mut std::iter::Peekable, callback: &mut F) + where + I: Iterator)>, + F: FnMut(TextRange, FormatSpecifier), + { + let (mut range, c) = chars.next().unwrap(); + assert!(c.as_ref().unwrap().is_alphabetic() || *c.as_ref().unwrap() == '_'); + while let Some((r, Ok(next_char))) = chars.peek() { + if *next_char == '_' || next_char.is_ascii_digit() || next_char.is_alphabetic() { + chars.next(); + range = range.cover(*r); + } else { + break; + } + } + callback(range, FormatSpecifier::Identifier); + } + } +} + +impl HasFormatSpecifier for String { + fn char_ranges( + &self, + ) -> Option)>> { + let text = self.text().as_str(); + let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()]; + let offset = self.text_range_between_quotes()?.start() - self.syntax().text_range().start(); + + let mut res = Vec::with_capacity(text.len()); + unescape_literal(text, Mode::Str, &mut |range, unescaped_char| { + res.push(( + TextRange::new(range.start.try_into().unwrap(), range.end.try_into().unwrap()) + + offset, + unescaped_char, + )) + }); + + Some(res) + } +} + +impl HasFormatSpecifier for RawString { + fn char_ranges( + &self, + ) -> Option)>> { + let text = self.text().as_str(); + let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()]; + let offset = self.text_range_between_quotes()?.start() - self.syntax().text_range().start(); + + let mut res = Vec::with_capacity(text.len()); + for (idx, c) in text.char_indices() { + res.push((TextRange::at(idx.try_into().unwrap(), TextSize::of(c)) + offset, Ok(c))); + } + Some(res) + } +} diff --git a/crates/ra_syntax/src/ast/tokens.rs b/crates/ra_syntax/src/ast/tokens.rs deleted file mode 100644 index 045f69133..000000000 --- a/crates/ra_syntax/src/ast/tokens.rs +++ /dev/null @@ -1,537 +0,0 @@ -//! There are many AstNodes, but only a few tokens, so we hand-write them here. - -use std::{ - borrow::Cow, - convert::{TryFrom, TryInto}, -}; - -use crate::{ - ast::{AstToken, Comment, RawString, String, Whitespace}, - TextRange, TextSize, -}; -use rustc_lexer::unescape::{unescape_literal, Mode}; - -impl Comment { - pub fn kind(&self) -> CommentKind { - kind_by_prefix(self.text()) - } - - pub fn prefix(&self) -> &'static str { - for (prefix, k) in COMMENT_PREFIX_TO_KIND.iter() { - if *k == self.kind() && self.text().starts_with(prefix) { - return prefix; - } - } - unreachable!() - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub struct CommentKind { - pub shape: CommentShape, - pub doc: Option, -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum CommentShape { - Line, - Block, -} - -impl CommentShape { - pub fn is_line(self) -> bool { - self == CommentShape::Line - } - - pub fn is_block(self) -> bool { - self == CommentShape::Block - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum CommentPlacement { - Inner, - Outer, -} - -const COMMENT_PREFIX_TO_KIND: &[(&str, CommentKind)] = { - use {CommentPlacement::*, CommentShape::*}; - &[ - ("////", CommentKind { shape: Line, doc: None }), - ("///", CommentKind { shape: Line, doc: Some(Outer) }), - ("//!", CommentKind { shape: Line, doc: Some(Inner) }), - ("/**", CommentKind { shape: Block, doc: Some(Outer) }), - ("/*!", CommentKind { shape: Block, doc: Some(Inner) }), - ("//", CommentKind { shape: Line, doc: None }), - ("/*", CommentKind { shape: Block, doc: None }), - ] -}; - -fn kind_by_prefix(text: &str) -> CommentKind { - if text == "/**/" { - return CommentKind { shape: CommentShape::Block, doc: None }; - } - for (prefix, kind) in COMMENT_PREFIX_TO_KIND.iter() { - if text.starts_with(prefix) { - return *kind; - } - } - panic!("bad comment text: {:?}", text) -} - -impl Whitespace { - pub fn spans_multiple_lines(&self) -> bool { - let text = self.text(); - text.find('\n').map_or(false, |idx| text[idx + 1..].contains('\n')) - } -} - -pub struct QuoteOffsets { - pub quotes: (TextRange, TextRange), - pub contents: TextRange, -} - -impl QuoteOffsets { - fn new(literal: &str) -> Option { - let left_quote = literal.find('"')?; - let right_quote = literal.rfind('"')?; - if left_quote == right_quote { - // `literal` only contains one quote - return None; - } - - let start = TextSize::from(0); - let left_quote = TextSize::try_from(left_quote).unwrap() + TextSize::of('"'); - let right_quote = TextSize::try_from(right_quote).unwrap(); - let end = TextSize::of(literal); - - let res = QuoteOffsets { - quotes: (TextRange::new(start, left_quote), TextRange::new(right_quote, end)), - contents: TextRange::new(left_quote, right_quote), - }; - Some(res) - } -} - -pub trait HasQuotes: AstToken { - fn quote_offsets(&self) -> Option { - let text = self.text().as_str(); - let offsets = QuoteOffsets::new(text)?; - let o = self.syntax().text_range().start(); - let offsets = QuoteOffsets { - quotes: (offsets.quotes.0 + o, offsets.quotes.1 + o), - contents: offsets.contents + o, - }; - Some(offsets) - } - fn open_quote_text_range(&self) -> Option { - self.quote_offsets().map(|it| it.quotes.0) - } - - fn close_quote_text_range(&self) -> Option { - self.quote_offsets().map(|it| it.quotes.1) - } - - fn text_range_between_quotes(&self) -> Option { - self.quote_offsets().map(|it| it.contents) - } -} - -impl HasQuotes for String {} -impl HasQuotes for RawString {} - -pub trait HasStringValue: HasQuotes { - fn value(&self) -> Option>; -} - -impl HasStringValue for String { - fn value(&self) -> Option> { - let text = self.text().as_str(); - let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()]; - - let mut buf = std::string::String::with_capacity(text.len()); - let mut has_error = false; - unescape_literal(text, Mode::Str, &mut |_, unescaped_char| match unescaped_char { - Ok(c) => buf.push(c), - Err(_) => has_error = true, - }); - - if has_error { - return None; - } - // FIXME: don't actually allocate for borrowed case - let res = if buf == text { Cow::Borrowed(text) } else { Cow::Owned(buf) }; - Some(res) - } -} - -impl HasStringValue for RawString { - fn value(&self) -> Option> { - let text = self.text().as_str(); - let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()]; - Some(Cow::Borrowed(text)) - } -} - -impl RawString { - pub fn map_range_up(&self, range: TextRange) -> Option { - let contents_range = self.text_range_between_quotes()?; - assert!(TextRange::up_to(contents_range.len()).contains_range(range)); - Some(range + contents_range.start()) - } -} - -#[derive(Debug)] -pub enum FormatSpecifier { - Open, - Close, - Integer, - Identifier, - Colon, - Fill, - Align, - Sign, - NumberSign, - Zero, - DollarSign, - Dot, - Asterisk, - QuestionMark, -} - -pub trait HasFormatSpecifier: AstToken { - fn char_ranges( - &self, - ) -> Option)>>; - - fn lex_format_specifier(&self, mut callback: F) - where - F: FnMut(TextRange, FormatSpecifier), - { - let char_ranges = if let Some(char_ranges) = self.char_ranges() { - char_ranges - } else { - return; - }; - let mut chars = char_ranges.iter().peekable(); - - while let Some((range, first_char)) = chars.next() { - match first_char { - Ok('{') => { - // Format specifier, see syntax at https://doc.rust-lang.org/std/fmt/index.html#syntax - if let Some((_, Ok('{'))) = chars.peek() { - // Escaped format specifier, `{{` - chars.next(); - continue; - } - - callback(*range, FormatSpecifier::Open); - - // check for integer/identifier - match chars - .peek() - .and_then(|next| next.1.as_ref().ok()) - .copied() - .unwrap_or_default() - { - '0'..='9' => { - // integer - read_integer(&mut chars, &mut callback); - } - c if c == '_' || c.is_alphabetic() => { - // identifier - read_identifier(&mut chars, &mut callback); - } - _ => {} - } - - if let Some((_, Ok(':'))) = chars.peek() { - skip_char_and_emit(&mut chars, FormatSpecifier::Colon, &mut callback); - - // check for fill/align - let mut cloned = chars.clone().take(2); - let first = cloned - .next() - .and_then(|next| next.1.as_ref().ok()) - .copied() - .unwrap_or_default(); - let second = cloned - .next() - .and_then(|next| next.1.as_ref().ok()) - .copied() - .unwrap_or_default(); - match second { - '<' | '^' | '>' => { - // alignment specifier, first char specifies fillment - skip_char_and_emit( - &mut chars, - FormatSpecifier::Fill, - &mut callback, - ); - skip_char_and_emit( - &mut chars, - FormatSpecifier::Align, - &mut callback, - ); - } - _ => match first { - '<' | '^' | '>' => { - skip_char_and_emit( - &mut chars, - FormatSpecifier::Align, - &mut callback, - ); - } - _ => {} - }, - } - - // check for sign - match chars - .peek() - .and_then(|next| next.1.as_ref().ok()) - .copied() - .unwrap_or_default() - { - '+' | '-' => { - skip_char_and_emit( - &mut chars, - FormatSpecifier::Sign, - &mut callback, - ); - } - _ => {} - } - - // check for `#` - if let Some((_, Ok('#'))) = chars.peek() { - skip_char_and_emit( - &mut chars, - FormatSpecifier::NumberSign, - &mut callback, - ); - } - - // check for `0` - let mut cloned = chars.clone().take(2); - let first = cloned.next().and_then(|next| next.1.as_ref().ok()).copied(); - let second = cloned.next().and_then(|next| next.1.as_ref().ok()).copied(); - - if first == Some('0') && second != Some('$') { - skip_char_and_emit(&mut chars, FormatSpecifier::Zero, &mut callback); - } - - // width - match chars - .peek() - .and_then(|next| next.1.as_ref().ok()) - .copied() - .unwrap_or_default() - { - '0'..='9' => { - read_integer(&mut chars, &mut callback); - if let Some((_, Ok('$'))) = chars.peek() { - skip_char_and_emit( - &mut chars, - FormatSpecifier::DollarSign, - &mut callback, - ); - } - } - c if c == '_' || c.is_alphabetic() => { - read_identifier(&mut chars, &mut callback); - // can be either width (indicated by dollar sign, or type in which case - // the next sign has to be `}`) - let next = - chars.peek().and_then(|next| next.1.as_ref().ok()).copied(); - match next { - Some('$') => skip_char_and_emit( - &mut chars, - FormatSpecifier::DollarSign, - &mut callback, - ), - Some('}') => { - skip_char_and_emit( - &mut chars, - FormatSpecifier::Close, - &mut callback, - ); - continue; - } - _ => continue, - }; - } - _ => {} - } - - // precision - if let Some((_, Ok('.'))) = chars.peek() { - skip_char_and_emit(&mut chars, FormatSpecifier::Dot, &mut callback); - - match chars - .peek() - .and_then(|next| next.1.as_ref().ok()) - .copied() - .unwrap_or_default() - { - '*' => { - skip_char_and_emit( - &mut chars, - FormatSpecifier::Asterisk, - &mut callback, - ); - } - '0'..='9' => { - read_integer(&mut chars, &mut callback); - if let Some((_, Ok('$'))) = chars.peek() { - skip_char_and_emit( - &mut chars, - FormatSpecifier::DollarSign, - &mut callback, - ); - } - } - c if c == '_' || c.is_alphabetic() => { - read_identifier(&mut chars, &mut callback); - if chars.peek().and_then(|next| next.1.as_ref().ok()).copied() - != Some('$') - { - continue; - } - skip_char_and_emit( - &mut chars, - FormatSpecifier::DollarSign, - &mut callback, - ); - } - _ => { - continue; - } - } - } - - // type - match chars - .peek() - .and_then(|next| next.1.as_ref().ok()) - .copied() - .unwrap_or_default() - { - '?' => { - skip_char_and_emit( - &mut chars, - FormatSpecifier::QuestionMark, - &mut callback, - ); - } - c if c == '_' || c.is_alphabetic() => { - read_identifier(&mut chars, &mut callback); - } - _ => {} - } - } - - if let Some((_, Ok('}'))) = chars.peek() { - skip_char_and_emit(&mut chars, FormatSpecifier::Close, &mut callback); - } else { - continue; - } - } - _ => { - while let Some((_, Ok(next_char))) = chars.peek() { - match next_char { - '{' => break, - _ => {} - } - chars.next(); - } - } - }; - } - - fn skip_char_and_emit<'a, I, F>( - chars: &mut std::iter::Peekable, - emit: FormatSpecifier, - callback: &mut F, - ) where - I: Iterator)>, - F: FnMut(TextRange, FormatSpecifier), - { - let (range, _) = chars.next().unwrap(); - callback(*range, emit); - } - - fn read_integer<'a, I, F>(chars: &mut std::iter::Peekable, callback: &mut F) - where - I: Iterator)>, - F: FnMut(TextRange, FormatSpecifier), - { - let (mut range, c) = chars.next().unwrap(); - assert!(c.as_ref().unwrap().is_ascii_digit()); - while let Some((r, Ok(next_char))) = chars.peek() { - if next_char.is_ascii_digit() { - chars.next(); - range = range.cover(*r); - } else { - break; - } - } - callback(range, FormatSpecifier::Integer); - } - - fn read_identifier<'a, I, F>(chars: &mut std::iter::Peekable, callback: &mut F) - where - I: Iterator)>, - F: FnMut(TextRange, FormatSpecifier), - { - let (mut range, c) = chars.next().unwrap(); - assert!(c.as_ref().unwrap().is_alphabetic() || *c.as_ref().unwrap() == '_'); - while let Some((r, Ok(next_char))) = chars.peek() { - if *next_char == '_' || next_char.is_ascii_digit() || next_char.is_alphabetic() { - chars.next(); - range = range.cover(*r); - } else { - break; - } - } - callback(range, FormatSpecifier::Identifier); - } - } -} - -impl HasFormatSpecifier for String { - fn char_ranges( - &self, - ) -> Option)>> { - let text = self.text().as_str(); - let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()]; - let offset = self.text_range_between_quotes()?.start() - self.syntax().text_range().start(); - - let mut res = Vec::with_capacity(text.len()); - unescape_literal(text, Mode::Str, &mut |range, unescaped_char| { - res.push(( - TextRange::new(range.start.try_into().unwrap(), range.end.try_into().unwrap()) - + offset, - unescaped_char, - )) - }); - - Some(res) - } -} - -impl HasFormatSpecifier for RawString { - fn char_ranges( - &self, - ) -> Option)>> { - let text = self.text().as_str(); - let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()]; - let offset = self.text_range_between_quotes()?.start() - self.syntax().text_range().start(); - - let mut res = Vec::with_capacity(text.len()); - for (idx, c) in text.char_indices() { - res.push((TextRange::at(idx.try_into().unwrap(), TextSize::of(c)) + offset, Ok(c))); - } - Some(res) - } -} -- cgit v1.2.3