Short-circuit full type (with lookupIndex) (#3804)

* Short-circuit full type (with lookupIndex)

* Defaults for type expansion

* Tuple with extra spaces removed

* Fill in generated types

* Adjust generation

* Adjust tests

* Adjust
This commit is contained in:
Jaco
2021-07-23 18:04:14 +02:00
committed by GitHub
parent 6e4c62ea99
commit 6e620e3bb3
8 changed files with 152 additions and 72 deletions
@@ -158,12 +158,12 @@
"type": "AccountId"
}
],
"type": "(AccountId, AccountId)",
"type": "(AccountId,AccountId)",
"name": "Occupied"
}
],
"info": 3,
"type": "{\"_enum\":{\"Vacant\":\"VacantEntry\",\"Occupied\":\"(AccountId, AccountId)\"}}"
"type": "{\"_enum\":{\"Vacant\":\"VacantEntry\",\"Occupied\":\"(AccountId,AccountId)\"}}"
},
{
"info": 10,
@@ -181,7 +181,7 @@
"type": "AccountId"
}
],
"type": "(AccountId, AccountId)"
"type": "(AccountId,AccountId)"
},
{
"info": 6,
@@ -198,4 +198,4 @@
"info": 5,
"type": "Option<AccountId>"
}
]
]
@@ -206,12 +206,12 @@
"type": "AccountId"
}
],
"type": "(AccountId, AccountId)",
"type": "(AccountId,AccountId)",
"name": "Occupied"
}
],
"info": 3,
"type": "{\"_enum\":{\"Vacant\":\"VacantEntry\",\"Occupied\":\"(AccountId, AccountId)\"}}"
"type": "{\"_enum\":{\"Vacant\":\"VacantEntry\",\"Occupied\":\"(AccountId,AccountId)\"}}"
},
{
"info": 10,
@@ -229,7 +229,7 @@
"type": "AccountId"
}
],
"type": "(AccountId, AccountId)"
"type": "(AccountId,AccountId)"
},
{
"displayName": "ValueEntry",
@@ -427,4 +427,4 @@
],
"type": "{\"_enum\":[\"NotOwner\",\"NotApproved\",\"TokenExists\",\"TokenNotFound\",\"CannotInsert\",\"CannotRemove\",\"CannotFetchValue\",\"NotAllowed\"]}"
}
]
]
@@ -63,12 +63,12 @@
"type": "AccountId"
}
],
"type": "(u32, AccountId)",
"type": "(u32,AccountId)",
"name": "Occupied"
}
],
"info": 3,
"type": "{\"_enum\":{\"Vacant\":\"VacantEntry\",\"Occupied\":\"(u32, AccountId)\"}}"
"type": "{\"_enum\":{\"Vacant\":\"VacantEntry\",\"Occupied\":\"(u32,AccountId)\"}}"
},
{
"info": 10,
@@ -84,7 +84,7 @@
"type": "AccountId"
}
],
"type": "(u32, AccountId)"
"type": "(u32,AccountId)"
},
{
"displayName": "AccountId",
@@ -409,7 +409,7 @@
"type": "{\"_enum\":{\"Confirmed\":\"()\",\"ConfirmationsNeeded\":\"u32\"}}"
}
],
"type": "(u32, ConfirmationStatus)"
"type": "(u32,ConfirmationStatus)"
},
{
"displayName": "ConfirmationStatus",
@@ -511,4 +511,4 @@
"info": 5,
"type": "Option<Vec<u8>>"
}
]
]
@@ -158,12 +158,12 @@
"type": "AccountId"
}
],
"type": "(AccountId, AccountId)",
"type": "(AccountId,AccountId)",
"name": "Occupied"
}
],
"info": 3,
"type": "{\"_enum\":{\"Vacant\":\"VacantEntry\",\"Occupied\":\"(AccountId, AccountId)\"}}"
"type": "{\"_enum\":{\"Vacant\":\"VacantEntry\",\"Occupied\":\"(AccountId,AccountId)\"}}"
},
{
"info": 10,
@@ -181,7 +181,7 @@
"type": "AccountId"
}
],
"type": "(AccountId, AccountId)"
"type": "(AccountId,AccountId)"
},
{
"info": 6,
@@ -202,4 +202,4 @@
"info": 5,
"type": "Option<AccountId>"
}
]
]
@@ -2,12 +2,15 @@
// SPDX-License-Identifier: Apache-2.0
import { encodeTypeDef } from './encodeTypes';
import { TypeRegistry } from './registry';
import { TypeDefInfo } from './types';
describe('encodeTypeDef', (): void => {
const registry = new TypeRegistry();
it('correctly encodes a complex struct', (): void => {
expect(
JSON.parse(encodeTypeDef({
JSON.parse(encodeTypeDef(registry, {
info: TypeDefInfo.Struct,
sub: [
{
@@ -47,7 +50,7 @@ describe('encodeTypeDef', (): void => {
it('correctly encodes a complex struct (named)', (): void => {
expect(
JSON.parse(encodeTypeDef({
JSON.parse(encodeTypeDef(registry, {
info: TypeDefInfo.Struct,
sub: [
{
@@ -88,7 +91,7 @@ describe('encodeTypeDef', (): void => {
it('correctly encodes a complex enum', (): void => {
expect(
JSON.parse(encodeTypeDef({
JSON.parse(encodeTypeDef(registry, {
info: TypeDefInfo.Enum,
sub: [
{
@@ -148,7 +151,7 @@ describe('encodeTypeDef', (): void => {
it('correctly encodes a complex enum (named)', (): void => {
expect(
JSON.parse(encodeTypeDef({
JSON.parse(encodeTypeDef(registry, {
info: TypeDefInfo.Enum,
sub: [
{
+66 -40
View File
@@ -20,7 +20,7 @@ export function paramsNotation <T> (outer: string, inner?: T | T[], transform: (
}`;
}
function encodeWithParams (typeDef: TypeDef, outer: string): string {
function encodeWithParams (registry: Registry, typeDef: TypeDef, lookupCounter: number, outer: string): string {
const { info, sub } = typeDef;
switch (info) {
@@ -32,24 +32,25 @@ function encodeWithParams (typeDef: TypeDef, outer: string): string {
case TypeDefInfo.Option:
case TypeDefInfo.Result:
case TypeDefInfo.Vec:
return paramsNotation(outer, sub, (param) => encodeTypeDef(param));
return paramsNotation(outer, sub, (param) => encodeTypeDef(registry, param, lookupCounter));
}
throw new Error(`Unable to encode ${stringify(typeDef)} with params`);
}
function encodeDoNotConstruct ({ displayName }: TypeDef): string {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function encodeDoNotConstruct (registry: Registry, { displayName }: TypeDef, lookupCounter: number): string {
return `DoNotConstruct<${displayName || 'Unknown'}>`;
}
function encodeSubTypes (sub: TypeDef[], asEnum?: boolean): string {
function encodeSubTypes (registry: Registry, sub: TypeDef[], lookupCounter: number, asEnum?: boolean): string {
const names = sub.map(({ name }) => name);
assert(names.every((n) => !!n), () => `Subtypes does not have consistent names, ${names.join(', ')}`);
const inner = sub.reduce< Record<string, string>>((result, type) => ({
...result,
[type.name as string]: encodeTypeDef(type)
[type.name as string]: encodeTypeDef(registry, type, lookupCounter)
}), {});
return stringify(
@@ -59,7 +60,7 @@ function encodeSubTypes (sub: TypeDef[], asEnum?: boolean): string {
);
}
function encodeEnum (typeDef: TypeDef): string {
function encodeEnum (registry: Registry, typeDef: TypeDef, lookupCounter: number): string {
assert(typeDef.sub && Array.isArray(typeDef.sub), 'Unable to encode Enum type');
const sub = typeDef.sub;
@@ -68,28 +69,29 @@ function encodeEnum (typeDef: TypeDef): string {
// TODO We need to take the disciminant into account and auto-add empty entries
return sub.every(({ type }) => type === 'Null')
? stringify({ _enum: sub.map(({ name }, index) => `${name || `Empty${index}`}`) })
: encodeSubTypes(sub, true);
: encodeSubTypes(registry, sub, lookupCounter, true);
}
function encodeStruct (typeDef: TypeDef): string {
function encodeStruct (registry: Registry, typeDef: TypeDef, lookupCounter: number): string {
assert(typeDef.sub && Array.isArray(typeDef.sub), 'Unable to encode Struct type');
return encodeSubTypes(typeDef.sub);
return encodeSubTypes(registry, typeDef.sub, lookupCounter);
}
function encodeTuple (typeDef: TypeDef): string {
function encodeTuple (registry: Registry, typeDef: TypeDef, lookupCounter: number): string {
assert(typeDef.sub && Array.isArray(typeDef.sub), 'Unable to encode Tuple type');
return `(${typeDef.sub.map((type) => encodeTypeDef(type)).join(', ')})`;
return `(${typeDef.sub.map((type) => encodeTypeDef(registry, type, lookupCounter)).join(',')})`;
}
function encodeUInt ({ length }: TypeDef, type: 'Int' | 'UInt'): string {
function encodeUInt (registry: Registry, { length }: TypeDef, lookupCounter: number, type: 'Int' | 'UInt'): string {
assert(isNumber(length), 'Unable to encode VecFixed type');
return `${type}<${length}>`;
}
function encodeVecFixed ({ length, sub }: TypeDef): string {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function encodeVecFixed (registry: Registry, { length, sub }: TypeDef, lookupCounter: number): string {
assert(isNumber(length) && !isUndefined(sub) && !Array.isArray(sub), 'Unable to encode VecFixed type');
return `[${sub.type};${length}]`;
@@ -97,38 +99,62 @@ function encodeVecFixed ({ length, sub }: TypeDef): string {
// We setup a record here to ensure we have comprehensive coverage (any item not covered will result
// in a compile-time error with the missing index)
const encoders: Record<TypeDefInfo, (typeDef: TypeDef) => string> = {
[TypeDefInfo.BTreeMap]: (typeDef: TypeDef): string => encodeWithParams(typeDef, 'BTreeMap'),
[TypeDefInfo.BTreeSet]: (typeDef: TypeDef): string => encodeWithParams(typeDef, 'BTreeSet'),
[TypeDefInfo.Compact]: (typeDef: TypeDef): string => encodeWithParams(typeDef, 'Compact'),
[TypeDefInfo.DoNotConstruct]: (typeDef: TypeDef): string => encodeDoNotConstruct(typeDef),
[TypeDefInfo.Enum]: (typeDef: TypeDef): string => encodeEnum(typeDef),
[TypeDefInfo.HashMap]: (typeDef: TypeDef): string => encodeWithParams(typeDef, 'HashMap'),
[TypeDefInfo.Int]: (typeDef: TypeDef): string => encodeUInt(typeDef, 'Int'),
[TypeDefInfo.Linkage]: (typeDef: TypeDef): string => encodeWithParams(typeDef, 'Linkage'),
const encoders: Record<TypeDefInfo, (registry: Registry, typeDef: TypeDef, lookupCounter: number) => string> = {
[TypeDefInfo.BTreeMap]: (registry: Registry, typeDef: TypeDef, lookupCounter: number) =>
encodeWithParams(registry, typeDef, lookupCounter, 'BTreeMap'),
[TypeDefInfo.BTreeSet]: (registry: Registry, typeDef: TypeDef, lookupCounter: number) =>
encodeWithParams(registry, typeDef, lookupCounter, 'BTreeSet'),
[TypeDefInfo.Compact]: (registry: Registry, typeDef: TypeDef, lookupCounter: number) =>
encodeWithParams(registry, typeDef, lookupCounter, 'Compact'),
[TypeDefInfo.DoNotConstruct]: (registry: Registry, typeDef: TypeDef, lookupCounter: number) =>
encodeDoNotConstruct(registry, typeDef, lookupCounter),
[TypeDefInfo.Enum]: (registry: Registry, typeDef: TypeDef, lookupCounter: number) =>
encodeEnum(registry, typeDef, lookupCounter),
[TypeDefInfo.HashMap]: (registry: Registry, typeDef: TypeDef, lookupCounter: number) =>
encodeWithParams(registry, typeDef, lookupCounter, 'HashMap'),
[TypeDefInfo.Int]: (registry: Registry, typeDef: TypeDef, lookupCounter: number) =>
encodeUInt(registry, typeDef, lookupCounter, 'Int'),
[TypeDefInfo.Linkage]: (registry: Registry, typeDef: TypeDef, lookupCounter: number) =>
encodeWithParams(registry, typeDef, lookupCounter, 'Linkage'),
// eslint-disable-next-line @typescript-eslint/no-unused-vars
[TypeDefInfo.Null]: (typeDef: TypeDef): string => 'Null',
[TypeDefInfo.Option]: (typeDef: TypeDef): string => encodeWithParams(typeDef, 'Option'),
[TypeDefInfo.Plain]: (typeDef: TypeDef): string => typeDef.displayName || typeDef.type,
[TypeDefInfo.Result]: (typeDef: TypeDef): string => encodeWithParams(typeDef, 'Result'),
[TypeDefInfo.Set]: (typeDef: TypeDef): string => typeDef.type,
[TypeDefInfo.Si]: (typeDef: TypeDef): string => typeDef.type,
[TypeDefInfo.Struct]: (typeDef: TypeDef): string => encodeStruct(typeDef),
[TypeDefInfo.Tuple]: (typeDef: TypeDef): string => encodeTuple(typeDef),
[TypeDefInfo.UInt]: (typeDef: TypeDef): string => encodeUInt(typeDef, 'UInt'),
[TypeDefInfo.Vec]: (typeDef: TypeDef): string => encodeWithParams(typeDef, 'Vec'),
[TypeDefInfo.VecFixed]: (typeDef: TypeDef): string => encodeVecFixed(typeDef)
[TypeDefInfo.Null]: (registry: Registry, typeDef: TypeDef, lookupCounter: number) =>
'Null',
[TypeDefInfo.Option]: (registry: Registry, typeDef: TypeDef, lookupCounter: number) =>
encodeWithParams(registry, typeDef, lookupCounter, 'Option'),
// eslint-disable-next-line @typescript-eslint/no-unused-vars
[TypeDefInfo.Plain]: (registry: Registry, typeDef: TypeDef, lookupCounter: number) =>
typeDef.displayName || typeDef.type,
[TypeDefInfo.Result]: (registry: Registry, typeDef: TypeDef, lookupCounter: number) =>
encodeWithParams(registry, typeDef, lookupCounter, 'Result'),
// eslint-disable-next-line @typescript-eslint/no-unused-vars
[TypeDefInfo.Set]: (registry: Registry, typeDef: TypeDef, lookupCounter: number) =>
typeDef.type,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
[TypeDefInfo.Si]: (registry: Registry, typeDef: TypeDef, lookupCounter: number) =>
typeDef.lookupName || typeDef.type,
[TypeDefInfo.Struct]: (registry: Registry, typeDef: TypeDef, lookupCounter: number) =>
encodeStruct(registry, typeDef, lookupCounter),
[TypeDefInfo.Tuple]: (registry: Registry, typeDef: TypeDef, lookupCounter: number) =>
encodeTuple(registry, typeDef, lookupCounter),
[TypeDefInfo.UInt]: (registry: Registry, typeDef: TypeDef, lookupCounter: number) =>
encodeUInt(registry, typeDef, lookupCounter, 'UInt'),
[TypeDefInfo.Vec]: (registry: Registry, typeDef: TypeDef, lookupCounter: number) =>
encodeWithParams(registry, typeDef, lookupCounter, 'Vec'),
[TypeDefInfo.VecFixed]: (registry: Registry, typeDef: TypeDef, lookupCounter: number) =>
encodeVecFixed(registry, typeDef, lookupCounter)
};
function encodeType (typeDef: TypeDef): string {
function encodeType (registry: Registry, typeDef: TypeDef, lookupCounter: number): string {
const encoder = encoders[typeDef.info];
assert(encoder, () => `Cannot encode type: info=${typeDef.info}, typeDef=${stringify(typeDef)}`);
assert(encoder, () => `Cannot encode type ${stringify(typeDef)}`);
return encoder(typeDef);
return lookupCounter < 0 && isNumber(typeDef.lookupIndex)
? typeDef.lookupName || registry.createLookupType(typeDef.lookupIndex)
: encoder(registry, typeDef, lookupCounter--);
}
export function encodeTypeDef (typeDef: TypeDef): string {
export function encodeTypeDef (registry: Registry, typeDef: TypeDef, lookupCounter = 0): string {
assert(!isUndefined(typeDef.info), () => `Invalid type definition with no instance info, typeDef=${stringify(typeDef)}`);
// In the case of contracts we do have the unfortunate situation where the displayName would
@@ -138,12 +164,12 @@ export function encodeTypeDef (typeDef: TypeDef): string {
return typeDef.displayName;
}
return encodeType(typeDef);
return encodeType(registry, typeDef, lookupCounter);
}
export function withTypeString (_: Registry, typeDef: Omit<TypeDef, 'type'>): TypeDef {
export function withTypeString (registry: Registry, typeDef: Omit<TypeDef, 'type'>, lookupCounter = 0): TypeDef {
return {
...typeDef,
type: encodeType(typeDef as TypeDef)
type: encodeType(registry, typeDef as TypeDef, lookupCounter)
};
}
+3 -1
View File
@@ -36,9 +36,11 @@ export interface TypeDef {
displayName?: string;
fallbackType?: string;
info: TypeDefInfo;
isFromSi?: boolean;
index?: number;
isFromSi?: boolean;
length?: number;
lookupIndex?: number;
lookupName?: string;
name?: string;
namespace?: string;
sub?: TypeDef | TypeDef[];
+59 -10
View File
@@ -7,7 +7,7 @@ import type { SiField, SiLookupTypeId, SiType, SiTypeDefArray, SiTypeDefCompact,
import type { Type } from '../primitive/Type';
import type { Registry, TypeDef } from '../types';
import { assert, isNumber, isString, stringCamelCase, stringify } from '@polkadot/util';
import { assert, isNumber, isString, stringCamelCase, stringify, stringUpperFirst } from '@polkadot/util';
import { Struct } from '../codec/Struct';
import { withTypeString } from '../create/encodeTypes';
@@ -30,13 +30,52 @@ const PRIMITIVE_SP = [
'pallet_identity::types::Data'
];
function removeDuplicateNames (names: (string | null)[]): (string | null)[] {
return names.map((name, index): string | null =>
!name || names.some((o, i) => index !== i && name === o)
? null
: name
);
}
function extractNames (types: PortableType[]): Record<number, string> {
return removeDuplicateNames(
types.map(({ type: { path } }): string | null => {
if (!path.length) {
return null;
}
const parts = path.map((p) => stringUpperFirst(stringCamelCase(p)));
if (parts.length >= 2 && parts[parts.length - 1] === parts[parts.length - 2]) {
parts.pop();
}
const typeName = parts.join('');
return ['BTreeMap', 'Cow', 'Result', 'Option'].includes(typeName) || ['frame_support'].includes(path[0].toString())
? null
: typeName;
})
).reduce<Record<number, string>>((all, name, index) => {
if (name) {
all[index] = name;
}
return all;
}, {});
}
export class GenericPortableRegistry extends Struct {
#names: Record<number, string>;
#typeDefs: Record<number, TypeDef> = {};
constructor (registry: Registry, value?: Uint8Array) {
super(registry, {
types: 'Vec<PortableType>'
}, value);
this.#names = extractNames(this.types);
}
/**
@@ -61,34 +100,39 @@ export class GenericPortableRegistry extends Struct {
* @description Lookup the type definition for the index
*/
public getTypeDef (lookupId: SiLookupTypeId | string | number): TypeDef {
const index = this.#getLookupId(lookupId);
const lookupIndex = this.#getLookupId(lookupId);
if (!this.#typeDefs[index]) {
if (!this.#typeDefs[lookupIndex]) {
// we set first since we will get into circular lookups along the way
this.#typeDefs[index] = {
this.#typeDefs[lookupIndex] = {
info: TypeDefInfo.DoNotConstruct,
type: this.registry.createLookupType(index)
lookupIndex,
lookupName: this.#names[lookupIndex],
type: this.registry.createLookupType(lookupIndex)
};
const typeDef = this.#extract(this.getSiType(lookupId), index);
const extracted = this.#extract(this.getSiType(lookupId), lookupIndex);
Object.keys(typeDef).forEach((key): void => {
Object.keys(extracted).forEach((key): void => {
// these are safe since we are looking through the keys as set
this.#typeDefs[index][key as 'info'] = typeDef[key as 'info'];
this.#typeDefs[lookupIndex][key as 'info'] = extracted[key as 'info'];
});
}
return this.#typeDefs[index];
return this.#typeDefs[lookupIndex];
}
#createSiDef (lookupId: SiLookupTypeId): TypeDef {
const typeDef = this.getTypeDef(lookupId);
const lookupIndex = lookupId.toNumber();
// Flatten Plain types immediately, otherwise setup for a lookup
return typeDef.info === TypeDefInfo.Plain
? typeDef
: {
info: TypeDefInfo.Si,
lookupIndex,
lookupName: this.#names[lookupIndex],
type: this.registry.createLookupType(lookupId)
};
}
@@ -254,7 +298,12 @@ export class GenericPortableRegistry extends Struct {
}
#extractTuple (_: number, ids: SiTypeDefTuple): TypeDef {
if (ids.length === 1) {
if (ids.length === 0) {
return {
info: TypeDefInfo.Null,
type: 'Null'
};
} else if (ids.length === 1) {
return this.getTypeDef(ids[0]);
}