Skip to content

llcppg: generate logical classes for MethodCheck and convert from the base class #948

Description

@xushiwei

Background

MethodCheck can now tell which logical type a function belongs to (for example PyList_GetItem belongs to List). However, the generated code still attaches every such function to the physical base class Object:

//go:linkname IsTrue C.Py_IsTrue
func IsTrue(x *Object) c.Int

// llgo:link (*Object).IsTrue C.PyObject_IsTrue
func (self *Object) IsTrue() c.Int { return 0 }

// llgo:link (*Object).Item C.PyList_GetItem
func (self *Object) Item(index *Object) *Object { return self }

Methods of List, Dict, Float and the rest all end up on Object. That pollutes the API surface and invites method-name conflicts (PyList_GetItem and PyDict_GetItem would both want to be Item).

Goal

Put each method on its own logical class, and give users a way to get from the base class to a logical class.

Proposed behavior

  1. Logical class. For a function whose MethodCheck result is List, emit the method on a new type List instead of on Object. The logical class embeds the base class, so everything defined on Object stays available on List.

  2. Conversion method. For every generated logical class, add a conversion method on the base class named As<Class>, such as (*Object).AsList() *List. The conversion only reinterprets the pointer; it does not call into C and does not check the object's real type.

    • The name is hard-coded as As + class name for now.
    • Later it can become configurable, with the default "As$0", where $0 stands for the logical class name.
  3. Lazy generation. A logical class is generated only when at least one function actually resolves to it. No methods, no class, no AsXxx.

  4. Base class stays as is. When the logical type is the physical type itself (PyObject_IsTrue resolves to Object), the method stays on Object, and no class or AsObject is generated. Functions with an empty logical type (Py_IsTrue) remain plain package functions.

Expected output

type List struct {
	Object
}

func (o *Object) AsList() *List { return (*List)(unsafe.Pointer(o)) }

// llgo:link (*List).Item C.PyList_GetItem
func (self *List) Item(index *Object) *Object { return nil }

Usage in XGo:

echo obj.AsList().Item(idx)

Notes and edge cases

  • If a type with the logical class name already exists in the package (generated from the headers or provided through TypeAlias), reuse it instead of generating a second one, and still add AsXxx when it is missing.
  • If As<Class> collides with an existing method on the base class, keep the existing method and report a diagnostic.
  • Generation order must be deterministic so the output is stable across runs.

Acceptance criteria

  • PyList_GetItem is emitted as a method of List, not Object.
  • Object gets AsList() only because List has at least one method.
  • A logical type with no matching functions produces no type and no AsXxx.
  • PyObject_IsTrue stays on Object with no AsObject; Py_IsTrue stays a package function.
  • Test case under cl/_testpy (or equivalent) covers the three functions above, plus a name-collision case such as PyDict_GetItem.

Out of scope

  • Making the conversion method name configurable (planned follow-up, default "As$0").
  • Runtime type checking in AsXxx.

No activity

Activity on this issue will appear here.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions