@ts-for-gir/lib
Version:
Typescript .d.ts generator from GIR for gjs
255 lines • 12.2 kB
JavaScript
import { ClosureType, GenericType, Generic, TypeIdentifier, GenerifiedTypeIdentifier, ThisType } from "../gir.js";
import { IntrospectedClass, IntrospectedBaseClass } from "../gir/class.js";
import { IntrospectedFunction, IntrospectedClassFunction, IntrospectedStaticClassFunction } from "../gir/function.js";
import { GenericNameGenerator } from "../gir/generics.js";
import { resolveTypeIdentifier } from "../gir/util.js";
import { GirVisitor } from "../visitor.js";
export class GenericVisitor extends GirVisitor {
registry;
inferGenerics;
constructor(registry, inferGenerics) {
super();
this.registry = registry;
this.inferGenerics = inferGenerics;
}
visitCallback = (node) => {
if (!this.inferGenerics) {
return node;
}
const shouldGenerify = node.parameters.some(p => {
const type = p.type.unwrap();
return type instanceof TypeIdentifier && type.is("GObject", "Object");
});
if (shouldGenerify) {
const generateName = GenericNameGenerator.new();
const generics = [];
const GenerifiedParameters = node.parameters.map(p => {
const type = p.type.unwrap();
if (type instanceof TypeIdentifier && type.is("GObject", "Object")) {
const Identifier = generateName();
const generic = new GenericType(Identifier, type);
generics.push(new Generic(generic, type));
return p.copy({
type: p.type.rewrap(generic)
});
}
return p;
});
const generified = node.copy({
parameters: GenerifiedParameters
});
generified.generics = generics;
return generified;
}
return node;
};
visitClass = (node) => {
return this.visitBaseClass(node);
};
visitInterface = (node) => {
return this.visitBaseClass(node);
};
visitBaseClass = (_node) => {
const node = _node.copy();
const { namespace } = _node;
const resolvedParent = node.superType ? resolveTypeIdentifier(namespace, node.superType) : null;
const derivatives = node.generics.filter(generic => generic.parent != null);
if (node instanceof IntrospectedClass) {
const resolvedInterfaces = node.interfaces
.map(i => resolveTypeIdentifier(namespace, i))
.filter((c) => c != null);
node.interfaces = node.interfaces.map(iface => {
const generic = derivatives.filter(d => d.parent?.is(iface.namespace, iface.name));
if (generic.length > 0) {
return new GenerifiedTypeIdentifier(iface.name, iface.namespace, generic.map(g => g.type));
}
const resolved = resolvedInterfaces.find(i => i.getType().equals(iface));
if (resolved) {
if (resolved.generics.length === 1) {
const generic = resolved.generics[0];
if (generic.propagate) {
const constrainedGeneric = node.generics.find(d => generic.constraint && d.constraint?.equals(generic.constraint));
if (constrainedGeneric) {
return new GenerifiedTypeIdentifier(iface.name, iface.namespace, [
constrainedGeneric.type
]);
}
if (!generic.defaultType?.equals(node.getType()) &&
!generic.constraint?.equals(node.getType())) {
node.addGeneric({
constraint: generic.constraint ?? undefined,
default: generic.defaultType ?? undefined,
deriveFrom: resolved.getType()
});
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()]);
}
}
return iface;
});
}
if (node.superType) {
const parentType = node.superType;
const generic = derivatives.filter(d => d.parent?.is(parentType.namespace, parentType.name));
if (node.superType instanceof GenerifiedTypeIdentifier) {
// Do nothing
}
else if (generic.length > 0) {
node.superType = new GenerifiedTypeIdentifier(parentType.name, parentType.namespace, generic.map(g => g.type));
}
else {
const resolved = resolvedParent;
if (resolved?.generics.length === 1) {
const [generic] = resolved.generics;
if (generic.propagate) {
const constrainedGeneric = node.generics.find(d => generic.constraint && d.constraint?.equals(generic.constraint));
if (constrainedGeneric) {
node.superType = new GenerifiedTypeIdentifier(resolved.name, resolved.namespace.namespace, [
constrainedGeneric.type
]);
}
else {
if (!generic.defaultType?.equals(node.getType()) &&
!generic.constraint?.equals(node.getType())) {
node.addGeneric({
constraint: generic.constraint ?? undefined,
default: generic.defaultType ?? undefined,
deriveFrom: resolved.getType()
});
const firstGeneric = node.generics[node.generics.length - 1];
node.superType = new GenerifiedTypeIdentifier(resolved.name, resolved.namespace.namespace, [firstGeneric.type]);
}
else if ([...node.resolveParents()].some(c => generic.defaultType && c.identifier.equals(generic.defaultType))) {
node.superType = new GenerifiedTypeIdentifier(resolved.name, resolved.namespace.namespace, [node.getType()]);
}
}
}
else if ([...node.resolveParents()].some(c => generic.defaultType && c.identifier.equals(generic.defaultType))) {
node.superType = new GenerifiedTypeIdentifier(resolved.name, resolved.namespace.namespace, [
node.getType()
]);
}
}
}
}
return node;
};
visitParameter = (node) => {
const { inferGenerics } = this;
const member = node.parent;
const unwrapped = node.type.unwrap();
// TODO I need a better system for this, but handling Gio.AsyncReadyCallback is the most common.
if (inferGenerics && unwrapped instanceof ClosureType) {
const internal = unwrapped.type.unwrap();
if (internal instanceof TypeIdentifier && internal.is("Gio", "AsyncReadyCallback")) {
if (member instanceof IntrospectedFunction && member.parameters.length >= 2) {
const generified = node.copy({
type: node.type.rewrap(new GenerifiedTypeIdentifier(internal.name, internal.namespace, [member.parameters[0].type]))
});
return generified;
}
else if (member instanceof IntrospectedStaticClassFunction) {
const generified = node.copy({
type: node.type.rewrap(new GenerifiedTypeIdentifier(internal.name, internal.namespace, [member.parent.getType()]))
});
return generified;
}
else if (member instanceof IntrospectedClassFunction) {
const generified = node.copy({
type: node.type.rewrap(new GenerifiedTypeIdentifier(internal.name, internal.namespace, [ThisType]))
});
return generified;
}
}
}
return node;
};
visitFunction = (node) => {
if (!this.inferGenerics) {
return node;
}
const unwrapped = node.return_type.unwrap();
const shouldGenerify = unwrapped instanceof TypeIdentifier && unwrapped.is("GObject", "Object");
if (shouldGenerify) {
const genericReturnType = new GenericType("T", unwrapped);
const copied = node.copy({
return_type: genericReturnType
});
copied.generics.push(new Generic(genericReturnType, unwrapped));
return copied;
}
return node;
};
generifyStandaloneClassFunction = (node) => {
const unwrapped = node.return().unwrap();
if (node.parent.getType().is("GObject", "Object")) {
return node;
}
if (unwrapped instanceof TypeIdentifier && unwrapped.is("GObject", "Object")) {
const genericReturnType = new GenericType("T", unwrapped);
const copied = node.copy({
returnType: genericReturnType
});
copied.generics.push(new Generic(genericReturnType, unwrapped, unwrapped));
// TODO: .copy() isn't generic.
return copied;
}
return node;
};
visitStaticClassFunction = (node) => {
if (this.inferGenerics) {
return this.generifyStandaloneClassFunction(node);
}
return node;
};
visitClassFunction = (node) => {
if (node.parent instanceof IntrospectedBaseClass) {
const clazz = node.parent;
if (clazz.generics.length > 0) {
let returnType = node.return();
for (const generic of clazz.generics) {
if (generic.defaultType?.equals(node.return().deepUnwrap())) {
returnType = node.return().rewrap(generic.type);
break;
}
}
return node.copy({
parameters: node.parameters.map(p => {
for (const generic of clazz.generics) {
if (generic.defaultType?.equals(p.type.deepUnwrap())) {
return p.copy({
type: p.type.rewrap(generic.type)
});
}
}
return p;
}),
outputParameters: node.output_parameters.map(p => {
for (const generic of clazz.generics) {
if (generic.defaultType?.equals(p.type.unwrap())) {
return p.copy({
type: p.type.rewrap(generic.type)
});
}
}
return p;
}),
returnType
});
}
}
if (this.inferGenerics) {
return this.generifyStandaloneClassFunction(node);
}
return node;
};
visitVirtualClassFunction = (node) => {
return this.visitClassFunction(node);
};
}
//# sourceMappingURL=visitor.js.map