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

Add informational field type name #30

Merged
merged 13 commits into from
Nov 30, 2020
Merged

Conversation

ascjones
Copy link
Contributor

@ascjones ascjones commented Nov 24, 2020

Given a complex type which incorporates a field with a type alias, we currently lose the information about the use of a type alias. e.g.

type Balance = u128

#[derive(TypeInfo)]
struct S { b: Balance }

This currently results in the field type being resolved to u128 in the type registry, and we have no way to distinguish between a plain u128 and an aliased u128, which we may want to treat differently, e.g. some custom decoding or display logic for that type.

This PR adds the optional type_name property to Field, and updates the derive macro to extract the field type names e,g, in the above example the underlying type is u128 but the type name is Balance.

Note that this will add a type_name even if it is non-aliased, e.g. a: u32 will still have the type display name "u32" as part of the field definition. Even though this is redundant, and it is possible to remove non aliased display names (I implemented then removed it), I have left them in for the sake of simplicity and flexibility, leaving it up to consumers to do the lookup to determine whether an alias is being used or not.

This is required for use-ink/cargo-contract#79 in order for custom encoding/decoding for aliased Balance types.

@ascjones ascjones requested a review from dvdplm November 24, 2020 16:26
Copy link
Contributor

@dvdplm dvdplm left a comment

Choose a reason for hiding this comment

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

Two main concerns (really: nitpicks) are:

  1. the API is a bit less ergonomic to use after this
  2. not a fan of the display_name name; could it be just alias?

Overall great stuff though.

derive/src/lib.rs Outdated Show resolved Hide resolved
src/build.rs Outdated Show resolved Hide resolved
src/ty/fields.rs Outdated Show resolved Hide resolved
src/ty/fields.rs Outdated Show resolved Hide resolved
src/build.rs Outdated Show resolved Hide resolved
src/build.rs Outdated Show resolved Hide resolved
test_suite/tests/derive.rs Outdated Show resolved Hide resolved
test_suite/tests/derive.rs Outdated Show resolved Hide resolved

#[test]
fn fields_with_type_alias() {
type BoolAlias = bool;
Copy link
Contributor

Choose a reason for hiding this comment

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

Unrelated to this PR, but do you think we could have this work at some point?

#[derive(TypeInfo)]
type BoolAlias = bool;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's a good idea, it may well be worth experimenting with. This would provide extra information about what aliases are available and may increase the accuracy of the heuristic identifying alias usage.

Ultimately it would also have the same limitation in that there is still no guarantee to identify when a given alias is being used, because the registry is using the underlying type id i.e. any::TypeId::of<BoolAlias>() == any::TypeId::of<bool>().

Copy link
Contributor

Choose a reason for hiding this comment

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

Never thought about this use case. I am not sure it goes too far but making it optional would probably be okay. The question is how to integrate it properly into the current scheme if we actually want ot.

# Conflicts:
#	derive/src/lib.rs
#	src/build.rs
#	src/ty/fields.rs
#	test_suite/tests/derive.rs
#	test_suite/tests/json.rs
This will display any valid field type name as specified in the source code.
@ascjones ascjones changed the title Add field type display name to capture aliases Add field type name Nov 25, 2020
@ascjones ascjones changed the title Add field type name Add informational field type name Nov 25, 2020
@ascjones
Copy link
Contributor Author

Updated so it now contains the raw string which is a result of stringify(#ty) where ty is a syn::Type. Let me know what you think @Robbepop.

Copy link
Contributor

@dvdplm dvdplm left a comment

Choose a reason for hiding this comment

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

I'll give it a proper review, but this real good. :)

{ "type": 2 },
{ "type": 4, "displayName": ["bool"] },
{ "type": 1, "typeName": "i32" },
{ "type": 2, "typeName": "[u8 ; 32]" },
Copy link
Contributor

Choose a reason for hiding this comment

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

This is nice. :)

Copy link
Contributor

Choose a reason for hiding this comment

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

Note sure we need displayName or typeName (why rename btw?) for types such as [u8; 32] because its primary use case is still to counterfeit type aliases and besides that I cannot think of any other serious use case. Also I am unsure about turning the namespace (array of strings) into a single string since that kinda dictates how 3rd party tools handle namespaces or simply removes namespaces which are kinda important to differentiate between types.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

displayName or typeName (why rename btw?)

To make it more general to imply we are not just dealing with paths/aliases now, but whatever type name is in that position.

Note sure we need displayName or typeName (why rename btw?) for types such as [u8; 32]

Indeed it is redundant because you can just look it up on the underlying type. We can easily filter those out, but in this iteration I have decided to always take the type name in the source for simplicity's sake, at the cost of some redundancy.

besides that I cannot think of any other serious use case

I believe we might need this initially if we want to be able to convert to the existing substrate metadata format, though of course it may be possible without, needs some investigation.

Also I am unsure about turning the namespace (array of strings) into a single string since that kinda dictates how 3rd party tools handle namespaces or simply removes namespaces which are kinda important to differentiate between types.

3rd party tools should not be relying on any namespace in the type/display name because it will only be there if the user decides to fully qualify their type e.g. struct S { field: foo::bar::Foo }, if they just use foo::bar::Foo then the display/type name will be Foo.

By not using the Path type anymore we are just giving an extra indication that this field does not provide any guarantees as to the content of that field other than it is the name of a valid Rust type in the context of the .rs file where it is defined.

Copy link
Contributor

@Robbepop Robbepop left a comment

Choose a reason for hiding this comment

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

already commented

Comment on lines +129 to +142
input
.replace(" ::", "::")
.replace(":: ", "::")
.replace(" ,", ",")
.replace(" ;", ";")
.replace(" [", "[")
.replace("[ ", "[")
.replace(" ]", "]")
.replace(" (", "(")
.replace("( ", "(")
.replace(" )", ")")
.replace(" <", "<")
.replace("< ", "<")
.replace(" >", ">")
Copy link
Contributor

Choose a reason for hiding this comment

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

😆

Copy link
Contributor

Choose a reason for hiding this comment

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

dq: why wouldn't .replace(" ", ""); work here? Is space ever significant?

Copy link
Contributor

@Robbepop Robbepop Nov 27, 2020

Choose a reason for hiding this comment

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

yes, e.g. something like &'a mut dyn Foo would become &'amutdynFoo.

Copy link
Contributor

Choose a reason for hiding this comment

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

🤦

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Robbepop
Copy link
Contributor

Robbepop commented Nov 27, 2020

The only thing that bogs me with this approach that we no longer really support overlapping aliases by replacing the namespace with plain strings. E.g.:

mod a {
    pub type Foo = i32;
}
mod b {
    pub type Foo = i64; // could also be just i32
}
fn do_stuff(foo1: a::Foo, foo2: b::Foo) { ... }

We probably would prefer having aliases here but we could no longer differentiate between them really with them being just strings.
I can imagine this to be commonly used in the Substrate codebase.

@ascjones
Copy link
Contributor Author

You can still differentiate between the two though, instead of ["a", "Foo"] and ["b", "Foo"] you'd just have "a::Foo" and "b::Foo".

@Robbepop
Copy link
Contributor

You can still differentiate between the two though, instead of ["a", "Foo"] and ["b", "Foo"] you'd just have "a::Foo" and "b::Foo".

But then how is "a::Foo" string in the metadata an improvement over the more generic ["a", "Foo"] array especially in case of our targeted cross-language ecosystem where not every supported language has the same Rust-y syntax for namespaces?

@ascjones
Copy link
Contributor Author

For the case of a plain TypePath like that it's not an improvement. a structured Path is clearly better. This was a way to get access to the unstructured type name for the field for any possible type without having to define a bunch of types to map to all possible variants of syn::Type. It's crude but KISS, and it provides enough information to do what I want for my use case.

However if we want something a bit more sophisticated it will just require a bit more thought.

As an aside, I think it's useful information anyway. We probably don't want to model type in source code being &mut <<T as MyTrait>::Type as MyOtherTrait>::Type, all you care about is that it eventually resolves to u64, good to know what the source definition was.

@Robbepop
Copy link
Contributor

For the case of a plain TypePath like that it's not an improvement. a structured Path is clearly better. This was a way to get access to the unstructured type name for the field for any possible type without having to define a bunch of types to map to all possible variants of syn::Type. It's crude but KISS, and it provides enough information to do what I want for my use case.

However if we want something a bit more sophisticated it will just require a bit more thought.

As an aside, I think it's useful information anyway. We probably don't want to model type in source code being &mut <<T as MyTrait>::Type as MyOtherTrait>::Type, all you care about is that it eventually resolves to u64, good to know what the source definition was.

ah okay I see.

@ascjones ascjones merged commit 7d6a837 into master Nov 30, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants