mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-11-15 17:28:09 +00:00
Merge #11167
11167: internal: check top level entry point invariants r=matklad a=matklad
bors r+
🤖
Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
commit
6e3d135f7b
7 changed files with 181 additions and 59 deletions
|
@ -105,21 +105,21 @@ macro_rules! m2 { ($x:ident) => {} }
|
|||
|
||||
#[test]
|
||||
fn expansion_does_not_parse_as_expression() {
|
||||
cov_mark::check!(expansion_does_not_parse_as_expression);
|
||||
check(
|
||||
r#"
|
||||
macro_rules! stmts {
|
||||
() => { let _ = 0; }
|
||||
}
|
||||
|
||||
fn f() { let _ = stmts!(); }
|
||||
fn f() { let _ = stmts!/*+errors*/(); }
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! stmts {
|
||||
() => { let _ = 0; }
|
||||
}
|
||||
|
||||
fn f() { let _ = /* error: could not convert tokens */; }
|
||||
fn f() { let _ = /* parse error: expected expression */
|
||||
let _ = 0;; }
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1148,7 +1148,7 @@ fn foo() { let a = id!([0u32, bar($0)] ); }
|
|||
fn test_hover_through_literal_string_in_macro() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! arr { ($($tt:tt)*) => { [$($tt)*)] } }
|
||||
macro_rules! arr { ($($tt:tt)*) => { [$($tt)*] } }
|
||||
fn foo() {
|
||||
let mastered_for_itunes = "";
|
||||
let _ = arr!("Tr$0acks", &mastered_for_itunes);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! Conversions between [`SyntaxNode`] and [`tt::TokenTree`].
|
||||
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use stdx::non_empty_vec::NonEmptyVec;
|
||||
use stdx::{never, non_empty_vec::NonEmptyVec};
|
||||
use syntax::{
|
||||
ast::{self, make::tokens::doc_comment},
|
||||
AstToken, Parse, PreorderWithTokens, SmolStr, SyntaxElement, SyntaxKind,
|
||||
|
@ -66,8 +66,7 @@ pub fn token_tree_to_syntax_node(
|
|||
parser::Step::Error { msg } => tree_sink.error(msg.to_string()),
|
||||
}
|
||||
}
|
||||
if tree_sink.roots.len() != 1 {
|
||||
cov_mark::hit!(expansion_does_not_parse_as_expression);
|
||||
if never!(tree_sink.roots.len() != 1) {
|
||||
return Err(ExpandError::ConversionError);
|
||||
}
|
||||
//FIXME: would be cool to report errors
|
||||
|
|
|
@ -135,6 +135,32 @@ pub(crate) mod entry {
|
|||
}
|
||||
m.complete(p, ERROR);
|
||||
}
|
||||
|
||||
pub(crate) fn expr(p: &mut Parser) {
|
||||
let m = p.start();
|
||||
expressions::expr(p);
|
||||
if p.at(EOF) {
|
||||
m.abandon(p);
|
||||
return;
|
||||
}
|
||||
while !p.at(EOF) {
|
||||
p.bump_any();
|
||||
}
|
||||
m.complete(p, ERROR);
|
||||
}
|
||||
|
||||
pub(crate) fn meta_item(p: &mut Parser) {
|
||||
let m = p.start();
|
||||
attributes::meta(p);
|
||||
if p.at(EOF) {
|
||||
m.abandon(p);
|
||||
return;
|
||||
}
|
||||
while !p.at(EOF) {
|
||||
p.bump_any();
|
||||
}
|
||||
m.complete(p, ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -41,6 +41,75 @@ pub use crate::{
|
|||
syntax_kind::SyntaxKind,
|
||||
};
|
||||
|
||||
/// Parse the whole of the input as a given syntactic construct.
|
||||
///
|
||||
/// This covers two main use-cases:
|
||||
///
|
||||
/// * Parsing a Rust file.
|
||||
/// * Parsing a result of macro expansion.
|
||||
///
|
||||
/// That is, for something like
|
||||
///
|
||||
/// ```
|
||||
/// quick_check! {
|
||||
/// fn prop() {}
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// the input to the macro will be parsed with [`PrefixEntryPoint::Item`], and
|
||||
/// the result will be [`TopEntryPoint::MacroItems`].
|
||||
///
|
||||
/// [`TopEntryPoint::parse`] makes a guarantee that
|
||||
/// * all input is consumed
|
||||
/// * the result is a valid tree (there's one root node)
|
||||
#[derive(Debug)]
|
||||
pub enum TopEntryPoint {
|
||||
SourceFile,
|
||||
MacroStmts,
|
||||
MacroItems,
|
||||
Pattern,
|
||||
Type,
|
||||
Expr,
|
||||
/// Edge case -- macros generally don't expand to attributes, with the
|
||||
/// exception of `cfg_attr` which does!
|
||||
MetaItem,
|
||||
}
|
||||
|
||||
impl TopEntryPoint {
|
||||
pub fn parse(&self, input: &Input) -> Output {
|
||||
let entry_point: fn(&'_ mut parser::Parser) = match self {
|
||||
TopEntryPoint::SourceFile => grammar::entry::top::source_file,
|
||||
TopEntryPoint::MacroStmts => grammar::entry::top::macro_stmts,
|
||||
TopEntryPoint::MacroItems => grammar::entry::top::macro_items,
|
||||
TopEntryPoint::Pattern => grammar::entry::top::pattern,
|
||||
TopEntryPoint::Type => grammar::entry::top::type_,
|
||||
TopEntryPoint::Expr => grammar::entry::top::expr,
|
||||
TopEntryPoint::MetaItem => grammar::entry::top::meta_item,
|
||||
};
|
||||
let mut p = parser::Parser::new(input);
|
||||
entry_point(&mut p);
|
||||
let events = p.finish();
|
||||
let res = event::process(events);
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
let mut depth = 0;
|
||||
let mut first = true;
|
||||
for step in res.iter() {
|
||||
assert!(depth > 0 || first);
|
||||
first = false;
|
||||
match step {
|
||||
Step::Enter { .. } => depth += 1,
|
||||
Step::Exit => depth -= 1,
|
||||
Step::Token { .. } | Step::Error { .. } => (),
|
||||
}
|
||||
}
|
||||
assert!(!first, "no tree at all");
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a prefix of the input as a given syntactic construct.
|
||||
///
|
||||
/// This is used by macro-by-example parser to implement things like `$i:item`
|
||||
|
@ -83,57 +152,6 @@ impl PrefixEntryPoint {
|
|||
}
|
||||
}
|
||||
|
||||
/// Parse the whole of the input as a given syntactic construct.
|
||||
///
|
||||
/// This covers two main use-cases:
|
||||
///
|
||||
/// * Parsing a Rust file.
|
||||
/// * Parsing a result of macro expansion.
|
||||
///
|
||||
/// That is, for something like
|
||||
///
|
||||
/// ```
|
||||
/// quick_check! {
|
||||
/// fn prop() {}
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// the input to the macro will be parsed with [`PrefixEntryPoint::Item`], and
|
||||
/// the result will be [`TopEntryPoint::Items`].
|
||||
///
|
||||
/// This *should* (but currently doesn't) guarantee that all input is consumed.
|
||||
#[derive(Debug)]
|
||||
pub enum TopEntryPoint {
|
||||
SourceFile,
|
||||
MacroStmts,
|
||||
MacroItems,
|
||||
Pattern,
|
||||
Type,
|
||||
Expr,
|
||||
/// Edge case -- macros generally don't expand to attributes, with the
|
||||
/// exception of `cfg_attr` which does!
|
||||
MetaItem,
|
||||
}
|
||||
|
||||
impl TopEntryPoint {
|
||||
pub fn parse(&self, input: &Input) -> Output {
|
||||
let entry_point: fn(&'_ mut parser::Parser) = match self {
|
||||
TopEntryPoint::SourceFile => grammar::entry::top::source_file,
|
||||
TopEntryPoint::MacroStmts => grammar::entry::top::macro_stmts,
|
||||
TopEntryPoint::MacroItems => grammar::entry::top::macro_items,
|
||||
TopEntryPoint::Pattern => grammar::entry::top::pattern,
|
||||
TopEntryPoint::Type => grammar::entry::top::type_,
|
||||
// FIXME
|
||||
TopEntryPoint::Expr => grammar::entry::prefix::expr,
|
||||
TopEntryPoint::MetaItem => grammar::entry::prefix::meta_item,
|
||||
};
|
||||
let mut p = parser::Parser::new(input);
|
||||
entry_point(&mut p);
|
||||
let events = p.finish();
|
||||
event::process(events)
|
||||
}
|
||||
}
|
||||
|
||||
/// A parsing function for a specific braced-block.
|
||||
pub struct Reparser(fn(&mut parser::Parser));
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
mod sourcegen_inline_tests;
|
||||
mod prefix_entries;
|
||||
mod top_entries;
|
||||
mod prefix_entries;
|
||||
|
||||
use std::{
|
||||
fmt::Write,
|
||||
|
|
|
@ -51,6 +51,13 @@ fn source_file() {
|
|||
|
||||
#[test]
|
||||
fn macro_stmt() {
|
||||
check(
|
||||
TopEntryPoint::MacroStmts,
|
||||
"",
|
||||
expect![[r#"
|
||||
MACRO_STMTS
|
||||
"#]],
|
||||
);
|
||||
check(
|
||||
TopEntryPoint::MacroStmts,
|
||||
"#!/usr/bin/rust",
|
||||
|
@ -94,6 +101,13 @@ fn macro_stmt() {
|
|||
|
||||
#[test]
|
||||
fn macro_items() {
|
||||
check(
|
||||
TopEntryPoint::MacroItems,
|
||||
"",
|
||||
expect![[r#"
|
||||
MACRO_ITEMS
|
||||
"#]],
|
||||
);
|
||||
check(
|
||||
TopEntryPoint::MacroItems,
|
||||
"#!/usr/bin/rust",
|
||||
|
@ -131,6 +145,14 @@ fn macro_items() {
|
|||
|
||||
#[test]
|
||||
fn macro_pattern() {
|
||||
check(
|
||||
TopEntryPoint::Pattern,
|
||||
"",
|
||||
expect![[r#"
|
||||
ERROR
|
||||
error 0: expected pattern
|
||||
"#]],
|
||||
);
|
||||
check(
|
||||
TopEntryPoint::Pattern,
|
||||
"Some(_)",
|
||||
|
@ -177,6 +199,15 @@ fn macro_pattern() {
|
|||
|
||||
#[test]
|
||||
fn type_() {
|
||||
check(
|
||||
TopEntryPoint::Type,
|
||||
"",
|
||||
expect![[r#"
|
||||
ERROR
|
||||
error 0: expected type
|
||||
"#]],
|
||||
);
|
||||
|
||||
check(
|
||||
TopEntryPoint::Type,
|
||||
"Option<!>",
|
||||
|
@ -224,6 +255,54 @@ fn type_() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expr() {
|
||||
check(
|
||||
TopEntryPoint::Expr,
|
||||
"",
|
||||
expect![[r#"
|
||||
ERROR
|
||||
error 0: expected expression
|
||||
"#]],
|
||||
);
|
||||
check(
|
||||
TopEntryPoint::Expr,
|
||||
"2 + 2 == 5",
|
||||
expect![[r#"
|
||||
BIN_EXPR
|
||||
BIN_EXPR
|
||||
LITERAL
|
||||
INT_NUMBER "2"
|
||||
WHITESPACE " "
|
||||
PLUS "+"
|
||||
WHITESPACE " "
|
||||
LITERAL
|
||||
INT_NUMBER "2"
|
||||
WHITESPACE " "
|
||||
EQ2 "=="
|
||||
WHITESPACE " "
|
||||
LITERAL
|
||||
INT_NUMBER "5"
|
||||
"#]],
|
||||
);
|
||||
check(
|
||||
TopEntryPoint::Expr,
|
||||
"let _ = 0;",
|
||||
expect![[r#"
|
||||
ERROR
|
||||
LET_KW "let"
|
||||
WHITESPACE " "
|
||||
UNDERSCORE "_"
|
||||
WHITESPACE " "
|
||||
EQ "="
|
||||
WHITESPACE " "
|
||||
INT_NUMBER "0"
|
||||
SEMICOLON ";"
|
||||
error 0: expected expression
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn check(entry: TopEntryPoint, input: &str, expect: expect_test::Expect) {
|
||||
let (parsed, _errors) = super::parse(entry, input);
|
||||
|
|
Loading…
Reference in a new issue