| use crate::syntax::attrs::OtherAttrs; |
| use crate::syntax::cfg::CfgExpr; |
| use crate::syntax::discriminant::DiscriminantSet; |
| use crate::syntax::file::{Item, ItemForeignMod}; |
| use crate::syntax::report::Errors; |
| use crate::syntax::Atom::*; |
| use crate::syntax::{ |
| attrs, error, Api, Array, Derive, Doc, Enum, EnumRepr, ExternFn, ExternType, ForeignName, Impl, |
| Include, IncludeKind, Lang, Lifetimes, NamedType, Namespace, Pair, Ptr, Receiver, Ref, |
| Signature, SliceRef, Struct, Ty1, Type, TypeAlias, Var, Variant, |
| }; |
| use proc_macro2::{Delimiter, Group, Span, TokenStream, TokenTree}; |
| use quote::{format_ident, quote, quote_spanned}; |
| use std::mem; |
| use syn::parse::{ParseStream, Parser}; |
| use syn::punctuated::Punctuated; |
| use syn::{ |
| Abi, Attribute, Error, Expr, Fields, FnArg, ForeignItem, ForeignItemFn, ForeignItemType, |
| GenericArgument, GenericParam, Generics, Ident, ItemEnum, ItemImpl, ItemStruct, Lit, LitStr, |
| Pat, PathArguments, Result, ReturnType, Signature as RustSignature, Token, TraitBound, |
| TraitBoundModifier, Type as RustType, TypeArray, TypeBareFn, TypeParamBound, TypePath, TypePtr, |
| TypeReference, Variant as RustVariant, Visibility, |
| }; |
| |
| pub mod kw { |
| syn::custom_keyword!(Pin); |
| syn::custom_keyword!(Result); |
| } |
| |
| pub fn parse_items( |
| cx: &mut Errors, |
| items: Vec<Item>, |
| trusted: bool, |
| namespace: &Namespace, |
| ) -> Vec<Api> { |
| let mut apis = Vec::new(); |
| for item in items { |
| match item { |
| Item::Struct(item) => match parse_struct(cx, item, namespace) { |
| Ok(strct) => apis.push(strct), |
| Err(err) => cx.push(err), |
| }, |
| Item::Enum(item) => apis.push(parse_enum(cx, item, namespace)), |
| Item::ForeignMod(foreign_mod) => { |
| parse_foreign_mod(cx, foreign_mod, &mut apis, trusted, namespace) |
| } |
| Item::Impl(item) => match parse_impl(cx, item) { |
| Ok(imp) => apis.push(imp), |
| Err(err) => cx.push(err), |
| }, |
| Item::Use(item) => cx.error(item, error::USE_NOT_ALLOWED), |
| Item::Other(item) => cx.error(item, "unsupported item"), |
| } |
| } |
| apis |
| } |
| |
| fn parse_struct(cx: &mut Errors, mut item: ItemStruct, namespace: &Namespace) -> Result<Api> { |
| let mut cfg = CfgExpr::Unconditional; |
| let mut doc = Doc::new(); |
| let mut derives = Vec::new(); |
| let mut namespace = namespace.clone(); |
| let mut cxx_name = None; |
| let mut rust_name = None; |
| let attrs = attrs::parse( |
| cx, |
| mem::take(&mut item.attrs), |
| attrs::Parser { |
| cfg: Some(&mut cfg), |
| doc: Some(&mut doc), |
| derives: Some(&mut derives), |
| namespace: Some(&mut namespace), |
| cxx_name: Some(&mut cxx_name), |
| rust_name: Some(&mut rust_name), |
| ..Default::default() |
| }, |
| ); |
| |
| let named_fields = match item.fields { |
| Fields::Named(fields) => fields, |
| Fields::Unit => return Err(Error::new_spanned(item, "unit structs are not supported")), |
| Fields::Unnamed(_) => { |
| return Err(Error::new_spanned(item, "tuple structs are not supported")); |
| } |
| }; |
| |
| let mut lifetimes = Punctuated::new(); |
| let mut has_unsupported_generic_param = false; |
| for pair in item.generics.params.into_pairs() { |
| let (param, punct) = pair.into_tuple(); |
| match param { |
| GenericParam::Lifetime(param) => { |
| if !param.bounds.is_empty() && !has_unsupported_generic_param { |
| let msg = "lifetime parameter with bounds is not supported yet"; |
| cx.error(¶m, msg); |
| has_unsupported_generic_param = true; |
| } |
| lifetimes.push_value(param.lifetime); |
| if let Some(punct) = punct { |
| lifetimes.push_punct(punct); |
| } |
| } |
| GenericParam::Type(param) => { |
| if !has_unsupported_generic_param { |
| let msg = "struct with generic type parameter is not supported yet"; |
| cx.error(¶m, msg); |
| has_unsupported_generic_param = true; |
| } |
| } |
| GenericParam::Const(param) => { |
| if !has_unsupported_generic_param { |
| let msg = "struct with const generic parameter is not supported yet"; |
| cx.error(¶m, msg); |
| has_unsupported_generic_param = true; |
| } |
| } |
| } |
| } |
| |
| if let Some(where_clause) = &item.generics.where_clause { |
| cx.error( |
| where_clause, |
| "struct with where-clause is not supported yet", |
| ); |
| } |
| |
| let mut fields = Vec::new(); |
| for field in named_fields.named { |
| let ident = field.ident.unwrap(); |
| let mut cfg = CfgExpr::Unconditional; |
| let mut doc = Doc::new(); |
| let mut cxx_name = None; |
| let mut rust_name = None; |
| let attrs = attrs::parse( |
| cx, |
| field.attrs, |
| attrs::Parser { |
| cfg: Some(&mut cfg), |
| doc: Some(&mut doc), |
| cxx_name: Some(&mut cxx_name), |
| rust_name: Some(&mut rust_name), |
| ..Default::default() |
| }, |
| ); |
| let ty = match parse_type(&field.ty) { |
| Ok(ty) => ty, |
| Err(err) => { |
| cx.push(err); |
| continue; |
| } |
| }; |
| let visibility = visibility_pub(&field.vis, ident.span()); |
| let name = pair(Namespace::default(), &ident, cxx_name, rust_name); |
| let colon_token = field.colon_token.unwrap(); |
| fields.push(Var { |
| cfg, |
| doc, |
| attrs, |
| visibility, |
| name, |
| colon_token, |
| ty, |
| }); |
| } |
| |
| let struct_token = item.struct_token; |
| let visibility = visibility_pub(&item.vis, struct_token.span); |
| let name = pair(namespace, &item.ident, cxx_name, rust_name); |
| let generics = Lifetimes { |
| lt_token: item.generics.lt_token, |
| lifetimes, |
| gt_token: item.generics.gt_token, |
| }; |
| let brace_token = named_fields.brace_token; |
| |
| Ok(Api::Struct(Struct { |
| cfg, |
| doc, |
| derives, |
| attrs, |
| visibility, |
| struct_token, |
| name, |
| generics, |
| brace_token, |
| fields, |
| })) |
| } |
| |
| fn parse_enum(cx: &mut Errors, item: ItemEnum, namespace: &Namespace) -> Api { |
| let mut cfg = CfgExpr::Unconditional; |
| let mut doc = Doc::new(); |
| let mut derives = Vec::new(); |
| let mut repr = None; |
| let mut namespace = namespace.clone(); |
| let mut cxx_name = None; |
| let mut rust_name = None; |
| let mut variants_from_header = None; |
| let attrs = attrs::parse( |
| cx, |
| item.attrs, |
| attrs::Parser { |
| cfg: Some(&mut cfg), |
| doc: Some(&mut doc), |
| derives: Some(&mut derives), |
| repr: Some(&mut repr), |
| namespace: Some(&mut namespace), |
| cxx_name: Some(&mut cxx_name), |
| rust_name: Some(&mut rust_name), |
| variants_from_header: Some(&mut variants_from_header), |
| ..Default::default() |
| }, |
| ); |
| |
| if !item.generics.params.is_empty() { |
| let vis = &item.vis; |
| let enum_token = item.enum_token; |
| let ident = &item.ident; |
| let generics = &item.generics; |
| let span = quote!(#vis #enum_token #ident #generics); |
| cx.error(span, "enum with generic parameters is not supported"); |
| } else if let Some(where_clause) = &item.generics.where_clause { |
| cx.error(where_clause, "enum with where-clause is not supported"); |
| } |
| |
| let mut variants = Vec::new(); |
| let mut discriminants = DiscriminantSet::new(repr); |
| for variant in item.variants { |
| match parse_variant(cx, variant, &mut discriminants) { |
| Ok(variant) => variants.push(variant), |
| Err(err) => cx.push(err), |
| } |
| } |
| |
| let enum_token = item.enum_token; |
| let visibility = visibility_pub(&item.vis, enum_token.span); |
| let brace_token = item.brace_token; |
| |
| let explicit_repr = repr.is_some(); |
| let mut repr = U8; |
| match discriminants.inferred_repr() { |
| Ok(inferred) => repr = inferred, |
| Err(err) => { |
| let span = quote_spanned!(brace_token.span=> #enum_token {}); |
| cx.error(span, err); |
| variants.clear(); |
| } |
| } |
| |
| let name = pair(namespace, &item.ident, cxx_name, rust_name); |
| let repr_ident = Ident::new(repr.as_ref(), Span::call_site()); |
| let repr_type = Type::Ident(NamedType::new(repr_ident)); |
| let repr = EnumRepr::Native { |
| atom: repr, |
| repr_type, |
| }; |
| let generics = Lifetimes { |
| lt_token: None, |
| lifetimes: Punctuated::new(), |
| gt_token: None, |
| }; |
| let variants_from_header_attr = variants_from_header; |
| let variants_from_header = variants_from_header_attr.is_some(); |
| |
| Api::Enum(Enum { |
| cfg, |
| doc, |
| derives, |
| attrs, |
| visibility, |
| enum_token, |
| name, |
| generics, |
| brace_token, |
| variants, |
| variants_from_header, |
| variants_from_header_attr, |
| repr, |
| explicit_repr, |
| }) |
| } |
| |
| fn parse_variant( |
| cx: &mut Errors, |
| mut variant: RustVariant, |
| discriminants: &mut DiscriminantSet, |
| ) -> Result<Variant> { |
| let mut cfg = CfgExpr::Unconditional; |
| let mut doc = Doc::new(); |
| let mut cxx_name = None; |
| let mut rust_name = None; |
| let attrs = attrs::parse( |
| cx, |
| mem::take(&mut variant.attrs), |
| attrs::Parser { |
| cfg: Some(&mut cfg), |
| doc: Some(&mut doc), |
| cxx_name: Some(&mut cxx_name), |
| rust_name: Some(&mut rust_name), |
| ..Default::default() |
| }, |
| ); |
| |
| match variant.fields { |
| Fields::Unit => {} |
| _ => { |
| let msg = "enums with data are not supported yet"; |
| return Err(Error::new_spanned(variant, msg)); |
| } |
| } |
| |
| let expr = variant.discriminant.as_ref().map(|(_, expr)| expr); |
| let try_discriminant = match &expr { |
| Some(lit) => discriminants.insert(lit), |
| None => discriminants.insert_next(), |
| }; |
| let discriminant = match try_discriminant { |
| Ok(discriminant) => discriminant, |
| Err(err) => return Err(Error::new_spanned(variant, err)), |
| }; |
| |
| let name = pair(Namespace::ROOT, &variant.ident, cxx_name, rust_name); |
| let expr = variant.discriminant.map(|(_, expr)| expr); |
| |
| Ok(Variant { |
| cfg, |
| doc, |
| attrs, |
| name, |
| discriminant, |
| expr, |
| }) |
| } |
| |
| fn parse_foreign_mod( |
| cx: &mut Errors, |
| foreign_mod: ItemForeignMod, |
| out: &mut Vec<Api>, |
| trusted: bool, |
| namespace: &Namespace, |
| ) { |
| let lang = match parse_lang(&foreign_mod.abi) { |
| Ok(lang) => lang, |
| Err(err) => return cx.push(err), |
| }; |
| |
| match lang { |
| Lang::Rust => { |
| if foreign_mod.unsafety.is_some() { |
| let unsafety = foreign_mod.unsafety; |
| let abi = &foreign_mod.abi; |
| let span = quote!(#unsafety #abi); |
| cx.error(span, "extern \"Rust\" block does not need to be unsafe"); |
| } |
| } |
| Lang::Cxx => {} |
| } |
| |
| let trusted = trusted || foreign_mod.unsafety.is_some(); |
| |
| let mut cfg = CfgExpr::Unconditional; |
| let mut namespace = namespace.clone(); |
| let attrs = attrs::parse( |
| cx, |
| foreign_mod.attrs, |
| attrs::Parser { |
| cfg: Some(&mut cfg), |
| namespace: Some(&mut namespace), |
| ..Default::default() |
| }, |
| ); |
| |
| let mut items = Vec::new(); |
| for foreign in foreign_mod.items { |
| match foreign { |
| ForeignItem::Type(foreign) => { |
| let ety = parse_extern_type(cx, foreign, lang, trusted, &cfg, &namespace, &attrs); |
| items.push(ety); |
| } |
| ForeignItem::Fn(foreign) => { |
| match parse_extern_fn(cx, foreign, lang, trusted, &cfg, &namespace, &attrs) { |
| Ok(efn) => items.push(efn), |
| Err(err) => cx.push(err), |
| } |
| } |
| ForeignItem::Macro(foreign) if foreign.mac.path.is_ident("include") => { |
| match foreign.mac.parse_body_with(parse_include) { |
| Ok(mut include) => { |
| include.cfg = cfg.clone(); |
| items.push(Api::Include(include)); |
| } |
| Err(err) => cx.push(err), |
| } |
| } |
| ForeignItem::Verbatim(tokens) => { |
| match parse_extern_verbatim(cx, tokens, lang, trusted, &cfg, &namespace, &attrs) { |
| Ok(api) => items.push(api), |
| Err(err) => cx.push(err), |
| } |
| } |
| _ => cx.error(foreign, "unsupported foreign item"), |
| } |
| } |
| |
| if !trusted |
| && items.iter().any(|api| match api { |
| Api::CxxFunction(efn) => efn.unsafety.is_none(), |
| _ => false, |
| }) |
| { |
| cx.error( |
| foreign_mod.abi, |
| "block must be declared `unsafe extern \"C++\"` if it contains any safe-to-call C++ functions", |
| ); |
| } |
| |
| let mut types = items.iter().filter_map(|item| match item { |
| Api::CxxType(ety) | Api::RustType(ety) => Some(&ety.name), |
| Api::TypeAlias(alias) => Some(&alias.name), |
| _ => None, |
| }); |
| if let (Some(single_type), None) = (types.next(), types.next()) { |
| let single_type = single_type.clone(); |
| for item in &mut items { |
| if let Api::CxxFunction(efn) | Api::RustFunction(efn) = item { |
| if let Some(receiver) = &mut efn.receiver { |
| if receiver.ty.rust == "Self" { |
| receiver.ty.rust = single_type.rust.clone(); |
| } |
| } |
| } |
| } |
| } |
| |
| out.extend(items); |
| } |
| |
| fn parse_lang(abi: &Abi) -> Result<Lang> { |
| let name = match &abi.name { |
| Some(name) => name, |
| None => { |
| return Err(Error::new_spanned( |
| abi, |
| "ABI name is required, extern \"C++\" or extern \"Rust\"", |
| )); |
| } |
| }; |
| |
| match name.value().as_str() { |
| "C++" => Ok(Lang::Cxx), |
| "Rust" => Ok(Lang::Rust), |
| _ => Err(Error::new_spanned( |
| abi, |
| "unrecognized ABI, requires either \"C++\" or \"Rust\"", |
| )), |
| } |
| } |
| |
| fn parse_extern_type( |
| cx: &mut Errors, |
| foreign_type: ForeignItemType, |
| lang: Lang, |
| trusted: bool, |
| extern_block_cfg: &CfgExpr, |
| namespace: &Namespace, |
| attrs: &OtherAttrs, |
| ) -> Api { |
| let mut cfg = extern_block_cfg.clone(); |
| let mut doc = Doc::new(); |
| let mut derives = Vec::new(); |
| let mut namespace = namespace.clone(); |
| let mut cxx_name = None; |
| let mut rust_name = None; |
| let mut attrs = attrs.clone(); |
| attrs.extend(attrs::parse( |
| cx, |
| foreign_type.attrs, |
| attrs::Parser { |
| cfg: Some(&mut cfg), |
| doc: Some(&mut doc), |
| derives: Some(&mut derives), |
| namespace: Some(&mut namespace), |
| cxx_name: Some(&mut cxx_name), |
| rust_name: Some(&mut rust_name), |
| ..Default::default() |
| }, |
| )); |
| |
| let type_token = foreign_type.type_token; |
| let visibility = visibility_pub(&foreign_type.vis, type_token.span); |
| let name = pair(namespace, &foreign_type.ident, cxx_name, rust_name); |
| let generics = extern_type_lifetimes(cx, foreign_type.generics); |
| let colon_token = None; |
| let bounds = Vec::new(); |
| let semi_token = foreign_type.semi_token; |
| |
| (match lang { |
| Lang::Cxx => Api::CxxType, |
| Lang::Rust => Api::RustType, |
| })(ExternType { |
| cfg, |
| lang, |
| doc, |
| derives, |
| attrs, |
| visibility, |
| type_token, |
| name, |
| generics, |
| colon_token, |
| bounds, |
| semi_token, |
| trusted, |
| }) |
| } |
| |
| fn parse_extern_fn( |
| cx: &mut Errors, |
| mut foreign_fn: ForeignItemFn, |
| lang: Lang, |
| trusted: bool, |
| extern_block_cfg: &CfgExpr, |
| namespace: &Namespace, |
| attrs: &OtherAttrs, |
| ) -> Result<Api> { |
| let mut cfg = extern_block_cfg.clone(); |
| let mut doc = Doc::new(); |
| let mut namespace = namespace.clone(); |
| let mut cxx_name = None; |
| let mut rust_name = None; |
| let mut attrs = attrs.clone(); |
| attrs.extend(attrs::parse( |
| cx, |
| mem::take(&mut foreign_fn.attrs), |
| attrs::Parser { |
| cfg: Some(&mut cfg), |
| doc: Some(&mut doc), |
| namespace: Some(&mut namespace), |
| cxx_name: Some(&mut cxx_name), |
| rust_name: Some(&mut rust_name), |
| ..Default::default() |
| }, |
| )); |
| |
| let generics = &foreign_fn.sig.generics; |
| if generics.where_clause.is_some() |
| || generics.params.iter().any(|param| match param { |
| GenericParam::Lifetime(lifetime) => !lifetime.bounds.is_empty(), |
| GenericParam::Type(_) | GenericParam::Const(_) => true, |
| }) |
| { |
| return Err(Error::new_spanned( |
| foreign_fn, |
| "extern function with generic parameters is not supported yet", |
| )); |
| } |
| |
| if let Some(variadic) = &foreign_fn.sig.variadic { |
| return Err(Error::new_spanned( |
| variadic, |
| "variadic function is not supported yet", |
| )); |
| } |
| |
| if foreign_fn.sig.asyncness.is_some() && !cfg!(feature = "experimental-async-fn") { |
| return Err(Error::new_spanned( |
| foreign_fn, |
| "async function is not directly supported yet, but see https://cxx.rs/async.html \ |
| for a working approach, and https://github.com/pcwalton/cxx-async for some helpers; \ |
| eventually what you wrote will work but it isn't integrated into the cxx::bridge \ |
| macro yet", |
| )); |
| } |
| |
| if foreign_fn.sig.constness.is_some() { |
| return Err(Error::new_spanned( |
| foreign_fn, |
| "const extern function is not supported", |
| )); |
| } |
| |
| if let Some(abi) = &foreign_fn.sig.abi { |
| return Err(Error::new_spanned( |
| abi, |
| "explicit ABI on extern function is not supported", |
| )); |
| } |
| |
| let mut receiver = None; |
| let mut args = Punctuated::new(); |
| for arg in foreign_fn.sig.inputs.pairs() { |
| let (arg, comma) = arg.into_tuple(); |
| match arg { |
| FnArg::Receiver(arg) => { |
| if let Some((ampersand, lifetime)) = &arg.reference { |
| receiver = Some(Receiver { |
| pinned: false, |
| ampersand: *ampersand, |
| lifetime: lifetime.clone(), |
| mutable: arg.mutability.is_some(), |
| var: arg.self_token, |
| colon_token: Token, |
| ty: NamedType::new(Ident::new("Self", arg.self_token.span)), |
| shorthand: true, |
| pin_tokens: None, |
| mutability: arg.mutability, |
| }); |
| continue; |
| } |
| if let Some(colon_token) = arg.colon_token { |
| let ty = parse_type(&arg.ty)?; |
| if let Type::Ref(reference) = ty { |
| if let Type::Ident(ident) = reference.inner { |
| receiver = Some(Receiver { |
| pinned: reference.pinned, |
| ampersand: reference.ampersand, |
| lifetime: reference.lifetime, |
| mutable: reference.mutable, |
| var: Token), |
| colon_token, |
| ty: ident, |
| shorthand: false, |
| pin_tokens: reference.pin_tokens, |
| mutability: reference.mutability, |
| }); |
| continue; |
| } |
| } |
| } |
| return Err(Error::new_spanned(arg, "unsupported method receiver")); |
| } |
| FnArg::Typed(arg) => { |
| let ident = match arg.pat.as_ref() { |
| Pat::Ident(pat) => pat.ident.clone(), |
| Pat::Wild(pat) => { |
| Ident::new(&format!("arg{}", args.len()), pat.underscore_token.span) |
| } |
| _ => return Err(Error::new_spanned(arg, "unsupported signature")), |
| }; |
| let ty = parse_type(&arg.ty)?; |
| let cfg = CfgExpr::Unconditional; |
| let doc = Doc::new(); |
| let attrs = OtherAttrs::none(); |
| let visibility = Token); |
| let name = pair(Namespace::default(), &ident, None, None); |
| let colon_token = arg.colon_token; |
| args.push_value(Var { |
| cfg, |
| doc, |
| attrs, |
| visibility, |
| name, |
| colon_token, |
| ty, |
| }); |
| if let Some(comma) = comma { |
| args.push_punct(*comma); |
| } |
| } |
| } |
| } |
| |
| let mut throws_tokens = None; |
| let ret = parse_return_type(&foreign_fn.sig.output, &mut throws_tokens)?; |
| let throws = throws_tokens.is_some(); |
| let asyncness = foreign_fn.sig.asyncness; |
| let unsafety = foreign_fn.sig.unsafety; |
| let fn_token = foreign_fn.sig.fn_token; |
| let inherited_span = unsafety.map_or(fn_token.span, |unsafety| unsafety.span); |
| let visibility = visibility_pub(&foreign_fn.vis, inherited_span); |
| let name = pair(namespace, &foreign_fn.sig.ident, cxx_name, rust_name); |
| let generics = generics.clone(); |
| let paren_token = foreign_fn.sig.paren_token; |
| let semi_token = foreign_fn.semi_token; |
| |
| Ok(match lang { |
| Lang::Cxx => Api::CxxFunction, |
| Lang::Rust => Api::RustFunction, |
| }(ExternFn { |
| cfg, |
| lang, |
| doc, |
| attrs, |
| visibility, |
| name, |
| sig: Signature { |
| asyncness, |
| unsafety, |
| fn_token, |
| generics, |
| receiver, |
| args, |
| ret, |
| throws, |
| paren_token, |
| throws_tokens, |
| }, |
| semi_token, |
| trusted, |
| })) |
| } |
| |
| fn parse_extern_verbatim( |
| cx: &mut Errors, |
| tokens: TokenStream, |
| lang: Lang, |
| trusted: bool, |
| extern_block_cfg: &CfgExpr, |
| namespace: &Namespace, |
| attrs: &OtherAttrs, |
| ) -> Result<Api> { |
| |input: ParseStream| -> Result<Api> { |
| let unparsed_attrs = input.call(Attribute::parse_outer)?; |
| let visibility: Visibility = input.parse()?; |
| if input.peek(Token![type]) { |
| parse_extern_verbatim_type( |
| cx, |
| unparsed_attrs, |
| visibility, |
| input, |
| lang, |
| trusted, |
| extern_block_cfg, |
| namespace, |
| attrs, |
| ) |
| } else if input.peek(Token![fn]) { |
| parse_extern_verbatim_fn(input) |
| } else { |
| let span = input.cursor().token_stream(); |
| Err(Error::new_spanned( |
| span, |
| "unsupported foreign item, expected `type` or `fn`", |
| )) |
| } |
| } |
| .parse2(tokens) |
| } |
| |
| fn parse_extern_verbatim_type( |
| cx: &mut Errors, |
| unparsed_attrs: Vec<Attribute>, |
| visibility: Visibility, |
| input: ParseStream, |
| lang: Lang, |
| trusted: bool, |
| extern_block_cfg: &CfgExpr, |
| namespace: &Namespace, |
| attrs: &OtherAttrs, |
| ) -> Result<Api> { |
| let type_token: Token![type] = input.parse()?; |
| let ident: Ident = input.parse()?; |
| let generics: Generics = input.parse()?; |
| let lifetimes = extern_type_lifetimes(cx, generics); |
| let lookahead = input.lookahead1(); |
| if lookahead.peek(Token![=]) { |
| // type Alias = crate::path::to::Type; |
| parse_type_alias( |
| cx, |
| unparsed_attrs, |
| visibility, |
| type_token, |
| ident, |
| lifetimes, |
| input, |
| lang, |
| extern_block_cfg, |
| namespace, |
| attrs, |
| ) |
| } else if lookahead.peek(Token![:]) { |
| // type Opaque: Bound2 + Bound2; |
| parse_extern_type_bounded( |
| cx, |
| unparsed_attrs, |
| visibility, |
| type_token, |
| ident, |
| lifetimes, |
| input, |
| lang, |
| trusted, |
| extern_block_cfg, |
| namespace, |
| attrs, |
| ) |
| } else { |
| Err(lookahead.error()) |
| } |
| } |
| |
| fn extern_type_lifetimes(cx: &mut Errors, generics: Generics) -> Lifetimes { |
| let mut lifetimes = Punctuated::new(); |
| let mut has_unsupported_generic_param = false; |
| for pair in generics.params.into_pairs() { |
| let (param, punct) = pair.into_tuple(); |
| match param { |
| GenericParam::Lifetime(param) => { |
| if !param.bounds.is_empty() && !has_unsupported_generic_param { |
| let msg = "lifetime parameter with bounds is not supported yet"; |
| cx.error(¶m, msg); |
| has_unsupported_generic_param = true; |
| } |
| lifetimes.push_value(param.lifetime); |
| if let Some(punct) = punct { |
| lifetimes.push_punct(punct); |
| } |
| } |
| GenericParam::Type(param) => { |
| if !has_unsupported_generic_param { |
| let msg = "extern type with generic type parameter is not supported yet"; |
| cx.error(¶m, msg); |
| has_unsupported_generic_param = true; |
| } |
| } |
| GenericParam::Const(param) => { |
| if !has_unsupported_generic_param { |
| let msg = "extern type with const generic parameter is not supported yet"; |
| cx.error(¶m, msg); |
| has_unsupported_generic_param = true; |
| } |
| } |
| } |
| } |
| Lifetimes { |
| lt_token: generics.lt_token, |
| lifetimes, |
| gt_token: generics.gt_token, |
| } |
| } |
| |
| fn parse_extern_verbatim_fn(input: ParseStream) -> Result<Api> { |
| input.parse::<RustSignature>()?; |
| input.parse::<Token![;]>()?; |
| unreachable!() |
| } |
| |
| fn parse_type_alias( |
| cx: &mut Errors, |
| unparsed_attrs: Vec<Attribute>, |
| visibility: Visibility, |
| type_token: Token![type], |
| ident: Ident, |
| generics: Lifetimes, |
| input: ParseStream, |
| lang: Lang, |
| extern_block_cfg: &CfgExpr, |
| namespace: &Namespace, |
| attrs: &OtherAttrs, |
| ) -> Result<Api> { |
| let eq_token: Token![=] = input.parse()?; |
| let ty: RustType = input.parse()?; |
| let semi_token: Token![;] = input.parse()?; |
| |
| let mut cfg = extern_block_cfg.clone(); |
| let mut doc = Doc::new(); |
| let mut derives = Vec::new(); |
| let mut namespace = namespace.clone(); |
| let mut cxx_name = None; |
| let mut rust_name = None; |
| let mut attrs = attrs.clone(); |
| attrs.extend(attrs::parse( |
| cx, |
| unparsed_attrs, |
| attrs::Parser { |
| cfg: Some(&mut cfg), |
| doc: Some(&mut doc), |
| derives: Some(&mut derives), |
| namespace: Some(&mut namespace), |
| cxx_name: Some(&mut cxx_name), |
| rust_name: Some(&mut rust_name), |
| ..Default::default() |
| }, |
| )); |
| |
| if lang == Lang::Rust { |
| let span = quote!(#type_token #semi_token); |
| let msg = "type alias in extern \"Rust\" block is not supported"; |
| return Err(Error::new_spanned(span, msg)); |
| } |
| |
| let visibility = visibility_pub(&visibility, type_token.span); |
| let name = pair(namespace, &ident, cxx_name, rust_name); |
| |
| Ok(Api::TypeAlias(TypeAlias { |
| cfg, |
| doc, |
| derives, |
| attrs, |
| visibility, |
| type_token, |
| name, |
| generics, |
| eq_token, |
| ty, |
| semi_token, |
| })) |
| } |
| |
| fn parse_extern_type_bounded( |
| cx: &mut Errors, |
| unparsed_attrs: Vec<Attribute>, |
| visibility: Visibility, |
| type_token: Token![type], |
| ident: Ident, |
| generics: Lifetimes, |
| input: ParseStream, |
| lang: Lang, |
| trusted: bool, |
| extern_block_cfg: &CfgExpr, |
| namespace: &Namespace, |
| attrs: &OtherAttrs, |
| ) -> Result<Api> { |
| let mut bounds = Vec::new(); |
| let colon_token: Option<Token![:]> = input.parse()?; |
| if colon_token.is_some() { |
| loop { |
| match input.parse()? { |
| TypeParamBound::Trait(TraitBound { |
| paren_token: None, |
| modifier: TraitBoundModifier::None, |
| lifetimes: None, |
| path, |
| }) if if let Some(derive) = path.get_ident().and_then(Derive::from) { |
| bounds.push(derive); |
| true |
| } else { |
| false |
| } => {} |
| bound => cx.error(bound, "unsupported trait"), |
| } |
| |
| let lookahead = input.lookahead1(); |
| if lookahead.peek(Token![+]) { |
| input.parse::<Token![+]>()?; |
| } else if lookahead.peek(Token![;]) { |
| break; |
| } else { |
| return Err(lookahead.error()); |
| } |
| } |
| } |
| let semi_token: Token![;] = input.parse()?; |
| |
| let mut cfg = extern_block_cfg.clone(); |
| let mut doc = Doc::new(); |
| let mut derives = Vec::new(); |
| let mut namespace = namespace.clone(); |
| let mut cxx_name = None; |
| let mut rust_name = None; |
| let mut attrs = attrs.clone(); |
| attrs.extend(attrs::parse( |
| cx, |
| unparsed_attrs, |
| attrs::Parser { |
| cfg: Some(&mut cfg), |
| doc: Some(&mut doc), |
| derives: Some(&mut derives), |
| namespace: Some(&mut namespace), |
| cxx_name: Some(&mut cxx_name), |
| rust_name: Some(&mut rust_name), |
| ..Default::default() |
| }, |
| )); |
| |
| let visibility = visibility_pub(&visibility, type_token.span); |
| let name = pair(namespace, &ident, cxx_name, rust_name); |
| |
| Ok(match lang { |
| Lang::Cxx => Api::CxxType, |
| Lang::Rust => Api::RustType, |
| }(ExternType { |
| cfg, |
| lang, |
| doc, |
| derives, |
| attrs, |
| visibility, |
| type_token, |
| name, |
| generics, |
| colon_token, |
| bounds, |
| semi_token, |
| trusted, |
| })) |
| } |
| |
| fn parse_impl(cx: &mut Errors, imp: ItemImpl) -> Result<Api> { |
| let impl_token = imp.impl_token; |
| |
| let mut cfg = CfgExpr::Unconditional; |
| attrs::parse( |
| cx, |
| imp.attrs, |
| attrs::Parser { |
| cfg: Some(&mut cfg), |
| ..Default::default() |
| }, |
| ); |
| |
| if !imp.items.is_empty() { |
| let mut span = Group::new(Delimiter::Brace, TokenStream::new()); |
| span.set_span(imp.brace_token.span.join()); |
| return Err(Error::new_spanned(span, "expected an empty impl block")); |
| } |
| |
| if let Some((bang, path, for_token)) = &imp.trait_ { |
| let self_ty = &imp.self_ty; |
| let span = quote!(#bang #path #for_token #self_ty); |
| return Err(Error::new_spanned( |
| span, |
| "unexpected impl, expected something like `impl UniquePtr<T> {}`", |
| )); |
| } |
| |
| if let Some(where_clause) = imp.generics.where_clause { |
| return Err(Error::new_spanned( |
| where_clause, |
| "where-clause on an impl is not supported yet", |
| )); |
| } |
| let mut impl_generics = Lifetimes { |
| lt_token: imp.generics.lt_token, |
| lifetimes: Punctuated::new(), |
| gt_token: imp.generics.gt_token, |
| }; |
| for pair in imp.generics.params.into_pairs() { |
| let (param, punct) = pair.into_tuple(); |
| match param { |
| GenericParam::Lifetime(def) if def.bounds.is_empty() => { |
| impl_generics.lifetimes.push_value(def.lifetime); |
| if let Some(punct) = punct { |
| impl_generics.lifetimes.push_punct(punct); |
| } |
| } |
| _ => { |
| let span = quote!(#impl_token #impl_generics); |
| return Err(Error::new_spanned( |
| span, |
| "generic parameter on an impl is not supported yet", |
| )); |
| } |
| } |
| } |
| |
| let mut negative_token = None; |
| let mut self_ty = *imp.self_ty; |
| if let RustType::Verbatim(ty) = &self_ty { |
| let mut iter = ty.clone().into_iter(); |
| if let Some(TokenTree::Punct(punct)) = iter.next() { |
| if punct.as_char() == '!' { |
| let ty = iter.collect::<TokenStream>(); |
| if !ty.is_empty() { |
| negative_token = Some(Token)); |
| self_ty = syn::parse2(ty)?; |
| } |
| } |
| } |
| } |
| |
| let ty = parse_type(&self_ty)?; |
| let ty_generics = match &ty { |
| Type::RustBox(ty) |
| | Type::RustVec(ty) |
| | Type::UniquePtr(ty) |
| | Type::SharedPtr(ty) |
| | Type::WeakPtr(ty) |
| | Type::CxxVector(ty) => match &ty.inner { |
| Type::Ident(ident) => ident.generics.clone(), |
| _ => Lifetimes::default(), |
| }, |
| Type::Ident(_) |
| | Type::Ref(_) |
| | Type::Ptr(_) |
| | Type::Str(_) |
| | Type::Fn(_) |
| | Type::Void(_) |
| | Type::SliceRef(_) |
| | Type::Array(_) => Lifetimes::default(), |
| }; |
| |
| let negative = negative_token.is_some(); |
| let brace_token = imp.brace_token; |
| |
| Ok(Api::Impl(Impl { |
| cfg, |
| impl_token, |
| impl_generics, |
| negative, |
| ty, |
| ty_generics, |
| brace_token, |
| negative_token, |
| })) |
| } |
| |
| fn parse_include(input: ParseStream) -> Result<Include> { |
| if input.peek(LitStr) { |
| let lit: LitStr = input.parse()?; |
| let span = lit.span(); |
| return Ok(Include { |
| cfg: CfgExpr::Unconditional, |
| path: lit.value(), |
| kind: IncludeKind::Quoted, |
| begin_span: span, |
| end_span: span, |
| }); |
| } |
| |
| if input.peek(Token![<]) { |
| let mut path = String::new(); |
| |
| let langle: Token![<] = input.parse()?; |
| while !input.is_empty() && !input.peek(Token![>]) { |
| let token: TokenTree = input.parse()?; |
| match token { |
| TokenTree::Ident(token) => path += &token.to_string(), |
| TokenTree::Literal(token) |
| if token |
| .to_string() |
| .starts_with(|ch: char| ch.is_ascii_digit()) => |
| { |
| path += &token.to_string(); |
| } |
| TokenTree::Punct(token) => path.push(token.as_char()), |
| _ => return Err(Error::new(token.span(), "unexpected token in include path")), |
| } |
| } |
| let rangle: Token![>] = input.parse()?; |
| |
| return Ok(Include { |
| cfg: CfgExpr::Unconditional, |
| path, |
| kind: IncludeKind::Bracketed, |
| begin_span: langle.span, |
| end_span: rangle.span, |
| }); |
| } |
| |
| Err(input.error("expected \"quoted/path/to\" or <bracketed/path/to>")) |
| } |
| |
| fn parse_type(ty: &RustType) -> Result<Type> { |
| match ty { |
| RustType::Reference(ty) => parse_type_reference(ty), |
| RustType::Ptr(ty) => parse_type_ptr(ty), |
| RustType::Path(ty) => parse_type_path(ty), |
| RustType::Array(ty) => parse_type_array(ty), |
| RustType::BareFn(ty) => parse_type_fn(ty), |
| RustType::Tuple(ty) if ty.elems.is_empty() => Ok(Type::Void(ty.paren_token.span.join())), |
| _ => Err(Error::new_spanned(ty, "unsupported type")), |
| } |
| } |
| |
| fn parse_type_reference(ty: &TypeReference) -> Result<Type> { |
| let ampersand = ty.and_token; |
| let lifetime = ty.lifetime.clone(); |
| let mutable = ty.mutability.is_some(); |
| let mutability = ty.mutability; |
| |
| if let RustType::Slice(slice) = ty.elem.as_ref() { |
| let inner = parse_type(&slice.elem)?; |
| let bracket = slice.bracket_token; |
| return Ok(Type::SliceRef(Box::new(SliceRef { |
| ampersand, |
| lifetime, |
| mutable, |
| bracket, |
| inner, |
| mutability, |
| }))); |
| } |
| |
| let inner = parse_type(&ty.elem)?; |
| let pinned = false; |
| let pin_tokens = None; |
| |
| Ok(match &inner { |
| Type::Ident(ident) if ident.rust == "str" => { |
| if ty.mutability.is_some() { |
| return Err(Error::new_spanned(ty, "unsupported type")); |
| } else { |
| Type::Str |
| } |
| } |
| _ => Type::Ref, |
| }(Box::new(Ref { |
| pinned, |
| ampersand, |
| lifetime, |
| mutable, |
| inner, |
| pin_tokens, |
| mutability, |
| }))) |
| } |
| |
| fn parse_type_ptr(ty: &TypePtr) -> Result<Type> { |
| let star = ty.star_token; |
| let mutable = ty.mutability.is_some(); |
| let constness = ty.const_token; |
| let mutability = ty.mutability; |
| |
| let inner = parse_type(&ty.elem)?; |
| |
| Ok(Type::Ptr(Box::new(Ptr { |
| star, |
| mutable, |
| inner, |
| mutability, |
| constness, |
| }))) |
| } |
| |
| fn parse_type_path(ty: &TypePath) -> Result<Type> { |
| let path = &ty.path; |
| if ty.qself.is_none() && path.leading_colon.is_none() && path.segments.len() == 1 { |
| let segment = &path.segments[0]; |
| let ident = segment.ident.clone(); |
| match &segment.arguments { |
| PathArguments::None => return Ok(Type::Ident(NamedType::new(ident))), |
| PathArguments::AngleBracketed(generic) => { |
| if ident == "UniquePtr" && generic.args.len() == 1 { |
| if let GenericArgument::Type(arg) = &generic.args[0] { |
| let inner = parse_type(arg)?; |
| return Ok(Type::UniquePtr(Box::new(Ty1 { |
| name: ident, |
| langle: generic.lt_token, |
| inner, |
| rangle: generic.gt_token, |
| }))); |
| } |
| } else if ident == "SharedPtr" && generic.args.len() == 1 { |
| if let GenericArgument::Type(arg) = &generic.args[0] { |
| let inner = parse_type(arg)?; |
| return Ok(Type::SharedPtr(Box::new(Ty1 { |
| name: ident, |
| langle: generic.lt_token, |
| inner, |
| rangle: generic.gt_token, |
| }))); |
| } |
| } else if ident == "WeakPtr" && generic.args.len() == 1 { |
| if let GenericArgument::Type(arg) = &generic.args[0] { |
| let inner = parse_type(arg)?; |
| return Ok(Type::WeakPtr(Box::new(Ty1 { |
| name: ident, |
| langle: generic.lt_token, |
| inner, |
| rangle: generic.gt_token, |
| }))); |
| } |
| } else if ident == "CxxVector" && generic.args.len() == 1 { |
| if let GenericArgument::Type(arg) = &generic.args[0] { |
| let inner = parse_type(arg)?; |
| return Ok(Type::CxxVector(Box::new(Ty1 { |
| name: ident, |
| langle: generic.lt_token, |
| inner, |
| rangle: generic.gt_token, |
| }))); |
| } |
| } else if ident == "Box" && generic.args.len() == 1 { |
| if let GenericArgument::Type(arg) = &generic.args[0] { |
| let inner = parse_type(arg)?; |
| return Ok(Type::RustBox(Box::new(Ty1 { |
| name: ident, |
| langle: generic.lt_token, |
| inner, |
| rangle: generic.gt_token, |
| }))); |
| } |
| } else if ident == "Vec" && generic.args.len() == 1 { |
| if let GenericArgument::Type(arg) = &generic.args[0] { |
| let inner = parse_type(arg)?; |
| return Ok(Type::RustVec(Box::new(Ty1 { |
| name: ident, |
| langle: generic.lt_token, |
| inner, |
| rangle: generic.gt_token, |
| }))); |
| } |
| } else if ident == "Pin" && generic.args.len() == 1 { |
| if let GenericArgument::Type(arg) = &generic.args[0] { |
| let inner = parse_type(arg)?; |
| let pin_token = kw::Pin(ident.span()); |
| if let Type::Ref(mut inner) = inner { |
| inner.pinned = true; |
| inner.pin_tokens = |
| Some((pin_token, generic.lt_token, generic.gt_token)); |
| return Ok(Type::Ref(inner)); |
| } |
| } |
| } else { |
| let mut lifetimes = Punctuated::new(); |
| let mut only_lifetimes = true; |
| for pair in generic.args.pairs() { |
| let (param, punct) = pair.into_tuple(); |
| if let GenericArgument::Lifetime(param) = param { |
| lifetimes.push_value(param.clone()); |
| if let Some(punct) = punct { |
| lifetimes.push_punct(*punct); |
| } |
| } else { |
| only_lifetimes = false; |
| break; |
| } |
| } |
| if only_lifetimes { |
| return Ok(Type::Ident(NamedType { |
| rust: ident, |
| generics: Lifetimes { |
| lt_token: Some(generic.lt_token), |
| lifetimes, |
| gt_token: Some(generic.gt_token), |
| }, |
| })); |
| } |
| } |
| } |
| PathArguments::Parenthesized(_) => {} |
| } |
| } |
| |
| Err(Error::new_spanned(ty, "unsupported type")) |
| } |
| |
| fn parse_type_array(ty: &TypeArray) -> Result<Type> { |
| let inner = parse_type(&ty.elem)?; |
| |
| let len_expr = if let Expr::Lit(lit) = &ty.len { |
| lit |
| } else { |
| let msg = "unsupported expression, array length must be an integer literal"; |
| return Err(Error::new_spanned(&ty.len, msg)); |
| }; |
| |
| let len_token = if let Lit::Int(int) = &len_expr.lit { |
| int.clone() |
| } else { |
| let msg = "array length must be an integer literal"; |
| return Err(Error::new_spanned(len_expr, msg)); |
| }; |
| |
| let len = len_token.base10_parse::<usize>()?; |
| if len == 0 { |
| let msg = "array with zero size is not supported"; |
| return Err(Error::new_spanned(ty, msg)); |
| } |
| |
| let bracket = ty.bracket_token; |
| let semi_token = ty.semi_token; |
| |
| Ok(Type::Array(Box::new(Array { |
| bracket, |
| inner, |
| semi_token, |
| len, |
| len_token, |
| }))) |
| } |
| |
| fn parse_type_fn(ty: &TypeBareFn) -> Result<Type> { |
| if ty.lifetimes.is_some() { |
| return Err(Error::new_spanned( |
| ty, |
| "function pointer with lifetime parameters is not supported yet", |
| )); |
| } |
| |
| if ty.variadic.is_some() { |
| return Err(Error::new_spanned( |
| ty, |
| "variadic function pointer is not supported yet", |
| )); |
| } |
| |
| let args = ty |
| .inputs |
| .iter() |
| .enumerate() |
| .map(|(i, arg)| { |
| let (ident, colon_token) = match &arg.name { |
| Some((ident, colon_token)) => (ident.clone(), *colon_token), |
| None => { |
| let fn_span = ty.paren_token.span.join(); |
| let ident = format_ident!("arg{}", i, span = fn_span); |
| let colon_token = Token; |
| (ident, colon_token) |
| } |
| }; |
| let ty = parse_type(&arg.ty)?; |
| let cfg = CfgExpr::Unconditional; |
| let doc = Doc::new(); |
| let attrs = OtherAttrs::none(); |
| let visibility = Token); |
| let name = pair(Namespace::default(), &ident, None, None); |
| Ok(Var { |
| cfg, |
| doc, |
| attrs, |
| visibility, |
| name, |
| colon_token, |
| ty, |
| }) |
| }) |
| .collect::<Result<_>>()?; |
| |
| let mut throws_tokens = None; |
| let ret = parse_return_type(&ty.output, &mut throws_tokens)?; |
| let throws = throws_tokens.is_some(); |
| |
| let asyncness = None; |
| let unsafety = ty.unsafety; |
| let fn_token = ty.fn_token; |
| let generics = Generics::default(); |
| let receiver = None; |
| let paren_token = ty.paren_token; |
| |
| Ok(Type::Fn(Box::new(Signature { |
| asyncness, |
| unsafety, |
| fn_token, |
| generics, |
| receiver, |
| args, |
| ret, |
| throws, |
| paren_token, |
| throws_tokens, |
| }))) |
| } |
| |
| fn parse_return_type( |
| ty: &ReturnType, |
| throws_tokens: &mut Option<(kw::Result, Token![<], Token![>])>, |
| ) -> Result<Option<Type>> { |
| let mut ret = match ty { |
| ReturnType::Default => return Ok(None), |
| ReturnType::Type(_, ret) => ret.as_ref(), |
| }; |
| |
| if let RustType::Path(ty) = ret { |
| let path = &ty.path; |
| if ty.qself.is_none() && path.leading_colon.is_none() && path.segments.len() == 1 { |
| let segment = &path.segments[0]; |
| let ident = segment.ident.clone(); |
| if let PathArguments::AngleBracketed(generic) = &segment.arguments { |
| if ident == "Result" && generic.args.len() == 1 { |
| if let GenericArgument::Type(arg) = &generic.args[0] { |
| ret = arg; |
| *throws_tokens = |
| Some((kw::Result(ident.span()), generic.lt_token, generic.gt_token)); |
| } |
| } |
| } |
| } |
| } |
| |
| match parse_type(ret)? { |
| Type::Void(_) => Ok(None), |
| ty => Ok(Some(ty)), |
| } |
| } |
| |
| fn visibility_pub(vis: &Visibility, inherited: Span) -> Token![pub] { |
| Token => vis.span, |
| Visibility::Restricted(vis) => vis.pub_token.span, |
| Visibility::Inherited => inherited, |
| }) |
| } |
| |
| fn pair( |
| namespace: Namespace, |
| default: &Ident, |
| cxx: Option<ForeignName>, |
| rust: Option<Ident>, |
| ) -> Pair { |
| Pair { |
| namespace, |
| cxx: cxx |
| .unwrap_or_else(|| ForeignName::parse(&default.to_string(), default.span()).unwrap()), |
| rust: rust.unwrap_or_else(|| default.clone()), |
| } |
| } |