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

Have #[handle_result] support Result types reardless of how they're referred to #1099

Merged
merged 4 commits into from
Oct 22, 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
8 changes: 6 additions & 2 deletions near-sdk-macros/src/core_impl/abi/abi_generator.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{format_ident, quote};
use syn::{Attribute, Expr, Lit::Str, Meta::NameValue, MetaNameValue, Type};
use syn::{parse_quote, Attribute, Expr, Lit::Str, Meta::NameValue, MetaNameValue, Type};

use crate::core_impl::{
utils, BindgenArgType, ImplItemMethodInfo, ItemImplInfo, MethodKind, ReturnKind, SerializerType,
Expand Down Expand Up @@ -204,7 +204,11 @@ impl ImplItemMethodInfo {
match &self.attr_signature_info.returns.kind {
Default => quote! { ::std::option::Option::None },
General(ty) => self.abi_result_tokens_with_return_value(ty),
HandlesResult { ok_type } => self.abi_result_tokens_with_return_value(ok_type),
HandlesResult(ty) => {
// extract the `Ok` type from the result
let ty = parse_quote! { <#ty as near_sdk::__private::ResultTypeExt>::Okay };
self.abi_result_tokens_with_return_value(&ty)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ expression: pretty_print_fn_body_syn_str(actual)
params: ::near_sdk::__private::AbiParameters::Borsh {
args: ::std::vec![
::near_sdk::__private::AbiBorshParameter { name :
::std::string::String::from("y"), type_schema : < String as
::near_sdk::borsh::BorshSchema > ::schema_container(), }
::std::string::String::from("y"), type_schema :
::near_sdk::borsh::schema_container_of:: < String > (), }
],
},
callbacks: ::std::vec![
::near_sdk::__private::AbiType::Borsh { type_schema : < u64 as
::near_sdk::borsh::BorshSchema > ::schema_container(), },
::near_sdk::__private::AbiType::Borsh { type_schema :
::near_sdk::borsh::schema_container_of:: < u64 > (), },
::near_sdk::__private::AbiType::Json { type_schema : gen.subschema_for:: <
Vec < u8 > > (), }
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ expression: pretty_print_fn_body_syn_str(actual)
params: ::near_sdk::__private::AbiParameters::Borsh {
args: ::std::vec![
::near_sdk::__private::AbiBorshParameter { name :
::std::string::String::from("arg0"), type_schema : < FancyStruct as
::near_sdk::borsh::BorshSchema > ::schema_container(), }
::std::string::String::from("arg0"), type_schema :
::near_sdk::borsh::schema_container_of:: < FancyStruct > (), }
],
},
callbacks: ::std::vec![],
callbacks_vec: ::std::option::Option::None,
result: ::std::option::Option::Some(::near_sdk::__private::AbiType::Borsh {
type_schema: <IsOk as ::near_sdk::borsh::BorshSchema>::schema_container(),
type_schema: ::near_sdk::borsh::schema_container_of::<
<Result<IsOk, Error> as near_sdk::__private::ResultTypeExt>::Okay,
>(),
}),
}

Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ expression: pretty_print_fn_body_syn_str(actual)
callbacks: ::std::vec![],
callbacks_vec: ::std::option::Option::None,
result: ::std::option::Option::Some(::near_sdk::__private::AbiType::Json {
type_schema: gen.subschema_for::<IsOk>(),
type_schema: gen
.subschema_for::<
<Result<IsOk, Error> as near_sdk::__private::ResultTypeExt>::Okay,
>(),
}),
}

13 changes: 11 additions & 2 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,7 @@
use super::visitor::Visitor;
use super::{ArgInfo, BindgenArgType, InitAttr, MethodKind, SerializerAttr, SerializerType};
use super::{
ArgInfo, BindgenArgType, HandleResultAttr, InitAttr, MethodKind, SerializerAttr, SerializerType,
};
use crate::core_impl::{utils, Returns};
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::ToTokens;
Expand Down Expand Up @@ -34,6 +36,7 @@ struct AttributeConfig {
borsh: Option<bool>,
json: Option<bool>,
ignore_state: Option<bool>,
aliased: Option<bool>,
}

impl AttrSigInfo {
Expand Down Expand Up @@ -133,7 +136,13 @@ impl AttrSigInfo {
visitor.visit_result_serializer_attr(attr, &serializer)?;
}
"handle_result" => {
visitor.visit_handle_result_attr();
if let Some(value) = args.aliased {
let handle_result = HandleResultAttr { check: value };
visitor.visit_handle_result_attr(&handle_result);
} else {
let handle_result = HandleResultAttr { check: false };
visitor.visit_handle_result_attr(&handle_result);
}
}
_ => {
non_bindgen_attrs.push((*attr).clone());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub struct HandleResultAttr {
pub check: bool,
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ mod tests {
pub fn method(&self) -> &'static str { }
};
let actual = ImplItemMethodInfo::new(&mut method, false, impl_type).map(|_| ()).unwrap_err();
let expected = "Function marked with #[handle_result] should return Result<T, E> (where E implements FunctionError).";
let expected = "Function marked with #[handle_result] should return Result<T, E> (where E implements FunctionError). If you're trying to use a type alias for `Result`, try `#[handle_result(aliased)]`.";
assert_eq!(expected, actual.to_string());
}

Expand Down
5 changes: 4 additions & 1 deletion near-sdk-macros/src/core_impl/info_extractor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ pub use serializer_attr::SerializerAttr;
mod arg_info;
pub use arg_info::{ArgInfo, BindgenArgType};

mod handle_result_attr;
pub use handle_result_attr::HandleResultAttr;

mod attr_sig_info;
pub use attr_sig_info::AttrSigInfo;

Expand Down Expand Up @@ -84,5 +87,5 @@ pub struct Returns {
pub enum ReturnKind {
Default,
General(Type),
HandlesResult { ok_type: Type },
HandlesResult(Type),
}
62 changes: 41 additions & 21 deletions near-sdk-macros/src/core_impl/info_extractor/visitor.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{InitAttr, MethodKind, ReturnKind, SerializerAttr};
use super::{HandleResultAttr, InitAttr, MethodKind, ReturnKind, SerializerAttr};
use crate::core_impl::{utils, CallMethod, InitMethod, Returns, SerializerType, ViewMethod};
use quote::ToTokens;
use syn::{spanned::Spanned, Attribute, Error, FnArg, Receiver, ReturnType, Signature, Type};
Expand All @@ -11,14 +11,30 @@ pub struct Visitor {
}

struct ParsedData {
handles_result: bool,
handles_result: ResultHandling,
is_payable: bool,
is_private: bool,
ignores_state: bool,
result_serializer: SerializerType,
receiver: Option<Receiver>,
}

#[derive(Copy, Clone, PartialEq, Eq)]
enum ResultHandling {
// No result handling.
None,
// Attempt to handle the `Result` without performing a heuristic type check.
NoCheck,
// Attempt to handle the `Result` with a heuristic type check.
Check,
}

impl Default for ResultHandling {
fn default() -> Self {
Self::None
}
}

impl Default for ParsedData {
fn default() -> Self {
Self {
Expand Down Expand Up @@ -110,8 +126,9 @@ impl Visitor {
}
}

pub fn visit_handle_result_attr(&mut self) {
self.parsed_data.handles_result = true
pub fn visit_handle_result_attr(&mut self, params: &HandleResultAttr) {
self.parsed_data.handles_result =
if params.check { ResultHandling::NoCheck } else { ResultHandling::Check }
}

pub fn visit_receiver(&mut self, receiver: &Receiver) -> syn::Result<()> {
Expand Down Expand Up @@ -192,26 +209,29 @@ fn is_view(sig: &Signature) -> bool {
}
}

fn parse_return_kind(typ: &Type, handles_result: bool) -> syn::Result<ReturnKind> {
if handles_result {
if let Some(ok_type) = utils::extract_ok_type(typ) {
Ok(ReturnKind::HandlesResult { ok_type: ok_type.clone() })
} else {
Err(Error::new(
typ.span(),
"Function marked with #[handle_result] should return Result<T, E> (where E implements FunctionError).",
))
fn parse_return_kind(typ: &Type, handles_result: ResultHandling) -> syn::Result<ReturnKind> {
match handles_result {
ResultHandling::NoCheck => Ok(ReturnKind::HandlesResult(typ.clone())),
ResultHandling::Check => {
if !utils::type_is_result(typ) {
Err(Error::new(typ.span(), "Function marked with #[handle_result] should return Result<T, E> (where E implements FunctionError). If you're trying to use a type alias for `Result`, try `#[handle_result(aliased)]`."))
} else {
Ok(ReturnKind::HandlesResult(typ.clone()))
}
}
} else if utils::type_is_result(typ) {
Err(Error::new(
typ.span(),
"Serializing Result<T, E> has been deprecated. Consider marking your method \
with #[handle_result] if the second generic represents a panicable error or \
ResultHandling::None => {
if utils::type_is_result(typ) {
Err(Error::new(
typ.span(),
"Serializing Result<T, E> has been deprecated. Consider marking your method \
with #[handle_result] if the second generic represents a panicable error or \
replacing Result with another two type sum enum otherwise. If you really want \
to keep the legacy behavior, mark the method with #[handle_result] and make \
it return Result<Result<T, E>, near_sdk::Abort>.",
))
} else {
Ok(ReturnKind::General(typ.clone()))
))
} else {
Ok(ReturnKind::General(typ.clone()))
}
}
}
}
1 change: 1 addition & 0 deletions near-sdk/compilation_tests/all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ fn compilation_tests() {
t.pass("compilation_tests/private_init_method.rs");
t.compile_fail("compilation_tests/self_forbidden_in_non_init_fn_return.rs");
t.compile_fail("compilation_tests/self_forbidden_in_non_init_fn_arg.rs");
t.pass("compilation_tests/handle_result_alias.rs");
}
20 changes: 20 additions & 0 deletions near-sdk/compilation_tests/handle_result_alias.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use borsh::{BorshDeserialize, BorshSerialize};
use near_sdk::near_bindgen;

type MyResult = Result<u32, &'static str>;

#[near_bindgen]
#[derive(Default, BorshDeserialize, BorshSerialize)]
struct Contract {
value: u32,
}

#[near_bindgen]
impl Contract {
#[handle_result(aliased)]
pub fn fun(&self) -> MyResult {
Err("error")
}
}

fn main() {}
17 changes: 17 additions & 0 deletions near-sdk/src/private/result_type_ext.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
pub trait ResultTypeExt: seal::ResultTypeExtSeal {
type Okay;
type Error;
}

impl<T, E> ResultTypeExt for Result<T, E> {
type Okay = T;
type Error = E;
}

// This is the "sealed trait" pattern:
// https://rust-lang.github.io/api-guidelines/future-proofing.html
mod seal {
pub trait ResultTypeExtSeal {}

impl<T, E> ResultTypeExtSeal for Result<T, E> {}
}
Loading