Proposal: `GoFiles` — mixing hand-written and generated Go code

クローズ
#1,100 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

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

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

評価

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

調査の方向性

Start from the cl design doc and issue #929 to learn the preload/load/compile phases. The proposal requires parsing GoFiles with go/parser in the preload phase and validating dotted TypeAlias entries against the manual symbol table. Done means llcppg skips emitting types marked manual and records them in llcppg.pub without overwriting hand-written files.

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

説明

Architecture-Design configuration feature proposal

Summary

llcppg.cfg already accepts a GoFiles field: a list of hand-written Go files that live in the target directory next to the generated ones. This proposal pins down what that field means, so that manual and automatic conversion can reference each other in both directions:

  • manual → auto: hand-written files use types, functions and methods produced by llcppg.
  • auto → manual: generated code uses types that the user translated by hand. The user says so explicitly through TypeAlias, whose right-hand side may now point at a symbol declared in GoFiles.

Nothing is inferred. A C type is treated as manually translated only when the configuration says so.

Motivation

Automatic conversion cannot cover everything. Typical cases:

  • A C struct that is better represented by a hand-written Go type (raylib Color as a packed uint32 with helper methods; a C++ template instance that needs a custom wrapper).
  • Convenience helpers layered on the generated API (constructors, String() methods, iterators, error-returning wrappers).
  • Types llcppg cannot yet express (see the union, bit-field and virtual-method proposals).

Today users work around this by post-editing generated output, which is lost on the next run, or by encoding everything in config options. GoFiles gives a stable, regeneration-safe place for manual code.

TypeAlias is the existing escape hatch for the auto → manual direction, but it only says "this C type is that Go symbol". It cannot say "this C type is translated by hand, and the translation lives in this package". The raylib Color case (Color redeclared in this block) is exactly where that gap shows.

Configuration

{
  ...
  "GoFiles": ["color.go", "helpers.go"],
  "TypeAlias": {
    "Vector2": ".Vector2",
    "Color": ".Color",
    "Rectangle": ".Rect"
  }
}
Rules for GoFiles
  • Entries are plain file names (no directories, no globs in the first version) relative to the target directory.
  • Every file must exist, must be a .go file, and must declare the same package name as the generated package.
  • llcppg never overwrites, reformats, renames or deletes a GoFiles entry.
  • A generated output file whose name equals a GoFiles entry is an error.
  • Hand-written files are ordinary Go files. They may have any imports and build constraints; llcppg only needs their top-level declarations.

Semantics

1. Manual symbol table

During the preload phase llcppg parses every GoFiles entry with go/parser and collects the top-level declarations by name: types, functions, methods (keyed by receiver type and name), constants and variables.

Only syntax is needed here. Manual files may reference generated symbols that do not exist yet, so llcppg must not type-check them at this stage and must not fail on undefined names.

2. Marking a type as manually translated

TypeAlias keeps its shape: the key is the C/C++ type name, the value says which Go symbol it maps to. A value that starts with a dot refers to a symbol of the current package, so .Vec2 means Vec2 in the package being generated.

"TypeAlias": {
  "Vector2": ".Vector2",
  "Rectangle": ".Rect"
}
  • "Vector2": ".Vector2" — the manual type keeps the C name.
  • "Rectangle": ".Rect" — the manual type has a different name.

When the value is a dotted name and it resolves to a type declared in the manual symbol table, the C type is manually translated:

  • llcppg emits no declaration for it;
  • every reference to it in generated code (struct fields, parameters, return types, pointer targets) resolves to the manual type;
  • it is recorded in llcppg.pub (see 5).

Values without a leading dot keep today's meaning (an alias to an existing Go symbol, a builtin type, a generic or an instance). The leading dot is the only marker; there is no second field and no special value for "same name".

There is no implicit override. Declaring type Vector2 ... in a GoFiles entry does not by itself stop llcppg from generating Vector2. Without a TypeAlias entry the generator emits its own Vector2, and the duplicate declaration is reported (see Errors).

3. Generated code that depends on manual types

Generated code only needs the name of a manual type, so it can be emitted without type-checking the manual files. llcppg does not look inside the manual type, and the author is responsible for layout compatibility (see 6).

A generated method may be attached to a manual receiver type when the manual files do not define a method of the same name. This is how functions that belong to a manual type (for example, MethodCheck or NewCheck results on a hand-written ModuleObject) still get generated.

4. Manual files that depend on generated symbols

Manual files can use generated symbols freely, because both sets of files compile as one Go package, and Go has no declaration-order requirement. llcppg only has to guarantee that the generated files exist and that the names are the stable, documented ones.

After generation, llcppg runs an optional verification step (go/types over the whole package, equivalent to go vet). Errors in manual files that come from an unresolved or changed generated symbol are reported with the manual file and line, plus a hint that a generated name may have changed.

5. Public symbol table (llcppg.pub)

Manually translated types are written to llcppg.pub like generated ones, under their C name with the Go name from the TypeAlias entry. Dependent packages then resolve cross-package references to the manual type exactly as they would to a generated one. Only exported manual types can be recorded; mapping a C type to an unexported manual type is an error.

6. ABI responsibility

llcppg cannot verify that a hand-written type matches the C layout. That responsibility belongs to the author of the manual file. A later enhancement could emit a compile-time size assertion for manual types that stand in for C structs (see open questions).

Processing order

This fits into the three phases described in the cl design doc:

  1. Preload: parse GoFiles, build the manual symbol table, validate the TypeAlias entries that start with a dot.
  2. Load: read C/C++ declarations as today; apply TypeAlias to decide which declarations are suppressed and which names they resolve to.
  3. Compile: emit the remaining declarations; references to manually translated types resolve to the manual names; write llcppg.pub; optionally verify the whole package.

Errors and diagnostics

llcppg reports an error for:

  • a missing GoFiles entry, a non-.go entry, or an entry containing a path separator;
  • a package-name mismatch between a manual file and the generated package;
  • a GoFiles name that collides with a generated file name;
  • a TypeAlias value starting with a dot whose target is not declared in GoFiles (the message names the entry, so a typo or a file missing from the list is easy to spot);
  • a dotted target that is not a type, or is unexported;
  • a generated function, method, constant or variable whose name collides with a manual declaration (the message lists both locations);
  • a generated type that collides with a manual type because no TypeAlias entry marks it as manually translated;
  • a manual file that fails to parse.

Example

color.go (hand-written):

package raylib

type Color uint32

func RGBA(r, g, b, a uint8) Color {
	return Color(uint32(r) | uint32(g)<<8 | uint32(b)<<16 | uint32(a)<<24)
}

func (c Color) R() uint8 { return uint8(c) }

With

"GoFiles": ["color.go"],
"TypeAlias": { "Color": ".Color" }

llcppg skips the C struct Color, and generated functions such as ClearBackground(color Color) take the manual Color. The Color redeclared in this block failure in the raylib test case goes away because the user states which of the two declarations is meant to exist.

Alternatives considered

  • Implicit override: a manual declaration with the same name silently replaces the generated one. Rejected. The same name in two places may be an accident, and silently dropping generated code is hard to notice. An explicit entry also lets llcppg validate that the manual type really exists.
  • A separate field such as ManualTypes (a list of names, or a map from C name to Go name). Clearer wording, but it overlaps with TypeAlias, so the mapping would be configured in two places. A list also cannot express renaming without extra syntax.
  • Grouping types under each GoFiles entry ({"File": "color.go", "Types": [...]}). Binds a type to its file, but turns GoFiles from a string list into an object list and makes the configuration longer.
  • Directive comments in manual files (for example //llcppg:type Vector2). Keeps the mapping next to the code, but spreads type mapping over config and source. Not chosen for now; it could be added later on top of the same symbol table.
  • Reusing TypeDef. TypeDef asks llcppg to produce a type; here llcppg must not produce it, so the semantics point in opposite directions.
  • Treat every .go file in the target directory as manual unless generated. Convenient, but regeneration then cannot tell stale generated files from manual ones. An explicit list is safer.

Open questions

  1. Should GoFiles accept glob patterns (*.go) in a later version?
  2. Should llcppg emit a size/alignment assertion for manual types that replace C structs?
  3. Functions, constants and variables currently have no explicit "I translated this by hand" marker; a collision is an error. Should a similar dotted syntax, or a separate skip option, be added for them?
  4. How should the manual symbol table interact with -doc? The proposed default is to show manual and generated symbols together in the same package documentation.

Related

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

環境構築

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

はじめの一歩

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

goplus/llcppg のほかの issue

goplus/llcppg の issue をすべて見る

似ている issue

Go の issue をもっと見る

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

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