Cross-referencing type failure (#2400)

* Cross-referencing type failure

* typo

* Adjust toRawType() with name from registry

* Rewrite yarn.lock

* Adjustments

* Test adjustments
This commit is contained in:
Jaco Greeff
2020-07-01 07:47:21 +02:00
committed by GitHub
parent f07c0628bc
commit bbc30ec9dc
19 changed files with 830 additions and 730 deletions
+1 -1
View File
@@ -171,7 +171,7 @@ export default class BTreeSet<V extends Codec = Codec> extends Set<V> implements
* @description Returns the base runtime type name for this instance
*/
public toRawType (): string {
return `BTreeSet<${new this.#ValClass(this.registry).toRawType()}>`;
return `BTreeSet<${this.registry.getClassName(this.#ValClass) || new this.#ValClass(this.registry).toRawType()}>`;
}
/**
+8 -2
View File
@@ -27,10 +27,16 @@ export interface CompactEncodable extends Codec {
* a number and making the compact representation thereof
*/
export default class Compact<T extends CompactEncodable> extends Base<T> implements ICompact<T> {
private readonly _Type: Constructor<T>;
constructor (registry: Registry, Type: Constructor<T> | keyof InterfaceTypes, value: Compact<T> | AnyNumber = 0) {
const _Type = typeToConstructor(registry, Type);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
super(registry, Compact.decodeCompact<T>(registry, typeToConstructor(registry, Type), value));
super(registry, Compact.decodeCompact<T>(registry, _Type, value));
this._Type = _Type;
}
public static with<T extends CompactEncodable> (Type: Constructor<T> | keyof InterfaceTypes): Constructor<Compact<T>> {
@@ -107,7 +113,7 @@ export default class Compact<T extends CompactEncodable> extends Base<T> impleme
* @description Returns the base runtime type name for this instance
*/
public toRawType (): string {
return `Compact<${this._raw.toRawType()}>`;
return `Compact<${this.registry.getClassName(this._Type) || this._raw.toRawType()}>`;
}
/**
+1 -1
View File
@@ -180,7 +180,7 @@ export default class CodecMap<K extends Codec = Codec, V extends Codec = Codec>
* @description Returns the base runtime type name for this instance
*/
public toRawType (): string {
return `${this.#type}<${new this.#KeyClass(this.registry).toRawType()},${new this.#ValClass(this.registry).toRawType()}>`;
return `${this.#type}<${this.registry.getClassName(this.#KeyClass) || new this.#KeyClass(this.registry).toRawType()},${this.registry.getClassName(this.#ValClass) || new this.#ValClass(this.registry).toRawType()}>`;
}
/**
+1 -1
View File
@@ -129,7 +129,7 @@ export default class Option<T extends Codec> extends Base<T> {
* @description Returns the base runtime type name for this instance
*/
public toRawType (isBare?: boolean): string {
const wrapped = new this.#Type(this.registry).toRawType();
const wrapped = this.registry.getClassName(this.#Type) || new this.#Type(this.registry).toRawType();
return isBare
? wrapped
+1 -1
View File
@@ -58,6 +58,6 @@ describe('Result', (): void => {
});
it('returns a proper raw typedef rom a built-in', (): void => {
expect(registry.createType('DispatchResult').toRawType()).toEqual('Result<(),{"_enum":{"Other":"Null","CannotLookup":"Null","BadOrigin":"Null","Module":"{\\"index\\":\\"u8\\",\\"error\\":\\"u8\\"}"}}>');
expect(registry.createType('DispatchResult').toRawType()).toEqual('Result<(),DispatchError>');
});
});
+6 -7
View File
@@ -5,7 +5,6 @@
/* eslint-disable sort-keys */
import { TypeRegistry } from '../create';
import AccountId from '../generic/AccountId';
import Text from '../primitive/Text';
import U32 from '../primitive/U32';
import { CodecTo } from '../types';
@@ -221,20 +220,20 @@ describe('Struct', (): void => {
it('generates sane toRawType', (): void => {
expect(
new Struct(registry, {
accountId: AccountId,
accountId: 'AccountId',
balanceCompact: registry.createClass('Compact<Balance>'),
blockNumber: registry.createClass('BlockNumber'),
compactNumber: registry.createClass('Compact<BlockNumber>'),
optionNumber: registry.createClass('Option<BlockNumber>'),
counter: U32,
vector: Vec.with(AccountId)
vector: Vec.with('AccountId')
}).toRawType()
).toEqual(JSON.stringify({
accountId: 'AccountId',
balanceCompact: 'Compact<Balance>', // Override in Uint
blockNumber: 'u32',
compactNumber: 'Compact<u32>',
optionNumber: 'Option<u32>',
blockNumber: 'BlockNumber',
compactNumber: 'Compact<BlockNumber>',
optionNumber: 'Option<BlockNumber>',
counter: 'u32',
vector: 'Vec<AccountId>'
}));
@@ -242,7 +241,7 @@ describe('Struct', (): void => {
it('generates sane toRawType (via with)', (): void => {
const Type = Struct.with({
accountId: AccountId,
accountId: 'AccountId',
balance: registry.createClass('Balance')
});
+3 -3
View File
@@ -267,11 +267,11 @@ export default class Struct<
}
public static typesToMap (registry: Registry, Types: Record<string, Constructor>): Record<string, string> {
return Object.entries(Types).reduce((result, [key, Type]): Record<string, string> => {
result[key] = new Type(registry).toRawType();
return Object.entries(Types).reduce((result: Record<string, string>, [key, Type]): Record<string, string> => {
result[key] = registry.getClassName(Type) || new Type(registry).toRawType();
return result;
}, {} as Record<string, string>);
}, {});
}
/**
+2 -2
View File
@@ -131,14 +131,14 @@ describe('Tuple', (): void => {
it('generates sane value with array types', (): void => {
expect(
new Tuple(registry, [U128, registry.createClass('BlockNumber')]).toRawType()
).toEqual('(u128,u32)');
).toEqual('(u128,BlockNumber)');
});
it('generates sane value with object types', (): void => {
expect(
// eslint-disable-next-line sort-keys
new Tuple(registry, { number: U128, blockNumber: registry.createClass('BlockNumber') }).toRawType()
).toEqual('(u128,u32)');
).toEqual('(u128,BlockNumber)');
});
});
});
+3 -1
View File
@@ -101,7 +101,9 @@ export default class Tuple extends AbstractArray<Codec> {
Array.isArray(this._Types)
? this._Types
: Object.values(this._Types)
).map((Type): string => new Type(this.registry).toRawType());
).map((Type) =>
this.registry.getClassName(Type) || new Type(this.registry).toRawType()
);
return `(${types.join(',')})`;
}
+1 -1
View File
@@ -92,6 +92,6 @@ export default class Vec<T extends Codec> extends AbstractArray<T> {
* @description Returns the base runtime type name for this instance
*/
public toRawType (): string {
return `Vec<${new this._Type(this.registry).toRawType()}>`;
return `Vec<${this.registry.getClassName(this._Type) || new this._Type(this.registry).toRawType()}>`;
}
}
+29 -1
View File
@@ -4,7 +4,7 @@
import { TypeDef, TypeDefInfo } from './types';
import { getTypeDef } from '.';
import { TypeRegistry, getTypeDef } from '.';
describe('getTypeDef', (): void => {
it('does not allow invalid tuples, end )', (): void => {
@@ -290,4 +290,32 @@ describe('getTypeDef', (): void => {
type: '[[u8;32];3]'
});
});
it('creates recursive structures', (): void => {
const registry = new TypeRegistry();
registry.register({
Recursive: {
data: 'Vec<Recursive>'
}
});
const raw = registry.createType('Recursive' as 'u32').toRawType();
expect(
getTypeDef(raw)
).toEqual({
info: TypeDefInfo.Struct,
sub: [{
info: TypeDefInfo.Vec,
name: 'data',
sub: {
info: TypeDefInfo.Plain,
type: 'Recursive'
},
type: 'Vec<Recursive>'
}],
type: '{"data":"Vec<Recursive>"}'
});
});
});
+33 -20
View File
@@ -9,10 +9,17 @@ import { assert } from '@polkadot/util';
import sanitize from './sanitize';
import { typeSplit } from './typeSplit';
interface TypeDefOptions {
name?: string;
displayName?: string;
}
const MAX_NESTED = 64;
// decode an enum of either of the following forms
// { _enum: ['A', 'B', 'C'] }
// { _enum: { A: AccountId, B: Balance, C: u32 } }
function _decodeEnum (value: TypeDef, details: string[] | Record<string, string>): TypeDef {
function _decodeEnum (value: TypeDef, details: string[] | Record<string, string>, count: number): TypeDef {
value.info = TypeDefInfo.Enum;
// not as pretty, but remain compatible with oo7 for both struct and Array types
@@ -24,7 +31,7 @@ function _decodeEnum (value: TypeDef, details: string[] | Record<string, string>
}))
: Object.entries(details).map(([name, type]): TypeDef =>
// eslint-disable-next-line @typescript-eslint/no-use-before-define
getTypeDef(type || 'Null', { name })
getTypeDef(type || 'Null', { name }, count)
);
return value;
@@ -50,13 +57,13 @@ function _decodeSet (value: TypeDef, details: Record<string, number>): TypeDef {
// decode a struct, set or enum
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function _decodeStruct (value: TypeDef, type: string, _: string): TypeDef {
function _decodeStruct (value: TypeDef, type: string, _: string, count: number): TypeDef {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const parsed: Record<string, any> = JSON.parse(type);
const keys = Object.keys(parsed);
if (keys.length === 1 && keys[0] === '_enum') {
return _decodeEnum(value, parsed[keys[0]]);
return _decodeEnum(value, parsed[keys[0]], count);
} else if (keys.length === 1 && keys[0] === '_set') {
return _decodeSet(value, parsed[keys[0]]);
}
@@ -66,7 +73,7 @@ function _decodeStruct (value: TypeDef, type: string, _: string): TypeDef {
: undefined;
value.sub = keys.filter((name) => !['_alias'].includes(name)).map((name): TypeDef =>
// eslint-disable-next-line @typescript-eslint/no-use-before-define
getTypeDef(parsed[name], { name })
getTypeDef(parsed[name], { name }, count)
);
return value;
@@ -74,7 +81,7 @@ function _decodeStruct (value: TypeDef, type: string, _: string): TypeDef {
// decode a fixed vector, e.g. [u8;32]
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function _decodeFixedVec (value: TypeDef, type: string, _: string): TypeDef {
function _decodeFixedVec (value: TypeDef, type: string, _: string, count: number): TypeDef {
const [vecType, strLength, displayName] = type.substr(1, type.length - 2).split(';');
const length = parseInt(strLength.trim(), 10);
@@ -84,18 +91,18 @@ function _decodeFixedVec (value: TypeDef, type: string, _: string): TypeDef {
value.displayName = displayName;
value.length = length;
// eslint-disable-next-line @typescript-eslint/no-use-before-define
value.sub = getTypeDef(vecType);
value.sub = getTypeDef(vecType, {}, count);
return value;
}
// decode a tuple
function _decodeTuple (value: TypeDef, _: string, subType: string): TypeDef {
function _decodeTuple (value: TypeDef, _: string, subType: string, count: number): TypeDef {
value.sub = subType.length === 0
? []
: typeSplit(subType).map((inner): TypeDef =>
// eslint-disable-next-line @typescript-eslint/no-use-before-define
getTypeDef(inner)
getTypeDef(inner, {}, count)
);
return value;
@@ -103,7 +110,7 @@ function _decodeTuple (value: TypeDef, _: string, subType: string): TypeDef {
// decode a Int/UInt<bitLength[, name]>
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function _decodeInt (value: TypeDef, type: string, _: string, clazz: 'Int' | 'UInt' = 'Int'): TypeDef {
function _decodeAnyInt (value: TypeDef, type: string, _: string, clazz: 'Int' | 'UInt'): TypeDef {
const [strLength, displayName] = type.substr(clazz.length + 1, type.length - clazz.length - 1 - 1).split(',');
const length = parseInt(strLength.trim(), 10);
@@ -116,8 +123,12 @@ function _decodeInt (value: TypeDef, type: string, _: string, clazz: 'Int' | 'UI
return value;
}
function _decodeInt (value: TypeDef, type: string, subType: string): TypeDef {
return _decodeAnyInt(value, type, subType, 'Int');
}
function _decodeUInt (value: TypeDef, type: string, subType: string): TypeDef {
return _decodeInt(value, type, subType, 'UInt');
return _decodeAnyInt(value, type, subType, 'UInt');
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -139,7 +150,7 @@ function hasWrapper (type: string, [start, end]: [string, string, TypeDefInfo, a
return true;
}
const nestedExtraction: [string, string, TypeDefInfo, (value: TypeDef, type: string, subType: string) => TypeDef][] = [
const nestedExtraction: [string, string, TypeDefInfo, (value: TypeDef, type: string, subType: string, count: number) => TypeDef][] = [
['[', ']', TypeDefInfo.VecFixed, _decodeFixedVec],
['{', '}', TypeDefInfo.Struct, _decodeStruct],
['(', ')', TypeDefInfo.Tuple, _decodeTuple],
@@ -164,15 +175,17 @@ function extractSubType (type: string, [start, end]: [string, string, TypeDefInf
return type.substr(start.length, type.length - start.length - end.length);
}
interface TypeDefOptions {
name?: string;
displayName?: string;
}
export function getTypeDef (_type: string, { displayName, name }: TypeDefOptions = {}): TypeDef {
export function getTypeDef (_type: string, { displayName, name }: TypeDefOptions = {}, count = 0): TypeDef {
// create the type via Type, allowing types to be sanitized
const type = sanitize(_type);
const value: TypeDef = { displayName, info: TypeDefInfo.Plain, name, type };
if (++count === MAX_NESTED) {
console.warn('getTypeDef: Maximum nested limit reached');
return value;
}
const nested = nestedExtraction.find((nested): boolean =>
hasWrapper(type, nested)
);
@@ -180,7 +193,7 @@ export function getTypeDef (_type: string, { displayName, name }: TypeDefOptions
if (nested) {
value.info = nested[2];
return nested[3](value, type, extractSubType(type, nested));
return nested[3](value, type, extractSubType(type, nested), count);
}
const wrapped = wrappedExtraction.find((wrapped): boolean =>
@@ -189,7 +202,7 @@ export function getTypeDef (_type: string, { displayName, name }: TypeDefOptions
if (wrapped) {
value.info = wrapped[2];
value.sub = getTypeDef(extractSubType(type, wrapped));
value.sub = getTypeDef(extractSubType(type, wrapped), {}, count);
}
return value;
+29 -1
View File
@@ -76,7 +76,35 @@ describe('TypeRegistry', (): void => {
expect((first as any).next.unwrap().next.isSome).toBe(false);
});
it('can register cross-referencing types ()', (): void => {
it('can register non-embedded recursive types', (): void => {
registry.register({
Operation: {
data: 'OperationData'
},
OperationData: {
ops: 'Vec<Operation>'
},
Rule: {
data: 'RuleData'
},
RuleData: {
ops: 'Vec<Operation>'
}
});
expect(registry.hasDef('Rule')).toBe(true);
expect(registry.hasClass('Rule')).toBe(false);
const Rule = registry.getOrThrow('Rule');
expect(registry.hasClass('Rule')).toBe(true);
const instance = new Rule(registry);
expect(instance.toRawType()).toEqual('{"data":"RuleData"}');
});
it('can register cross-referencing types', (): void => {
registry.register({
A: {
next: 'B'
+8
View File
@@ -225,6 +225,14 @@ export class TypeRegistry implements Registry {
return this.#chainProperties;
}
public getClassName (clazz: Constructor): string | undefined {
const entry = [...this.#classes.entries()].find(([, test]) => test === clazz);
return entry
? entry[0]
: undefined;
}
public getDefinition (name: string): string | undefined {
return this.#definitions.get(name);
}
+1 -9
View File
@@ -24,15 +24,7 @@ describe('Block', (): void => {
).toEqual(
// each of the containing structures have been stringified on their own
JSON.stringify({
header: JSON.stringify({
parentHash: 'H256',
number: 'Compact<u32>',
stateRoot: 'H256',
extrinsicsRoot: 'H256',
digest: JSON.stringify({
logs: 'Vec<{"_enum":{"Other":"Bytes","AuthoritiesChange":"Vec<AccountId>","ChangesTrieRoot":"H256","SealV0":"(u64,H512)","Consensus":"(u32,Bytes)","Seal":"(u32,Bytes)","PreRuntime":"(u32,Bytes)"}}>'
})
}),
header: 'Header',
extrinsics: 'Vec<Extrinsic>'
})
);
+1
View File
@@ -124,6 +124,7 @@ export interface Registry {
createType <K extends keyof InterfaceTypes> (type: K, ...params: unknown[]): InterfaceTypes[K];
get <T extends Codec = Codec> (name: string, withUnknown?: boolean): Constructor<T> | undefined;
getChainProperties (): ChainProperties | undefined;
getClassName (clazz: Constructor): string | undefined;
getDefinition (name: string): string | undefined;
getOrThrow <T extends Codec = Codec> (name: string, msg?: string): Constructor<T>;
getOrUnknown <T extends Codec = Codec> (name: string): Constructor<T>;