diff --git a/packages/lib/src/gir.ts b/packages/lib/src/gir.ts index c0153a52..06b3b5aa 100644 --- a/packages/lib/src/gir.ts +++ b/packages/lib/src/gir.ts @@ -16,7 +16,7 @@ export { promisifyFunctions } from './gir/promisify.js' export { resolveDirectedType, resolvePrimitiveType } from './gir/util.js' export * from './gir/nodes.js' -import type { OptionsGeneration } from './types/index.js' +import type { OptionsBase } from './types/index.js' export abstract class TypeExpression { isPointer = false @@ -29,10 +29,10 @@ export abstract class TypeExpression { } abstract rewrap(type: TypeExpression): TypeExpression - abstract resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeExpression + abstract resolve(namespace: IntrospectedNamespace, options: OptionsBase): TypeExpression - abstract print(namespace: IntrospectedNamespace, options: OptionsGeneration): string - rootPrint(namespace: IntrospectedNamespace, options: OptionsGeneration): string { + abstract print(namespace: IntrospectedNamespace, options: OptionsBase): string + rootPrint(namespace: IntrospectedNamespace, options: OptionsBase): string { return this.print(namespace, options) } } @@ -72,7 +72,7 @@ export class TypeIdentifier extends TypeExpression { return new TypeIdentifier(sanitizeIdentifierName(this.namespace, this.name), sanitizeNamespace(this.namespace)) } - protected _resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeIdentifier | null { + protected _resolve(namespace: IntrospectedNamespace, options: OptionsBase): TypeIdentifier | null { const name: string = sanitizeIdentifierName(null, this.name) const unresolvedNamespaceName = this.namespace @@ -144,11 +144,11 @@ export class TypeIdentifier extends TypeExpression { return null } - resolveIdentifier(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeIdentifier | null { + resolveIdentifier(namespace: IntrospectedNamespace, options: OptionsBase): TypeIdentifier | null { return this._resolve(namespace, options) } - resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeExpression { + resolve(namespace: IntrospectedNamespace, options: OptionsBase): TypeExpression { const resolved = this._resolve(namespace, options) // Generally if we can't resolve a type it is not introspectable, @@ -161,7 +161,7 @@ export class TypeIdentifier extends TypeExpression { } // eslint-disable-next-line @typescript-eslint/no-unused-vars - print(namespace: IntrospectedNamespace, _options: OptionsGeneration): string { + print(namespace: IntrospectedNamespace, _options: OptionsBase): string { if (namespace.hasSymbol(this.namespace) && this.namespace !== namespace.namespace) { // TODO: Move to TypeScript generator... // Libraries like zbar have classes named things like "Gtk" @@ -212,12 +212,12 @@ export class ModuleTypeIdentifier extends TypeIdentifier { } // eslint-disable-next-line @typescript-eslint/no-unused-vars - protected _resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): ModuleTypeIdentifier | null { + protected _resolve(namespace: IntrospectedNamespace, options: OptionsBase): ModuleTypeIdentifier | null { return this } // eslint-disable-next-line @typescript-eslint/no-unused-vars - print(namespace: IntrospectedNamespace, options: OptionsGeneration): string { + print(namespace: IntrospectedNamespace, options: OptionsBase): string { if (namespace.namespace === this.namespace) { return `${this.moduleName}.${this.name}` } else { @@ -239,7 +239,7 @@ export class ClassStructTypeIdentifier extends TypeIdentifier { } // eslint-disable-next-line @typescript-eslint/no-unused-vars - print(namespace: IntrospectedNamespace, options: OptionsGeneration): string { + print(namespace: IntrospectedNamespace, options: OptionsBase): string { if (namespace.namespace === this.namespace) { // TODO: Mapping to invalid names should happen at the generator level... return `typeof ${isInvalid(this.name) ? `__${this.name}` : this.name}` @@ -257,7 +257,7 @@ export class GenerifiedTypeIdentifier extends TypeIdentifier { this.generics = generics } - print(namespace: IntrospectedNamespace, options: OptionsGeneration): string { + print(namespace: IntrospectedNamespace, options: OptionsBase): string { const Generics = this.generics.map((generic) => generic.print(namespace, options)).join(', ') if (namespace.namespace === this.namespace) { @@ -267,7 +267,7 @@ export class GenerifiedTypeIdentifier extends TypeIdentifier { } } - _resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeIdentifier | null { + _resolve(namespace: IntrospectedNamespace, options: OptionsBase): TypeIdentifier | null { const iden = super._resolve(namespace, options) if (iden) { @@ -279,9 +279,9 @@ export class GenerifiedTypeIdentifier extends TypeIdentifier { } export class NativeType extends TypeExpression { - readonly expression: (options?: OptionsGeneration) => string + readonly expression: (options?: OptionsBase) => string - constructor(expression: ((options?: OptionsGeneration) => string) | string) { + constructor(expression: ((options?: OptionsBase) => string) | string) { super() this.expression = typeof expression === 'string' ? () => expression : expression @@ -295,11 +295,11 @@ export class NativeType extends TypeExpression { return this } - print(_namespace: IntrospectedNamespace, options: OptionsGeneration) { + print(_namespace: IntrospectedNamespace, options: OptionsBase) { return this.expression(options) } - equals(type: TypeExpression, options?: OptionsGeneration): boolean { + equals(type: TypeExpression, options?: OptionsBase): boolean { return type instanceof NativeType && this.expression(options) === type.expression(options) } @@ -307,7 +307,7 @@ export class NativeType extends TypeExpression { return this } - static withGenerator(generator: (options?: OptionsGeneration) => string): TypeExpression { + static withGenerator(generator: (options?: OptionsBase) => string): TypeExpression { return new NativeType(generator) } @@ -332,17 +332,17 @@ export class OrType extends TypeExpression { return this } - resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeExpression { + resolve(namespace: IntrospectedNamespace, options: OptionsBase): TypeExpression { const [type, ...types] = this.types return new OrType(type.resolve(namespace, options), ...types.map((t) => t.resolve(namespace, options))) } - print(namespace: IntrospectedNamespace, options: OptionsGeneration): string { + print(namespace: IntrospectedNamespace, options: OptionsBase): string { return `(${this.types.map((t) => t.print(namespace, options)).join(' | ')})` } - rootPrint(namespace: IntrospectedNamespace, options: OptionsGeneration): string { + rootPrint(namespace: IntrospectedNamespace, options: OptionsBase): string { return `${this.types.map((t) => t.print(namespace, options)).join(' | ')}` } @@ -356,15 +356,15 @@ export class OrType extends TypeExpression { } export class TupleType extends OrType { - print(namespace: IntrospectedNamespace, options: OptionsGeneration): string { + print(namespace: IntrospectedNamespace, options: OptionsBase): string { return `[${this.types.map((t) => t.print(namespace, options)).join(', ')}]` } - rootPrint(namespace: IntrospectedNamespace, options: OptionsGeneration): string { + rootPrint(namespace: IntrospectedNamespace, options: OptionsBase): string { return this.print(namespace, options) } - resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeExpression { + resolve(namespace: IntrospectedNamespace, options: OptionsBase): TypeExpression { const [type, ...types] = this.types return new TupleType(type.resolve(namespace, options), ...types.map((t) => t.resolve(namespace, options))) @@ -388,7 +388,7 @@ export class BinaryType extends OrType { return this } - resolve(namespace: IntrospectedNamespace, options: OptionsGeneration) { + resolve(namespace: IntrospectedNamespace, options: OptionsBase) { return new BinaryType(this.a.resolve(namespace, options), this.b.resolve(namespace, options)) } @@ -440,7 +440,7 @@ export class FunctionType extends TypeExpression { return this } - resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeExpression { + resolve(namespace: IntrospectedNamespace, options: OptionsBase): TypeExpression { return new FunctionType( Object.fromEntries( Object.entries(this.parameterTypes).map(([k, p]) => { @@ -451,7 +451,7 @@ export class FunctionType extends TypeExpression { ) } - rootPrint(namespace: IntrospectedNamespace, options: OptionsGeneration): string { + rootPrint(namespace: IntrospectedNamespace, options: OptionsBase): string { const Parameters = Object.entries(this.parameterTypes) .map(([k, v]) => { return `${k}: ${v.rootPrint(namespace, options)}` @@ -461,7 +461,7 @@ export class FunctionType extends TypeExpression { return `(${Parameters}) => ${this.returnType.print(namespace, options)}` } - print(namespace: IntrospectedNamespace, options: OptionsGeneration): string { + print(namespace: IntrospectedNamespace, options: OptionsBase): string { return `(${this.rootPrint(namespace, options)})` } } @@ -523,7 +523,7 @@ export class GenerifiedType extends TypeExpression { this.generic = generic } - resolve(namespace: IntrospectedNamespace, options: OptionsGeneration) { + resolve(namespace: IntrospectedNamespace, options: OptionsBase) { return new GenerifiedType(this.type.resolve(namespace, options), this.generic.resolve()) } @@ -531,11 +531,11 @@ export class GenerifiedType extends TypeExpression { return this.type } - rootPrint(namespace: IntrospectedNamespace, options: OptionsGeneration) { + rootPrint(namespace: IntrospectedNamespace, options: OptionsBase) { return `${this.type.print(namespace, options)}<${this.generic.print()}>` } - print(namespace: IntrospectedNamespace, options: OptionsGeneration) { + print(namespace: IntrospectedNamespace, options: OptionsBase) { return `${this.type.print(namespace, options)}<${this.generic.print()}>` } @@ -625,11 +625,11 @@ export class PromiseType extends TypeExpression { return new PromiseType(this.type.rewrap(type)) } - resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeExpression { + resolve(namespace: IntrospectedNamespace, options: OptionsBase): TypeExpression { return new PromiseType(this.type.resolve(namespace, options)) } - print(namespace: IntrospectedNamespace, options: OptionsGeneration): string { + print(namespace: IntrospectedNamespace, options: OptionsBase): string { // TODO: Optimize this check. if (!namespace.hasSymbol('Promise')) { return `Promise<${this.type.print(namespace, options)}>` @@ -693,7 +693,7 @@ export class TypeConflict extends TypeExpression { return true } - resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeExpression { + resolve(namespace: IntrospectedNamespace, options: OptionsBase): TypeExpression { throw new Error( `Type conflict was not resolved for ${this.type.resolve(namespace, options).print(namespace, options)} in ${ namespace.namespace @@ -701,7 +701,7 @@ export class TypeConflict extends TypeExpression { ) } - print(namespace: IntrospectedNamespace, options: OptionsGeneration): string { + print(namespace: IntrospectedNamespace, options: OptionsBase): string { throw new Error( `Type conflict was not resolved for ${this.type.resolve(namespace, options).print(namespace, options)} in ${ namespace.namespace @@ -744,7 +744,7 @@ export class ClosureType extends TypeExpression { return this } - resolve(namespace: IntrospectedNamespace, options: OptionsGeneration) { + resolve(namespace: IntrospectedNamespace, options: OptionsBase) { const { user_data, type } = this return ClosureType.new({ @@ -753,7 +753,7 @@ export class ClosureType extends TypeExpression { }) } - print(namespace: IntrospectedNamespace, options: OptionsGeneration): string { + print(namespace: IntrospectedNamespace, options: OptionsBase): string { return this.type.print(namespace, options) } @@ -801,7 +801,7 @@ export class ArrayType extends TypeExpression { return false } - resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeExpression { + resolve(namespace: IntrospectedNamespace, options: OptionsBase): TypeExpression { const { type, arrayDepth, length } = this return ArrayType.new({ type: type.resolve(namespace, options), @@ -810,7 +810,7 @@ export class ArrayType extends TypeExpression { }) } - print(namespace: IntrospectedNamespace, options: OptionsGeneration): string { + print(namespace: IntrospectedNamespace, options: OptionsBase): string { const depth = this.arrayDepth let typeSuffix: string = '' diff --git a/packages/lib/src/gir/registry.ts b/packages/lib/src/gir/registry.ts index 4c36f4e3..db488517 100644 --- a/packages/lib/src/gir/registry.ts +++ b/packages/lib/src/gir/registry.ts @@ -17,6 +17,7 @@ import { ParsedGir } from "../types/parsed-gir.js"; import { GirModule } from "../index.js"; import type { OptionsGeneration, OptionsTransform } from "../types/index.js"; +import { FunctionParametersVisitor } from "../validators/function-parameters.js"; export interface NSLoader { load(namespace: string, version: string): ParsedGir | null; @@ -147,13 +148,14 @@ export class NSRegistry { GObject.package_version = [...GLib.package_version]; const interfaceVisitor = new InterfaceVisitor(); - this.registerTransformation(interfaceVisitor); const classVisitor = new ClassVisitor(); - this.registerTransformation(classVisitor); + const enumParamsVisitor = new FunctionParametersVisitor(); + this.registerTransformation(enumParamsVisitor); + console.log("Adding generics..."); generify(this, options.inferGenerics); diff --git a/packages/lib/src/validators/function-parameters.ts b/packages/lib/src/validators/function-parameters.ts new file mode 100644 index 00000000..fca0542a --- /dev/null +++ b/packages/lib/src/validators/function-parameters.ts @@ -0,0 +1,52 @@ +import { IntrospectedFunction, IntrospectedClassFunction } from "../gir/function.js"; +import { IntrospectedBaseClass } from "../gir/class.js"; +import { IntrospectedEnum } from "../gir/enum.js"; + +import { TypeIdentifier, NullableType } from "../gir.js"; +import { GirVisitor } from "../visitor.js"; + +export class FunctionParametersVisitor extends GirVisitor { + /** + * Marks all enum parameters of a function as nullable, + * because GJS allows null values for enum parameters and treats them as a 0 value. + * See issue [#207](https://github.com/gjsify/ts-for-gir/issues/207). + */ + private makeEnumParamsNullable(node: IntrospectedFunction): IntrospectedFunction; + private makeEnumParamsNullable( + node: IntrospectedClassFunction + ): IntrospectedClassFunction; + private makeEnumParamsNullable( + node: IntrospectedFunction | IntrospectedClassFunction + ): IntrospectedFunction | IntrospectedClassFunction { + return node.copy({ + parameters: node.parameters.map(param => { + const type = param.type.deepUnwrap(); + if (type instanceof TypeIdentifier) { + // Get the namespace where this type should be defined + const ns = node.namespace.assertInstalledImport(type.namespace); + + // Check if the type is an enum + const isEnumType = !!ns.getEnum(type.name); + + // If it is, make the parameter nullable + if (isEnumType) { + return param.copy({ + type: new NullableType(param.type) + }); + } + } + return param; + }) + }); + } + + visitFunction = (node: IntrospectedFunction): IntrospectedFunction => { + return this.makeEnumParamsNullable(node); + }; + + visitClassFunction = ( + node: IntrospectedClassFunction + ): IntrospectedClassFunction => { + return this.makeEnumParamsNullable(node); + }; +}