From 7df6a5251d061ece039a2fe78f3e2a6fb5a4cc16 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Tue, 27 Oct 2026 17:46:02 +0100 Subject: [PATCH] Compiler Constants, #If/#Const: #Const applies to the end of its file (like #define); #If Win64 = 0 replaces Not CBool(Win64); WARNING BETA 1010 and queued bug ifdef-function-call-is-zero (a function call in #If is 0) (#250) --- BUGS-TO-REPORT.md | 134 ++++++++++++++++++ .../ifdef-function-call-is-zero.twinproj | Bin 0 -> 5302 bytes bugs/ifdef-function-call-is-zero/repro.json | 17 +++ bugs/ifdef-function-call-is-zero/src/Settings | 75 ++++++++++ .../src/Sources/RegressionTests.twin | 106 ++++++++++++++ .../src/Sources/Startup.twin | 6 + .../vb6/Module1.bas | 59 ++++++++ .../ifdef-function-call-is-zero/vb6/Probe.vbp | 7 + docs/Reference/Compiler Constants.md | 8 +- docs/Reference/Core/Topic-Preprocessor.md | 8 +- 10 files changed, 416 insertions(+), 4 deletions(-) create mode 100644 bugs/ifdef-function-call-is-zero/ifdef-function-call-is-zero.twinproj create mode 100644 bugs/ifdef-function-call-is-zero/repro.json create mode 100644 bugs/ifdef-function-call-is-zero/src/Settings create mode 100644 bugs/ifdef-function-call-is-zero/src/Sources/RegressionTests.twin create mode 100644 bugs/ifdef-function-call-is-zero/src/Sources/Startup.twin create mode 100644 bugs/ifdef-function-call-is-zero/vb6/Module1.bas create mode 100644 bugs/ifdef-function-call-is-zero/vb6/Probe.vbp diff --git a/BUGS-TO-REPORT.md b/BUGS-TO-REPORT.md index 5afd4d9f7..e9cc9724a 100644 --- a/BUGS-TO-REPORT.md +++ b/BUGS-TO-REPORT.md @@ -7911,3 +7911,137 @@ Severity: high for the crash: a program loses its unsaved data, and an error han A 64-bit build gives the same results. + +--- + +## A function call in an `#If`, `#ElseIf` or `#Const` expression is always 0, so `#If CBool(1)` is false + +**Describe the bug** +Every function call in a conditional compilation expression evaluates as 0, and the IDE reports nothing. `#If CBool(1) Then`, `#If CLng(1) = 1 Then`, `#If Abs(-1) = 1 Then`, `#If Len("abc") = 3 Then` and `#If Len(TWINBASIC_BUILD_TYPE) = 12 Then` (in a Standard EXE) all take the `#Else` branch, and `#Const X = CBool(1)` makes `X` 0. As a result `#If Not CBool(Win64) Then`, the usual way to select 32-bit-only code from a constant that is 1 rather than `True`, is true in a 64-bit build too. Observed in the compiler's test mode, in a `[RunAfterBuild]` run and in the DIAGNOSTICS list (a branch that declares a variable of a type that does not exist reports an error only when it is active), 32-bit and 64-bit. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open `ifdef-function-call-is-zero.twinproj` (attached as `ifdef-function-call-is-zero.zip`). +2. Run its test cases, the module below. On BETA 1010 the two controls pass and the seven cases with a function call fail with `Actual: False`; in a 64-bit build `If_NotCBoolOfWin64_AgreesWithWin64EqualsZero` fails too. + +The module is meant to be adapted as a regression test. Each case records which branch an `#If` takes and asserts the branch VB6 takes, which the attached VB6 project (`ifdef-function-call-is-zero-vb6.zip`) prints. +```vb +[TestFixture] +Module RegressionTests + + ' Controls: literals, constants and operators alone are evaluated. + [TestCase] + Public Sub If_LiteralComparison_IsTrue() + Dim taken As Boolean +#If 1 = 1 Then + taken = True +#End If + Assert.Strict.AreEqual True, taken + End Sub + + [TestCase] + Public Sub If_Win32EqualsOne_IsTrue() + Dim taken As Boolean +#If Win32 = 1 Then + taken = True +#End If + Assert.Strict.AreEqual True, taken + End Sub + + ' The defect: each call is 0. + [TestCase] + Public Sub If_CBoolOfOne_IsTrue() + Dim taken As Boolean +#If CBool(1) Then + taken = True +#End If + Assert.Strict.AreEqual True, taken + End Sub + + [TestCase] + Public Sub If_CBoolOfWin32_IsTrue() + Dim taken As Boolean +#If CBool(Win32) Then + taken = True +#End If + Assert.Strict.AreEqual True, taken + End Sub + + [TestCase] + Public Sub If_NotCBoolOfWin64_AgreesWithWin64EqualsZero() + Dim notCBool As Boolean, equalsZero As Boolean +#If Not CBool(Win64) Then + notCBool = True +#End If +#If Win64 = 0 Then + equalsZero = True +#End If + Assert.Strict.AreEqual equalsZero, notCBool + End Sub + + [TestCase] + Public Sub If_CLngOfOneEqualsOne_IsTrue() + Dim taken As Boolean +#If CLng(1) = 1 Then + taken = True +#End If + Assert.Strict.AreEqual True, taken + End Sub + + [TestCase] + Public Sub If_AbsOfMinusOneEqualsOne_IsTrue() + Dim taken As Boolean +#If Abs(-1) = 1 Then + taken = True +#End If + Assert.Strict.AreEqual True, taken + End Sub + + [TestCase] + Public Sub If_LenOfLiteralEqualsThree_IsTrue() + Dim taken As Boolean +#If Len("abc") = 3 Then + taken = True +#End If + Assert.Strict.AreEqual True, taken + End Sub + + [TestCase] + Public Sub If_LenOfBuildTypeEqualsTwelve_IsTrue() + ' The project is a Standard EXE: "Standard EXE" has 12 characters. + Dim taken As Boolean +#If Len(TWINBASIC_BUILD_TYPE) = 12 Then + taken = True +#End If + Assert.Strict.AreEqual True, taken + End Sub + +#Const FROM_CALL = CBool(1) + + [TestCase] + Public Sub Const_FromCBoolOfOne_IsTrue() + Dim taken As Boolean +#If FROM_CALL Then + taken = True +#End If + Assert.Strict.AreEqual True, taken + End Sub + +End Module +``` + +**Expected behavior** +Each call is evaluated, as VB6 evaluates it: `CBool(1)`, `CBool(Win32)`, `CLng(1) = 1`, `Abs(-1) = 1`, `Len("abc") = 3` and `#Const FROM_CALL = CBool(1)` are all true there. If twinBASIC is not meant to support them, the call should be a compile error rather than a silent 0. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1010 + +**Additional context** +Severity: medium. The wrong branch is compiled with no error or warning, so a program can call a 32-bit declaration in a 64-bit build, or leave out code it meant to keep. `#If Not CBool(Win64)` was the documentation's own advice until now. + +A 64-bit build gives the same results. Reported independently on BETA 1024 (docs issue twinbasic/documentation#250), with `VarType(...)` as well. + +What does not reproduce it: an expression of constants, literals and operators alone, such as `#If Win64 = 0`, `#If Win64 <> 1` or `#If Not (Win64 = 1)`, each true in a 32-bit build only, as it should be. + + diff --git a/bugs/ifdef-function-call-is-zero/ifdef-function-call-is-zero.twinproj b/bugs/ifdef-function-call-is-zero/ifdef-function-call-is-zero.twinproj new file mode 100644 index 0000000000000000000000000000000000000000..543ea4a3dad3b374cf7edad476d524937b6c7a91 GIT binary patch literal 5302 zcmd5=-EZ5-5!dDVaaOCh7Jcc%LJ1HDFa=7YCCk7)kQ8+gDo2K`i`!a(K~XDfbJVW7 zyNqKP{&V`?zpH;mXLd=8vYkVGfUgl?n)@*`JHPqZ8UFR(fB5e=mA_OfmEmGe7lVu^ znh74pX*yK@q*B1j=Jz{zeNp){Jcp~bkebdX*K0arGZ{-p)n;R*@>?9dsMO$j#8g7l zH0D%fTYHcl?D-C!$8qvge7Viy_jY*){+&=IGMQ|zzp(4L?eyX5x48UG#WtdG-yw;~A_s&dfCyon#GJewE(ra!&RLRGrZkyR zLO;f776T@GBv#~I(1E@4TJ<%&`ge{2d5=0Z9G~K2PD^y=kkM$Q9E|!UdVL z_>v2yS+YlzAT&;vM)ZuP;tKidWvmUMDj!tGKT!o@6nPn?iW0C5PnHc5A$=0VjHHQ> za|QIpDnTOD&73z@ib|j_t)n)Eaidl}M-T_>lg=c4U#lL8d6v==cn0HIwN@qYdrd;Y zwG=6!nKDgfoT@#N2(EO@K?aB>}O7@;AY9wg%UIGd#`A*b1l z3>Oz8%Q+OQH8`RY{9>pkGNZe1>~5s!vsJF9uSg(7N@HHDqAi=`d-$6yDU5L^{wyHh z6XXKBA~@!7v03&FM2%A|S)v=hq|v{!I5oTXEUn#(ba2K3ji1-zjPZ8MFj8YqpQM5z z{ngZXjbIT-7c|j_)-i2(fd77xy+Xu)ym+dXhHSU_<}0vOu7Sb*NtUuY7`m@PkGDc^ zROxsZKBPdZ&L9QB5j&psM!W~^hZ4^T$tIfi2*oHc*G6gD1{nwu_$-=jlPFAwWv38hQ3MUrJK;{>j2mFIJe zrqAD?h|%P1_%`rQhv7x=<8ai!nEc~7GLGGPcFbNvjR7^-;ADJs5&EMM7|N!aCfxrv zsDI|>HN+PKDOPzVd}>%o`3TJR&Hw5ojR0t3wD`7F$bk~gYhMmP>0sR$@+8= zXDPhz;lA7*8}WKQVLGL_<1_8YHo*+N{y5f4qt9~5cPNya!rqfq9$JW{3#w&Vz z@#@Bx$r8%L1k2Ui#;cnFOKJ2;Q;z+>?IZlLr9u8%fw6V&IaP^dYt#}qo={L|o3R&`6;UL-no&>@-xIThiDZh1~S^gMr^0gifd5={uh5 zx=zr7M}NN`w1Uu$x?T64IvgNC+AqRZ7=5BoYpAQ4e(WF;3O-3W=n1RmV8|V``p!;0 zKOr2c<$7(`ftUOd7H>G^x?abg5NOiGP`Rio42z?VM-lNeEmp>d3{$VQ= zpkWFd8E{jXcf}yE@CrnZC1?cH>}WVC4p;zq9*&QWhok6(9Q)zl{2!wC1lwJcnTZ3v zC^TfyC22Ne(2re{Tm;!22)R;k3U(XB%QF_wyBX}k6a+aQj0m2^K&K$o4wW`X!o3s7y6FZ?*&fab-RwgzwfpN z-a#}7!(YV&{(4O=Q!#^nF-f7 [!IMPORTANT] -> Compiler constants are not `Boolean` values, so a test such as `#If Not Win64 Then` does not do what it appears to. It is `True` in both 32-bit and 64-bit mode, where the intent is usually `False` under 64-bit, to select 32-bit-only code. To treat a constant as a `Boolean`, convert it with `CBool()`, as in `#If Not CBool(Win64) Then`. +> Compiler constants are numbers, not `Boolean` values. `Win64` is 1 in a 64-bit build, and `Not 1` is -2, which counts as true, so `#If Not Win64 Then` is true in both 32-bit and 64-bit builds. To select 32-bit-only code, compare the constant with 0, as in `#If Win64 = 0 Then`, or put the code in the `#Else` branch of `#If Win64 Then`. + + +> [!WARNING] BETA 1010 +> A function call in an `#If`, `#ElseIf` or `#Const` expression is always 0, with no error or warning. `#If CBool(1) Then` and `#If Len("abc") = 3 Then` are both false, and `#If Not CBool(Win64) Then` is true in a 64-bit build. VB6 evaluates `CBool`, `CLng`, `Abs` and `Len` there. > [!NOTE] > **`vbPtrSize` is not a compiler constant.** [**vbPtrSize**](../tB/Modules/Constants/#vbPtrSize) is a constant of the VBA package, **4** or **8** in code, but `#If` does not see it: like any name that is not a compiler constant it is 0 there, so `#If vbPtrSize = 8 Then` is false in a 64-bit build too. To choose code by pointer size, test [`Win64`](#win64). diff --git a/docs/Reference/Core/Topic-Preprocessor.md b/docs/Reference/Core/Topic-Preprocessor.md index 2f8943aad..7efd0027b 100644 --- a/docs/Reference/Core/Topic-Preprocessor.md +++ b/docs/Reference/Core/Topic-Preprocessor.md @@ -37,6 +37,10 @@ The directive's behaviour mirrors the runtime [**If...Then...Else**](If-Then-Els > [!NOTE] > The [**Option Compare**](Option) statement does not affect expressions in `#If`/`#ElseIf`. They are always evaluated as if **Option Compare Text** were in effect. + +> [!WARNING] BETA 1010 +> A function call in an `#If`, `#ElseIf` or `#Const` expression is always 0, with no error or warning. `#If CBool(1) Then` and `#If Len("abc") = 3 Then` are both false, and `#If Not CBool(Win64) Then` is true in a 64-bit build. VB6 evaluates `CBool`, `CLng`, `Abs` and `Len` there. To test whether a constant is 0, compare it with 0, as in `#If Win64 = 0 Then`. + ## #Const directive Syntax: @@ -49,7 +53,7 @@ Syntax: *expression* : A literal, another conditional compiler constant, or any combination using arithmetic or logical operators (except [**Is**](Is)). Standard runtime constants (declared with [**Const**](Const)) are *not* allowed here. -Conditional compiler constants declared with `#Const` are private to the module in which they appear. Project-wide conditional constants must be defined in the project's compilation settings --- `#Const` cannot create them. +A conditional compiler constant declared with `#Const` works like C's `#define`: it applies from the `#Const` line to the end of the `.twin` file that holds it, including any modules after it in that file. Lines above it, and other files, do not see it. In VB6 and VBA, where each file holds one module, that is the rest of the module. Project-wide conditional constants must be defined in the project's compilation settings --- `#Const` cannot create them. Conditional compiler constants are always evaluated at the module level regardless of where they appear in code; they can only be used in `#If`/`#ElseIf` expressions. @@ -57,7 +61,7 @@ Conditional compiler constants are always evaluated at the module level regardle twinBASIC provides a set of built-in compiler constants --- `Win64`, `Win32`, `TWINBASIC`, `TWINBASIC_BUILD`, `VBA7`, etc. See the dedicated [Compiler Constants](../../Reference/Compiler-Constants) page for the full list and what each one means. -`#Const` can redefine a built-in compiler constant. As with any `#Const`, the new value applies only in the module that declares it: after `#Const Win32 = 0`, `#If Win32` is false in that module and still true everywhere else. +`#Const` can redefine a built-in compiler constant. As with any `#Const`, the new value applies from the `#Const` line to the end of its file: after `#Const Win32 = 0`, `#If Win32` is false below that line, in that module and any after it in the same file, and still true above it and in every other file. ### Example