Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rustc_span: Generate keyword classification functions automatically #75309

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3625,6 +3625,7 @@ dependencies = [
name = "rustc_macros"
version = "0.1.0"
dependencies = [
"indexmap",
"proc-macro2",
"quote",
"syn",
Expand Down
1 change: 1 addition & 0 deletions src/librustc_macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ edition = "2018"
proc-macro = true

[dependencies]
indexmap = "1"
synstructure = "0.12.1"
syn = { version = "1", features = ["full"] }
proc-macro2 = "1"
Expand Down
81 changes: 63 additions & 18 deletions src/librustc_macros/src/symbols.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use indexmap::IndexMap;
use proc_macro::TokenStream;
use proc_macro2::TokenTree;
use quote::quote;
use std::collections::hash_map::RandomState;
use std::collections::HashSet;
use syn::parse::{Parse, ParseStream, Result};
use syn::{braced, parse_macro_input, Ident, LitStr, Token};
Expand Down Expand Up @@ -44,22 +47,43 @@ impl Parse for Symbol {
}
}

/// A type used to greedily parse another type until the input is empty.
struct List<T>(Vec<T>);
// Map from an optional keyword class to the list of keywords in it.
// FIXME: the indexmap crate thinks `has_std` is false when building `rustc_macros`,
// so we have to provide the hasher manually.
struct Keywords(IndexMap<Option<Ident>, Vec<Keyword>, RandomState>);

impl<T: Parse> Parse for List<T> {
impl Parse for Keywords {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let mut classes = IndexMap::<_, Vec<_>, _>::with_hasher(Default::default());
let mut current_class = None;
while !input.is_empty() {
if input.peek(Token![fn]) {
input.parse::<TokenTree>()?;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if there's any simpler way to skip a token with syn parser.

current_class = Some(input.parse::<Ident>()?);
input.parse::<Token![:]>()?;
} else {
classes.entry(current_class.clone()).or_default().push(input.parse()?);
}
}
Ok(Keywords(classes))
}
}

struct Symbols(Vec<Symbol>);

impl Parse for Symbols {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let mut list = Vec::new();
while !input.is_empty() {
list.push(input.parse()?);
}
Ok(List(list))
Ok(Symbols(list))
}
}

struct Input {
keywords: List<Keyword>,
symbols: List<Symbol>,
keywords: Keywords,
symbols: Symbols,
}

impl Parse for Input {
Expand All @@ -85,6 +109,7 @@ pub fn symbols(input: TokenStream) -> TokenStream {
let mut symbols_stream = quote! {};
let mut digits_stream = quote! {};
let mut prefill_stream = quote! {};
let mut keyword_class_stream = quote! {};
let mut counter = 0u32;
let mut keys = HashSet::<String>::new();
let mut prev_key: Option<String> = None;
Expand All @@ -106,18 +131,34 @@ pub fn symbols(input: TokenStream) -> TokenStream {
};

// Generate the listed keywords.
for keyword in &input.keywords.0 {
let name = &keyword.name;
let value = &keyword.value;
check_dup(&value.value(), &mut errors);
prefill_stream.extend(quote! {
#value,
});
keyword_stream.extend(quote! {
#[allow(non_upper_case_globals)]
pub const #name: Symbol = Symbol::new(#counter);
});
counter += 1;
for (class, keywords) in &input.keywords.0 {
let mut class_stream = quote! {};
for keyword in keywords {
let name = &keyword.name;
let value = &keyword.value;
check_dup(&value.value(), &mut errors);
prefill_stream.extend(quote! {
#value,
});
keyword_stream.extend(quote! {
#[allow(non_upper_case_globals)]
pub const #name: Symbol = Symbol::new(#counter);
});
class_stream.extend(quote! {
| kw::#name
});
counter += 1;
}
if let Some(class) = class {
keyword_class_stream.extend(quote! {
fn #class(self) -> bool {
match self {
#class_stream => true,
_ => false
}
}
});
}
}

// Generate the listed symbols.
Expand Down Expand Up @@ -185,6 +226,10 @@ pub fn symbols(input: TokenStream) -> TokenStream {
])
}
}

impl Symbol {
#keyword_class_stream
}
});

// To see the generated code generated, uncomment this line, recompile, and
Expand Down
30 changes: 12 additions & 18 deletions src/librustc_span/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ symbols! {
Keywords {
// Special reserved identifiers used internally for elided lifetimes,
// unnamed method parameters, crate root module, error recovery etc.
fn is_special:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This confuses code highlighting in my editor (vscode + rust-analyzer), but fn is required for searchability.
Perhaps the list needs to go into braces, e.g. like this:

fn is_special {
    Invalid:            "",
    PathRoot:           "{{root}}",
    DollarCrate:        "$crate",
    Underscore:         "_",
}

Invalid: "",
PathRoot: "{{root}}",
DollarCrate: "$crate",
Underscore: "_",

// Keywords that are used in stable Rust.
// Keywords that are used in stable Rust on all editions.
fn is_used_keyword_20xx:
As: "as",
Break: "break",
Const: "const",
Expand Down Expand Up @@ -67,7 +69,8 @@ symbols! {
Where: "where",
While: "while",

// Keywords that are used in unstable Rust or reserved for future use.
// Keywords that are used in unstable Rust or reserved for future use on all editions.
fn is_unused_keyword_20xx:
Abstract: "abstract",
Become: "become",
Box: "box",
Expand All @@ -82,18 +85,22 @@ symbols! {
Yield: "yield",

// Edition-specific keywords that are used in stable Rust.
fn is_used_keyword_2018:
Async: "async", // >= 2018 Edition only
Await: "await", // >= 2018 Edition only
Dyn: "dyn", // >= 2018 Edition only

// Edition-specific keywords that are used in unstable Rust or reserved for future use.
fn is_unused_keyword_2018:
Try: "try", // >= 2018 Edition only

// Special lifetime names
fn is_special_lifetime:
UnderscoreLifetime: "'_",
StaticLifetime: "'static",

// Weak keywords, have special meaning only in specific contexts.
fn is_weak_keyword:
Auto: "auto",
Catch: "catch",
Default: "default",
Expand Down Expand Up @@ -1546,19 +1553,6 @@ pub mod sym {
}

impl Symbol {
fn is_used_keyword_2018(self) -> bool {
self >= kw::Async && self <= kw::Dyn
}

fn is_unused_keyword_2018(self) -> bool {
self == kw::Try
}

/// Used for sanity checking rustdoc keyword sections.
pub fn is_doc_keyword(self) -> bool {
self <= kw::Union
}

/// A keyword or reserved identifier that can be used as a path segment.
pub fn is_path_segment_keyword(self) -> bool {
self == kw::Super
Expand All @@ -1584,20 +1578,20 @@ impl Ident {
// Returns `true` for reserved identifiers used internally for elided lifetimes,
// unnamed method parameters, crate root module, error recovery etc.
pub fn is_special(self) -> bool {
self.name <= kw::Underscore
self.name.is_special()
}

/// Returns `true` if the token is a keyword used in the language.
pub fn is_used_keyword(self) -> bool {
// Note: `span.edition()` is relatively expensive, don't call it unless necessary.
self.name >= kw::As && self.name <= kw::While
self.name.is_used_keyword_20xx()
|| self.name.is_used_keyword_2018() && self.span.rust_2018()
}

/// Returns `true` if the token is a keyword reserved for possible future use.
pub fn is_unused_keyword(self) -> bool {
// Note: `span.edition()` is relatively expensive, don't call it unless necessary.
self.name >= kw::Abstract && self.name <= kw::Yield
self.name.is_unused_keyword_20xx()
|| self.name.is_unused_keyword_2018() && self.span.rust_2018()
}

Expand Down
7 changes: 2 additions & 5 deletions src/librustdoc/clean/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,8 @@ impl Clean<ExternalCrate> for CrateNum {
for attr in attrs.lists(sym::doc) {
if let Some(v) = attr.value_str() {
if attr.has_name(sym::keyword) {
if v.is_doc_keyword() {
keyword = Some(v.to_string());
break;
}
// FIXME: should warn on unknown keywords?
keyword = Some(v.to_string());
break;
}
}
}
Expand Down