Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

Proposal: `TypeDef` — supplement types that should exist but are missing

Chiusa
#1,079 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
5/5
Tempo stimato
Più di una settimana
Idoneità per principianti
35/100
Tipo di issue
Funzionalità
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
go
Ambito
tooling

Direzione di ricerca

Start by tracing the configuration loading, type registration, and generation flow described in the proposal, especially the phases that consume MethodCheck, TypeAbbr, and NewCheck. Check the existing TypeAlias handling and its tests to find the extension points. Done means TypeDef definitions are validated, registered before attribution, and emitted to their configured files in order; the issue does not name specific source files or tests.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

configuration

Summary

Add a new llcppg.cfg option, TypeDef, that lets a binding author declare a Go type that should exist in the generated package but is missing from the C/C++ headers llcppg parses. The author names the file the type goes into, the type's name, and its fields. llcppg then emits the type into that file as part of the normal generation flow.

"TypeDef": {
    "moduleobject.go": [{
        "Name": "ModuleObject",
        "Fields": [".Object"]
    }]
}

This means: in moduleobject.go, generate a ModuleObject struct with one embedded field, Object, where the leading dot in .Object says the symbol is the Object type of the current package. Each file maps to an array of type definitions, so the order in which types are emitted is exactly the order written in the config.

Motivation

Some C libraries use a type in their public API surface conceptually, but never expose its definition in the public headers.

The motivating case is the Python C API. PyModuleObject is the concrete object type behind every module, yet its definition lives in an internal header and is not part of what users include. The public API only ever sees it as PyObject *. The PyModule_* functions (PyModule_GetDict, PyModule_GetName, ...) clearly belong to a "module" type.

After conversion to Go, we would like:

  • the type to be called ModuleObject (following the existing Py prefix stripping), and
  • the PyModule_* functions to become methods on *ModuleObject, in the same way PyFloat_AsDouble becomes a method on Float via MethodCheck.

Today this is impossible, because there is no PyModuleObject in the parsed headers, so there is no type to attach methods to and nothing for other config options (MethodCheck, TypeAbbr, NewCheck) to refer to.

Existing options do not cover this:

  • TypeAlias maps an existing C/C++ type to a Go symbol. It cannot invent a new one.
  • Hand-written Go files next to the generated ones work, but they are outside the generation flow: they are invisible to llcppg's name matching and method attribution, and they are lost or must be re-synchronized when the package is regenerated.

TypeDef fills the gap: the type is declared in the config, created by llcppg, and participates in the rest of the pipeline like any other type.

Configuration

TypeDef is an object whose keys are file names and whose values are arrays of type definitions:

"TypeDef": {
    "<file>.go": [
        { "Name": "<TypeName>", "Fields": [ ... ] },
        { "Name": "<AnotherType>", "Fields": [ ... ] }
    ]
}
  • Key — the target Go file name (for example moduleobject.go). The file is created in the output package directory, next to the other generated files.
  • Value — an ordered array of type definitions. Several types may share one file by listing several entries. The array order is the emission order, so the result is deterministic and a type can be written after the types it depends on.
  • Name — the Go name of the type to define. This is the final Go name, exactly as it should appear in the package (ModuleObject, not PyModuleObject). It is not run through the prefix-stripping or naming rules.
  • Fields — an ordered list of strings, one per field, in struct order.
Field forms

Each entry in Fields is either an embedded field or a named field, distinguished by its shape:

  • Embedded field — a single token, the type itself: ".Object". This produces an embedded field. A pointer embed is written "*.Object".
  • Named field — two tokens separated by a space, "Name Type": "name *c.Char", "dict *.Object", "count int". This produces an ordinary field named Name of type Type.
Type references: the leading dot

The type part of a field uses a small convention to say where a symbol comes from:

  • A leading dot means "this package". .Object is the Object type of the package being generated, and .ModuleObject is another type in the same package (including one defined by TypeDef itself). The dot is written right before the symbol, after any *, [] or [N] prefix: *.Object, []*.Object, [4].Object.
  • No leading dot means a global predefined type, such as int, uint8, float64, string or bool.
  • Types from other packages that the generated file imports keep their qualified form (c.Int, c.Char), since the qualifier already says where they come from.

The dot removes any ambiguity between a name in the package and a predeclared one. A package may well contain a type that happens to be spelled like a predefined type, and .string and string then mean different things.

Example
"TypeDef": {
    "moduleobject.go": [
        {
            "Name": "ModuleObject",
            "Fields": [".Object"]
        },
        {
            "Name": "ModuleDef",
            "Fields": [".Object", "name *c.Char", "size c.Long", "dict *.Object", "flags int"]
        }
    ]
}

generates moduleobject.go:

package python

import "github.com/goplus/lib/c"

type ModuleObject struct {
	Object
}

type ModuleDef struct {
	Object
	name *c.Char
	size c.Long
	dict *Object
	flags int
}

(Field-name export rules follow the usual llcppg conventions; the example above shows names exactly as written in the config.)

Semantics

Where the type is emitted

The type is written to the file named by the key, in the order it appears in that file's array. Files themselves are processed in the order their keys appear in the config. Files created only for TypeDef contain just the package clause, the needed imports, and the type declarations. If the file name collides with a file llcppg generates for another reason, the declarations are appended to that file rather than overwriting it.

Layout

The generated struct is an ordinary LLGo struct whose layout follows from its fields. For the embedded-base case this means ModuleObject has the same leading layout as Object, so a *ModuleObject can be used wherever the base is expected through the embedding, and the Go-side type reflects the C-side "subtype" relationship (PyModuleObject begins with PyObject_HEAD).

TypeDef does not attempt to reproduce the real C layout beyond what the author lists. If the author lists only Object, the Go type is only guaranteed to be a handle that starts with Object; code that needs fields of the hidden C struct must not rely on this definition.

Interaction with other options

A TypeDef type is registered before the load and attribution phases that consume type names, so it behaves like a type that came from the headers:

  • MethodCheck can attribute functions such as PyModule_GetDict to ModuleObject, turning them into methods.
  • TypeAbbr and NewCheck can reference it by its Go name.
  • It can be used as a field or parameter type by later TypeDef entries.

Nothing in this proposal changes how those options work; it only guarantees the type exists when they run.

Relation to llcppg.pub

A TypeDef type has no C declaration to map from. By default it is not written to llcppg.pub, which records C-name to Go-name mappings used by dependent packages. See the open questions below for the dependent-package case.

Validation and errors

llcppg reports a configuration error (and stops before generating) when:

  • a file key is empty or does not end in .go;
  • an entry has no Name, or its Name is not a valid Go identifier;
  • a type name already exists in the package, whether from the headers or from another TypeDef entry. TypeDef only supplies missing types; it never overrides existing ones (use TypeAlias for that);
  • a Fields entry is empty, has more than two tokens, or its type part does not parse as a Go type expression;
  • two fields end up with the same name (including an embedded field whose type name equals another field's name);
  • a dotted reference (.Name) names a type that does not exist in the generated package, or an undotted name is neither a predefined type nor a qualified type from an imported package;
  • a referenced type otherwise cannot be resolved in the generated package or its imports.

Errors name the file, the type, and the offending field entry.

Alternatives considered

  1. Hand-written files in the package. Works today but sits outside generation: MethodCheck cannot see the type, and regeneration needs manual care. Rejected as the primary mechanism, though it remains available for anything TypeDef cannot express.
  2. Extending TypeAlias to allow undefined targets. Mixes two different intents (re-map an existing type vs. create a new one) and has no natural place for fields.
  3. A flat list instead of a file-keyed object. Simpler, but loses control over which file a type lands in, which matters for readable output and for keeping generated files stable across regenerations.
  4. Keying definitions by type name ("moduleobject.go": { "ModuleObject": {...} }). This was the first draft. JSON objects are unordered, so the emission order of several types in one file would be undefined, and a type could not reliably be placed after the types it refers to. An array of { "Name", "Fields" } entries makes the order explicit.

Open questions

  1. Empty Fields. Should "Fields": [] (or omitting it) produce an empty struct, or be rejected? An empty struct seems useful for opaque handle types, but it should be a deliberate choice.
  2. llcppg.pub entries. If a dependent package needs to refer to ModuleObject by its C name PyModuleObject, an optional C-name field (for example "CName": "PyModuleObject") could register the mapping. It is left out of the first version to keep the surface small.
  3. Doc comments. Should a TypeDef entry accept an optional "Doc" string to attach a doc comment to the generated type? Useful for llcppg -doc, but not required for the core feature.
  4. Methods on TypeDef types beyond MethodCheck. Out of scope here; the type only needs to exist so that existing attribution mechanisms can target it.

Compatibility

Purely additive. Configs without TypeDef behave exactly as before.

Lingua principale
Go
Stelle
40
Fork
13
Merge medio
1h 34m
PR unite (30g)
251

Preparare l'ambiente

Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di goplus/llcppg

Tutte le issue di goplus/llcppg

Issue simili

Altre issue su Go

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.