Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

Proposal: `FuncDef` — add functions and methods to the generated Go package

オープン
#1,083 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
5/5
見積もり時間
1週間以上
初心者へのやさしさ
35/100
issue の種類
機能追加
明瞭さ
おおむね明確
活発さ
活発
技術スタック
go
領域
tooling

調査の方向性

Start by reading the existing TypeDef, MethodCheck, and NewCheck configuration and generation paths, plus issue #1079 for the companion option. Then trace how functions are registered, mapped from C signatures, emitted into Go files, and checked for name collisions and receiver compatibility. Done means implementing and documenting FuncDef with validation and tests for the listed forms and interactions; the proposal leaves several semantics open for resolution.

索引モデルが issue の本文から書いたものです。

説明

Architecture-Design configuration

Summary

Add a new llcppg.cfg option, FuncDef, that lets a binding author declare a Go function or method that should exist in the generated package but is not produced by llcppg's default conversion. The author names the file it goes into, the Go name of the function (a plain identifier for a package-level function, or (*Type).Method / (Type).Method for a method), and the C symbol it links to.

"FuncDef": {
    "moduleobject.go": [
        {
            "Name": "(*ModuleObject).GetDict",
            "Link": "PyModule_GetDict"
        }
    ]
}

This means: in moduleobject.go, emit a method GetDict on *ModuleObject, bound to the C symbol PyModule_GetDict.

FuncDef is the companion of TypeDef (#1079). Together they form a family of options that add new symbols to the generated package, and this proposal also fixes the common syntax shared by that family.

Motivation

TypeDef makes it possible to declare a type that the public headers never expose, such as ModuleObject for the Python C API. Once the type exists, the natural next step is to make functions belong to it: PyModule_GetDict, PyModule_GetName and friends should become methods on *ModuleObject.

The existing options cannot always express this:

  • MethodCheck and NewCheck decide automatically whether a C function belongs to a type, based on name matching rules. They are a good fit for regular naming schemes, but they cannot cover one-off cases: a function whose C name does not follow the pattern, a function that should be exposed under a different Go name, or a method whose receiver is a TypeDef type that the C signature never mentions.
  • Hand-written Go files with //go:linkname work, but they sit outside the generation flow. They are invisible to llcppg's name matching, and they must be kept in sync by hand when the package is regenerated.

FuncDef is the explicit, per-function escape hatch: the author states exactly which Go symbol should exist and which C symbol it links to, and llcppg emits it as part of normal generation.

Common syntax for xxxDef options

All options that add new symbols (currently TypeDef and FuncDef) use the same macro-level shape:

"xxxDef": {
    "<file>.go": [
        {
            "Name": "<name>",
            ...
        }
    ]
}

The rules below apply to every xxxDef option, now and in the future:

  • Key — the target Go file name (for example moduleobject.go). It must end in .go. The file is created in the output package directory, next to the other generated files.
  • Value — an ordered array of entries. Several symbols may share one file by listing several entries. The array order is the emission order, so output is deterministic.
  • Name — the Go-side name of the symbol being added. It is the final Go name, exactly as it should appear in the package. It is not run through prefix stripping or any other naming rule.
  • Other fields — specific to each option (Fields for TypeDef, Link for FuncDef).
  • File processing order — files are processed in the order their keys appear in the config. Within a file, entries are processed in array order. If TypeDef and FuncDef target the same file, the file collects declarations from both (see "Where the function is emitted").
  • Name collisions — a xxxDef entry only supplies missing symbols. If the name already exists in the package, from the headers or from another entry, it is a configuration error. It never silently overrides an existing symbol.

Keeping one structure means a binding author learns the shape once, and tooling (validation, formatting, documentation) can treat all xxxDef options uniformly.

Configuration

"FuncDef": {
    "<file>.go": [
        { "Name": "<FuncName>",              "Link": "<c_symbol>" },
        { "Name": "(*<Type>).<Method>",      "Link": "<c_symbol>" },
        { "Name": "(<Type>).<Method>",       "Link": "<c_symbol>" }
    ]
}
Name

Name takes one of three forms:

Form Meaning
Ident A package-level function, for example NewModule.
(*Type).Method A method with a pointer receiver, for example (*ModuleObject).GetDict.
(Type).Method A method with a value receiver, for example (Object).IsNil.

The receiver form follows ordinary Go method-expression notation, so it reads the same way it will look in the generated code. Type and Method are Go identifiers in their final form; Type refers to a type of the package being generated, either one produced from the headers or one defined by TypeDef. Because Type is always a type of the current package, it is written without a leading dot, unlike field types in TypeDef.

Link

Link is the name of the C symbol the Go function is linked to. It becomes the target of the //go:linkname directive in the generated code. It is currently the only field supported besides Name, and it is required.

The signature of the generated function is derived from the C declaration of the Link symbol, converted with the same type-mapping rules llcppg uses for every other function. The author does not restate parameters or results in the config.

Further fields (for example a doc comment, or an explicit signature) may be added later without changing the structure above.

Example

Combined with the TypeDef example, a binding author can add the ModuleObject type and attach functions to it:

"TypeDef": {
    "moduleobject.go": [
        { "Name": "ModuleObject", "Fields": [".Object"] }
    ]
},
"FuncDef": {
    "moduleobject.go": [
        { "Name": "(*ModuleObject).GetDict", "Link": "PyModule_GetDict" },
        { "Name": "(*ModuleObject).GetName", "Link": "PyModule_GetName" },
        { "Name": "NewModuleObject",         "Link": "PyModule_New" }
    ]
}

generates a moduleobject.go along these lines:

package python

import (
	"github.com/goplus/lib/c"
	_ "unsafe"
)

type ModuleObject struct {
	Object
}

//go:linkname (*ModuleObject).GetDict C.PyModule_GetDict
func (recv_ *ModuleObject) GetDict() *Object

//go:linkname (*ModuleObject).GetName C.PyModule_GetName
func (recv_ *ModuleObject) GetName() *c.Char

//go:linkname NewModuleObject C.PyModule_New
func NewModuleObject(name *c.Char) *ModuleObject

The exact spelling of the linkname directive and of the receiver variable follows what llcppg already generates for methods produced by MethodCheck, so FuncDef output is indistinguishable from automatically attributed functions.

Semantics

Where the function is emitted

The declaration is written to the file named by the key, in the order of that file's array. When a TypeDef and a FuncDef target the same file, type declarations come first, followed by function declarations, so a file reads naturally from types to the functions that use them. Files created only for FuncDef contain the package clause, the needed imports, and the function 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.

Signature and receiver
  • For a package-level function, the signature is the converted C signature of the Link symbol.
  • For a method, the receiver is the type named in Name. The C function's first parameter is taken as the receiver position, and the remaining parameters become the method's parameters. The first parameter's converted type does not have to be spelled identically to the receiver type. For example, PyModule_GetDict takes a PyObject *, while the receiver is *ModuleObject. This is valid when the receiver type is layout-compatible with the converted first parameter type, as in the embedded-base case produced by TypeDef. llcppg checks that the receiver type is the first parameter type itself, or a type that embeds it as its first field.
  • Value receivers (Type).Method follow the same rule with a non-pointer first parameter.
Interaction with other options

FuncDef entries are registered together with TypeDef entries, before the phases that consume names, so they behave like functions that came from the headers:

  • A function that appears in FuncDef is not also emitted by the default conversion, MethodCheck or NewCheck. The C symbol is claimed by the FuncDef entry, so there is no duplicate binding of the same symbol under two Go names. See open question 1 for the exact rule.
  • A method's receiver type may be a TypeDef type, which is the main motivating case.
  • The C symbol named by Link must be resolvable, either from the parsed headers or, if it is not declared there, through the rules in open question 2.
Relation to llcppg.pub

A FuncDef entry adds no C-to-Go type mapping, so it is not written to llcppg.pub.

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 none of the three accepted forms (Ident, (*Type).Method, (Type).Method), or an identifier in it is not a valid Go identifier;
  • Link is missing or empty;
  • the Link symbol cannot be found, or is not a function;
  • for a method, the receiver Type does not exist in the generated package, or the receiver is not compatible with the first parameter of the C function (see "Signature and receiver"), or the C function has no parameters;
  • the resulting name already exists in the package: a function, a method of the same type, or a type. FuncDef only supplies missing symbols; it never overrides existing ones;
  • two FuncDef entries produce the same Go name;
  • an unknown field appears in an entry.

Errors name the file, the entry's Name, and the problem.

Alternatives considered

  1. Hand-written files with //go:linkname. Works today but sits outside generation: the symbols are not visible to llcppg, and regeneration needs manual care. It remains available for anything FuncDef cannot express.
  2. Extending MethodCheck with per-function exceptions. MethodCheck is a rule-based, pattern-driven mechanism. Adding explicit one-off entries to it mixes two intents (automatic rule vs. explicit declaration) and has no natural place for per-entry fields such as Link.
  3. Writing the whole Go signature in Name. This would let the author declare functions that have no C declaration at all, but it duplicates the type-mapping work llcppg already does and reintroduces mistakes that automatic conversion avoids. A signature field can still be added later as an additional option.
  4. Keying entries by function name. Rejected for the same reason as in TypeDef: JSON objects are unordered, so emission order would be undefined. An ordered array of entries is used for all xxxDef options.

Open questions

  1. Interaction with default emission. When the Link symbol is also a regular C function in the headers, should the FuncDef entry replace the default binding (as proposed above), or should both be emitted? Replacing avoids duplicate bindings; the cost is that using FuncDef to merely rename or re-home a function also removes the original. A later option could keep the original if needed.
  2. Symbols without a C declaration. Should Link be allowed to name a symbol that is not declared in the parsed headers? That would need an explicit signature field, which is out of scope for the first version, so the first version requires the symbol to be declared.
  3. Optional Link. For a package-level function whose Go name already equals the C name, Link could be omitted and inferred. The first version requires Link to keep the config explicit.
  4. Doc comments. As with TypeDef, an optional "Doc" string could attach a doc comment to the generated function. Not required for the core feature.
  5. Receiver compatibility check. The rule "same type, or embeds it as its first field" is deliberately strict. Whether it should be relaxed, for instance to allow any layout-compatible pointer type, can be decided after real-world use with the Python binding.

Compatibility

Purely additive. Configs without FuncDef behave exactly as before. The common xxxDef syntax described above matches TypeDef as proposed in #1079, so no change to TypeDef is required by this proposal.

主要言語
Go
スター
40
フォーク
13
平均マージ
1時間 34分
マージ済み PR(30日)
251

環境構築

このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

goplus/llcppg のほかの issue

goplus/llcppg の issue をすべて見る

似ている issue

Go の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。