Skip to content

Commit c0ce6ad

Browse files
authored
Merge pull request #2892 from GaijinEntertainment/claude/silly-wing-55035c
class_super: CFG-aware super(...) lint + reject super-skip-intermediate
2 parents 4d0a63c + 5dc69e1 commit c0ce6ad

27 files changed

Lines changed: 826 additions & 183 deletions

doc/source/reference/language/classes.rst

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,17 @@ up the inheritance chain to the nearest ancestor that does:
169169
170170
Walk-up matches by argument types, so overloaded ``super(args)`` calls pick the closest
171171
ancestor whose constructor or method accepts those arguments. If no ancestor matches, the
172-
call is rejected at compile time.
172+
call is rejected at compile time. The walk-up may only step past a class that has no
173+
user-defined constructor (or method, in the case of ``super.method()``); attempting to
174+
skip a class whose user constructor (or method) would otherwise establish invariants is
175+
rejected — call the immediate parent and let it chain via its own ``super()``.
176+
177+
Every constructor in a derived class whose parent has a user-defined constructor must call
178+
``super(...)`` exactly once on every control-flow path. The lint is unconditional:
179+
180+
* zero ``super(...)`` calls on any reachable path → error;
181+
* two or more ``super(...)`` calls on any path → error;
182+
* ``super(...)`` inside a loop body → error (call count is not bounded to one).
173183

174184
Inside a derived class's finalizer (``operator delete``), ``delete super.self`` runs the
175185
parent's finalizer on the current object:
@@ -197,9 +207,6 @@ functions — see :ref:`Structs <structs>`.
197207
Base-class finalization is explicit, not automatic: a derived finalizer that omits
198208
``delete super.self`` will not run any ancestor finalizer.
199209

200-
The option ``always_call_super`` can be enabled to require ``super()`` in every constructor
201-
(see :ref:`Options <options>`).
202-
203210
Alternatively, the parent's method can be called directly using the backtick syntax:
204211

205212
.. code-block:: das

doc/source/reference/language/options.rst

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,6 @@ Lint Control
9292
- bool
9393
- true
9494
- Disallows writing to nameless (intermediate) variables on the stack.
95-
* - ``always_call_super``
96-
- bool
97-
- false
98-
- Requires ``super()`` to be called in every class constructor.
9995
* - ``no_unused_function_arguments``
10096
- bool
10197
- false

doc/source/reference/tutorials/18_classes.rst

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,67 @@ bypassing virtual dispatch::
7575
The compiler rewrites ``super()`` to ``Parent`Constructor(self, ...)`` and
7676
``super.method()`` to ``Parent`method(self, ...)``.
7777

78+
``super(...)`` is required exactly once per path
79+
================================================
80+
81+
Every constructor in a derived class whose parent has a user-defined constructor
82+
must call ``super(...)`` exactly once on every control-flow path. The lint catches:
83+
84+
::
85+
86+
class Bad : Base {
87+
def Bad(f : bool) {
88+
if (f) {
89+
super() // ERROR: false branch has zero super() calls
90+
}
91+
}
92+
}
93+
94+
class AlsoBad : Base {
95+
def AlsoBad {
96+
super()
97+
super() // ERROR: super() called more than once
98+
}
99+
}
100+
101+
A call to ``super(...)`` inside a loop is also rejected — the count is not
102+
bounded to one. To branch on arguments, both branches must call ``super(...)``::
103+
104+
class OK : Shape {
105+
def OK(big : bool) {
106+
if (big) {
107+
super("Big")
108+
} else {
109+
super("Small")
110+
}
111+
}
112+
}
113+
114+
``super.X()`` cannot skip an intermediate
115+
=========================================
116+
117+
``super.X()`` (whether ``X`` is a method or the name of an ancestor class) may
118+
walk past only intermediate classes that have **no** user-defined constructor or
119+
matching method. Skipping a class whose user code would otherwise establish
120+
invariants is rejected. Empty intermediates are still legal::
121+
122+
class A {
123+
def A { /* ... */ }
124+
}
125+
126+
class B : A {
127+
def B { super() } // B has its own ctor
128+
}
129+
130+
class C : B {
131+
def C {
132+
super.A() // ERROR: would silently skip B's invariants
133+
}
134+
}
135+
136+
If you really want only ``A``'s setup, name the immediate parent:
137+
``super.B()`` (or ``super()``) and let ``B``'s constructor chain to ``A`` itself.
138+
78139
Creating instances
79140
==================
80141

include/daScript/ast/ast.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1567,7 +1567,6 @@ namespace das
15671567
/*option*/ bool no_unsafe_uninitialized_structures = true; // if true, then unsafe uninitialized structures are not allowed
15681568
/*option*/ bool strict_properties = false; // if true, then properties are strict, i.e. a.prop = b does not get promoted to a.prop := b
15691569
/*option*/ bool no_writing_to_nameless = true; // if true, then writing to nameless variables (intermediate on the stack) is not allowed
1570-
/*option*/ bool always_call_super = false; // if true, then super() needs to be called from every class constructor
15711570
// environment
15721571
/*option*/ bool no_optimizations = false; // disable optimizations, regardless of settings
15731572
/*option*/ bool no_infer_time_folding = false; // disable infer-time constant folding

modules/dasClangBind/bind/bind_bgfx.das

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ class BgfxGen : CppGenBind {
2929
"-I{get_full_file_name(inc_bx)}"
3030
)
3131
func_per_chunk = 20
32-
init_args(pfn, pfp, args)
33-
setDefaultFiles()
32+
super(pfn, pfp, args)
3433
init_skip()
3534
openAllFiles()
3635
}
@@ -49,10 +48,7 @@ class BgfxGen : CppGenBind {
4948
}
5049
}
5150
def override skip_const(name : string) : bool {
52-
if (name |> starts_with("GL_")) {
53-
return true
54-
}
55-
return false
51+
return name |> starts_with("GL_")
5652
}
5753
def override skip_struct(name : string) {
5854
return bgfx_skip_struct |> key_exists(name)

modules/dasClangBind/bind/bind_clangbind.das

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ class ClangGen : CppGenBind {
1919
"-DCINDEX_EXPORTS"
2020
)
2121
func_per_chunk = 20
22-
init_args(pfn, pfp, args)
23-
setDefaultFiles()
22+
super(pfn, pfp, args)
2423
init_enum_prefix()
2524
openAllFiles()
2625
}
@@ -70,15 +69,7 @@ class ClangGen : CppGenBind {
7069
return true
7170
}
7271
def override skip_file(fname : string) : bool {
73-
if (fname |> ends_with("CXErrorCode.h")) {
74-
return false
75-
} elif (fname |> ends_with("CXString.h")) {
76-
return false
77-
} elif (fname |> ends_with("CXDiagnostic.h")) {
78-
return false
79-
} elif (fname |> ends_with("CXFile.h")) {
80-
return false
81-
} elif (fname |> ends_with("CXSourceLocation.h")) {
72+
if (fname |> ends_with("CXErrorCode.h") || fname |> ends_with("CXString.h") || fname |> ends_with("CXDiagnostic.h") || fname |> ends_with("CXFile.h") || fname |> ends_with("CXSourceLocation.h")) {
8273
return false
8374
}
8475
return !fname |> ends_with(PARSE_FILE_NAME)

modules/dasClangBind/bind/bind_llvm.das

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,7 @@ class LlvmGen : CppGenBind {
9494
let pfn = "cb_dasLLVM.h"
9595
let pfp = "{get_das_root()}/modules/dasLLVM/src/"
9696
func_per_chunk = 20
97-
init_args(pfn, pfp, args)
98-
setDefaultFiles()
97+
super(pfn, pfp, args)
9998
// init_enum_prefix()
10099
openAllFiles()
101100
}
@@ -137,7 +136,7 @@ class LlvmGen : CppGenBind {
137136

138137
class LlvmGenDinamic : DasGenBind {
139138
def LlvmGenDinamic(pfn, pfp : string; args : array<string>) {
140-
init_args(pfn, pfp, args)
139+
super(pfn, pfp, args)
141140
}
142141
def override skip_const(name : string) : bool {
143142
return name == "true" || name == "false"

modules/dasClangBind/bind/bind_opengl.das

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ require daslib/fio
88
class OpenGLBind : DasGenBind {
99
def OpenGLBind(pfn, pfp : string) {
1010
let args <- array<string>("-DGL_GLEXT_PROTOTYPES")
11-
init_args(pfn, pfp, args)
11+
super(pfn, pfp, args)
1212
init_defaults()
1313
}
1414
def init_defaults {
@@ -19,10 +19,7 @@ class OpenGLBind : DasGenBind {
1919
}
2020
}
2121
def override skip_const(name : string) : bool {
22-
if (name == "true" || name == "false") {
23-
return true
24-
}
25-
return false
22+
return name == "true" || name == "false"
2623
}
2724
def override functionArgumentRules : TypeRules {
2825
return (
@@ -47,10 +44,10 @@ class OpenGLBind : DasGenBind {
4744
[export]
4845
def main {
4946
let output_name = "{get_das_root()}/modules/dasOpenGL/opengl/opengl_func.das"
50-
fopen(output_name, "wb") <| $(fdf) {
47+
fopen(output_name, "wb") $(fdf) {
5148
fprint(fdf, "require dasbind public\n\n")
5249
let output_const_name = "{get_das_root()}/modules/dasOpenGL/opengl/opengl_const.das"
53-
fopen(output_const_name, "wb") <| $(dcf) {
50+
fopen(output_const_name, "wb") $(dcf) {
5451
let pfn = "glcorearb.h"
5552
let pfp = "{get_das_root()}/modules/dasOpenGL/opengl/OpenGL/"
5653
var glbind = new OpenGLBind(pfn, pfp)

modules/dasClangBind/bind/bind_quirrel.das

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@ class QuirrelGen : CppGenBind {
3535
"-DCINDEX_EXPORTS"
3636
)
3737
func_per_chunk = 20
38-
init_args(pfn, pfp, args)
39-
setDefaultFiles()
38+
super(pfn, pfp, args)
4039
// init_enum_prefix()
4140
openAllFiles()
4241
for (h in quirrel_c_headers) {
@@ -58,30 +57,21 @@ class QuirrelGen : CppGenBind {
5857
}
5958
def override skip_file(fname : string) : bool {
6059
let ufname = to_upper(fname)
61-
if (!any <| [iterator for(h in quirrel_c_headers); ufname |> ends_with(h)]) {
62-
return true
63-
}
64-
return false
60+
return !any <| [iterator for(h in quirrel_c_headers); ufname |> ends_with(h)]
6561
}
6662
def override skip_alias(var c : CXCursor) {
6763
let aliasn = string(clang_getCursorSpelling(c))
6864
if (aliasn == "SQPRINTFUNCTION") {
6965
return true
7066
}
7167
let aliasf = to_upper(clang_getCursorLocationFileName(c))
72-
if (!any <| [iterator for(h in quirrel_c_headers); aliasf |> ends_with(h)]) {
73-
return true
74-
}
75-
return false
68+
return !any <| [iterator for(h in quirrel_c_headers); aliasf |> ends_with(h)]
7669
}
7770
def override skip_enum(ns_en, en : string) {
7871
return false
7972
}
8073
def override skip_struct(sn : string) {
81-
if (sn |> starts_with("__crt")) {
82-
return true
83-
}
84-
return false
74+
return sn |> starts_with("__crt")
8575
}
8676
def override skip_function(var c : CXCursor) : bool {
8777
/*
@@ -91,13 +81,7 @@ class QuirrelGen : CppGenBind {
9181
return true
9282
*/
9383
let funf = to_upper(clang_getCursorLocationFileName(c))
94-
if (!any <| [iterator for(h in quirrel_c_headers); funf |> ends_with(h)]) {
95-
return true
96-
}
97-
if (skip_anyFunction(c, false)) {
98-
return true
99-
}
100-
return false
84+
return !any <| [iterator for(h in quirrel_c_headers); funf |> ends_with(h)] || skip_anyFunction(c, false)
10185
}
10286
}
10387

modules/dasClangBind/bind/bind_sfml.das

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@ class SFMLGen : CppGenBind {
3131
"-I{get_full_file_name(isfml)}"
3232
)
3333
func_per_chunk = 100
34-
init_args(pfn, pfp, args)
35-
setDefaultFiles()
34+
super(pfn, pfp, args)
3635
init_skip()
3736
openAllFiles()
3837
}
@@ -84,10 +83,7 @@ class SFMLGen : CppGenBind {
8483
return false
8584
}
8685
def override skip_struct(name : string) {
87-
if (name == "HWND__") {
88-
return true
89-
}
90-
return false
86+
return name == "HWND__"
9187
}
9288
}
9389

0 commit comments

Comments
 (0)