@ts-for-gir/lib
Version:
Typescript .d.ts generator from GIR for gjs
485 lines (397 loc) • 14.8 kB
text/typescript
import type { IntrospectedCallback } from "../gir/callback.ts";
import type { IntrospectedConstructor } from "../gir/constructor.ts";
import type { IntrospectedDirectAllocationConstructor } from "../gir/direct-allocation-constructor.ts";
import type { IntrospectedEnum } from "../gir/enum.ts";
import { IntrospectedFunction } from "../gir/function.ts";
import { createGenericNameGenerator } from "../gir/generics.ts";
import type { IntrospectedInterface, IntrospectedVirtualClassFunction } from "../gir/introspected-classes.ts";
import {
IntrospectedBaseClass,
IntrospectedClass,
IntrospectedClassFunction,
IntrospectedStaticClassFunction,
} from "../gir/introspected-classes.ts";
import type { IntrospectedFunctionParameter } from "../gir/parameter.ts";
import type { IntrospectedSignal } from "../gir/signal.ts";
import {
ClosureType,
Generic,
GenericType,
GenerifiedTypeIdentifier,
ThisType,
type TypeExpression,
TypeIdentifier,
} from "../gir.ts";
import { resolveTypeIdentifier } from "../utils/type-resolution.ts";
import { GirVisitor } from "../visitor.ts";
/**
* Visitor that handles generic type inference and transformations
*/
export class GenericVisitor extends GirVisitor {
private readonly inferGenerics: boolean;
constructor(inferGenerics: boolean) {
super();
this.inferGenerics = inferGenerics;
}
visitCallback = (node: IntrospectedCallback): IntrospectedCallback => {
if (!this.inferGenerics) {
return node;
}
const shouldGenerify = this.shouldGenerifyCallback(node);
if (!shouldGenerify) {
return node;
}
return this.createGenerifiedCallback(node);
};
visitClass = (node: IntrospectedClass): IntrospectedClass => {
return this.visitBaseClass(node);
};
visitInterface = (node: IntrospectedInterface): IntrospectedInterface => {
return this.visitBaseClass(node);
};
visitParameter = (node: IntrospectedFunctionParameter): IntrospectedFunctionParameter => {
if (!this.inferGenerics) {
return node;
}
const unwrapped = node.type.unwrap();
if (!(unwrapped instanceof ClosureType)) {
return node;
}
return this.processAsyncReadyCallback(node) ?? node;
};
visitFunction = (node: IntrospectedFunction): IntrospectedFunction => {
if (!this.inferGenerics) {
return node;
}
const unwrapped = node.return_type.unwrap();
const shouldGenerify = unwrapped instanceof TypeIdentifier && unwrapped.is("GObject", "Object");
if (!shouldGenerify) {
return node;
}
return this.createGenerifiedFunction(node, unwrapped);
};
visitStaticClassFunction = (node: IntrospectedStaticClassFunction): IntrospectedStaticClassFunction => {
if (this.inferGenerics) {
return this.generifyStandaloneClassFunction(node);
}
return node;
};
visitClassFunction = <T extends IntrospectedBaseClass | IntrospectedEnum>(
node: IntrospectedClassFunction<T>,
): IntrospectedClassFunction<T> => {
if (node.parent instanceof IntrospectedBaseClass) {
const processedNode = this.processClassFunctionWithGenerics(node);
if (processedNode) {
return processedNode;
}
}
if (this.inferGenerics) {
return this.generifyStandaloneClassFunction(node);
}
return node;
};
visitVirtualClassFunction = (node: IntrospectedVirtualClassFunction): IntrospectedVirtualClassFunction => {
return this.visitClassFunction(node);
};
// Private helper methods
private visitBaseClass<T extends IntrospectedBaseClass>(node: T): T {
const copiedNode = node.copy() as T;
const derivatives = copiedNode.generics.filter((generic) => generic.parent != null);
this.processInterfaces(copiedNode, derivatives);
this.processSuperType(copiedNode, derivatives);
return copiedNode;
}
private shouldGenerifyCallback(node: IntrospectedCallback): boolean {
return node.parameters.some((parameter) => this.isGObjectType(parameter.type.unwrap()));
}
private isGObjectType(type: TypeExpression): boolean {
return type instanceof TypeIdentifier && type.is("GObject", "Object");
}
private createGenerifiedCallback(node: IntrospectedCallback): IntrospectedCallback {
const generateName = createGenericNameGenerator();
const generics: Generic[] = [];
const generifiedParameters = node.parameters.map((parameter) => {
const type = parameter.type.unwrap();
if (this.isGObjectType(type)) {
const { identifier, generic } = this.createGeneric(generateName(), type as TypeIdentifier);
generics.push(generic);
return parameter.copy({
type: parameter.type.rewrap(identifier),
});
}
return parameter;
});
const generifiedCallback = node.copy({
parameters: generifiedParameters,
});
generifiedCallback.generics = generics;
return generifiedCallback;
}
private createGeneric(identifier: string, baseType: TypeIdentifier): { identifier: GenericType; generic: Generic } {
const genericType = new GenericType(identifier, baseType);
const generic = new Generic(genericType, baseType);
return { identifier: genericType, generic };
}
private processInterfaces<T extends IntrospectedBaseClass>(node: T, derivatives: Generic[]): void {
if (!(node instanceof IntrospectedClass)) {
return;
}
const resolvedInterfaces = this.resolveInterfaces(node);
node.interfaces = node.interfaces.map((iface) =>
this.processInterface(node, iface, derivatives, resolvedInterfaces),
);
}
private resolveInterfaces(node: IntrospectedClass): IntrospectedBaseClass[] {
return node.interfaces
.map((i) => resolveTypeIdentifier(node.namespace, i))
.filter((c): c is IntrospectedBaseClass => c != null);
}
private processInterface<T extends IntrospectedBaseClass>(
node: T,
iface: TypeIdentifier,
derivatives: Generic[],
resolvedInterfaces: IntrospectedBaseClass[],
): TypeIdentifier {
const matchingGenerics = derivatives.filter((d) => d.parent?.is(iface.namespace, iface.name));
if (matchingGenerics.length > 0) {
return new GenerifiedTypeIdentifier(
iface.name,
iface.namespace,
matchingGenerics.map((g) => g.type),
);
}
const resolved = resolvedInterfaces.find((i) => i.getType().equals(iface));
if (resolved) {
return this.processResolvedInterface(node, iface, resolved);
}
return iface;
}
private processResolvedInterface<T extends IntrospectedBaseClass>(
node: T,
iface: TypeIdentifier,
resolved: IntrospectedBaseClass,
): TypeIdentifier {
if (resolved.generics.length !== 1) {
return iface;
}
const [generic] = resolved.generics;
if (generic.propagate) {
return this.handlePropagatingInterfaceGeneric(node, iface, resolved, generic);
}
return new GenerifiedTypeIdentifier(iface.name, iface.namespace, [node.getType()]);
}
private handlePropagatingInterfaceGeneric<T extends IntrospectedBaseClass>(
node: T,
iface: TypeIdentifier,
resolved: IntrospectedBaseClass,
generic: Generic,
): TypeIdentifier {
const constrainedGeneric = this.findConstrainedGeneric(node, generic);
if (constrainedGeneric) {
return new GenerifiedTypeIdentifier(iface.name, iface.namespace, [constrainedGeneric.type]);
}
if (this.shouldAddNewGeneric(node, generic)) {
this.addGenericToNode(node, resolved, generic);
const firstGeneric = node.generics[node.generics.length - 1];
return new GenerifiedTypeIdentifier(resolved.name, resolved.namespace.namespace, [firstGeneric.type]);
}
return new GenerifiedTypeIdentifier(iface.name, iface.namespace, [node.getType()]);
}
private findConstrainedGeneric<T extends IntrospectedBaseClass>(node: T, generic: Generic): Generic | undefined {
return node.generics.find((d) => generic.constraint && d.constraint?.equals(generic.constraint));
}
private shouldAddNewGeneric<T extends IntrospectedBaseClass>(node: T, generic: Generic): boolean {
return !generic.defaultType?.equals(node.getType()) && !generic.constraint?.equals(node.getType());
}
private addGenericToNode<T extends IntrospectedBaseClass>(
node: T,
resolved: IntrospectedBaseClass,
generic: Generic,
): void {
node.addGeneric({
constraint: generic.constraint ?? undefined,
default: generic.defaultType ?? undefined,
deriveFrom: resolved.getType(),
});
}
private processSuperType<T extends IntrospectedBaseClass>(node: T, derivatives: Generic[]): void {
if (!node.superType) {
return;
}
const parentType = node.superType;
const matchingGenerics = derivatives.filter((d) => d.parent?.is(parentType.namespace, parentType.name));
if (node.superType instanceof GenerifiedTypeIdentifier) {
return;
}
if (matchingGenerics.length > 0) {
node.superType = new GenerifiedTypeIdentifier(
parentType.name,
parentType.namespace,
matchingGenerics.map((g) => g.type),
);
return;
}
const resolvedParent = resolveTypeIdentifier(node.namespace, node.superType);
this.processResolvedParent(node, resolvedParent);
}
private processResolvedParent<T extends IntrospectedBaseClass>(
node: T,
resolved: IntrospectedBaseClass | null,
): void {
if (!resolved || resolved.generics.length !== 1) {
return;
}
const [generic] = resolved.generics;
if (generic.propagate) {
this.handlePropagatingGeneric(node, resolved, generic);
} else if (this.shouldUseNodeType(node, generic)) {
node.superType = new GenerifiedTypeIdentifier(resolved.name, resolved.namespace.namespace, [node.getType()]);
}
}
private handlePropagatingGeneric<T extends IntrospectedBaseClass>(
node: T,
resolved: IntrospectedBaseClass,
generic: Generic,
): void {
const constrainedGeneric = this.findConstrainedGeneric(node, generic);
if (constrainedGeneric) {
node.superType = new GenerifiedTypeIdentifier(resolved.name, resolved.namespace.namespace, [
constrainedGeneric.type,
]);
return;
}
if (this.shouldAddNewGeneric(node, generic)) {
this.addGenericToNode(node, resolved, generic);
const firstGeneric = node.generics[node.generics.length - 1];
node.superType = new GenerifiedTypeIdentifier(resolved.name, resolved.namespace.namespace, [firstGeneric.type]);
} else if (this.shouldUseNodeType(node, generic)) {
node.superType = new GenerifiedTypeIdentifier(resolved.name, resolved.namespace.namespace, [node.getType()]);
}
}
private shouldUseNodeType<T extends IntrospectedBaseClass>(node: T, generic: Generic): boolean {
return [...node.resolveParents()].some((c) => generic.defaultType && c.identifier.equals(generic.defaultType));
}
private processAsyncReadyCallback(node: IntrospectedFunctionParameter): IntrospectedFunctionParameter | null {
const unwrapped = node.type.unwrap();
if (!(unwrapped instanceof ClosureType)) {
return null;
}
const internal = unwrapped.type.unwrap();
if (!(internal instanceof TypeIdentifier) || !internal.is("Gio", "AsyncReadyCallback")) {
return null;
}
const member = node.parent;
if (!member) {
return null;
}
const generifiedType = this.createAsyncReadyCallbackType(member, internal);
if (generifiedType) {
return node.copy({
type: node.type.rewrap(generifiedType),
});
}
return null;
}
private createAsyncReadyCallbackType(
member:
| IntrospectedFunction
| IntrospectedClassFunction
| IntrospectedSignal
| IntrospectedConstructor
| IntrospectedDirectAllocationConstructor,
internal: TypeIdentifier,
): GenerifiedTypeIdentifier | null {
if (member instanceof IntrospectedFunction && member.parameters.length >= 2) {
return new GenerifiedTypeIdentifier(internal.name, internal.namespace, [member.parameters[0].type]);
}
if (member instanceof IntrospectedStaticClassFunction) {
return new GenerifiedTypeIdentifier(internal.name, internal.namespace, [member.parent.getType()]);
}
if (member instanceof IntrospectedClassFunction) {
return new GenerifiedTypeIdentifier(internal.name, internal.namespace, [ThisType]);
}
return null;
}
private createGenerifiedFunction(node: IntrospectedFunction, unwrapped: TypeIdentifier): IntrospectedFunction {
const genericReturnType = new GenericType("T", unwrapped);
const copied = node.copy({
return_type: genericReturnType,
});
copied.generics.push(new Generic(genericReturnType, unwrapped));
return copied;
}
private generifyStandaloneClassFunction<T extends IntrospectedClassFunction>(node: T): T {
if (this.shouldSkipFunctionGenerification(node)) {
return node;
}
const unwrapped = node.return().unwrap();
if (this.isGObjectType(unwrapped)) {
const genericReturnType = new GenericType("T", unwrapped as TypeIdentifier);
const copied = node.copy({
returnType: genericReturnType,
});
copied.generics.push(new Generic(genericReturnType, unwrapped as TypeIdentifier, unwrapped as TypeIdentifier));
return copied as T;
}
return node;
}
private shouldSkipFunctionGenerification<T extends IntrospectedClassFunction>(node: T): boolean {
return node.parent.getType().is("GObject", "Object");
}
private processClassFunctionWithGenerics<T extends IntrospectedBaseClass | IntrospectedEnum>(
node: IntrospectedClassFunction<T>,
): IntrospectedClassFunction<T> | null {
const clazz = node.parent as IntrospectedBaseClass;
if (clazz.generics.length === 0) {
return null;
}
const returnType = this.processReturnTypeWithGenerics(node.return(), clazz.generics);
return node.copy({
parameters: this.processParametersWithGenerics(node.parameters, clazz.generics),
outputParameters: this.processOutputParametersWithGenerics(node.output_parameters, clazz.generics),
returnType,
});
}
private processReturnTypeWithGenerics(returnType: TypeExpression, generics: Generic[]): TypeExpression {
for (const generic of generics) {
if (generic.defaultType?.equals(returnType.deepUnwrap())) {
return returnType.rewrap(generic.type);
}
}
return returnType;
}
private processParametersWithGenerics(
parameters: IntrospectedFunctionParameter[],
generics: Generic[],
): IntrospectedFunctionParameter[] {
return parameters.map((parameter) => this.processParameterWithGenerics(parameter, generics));
}
private processParameterWithGenerics(
parameter: IntrospectedFunctionParameter,
generics: Generic[],
): IntrospectedFunctionParameter {
for (const generic of generics) {
if (generic.defaultType?.equals(parameter.type.deepUnwrap())) {
return parameter.copy({
type: parameter.type.rewrap(generic.type),
});
}
}
return parameter;
}
private processOutputParametersWithGenerics(
outputParameters: IntrospectedFunctionParameter[],
generics: Generic[],
): IntrospectedFunctionParameter[] {
return outputParameters.map((parameter) => {
for (const generic of generics) {
if (generic.defaultType?.equals(parameter.type.unwrap())) {
return parameter.copy({
type: parameter.type.rewrap(generic.type),
});
}
}
return parameter;
});
}
}