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

fix: concretize Self references in method signatures #1001

Merged
merged 9 commits into from
Jan 23, 2023
Merged
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
4 changes: 2 additions & 2 deletions near-sdk-macros/src/core_impl/abi/abi_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ impl ImplItemMethodInfo {
} else {
return syn::Error::new_spanned(
&arg.ty,
"Function parameters marked with #[callback_vec] should have type Vec<T>",
"Function parameters marked with #[callback_vec] should have type Vec<T>",
)
.into_compile_error();
};
Expand Down Expand Up @@ -257,7 +257,7 @@ fn generate_schema(ty: &Type, serializer_type: &SerializerType) -> TokenStream2
}

fn generate_abi_type(ty: &Type, serializer_type: &SerializerType) -> TokenStream2 {
let schema = generate_schema(ty, serializer_type);
let schema = generate_schema(&ty, serializer_type);
match serializer_type {
SerializerType::JSON => quote! {
near_sdk::__private::AbiType::Json {
Expand Down
17 changes: 9 additions & 8 deletions near-sdk-macros/src/core_impl/info_extractor/arg_info.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::core_impl::info_extractor::serializer_attr::SerializerAttr;
use crate::core_impl::info_extractor::SerializerType;
use crate::core_impl::info_extractor::{SerializerAttr, SerializerType};
use crate::core_impl::utils;
use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{spanned::Spanned, Attribute, Error, Ident, Pat, PatType, Token, Type};

Expand Down Expand Up @@ -40,16 +41,15 @@ pub struct ArgInfo {

impl ArgInfo {
/// Extract near-sdk specific argument info.
pub fn new(original: &mut PatType) -> syn::Result<Self> {
pub fn new(original: &mut PatType, source_type: &TokenStream) -> syn::Result<Self> {
let mut non_bindgen_attrs = vec![];
let pat_reference;
let pat_mutability;
let ident;
match original.pat.as_ref() {
let ident = match original.pat.as_ref() {
Pat::Ident(pat_ident) => {
pat_reference = pat_ident.by_ref;
pat_mutability = pat_ident.mutability;
ident = pat_ident.ident.clone();
pat_ident.ident.clone()
}
_ => {
return Err(Error::new(
Expand All @@ -58,8 +58,9 @@ impl ArgInfo {
));
}
};
*original.ty.as_mut() = utils::sanitize_self(&original.ty, source_type)?;
let (reference, mutability, ty) = match original.ty.as_ref() {
x @ Type::Array(_) | x @ Type::Path(_) | x @ Type::Tuple(_) => {
x @ (Type::Array(_) | Type::Path(_) | Type::Tuple(_) | Type::Group(_)) => {
(None, None, (*x).clone())
}
Type::Reference(r) => (Some(r.and_token), r.mutability, (*r.elem.as_ref()).clone()),
Expand All @@ -69,7 +70,7 @@ impl ArgInfo {
let mut bindgen_ty = BindgenArgType::Regular;
// In the absence of serialization attributes this is a JSON serialization.
let mut serializer_ty = SerializerType::JSON;
for attr in &mut original.attrs {
for attr in &original.attrs {
let attr_str = attr.path.to_token_stream().to_string();
match attr_str.as_str() {
"callback" | "callback_unwrap" => {
Expand Down
12 changes: 9 additions & 3 deletions near-sdk-macros/src/core_impl/info_extractor/attr_sig_info.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::{ArgInfo, BindgenArgType, InitAttr, MethodType, SerializerAttr, SerializerType};
use proc_macro2::Span;
use crate::core_impl::utils;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::ToTokens;
use syn::spanned::Spanned;
use syn::{Attribute, Error, FnArg, Ident, Receiver, ReturnType, Signature};
Expand Down Expand Up @@ -37,6 +38,7 @@ impl AttrSigInfo {
pub fn new(
original_attrs: &mut Vec<Attribute>,
original_sig: &mut Signature,
source_type: &TokenStream2,
) -> syn::Result<Self> {
if original_sig.asyncness.is_some() {
return Err(Error::new(
Expand Down Expand Up @@ -104,7 +106,7 @@ impl AttrSigInfo {
match fn_arg {
FnArg::Receiver(r) => receiver = Some((*r).clone()),
FnArg::Typed(pat_typed) => {
args.push(ArgInfo::new(pat_typed)?);
args.push(ArgInfo::new(pat_typed, source_type)?);
}
}
}
Expand Down Expand Up @@ -132,7 +134,11 @@ impl AttrSigInfo {
}

*original_attrs = non_bindgen_attrs.clone();
let returns = original_sig.output.clone();
let mut returns = original_sig.output.clone();

if let ReturnType::Type(_, ref mut ty) = returns {
*ty.as_mut() = utils::sanitize_self(&*ty, source_type)?;
}

let mut result = Self {
ident,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::core_impl::info_extractor::AttrSigInfo;
use quote::ToTokens;
use syn::{ImplItemMethod, Type, Visibility};

/// Information extracted from `ImplItemMethod`.
Expand All @@ -15,7 +16,7 @@ impl ImplItemMethodInfo {
/// Process the method and extract information important for near-sdk.
pub fn new(original: &mut ImplItemMethod, struct_type: Type) -> syn::Result<Self> {
let ImplItemMethod { attrs, sig, .. } = original;
let attr_signature_info = AttrSigInfo::new(attrs, sig)?;
let attr_signature_info = AttrSigInfo::new(attrs, sig, &struct_type.to_token_stream())?;
let is_public = matches!(original.vis, Visibility::Public(_));
Ok(Self { attr_signature_info, is_public, struct_type })
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::TraitItemMethodInfo;
use inflector::Inflector;
use quote::ToTokens;
use syn::spanned::Spanned;
use syn::{Error, Ident, ItemTrait, TraitItem};

Expand Down Expand Up @@ -30,7 +31,8 @@ impl ItemTraitInfo {
))
}
TraitItem::Method(method) => {
methods.push(TraitItemMethodInfo::new(method)?);
methods
.push(TraitItemMethodInfo::new(method, &original.ident.to_token_stream())?);
if method.default.is_some() {
return Err(Error::new(
method.span(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::AttrSigInfo;
use proc_macro2::TokenStream as TokenStream2;
use syn::spanned::Spanned;
use syn::{Error, LitStr, TraitItemMethod};

Expand All @@ -13,7 +14,7 @@ pub struct TraitItemMethodInfo {
}

impl TraitItemMethodInfo {
pub fn new(original: &mut TraitItemMethod) -> syn::Result<Self> {
pub fn new(original: &mut TraitItemMethod, trait_name: &TokenStream2) -> syn::Result<Self> {
if original.default.is_some() {
return Err(Error::new(
original.span(),
Expand All @@ -25,7 +26,7 @@ impl TraitItemMethodInfo {

let TraitItemMethod { attrs, sig, .. } = original;

let attr_sig_info = AttrSigInfo::new(attrs, sig)?;
let attr_sig_info = AttrSigInfo::new(attrs, sig, trait_name)?;

let ident_byte_str =
LitStr::new(&attr_sig_info.ident.to_string(), attr_sig_info.ident.span());
Expand Down
58 changes: 58 additions & 0 deletions near-sdk-macros/src/core_impl/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use proc_macro2::{Group, TokenStream as TokenStream2, TokenTree};
use quote::quote;
use syn::{GenericArgument, Path, PathArguments, Type};

/// Checks whether the given path is literally "Result".
Expand Down Expand Up @@ -73,3 +75,59 @@ pub(crate) fn extract_vec_type(ty: &Type) -> Option<&Type> {
_ => None,
}
}

fn _sanitize_self(typ: TokenStream2, replace_with: &TokenStream2) -> TokenStream2 {
let trees = typ.into_iter().map(|t| match t {
TokenTree::Ident(ident) if ident == "Self" => replace_with
.clone()
.into_iter()
.map(|mut t| {
t.set_span(ident.span());
t
})
.collect::<TokenStream2>(),
TokenTree::Group(group) => {
let stream = _sanitize_self(group.stream(), replace_with);
TokenTree::Group(Group::new(group.delimiter(), stream)).into()
}
rest => rest.into(),
});
trees.collect()
}

pub fn sanitize_self(typ: &Type, replace_with: &TokenStream2) -> syn::Result<Type> {
syn::parse2(_sanitize_self(quote! { #typ }, replace_with)).map_err(|original| {
Copy link
Contributor

Choose a reason for hiding this comment

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

Sanitizing TokenStream instead of Type is a nice trick. I remember trying to sanitize Self in the past, and it required so much boilerplate to traverse all Type variants.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, nightmarish 😨

syn::Error::new(original.span(), "Self sanitization failed. Please report this as a bug.")
})
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn sanitize_self_works() {
let typ: Type = syn::parse_str("Self").unwrap();
let replace_with: TokenStream2 = syn::parse_str("MyType").unwrap();
let sanitized = sanitize_self(&typ, &replace_with).unwrap();
assert_eq!(quote! { #sanitized }.to_string(), "MyType");

let typ: Type = syn::parse_str("Vec<Self>").unwrap();
let replace_with: TokenStream2 = syn::parse_str("MyType").unwrap();
let sanitized = sanitize_self(&typ, &replace_with).unwrap();
assert_eq!(quote! { #sanitized }.to_string(), "Vec < MyType >");

let typ: Type = syn::parse_str("Vec<Vec<Self>>").unwrap();
let replace_with: TokenStream2 = syn::parse_str("MyType").unwrap();
let sanitized = sanitize_self(&typ, &replace_with).unwrap();
assert_eq!(quote! { #sanitized }.to_string(), "Vec < Vec < MyType > >");

let typ: Type = syn::parse_str("Option<[(Self, Result<Self, ()>); 2]>").unwrap();
let replace_with: TokenStream2 = syn::parse_str("MyType").unwrap();
let sanitized = sanitize_self(&typ, &replace_with).unwrap();
assert_eq!(
quote! { #sanitized }.to_string(),
"Option < [(MyType , Result < MyType , () >) ; 2] >"
);
}
}
1 change: 1 addition & 0 deletions near-sdk/compilation_tests/all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ fn compilation_tests() {
t.pass("compilation_tests/enum_near_bindgen.rs");
t.pass("compilation_tests/schema_derive.rs");
t.compile_fail("compilation_tests/schema_derive_invalids.rs");
t.pass("compilation_tests/self_support.rs");
}
45 changes: 45 additions & 0 deletions near-sdk/compilation_tests/self_support.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//! Method signature uses Self.

use near_sdk::near_bindgen;
use serde::{Deserialize, Serialize};

#[near_bindgen]
#[derive(Default, Serialize, Deserialize)]
pub struct Ident {
value: u32,
}

#[near_bindgen]
impl Ident {
pub fn plain_arg(_a: Self) {
unimplemented!()
}
pub fn plain_ret() -> Self {
unimplemented!()
}
pub fn plain_arg_ret(a: Self) -> Self {
a
}
pub fn nested_arg(_a: Vec<Self>) {
unimplemented!()
}
pub fn nested_ret() -> Vec<Self> {
unimplemented!()
}
pub fn nested_arg_ret(a: Vec<Self>) -> Vec<Self> {
a
}
pub fn deeply_nested_arg(_a: Option<[(Self, Result<Self, ()>); 2]>) {
unimplemented!()
}
pub fn deeply_nested_ret() -> Option<[(Self, Result<Self, ()>); 2]> {
unimplemented!()
}
pub fn deeply_nested_arg_ret(
a: Option<[(Self, Result<Self, ()>); 2]>,
) -> Option<[(Self, Result<Self, ()>); 2]> {
a
}
}

fn main() {}