Skip to content

Commit

Permalink
Merge branch 'ns/feat/abi-parser' into ns/feat/abi-typegen
Browse files Browse the repository at this point in the history
  • Loading branch information
nedsalk committed Dec 17, 2024
2 parents 05a613a + 3de5f80 commit e354ddc
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 191 deletions.
94 changes: 94 additions & 0 deletions packages/abi/src/parser/specifications/v1/abi-type-mappers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/* eslint-disable @typescript-eslint/no-use-before-define */
import type {
AbiConcreteType,
AbiMetadataType,
AbiTypeArgument,
AbiTypeComponent,
} from '../../abi';

import type { ResolvableComponent, ResolvableType } from './resolvable-type';
import type { ResolvedType } from './resolved-type';

function mapMetadata(type: ResolvableType | ResolvedType) {
const result: AbiTypeComponent['type']['metadata'] = {
metadataTypeId: type.metadataType?.metadataTypeId as number,
};

if (type.typeParamsArgsMap && type.metadataType?.typeParameters?.length) {
result.typeArguments = type.typeParamsArgsMap.map((t) => toTypeArgument(t[1]));
}

return result;
}

function isResolvedType(type: ResolvableType | ResolvedType): type is ResolvedType {
return 'typeId' in type;
}

function isResolvedConcreteType(
type: ResolvableType | ResolvedType
): type is ResolvedType & { typeId: string } {
return isResolvedType(type) && typeof type.typeId === 'string';
}

function mapComponentType(component: ResolvableComponent): AbiTypeComponent {
const { name, type } = component;

let result: AbiTypeComponent['type'];

if (isResolvedConcreteType(type)) {
result = {
swayType: type.swayType,
concreteTypeId: type.typeId,
};
if (type.metadataType) {
result.metadata = mapMetadata(type) as AbiConcreteType['metadata'];
}
} else {
result = {
swayType: type.swayType,
metadata: mapMetadata(type),
};
}

if (type.components) {
result.components = type.components.map(mapComponentType);
}

return { name, type: result };
}

function toTypeArgument(type: ResolvableType | ResolvedType): AbiTypeArgument {
// type args and components follow the same mapping logic
return mapComponentType({ name: '', type }).type;
}

export function toAbiType(t: ResolvableType | ResolvedType): AbiConcreteType | AbiMetadataType {
let result: AbiConcreteType | AbiMetadataType;

if (isResolvedConcreteType(t)) {
result = {
concreteTypeId: t.typeId,
swayType: t.swayType,
};

if (t.metadataType) {
result.metadata = mapMetadata(t) as AbiConcreteType['metadata'];
}
} else {
result = {
swayType: t.swayType,
metadataTypeId: t.metadataType?.metadataTypeId as number,
};

if (t.typeParamsArgsMap) {
result.typeParameters = t.typeParamsArgsMap.map(([, rt]) => toAbiType(rt) as AbiMetadataType);
}
}

if (t.components) {
result.components = t.components.map(mapComponentType);
}

return result;
}
3 changes: 1 addition & 2 deletions packages/abi/src/parser/specifications/v1/cleanup-abi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ export function cleanupAbi(abi: AbiSpecificationV1): AbiSpecificationV1 {
* but we only care about the `buf`'s first type argument
* which defines the type of the vector data.
* Everything else is being ignored,
* as it's then easier to reason about the vector
* (you just treat is as a regular struct).
* as it's then easier to reason about the vector.
*/
case 'struct std::vec::Vec':
return {
Expand Down
15 changes: 11 additions & 4 deletions packages/abi/src/parser/specifications/v1/parser.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Abi, AbiConcreteType } from '../../abi';
import type { Abi, AbiConcreteType, AbiMetadataType } from '../../abi';

import { toAbiType } from './abi-type-mappers';
import { cleanupAbi } from './cleanup-abi';
import { mapAttribute } from './map-attribute';
import { ResolvableType } from './resolvable-type';
Expand All @@ -16,8 +17,14 @@ import type {
export class AbiParserV1 {
static parse(abi: AbiSpecificationV1): Abi {
const cleanAbi = cleanupAbi(abi);

const abiTypeMaps = {
metadataTypes: new Map(cleanAbi.metadataTypes.map((type) => [type.metadataTypeId, type])),
concreteTypes: new Map(cleanAbi.concreteTypes.map((type) => [type.concreteTypeId, type])),
};

const resolvableTypes = cleanAbi.metadataTypes.map(
(metadataType) => new ResolvableType(cleanAbi, metadataType.metadataTypeId, undefined)
(metadataType) => new ResolvableType(abiTypeMaps, metadataType.metadataTypeId, undefined)
);

const concreteTypes = cleanAbi.concreteTypes.map((concreteType) => {
Expand All @@ -29,15 +36,15 @@ export class AbiParserV1 {
? resolvableType.resolve(concreteType)
: new ResolvedType({ swayType: concreteType.type, typeId: concreteType.concreteTypeId });

return resolvedType.toAbiType();
return toAbiType(resolvedType) as AbiConcreteType;
});

const getType = (concreteTypeId: string) =>
// this will always be defined because it's in the context of the same ABI
concreteTypes.find((abiType) => abiType.concreteTypeId === concreteTypeId) as AbiConcreteType;

return {
metadataTypes: resolvableTypes.map((rt) => rt.toAbiType()),
metadataTypes: resolvableTypes.map((rt) => toAbiType(rt) as AbiMetadataType),
concreteTypes,
encodingVersion: cleanAbi.encodingVersion,
programType: cleanAbi.programType as Abi['programType'],
Expand Down
121 changes: 28 additions & 93 deletions packages/abi/src/parser/specifications/v1/resolvable-type.ts
Original file line number Diff line number Diff line change
@@ -1,106 +1,44 @@
import { FuelError } from '@fuel-ts/errors';

import { swayTypeMatchers } from '../../../matchers/sway-type-matchers';
import type { AbiTypeComponent, AbiMetadataType, AbiTypeArgument } from '../../abi';

import type { ResolvedComponent } from './resolved-type';
import { ResolvedType } from './resolved-type';
import type {
AbiComponentV1,
AbiConcreteTypeV1,
AbiMetadataTypeV1,
AbiSpecificationV1,
AbiTypeArgumentV1,
} from './specification';

interface ResolvableComponent {
export interface ResolvableComponent {
name: string;
type: ResolvableType | ResolvedType;
}

export class ResolvableType {
private metadataType: AbiMetadataTypeV1;
metadataType: AbiMetadataTypeV1;
swayType: string;
components: ResolvableComponent[] | undefined;

constructor(
private abi: AbiSpecificationV1,
private abiTypeMaps: {
metadataTypes: Map<number, AbiMetadataTypeV1>;
concreteTypes: Map<string, AbiConcreteTypeV1>;
},
public metadataTypeId: number,
public typeParamsArgsMap: Array<[number, ResolvedType | ResolvableType]> | undefined
) {
this.metadataType = this.findMetadataType(metadataTypeId);
this.swayType = this.metadataType.type;
this.typeParamsArgsMap ??= this.metadataType.typeParameters?.map((tp) => [
tp,
new ResolvableType(this.abi, tp, undefined),
new ResolvableType(this.abiTypeMaps, tp, undefined),
]);

this.components = this.metadataType.components?.map((c) => this.handleComponent(this, c));
}

toComponentType(): AbiTypeComponent['type'] {
const result: AbiTypeComponent['type'] = {
swayType: this.swayType,
metadata: {
metadataTypeId: this.metadataTypeId,
},
};

if (this.components) {
result.components = this.components.map((component) => ({
name: component.name,
type: component.type.toComponentType(),
}));
}
if (this.typeParamsArgsMap) {
result.metadata.typeArguments = this.typeParamsArgsMap.map(([, rt]) => rt.toTypeArgument());
}

return result;
}

toTypeArgument(): AbiTypeArgument {
const result: AbiTypeArgument = {
swayType: this.swayType,
metadata: {
metadataTypeId: this.metadataTypeId,
},
};

if (this.typeParamsArgsMap) {
result.metadata.typeArguments = this.typeParamsArgsMap.map(([, ta]) => ta.toTypeArgument());
}

if (this.components) {
result.components = this.components.map((component) => ({
name: component.name,
type: component.type.toComponentType(),
}));
}

return result;
}

toAbiType(): AbiMetadataType {
const result: AbiMetadataType = {
metadataTypeId: this.metadataTypeId,
swayType: this.swayType,
};

if (this.components) {
result.components = this.components?.map((component) => ({
name: component.name,
type: component.type.toComponentType(),
})) as AbiTypeComponent[];
}

if (this.typeParamsArgsMap) {
result.typeParameters = this.typeParamsArgsMap.map(
([, rt]) => rt.toAbiType() as AbiMetadataType
);
}

return result;
this.components = this.metadataType.components?.map((c) =>
this.createResolvableComponent(this, c)
);
}

/**
Expand All @@ -111,9 +49,8 @@ export class ResolvableType {
* @throws If the metadata type can not be found in the ABI.
*/
private findMetadataType(metadataTypeId: number): AbiMetadataTypeV1 {
const metadataType = this.abi.metadataTypes.find(
(type) => type.metadataTypeId === metadataTypeId
);
const metadataType = this.abiTypeMaps.metadataTypes.get(metadataTypeId);

if (!metadataType) {
throw new FuelError(
FuelError.CODES.TYPE_NOT_FOUND,
Expand All @@ -131,9 +68,8 @@ export class ResolvableType {
* @throws If the concrete type can not be found in the ABI.
*/
private findConcreteType(concreteTypeId: string): AbiConcreteTypeV1 {
const concreteType = this.abi.concreteTypes.find(
(type) => type.concreteTypeId === concreteTypeId
);
const concreteType = this.abiTypeMaps.concreteTypes.get(concreteTypeId);

if (!concreteType) {
throw new FuelError(
FuelError.CODES.TYPE_NOT_FOUND,
Expand All @@ -150,26 +86,24 @@ export class ResolvableType {
return metadataType.typeParameters?.map((typeParameter, idx) => [typeParameter, args[idx]]);
}

private handleComponent(
private createResolvableComponent(
parent: ResolvableType,
component: AbiComponentV1 | AbiTypeArgumentV1
{ typeId, typeArguments, name }: AbiComponentV1 | AbiTypeArgumentV1
): ResolvableComponent {
const name = (component as AbiComponentV1).name;

const isConcreteType = typeof component.typeId === 'string';
const isConcreteType = typeof typeId === 'string';

if (isConcreteType) {
const concreteType = this.findConcreteType(component.typeId);
const concreteType = this.findConcreteType(typeId);
return {
name,
type: this.resolveConcreteType(concreteType),
};
}

const metadataType = this.findMetadataType(component.typeId);
const metadataType = this.findMetadataType(typeId);
return {
name,
type: this.handleMetadataType(parent, metadataType, component.typeArguments),
type: this.handleMetadataType(parent, metadataType, typeArguments),
};
}

Expand All @@ -194,7 +128,7 @@ export class ResolvableType {
* This would be the case for e.g. non-generic structs and enums.
*/
if (!type.typeArguments) {
return new ResolvableType(this.abi, type.metadataTypeId, undefined).resolveInternal(
return new ResolvableType(this.abiTypeMaps, type.metadataTypeId, undefined).resolveInternal(
type.concreteTypeId,
undefined
);
Expand All @@ -213,7 +147,7 @@ export class ResolvableType {
});

return new ResolvableType(
this.abi,
this.abiTypeMaps,
type.metadataTypeId,
ResolvableType.mapTypeParametersAndArgs(metadataType, concreteTypeArgs)
).resolveInternal(type.concreteTypeId, undefined);
Expand Down Expand Up @@ -248,7 +182,7 @@ export class ResolvableType {

return (
resolvableTypeParameter ??
new ResolvableType(this.abi, metadataType.metadataTypeId, undefined)
new ResolvableType(this.abiTypeMaps, metadataType.metadataTypeId, undefined)
);
}

Expand All @@ -258,18 +192,19 @@ export class ResolvableType {
* if they aren't used _directly_ in a function-input/function-output/log/configurable/messageType
* These types are characterized by not having components and we can resolve them as-is
*/
return new ResolvableType(this.abi, metadataType.metadataTypeId, undefined).resolveInternal(
return new ResolvableType(
this.abiTypeMaps,
metadataType.metadataTypeId,
undefined
);
).resolveInternal(metadataType.metadataTypeId, undefined);
}

const typeArgs = typeArguments?.map(
(typeArgument) => this.handleComponent(parent, typeArgument).type
(typeArgument) => this.createResolvableComponent(parent, typeArgument).type
);

const resolvable = new ResolvableType(
this.abi,
this.abiTypeMaps,
metadataType.metadataTypeId,
!typeArgs?.length
? undefined
Expand Down
Loading

0 comments on commit e354ddc

Please sign in to comment.