Skip to content

Commit

Permalink
improve handling of Result<T, E> and Option<T> types
Browse files Browse the repository at this point in the history
  • Loading branch information
eldargab committed Sep 28, 2023
1 parent 689ddbe commit 6c4aacc
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 73 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@subsquid/scale-type-system",
"comment": "improve handling of `Result<T, E>` and `Option<T>` types",
"type": "minor"
}
],
"packageName": "@subsquid/scale-type-system"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@subsquid/substrate-typegen",
"comment": "improve handling of `Result<T, E>` and `Option<T>` types",
"type": "patch"
}
],
"packageName": "@subsquid/substrate-typegen"
}
48 changes: 39 additions & 9 deletions substrate/scale-type-system/src/dsl.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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<T> = ValueCase<'Some', T> | {__kind: 'None'}
export {GetType, ExternalEnum, ValueCase}


const numberType = new NumberType()
Expand Down Expand Up @@ -121,7 +117,7 @@ export function struct<T extends Record<string, Type>>(def: Get<T>): Type<GetStr
}


export function option<T>(type: Get<Type<T>>): Type<T | undefined> {
export function option<T extends Type>(type: Get<T>): Type<GetType<T> | undefined> {
return new OptionType(getter(type))
}

Expand Down Expand Up @@ -154,8 +150,42 @@ export function externalEnum(variants?: Get<EnumDefinition>): Type<any> {
}


export function result<T extends Type, E extends Type>(ok: T, err: E): Type<Result<GetType<T>, GetType<E>>> {
return new ResultType(ok, err)
export type Option<T> = Simplify<ValueCase<'Some', T> | {__kind: 'None'}>


export function enumOption<T extends Type<any>>(some: Get<T>): Type<Option<GetType<T>>> {
return makeEnumOption<GetType<T>>(some)
}


function makeEnumOption<T>(some: Get<Type<T>>): Type<Option<T>> {
let getType = getter(some)
return closedEnum(() => {
return {
Some: getType(),
None: unit()
}
})
}


export type Result<T, E> = Simplify<ValueCase<'Ok', T> | ValueCase<'Err', E>>


export function result<T extends Type<any>, E extends Type<any>>(ok: Get<T>, err: Get<E>): Type<Result<GetType<T>, GetType<E>>> {
return makeResult<GetType<T>, GetType<E>>(ok, err)
}


function makeResult<T, E>(ok: Get<Type<T>>, err: Get<Type<E>>): Type<Result<T, E>> {
let getOk = getter(ok)
let getErr = getter(err)
return closedEnum(() => {
return {
Ok: getOk(),
Err: getErr()
}
})
}


Expand Down
9 changes: 5 additions & 4 deletions substrate/scale-type-system/src/types/option.ts
Original file line number Diff line number Diff line change
@@ -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<T> extends BaseType<T | undefined> {
constructor(private value: () => Type<T>) {
export class OptionType<T extends Type> extends BaseType<GetType<T> | undefined> {
constructor(private _value: () => T) {
super()
}

Expand All @@ -17,7 +18,7 @@ export class OptionType<T> extends BaseType<T | undefined> {
}

@def
private getValue(): Type<T> {
return this.value()
private getValue(): T {
return this._value()
}
}
28 changes: 0 additions & 28 deletions substrate/scale-type-system/src/types/result.ts

This file was deleted.

14 changes: 10 additions & 4 deletions substrate/substrate-typegen/src/ifs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down
58 changes: 30 additions & 28 deletions substrate/substrate-typegen/src/util.ts
Original file line number Diff line number Diff line change
@@ -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'

Expand All @@ -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":
Expand Down

0 comments on commit 6c4aacc

Please sign in to comment.