diff --git a/common/changes/@subsquid/scale-type-system/master_2023-09-28-18-40.json b/common/changes/@subsquid/scale-type-system/master_2023-09-28-18-40.json new file mode 100644 index 000000000..4fd35dba4 --- /dev/null +++ b/common/changes/@subsquid/scale-type-system/master_2023-09-28-18-40.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@subsquid/scale-type-system", + "comment": "improve handling of `Result` and `Option` types", + "type": "minor" + } + ], + "packageName": "@subsquid/scale-type-system" +} \ No newline at end of file diff --git a/common/changes/@subsquid/substrate-typegen/master_2023-09-28-18-40.json b/common/changes/@subsquid/substrate-typegen/master_2023-09-28-18-40.json new file mode 100644 index 000000000..9c6b336dd --- /dev/null +++ b/common/changes/@subsquid/substrate-typegen/master_2023-09-28-18-40.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@subsquid/substrate-typegen", + "comment": "improve handling of `Result` and `Option` types", + "type": "patch" + } + ], + "packageName": "@subsquid/substrate-typegen" +} \ No newline at end of file diff --git a/substrate/scale-type-system/src/dsl.ts b/substrate/scale-type-system/src/dsl.ts index f1150c659..cf542dbc0 100644 --- a/substrate/scale-type-system/src/dsl.ts +++ b/substrate/scale-type-system/src/dsl.ts @@ -1,6 +1,6 @@ import {BitSequence, Bytes} from '@subsquid/scale-codec' import {Type} from './type-checker' -import {GetType, ValueCase} from './type-util' +import {GetType, Simplify, ValueCase} from './type-util' import {ArrayType} from './types/array' import {EnumDefinition, EnumStruct, EnumType, GetEnumType} from './types/enum' import {ExternalEnum, ExternalEnumType} from './types/externalEnum' @@ -17,16 +17,12 @@ import { UnitType, UnknownType } from './types/primitives' -import {Result, ResultType} from './types/result' import {GetStructType, StructType} from './types/struct' import {TupleType} from './types/tuple' import {UnionType} from './types/union' -export {GetType, ExternalEnum, Result, ValueCase} - - -export type Option = ValueCase<'Some', T> | {__kind: 'None'} +export {GetType, ExternalEnum, ValueCase} const numberType = new NumberType() @@ -121,7 +117,7 @@ export function struct>(def: Get): Type(type: Get>): Type { +export function option(type: Get): Type | undefined> { return new OptionType(getter(type)) } @@ -154,8 +150,42 @@ export function externalEnum(variants?: Get): Type { } -export function result(ok: T, err: E): Type, GetType>> { - return new ResultType(ok, err) +export type Option = Simplify | {__kind: 'None'}> + + +export function enumOption>(some: Get): Type>> { + return makeEnumOption>(some) +} + + +function makeEnumOption(some: Get>): Type> { + let getType = getter(some) + return closedEnum(() => { + return { + Some: getType(), + None: unit() + } + }) +} + + +export type Result = Simplify | ValueCase<'Err', E>> + + +export function result, E extends Type>(ok: Get, err: Get): Type, GetType>> { + return makeResult, GetType>(ok, err) +} + + +function makeResult(ok: Get>, err: Get>): Type> { + let getOk = getter(ok) + let getErr = getter(err) + return closedEnum(() => { + return { + Ok: getOk(), + Err: getErr() + } + }) } diff --git a/substrate/scale-type-system/src/types/option.ts b/substrate/scale-type-system/src/types/option.ts index ce999ee3e..ca10d48af 100644 --- a/substrate/scale-type-system/src/types/option.ts +++ b/substrate/scale-type-system/src/types/option.ts @@ -1,10 +1,11 @@ import {TypeKind} from '@subsquid/scale-codec' import {def} from '@subsquid/util-internal' import {BaseType, ScaleType, Type, TypeChecker} from '../type-checker' +import {GetType} from '../type-util' -export class OptionType extends BaseType { - constructor(private value: () => Type) { +export class OptionType extends BaseType | undefined> { + constructor(private _value: () => T) { super() } @@ -17,7 +18,7 @@ export class OptionType extends BaseType { } @def - private getValue(): Type { - return this.value() + private getValue(): T { + return this._value() } } diff --git a/substrate/scale-type-system/src/types/result.ts b/substrate/scale-type-system/src/types/result.ts deleted file mode 100644 index 0f506f8c5..000000000 --- a/substrate/scale-type-system/src/types/result.ts +++ /dev/null @@ -1,28 +0,0 @@ -import {TypeKind} from '@subsquid/scale-codec' -import {BaseType, ScaleType, Type, TypeChecker} from '../type-checker' -import {GetType, ValueCase} from '../type-util' - - -export type Result = ValueCase<'Ok', T> | ValueCase<'Err', E> - - -export class ResultType extends BaseType, GetType>> { - constructor(private ok: T, private err: E) { - super() - } - - match(typeChecker: TypeChecker, ty: ScaleType): boolean { - if (ty.kind != TypeKind.Variant) return false - if (ty.variants.length != 2) return false - let v0 = ty.variants[0] - let v1 = ty.variants[1] - return v0.name == 'Ok' - && v0.fields.length == 1 - && v0.fields[0].name == null - && v1.name == 'Err' - && v1.fields.length == 1 - && v1.fields[0].name == null - && typeChecker.match(v0.fields[0].type, this.ok) - && typeChecker.match(v1.fields[0].type, this.err) - } -} diff --git a/substrate/substrate-typegen/src/ifs.ts b/substrate/substrate-typegen/src/ifs.ts index d698d4295..41ba46eb4 100644 --- a/substrate/substrate-typegen/src/ifs.ts +++ b/substrate/substrate-typegen/src/ifs.ts @@ -135,11 +135,14 @@ export class Interfaces { case TypeKind.Variant: { let result = asResultType(ty) if (result) { - return `Result<${this.use(result.ok)}, ${this.use(result.err)}>` + let ok = result.ok == null ? 'null' : this.use(result.ok) + let err = result.err == null ? 'null' : this.use(result.err) + return `Result<${ok}, ${err}>` } let option = asOptionType(ty) if (option) { - return `Option<${this.use(option.some)}>` + let some = option.some == null ? 'null' : this.use(option.some) + return `Option<${some}>` } return this.makeVariant(ty, ti) } @@ -297,11 +300,14 @@ export class Sts { case TypeKind.Variant: { let result = asResultType(ty) if (result) { - return `sts.result(${this.use(result.ok)}, ${this.use(result.err)})` + let ok = result.ok == null ? 'sts.unit()' : this.use(result.ok) + let err = result.err == null ? 'sts.unit()' : this.use(result.err) + return `sts.result(() => ${ok}, () => ${err})` } let option = asOptionType(ty) if (option) { - return `sts.closedEnum({Some: ${this.use(option.some)}, None: sts.unit()})` + let some = option.some == null ? 'sts.unit()' : this.use(option.some) + return `sts.enumOption(() => ${some})` } return this.makeVariant(ty, ti) } diff --git a/substrate/substrate-typegen/src/util.ts b/substrate/substrate-typegen/src/util.ts index b36854c24..f8ac36934 100644 --- a/substrate/substrate-typegen/src/util.ts +++ b/substrate/substrate-typegen/src/util.ts @@ -1,4 +1,4 @@ -import {Primitive, Ti, Type, TypeKind} from '@subsquid/substrate-runtime/lib/metadata' +import {Primitive, Ti, Type, TypeKind, Variant} from '@subsquid/substrate-runtime/lib/metadata' import {unexpectedCase} from '@subsquid/util-internal' import {toCamelCase} from '@subsquid/util-naming' @@ -8,42 +8,44 @@ export function isEmptyVariant(type: Type): boolean { } -export function asResultType(type: Type): {ok: Ti, err: Ti} | undefined { - if (type.kind != TypeKind.Variant) return undefined - if (type.variants.length != 2) return undefined - let v0 = type.variants[0] - let v1 = type.variants[1] - let yes = v0.name == 'Ok' && - v0.index == 0 && - v0.fields.length == 1 && - v0.fields[0].name == null && - v1.name == 'Err' && - v1.index == 1 && - v1.fields.length == 1 && - v1.fields[0].name == null - return yes ? {ok: v0.fields[0].type, err: v1.fields[0].type} : undefined +export function asResultType(type: Type): {ok?: Ti, err?: Ti} | undefined { + if (type.kind != TypeKind.Variant) return + if (type.variants.length != 2) return + + let ok = type.variants.find(v => v.name == 'Ok') + if (ok == null) return + + let err = type.variants.find(v => v.name == 'Err') + if (err == null) return + + if (isValueVariant(ok) && isValueVariant(err)) return { + ok: ok.fields[0]?.type, + err: err.fields[0]?.type + } } -export function asOptionType(type: Type): {some: Ti} | undefined { +export function asOptionType(type: Type): {some?: Ti} | undefined { if (type.kind !== TypeKind.Variant) return if (type.variants.length != 2) return - let v0 = type.variants[0] - let v1 = type.variants[1] - let yes = v0.name == 'None' && - v0.fields.length == 0 && - v0.index == 0 && - v1.name == 'Some' && - v1.index == 1 && - v1.fields.length == 1 && - v1.fields[0].name == null - - if (yes) return { - some: v1.fields[0].type + + let some = type.variants.find(v => v.name == 'Some') + if (some == null) return + + let none = type.variants.find(v => v.name == 'None') + if (none == null) return + + if (isValueVariant(some) && none.fields.length == 0) return { + some: some.fields[0]?.type } } +function isValueVariant(v: Variant): boolean { + return v.fields.length < 2 && v.fields[0]?.name == null +} + + export function toNativePrimitive(primitive: Primitive): string { switch(primitive) { case "I8":