Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add subclassing support #78

Merged
merged 22 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9b37750
genbindings: support protected methods
mappu Nov 11, 2024
aa2fdf9
genbindings: subclass support for all virtual methods (1/3)
mappu Nov 11, 2024
d25301c
genbindings: track private methods, exclude from virtual overrides
mappu Nov 15, 2024
2ae1e60
genbindings/clang2il: detect pure virtual, detect overrides
mappu Nov 15, 2024
58f2123
genbindings: subclass support for all virtual methods (2/3)
mappu Nov 15, 2024
943ccf7
genbindings: subclass support for all virtual methods (3/3)
mappu Nov 15, 2024
73089d5
examples/subclass: initial commit
mappu Nov 15, 2024
8d1c871
genbindings/inherits: direct vs indirect inheritance
mappu Nov 19, 2024
c6381d4
genbindings/util: add slice_copy helper
mappu Nov 19, 2024
eca8747
genbindings/types: pointer and return type fixes
mappu Nov 19, 2024
fb56258
genbindings: constructors return every subclass pointer
mappu Nov 19, 2024
6fa9772
genbindings: delete either subclass or direct class
mappu Nov 19, 2024
40abeec
genbindings/subclassing: accurate pointer type management for subclasses
mappu Nov 19, 2024
0ff7507
examples/subclass: show super() calls
mappu Nov 19, 2024
0884f37
qt: rebuild (constructors, subclasses)
mappu Nov 19, 2024
90de717
genbindings: fix some cases of missing :: escaping
mappu Nov 19, 2024
b50870b
genbindings: intptr_t-qintptr pointer casts require C-style casts
mappu Nov 19, 2024
392a924
genbindings/config: omit QxxPrivate::xx
mappu Nov 19, 2024
3902c9d
qt: rebuild (hide private classes, fix qintptr pointers)
mappu Nov 19, 2024
bbc75b8
linuxonly: fix ifdef quirks for building on windows
mappu Nov 19, 2024
1b577f8
doc/README: add note regarding virtual subclassing
mappu Nov 19, 2024
60e0968
examples/subclass: add screenshot
mappu Nov 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ examples/mdoutliner/mdoutliner6
examples/windowsmanifest/windowsmanifest
examples/uidesigner/uidesigner
examples/trivialwizard6/trivialwizard6
examples/subclass/subclass
examples/libraries/extras-scintillaedit/extras-scintillaedit
examples/libraries/qt-multimedia/qt-multimedia
examples/libraries/qt-network/qt-network
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ Where Qt returns a C++ object by value (e.g. `QSize`), the binding may have move

The `connect(sourceObject, sourceSignal, targetObject, targetSlot)` is projected as `targetObject.onSourceSignal(func()...)`.

- You can also override virtual methods like PaintEvent in the same way. Your callback `func()` receives `super()` as a first argument that can be used to call the base class implementation.

Qt class inherited types are projected as a Go embedded struct. For example, to pass a `var myLabel *qt.QLabel` to a function taking only the `*qt.QWidget` base class, write `myLabel.QWidget`.

- When a Qt subclass adds a method overload (e.g. `QMenu::addAction(QString)` vs `QWidget::addAction(QAction*)`), the base class version is shadowed and can only be called via `myQMenu.QWidget.AddAction(QAction*)`.
Expand Down
78 changes: 58 additions & 20 deletions cmd/genbindings/clang2il.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,14 @@ func processTypedef(node map[string]interface{}, addNamePrefix string) (CppTyped
return CppTypedef{}, errors.New("processTypedef: ???")
}

type visibilityState int

const (
VsPublic visibilityState = 1
VsProtected = 2
VsPrivate = 3
)

// processClassType parses a single C++ class definition into our intermediate format.
func processClassType(node map[string]interface{}, addNamePrefix string) (CppClass, error) {
var ret CppClass
Expand Down Expand Up @@ -229,10 +237,10 @@ func processClassType(node map[string]interface{}, addNamePrefix string) (CppCla
}
}

// Check if this was 'struct' (default visible) or 'class' (default invisible)
visibility := true
// Check if this was 'struct' (default public) or 'class' (default private)
visibility := VsPublic
if tagUsed, ok := node["tagUsed"].(string); ok && tagUsed == "class" {
visibility = false
visibility = VsPrivate
}

// Check if this is an abstract class
Expand All @@ -257,7 +265,7 @@ func processClassType(node map[string]interface{}, addNamePrefix string) (CppCla

if typ, ok := base["type"].(map[string]interface{}); ok {
if qualType, ok := typ["qualType"].(string); ok {
ret.Inherits = append(ret.Inherits, qualType)
ret.DirectInherits = append(ret.DirectInherits, qualType)
}
}
}
Expand Down Expand Up @@ -289,9 +297,11 @@ nextMethod:

switch access {
case "public":
visibility = true
case "private", "protected":
visibility = false
visibility = VsPublic
case "protected":
visibility = VsProtected
case "private":
visibility = VsPrivate
default:
panic("unexpected access visibility '" + access + "'")
}
Expand All @@ -315,7 +325,7 @@ nextMethod:
// Child class type definition e.g. QAbstractEventDispatcher::TimerInfo
// Parse as a whole child class

if !visibility {
if visibility != VsPublic {
continue // Skip private/protected
}

Expand All @@ -341,8 +351,8 @@ nextMethod:
case "EnumDecl":
// Child class enum

if !visibility {
continue // Skip private/protected
if visibility == VsPrivate {
continue // Skip private, ALLOW protected
}

en, err := processEnum(node, nodename+"::")
Expand All @@ -359,7 +369,7 @@ nextMethod:
// This is an implicit ctor. Therefore the class is constructable
// even if we're currently in a `private:` block.

} else if !visibility {
} else if visibility != VsPublic {
continue // Skip private/protected
}

Expand Down Expand Up @@ -403,7 +413,8 @@ nextMethod:
continue
}

if !visibility {
if visibility != VsPublic {
// TODO Is there any use case for allowing MIQT to overload a virtual destructor?
ret.CanDelete = false
continue
}
Expand All @@ -415,21 +426,26 @@ nextMethod:
}

case "CXXMethodDecl":
if !visibility {
continue // Skip private/protected
}

// Check if this is `= delete`
if isExplicitlyDeleted(node) {
continue
}

// Method
methodName, ok := node["name"].(string)
if !ok {
return CppClass{}, errors.New("method has no name")
}

// If this is a virtual method, we want to allow overriding it even
// if it is protected
// But we can only call it if it is public
if visibility == VsPrivate {
ret.PrivateMethods = append(ret.PrivateMethods, methodName)
continue // Skip private, ALLOW protected
}

// Check if this is `= delete`
if isExplicitlyDeleted(node) {
continue
}

var mm CppMethod
mm.MethodName = methodName

Expand All @@ -445,6 +461,14 @@ nextMethod:
}

mm.IsSignal = isSignal && !mm.IsStatic && AllowSignal(mm)
mm.IsProtected = (visibility == VsProtected)

if mm.IsProtected && !mm.IsVirtual {
// Protected method, so we can't call it
// Non-virtual, so we can't override it
// There is nothing we can do with this function
continue nextMethod
}

// Once all processing is complete, pass to exceptions for final decision

Expand Down Expand Up @@ -648,6 +672,14 @@ func parseMethod(node map[string]interface{}, mm *CppMethod) error {
mm.IsStatic = true
}

if virtual, ok := node["virtual"].(bool); ok && virtual {
mm.IsVirtual = true
}

if pure, ok := node["pure"].(bool); ok && pure {
mm.IsPureVirtual = true
}

if methodInner, ok := node["inner"].([]interface{}); ok {
paramCounter := 0
for _, methodObj := range methodInner {
Expand Down Expand Up @@ -690,6 +722,12 @@ func parseMethod(node map[string]interface{}, mm *CppMethod) error {
// Next
paramCounter++

case "OverrideAttr":
// void keyPressEvent(QKeyEvent *e) override;
// This is a virtual method being overridden and is a replacement
// for actually using the 'virtual' keyword
mm.IsVirtual = true

default:
// Something else inside a declaration??
log.Printf("==> NOT IMPLEMENTED CXXMethodDecl->%q\n", methodObj["kind"])
Expand Down
15 changes: 14 additions & 1 deletion cmd/genbindings/config-allowlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ func ImportHeaderForClass(className string) bool {

func AllowClass(className string) bool {

if strings.HasSuffix(className, "Private") || strings.HasSuffix(className, "PrivateShared") {
if strings.HasSuffix(className, "Private") || strings.HasSuffix(className, "PrivateShared") ||
strings.Contains(className, "Private::") || strings.HasSuffix(className, "PrivateShared::") {
return false
}

Expand Down Expand Up @@ -186,6 +187,15 @@ func AllowSignal(mm CppMethod) bool {
}
}

func AllowVirtual(mm CppMethod) bool {

if mm.MethodName == "metaObject" || mm.MethodName == "qt_metacast" {
return false
}

return true // AllowSignal(mm)
}

func AllowMethod(className string, mm CppMethod) error {

for _, p := range mm.Parameters {
Expand Down Expand Up @@ -436,6 +446,9 @@ func AllowType(p CppParameter, isReturnType bool) error {
"QAbstractAudioBuffer", // Qt 5 Multimedia, this is a private/internal type only
"QAbstractVideoBuffer", // Works in Qt 5, but in Qt 6 Multimedia this type is used in qvideoframe.h but is not defined anywhere (it was later added in Qt 6.8)
"QRhi", // Qt 6 unstable types, used in Multimedia
"QPostEventList", // Qt QCoreApplication: private headers required
"QMetaCallEvent", // ..
"QPostEvent", // ..
"____last____":
return ErrTooComplex
}
Expand Down
Loading