Skip to content
This repository was archived by the owner on May 4, 2024. It is now read-only.

Commit b1f887f

Browse files
committed
[stdlib] add type_name module for type reflection
Move allows type-safe implementations of key types like fungible tokens using the Coin<T> standard. However, distinguishing coins at the type level only is sometimes limiting--e.g., you might want to write a DEX that has at most one pool for a given pair of currencies (need to compare types + reify the result in a value) or write a lending protocol that supports an extensible pool of currencies (need to reify the currency types + map them to the reserve coin supply). This PR seeks to add the weakest form of reflection that supports use-cases like the ones above in order to keep static reasoning as simple/predictable as possible. Similar modules exist in the Starcoin and Aptos frameworks, and Sui users have also been requesting this feature. - Add opaque `TypeName` value that can be produced from a Move type via `get<T>(): TypeName` - Add functions for converting a `TypeName`'s into its inner representation as an ASCII strings so it can be compared or inspected. This is safe because the Move binary format enforces ASCII encoding for all identifiers One issue worth discussing is whether we want to use the full-length representation of addresses (e.g., always produce a 16, 20, or 32 byte string, depending on the platform address length) or the "short" representation which strips leading 0's (e.g., the convention `0x1` for the Move stdlib). I think the former is slightly cleaner/more predictable, but I went with the latter because it already seems to be the standard in the `TypeTag` implementation of `Display`, as well as in the Aptos/Starcoin `TypeInfo` module that provides similar functionality to this code.
1 parent 9b85f4e commit b1f887f

File tree

6 files changed

+270
-0
lines changed

6 files changed

+270
-0
lines changed

language/move-stdlib/docs/overview.md

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ This is the root document for the Move stdlib module documentation. The Move std
2121
- [`0x1::option`](option.md#0x1_option)
2222
- [`0x1::signer`](signer.md#0x1_signer)
2323
- [`0x1::string`](string.md#0x1_string)
24+
- [`0x1::type_name`](type_name.md#0x1_type_name)
2425
- [`0x1::vector`](vector.md#0x1_vector)
2526

2627

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
2+
<a name="0x1_type_name"></a>
3+
4+
# Module `0x1::type_name`
5+
6+
Functionality for converting Move types into values. Use with care!
7+
8+
9+
- [Struct `TypeName`](#0x1_type_name_TypeName)
10+
- [Function `get`](#0x1_type_name_get)
11+
- [Function `borrow_string`](#0x1_type_name_borrow_string)
12+
- [Function `into_string`](#0x1_type_name_into_string)
13+
14+
15+
<pre><code><b>use</b> <a href="ascii.md#0x1_ascii">0x1::ascii</a>;
16+
</code></pre>
17+
18+
19+
20+
<a name="0x1_type_name_TypeName"></a>
21+
22+
## Struct `TypeName`
23+
24+
25+
26+
<pre><code><b>struct</b> <a href="type_name.md#0x1_type_name_TypeName">TypeName</a> <b>has</b> <b>copy</b>, drop, store
27+
</code></pre>
28+
29+
30+
31+
<details>
32+
<summary>Fields</summary>
33+
34+
35+
<dl>
36+
<dt>
37+
<code>name: <a href="ascii.md#0x1_ascii_String">ascii::String</a></code>
38+
</dt>
39+
<dd>
40+
String representation of the type. All types are represented
41+
using their source syntax:
42+
"u8", "u64", "u128", "bool", "address", "vector", "signer" for ground types.
43+
Struct types are represented as fully qualified, <code>0x</code>-prefixed short type names
44+
<code><a href="string.md#0x1_string_String">0x1::string::String</a></code> or
45+
<code>0xabcd::module_name1::type_name1&lt;0x1234::module_name2::type_name2&lt;u64&gt;&gt;</code>
46+
Note: addresses in short type names are *always* shortened by stripping leading 0's
47+
e.g., 0xA rather than 0x00000000A,
48+
</dd>
49+
</dl>
50+
51+
52+
</details>
53+
54+
<a name="0x1_type_name_get"></a>
55+
56+
## Function `get`
57+
58+
Return a value representation of the type <code>T</code>.
59+
60+
61+
<pre><code><b>public</b> <b>fun</b> <a href="type_name.md#0x1_type_name_get">get</a>&lt;T&gt;(): <a href="type_name.md#0x1_type_name_TypeName">type_name::TypeName</a>
62+
</code></pre>
63+
64+
65+
66+
<details>
67+
<summary>Implementation</summary>
68+
69+
70+
<pre><code><b>public</b> <b>native</b> <b>fun</b> <a href="type_name.md#0x1_type_name_get">get</a>&lt;T&gt;(): <a href="type_name.md#0x1_type_name_TypeName">TypeName</a>;
71+
</code></pre>
72+
73+
74+
75+
</details>
76+
77+
<a name="0x1_type_name_borrow_string"></a>
78+
79+
## Function `borrow_string`
80+
81+
Get the String representation of <code>self</code>
82+
83+
84+
<pre><code><b>public</b> <b>fun</b> <a href="type_name.md#0x1_type_name_borrow_string">borrow_string</a>(self: &<a href="type_name.md#0x1_type_name_TypeName">type_name::TypeName</a>): &<a href="ascii.md#0x1_ascii_String">ascii::String</a>
85+
</code></pre>
86+
87+
88+
89+
<details>
90+
<summary>Implementation</summary>
91+
92+
93+
<pre><code><b>public</b> <b>fun</b> <a href="type_name.md#0x1_type_name_borrow_string">borrow_string</a>(self: &<a href="type_name.md#0x1_type_name_TypeName">TypeName</a>): &String {
94+
&self.name
95+
}
96+
</code></pre>
97+
98+
99+
100+
</details>
101+
102+
<a name="0x1_type_name_into_string"></a>
103+
104+
## Function `into_string`
105+
106+
Convert <code>self</code> into its inner String
107+
108+
109+
<pre><code><b>public</b> <b>fun</b> <a href="type_name.md#0x1_type_name_into_string">into_string</a>(self: <a href="type_name.md#0x1_type_name_TypeName">type_name::TypeName</a>): <a href="ascii.md#0x1_ascii_String">ascii::String</a>
110+
</code></pre>
111+
112+
113+
114+
<details>
115+
<summary>Implementation</summary>
116+
117+
118+
<pre><code><b>public</b> <b>fun</b> <a href="type_name.md#0x1_type_name_into_string">into_string</a>(self: <a href="type_name.md#0x1_type_name_TypeName">TypeName</a>): String {
119+
self.name
120+
}
121+
</code></pre>
122+
123+
124+
125+
</details>
126+
127+
128+
[//]: # ("File containing references which can be used from documentation")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/// Functionality for converting Move types into values. Use with care!
2+
module std::type_name {
3+
use std::ascii::String;
4+
5+
struct TypeName has copy, drop, store {
6+
/// String representation of the type. All types are represented
7+
/// using their source syntax:
8+
/// "u8", "u64", "u128", "bool", "address", "vector", "signer" for ground types.
9+
/// Struct types are represented as fully qualified, `0x`-prefixed short type names
10+
/// `0x1::string::String` or
11+
/// `0xabcd::module_name1::type_name1<0x1234::module_name2::type_name2<u64>>`
12+
/// Note: addresses in short type names are *always* shortened by stripping leading 0's
13+
/// e.g., 0xA rather than 0x00000000A,
14+
name: String
15+
}
16+
17+
/// Return a value representation of the type `T`.
18+
public native fun get<T>(): TypeName;
19+
20+
/// Get the String representation of `self`
21+
public fun borrow_string(self: &TypeName): &String {
22+
&self.name
23+
}
24+
25+
/// Convert `self` into its inner String
26+
public fun into_string(self: TypeName): String {
27+
self.name
28+
}
29+
}

language/move-stdlib/src/natives/mod.rs

+9
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub mod event;
88
pub mod hash;
99
pub mod signer;
1010
pub mod string;
11+
pub mod type_name;
1112
#[cfg(feature = "testing")]
1213
pub mod unit_test;
1314
pub mod vector;
@@ -23,6 +24,7 @@ pub struct GasParameters {
2324
pub hash: hash::GasParameters,
2425
pub signer: signer::GasParameters,
2526
pub string: string::GasParameters,
27+
pub type_name: type_name::GasParameters,
2628
pub vector: vector::GasParameters,
2729

2830
#[cfg(feature = "testing")]
@@ -52,6 +54,12 @@ impl GasParameters {
5254
legacy_min_input_len: 0.into(),
5355
},
5456
},
57+
type_name: type_name::GasParameters {
58+
get: type_name::GetGasParameters {
59+
base: 0.into(),
60+
per_byte: 0.into(),
61+
}
62+
},
5563
signer: signer::GasParameters {
5664
borrow_address: signer::BorrowAddressGasParameters { base: 0.into() },
5765
},
@@ -112,6 +120,7 @@ pub fn all_natives(
112120
add_natives!("hash", hash::make_all(gas_params.hash));
113121
add_natives!("signer", signer::make_all(gas_params.signer));
114122
add_natives!("string", string::make_all(gas_params.string));
123+
add_natives!("type_name", type_name::make_all(gas_params.type_name));
115124
add_natives!("vector", vector::make_all(gas_params.vector));
116125
#[cfg(feature = "testing")]
117126
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright (c) The Move Core Contributors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use move_binary_format::errors::PartialVMResult;
5+
use move_core_types::{
6+
gas_algebra::{InternalGas, InternalGasPerByte, NumBytes},
7+
};
8+
use move_vm_runtime::native_functions::{NativeContext, NativeFunction};
9+
use move_vm_types::{
10+
loaded_data::runtime_types::Type,
11+
natives::function::NativeResult,
12+
values::{Struct, Value},
13+
};
14+
15+
use smallvec::{smallvec};
16+
use std::{collections::VecDeque, sync::Arc};
17+
18+
19+
#[derive(Debug, Clone)]
20+
pub struct GetGasParameters {
21+
pub base: InternalGas,
22+
pub per_byte: InternalGasPerByte,
23+
}
24+
25+
// Adapted froom
26+
fn native_get(
27+
gas_params: &GetGasParameters,
28+
context: &mut NativeContext,
29+
ty_args: Vec<Type>,
30+
arguments: VecDeque<Value>,
31+
) -> PartialVMResult<NativeResult> {
32+
debug_assert_eq!(ty_args.len(), 1);
33+
debug_assert!(arguments.is_empty());
34+
35+
let type_tag = context.type_to_type_tag(&ty_args[0])?;
36+
let type_name = type_tag.to_string();
37+
// make a std::string::String
38+
let string_val = Value::struct_(Struct::pack(vec![Value::vector_u8(
39+
type_name.as_bytes().to_vec())]));
40+
// make a std::type_name::TypeName
41+
let type_name_val = Value::struct_(Struct::pack(vec![string_val]));
42+
43+
let cost = gas_params.base + gas_params.per_byte * NumBytes::new(type_name.len() as u64);
44+
45+
Ok(NativeResult::ok(
46+
cost,
47+
smallvec![type_name_val],
48+
))
49+
}
50+
51+
pub fn make_native_get(gas_params: GetGasParameters) -> NativeFunction {
52+
Arc::new(move |context, ty_args, args| native_get(&gas_params, context, ty_args, args))
53+
}
54+
55+
#[derive(Debug, Clone)]
56+
pub struct GasParameters {
57+
pub get: GetGasParameters
58+
}
59+
60+
pub fn make_all(gas_params: GasParameters) -> impl Iterator<Item = (String, NativeFunction)> {
61+
let natives = [
62+
("get", make_native_get(gas_params.get)),
63+
];
64+
65+
crate::natives::helpers::make_module_natives(natives)
66+
}
67+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
module std::type_name_tests {
2+
#[test_only]
3+
use std::type_name::{get, into_string};
4+
#[test_only]
5+
use std::ascii::string;
6+
7+
struct TestStruct {}
8+
9+
struct TestGenerics<phantom T> { }
10+
11+
#[test]
12+
fun test_ground_types() {
13+
assert!(into_string(get<u8>()) == string(b"u8"), 0);
14+
assert!(into_string(get<u64>()) == string(b"u64"), 0);
15+
assert!(into_string(get<u128>()) == string(b"u128"), 0);
16+
assert!(into_string(get<address>()) == string(b"address"), 0);
17+
assert!(into_string(get<signer>()) == string(b"signer"), 0);
18+
assert!(into_string(get<vector<u8>>()) == string(b"vector<u8>"), 0)
19+
}
20+
21+
#[test]
22+
fun test_structs() {
23+
assert!(into_string(get<TestStruct>()) == string(b"0x1::type_name_tests::TestStruct"), 0);
24+
assert!(into_string(get<std::ascii::String>()) == string(b"0x1::ascii::String"), 0);
25+
assert!(into_string(get<std::option::Option<u64>>()) == string(b"0x1::option::Option<u64>"), 0);
26+
assert!(into_string(get<std::string::String>()) == string(b"0x1::string::String"), 0);
27+
}
28+
29+
#[test]
30+
fun test_generics() {
31+
assert!(into_string(get<TestGenerics<bool>>()) == string(b"0x1::type_name_tests::TestGenerics<bool>"), 0);
32+
assert!(into_string(get<vector<TestGenerics<u64>>>()) == string(b"vector<0x1::type_name_tests::TestGenerics<u64>>"), 0);
33+
assert!(into_string(get<std::option::Option<TestGenerics<u8>>>()) == string(b"0x1::option::Option<0x1::type_name_tests::TestGenerics<u8>>"), 0);
34+
}
35+
36+
}

0 commit comments

Comments
 (0)