Skip to content

Commit

Permalink
Support extern crate + extern block
Browse files Browse the repository at this point in the history
  • Loading branch information
Bromeon committed Feb 1, 2024
1 parent 26e7cf1 commit 5f48c4d
Show file tree
Hide file tree
Showing 15 changed files with 655 additions and 38 deletions.
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ mod tests;

mod error;
mod parse;
mod parse_extern;
mod parse_fn;
mod parse_impl;
mod parse_mod;
Expand Down
10 changes: 7 additions & 3 deletions src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,21 +202,25 @@ pub fn consume_declaration(tokens: &mut Peekable<IntoIter>) -> Result<Declaratio
let static_decl = parse_const_or_static(tokens, attributes, vis_marker);
Declaration::Constant(static_decl)
}
// Note: fn qualifiers appear always in this order in Rust: default const async unsafe extern fn
Some(TokenTree::Ident(keyword)) if keyword == "use" => {
let use_decl = parse_use_declaration(tokens, attributes, vis_marker);

Declaration::Use(use_decl)
}
// Note: fn qualifiers appear always in this order in Rust
// Note: fn qualifiers appear always in this order in Rust: default const async unsafe extern fn
Some(TokenTree::Ident(keyword))
if matches!(
keyword.to_string().as_str(),
"default" | "const" | "async" | "unsafe" | "extern" | "fn" | "type"
) =>
{
// Reuse impl parsing
consume_either_fn_type_const_static_impl(tokens, attributes, vis_marker, "declaration")
consume_either_fn_type_const_static_impl(
tokens,
attributes,
vis_marker,
"fn/type/const/static/extern/extern crate",
)
}
Some(token) => {
if let Some(macro_) = consume_macro(tokens, attributes) {
Expand Down
99 changes: 99 additions & 0 deletions src/parse_extern.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use crate::parse_impl::parse_impl_body;
use crate::parse_utils::{consume_ident, parse_any_ident, parse_ident, parse_punct, TokenIter};
use crate::{Attribute, ExternBlock, ExternCrate, VisMarker};
use proc_macro2::{Delimiter, TokenTree};

pub(crate) fn parse_extern_crate(
tokens: &mut TokenIter,
attributes: Vec<Attribute>,
vis_marker: Option<VisMarker>,
) -> ExternCrate {
consume_extern_crate(tokens, attributes, vis_marker).expect("cannot parse extern crate")
}

fn consume_extern_crate(
tokens: &mut TokenIter,
attributes: Vec<Attribute>,
vis_marker: Option<VisMarker>,
) -> Option<ExternCrate> {
let tk_extern = consume_ident(tokens, "extern")?;
let tk_crate = consume_ident(tokens, "crate")?;

let name = parse_any_ident(tokens, "extern crate");
let tk_as = consume_ident(tokens, "as");

let alias;
let tk_underscore;
if tk_as.is_some() {
alias = Some(parse_any_ident(tokens, "extern crate: alias"));
if alias.is_none() {
tk_underscore = Some(parse_ident(tokens, "_", "extern crate"));
} else {
tk_underscore = None;
}
} else {
alias = None;
tk_underscore = None;
}

let tk_semicolon = parse_punct(tokens, ';', "extern crate");

Some(ExternCrate {
attributes,
vis_marker,
tk_extern,
tk_crate,
name,
tk_as,
alias,
tk_underscore,
tk_semicolon,
})
}

pub(crate) fn parse_extern_block(
tokens: &mut TokenIter,
attributes: Vec<Attribute>,
vis_marker: Option<VisMarker>,
) -> ExternBlock {
consume_extern_block(tokens, attributes, vis_marker).expect("cannot parse extern block")
}

fn consume_extern_block(
tokens: &mut TokenIter,
attributes: Vec<Attribute>,
vis_marker: Option<VisMarker>,
) -> Option<ExternBlock> {
let tk_unsafe = consume_ident(tokens, "unsafe");
let tk_extern = consume_ident(tokens, "extern")?;

let extern_abi = match tokens.peek() {
Some(TokenTree::Literal(lit)) => {
let lit = Some(lit.clone());
tokens.next();
lit
}
_ => None,
};

let (tk_braces, inner_attributes, body_items) = match tokens.next() {
Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Brace => {
parse_impl_body(group, true)
}
_ => {
// Only here we know that it's not an extern crate or extern block, so try other options on call-site (fn).
return None;
}
};

Some(ExternBlock {
attributes,
vis_marker,
tk_unsafe,
tk_extern,
extern_abi,
tk_braces,
inner_attributes,
body_items,
})
}
84 changes: 57 additions & 27 deletions src/parse_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,16 @@ type TokenIter = Peekable<proc_macro2::token_stream::IntoIter>;

/// If venial fails to parse the declaration as a function, it can detect that it
/// is either a constant (`const` ambiguity), an impl or a module (`unsafe` ambiguity).
#[derive(Debug)]
pub(crate) enum NotFunction {
Const,
Static,
Trait,
Impl,
Mod,
ExternCrate,
ExternBlock,
}

pub(crate) fn consume_fn_qualifiers(tokens: &mut TokenIter) -> FnQualifiers {
Expand Down Expand Up @@ -148,36 +153,61 @@ pub(crate) fn consume_fn(

// fn keyword, or const fallback
let next_token = tokens.next();
let tk_fn_keyword = if let Some(TokenTree::Ident(ident)) = &next_token {
if ident == "fn" {
ident.clone()
} else if qualifiers.has_only_const_xor_unsafe() {
// This is not a function, detect what else it is
// Note: detection already done here, because then we only need the lookahead/rollback once
let declaration_type = if qualifiers.tk_const.is_some() {
NotFunction::Const
} else if qualifiers.tk_unsafe.is_some() {
match &next_token {
Some(TokenTree::Ident(ident)) if ident == "trait" => NotFunction::Trait,
Some(TokenTree::Ident(ident)) if ident == "impl" => NotFunction::Impl,
Some(TokenTree::Ident(ident)) if ident == "mod" => NotFunction::Mod,
token => panic!(
"expected one of 'fn|trait|impl|mod' after 'unsafe', got {:?}",
token
),
}
let tk_fn_keyword = match &next_token {
Some(TokenTree::Ident(ident)) => {
if ident == "fn" {
ident.clone()
} else if qualifiers.tk_extern.is_some() && ident == "crate" {
*tokens = before_start; // rollback
return Err(NotFunction::ExternCrate);
} else if ident == "static" {
// rollback iterator, could be start of const declaration
*tokens = before_start;
return Err(NotFunction::Static);
} else if qualifiers.has_only_const_xor_unsafe() {
// This is not a function, detect what else it is.
// Note: detection already done here, because then we only need the lookahead/rollback once.
let declaration_type = if qualifiers.tk_const.is_some() {
NotFunction::Const
} else if qualifiers.tk_unsafe.is_some() {
if ident == "trait" {
NotFunction::Trait
} else if ident == "impl" {
NotFunction::Impl
} else if ident == "mod" {
NotFunction::Mod
} else {
panic!("expected one of 'fn|trait|impl|mod' after 'unsafe', got {ident:?}")
}
} else {
unreachable!()
};

// rollback iterator, could be start of const declaration
*tokens = before_start;
return Err(declaration_type);
} else {
unreachable!()
};
panic!("expected 'fn' keyword, got ident '{}'", ident)
}
}

// rollback iterator, could be start of const declaration
*tokens = before_start;
return Err(declaration_type);
} else {
panic!("expected 'fn' keyword, got ident '{}'", ident)
// extern "C" { ...
Some(TokenTree::Literal(_)) if qualifiers.tk_extern.is_some() => {
*tokens = before_start; // rollback
return Err(NotFunction::ExternBlock);
}

// extern { ...
Some(TokenTree::Group(group))
if qualifiers.tk_extern.is_some() && group.delimiter() == Delimiter::Brace =>
{
*tokens = before_start; // rollback
return Err(NotFunction::ExternBlock);
}

_ => {
panic!("expected 'fn' keyword")
}
} else {
panic!("expected 'fn' keyword")
};

let fn_name = consume_declaration_name(tokens);
Expand Down
40 changes: 35 additions & 5 deletions src/parse_impl.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::parse_extern::{parse_extern_block, parse_extern_crate};
use crate::parse_fn::{consume_fn, consume_macro, NotFunction};
use crate::parse_mod::parse_mod;
use crate::parse_type::{consume_bound, consume_generic_params, consume_where_clause};
Expand Down Expand Up @@ -116,6 +117,7 @@ pub(crate) fn consume_ty_definition(
})
}

// TODO could accept a mask, allowing only certain sub-items. This could be made to reject e.g. extern crate in impl blocks.
pub(crate) fn consume_either_fn_type_const_static_impl(
tokens: &mut TokenIter,
attributes: Vec<Attribute>,
Expand All @@ -129,13 +131,19 @@ pub(crate) fn consume_either_fn_type_const_static_impl(
let assoc_ty = consume_ty_definition(tokens, attributes, vis_marker);
Declaration::TyDefinition(assoc_ty.unwrap())
}
"default" | "const" | "async" | "unsafe" | "extern" | "fn" => {

// Note: `static` is only used for extern "abi" {} blocks. Checked in call site.
"default" | "const" | "static" | "async" | "unsafe" | "extern" | "fn" => {
match consume_fn(tokens, attributes.clone(), vis_marker.clone()) {
Ok(method) => Declaration::Function(method),
Err(NotFunction::Const) => {
let constant = parse_const_or_static(tokens, attributes, vis_marker);
Declaration::Constant(constant)
}
Err(NotFunction::Static) => {
let static_decl = parse_const_or_static(tokens, attributes, vis_marker);
Declaration::Constant(static_decl)
}
Err(NotFunction::Trait) => {
let trait_decl = parse_trait(tokens, attributes, vis_marker);
Declaration::Trait(trait_decl)
Expand All @@ -148,6 +156,14 @@ pub(crate) fn consume_either_fn_type_const_static_impl(
let mod_decl = parse_mod(tokens, attributes, vis_marker);
Declaration::Module(mod_decl)
}
Err(NotFunction::ExternBlock) => {
let extern_decl = parse_extern_block(tokens, attributes, vis_marker);
Declaration::ExternBlock(extern_decl)
}
Err(NotFunction::ExternCrate) => {
let crate_decl = parse_extern_crate(tokens, attributes, vis_marker);
Declaration::ExternCrate(crate_decl)
}
}
}
ident => {
Expand All @@ -168,7 +184,10 @@ pub(crate) fn consume_either_fn_type_const_static_impl(
}
}

pub(crate) fn parse_impl_body(token_group: Group) -> (GroupSpan, Vec<Attribute>, Vec<ImplMember>) {
pub(crate) fn parse_impl_body(
token_group: Group,
allow_static: bool,
) -> (GroupSpan, Vec<Attribute>, Vec<ImplMember>) {
let mut body_items = vec![];

let mut tokens = token_group.stream().into_iter().peekable();
Expand All @@ -187,7 +206,14 @@ pub(crate) fn parse_impl_body(token_group: Group) -> (GroupSpan, Vec<Attribute>,
"impl",
) {
Declaration::Function(function) => ImplMember::Method(function),
Declaration::Constant(constant) => ImplMember::Constant(constant),
Declaration::Constant(const_) if const_.tk_const_or_static == "const" => {
// `const` can appear in impl/trait blocks.
ImplMember::Constant(const_)
}
Declaration::Constant(static_) if allow_static => {
// `static` can appear in `extern "abi" {}` blocks.
ImplMember::Constant(static_)
}
Declaration::TyDefinition(ty_def) => ImplMember::AssocTy(ty_def),
Declaration::Macro(macro_) => ImplMember::Macro(macro_),
_ => panic!("unsupported impl item `{:?}`", tokens.peek()),
Expand Down Expand Up @@ -245,7 +271,9 @@ pub(crate) fn parse_impl(tokens: &mut TokenIter, attributes: Vec<Attribute>) ->
let where_clause = consume_where_clause(tokens);

let (tk_braces, inner_attributes, body_items) = match tokens.next().unwrap() {
TokenTree::Group(group) if group.delimiter() == Delimiter::Brace => parse_impl_body(group),
TokenTree::Group(group) if group.delimiter() == Delimiter::Brace => {
parse_impl_body(group, false)
}
token => panic!("cannot parse impl: unexpected token {:?}", token),
};

Expand Down Expand Up @@ -282,7 +310,9 @@ pub(crate) fn parse_trait(

// For trait body, at the moment reuse impl parsing
let (tk_braces, inner_attributes, body_items) = match tokens.next().unwrap() {
TokenTree::Group(group) if group.delimiter() == Delimiter::Brace => parse_impl_body(group),
TokenTree::Group(group) if group.delimiter() == Delimiter::Brace => {
parse_impl_body(group, false)
}
token => panic!("cannot parse trait: unexpected token {:?}", token),
};

Expand Down
2 changes: 0 additions & 2 deletions src/parse_mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ pub(crate) fn parse_mod(
attributes: Vec<Attribute>,
vis_marker: Option<VisMarker>,
) -> Module {
// TODO some items currently unsupported: decl-macros, extern crate

let tk_unsafe = consume_ident(tokens, "unsafe");
let tk_mod = parse_ident(tokens, "mod", "module declaration");
let module_name = consume_declaration_name(tokens);
Expand Down
22 changes: 22 additions & 0 deletions src/snapshots/venial__tests__parse_extern_block-2.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
source: src/tests.rs
expression: unsafe_extern
---
ExternBlock(
ExternBlock {
attributes: [],
vis_marker: None,
tk_unsafe: Some(
Ident(
unsafe,
),
),
tk_extern: Ident(
extern,
),
extern_abi: None,
tk_braces: {},
inner_attributes: [],
body_items: [],
},
)
Loading

0 comments on commit 5f48c4d

Please sign in to comment.