From 8258a108f19d79b1fd468774cb51ebc29c0f5ed1 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Mon, 2 Nov 2026 20:47:07 +0100 Subject: [PATCH 01/19] FAQ on BETA 1034: entries for binary compatibility with a VB6 build, VB6's own OCXs (SysWOW64, design-time licences from .srg files) and whether packages are reviewed; bug reports (BETA number, minimal project, trace log), the Help add-in, TypeLibrary TLB and the Object Browser added; Build Type, Startup Object and TWINBASIC_BUILD_TYPE list TypeLibrary TLB; RunAfterBuild ignored and RunBeforeStartupObject run in a package --- docs/IDE/Project Settings.md | 4 +- docs/Miscellaneous/FAQs.md | 75 +++++++++++++++++++++++++++- docs/Reference/Attributes.md | 15 +++++- docs/Reference/Compiler Constants.md | 2 +- 4 files changed, 90 insertions(+), 6 deletions(-) diff --git a/docs/IDE/Project Settings.md b/docs/IDE/Project Settings.md index 9585cf028..1435c5d6b 100644 --- a/docs/IDE/Project Settings.md +++ b/docs/IDE/Project Settings.md @@ -69,7 +69,7 @@ See [Packages](../../Features/Packages/). [References and packages](Projects/Ref ### Build Type {: index_also="standard exe; create an ActiveX DLL; create ActiveX DLL" } -The type of file the compiler creates: **Standard EXE**, **ActiveX DLL**, **ActiveX Control**, **Standard DLL** or **Package TWINPACK**. [Project Types](../../Features/Project-Configuration/Project-Types) describes the Standard DLL, and code can test the setting with the [`TWINBASIC_BUILD_TYPE`](../../Reference/Compiler-Constants#twinbasic_build_type) compiler constant. In the `Settings` file it is `project.buildType`. +The type of file the compiler creates: **Standard EXE**, **ActiveX DLL**, **ActiveX Control**, **Standard DLL**, **Package TWINPACK** or **TypeLibrary TLB**. **TypeLibrary TLB**, experimental since BETA 1018, builds a type library, a `.tlb` file, from the interfaces, coclasses, enumerations, types, modules and classes the project declares. [Project Types](../../Features/Project-Configuration/Project-Types) describes the Standard DLL, and code can test the setting with the [`TWINBASIC_BUILD_TYPE`](../../Reference/Compiler-Constants#twinbasic_build_type) compiler constant. In the `Settings` file it is `project.buildType`. ### Build Output Path @@ -144,7 +144,7 @@ The full path of the host EXE that the compiler creates for [Fusion](../../Featu ### Startup Object -The form that the program shows when it starts, or **Sub Main**. When it is not set, the default is **Sub Main** for an EXE and **(none)** for a DLL. For a Standard EXE the list does not offer **(none)**. In the `Settings` file it is `project.startupObject`. +The form that the program shows when it starts, or **Sub Main**. When it is not set, the default is **Sub Main** for an EXE and **(none)** for a DLL. For a Standard EXE the list does not offer **(none)**, and for a Package TWINPACK or a TypeLibrary TLB it offers nothing else. In the `Settings` file it is `project.startupObject`. ### Icon Form diff --git a/docs/Miscellaneous/FAQs.md b/docs/Miscellaneous/FAQs.md index 253554164..155d9be34 100644 --- a/docs/Miscellaneous/FAQs.md +++ b/docs/Miscellaneous/FAQs.md @@ -56,9 +56,9 @@ Yes, see the [twinBASIC Roadmap](https://twinbasic.com/roadmap) on the twinBASIC
What new features does twinBASIC have compared to VB6? -**Many!** It has 64bit compilation (using VBA7x64 compatible syntax), generics, overloading, [multithreading](Features/Advanced/Multithreading) (through the API for now; built-in syntax is on the roadmap for 2027), inheritance, ability to define interfaces and coclasses in your project using BASIC-style syntax, Unicode support in all controls and the editor (.twin files only), support for modern image formats, numerous enhancements to *Implements*, ability to create standard DLLs and kernel mode drivers, ability to set UDT packing alignment, and dozens of others, all available *right now*, with many more planned in the future. +**Many!** It has 64bit compilation (using VBA7x64 compatible syntax), generics, overloading, [multithreading](Features/Advanced/Multithreading) (through the API for now; built-in syntax is on the roadmap for 2027), inheritance, ability to define interfaces and coclasses in your project using BASIC-style syntax, Unicode support in all controls and the editor (.twin files only), support for modern image formats, numerous enhancements to *Implements*, ability to create standard DLLs and kernel mode drivers, ability to set UDT packing alignment, and dozens of others, all available *right now*, with many more planned in the future. Since BETA 1018 it can also build a type library on its own, a `.tlb` file, from the interfaces, coclasses, enumerations and types a project declares in BASIC: the [**TypeLibrary TLB**](tB/IDE/Project-Settings#build-type) build type, which is still experimental. -For a full list of all the new features available right now, see the [Features](Features/) section of this documentation. +For a full list of all the new features available right now, see the [Features](Features/) section of this documentation. Since BETA 1023 the IDE also has an [Object Browser](tB/IDE/Code/Object-Browser), which F2 opens, as in VB6.
@@ -133,6 +133,14 @@ The best way is to [create an issue](https://github.com/twinbasic/twinbasic/issu You can also create a post in the #bugs channel of the [twinBASIC Discord server](https://discord.gg/UaW9GgKKuE). +A bug can be fixed only once it can be reproduced, so a report needs: + +- the twinBASIC version, which **Help → About twinBASIC...** shows as *twinBASIC IDE BETA* and a number, and the version of Windows; +- the smallest project that shows the problem, ready to run: attach the `.twinproj` file, or paste the code; +- the steps that make the problem appear, what you expected, and what happened instead. + +For a program that crashes, also attach a trace log. In Project Settings, tick every box under [Trace Flags](tB/IDE/Project-Settings#trace-flags) except **Buffered Trace Log File Writing**, which is not for tracing a crash, and set [Trace Output](tB/IDE/Project-Settings#trace-output) to the path of a file that includes `${SESSIONID}`. Then run the program until it crashes. With `${DEBUG}` as the trace output, the log goes to the DEBUG CONSOLE instead. [The trace logger](tB/IDE/Debugging/Debug-Console#the-trace-logger) describes the log. +
@@ -303,6 +311,56 @@ For an existing project, the Project Scope Option Explicit can be turned on or o
+
+How do I keep an ActiveX DLL or control binary compatible with its VB6 build? + +twinBASIC has no **Binary Compatibility** setting like VB6's. A program that uses a COM component finds its type library and its classes by their GUIDs, so a twinBASIC build that is to replace the VB6 one must have the same GUIDs. Unless the project gives them, every build generates new ones. + +- Give each public class the GUIDs of the VB6 class: [`[ClassId]`](tB/Core/Attributes#classid) for the class, [`[InterfaceId]`](tB/Core/Attributes#interfaceid) for its interface and [`[EventInterfaceId]`](tB/Core/Attributes#eventinterfaceid) for its event interface. Give each public **Enum** its GUID with [`[EnumId]`](tB/Core/Attributes#enumid); without it, the enumeration has no GUID in the type library. +- Set [Project ID](tB/IDE/Project-Settings#project-id) to the GUID of the VB6 type library, and [Use Project ID for type library ID](tB/IDE/Project-Settings#use-project-id-for-type-library-id) to **Yes**. With **No**, the default, the type library gets a new GUID at every build. Set its version too, under [Type Library Version](tB/IDE/Project-Settings#type-library-version). + +```tb check_build +[ClassId("6F1B2D4A-3C5E-4A7B-9D2F-1E3C5A7B9D01")] +[InterfaceId("6F1B2D4A-3C5E-4A7B-9D2F-1E3C5A7B9D02")] +[EventInterfaceId("6F1B2D4A-3C5E-4A7B-9D2F-1E3C5A7B9D03")] +Public Class Customer + [EnumId("6F1B2D4A-3C5E-4A7B-9D2F-1E3C5A7B9D04")] + Public Enum CustomerKind + ckPerson = 1 + ckCompany = 2 + End Enum + + Public Event Changed() + + Public Kind As CustomerKind +End Class +``` + +The OLE/COM Object Viewer of the Windows SDK (`oleview.exe`) shows the GUIDs of the VB6 build: **File → View TypeLib...** opens the type library of its DLL or OCX. + +The attributes go in front of the `Class` line, which only a `.twin` file has: in a `.cls` file a `[ClassId]` line is a syntax error, TB5182. So move each public class of a `.cls` file into a `.twin` file, between `Class` and `End Class` lines. The UserControls and property pages of an imported project are already in `.twin` files, where the import gave them new GUIDs: replace those with the VB6 ones. + +
+ +
+Can I use VB6's own ActiveX controls, such as MSCOMCTL.OCX? + +Yes, in a 32-bit build. The controls that ship with VB6 are 32-bit, and a 64-bit program cannot load them, except through [Fusion](Features/Fusion), which hosts some 32-bit controls in a helper process. The [WinNativeCommonCtls](tB/Packages/WinNativeCommonCtls/) package has 64-bit versions of eight of the Common Controls. + +Each control must be registered on the computer. On 64-bit Windows, VB6 puts its controls in `C:\Windows\SysWOW64`, and a 32-bit control is registered from there, from a command prompt run as administrator: + +```batch +C:\Windows\SysWOW64\regsvr32.exe C:\Windows\SysWOW64\MSCOMCTL.OCX +``` + +Then add the control's library to the project as a COM reference, as [Add ActiveX controls and components](tB/IDE/Forms/Toolbox#add-activex-controls-and-components) describes, and its controls are in the Toolbox. + +Most of VB6's controls are licensed: a development environment can put one on a form only when the control's design-time licence is in the registry. Installing VB6 installs the licences, so without VB6 the twinBASIC form designer cannot create the controls. The licences are on the VB6 or Visual Studio 6 CD, as `.srg` files in its `OS\SYSTEM` folder, most of them named like the control's file; **MSFLXGRD.OCX**'s is `VSDBFLEX.SRG`. An `.srg` file is a registry file without its first line. Add the line `Windows Registry Editor Version 5.00` and an empty line at its top, save it with the extension `.reg`, and open it to add the licence to the registry. + +A program built with these controls needs them registered on every computer it runs on, as a VB6 program does. + +
+
Does twinBASIC support addins? @@ -319,6 +377,19 @@ There are two locations that the addins can be installed to. Each has a `win32` The IDE loads the addins in the folder that matches the project's build target: `win32`, unless the project is set to build for win64. An addin that should load either way needs a build of each. See [Add Ins](tB/IDE/AddIns/) for more. +This documentation has an addin of its own, the [Help Add-In](tB/IDE/AddIns/Help): press F1 on a name in the code editor, and the page that documents the name opens in a pane of the IDE. [Getting the add-in](tB/IDE/AddIns/Help#getting-the-add-in) says how to build and install it. + +
+ +
+Are the packages in the Available Packages list reviewed or endorsed? + +The packages that ship with twinBASIC show `[BUILT-IN]` as their publisher in the [list](tB/IDE/Projects/References-and-Packages#available-packages). Every other package there is published on TWINSERV by its author, and anyone with a TWINSERV account can publish one, as [Creating a TWINPACK Package](Features/Packages/Creating-TWINPACK) describes. Those packages are not part of twinBASIC. The list shows who published a package, and nothing in it marks a package as reviewed or endorsed. + +A package is not compiled code but twinBASIC source, so you can read all of it before you build: a package that the project embeds has a folder under **Packages** in the [Project Explorer](tB/IDE/Projects/Project-Explorer). Its code runs as part of your program. A [`[RunAfterBuild]`](tB/Core/Attributes#runafterbuild) procedure in a package does not run, so building a project runs nothing from its packages. A [`[RunBeforeStartupObject]`](tB/Core/Attributes#runbeforestartupobject) procedure in a package does run, in the built program, before the startup object, even when nothing in the project calls the package. + +This documentation describes a number of the packages under [Contributed Packages](tB/Packages/Contributed/), each with its defects, and lists the ones that do not compile or have defects a program would meet under [Experimental Packages](tB/Packages/Experimental/). +
diff --git a/docs/Reference/Attributes.md b/docs/Reference/Attributes.md index 0db97931e..99e169a25 100644 --- a/docs/Reference/Attributes.md +++ b/docs/Reference/Attributes.md @@ -1210,7 +1210,13 @@ Applicable to: [**Function**](Function), [**Sub**](Sub), [**Property Get**](Prop Specifies a function that runs after your exe is built. There's `App.LastBuildPath` to know where it is if you're e.g. signing the executable. -Only one **[RunAfterBuild]** is allowed per project. A second one is a compile error. +Only one **[RunAfterBuild]** is allowed per project. A second one is a compile error. A **[RunAfterBuild]** procedure in a package that the project references does not run, and does not count as the project's one. + + The procedure runs whatever the project's modules and procedures are called: a module may have the project's name, and the module that holds the procedure may also hold a procedure with the module's own name, such as a `Sub Probe` in a module `Probe`. @@ -1236,6 +1242,13 @@ Runs the function before the project's startup object. Returning **True** suppre A **Function** is the usual form, returning a **Boolean**. A **Function** of another type, or a **Property Get**, works the same way: a result of 0 or **Nothing** lets startup proceed, and any other result suppresses the startup object. A **Sub** runs too, and then the startup object never runs. +A procedure with the attribute in a package that the project references runs too, before the startup object, whether or not the project calls the package. + + + > [!NOTE] BETA 1010 > The attribute is not allowed on a generic procedure, such as `Sub Pre(Of T)()`. The project compiles in the editor without an error, but building it fails with the message *Build FAILED due to invalid RunBeforeStartupObject procedure*, which names neither the procedure nor the reason. diff --git a/docs/Reference/Compiler Constants.md b/docs/Reference/Compiler Constants.md index c96d08118..45c8e16d3 100644 --- a/docs/Reference/Compiler Constants.md +++ b/docs/Reference/Compiler Constants.md @@ -60,7 +60,7 @@ Each value below is the one code sees unless a `#Const` redefines the constant. ### `TWINBASIC_BUILD_TYPE` **Purpose:** Allows conditional compilation based on whether the project is an exe, dll, or ocx.\ -**Value:** A `String` that can be one of "Standard EXE", "Standard DLL", "ActiveX DLL", or "ActiveX Control", determined by the "Build Type" option in Project Settings. +**Value:** A `String` that can be one of "Standard EXE", "Standard DLL", "ActiveX DLL", "ActiveX Control", "Package TWINPACK" or "TypeLibrary TLB", determined by the "Build Type" option in Project Settings. ## Usage From 5e8e4a7ff436019e69f132c1535a4f42f84cfbea Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Mon, 2 Nov 2026 22:17:17 +0100 Subject: [PATCH 02/19] Callouts rechecked on BETA 1034 and relabelled from 1010/1005 (contributed sections still to come); 14 filed defects fixed in 1034 retired with their callouts and stated as correct behaviour with run samples (ReDim inverted bounds and empty range, CDate, Val suffix, Abs, Round(Null), String(n, Null), Sin/Cos from 2^63, DateAdd month end, TextBox MaxLength, Err.Raise HelpContext:=, LLVM rounding and ByVal-to-ByRef, WebView2 header Clone); RunAfterBuild only on a parameterless Sub (TB5155); LSet/RSet Variant and trig partly fixed; error data at beta 1034; LLVM claims probe test/llvm with Double overflow and For-Step WARNINGs corrected --- BUGS-TO-REPORT.md | 2 +- WIP.Errors.md | 14 +- WIP.md | 2 +- bugs/filed/abs-minimum-widens-type/REPORT.md | 90 ---- .../abs-minimum-widens-type.twinproj | Bin 4483 -> 0 bytes bugs/filed/abs-minimum-widens-type/repro.json | 15 - .../abs-minimum-widens-type/src/Settings | 75 ---- .../src/Sources/RegressionTests.twin | 61 --- .../abs-minimum-widens-type/vb6/Module1.bas | 22 - .../abs-minimum-widens-type/vb6/Probe.vbp | 7 - .../REPORT.md | 95 ---- ...cdate-string-decimal-out-of-range.twinproj | Bin 4682 -> 0 bytes .../repro.json | 17 - .../src/Settings | 75 ---- .../src/Sources/RegressionTests.twin | 66 --- .../src/Sources/Startup.twin | 6 - .../vb6/Module1.bas | 26 -- .../vb6/Probe.vbp | 7 - .../dateadd-month-end-overflow/REPORT.md | 59 --- .../dateadd-month-end-overflow.twinproj | Bin 4224 -> 0 bytes .../dateadd-month-end-overflow/repro.json | 27 -- .../dateadd-month-end-overflow/src/Settings | 61 --- .../src/Sources/Startup.twin | 35 -- .../vb6/Module1.bas | 40 -- .../dateadd-month-end-overflow/vb6/Probe.vbp | 7 - bugs/filed/err-raise-helpcontext/REPORT.md | 40 -- .../err-raise-helpcontext.twinproj | Bin 2602 -> 0 bytes bugs/filed/err-raise-helpcontext/repro.json | 9 - bugs/filed/err-raise-helpcontext/src/Settings | 61 --- .../src/Sources/Probe.twin | 9 - .../src/Sources/Startup.twin | 6 - .../err-raise-helpcontext/vb6/Module1.bas | 34 -- .../filed/err-raise-helpcontext/vb6/Probe.vbp | 7 - .../llvm-byval-to-byref-sub-codegen/REPORT.md | 48 -- .../llvm-byval-to-byref-sub-codegen.twinproj | Bin 3255 -> 0 bytes .../repro.json | 15 - .../src/Settings | 61 --- .../src/Sources/Startup.twin | 33 -- .../REPORT.md | 35 -- .../llvm-conversion-rounds-half-away.twinproj | Bin 3472 -> 0 bytes .../repro.json | 13 - .../src/Settings | 61 --- .../src/Sources/Startup.twin | 31 -- .../vb6/Module1.bas | 23 - .../vb6/Probe.vbp | 7 - .../llvm-double-overflow-no-error/REPORT.md | 2 +- .../lset-variant-destination-types/REPORT.md | 2 +- .../lset-variant-destination-types.twinproj | Bin 2643 -> 3071 bytes .../lset-variant-destination-types/repro.json | 6 +- .../src/Sources/Startup.twin | 17 +- .../vb6/Module1.bas | 17 +- .../REPORT.md | 52 --- ...dim-empty-range-unspecified-error.twinproj | Bin 4663 -> 0 bytes .../repro.json | 34 -- .../src/Settings | 61 --- .../src/Sources/Startup.twin | 69 --- .../vb6/Module1.bas | 78 ---- .../vb6/Probe.vbp | 7 - .../redim-inverted-bounds-error/REPORT.md | 91 ---- .../redim-inverted-bounds-error.twinproj | Bin 4465 -> 0 bytes .../redim-inverted-bounds-error/repro.json | 16 - .../redim-inverted-bounds-error/src/Settings | 75 ---- .../src/Sources/RegressionTests.twin | 62 --- .../src/Sources/Startup.twin | 6 - .../vb6/Module1.bas | 30 -- .../redim-inverted-bounds-error/vb6/Probe.vbp | 7 - .../round-null-negative-digits/REPORT.md | 69 --- .../round-null-negative-digits/repro.json | 13 - .../round-null-negative-digits.twinproj | Bin 3804 -> 0 bytes .../src/Sources/RegressionTests.twin | 40 -- .../src/Sources/Startup.twin | 6 - .../vb6/Module1.bas | 21 - .../round-null-negative-digits/vb6/Probe.vbp | 7 - .../REPORT.md | 41 -- .../repro.json | 15 - ...afterbuild-generic-sub-never-runs.twinproj | Bin 2577 -> 0 bytes .../src/Settings | 61 --- .../src/Sources/Startup.twin | 15 - bugs/filed/string-null-character/REPORT.md | 89 ---- bugs/filed/string-null-character/repro.json | 16 - .../src/Sources/RegressionTests.twin | 60 --- .../src/Sources/Startup.twin | 6 - .../string-null-character.twinproj | Bin 4359 -> 0 bytes .../string-null-character/vb6/Module1.bas | 23 - .../filed/string-null-character/vb6/Probe.vbp | 7 - .../textbox-text-ignores-maxlength/REPORT.md | 34 -- .../textbox-text-ignores-maxlength/repro.json | 11 - .../src/Settings | 61 --- .../src/Sources/Form1.tbform | 153 ------- .../src/Sources/Form1.twin | 9 - .../src/Sources/Startup.twin | 15 - .../textbox-text-ignores-maxlength.twinproj | Bin 6582 -> 0 bytes .../vb6/Form1.frm | 26 -- .../vb6/Module1.bas | 28 -- .../vb6/Probe.vbp | 8 - bugs/filed/trig-above-2-to-63/REPORT.md | 2 +- bugs/filed/trig-above-2-to-63/repro.json | 6 +- .../val-suffix-overflow-no-error/REPORT.md | 88 ---- .../val-suffix-overflow-no-error/repro.json | 16 - .../val-suffix-overflow-no-error/src/Settings | 75 ---- .../src/Sources/RegressionTests.twin | 59 --- .../src/Sources/Startup.twin | 6 - .../val-suffix-overflow-no-error.twinproj | Bin 4391 -> 0 bytes .../vb6/Module1.bas | 25 -- .../vb6/Probe.vbp | 7 - .../REPORT.md | 180 -------- .../repro.json | 15 - .../src/Settings | 89 ---- .../src/Sources/Form1.tbform | 146 ------ .../src/Sources/Form1.twin | 32 -- .../src/Sources/HeaderEnumerationTests.twin | 125 ------ .../src/Sources/Startup.twin | 6 - ...iew2-response-headers-reset-clone.twinproj | Bin 13395 -> 0 bytes bugs/rate-fails/repro.json | 4 +- docs/Features/Advanced/API-Declarations.md | 4 +- docs/Features/Advanced/Classes-and-Modules.md | 2 +- .../Compiler-IDE/Compiler-Warnings.md | 4 +- docs/Features/Language/UDTs.md | 4 +- docs/IDE/Building/ActiveX-Registration.md | 4 +- docs/IDE/Building/Build-Output.md | 4 +- docs/IDE/Building/Build-and-Clean.md | 2 +- docs/IDE/Building/LLVM-in-the-IDE.md | 2 +- docs/IDE/Building/Package-Publishing.md | 6 +- docs/IDE/Code/Diagnostics.md | 2 +- docs/IDE/Code/Editor.md | 8 +- docs/IDE/Code/Finding.md | 10 +- docs/IDE/Code/Navigating.md | 4 +- docs/IDE/Customising/Themes.md | 2 +- .../IDE/Debugging/Breakpoints-and-Stepping.md | 10 +- docs/IDE/Debugging/Debug-Console.md | 4 +- docs/IDE/Debugging/Inspecting.md | 6 +- docs/IDE/Debugging/Memory.md | 8 +- docs/IDE/Debugging/Running.md | 8 +- docs/IDE/Forms/Arranging.md | 10 +- docs/IDE/Forms/Controls.md | 2 +- docs/IDE/Forms/Designer.md | 18 +- docs/IDE/Forms/Properties.md | 4 +- docs/IDE/Forms/Toolbox.md | 2 +- docs/IDE/Menu/Format.md | 2 +- docs/IDE/Projects/Adding-Files.md | 8 +- docs/IDE/Projects/Files-and-Folders.md | 18 +- docs/IDE/Projects/Project-Explorer.md | 4 +- docs/IDE/Projects/References-and-Packages.md | 10 +- docs/IDE/Projects/Saving-and-Exporting.md | 4 +- docs/IDE/Reports/index.md | 2 +- docs/LLVM/Getting-Started.md | 24 +- docs/Reference/Attributes.md | 41 +- .../WebView2/WebView2HeadersCollection.md | 5 +- .../WinNativeCommonCtls/ListView/ListItem.md | 2 +- .../WinNativeCommonCtls/ListView/index.md | 2 +- .../Built-In/WinNativeCommonCtls/Slider.md | 2 +- .../Built-In/tbIDE/FileSystemItem.md | 2 +- docs/Reference/Built-In/tbIDE/HtmlElement.md | 2 +- .../Built-In/tbIDE/HtmlElementProperties.md | 2 +- .../Built-In/tbIDE/KeyboardShortcuts.md | 2 +- docs/Reference/COM-Interfaces/IDispatch.md | 2 +- docs/Reference/COM-Interfaces/IErrorInfo.md | 4 +- docs/Reference/COM-Interfaces/IUnknown.md | 4 +- docs/Reference/Compiler Constants.md | 2 +- docs/Reference/Core/Call.md | 18 +- docs/Reference/Core/Const.md | 4 +- docs/Reference/Core/Declare.md | 2 +- docs/Reference/Core/Divide.md | 2 +- docs/Reference/Core/Error.md | 2 +- docs/Reference/Core/For-Each-Next.md | 2 +- docs/Reference/Core/For-Next.md | 4 +- docs/Reference/Core/Function.md | 2 +- docs/Reference/Core/Get.md | 2 +- docs/Reference/Core/GoSub-Return.md | 2 +- docs/Reference/Core/Graphics-Methods.md | 6 +- docs/Reference/Core/LSet.md | 16 +- docs/Reference/Core/LeftShift.md | 2 +- docs/Reference/Core/Line-Input.md | 2 +- docs/Reference/Core/Minus.md | 2 +- docs/Reference/Core/Multiply.md | 2 +- docs/Reference/Core/Name.md | 2 +- docs/Reference/Core/On-Error.md | 2 +- docs/Reference/Core/Open.md | 2 +- docs/Reference/Core/ParamArray.md | 2 +- docs/Reference/Core/Plus.md | 2 +- docs/Reference/Core/Print.md | 2 +- docs/Reference/Core/Put.md | 2 +- docs/Reference/Core/RSet.md | 14 +- docs/Reference/Core/ReDim.md | 6 +- docs/Reference/Core/RightShift.md | 2 +- docs/Reference/Core/Topic-Preprocessor.md | 2 +- docs/Reference/Core/Type.md | 8 +- docs/Reference/Data-Types.md | 2 +- docs/Reference/Data-Types/Date.md | 2 +- docs/Reference/Data-Types/Double.md | 6 +- docs/Reference/Data-Types/LongLong.md | 4 +- docs/Reference/Data-Types/Object.md | 2 +- docs/Reference/Data-Types/Single.md | 2 +- docs/Reference/Default/VB/Data/index.md | 4 +- docs/Reference/Default/VB/MultiFrame/index.md | 2 +- docs/Reference/Default/VB/OLE/index.md | 2 +- docs/Reference/Default/VB/TextBox/index.md | 15 +- docs/Reference/Default/VBA/Collection/Add.md | 8 +- docs/Reference/Default/VBA/Collection/Item.md | 6 +- .../Default/VBA/Collection/Remove.md | 4 +- .../Reference/Default/VBA/Conversion/CDate.md | 15 +- docs/Reference/Default/VBA/Conversion/CLng.md | 8 + .../Default/VBA/Conversion/CLngLng.md | 2 +- docs/Reference/Default/VBA/Conversion/CStr.md | 2 +- .../Reference/Default/VBA/Conversion/Error.md | 2 +- docs/Reference/Default/VBA/Conversion/Str.md | 2 +- docs/Reference/Default/VBA/Conversion/Val.md | 5 +- .../Default/VBA/DateTime/Calendar.md | 2 +- docs/Reference/Default/VBA/DateTime/Date.md | 8 +- .../Reference/Default/VBA/DateTime/DateAdd.md | 20 +- .../Default/VBA/DateTime/DateDiff.md | 6 +- .../Default/VBA/DateTime/DatePart.md | 10 +- docs/Reference/Default/VBA/DateTime/Time.md | 6 +- docs/Reference/Default/VBA/DateTime/index.md | 5 +- .../Default/VBA/ErrObject/Description.md | 2 +- .../Default/VBA/ErrObject/HelpContext.md | 2 +- .../Default/VBA/ErrObject/HelpFile.md | 2 +- .../Reference/Default/VBA/ErrObject/Number.md | 2 +- docs/Reference/Default/VBA/ErrObject/Raise.md | 6 +- .../Default/VBA/ErrObject/ReturnHResult.md | 2 +- .../Reference/Default/VBA/ErrObject/Source.md | 8 +- .../Reference/Default/VBA/FileSystem/ChDir.md | 2 +- .../Default/VBA/FileSystem/ChDrive.md | 4 +- .../Default/VBA/FileSystem/CurDir.md | 6 +- docs/Reference/Default/VBA/FileSystem/Dir.md | 8 +- docs/Reference/Default/VBA/FileSystem/EOF.md | 2 +- .../Default/VBA/FileSystem/FileAttr.md | 4 +- .../Default/VBA/FileSystem/FileCopy.md | 10 +- .../Default/VBA/FileSystem/FileDateTime.md | 10 +- .../Default/VBA/FileSystem/FileLen.md | 10 +- .../Default/VBA/FileSystem/FreeFile.md | 2 +- .../Default/VBA/FileSystem/GetAttr.md | 8 +- .../Reference/Default/VBA/FileSystem/Input.md | 10 +- .../Default/VBA/FileSystem/InputB.md | 8 +- docs/Reference/Default/VBA/FileSystem/Kill.md | 10 +- docs/Reference/Default/VBA/FileSystem/LOF.md | 2 +- docs/Reference/Default/VBA/FileSystem/Loc.md | 2 +- .../Reference/Default/VBA/FileSystem/MkDir.md | 4 +- .../Reference/Default/VBA/FileSystem/RmDir.md | 4 +- docs/Reference/Default/VBA/FileSystem/Seek.md | 4 +- .../Default/VBA/FileSystem/SetAttr.md | 8 +- .../Reference/Default/VBA/FileSystem/Width.md | 6 +- docs/Reference/Default/VBA/Financial/DDB.md | 6 +- docs/Reference/Default/VBA/Financial/FV.md | 4 +- docs/Reference/Default/VBA/Financial/IPmt.md | 8 +- docs/Reference/Default/VBA/Financial/IRR.md | 6 +- docs/Reference/Default/VBA/Financial/MIRR.md | 10 +- docs/Reference/Default/VBA/Financial/NPV.md | 8 +- docs/Reference/Default/VBA/Financial/NPer.md | 10 +- docs/Reference/Default/VBA/Financial/PPmt.md | 10 +- docs/Reference/Default/VBA/Financial/PV.md | 8 +- docs/Reference/Default/VBA/Financial/Pmt.md | 6 +- docs/Reference/Default/VBA/Financial/Rate.md | 16 +- docs/Reference/Default/VBA/Financial/SLN.md | 4 +- docs/Reference/Default/VBA/Financial/SYD.md | 2 +- .../VBA/HiddenModule/AddClassInstanceFlag.md | 2 +- .../VBA/HiddenModule/ConvertIconToBitmap.md | 2 +- .../Default/VBA/HiddenModule/Eval.md | 8 +- .../VBA/HiddenModule/GetInheritedOwner.md | 2 +- .../RuntimeCreateGetMessageHook.md | 2 +- .../Default/VBA/HiddenModule/vbaCastObj.md | 4 +- .../Default/VBA/Information/LBound.md | 4 +- .../Default/VBA/Information/UBound.md | 4 +- .../Default/VBA/Interaction/AppActivate.md | 8 +- .../Default/VBA/Interaction/CallByName.md | 16 +- .../Default/VBA/Interaction/CreateObject.md | 8 +- .../Default/VBA/Interaction/DeleteSetting.md | 2 +- .../Default/VBA/Interaction/Environ.md | 4 +- .../Default/VBA/Interaction/GetAllSettings.md | 2 +- .../Default/VBA/Interaction/GetObject.md | 10 +- .../Default/VBA/Interaction/GetSetting.md | 2 +- .../Default/VBA/Interaction/InputBox.md | 8 +- .../Default/VBA/Interaction/MsgBox.md | 8 +- .../Default/VBA/Interaction/Partition.md | 6 +- .../VBA/Interaction/RaiseEventByName.md | 4 +- .../VBA/Interaction/RaiseEventByName2.md | 4 +- .../Default/VBA/Interaction/SaveSetting.md | 2 +- .../Default/VBA/Interaction/SendKeys.md | 8 +- .../Default/VBA/Interaction/Shell.md | 8 +- .../Default/VBA/Interaction/Switch.md | 2 +- docs/Reference/Default/VBA/Math/Abs.md | 5 +- docs/Reference/Default/VBA/Math/Cos.md | 8 +- docs/Reference/Default/VBA/Math/Round.md | 3 - .../Default/VBA/Math/ShiftRotateLeft.md | 2 +- .../Default/VBA/Math/ShiftRotateRight.md | 2 +- .../Default/VBA/Math/ShiftUnsignedRight.md | 2 +- docs/Reference/Default/VBA/Math/Sin.md | 8 +- docs/Reference/Default/VBA/Math/Tan.md | 4 +- docs/Reference/Default/VBA/Strings/Filter.md | 2 +- docs/Reference/Default/VBA/Strings/Format.md | 2 +- docs/Reference/Default/VBA/Strings/InStr.md | 4 +- docs/Reference/Default/VBA/Strings/Len.md | 2 +- docs/Reference/Default/VBA/Strings/StrComp.md | 2 +- docs/Reference/Default/VBA/Strings/StrConv.md | 2 +- docs/Reference/Default/VBA/Strings/String.md | 7 +- .../TbExpressionService/AddCustomBinder.md | 2 +- .../AddCustomBinderObject.md | 2 +- .../VBA/TbExpressionService/Compile.md | 4 +- .../VBA/TbExpressionService/Evaluate.md | 4 +- .../Default/VBRUN/ErrorCallstack/index.md | 2 +- .../Default/VBRUN/ErrorContext/index.md | 2 +- .../Default/VBRUN/ErrorStackFrame/index.md | 2 +- docs/Reference/Literal-Keywords.md | 4 +- docs/Reference/Operators.md | 2 +- docs/Reference/Run-time-Errors.md | 16 +- errors/Core.yml | 6 +- errors/VBA.Collection.yml | 2 +- errors/VBA.Compilation.yml | 2 +- errors/VBA.Conversion.yml | 33 +- errors/VBA.DateTime.yml | 2 +- errors/VBA.ErrObject.yml | 2 +- errors/VBA.FileSystem.yml | 2 +- errors/VBA.Financial.yml | 2 +- errors/VBA.HiddenModule.yml | 2 +- errors/VBA.Information.yml | 2 +- errors/VBA.Interaction.yml | 2 +- errors/VBA.Math.yml | 41 +- errors/VBA.Strings.yml | 9 +- errors/VBA.TbExpressionService.yml | 2 +- scripts/check_attribute_sweep.mjs | 2 +- test/addin/appdata.test.mjs | 2 +- test/addin/arch.test.mjs | 2 +- test/addin/detach.test.mjs | 2 +- test/addin/entry.test.mjs | 2 +- test/addin/env.test.mjs | 2 +- test/addin/ideserver.test.mjs | 2 +- test/addin/keys.test.mjs | 2 +- test/addin/panes.test.mjs | 2 +- test/addin/parent.test.mjs | 2 +- test/addin/references.test.mjs | 2 +- test/addin/reload.test.mjs | 2 +- test/addin/symbols.test.mjs | 2 +- test/ide/assert.test.mjs | 2 +- test/ide/break-into.test.mjs | 2 +- test/ide/breakpoints.test.mjs | 2 +- test/ide/build-and-exit-silent.test.mjs | 2 +- test/ide/building-hklm.test.mjs | 2 +- test/ide/building-llvm-box.test.mjs | 2 +- test/ide/code-find.test.mjs | 2 +- test/ide/console-completion.test.mjs | 2 +- test/ide/debugger.test.mjs | 2 +- test/ide/debugging.test.mjs | 2 +- test/ide/forms.test.mjs | 2 +- test/ide/memory.test.mjs | 2 +- test/ide/restart-file-order.test.mjs | 2 +- test/ide/typing-after-open.test.mjs | 2 +- test/ide/watches.test.mjs | 2 +- test/llvm/README.md | 55 +++ test/llvm/expect.json | 208 +++++++++ .../src => test/llvm/gra64}/Settings | 8 +- test/llvm/gra64/Sources/Probe.twin | 17 + .../llvm/gra64}/Sources/Startup.twin | 0 test/llvm/run.mjs | 137 ++++++ .../llvm}/src/Settings | 8 +- test/llvm/src/Sources/Claims.twin | 421 ++++++++++++++++++ test/llvm/src/Sources/Startup.twin | 8 + 356 files changed, 1509 insertions(+), 4180 deletions(-) delete mode 100644 bugs/filed/abs-minimum-widens-type/REPORT.md delete mode 100644 bugs/filed/abs-minimum-widens-type/abs-minimum-widens-type.twinproj delete mode 100644 bugs/filed/abs-minimum-widens-type/repro.json delete mode 100644 bugs/filed/abs-minimum-widens-type/src/Settings delete mode 100644 bugs/filed/abs-minimum-widens-type/src/Sources/RegressionTests.twin delete mode 100644 bugs/filed/abs-minimum-widens-type/vb6/Module1.bas delete mode 100644 bugs/filed/abs-minimum-widens-type/vb6/Probe.vbp delete mode 100644 bugs/filed/cdate-string-decimal-out-of-range/REPORT.md delete mode 100644 bugs/filed/cdate-string-decimal-out-of-range/cdate-string-decimal-out-of-range.twinproj delete mode 100644 bugs/filed/cdate-string-decimal-out-of-range/repro.json delete mode 100644 bugs/filed/cdate-string-decimal-out-of-range/src/Settings delete mode 100644 bugs/filed/cdate-string-decimal-out-of-range/src/Sources/RegressionTests.twin delete mode 100644 bugs/filed/cdate-string-decimal-out-of-range/src/Sources/Startup.twin delete mode 100644 bugs/filed/cdate-string-decimal-out-of-range/vb6/Module1.bas delete mode 100644 bugs/filed/cdate-string-decimal-out-of-range/vb6/Probe.vbp delete mode 100644 bugs/filed/dateadd-month-end-overflow/REPORT.md delete mode 100644 bugs/filed/dateadd-month-end-overflow/dateadd-month-end-overflow.twinproj delete mode 100644 bugs/filed/dateadd-month-end-overflow/repro.json delete mode 100644 bugs/filed/dateadd-month-end-overflow/src/Settings delete mode 100644 bugs/filed/dateadd-month-end-overflow/src/Sources/Startup.twin delete mode 100644 bugs/filed/dateadd-month-end-overflow/vb6/Module1.bas delete mode 100644 bugs/filed/dateadd-month-end-overflow/vb6/Probe.vbp delete mode 100644 bugs/filed/err-raise-helpcontext/REPORT.md delete mode 100644 bugs/filed/err-raise-helpcontext/err-raise-helpcontext.twinproj delete mode 100644 bugs/filed/err-raise-helpcontext/repro.json delete mode 100644 bugs/filed/err-raise-helpcontext/src/Settings delete mode 100644 bugs/filed/err-raise-helpcontext/src/Sources/Probe.twin delete mode 100644 bugs/filed/err-raise-helpcontext/src/Sources/Startup.twin delete mode 100644 bugs/filed/err-raise-helpcontext/vb6/Module1.bas delete mode 100644 bugs/filed/err-raise-helpcontext/vb6/Probe.vbp delete mode 100644 bugs/filed/llvm-byval-to-byref-sub-codegen/REPORT.md delete mode 100644 bugs/filed/llvm-byval-to-byref-sub-codegen/llvm-byval-to-byref-sub-codegen.twinproj delete mode 100644 bugs/filed/llvm-byval-to-byref-sub-codegen/repro.json delete mode 100644 bugs/filed/llvm-byval-to-byref-sub-codegen/src/Settings delete mode 100644 bugs/filed/llvm-byval-to-byref-sub-codegen/src/Sources/Startup.twin delete mode 100644 bugs/filed/llvm-conversion-rounds-half-away/REPORT.md delete mode 100644 bugs/filed/llvm-conversion-rounds-half-away/llvm-conversion-rounds-half-away.twinproj delete mode 100644 bugs/filed/llvm-conversion-rounds-half-away/repro.json delete mode 100644 bugs/filed/llvm-conversion-rounds-half-away/src/Settings delete mode 100644 bugs/filed/llvm-conversion-rounds-half-away/src/Sources/Startup.twin delete mode 100644 bugs/filed/llvm-conversion-rounds-half-away/vb6/Module1.bas delete mode 100644 bugs/filed/llvm-conversion-rounds-half-away/vb6/Probe.vbp delete mode 100644 bugs/filed/redim-empty-range-unspecified-error/REPORT.md delete mode 100644 bugs/filed/redim-empty-range-unspecified-error/redim-empty-range-unspecified-error.twinproj delete mode 100644 bugs/filed/redim-empty-range-unspecified-error/repro.json delete mode 100644 bugs/filed/redim-empty-range-unspecified-error/src/Settings delete mode 100644 bugs/filed/redim-empty-range-unspecified-error/src/Sources/Startup.twin delete mode 100644 bugs/filed/redim-empty-range-unspecified-error/vb6/Module1.bas delete mode 100644 bugs/filed/redim-empty-range-unspecified-error/vb6/Probe.vbp delete mode 100644 bugs/filed/redim-inverted-bounds-error/REPORT.md delete mode 100644 bugs/filed/redim-inverted-bounds-error/redim-inverted-bounds-error.twinproj delete mode 100644 bugs/filed/redim-inverted-bounds-error/repro.json delete mode 100644 bugs/filed/redim-inverted-bounds-error/src/Settings delete mode 100644 bugs/filed/redim-inverted-bounds-error/src/Sources/RegressionTests.twin delete mode 100644 bugs/filed/redim-inverted-bounds-error/src/Sources/Startup.twin delete mode 100644 bugs/filed/redim-inverted-bounds-error/vb6/Module1.bas delete mode 100644 bugs/filed/redim-inverted-bounds-error/vb6/Probe.vbp delete mode 100644 bugs/filed/round-null-negative-digits/REPORT.md delete mode 100644 bugs/filed/round-null-negative-digits/repro.json delete mode 100644 bugs/filed/round-null-negative-digits/round-null-negative-digits.twinproj delete mode 100644 bugs/filed/round-null-negative-digits/src/Sources/RegressionTests.twin delete mode 100644 bugs/filed/round-null-negative-digits/src/Sources/Startup.twin delete mode 100644 bugs/filed/round-null-negative-digits/vb6/Module1.bas delete mode 100644 bugs/filed/round-null-negative-digits/vb6/Probe.vbp delete mode 100644 bugs/filed/runafterbuild-generic-sub-never-runs/REPORT.md delete mode 100644 bugs/filed/runafterbuild-generic-sub-never-runs/repro.json delete mode 100644 bugs/filed/runafterbuild-generic-sub-never-runs/runafterbuild-generic-sub-never-runs.twinproj delete mode 100644 bugs/filed/runafterbuild-generic-sub-never-runs/src/Settings delete mode 100644 bugs/filed/runafterbuild-generic-sub-never-runs/src/Sources/Startup.twin delete mode 100644 bugs/filed/string-null-character/REPORT.md delete mode 100644 bugs/filed/string-null-character/repro.json delete mode 100644 bugs/filed/string-null-character/src/Sources/RegressionTests.twin delete mode 100644 bugs/filed/string-null-character/src/Sources/Startup.twin delete mode 100644 bugs/filed/string-null-character/string-null-character.twinproj delete mode 100644 bugs/filed/string-null-character/vb6/Module1.bas delete mode 100644 bugs/filed/string-null-character/vb6/Probe.vbp delete mode 100644 bugs/filed/textbox-text-ignores-maxlength/REPORT.md delete mode 100644 bugs/filed/textbox-text-ignores-maxlength/repro.json delete mode 100644 bugs/filed/textbox-text-ignores-maxlength/src/Settings delete mode 100644 bugs/filed/textbox-text-ignores-maxlength/src/Sources/Form1.tbform delete mode 100644 bugs/filed/textbox-text-ignores-maxlength/src/Sources/Form1.twin delete mode 100644 bugs/filed/textbox-text-ignores-maxlength/src/Sources/Startup.twin delete mode 100644 bugs/filed/textbox-text-ignores-maxlength/textbox-text-ignores-maxlength.twinproj delete mode 100644 bugs/filed/textbox-text-ignores-maxlength/vb6/Form1.frm delete mode 100644 bugs/filed/textbox-text-ignores-maxlength/vb6/Module1.bas delete mode 100644 bugs/filed/textbox-text-ignores-maxlength/vb6/Probe.vbp delete mode 100644 bugs/filed/val-suffix-overflow-no-error/REPORT.md delete mode 100644 bugs/filed/val-suffix-overflow-no-error/repro.json delete mode 100644 bugs/filed/val-suffix-overflow-no-error/src/Settings delete mode 100644 bugs/filed/val-suffix-overflow-no-error/src/Sources/RegressionTests.twin delete mode 100644 bugs/filed/val-suffix-overflow-no-error/src/Sources/Startup.twin delete mode 100644 bugs/filed/val-suffix-overflow-no-error/val-suffix-overflow-no-error.twinproj delete mode 100644 bugs/filed/val-suffix-overflow-no-error/vb6/Module1.bas delete mode 100644 bugs/filed/val-suffix-overflow-no-error/vb6/Probe.vbp delete mode 100644 bugs/filed/webview2-response-headers-reset-clone/REPORT.md delete mode 100644 bugs/filed/webview2-response-headers-reset-clone/repro.json delete mode 100644 bugs/filed/webview2-response-headers-reset-clone/src/Settings delete mode 100644 bugs/filed/webview2-response-headers-reset-clone/src/Sources/Form1.tbform delete mode 100644 bugs/filed/webview2-response-headers-reset-clone/src/Sources/Form1.twin delete mode 100644 bugs/filed/webview2-response-headers-reset-clone/src/Sources/HeaderEnumerationTests.twin delete mode 100644 bugs/filed/webview2-response-headers-reset-clone/src/Sources/Startup.twin delete mode 100644 bugs/filed/webview2-response-headers-reset-clone/webview2-response-headers-reset-clone.twinproj create mode 100644 test/llvm/README.md create mode 100644 test/llvm/expect.json rename {bugs/filed/string-null-character/src => test/llvm/gra64}/Settings (86%) create mode 100644 test/llvm/gra64/Sources/Probe.twin rename {bugs/filed/abs-minimum-widens-type/src => test/llvm/gra64}/Sources/Startup.twin (100%) create mode 100644 test/llvm/run.mjs rename {bugs/filed/round-null-negative-digits => test/llvm}/src/Settings (88%) create mode 100644 test/llvm/src/Sources/Claims.twin create mode 100644 test/llvm/src/Sources/Startup.twin diff --git a/BUGS-TO-REPORT.md b/BUGS-TO-REPORT.md index 66d2f354e..df92fd047 100644 --- a/BUGS-TO-REPORT.md +++ b/BUGS-TO-REPORT.md @@ -3949,7 +3949,7 @@ Severity: high for a program built with LLVM that has such a loop: a wrong resul Not tried: `For Each`, and the generated code. Not understood: a `Double` loop variable with a `Long` step is wrong in the 64-bit build and right in the 32-bit one; a `Single` step is about 0 in the 64-bit build and 0.5000014 in place of 0.5 in the 32-bit one; and the cause of the 32-bit crash. - + --- diff --git a/WIP.Errors.md b/WIP.Errors.md index aba6e02dd..3a92fd98e 100644 --- a/WIP.Errors.md +++ b/WIP.Errors.md @@ -7,7 +7,19 @@ exactly that. Like WIP.md, this file is not rendered through tbdocs. -## Where things stand (2026-10-22) +## Where things stand (2026-11-02) + +**Re-checked on BETA 1034 (2026-11-02):** `errors.mjs run --runtime tb --runtime win64` over +every file, 2,911 cases. 24 disagreed, each a fix in BETA 1034 that brings twinBASIC to VB6: +their rows are now plain rows and their filed reproducers are deleted +(`redim-inverted-bounds-error`, `cdate-string-decimal-out-of-range`, +`val-suffix-overflow-no-error`, `abs-minimum-widens-type`, `round-null-negative-digits`, +`string-null-character`). `trig-above-2-to-63` is fixed for Cos and Sin from 2^63 up only: +from 9,223,372,000,000,000,000 to 2^63 all three still return a value where VB6 raises 5, +and Tan still raises -2147348207 from 2^63 up (rows kept, the Cos and Sin ones split). Every +file's `beta:` is 1034. The changed pages (ReDim, CDate, Val, String, Abs, Cos, Sin, Round) +were run in all four runtimes and agree; VB6 and 64-bit VBA were not re-run for the rest. +The batch narratives below are as each batch left them. **Batches 0, 1 and 2 are done, reviewed by the owner, and committed on `staging`.** The tooling, the central page and every page of the VBA `Strings` module are in place: 339 cases diff --git a/WIP.md b/WIP.md index 67de46f0b..2352e83d6 100644 --- a/WIP.md +++ b/WIP.md @@ -90,7 +90,7 @@ The rest of this file is the maintenance guide for updating existing pages or ad - `docs/Reference/Data-Types.md` and `docs/Reference/Data-Types/.md` — the intrinsic data types (owner, 2026-10-18): one page per type at `/tB/Core/` (`parent: Data Types`, `grand_parent: Reference Section`, since the Features page *New Data Types* has the same title), and the summary page, `/Reference/Data-Types`, with one `## ` section per type that links to it. The summary's `symbols:` names the types, and the symbol index points each at its section (kind `type`, the one URL outside `/tB/`), so keep those headings' text. `Long.md` is the model page. - `docs/Reference/Statements.md` — alphabetical index of language statements. - `docs/Reference/Procedures and Functions.md` — alphabetical index of procedures/functions. -- `docs/LLVM/` — the LLVM section: compiling with the LLVM back end. A top-level section between Features and Reference Section in the nav, at `nav_order: 6` (Features is 5). `index.md` is the landing page and `Getting-Started.md` the only page so far, with its screenshots under `Images/`. Everything it describes arrived in **BETA 984**. Its language claims are checked on BETA 1010 with `tbrun --llvm` and `--exe`: the comma, space and comma-space spellings of `[CompilerOptions]` all turn LLVM on (`+optimize` alone does not, and an attribute replaces the project's options, so `[CompilerOptions("+optimize")]` is non-LLVM under project-wide LLVM), `[CompilerOptions("")]` turns it off under project-wide LLVM, the long CPU-flag sample runs, an error is lost between procedures when either the callee or the caller is LLVM-compiled, `Err.Raise` is unreadable in an LLVM handler (`-353703423`), a `Double` `*`, `+`, `-` or `^` overflow raises no error (`^` gives 0, the others infinity; `FloatingPointErrorChecks(True)` changes nothing), the conversions round half away from zero, and `Debug.Assert` is skipped and its condition not evaluated, and an exe built with LLVM ends with an access violation and no box at the first `GoSub` it runs, 32-bit and 64-bit, while an IDE run is right (the page's WARNING and the GoSub...Return page's, `bugs/gosub-llvm-exe-crash`), and a 64-bit LLVM call of a `Declare` that passes `ByVal 0&` for a ByRef eighth parameter of a 32-bit type can end with an access violation in the API, in an exe and in an IDE run, depending on what the stack held (the first to seventh parameters and a `ByRef LongPtr` eighth are right; the page's WARNING and the Declare page's, `bugs/llvm64-byval-to-byref-declare-crash`), and a call that passes `ByVal` for a ByRef parameter of a twinBASIC Sub or Function is a codegen error in either architecture, after which the calling procedure does not run (a 64-bit exe faults on entering it; the page's WARNING and the Call page's, `bugs/llvm-byval-to-byref-sub-codegen`), and a `For i = 1 To 7 Step x` whose `x` is a `ByVal Long` or `Single` parameter, in a procedure with a `ByRef ... As Boolean` or `As Integer` parameter after `x`, runs with a wrong step (or a wrong start or end, when those are parameters too) in an exe and in an IDE run, silently; the value depends on where the caller's argument for the `ByRef` parameter is stored, 20 in a 64-bit build and 25 in a 32-bit one for a local (a second parameter of another type, `ByVal`, or placed first, an `Integer`, `Double` or `Currency` step, a `Do` loop, no `Step` and a literal `Step` are right; the page's WARNING and the For...Next page's, `bugs/llvm-for-step-constant`). Also on 1010: with LLVM on under Compiler Options (DEBUG) for the whole project, an IDE run of a `[CompilerOptions("")]` procedure that calls an LLVM-compiled one ends in an access violation at the call (a built exe is fine; the page's WARNING). Its samples are marked `check_build`, which compiles them; `examples.bat --llvm` also builds them with LLVM. +- `docs/LLVM/` — the LLVM section: compiling with the LLVM back end. A top-level section between Features and Reference Section in the nav, at `nav_order: 6` (Features is 5). `index.md` is the landing page and `Getting-Started.md` the only page so far, with its screenshots under `Images/`. Everything it describes arrived in **BETA 984**. Its language claims are checked on BETA 1034 by `node test/llvm/run.mjs` (its README lists each claim and its page; run it after a new BETA), its callouts by their reproducers, and both with `tbrun --llvm` and `--exe`: the comma, space and comma-space spellings of `[CompilerOptions]` all turn LLVM on (`+optimize` alone does not, and an attribute replaces the project's options, so `[CompilerOptions("+optimize")]` is non-LLVM under project-wide LLVM), `[CompilerOptions("")]` turns it off under project-wide LLVM, the long CPU-flag sample runs, an error is lost between procedures when either the callee or the caller is LLVM-compiled, `Err.Raise` is unreadable in an LLVM handler (`-353703423`), a `Double` `^` overflow raises no error and gives 0, and in 32-bit code not compiled for SSE2 (`+sse2` or a flag that includes it) a `*`, `+` or `-` overflow raises none either and gives infinity, while a 64-bit build raises 6 and keeps the old value (`FloatingPointErrorChecks(True)` changes nothing), and `Debug.Assert` is skipped and its condition not evaluated, and an exe built with LLVM ends with an access violation and no box at the first `GoSub` it runs, 32-bit and 64-bit, while an IDE run is right (the page's WARNING and the GoSub...Return page's, `bugs/gosub-llvm-exe-crash`), and a 64-bit LLVM call of a `Declare` that passes `ByVal 0&` for a ByRef eighth parameter of a 32-bit type can end with an access violation in the API, in an exe and in an IDE run, depending on what the stack held (the first to seventh parameters and a `ByRef LongPtr` eighth are right; the page's WARNING and the Declare page's, `bugs/llvm64-byval-to-byref-declare-crash`), and a `For i = 1 To 7 Step x` whose `x` is a `ByVal Long` or `Single` parameter, in a procedure with a `ByRef ... As Boolean` or `As Integer` parameter after `x`, runs with a wrong step (or a wrong start or end, when those are parameters too) in an exe and in an IDE run, silently; the value depends on where the caller's argument for the `ByRef` parameter is stored, 20 in a 64-bit build and 25 in a 32-bit one for a local (a second parameter of another type, `ByVal`, or placed first, an `Integer`, `Double` or `Currency` step, a `Do` loop, no `Step` and a literal `Step` are right; the page's WARNING and the For...Next page's, `bugs/llvm-for-step-constant`). Also on 1034: with LLVM on under Compiler Options (DEBUG) for the whole project, an IDE run of a `[CompilerOptions("")]` procedure that calls an LLVM-compiled one ends in an access violation at the call (a built exe is fine; the page's WARNING). Its samples are marked `check_build`, which compiles them; `examples.bat --llvm` also builds them with LLVM. **A new top-level section needs four things besides its folder**, and nothing checks the first and the third: a `nav_order` between its neighbours; an entry in `docs/_book.yml` for every page, in a part or in `left_out:` with a reason, which the build warns about under its `pdf:` summary when one is missing; a line in *Where content lives* in `docs/Documentation/Authoring.md`; and the page-count rise that `build.bat` writes to `builder/page-baseline.json`, committed with the pages. The Challenges and Videos sections are in `left_out:`, and so are the IDE pages that are still screenshots and labels; the IDE part names its pages one by one, so a new IDE page warns until it goes into the part or into `left_out:`. A link from the book to a left-out page opens the website, and the pass over `book.html` lists it as `OUT OF BOOK`. A section index lists its topics by hand and sets `has_toc: false`, or the template appends a second, automatic list of its children. - Footer rendering — [builder/template.mjs](builder/template.mjs)'s `renderFooterCustom()` renders the copyright line and, when `vba_attribution: true` is set in a page's frontmatter, an additional CC-BY-4.0 attribution line beneath it. diff --git a/bugs/filed/abs-minimum-widens-type/REPORT.md b/bugs/filed/abs-minimum-widens-type/REPORT.md deleted file mode 100644 index ae933b658..000000000 --- a/bugs/filed/abs-minimum-widens-type/REPORT.md +++ /dev/null @@ -1,90 +0,0 @@ -Filed as [twinbasic/twinbasic#2541](https://github.com/twinbasic/twinbasic/issues/2541). - -## `Abs` of the smallest `Integer` or `Long` returns a `Long` or a `Double`, where VB6 raises error 6 - -**Describe the bug** -`Abs` returns a value of the type it is given: `Abs` of the `Integer` -32767 is the `Integer` 32767. The smallest `Integer`, -32768, has no positive counterpart in an `Integer`, and VB6 raises error 6, *Overflow*, for it, as for the smallest `Long` and the smallest `Currency`. twinBASIC raises 6 for the `Currency`, but for the `Integer` it returns the `Long` 32768, for the `Long` -2147483648 the `Double` 2147483648, and for the smallest `LongLong` a `Double`. Observed in the compiler's test mode, 32-bit and 64-bit, and in a built exe. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `abs-minimum-widens-type.twinproj` (attached as `abs-minimum-widens-type.zip`). -2. Run its test cases, the module below. On BETA 1010 the three controls pass (an `Integer` one above the smallest keeps its type, the smallest `Currency` raises 6, and a `Variant` holding the smallest `Integer` gives a `Long`, as in VB6) and the three cases fail with `Actual: 0`. - -The module is meant to be adapted as a regression test. Each case asserts VB6's result, which the attached VB6 project (`abs-minimum-widens-type-vb6.zip`) prints: error 6 for the `Integer`, the `Long` and the `Currency`, and the `Integer` 32767 and the `Long` 32768 for the controls. VB6 has no `LongLong`; its case asserts the same rule. -```vb -' Regression tests: Abs of the smallest Integer or Long raises error 6, as Abs of the -' smallest Currency does, because the result keeps the argument's type and the type -' cannot hold it. Expected results are VB6's (the VB6 project beside this one prints -' them). tbrun --tests runs each case and prints PASS or FAIL. -[TestFixture] -Module RegressionTests - - ' Controls: a value one above the smallest keeps its type; the smallest Currency - ' raises 6; a Variant holding the smallest Integer gives a Long (in VB6 too). - [TestCase] - Public Sub Abs_IntegerAboveSmallest_KeepsInteger() - Dim I As Integer = -32767 - Dim V As Variant = Abs(I) - Assert.Strict.AreEqual "Integer", TypeName(V) - End Sub - - [TestCase] - Public Sub Abs_SmallestCurrency_Raises6() - Dim C As Currency = -922337203685477.5807@ - 0.0001@ - Dim V As Variant - On Error Resume Next - V = Abs(C) - Assert.Strict.AreEqual 6&, Err.Number - End Sub - - [TestCase] - Public Sub Abs_VariantSmallestInteger_GivesLong() - Dim V As Variant = CInt(-32768) - V = Abs(V) - Assert.Strict.AreEqual "Long", TypeName(V) - End Sub - - ' The defect: the result is widened to a Long or a Double, and no error is raised. - [TestCase] - Public Sub Abs_SmallestInteger_Raises6() - Dim I As Integer = -32768 - Dim V As Variant - On Error Resume Next - V = Abs(I) - Assert.Strict.AreEqual 6&, Err.Number - End Sub - - [TestCase] - Public Sub Abs_SmallestLong_Raises6() - Dim L As Long = -2147483647 - 1 - Dim V As Variant - On Error Resume Next - V = Abs(L) - Assert.Strict.AreEqual 6&, Err.Number - End Sub - - [TestCase] - Public Sub Abs_SmallestLongLong_Raises6() - Dim L As LongLong = -9223372036854775807^ - 1 - Dim V As Variant - On Error Resume Next - V = Abs(L) - Assert.Strict.AreEqual 6&, Err.Number - End Sub - -End Module -``` - -**Expected behavior** -Error 6, *Overflow*, for the smallest `Integer`, `Long` and `LongLong`, as for the smallest `Currency` and as VB6 does: the result has the argument's type. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1010 - -**Additional context** -Severity: low. The value is right, and assigning it back to the argument's type raises 6 there (`I = Abs(I)`); a program that relies on the error from `Abs` itself, or on the result's type (`TypeName`, a `Variant` passed on), sees a difference. - -What does not reproduce it: any other `Integer` or `Long`; the smallest `Currency` (6 in both); a `Variant` holding the smallest `Integer` or `Long`, which gives a `Long` or a `Double` in VB6 too. - - diff --git a/bugs/filed/abs-minimum-widens-type/abs-minimum-widens-type.twinproj b/bugs/filed/abs-minimum-widens-type/abs-minimum-widens-type.twinproj deleted file mode 100644 index 0cfb65d58a964986953492b6ddd0daf333765940..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4483 zcmd5=S#R4$5LVJtYW>!}^Ed316K{(?=Q6EXR*+jU4Q8_0pmA=7`m!&0m?l9@mAaEnf;{_TN2Q=Tq zbKmveyVFGx-=*7S_;WxdkA=6m|1#Z2vf;zsuW&oH>wr!Lm6Gx3kV++M>XJnXGYY`p zKOkd;%*lvCx=jLr5Pn9~IVCc5g8){^AX0Qn1>u5h^Jq$h%OsVA3IQw4b>hkb+frp^ z&gsNL(8#+cKBsb>OsMC^k|xqYy*NPZCipubf`cWv7sN5JE~2o`A|+8OP=;$Y zq9!7ah^8ed5O@dkT<;tX1$9xAl!NSB!y)3(w+7p_rOGj)+hNxX(?UxyqshD#iI4ScHluRyklfn1tW^@g!g#8O9Uz^Jx|i7OFkW zV&wEK`Z3#CT}%0px648@AeOufqSr)g7_DY&kz|A{^nAJf+iX|l#sy0-Dd8%d! z+WRwh15(WntgaKx%3U|4tD}sq2TlWxbMQZ4Q*K9&?dd@ho zLvJ+O8)mCj+i2^pH$)?PP1p7Mo2RXu@9agSm!uM;JY)dbrB`acJAzXX4(G#gG=E)3 zNwwWLoKTU9_&69c59DZM@SbjAz+md%hvS~x9AH>Y+}kc_n_G~32E*bhhvFg6@a76R zgberT86@{+669Gqf&9M2LJnm?@jS#KUtQAW5XBUV6~slvv*HEhN%r{9i~RezJP{<1 z#d~}1QA~*dqJyciTp%)_H_(iu{vpc%>H0P^*}Fo4`t%2kg?XKWe^SHZASW6?Pk(D$mFq}rut zE{q7Wx(;MnC$G7)*&$N_MFJ>`4~Dk|;z`T`f8SN-NWFZMj!gJ{J09D(Hy$tF?58sV zHjmrWaG0+mmtC)f#_YBX+jbgSz1^;BX5H#&ovveQ zcB5(aTdi*2up5htMqnGVeh@I8s0ou5^n0crQ9(e-#fgrhhmI@dGPtl@u4bA+%QJMd zp=ALDO5@SpcNJq1$bvt7~1yvNf~O?`fT8qpsEKosCY{w9J;#&H-W4;o%IT zO=LF%I4g?)go}9yJ6g0eILtK5Q9E|_cJ>F`y#ts}owwFj?}XsMZ(GBG1J!6`!{iwP zifqCHrmjhL09oyG5z4g!W+}{tg%rem0S7?|8H#Eg9u0Pr6SVyFjH(Fa$j%lsxRR^` z?#%5uh#9o^R&!_<06(A?4BY`j8udp(cj_IZZ#Ozx({wD&>~?I;hS5y3Ha2vlZ?=1V z$NBQ0TeBIN27Cfzsu#Et?i~}nisVS%g>+%z6S0O9w~_-LJTEJ29|M1V0ca?#X=E>~ z7T|w8}hJMi=BIUemBWBM^5~m#&Dg1p_x0sA_U|Pii=x zRj!^hxDUhTar()o;CqUtFkgty`24FN?f9gU)V(U)nH39Z4es~9Ma<(q%{yZH&ys6k M$8uJ=O@@&F0DHLBVE_OC diff --git a/bugs/filed/abs-minimum-widens-type/repro.json b/bugs/filed/abs-minimum-widens-type/repro.json deleted file mode 100644 index 08908ce04..000000000 --- a/bugs/filed/abs-minimum-widens-type/repro.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "mode": "test", - "expect": { - "exit": 7, - "output": [ - "^PASS RegressionTests.Abs_IntegerAboveSmallest_KeepsInteger$", - "^PASS RegressionTests.Abs_SmallestCurrency_Raises6$", - "^PASS RegressionTests.Abs_VariantSmallestInteger_GivesLong$", - "^FAIL RegressionTests.Abs_SmallestInteger_Raises6: .*Actual: 0 ", - "^FAIL RegressionTests.Abs_SmallestLong_Raises6: .*Actual: 0 ", - "^FAIL RegressionTests.Abs_SmallestLongLong_Raises6: .*Actual: 0 " - ] - }, - "issue": 2541 -} diff --git a/bugs/filed/abs-minimum-widens-type/src/Settings b/bugs/filed/abs-minimum-widens-type/src/Settings deleted file mode 100644 index 21cf1b986..000000000 --- a/bugs/filed/abs-minimum-widens-type/src/Settings +++ /dev/null @@ -1,75 +0,0 @@ -{ - "configuration.inherits": "Defaults", - "project.appTitle": "AbsMinimumWidensType", - "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", - "project.buildType": "Standard EXE", - "project.description": "Reproduces: x", - "project.exportPathIsV2": true, - "project.id": "{4D72BBC3-1881-41A9-9DC4-B36AF77DF2B3}", - "project.name": "AbsMinimumWidensType", - "project.optionExplicit": true, - "project.references": [ - { - "id": "{00020430-0000-0000-C000-000000000046}", - "lcid": 0, - "name": "OLE Automation", - "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", - "path64": "C:\\Windows\\System32\\stdole2.tlb", - "symbolId": "stdole", - "versionMajor": 2, - "versionMinor": 0 - }, - { - "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", - "isCompilerPackage": true, - "licence": "MIT", - "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", - "path32": "", - "path64": "", - "publisher": "TWINBASIC-COMPILER", - "symbolId": "VB", - "versionBuild": 0, - "versionMajor": 0, - "versionMinor": 0, - "versionRevision": 31 - }, - { - "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", - "isCompilerPackage": true, - "licence": "MIT", - "name": "[COMPILER PACKAGE] twinBASIC - App global class object", - "path32": "", - "path64": "", - "publisher": "TWINBASIC-COMPILER", - "symbolId": "AppGlobalClassProject", - "versionBuild": 0, - "versionMajor": 1, - "versionMinor": 0, - "versionRevision": 0 - }, - { - "id": "{9847A10D-8987-44B2-8B87-EA77DEF7F1D9}", - "isCompilerPackage": true, - "licence": "MIT", - "name": "[COMPILER PACKAGE] Assert", - "path32": "", - "path64": "", - "publisher": "TWINBASIC-COMPILER", - "symbolId": "Assert", - "versionBuild": 0, - "versionMajor": 0, - "versionMinor": 0, - "versionRevision": 24 - } - ], - "project.settingsVersion": 1, - "project.startupObject": "Sub Main", - "project.warnings": { - "errors": [], - "hints": [], - "ignored": [], - "info": [], - "warnings": [] - }, - "runtime.useUnicodeStandardLibrary": true -} diff --git a/bugs/filed/abs-minimum-widens-type/src/Sources/RegressionTests.twin b/bugs/filed/abs-minimum-widens-type/src/Sources/RegressionTests.twin deleted file mode 100644 index fdd134d6d..000000000 --- a/bugs/filed/abs-minimum-widens-type/src/Sources/RegressionTests.twin +++ /dev/null @@ -1,61 +0,0 @@ -' Regression tests: Abs of the smallest Integer or Long raises error 6, as Abs of the -' smallest Currency does, because the result keeps the argument's type and the type -' cannot hold it. Expected results are VB6's (the VB6 project beside this one prints -' them). tbrun --tests runs each case and prints PASS or FAIL. -[TestFixture] -Module RegressionTests - - ' Controls: a value one above the smallest keeps its type; the smallest Currency - ' raises 6; a Variant holding the smallest Integer gives a Long (in VB6 too). - [TestCase] - Public Sub Abs_IntegerAboveSmallest_KeepsInteger() - Dim I As Integer = -32767 - Dim V As Variant = Abs(I) - Assert.Strict.AreEqual "Integer", TypeName(V) - End Sub - - [TestCase] - Public Sub Abs_SmallestCurrency_Raises6() - Dim C As Currency = -922337203685477.5807@ - 0.0001@ - Dim V As Variant - On Error Resume Next - V = Abs(C) - Assert.Strict.AreEqual 6&, Err.Number - End Sub - - [TestCase] - Public Sub Abs_VariantSmallestInteger_GivesLong() - Dim V As Variant = CInt(-32768) - V = Abs(V) - Assert.Strict.AreEqual "Long", TypeName(V) - End Sub - - ' The defect: the result is widened to a Long or a Double, and no error is raised. - [TestCase] - Public Sub Abs_SmallestInteger_Raises6() - Dim I As Integer = -32768 - Dim V As Variant - On Error Resume Next - V = Abs(I) - Assert.Strict.AreEqual 6&, Err.Number - End Sub - - [TestCase] - Public Sub Abs_SmallestLong_Raises6() - Dim L As Long = -2147483647 - 1 - Dim V As Variant - On Error Resume Next - V = Abs(L) - Assert.Strict.AreEqual 6&, Err.Number - End Sub - - [TestCase] - Public Sub Abs_SmallestLongLong_Raises6() - Dim L As LongLong = -9223372036854775807^ - 1 - Dim V As Variant - On Error Resume Next - V = Abs(L) - Assert.Strict.AreEqual 6&, Err.Number - End Sub - -End Module diff --git a/bugs/filed/abs-minimum-widens-type/vb6/Module1.bas b/bugs/filed/abs-minimum-widens-type/vb6/Module1.bas deleted file mode 100644 index a2c945794..000000000 --- a/bugs/filed/abs-minimum-widens-type/vb6/Module1.bas +++ /dev/null @@ -1,22 +0,0 @@ -Attribute VB_Name = "Module1" -Option Explicit - -' What VB6 does with Abs of the smallest Integer, Long and Currency: error 6. -' Build and run it with: node scripts/bug_repro.mjs vb6 abs-minimum-widens-type - -Private Sub Report(ByVal Label As String, ByVal Result As Variant) - Print #9, Label & ": " & Err.Number & " " & Err.Description & " (" & TypeName(Result) & " " & Result & ")" - Err.Clear -End Sub - -Sub Main() - Dim I As Integer, L As Long, C As Currency, V As Variant - Open App.Path & "\out.txt" For Output As #9 - On Error Resume Next - I = -32767: V = Empty: V = Abs(I): Report "Abs_IntegerAboveSmallest", V - C = -922337203685477.5807@ - 0.0001@: V = Empty: V = Abs(C): Report "Abs_SmallestCurrency", V - V = CInt(-32768): V = Abs(V): Report "Abs_VariantSmallestInteger", V - I = -32768: V = Empty: V = Abs(I): Report "Abs_SmallestInteger", V - L = -2147483647 - 1: V = Empty: V = Abs(L): Report "Abs_SmallestLong", V - Close #9 -End Sub diff --git a/bugs/filed/abs-minimum-widens-type/vb6/Probe.vbp b/bugs/filed/abs-minimum-widens-type/vb6/Probe.vbp deleted file mode 100644 index 4d210b0e8..000000000 --- a/bugs/filed/abs-minimum-widens-type/vb6/Probe.vbp +++ /dev/null @@ -1,7 +0,0 @@ -Type=Exe -Module=Module1; Module1.bas -Startup="Sub Main" -ExeName32="Probe.exe" -Name="Probe" -CompilationType=0 -OptimizationType=0 diff --git a/bugs/filed/cdate-string-decimal-out-of-range/REPORT.md b/bugs/filed/cdate-string-decimal-out-of-range/REPORT.md deleted file mode 100644 index 9edeaa826..000000000 --- a/bugs/filed/cdate-string-decimal-out-of-range/REPORT.md +++ /dev/null @@ -1,95 +0,0 @@ -Filed as [twinbasic/twinbasic#2537](https://github.com/twinbasic/twinbasic/issues/2537). - -## `CDate` of a string holding a number outside the `Date` range raises no error, and of such a `Decimal` raises error 6, where VB6 raises error 13 - -**Describe the bug** -A `Date` holds a day number from -657434 (January 1, 100) to 2958465 (December 31, 9999). VB6's `CDate` raises error 13, *Type mismatch*, for a string that holds a number outside that range (`CDate("2958466")`, `CDate("-657435")`) and for a `Decimal` outside it (`CDate(CDec(2958466))`). twinBASIC raises nothing for the string, and the `Date` holds the number, as in the entry *A `Date` outside January 1, 100 to December 31, 9999 is accepted with no error*; for the `Decimal` it raises error 6, *Overflow*, so a handler that tests for 13 does not recognise it. Observed in the compiler's test mode, 32-bit and 64-bit, and in a built exe. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `cdate-string-decimal-out-of-range.twinproj` (attached as `cdate-string-decimal-out-of-range.zip`). -2. Run its test cases, the module below. On BETA 1010 the four controls pass (the two ends of the range as strings, a `Decimal` in range, and `CDate("abc")`, which raises 13) and the four cases fail: `Actual: 0` for the strings, `Actual: 6` for the `Decimal`s. - -The module is meant to be adapted as a regression test. Each case asserts VB6's result, which the attached VB6 project (`cdate-string-decimal-out-of-range-vb6.zip`) prints: error 13 for the four, and the day numbers and 13 for the controls. -```vb -' Regression tests: CDate of a string that holds a number outside the Date range, and of -' a Decimal outside it, raise error 13. Expected results are VB6's (the VB6 project beside -' this one prints them). tbrun --tests runs each case and prints PASS or FAIL. -[TestFixture] -Module RegressionTests - - ' Controls: the ends of the range as strings and as Decimals, and a string that is - ' not a date (error 13 in both). - [TestCase] - Public Sub CDate_StringLargest_Accepted() - Assert.Strict.AreEqual 2958465#, CDbl(CDate("2958465")) - End Sub - - [TestCase] - Public Sub CDate_StringSmallest_Accepted() - Assert.Strict.AreEqual -657434#, CDbl(CDate("-657434")) - End Sub - - [TestCase] - Public Sub CDate_DecimalInRange_Accepted() - Assert.Strict.AreEqual 5#, CDbl(CDate(CDec(5))) - End Sub - - [TestCase] - Public Sub CDate_NotADate_Raises13() - Dim D As Date - On Error Resume Next - D = CDate("abc") - Assert.Strict.AreEqual 13&, Err.Number - End Sub - - ' The defect: a string holding a number past either end raises nothing. - [TestCase] - Public Sub CDate_StringPastLargest_Raises13() - Dim D As Date - On Error Resume Next - D = CDate("2958466") - Assert.Strict.AreEqual 13&, Err.Number - End Sub - - [TestCase] - Public Sub CDate_StringBeforeSmallest_Raises13() - Dim D As Date - On Error Resume Next - D = CDate("-657435") - Assert.Strict.AreEqual 13&, Err.Number - End Sub - - ' The defect: a Decimal past either end raises 6. - [TestCase] - Public Sub CDate_DecimalPastLargest_Raises13() - Dim D As Date - On Error Resume Next - D = CDate(CDec(2958466)) - Assert.Strict.AreEqual 13&, Err.Number - End Sub - - [TestCase] - Public Sub CDate_DecimalBeforeSmallest_Raises13() - Dim D As Date - On Error Resume Next - D = CDate(CDec(-657435)) - Assert.Strict.AreEqual 13&, Err.Number - End Sub - -End Module -``` - -**Expected behavior** -Error 13, *Type mismatch*, for both, as VB6 does. `CVDate` already raises 13 for both in twinBASIC. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1010 - -**Additional context** -Severity: medium for the string, which stores an out-of-range `Date` silently (`CStr`, `Format` and `Year` of it then raise error 5); low for the `Decimal`, which raises an error, only another one. - -What does not reproduce it: `CVDate` of the same values (13 in both); a string that is not a number (`"1/1/10000"` raises 13 in both). A `Double`, `Long`, `Single` or `Currency` outside the range is the other entry: VB6 raises 6 for it, twinBASIC nothing. - - diff --git a/bugs/filed/cdate-string-decimal-out-of-range/cdate-string-decimal-out-of-range.twinproj b/bugs/filed/cdate-string-decimal-out-of-range/cdate-string-decimal-out-of-range.twinproj deleted file mode 100644 index 95102c79944ee0040c2a90da4d565e225eb96ab3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4682 zcmcIoTTkOg6wYq1NsH8n?b|+#QdEM3BPVfgNPTc@XB8DlL?~1u2>CLQ**bPQGhRT5 zAJQMtKi6N;bH+AFfbPN;@?h+lbIzRk&Sk#g?cd-1^Q!Q+P$*b|E9qEr7M=!uws_;f>pK~e%i=zW7 zq^QaZ7Aa*kfWIHWg$=S#b1@=OZkt#(07K$4;u4|YA#&LDJm#djeY9|EV1);7#CB6%5qGy@- z$Q+LeIM6o-JJq$)F(TAwmonk>WUaIt2T4emkWm1xmDWlGejCJ!Bgx|sph5>}1h4>F z_^vP@uE>Cc&A^)QnE@eEcvf*`BDWjG5!Xo0ua=Hm_;_Av(3$_D(;`b== zLgtfk;-zsrO*4AO<&anM)bxEi2Xrf&sW_^bLQpQNDCoa;)bN&ZFY=S^QSE^ zlelBPhV z?0SCr(*dtFe%?ZZ)jd_wsiiyd-5>{$)&V_(^4nfC1ndRSHy7Pt?g~jLgBsvi5NVYQ z0vSC8xu@ms2yEr~`x5A+DcyP|gdSM|J$e>%dRqrCv9Gi=n$NVayOGa(`F}*Z_1G9^ zc3wEhsyeek=Oy})-uIYN_++57oxOad)#kEjKf~s*t%SVbRpEPhj-|_GGQUq7 zY?#bW$V?_b43^HKU6)09M_Xzs_m@H%G^SEQmk`VBdl($LQre`iPhVmAOWfY<6&gTbAh7<)p#_(2@yo80p$dyynTE9+t6@K4LCOw>+ zOxCYQDGAwgXY}TD{o3UIlu2ldz!`3;>(_l2Qs+|A2pQMSee=i}(L7)l1uhSW^SiSm z6;R=GHpgwE+&+aZ1qrafhZxzt2K+D`TlC?Sapm-khy+;Cf}kl<23$#zy{*54&FTL-Lti(-R^3Jt+%vZr)_KX zW~bNG9mj0xjaBF(KpRyb9m-e%2&58N5t9$N!r?6AGy+^<^0-tiLXs7W<=jJEuj@ub z*L3)$4=aDeAze3G%0o~IeWj$sM&T>>bhzUXGm-I3B`m@OlXHNkfxwil?aAbbML~Qa zCX?}%I2s#0-uD2vpn4xZlN%nBg=rrzt+ZC$f1v!@x2zN2+pjha@gb(>w=FpYM-lLNwp70>2S z2Rysjz*<=*AY9Bx*l|a@gM-Yo9JOO>xH}r`IQwK|TECj_oD+igm7X~sSVV(M0Fdq( zpvXKHGI>R^1xTeI^O@LO!YqY(X&?nLA3(Y+AiL3xgQLNoT0zTK&!m=tJnUtI8COL#taGOsM~5(!;5FriDu z69~lZnJlvN{V;7qkoOp;#dOLvhG}*|UE*aH2?PRhD|O-W2uT8HV}mNIvr6@FT&q}y zdxoL~$LS|KhptAk73KrcS-kjKvUYq@Qhl!s|1OYhMyrXSzeUWC1NiSydRC|Z>$w7T LY-gn#MU(yo+iMnq diff --git a/bugs/filed/cdate-string-decimal-out-of-range/repro.json b/bugs/filed/cdate-string-decimal-out-of-range/repro.json deleted file mode 100644 index 69e0c558c..000000000 --- a/bugs/filed/cdate-string-decimal-out-of-range/repro.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "mode": "test", - "expect": { - "exit": 7, - "output": [ - "^PASS RegressionTests.CDate_StringLargest_Accepted$", - "^PASS RegressionTests.CDate_StringSmallest_Accepted$", - "^PASS RegressionTests.CDate_DecimalInRange_Accepted$", - "^PASS RegressionTests.CDate_NotADate_Raises13$", - "^FAIL RegressionTests.CDate_StringPastLargest_Raises13: .*Actual: 0 ", - "^FAIL RegressionTests.CDate_StringBeforeSmallest_Raises13: .*Actual: 0 ", - "^FAIL RegressionTests.CDate_DecimalPastLargest_Raises13: .*Actual: 6 ", - "^FAIL RegressionTests.CDate_DecimalBeforeSmallest_Raises13: .*Actual: 6 " - ] - }, - "issue": 2537 -} diff --git a/bugs/filed/cdate-string-decimal-out-of-range/src/Settings b/bugs/filed/cdate-string-decimal-out-of-range/src/Settings deleted file mode 100644 index f5362c8bd..000000000 --- a/bugs/filed/cdate-string-decimal-out-of-range/src/Settings +++ /dev/null @@ -1,75 +0,0 @@ -{ - "configuration.inherits": "Defaults", - "project.appTitle": "CdateStringDecimalOutOfRange", - "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", - "project.buildType": "Standard EXE", - "project.description": "Reproduces: CDate of a string holding a number outside the Date range raises no error, and of such a Decimal raises error 6, where VB6 raises error 13 for both", - "project.exportPathIsV2": true, - "project.id": "{02D867BD-5D79-4D06-B87D-258B50EEA603}", - "project.name": "CdateStringDecimalOutOfRange", - "project.optionExplicit": true, - "project.references": [ - { - "id": "{00020430-0000-0000-C000-000000000046}", - "lcid": 0, - "name": "OLE Automation", - "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", - "path64": "C:\\Windows\\System32\\stdole2.tlb", - "symbolId": "stdole", - "versionMajor": 2, - "versionMinor": 0 - }, - { - "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", - "isCompilerPackage": true, - "licence": "MIT", - "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", - "path32": "", - "path64": "", - "publisher": "TWINBASIC-COMPILER", - "symbolId": "VB", - "versionBuild": 0, - "versionMajor": 0, - "versionMinor": 0, - "versionRevision": 31 - }, - { - "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", - "isCompilerPackage": true, - "licence": "MIT", - "name": "[COMPILER PACKAGE] twinBASIC - App global class object", - "path32": "", - "path64": "", - "publisher": "TWINBASIC-COMPILER", - "symbolId": "AppGlobalClassProject", - "versionBuild": 0, - "versionMajor": 1, - "versionMinor": 0, - "versionRevision": 0 - }, - { - "id": "{9847A10D-8987-44B2-8B87-EA77DEF7F1D9}", - "isCompilerPackage": true, - "licence": "MIT", - "name": "[COMPILER PACKAGE] Assert", - "path32": "", - "path64": "", - "publisher": "TWINBASIC-COMPILER", - "symbolId": "Assert", - "versionBuild": 0, - "versionMajor": 0, - "versionMinor": 0, - "versionRevision": 24 - } - ], - "project.settingsVersion": 1, - "project.startupObject": "Sub Main", - "project.warnings": { - "errors": [], - "hints": [], - "ignored": [], - "info": [], - "warnings": [] - }, - "runtime.useUnicodeStandardLibrary": true -} diff --git a/bugs/filed/cdate-string-decimal-out-of-range/src/Sources/RegressionTests.twin b/bugs/filed/cdate-string-decimal-out-of-range/src/Sources/RegressionTests.twin deleted file mode 100644 index 67972cfec..000000000 --- a/bugs/filed/cdate-string-decimal-out-of-range/src/Sources/RegressionTests.twin +++ /dev/null @@ -1,66 +0,0 @@ -' Regression tests: CDate of a string that holds a number outside the Date range, and of -' a Decimal outside it, raise error 13. Expected results are VB6's (the VB6 project beside -' this one prints them). tbrun --tests runs each case and prints PASS or FAIL. -[TestFixture] -Module RegressionTests - - ' Controls: the ends of the range as strings and as Decimals, and a string that is - ' not a date (error 13 in both). - [TestCase] - Public Sub CDate_StringLargest_Accepted() - Assert.Strict.AreEqual 2958465#, CDbl(CDate("2958465")) - End Sub - - [TestCase] - Public Sub CDate_StringSmallest_Accepted() - Assert.Strict.AreEqual -657434#, CDbl(CDate("-657434")) - End Sub - - [TestCase] - Public Sub CDate_DecimalInRange_Accepted() - Assert.Strict.AreEqual 5#, CDbl(CDate(CDec(5))) - End Sub - - [TestCase] - Public Sub CDate_NotADate_Raises13() - Dim D As Date - On Error Resume Next - D = CDate("abc") - Assert.Strict.AreEqual 13&, Err.Number - End Sub - - ' The defect: a string holding a number past either end raises nothing. - [TestCase] - Public Sub CDate_StringPastLargest_Raises13() - Dim D As Date - On Error Resume Next - D = CDate("2958466") - Assert.Strict.AreEqual 13&, Err.Number - End Sub - - [TestCase] - Public Sub CDate_StringBeforeSmallest_Raises13() - Dim D As Date - On Error Resume Next - D = CDate("-657435") - Assert.Strict.AreEqual 13&, Err.Number - End Sub - - ' The defect: a Decimal past either end raises 6. - [TestCase] - Public Sub CDate_DecimalPastLargest_Raises13() - Dim D As Date - On Error Resume Next - D = CDate(CDec(2958466)) - Assert.Strict.AreEqual 13&, Err.Number - End Sub - - [TestCase] - Public Sub CDate_DecimalBeforeSmallest_Raises13() - Dim D As Date - On Error Resume Next - D = CDate(CDec(-657435)) - Assert.Strict.AreEqual 13&, Err.Number - End Sub - -End Module diff --git a/bugs/filed/cdate-string-decimal-out-of-range/src/Sources/Startup.twin b/bugs/filed/cdate-string-decimal-out-of-range/src/Sources/Startup.twin deleted file mode 100644 index be8f5c8bc..000000000 --- a/bugs/filed/cdate-string-decimal-out-of-range/src/Sources/Startup.twin +++ /dev/null @@ -1,6 +0,0 @@ -Module Startup - - Public Sub Main() - End Sub - -End Module diff --git a/bugs/filed/cdate-string-decimal-out-of-range/vb6/Module1.bas b/bugs/filed/cdate-string-decimal-out-of-range/vb6/Module1.bas deleted file mode 100644 index c05249c4e..000000000 --- a/bugs/filed/cdate-string-decimal-out-of-range/vb6/Module1.bas +++ /dev/null @@ -1,26 +0,0 @@ -Attribute VB_Name = "Module1" -Option Explicit - -' What VB6 does with CDate of a string holding a number outside the Date range, and of a -' Decimal outside it: error 13 for both. -' Build and run it with: node scripts/bug_repro.mjs vb6 cdate-string-decimal-out-of-range - -Private Sub Report(ByVal Label As String, ByVal Result As Variant) - Print #9, Label & ": " & Err.Number & " " & Err.Description & " (" & Result & ")" - Err.Clear -End Sub - -Sub Main() - Dim R As Variant - Open App.Path & "\out.txt" For Output As #9 - On Error Resume Next - R = Empty: R = CDbl(CDate("2958465")): Report "CDate_StringLargest", R - R = Empty: R = CDbl(CDate("-657434")): Report "CDate_StringSmallest", R - R = Empty: R = CDbl(CDate(CDec(5))): Report "CDate_DecimalInRange", R - R = Empty: R = CDate("abc"): Report "CDate_NotADate", R - R = Empty: R = CDate("2958466"): Report "CDate_StringPastLargest", R - R = Empty: R = CDate("-657435"): Report "CDate_StringBeforeSmallest", R - R = Empty: R = CDate(CDec(2958466)): Report "CDate_DecimalPastLargest", R - R = Empty: R = CDate(CDec(-657435)): Report "CDate_DecimalBeforeSmallest", R - Close #9 -End Sub diff --git a/bugs/filed/cdate-string-decimal-out-of-range/vb6/Probe.vbp b/bugs/filed/cdate-string-decimal-out-of-range/vb6/Probe.vbp deleted file mode 100644 index 4d210b0e8..000000000 --- a/bugs/filed/cdate-string-decimal-out-of-range/vb6/Probe.vbp +++ /dev/null @@ -1,7 +0,0 @@ -Type=Exe -Module=Module1; Module1.bas -Startup="Sub Main" -ExeName32="Probe.exe" -Name="Probe" -CompilationType=0 -OptimizationType=0 diff --git a/bugs/filed/dateadd-month-end-overflow/REPORT.md b/bugs/filed/dateadd-month-end-overflow/REPORT.md deleted file mode 100644 index ec073538a..000000000 --- a/bugs/filed/dateadd-month-end-overflow/REPORT.md +++ /dev/null @@ -1,59 +0,0 @@ -Filed as [twinbasic/twinbasic#2525](https://github.com/twinbasic/twinbasic/issues/2525). - -## `DateAdd` with a month, quarter or year interval runs into the next month instead of stopping at the last day of a shorter month - -**Describe the bug** -`DateAdd` with the interval `"m"`, `"q"` or `"yyyy"` keeps the day of the month and lets it overflow into the next month, where it should stop at the last day of the month it lands in. `DateAdd("m", 1, #1/31/2026#)` returns March 3, 2026, the date `DateSerial(2026, 2, 31)` gives, and not February 28. No error is raised, so nothing shows that the date is wrong. The VBA documentation, and twinBASIC's own page for `DateAdd` after it, say that `DateAdd` does not return an invalid date and that adding a month to January 31 returns February 28; VB6 does that. The other intervals (`"d"`, `"y"`, `"w"`, `"ww"`, `"h"`, `"n"` and `"s"`) give the right dates. Observed in the IDE, with the result of each call written to the DEBUG CONSOLE as `Format(..., "yyyy-mm-dd")`. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `dateadd-month-end-overflow.twinproj` (attached as `dateadd-month-end-overflow.zip`). Its one source file, `Startup.twin`, holds 18 calls, the first four of which are enough: - ``` - Module Startup - - Public Sub Main() - Debug.Print Format(DateAdd("m", 1, #1/31/2026#), "yyyy-mm-dd") - Debug.Print Format(DateAdd("m", -1, #3/31/2026#), "yyyy-mm-dd") - Debug.Print Format(DateAdd("yyyy", 1, #2/29/2024#), "yyyy-mm-dd") - Debug.Print Format(DateAdd("q", 1, #11/30/2025#), "yyyy-mm-dd") - End Sub - - End Module - ``` -2. Run the project in the IDE (F5) and read the DEBUG CONSOLE. -3. See `2026-03-03`, `2026-03-03`, `2025-03-01` and `2026-03-02`. VB6 (the same calls, attached as `dateadd-month-end-overflow-vb6.zip`, which writes each result to `out.txt`) gives `2026-02-28`, `2026-02-28`, `2025-02-28` and `2026-02-28`. The calls of the project, with the dates each gives: - - | call | twinBASIC | VB6 | - |---|---|---| - | `DateAdd("m", 1, #1/31/2026#)` | 2026-03-03 | 2026-02-28 | - | the same, with the date in a `Date` variable | 2026-03-03 | 2026-02-28 | - | `DateAdd("m", 1, "31-Jan-95")` | 1995-03-03 | 1995-02-28 | - | `DateAdd("m", 1, #1/30/2026#)` | 2026-03-02 | 2026-02-28 | - | `DateAdd("m", 1, #1/29/2026#)` | 2026-03-01 | 2026-02-28 | - | `DateAdd("m", 1, #1/31/2024#)` | 2024-03-02 | 2024-02-29 | - | `DateAdd("m", 1, #10/31/2026#)` | 2026-12-01 | 2026-11-30 | - | `DateAdd("m", -1, #3/31/2026#)` | 2026-03-03 | 2026-02-28 | - | `DateAdd("m", -1, #3/30/2026#)` | 2026-03-02 | 2026-02-28 | - | `DateAdd("yyyy", 1, #2/29/2024#)` | 2025-03-01 | 2025-02-28 | - | `DateAdd("yyyy", -1, #2/29/2024#)` | 2023-03-01 | 2023-02-28 | - | `DateAdd("q", 1, #11/30/2025#)` | 2026-03-02 | 2026-02-28 | - -What decides it: the result is the date `DateSerial(year, month + number, day)` gives, so a day that exists in the target month is not affected, and one that does not is counted into the month after. -- A day that exists in the target month is right: `DateAdd("m", 1, #1/28/2026#)` is 2026-02-28, and `DateAdd("yyyy", 4, #2/29/2024#)` is 2028-02-29, in both. -- The other intervals are right: on January 31, 2026, `"d"` and `"y"` with 1 give 2026-02-01 and `"ww"` with 1 gives 2026-02-07, in both. -- It makes no difference whether the date is a literal, a `Date` variable, `DateSerial(2026, 1, 31)` or a string; whether the number is positive or negative; or whether the date has a time of day, which is kept (`DateAdd("m", 1, #1/31/2026 13:45:10#)` is 2026-03-03 13:45:10). `DateAdd("m", 13, #1/31/2025#)` is 2026-03-03 as well. -- `DateSerial(2026, 2, 31)` is 2026-03-03 in VB6 too, by design: it is not the defect. - -**Expected behavior** -`DateAdd` with the interval `"m"`, `"q"` or `"yyyy"` returns the last day of the target month when the day of the date does not exist in it, as VB6 does and as the VBA documentation says: January 31 plus one month is February 28 (29 in a leap year), and February 29 plus one year is February 28. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1005 - -**Additional context** -The same on BETA 997, 995 and 957. Not tried: an exe built from the project, and a project compiled with LLVM. - -Severity: high. The date is wrong and nothing signals it. A program that steps by months or years from a date late in the month (due dates, subscriptions, schedules, a calendar control's next-month button) is up to three days late, and a second step continues from the wrong day: `DateAdd("m", 1, DateAdd("m", 1, #1/31/2026#))` is 2026-04-03, where VB6 gives 2026-03-28. A workaround: `DateSerial(Year(d), Month(d) + n + 1, 0)` is the last day of the month `n` months after `d`, so the result is `DateSerial(Year(d), Month(d) + n, Day(d))` when `Day(d)` is smaller than that day, and that day otherwise. - - diff --git a/bugs/filed/dateadd-month-end-overflow/dateadd-month-end-overflow.twinproj b/bugs/filed/dateadd-month-end-overflow/dateadd-month-end-overflow.twinproj deleted file mode 100644 index b4cd4462c2ba6445c1655d50620bf8c72e771413..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4224 zcmcgwTXWh*6vpY*v^;kD(uWQu8bLv ze@_2ge?`w(N!*MLd1&<@OWJe3J^P(2{^{@U{@Kg?n8{?CuB5K#b;3Z7T7h?RL;1BI zPGMzneGQM#GC#n*J)VSIQtx~=p>J8AyPQ$65X@x0!k1^69Narh45;tB0S%)S8H59w zZ{dFG4nDfWRfuom<$d^bMnxF$!Rq?6cpb&M4_Cj~gYSEp?{QVSTt<_EoU%YEp(0*i z!`3edp1kIHWXj}-xMYkBcZGZaT*zfRp%?j-BuB|* zb6En{DQ7oeJ#nI7AXyj?=g=cHp^kKf4_(PwFpPy!!6Cm8C*)&S9(sAA&EV3IxY(i6H&x#vN4(OxY)*nZJwL2ismcJu*%OD3on+~qJf7z zx{`!Rur1`8v>y!%aJ)bgZA@O8M22=+%b~~sM^Sd3;s_q16Rd)r9U12AO z-r@@T`O_Aj4CIc!HRdt(1fp(2obv66I)+Q}a}c+=B}Lrk7tA{hYKQho>>9=78jUEG z1hw@lcFhw^Doi-~PMFHj9Y8;!mG8nEQdCDzgM7`Q2LUPT3o zYhYSN=`9ly>R_Pi1JvswomWe&swfx2ZBd7z$fg;$xmZ}oyM_GmmXKGxzO;Aa)rN@L zfmUy)isq%XZVyovOT})L)fso)Zd_yVQa%y7q``17N>zd zHHmbol+ZH8@}ThL?=WQC<+A(QAPlbAFybzD+X4$9BAFE0YoayjwHx{HoJY9{4?oa> zEVz@&Ig>tB5@7a*32RlMA2HuMb>#>(4({T?2H)>{y*jS-dIxu>G5R~msQbST?rMB6 zViLBCIK3|%+&Rprty@V00Y=_$z{jZ#@nM<>JePZ<^=E5U%%ft!*#u9Cf@c&CjzU1d=tnAdR|RIK>GR+v}zN*#hDYiXp0LA%#u8!D{M0ljlHWht0kjk zR`gc2(bDZ>)6r|yVqI^PN{&;hSY^v})@TWUO4P;B+UlcF@zT1Tz*NNKCPO&Ah6yhK zzF_qV0%9MdXMbNyEHn(mGVG$E!$Wdwq)+(CFzj+{A?Wx6rDfnz;Wah&=u@j8{r<8}+1{ih>9}ZZ)Dg3B zKlIy*@E9^hgINyauH$|PIbd3eVd-ZEbpSpac$j;&wn~jtGU~@x)6koZT3xq`PD`(r zVRkajYN^_^Yj(vto>Rj_BOFg4+$V$2hw+>sII!DiK9e(&Y(WljfEGt7+=B7tFEO9ChS z`DOc^+C)zcNX3>(y{M;O6XsO*BXetnCOofyPI+FHKBG4by#qK!^D!4R%&O(oi&edB zH)^`wtk(6qU8(4`QpvC!`?%#a8vlz6YLf{W`e7fs2b2gQNZ7}!niBILF%P@GQJXe! z&!pC-d+z28bCvbZR%gWN01kPNn)CJ{l4f)f=M;ENAurbBU$!SPEf8DjZ!~4@Tx{y{ z0DBtft7OwC=OI_MN$t0 diff --git a/bugs/filed/err-raise-helpcontext/err-raise-helpcontext.twinproj b/bugs/filed/err-raise-helpcontext/err-raise-helpcontext.twinproj deleted file mode 100644 index dffb55351bacbd5df8ed89f76f77d8276ae557a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2602 zcmcImOK&1a5XPG)B%GqX<jvdKZ>v=TLyTkOTyT=$I zeomDCmV550ng@@VXca}7L(^UF`nsx%?|=K|kCWVYxm?Z?;>u={{zU!I<$H@C|I6b&SVw|Q0%~XvEc%3x3uZi!H+68YL`3%mn1?Z~t$xdD>FuW0JJV~L-tQS&%jm(e-tD%U zMx$Tr)edvVRz}rNT>+1)-+Bt6O_@3pfuK_=XaINs^RAH3LrvuKr8KZ=wOX(0jj9Hl zbTP9XlB!j`83zU`z8fo5AwM`}0bX8M#E2AM#Px;f05b$c8i+^9>`W%NEb#bJPA21( zyuG|_>XV679`|XzqI_phrKulNQFPI$A5)R5g~Rx$}2~P~vib#h7 z_(}@^;bJbrju{OH*J)%KYIo-4a5T8EuE@wRe>UD)_XJ=4t}z~%L?bs{f~Emdhxtsc zNa}#R>T|J>ulF!ZVBRAnAZ7%7za_LhhH-s6co%!nGViI>K9D!vl$gR5_c3Uv*Pelx zLi;yr2JHdhSM-tLb%2pZ?I`GGtzGYT8*Qzrn}(+M+Fh-ypPy^S*;%#T*IQQKH2;6l zVc?LN&mG$*E>0c7t4quD@6{?x5N9eb~^iAV5XV(-jq diff --git a/bugs/filed/err-raise-helpcontext/repro.json b/bugs/filed/err-raise-helpcontext/repro.json deleted file mode 100644 index 0c79bb723..000000000 --- a/bugs/filed/err-raise-helpcontext/repro.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "mode": "compile", - "expect": { - "diagnostics": [ - "TB5090" - ] - }, - "issue": 2455 -} diff --git a/bugs/filed/err-raise-helpcontext/src/Settings b/bugs/filed/err-raise-helpcontext/src/Settings deleted file mode 100644 index b16ac6b85..000000000 --- a/bugs/filed/err-raise-helpcontext/src/Settings +++ /dev/null @@ -1,61 +0,0 @@ -{ - "configuration.inherits": "Defaults", - "project.appTitle": "ErrRaiseHelpcontext", - "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", - "project.buildType": "Standard EXE", - "project.description": "Reproduces: `Err.Raise` rejects `HelpContext` as a named argument, while its three siblings work", - "project.exportPathIsV2": true, - "project.id": "{EF8E8496-D541-4FDA-8AD6-D2BB86A3F1D1}", - "project.name": "ErrRaiseHelpcontext", - "project.optionExplicit": true, - "project.references": [ - { - "id": "{00020430-0000-0000-C000-000000000046}", - "lcid": 0, - "name": "OLE Automation", - "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", - "path64": "C:\\Windows\\System32\\stdole2.tlb", - "symbolId": "stdole", - "versionMajor": 2, - "versionMinor": 0 - }, - { - "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", - "isCompilerPackage": true, - "licence": "MIT", - "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", - "path32": "", - "path64": "", - "publisher": "TWINBASIC-COMPILER", - "symbolId": "VB", - "versionBuild": 0, - "versionMajor": 0, - "versionMinor": 0, - "versionRevision": 31 - }, - { - "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", - "isCompilerPackage": true, - "licence": "MIT", - "name": "[COMPILER PACKAGE] twinBASIC - App global class object", - "path32": "", - "path64": "", - "publisher": "TWINBASIC-COMPILER", - "symbolId": "AppGlobalClassProject", - "versionBuild": 0, - "versionMajor": 1, - "versionMinor": 0, - "versionRevision": 0 - } - ], - "project.settingsVersion": 1, - "project.startupObject": "Sub Main", - "project.warnings": { - "errors": [], - "hints": [], - "ignored": [], - "info": [], - "warnings": [] - }, - "runtime.useUnicodeStandardLibrary": true -} diff --git a/bugs/filed/err-raise-helpcontext/src/Sources/Probe.twin b/bugs/filed/err-raise-helpcontext/src/Sources/Probe.twin deleted file mode 100644 index 85184d2a3..000000000 --- a/bugs/filed/err-raise-helpcontext/src/Sources/Probe.twin +++ /dev/null @@ -1,9 +0,0 @@ -' Err.Raise with its fifth argument named: HelpContext is rejected, the other three names are not. -Module Probe - Public Sub T() - Dim myHelpFile As String, myHelpContext As Long - Err.Raise vbObjectError + 894, Source:="MyApp.MyClass", _ - Description:="Was not able to complete your task", _ - HelpFile:=myHelpFile, HelpContext:=myHelpContext - End Sub -End Module diff --git a/bugs/filed/err-raise-helpcontext/src/Sources/Startup.twin b/bugs/filed/err-raise-helpcontext/src/Sources/Startup.twin deleted file mode 100644 index 20233b3a9..000000000 --- a/bugs/filed/err-raise-helpcontext/src/Sources/Startup.twin +++ /dev/null @@ -1,6 +0,0 @@ -Module Startup - - Public Sub Main() - End Sub - -End Module diff --git a/bugs/filed/err-raise-helpcontext/vb6/Module1.bas b/bugs/filed/err-raise-helpcontext/vb6/Module1.bas deleted file mode 100644 index 0e60b8f36..000000000 --- a/bugs/filed/err-raise-helpcontext/vb6/Module1.bas +++ /dev/null @@ -1,34 +0,0 @@ -Attribute VB_Name = "Module1" -Option Explicit - -' The VB6 side of err-raise-helpcontext: the twinBASIC project's call, with all five -' arguments named, compiles; the handler writes what Err holds to out.txt. - -Sub Main() - On Error GoTo Fail - Open App.Path & "\out.txt" For Output As #9 - T - Close #9 - Exit Sub -Fail: - On Error Resume Next - Print #9, "unhandled error " & Err.Number & ": " & Err.Description - Close #9 -End Sub - -Sub T() - Dim myHelpFile As String, myHelpContext As Long - myHelpFile = "my.hlp" - myHelpContext = 42 - On Error GoTo Handler - Err.Raise vbObjectError + 894, Source:="MyApp.MyClass", _ - Description:="Was not able to complete your task", _ - HelpFile:=myHelpFile, HelpContext:=myHelpContext - Exit Sub -Handler: - Print #9, "Number: "; Err.Number - vbObjectError; "+ vbObjectError" - Print #9, "Source: "; Err.Source - Print #9, "Description: "; Err.Description - Print #9, "HelpFile: "; Err.HelpFile - Print #9, "HelpContext: "; Err.HelpContext -End Sub diff --git a/bugs/filed/err-raise-helpcontext/vb6/Probe.vbp b/bugs/filed/err-raise-helpcontext/vb6/Probe.vbp deleted file mode 100644 index 4d210b0e8..000000000 --- a/bugs/filed/err-raise-helpcontext/vb6/Probe.vbp +++ /dev/null @@ -1,7 +0,0 @@ -Type=Exe -Module=Module1; Module1.bas -Startup="Sub Main" -ExeName32="Probe.exe" -Name="Probe" -CompilationType=0 -OptimizationType=0 diff --git a/bugs/filed/llvm-byval-to-byref-sub-codegen/REPORT.md b/bugs/filed/llvm-byval-to-byref-sub-codegen/REPORT.md deleted file mode 100644 index 0168edd84..000000000 --- a/bugs/filed/llvm-byval-to-byref-sub-codegen/REPORT.md +++ /dev/null @@ -1,48 +0,0 @@ -Filed as [twinbasic/twinbasic#2513](https://github.com/twinbasic/twinbasic/issues/2513). - -## LLVM: passing `ByVal` for a `ByRef` parameter of a twinBASIC `Sub` is a codegen error, and the calling procedure does not run - -**Describe the bug** -In a project compiled with LLVM, in a 32-bit and in a 64-bit build, a call that passes `ByVal` for a `ByRef` parameter of a twinBASIC `Sub` or `Function` (for example `Take ByVal 0&` for `Sub Take(ByRef h As Long)`) makes the build print `[LLVM] codegen error occurred when compiling 'Startup.Caller': Call parameter type does not match function signature!`, followed by the offending instruction, `call x86_stdcallcc void @"#0"(i32 0)` (an `i32` where the signature has a pointer; `double 0.0` for a `Double`). The build still makes the exe, and the procedure that contains the call does not run: the 64-bit exe ends with an access violation (0xC0000005, no message box, in `ntdll.dll` or `USER32.dll`) when it enters the procedure, and the 32-bit exe skips the procedure and then faults later, in `OLEAUT32.dll`. Without LLVM the call compiles and passes a NULL pointer. VB6 refuses the same call with the error *Type mismatch*, so the call is a twinBASIC extension that the LLVM back end cannot compile. Observed with `bug_repro.mjs run` (which uses `tbrun`) on an exe and in the IDE run, and with the exe's exit code read. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `llvm-byval-to-byref-sub-codegen.twinproj` (attached as `llvm-byval-to-byref-sub-codegen.zip`). It needs an LLVM licence (Ultimate). Its module `Startup` is: - ``` - Private Declare PtrSafe Sub ExitProcess Lib "kernel32" (ByVal uExitCode As Long) - - Private Sub Take(ByRef h As Long) - End Sub - - Private Function Caller() As Long - Take ByVal 0& - Caller = 7 - End Function - - Public Sub Main() - If App.IsInIDE Then - Debug.Print "Caller = " & Caller() - Else - ExitProcess Caller() - End If - End Sub - ``` -2. In Project Settings, set the architecture to 64-bit and turn **Enable LLVM Compilation** on under **Compiler Options (BUILD)** and under **Compiler Options (DEBUG)**. -3. Run the project in the IDE (F5), and build the exe and run it from a command prompt, reading its exit code (`echo %errorlevel%`). -4. The DEBUG CONSOLE shows the `[LLVM] codegen error` for `Startup.Caller`, the IDE run does not print `Caller = 7`, and the exe exits with -1073741819 (0xC0000005). Turn LLVM off and run again: it prints `Caller = 7`, and the exe exits with code 7. - -**Expected behavior** -Either the same as without LLVM, `Caller` returning 7 with a NULL pointer passed to `Take`, so the exe exits with code 7, or a compile error that names the call, as VB6 gives. A codegen error that the build reports and then ignores leaves a program that fails at run time. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1005 - -**Additional context** -The same error appears at the first and at the eighth parameter, for a `Long` and for a `Double` parameter, for an `Optional ByRef` parameter that is passed `ByVal 0&`, and with five instead of 0 as the value. These are right: a variable for the `ByRef` parameter, an `Optional ByRef` parameter that is left out, and a `ByVal` parameter. A smaller calling procedure fails the same way. This is not the crash of `llvm64-byval-to-byref-declare-crash`: that one is a call of a `Declare`d API in a 64-bit build only, and the calls in that entry compile. - -Severity: medium. A 64-bit program built with LLVM that has such a call ends at the calling procedure, with no message box; a 32-bit one silently skips the procedure, which is worse. The build does report the error, but as a log line, with a successful build after it. Passing a variable for the parameter avoids it. - -Not tried: `ByVal Nothing` for a `ByRef` object parameter of a twinBASIC procedure (the documentation's Pointers page describes it), a `ByVal` string or UDT argument, a method of a class, and a `Property` with a `ByRef` parameter. The generated code was not read. - - diff --git a/bugs/filed/llvm-byval-to-byref-sub-codegen/llvm-byval-to-byref-sub-codegen.twinproj b/bugs/filed/llvm-byval-to-byref-sub-codegen/llvm-byval-to-byref-sub-codegen.twinproj deleted file mode 100644 index 3f15f1fbf8178265f897ee0e0443947151a1543d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3255 zcmcgvTTdHD6gFvkftJUV*ge78aEgS}oyR_$N`fl3%vM*UvZb^Ht_Ycz5RusSF!mE*GrFM~W)W^ja{J`5Hf7W%l9S=Q?CjLPK`&^UtdbC|IWo)jMZf_jabxQw%ouz?R_`x@^K$7ZPp(=usLBLTbY8I=Ft;2wH<@#7qdtNn_T@N^7)f&8 z-`n3K@C+2cqlS?-3nQu+85ngzCm?#It6ET$d{I=fKv7FEmhNa&Y&m0bnOxjJ92wfZ98TUct~NwU42yo-$uo33spYGh#rG_P%D&* zZ7--bie9DBXn2ikz3f%%L8IIb%GG92e3rWayioT`Yawm^!NUT^hMSjSffd9Q6MzeF zZ=m+O*eJWZ*(6BVW~D-geTs zB%`jy+?hN^`u>CR_dk33L36`cR;J;(g65`xA7-4TztS(3yB$#tZf>!Z}%8u3kv!*fN!6{}${U?`W@-FNoB;iUnj zVw<2|wNh$=oSkyePx~MRG{N~fImLM!{&RN6u{vOM-K{NhQe(<+~MwTj>LD(#b& z*Q(U(Ub9*)lsc70(DD8Mr37&HkZB}GaDRj27>->z!u^&4^B*A(Uf)`$KKe{{YU=aL zic#D~z5lPpaN__4xkb&@NuiT-=gQ3#RLxpm^WAOu%)tV^g=Zh0Yc|}K*gaJOk1x=- z!=b^pZj}H?s)wNHHL#e diff --git a/bugs/filed/llvm-byval-to-byref-sub-codegen/repro.json b/bugs/filed/llvm-byval-to-byref-sub-codegen/repro.json deleted file mode 100644 index 48d777af4..000000000 --- a/bugs/filed/llvm-byval-to-byref-sub-codegen/repro.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "mode": "run", - "arch": "win64", - "llvm": true, - "exe": true, - "expect": { - "exit": 2, - "message": "\\[LLVM\\] codegen error occurred when compiling 'Startup\\.Caller': Call parameter type does not match function signature!", - "absent": [ - "^Caller = 7$", - "^--- exe: exit 7$" - ] - }, - "issue": 2513 -} diff --git a/bugs/filed/llvm-byval-to-byref-sub-codegen/src/Settings b/bugs/filed/llvm-byval-to-byref-sub-codegen/src/Settings deleted file mode 100644 index 405fb4288..000000000 --- a/bugs/filed/llvm-byval-to-byref-sub-codegen/src/Settings +++ /dev/null @@ -1,61 +0,0 @@ -{ - "configuration.inherits": "Defaults", - "project.appTitle": "LlvmByvalToByrefSubCodegen", - "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", - "project.buildType": "Standard EXE", - "project.description": "Reproduces: LLVM: ByVal argument for a ByRef parameter of a twinBASIC Sub is a codegen error", - "project.exportPathIsV2": true, - "project.id": "{E7E6021D-E681-4888-8573-57E83DE35AE1}", - "project.name": "LlvmByvalToByrefSubCodegen", - "project.optionExplicit": true, - "project.references": [ - { - "id": "{00020430-0000-0000-C000-000000000046}", - "lcid": 0, - "name": "OLE Automation", - "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", - "path64": "C:\\Windows\\System32\\stdole2.tlb", - "symbolId": "stdole", - "versionMajor": 2, - "versionMinor": 0 - }, - { - "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", - "isCompilerPackage": true, - "licence": "MIT", - "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", - "path32": "", - "path64": "", - "publisher": "TWINBASIC-COMPILER", - "symbolId": "VB", - "versionBuild": 0, - "versionMajor": 0, - "versionMinor": 0, - "versionRevision": 31 - }, - { - "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", - "isCompilerPackage": true, - "licence": "MIT", - "name": "[COMPILER PACKAGE] twinBASIC - App global class object", - "path32": "", - "path64": "", - "publisher": "TWINBASIC-COMPILER", - "symbolId": "AppGlobalClassProject", - "versionBuild": 0, - "versionMajor": 1, - "versionMinor": 0, - "versionRevision": 0 - } - ], - "project.settingsVersion": 1, - "project.startupObject": "Sub Main", - "project.warnings": { - "errors": [], - "hints": [], - "ignored": [], - "info": [], - "warnings": [] - }, - "runtime.useUnicodeStandardLibrary": true -} diff --git a/bugs/filed/llvm-byval-to-byref-sub-codegen/src/Sources/Startup.twin b/bugs/filed/llvm-byval-to-byref-sub-codegen/src/Sources/Startup.twin deleted file mode 100644 index 9a4526796..000000000 --- a/bugs/filed/llvm-byval-to-byref-sub-codegen/src/Sources/Startup.twin +++ /dev/null @@ -1,33 +0,0 @@ -' A call that passes ByVal for a ByRef parameter of a twinBASIC Sub, in a program compiled with LLVM. -' Caller returns 7. Without LLVM the call compiles and passes a NULL pointer (Take does not use it), and -' the exe exits with code 7. With LLVM on, the build prints "[LLVM] codegen error occurred when compiling -' 'Startup.Caller': Call parameter type does not match function signature!" and still makes the exe, and -' Caller does not run: the exe faults (exit code 0xC0000005 in 64-bit, in ntdll or USER32). VB6 refuses the call -' (Type mismatch). -Module Startup - - Private Declare PtrSafe Sub ExitProcess Lib "kernel32" (ByVal uExitCode As Long) - - Private Sub Take(ByRef h As Long) - End Sub - - Private Function Caller() As Long - Take ByVal 0& - Caller = 7 - End Function - - Public Sub Main() - If App.IsInIDE Then - Debug.Print "Caller = " & Caller() - Else - ExitProcess Caller() - End If - End Sub - - [RunAfterBuild] - Private Sub Built() - Debug.Cls - Main - End Sub - -End Module diff --git a/bugs/filed/llvm-conversion-rounds-half-away/REPORT.md b/bugs/filed/llvm-conversion-rounds-half-away/REPORT.md deleted file mode 100644 index 15d9ecb41..000000000 --- a/bugs/filed/llvm-conversion-rounds-half-away/REPORT.md +++ /dev/null @@ -1,35 +0,0 @@ -Filed as [twinbasic/twinbasic#2469](https://github.com/twinbasic/twinbasic/issues/2469). - -## LLVM-compiled code rounds a `Single` or `Double` to an integer half away from zero, where VB6 and plain code round half to even - -**Describe the bug** -In a procedure compiled with LLVM, `CLng`, `CInt` and `CLngLng` of a `Single` or `Double` variable, an assignment of one to a `Long` or `Integer`, and the operand conversion of `\` round a value exactly halfway between two integers away from zero: `CLng(2.5)` of a `Double` variable is 3 and `CLng(-0.5)` is -1. The same code without LLVM rounds to the even integer, 2 and 0, as VB6 does. A value that is not a tie rounds the same everywhere. Observed in a run of the reproducer project. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `llvm-conversion-rounds-half-away.twinproj` (attached as `llvm-conversion-rounds-half-away.zip`). It needs an LLVM licence (Ultimate): `LlvmCases` is compiled with LLVM by its `[CompilerOptions("+llvm")]`, and `PlainCases` holds the same statements without it. Each procedure converts 0.5, 2.5, 3.5, -0.5 and -2.5. -2. Run it (F5). The DEBUG CONSOLE has one line per value and mode; for 2.5 and -0.5 they read: - ``` - plain 2.5: CLng(d)=2 CLng(s)=2 CInt(d)=2 CLngLng(d)=2 l=d:2 d\1=2 CLng(v)=2 Round=2 - plain -0.5: CLng(d)=0 CLng(s)=0 CInt(d)=0 CLngLng(d)=0 l=d:0 d\1=0 CLng(v)=0 Round=0 - LLVM 2.5: CLng(d)=3 CLng(s)=3 CInt(d)=3 CLngLng(d)=3 l=d:3 d\1=3 CLng(v)=2 Round=2 - LLVM -0.5: CLng(d)=-1 CLng(s)=-1 CInt(d)=-1 CLngLng(d)=-1 l=d:-1 d\1=-1 CLng(v)=0 Round=0 - ``` - -**Expected behavior** -The same result in both: round half to even, as in VB6 (attached as `llvm-conversion-rounds-half-away-vb6.zip`), which prints for the same values `CLng(d)=2` for 2.5 and `CLng(d)=0` for -0.5, `l=d:2`, `d\1=2`, `CLng(v)=2` and `Round=2`, `0` and `0`. VB6 has no `LongLong`, so that column is missing from its output. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 997 - -**Additional context** -Also on BETA 995, identically, and on win64. - -What does not differ in LLVM-compiled code: `CLng` of a `Variant` holding the `Double`, `Round`, `Int` and `Fix`, and conversions of a literal such as `CLng(2.5)` (folded to 2 by the compiler). - -A consequence: a value that rounds to zero in plain code can raise an error under LLVM. In a `Byte` conversion, `CByte(-0.5)` and `b = -0.5` (a `Double` variable) give 0 without LLVM and error 6, *Overflow*, with it, because the value becomes -1. - -Severity: a silent wrong result, at ties only, unless the rounding makes a conversion overflow. - - diff --git a/bugs/filed/llvm-conversion-rounds-half-away/llvm-conversion-rounds-half-away.twinproj b/bugs/filed/llvm-conversion-rounds-half-away/llvm-conversion-rounds-half-away.twinproj deleted file mode 100644 index bd96cc769c5078f0858c94c3c9c7ea87b449f322..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3472 zcmd^CUvJ_@5GU8a(B`pk_pn0I9Z_Rsz>pB>9*lu2DkLb$C6z*SGWG`cYSw6XO`uUe zPQP3~MQ3(RFqfRJRn?c)5BBcP-~G+(%=+c;-~F+Zdzs7S`q5(M#e6}9WHCRB6CTQ+ z{OH!b^H)ILJYU1`MeYZ9_h$20C>ma@=Cseo!WWFn4P!3%6;58{cHuo>GN4i9a~daG zJg5$MzJ>SD4}SG0+bX^>pQvck*a*5l6hI(F|LJB z5FZD;d`q+ABw_*O0VSn(kzzAn(fwnh z!{SRS36r2JReHzN)X0oigegg`Jwp+n7}qt1a=F%1p?1d zum#v4=gF81aZ4VWDeW67auF zr3=`0{9sC!q{oRXgug1;<@y1s;;V))OV2G>$T);fh2#xc9)m0Tvw%;yQH%`H)1@{Y zCzCR4m@85=;VY6?pz0MV9uwmA`J@!?zrnS1jFglhGX%Mx5_()cN|^CR;v;QlYRJ)> z@EEN`W*Uwv+Gr^xBT`xGMuA1f=prTP6xIBC1ok%@KcP!yW*r`4NY_#RbYd9xY!*D` z+J8R@^m&62|38vo2xW`~`lJuwpl8FoIr*Dpdd!0-8HLGZs>yBvYv+!x+vij%sF^Zb zR=+GmNxm=Si$Tn9*(4D@)}=DzQ!1cf!Lcj0>8+ncFg^gbxrjf|K$ZRZ{DP^7B7^qh z6D?^=Xq>Pp9QtaCQhWEN5#an`G-@GjG}^l#8Ve`>j6S^IyLUw}WeQ9tfQ} zJ4;15w!ep`>aoe8dZ?m2^hHQIzjU^^LMj8nbooWTGYVS@6KGdZ#GiJAv~zr}+nCm} z-8l}(9e_y5Wvcbw)XV2-yv?IV(7CFaD)knbAYCX zz!bgX(ddftFus$c(fLYVonAGZ(MYN=j%c;4qH#u~;XEgz=&V+KPDHL|<2dSRtc}7{ zS9s^(y0f)do)=kg^3xw{EwNs%_h9dv42eYTb_2Y}6`NrP8c7+m7oT zR*%+zFuA@hq&F_Gm1Y9MWo{w3qe1T?^=ysWwRbug_WGSOGIYJ4-M5_^f)}cmd*1Vi z1u02TH9$IM5mPIY8XzTT`7^nn!OXy%Suh~37qDVUNON@K;;MI|Em&*oDOVQA%T~IW z!llnWNT*?6gP215(dru7Ex^y{f?+WLCAG@4pnH{OwcDySt%l>dmeX#wtd?_lXu0*e zUF|wYov!Eo`=Gn?IhjQ9*pEmM`BIX2jHPsqKwRD_cMh zJ_o-}`9l5%q#hfEOqmSO5S3 diff --git a/bugs/filed/llvm-conversion-rounds-half-away/repro.json b/bugs/filed/llvm-conversion-rounds-half-away/repro.json deleted file mode 100644 index 4b57886f9..000000000 --- a/bugs/filed/llvm-conversion-rounds-half-away/repro.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "mode": "run", - "expect": { - "exit": 0, - "output": [ - "^plain 2\\.5:\\s+CLng\\(d\\)=2\\s", - "^plain -0\\.5:\\s+CLng\\(d\\)=0\\s", - "^LLVM\\s+2\\.5:\\s+CLng\\(d\\)=3\\s+CLng\\(s\\)=3\\s+CInt\\(d\\)=3\\s+CLngLng\\(d\\)=3\\s+l=d:3\\s+d\\\\1=3\\s+CLng\\(v\\)=2\\s+Round=2$", - "^LLVM\\s+-0\\.5:\\s+CLng\\(d\\)=-1\\s" - ] - }, - "issue": 2469 -} diff --git a/bugs/filed/llvm-conversion-rounds-half-away/src/Settings b/bugs/filed/llvm-conversion-rounds-half-away/src/Settings deleted file mode 100644 index a9ff307f5..000000000 --- a/bugs/filed/llvm-conversion-rounds-half-away/src/Settings +++ /dev/null @@ -1,61 +0,0 @@ -{ - "configuration.inherits": "Defaults", - "project.appTitle": "LlvmConversionRoundsHalfAway", - "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", - "project.buildType": "Standard EXE", - "project.description": "Reproduces: LLVM-compiled code rounds a Single or Double to an integer half away from zero, where VB6 and plain code round half to even", - "project.exportPathIsV2": true, - "project.id": "{8ABB5233-02CD-48C9-A0C8-C67DD5442C43}", - "project.name": "LlvmConversionRoundsHalfAway", - "project.optionExplicit": true, - "project.references": [ - { - "id": "{00020430-0000-0000-C000-000000000046}", - "lcid": 0, - "name": "OLE Automation", - "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", - "path64": "C:\\Windows\\System32\\stdole2.tlb", - "symbolId": "stdole", - "versionMajor": 2, - "versionMinor": 0 - }, - { - "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", - "isCompilerPackage": true, - "licence": "MIT", - "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", - "path32": "", - "path64": "", - "publisher": "TWINBASIC-COMPILER", - "symbolId": "VB", - "versionBuild": 0, - "versionMajor": 0, - "versionMinor": 0, - "versionRevision": 31 - }, - { - "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", - "isCompilerPackage": true, - "licence": "MIT", - "name": "[COMPILER PACKAGE] twinBASIC - App global class object", - "path32": "", - "path64": "", - "publisher": "TWINBASIC-COMPILER", - "symbolId": "AppGlobalClassProject", - "versionBuild": 0, - "versionMajor": 1, - "versionMinor": 0, - "versionRevision": 0 - } - ], - "project.settingsVersion": 1, - "project.startupObject": "Sub Main", - "project.warnings": { - "errors": [], - "hints": [], - "ignored": [], - "info": [], - "warnings": [] - }, - "runtime.useUnicodeStandardLibrary": true -} diff --git a/bugs/filed/llvm-conversion-rounds-half-away/src/Sources/Startup.twin b/bugs/filed/llvm-conversion-rounds-half-away/src/Sources/Startup.twin deleted file mode 100644 index 51dc658c7..000000000 --- a/bugs/filed/llvm-conversion-rounds-half-away/src/Sources/Startup.twin +++ /dev/null @@ -1,31 +0,0 @@ -' Rounding a Single or Double to an integer: plain code rounds half to even, LLVM-compiled code half away from zero. -' Needs an LLVM licence (Ultimate): LlvmCases is compiled with LLVM by its [CompilerOptions("+llvm")]. -Module Startup - - Private Sub PlainCases() - Dim d As Double, s As Single, v As Variant, l As Long - Dim x As Variant - For Each x In Array(0.5, 2.5, 3.5, -0.5, -2.5) - d = x: s = x: v = d - l = d - Debug.Print "plain " & d & ": CLng(d)=" & CLng(d) & " CLng(s)=" & CLng(s) & " CInt(d)=" & CInt(d) & " CLngLng(d)=" & CLngLng(d) & " l=d:" & l & " d\1=" & (d \ 1) & " CLng(v)=" & CLng(v) & " Round=" & Round(d) - Next - End Sub - - [CompilerOptions("+llvm")] - Private Sub LlvmCases() - Dim d As Double, s As Single, v As Variant, l As Long - Dim x As Variant - For Each x In Array(0.5, 2.5, 3.5, -0.5, -2.5) - d = x: s = x: v = d - l = d - Debug.Print "LLVM " & d & ": CLng(d)=" & CLng(d) & " CLng(s)=" & CLng(s) & " CInt(d)=" & CInt(d) & " CLngLng(d)=" & CLngLng(d) & " l=d:" & l & " d\1=" & (d \ 1) & " CLng(v)=" & CLng(v) & " Round=" & Round(d) - Next - End Sub - - Public Sub Main() - PlainCases - LlvmCases - End Sub - -End Module diff --git a/bugs/filed/llvm-conversion-rounds-half-away/vb6/Module1.bas b/bugs/filed/llvm-conversion-rounds-half-away/vb6/Module1.bas deleted file mode 100644 index fd2a36fae..000000000 --- a/bugs/filed/llvm-conversion-rounds-half-away/vb6/Module1.bas +++ /dev/null @@ -1,23 +0,0 @@ -Attribute VB_Name = "Module1" -Option Explicit - -Sub Main() - Dim d As Double - Dim s As Single - Dim v As Variant - Dim l As Long - Dim x As Variant - On Error GoTo Fail - Open App.Path & "\out.txt" For Output As #9 - For Each x In Array(0.5, 2.5, 3.5, -0.5, -2.5) - d = x: s = x: v = d - l = d - Print #9, "VB6 " & d & ": CLng(d)=" & CLng(d) & " CLng(s)=" & CLng(s) & " CInt(d)=" & CInt(d) & " l=d:" & l & " d\1=" & (d \ 1) & " CLng(v)=" & CLng(v) & " Round=" & Round(d) - Next - Close #9 - Exit Sub -Fail: - On Error Resume Next - Print #9, "unhandled error " & Err.Number & ": " & Err.Description - Close #9 -End Sub diff --git a/bugs/filed/llvm-conversion-rounds-half-away/vb6/Probe.vbp b/bugs/filed/llvm-conversion-rounds-half-away/vb6/Probe.vbp deleted file mode 100644 index 4d210b0e8..000000000 --- a/bugs/filed/llvm-conversion-rounds-half-away/vb6/Probe.vbp +++ /dev/null @@ -1,7 +0,0 @@ -Type=Exe -Module=Module1; Module1.bas -Startup="Sub Main" -ExeName32="Probe.exe" -Name="Probe" -CompilationType=0 -OptimizationType=0 diff --git a/bugs/filed/llvm-double-overflow-no-error/REPORT.md b/bugs/filed/llvm-double-overflow-no-error/REPORT.md index b5aa9b4f6..bbea37472 100644 --- a/bugs/filed/llvm-double-overflow-no-error/REPORT.md +++ b/bugs/filed/llvm-double-overflow-no-error/REPORT.md @@ -52,4 +52,4 @@ A `Single` addition, subtraction, multiplication or division that overflows rais Severity: a silent wrong result. An overflow that raises 6 in a debug run and no error in the LLVM-compiled build changes what a program computes, and nothing marks the difference. - + diff --git a/bugs/filed/lset-variant-destination-types/REPORT.md b/bugs/filed/lset-variant-destination-types/REPORT.md index 7eea25fed..cce114aca 100644 --- a/bugs/filed/lset-variant-destination-types/REPORT.md +++ b/bugs/filed/lset-variant-destination-types/REPORT.md @@ -27,4 +27,4 @@ Severity: low. Programs rarely align into a `Variant` that holds `Null` or an ob A `Variant` holding an `Integer`, a `Long`, a `Double` or a `Date` is aligned within its text as in VB6, and one holding an error value or an array raises 13 in both. A `Null` source into a `String` destination raises 94 in both. The same results on BETA 983. - + diff --git a/bugs/filed/lset-variant-destination-types/lset-variant-destination-types.twinproj b/bugs/filed/lset-variant-destination-types/lset-variant-destination-types.twinproj index 1c517736f2d16172e628d2a013f86688c690cf81..7aead836d37900b841b70274432c1252893e4fc2 100644 GIT binary patch delta 109 zcmcaC@?U%d6C=|x=E*FKpO}MFOD2D1oB*W_Ca+@RnY@Tma`HJQp~*2!D<@Yo=`;FF uUd^ODc{iib diff --git a/bugs/filed/redim-empty-range-unspecified-error/redim-empty-range-unspecified-error.twinproj b/bugs/filed/redim-empty-range-unspecified-error/redim-empty-range-unspecified-error.twinproj deleted file mode 100644 index 50571981006842b3c5c902a24077d6a1cb640741..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4663 zcmcgwS#RSu5O%g_()zJ)1xgsf#sMtDhwQ`w`rvaBG>4I7lWo_FLbe#2TUit+%1IF9 z=k%?=tiPf&qG%mfvTcFZKpbf<&U~C14*mV_-~72%_@PiJTu_@Whf7aBTv+a$zIA<% z&e$!Z_D~2eKqCHp36HM|-@*6s(&Iu>`|81?Cu}M#!Kfb#77AbB%d0{WzE7DyqmE;_ zlm{spGzT(Y!}r*l{btS6CcaYaZTNRVeIAHen*U1iQS5S)``H%Iw+i1PS6V^_URB;P zH&jAJ{QLp7_s9huuqC-;azQMY1fE9)nexE3$&@<$j>rWi@PADe7W1i3D8fUGM%_Fx z+XszSr$Q3o6?gWOM1|ZfsG#J!*Cr`VkiQJ3{!B1W5+1hA*1G{ z6wICP$Myzv8qBNUfh!}a*JL%lvZmxUfkdgE1qJMRo+hrhT-G(`HG?Zju%6gr&vkAkjxeNeEN~j5?CT@g=<_x zPFrr46FHE}bn{&ckeqew+(pa>J-#-d>law$S$eLQ(RH&muEh8ThbHrOH-pGhY#2j&>8T!-8D6GkODp}_t znuM53euVfo<8U)exce2NWE09Ml(HHjbT6w_$TRrHjGWaJnvqC0x(pN4RbLcyQ zP=_ul)SQIk^IHeeA+HMN1(nriI!5K9viBv`jn%Z~ z3afDnJ#j6OKzs$s(Z#|j*DIt^AHJAq38xRe5Sw!mY>&M^AmbNOu# z54y6opIA8A>Pv)Jw-WM@NjToLFq(x{AHZL!5*>s{h!27|zD&FSXCddcojBs>`cG6A zFM#=M;l#!@+6mM?on~wG=kskXlH9NuE>-(8?n=QO{|MTCI1}J-z+I}_K?Ty)#+0Qv zxJ8L87nUTEgp8Y>@g7$bHq3N&@Lo~ z0(?~CP;m!mwykhpyrfb>)%wYC?(<(@6nrf1Y?lDOTQ(1bC1LWYG8Y;tlYZ%llm_(H z3LJQ@pqwW-9{yF!^RAe5C`x={9!@Jx1LoLcOD<4z_fd_Z`2IAR^pH21>^_dwDreS` zKK;7;*cGz{ldxs~al{-tyqDDV!P%!A`#86ueV7Ja+Y&Yzz8j{+?0C*nAwXbu z0ORt}za4H9ldK+NF|G0k*0eM99SnWDuMabLxPYOG(0=LCl6HuDmc2EQOzJGo?K0X;X_=&RMd?ISu~>T`}Apu+xO$H-8KI^-g2dYj%vb z+3y%C=_fZsl>= diff --git a/bugs/filed/redim-inverted-bounds-error/redim-inverted-bounds-error.twinproj b/bugs/filed/redim-inverted-bounds-error/redim-inverted-bounds-error.twinproj deleted file mode 100644 index 49756c5adbd43a74e63f512da4113e128fab1b34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4465 zcmcgw>u%dN6n497y!qb-48wj9MzA|JKs4x=cvE^^TU773cnNzg?${5Xh6>}mpJIK zga*RloN-vX{e26==Y^l)If!DWB*&LA-X*Wk}S7A)cZ^7`{^U5SnEuYRSLpA~*ecTYJMf-pM9LW-(9 zCp6ux6sX^?pko8=<6I0$CHE3$dL%;UM9z>)Q4+_PBTsRILePig3}%TGC}ih1E8Hal z3xpMU$l59uH&A($c*5r-mWU zF=uZ<2zgkL0K=g~AV#rL9CJe9c#w=Xt4Ml0p-9tG@Ce3$!u4m!cLfIgK`uVDMyzI$}2`!H^;4#2lR6*vR} z4s6cdOTamgTH9l1cwdF=$$lE$)?LEwN=?D5ub_R8qBN0!_e2t5w2Lp~tyAoZxrzQj zwS}40-mt(Z&{N$_FG?ogeC%l=pkEH2G9GgSPHGj_~>(3^MrPMDHHl| zH^!_F$@^r4eMSSgyqKy){RGZws0cN;&?BzU=WtIN^gW#AeX!{6mH853_E6!;uF?Xy zd-q9Qt*jE1imF0BEBpx0k#xCC;`;@(2aEH&c{})+Uqnt6^4}cijkIpR8Zm+@BE%wHhkvb#?u! zPeSZm$Sk6-SLj#WlhO~I(ZJ;aa*mv3vEVj^dM==@?r~}1DjdvvE0inBf}S}15}I*#DM|eBv0@X=_F8)ubj5s zs<(QU=2(`anax^D>(s4|X4M*Yquw(N!#3}bLxIrD+|^C#G7=W6D1cT9`UE0ZA>sHG zOrk&=tR6#Ptf?qkTPx2Y>bh>|W?k1{Fn@IOk($zVvzbAJJ)xiO(v_|7nq%GDc95M& z7Ns$nB81f#_^B&)%H5Zf$swTuI~SA5=u#Z+9X8F$M96@J*r>|TTinw$SM8BFsv9f! zh|9=h;UHx^Q<-anj$S3y9rrEcpwpPc&Zh}Yr*&n_b-K1pO~0Xc+D1>;dR@Dtnf1P- zSZS#wn*whC!d`&mnH$&pT|K?=$z-f(>c$}^zY*{qPzoo>d*tNc0sph z8GX1iTGQ;>n%RT9(=l5u&2BVwqi?pIez*HYTwupBnug4S=GYHi_yA&_`ZCFhc_QZF z)K}@DU3KO><>qHT&Kb33)}eFCf_`~=!5I%;2dizi?3&)w+E%-znP$h(+8r1?!T2fJaW!48i%$_F=fTFk5dL6)@OTBNSKUC$bj zy)5qF@~Y25)mNl=Z^X`BPF3>&`_pVzPD@*w{L}N+$}{!h49my!cQS>Lz$$&-%+OP| s@VcP#7`{xiTv>*XYzZxEH4*qVB|ZyqUdpro6uATqDgl;?*I6I+KfT4llmGw# diff --git a/bugs/filed/redim-inverted-bounds-error/repro.json b/bugs/filed/redim-inverted-bounds-error/repro.json deleted file mode 100644 index 020b28ffc..000000000 --- a/bugs/filed/redim-inverted-bounds-error/repro.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "mode": "test", - "expect": { - "exit": 7, - "output": [ - "^PASS RegressionTests.ReDim_OneElement_Succeeds$", - "^PASS RegressionTests.ReDim_NegativeBoundsInOrder_Succeeds$", - "^PASS RegressionTests.FixedArray_SubscriptAboveRange_Raises9$", - "^FAIL RegressionTests.ReDim_ZeroToMinusOne_Raises9: .*Actual: -2147467259", - "^FAIL RegressionTests.ReDim_MinusOne_Raises9: .*Actual: -2147467259", - "^FAIL RegressionTests.ReDim_FiveToFour_SecondDimension_Raises9: .*Actual: -2147467259", - "^FAIL RegressionTests.ReDim_ErrorDescription: .*Actual: \"Unspecified error\"" - ] - }, - "issue": 2544 -} diff --git a/bugs/filed/redim-inverted-bounds-error/src/Settings b/bugs/filed/redim-inverted-bounds-error/src/Settings deleted file mode 100644 index 526080587..000000000 --- a/bugs/filed/redim-inverted-bounds-error/src/Settings +++ /dev/null @@ -1,75 +0,0 @@ -{ - "configuration.inherits": "Defaults", - "project.appTitle": "RedimInvertedBoundsError", - "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", - "project.buildType": "Standard EXE", - "project.description": "Reproduces: ReDim with an upper bound below its lower bound raises -2147467259, *Unspecified error*, where VB6 raises error 9", - "project.exportPathIsV2": true, - "project.id": "{E8A737D9-E99E-4617-B39B-915A53D222A4}", - "project.name": "RedimInvertedBoundsError", - "project.optionExplicit": true, - "project.references": [ - { - "id": "{00020430-0000-0000-C000-000000000046}", - "lcid": 0, - "name": "OLE Automation", - "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", - "path64": "C:\\Windows\\System32\\stdole2.tlb", - "symbolId": "stdole", - "versionMajor": 2, - "versionMinor": 0 - }, - { - "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", - "isCompilerPackage": true, - "licence": "MIT", - "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", - "path32": "", - "path64": "", - "publisher": "TWINBASIC-COMPILER", - "symbolId": "VB", - "versionBuild": 0, - "versionMajor": 0, - "versionMinor": 0, - "versionRevision": 31 - }, - { - "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", - "isCompilerPackage": true, - "licence": "MIT", - "name": "[COMPILER PACKAGE] twinBASIC - App global class object", - "path32": "", - "path64": "", - "publisher": "TWINBASIC-COMPILER", - "symbolId": "AppGlobalClassProject", - "versionBuild": 0, - "versionMajor": 1, - "versionMinor": 0, - "versionRevision": 0 - }, - { - "id": "{9847A10D-8987-44B2-8B87-EA77DEF7F1D9}", - "isCompilerPackage": true, - "licence": "MIT", - "name": "[COMPILER PACKAGE] Assert", - "path32": "", - "path64": "", - "publisher": "TWINBASIC-COMPILER", - "symbolId": "Assert", - "versionBuild": 0, - "versionMajor": 0, - "versionMinor": 0, - "versionRevision": 24 - } - ], - "project.settingsVersion": 1, - "project.startupObject": "Sub Main", - "project.warnings": { - "errors": [], - "hints": [], - "ignored": [], - "info": [], - "warnings": [] - }, - "runtime.useUnicodeStandardLibrary": true -} diff --git a/bugs/filed/redim-inverted-bounds-error/src/Sources/RegressionTests.twin b/bugs/filed/redim-inverted-bounds-error/src/Sources/RegressionTests.twin deleted file mode 100644 index de377aac3..000000000 --- a/bugs/filed/redim-inverted-bounds-error/src/Sources/RegressionTests.twin +++ /dev/null @@ -1,62 +0,0 @@ -' Regression tests: ReDim with an upper bound less than its lower bound raises error 9, -' "Subscript out of range". Expected numbers are VB6's (the VB6 project beside this one -' prints them). tbrun --tests runs each case and prints PASS or FAIL. -[TestFixture] -Module RegressionTests - - ' Controls: bounds in order, an empty-looking but valid range, and a subscript out of range. - [TestCase] - Public Sub ReDim_OneElement_Succeeds() - Dim X() As Long - ReDim X(0 To 0) - Assert.Strict.AreEqual 0&, UBound(X) - End Sub - - [TestCase] - Public Sub ReDim_NegativeBoundsInOrder_Succeeds() - Dim X() As Long - ReDim X(-3 To -1) - Assert.Strict.AreEqual -3&, LBound(X) - End Sub - - [TestCase] - Public Sub FixedArray_SubscriptAboveRange_Raises9() - Dim A(1 To 3) As Long - On Error Resume Next - A(4) = 1 - Assert.Strict.AreEqual 9&, Err.Number - End Sub - - [TestCase] - Public Sub ReDim_ZeroToMinusOne_Raises9() - Dim X() As Long - On Error Resume Next - ReDim X(0 To -1) - Assert.Strict.AreEqual 9&, Err.Number - End Sub - - [TestCase] - Public Sub ReDim_MinusOne_Raises9() - Dim X() As Long - On Error Resume Next - ReDim X(-1) - Assert.Strict.AreEqual 9&, Err.Number - End Sub - - [TestCase] - Public Sub ReDim_FiveToFour_SecondDimension_Raises9() - Dim X() As Long - On Error Resume Next - ReDim X(5 To 4, 1) - Assert.Strict.AreEqual 9&, Err.Number - End Sub - - [TestCase] - Public Sub ReDim_ErrorDescription() - Dim X() As Long - On Error Resume Next - ReDim X(0 To -1) - Assert.Strict.AreEqual "Subscript out of range", Err.Description - End Sub - -End Module diff --git a/bugs/filed/redim-inverted-bounds-error/src/Sources/Startup.twin b/bugs/filed/redim-inverted-bounds-error/src/Sources/Startup.twin deleted file mode 100644 index be8f5c8bc..000000000 --- a/bugs/filed/redim-inverted-bounds-error/src/Sources/Startup.twin +++ /dev/null @@ -1,6 +0,0 @@ -Module Startup - - Public Sub Main() - End Sub - -End Module diff --git a/bugs/filed/redim-inverted-bounds-error/vb6/Module1.bas b/bugs/filed/redim-inverted-bounds-error/vb6/Module1.bas deleted file mode 100644 index 9cff43dae..000000000 --- a/bugs/filed/redim-inverted-bounds-error/vb6/Module1.bas +++ /dev/null @@ -1,30 +0,0 @@ -Attribute VB_Name = "Module1" -Option Explicit - -' What VB6 raises for ReDim with an upper bound less than its lower bound: error 9. -' Build and run it with: node scripts/bug_repro.mjs vb6 redim-inverted-bounds-error - -Private Sub Report(ByVal Label As String) - Print #9, Label & ": " & Err.Number & " " & Err.Description - Err.Clear -End Sub - -Sub Main() - Open App.Path & "\out.txt" For Output As #9 - On Error Resume Next - Dim X() As Long - Dim A(1 To 3) As Long - ReDim X(0 To 0) - Report "ReDim_OneElement" - ReDim X(-3 To -1) - Report "ReDim_NegativeBoundsInOrder" - A(4) = 1 - Report "FixedArray_SubscriptAboveRange" - ReDim X(0 To -1) - Report "ReDim_ZeroToMinusOne" - ReDim X(-1) - Report "ReDim_MinusOne" - ReDim X(5 To 4, 1) - Report "ReDim_FiveToFour_SecondDimension" - Close #9 -End Sub diff --git a/bugs/filed/redim-inverted-bounds-error/vb6/Probe.vbp b/bugs/filed/redim-inverted-bounds-error/vb6/Probe.vbp deleted file mode 100644 index 4d210b0e8..000000000 --- a/bugs/filed/redim-inverted-bounds-error/vb6/Probe.vbp +++ /dev/null @@ -1,7 +0,0 @@ -Type=Exe -Module=Module1; Module1.bas -Startup="Sub Main" -ExeName32="Probe.exe" -Name="Probe" -CompilationType=0 -OptimizationType=0 diff --git a/bugs/filed/round-null-negative-digits/REPORT.md b/bugs/filed/round-null-negative-digits/REPORT.md deleted file mode 100644 index 5ad6f3487..000000000 --- a/bugs/filed/round-null-negative-digits/REPORT.md +++ /dev/null @@ -1,69 +0,0 @@ -Filed as [twinbasic/twinbasic#2540](https://github.com/twinbasic/twinbasic/issues/2540). - -## `Round` of `Null` with a negative *numdecimalplaces* returns `Null`, where VB6 raises error 5 - -**Describe the bug** -`Round` raises error 5, *Invalid procedure call or argument*, for a negative *numdecimalplaces*: `Round(1.5, -1)` and `Round(Empty, -1)` raise it in twinBASIC and VB6. When the *expression* is `Null`, VB6 still raises 5, and twinBASIC returns `Null`. Observed in the compiler's test mode, 32-bit and 64-bit, and in a built exe. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `round-null-negative-digits.twinproj` (attached as `round-null-negative-digits.zip`). -2. Run its test cases, the module below. On BETA 1010 the three controls pass and `Round_NullNegativeDigits_Raises5` fails with `Actual: 0`. - -The module is meant to be adapted as a regression test. Each case asserts VB6's result, which the attached VB6 project (`round-null-negative-digits-vb6.zip`) prints. -```vb -' Regression tests: Round raises error 5 for a negative numdecimalplaces when the -' expression is Null, as it does for any other expression, and as VB6 does (the VB6 -' project beside this one prints it). tbrun --tests runs each case and prints PASS or FAIL. -[TestFixture] -Module RegressionTests - - ' Controls: a negative numdecimalplaces raises 5 for a number and for Empty, and - ' Round of Null with a valid numdecimalplaces returns Null. - [TestCase] - Public Sub Round_NumberNegativeDigits_Raises5() - Dim V As Variant - On Error Resume Next - V = Round(1.5, -1) - Assert.Strict.AreEqual 5&, Err.Number - End Sub - - [TestCase] - Public Sub Round_EmptyNegativeDigits_Raises5() - Dim V As Variant - On Error Resume Next - V = Round(Empty, -1) - Assert.Strict.AreEqual 5&, Err.Number - End Sub - - [TestCase] - Public Sub Round_NullValidDigits_ReturnsNull() - Dim V As Variant = Round(Null, 2) - Assert.Strict.IsTrue IsNull(V) - End Sub - - ' The defect: Null with a negative numdecimalplaces returns Null. - [TestCase] - Public Sub Round_NullNegativeDigits_Raises5() - Dim V As Variant - On Error Resume Next - V = Round(Null, -1) - Assert.Strict.AreEqual 5&, Err.Number - End Sub - -End Module -``` - -**Expected behavior** -Error 5 for a negative *numdecimalplaces* whatever the *expression*, as VB6 does. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1010 - -**Additional context** -Severity: low. An invalid argument goes unreported only while the value is `Null`. - -What does not reproduce it: a *numdecimalplaces* above 22 with `Null`, which returns `Null` in VB6 too (the limit of 22 applies only to a `Single`, `Double`, `Date` or string); a `Null` *numdecimalplaces*, which raises 94 in both. - - diff --git a/bugs/filed/round-null-negative-digits/repro.json b/bugs/filed/round-null-negative-digits/repro.json deleted file mode 100644 index 4a2da722d..000000000 --- a/bugs/filed/round-null-negative-digits/repro.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "mode": "test", - "expect": { - "exit": 7, - "output": [ - "^PASS RegressionTests.Round_NumberNegativeDigits_Raises5$", - "^PASS RegressionTests.Round_EmptyNegativeDigits_Raises5$", - "^PASS RegressionTests.Round_NullValidDigits_ReturnsNull$", - "^FAIL RegressionTests.Round_NullNegativeDigits_Raises5: .*Actual: 0 " - ] - }, - "issue": 2540 -} diff --git a/bugs/filed/round-null-negative-digits/round-null-negative-digits.twinproj b/bugs/filed/round-null-negative-digits/round-null-negative-digits.twinproj deleted file mode 100644 index f94d3ef5a16cca1830f873ea4d883f7f51b47594..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3804 zcmcgvTTkOg6wYq1Ny}TSzVu-fp~6ZyauVl;)Cb3LR#CYrp`j8%$gzhoTi-e}1|r1I z>7VO=sMK@DHrNZhtG3JL!5PoFf9K3O;~)S0=C2o(A1al~G)hA6Ao2YJx^g7Dq9e9q zQUK-Q_a!V}R=$VlB#0v}sdv1N=^k5hhcjB@{Q{+4RyN?d&xA{T-wA1ylz31b@O%x= zBgg&SS(R0Mm6EIQH>DyEf0*|^ZaQxVJz(aC`5M_%I70x$}{joLxg=OZv=|}AZwIkltAKN@DeBRM z!i!upc?s&T9G`iQ!R8cPh2Ric@E_e$F<1~M1c2{H$M{u^IiNQM>YV(XTH07)I!F71VJRR#MO0bFK2=(3${u)474b0un^iT%(VJ0sgLM|llZXS0k*88Hk>#+ucF>a2up*_;IZntX>`onfp@Ab-#h7g;w{q!B8n+`A>l!Swhn25zOV^FAat=g%R|)A5 zBfRBK9`4!1Ok@i57X*` zbBT9p9MTBHJ=e9vLmb2)jSZ@Jem*1^b>Rq;@Hk=cj_wg59*I63;+NC diff --git a/bugs/filed/round-null-negative-digits/src/Sources/RegressionTests.twin b/bugs/filed/round-null-negative-digits/src/Sources/RegressionTests.twin deleted file mode 100644 index c2dbb797a..000000000 --- a/bugs/filed/round-null-negative-digits/src/Sources/RegressionTests.twin +++ /dev/null @@ -1,40 +0,0 @@ -' Regression tests: Round raises error 5 for a negative numdecimalplaces when the -' expression is Null, as it does for any other expression, and as VB6 does (the VB6 -' project beside this one prints it). tbrun --tests runs each case and prints PASS or FAIL. -[TestFixture] -Module RegressionTests - - ' Controls: a negative numdecimalplaces raises 5 for a number and for Empty, and - ' Round of Null with a valid numdecimalplaces returns Null. - [TestCase] - Public Sub Round_NumberNegativeDigits_Raises5() - Dim V As Variant - On Error Resume Next - V = Round(1.5, -1) - Assert.Strict.AreEqual 5&, Err.Number - End Sub - - [TestCase] - Public Sub Round_EmptyNegativeDigits_Raises5() - Dim V As Variant - On Error Resume Next - V = Round(Empty, -1) - Assert.Strict.AreEqual 5&, Err.Number - End Sub - - [TestCase] - Public Sub Round_NullValidDigits_ReturnsNull() - Dim V As Variant = Round(Null, 2) - Assert.Strict.IsTrue IsNull(V) - End Sub - - ' The defect: Null with a negative numdecimalplaces returns Null. - [TestCase] - Public Sub Round_NullNegativeDigits_Raises5() - Dim V As Variant - On Error Resume Next - V = Round(Null, -1) - Assert.Strict.AreEqual 5&, Err.Number - End Sub - -End Module diff --git a/bugs/filed/round-null-negative-digits/src/Sources/Startup.twin b/bugs/filed/round-null-negative-digits/src/Sources/Startup.twin deleted file mode 100644 index be8f5c8bc..000000000 --- a/bugs/filed/round-null-negative-digits/src/Sources/Startup.twin +++ /dev/null @@ -1,6 +0,0 @@ -Module Startup - - Public Sub Main() - End Sub - -End Module diff --git a/bugs/filed/round-null-negative-digits/vb6/Module1.bas b/bugs/filed/round-null-negative-digits/vb6/Module1.bas deleted file mode 100644 index be47aef74..000000000 --- a/bugs/filed/round-null-negative-digits/vb6/Module1.bas +++ /dev/null @@ -1,21 +0,0 @@ -Attribute VB_Name = "Module1" -Option Explicit - -' What VB6 does with Round of Null and a negative numdecimalplaces: error 5. -' Build and run it with: node scripts/bug_repro.mjs vb6 round-null-negative-digits - -Private Sub Report(ByVal Label As String, ByVal Result As Variant) - Print #9, Label & ": " & Err.Number & " " & Err.Description & " (" & TypeName(Result) & ")" - Err.Clear -End Sub - -Sub Main() - Dim V As Variant - Open App.Path & "\out.txt" For Output As #9 - On Error Resume Next - V = Empty: V = Round(1.5, -1): Report "Round_NumberNegativeDigits", V - V = Empty: V = Round(Empty, -1): Report "Round_EmptyNegativeDigits", V - V = Empty: V = Round(Null, 2): Report "Round_NullValidDigits", V - V = Empty: V = Round(Null, -1): Report "Round_NullNegativeDigits", V - Close #9 -End Sub diff --git a/bugs/filed/round-null-negative-digits/vb6/Probe.vbp b/bugs/filed/round-null-negative-digits/vb6/Probe.vbp deleted file mode 100644 index 4d210b0e8..000000000 --- a/bugs/filed/round-null-negative-digits/vb6/Probe.vbp +++ /dev/null @@ -1,7 +0,0 @@ -Type=Exe -Module=Module1; Module1.bas -Startup="Sub Main" -ExeName32="Probe.exe" -Name="Probe" -CompilationType=0 -OptimizationType=0 diff --git a/bugs/filed/runafterbuild-generic-sub-never-runs/REPORT.md b/bugs/filed/runafterbuild-generic-sub-never-runs/REPORT.md deleted file mode 100644 index 05b19c6a1..000000000 --- a/bugs/filed/runafterbuild-generic-sub-never-runs/REPORT.md +++ /dev/null @@ -1,41 +0,0 @@ -Filed as [twinbasic/twinbasic#2516](https://github.com/twinbasic/twinbasic/issues/2516). - -## `[RunAfterBuild]` on a generic Sub is accepted and the Sub never runs - -**Describe the bug** -A Sub marked `[RunAfterBuild]` runs in the IDE once the build has finished. When the Sub is generic (`Public Sub Built(Of T)()`), the attribute is accepted with no diagnostic, the project builds, and the DEBUG CONSOLE says `[BUILD] Executing '...Built'...` as it does for any other such Sub, but the body of the Sub is never run: its `Debug.Print` prints nothing and a file it opens is not written. The compiler does count the Sub as the project's `[RunAfterBuild]`, since a second one is TB5114, *encountered too many [RunAfterBuild] attributes. Only allowed one per-project*. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `runafterbuild-generic-sub-never-runs.twinproj` (attached as `runafterbuild-generic-sub-never-runs.zip`). Its one source file, `Startup.twin`, holds the whole bug: - ``` - Module Startup - - Public Sub Main() - End Sub - - [RunAfterBuild] - Public Sub Built(Of T)() - Debug.Cls - Debug.Print "built" - End Sub - - End Module - ``` -2. Build the project. -3. See the DEBUG CONSOLE: the build log, ending `[BUILD] Executing 'RunafterbuildGenericSubNeverRuns.Startup.Built'...`, and no line `built` after it. Remove `(Of T)` and build again: `built` is printed (the console is cleared first, so it is the only line). - -Tried in a project of its own: a body that opens a file under `Environ("TEMP")` and writes a line to it, in place of the `Debug.Print`; the file is not created. The same in a 32-bit and a 64-bit build. - -**Expected behavior** -The Sub runs after the build, as it does without `(Of T)`, or the attribute is refused on a generic Sub with a diagnostic (as `[RunAfterBuild]` is refused on a second procedure, TB5114). A Sub that the compiler accepts, counts and announces as executing runs. The attribute does not exist in VB6. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1005 - -**Additional context** -`[RunAfterBuild]` on a `Function` and on a `Property Get` in a Module runs as it does on a Sub, in the same project layout. -Severity: low. Nothing crashes and nothing is lost; the code the author expected to run does not, with no message. - - diff --git a/bugs/filed/runafterbuild-generic-sub-never-runs/repro.json b/bugs/filed/runafterbuild-generic-sub-never-runs/repro.json deleted file mode 100644 index def14e2fd..000000000 --- a/bugs/filed/runafterbuild-generic-sub-never-runs/repro.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "mode": "run", - "exe": true, - "expect": { - "exit": 0, - "output": [ - "^\\[BUILD\\] Executing 'RunafterbuildGenericSubNeverRuns\\.Startup\\.Built'\\.\\.\\.$", - "^--- exe: exit 0$" - ], - "absent": [ - "^built$" - ] - }, - "issue": 2516 -} diff --git a/bugs/filed/runafterbuild-generic-sub-never-runs/runafterbuild-generic-sub-never-runs.twinproj b/bugs/filed/runafterbuild-generic-sub-never-runs/runafterbuild-generic-sub-never-runs.twinproj deleted file mode 100644 index fefd19d82a04a48e4f75940639cae05878dd1409..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2577 zcmcgu%}(n^6b|$^X|wF^E=Cd31qnw^Vjy8>I}VCUAc9<|L{R0}Lzr7<#+n&lh!Bs{ zSL>p0&|S|N+mHYi-LzRao;iQt`OY~r{_y(`e{NJrrBXS|czC0!oMkMDk0_^-Md#V< zgnpqCWC~QCf8WCEX5}Y%4i~AAn#Pl5N{`u0hLX`W-EYupvr>oWh^dGsNyw?l)^yMv z=zI^)aTxs;&evUhmy4_LcSe=SWV9~d%;l(d{#yFg2JCNCenhDbrOwi(zGK`Np&|bM z0G*HI3i5Q30}na4CW4cY%yTLTa6y4^BJC+DZf0a4*n)oULK)98oZ(o)n3r$kVI3Q?}Z445LMamM;jRjS(%DBcTh1WyKK+#A5*q7NcZj@3fKGVoxns|Gy zmebrJmynPEG~1^)WU{ln3ZM*LuZ={UB>+9=v|g*%2>iy`EMbvhYZNlRy_0VT005FK zwO(L(Wr_vRdGyi)I`7k2HgEa~@Q6$NvZ zsBh79>ml!8Tt7~y$U{7xZas|iDxHK2`uOYCgDaz3rlD+^q>s(5hXHgzaIYy>fcEhX z|2UrLAF9RUP{t(qC0JLBsfr{^FY@$GT4r>O3>Kq{ zhrM2Z(02C%%kkWn<+KhQtJ^-XtzIt}w7dJg?%=?C0hU98Cj2|6(K;ewUwxM zA(HflO3EP}kX}LIs#xV}wNXH|ZM$td9ovGJ;^CKXxMkbUehw9ElE_%um@_O(%sAw3 zrp3baj6n#}6nN?&OAY^UI=y5(7I$hoJzuKJ)60ElI#oIr32irZGF!FTciytmbkS+Q zX`_~lnMj6)`P^mc4Gt1)i&6MlNbuP%V3*sBn{6ADc{H+hY6g3@*KPN0tM9v><#Yyt z)!Xm1tX8WB1$JELpxu3)W^E&-!^b5o4fS*q5pGx4jdhLPN=~?@Z z?^;g3=UJX}aA3K6dv<%^bc2EK|1TwQ)0E5;F$)tCC81J8%<%FkiTRJ1hp0y;sE;v= zqp*zm?~2h{XMOZ;G4gSMf?T8J(=o4#OXDJ+DX5z9{4CFJ!_%A==q>zgz>CnpeNwv% zCC9r5?9H$>sFXsQv&qD(Hg4fUTU|5wvXB&~`>N1j{6?%SS6p6Q17tJG4fuf2Y(blu aqMta6L`=&YApclff(M+>dhId48~+AMExpD7 diff --git a/bugs/filed/runafterbuild-generic-sub-never-runs/src/Settings b/bugs/filed/runafterbuild-generic-sub-never-runs/src/Settings deleted file mode 100644 index fb08c1b68..000000000 --- a/bugs/filed/runafterbuild-generic-sub-never-runs/src/Settings +++ /dev/null @@ -1,61 +0,0 @@ -{ - "configuration.inherits": "Defaults", - "project.appTitle": "RunafterbuildGenericSubNeverRuns", - "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", - "project.buildType": "Standard EXE", - "project.description": "Reproduces: t", - "project.exportPathIsV2": true, - "project.id": "{59DF2A5E-4BA1-4174-8270-99EF28698F7B}", - "project.name": "RunafterbuildGenericSubNeverRuns", - "project.optionExplicit": true, - "project.references": [ - { - "id": "{00020430-0000-0000-C000-000000000046}", - "lcid": 0, - "name": "OLE Automation", - "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", - "path64": "C:\\Windows\\System32\\stdole2.tlb", - "symbolId": "stdole", - "versionMajor": 2, - "versionMinor": 0 - }, - { - "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", - "isCompilerPackage": true, - "licence": "MIT", - "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", - "path32": "", - "path64": "", - "publisher": "TWINBASIC-COMPILER", - "symbolId": "VB", - "versionBuild": 0, - "versionMajor": 0, - "versionMinor": 0, - "versionRevision": 31 - }, - { - "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", - "isCompilerPackage": true, - "licence": "MIT", - "name": "[COMPILER PACKAGE] twinBASIC - App global class object", - "path32": "", - "path64": "", - "publisher": "TWINBASIC-COMPILER", - "symbolId": "AppGlobalClassProject", - "versionBuild": 0, - "versionMajor": 1, - "versionMinor": 0, - "versionRevision": 0 - } - ], - "project.settingsVersion": 1, - "project.startupObject": "Sub Main", - "project.warnings": { - "errors": [], - "hints": [], - "ignored": [], - "info": [], - "warnings": [] - }, - "runtime.useUnicodeStandardLibrary": true -} diff --git a/bugs/filed/runafterbuild-generic-sub-never-runs/src/Sources/Startup.twin b/bugs/filed/runafterbuild-generic-sub-never-runs/src/Sources/Startup.twin deleted file mode 100644 index e48d669f7..000000000 --- a/bugs/filed/runafterbuild-generic-sub-never-runs/src/Sources/Startup.twin +++ /dev/null @@ -1,15 +0,0 @@ -' [RunAfterBuild] on a generic Sub is accepted and builds with no diagnostic. The DEBUG CONSOLE says -' it is executing the Sub, but its body never runs: "built" is not printed. (The compiler does count -' the Sub: a second [RunAfterBuild] in the project is TB5114.) Without (Of T), "built" is printed. -Module Startup - - Public Sub Main() - End Sub - - [RunAfterBuild] - Public Sub Built(Of T)() - Debug.Cls - Debug.Print "built" - End Sub - -End Module diff --git a/bugs/filed/string-null-character/REPORT.md b/bugs/filed/string-null-character/REPORT.md deleted file mode 100644 index 57710825b..000000000 --- a/bugs/filed/string-null-character/REPORT.md +++ /dev/null @@ -1,89 +0,0 @@ -Filed as [twinbasic/twinbasic#2543](https://github.com/twinbasic/twinbasic/issues/2543). - -## `String` with a *character* of `Null` raises error 94, where VB6 returns `Null` - -**Describe the bug** -`String(3, Null)`, the **Variant** form of `String`, raises error 94, *Invalid use of Null*. VB6 raises no error and returns **Null** (`VarType` 1), as it does for `String(0, Null)`. `String$(3, Null)` raises 94 in both, and so does a *number* of **Null** in either form, so only the **Variant** form with a **Null** *character* differs. Observed in the compiler's test mode, 32-bit and 64-bit. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `string-null-character.twinproj` (attached as `string-null-character.zip`). -2. Run its test cases, the module below. On BETA 1010 the four controls pass (`String(3, "a")`, a **Null** *number* in `String` and `String$`, and `String$(3, Null)`, which raise 94 in VB6 too) and the three cases on `String` with a **Null** *character* fail: `Actual: 94`, or `runtime error 94: Invalid use of Null`. - -The module is meant to be adapted as a regression test. Each case asserts VB6's result, which the attached VB6 project (`string-null-character-vb6.zip`) prints: no error and `VarType` 1 (**Null**) for `String(3, Null)` and `String(0, Null)`, and 94 for the controls. -```vb -' Regression tests: the Variant form String, given a character of Null, returns Null -' with no error; String$ raises error 94. Expected results are VB6's (the VB6 project -' beside this one prints them). tbrun --tests runs each case and prints PASS or FAIL. -[TestFixture] -Module RegressionTests - - ' Controls: a letter, a Null number in either form, and String$ with a Null character. - [TestCase] - Public Sub String_Letter_Repeats() - Assert.Strict.AreEqual CVar("aaa"), String(3, "a") - End Sub - - [TestCase] - Public Sub String_NullNumber_Raises94() - On Error Resume Next - Dim R As Variant - R = String(Null, "a") - Assert.Strict.AreEqual 94&, Err.Number - End Sub - - [TestCase] - Public Sub StringDollar_NullNumber_Raises94() - On Error Resume Next - Dim R As Variant - R = String$(Null, "a") - Assert.Strict.AreEqual 94&, Err.Number - End Sub - - [TestCase] - Public Sub StringDollar_NullCharacter_Raises94() - On Error Resume Next - Dim R As Variant - R = String$(3, Null) - Assert.Strict.AreEqual 94&, Err.Number - End Sub - - ' The defect: String with a Null character returns Null. - [TestCase] - Public Sub String_NullCharacter_NoError() - On Error Resume Next - Dim R As Variant - R = String(3, Null) - Assert.Strict.AreEqual 0&, Err.Number - End Sub - - [TestCase] - Public Sub String_NullCharacter_ReturnsNull() - Dim R As Variant - R = String(3, Null) - Assert.Strict.AreEqual vbNull, VarType(R) - End Sub - - [TestCase] - Public Sub String_ZeroNullCharacter_ReturnsNull() - Dim R As Variant - R = String(0, Null) - Assert.Strict.AreEqual vbNull, VarType(R) - End Sub - -End Module -``` - -**Expected behavior** -`String(number, Null)` returns **Null** with no error, as VB6 does, and as the **Variant** forms of other string functions propagate **Null**; `String$` keeps raising 94. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1010 - -**Additional context** -Severity: low. Code that relies on **Null** propagating through `String`, as VB6 code reading database fields can, stops with error 94. - -What does not reproduce it: `String$` with a **Null** *character*, and a **Null** *number* in either form, all of which raise 94 in VB6 as well. - - diff --git a/bugs/filed/string-null-character/repro.json b/bugs/filed/string-null-character/repro.json deleted file mode 100644 index 9ff7ddd9b..000000000 --- a/bugs/filed/string-null-character/repro.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "mode": "test", - "expect": { - "exit": 7, - "output": [ - "^PASS RegressionTests.String_Letter_Repeats$", - "^PASS RegressionTests.String_NullNumber_Raises94$", - "^PASS RegressionTests.StringDollar_NullNumber_Raises94$", - "^PASS RegressionTests.StringDollar_NullCharacter_Raises94$", - "^FAIL RegressionTests.String_NullCharacter_NoError: .*Actual: 94 ", - "^FAIL RegressionTests.String_NullCharacter_ReturnsNull: [(]runtime error 94: ", - "^FAIL RegressionTests.String_ZeroNullCharacter_ReturnsNull: [(]runtime error 94: " - ] - }, - "issue": 2543 -} diff --git a/bugs/filed/string-null-character/src/Sources/RegressionTests.twin b/bugs/filed/string-null-character/src/Sources/RegressionTests.twin deleted file mode 100644 index 54474e3ad..000000000 --- a/bugs/filed/string-null-character/src/Sources/RegressionTests.twin +++ /dev/null @@ -1,60 +0,0 @@ -' Regression tests: the Variant form String, given a character of Null, returns Null -' with no error; String$ raises error 94. Expected results are VB6's (the VB6 project -' beside this one prints them). tbrun --tests runs each case and prints PASS or FAIL. -[TestFixture] -Module RegressionTests - - ' Controls: a letter, a Null number in either form, and String$ with a Null character. - [TestCase] - Public Sub String_Letter_Repeats() - Assert.Strict.AreEqual CVar("aaa"), String(3, "a") - End Sub - - [TestCase] - Public Sub String_NullNumber_Raises94() - On Error Resume Next - Dim R As Variant - R = String(Null, "a") - Assert.Strict.AreEqual 94&, Err.Number - End Sub - - [TestCase] - Public Sub StringDollar_NullNumber_Raises94() - On Error Resume Next - Dim R As Variant - R = String$(Null, "a") - Assert.Strict.AreEqual 94&, Err.Number - End Sub - - [TestCase] - Public Sub StringDollar_NullCharacter_Raises94() - On Error Resume Next - Dim R As Variant - R = String$(3, Null) - Assert.Strict.AreEqual 94&, Err.Number - End Sub - - ' The defect: String with a Null character returns Null. - [TestCase] - Public Sub String_NullCharacter_NoError() - On Error Resume Next - Dim R As Variant - R = String(3, Null) - Assert.Strict.AreEqual 0&, Err.Number - End Sub - - [TestCase] - Public Sub String_NullCharacter_ReturnsNull() - Dim R As Variant - R = String(3, Null) - Assert.Strict.AreEqual vbNull, VarType(R) - End Sub - - [TestCase] - Public Sub String_ZeroNullCharacter_ReturnsNull() - Dim R As Variant - R = String(0, Null) - Assert.Strict.AreEqual vbNull, VarType(R) - End Sub - -End Module diff --git a/bugs/filed/string-null-character/src/Sources/Startup.twin b/bugs/filed/string-null-character/src/Sources/Startup.twin deleted file mode 100644 index be8f5c8bc..000000000 --- a/bugs/filed/string-null-character/src/Sources/Startup.twin +++ /dev/null @@ -1,6 +0,0 @@ -Module Startup - - Public Sub Main() - End Sub - -End Module diff --git a/bugs/filed/string-null-character/string-null-character.twinproj b/bugs/filed/string-null-character/string-null-character.twinproj deleted file mode 100644 index 4d8919c87268241fec5c80c386364460dfaa372d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4359 zcmc&&+iv4F5cO`acAKZRZ+##Pqj46NA=`>CDf%GG(!ywNu+ya2I7n!jiMf>}m!xt* zkk9GQ^(#6fO0m63vFYvx^@AdEhMY5p!!KrU7x_Aj?HcX>p^HZc=VkND;<*t&7;=apvCpA)uqXiJzx~ zGJBN$El?KM+cV{K_ zJozFa(gh{EQ4+JCIF6r8i1`VEI0@f{M+2RY(amMAo^=17jQet|0~&G+R>bn)LvzX- zCZg37`564>Qv>k{_%S`|g0NP=n)xF4Ej)*c3Kh>Emed{y)B2I>WQlbtm+w#>W{0wr zejl2`7GVe(AG#B{T)97YKyq8i7yT$a=NGY{3aWgShfs33lEp1j?6Gr-^|V+4wX}d% z>3n{`Rlrc;k)~%wcO3IzvQO0%l}op})xh!HXw*g7XjHo0*Ohsf&e+{)>DCthlqO7?FQ&ju)2y3T!!+O}eK^@0PMN0FOn||Xz)u#L;2(@KfP336v11j@ zlF&kIAe;ju4dkQfY>h@oJe)*Vax@y=$fLcZrZpN#HHiXNuc~0Yw4`Z0wnVX6qyE?u zc{3YFK|i5bZ^eo$I)!P{%TsqNg4xua3_8e@b0f2OIA8ALAcKQb?vW&H3XK!b}zwHjl zzU};KzjjXucHLcj*msBleJwPPGhmUAdBD{TNez&)2YsU4SmI36yfmRn%qFlgln}}Y zjn^-V1>#5cqff^Z?4^H^<1yz)q0)h zVPE&^OMHInU|uos906UoFrW+h6F7+b>M7Goe5lh9#66zVLacj&Jsn+eFY&VtOFa^C zuXRO5h~*qsV}dFs-^WP~M_c*g6u)Odc&vx~0 diff --git a/bugs/filed/textbox-text-ignores-maxlength/repro.json b/bugs/filed/textbox-text-ignores-maxlength/repro.json deleted file mode 100644 index 359ffd01b..000000000 --- a/bugs/filed/textbox-text-ignores-maxlength/repro.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "mode": "run", - "expect": { - "exit": 0, - "output": [ - "^Text = \"abcdef\": \\[abcdef\\] Len 6$", - "^SelText = \"cdef\" after \"ab\": \\[abcdef\\] Len 6$" - ] - }, - "issue": 2483 -} diff --git a/bugs/filed/textbox-text-ignores-maxlength/src/Settings b/bugs/filed/textbox-text-ignores-maxlength/src/Settings deleted file mode 100644 index 236a013fc..000000000 --- a/bugs/filed/textbox-text-ignores-maxlength/src/Settings +++ /dev/null @@ -1,61 +0,0 @@ -{ - "configuration.inherits": "Defaults", - "project.appTitle": "TextboxTextIgnoresMaxlength", - "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", - "project.buildType": "Standard EXE", - "project.description": "Reproduces: TextBox.Text assigned in code is not limited by MaxLength, where VB6 truncates it", - "project.exportPathIsV2": true, - "project.id": "{5DA15EF5-2EA2-4146-BDE6-E2E6333C8851}", - "project.name": "TextboxTextIgnoresMaxlength", - "project.optionExplicit": true, - "project.references": [ - { - "id": "{00020430-0000-0000-C000-000000000046}", - "lcid": 0, - "name": "OLE Automation", - "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", - "path64": "C:\\Windows\\System32\\stdole2.tlb", - "symbolId": "stdole", - "versionMajor": 2, - "versionMinor": 0 - }, - { - "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", - "isCompilerPackage": true, - "licence": "MIT", - "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", - "path32": "", - "path64": "", - "publisher": "TWINBASIC-COMPILER", - "symbolId": "VB", - "versionBuild": 0, - "versionMajor": 0, - "versionMinor": 0, - "versionRevision": 31 - }, - { - "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", - "isCompilerPackage": true, - "licence": "MIT", - "name": "[COMPILER PACKAGE] twinBASIC - App global class object", - "path32": "", - "path64": "", - "publisher": "TWINBASIC-COMPILER", - "symbolId": "AppGlobalClassProject", - "versionBuild": 0, - "versionMajor": 1, - "versionMinor": 0, - "versionRevision": 0 - } - ], - "project.settingsVersion": 1, - "project.startupObject": "Sub Main", - "project.warnings": { - "errors": [], - "hints": [], - "ignored": [], - "info": [], - "warnings": [] - }, - "runtime.useUnicodeStandardLibrary": true -} diff --git a/bugs/filed/textbox-text-ignores-maxlength/src/Sources/Form1.tbform b/bugs/filed/textbox-text-ignores-maxlength/src/Sources/Form1.tbform deleted file mode 100644 index 7c968eda4..000000000 --- a/bugs/filed/textbox-text-ignores-maxlength/src/Sources/Form1.tbform +++ /dev/null @@ -1,153 +0,0 @@ -[ - { - "AlwaysShowKeyboardCues": false, - "Appearance": "vbAppear3d", - "AutoRedraw": false, - "BackColor": -2147483633, - "BorderStyle": "vbSizable", - "Caption": "Form1", - "ClipControls": true, - "ControlBox": true, - "DrawMode": "vbCopyPen", - "DrawStyle": "vbSolid", - "DrawWidth": 1, - "Enabled": true, - "FillColor": 0, - "FillStyle": "vbFSTransparent", - "FontBold": false, - "FontItalic": false, - "FontName": "Segoe UI", - "FontSize": 8, - "FontStrikethru": false, - "FontTransparent": true, - "FontUnderline": false, - "ForeColor": -2147483630, - "FormDesignerId": "{EAEAEAEA-EAEA-EAEA-EAEA-EAEAEAEAEA01}", - "HasDC": true, - "Height": 300, - "HelpContextID": 0, - "Icon": [], - "KeyPreview": false, - "Left": 0, - "LinkMode": "vbLinkNone", - "LinkTopic": null, - "MDIChild": false, - "MaxButton": true, - "MaxHeight": 0, - "MaxWidth": 0, - "MinButton": true, - "MinHeight": 0, - "MinWidth": 0, - "MouseIcon": [], - "MousePointer": "vbDefault", - "Moveable": true, - "Name": "Form1", - "NegotiateMenus": false, - "OLEDropMode": "vbOLEDropNone", - "Opacity": 100, - "Palette": [], - "PaletteMode": "vbPaletteModeHalftone", - "Picture": [], - "PictureDpiScaling": false, - "RightToLeft": false, - "ScaleHeight": 4500, - "ScaleLeft": 0, - "ScaleMode": "vbTwips", - "ScaleTop": 0, - "ScaleWidth": 4500, - "ShowInTaskbar": true, - "StartUpPosition": "vbStartUpScreen", - "TabFocusAutoSelect": false, - "Tag": null, - "Top": 0, - "TopMost": false, - "TransparencyKey": -1, - "Visible": false, - "WhatsThisButton": false, - "WhatsThisHelp": false, - "Width": 300, - "WindowState": "vbNormal", - "__IDEOptions": { - "alignToGrid": false, - "gridHeight": 10, - "gridWidth": 10, - "lockedControls": [], - "multiColoredGrabbers": false, - "showGrid": true, - "showOutlines": false - }, - "__lastUpdateMarker": 2061908507, - "_className": "Form", - "_clsid": "{33AD4F38-6699-11CF-B70C-00AA0060D393}", - "_children": [ - { - "Alignment": "vbLeftJustify", - "Anchors": { - "Bottom": false, - "Left": true, - "Right": true, - "Top": true, - "_clsid": "{8524D4B5-72A9-40A9-A189-29E7905C40CA}" - }, - "Appearance": "vbAppear3d", - "BackColor": -2147483643, - "BorderStyle": "vbFixedSingleBorder", - "CausesValidation": true, - "DataField": null, - "DataFormat": null, - "DataMember": null, - "DataSource": null, - "Dock": "vbDockNone", - "DragIcon": "", - "DragMode": "vbManual", - "Enabled": true, - "FontBold": false, - "FontItalic": false, - "FontName": "Segoe UI", - "FontSize": 12, - "FontStrikethru": false, - "FontUnderline": false, - "ForeColor": -2147483640, - "Height": 30, - "HelpContextID": 0, - "HideSelection": true, - "Index": -1, - "Left": 20, - "LinkItem": null, - "LinkMode": "vbLinkNone", - "LinkTimeout": 50, - "LinkTopic": null, - "Locked": false, - "MaxLength": 0, - "MouseIcon": "", - "MousePointer": "vbDefault", - "MultiLine": false, - "Name": "Text1", - "NumbersOnly": false, - "OLEDragMode": "vbOLEDragManual", - "OLEDropMode": "vbOLEDropNone", - "Opacity": 100, - "PasswordChar": null, - "RightToLeft": false, - "ScrollBars": "vbSBNone", - "TabFocusAutoSelect": true, - "TabIndex": 0, - "TabStop": true, - "Tag": null, - "Text": "", - "TextHint": null, - "TextHintAlways": false, - "ToolTipText": null, - "Top": 20, - "TransparencyKey": -1, - "Visible": true, - "VisualStyles": true, - "WhatsThisHelpID": 0, - "WheelScrollEvent": true, - "Width": 200, - "_className": "TextBox", - "_clsid": "{33AD4EE0-6699-11CF-B70C-00AA0060D393}" - } - ] - } -] \ No newline at end of file diff --git a/bugs/filed/textbox-text-ignores-maxlength/src/Sources/Form1.twin b/bugs/filed/textbox-text-ignores-maxlength/src/Sources/Form1.twin deleted file mode 100644 index fa6a1c3a9..000000000 --- a/bugs/filed/textbox-text-ignores-maxlength/src/Sources/Form1.twin +++ /dev/null @@ -1,9 +0,0 @@ -[Description("")] -[FormDesignerId("EAEAEAEA-EAEA-EAEA-EAEA-EAEAEAEAEA01")] -[PredeclaredId] -Class Form1 - - Sub New() - End Sub - -End Class diff --git a/bugs/filed/textbox-text-ignores-maxlength/src/Sources/Startup.twin b/bugs/filed/textbox-text-ignores-maxlength/src/Sources/Startup.twin deleted file mode 100644 index 4e0fa7ad3..000000000 --- a/bugs/filed/textbox-text-ignores-maxlength/src/Sources/Startup.twin +++ /dev/null @@ -1,15 +0,0 @@ -Module Startup - - Public Sub Main() - Load Form1 - Form1.Text1.MaxLength = 3 - Form1.Text1.Text = "abcdef" - Debug.Print "Text = ""abcdef"": [" & Form1.Text1.Text & "] Len " & Len(Form1.Text1.Text) - Form1.Text1.Text = "ab" - Form1.Text1.SelStart = 2 - Form1.Text1.SelText = "cdef" - Debug.Print "SelText = ""cdef"" after ""ab"": [" & Form1.Text1.Text & "] Len " & Len(Form1.Text1.Text) - Unload Form1 - End Sub - -End Module diff --git a/bugs/filed/textbox-text-ignores-maxlength/textbox-text-ignores-maxlength.twinproj b/bugs/filed/textbox-text-ignores-maxlength/textbox-text-ignores-maxlength.twinproj deleted file mode 100644 index 7789521e1350b908320f0415ca7c38f543208189..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6582 zcmcIpTW{OQ6;`sn*=_o^PwfLi1d9Y%8PwHQfIdi+6sv35f~7bGY{M9lV`B<2x|1dnsVO68aI^1AW{&#{O- z9t14pGA+>%JJ9?F&k6Hx*rLqh*BZTpuTvh&H1f*$*BVb`)0fz9z5)DI<#%`vWprEH zOXk=3R*PsMeg5(LO6B~`?#>hb@7Ueoo;}9XrM&-vKh7nKd^hE>{lU6sLCn9`G^-V7 z5eq#IxP3S8s`rL(8+4k;Q|?FXzPzjl^IRFo2;geH*4%F%G}?`ZUeuG3&!cJb7#OBg z@mDs-i$=R_l?WMA+amz z74gi*d=oQk5nFv8i$%zz5gBAZ4Z8YYmA*C}8spTSjRA)&?z>o(Z{r~si)8{(!!aTb zd7yTjva}C`0gpTt9OswXaK!E;;&*~?gy0dsCT?cYkqB?{XrQ-a341jQdY#FY@_(2H zfrgIzBX=n_Hw%TLmnMl)Ay*#Iwf;sTKv^7R5D{)$D#8~_MYy>Xm4t5?J|@^iiZJ1n zW=PIHzh-HW1>0EOag|m@FK6*o%&cR?Rw7u!$2?3o^8e&$(2wLQcbx$gymzut}K4i(sXZlEqYy4wG-G6Y9-}3-@G_S1=8AO@@*a^Y zSY0(`?M%dis%}ZZ=Os(x*;2&oNG(WTBPlaC)|ryIPTtQV^d*Wc!U~iK&SMk|78svi zU5)yK6IJIa;8Z`_v9Y&{a3()SsK!ODcI*W}>y51GZP3tc8`i);dN#E-P6#MG(qI$?UB0Uvn~`@R{@K$y*{N7i*8gp)}3~( z;~cb{eM97dC@!X7<_a%}mA&>;qtWd*hmC`3yWQzjYc+RR?d>~m)p5FA$7wtLMyHV# z&?}E>6M73gMJ;_tH+9w4;l*2OI?C@9=YLM)L|i{=i9`~5OR0?4o(HHWQPghBZU@+8 zN$w25RK9_-1k{mO0$t^n9klArezVuA?$^7WYSY1^TRZ4f>z%=V$7#7u$L&7b$_(RG z((NC2T0T>ILiM zg9HVqxF%n3BF6j{5w-=Phqp}x!WJN^;`o)9SCSI3g{l4aT4Ms{^=iz*G)pe5*fOyI zx^?<6@GbL)!DJy@o+<<>FGm=5Xi_v~5Y#5d>ubI=4~!B{*-+e60_QHLx6(EdU?y%c^iwpnO5Bhg<>`XN zszL?Qyut#CvEfJ^M1WYcP_6z&2>iqVK+LG(L9cR=2O`^4Isw(zF?F0$#KtFKKz*X5 zmTLROh|2)wz%$c9?j-{#IqK!W0F5T7j`v7dcd2@3(eGdC?^uQADTAQLR4qg9OnW6s zTia)3riz9%#o7?sOX|GR+!p-Aq$hfkI#C zZDa}^#+13xb5?E0#*i>Eo--ixqwu>BiQ=Qxgdkx)a$ zJKKJb#pgtxpHkl0Kj(;|SK>_JbJ&Hikq=fbnP@5TZ+748TKG)UxpmC%-@VtbgU}}s zVd3Yr*bv#rK`PUQ~7_r!kvNi(|}usCX+Bp=QtHAL1QMuOcDt{lFZN4p?CUd z=aBzvd+R!4{mE)*1vg2E5LlbdJ)d8r`zj`Q^!Yqp>`fxXvZcbDs4_AH@VRY$R}!W; zeP`L17HXG8SbV=LszVgUPhVD}ph9siD!K9tw5aDwZct>#e9aUv%E)X*XItzVrzd5c z{Xa46Qy6S)%tlz~jn3kNu7ajGDq$vxivj(M*SO_*+TF2n*1Z;sG*VN^o(Pvb5(y=< zZ4P@lrGRV|$sci8*kh~JOeFeVg-rdzC0A+9JjEb6Vd}=ie*2`S6MBEXxabk?;^OVo zL<^w4@qG36sT*PZOdt^lwdcLJPq_QwgNKBNF&ZvL)PhQiJzblT(%8q)$3>Gh_{*R~ z<>$Febf+8^`6({IK3HT2>dBtkIwT=oIrx?cEf2RlmWZuTCRQMBp^tCPAFXxde{bEx zUEDhBwP{ujJkr!1L4AOWt%iGW(4xzY!otv8-Td5rkg9SG9#%+sDEU&8h+o4TT*cA;pQAh?Ku~>+ zf{x?Voo2(qX~8iMH~U2($7!~ef>;uGYLNqV@s=ryUTx9A6XPt6O&Ym^XBy-e+x>8H zp-*-3#l`e7{(SPe-MqMn6JG|rzLx~^!jg7#+Y-!Fjrz7F@#F1W1|xNkq!}7scbG?@ z%$WVC?h@--sSGM`cMCZV%y=eD$sxm*(>tj5ooe6h_NvXsa8T{E8#SD(I;~E>*=_FE z4>E^{*p;^{1aV|8X2I120=Y|g9pD>-`n zQ|?zGzq@mZRqNs(n?`b`a|WA4Omj3{LZzz{?Fs}D%`Y@okeX|%dlrS%jj)=s$>vd{ zZwhdYLhnlq1*-n&R}^FX`GoS;;6->X^S1)G^GnQfBng!`!7*tMC+<(7pdy}i1^tH= NI(ty=?LMpF>EAG=(q8}o diff --git a/bugs/filed/textbox-text-ignores-maxlength/vb6/Form1.frm b/bugs/filed/textbox-text-ignores-maxlength/vb6/Form1.frm deleted file mode 100644 index 5e59465ac..000000000 --- a/bugs/filed/textbox-text-ignores-maxlength/vb6/Form1.frm +++ /dev/null @@ -1,26 +0,0 @@ -VERSION 5.00 -Begin VB.Form Form1 - Caption = "Form1" - ClientHeight = 3000 - ClientLeft = 60 - ClientTop = 345 - ClientWidth = 4000 - LinkTopic = "Form1" - ScaleHeight = 3000 - ScaleWidth = 4000 - StartUpPosition = 3 'Windows Default - Visible = 0 'False - Begin VB.TextBox Text1 - Height = 495 - Left = 240 - TabIndex = 0 - Top = 240 - Width = 2415 - End -End -Attribute VB_Name = "Form1" -Attribute VB_GlobalNameSpace = False -Attribute VB_Creatable = False -Attribute VB_PredeclaredId = True -Attribute VB_Exposed = False -Option Explicit diff --git a/bugs/filed/textbox-text-ignores-maxlength/vb6/Module1.bas b/bugs/filed/textbox-text-ignores-maxlength/vb6/Module1.bas deleted file mode 100644 index 24aaa0cd9..000000000 --- a/bugs/filed/textbox-text-ignores-maxlength/vb6/Module1.bas +++ /dev/null @@ -1,28 +0,0 @@ -Attribute VB_Name = "Module1" -Option Explicit - -' The VB6 side of the reproducer: node scripts/bug_repro.mjs vb6 textbox-text-ignores-maxlength - -Private Sub Cases() - Load Form1 - Form1.Text1.MaxLength = 3 - Form1.Text1.Text = "abcdef" - Print #9, "Text = ""abcdef"": [" & Form1.Text1.Text & "] Len " & Len(Form1.Text1.Text) - Form1.Text1.Text = "ab" - Form1.Text1.SelStart = 2 - Form1.Text1.SelText = "cdef" - Print #9, "SelText = ""cdef"" after ""ab"": [" & Form1.Text1.Text & "] Len " & Len(Form1.Text1.Text) - Unload Form1 -End Sub - -Sub Main() - On Error GoTo Fail - Open App.Path & "\out.txt" For Output As #9 - Cases - Close #9 - Exit Sub -Fail: - On Error Resume Next - Print #9, "unhandled error " & Err.Number & ": " & Err.Description - Close #9 -End Sub diff --git a/bugs/filed/textbox-text-ignores-maxlength/vb6/Probe.vbp b/bugs/filed/textbox-text-ignores-maxlength/vb6/Probe.vbp deleted file mode 100644 index ad33b4479..000000000 --- a/bugs/filed/textbox-text-ignores-maxlength/vb6/Probe.vbp +++ /dev/null @@ -1,8 +0,0 @@ -Type=Exe -Form=Form1.frm -Module=Module1; Module1.bas -Startup="Sub Main" -ExeName32="Probe.exe" -Name="Probe" -CompilationType=0 -OptimizationType=0 diff --git a/bugs/filed/trig-above-2-to-63/REPORT.md b/bugs/filed/trig-above-2-to-63/REPORT.md index 5f1bd8604..2bb34339d 100644 --- a/bugs/filed/trig-above-2-to-63/REPORT.md +++ b/bugs/filed/trig-above-2-to-63/REPORT.md @@ -96,4 +96,4 @@ Between 9.223372E+18 and 2^63 in magnitude, twinBASIC returns a value where VB6 What does not reproduce it: any number below 9.223372E+18 in magnitude. - + diff --git a/bugs/filed/trig-above-2-to-63/repro.json b/bugs/filed/trig-above-2-to-63/repro.json index 1a6120936..2395902c3 100644 --- a/bugs/filed/trig-above-2-to-63/repro.json +++ b/bugs/filed/trig-above-2-to-63/repro.json @@ -6,9 +6,9 @@ "^PASS RegressionTests.Cos_2To62_InRange$", "^PASS RegressionTests.Sin_2To62_InRange$", "^PASS RegressionTests.Tan_2To62_NoError$", - "^FAIL RegressionTests.Cos_2To63_Raises5: .*Actual: 0 ", - "^FAIL RegressionTests.Cos_1E300_Raises5: .*Actual: 0 ", - "^FAIL RegressionTests.Sin_Minus2To63_Raises5: .*Actual: 0 ", + "^PASS RegressionTests.Cos_2To63_Raises5$", + "^PASS RegressionTests.Cos_1E300_Raises5$", + "^PASS RegressionTests.Sin_Minus2To63_Raises5$", "^FAIL RegressionTests.Tan_2To63_Raises5: .*Actual: -2147348207 " ] }, diff --git a/bugs/filed/val-suffix-overflow-no-error/REPORT.md b/bugs/filed/val-suffix-overflow-no-error/REPORT.md deleted file mode 100644 index d1ba5fa0b..000000000 --- a/bugs/filed/val-suffix-overflow-no-error/REPORT.md +++ /dev/null @@ -1,88 +0,0 @@ -Filed as [twinbasic/twinbasic#2542](https://github.com/twinbasic/twinbasic/issues/2542). - -## `Val` raises no error for a number followed by `%`, `&` or `@` that does not fit that type, where VB6 raises error 6 - -**Describe the bug** -`Val` reads a type-declaration character after a number: `Val("50%")` is 50, and `Val("50.5%")` raises error 13, *Type mismatch*, in both twinBASIC and VB6, because a fraction is not an **Integer**. When the number is whole but outside the range of the type the character names, VB6 raises error 6, *Overflow*: `Val("32768%")`, `Val("-32769%")`, `Val("2147483648&")`, `Val("1e15@")`. twinBASIC raises nothing and returns the number as a **Double** (`Val("70000%")` is 70000). Observed in the compiler's test mode, 32-bit and 64-bit, and in a built exe. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `val-suffix-overflow-no-error.twinproj` (attached as `val-suffix-overflow-no-error.zip`). -2. Run its test cases, the module below. On BETA 1010 the three controls pass (the largest **Integer** and **Long** with their characters, and `"50.5%"`, which raises 13) and the four cases past a type's range fail with `Actual: 0`. - -The module is meant to be adapted as a regression test. Each case asserts VB6's result, which the attached VB6 project (`val-suffix-overflow-no-error-vb6.zip`) prints: error 6 for the four, and 32767, 2147483647 and error 13 for the controls. -```vb -' Regression tests: Val of a number followed by a type-declaration character raises -' error 6 when the number does not fit that type. Expected results are VB6's (the VB6 -' project beside this one prints them). tbrun --tests runs each case and prints PASS or FAIL. -[TestFixture] -Module RegressionTests - - ' Controls: numbers that fit their type, and a fraction with % (error 13 in both). - [TestCase] - Public Sub Val_LargestInteger_Accepted() - Assert.Strict.AreEqual 32767#, Val("32767%") - End Sub - - [TestCase] - Public Sub Val_LargestLong_Accepted() - Assert.Strict.AreEqual 2147483647#, Val("2147483647&") - End Sub - - [TestCase] - Public Sub Val_FractionInteger_Raises13() - Dim R As Double - On Error Resume Next - R = Val("50.5%") - Assert.Strict.AreEqual 13&, Err.Number - End Sub - - ' The defect: each number is outside the range of its type, and twinBASIC returns it. - [TestCase] - Public Sub Val_IntegerPastLargest_Raises6() - Dim R As Double - On Error Resume Next - R = Val("32768%") - Assert.Strict.AreEqual 6&, Err.Number - End Sub - - [TestCase] - Public Sub Val_IntegerPastSmallest_Raises6() - Dim R As Double - On Error Resume Next - R = Val("-32769%") - Assert.Strict.AreEqual 6&, Err.Number - End Sub - - [TestCase] - Public Sub Val_LongPastLargest_Raises6() - Dim R As Double - On Error Resume Next - R = Val("2147483648&") - Assert.Strict.AreEqual 6&, Err.Number - End Sub - - [TestCase] - Public Sub Val_CurrencyPastLargest_Raises6() - Dim R As Double - On Error Resume Next - R = Val("1e15@") - Assert.Strict.AreEqual 6&, Err.Number - End Sub - -End Module -``` - -**Expected behavior** -Error 6, *Overflow*, for a number outside the range of the type its character names, as VB6 does, and as `Val` already raises error 13 for a fraction with `%` or `&`. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1010 - -**Additional context** -Severity: low. A program that counts on the error to reject text such as `"70000%"` accepts it. - -What does not reproduce it: a number that fits its type; `!` and `#` (`Val("1e39!")` raises nothing in VB6 either, and `Val("1e400#")` raises 6 in both); a number past the **Double** range with no character, which raises 6 in both. - - diff --git a/bugs/filed/val-suffix-overflow-no-error/repro.json b/bugs/filed/val-suffix-overflow-no-error/repro.json deleted file mode 100644 index e89c15bcd..000000000 --- a/bugs/filed/val-suffix-overflow-no-error/repro.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "mode": "test", - "expect": { - "exit": 7, - "output": [ - "^PASS RegressionTests.Val_LargestInteger_Accepted$", - "^PASS RegressionTests.Val_LargestLong_Accepted$", - "^PASS RegressionTests.Val_FractionInteger_Raises13$", - "^FAIL RegressionTests.Val_IntegerPastLargest_Raises6: .*Actual: 0 ", - "^FAIL RegressionTests.Val_IntegerPastSmallest_Raises6: .*Actual: 0 ", - "^FAIL RegressionTests.Val_LongPastLargest_Raises6: .*Actual: 0 ", - "^FAIL RegressionTests.Val_CurrencyPastLargest_Raises6: .*Actual: 0 " - ] - }, - "issue": 2542 -} diff --git a/bugs/filed/val-suffix-overflow-no-error/src/Settings b/bugs/filed/val-suffix-overflow-no-error/src/Settings deleted file mode 100644 index fd1330785..000000000 --- a/bugs/filed/val-suffix-overflow-no-error/src/Settings +++ /dev/null @@ -1,75 +0,0 @@ -{ - "configuration.inherits": "Defaults", - "project.appTitle": "ValSuffixOverflowNoError", - "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", - "project.buildType": "Standard EXE", - "project.description": "Reproduces: Val with a type-declaration character raises no error when the number does not fit the type, where VB6 raises error 6", - "project.exportPathIsV2": true, - "project.id": "{FBBAB053-EB70-47F7-B4F8-D8FCBC50E172}", - "project.name": "ValSuffixOverflowNoError", - "project.optionExplicit": true, - "project.references": [ - { - "id": "{00020430-0000-0000-C000-000000000046}", - "lcid": 0, - "name": "OLE Automation", - "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", - "path64": "C:\\Windows\\System32\\stdole2.tlb", - "symbolId": "stdole", - "versionMajor": 2, - "versionMinor": 0 - }, - { - "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", - "isCompilerPackage": true, - "licence": "MIT", - "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", - "path32": "", - "path64": "", - "publisher": "TWINBASIC-COMPILER", - "symbolId": "VB", - "versionBuild": 0, - "versionMajor": 0, - "versionMinor": 0, - "versionRevision": 31 - }, - { - "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", - "isCompilerPackage": true, - "licence": "MIT", - "name": "[COMPILER PACKAGE] twinBASIC - App global class object", - "path32": "", - "path64": "", - "publisher": "TWINBASIC-COMPILER", - "symbolId": "AppGlobalClassProject", - "versionBuild": 0, - "versionMajor": 1, - "versionMinor": 0, - "versionRevision": 0 - }, - { - "id": "{9847A10D-8987-44B2-8B87-EA77DEF7F1D9}", - "isCompilerPackage": true, - "licence": "MIT", - "name": "[COMPILER PACKAGE] Assert", - "path32": "", - "path64": "", - "publisher": "TWINBASIC-COMPILER", - "symbolId": "Assert", - "versionBuild": 0, - "versionMajor": 0, - "versionMinor": 0, - "versionRevision": 24 - } - ], - "project.settingsVersion": 1, - "project.startupObject": "Sub Main", - "project.warnings": { - "errors": [], - "hints": [], - "ignored": [], - "info": [], - "warnings": [] - }, - "runtime.useUnicodeStandardLibrary": true -} diff --git a/bugs/filed/val-suffix-overflow-no-error/src/Sources/RegressionTests.twin b/bugs/filed/val-suffix-overflow-no-error/src/Sources/RegressionTests.twin deleted file mode 100644 index fa4b0507b..000000000 --- a/bugs/filed/val-suffix-overflow-no-error/src/Sources/RegressionTests.twin +++ /dev/null @@ -1,59 +0,0 @@ -' Regression tests: Val of a number followed by a type-declaration character raises -' error 6 when the number does not fit that type. Expected results are VB6's (the VB6 -' project beside this one prints them). tbrun --tests runs each case and prints PASS or FAIL. -[TestFixture] -Module RegressionTests - - ' Controls: numbers that fit their type, and a fraction with % (error 13 in both). - [TestCase] - Public Sub Val_LargestInteger_Accepted() - Assert.Strict.AreEqual 32767#, Val("32767%") - End Sub - - [TestCase] - Public Sub Val_LargestLong_Accepted() - Assert.Strict.AreEqual 2147483647#, Val("2147483647&") - End Sub - - [TestCase] - Public Sub Val_FractionInteger_Raises13() - Dim R As Double - On Error Resume Next - R = Val("50.5%") - Assert.Strict.AreEqual 13&, Err.Number - End Sub - - ' The defect: each number is outside the range of its type, and twinBASIC returns it. - [TestCase] - Public Sub Val_IntegerPastLargest_Raises6() - Dim R As Double - On Error Resume Next - R = Val("32768%") - Assert.Strict.AreEqual 6&, Err.Number - End Sub - - [TestCase] - Public Sub Val_IntegerPastSmallest_Raises6() - Dim R As Double - On Error Resume Next - R = Val("-32769%") - Assert.Strict.AreEqual 6&, Err.Number - End Sub - - [TestCase] - Public Sub Val_LongPastLargest_Raises6() - Dim R As Double - On Error Resume Next - R = Val("2147483648&") - Assert.Strict.AreEqual 6&, Err.Number - End Sub - - [TestCase] - Public Sub Val_CurrencyPastLargest_Raises6() - Dim R As Double - On Error Resume Next - R = Val("1e15@") - Assert.Strict.AreEqual 6&, Err.Number - End Sub - -End Module diff --git a/bugs/filed/val-suffix-overflow-no-error/src/Sources/Startup.twin b/bugs/filed/val-suffix-overflow-no-error/src/Sources/Startup.twin deleted file mode 100644 index be8f5c8bc..000000000 --- a/bugs/filed/val-suffix-overflow-no-error/src/Sources/Startup.twin +++ /dev/null @@ -1,6 +0,0 @@ -Module Startup - - Public Sub Main() - End Sub - -End Module diff --git a/bugs/filed/val-suffix-overflow-no-error/val-suffix-overflow-no-error.twinproj b/bugs/filed/val-suffix-overflow-no-error/val-suffix-overflow-no-error.twinproj deleted file mode 100644 index b83da0c8c93527d581de9eea800ed0a163c72031..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4391 zcmcgwTTkOg6wYq1Ny}TS_Ms1>lwCl=k&`$#q)N3Ndli*SL?~1u2s!p7nXThVXT}Q% z@pJm;`YU>lFC~^56{>Swt8tngi;JrPt&#-k%Dm8_@> z7A4AP0DnJ#jSX^0b1|Y&ZifIUgpY|wqIfoXGi8vyfmK=w`SQ6^_Q{sD) z5-$pJ@xD125OC8qd%Kmj(ka5^vP%^UdbU>D<3SwKC3q4P*Gg+80>2Gn^GFFE0$wRh zxILUFJs4#oIoWoypGU@s5Q25V)Rep+n<+rmI$;qRaW&ln2heWfgbj{@2l(EPMj+fCM(kkM=khc6l^;pZ#l9qh++~P2<*y#mvhO^Tf?S&Cxha!mnCiMx)DS zUv8tp%6^hFsin7o z-5^I0$^jii2=AmJnPnJ;ajdeKrjSR`gklOZ3~ES_G>kBTEpyPbAxj`gA*`8tT-am? z^*yPw2s%%}YJN(rFsj;*0jv2iRu2T>(t!puFAO0kp5jCr9;x&H90?QZi7)6jZF$;sfr>3Z}ew|Vae7_kEEnFK8*RT6&|K0ay z^yYm1+7$kjDX3=9F>fmC*DedGbE#;Aq~qqUd8AEf9`K3+PXxsI! EsPqM!W2Z#9 zLkh%Y$8q zn6cusg#IB!us8=;>Ih2N-Wd*0SQPLJIUEkIs75)Jfj;& zC%yfIF=+Yjnbb0n$5u9(!Ifk?XlK`+gP1}4)@%;#3gCzI4a4gIBlYUNpxf0>&9&+s zt!db%W^_9i{CbB(s++V<0fZqDaq67mtyWB8nrK*C1&d66S|8`4V?ABi>W z_$t}Z#{IH#`abYiSAgDo2>$y+zSA~ZW>xQM?M}O;8HQEU+7^r)v(@T4Zp*E9J5Pgt z`f;*^=cf+ll?|REplb#Obg6g-7jfHdW!AZm(=r5ckN31#PC3Lu8eNc=_&JBv0D-ub zy6{AV?H{zUK$VkE#H5DfUd8g1!8a}}o~EyC0#%J-DaR8T7Hwgjy7X$3XiU0rr diff --git a/bugs/filed/val-suffix-overflow-no-error/vb6/Module1.bas b/bugs/filed/val-suffix-overflow-no-error/vb6/Module1.bas deleted file mode 100644 index 7974cf778..000000000 --- a/bugs/filed/val-suffix-overflow-no-error/vb6/Module1.bas +++ /dev/null @@ -1,25 +0,0 @@ -Attribute VB_Name = "Module1" -Option Explicit - -' What VB6 does with Val of a number followed by a type-declaration character that the -' number does not fit: error 6. -' Build and run it with: node scripts/bug_repro.mjs vb6 val-suffix-overflow-no-error - -Private Sub Report(ByVal Label As String, ByVal Result As Variant) - Print #9, Label & ": " & Err.Number & " " & Err.Description & " (" & Result & ")" - Err.Clear -End Sub - -Sub Main() - Dim R As Variant - Open App.Path & "\out.txt" For Output As #9 - On Error Resume Next - R = Empty: R = Val("32767%"): Report "Val_LargestInteger", R - R = Empty: R = Val("2147483647&"): Report "Val_LargestLong", R - R = Empty: R = Val("50.5%"): Report "Val_FractionInteger", R - R = Empty: R = Val("32768%"): Report "Val_IntegerPastLargest", R - R = Empty: R = Val("-32769%"): Report "Val_IntegerPastSmallest", R - R = Empty: R = Val("2147483648&"): Report "Val_LongPastLargest", R - R = Empty: R = Val("1e15@"): Report "Val_CurrencyPastLargest", R - Close #9 -End Sub diff --git a/bugs/filed/val-suffix-overflow-no-error/vb6/Probe.vbp b/bugs/filed/val-suffix-overflow-no-error/vb6/Probe.vbp deleted file mode 100644 index 4d210b0e8..000000000 --- a/bugs/filed/val-suffix-overflow-no-error/vb6/Probe.vbp +++ /dev/null @@ -1,7 +0,0 @@ -Type=Exe -Module=Module1; Module1.bas -Startup="Sub Main" -ExeName32="Probe.exe" -Name="Probe" -CompilationType=0 -OptimizationType=0 diff --git a/bugs/filed/webview2-response-headers-reset-clone/REPORT.md b/bugs/filed/webview2-response-headers-reset-clone/REPORT.md deleted file mode 100644 index be22ffd5d..000000000 --- a/bugs/filed/webview2-response-headers-reset-clone/REPORT.md +++ /dev/null @@ -1,180 +0,0 @@ -Filed as [twinbasic/twinbasic#2531](https://github.com/twinbasic/twinbasic/issues/2531). - -## After `Reset`, `Clone` of the WebView2 response headers enumerator skips the headers read before the `Reset` - -**Describe the bug** -Follow-up to #2496, which is fixed in BETA 1005 for `Clone` after `Next` and after `Skip`, on the enumerators of the request headers and the response headers alike, and after `Reset` on the request headers'. This is the response headers' enumerator after a `Reset`: after two `Next` calls and a `Reset`, the enumerator's `Next` returns the first header, `Ha`, and its clone's `Next` returns the third, `Hc`. Observed by running the reproducer's test cases. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `webview2-response-headers-reset-clone.twinproj` (attached as `webview2-response-headers-reset-clone.zip`). Its `Form1` holds one WebView2 control, which navigates to `https://example.com/page` and answers that request itself in `WebResourceRequested`, so nothing goes to the network. The handler appends four headers, `Ha` to `Hd`, to the response, and records what three fresh enumerators of `Request.Headers`, then three of `Response.Headers`, return (module `HeaderEnumeration`, below). -2. Call `HeaderEnumerationTests.Response_CloneAfterReset` from `Sub Main` and press F5. The form shows until its request is answered, and closes. -3. See the assertion fail with `Expected: "Ha|Ha" Actual: "Ha|Hc"`: the enumerator's `Next`, then the clone's. - -The module below is the reproducer's, and is meant to be adapted as a regression test. It shows `Form1` while the control answers its request, and unloads it. On BETA 1005 the five cases from #2496 pass, and `Response_CloneAfterReset` fails. -```vb -' Regression tests for #2496 and its follow-up. IEnumVARIANT::Clone returns an -' enumerator with the same state as the one it is called on (the COM -' documentation of IEnumVARIANT::Clone), so the clone's Next returns what the -' enumerator's own Next returns next. Each case compares the two after one Next, -' after Skip 2, and after two Next and a Reset, for the request headers and for -' the response headers, which hold Ha, Hb, Hc and Hd. - -' IEnumVARIANT, declared here because the package's own declaration is private. -[InterfaceId("00020404-0000-0000-C000-000000000046")] -Interface IEnumVARIANTOwn Extends stdole.IUnknown - Type CArrayOfVariants - Elements(1) As Variant - End Type - - Sub Next(ByVal celt As Long, ByRef rgVar As CArrayOfVariants, ByRef pCeltFetched As Long) - Sub Skip(ByVal celt As Long) - Sub Reset() - Function Clone() As stdole.IUnknown -End Interface - -' What the enumerators returned, recorded by Form1 while it answers its request. -Module HeaderEnumeration - - Public Recorded As Boolean - Public Results As New Collection ' " " -> "|" - - Private Function NextName(ByVal e As IEnumVARIANTOwn) As String - Dim arr As IEnumVARIANTOwn.CArrayOfVariants - Dim fetched As Long - e.Next 1, arr, fetched - If fetched = 0 Then Return "(end)" - Dim hd As WebView2Header = arr.Elements(0) - Return hd.Name - End Function - - ' Three fresh enumerators of one collection. - Public Sub Record(ByVal Side As String, ByVal u0 As stdole.IUnknown, ByVal u1 As stdole.IUnknown, ByVal u2 As stdole.IUnknown) - Dim e As IEnumVARIANTOwn, c As IEnumVARIANTOwn, first As String - - Set e = u0 - first = NextName(e) - Set c = e.Clone() - Results.Add NextName(e) & "|" & NextName(c), Side & " after one Next" - - Set e = u1 - NextName e - NextName e - e.Reset - Set c = e.Clone() - Results.Add NextName(e) & "|" & NextName(c), Side & " after Reset" - - Set e = u2 - e.Skip 2 - Set c = e.Clone() - Results.Add NextName(e) & "|" & NextName(c), Side & " after Skip 2" - End Sub - -End Module - -[TestFixture] -Module HeaderEnumerationTests - - Private Declare PtrSafe Function SetEnvironmentVariableW Lib "kernel32" (ByVal lpName As LongPtr, ByVal lpValue As LongPtr) As Long - - ' Shows Form1 until its request has been answered, once. - Private Sub Collect() - If HeaderEnumeration.Recorded Then Exit Sub - ' Only for an IDE that was started with these variables, as this repository's - ' test harness starts it: WebView2 would read them, and the control would fail. - Dim folder As String = Environ$("WEBVIEW2_USER_DATA_FOLDER") - Dim args As String = Environ$("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS") - SetEnvironmentVariableW StrPtr("WEBVIEW2_USER_DATA_FOLDER"), 0 - SetEnvironmentVariableW StrPtr("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS"), 0 - Form1.Show - Dim started As Double = Timer - Do Until HeaderEnumeration.Recorded Or Timer - started > 30 - DoEvents - Loop - Unload Form1 - If Len(folder) Then SetEnvironmentVariableW StrPtr("WEBVIEW2_USER_DATA_FOLDER"), StrPtr(folder) - If Len(args) Then SetEnvironmentVariableW StrPtr("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS"), StrPtr(args) - End Sub - - ' "|" - Private Function Pair(ByVal Key As String) As String - Collect - Assert.Strict.IsTrue HeaderEnumeration.Recorded, "the request was answered" - Return HeaderEnumeration.Results(Key) - End Function - - ' Fixed in #2496 (BETA 1005): the clone continues from the enumerator's position. - - [TestCase] - Public Sub Request_CloneAfterNext() - Assert.Strict.AreEqual "Upgrade-Insecure-Requests|Upgrade-Insecure-Requests", Pair("request after one Next") - End Sub - - [TestCase] - Public Sub Request_CloneAfterSkip() - Assert.Strict.AreEqual "User-Agent|User-Agent", Pair("request after Skip 2") - End Sub - - [TestCase] - Public Sub Request_CloneAfterReset() - Assert.Strict.AreEqual "Accept|Accept", Pair("request after Reset") - End Sub - - [TestCase] - Public Sub Response_CloneAfterNext() - Assert.Strict.AreEqual "Hb|Hb", Pair("response after one Next") - End Sub - - [TestCase] - Public Sub Response_CloneAfterSkip() - Assert.Strict.AreEqual "Hc|Hc", Pair("response after Skip 2") - End Sub - - ' Left in BETA 1005: Reset does not rewind the position Clone copies. - - [TestCase] - Public Sub Response_CloneAfterReset() - Assert.Strict.AreEqual "Ha|Ha", Pair("response after Reset") - End Sub - -End Module -``` -`Form1`'s handlers, which call `Record`: -```vb - Private Sub WebView21_Ready() Handles WebView21.Ready - WebView21.AddWebResourceRequestedFilter "*", wv2All - WebView21.Navigate "https://example.com/page" - End Sub - - Private Sub WebView21_WebResourceRequested(ByVal Request As WebView2Request, ByVal Response As WebView2Response) _ - Handles WebView21.WebResourceRequested - Response.Headers.AppendHeader "Ha", "1" - Response.Headers.AppendHeader "Hb", "2" - Response.Headers.AppendHeader "Hc", "3" - Response.Headers.AppendHeader "Hd", "4" - If Not HeaderEnumeration.Recorded Then - Dim rq As WebView2RequestHeaders = Request.Headers - HeaderEnumeration.Record "request", rq._NewEnum(), rq._NewEnum(), rq._NewEnum() - Dim rs As WebView2ResponseHeaders = Response.Headers - HeaderEnumeration.Record "response", rs._NewEnum(), rs._NewEnum(), rs._NewEnum() - HeaderEnumeration.Recorded = True - End If - Response.StatusCode = 200 - Response.ReasonPhrase = "OK" - Response.ContentUTF8 = "probe" - End Sub -``` - -**Expected behavior** -`Ha|Ha`: `IEnumVARIANT::Clone` creates an enumerator with the same state as the current one, so after a `Reset` the clone starts at the first header, as the request headers' clone does. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1005 - -**Additional context** -In BETA 1005's package source, `WebView2HeadersCollection.twin`, `Next` and `Skip` advance `IterationIdx`, which `Clone` passes to the new enumerator. The two request headers classes set `IterationIdx = 0` in their `Reset`; the two response headers classes in `WebView2ResponseHeaders.twin`, `WebView2HeadersCollection_ResponseGetHeaders` and `WebView2HeadersCollection_ResponseEnumerator`, do not, so after a `Reset` their clone skips as many headers as were read before it. Setting `IterationIdx = 0` in those two `Reset`s, as the request side does, would fix it. Only `_NewEnum` (the `ResponseEnumerator` class) was run; `GetHeaders` (the other class) has the same `Reset`. - -Severity: low. `For Each` does not call `Clone`, so only a COM client that clones the enumerator after resetting it gets the wrong headers. - - diff --git a/bugs/filed/webview2-response-headers-reset-clone/repro.json b/bugs/filed/webview2-response-headers-reset-clone/repro.json deleted file mode 100644 index 31b557868..000000000 --- a/bugs/filed/webview2-response-headers-reset-clone/repro.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "mode": "test", - "expect": { - "exit": 7, - "output": [ - "^PASS HeaderEnumerationTests\\.Request_CloneAfterNext$", - "^PASS HeaderEnumerationTests\\.Request_CloneAfterSkip$", - "^PASS HeaderEnumerationTests\\.Request_CloneAfterReset$", - "^PASS HeaderEnumerationTests\\.Response_CloneAfterNext$", - "^PASS HeaderEnumerationTests\\.Response_CloneAfterSkip$", - "^FAIL HeaderEnumerationTests\\.Response_CloneAfterReset: .*Expected: \"Ha\\|Ha\" Actual: \"Ha\\|Hc\"" - ] - }, - "issue": 2531 -} diff --git a/bugs/filed/webview2-response-headers-reset-clone/src/Settings b/bugs/filed/webview2-response-headers-reset-clone/src/Settings deleted file mode 100644 index 8e23fbc7b..000000000 --- a/bugs/filed/webview2-response-headers-reset-clone/src/Settings +++ /dev/null @@ -1,89 +0,0 @@ -{ - "configuration.inherits": "Defaults", - "project.appTitle": "Webview2ResponseHeadersResetClone", - "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", - "project.buildType": "Standard EXE", - "project.description": "Reproduces: After `Reset`, `Clone` of the WebView2 response headers enumerator skips the headers read before the `Reset`", - "project.exportPathIsV2": true, - "project.id": "{C00329BA-9C82-4E94-8D31-D766BEC9AA88}", - "project.name": "Webview2ResponseHeadersResetClone", - "project.optionExplicit": true, - "project.references": [ - { - "id": "{00020430-0000-0000-C000-000000000046}", - "lcid": 0, - "name": "OLE Automation", - "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", - "path64": "C:\\Windows\\System32\\stdole2.tlb", - "symbolId": "stdole", - "versionMajor": 2, - "versionMinor": 0 - }, - { - "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", - "isCompilerPackage": true, - "licence": "MIT", - "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", - "path32": "", - "path64": "", - "publisher": "TWINBASIC-COMPILER", - "symbolId": "VB", - "versionBuild": 0, - "versionMajor": 0, - "versionMinor": 0, - "versionRevision": 31 - }, - { - "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", - "isCompilerPackage": true, - "licence": "MIT", - "name": "[COMPILER PACKAGE] twinBASIC - App global class object", - "path32": "", - "path64": "", - "publisher": "TWINBASIC-COMPILER", - "symbolId": "AppGlobalClassProject", - "versionBuild": 0, - "versionMajor": 1, - "versionMinor": 0, - "versionRevision": 0 - }, - { - "id": "{9847A10D-8987-44B2-8B87-EA77DEF7F1D9}", - "isCompilerPackage": true, - "licence": "MIT", - "name": "[COMPILER PACKAGE] Assert", - "path32": "", - "path64": "", - "publisher": "TWINBASIC-COMPILER", - "symbolId": "Assert", - "versionBuild": 0, - "versionMajor": 0, - "versionMinor": 0, - "versionRevision": 24 - }, - { - "id": "{5D19653F-F49E-41F2-ABF8-16E473A89A7C}", - "isCompilerPackage": true, - "licence": "MIT", - "name": "[COMPILER PACKAGE] WebView2 Package", - "path32": "", - "path64": "", - "publisher": "TWINBASIC-COMPILER", - "symbolId": "WebView2Package", - "versionBuild": 0, - "versionMajor": 0, - "versionMinor": 0, - "versionRevision": 3 - } - ], - "project.settingsVersion": 1, - "project.startupObject": "Sub Main", - "project.warnings": { - "errors": [], - "hints": [], - "ignored": [], - "info": [], - "warnings": [] - }, - "runtime.useUnicodeStandardLibrary": true -} diff --git a/bugs/filed/webview2-response-headers-reset-clone/src/Sources/Form1.tbform b/bugs/filed/webview2-response-headers-reset-clone/src/Sources/Form1.tbform deleted file mode 100644 index 5610cc552..000000000 --- a/bugs/filed/webview2-response-headers-reset-clone/src/Sources/Form1.tbform +++ /dev/null @@ -1,146 +0,0 @@ -[ - { - "AlwaysShowKeyboardCues": false, - "Appearance": "vbAppear3d", - "AutoRedraw": false, - "BackColor": 13223074, - "BorderStyle": "vbSizable", - "Caption": "WebView2 form", - "ClipControls": true, - "ControlBox": true, - "DrawMode": "vbCopyPen", - "DrawStyle": "vbSolid", - "DrawWidth": 1, - "Enabled": true, - "FillColor": 0, - "FillStyle": "vbFSTransparent", - "FontBold": false, - "FontItalic": false, - "FontName": "Segoe UI", - "FontSize": 8, - "FontStrikethru": false, - "FontTransparent": true, - "FontUnderline": false, - "ForeColor": -2147483630, - "FormDesignerId": "{7B247400-0000-4000-9000-7B2474000F01}", - "HasDC": true, - "Height": 480, - "HelpContextID": 0, - "Icon": "", - "KeyPreview": false, - "Left": 0, - "LinkMode": "vbLinkNone", - "LinkTopic": null, - "MDIChild": false, - "MaxButton": true, - "MaxHeight": 0, - "MaxWidth": 0, - "MinButton": true, - "MinHeight": 0, - "MinWidth": 0, - "MouseIcon": "", - "MousePointer": "vbDefault", - "Moveable": true, - "Name": "Form1", - "NegotiateMenus": false, - "OLEDropMode": "vbOLEDropNone", - "Opacity": 100, - "Palette": "", - "PaletteMode": "vbPaletteModeHalftone", - "Picture": "", - "PictureDpiScaling": false, - "RightToLeft": false, - "ScaleHeight": 8560, - "ScaleLeft": 0, - "ScaleMode": "vbTwips", - "ScaleTop": 0, - "ScaleWidth": 11760, - "ShowInTaskbar": true, - "StartUpPosition": "vbStartUpScreen", - "TabFocusAutoSelect": false, - "Tag": null, - "Top": 0, - "TopMost": false, - "TransparencyKey": -1, - "Visible": true, - "WhatsThisButton": false, - "WhatsThisHelp": false, - "Width": 640, - "WindowState": "vbNormal", - "__IDEOptions": { - "alignToGrid": false, - "gridHeight": 10, - "gridWidth": 10, - "lockedControls": [], - "multiColoredGrabbers": true, - "showGrid": true, - "showOutlines": false - }, - "__lastUpdateMarker": 3573907440, - "_children": [ - { - "AdditionalAllowedFrameAncestors": null, - "Anchors": { - "Bottom": true, - "Left": true, - "Right": true, - "Top": true, - "_clsid": "{8524D4B5-72A9-40A9-A189-29E7905C40CA}" - }, - "AreBrowserAcceleratorKeysEnabled": true, - "AreDefaultContextMenusEnabled": true, - "AreDefaultScriptDialogsEnabled": true, - "AreDevToolsEnabled": true, - "AreHostObjectsAllowed": true, - "BackColor": 10534293, - "CausesValidation": true, - "Dock": "vbDockNone", - "DocumentURL": "about:blank", - "DragIcon": "", - "DragMode": "vbManual", - "Enabled": true, - "EnvironmentOptions": { - "AdditionalBrowserArguments": null, - "AllowSingleSignOnUsingOSPrimaryAccount": false, - "BrowserExecutableFolder": null, - "EnableTrackingPrevention": true, - "ExclusiveUserDataFolderAccess": false, - "Language": null, - "TargetCompatibleBrowserVersion": "86.0.616.0", - "UserDataFolder": "", - "_className": "WebView2EnvironmentOptions", - "_clsid": "{185E63C0-E641-47ED-B6A9-8565168A7FCF}" - }, - "Height": 440, - "Index": -1, - "IsBuiltInErrorPageEnabled": true, - "IsGeneralAutoFillEnabled": true, - "IsMuted": false, - "IsPasswordAutoSaveEnabled": false, - "IsPinchZoomEnabled": true, - "IsScriptEnabled": true, - "IsStatusBarEnabled": false, - "IsSwipeNavigationEnabled": true, - "IsWebMessageEnabled": true, - "IsZoomControlEnabled": true, - "Left": 0, - "MouseIcon": "", - "MousePointer": "vbDefault", - "Name": "WebView21", - "TabIndex": 7, - "TabStop": true, - "Tag": null, - "Top": 0, - "UseDeferredEvents": true, - "UserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/132.0.0.0", - "Visible": true, - "Width": 620, - "ZoomFactor": 1, - "_className": "WebView2", - "_clsid": "{93B7E07E-6C7F-4758-8E51-4B1B17EC0A62}" - } - ], - "_className": "Form", - "_clsid": "{33AD4F38-6699-11CF-B70C-00AA0060D393}" - } -] diff --git a/bugs/filed/webview2-response-headers-reset-clone/src/Sources/Form1.twin b/bugs/filed/webview2-response-headers-reset-clone/src/Sources/Form1.twin deleted file mode 100644 index 53581f1e3..000000000 --- a/bugs/filed/webview2-response-headers-reset-clone/src/Sources/Form1.twin +++ /dev/null @@ -1,32 +0,0 @@ -' A form holding one WebView2 control. Once the control is ready it navigates to a -' page whose request it answers itself in WebResourceRequested, so nothing goes to -' the network; there it records what the headers enumerators do (HeaderEnumeration). - -[FormDesignerId("7B247400-0000-4000-9000-7B2474000F01")] -[PredeclaredId] -Class Form1 - - Private Sub WebView21_Ready() Handles WebView21.Ready - WebView21.AddWebResourceRequestedFilter "*", wv2All - WebView21.Navigate "https://example.com/page" - End Sub - - Private Sub WebView21_WebResourceRequested(ByVal Request As WebView2Request, ByVal Response As WebView2Response) _ - Handles WebView21.WebResourceRequested - Response.Headers.AppendHeader "Ha", "1" - Response.Headers.AppendHeader "Hb", "2" - Response.Headers.AppendHeader "Hc", "3" - Response.Headers.AppendHeader "Hd", "4" - If Not HeaderEnumeration.Recorded Then - Dim rq As WebView2RequestHeaders = Request.Headers - HeaderEnumeration.Record "request", rq._NewEnum(), rq._NewEnum(), rq._NewEnum() - Dim rs As WebView2ResponseHeaders = Response.Headers - HeaderEnumeration.Record "response", rs._NewEnum(), rs._NewEnum(), rs._NewEnum() - HeaderEnumeration.Recorded = True - End If - Response.StatusCode = 200 - Response.ReasonPhrase = "OK" - Response.ContentUTF8 = "probe" - End Sub - -End Class diff --git a/bugs/filed/webview2-response-headers-reset-clone/src/Sources/HeaderEnumerationTests.twin b/bugs/filed/webview2-response-headers-reset-clone/src/Sources/HeaderEnumerationTests.twin deleted file mode 100644 index 86a6c3c5e..000000000 --- a/bugs/filed/webview2-response-headers-reset-clone/src/Sources/HeaderEnumerationTests.twin +++ /dev/null @@ -1,125 +0,0 @@ -' Regression tests for #2496 and its follow-up. IEnumVARIANT::Clone returns an -' enumerator with the same state as the one it is called on (the COM -' documentation of IEnumVARIANT::Clone), so the clone's Next returns what the -' enumerator's own Next returns next. Each case compares the two after one Next, -' after Skip 2, and after two Next and a Reset, for the request headers and for -' the response headers, which hold Ha, Hb, Hc and Hd. - -' IEnumVARIANT, declared here because the package's own declaration is private. -[InterfaceId("00020404-0000-0000-C000-000000000046")] -Interface IEnumVARIANTOwn Extends stdole.IUnknown - Type CArrayOfVariants - Elements(1) As Variant - End Type - - Sub Next(ByVal celt As Long, ByRef rgVar As CArrayOfVariants, ByRef pCeltFetched As Long) - Sub Skip(ByVal celt As Long) - Sub Reset() - Function Clone() As stdole.IUnknown -End Interface - -' What the enumerators returned, recorded by Form1 while it answers its request. -Module HeaderEnumeration - - Public Recorded As Boolean - Public Results As New Collection ' " " -> "|" - - Private Function NextName(ByVal e As IEnumVARIANTOwn) As String - Dim arr As IEnumVARIANTOwn.CArrayOfVariants - Dim fetched As Long - e.Next 1, arr, fetched - If fetched = 0 Then Return "(end)" - Dim hd As WebView2Header = arr.Elements(0) - Return hd.Name - End Function - - ' Three fresh enumerators of one collection. - Public Sub Record(ByVal Side As String, ByVal u0 As stdole.IUnknown, ByVal u1 As stdole.IUnknown, ByVal u2 As stdole.IUnknown) - Dim e As IEnumVARIANTOwn, c As IEnumVARIANTOwn, first As String - - Set e = u0 - first = NextName(e) - Set c = e.Clone() - Results.Add NextName(e) & "|" & NextName(c), Side & " after one Next" - - Set e = u1 - NextName e - NextName e - e.Reset - Set c = e.Clone() - Results.Add NextName(e) & "|" & NextName(c), Side & " after Reset" - - Set e = u2 - e.Skip 2 - Set c = e.Clone() - Results.Add NextName(e) & "|" & NextName(c), Side & " after Skip 2" - End Sub - -End Module - -[TestFixture] -Module HeaderEnumerationTests - - Private Declare PtrSafe Function SetEnvironmentVariableW Lib "kernel32" (ByVal lpName As LongPtr, ByVal lpValue As LongPtr) As Long - - ' Shows Form1 until its request has been answered, once. - Private Sub Collect() - If HeaderEnumeration.Recorded Then Exit Sub - ' Only for an IDE that was started with these variables, as this repository's - ' test harness starts it: WebView2 would read them, and the control would fail. - Dim folder As String = Environ$("WEBVIEW2_USER_DATA_FOLDER") - Dim args As String = Environ$("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS") - SetEnvironmentVariableW StrPtr("WEBVIEW2_USER_DATA_FOLDER"), 0 - SetEnvironmentVariableW StrPtr("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS"), 0 - Form1.Show - Dim started As Double = Timer - Do Until HeaderEnumeration.Recorded Or Timer - started > 30 - DoEvents - Loop - Unload Form1 - If Len(folder) Then SetEnvironmentVariableW StrPtr("WEBVIEW2_USER_DATA_FOLDER"), StrPtr(folder) - If Len(args) Then SetEnvironmentVariableW StrPtr("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS"), StrPtr(args) - End Sub - - ' "|" - Private Function Pair(ByVal Key As String) As String - Collect - Assert.Strict.IsTrue HeaderEnumeration.Recorded, "the request was answered" - Return HeaderEnumeration.Results(Key) - End Function - - ' Fixed in #2496 (BETA 1005): the clone continues from the enumerator's position. - - [TestCase] - Public Sub Request_CloneAfterNext() - Assert.Strict.AreEqual "Upgrade-Insecure-Requests|Upgrade-Insecure-Requests", Pair("request after one Next") - End Sub - - [TestCase] - Public Sub Request_CloneAfterSkip() - Assert.Strict.AreEqual "User-Agent|User-Agent", Pair("request after Skip 2") - End Sub - - [TestCase] - Public Sub Request_CloneAfterReset() - Assert.Strict.AreEqual "Accept|Accept", Pair("request after Reset") - End Sub - - [TestCase] - Public Sub Response_CloneAfterNext() - Assert.Strict.AreEqual "Hb|Hb", Pair("response after one Next") - End Sub - - [TestCase] - Public Sub Response_CloneAfterSkip() - Assert.Strict.AreEqual "Hc|Hc", Pair("response after Skip 2") - End Sub - - ' Left in BETA 1005: Reset does not rewind the position Clone copies. - - [TestCase] - Public Sub Response_CloneAfterReset() - Assert.Strict.AreEqual "Ha|Ha", Pair("response after Reset") - End Sub - -End Module diff --git a/bugs/filed/webview2-response-headers-reset-clone/src/Sources/Startup.twin b/bugs/filed/webview2-response-headers-reset-clone/src/Sources/Startup.twin deleted file mode 100644 index be8f5c8bc..000000000 --- a/bugs/filed/webview2-response-headers-reset-clone/src/Sources/Startup.twin +++ /dev/null @@ -1,6 +0,0 @@ -Module Startup - - Public Sub Main() - End Sub - -End Module diff --git a/bugs/filed/webview2-response-headers-reset-clone/webview2-response-headers-reset-clone.twinproj b/bugs/filed/webview2-response-headers-reset-clone/webview2-response-headers-reset-clone.twinproj deleted file mode 100644 index 35029800cc5ac8a35bd99bc22b24e79d95d53f5a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13395 zcmcIrTW{N1cJ?`)cIvc2fdS^Jeb{A#+Ad5*H|vs<1fV1;7UCN$*)AF%_)yfA#4$zc zkd!Zztj7fj~27msz@q7I4&E{d0@X_FY&JVm{hkx7r8M+4rBfnzfTRiry_W)8Vl<33$|_ z4H+Nntun?Gi)Il&CjKazP5PzVi``=Bv)aJE6L;E}m8x-{dy{E`rfNeivCn;>aekNd zI_OZ%Jy#luq67mzi8y&ShGe)u;A5huCLMUeb!rXtbp(%BBh=k6>Gg=Op})2a6RDI6kmDQ8-UUry4TSJDxkPm)wJe zQc>;n#PNBO2*XutsFsvYreWXl#}KI2#);=9izqi)wshuR--Vk5lf0%+$#4dtQh3G$ zXf$A6*;Qk&rq~nhdHWDeDR+ZgZyqbwMHkE?H73!bHB>6qmvn)ry#<-iIYV5laUn0}M8FoW=5nDjlKKHpxHk{egz@g2jMw;UbABSPQI%A1K z?mc`3vrUC-=U(g){AL-wm^w*3n0oQjQZtJ$JIOQid}(Ras!GNeUN8y)vM?)*gmE4L zFdSdW^y;eDv5y7blfj9gv}r)MlVA|Oi-5-2U2hr_G%Xcbl1s=|z3{Tp58Z1%%0!9} zKMQ}}G-mJ(PXHPpy^EaT5YZp&EVZ<0#89AGeWmqyk-+fM5J?g8v&4z3E8mHs+!1+@ z6I}~$tnAe*O+-E|jw_d-1fc*6QW)NpftW(^Xe4aH@h#sEZ~3Sj0rM?{-8cyagG+7G z=)j%IRw*+ywL-wrEVmJ~D)Uaafi~eNs{^G4<;GpPek@dAd~EEMtDS0V&#afNCNLep ztWu+CmYa6HS=?(^i*4((AvUDsK}<%x6@|Akk1QAI5RV+}1hyBiiKKG4LN3x8&|cus zm&U?vym`{`96y{qMfheAB4c=z+CCiSc=!i6TCC)s7m$(5XNr53YPngF2DNFl9YAn= z4!ec&W$uy@q|t#Xf*K(+{z@Sedq%@zhQ#3P^ngYS&DlNsua<|?*oxyk9V%Wh`k$r z2J`ATi6hC8p~d;w1s^jGoM5s*vc0;OffG%T^@p=L^ah?3ljlGLm4Y^Eg<_#r!f&mR zO(VY~=`HPp&p2_c!>dkU*T`Pk&Zd#^vQlHuu2tGa)2>xZX0>j2%vKGqhLE*asx_>7 zx7~f@Lu0aQF z{Y0cA*$u5E2B4Zv4e$x$1%#H@g z@CwDB7#eETpV(cky2EfP^f_et-b-HYRqBOGjcvc*9~>U+G9QN#_Kv&P;SOt0qj1Jw z;y?(u!v9#`8RHOngj(K(GkB^R(ZgtL7M71E}{&Wm)Q*lu6M~L^^E^D zE3LX+tlMU-UGKvG_8Mlx-h(H$O081eZWpauS^3{*ighxhSXqElYDqln+Lemcsdg(3 zvsP<1%~Gk|HCy#!8)qcTDi&+SPNi89q(S)S=g;V<{=)|SBQo?ta_a@TbNqj!+OWke zaWZC8Kq=6YA$evRau|+BbVx3+W8@GlnR4C2yqHBOdfYQFVS#iI9XZbxy~FmMVB}+e zTHK|A=#f)tdfI`|x=vOASDe_G@o&a1yS*tRtgf4^0E{zZnk4i1)ytRs&Y8j23T`-i zNxDMTMeEtYh**7^`*nP5x9*X-Fr`CgrF&9MyG)Oe1+)AR*|x*3QtpYJhw@psb~;B_ z;|i)ASD-RefMlPFbIjOxASI)u_DTzUX~K{ul)qtuOA{*JFaf0^W>hm1dSiAJChUQ= z!ZgUx`G^gsJV^IU+vs>R7X6wRsc31q^BQ|3{$zgB=cl$MM5(@PEIfaB#{e5po#-Bui)Hy6VRZwR^ffNmFn>yOZheTzh&Nw0sW8rw+!Z1rj4w~dr34RM%1`dErc65Zu zGlfy%ZIB-sU|NCMjynZ6P!~{=O0SYr1R&;&33Z8QS^>$_mTKx>dvjLa6_S#JXt-d5 zgy#_A@MKrWlo(b1#ilA~(ICj12+D40-?|?lKlQ*B#cH7AF54gCKUXZbKN9h5D<_#< zrXv(cAYcx;OUFz>9Hln8J5U0UI^wHn zw&@rBC4Z_l5gnJaa{L@a)b?EhP>!MhQHT)KI}5G@s6hG*U5v1{6-Cbd@%S8BorCN$ zbtl{BboPw5OUM>urUuA|s<=O7Fw6pc>#m_-v%pvKh|^?-RB zv${NSr!Yx1c}Ffs~OKV>p5ZEH{|4*d)CKfnboxs$~Z7YMLb6>-333xy9( z4o&f9*&m|{CZHXWfuR~kHAdQ>_}Kz}q4dMwve)EAZw+R?MN_&5<)Cjrz0UgUTcDa+ zMkP;6v5_|Ee4|9pNq$)Xf^`X*(%nTckwl0U3PDaJ6umOMu&Vg{q;X#JX(ulTpDw}E zqX_OS$EE#<;(G2IRumx{;v|JJV;k1LlL3F?ZYr!xr+ZQ{2ml~nSekrMSrP3>(VC75 zBoDbR8IBf}7U33_Mn#y7fnL)L6a)0e0>9j)(qI=vS?QLnTk2m4yAMs0yrpb)I=3jU zvEgNB>8Z~0n$FA)Nh<3UyvtmgxS~g>z>9FmSTd*uKwIkb1f<@uMJ6uG;co;$2$SNO zwP}Kj3A~^jHB~J9nQXp*d^|gs{VOv*8Th?4Q5Vrj$P7Iz@nUJ$-Kv_=LyQBLb9L^rY zJdq1&YthHGjy%?}lO#fIfiK;POes@_6X6FHzYB)Dd}g3pA8@}?Hkk4me@>=FMoB>j z#I$GlbDWkI*&cygG)l9w#pp&|9VZ!zp`I^tuVu#I#E9lRK0RLX3E>6I3=Yo)gxO59fkjl}khGl|>4C97k zH)In7A}Mha^@2IwS%uO4Rwkq))1mUf)PeADd={jrMDS{P`enD_0{IS=8z>6SWO^m? zJXxMl1ICW$tNjzdgdoUN>F5UyNZf)`EB(FLHZJVedC$HmU!C>s)2og(u&%nt2Oay= z$Y6nt9TO4do}yuOI=w;f_{ciAYMmZm5Jl_s-PxgiH0WpPJ!V0m4Fh?Ssa=)oQ+b zgj!ONmJ4REjW$UM)z@TCG*%;-P6*hE<3$RVaC??|#M*>OK}Nc5E5;2;QXmI#lR_`1 zbMX@pf|^G5bVCrKVJegOs3c~c#fTug4MvqGuFq|N?f@N>5#{l8yJZh7dN{DRgPT}= z3=|bhFF*|vSx`6=9XTlqP(s!U=#U#0k)w^26=6fjQl`T)B@%U&E0GsibPA-noQ2lB zq^yYBzv5br8E5lJgff)b!)q}YA<0yQvG~)YJ*a93t?4SvY8sJ|jf#N8?Kg2R4lo(C zdz5>q-?XTr|A{WHlj)mc7YJkYUW#n+$I;scJpR*n1ar+q3>uI1=Sy=?(s$EeQ)Z0TgLa6pKWDUYZ` zk9s`ph5*pOE z9$cAp(5X^MHAbUG=HPVbc!4(Cj9wouMNc>prV^Z1P9Pi6fpW8ZSP| zhoSWS`SOx3eMgs^HZ z8DbrR3r2V*%Itr)bE8H)77sBnoyMJV>}#~ZW&A59u$KKIwD`*|`$YioFLa6}pw|ay zn4og9sVb$g2u0#cvx`T2j4tM9!X`;p z=$u17IQU>U?N^tV>XP^JvVR|69ADI`mzT24RW2kx5OF!BR$VtG;j>D4-IVx#HVplq zcp)kYDq1)4t;M192XSj#mMiF1FA%LoEc=;yasuh@6WyTG68~%tE63XhQ9Dh=PvY`i5Vg{VZk(yB$96Zk1!(5j~|mC{rKAdsMf7gv12xxjk;N_w#sIsg}-d8Uhml5dbiYRejn+} z^fDvQzqB#u7Wf_pU9PS*=)F#ZS54eGe&DNXXCdG$s>gk!p%O>3sRFj+|kc`iL6EVlJOw68O<^ zjS?~1|8mqVs!Wt@VE{z|15cRb3s^ktrEk1B5$Tos#M4CN1{7~}Log20uMD*hKR*+} j#fYd>H{%7|vYiE [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > A class cannot implement a member marked [**[PreserveSig]**](../../tB/Core/Attributes#preservesig): the compiler reports the implementation as unmatched. Interfaces of this kind often return a value directly rather than an **HRESULT**, and are declared with **[PreserveSig]** for that reason. For a member that returns a 4-byte value, such as a **Long** or a C `BOOL`, declare it in the interface the class implements as a **Sub** without **[PreserveSig]**, and return the value with [**Err.ReturnHResult**](../../tB/Modules/ErrObject/ReturnHResult). Native code that calls this member as `long __thiscall Count()` gets 3 as its return value: > > ```tb check_build projname=calling-conventions-thiscall-return @@ -122,7 +122,7 @@ An ActiveX DLL's type library records a **ThisCall** or **FastCall** member as s A callback passed to an API must use the convention the API calls it with. Declare a [**Delegate**](../../tB/Core/Delegate) with that convention, use it as the parameter type, and give the target procedure the same keyword. [**AddressOf**](../../tB/Core/AddressOf) of a procedure with a different convention is accepted with a warning only (TB0026). -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > On a 32-bit build, a call made from twinBASIC code *through* a **ThisCall** or **FastCall** delegate passes its arguments as stdcall does, and fails with *Bad DLL definition*. Calling the procedure directly works. **CDecl** delegates are not affected, and neither is a 64-bit build. The following example performs a quicksort using the C runtime's [`qsort` function](https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/qsort), which calls its comparator with the cdecl convention: diff --git a/docs/Features/Advanced/Classes-and-Modules.md b/docs/Features/Advanced/Classes-and-Modules.md index 22fceb821..f371e6ef1 100644 --- a/docs/Features/Advanced/Classes-and-Modules.md +++ b/docs/Features/Advanced/Classes-and-Modules.md @@ -18,7 +18,7 @@ A class can have a `Sub New` constructor, and the constructor can take arguments - A class that has both a `Class_Initialize` and a `Sub New` with no parameters cannot be created with a plain `New`. The call matches both, and fails with TB5073. - A class whose only `Sub New` has required arguments, with no `Class_Initialize`, cannot be declared [`As New`](../../tB/Core/New), which creates the object without arguments. The declaration fails with TB5121. If every argument of the `Sub New` is `Optional`, the class can be declared `As New`: the object is created by calling `Sub New` with the default values. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > A `Sub New` declared `Private` is meant to make construction private, so that only code inside the class creates objects with it. This is not enforced: `New MyClass` in another module of the project, or in a project that references a package with the class, compiles and runs the private constructor. ### Example diff --git a/docs/Features/Compiler-IDE/Compiler-Warnings.md b/docs/Features/Compiler-IDE/Compiler-Warnings.md index cec6f4743..c3a7b7742 100644 --- a/docs/Features/Compiler-IDE/Compiler-Warnings.md +++ b/docs/Features/Compiler-IDE/Compiler-Warnings.md @@ -261,7 +261,7 @@ Public Sub Convert() End Sub ``` -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > A member of another enumeration, written as a constant (`day = vbOK`), converts without a warning. ### TB0020: Suspicious Interface Conversion @@ -284,7 +284,7 @@ End Sub Reports an implicit conversion between an enumeration and a numeric type. It is at **IGNORE** by default. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > None of these conversions draws TB0021. A whole number assigned to a variable of an enumeration, or an enumeration value assigned to a **Long**, converts without a warning. A **Double**, **Single**, **Currency** or **Variant** assigned to a variable of an enumeration draws [TB0019](#tb0019-implicit-enumeration-conversion), and an enumeration value assigned to an **Integer**, **Byte** or **Single** draws [TB0018](#tb0018-implicit-narrowing-conversion). ### TB0022: Suspicious Assignment diff --git a/docs/Features/Language/UDTs.md b/docs/Features/Language/UDTs.md index 1922891cb..ec8ef17f0 100644 --- a/docs/Features/Language/UDTs.md +++ b/docs/Features/Language/UDTs.md @@ -58,10 +58,10 @@ End Type UDTs of these types are still stack allocated structs that can be used with standard Win32 APIs. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > A method or property of a UDT called on an element of an array of the UDT, such as `a(1).Sum`, ends the program with an access violation, in 32-bit and 64-bit builds. A variable of the type, a UDT member of another UDT, and an element of an array declared inside a UDT are not affected. Copy the element to a variable first. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > Assigning **Me** whole inside a method of a UDT, as in `Copy = Me` or `Return Me`, is a code generation error that fails the build once the method is called. Copy the members one by one. ## Custom UDT Packing diff --git a/docs/IDE/Building/ActiveX-Registration.md b/docs/IDE/Building/ActiveX-Registration.md index e52e32ab6..69ae8be2b 100644 --- a/docs/IDE/Building/ActiveX-Registration.md +++ b/docs/IDE/Building/ActiveX-Registration.md @@ -62,7 +62,7 @@ Its one button is **Continue Anyway**. The box does not appear when *Register DL ![The warning box the IDE shows as it opens a project that registers to HKEY_LOCAL_MACHINE while the IDE is not elevated, with one button.](Images/HKLM_Warning.png){:width="541" height="249"} -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > The warning opens twice each time you open the project, and also for a Standard EXE project that has *Register DLLs to HKLM* at **Yes**, although an EXE registers nothing. Choose **Continue Anyway** each time. To stop the box, set *Register DLLs to HKLM* or *Register DLL after build* to **No**. If you build anyway, the DLL is written and its registration fails: @@ -88,7 +88,7 @@ For a DLL registered under `HKEY_LOCAL_MACHINE`, in an IDE that is not elevated, **Delete It And Continue** builds again and ignores the old build. The console prints `[BUILD] Starting...` again. **Cancel** stops there. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > The box that opens at **Clean** has the same buttons, but **Delete It And Continue** there builds a new DLL instead of deleting the old one: the console prints `[LINKER] SUCCESS` and a failed `[REGISTER]` line. Choose **Cancel** at **Clean**: the box does not clean. If the previous file is not a DLL any more, for example because something else replaced it, the deregistration fails with `-2147467259`, the IDE asks nothing and the build goes on. The new DLL is registered normally. diff --git a/docs/IDE/Building/Build-Output.md b/docs/IDE/Building/Build-Output.md index 34f47be92..81a082847 100644 --- a/docs/IDE/Building/Build-Output.md +++ b/docs/IDE/Building/Build-Output.md @@ -32,7 +32,7 @@ A build makes the folders of the path when they are missing, nested ones too. Th - An unknown variable such as `${Nope}` prints `[COMPILER] build path contains unrecognized variable.` and builds nothing. - `${IdePath}`, which the samples that build an add-in use, is the folder the IDE is installed in. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > `${ProjectID}` in the path is replaced with nothing. A path of `${SourcePath}\Build\${ProjectID}_x.exe` builds a file called `_x.exe`, with no message, and two projects that use the same pattern overwrite each other's file. Do not use `${ProjectID}`; name the file with `${ProjectName}`. The other variables, `${VersionMajor}`, `${VersionMinor}`, `${VersionBuild}`, `${VersionRevision}` and `${Bitness}`, give the numbers and the bitness as the settings and the box hold them. @@ -60,7 +60,7 @@ The *Major*, *Minor*, *Build* and *Revision* settings of the [version resource]( The type library of an ActiveX DLL or control has its own pair of settings, [*Type Library Version*](../Project-Settings#type-library-version), and its own *Auto-Increment*. A build prints `[BUILD] Auto-incremented type-library version number to: ` for it. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > The number that *Auto-Increment* raises is changed in the IDE's memory only. The project is not marked as changed, the `Settings` file is not written, and the **Version** row of the **PACKAGE PUBLISHING** pane stays as it was. **Close Project** asks nothing, and the increments are lost: the next session builds again with the old numbers, and a package is published with a number that was used already. Set the numbers by hand in Project Settings before you build a version that you release or publish, and save the project. ## What each project type produces diff --git a/docs/IDE/Building/Build-and-Clean.md b/docs/IDE/Building/Build-and-Clean.md index 36c042dcd..f77f830a7 100644 --- a/docs/IDE/Building/Build-and-Clean.md +++ b/docs/IDE/Building/Build-and-Clean.md @@ -49,7 +49,7 @@ A project made from a template, a sample or a folder, or imported from a VB6 pro The build does not go on after the dialog. Press **Build** again. The folder you chose is now `${SourcePath}`, so the default build path puts the file in a `Build` folder in it. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > **Save** in this box makes the file you choose the project's file, but writes no `.twinproj` there, and nothing is built. The project stays changed, and **File → Close Project** still asks **You've made changes to this project. Save now?** Press Ctrl+S after the dialog to write the file. A project that you opened from a `.twinproj` file has its file, and builds with no question. [Saving and exporting](../Projects/Saving-and-Exporting#a-project-with-no-file) describes the project with no file. diff --git a/docs/IDE/Building/LLVM-in-the-IDE.md b/docs/IDE/Building/LLVM-in-the-IDE.md index 19520a8c7..9de370a8e 100644 --- a/docs/IDE/Building/LLVM-in-the-IDE.md +++ b/docs/IDE/Building/LLVM-in-the-IDE.md @@ -42,7 +42,7 @@ When the compiler reports progress, the IDE opens a box titled **Compiling...** ![The Compiling... box of an LLVM build, its progress bar at the start.](Images/Compiling.png){:width="302" height="74"} -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > If another box opens while **Compiling...** is on screen, the end of the build closes that box instead of the progress box, so the other box is lost. The **Compiling...** box stays, with no button, and the IDE cannot be used. Restart the IDE. ## The cache diff --git a/docs/IDE/Building/Package-Publishing.md b/docs/IDE/Building/Package-Publishing.md index bead0c8ad..ab1529ff9 100644 --- a/docs/IDE/Building/Package-Publishing.md +++ b/docs/IDE/Building/Package-Publishing.md @@ -74,10 +74,10 @@ The IDE saves the publisher name. It saves the password only when **Remember pas The password is stored in the IDE's settings in the registry with a simple obfuscation, which is not encryption. Anyone who can read your registry can recover it. Leave the box unticked on a machine that others use. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > A remembered password that contains `~` comes back wrong: the form is filled in with a different password, and the login fails. Do not tick **Remember password on this device** for such a password, and type it each time. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > A saved publisher name that contains a single quote, such as `O'Brien`, is cut at the quote in the pre-filled form (**USERNAME** shows `O`). A remembered password with a quote is cut in the same way. Type the whole name and password into the boxes. ## Publishing @@ -105,7 +105,7 @@ The confirmation reads **IMPORTANT: Please check the following information befor | The login has ended | The sign-in box opens again. | | Anything else | **[PUBLISH] FAILED. Server returned HTTPS code** and the number. | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > After you press **OK** in a box that says **[PUBLISH] FAILED. Server returned HTTPS code**, the IDE raises a script error (`t is not a function`) that only the IDE's developer tools show. Nothing else changes, and the failure is as the box said: check the number, then publish again. The IDE has no command that removes or overwrites a published package. A version that has been uploaded stays on TWINSERV, and the next upload needs a higher version. diff --git a/docs/IDE/Code/Diagnostics.md b/docs/IDE/Code/Diagnostics.md index ba80b8153..796999262 100644 --- a/docs/IDE/Code/Diagnostics.md +++ b/docs/IDE/Code/Diagnostics.md @@ -72,7 +72,7 @@ Right-click in the list for a menu of five items. The menu is in English whateve A copied row reads `{ERROR}` or `{WARNING}`, the path of the file, the position, and the code and message. **Copy All** and **Copy All Errors** begin with a line that names the IDE: `twinBASIC (` and the version, then `) compilation error report:`. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > Each copied message contains the HTML tag `TBnnnn` around the code, and **Copy All** labels hints and information messages `{WARNING}`. Remove the tags from the pasted text by hand. ## In the code diff --git a/docs/IDE/Code/Editor.md b/docs/IDE/Code/Editor.md index 9303e7660..67b17e7d6 100644 --- a/docs/IDE/Code/Editor.md +++ b/docs/IDE/Code/Editor.md @@ -26,7 +26,7 @@ Each open file has a tab. The tab shows the file's icon, its name and a close bu - Drag a tab to move it. A mark on the left or the right half of the tab under the pointer shows where it will land. - Click the middle mouse button on a tab to close it. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > A tab is tinted only when the compiler reports its file while the tab is open. A tab opened after that report stays untinted until the compiler checks the file again, for example after the next edit to it. The Project Explorer row is tinted either way. A tab in italics is a *preview tab*. When **Single-Click Preview** is on, a single click on a file in the Project Explorer opens it in a preview tab, and the next single click replaces it. A double-click, or any edit, makes the tab permanent. The **Find / Replace** panel also opens the files it visits in a preview tab. [IDE Options](../Customising/IDE-Options) describes the option. @@ -188,7 +188,7 @@ Typing, deleting and most commands act at every cursor. One Ctrl+Ctrl+D and Ctrl+Shift+L match whole words in the current file, including words inside strings and comments. Ctrl+Shift+L and **Select All Matches** (see [Finding](Finding#select-all-matches)) both give a cursor to each match. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > Esc does not remove the extra cursors. Click in the text, or run any command that places the caret, to go back to one cursor. ### Change case @@ -270,7 +270,7 @@ Paste on a blank line, or with the caret at the end of the indent. Pasting at th The formatter changes only the indent of each line. It trims the line and sets the indent to four spaces for each level, whatever **TAB Spaces Amount** is. It does not change the words, their case or the spacing inside the line. It adds a level after `Class`, `Module`, `Type`, `Enum`, `For`, `While`, `Do`, `With`, a block `If`, `#If`, `Sub`, `Function` and `Property`, and takes it away at the matching End. `ElseIf` and `Else` lines sit at the level of their `If`. A `Select Case` indents its `Case` lines one level and their bodies two. A continuation line is indented two levels deeper than its statement. The members of an interface get one level and no End. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > **Format Selection** fails when the selection starts on a continuation line, which is a line after one that ends in ` _`. Nothing is formatted, and a box opens with the text *IDE startup failure*, followed by *JS Promise Rejection(1): Assignment to constant variable.* If the IDE was started by opening the project's `.twinproj` file, it then closes. With unsaved changes, it first asks whether to save them: choose **Save**, or **Cancel** to keep the IDE open. Save before you format, start the selection on the first line of the statement, or use **Format Document**. ## Auto Prettify @@ -306,7 +306,7 @@ A right-click in the editor moves the caret to the point, unless you click insid Shift+F10 opens the same menu from the keyboard. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > Shift+F10 opens the menu at the top-left corner of the IDE window, not at the caret. Right-click in the editor to open it at the pointer. ## The command palette diff --git a/docs/IDE/Code/Finding.md b/docs/IDE/Code/Finding.md index 7b194c575..455caa51b 100644 --- a/docs/IDE/Code/Finding.md +++ b/docs/IDE/Code/Finding.md @@ -120,14 +120,14 @@ A search that reaches the end of its scope with **Down** asks **End of search sc A search with **All**, a project search, and **Replace All** end with **The specified region has been searched.** After a **Replace All**, a second box says how many replacements were made: **N replacements were made.**, or **1 replacement was made.** With no scope ticked, the panel says **Scope not selected**. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > After a search of **Selected Text**, moving the caret leaves **Selected Text** ticked but greyed out, and **Current File** is no longer ticked. **Find Next** then says at once that the end of the search scope has been reached. Tick **Current File** again to search on. ## Match text A plain search ignores case unless **Match Case** is ticked. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > **Whole Word Only** never finds a match that starts at the very first character of the file. For example, searching for `Module` with **Whole Word Only** skips a `Module` on line 1, column 1. A **Replace All** with **Whole Word Only** leaves that word unchanged. Search without **Whole Word Only**, and check the first line yourself. ### Pattern matching @@ -150,13 +150,13 @@ With **Match Regular Expressions**, **Find What** is a JavaScript regular expres If **Find What** is not in the `/pattern/flags` form, a box says **Invalid format. Expecting /pattern/flags**. If the pattern is not a valid expression, a box says **Invalid RegExp:**. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > A regular expression that has a capital letter finds nothing unless it has the `i` flag. The search runs on the text of the file turned into small letters, and the pattern is left as written. `/End Sub/` finds nothing and reports **The specified region has been searched.**, while `/end sub/` and `/End Sub/i` find every `End Sub`. Write the pattern in small letters, or add the `i` flag. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > A regular expression whose match spans a line break, such as `/^\s+end sub$/`, selects nothing. The selection is an empty one at the end of the line before the match, and **Find Next** stays there. Search for one line at a time. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > The **Invalid RegExp:** box leaves out the reason. It shows the words and nothing after them. ## Search the whole project diff --git a/docs/IDE/Code/Navigating.md b/docs/IDE/Code/Navigating.md index 0273c023b..a2a840021 100644 --- a/docs/IDE/Code/Navigating.md +++ b/docs/IDE/Code/Navigating.md @@ -38,7 +38,7 @@ Where the caret lands: | A member of a package, or of the VBA library | A new tab with the package's source. The tab is read-only. | | A keyword, or text in a string | Nowhere. | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > Ctrl+Up and Ctrl+Down, and Go To Definition for most names, land on the line after the declaration, not on the declaration itself. Press Up once to reach it. Ctrl+click does not follow a name, and the editor has no Peek Definition. @@ -99,7 +99,7 @@ The **OUTLINE** panel lists the symbols of the file you are editing. It is empty ![The Outline pane listing the two top level items of the open file, the module MainModule and the class GlobalSearchAddIn.](../Images/Outline_1.png){:width="300" height="96"} -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > The Outline lists only the top-level types of a file, such as the module or the classes. The procedures, fields and properties inside them are missing, rows have no expand arrow, and the filters of the **...** menu below change nothing you can see. To go to a procedure, use the [combo boxes](Editor#the-navigation-combo-boxes). The **...** button in the header of the panel opens a menu of eleven items that you tick. The IDE keeps them for each project. diff --git a/docs/IDE/Customising/Themes.md b/docs/IDE/Customising/Themes.md index 1eed77f83..2135bedca 100644 --- a/docs/IDE/Customising/Themes.md +++ b/docs/IDE/Customising/Themes.md @@ -70,7 +70,7 @@ MenuBackColor: red; SymbolClassTextDecoration: underline; ``` -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > A file named `Dark.theme` or `Light.theme` in `%APPDATA%\twinBASIC\themes` breaks the inheritance of every custom theme. The IDE ignores the file's contents, but still counts it, and so it never links the themes to their parents. Every custom theme, and **Classic (Light)** with them, is listed as `(undefined)` in place of **Light** or **Dark**. Choosing one gives the dark base with only that theme's own properties changed, and **Reload from disk** no longer applies the edits you make. No error is shown. To change one of the shipped themes, give your file another name and inherit from the shipped theme: `inherits: dark;`. A chain of `inherits:` lines must not return to a theme in it. When `LoopA` has the line `inherits: LoopB;` and `LoopB` has `inherits: LoopA;`, **Reload from disk** shows the alert *Invalid theme file detected: LoopB.theme (recursive inheritance)*. `LoopA` is then left out of the **Theme** list, and `LoopB` is listed with the dark base. diff --git a/docs/IDE/Debugging/Breakpoints-and-Stepping.md b/docs/IDE/Debugging/Breakpoints-and-Stepping.md index 5eff841a1..4a6d52671 100644 --- a/docs/IDE/Debugging/Breakpoints-and-Stepping.md +++ b/docs/IDE/Debugging/Breakpoints-and-Stepping.md @@ -87,7 +87,7 @@ A procedure in another file opens in the editor when you step into it, as a prev At the `End Sub` of a procedure whose local variables hold objects, a step goes into a frame named `{class_final_destruction}` and then into the `Class_Terminate` procedures of those objects. A step off the `End Sub` of the `Click` procedure of a form lets the program run on, and the form waits for input again. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > Once the program has stopped at a breakpoint, no step stops in a caller of the procedure it stopped in. Step Out, F8 at the `End Sub` and Step Over all run the caller on until the next breakpoint or the end of the program. Steps inside the procedure, and into the procedures it calls and back out to it, work. To stop in the caller, put a breakpoint there, or stop on the line of the call and step into the procedure. ## Set Next Statement @@ -168,7 +168,7 @@ Running any line in the console removes the error panel. The failing line stays - **Stop** does not end the program at an error. See [Traps](#traps). - **Search Online** opens a Google search in the default browser. The search text is `twinBASIC VB6`, the header and the description of the error. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > **Search Online** puts the error text into the address as it is, without encoding it. An `&` in the description ends the search text at that point, so the search loses the rest, and a `"` ends the address. Copy the description from the panel and search for it in your browser instead. ### Stepping on through the rest of the loop @@ -184,17 +184,17 @@ To run on to the end instead, correct the value and press F5, or pres Edit and Continue is not supported at the error panel either: when you change the code, the console writes `[EDIT & CONTINUE] not yet supported. Aborting.`. After you change the failing line, F5 starts a new run that runs the changed line. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > An edit at the error panel does not abort the program, although the console says `Aborting`. The rest of the failing procedure is skipped, and the code after its call runs: its output appears in the console, and the run then ends. At a breakpoint the same edit does abort the program. Do not edit the code while the error panel is showing. End the run first, as the **Stop** warning under [Traps](#traps) describes, and then edit. ### Traps - **The panel disappears, and Ignore with it.** Running a line in the Debug Console, or clicking another procedure in the Call Stack, removes the panel. F5 brings it back only by running the failing line again. Set Next Statement onto the next line, then F5, does what **Ignore (Resume Next)** would have done. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > A step key pressed on the failing line leaves a step waiting. After F8 on the failing line, **Ignore (Resume Next)** stops again on the next line instead of going on, and the next F5 is used up as well. Press F5 to go on, or use Set Next Statement as above instead of a step key. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > **Stop** at an error ends only the procedure that failed. The procedure that called it goes on from its next line: if `Sub Main` called `FillTable`, the rest of `Sub Main` still runs. The panel's **Stop**, the toolbar's **Stop** and **Run → End** all do this. At a failed `Debug.Assert`, **Stop** ends only the check, and the test goes on past it as if the check had passed. To end the whole run, first move the arrow to a later line with Ctrl+F9, then press **Stop**. Away from an error --- at a breakpoint, after a step, or after Set Next Statement --- **Stop** ends the run. ## See Also diff --git a/docs/IDE/Debugging/Debug-Console.md b/docs/IDE/Debugging/Debug-Console.md index f76fa6d21..5fdc86b9d 100644 --- a/docs/IDE/Debugging/Debug-Console.md +++ b/docs/IDE/Debugging/Debug-Console.md @@ -95,13 +95,13 @@ The input row is one line. There is no way to enter a multi-line construct. - The row has the IntelliSense of the editor, in the mode that [IDE Options](../Customising/IDE-Options#intellisense-modes) sets, and Ctrl+Space opens the list. It has a line for signature help of its own. - A [keyboard shortcut](../Customising/Keyboard-Shortcuts#the-text-of-a-group) can run a line of code in the console: `tbDebugConsole_ExecuteCode('? 6*7')` is the one command that takes an argument. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > The input row offers no completion for a name typed after `?` and a space, and Ctrl+Space there opens no list. Type the name without the space, as `?total`, or start the line with the name. > [!NOTE] BETA 1034 > In the **MODERN** [Intellisense mode](../Customising/IDE-Options#intellisense-modes), a space typed while the completion list is open accepts the list's first entry, and the space is lost. Typing `Print total` gives `Printertotal`, because the list offers `Printer` but not the keyword `Print`; `Call Touch(total)` and `Set obj = items` are changed in the same way. The console refuses the changed line. Press Escape to close the list before you type the space: the text you typed stays. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > `? p`, where `p` is a variable of a user-defined type, fails with `(compile error: codegen error; check for compilation errors)`, and the console prints a `[LINKER] compilation (codegen) error` line. The [Variables](Inspecting#variables) panel and a watch show the same variable, so read it there. ## Copy the output diff --git a/docs/IDE/Debugging/Inspecting.md b/docs/IDE/Debugging/Inspecting.md index bc3da77dd..136347edd 100644 --- a/docs/IDE/Debugging/Inspecting.md +++ b/docs/IDE/Debugging/Inspecting.md @@ -63,10 +63,10 @@ Double-click a value to edit it. A text box replaces the value. Enter - A row that is a folder, an object, a user-defined type or an array, has no text box. Expand it and edit its parts. - A line typed in the Debug Console, such as `s = "it's"`, changes a variable too, and the panel shows the change. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > The edit box cuts a string at an apostrophe, and leaving the box changes the variable. With `s` holding `it's`, double-click `s`, and the box holds only `"it`. A click on another row, or Enter, with nothing typed, then makes `s` the empty string. Escape leaves it as it was. Do not edit a string that contains an apostrophe in the panel. Assign it in the [Debug Console](Debug-Console) instead. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > A **Date** or **Double** row shows a wrong value for a while after you edit it. A **Date** edited to `#1/2/2020#` shows `#12:00:00 AM#`, and a **Double** edited to `7.25` shows `0`. The variable holds the value you typed. The row shows it correctly after the next line you run in the console, or at the next stop. To check the value now, run `? d` in the console, where `d` is the variable. ### What the tooltip shows @@ -145,7 +145,7 @@ A user-defined type shows `{user defined type, 8 bytes}` and one line for each f Pointing at a function call or at a property runs it, so a function with side effects changes the state of the program. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > Pointing at a call to a **Sub** runs the Sub, and the popup shows `t.Bump: 0`, a value that means nothing. The Sub, and any event handlers it raises, runs once for each hover. Do not point at the name of a procedure that changes the program's state while the program is paused. To read a variable that such a procedure uses, point at the variable. When the program is not paused, the same pointing shows the compiler's usual hover text, which [IntelliSense](../Code/IntelliSense#hover) describes. diff --git a/docs/IDE/Debugging/Memory.md b/docs/IDE/Debugging/Memory.md index 8e3c42a52..9b6a14398 100644 --- a/docs/IDE/Debugging/Memory.md +++ b/docs/IDE/Debugging/Memory.md @@ -38,12 +38,12 @@ The debugger evaluates the expression in the box as it does a watch, so the box - An **array**: the text is replaced with `VarPtr(`*expression*`)`, and the pane shows the array variable. - Any other result is a number, and **the number itself is the address**. The name of a **Long** variable therefore shows the memory at the address the variable *holds*, not the memory of the variable. That is what a **LongPtr** holding an address needs. For a variable's own bytes, enter `VarPtr(`*name*`)`. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > A result that is not a whole number --- the name of a **Double**, a **Boolean** or a variable of a user-defined type --- gives no address and does not turn the box red. The rows stay as they were, and the DEBUG CONSOLE prints an *IDE internal error* such as `Uncaught SyntaxError: Cannot convert 1.5 to a BigInt`. Enter `VarPtr(`*name*`)` for such a variable. The pane starts its first row at that address exactly, and the cell at the address takes the keyboard focus. An expression that cannot be evaluated --- an unknown name, an empty box, anything entered while the program is not paused --- turns the box red and leaves the rows as they were. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > A negative number is taken as an address too. With -8, the first row is labelled `&HFFFFFFF8` and the rows after it `&H00000008`, `&H00000018` and so on. None of them can be read. The table shows what each kind of expression gives in a 32-bit program paused after these lines: @@ -86,7 +86,7 @@ Below, `s` holds "Hello twinBASIC". After `s` is entered, the box reads `StrPtr( `??` in the value cells, and `·` in the text column, mark memory the program cannot read. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > `??` and `·` are also what a cell shows while its bytes are still being read, so unreadable memory looks the same as memory not read yet. Memory that keeps showing `??` cannot be read. ## Display formats and columns @@ -106,7 +106,7 @@ The formats of two and four bytes show each value as a number, so the hexadecima The second list, **Cols 8**, **Cols 12**, **Cols 16**, **Cols 24** or **Cols 32**, sets the number of values in a row. A row therefore covers more bytes in the larger formats: 16 in **Byte (Hex)** at **Cols 16**, 32 in an **Integer** format and 64 in a **Long** format. The address column steps by the same amount, and is written as `&H` and eight hexadecimal digits. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > The address column of a 64-bit program shows only the low 32 bits of each address. The bytes shown are those at the whole address, but an address of `&H4232CFF438` is labelled `&H32CFF438`, which is another address. Do not copy an address from the column in a 64-bit program; print it with `Hex(VarPtr(`*name*`))` instead. The text column on the right always shows every byte of the row, one character each. A byte from 32 to 126 shows as its ASCII character, and any other byte as a dot. diff --git a/docs/IDE/Debugging/Running.md b/docs/IDE/Debugging/Running.md index d6f4d4cd0..4aaee49a3 100644 --- a/docs/IDE/Debugging/Running.md +++ b/docs/IDE/Debugging/Running.md @@ -45,7 +45,7 @@ At a run-time error, **End** ends only the procedure that failed; see [Traps](Br In a form that is waiting for input, and in a loop that holds only `DoEvents`, there is no line of yours to stop at. The IDE opens a file of the VB package, `IdleMessageLoopBreak.twin`, and stops in its procedure `IdleMessageLoopBreakpoint`. With a form waiting for input, a line typed in the console works there. Press F5 and the program stops again in the same file. Press it once more to run on. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > A program that is stopped by **Break** in a loop that holds only `DoEvents` ends with a native access violation as soon as the IDE evaluates anything. A line typed in the console does it, even `? 1`, and so does any watch, because the IDE evaluates every watch at every stop. The DEBUG CONSOLE prints `NATIVE EXCEPTION: ACCESS_VIOLATION`, and the IDE stays in the paused state, with the arrow on the VB package's line and **Stop** and the step commands enabled, although the program has ended. Do not use **Break** on a loop that holds only `DoEvents`. Put a statement in the loop and a [breakpoint](Breakpoints-and-Stepping#breakpoints) on it instead. If the IDE is already in that state, **Restart the compiler** in the toolbar puts it right. ## What Start runs @@ -69,7 +69,7 @@ Start shows no dialog for a compile error, and it opens no panel. The [DIAGNOSTI - **In a procedure that `Main` calls.** The console shows `[LINKER] compilation (codegen) error detected in 'Startup.Main' at line #42`, where 42 is the line of the call. The editor opens `Main`'s module and stops at its `Sub` line with the error panel, titled **ERROR OCCURRED**. Nothing in `Main` has run. - **In `Main` itself.** No panel opens. The console shows `[LINKER] compilation (codegen) error detected in '{temp_component}.{temp_procedure}' at line #1`, then `(compile error: codegen error; check for compilation errors)`, and the program is idle again. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > The panel for a compile error gives the description `COMPILATION (CODEGEN) ERROR AT LINE #42. PLEASE REPORT THIS ERROR TO https://git.io/JMh6S`, and the line it names is the line of the call, not the line of the error. A compile error in your code is not a defect to report. Read the real error in the DIAGNOSTICS panel and fix it. ## Run Or Preview @@ -89,7 +89,7 @@ A preview is also a debug session. A breakpoint in `Form_Load` stops it, and a c - A caret at module level, outside a procedure: `RunCodeUnderCursor: failed to identify the procedure at the cursor position.` - With no editor active: `runCodeUnderCursor failed: no active text editor.` -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > F6 also gives `failed to identify the procedure at the cursor position.` when the caret is on the procedure's `End Sub` or `End Function` line, or on another of its lines at or past the column where that `End` line ends. A comment at the end of the `End` line moves that limit to where the comment starts. Nothing runs. Put the caret nearer the start of a line of the procedure. The CodeLens **▶ run** link above a procedure runs it by another route: [CodeLens](../Code/IntelliSense#codelens) describes it. [Testing with Assert](../../../Tutorials/Testing-with-Assert) uses both. @@ -119,7 +119,7 @@ A `Shell` that runs from a line you type in the console makes a box too, with no - **Opening a form designer.** The IDE tells the compiler to end the program if it is busy. A form program that is waiting for input ends, with `form terminate` and no `Form_Unload`. A loop with no `DoEvents` keeps running, and so does a program that is paused. - **Editing the code.** See the next section. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > An `End` statement in a procedure that LLVM compiles ends the run, and then the console shows `restarting from MEMORY []`: the IDE restarts the compiler and compiles the project again. The IDE recovers by itself. Remove the `End` statement, or compile the procedure without LLVM, to avoid the restart. ## Editing while the program runs diff --git a/docs/IDE/Forms/Arranging.md b/docs/IDE/Forms/Arranging.md index b4e3313a8..4e4a1ed6f 100644 --- a/docs/IDE/Forms/Arranging.md +++ b/docs/IDE/Forms/Arranging.md @@ -50,10 +50,10 @@ None of the controls is the reference. The first control you clicked has no spec **Center (Horizontal)** and **Center (Vertical)** have no keys. **To Grid** has none either. It rounds the **Left** and **Top** of each selected control down to the grid, and rounds the size down too: a control 24 pixels high becomes 20 with a 10-pixel grid. It acts at once, even when **Align Controls To Grid** is off. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > **Center (Horizontal)** and **Center (Vertical)** do not centre the controls on the middle of the selection. They centre each control on the left edge, or the top edge, of the selection. Three controls 100 pixels wide at **Left** 10, 140 and 250 all end at **Left** -40, their middles on 10, and may end outside their container. To line up the middles of a group, align the edges and then type the **Left** or **Top** values in [PROPERTIES](Properties). **Center In Container** centres a group in its container correctly at a zoom of 100 %. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > Each Alt+arrow also opens the IDE's menu bar when you release the keys. The controls are aligned all the same, and nothing is lost. If the menu bar is in the way, align with the **Format → Align** commands or the toolbar buttons instead. ## Make the controls the same size @@ -71,7 +71,7 @@ None of the controls is the reference. The first control you clicked has no spec A command changes nothing when the controls already have the same size. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > **Make Same Size** changes the controls on the surface and marks the form changed, but it does not reach the saved form. If you press Ctrl+S, close the tab and open the form again, the controls have their old sizes. A later change, such as moving a control with an arrow key, saves the new sizes with it. After you use **Make Same Size**, make such a change before you save, or type the **Width** and **Height** values in [PROPERTIES](Properties) instead. ## Space the controls evenly @@ -100,14 +100,14 @@ For example, take three controls 100 pixels wide with **Left** 10, 140 and 250, The spacing commands compare positions that count from each control's own parent. Controls in different containers therefore give odd results. For example, **Remove** on a text box in a frame and a button on the form places the button at the text box's right edge as counted from the frame's corner, not from the form's. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > **Horizontal Spacing** and **Vertical Spacing** move the controls on the surface, but do not mark the form changed and do not make an undo step. Ctrl+Z does not undo them. If you close the tab, the IDE asks no question, and the form opens again with the old positions. The new positions are saved only when a later real change follows, for example moving a control with an arrow key, because that change saves the whole surface. After you use these commands, make such a change before you save, and check the positions in [PROPERTIES](Properties). To go back, type the old **Left** or **Top** values there. ## Center in the container **Center In Container (Horizontally)** and **Center In Container (Vertically)** move the selection as a group, so that the group's bounding box is centred in the container the controls share. The controls keep their positions relative to one another. They work for one control or for many, and need all of them to have the same parent. If they do not, nothing moves and a notification says so. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > **Center In Container** reads the width of the container as it is drawn, so at a zoom other than 100 % it moves the controls to the wrong place. At 50 %, a text box in a frame ended at **Left** -9.98 instead of 40, and the group on the form moved by -80 instead of 20. Set the zoom to 100 % before you use the command. ## Bring to front and send to back diff --git a/docs/IDE/Forms/Controls.md b/docs/IDE/Forms/Controls.md index aa8e03dd3..a081fcaca 100644 --- a/docs/IDE/Forms/Controls.md +++ b/docs/IDE/Forms/Controls.md @@ -124,7 +124,7 @@ There are three ways to make an array: The last two give the new controls the highest **Index** plus one, and turn a lone original with **Index** -1 into index 0. Choosing an event of a member in the [EVENTS tab](Properties#events) inserts a handler with an `Index As Integer` argument, such as `Private Sub Command2_GotFocus(Index As Integer)`. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > The box that asks about the control array comes twice when you give a control the name of a single control. The first **YES** sets the other control's **Index** to 0 and leaves the renamed control's name unchanged, and the same box comes again. The second **YES** renames the control to `X(1)`. Answer **YES** both times. **NO** puts the old name back. ## Names and code diff --git a/docs/IDE/Forms/Designer.md b/docs/IDE/Forms/Designer.md index 9d01e3826..374be4c43 100644 --- a/docs/IDE/Forms/Designer.md +++ b/docs/IDE/Forms/Designer.md @@ -40,7 +40,7 @@ The new files go into the folder that the Project Explorer has selected. The Pro Enter creates both files at once and opens the designer in a normal tab, not a preview tab. Esc, or an empty name, creates nothing. A name with a space is written in square brackets, such as `Class [My Form]`, both in the class and in the form's **Name** property. A Windows form, an MDI form and a report are bracketed for a punctuation mark too, such as the dot in `a.b`. A user control, a property page and a CustomControls form are bracketed for a space only. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > The name box accepts a name that is not an identifier, such as `1bad`, and creates the files without a message. The class is then `Class 1bad`. What the new files hold depends on the kind: @@ -82,10 +82,10 @@ A file that the designer cannot use opens in one of three ways: - A file with a JSON error shows the notification *ERROR: bad JSON encountered when parsing tbform*. - A file without a `FormDesignerId` opens normally. The designer makes a new id for the session and does not write it to the file. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > A read-only designer file still gets a tab, which is empty and selected. Close it. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > A form file with a JSON error opens an empty designer with no controls, and PROPERTIES is empty. An edit there is refused, and Ctrl+S leaves the file as it was, so nothing is lost. Correct the JSON in a text editor outside the IDE, and open the form again. ### The two files of a form @@ -110,7 +110,7 @@ From the code file, **View → Object Designer** and Shift+F7 [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > A folder whose name contains `.twin`, such as `v1.twin.forms`, breaks the switch from the code to the designer, and the notification above appears. The switch from the designer to the code still works. Open the designer from the Project Explorer, or rename the folder. ## The parts of the window @@ -177,10 +177,10 @@ With a designer focused, the **View** menu shows **Zoom In** and **Zoom Out** in ![frmMain in the designer at 50 %, drawn at half size with its grid hidden; the readout under it still gives 380 x 270.](Images/Designer_Zoom50.png){:width="197" height="173"} -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > **Zoom Out** stops at 50 %, although the box offers 25 %. Choose 25 % in the box. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > The arrow keys also move the selected controls while the zoom box has the focus. With a control selected, Up pressed twice there changes the zoom by two steps and moves the control up by two steps. The form is marked changed, each press is an undo step, and a save keeps the new position. Nothing on the screen says the control moved. Check the positions of the controls before you save. ## Lock controls @@ -193,7 +193,7 @@ The lock is stored under the control's name, so it follows a rename, and it cove ![A form with Show Outlines on, so that a dashed outline surrounds every control. The command button Command1 is selected and locked: a grey lock icon shows on it and its grabbers are grey. The other buttons are unlocked.](Images/Designer_Locked.png){:width="387" height="333"} -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > Locking or unlocking controls does not mark the form changed. If you lock a control and close the tab, the IDE asks no question, and the lock is gone when you open the form again. The lock is saved only with the next real change. After you lock a control, make another change to the form, such as moving a control, before you save. ## Tab order @@ -216,10 +216,10 @@ A designer tab shows the dot for unsaved changes as soon as the form changes. [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > The tooltip of a form's entry in **HISTORY** ends with `[line: undefined]`, because a form change has no line. Nothing is lost. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > A designer can be marked changed with no edit. Click a control, press Esc to select the form, and then click another control or only move the pointer over the form: the tab gets the dot, and closing asks to save. It does not happen every time. Every control is where it was, so saving or discarding gives the same form. Ctrl+Z then steps through two empty steps before it reaches your last real change. ## See Also diff --git a/docs/IDE/Forms/Properties.md b/docs/IDE/Forms/Properties.md index 867a5e2a3..daa1e697d 100644 --- a/docs/IDE/Forms/Properties.md +++ b/docs/IDE/Forms/Properties.md @@ -70,7 +70,7 @@ A property has the editor that suits its type. A value is committed as the table If the compiler is busy when you open a property page, the IDE says *Backend compiler seems to be busy. Check there isn't a modal dialog box open behind the IDE window.* -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > Ctrl+A does nothing in a property box. It does not select the text, and the selection of the controls on the form is unchanged. Text that you type is added to the text already in the box. ### Colours @@ -125,7 +125,7 @@ For example, a button at **Left** 10 and **Top** 50 pixels, 100 pixels wide and A value you type is taken in the same unit. Typing **Left** 450 on that form moves the button to 30 pixels. A font size, **Opacity**, **DrawWidth** and other properties that are not positions show as they are. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > The pane shows **Left**, **Top**, **Width** and **Height** of a control on a CustomControls form multiplied by 15, as if the form were in twips, but takes the value you type as pixels. A button at **Left** 20 pixels shows 300. Typing 40 moves it to 40 pixels, and the box then shows 600. Do not type the value the pane shows. Type pixels, as the readouts on the surface show them. ## See Also diff --git a/docs/IDE/Forms/Toolbox.md b/docs/IDE/Forms/Toolbox.md index 05aa5cbf3..7e6522116 100644 --- a/docs/IDE/Forms/Toolbox.md +++ b/docs/IDE/Forms/Toolbox.md @@ -52,7 +52,7 @@ The magnifier in the title bar opens a search box under it. Typing a printable c ![The Toolbox with its search box holding Command, Text, beside three tool icons, the pointer, a text box and a command button, and More components below them. The menu of the ... button is open at the right, holding Show Control Names.](Images/Toolbox_Search.png){:width="377" height="129"} -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > A **floating** Toolbox lists every tool when a report is open, 24 controls and **More components**, where the docked Toolbox lists seven. A tool that a report does not allow is refused when you use it, so nothing is lost. Dock the Toolbox, or choose only the seven tools that a report allows. ## Add ActiveX controls and components diff --git a/docs/IDE/Menu/Format.md b/docs/IDE/Menu/Format.md index 4ab76b2e9..8a4dcb3c5 100644 --- a/docs/IDE/Menu/Format.md +++ b/docs/IDE/Menu/Format.md @@ -16,7 +16,7 @@ The Format menu holds the commands that line up and order the controls selected Every command acts on the selection in a form designer, so every command is greyed out until a designer is open with a selection. The commands need different numbers of controls: **Align**, **Make Same Size** and the two spacing submenus need two or more, **Make Equal** three or more, and the other commands one or more. The same commands are in the designer's [right-click menu](../../Forms/Controls#the-right-click-menu). -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > The spacing commands and **Make Same Size** can leave the new positions or sizes out of the saved form. [Arranging controls](../../Forms/Arranging) says what to do instead. ![The Format menu with no control selected, every command greyed out.](Images/Menu_Format.png){:width="233" height="281"} diff --git a/docs/IDE/Projects/Adding-Files.md b/docs/IDE/Projects/Adding-Files.md index 1fbb0767c..bd14252e0 100644 --- a/docs/IDE/Projects/Adding-Files.md +++ b/docs/IDE/Projects/Adding-Files.md @@ -67,7 +67,7 @@ The new file goes into the selected folder, or into the folder of the selected f **Add Folder** and **Add Other File** are not moved to `Sources`: a file made with **Add Other File** while a file in `Resources/CUSTOM` is selected is made in `Resources/CUSTOM`. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > **Add Other File**, and **Paste**, in the `Packages` folder create nothing. The notification says *cannot create files inside the root resources folder*, which names the wrong folder. A file that was cut and then pasted there is deleted: see [Cut, copy and paste](Files-and-Folders#cut-copy-and-paste). ## What the new files hold @@ -102,7 +102,7 @@ The `.twin` files are written as UTF-8 with Windows line endings. The `.bas` and A file with any extension but the three below is copied into the project unchanged. It goes into the selected folder, or the folder of the selected file, or `Sources` if nothing is selected. The IDE does not ask when a file of the same name is there already: the new file replaces it. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > Importing a file with the name of a file already in the project crashes the compiler, whether or not the file is open. The Debug Console prints `NATIVE EXCEPTION: ACCESS_VIOLATION`, and the IDE restarts the compiler. When the file is open in a tab, the project then holds the imported text, but the tab keeps the old text. If you edit the tab and press Ctrl+S, the old text with your edit is saved over the import. Close the tab of a file before you import a file with its name. ### Projects and packages @@ -115,7 +115,7 @@ The tree shows the project's folders and files but not its `Packages`, `Settings **Import** writes each ticked file at the same path in the current project, and makes the folders for it. A file already at that path is replaced with no question. If the import fails, a box says **Import failed. Please check the DEBUG CONSOLE for details of the errors that occurred.** -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > Importing a project's files replaces a file of the same path without a question, and closes the tab open on it first. Unsaved changes in that tab are lost, and so is the saved copy of the file, because the import also saves the project. Do not tick a file whose path is already in your project. This is for taking files from another project. To open a whole VB6 project as a new twinBASIC project, use **File → Open Project...** on its `.vbp`, or the **Import from VBP...** and **Import from folder...** tiles of the [New Project](../Project/New) dialog. The [FAQ](../../../FAQ#vb6-import) describes the VB6 import. A package is added as a package in [Project Settings](References-and-Packages#available-packages), not here. @@ -124,7 +124,7 @@ This is for taking files from another project. To open a whole VB6 project as a When every file of the command is done, the IDE saves the project, as if you had pressed Ctrl+S. For a project with no file yet, the **Save As** dialog opens instead. Cancelling it leaves the file imported and the project unsaved. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > An import saves the project, with the unsaved changes of every open editor, although you did not ask for a save. **Cancel** in the **Import from twinproj...** dialog does the same, and then the IDE restarts its compiler. ## Resources diff --git a/docs/IDE/Projects/Files-and-Folders.md b/docs/IDE/Projects/Files-and-Folders.md index 9924a82fb..05f68d762 100644 --- a/docs/IDE/Projects/Files-and-Folders.md +++ b/docs/IDE/Projects/Files-and-Folders.md @@ -66,7 +66,7 @@ A name is refused, with a notification in the corner of the window, when it: The notification reads **Cannot rename: the new name entered is invalid.** The seven special folders, `Settings`, `Sources`, `Resources`, `References`, `Packages`, `Miscellaneous` and `ImportedTypeLibraries`, cannot be renamed at all: the notification is **Cannot rename: permission denied.** -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > A rename that changes only the case of a name, or nothing, is refused with the same message as an invalid name, so `legacy.bas` cannot be changed to `Legacy.bas` in the tree. If the file is open in a tab with changes you have not saved, the IDE refuses the rename and shows a box, **Rename/Move failed. Please close the following editors that are preventing the operation:**, with the paths of the files. The same box appears for a folder that holds such a file. Save the file, or close its tab, and rename again. A tab on a file with no unsaved changes does not stop the rename. The tab closes, and the IDE does not open it again under the new name. Bookmarks move with the file. @@ -81,7 +81,7 @@ A form's designer file and its `.twin` file belong together. After you rename on The top row of the tree is the project. Renaming it changes the **Project Name** setting in the `Settings` file. The row, the title bar and the `.twinproj` file keep their names. A name that is not a valid project name gives **Cannot rename the project: the new name entered is invalid.** -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > Renaming the project also saves it, with the unsaved edits of the open editors, and the title bar's changed mark goes. ## Move a file or folder by dragging @@ -93,10 +93,10 @@ Drag a row, or the selected rows, onto a folder. The folder under the pointer is - The `Resources` folder accepts a dropped file, although **Paste** refuses to put one there. - A file dragged in from Windows is not accepted. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > Dragging a folder onto one of its own files, or onto one of its own subfolders, is accepted with no message. The tree then nests the folder inside itself, about fifty levels deep. The next Ctrl+S prints the green `[DONE]` line in the Debug Console, and then an error, and it writes a project file that the IDE cannot open: opening it stops at *Please wait... 25.0%*. Never drop a folder onto anything inside it. If you did, do not save. Close the project with **Discard Changes** in the question that follows, and open it again. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > Nothing stops a drag of a special folder. Dragging `Sources` onto `Miscellaneous` moves it there with no message, and the next save keeps it there. Do not drag the special folders. A drop of a file on `Packages` does nothing. ## Cut, copy and paste @@ -109,10 +109,10 @@ Drag a row, or the selected rows, onto a folder. The folder under the pointer is - The menu greys out **Cut** for a read-only row, such as a file of a package. Ctrl+X on a `References` entry still puts a cut on the clipboard. - The menu greys out **Cut** and **Copy** for a folder. Ctrl+C on a folder still puts its path on the clipboard. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > A file that you cut and paste onto the `Resources` folder itself, or onto `Packages`, is deleted. The IDE shows the notification *cannot create files inside the root resources folder*, which does not say so. The file is gone from the whole tree, and the next save writes the project without it. Do not cut a file into either folder. If a file went, close the project with **Discard Changes** before you save, and the saved project still has it. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > Pasting a file with no extension into a folder whose own name contains a period, such as `v1.2`, creates nothing and gives no message, when a file of that name is in the folder already. The IDE takes the file's extension from the last period of the whole path. ## Export a file @@ -155,13 +155,13 @@ With the option off, the rows are deleted at once. Some folders cannot be deleted. For `Sources`, `References`, `Miscellaneous`, `ImportedTypeLibraries` and the compiler packages under `Packages`, the box is shown, but nothing is deleted and no message follows. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > A file with unsaved changes in an open tab is deleted without a word about the changes. The box is the plain one, the tab closes, and the changes go with the file. A folder that holds such a file is the same. Look for the dot on a tab before you delete its file. The saved copy stays in the `.twinproj` file until the next save, so closing the project with **Discard Changes** brings the file back. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > Deleting the project's own row, the top row of the tree, deletes every file in the project. The box reads like a file's: *Are you sure you want to delete* the project's path. After you confirm, the tree is empty, and the only message is an *IDE internal error* line in the Debug Console. The next Ctrl+S prints the green `[DONE]` line and writes a project with no files over the project file. Never delete the top row. If you did, do not save: close the project with **Discard Changes** and open it again. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > Delete with nothing selected shows the box **Are you sure you want to delete the selected items?**, and deletes nothing. Choose **Cancel**. Deleting an entry under `References` removes its row from the tree, but not the reference: it stays in the project's settings. Remove a reference in [Project Settings](References-and-Packages#the-enabled-libraries-list). ## See Also diff --git a/docs/IDE/Projects/Project-Explorer.md b/docs/IDE/Projects/Project-Explorer.md index 9fe3db4d5..f028de4cd 100644 --- a/docs/IDE/Projects/Project-Explorer.md +++ b/docs/IDE/Projects/Project-Explorer.md @@ -100,7 +100,7 @@ These keys work while the tree has the focus. The tree scrolls to keep the selected row in view. Page Up, Page Down, Home and End do not move the selection. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > Several keys of the tree do not work as they should. > > - Space does not open or close a folder. Click the folder's plus or minus box. @@ -147,7 +147,7 @@ The **Search** button, or the first letter you type with a row selected, opens a ![The Project Explorer in its object view, with the search bar open at the top right of the list and Util typed in it. Under MyProject the Modules group lists only Util, beside its file Util.twin, and the Classes and Forms groups list nothing. The VBA, VBRUN and VB packages below are drawn open, and every group shows its full count.](Images/ObjectView_Search.png){:width="267" height="478"} -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > The object view has some defects. > > - A right click on a row opens no menu, so the menu's **View Object**, **View Code**, **Add**, **Rename** and **Delete** cannot be reached. To rename or delete an object's file, press Ctrl+R to return to the file view. diff --git a/docs/IDE/Projects/References-and-Packages.md b/docs/IDE/Projects/References-and-Packages.md index 9fe7bd720..be42abbef 100644 --- a/docs/IDE/Projects/References-and-Packages.md +++ b/docs/IDE/Projects/References-and-Packages.md @@ -109,13 +109,13 @@ The **Import from file...** button under the lists opens the Windows **Open** di - On **Available COM References**, and on **Enabled Libraries**, it asks for a type library. The IDE copies the file into the project as `ImportedTypeLibraries/`, and adds it to the references at once, ticked. - On **Available Packages**, it asks for a `.twinpack` file. The package is added to the **Available Packages** list. [Importing a Package from a TWINPACK File](../../../Features/Packages/Importing-TWINPACK) gives the steps. -> [!NOTE] BETA 1033 +> [!NOTE] BETA 1034 > A package imported with **Import from file...** is listed but not ticked, so the project does not use it. Tick it yourself after the import. > [!NOTE] BETA 1033 > **Import from file...** puts a copy of the package under the project's `Packages` folder. Closing the dialog with **Cancel** leaves the copy there, and the project is not marked as changed. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > A type library added with **Import from file...** is drawn on the **Enabled Libraries** list as `[LINKED-PACKAGE]`, and on **Available COM References** it is listed twice, once from the project and once from the registered library, both ticked. Clearing the tick of either row removes the reference. ## Package needed @@ -124,7 +124,7 @@ Choosing an **Add** item for a Windows form without the **WinNativeForms** packa The reference is listed under the package's own name, `WinNativeForms` or `CustomControlsPackage`, until the compiler has answered, and then under the name of the library, such as `twinBASIC - VB Compatibility Package (Forms)`. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > In a project that has a file, the button also saves the project, although the box speaks only of restarting the compiler. A project with no file yet is not saved, and restarts from memory. ## Apply Changes and closing the dialog @@ -137,7 +137,7 @@ The foot of the dialog has, at the right, the number of settings changed, such a ![Project Settings on its General group, with a word added to its Project Description, and the box that its title bar's close button opened over it: WARNING: you made changes but haven't saved them. Close without saving?, with Yes, close and No, keep editing. The foot of the dialog behind it reads 2 changed, beside Apply Changes and Cancel.](Images/Settings_Close.png){:width="1202" height="838"} -> [!WARNING] BETA 1033 +> [!WARNING] BETA 1034 > The close button of the title bar does not ask when the only change is to the references. The dialog closes, the change is dropped, and nothing says so. The code that compares the settings leaves out the compiler warnings as well. Press **Apply Changes** before you close the dialog. ## The References folder @@ -146,7 +146,7 @@ The `References` folder of the Project Explorer has one entry for each library t **Cut**, **Rename** and **Delete Permanently** are greyed out on the right-click menu for these entries. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > The Delete key removes a `References` entry from the tree, although its menu item is greyed out. The reference itself stays in the project. To remove a reference, untick it in the **Enabled Libraries** list. ## Where the project keeps them diff --git a/docs/IDE/Projects/Saving-and-Exporting.md b/docs/IDE/Projects/Saving-and-Exporting.md index 01b2aeb81..05529394a 100644 --- a/docs/IDE/Projects/Saving-and-Exporting.md +++ b/docs/IDE/Projects/Saving-and-Exporting.md @@ -52,7 +52,7 @@ The compiler can also ask the IDE for another path: a box, **Failed to save the Some commands save the project besides **Save Project**: **Save Project As** saves after it chooses the path, and **Apply Changes** in [Project Settings](References-and-Packages#apply-changes-and-closing-the-dialog) saves when the compiler restarts. With the setting [Export After Save](../Project-Settings#export-after-save), a save runs **Export Project** first. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > Several actions save the project although you did not ask. They are [**Import...**](Adding-Files#the-project-is-saved) of ordinary files; the **Import** and **Cancel** buttons of the **Import from twinproj...** dialog; [renaming the project](Files-and-Folders#the-project); and the button of a [Package needed](References-and-Packages#package-needed) box in a project that has a file. Importing ordinary files, **Cancel** in the import dialog and renaming the project write the unsaved changes of the open editors into the file as well. ## Save Project As @@ -120,7 +120,7 @@ To write only some of the files, use [**Export...**](Files-and-Folders#export-a- With [*Export After Save*](../Project-Settings#export-after-save) set, every save runs the export again before it writes the project, into *Export Path*, and empties that folder again. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When the `Settings` file has an *Export Path* but no `project.exportPathIsV2` entry, the project's name is joined to the path with no separator: `${SourcePath}\exp2` exports into `exp2ProjTry\` for a project named `ProjTry`. ### When a file cannot be deleted diff --git a/docs/IDE/Reports/index.md b/docs/IDE/Reports/index.md index 76653f303..3a1f336e1 100644 --- a/docs/IDE/Reports/index.md +++ b/docs/IDE/Reports/index.md @@ -65,7 +65,7 @@ A report holds windowless controls only. Its Toolbox lists seven tools: **Pointe A control belongs to the section where you draw it. A double-click on a tool places the control in the current section, and a control that you paste goes into the section that is shown. Drawing or pasting any other control gives the notification *Can't add* the control's type *to a report. Only windowless controls allowed.*, and adds nothing. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > A floating Toolbox lists all 24 controls and **More components** when a report is open. A control that a report does not allow is refused when you use it, so nothing is lost. Dock the Toolbox, or use only the seven tools above. A report has no **Form Menu** bar and no control arrays, and its controls have no **Index** property. The other designer features work: selection, moving and sizing, the grid, copy and paste, locks, undo, zoom, the right-click menu and the [Format](../Forms/Arranging) commands. diff --git a/docs/LLVM/Getting-Started.md b/docs/LLVM/Getting-Started.md index f9dc8d5eb..886311cb7 100644 --- a/docs/LLVM/Getting-Started.md +++ b/docs/LLVM/Getting-Started.md @@ -57,29 +57,23 @@ Building with LLVM does not currently work on Windows 7; Windows 10 or 11 is rec The main feature not yet supported is passing an error up to the calling procedure. A fix is planned. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > An error that occurs in a procedure with no error handler is not passed to its caller when either of the two procedures is compiled with LLVM: the caller's own `On Error` statement does not catch it. A run in the IDE ends without a message. A built program stops as it does for an unhandled error when only the caller is compiled with LLVM. When the procedure that has no handler is compiled with LLVM, the program shows a box that names the procedure but no error, and then ends with an access violation. Without LLVM, and in VB6, the caller's handler catches the error. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > An error handler in a procedure compiled with LLVM cannot read an error raised with [**Err.Raise**](../tB/Modules/ErrObject/Raise) in that procedure: **Err.Number** returns -353703423 (`&HEAEAEA01`), **Err.Source** is empty, and **Err.Description** is the generic "Application-defined or object-defined error". An error that a statement causes, such as a division by zero, is read correctly. -> [!WARNING] BETA 1010 -> A **Double** addition, subtraction, multiplication or exponentiation (`^`) that overflows raises no error in code compiled with LLVM: the result of the first three is infinity (`1.#INF` or `-1.#INF`) and the result of `^` is 0, where the same code compiled without LLVM raises error 6, *Overflow*, as VB6 does. [**FloatingPointErrorChecks(True)**](../tB/Core/Attributes#floatingpointerrorchecks) does not change this. A **Double** division that overflows, and a division by zero, still raise their errors. After either, under `On Error Resume Next`, the variable that was to receive the result keeps its old value; without LLVM it holds infinity. +> [!WARNING] BETA 1034 +> An exponentiation (`^`) that overflows raises no error in code compiled with LLVM, and its result is 0, where the same code compiled without LLVM raises error 6, *Overflow*, as VB6 does. In a 32-bit build, a **Double** addition, subtraction or multiplication that overflows raises no error either, and its result is infinity (`1.#INF` or `-1.#INF`), unless the procedure is compiled for SSE2: with `+sse2`, or with the flag of an instruction set that includes it, `+sse3`, `+ssse3`, `+sse4.1`, `+sse4.2`, `+avx`, `+avx2`, `+fma`, `+aes` or `+sha`. [**FloatingPointErrorChecks(True)**](../tB/Core/Attributes#floatingpointerrorchecks) does not change this. A division that overflows and a division by zero raise their errors, and so do an addition, subtraction and multiplication in a 64-bit build or compiled for SSE2. After any of these errors, under `On Error Resume Next`, the variable that was to receive the result keeps its old value; without LLVM it holds infinity. -> [!WARNING] BETA 1010 -> Code compiled with LLVM rounds a **Single** or **Double** that is exactly halfway between two integers away from zero when it converts the value to an integer type: in [**CLng**](../tB/Modules/Conversion/CLng), **CInt** and **CLngLng** of a variable, in an assignment to an **Integer** or **Long**, and in the operands of [**\\**](../tB/Core/IntegerDivide). With a **Double** variable holding 2.5, `CLng(x)` is 3, and with -0.5 it is -1. Without LLVM the value is rounded to the even integer, 2 and 0, as VB6 does. A constant expression such as `CLng(2.5)`, a **Variant** argument, and [**Round**](../tB/Modules/Math/Round), **Int** and **Fix** are not affected. - -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > A program built with LLVM ends with an access violation (0xC0000005) and no message box at the first [**GoSub**](../tB/Core/GoSub-Return) it runs, in a 32-bit and in a 64-bit build. A run in the IDE is not affected, and neither is a program built without LLVM. Replace the **GoSub** with a call to a **Private Sub**. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > In a 64-bit build with LLVM, a call to a [**Declare**](../tB/Core/Declare)d procedure that passes `ByVal 0&` for its eighth parameter, declared `ByRef` with a 32-bit type such as **Long**, to give the procedure a NULL pointer, can end with an access violation (0xC0000005) and no message box, in the procedure. The eighth parameter of **WideCharToMultiByte** is one such case. Whether the call fails depends on what the stack held before it. The same override for the first to seventh parameters is right. Declare the parameter `ByVal ... As LongPtr` instead. It affects a built exe and a run in the IDE with LLVM turned on, and not a 32-bit build or a build without LLVM. -> [!WARNING] BETA 1010 -> A call that passes an argument with `ByVal` for a `ByRef` parameter of a twinBASIC [**Sub**](../tB/Core/Sub) or [**Function**](../tB/Core/Function), such as `Take ByVal 0&` for `Sub Take(ByRef h As Long)`, does not compile with LLVM, in a 32-bit and in a 64-bit build. The build prints `[LLVM] codegen error occurred when compiling '': Call parameter type does not match function signature!`, still makes the program, and the procedure that contains the call does not run: a 64-bit program ends with an access violation (0xC0000005) when it enters that procedure, and a 32-bit program skips the procedure and can fault later. Without LLVM the call passes a NULL pointer. VB6 refuses the call with the error *Type mismatch*. Pass a variable for the parameter instead. - -> [!WARNING] BETA 1010 -> In a procedure that has a `ByVal` **Long** or **Single** parameter and, after it, a `ByRef` parameter declared `As Boolean` or `As Integer`, a [**For...Next**](../tB/Core/For-Next) loop whose **Step** is that `ByVal` parameter (or a variable set from it) runs with a wrong step, or with a wrong start or end when those are parameters too. The wrong value depends on where the caller's argument for the `ByRef` parameter is stored: 20 in a 64-bit build and 25 in a 32-bit build when it is a local variable of the caller, other values when it is a module-level variable or an array element. `For i = 1 To 7 Step x` with `x` equal to 3 and a local variable passed runs once, not three times. No error is raised, so the loop silently gives a wrong result. It affects a **Sub**, a **Function** and a class method, a built exe and a run in the IDE with LLVM turned on, in a 32-bit and in a 64-bit build, and not a build without LLVM. Whether a larger procedure shows it can depend on its other statements. A second parameter that is `ByRef ... As Long` (or **Byte**, **Single**, **Double**, **Currency**, **Date**, **String**, **Variant**, an Enum, a UDT, an array, an object or a pointer), `ByVal ... As Boolean`, a `ByRef ... As Boolean` placed before the step parameter, a step parameter of type **Integer**, **Double** or **Currency**, a `Do` loop, a loop with no **Step** and a loop with a literal **Step** are right. Declare the second parameter `ByVal`, or place it before the step parameter. +> [!WARNING] BETA 1034 +> In a procedure that has a `ByVal` **Long** or **Single** parameter and, after it, a `ByRef` parameter declared `As Boolean` or `As Integer`, a [**For...Next**](../tB/Core/For-Next) loop whose **Step** is that `ByVal` parameter (or a variable set from it) runs with a wrong step, or with a wrong start or end when those are parameters too. The wrong value depends on where the caller's argument for the `ByRef` parameter is stored. In a built exe it is 20 in a 64-bit build and 25 in a 32-bit build when the argument is a local variable of the caller, and other values when it is a module-level variable or an array element; in a run in the IDE it varies more, up to millions. `For i = 1 To 7 Step x` with `x` equal to 3 and a local variable passed runs once, not three times. No error is raised, so the loop silently gives a wrong result. It affects a **Sub**, a **Function** and a class method, a built exe and a run in the IDE with LLVM turned on, in a 32-bit and in a 64-bit build, and not a build without LLVM. Whether a larger procedure shows it can depend on its other statements. A second parameter that is `ByRef ... As Long` (or **Byte**, **Single**, **Double**, **Currency**, **Date**, **String**, **Variant**, an Enum, a UDT, an array, an object or a pointer), `ByVal ... As Boolean`, a `ByRef ... As Boolean` placed before the step parameter, a step parameter of type **Integer**, **Double** or **Currency**, a `Do` loop, a loop with no **Step** and a loop with a literal **Step** are right. Declare the second parameter `ByVal`, or place it before the step parameter. Code compiled with LLVM ignores [**Debug.Assert**](../tB/Modules/Debug#assert): the statement does not stop, and its condition is not evaluated. @@ -111,7 +105,7 @@ Public Sub DoNotOptimizeMe() End Sub ``` -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > A run in the IDE with LLVM turned on for the whole project under **Compiler Options (DEBUG)** ends with an access violation when a procedure marked `[CompilerOptions("")]` calls a procedure that is compiled with LLVM, and `Sub Main` reaches the marked procedure: it calls it, or calls a procedure that does. A marked procedure that `Sub Main` does not reach is not affected, and neither is a built program. The same attribute can also turn LLVM on for chosen procedures when it is off in Project Settings. List the flags, separated by commas: `+llvm`, `+optimize` and `+optimizesize`, then `+` and the name of each CPU instruction set to use. Each flag is the direct equivalent of an option in Project Settings: diff --git a/docs/Reference/Attributes.md b/docs/Reference/Attributes.md index 99e169a25..0d3cc567f 100644 --- a/docs/Reference/Attributes.md +++ b/docs/Reference/Attributes.md @@ -294,7 +294,7 @@ The restriction to modules is not an oversight. Folding a call to a method would Calls are folded only when **Constant Function Folding** is set to **Yes** in [Project Settings](../IDE/Project-Settings#constant-function-folding). A **Property Get** in a module folds as a function does. On a **Sub** the attribute is accepted and has no effect. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > The compiler also accepts the attribute on a **Declare Function**. With **Constant Function Folding** on, a call to such a function whose arguments are all constants crashes the compiler during the build, though the project compiles without an error. + ## RunBeforeStartupObject {: #runbeforestartupobject } @@ -1249,10 +1262,10 @@ A procedure with the attribute in a package that the project references runs too the project called nothing in the package. The exe printed the package's line, then Main's. --> -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > The attribute is not allowed on a generic procedure, such as `Sub Pre(Of T)()`. The project compiles in the editor without an error, but building it fails with the message *Build FAILED due to invalid RunBeforeStartupObject procedure*, which names neither the procedure nor the reason. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > The compiler accepts the attribute on more than one procedure, with no error or warning, and the built program runs only the last of them; the others never run. It also accepts the attribute on a procedure with parameters, which then runs with arbitrary values for its parameters, even for an **Optional** parameter that has a default. Give the attribute to one procedure, with no parameters. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > 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] diff --git a/docs/Reference/Core/Call.md b/docs/Reference/Core/Call.md index 9ddb32f0a..8a21e5c1a 100644 --- a/docs/Reference/Core/Call.md +++ b/docs/Reference/Core/Call.md @@ -32,8 +32,22 @@ To pass a whole array to a procedure, use the array name followed by empty paren Enclosing a single argument in its own parentheses, as in `MyProc (x)`, makes it an expression: the procedure receives a copy, even for a **ByRef** parameter. See [Passing arguments ByRef and ByVal](Sub#passing-arguments-byref-and-byval). -> [!WARNING] BETA 1010 -> With [LLVM](../../LLVM/Getting-Started), a call that passes an argument with `ByVal` for a `ByRef` parameter of a twinBASIC procedure, such as `Take ByVal 0&` for `Sub Take(ByRef h As Long)`, does not compile: the build prints an `[LLVM] codegen error`, and the procedure that contains the call does not run. Without LLVM the call passes a NULL pointer. VB6 refuses the call with the error *Type mismatch*. Pass a variable for the parameter instead. +A call can pass an argument with `ByVal` for a `ByRef` parameter of a twinBASIC procedure. The procedure then receives the value as the parameter's address, so `ByVal 0&` passes a NULL pointer: + +```tb check_build projname=call-byval-null +Public Function ArgumentAddress(ByRef h As Long) As LongPtr + ArgumentAddress = VarPtr(h) +End Function +``` + +```tb check_build check_run projname=call-byval-null +Dim n As Long +Debug.Print ArgumentAddress(n) <> 0 ' True +Debug.Print ArgumentAddress(ByVal 0&) ' 0 +``` + +> [!NOTE] +> VB6 refuses `ByVal` on an argument for a `ByRef` parameter of a procedure written in Basic, with the error *Type mismatch*. ### Example diff --git a/docs/Reference/Core/Const.md b/docs/Reference/Core/Const.md index 016fd7f57..4bb4ffffa 100644 --- a/docs/Reference/Core/Const.md +++ b/docs/Reference/Core/Const.md @@ -78,10 +78,10 @@ Debug.Print UBound(Primes) ' 4 Debug.Print Total ' 28 ``` -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > A module-level **Single** array constant whose element is too large for a **Single**, such as `Const Big() As Single = Array(1E+39)`, compiles without an error, and the element holds infinity. An array constant declared after it whose element calls **Sqr**, **Timer** or **Rnd** is then compile error **TB5001**, although it is correct. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > In a module, an array constant declared after a procedure is compile error **TB5079**, *Unrecognized symbol*, in the procedures that follow it. The procedures before it see an empty array: reading an element raises error 9. Declare array constants above the module's first procedure. > [!NOTE] diff --git a/docs/Reference/Core/Declare.md b/docs/Reference/Core/Declare.md index 0baaaf357..205f6ffc2 100644 --- a/docs/Reference/Core/Declare.md +++ b/docs/Reference/Core/Declare.md @@ -114,7 +114,7 @@ Declare Sub Second Lib "MyLib" () > - Fixed-length strings cannot appear in the argument list of a **Declare** statement; only variable-length strings can be passed to procedures. Fixed-length strings can appear as procedure arguments, but they are converted to variable-length strings before being passed. > - The **vbNullString** constant is used when calling external procedures, where the external procedure requires a string whose value is zero. This is not the same thing as a zero-length string (""). -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > In a 64-bit build with [LLVM](../../LLVM/Getting-Started), a call that passes `ByVal 0&` for the eighth parameter, declared `ByRef` with a 32-bit type such as **Long**, to give the procedure a NULL pointer, can end with an access violation (0xC0000005) and no message box. The eighth parameter of **WideCharToMultiByte** is one such case. Whether the call fails depends on what the stack held before it. The same override for the first to seventh parameters is right. Declare the parameter `ByVal ... As LongPtr` instead. A 32-bit build and a build without LLVM are not affected. ### Example diff --git a/docs/Reference/Core/Divide.md b/docs/Reference/Core/Divide.md index e42eb2546..1a416c0c1 100644 --- a/docs/Reference/Core/Divide.md +++ b/docs/Reference/Core/Divide.md @@ -36,7 +36,7 @@ A **Single** divided by a **Long**, **LongLong**, **Double**, **Currency** or ** Dividing by zero raises error 11, *Division by zero*, and zero divided by zero raises error 6, *Overflow*. Use [**\\**](IntegerDivide) for truncating-integer division and [**Mod**](Mod) for remainder. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > A division whose result is a declared **Single** raises no error when the result overflows: the **Single** holds infinity (`1.#INF`) instead. VB6 raises error 6, *Overflow*. Compiled with LLVM, the division raises error 6 as it should. Declare the variable **Double** where an overflow must be caught. > [!NOTE] diff --git a/docs/Reference/Core/Error.md b/docs/Reference/Core/Error.md index 2504c36bf..e4a8d7d4b 100644 --- a/docs/Reference/Core/Error.md +++ b/docs/Reference/Core/Error.md @@ -30,7 +30,7 @@ If *errornumber* is defined, the **Error** statement calls the error handler aft If no error handler exists or if none is enabled, an error message is created and displayed from the **Err** object properties. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > An error that a class member raises with the **Error** statement reaches a caller that called the member late-bound --- through an **Object** variable or [**CallByName**](../Modules/Interaction/CallByName) --- as error -2147352567 (`&H80020009`), *Exception occurred.*, not as *errornumber*. VB6 raises *errornumber*. An early-bound caller receives *errornumber* unchanged. ### Example diff --git a/docs/Reference/Core/For-Each-Next.md b/docs/Reference/Core/For-Each-Next.md index 276edc2ae..9f22d7fcb 100644 --- a/docs/Reference/Core/For-Each-Next.md +++ b/docs/Reference/Core/For-Each-Next.md @@ -50,7 +50,7 @@ The **For...Each...Next** statement cannot be used with an array of user-defined An object of a class can be the *group* when the class has a member marked [**[Enumerator]**](Attributes#enumerator) that supplies an enumerator. See [Custom Enumerators](../../Features/Language/Custom-Enumerators) for how to write one. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > A **For Each** that cannot get an enumerator from *group* never ends when it runs under `On Error Resume Next`, or under an error handler that ends with **Resume Next**. That happens when *group* is **Nothing**, an object of a class with no **[Enumerator]** member, or an object whose **[Enumerator]** member returns **Nothing** or raises an error. The error resumes into the loop body, and **Next** raises error 91 and goes back to the body, again and again. Test *group* before the loop. VB6 runs the body once, then raises error 92 at **Next**, and the loop ends. ### Example diff --git a/docs/Reference/Core/For-Next.md b/docs/Reference/Core/For-Next.md index cbe5c9e1e..ba39450ca 100644 --- a/docs/Reference/Core/For-Next.md +++ b/docs/Reference/Core/For-Next.md @@ -54,8 +54,8 @@ The *step* argument can be either positive or negative. The value of the *step* After all statements in the loop have executed, *step* is added to *counter*. At this point, either the statements in the loop execute again (based on the same test that caused the loop to execute initially), or the loop is exited and execution continues with the statement following the **Next** statement. -> [!WARNING] BETA 1010 -> With [LLVM](../../LLVM/Getting-Started), a loop whose *step* is a `ByVal` **Long** or **Single** parameter of the procedure runs with a wrong step, or a wrong *start* or *end* when those are parameters too, when a later parameter of the procedure is declared `ByRef ... As Boolean` or `ByRef ... As Integer`. The wrong value depends on where the caller's argument for that parameter is stored: 20 in a 64-bit build and 25 in a 32-bit build for a local variable. No error is raised. Declare that parameter `ByVal`, or place it before the *step* parameter. +> [!WARNING] BETA 1034 +> With [LLVM](../../LLVM/Getting-Started), a loop whose *step* is a `ByVal` **Long** or **Single** parameter of the procedure runs with a wrong step, or a wrong *start* or *end* when those are parameters too, when a later parameter of the procedure is declared `ByRef ... As Boolean` or `ByRef ... As Integer`. The wrong value depends on where the caller's argument for that parameter is stored: for a local variable, 20 in a 64-bit exe and 25 in a 32-bit exe, and other values in a run in the IDE. No error is raised. Declare that parameter `ByVal`, or place it before the *step* parameter. > [!TIP] > diff --git a/docs/Reference/Core/Function.md b/docs/Reference/Core/Function.md index f4003bd56..aa0631ddb 100644 --- a/docs/Reference/Core/Function.md +++ b/docs/Reference/Core/Function.md @@ -22,7 +22,7 @@ Syntax: *attributes* : One or more of: -[ArrayBoundsChecks](Attributes#arrayboundschecks), [BindOnlyIfNoArguments](Attributes#bindonlyifnoarguments), [BindOnlyIfStringSuffix](Attributes#bindonlyifstringsuffix), [CompileIf](Attributes#compileif), [ConstantFoldable](Attributes#constantfoldable), [ConstantFoldableNumericsOnly](Attributes#constantfoldablenumericsonly), [Debuggable](Attributes#debuggable), [DebugOnly](Attributes#debugonly), [Description](Attributes#description), [EnforceErrors](Attributes#enforceerrors), [EnforceWarnings](Attributes#enforcewarnings), [FloatingPointErrorChecks](Attributes#floatingpointerrorchecks), [IntegerOverflowChecks](Attributes#integeroverflowchecks), [MustBeQualified](Attributes#mustbequalified), [RunAfterBuild](Attributes#runafterbuild), [SimplerByVals](Attributes#simplerbyvals), [TestCase](Attributes#testcase), [Unimplemented](Attributes#unimplemented) +[ArrayBoundsChecks](Attributes#arrayboundschecks), [BindOnlyIfNoArguments](Attributes#bindonlyifnoarguments), [BindOnlyIfStringSuffix](Attributes#bindonlyifstringsuffix), [CompileIf](Attributes#compileif), [ConstantFoldable](Attributes#constantfoldable), [ConstantFoldableNumericsOnly](Attributes#constantfoldablenumericsonly), [Debuggable](Attributes#debuggable), [DebugOnly](Attributes#debugonly), [Description](Attributes#description), [EnforceErrors](Attributes#enforceerrors), [EnforceWarnings](Attributes#enforcewarnings), [FloatingPointErrorChecks](Attributes#floatingpointerrorchecks), [IntegerOverflowChecks](Attributes#integeroverflowchecks), [MustBeQualified](Attributes#mustbequalified), [SimplerByVals](Attributes#simplerbyvals), [TestCase](Attributes#testcase), [Unimplemented](Attributes#unimplemented) **Public** : *optional*. Indicates that the **Function** procedure is accessible to all other procedures in all modules. If used in a module that contains an **Option Private**, the procedure is not available outside the project. diff --git a/docs/Reference/Core/Get.md b/docs/Reference/Core/Get.md index 827aad946..b6f5f01ef 100644 --- a/docs/Reference/Core/Get.md +++ b/docs/Reference/Core/Get.md @@ -24,7 +24,7 @@ Syntax: *varname* : Valid variable name into which data is read. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > **Get** raises run-time error -2147467259 (`&H80004005`) for a *filenumber* that is not open, where VB6 raises 52 (*Bad file name or number*). Data read with **Get** is usually written to a file with [**Put**](Put). The first record or byte in a file is at position 1, the second record or byte is at position 2, and so on. When *recnumber* is omitted, the next record or byte following the last **Get** or **Put** statement (or pointed to by the last [**Seek**](../Modules/FileSystem/Seek) function) is read. The delimiting commas must be included: diff --git a/docs/Reference/Core/GoSub-Return.md b/docs/Reference/Core/GoSub-Return.md index f2b99f23b..a4f5a2d79 100644 --- a/docs/Reference/Core/GoSub-Return.md +++ b/docs/Reference/Core/GoSub-Return.md @@ -24,7 +24,7 @@ Use **GoSub** and **Return** anywhere in a procedure, but **GoSub** and the corr > [!NOTE] > **GoSub...Return** cannot enter or exit [**Sub**](Sub) procedures. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > A program built with [LLVM](../../LLVM/Getting-Started) ends with an access violation (0xC0000005) and no message box at the first **GoSub** it runs, in a 32-bit and in a 64-bit build. A program built without LLVM, and a run in the IDE, are not affected. > [!TIP] diff --git a/docs/Reference/Core/Graphics-Methods.md b/docs/Reference/Core/Graphics-Methods.md index 822119b71..9764f2ac4 100644 --- a/docs/Reference/Core/Graphics-Methods.md +++ b/docs/Reference/Core/Graphics-Methods.md @@ -121,10 +121,10 @@ The method must accept the whole argument list: six arguments for **Line**, eigh The plain call forms are not rewritten. `o.Line 99, 1, 2, 3, 4, 5` and `Call o.Line(99, 1, 2, 3, 4, 5)` pass their arguments through unchanged. A trailing comma is accepted after the second point of **Line** and after the point of **PSet**, but not after the radius of **Circle**, where it is error TB5075. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > A call of **Scale** or **Line** with a single point in parentheses, such as `o.Scale (2, 3)`, `o.Scale(2, 3)` or `o.Line (2, 3)`, compiles and does nothing: the method is not called and no error is raised. VB6 reports a syntax error. Write the call with **Call**, or without parentheses: `Call o.Scale(2, 3)` or `o.Scale 2, 3`. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > A call of **Scale** or **Line** with a single value in parentheses, such as `o.Scale(2)`, is error TB5212, because the compiler reads the graphics syntax from the method name. Write `Call o.Scale(2)` or `o.Scale 2`. > > ```tb check_build expect-error=TB5212 slot=file @@ -166,7 +166,7 @@ The built-in surfaces read *Color* as a **Long**. When *Color* is left out they The line of a **Line** and the fill of **BF** use the same rule, as does the circle of **Circle** and the point of **PSet**. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > System colours are read wrongly. VB6 takes the system colour index from the low 16 bits of the value, so that `&H80010003` draws system colour 3 and `&H80FF000F` draws system colour 15. twinBASIC draws both black, without raising an error, because it reads the value as a system colour only when it lies between `&H80000000` and `&H8000001E`. Pass a system colour as one of the `vb` constants, such as `vbButtonFace`, or as `&H800000nn` with nothing in the second and third bytes. > [!NOTE] diff --git a/docs/Reference/Core/LSet.md b/docs/Reference/Core/LSet.md index 7413bf77b..a6306a126 100644 --- a/docs/Reference/Core/LSet.md +++ b/docs/Reference/Core/LSet.md @@ -31,7 +31,21 @@ If *string* is longer than *stringvar*, **LSet** places only the leftmost charac **LSet** never changes the length of *stringvar*, so a *stringvar* of zero length stays empty. *stringvar* can be a variable-length string, a fixed-length string, or a **Variant** that holds a string. A *string* that is **Null** raises error 94. -> [!WARNING] BETA 1010 +A **Variant** *stringvar* that holds a number or a date is aligned within the text of its value, which becomes its new value: one that holds 12345 has five characters. One that holds **Null** raises error 94. + +```tb check_run +Dim v As Variant +v = 12345 +LSet v = "ab" +Debug.Print "[" & v & "]" ' [ab ] +``` + +> [!WARNING] BETA 1034 +> A **Variant** *stringvar* that holds a **Boolean** is aligned within `-1` or `0`, the text of its number, not within `True` or `False`: with `v = True`, `LSet v = "ab"` leaves `ab`, and with `v = False` it leaves `a`. VB6 leaves `ab ` and `ab `. **RSet** aligns within `True` and `False`. Assign the text to the **Variant** first, as `v = CStr(v)`. + +A **Variant** *stringvar* that holds an object raises error 450, *Wrong number of arguments or invalid property assignment*. + +> [!WARNING] BETA 1034 > **LSet** on an element of a **Variant** that holds an array, such as `LSet v(0) = "zz"` with `v = Array("0123456789", "abcde")` or a **Variant** that was given its bounds with **ReDim**, changes nothing and raises no error. VB6 changes the element. An element of an array declared `Dim w(0 To 1) As Variant` works. To change an element of a **Variant** array, copy it to a **String** variable, use **LSet** on the variable and assign the variable back. When copying between user-defined types, **LSet** copies as many bytes as the smaller of the two types holds. If *varname1* is larger than *varname2*, its bytes beyond the end of the source keep their old values. Both types must be free of variable-length **String**, **Variant**, **Object** and dynamic-array fields; the compiler refuses the statement otherwise (error TB5249). Fixed-length **String** fields are allowed. diff --git a/docs/Reference/Core/LeftShift.md b/docs/Reference/Core/LeftShift.md index c287fe610..64ce34a3b 100644 --- a/docs/Reference/Core/LeftShift.md +++ b/docs/Reference/Core/LeftShift.md @@ -41,7 +41,7 @@ An integer literal with no type suffix is an **Integer**, so `1 << 20` shifts a > > Convert such a value with [**CLng**](../Modules/Conversion/CLng) or [**CLngLng**](../Modules/Conversion/CLngLng) before shifting it. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > Code compiled with LLVM cuts the shift count to the size of the shifted **Byte** or **Integer** before shifting: for a **Byte**, a count of 256 becomes 0 and 257 becomes 1. So a **Byte** shifted by 256 is unchanged, where the result should be 0, as it is without LLVM. An **Integer** shifted by 65,536 or more behaves the same way. A **Long** or **LongLong** is not affected. ### Compound assignment diff --git a/docs/Reference/Core/Line-Input.md b/docs/Reference/Core/Line-Input.md index d12937b96..b984b3650 100644 --- a/docs/Reference/Core/Line-Input.md +++ b/docs/Reference/Core/Line-Input.md @@ -24,7 +24,7 @@ The **Line Input #** statement reads from a file one character at a time until i Reading past the last line of the file raises run-time error 62 (*Input past end of file*), and leaves *varname* unchanged. Test for the end of the file with [**EOF**](../Modules/FileSystem/EOF) before each read. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > **Line Input #** raises run-time error -2147467259 (`&H80004005`) for a *filenumber* that is not open, where VB6 raises 52 (*Bad file name or number*). ### Example diff --git a/docs/Reference/Core/Minus.md b/docs/Reference/Core/Minus.md index 3ff5ef32a..d6b9fa820 100644 --- a/docs/Reference/Core/Minus.md +++ b/docs/Reference/Core/Minus.md @@ -39,7 +39,7 @@ The data type of *result* is usually the same as that of the most precise expres A declared (non-**Variant**) result that overflows raises error 6, *Overflow*. If one or both expressions are **Null** expressions, *result* is **Null**. If an expression is **Empty**, it is treated as 0. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > A subtraction whose result is a declared **Single** raises no error when the result overflows: the **Single** holds infinity (`1.#INF`) instead. VB6 raises error 6, *Overflow*. The compound form **-=** does the same. Declare the variable **Double** where an overflow must be caught. > [!NOTE] diff --git a/docs/Reference/Core/Multiply.md b/docs/Reference/Core/Multiply.md index 4abdff31d..fd8b085da 100644 --- a/docs/Reference/Core/Multiply.md +++ b/docs/Reference/Core/Multiply.md @@ -35,7 +35,7 @@ The data type of *result* is usually the same as that of the most precise expres A declared (non-**Variant**) result that overflows raises error 6, *Overflow*. If one or both expressions are **Null** expressions, *result* is **Null**. If an expression is **Empty**, it is treated as 0. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > A multiplication whose result is a declared **Single** raises no error when the result overflows: the **Single** holds infinity (`1.#INF`) instead. VB6 raises error 6, *Overflow*. The compound form ***=** does the same. Declare the variable **Double** where an overflow must be caught. > [!NOTE] diff --git a/docs/Reference/Core/Name.md b/docs/Reference/Core/Name.md index bac866ec0..14c561007 100644 --- a/docs/Reference/Core/Name.md +++ b/docs/Reference/Core/Name.md @@ -22,7 +22,7 @@ The **Name** statement renames a file and moves it to a different directory or f Using **Name** on an open file produces an error. An open file must be closed before renaming it. **Name** arguments cannot include multiple-character (`*`) and single-character (`?`) wildcards. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > Each of these failures of **Name** raises run-time error -2147467259 (`&H80004005`), where VB6 raises the numbered error shown: > > - 53 (*File not found*) when *oldpathname* does not exist; diff --git a/docs/Reference/Core/On-Error.md b/docs/Reference/Core/On-Error.md index 882fcc37d..0f0c48b7a 100644 --- a/docs/Reference/Core/On-Error.md +++ b/docs/Reference/Core/On-Error.md @@ -25,7 +25,7 @@ Syntax: Without an **On Error** statement, any run-time error that occurs is fatal; that is, an error message is displayed and execution stops. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > In a built program, twinBASIC does not end the program after an unhandled error in an event procedure of a form, such as **Form_Load** or **Form_Activate**. It displays the error message, and once the message is closed the program goes on running: the form is shown and its later events run. VB6 ends the program. Until this is fixed, handle errors in a form's event procedures, and unload the form or end the program there when it cannot continue. An "enabled" error handler is one that is turned on by an **On Error** statement; an "active" error handler is an enabled handler that is in the process of handling an error. If an error occurs while an error handler is active (between the occurrence of the error and a [**Resume**](Resume), [**Exit Sub**](Exit), **Exit Function**, or **Exit Property** statement), the current procedure's error handler can't handle the error. Control returns to the calling procedure. diff --git a/docs/Reference/Core/Open.md b/docs/Reference/Core/Open.md index 2e6c614f2..4a9574f11 100644 --- a/docs/Reference/Core/Open.md +++ b/docs/Reference/Core/Open.md @@ -41,7 +41,7 @@ If the file specified by *pathname* doesn't exist, it is created when a file is If the file is already opened by another process, and the specified type of access is not allowed, the **Open** operation fails and an error occurs. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > Each of these failures of **Open** raises run-time error -2147467259 (`&H80004005`), where VB6 raises the numbered error shown: > > - 53 (*File not found*) for a file that does not exist, opened for **Input**; diff --git a/docs/Reference/Core/ParamArray.md b/docs/Reference/Core/ParamArray.md index 858afe863..bf98f9bc0 100644 --- a/docs/Reference/Core/ParamArray.md +++ b/docs/Reference/Core/ParamArray.md @@ -24,7 +24,7 @@ Syntax: When the procedure is called, each argument supplied in the call becomes a corresponding element of the **Variant** array. If no arguments are supplied for the **ParamArray** position, the array is empty. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When an argument is a **Variant** variable that holds an array, its element cannot be used as an array: `Args(0)(1)`, `Args(0)(1) = x`, and `b = Args(0)` where *b* is a dynamic array of any type raise run-time error -2147467259, *Unspecified error*, and so does passing `Args(0)` on to another **ParamArray** that indexes it; **Join** raises error 13. **LBound**, **UBound**, **IsArray**, **VarType** and **For Each** work on the element, and an argument that is an array variable or an expression is not affected. VB6 raises no error. Assign the element to a **Variant** first (`v = Args(0)`), or have the caller put the argument in parentheses, which passes a copy. > [!NOTE] diff --git a/docs/Reference/Core/Plus.md b/docs/Reference/Core/Plus.md index faebd0de8..352e4f054 100644 --- a/docs/Reference/Core/Plus.md +++ b/docs/Reference/Core/Plus.md @@ -58,7 +58,7 @@ For simple arithmetic addition involving only numeric expressions, the data type A declared (non-**Variant**) result that overflows raises error 6, *Overflow*. If one or both expressions are **Null** expressions, *result* is **Null**. If both expressions are **Empty**, *result* is an **Integer**. However, if only one expression is **Empty**, the other expression is returned unchanged as *result*. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > An addition whose result is a declared **Single** raises no error when the result overflows: the **Single** holds infinity (`1.#INF`) instead. VB6 raises error 6, *Overflow*. The compound form **+=** does the same. Declare the variable **Double** where an overflow must be caught. > [!NOTE] diff --git a/docs/Reference/Core/Print.md b/docs/Reference/Core/Print.md index 186053043..d2c95abf8 100644 --- a/docs/Reference/Core/Print.md +++ b/docs/Reference/Core/Print.md @@ -55,7 +55,7 @@ An expression that reaches the end of its zone pushes the next one into the zone *filenumber* : Any valid file number. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > **Print #** raises run-time error -2147467259 (`&H80004005`) for a *filenumber* that is not open, where VB6 raises 52 (*Bad file name or number*). When *outputlist* is omitted and only a list separator follows *filenumber*, a blank line is written. diff --git a/docs/Reference/Core/Put.md b/docs/Reference/Core/Put.md index 108c9fe9d..cd528ae23 100644 --- a/docs/Reference/Core/Put.md +++ b/docs/Reference/Core/Put.md @@ -21,7 +21,7 @@ Syntax: *varname* : Name of the variable containing data to be written to disk. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > **Put** raises run-time error -2147467259 (`&H80004005`) for a *filenumber* that is not open, where VB6 raises 52 (*Bad file name or number*). It raises the same error for data longer than the record length of a **Random** file, where VB6 raises 59 (*Bad record length*). Data written with **Put** is usually read from a file with [**Get**](Get). diff --git a/docs/Reference/Core/RSet.md b/docs/Reference/Core/RSet.md index b230993d1..f5611c345 100644 --- a/docs/Reference/Core/RSet.md +++ b/docs/Reference/Core/RSet.md @@ -24,7 +24,19 @@ If *string* is longer than *stringvar*, **RSet** places only the leftmost charac **RSet** never changes the length of *stringvar*, so a *stringvar* of zero length stays empty. *stringvar* can be a variable-length string, a fixed-length string, or a **Variant** that holds a string. A *string* that is **Null** raises error 94. -> [!WARNING] BETA 1010 +A **Variant** *stringvar* that holds a number, a date or a **Boolean** is aligned within the text of its value, which becomes its new value: one that holds **True** has the four characters of `True`. One that holds **Null** raises error 94. + +```tb check_run +Dim v As Variant +v = True +RSet v = "ab" +Debug.Print "[" & v & "]" ' [ ab] +``` + +> [!NOTE] BETA 1034 +> A **Variant** *stringvar* that holds an object raises error 13, *Type mismatch*. VB6 raises error 450, *Wrong number of arguments or invalid property assignment*. + +> [!WARNING] BETA 1034 > **RSet** on an element of a **Variant** that holds an array, such as `RSet v(1) = "zz"` with `v = Array("0123456789", "abcde")` or a **Variant** that was given its bounds with **ReDim**, changes nothing and raises no error. VB6 changes the element. An element of an array declared `Dim w(0 To 1) As Variant` works. To change an element of a **Variant** array, copy it to a **String** variable, use **RSet** on the variable and assign the variable back. > [!NOTE] diff --git a/docs/Reference/Core/ReDim.md b/docs/Reference/Core/ReDim.md index 2d464b090..ec813166f 100644 --- a/docs/Reference/Core/ReDim.md +++ b/docs/Reference/Core/ReDim.md @@ -60,13 +60,9 @@ The **ReDim** statement acts as a declarative statement if the variable it decla | Error | When | |---|---| -| [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | an upper bound is less than its lower bound | -| [9 (`&H9`)](tb:Errors#9), *Subscript out of range* | **Preserve** changes a lower bound, or the bounds of any dimension but the last | +| [9 (`&H9`)](tb:Errors#9), *Subscript out of range* | an upper bound is less than its lower bound
**Preserve** changes a lower bound, or the bounds of any dimension but the last | | [13 (`&HD`)](tb:Errors#13), *Type mismatch* | **Preserve** is used on a **Variant** that does not hold an array | -> [!NOTE] BETA 1010 -> When an upper bound is less than its lower bound, VB6 and 64-bit VBA raise error [9 (`&H9`)](tb:Errors#9), *Subscript out of range*, instead. - ### Example diff --git a/docs/Reference/Core/RightShift.md b/docs/Reference/Core/RightShift.md index 0d429873f..391871717 100644 --- a/docs/Reference/Core/RightShift.md +++ b/docs/Reference/Core/RightShift.md @@ -39,7 +39,7 @@ The data type of *result* matches the type of *number*, whatever the type of *co > > Convert such a value with [**CLng**](../Modules/Conversion/CLng) or [**CLngLng**](../Modules/Conversion/CLngLng) before shifting it. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > Code compiled with LLVM cuts the shift count to the size of the shifted **Byte** or **Integer** before shifting: for a **Byte**, a count of 256 becomes 0 and 257 becomes 1. So a **Byte** shifted by 256 is unchanged, where the result should be 0, as it is without LLVM. An **Integer** shifted by 65,536 or more behaves the same way. A **Long** or **LongLong** is not affected. ### Compound assignment diff --git a/docs/Reference/Core/Topic-Preprocessor.md b/docs/Reference/Core/Topic-Preprocessor.md index 7efd0027b..a0098db1b 100644 --- a/docs/Reference/Core/Topic-Preprocessor.md +++ b/docs/Reference/Core/Topic-Preprocessor.md @@ -38,7 +38,7 @@ The directive's behaviour mirrors the runtime [**If...Then...Else**](If-Then-Els > 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 +> [!WARNING] BETA 1034 > 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 diff --git a/docs/Reference/Core/Type.md b/docs/Reference/Core/Type.md index b04efb9d0..4bca9f4d2 100644 --- a/docs/Reference/Core/Type.md +++ b/docs/Reference/Core/Type.md @@ -102,16 +102,16 @@ twinBASIC extends classic VBA's **Type** in several ways. UDTs remain stack-allo **Member procedures.** [**Sub**](Sub), [**Function**](Function), and [**Property**](Property) procedures may appear inside `Type ... End Type`, and are called through any variable of the type. Inside a member procedure, other members of the same UDT must be accessed with the explicit `Me.` prefix. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > A member procedure called on an element of an array of the UDT, such as `a(1).Sum` or `a(1).Diff(a(2))`, ends the program with an access violation, in 32-bit and 64-bit builds. This holds for local, static, module-level, dynamic and parameter arrays, and in a **With** block. A variable of the type, a UDT member of another UDT, and an element of an array declared inside a UDT are not affected. Copy the element to a variable first. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > Assigning **Me** whole inside a member procedure, as in `Copy = Me`, `Return Me` or `t = Me`, is a code generation error that fails the build once the procedure is called. `Me.X`, passing **Me** as an argument, and `Me = other` are not affected. Copy the members one by one. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > A UDT larger than 4 bytes passed **ByVal** from an element of an array, as in `Take a(1)` for `Sub Take(ByVal p As Pair)`, damages the stack in a 32-bit build: the procedure receives the right value, and the calling procedure ends with an access violation when it returns. A UDT of 4 bytes or less, a variable, an element copied to a variable first, and a **ByRef** parameter are not affected, nor are 64-bit and LLVM builds. VB6 does not accept a UDT passed **ByVal**. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > Assigning without **Set** to a member of a UDT whose type is a class, as in `h.o = 5` for a class with a default member, is a code generation error that fails the build. VB6 assigns the default member. Use `Set h.o = ...` to assign the object, or name the default member, as in `h.o.Value = 5`. **Lifecycle and operator hooks.** Well-known member names are connected by the compiler: diff --git a/docs/Reference/Data-Types.md b/docs/Reference/Data-Types.md index 6856eeaad..30fc62ab9 100644 --- a/docs/Reference/Data-Types.md +++ b/docs/Reference/Data-Types.md @@ -46,7 +46,7 @@ Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _ (ByRef Destination As Any, ByRef Source As Any, ByVal Length As LongPtr) ``` -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > A local variable, a **Static** variable, or a module or class field declared **As Any** compiles with no diagnostic; VB6 refuses each of them. The variable cannot be used: assigning to it is refused (TB5028), reading it into a **Variant** makes the build fail, and **TypeName** of it raises error 458 in a built program. Declare such a variable with a real type. A **Function**'s return type, a **Sub**'s parameter, a UDT field and an array **As Any** are refused (TB5079). --- diff --git a/docs/Reference/Data-Types/Date.md b/docs/Reference/Data-Types/Date.md index ce15199ed..b914e3e8f 100644 --- a/docs/Reference/Data-Types/Date.md +++ b/docs/Reference/Data-Types/Date.md @@ -27,7 +27,7 @@ A **Date** is stored in 8 bytes as a **Double**. The integer part counts days fr A number or a date outside that range cannot be a **Date**, and an assignment, a conversion or an addition that would make one raises error 6, *Overflow*. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > A **Date** outside the range is accepted with no error: `d = 2958466`, `CDate(-657435)`, `#12/31/9999# + 1` and an assignment of `1E+300` each store the number, and **CStr**, **Format** and **Year** of the value then raise error 5, *Invalid procedure call or argument*. VB6 raises error 6, *Overflow*, at the assignment. Compare a day number with -657,434 and 2,958,465 before assigning it to a **Date**. A **Date** prints, and is converted to a string, in the short date and time formats of the regional settings of the computer that runs the program. To get the same text on every computer, give [**Format**](../Modules/Strings/Format) a format string such as `"yyyy-mm-dd hh:nn:ss"`. diff --git a/docs/Reference/Data-Types/Double.md b/docs/Reference/Data-Types/Double.md index 8bf87e293..7e6bb0411 100644 --- a/docs/Reference/Data-Types/Double.md +++ b/docs/Reference/Data-Types/Double.md @@ -77,7 +77,7 @@ A result too large for the type raises error 6, *Overflow*: `1E+308 * 10` does, A **Double** can hold the IEEE values infinity and NaN, which can arrive from memory or from a COM component, but arithmetic never produces one: it raises an error instead. As in VB6, they print as `1.#INF`, `-1.#INF`, `1.#QNAN` and, for the indeterminate value, `-1.#IND`. A comparison with NaN does not follow the IEEE rules: `NaN = NaN` is True, as in VB6, and so is `NaN = 0`, so a comparison cannot tell a NaN from a number. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > A 64-bit build prints infinity and NaN as `inf`, `-inf`, `nan` and `-nan(ind)`, and **Format** with a number picture such as `"0.00"` gives `inf.00` and `nan.00`. A 32-bit build and VB6 print `1.#INF` and `1.#QNAN`, and **Format** gives `1.#INF` and `1.#R`. Do not test or parse the text of these values. ```tb check_run @@ -96,8 +96,8 @@ Debug.Print Err.Number ' 0 Debug.Print Value ' 0 ``` -> [!WARNING] BETA 1010 -> A **Double** addition, subtraction, multiplication or exponentiation (`^`) that overflows raises no error in code compiled with LLVM: the result is infinity, and 0 for `^`. Code compiled without LLVM raises error 6, as VB6 does. See [Language support](../../LLVM/Getting-Started#language-support). +> [!WARNING] BETA 1034 +> An exponentiation (`^`) that overflows raises no error in code compiled with LLVM, and its result is 0. In a 32-bit build compiled with LLVM, and not for SSE2, a **Double** addition, subtraction or multiplication that overflows raises no error either, and its result is infinity. Code compiled without LLVM raises error 6, as VB6 does. See [Language support](../../LLVM/Getting-Started#language-support). ## Example diff --git a/docs/Reference/Data-Types/LongLong.md b/docs/Reference/Data-Types/LongLong.md index e088d1389..6e91c7726 100644 --- a/docs/Reference/Data-Types/LongLong.md +++ b/docs/Reference/Data-Types/LongLong.md @@ -77,10 +77,10 @@ Big = CLngLng(1E+19) Debug.Print Err.Number ' 6 ``` -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > The constant 2^63, one past the largest **LongLong**, converts to -9223372036854775808 with no error: `CLngLng(2 ^ 63)`, `CLngLng(9223372036854775808#)` and `CLngLng(CSng(2 ^ 63))` all do, and so does `Big = 9223372036854775807`, because a whole-number literal without the `^` suffix is a **Double** that rounds to 2^63. A **Double** variable holding 2^63 raises error 6, and so does `1E+19`. VB6 has no **LongLong**; it raises error 6 for the first **Double** beyond the **Long** range. Write the largest value as `9223372036854775807^`. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > In a 32-bit build, a constant expression with an operator after the `^`, such as `Big = 2 ^ 63 - 1024`, raises error 6 when its procedure has already converted a constant **Double** that does not fit a **Long** or a **LongLong**: `Small = 2 ^ 31`, `Big = CLngLng(1E+19)` and `Big = CLngLng(2 ^ 63)` all do. The value fits, and the statement stores it; the error is raised once, at the first such expression. A 64-bit build raises no error. A single literal, a single power such as `2 ^ 62` and arithmetic on a variable are not affected. A function whose body is the expression raises the error in the same way when a procedure that calls it holds such a conversion. Write the value as one literal, as in `Big = 9223372036854774784^`; `CLngLng` around the expression does not avoid the error. ## Arithmetic and overflow diff --git a/docs/Reference/Data-Types/Object.md b/docs/Reference/Data-Types/Object.md index 56934977d..1e9821805 100644 --- a/docs/Reference/Data-Types/Object.md +++ b/docs/Reference/Data-Types/Object.md @@ -50,7 +50,7 @@ An object reference is assigned with [**Set**](Set): `Set Item = New Collection` A reference can be assigned to a variable of a more specific type, `Set Items = Item`, which checks that the object supports that type. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > Assigning an object to a variable of a class or interface it does not support raises the error -2147467262, the **HRESULT** `E_NOINTERFACE`, *No such interface supported*. VB6 raises error 13, *Type mismatch*. Test the object first with `If TypeOf Item Is Collection Then`, or have the handler accept both numbers. ## Late binding diff --git a/docs/Reference/Data-Types/Single.md b/docs/Reference/Data-Types/Single.md index d203d5f03..29f091c10 100644 --- a/docs/Reference/Data-Types/Single.md +++ b/docs/Reference/Data-Types/Single.md @@ -91,7 +91,7 @@ Debug.Print 0.1! + 0.2! = 0.3! ' True Debug.Print 0.1 + 0.2 = 0.3 ' False ``` -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > An addition, subtraction, multiplication or division whose result is a declared **Single** raises no error when the result overflows: the **Single** holds infinity (`1.#INF`) instead. VB6 raises error 6, *Overflow*. Declare the variable **Double** where an overflow must be caught. Code compiled with LLVM has a similar defect for **Double**; see [Language support](../../LLVM/Getting-Started#language-support). A **Variant** holding a **Single** does not overflow: a result that does not fit becomes a **Double**. diff --git a/docs/Reference/Default/VB/Data/index.md b/docs/Reference/Default/VB/Data/index.md index 2f28b34ec..3cda23792 100644 --- a/docs/Reference/Default/VB/Data/index.md +++ b/docs/Reference/Default/VB/Data/index.md @@ -85,7 +85,7 @@ Determines how the control's border is drawn by the OS. A member of [**Appearanc The fill colour of the band behind the [**Caption**](#caption), as an **OLE_COLOR**. Defaults to the system window-background colour. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > Assigning a value that is not a colour, such as `-1`, raises no error and stores the value, so a later read returns it. Check a colour that comes from user input before assigning it. ### BOFAction @@ -182,7 +182,7 @@ The **StdFont** used to render [**Caption**](#caption). The convenience properti The text colour for the caption, as an **OLE_COLOR**. Defaults to the system window-text colour. A disabled control draws the caption in the system grey-text colour instead. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > Assigning a value that is not a colour, such as `-1`, raises no error and stores the value, so a later read returns it. Check a colour that comes from user input before assigning it. ### Height diff --git a/docs/Reference/Default/VB/MultiFrame/index.md b/docs/Reference/Default/VB/MultiFrame/index.md index 35519eebb..a66f3ff7d 100644 --- a/docs/Reference/Default/VB/MultiFrame/index.md +++ b/docs/Reference/Default/VB/MultiFrame/index.md @@ -78,7 +78,7 @@ The set of edges of the parent that the **MultiFrame**'s corresponding edges fol The background colour of the **MultiFrame**'s drawing surface, as an **OLE_COLOR**. Defaults to the system window-background colour. Visible only where the contained frames do not fully cover the extent --- e.g. when their **MultiFrameSize**s sum to less than 100%. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > Assigning a value that is not a colour, such as `-1`, raises no error and stores the value, so a later read returns it. Check a colour that comes from user input before assigning it. ### Container diff --git a/docs/Reference/Default/VB/OLE/index.md b/docs/Reference/Default/VB/OLE/index.md index 2908f0311..e273a910e 100644 --- a/docs/Reference/Default/VB/OLE/index.md +++ b/docs/Reference/Default/VB/OLE/index.md @@ -107,7 +107,7 @@ When **True** (default), right-clicking the container automatically pops up a me The background colour, as an **OLE_COLOR**. Defaults to the system window-background colour. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > Assigning a value that is not a colour, such as `-1`, raises no error and stores the value, so a later read returns it. VB6 raises error 380, *Invalid property value*, and keeps the old colour. Check a colour that comes from user input before assigning it. ### BackStyle diff --git a/docs/Reference/Default/VB/TextBox/index.md b/docs/Reference/Default/VB/TextBox/index.md index 66a2d8b18..40ad37408 100644 --- a/docs/Reference/Default/VB/TextBox/index.md +++ b/docs/Reference/Default/VB/TextBox/index.md @@ -279,10 +279,17 @@ When **True**, the user can scroll, select, and copy text but cannot modify it. ### MaxLength {: .no_toc } -The maximum number of characters the user can type into the control. **Long**, default `0` --- when zero, the OS imposes its own limit (typically 32 767 characters for single-line, much larger for multi-line). Setting **MaxLength** below the current text length does not truncate what is already there, but blocks further typing until the user deletes enough characters. - -> [!WARNING] BETA 1010 -> Assigning [**Text**](#text) in code is not limited by **MaxLength**. The whole string is stored, and no error is raised. VB6 truncates the string to **MaxLength** characters. Truncate the string in code before assigning it, with **Left$**. +The maximum number of characters the user can type into the control. **Long**, default `0` --- when zero, the OS imposes its own limit (typically 32 767 characters for single-line, much larger for multi-line). Setting **MaxLength** below the current text length does not truncate what is already there, but blocks further typing until the user deletes enough characters. A string assigned to [**Text**](#text) in code is cut to **MaxLength** characters, and one assigned to **SelText** adds only as many characters as **MaxLength** leaves room for. Neither raises an error: + +```tb check_build check_run project=form +Load Form1 +Dim t As TextBox +Set t = Form1.Controls.Add("VB.TextBox", "txtLimit") +t.MaxLength = 3 +t.Text = "abcdef" +Debug.Print t.Text ' abc +Unload Form1 +``` ### MouseIcon {: .no_toc } diff --git a/docs/Reference/Default/VBA/Collection/Add.md b/docs/Reference/Default/VBA/Collection/Add.md index 438f2c113..a1982625d 100644 --- a/docs/Reference/Default/VBA/Collection/Add.md +++ b/docs/Reference/Default/VBA/Collection/Add.md @@ -43,16 +43,16 @@ Key comparison is governed by the [**KeyCompareMode**](KeyCompareMode) property. | [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument* | both *before* and *after* are given
*before* or *after* is given and the collection is empty
*before* or *after* is a string that is not the key of an item | | [9 (`&H9`)](tb:Errors#9), *Subscript out of range* | *before* is a number that, rounded, is less than 1 or greater than **Count** + 1, or *after* one that is less than 0 or greater than **Count**
*before* or *after* is a number outside the **Long** range | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *key* is **Empty**, VB6 and 64-bit VBA raise error [13 (`&HD`)](tb:Errors#13), *Type mismatch*; twinBASIC raises none. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *before* or *after* is -1, **True** or **Empty**, VB6 and 64-bit VBA raise error [9 (`&H9`)](tb:Errors#9), *Subscript out of range*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *before* or *after* is a **Currency**, **Decimal** or **Date** value, VB6 and 64-bit VBA raise no error. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *before* or *after* is a number outside the **Long** range, VB6 and 64-bit VBA raise error [13 (`&HD`)](tb:Errors#13), *Type mismatch*, instead. diff --git a/docs/Reference/Default/VBA/Collection/Item.md b/docs/Reference/Default/VBA/Collection/Item.md index 8cb264288..ddba55e5f 100644 --- a/docs/Reference/Default/VBA/Collection/Item.md +++ b/docs/Reference/Default/VBA/Collection/Item.md @@ -37,13 +37,13 @@ Key comparison is governed by the [**KeyCompareMode**](KeyCompareMode) property. | [9 (`&H9`)](tb:Errors#9), *Subscript out of range* | *index* is a number that, rounded, is less than 1 or greater than **Count**, and the collection is not empty
the collection is empty and *index* is not a string
*index* is **Null**, an object, **Nothing**, an array, an error value or a number outside the **Long** range, and the collection is not empty
*index* is a **Currency**, **Decimal** or **Date** value | | [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument* | *index* is a string that is not the key of an item | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When the collection is empty and *index* is not a string, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *index* is **Null**, an object, **Nothing**, an array, an error value or a number outside the **Long** range, and the collection is not empty, VB6 and 64-bit VBA raise error [13 (`&HD`)](tb:Errors#13), *Type mismatch*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *index* is a **Currency**, **Decimal** or **Date** value, VB6 and 64-bit VBA raise no error. diff --git a/docs/Reference/Default/VBA/Collection/Remove.md b/docs/Reference/Default/VBA/Collection/Remove.md index 9f8e45f75..007fc7e8f 100644 --- a/docs/Reference/Default/VBA/Collection/Remove.md +++ b/docs/Reference/Default/VBA/Collection/Remove.md @@ -29,10 +29,10 @@ Key comparison is governed by the [**KeyCompareMode**](KeyCompareMode) property. | [9 (`&H9`)](tb:Errors#9), *Subscript out of range* | *index* is a number that, rounded, is less than 1 or greater than **Count**, and the collection is not empty
*index* is **Null**, an object, **Nothing**, an array or a number outside the **Long** range, and the collection is not empty
*index* is a **Currency**, **Decimal** or **Date** value | | [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument* | *index* is a string that is not the key of an item
the collection is empty | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *index* is **Null**, an object, **Nothing**, an array or a number outside the **Long** range, and the collection is not empty, VB6 and 64-bit VBA raise error [13 (`&HD`)](tb:Errors#13), *Type mismatch*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *index* is a **Currency**, **Decimal** or **Date** value, VB6 and 64-bit VBA raise no error. diff --git a/docs/Reference/Default/VBA/Conversion/CDate.md b/docs/Reference/Default/VBA/Conversion/CDate.md index 8c60ad1c7..f0f4b3b6c 100644 --- a/docs/Reference/Default/VBA/Conversion/CDate.md +++ b/docs/Reference/Default/VBA/Conversion/CDate.md @@ -26,27 +26,20 @@ Use the **IsDate** function to determine whether *expression* can be converted t A **Date** holds a day number from -657,434 (January 1, 100) to 2,958,465 (December 31, 9999), with the time of day as its fraction. -> [!WARNING] BETA 1010 -> **CDate** of a number outside that range, or of a string that holds one, raises no error: `CDate(2958466)`, `CDate(-657435)`, `CDate(1E+300)` and `CDate("3000000")` each return a **Date** that holds the number, and **CStr**, **Format** and **Year** of it then raise error 5, *Invalid procedure call or argument*. VB6 raises error 6, *Overflow*, for the number and error 13, *Type mismatch*, for the string. Compare a day number with -657,434 and 2,958,465 before converting it, or use [**CVDate**](CVDate), which raises error 13 for a number outside the range. +> [!WARNING] BETA 1034 +> **CDate** of a number outside that range raises no error: `CDate(2958466)`, `CDate(-657435)` and `CDate(1E+300)` each return a **Date** that holds the number, and **CStr**, **Format** and **Year** of it then raise error 5, *Invalid procedure call or argument*. VB6 raises error 6, *Overflow*. Compare a day number with -657,434 and 2,958,465 before converting it, or use [**CVDate**](CVDate), which raises error 13 for a number outside the range. ### Common Errors | Error | When | |---|---| -| [6 (`&H6`)](tb:Errors#6), *Overflow* | *expression* is a **Decimal** less than -657,434 or 2,958,466 or more | -| [13 (`&HD`)](tb:Errors#13), *Type mismatch* | *expression* is a string that is not a date, a time or a number (a zero-length string included), or a date after December 31, 9999 | +| [13 (`&HD`)](tb:Errors#13), *Type mismatch* | *expression* is a string that is not a date, a time or a number (a zero-length string included), or a date after December 31, 9999
*expression* is a **Decimal**, or a string that holds a number, less than -657,434 or 2,958,466 or more | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *expression* is **Null** | -> [!NOTE] BETA 1010 -> When *expression* is a **Decimal** less than -657,434 or 2,958,466 or more, VB6 and 64-bit VBA raise error [13 (`&HD`)](tb:Errors#13), *Type mismatch*, instead. - -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When **CDate** is given a number of another type than **Decimal** that is less than -657,434 or 2,958,466 or more, VB6 and 64-bit VBA raise error [6 (`&H6`)](tb:Errors#6), *Overflow*; twinBASIC raises none. -> [!NOTE] BETA 1010 -> When **CDate** is given a string that holds a number less than -657,434 or 2,958,466 or more, VB6 and 64-bit VBA raise error [13 (`&HD`)](tb:Errors#13), *Type mismatch*; twinBASIC raises none. - ### Example diff --git a/docs/Reference/Default/VBA/Conversion/CLng.md b/docs/Reference/Default/VBA/Conversion/CLng.md index ca32e501f..62441cf79 100644 --- a/docs/Reference/Default/VBA/Conversion/CLng.md +++ b/docs/Reference/Default/VBA/Conversion/CLng.md @@ -20,6 +20,14 @@ The return type is **Long**. When the fractional part is exactly `0.5`, **CLng** always rounds it to the nearest even number. For example, `0.5` rounds to `0`, and `1.5` rounds to `2`. **CLng** differs from the [**Fix**](Fix) and [**Int**](Int) functions, which truncate, rather than round, the fractional part of a number. +```tb check_build check_run +Dim Half As Double, TwoAndAHalf As Double, MinusHalf As Double +Half = 0.5: TwoAndAHalf = 2.5: MinusHalf = -0.5 +Debug.Print CLng(Half) ' 0 +Debug.Print CLng(TwoAndAHalf) ' 2 +Debug.Print CLng(MinusHalf) ' 0 +``` + ### Common Errors diff --git a/docs/Reference/Default/VBA/Conversion/CLngLng.md b/docs/Reference/Default/VBA/Conversion/CLngLng.md index 70a01ff2b..28896bdbc 100644 --- a/docs/Reference/Default/VBA/Conversion/CLngLng.md +++ b/docs/Reference/Default/VBA/Conversion/CLngLng.md @@ -26,7 +26,7 @@ When the fractional part is exactly `0.5`, **CLngLng** always rounds it to the n > [!NOTE] > In VBA, **LongLong** (and therefore **CLngLng**) is restricted to 64-bit hosts. twinBASIC supports **LongLong** in both 32-bit and 64-bit modes --- see [Data Types](../../../Features/Language/Data-Types). -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > The constant 2^63, one past the largest **LongLong**, converts to -9223372036854775808 with no error: `CLngLng(2 ^ 63)` and `CLngLng(9223372036854775808#)` do, and so does `CLngLng(9223372036854775807)`, because a whole-number literal without the `^` suffix is a **Double** that rounds to 2^63. A **Double** variable holding 2^63 raises error 6, as the table below says. Write the largest value as `9223372036854775807^`. 64-bit VBA raises error 6 for each of these constants. diff --git a/docs/Reference/Default/VBA/Conversion/CStr.md b/docs/Reference/Default/VBA/Conversion/CStr.md index 5153eebee..3dcd06f84 100644 --- a/docs/Reference/Default/VBA/Conversion/CStr.md +++ b/docs/Reference/Default/VBA/Conversion/CStr.md @@ -28,7 +28,7 @@ The return type is **String**. The result depends on the type of *expression*: **CStr** is the internationally aware alternative to [**Str**](Str) for converting a number to a string. **CStr** recognizes different decimal separators properly, depending on the system's locale setting. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > A 64-bit build converts a negative zero of type **Single** or **Double** (a value that equals 0 and has its sign bit set) to `"-0"`. A 32-bit build and VB6 give `"0"`. The same happens when such a value is joined to a string with `&`. diff --git a/docs/Reference/Default/VBA/Conversion/Error.md b/docs/Reference/Default/VBA/Conversion/Error.md index 55cc06a64..826645a8c 100644 --- a/docs/Reference/Default/VBA/Conversion/Error.md +++ b/docs/Reference/Default/VBA/Conversion/Error.md @@ -31,7 +31,7 @@ Examine the property settings of the **Err** object to identify the most recent |---|---| | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *errornumber* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When **Error** or **Error$** is given an *errornumber* that rounds to a number greater than 65,535, VB6 and 64-bit VBA raise error [6 (`&H6`)](tb:Errors#6), *Overflow*; twinBASIC raises none. diff --git a/docs/Reference/Default/VBA/Conversion/Str.md b/docs/Reference/Default/VBA/Conversion/Str.md index ec9f7bef7..46a2d5603 100644 --- a/docs/Reference/Default/VBA/Conversion/Str.md +++ b/docs/Reference/Default/VBA/Conversion/Str.md @@ -25,7 +25,7 @@ When numbers are converted to strings, a leading space is always reserved for th Use the [**Format**](../Strings/Format) function to convert numeric values to be formatted as dates, times, or currency, or in other user-defined formats. Unlike **Str**, the **Format** function doesn't include a leading space for the sign of *number*. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > A 64-bit build converts a negative zero of type **Single** or **Double** (a value that equals 0 and has its sign bit set) to `"-0"`, with no leading space. A 32-bit build and VB6 give `" 0"`. > [!NOTE] diff --git a/docs/Reference/Default/VBA/Conversion/Val.md b/docs/Reference/Default/VBA/Conversion/Val.md index f6cfb7fa6..a7e41e248 100644 --- a/docs/Reference/Default/VBA/Conversion/Val.md +++ b/docs/Reference/Default/VBA/Conversion/Val.md @@ -45,13 +45,10 @@ Val("&HFFFF") | Error | When | |---|---| -| [6 (`&H6`)](tb:Errors#6), *Overflow* | *string* holds a number outside the range of a **Double** | +| [6 (`&H6`)](tb:Errors#6), *Overflow* | *string* holds a number outside the range of a **Double**
*string* holds a number followed by the **Integer** (`%`), **Long** (`&`) or **Currency** (`@`) type-declaration character, and the number is outside the range of that type | | [13 (`&HD`)](tb:Errors#13), *Type mismatch* | *string* holds a number with a fractional part followed by the **Integer** (`%`) or **Long** (`&`) type-declaration character, such as `"50.5%"` | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *string* is **Null** | -> [!NOTE] BETA 1010 -> When **Val** is given a *string* that holds a number followed by the **Integer** (`%`), **Long** (`&`) or **Currency** (`@`) type-declaration character, and the number is outside the range of that type, VB6 and 64-bit VBA raise error [6 (`&H6`)](tb:Errors#6), *Overflow*; twinBASIC raises none. - ### Example diff --git a/docs/Reference/Default/VBA/DateTime/Calendar.md b/docs/Reference/Default/VBA/DateTime/Calendar.md index 1e1ba7d2d..c4eae318b 100644 --- a/docs/Reference/Default/VBA/DateTime/Calendar.md +++ b/docs/Reference/Default/VBA/DateTime/Calendar.md @@ -23,7 +23,7 @@ Syntax: **Calendar** [ **=** *calendartype* ] The **Calendar** property can only be set programmatically. The setting of **Calendar** affects the string returned by the [**Date$**](Date#date-1) property when the calendar is set to Hijri. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > twinBASIC has not implemented **Calendar** yet. The compiler warns TB0009 at each use, **Calendar** always reads as **vbCalGreg**, and setting it raises an error, **vbCalGreg** included. diff --git a/docs/Reference/Default/VBA/DateTime/Date.md b/docs/Reference/Default/VBA/DateTime/Date.md index 03aecd280..c0e8baabb 100644 --- a/docs/Reference/Default/VBA/DateTime/Date.md +++ b/docs/Reference/Default/VBA/DateTime/Date.md @@ -117,16 +117,16 @@ Date$ = MyDate ' Change the system date. | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | **Date$** is given a value that is not a date of the form *mm-dd-yyyy*
**Date** or **Date$** is given a date before January 1, 1601, which Windows cannot set
**Date$** is given a date in another form VB6 reads, such as *m-d-yyyy*, *m/d/yyyy* or *mm-dd-yy* | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | **Date$** is given **Null**
**Date** is given **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When **Date$** is given a value that is not a date of the form *mm-dd-yyyy*, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When **Date** or **Date$** is given a date before January 1, 1601, which Windows cannot set, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When **Date$** is given a date in another form VB6 reads, such as *m-d-yyyy*, *m/d/yyyy* or *mm-dd-yy*, VB6 and 64-bit VBA raise no error. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When **Date** is given **Null**, VB6 and 64-bit VBA raise error [13 (`&HD`)](tb:Errors#13), *Type mismatch*, instead. diff --git a/docs/Reference/Default/VBA/DateTime/DateAdd.md b/docs/Reference/Default/VBA/DateTime/DateAdd.md index 546534ca1..636171d07 100644 --- a/docs/Reference/Default/VBA/DateTime/DateAdd.md +++ b/docs/Reference/Default/VBA/DateTime/DateAdd.md @@ -42,18 +42,18 @@ To add days to *date*, use Day of Year ("y"), Day ("d"), or Weekday ("w"). > [!NOTE] > When the "w" interval is used to add days to a date, **DateAdd** adds the total number of days specified, not just workdays (Monday through Friday). -**DateAdd** won't return an invalid date. The following example adds one month to January 31: +**DateAdd** won't return an invalid date. The first two lines of the following example add one month to January 31: -```tb check_build -DateAdd("m", 1, "31-Jan-95") +```tb check_build check_run +Debug.Print Format$(DateAdd("m", 1, #1/31/1995#), "yyyy-mm-dd") ' 1995-02-28 +Debug.Print Format$(DateAdd("m", 1, #1/31/1996#), "yyyy-mm-dd") ' 1996-02-29 +Debug.Print Format$(DateAdd("m", -1, #3/31/2026#), "yyyy-mm-dd") ' 2026-02-28 +Debug.Print Format$(DateAdd("yyyy", 1, #2/29/2024#), "yyyy-mm-dd") ' 2025-02-28 ``` -In this case, **DateAdd** returns 28-Feb-95, not 31-Feb-95. If *date* is 31-Jan-96, it returns 29-Feb-96 because 1996 is a leap year. - -> [!WARNING] BETA 1010 -> **DateAdd** with the "m", "q" or "yyyy" interval does not stop at the last day of a shorter month: it counts the extra days into the next month and raises no error. `DateAdd("m", 1, "31-Jan-95")` returns 3-Mar-95, `DateAdd("m", -1, #3/31/2026#)` returns 3-Mar-2026, and `DateAdd("yyyy", 1, #2/29/2024#)` returns 1-Mar-2025. VB6 returns 28-Feb-95, 28-Feb-2026 and 28-Feb-2025. +**DateAdd** returns 28-Feb-95, not 31-Feb-95. If *date* is 31-Jan-96, it returns 29-Feb-96 because 1996 is a leap year. Subtracting a month, and adding or subtracting a quarter or a year, stops at the last day of the shorter month in the same way, as the last two lines show. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > **DateAdd** with the "yyyy", "q" or "m" interval raises no error when the result would be before January 1, 100, and returns a date in the 1900s or 2000s instead: `DateAdd("yyyy", -1, #1/1/100#)` returns January 1, 1999, and `DateAdd("m", -1, #1/1/100#)` returns December 1, 1999. VB6 raises error 5. If *number* isn't a **Long** value, it is rounded to the nearest whole number before being evaluated. @@ -72,10 +72,10 @@ If the [**Calendar**](Calendar) property setting is Gregorian, the supplied date | [13 (`&HD`)](tb:Errors#13), *Type mismatch* | *date* is a string that is not a date | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *interval* or *number* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When the result with an *interval* of "yyyy", "q" or "m" would be before January 1, 100, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*; twinBASIC raises none. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *date* is a number outside the range of dates, VB6 and 64-bit VBA raise error [13 (`&HD`)](tb:Errors#13), *Type mismatch*, instead. diff --git a/docs/Reference/Default/VBA/DateTime/DateDiff.md b/docs/Reference/Default/VBA/DateTime/DateDiff.md index 14030f20e..959e433d2 100644 --- a/docs/Reference/Default/VBA/DateTime/DateDiff.md +++ b/docs/Reference/Default/VBA/DateTime/DateDiff.md @@ -66,7 +66,7 @@ To calculate the number of days between *date1* and *date2*, use either Day of Y If *interval* is Week ("ww"), however, **DateDiff** returns the number of calendar weeks between the two dates. It counts the number of Sundays between *date1* and *date2*. **DateDiff** counts *date2* if it falls on a Sunday, but it doesn't count *date1*, even if it does fall on a Sunday. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > With the "ww" interval, **DateDiff** raises no error for a *firstdayofweek* outside 0 to 7, and what it returns follows no rule: a negative value acts as if taken modulo 7, 8 to 13 act as **vbSunday**, and a larger value matches no setting. VB6 raises error 5. If *date1* refers to a later point in time than *date2*, the function returns a negative number. The *firstdayofweek* argument affects calculations that use the "w" and "ww" interval symbols. @@ -87,10 +87,10 @@ If the [**Calendar**](Calendar) property setting is Gregorian, the supplied date | [13 (`&HD`)](tb:Errors#13), *Type mismatch* | *date1* or *date2* is a string that is not a date | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *interval*, *firstdayofweek* or *firstweekofyear* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *interval* is "ww" and *firstdayofweek* is outside 0 to 7, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*; twinBASIC raises none. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *date1* or *date2* is a number outside the range of dates, VB6 and 64-bit VBA raise error [13 (`&HD`)](tb:Errors#13), *Type mismatch*, instead. diff --git a/docs/Reference/Default/VBA/DateTime/DatePart.md b/docs/Reference/Default/VBA/DateTime/DatePart.md index 5d152c3b8..3924f6cfd 100644 --- a/docs/Reference/Default/VBA/DateTime/DatePart.md +++ b/docs/Reference/Default/VBA/DateTime/DatePart.md @@ -64,10 +64,10 @@ Syntax: **DatePart** ( *interval*, *date* [, *firstdayofweek* [, *firstweekofyea The *firstdayofweek* argument affects calculations that use the "w" and "ww" interval symbols. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > With the "w" or "ww" interval, **DatePart** raises no error for a *firstdayofweek* outside 0 to 7, and what it returns follows no rule: a negative value acts as if taken modulo 7, 8 acts as **vbSunday**, and a larger value matches no setting. `DatePart("w", #1/1/2000#, 255)` returns -2. VB6 raises error 5. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > With the "ww" interval, **DatePart** goes wrong for a date in the first days of year 100 whose week belongs to year 99. With **vbFirstFullWeek** it never returns, and the program stops responding: `DatePart("ww", #1/1/100#, vbSunday, vbFirstFullWeek)` is one such call. With **vbFirstFourDays** it returns a wrong number: -98980 for January 1, 100. VB6 returns 52 for both. If *date* is a date literal, the specified year becomes a permanent part of that date. If *date* is enclosed in double quotation marks and the year is omitted, the current year is inserted each time the expression is evaluated. @@ -83,13 +83,13 @@ If the [**Calendar**](Calendar) property setting is Gregorian, the supplied date | [13 (`&HD`)](tb:Errors#13), *Type mismatch* | *date* is a string that is not a date, or a number outside the range of dates | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *interval*, *firstdayofweek* or *firstweekofyear* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *interval* is not one of the settings listed and *date* is **Null**, VB6 and 64-bit VBA raise no error. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *interval* is "w" or "ww" and *firstdayofweek* is outside 0 to 7, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*; twinBASIC raises none. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *interval* is "ww" and *firstweekofyear* is outside 0 to 3, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*; twinBASIC raises none. diff --git a/docs/Reference/Default/VBA/DateTime/Time.md b/docs/Reference/Default/VBA/DateTime/Time.md index f51ba4fbc..0fea97950 100644 --- a/docs/Reference/Default/VBA/DateTime/Time.md +++ b/docs/Reference/Default/VBA/DateTime/Time.md @@ -117,13 +117,13 @@ Time$ = MyTime ' Set system time. | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | **Time$** is given a value that is not a string of the form *hh:mm:ss*, or one outside 00:00:00 to 23:59:59
**Time$** is given a time in another form VB6 reads, such as *hh:mm* or *hh* | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | **Time$** is given **Null**
**Time** is given **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When **Time$** is given a value that is not a string of the form *hh:mm:ss*, or one outside 00:00:00 to 23:59:59, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When **Time$** is given a time in another form VB6 reads, such as *hh:mm* or *hh*, VB6 and 64-bit VBA raise no error. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When **Time** is given **Null**, VB6 and 64-bit VBA raise error [13 (`&HD`)](tb:Errors#13), *Type mismatch*, instead. diff --git a/docs/Reference/Default/VBA/DateTime/index.md b/docs/Reference/Default/VBA/DateTime/index.md index 5844bbd25..1f47d237e 100644 --- a/docs/Reference/Default/VBA/DateTime/index.md +++ b/docs/Reference/Default/VBA/DateTime/index.md @@ -58,14 +58,11 @@ Debug.Print DateAdd("m", 1, #1/31/2026#) ' 2/28/2026 -- clamped to last Debug.Print DateDiff("d", #1/1/2026#, #5/9/2026#) ' 128 ``` -> [!WARNING] BETA 1010 -> **DateAdd** does not clamp to the last day of the month, and the first line of the sample prints `3/3/2026`. VB6 prints `2/28/2026`. [**DateAdd**](DateAdd) describes the cases. - ## Calendar selection The [**Calendar**](Calendar) property selects the calendar --- **vbCalGreg** (Gregorian, the default) or **vbCalHijri** (Hijri) --- used by the rest of the module. The setting controls how **Date$** formats the system date, how arguments to **DateSerial**, **DateValue**, **DateAdd**, and **DateDiff** are interpreted, and how the parts returned by **DatePart**, **Year**, **Month**, **Day**, and **Weekday** are reported. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > twinBASIC has not implemented **Calendar** yet: it always reads as **vbCalGreg**, and setting it raises an error. ## Members diff --git a/docs/Reference/Default/VBA/ErrObject/Description.md b/docs/Reference/Default/VBA/ErrObject/Description.md index a14ec859e..8eac12980 100644 --- a/docs/Reference/Default/VBA/ErrObject/Description.md +++ b/docs/Reference/Default/VBA/ErrObject/Description.md @@ -20,7 +20,7 @@ The **Description** setting consists of a short description of the error. Use th When generating a user-defined error, assign a short description of the error to the **Description** property. If **Description** isn't filled in and the value of [**Number**](Number) corresponds to a built-in run-time error, the string returned by the [**Error**](../Conversion/Error) function is placed in **Description** when the error is generated. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > For a number that has no built-in message, **Raise** without a *description* leaves **Description** as an empty string, a Windows system message or `Automation error`, depending on the number, and raises no error. VBA and VB6 place `Application-defined or object-defined error` there. [**Raise**](Raise) has the table. diff --git a/docs/Reference/Default/VBA/ErrObject/HelpContext.md b/docs/Reference/Default/VBA/ErrObject/HelpContext.md index f6ef00da4..1dd0a41fa 100644 --- a/docs/Reference/Default/VBA/ErrObject/HelpContext.md +++ b/docs/Reference/Default/VBA/ErrObject/HelpContext.md @@ -23,7 +23,7 @@ If both **HelpFile** and **HelpContext** are empty, the value of [**Number**](Nu Write routines to handle typical errors. When programming with an object, the object's Help file can improve error handling or display a meaningful message to the user when an error isn't recoverable. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > A statement that assigns **HelpContext**, such as `Err.HelpContext = 5`, compiles, but the build fails with a code-generation error in the procedure that holds it. [**Raise**](Raise) sets **HelpContext** without the problem, and so does an assignment through a variable declared **As Object** that holds **Err**. ### Example diff --git a/docs/Reference/Default/VBA/ErrObject/HelpFile.md b/docs/Reference/Default/VBA/ErrObject/HelpFile.md index 97db54911..b0fa79686 100644 --- a/docs/Reference/Default/VBA/ErrObject/HelpFile.md +++ b/docs/Reference/Default/VBA/ErrObject/HelpFile.md @@ -21,7 +21,7 @@ If a Help file is specified in **HelpFile**, it is automatically called when the Write routines to handle typical errors. When programming with an object, the object's Help file can improve error handling or display a meaningful message to the user when an error isn't recoverable. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > A statement that assigns **HelpFile**, such as `Err.HelpFile = "MyHelp.chm"`, compiles, but the build fails with a code-generation error in the procedure that holds it. [**Raise**](Raise) sets **HelpFile** without the problem, and so does an assignment through a variable declared **As Object** that holds **Err**. ### Example diff --git a/docs/Reference/Default/VBA/ErrObject/Number.md b/docs/Reference/Default/VBA/ErrObject/Number.md index 3225b71b7..8c229c80d 100644 --- a/docs/Reference/Default/VBA/ErrObject/Number.md +++ b/docs/Reference/Default/VBA/ErrObject/Number.md @@ -26,7 +26,7 @@ Err.Raise Number:=vbObjectError + 1051, Source:="SomeClass" > **Number** accepts a number above 65535. VB6 and 64-bit VBA raise error 5, *Invalid procedure call or argument*, for one. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > **Number** keeps an **HRESULT** assigned to it as it is. VB6 and 64-bit VBA store the VB error of the same meaning for the ones that [**Raise**](Raise) turns into VB errors: after `Err.Number = &H80070057`, **Number** is 5 there, and in twinBASIC it is -2147024809. diff --git a/docs/Reference/Default/VBA/ErrObject/Raise.md b/docs/Reference/Default/VBA/ErrObject/Raise.md index 80c2cda08..bc5d992e3 100644 --- a/docs/Reference/Default/VBA/ErrObject/Raise.md +++ b/docs/Reference/Default/VBA/ErrObject/Raise.md @@ -44,14 +44,14 @@ A *description* of an empty string gives an empty **Description** for every numb When the error is raised in a method of a class and the caller handles it, an empty **Description** reaches the caller as `Application-defined or object-defined error`. The other texts arrive unchanged. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > **Raise** differs from VBA and VB6 in three ways, and none of them raises an error. Without *source*, VBA sets **Source** to the name of the project, and twinBASIC leaves it empty. Without *description*, VBA gives every number that has no built-in message the text `Application-defined or object-defined error`, from 1 up to 65535, and twinBASIC gives an empty string or one of the texts above. And an omitted argument keeps the value left by an earlier error in VBA, and is reset in twinBASIC. A program that relies on **Err.Source** being the project name, or on **Err.Description** being non-empty, has to pass those arguments. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > An error that a class member raises with a constant *number*, such as `Err.Raise 380`, reaches a caller that called the member late-bound --- through an **Object** variable or [**CallByName**](../Interaction/CallByName) --- as error -2147352567 (`&H80020009`), *Exception occurred.*, not as 380. VB6 raises 380. The number arrives unchanged when the member passes it in a variable, as in `Err.Raise Number`, and in every early-bound call. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > **Raise** raises an **HRESULT** *number* as it is. VB6 and 64-bit VBA raise many **HRESULT**s as the VB error of the same meaning instead, including these: > > | *number* | VB6 and 64-bit VBA raise | diff --git a/docs/Reference/Default/VBA/ErrObject/ReturnHResult.md b/docs/Reference/Default/VBA/ErrObject/ReturnHResult.md index 3d34d4286..4e7b6ca26 100644 --- a/docs/Reference/Default/VBA/ErrObject/ReturnHResult.md +++ b/docs/Reference/Default/VBA/ErrObject/ReturnHResult.md @@ -18,7 +18,7 @@ Syntax: **Err**.**ReturnHResult** **=** *value* A calling procedure can then use [**LastHresult**](LastHresult) to read the positive HRESULT that was returned from the method. Set **ReturnHResult** only when needed, typically just before the method exit point, so that the intended HRESULT is the one that's actually returned. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > Assigning **ReturnHResult** in a procedure of a module, rather than in a method of a class, compiles, but the build fails with a code-generation error in that procedure. ### Example diff --git a/docs/Reference/Default/VBA/ErrObject/Source.md b/docs/Reference/Default/VBA/ErrObject/Source.md index 9d6696eff..ae49f8456 100644 --- a/docs/Reference/Default/VBA/ErrObject/Source.md +++ b/docs/Reference/Default/VBA/ErrObject/Source.md @@ -24,7 +24,7 @@ When generating an error from user code, pass the source to [**Raise**](Raise). When an error occurs that twinBASIC raises itself, such as a division by zero, **Source** is an empty string too, in a standard module and in a class module alike. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > It does not fill in **Source**, and raises no error. VBA and VB6 fill it in automatically with the name of the project, for an error that the runtime raises and for **Raise** without a source argument. A program that reads **Err.Source** to find where an error came from gets an empty string. @@ -45,9 +45,9 @@ When handling errors, do not rely on the **Source** property (or any **Err** pro ```tb check_build Dim myObjectID As String, myHelpFile As String, myHelpContext As Long myObjectID = "MyApp.MyClass" -Err.Raise vbObjectError + 894, myObjectID, _ - "Was not able to complete your task", _ - myHelpFile, myHelpContext +Err.Raise Number:=vbObjectError + 894, Source:=myObjectID, _ + Description:="Was not able to complete your task", _ + HelpFile:=myHelpFile, HelpContext:=myHelpContext ``` ### See Also diff --git a/docs/Reference/Default/VBA/FileSystem/ChDir.md b/docs/Reference/Default/VBA/FileSystem/ChDir.md index ac81296b1..42de9831c 100644 --- a/docs/Reference/Default/VBA/FileSystem/ChDir.md +++ b/docs/Reference/Default/VBA/FileSystem/ChDir.md @@ -47,7 +47,7 @@ On MacOS, the default drive name is "HD" and portions of the pathname are separa | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | *path* names no folder that exists, which includes a file, a drive that does not exist, a zero-length string, and a name with a wildcard or a character a name cannot hold | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *path* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *path* names no folder that exists, which includes a file, a drive that does not exist, a zero-length string, and a name with a wildcard or a character a name cannot hold, VB6 and 64-bit VBA raise error [76 (`&H4C`)](tb:Errors#76), *Path not found*, instead. diff --git a/docs/Reference/Default/VBA/FileSystem/ChDrive.md b/docs/Reference/Default/VBA/FileSystem/ChDrive.md index e302dce80..70217bbc9 100644 --- a/docs/Reference/Default/VBA/FileSystem/ChDrive.md +++ b/docs/Reference/Default/VBA/FileSystem/ChDrive.md @@ -24,10 +24,10 @@ Syntax: **ChDrive** *drive* | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | *drive* starts with a letter that no drive has
*drive* starts with a digit, a space or a punctuation character | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *drive* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *drive* starts with a letter that no drive has, VB6 and 64-bit VBA raise error [68 (`&H44`)](tb:Errors#68), *Device unavailable*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *drive* starts with a digit, a space or a punctuation character, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. diff --git a/docs/Reference/Default/VBA/FileSystem/CurDir.md b/docs/Reference/Default/VBA/FileSystem/CurDir.md index b1d948842..6ecef5a20 100644 --- a/docs/Reference/Default/VBA/FileSystem/CurDir.md +++ b/docs/Reference/Default/VBA/FileSystem/CurDir.md @@ -69,13 +69,13 @@ MyPath = CurDir$("D") ' Returns "D:\EXCEL". | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | *drive* starts with a letter that no drive has
*drive* starts with a digit, a space or a punctuation character | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *drive* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *drive* starts with a letter that no drive has, VB6 and 64-bit VBA raise error [68 (`&H44`)](tb:Errors#68), *Device unavailable*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *drive* starts with a digit, a space or a punctuation character, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *drive* is **Null**, VB6 and 64-bit VBA raise error [13 (`&HD`)](tb:Errors#13), *Type mismatch*, instead. diff --git a/docs/Reference/Default/VBA/FileSystem/Dir.md b/docs/Reference/Default/VBA/FileSystem/Dir.md index 38ef572b8..4bac400dd 100644 --- a/docs/Reference/Default/VBA/FileSystem/Dir.md +++ b/docs/Reference/Default/VBA/FileSystem/Dir.md @@ -52,16 +52,16 @@ A new *pathname* can be specified without first retrieving all of the file names | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | **Dir** is called without *pathname* after it has returned a zero-length string
*pathname* names a file and ends in a backslash
a name in *pathname* is longer than 255 characters | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *pathname* is **Null**
*attributes* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When **Dir** is called without *pathname* after it has returned a zero-length string, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *pathname* names a file and ends in a backslash, VB6 and 64-bit VBA raise error [52 (`&H34`)](tb:Errors#52), *Bad file name or number*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When a name in *pathname* is longer than 255 characters, VB6 and 64-bit VBA raise error [53 (`&H35`)](tb:Errors#53), *File not found*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *pathname* is **Null**, VB6 and 64-bit VBA raise error [13 (`&HD`)](tb:Errors#13), *Type mismatch*, instead. diff --git a/docs/Reference/Default/VBA/FileSystem/EOF.md b/docs/Reference/Default/VBA/FileSystem/EOF.md index f5692d577..3a720db7e 100644 --- a/docs/Reference/Default/VBA/FileSystem/EOF.md +++ b/docs/Reference/Default/VBA/FileSystem/EOF.md @@ -32,7 +32,7 @@ With files opened for **Binary** access, an attempt to read through the file by | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | *filenumber* is not the number of an open file | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *filenumber* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *filenumber* is not the number of an open file, VB6 and 64-bit VBA raise error [52 (`&H34`)](tb:Errors#52), *Bad file name or number*, instead. diff --git a/docs/Reference/Default/VBA/FileSystem/FileAttr.md b/docs/Reference/Default/VBA/FileSystem/FileAttr.md index 861a445c7..6d9bf98e2 100644 --- a/docs/Reference/Default/VBA/FileSystem/FileAttr.md +++ b/docs/Reference/Default/VBA/FileSystem/FileAttr.md @@ -39,10 +39,10 @@ The following return values indicate the file access mode: | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | *filenumber* is not the number of an open file
*returntype* is not 1, after rounding | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *filenumber* or *returntype* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *filenumber* is not the number of an open file, VB6 and 64-bit VBA raise error [52 (`&H34`)](tb:Errors#52), *Bad file name or number*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *returntype* is not 1, after rounding, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. diff --git a/docs/Reference/Default/VBA/FileSystem/FileCopy.md b/docs/Reference/Default/VBA/FileSystem/FileCopy.md index dc7443b6c..f2b7dccb6 100644 --- a/docs/Reference/Default/VBA/FileSystem/FileCopy.md +++ b/docs/Reference/Default/VBA/FileSystem/FileCopy.md @@ -36,19 +36,19 @@ A *source* that the program has open is copied when it is open for reading only: | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | *source* or *destination* is a zero-length string
*source* or *destination* holds a wildcard or a character a file name cannot hold | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *source* or *destination* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *destination* names the same file as *source*, VB6 and 64-bit VBA raise error [70 (`&H46`)](tb:Errors#70), *Permission denied*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *source* is open **For Input** with **Lock Read**, VB6 and 64-bit VBA raise error [70 (`&H46`)](tb:Errors#70), *Permission denied*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *source* is a folder, or *destination* is a folder or a read-only or hidden file, VB6 and 64-bit VBA raise error [75 (`&H4B`)](tb:Errors#75), *Path/File access error*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *source* or *destination* is a zero-length string, VB6 and 64-bit VBA raise error [75 (`&H4B`)](tb:Errors#75), *Path/File access error*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *source* or *destination* holds a wildcard or a character a file name cannot hold, VB6 and 64-bit VBA raise error [52 (`&H34`)](tb:Errors#52), *Bad file name or number*, instead. diff --git a/docs/Reference/Default/VBA/FileSystem/FileDateTime.md b/docs/Reference/Default/VBA/FileSystem/FileDateTime.md index 28b7702f3..3a742bd71 100644 --- a/docs/Reference/Default/VBA/FileSystem/FileDateTime.md +++ b/docs/Reference/Default/VBA/FileSystem/FileDateTime.md @@ -17,7 +17,7 @@ Syntax: **FileDateTime(** *pathname* **)** : *required* String expression that specifies a file name. The *pathname* may include the directory or folder, and the drive. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > Given a *pathname* with a wildcard (`*` or `?`) that matches a file or a folder, **FileDateTime** returns the date and time of the first match, which may be a hidden file or a folder, and raises no error. VB6 and 64-bit VBA raise error 52, *Bad file name or number*. @@ -28,16 +28,16 @@ Syntax: **FileDateTime(** *pathname* **)** | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | no file or folder has the name *pathname*, which includes a zero-length string, a folder named with a backslash at its end, and a name with a character a name cannot hold
the folder or drive named in *pathname* does not exist
*pathname* holds a wildcard that matches no file or folder | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *pathname* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When no file or folder has the name *pathname*, which includes a zero-length string, a folder named with a backslash at its end, and a name with a character a name cannot hold, VB6 and 64-bit VBA raise error [53 (`&H35`)](tb:Errors#53), *File not found*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When the folder or drive named in *pathname* does not exist, VB6 and 64-bit VBA raise error [76 (`&H4C`)](tb:Errors#76), *Path not found*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *pathname* holds a wildcard that matches no file or folder, VB6 and 64-bit VBA raise error [52 (`&H34`)](tb:Errors#52), *Bad file name or number*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *pathname* holds a wildcard that matches a file or folder, VB6 and 64-bit VBA raise error [52 (`&H34`)](tb:Errors#52), *Bad file name or number*; twinBASIC raises none. diff --git a/docs/Reference/Default/VBA/FileSystem/FileLen.md b/docs/Reference/Default/VBA/FileSystem/FileLen.md index 3669cb238..cf1dbcaf0 100644 --- a/docs/Reference/Default/VBA/FileSystem/FileLen.md +++ b/docs/Reference/Default/VBA/FileSystem/FileLen.md @@ -22,7 +22,7 @@ If the specified file is open when the **FileLen** function is called, the value > Use the [LOF](LOF) function to obtain the length of an open file. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > Given a *pathname* with a wildcard (`*` or `?`) that matches a file or a folder, **FileLen** returns the length of the first match, which may be a hidden file, or a folder, whose length is 0, and raises no error. VB6 and 64-bit VBA raise error 52, *Bad file name or number*. @@ -33,16 +33,16 @@ If the specified file is open when the **FileLen** function is called, the value | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | no file or folder has the name *pathname*, which includes a zero-length string, a folder named with a backslash at its end, and a name with a character a name cannot hold
the folder or drive named in *pathname* does not exist
*pathname* holds a wildcard that matches no file or folder | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *pathname* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When no file or folder has the name *pathname*, which includes a zero-length string, a folder named with a backslash at its end, and a name with a character a name cannot hold, VB6 and 64-bit VBA raise error [53 (`&H35`)](tb:Errors#53), *File not found*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When the folder or drive named in *pathname* does not exist, VB6 and 64-bit VBA raise error [76 (`&H4C`)](tb:Errors#76), *Path not found*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *pathname* holds a wildcard that matches no file or folder, VB6 and 64-bit VBA raise error [52 (`&H34`)](tb:Errors#52), *Bad file name or number*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *pathname* holds a wildcard that matches a file or folder, VB6 and 64-bit VBA raise error [52 (`&H34`)](tb:Errors#52), *Bad file name or number*; twinBASIC raises none. diff --git a/docs/Reference/Default/VBA/FileSystem/FreeFile.md b/docs/Reference/Default/VBA/FileSystem/FreeFile.md index 146258015..d261b7eac 100644 --- a/docs/Reference/Default/VBA/FileSystem/FreeFile.md +++ b/docs/Reference/Default/VBA/FileSystem/FreeFile.md @@ -26,7 +26,7 @@ Use **FreeFile** to supply a file number that is not already in use. | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | every file number from 1 to 255 is in use | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *rangenumber* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When every file number from 1 to 255 is in use, VB6 and 64-bit VBA raise error [67 (`&H43`)](tb:Errors#67), *Too many files*, instead. diff --git a/docs/Reference/Default/VBA/FileSystem/GetAttr.md b/docs/Reference/Default/VBA/FileSystem/GetAttr.md index 37205b989..626d75c66 100644 --- a/docs/Reference/Default/VBA/FileSystem/GetAttr.md +++ b/docs/Reference/Default/VBA/FileSystem/GetAttr.md @@ -46,16 +46,16 @@ A nonzero value is returned if the Archive attribute is set. | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | no file or folder has the name *pathname*, or it is a zero-length string
the folder or drive named in *pathname* does not exist
*pathname* holds a wildcard
*pathname* holds a character a name cannot hold, or names a file with a backslash at its end | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *pathname* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When no file or folder has the name *pathname*, or it is a zero-length string, VB6 and 64-bit VBA raise error [53 (`&H35`)](tb:Errors#53), *File not found*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When the folder or drive named in *pathname* does not exist, VB6 and 64-bit VBA raise error [76 (`&H4C`)](tb:Errors#76), *Path not found*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *pathname* holds a wildcard, VB6 and 64-bit VBA raise error [52 (`&H34`)](tb:Errors#52), *Bad file name or number*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *pathname* holds a character a name cannot hold, or names a file with a backslash at its end, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. diff --git a/docs/Reference/Default/VBA/FileSystem/Input.md b/docs/Reference/Default/VBA/FileSystem/Input.md index 0b2635202..d5767fba7 100644 --- a/docs/Reference/Default/VBA/FileSystem/Input.md +++ b/docs/Reference/Default/VBA/FileSystem/Input.md @@ -38,19 +38,19 @@ For files opened for **Binary** access, an attempt to read through the file usin | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | *Number* is more characters than are left in a file open for **Input**
the file is open for **Output**, **Append** or **Random**
*Number* is negative, after rounding
*FileNumber* is not the number of an open file
*Number* is 0, after rounding | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *Number* or *FileNumber* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *Number* is more characters than are left in a file open for **Input**, VB6 and 64-bit VBA raise error [62 (`&H3E`)](tb:Errors#62), *Input past end of file*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When the file is open for **Output**, **Append** or **Random**, VB6 and 64-bit VBA raise error [54 (`&H36`)](tb:Errors#54), *Bad file mode*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *Number* is negative, after rounding, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *FileNumber* is not the number of an open file, VB6 and 64-bit VBA raise error [52 (`&H34`)](tb:Errors#52), *Bad file name or number*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *Number* is 0, after rounding, VB6 and 64-bit VBA raise no error. diff --git a/docs/Reference/Default/VBA/FileSystem/InputB.md b/docs/Reference/Default/VBA/FileSystem/InputB.md index 03eaea785..67f8e764e 100644 --- a/docs/Reference/Default/VBA/FileSystem/InputB.md +++ b/docs/Reference/Default/VBA/FileSystem/InputB.md @@ -33,16 +33,16 @@ The bytes are packed into the result without any character-set translation; the | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | *Number* is more bytes than are left in a file open for **Input**
the file is open for **Output**, **Append** or **Random**
*Number* is negative, after rounding
*FileNumber* is not the number of an open file | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *Number* or *FileNumber* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *Number* is more bytes than are left in a file open for **Input**, VB6 and 64-bit VBA raise error [62 (`&H3E`)](tb:Errors#62), *Input past end of file*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When the file is open for **Output**, **Append** or **Random**, VB6 and 64-bit VBA raise error [54 (`&H36`)](tb:Errors#54), *Bad file mode*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *Number* is negative, after rounding, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *FileNumber* is not the number of an open file, VB6 and 64-bit VBA raise error [52 (`&H34`)](tb:Errors#52), *Bad file name or number*, instead. diff --git a/docs/Reference/Default/VBA/FileSystem/Kill.md b/docs/Reference/Default/VBA/FileSystem/Kill.md index 463baff3e..dc85c2f4f 100644 --- a/docs/Reference/Default/VBA/FileSystem/Kill.md +++ b/docs/Reference/Default/VBA/FileSystem/Kill.md @@ -19,7 +19,7 @@ Syntax: **Kill** *pathname* **Kill** supports the use of multiple-character (`*`) and single-character (`?`) wildcards to specify multiple files. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > **Kill** deletes a hidden or a system file, whether *pathname* names it or a wildcard matches it, and raises no error. VB6 and 64-bit VBA never delete such a file: they raise error 53, *File not found*, when *pathname* matches only hidden or system files, and otherwise delete the other files a wildcard matches and leave these. > [!NOTE] @@ -33,16 +33,16 @@ Syntax: **Kill** *pathname* | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | no file matches *pathname*, which includes a folder, a folder or drive that does not exist, and a zero-length string
the file, or a file a wildcard matches, is read-only
the file, or a file a wildcard matches, is open | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *pathname* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When no file matches *pathname*, which includes a folder, a folder or drive that does not exist, and a zero-length string, VB6 and 64-bit VBA raise error [53 (`&H35`)](tb:Errors#53), *File not found*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When the file, or a file a wildcard matches, is read-only, VB6 and 64-bit VBA raise error [75 (`&H4B`)](tb:Errors#75), *Path/File access error*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When the file, or a file a wildcard matches, is open, VB6 and 64-bit VBA raise error [55 (`&H37`)](tb:Errors#55), *File already open*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *pathname* names a hidden or system file, VB6 and 64-bit VBA raise error [53 (`&H35`)](tb:Errors#53), *File not found*; twinBASIC raises none. diff --git a/docs/Reference/Default/VBA/FileSystem/LOF.md b/docs/Reference/Default/VBA/FileSystem/LOF.md index 6bb21e135..e360480db 100644 --- a/docs/Reference/Default/VBA/FileSystem/LOF.md +++ b/docs/Reference/Default/VBA/FileSystem/LOF.md @@ -27,7 +27,7 @@ Syntax: **LOF(** *filenumber* **)** | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | *filenumber* is not the number of an open file | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *filenumber* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *filenumber* is not the number of an open file, VB6 and 64-bit VBA raise error [52 (`&H34`)](tb:Errors#52), *Bad file name or number*, instead. diff --git a/docs/Reference/Default/VBA/FileSystem/Loc.md b/docs/Reference/Default/VBA/FileSystem/Loc.md index f56831619..3e9cc92cc 100644 --- a/docs/Reference/Default/VBA/FileSystem/Loc.md +++ b/docs/Reference/Default/VBA/FileSystem/Loc.md @@ -34,7 +34,7 @@ The return value depends on the file access mode: | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | *filenumber* is not the number of an open file | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *filenumber* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *filenumber* is not the number of an open file, VB6 and 64-bit VBA raise error [52 (`&H34`)](tb:Errors#52), *Bad file name or number*, instead. diff --git a/docs/Reference/Default/VBA/FileSystem/MkDir.md b/docs/Reference/Default/VBA/FileSystem/MkDir.md index a81e44017..2299b1465 100644 --- a/docs/Reference/Default/VBA/FileSystem/MkDir.md +++ b/docs/Reference/Default/VBA/FileSystem/MkDir.md @@ -24,10 +24,10 @@ Syntax: **MkDir** *path* | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | *path* already exists, as a folder or as a file
the folder or drive that would hold *path* does not exist, or *path* is a zero-length string or a name a folder cannot have | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *path* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *path* already exists, as a folder or as a file, VB6 and 64-bit VBA raise error [75 (`&H4B`)](tb:Errors#75), *Path/File access error*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When the folder or drive that would hold *path* does not exist, or *path* is a zero-length string or a name a folder cannot have, VB6 and 64-bit VBA raise error [76 (`&H4C`)](tb:Errors#76), *Path not found*, instead. diff --git a/docs/Reference/Default/VBA/FileSystem/RmDir.md b/docs/Reference/Default/VBA/FileSystem/RmDir.md index 37e7674e7..0ca90ac08 100644 --- a/docs/Reference/Default/VBA/FileSystem/RmDir.md +++ b/docs/Reference/Default/VBA/FileSystem/RmDir.md @@ -26,10 +26,10 @@ Syntax: **RmDir** *path* | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | no folder has the name *path*, which includes a file, a zero-length string, and a name with a wildcard or a character a name cannot hold
the folder holds a file or a folder, or it is the current directory | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *path* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When no folder has the name *path*, which includes a file, a zero-length string, and a name with a wildcard or a character a name cannot hold, VB6 and 64-bit VBA raise error [76 (`&H4C`)](tb:Errors#76), *Path not found*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When the folder holds a file or a folder, or it is the current directory, VB6 and 64-bit VBA raise error [75 (`&H4B`)](tb:Errors#75), *Path/File access error*, instead. diff --git a/docs/Reference/Default/VBA/FileSystem/Seek.md b/docs/Reference/Default/VBA/FileSystem/Seek.md index 36e99799f..9e5777603 100644 --- a/docs/Reference/Default/VBA/FileSystem/Seek.md +++ b/docs/Reference/Default/VBA/FileSystem/Seek.md @@ -51,10 +51,10 @@ Performing a file-write operation after a **Seek** operation beyond the end of a | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | *filenumber* is not the number of an open file
the statement's *position* is less than 1, after rounding | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *filenumber* or *position* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *filenumber* is not the number of an open file, VB6 and 64-bit VBA raise error [52 (`&H34`)](tb:Errors#52), *Bad file name or number*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When the statement's *position* is less than 1, after rounding, VB6 and 64-bit VBA raise error [63 (`&H3F`)](tb:Errors#63), *Bad record number*, instead. diff --git a/docs/Reference/Default/VBA/FileSystem/SetAttr.md b/docs/Reference/Default/VBA/FileSystem/SetAttr.md index 52c0aa38f..b889a3387 100644 --- a/docs/Reference/Default/VBA/FileSystem/SetAttr.md +++ b/docs/Reference/Default/VBA/FileSystem/SetAttr.md @@ -39,7 +39,7 @@ The *attributes* settings are: **SetAttr** sets the attributes of a file that is open, whatever mode it is open in, without an error. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > This differs from VB6 and 64-bit VBA, which raise run-time error 55 (*File already open*) for a file open for **Output**, **Append**, **Binary** or **Random**, and leave its attributes unchanged. They set them only for a file open for **Input**. So twinBASIC can make a file read-only while the program still has it open for writing. @@ -50,13 +50,13 @@ The *attributes* settings are: | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | no file or folder has the name *pathname*, which includes one in a folder or drive that does not exist, a zero-length string, and a name with a character a name cannot hold
*pathname* holds a wildcard | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *pathname* or *attributes* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When no file or folder has the name *pathname*, which includes one in a folder or drive that does not exist, a zero-length string, and a name with a character a name cannot hold, VB6 and 64-bit VBA raise error [53 (`&H35`)](tb:Errors#53), *File not found*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *pathname* holds a wildcard, VB6 and 64-bit VBA raise error [52 (`&H34`)](tb:Errors#52), *Bad file name or number*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When the file is open for **Output**, **Append**, **Binary** or **Random**, VB6 and 64-bit VBA raise error [55 (`&H37`)](tb:Errors#55), *File already open*; twinBASIC raises none. diff --git a/docs/Reference/Default/VBA/FileSystem/Width.md b/docs/Reference/Default/VBA/FileSystem/Width.md index eb79e5bdd..b9d22c131 100644 --- a/docs/Reference/Default/VBA/FileSystem/Width.md +++ b/docs/Reference/Default/VBA/FileSystem/Width.md @@ -24,7 +24,7 @@ Syntax: **Width #** *FileNumber* **,** *Width* > VB6 and 64-bit VBA take a *Width* from 0 to 255 only, and raise error 5, *Invalid procedure call or argument*, for one above 255. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > A negative *Width* raises no error, and **Print #** then starts a new line before each item after the first. VB6 and 64-bit VBA raise error 5, *Invalid procedure call or argument*. @@ -35,10 +35,10 @@ Syntax: **Width #** *FileNumber* **,** *Width* | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | *FileNumber* is not the number of an open file | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *Width* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *FileNumber* is not the number of an open file, VB6 and 64-bit VBA raise error [52 (`&H34`)](tb:Errors#52), *Bad file name or number*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *Width* is negative, after rounding, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*; twinBASIC raises none. diff --git a/docs/Reference/Default/VBA/Financial/DDB.md b/docs/Reference/Default/VBA/Financial/DDB.md index 16603d64d..2897b843b 100644 --- a/docs/Reference/Default/VBA/Financial/DDB.md +++ b/docs/Reference/Default/VBA/Financial/DDB.md @@ -45,13 +45,13 @@ Depreciation / *period* = ((*cost* - *salvage*) * *factor*) / *life* | [13 (`&HD`)](tb:Errors#13), *Type mismatch* | *factor* is not a number or a string that holds one
*factor* is **Null** | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *cost*, *salvage*, *life* or *period* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *life* is 0 or less, *period* is 0 or less or greater than *life*, *salvage* is less than 0, or *factor* is 0 or less (**Empty** and **True** included), VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *life* is greater than 0 and less than 1, VB6 and 64-bit VBA raise no error. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *factor* is **Null**, VB6 and 64-bit VBA raise error [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null*, instead. diff --git a/docs/Reference/Default/VBA/Financial/FV.md b/docs/Reference/Default/VBA/Financial/FV.md index 9e058b7fc..9b4469463 100644 --- a/docs/Reference/Default/VBA/Financial/FV.md +++ b/docs/Reference/Default/VBA/Financial/FV.md @@ -42,10 +42,10 @@ For all arguments, cash paid out (such as deposits to savings) is represented by | [13 (`&HD`)](tb:Errors#13), *Type mismatch* | *pv* or *type* is a string that is not a number, or an error value
*pv* or *type* is **Null** | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *rate*, *nper* or *pmt* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *rate* is -1 and *nper* is less than 0, VB6 raises error [11 (`&HB`)](tb:Errors#11), *Division by zero*, instead; 64-bit VBA raises error [6 (`&H6`)](tb:Errors#6), *Overflow*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *pv* or *type* is **Null**, VB6 and 64-bit VBA raise error [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null*, instead. diff --git a/docs/Reference/Default/VBA/Financial/IPmt.md b/docs/Reference/Default/VBA/Financial/IPmt.md index 17fb20a0b..c90a15149 100644 --- a/docs/Reference/Default/VBA/Financial/IPmt.md +++ b/docs/Reference/Default/VBA/Financial/IPmt.md @@ -36,7 +36,7 @@ The *rate* and *nper* arguments must be calculated by using payment periods expr For all arguments, cash paid out (such as deposits to savings) is represented by negative numbers; cash received (such as dividend checks) is represented by positive numbers. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > Given a *per* of 0 or less, or of *nper* + 1 or more, **IPmt** returns a value computed for that period, outside the annuity, and raises no error. VB6 and 64-bit VBA raise error 5, *Invalid procedure call or argument*. @@ -48,13 +48,13 @@ For all arguments, cash paid out (such as deposits to savings) is represented by | [13 (`&HD`)](tb:Errors#13), *Type mismatch* | *fv* or *type* is a string that is not a number
*fv* or *type* is **Null** | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *rate*, *per*, *nper* or *pv* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *per* is 0 or less, or *nper* + 1 or more, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*; twinBASIC raises none. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *nper* is 0 or less, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *fv* or *type* is **Null**, VB6 and 64-bit VBA raise error [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null*, instead. diff --git a/docs/Reference/Default/VBA/Financial/IRR.md b/docs/Reference/Default/VBA/Financial/IRR.md index 80b083f7c..7fa486132 100644 --- a/docs/Reference/Default/VBA/Financial/IRR.md +++ b/docs/Reference/Default/VBA/Financial/IRR.md @@ -24,7 +24,7 @@ The **IRR** function uses the order of values within the array to interpret the **IRR** is calculated by iteration. Starting with the value of *guess*, **IRR** cycles through the calculation until the result is accurate to within 0.00001 percent, and fails if it finds none in 20 tries. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > Given an array that has never been sized (`Dim A() As Double` with no **ReDim**, or one emptied by **Erase**), **IRR** ends the program with an access violation, in a 32-bit and a 64-bit build. VB6 and 64-bit VBA raise error 5, *Invalid procedure call or argument*. @@ -37,10 +37,10 @@ The **IRR** function uses the order of values within the array to interpret the | [13 (`&HD`)](tb:Errors#13), *Type mismatch* | *guess* is a string that is not a number | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *guess* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *values* has no element less than 0 or none greater than 0, or **IRR** finds no result in 20 tries from *guess* (a *guess* of -1 or less, or far from the result, included), VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *values* has more than one dimension, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. diff --git a/docs/Reference/Default/VBA/Financial/MIRR.md b/docs/Reference/Default/VBA/Financial/MIRR.md index 1e088baf1..4605c7875 100644 --- a/docs/Reference/Default/VBA/Financial/MIRR.md +++ b/docs/Reference/Default/VBA/Financial/MIRR.md @@ -30,11 +30,11 @@ The *finance_rate* and *reinvest_rate* arguments are percentages expressed as de The **MIRR** function uses the order of values within the array to interpret the order of payments and receipts. The payment and receipt values must be in the correct sequence. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > Given an array that has never been sized (`Dim A() As Double` with no **ReDim**, or one emptied by **Erase**), **MIRR** ends the program with an access violation, in a 32-bit and a 64-bit build. VB6 and 64-bit VBA raise error 5, *Invalid procedure call or argument*. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > **MIRR** returns a value and raises no error for arguments VB6 and 64-bit VBA refuse. Given *values* with one element it returns -1, and given a *finance_rate* or *reinvest_rate* of -1 it returns a rate; VB6 and 64-bit VBA raise error 5, *Invalid procedure call or argument*. Given *values* with no element less than 0 it returns infinity or NaN (not a number); VB6 and 64-bit VBA raise error 11, *Division by zero*. @@ -45,13 +45,13 @@ The **MIRR** function uses the order of values within the array to interpret the | [-2147418113 (`&H8000FFFF`)](tb:Errors#-2147418113), *Catastrophic failure* | *values* has more than one dimension | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *finance_rate* or *reinvest_rate* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *values* has one element, or *finance_rate* or *reinvest_rate* is -1, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*; twinBASIC raises none. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *values* has no element less than 0, VB6 and 64-bit VBA raise error [11 (`&HB`)](tb:Errors#11), *Division by zero*; twinBASIC raises none. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *values* has more than one dimension, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. diff --git a/docs/Reference/Default/VBA/Financial/NPV.md b/docs/Reference/Default/VBA/Financial/NPV.md index 7b6979115..0b41a82a6 100644 --- a/docs/Reference/Default/VBA/Financial/NPV.md +++ b/docs/Reference/Default/VBA/Financial/NPV.md @@ -31,11 +31,11 @@ The net present value calculation is based on future cash flows. If the first ca The **NPV** function is similar to the [**PV**](PV) function (present value) except that the **PV** function allows cash flows to begin either at the end or the beginning of a period. Unlike the variable **NPV** cash flow values, **PV** cash flows must be fixed throughout the investment. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > Given an array that has never been sized (`Dim A() As Double` with no **ReDim**, or one emptied by **Erase**), **NPV** ends the program with an access violation, in a 32-bit and a 64-bit build. VB6 and 64-bit VBA raise error 5, *Invalid procedure call or argument*. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > Given a *rate* of -1, **NPV** returns NaN (not a number) and raises no error. VB6 and 64-bit VBA raise error 5, *Invalid procedure call or argument*. @@ -46,10 +46,10 @@ The **NPV** function is similar to the [**PV**](PV) function (present value) exc | [-2147418113 (`&H8000FFFF`)](tb:Errors#-2147418113), *Catastrophic failure* | *values* has more than one dimension | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *rate* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *rate* is -1, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*; twinBASIC raises none. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *values* has more than one dimension, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. diff --git a/docs/Reference/Default/VBA/Financial/NPer.md b/docs/Reference/Default/VBA/Financial/NPer.md index 2061e3db8..7c94a3cd9 100644 --- a/docs/Reference/Default/VBA/Financial/NPer.md +++ b/docs/Reference/Default/VBA/Financial/NPer.md @@ -41,19 +41,19 @@ For all arguments, cash paid out (such as deposits to savings) is represented by | [13 (`&HD`)](tb:Errors#13), *Type mismatch* | *fv* or *type* is a string that is not a number
*fv* or *type* is **Null** | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *rate*, *pmt* or *pv* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *rate* and *pmt* are of opposite signs, as in a loan or a savings plan, and a number of periods exists, VB6 and 64-bit VBA raise no error. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When no number of periods exists, as when *pmt* does not cover the interest, or *rate* is -1 and *pmt* is greater than 0, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *pmt* is 0 and *rate* is not 0, or *rate* is -1 and *pmt* is less than 0, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *rate* and *pmt* are both 0, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *fv* or *type* is **Null**, VB6 and 64-bit VBA raise error [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null*, instead. diff --git a/docs/Reference/Default/VBA/Financial/PPmt.md b/docs/Reference/Default/VBA/Financial/PPmt.md index 3d971bee1..013abde51 100644 --- a/docs/Reference/Default/VBA/Financial/PPmt.md +++ b/docs/Reference/Default/VBA/Financial/PPmt.md @@ -36,7 +36,7 @@ The *rate* and *nper* arguments must be calculated using payment periods express For all arguments, cash paid out (such as deposits to savings) is represented by negative numbers; cash received (such as dividend checks) is represented by positive numbers. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > Given a *per* of 0 or less, or of *nper* + 1 or more, **PPmt** returns a value computed for that period, outside the annuity, and raises no error. VB6 and 64-bit VBA raise error 5, *Invalid procedure call or argument*. @@ -49,16 +49,16 @@ For all arguments, cash paid out (such as deposits to savings) is represented by | [13 (`&HD`)](tb:Errors#13), *Type mismatch* | *fv* or *type* is a string that is not a number
*fv* or *type* is **Null** | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *rate*, *per*, *nper* or *pv* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *per* is 0 or less, or *nper* + 1 or more, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*; twinBASIC raises none. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *nper* is 0, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *nper* is less than 0, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *fv* or *type* is **Null**, VB6 and 64-bit VBA raise error [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null*, instead. diff --git a/docs/Reference/Default/VBA/Financial/PV.md b/docs/Reference/Default/VBA/Financial/PV.md index 0eaa48ad5..6d07e69d1 100644 --- a/docs/Reference/Default/VBA/Financial/PV.md +++ b/docs/Reference/Default/VBA/Financial/PV.md @@ -45,16 +45,16 @@ For all arguments, cash paid out (such as deposits to savings) is represented by | [13 (`&HD`)](tb:Errors#13), *Type mismatch* | *fv* or *type* is a string that is not a number
*fv* or *type* is **Null** | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *rate*, *nper* or *pmt* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *rate* is -1 and *nper* is 0, VB6 and 64-bit VBA raise no error. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *rate* is -1 and *nper* is greater than 0, VB6 raises no error; 64-bit VBA raises error [11 (`&HB`)](tb:Errors#11), *Division by zero*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *rate* is -1 and *nper* is less than 0, VB6 raises no error; 64-bit VBA raises error [6 (`&H6`)](tb:Errors#6), *Overflow*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *fv* or *type* is **Null**, VB6 and 64-bit VBA raise error [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null*, instead. diff --git a/docs/Reference/Default/VBA/Financial/Pmt.md b/docs/Reference/Default/VBA/Financial/Pmt.md index bbee33c0b..e96521fa4 100644 --- a/docs/Reference/Default/VBA/Financial/Pmt.md +++ b/docs/Reference/Default/VBA/Financial/Pmt.md @@ -43,13 +43,13 @@ For all arguments, cash paid out (such as deposits to savings) is represented by | [13 (`&HD`)](tb:Errors#13), *Type mismatch* | *fv* or *type* is a string that is not a number
*fv* or *type* is **Null** | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *rate*, *nper* or *pv* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *nper* is 0 and *rate* is not 0, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *nper* and *rate* are both 0, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *fv* or *type* is **Null**, VB6 and 64-bit VBA raise error [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null*, instead. diff --git a/docs/Reference/Default/VBA/Financial/Rate.md b/docs/Reference/Default/VBA/Financial/Rate.md index 07ef7cdea..060e045dd 100644 --- a/docs/Reference/Default/VBA/Financial/Rate.md +++ b/docs/Reference/Default/VBA/Financial/Rate.md @@ -36,7 +36,7 @@ For all arguments, cash paid out (such as deposits to savings) is represented by **Rate** is calculated by iteration. Starting with the value of *guess*, **Rate** cycles through the calculation until the result is accurate to within 0.00001 percent, and fails if it finds none in 20 tries. If it fails from the default of 10 percent, try a different value for *guess*. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > Given a lump sum (a *pmt* of 0) over an even number of periods, **Rate** can return a rate below -1 and raise no error: `Rate(2, 0, 100, -121)` and `Rate(4, 0, 100, -146.41)` return -2.1. VB6 returns 0.1. @@ -49,25 +49,25 @@ For all arguments, cash paid out (such as deposits to savings) is represented by | [13 (`&HD`)](tb:Errors#13), *Type mismatch* | *fv* or *type* is a string that is not a number
*fv* or *type* is **Null** | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *nper*, *pmt* or *pv* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When a rate exists (twinBASIC finds one only for a few annuities, none of them with a *pmt* other than 0), VB6 and 64-bit VBA raise no error. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When **Rate** finds no result in 20 tries from *guess* (a *guess* of -1 or less, or far from the result, included), or *nper* is 0 or less, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *nper* is less than 1 and the result is beyond the range of a **Double**, VB6 and 64-bit VBA raise error [6 (`&H6`)](tb:Errors#6), *Overflow*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *nper* is less than 1 and *pmt* is 0, VB6 and 64-bit VBA raise no error. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *guess* is **Null**, VB6 and 64-bit VBA raise error [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null*; twinBASIC raises none. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *guess* is a string that is not a number, or an error value, VB6 and 64-bit VBA raise error [13 (`&HD`)](tb:Errors#13), *Type mismatch*; twinBASIC raises none. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *fv* or *type* is **Null**, VB6 and 64-bit VBA raise error [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null*, instead. diff --git a/docs/Reference/Default/VBA/Financial/SLN.md b/docs/Reference/Default/VBA/Financial/SLN.md index 3814a1818..54acb5f73 100644 --- a/docs/Reference/Default/VBA/Financial/SLN.md +++ b/docs/Reference/Default/VBA/Financial/SLN.md @@ -32,10 +32,10 @@ The depreciation period must be expressed in the same unit as the *life* argumen | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | *life* is 0 | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *cost*, *salvage* or *life* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *life* is 0, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When the result is beyond the range of a **Double** (twinBASIC returns infinity), 64-bit VBA raises error [6 (`&H6`)](tb:Errors#6), *Overflow*; twinBASIC raises none. diff --git a/docs/Reference/Default/VBA/Financial/SYD.md b/docs/Reference/Default/VBA/Financial/SYD.md index e7f414cc3..9f88d0dbf 100644 --- a/docs/Reference/Default/VBA/Financial/SYD.md +++ b/docs/Reference/Default/VBA/Financial/SYD.md @@ -35,7 +35,7 @@ The *life* and *period* arguments must be expressed in the same units. For examp | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | *life* is 0 or less, *period* is 0 or less or greater than *life*, or *salvage* is less than 0 | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *cost*, *salvage*, *life* or *period* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *life* is 0 or less, *period* is 0 or less or greater than *life*, or *salvage* is less than 0, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. diff --git a/docs/Reference/Default/VBA/HiddenModule/AddClassInstanceFlag.md b/docs/Reference/Default/VBA/HiddenModule/AddClassInstanceFlag.md index 1aa8bc923..db46772ef 100644 --- a/docs/Reference/Default/VBA/HiddenModule/AddClassInstanceFlag.md +++ b/docs/Reference/Default/VBA/HiddenModule/AddClassInstanceFlag.md @@ -53,7 +53,7 @@ Syntax: **InternalGetReturnAddress()** **As LongPtr** The function is provided for code generated in debug mode. It uses special internal bindings, so it may not behave like a regular function. In a 32-bit build compiled without LLVM it returns a non-zero address, in an IDE run and in a built exe alike. In code compiled with LLVM it returns 0. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > A procedure that calls **InternalGetReturnAddress** fails code generation in a 64-bit build, with or without LLVM, and the project does not build. diff --git a/docs/Reference/Default/VBA/HiddenModule/ConvertIconToBitmap.md b/docs/Reference/Default/VBA/HiddenModule/ConvertIconToBitmap.md index 31dd91af0..82a155500 100644 --- a/docs/Reference/Default/VBA/HiddenModule/ConvertIconToBitmap.md +++ b/docs/Reference/Default/VBA/HiddenModule/ConvertIconToBitmap.md @@ -19,7 +19,7 @@ Syntax: **ConvertIconToBitmap(** *IconPicture* [ **,** *BackColor* ] **)** **As The returned picture is a fresh bitmap-typed **stdole.StdPicture** with the icon rasterised on top of the chosen background. The original icon picture is unchanged. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > An *IconPicture* of **Nothing** ends the program with an access violation, in a 32-bit and a 64-bit build alike. diff --git a/docs/Reference/Default/VBA/HiddenModule/Eval.md b/docs/Reference/Default/VBA/HiddenModule/Eval.md index f206541c3..30459488f 100644 --- a/docs/Reference/Default/VBA/HiddenModule/Eval.md +++ b/docs/Reference/Default/VBA/HiddenModule/Eval.md @@ -18,17 +18,17 @@ A fresh [**TbExpressionService**](../TbExpressionService/) is built for every ca For repeated evaluation of the same source, or for expressions that need to see application objects, construct the service explicitly and reuse a compiled [**ITbExpression**](../TbExpressionService/#itbexpression-interface). -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > Some malformed expressions are not refused: the part that cannot be read is left out, and no error is raised. `Eval("2 +")`, `Eval("2 Mod")`, `Eval("(2")` and `Eval("2)")` each return 2. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > **Eval** raises -2147467259 for any error inside the expression: the error's own number and description are lost. `Eval("1 / 0")` raises -2147467259, not 11. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > **Eval** also raises -2147467259 for an expression whose value is an **Error** value, such as `Eval("CVErr(5)")`, and for **Len** of **Null**, which outside an expression returns **Null**. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > In a built program, the first error the expression service raises has the description *Automation error*, and every later one *Unspecified error*. In a run from the IDE, every one has the description *Automation error*. diff --git a/docs/Reference/Default/VBA/HiddenModule/GetInheritedOwner.md b/docs/Reference/Default/VBA/HiddenModule/GetInheritedOwner.md index 6f611abc1..d66c6232a 100644 --- a/docs/Reference/Default/VBA/HiddenModule/GetInheritedOwner.md +++ b/docs/Reference/Default/VBA/HiddenModule/GetInheritedOwner.md @@ -16,7 +16,7 @@ Syntax: **GetInheritedOwner(** *Value* **)** **As Object** For controls that participate in a control-container hierarchy, the inherited owner is the topmost owning object that supplies ambient settings --- typically the form. Returns **Nothing** when no inherited owner is set. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > A *Value* of **Nothing**, or an object that is not a control, ends the program with an access violation: **Nothing** and a **Collection** in a 32-bit and a 64-bit build, a **Scripting.Dictionary** in a 64-bit build. diff --git a/docs/Reference/Default/VBA/HiddenModule/RuntimeCreateGetMessageHook.md b/docs/Reference/Default/VBA/HiddenModule/RuntimeCreateGetMessageHook.md index a06eadfd0..fff6a733d 100644 --- a/docs/Reference/Default/VBA/HiddenModule/RuntimeCreateGetMessageHook.md +++ b/docs/Reference/Default/VBA/HiddenModule/RuntimeCreateGetMessageHook.md @@ -13,7 +13,7 @@ Syntax: **RuntimeCreateGetMessageHook()** **As IGetMessageHook** The returned hook starts dormant. Subscribe a callback for one or more message types with [**RegisterMessage**](RegisterMessage), then call [**Start**](Start) to activate the subscriptions and [**Stop**](Stop) to remove them. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > Assigning the result to a **Variant** without **Set**, as in `V = RuntimeCreateGetMessageHook()`, ends the program with an access violation, in a 32-bit and a 64-bit build alike. Assign it with **Set**, or to a variable declared **As IGetMessageHook**. ### Example diff --git a/docs/Reference/Default/VBA/HiddenModule/vbaCastObj.md b/docs/Reference/Default/VBA/HiddenModule/vbaCastObj.md index e2a17b0a0..f13169454 100644 --- a/docs/Reference/Default/VBA/HiddenModule/vbaCastObj.md +++ b/docs/Reference/Default/VBA/HiddenModule/vbaCastObj.md @@ -21,7 +21,7 @@ A direct wrapper over **IUnknown::QueryInterface**, as VB6's runtime function `_ *IID* is read as the address of a **GUID**. A **String** is not converted: its text is not an interface ID, as `"{00000000-0000-0000-C000-000000000046}"` is not. A **GUID** variable passed itself, rather than its address, ends a 32-bit program with an access violation. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > **vbaCastObj** raises no error when *Obj* does not have the interface, or when *IID* is a **String**: it returns **Nothing**. @@ -31,7 +31,7 @@ A direct wrapper over **IUnknown::QueryInterface**, as VB6's runtime function `_ |---|---| | [424 (`&H1A8`)](tb:Errors#424), *Object required* | *Obj* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *Obj* does not have the interface *IID* names, or *IID* is a **String**, whose text is not read as an interface ID, VB6 raises error [13 (`&HD`)](tb:Errors#13), *Type mismatch*; twinBASIC raises none. diff --git a/docs/Reference/Default/VBA/Information/LBound.md b/docs/Reference/Default/VBA/Information/LBound.md index 0d11d52af..012fce9e7 100644 --- a/docs/Reference/Default/VBA/Information/LBound.md +++ b/docs/Reference/Default/VBA/Information/LBound.md @@ -49,10 +49,10 @@ The default lower bound for any dimension is either 0 or 1, depending on the **O | [13 (`&HD`)](tb:Errors#13), *Type mismatch* | *arrayname* is a **Variant** that holds no array, **Empty** and **Null** included | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *dimension* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *dimension* is less than 1 or greater than the number of dimensions of *arrayname*, after rounding, VB6 and 64-bit VBA raise error [9 (`&H9`)](tb:Errors#9), *Subscript out of range*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *dimension* is greater than 32,767, VB6 and 64-bit VBA raise error [6 (`&H6`)](tb:Errors#6), *Overflow*, instead. diff --git a/docs/Reference/Default/VBA/Information/UBound.md b/docs/Reference/Default/VBA/Information/UBound.md index bd13b8fc4..5ae1d4f0f 100644 --- a/docs/Reference/Default/VBA/Information/UBound.md +++ b/docs/Reference/Default/VBA/Information/UBound.md @@ -47,10 +47,10 @@ For an array `Dim A(1 To 100, 0 To 3, -3 To 4)`, **UBound** returns: | [13 (`&HD`)](tb:Errors#13), *Type mismatch* | *arrayname* is a **Variant** that holds no array, **Empty** and **Null** included | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *dimension* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *dimension* is less than 1 or greater than the number of dimensions of *arrayname*, after rounding, VB6 and 64-bit VBA raise error [9 (`&H9`)](tb:Errors#9), *Subscript out of range*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *dimension* is greater than 32,767, VB6 and 64-bit VBA raise error [6 (`&H6`)](tb:Errors#6), *Overflow*, instead. diff --git a/docs/Reference/Default/VBA/Interaction/AppActivate.md b/docs/Reference/Default/VBA/Interaction/AppActivate.md index de6dbfdf5..7661d550f 100644 --- a/docs/Reference/Default/VBA/Interaction/AppActivate.md +++ b/docs/Reference/Default/VBA/Interaction/AppActivate.md @@ -40,16 +40,16 @@ In determining which application to activate, *title* is compared to the title s | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *title* is **Null** | | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | no window has *title* as its title, or its beginning or end, and no process with *title* as its task ID has a window (0, a negative number, **True** and **Empty** included) | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When no window has *title* as its title, or its beginning or end, and no process with *title* as its task ID has a window (0, a negative number, **True** and **Empty** included), VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When the window *title* names is hidden, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*; twinBASIC raises none. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *wait* is **Null**, VB6 and 64-bit VBA raise error [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null*; twinBASIC raises none. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *wait* is a string that is not a **Boolean**, VB6 and 64-bit VBA raise error [13 (`&HD`)](tb:Errors#13), *Type mismatch*; twinBASIC raises none. diff --git a/docs/Reference/Default/VBA/Interaction/CallByName.md b/docs/Reference/Default/VBA/Interaction/CallByName.md index 4cd382ae3..5022169b9 100644 --- a/docs/Reference/Default/VBA/Interaction/CallByName.md +++ b/docs/Reference/Default/VBA/Interaction/CallByName.md @@ -27,7 +27,7 @@ Syntax: **CallByName(** *object* **,** *procname* **,** *calltype* [ **,** *args The return value is a **Variant** containing whatever the call returned. For methods that return nothing, or for property assignments, the result is **Empty**. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When the member called raises an error itself with a constant number, as in `Err.Raise 380`, or with the [**Error**](../../Core/Error) statement, **CallByName** raises error -2147352567 (`&H80020009`), *Exception occurred.*, not that number. VB6 raises the member's own number. A number the member passes in a variable, as in `Err.Raise Number`, and a run-time error such as a division by zero arrive unchanged. @@ -42,25 +42,25 @@ The return value is a **Variant** containing whatever the call returned. For met | [-2147352567 (`&H80020009`)](tb:Errors#-2147352567), *Exception occurred.* | the member raises an error of its own, such as **Add** of a **Dictionary** given a key it holds
the member raises an error of its own, such as a **Dictionary**'s **CompareMode** set when it has items | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *procname* or *calltype* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *procname* is a zero-length string, VB6 and 64-bit VBA raise error [450 (`&H1C2`)](tb:Errors#450), *Wrong number of arguments or invalid property assignment*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *calltype* is not one of the **VbCallType** values (3, **vbMethod** + **vbGet**, is accepted), VB6 and 64-bit VBA raise error [438 (`&H1B6`)](tb:Errors#438), *Object doesn't support this property or method*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *object* is **Nothing**, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When the number of *args* is not the number the member takes, or a read-only property is assigned, VB6 and 64-bit VBA raise error [450 (`&H1C2`)](tb:Errors#450), *Wrong number of arguments or invalid property assignment*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *args* are given to a property that takes none, VB6 and 64-bit VBA raise error [451 (`&H1C3`)](tb:Errors#451), *Property let procedure not defined and property get procedure did not return an object*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When the member raises an error of its own, such as **Add** of a **Dictionary** given a key it holds, VB6 and 64-bit VBA raise error [457 (`&H1C9`)](tb:Errors#457), *This key is already associated with an element of this collection*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When the member raises an error of its own, such as a **Dictionary**'s **CompareMode** set when it has items, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. diff --git a/docs/Reference/Default/VBA/Interaction/CreateObject.md b/docs/Reference/Default/VBA/Interaction/CreateObject.md index 67de0caa5..5a942eca0 100644 --- a/docs/Reference/Default/VBA/Interaction/CreateObject.md +++ b/docs/Reference/Default/VBA/Interaction/CreateObject.md @@ -45,16 +45,16 @@ If an object has registered itself as single-instance, only one instance is ever | [-2147023174 (`&H800706BA`)](tb:Errors#-2147023174), *The RPC server is unavailable.* | *servername* names a computer that cannot be reached | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *class* or *servername* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *class* is not a registered class name, such as a number, or a CLSID in braces without `new:`, VB6 and 64-bit VBA raise error [429 (`&H1AD`)](tb:Errors#429), *ActiveX component can't create object*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *class* is `new:` with a CLSID that is not registered, VB6 and 64-bit VBA raise error [429 (`&H1AD`)](tb:Errors#429), *ActiveX component can't create object*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *class* is `new:` followed by text that is not a CLSID, VB6 and 64-bit VBA raise error [429 (`&H1AD`)](tb:Errors#429), *ActiveX component can't create object*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *class* is a zero-length string, VB6 and 64-bit VBA raise error [429 (`&H1AD`)](tb:Errors#429), *ActiveX component can't create object*, instead. diff --git a/docs/Reference/Default/VBA/Interaction/DeleteSetting.md b/docs/Reference/Default/VBA/Interaction/DeleteSetting.md index 6993bbb7a..2d3889ab0 100644 --- a/docs/Reference/Default/VBA/Interaction/DeleteSetting.md +++ b/docs/Reference/Default/VBA/Interaction/DeleteSetting.md @@ -36,7 +36,7 @@ The root of these registry settings is: `Computer\HKEY_CURRENT_USER\Software\VB | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | the application, section or key named does not exist, or *appname* or *section* is a zero-length string | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | an argument is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When the application, section or key named does not exist, or *appname* or *section* is a zero-length string, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. diff --git a/docs/Reference/Default/VBA/Interaction/Environ.md b/docs/Reference/Default/VBA/Interaction/Environ.md index bb0ba75ec..93bcf1910 100644 --- a/docs/Reference/Default/VBA/Interaction/Environ.md +++ b/docs/Reference/Default/VBA/Interaction/Environ.md @@ -38,10 +38,10 @@ When called with *number*, **Environ** returns the entire entry at that position | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | *number* is less than 1 after rounding (**Empty** and **True** included), no entry has that position, or *envstring* is a zero-length string
*number* is greater than 32,767 | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *envstring* or *number* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *number* is less than 1 after rounding (**Empty** and **True** included), no entry has that position, or *envstring* is a zero-length string, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *number* is greater than 32,767, VB6 and 64-bit VBA raise error [6 (`&H6`)](tb:Errors#6), *Overflow*, instead. diff --git a/docs/Reference/Default/VBA/Interaction/GetAllSettings.md b/docs/Reference/Default/VBA/Interaction/GetAllSettings.md index 2fc65696d..36cdb5730 100644 --- a/docs/Reference/Default/VBA/Interaction/GetAllSettings.md +++ b/docs/Reference/Default/VBA/Interaction/GetAllSettings.md @@ -31,7 +31,7 @@ The root of these registry settings is: `Computer\HKEY_CURRENT_USER\Software\VB | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | *appname* or *section* is a zero-length string | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *appname* or *section* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *appname* or *section* is a zero-length string, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. diff --git a/docs/Reference/Default/VBA/Interaction/GetObject.md b/docs/Reference/Default/VBA/Interaction/GetObject.md index d9fa62ce6..387022543 100644 --- a/docs/Reference/Default/VBA/Interaction/GetObject.md +++ b/docs/Reference/Default/VBA/Interaction/GetObject.md @@ -63,19 +63,19 @@ For an object registered as single-instance, **GetObject** with the zero-length- | [-2147221020 (`&H800401E4`)](tb:Errors#-2147221020), *Invalid syntax* | *pathname* is neither a file name nor a name Windows can bind to an object | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *pathname* or *class* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *pathname* names a file that does not exist, and *class* is omitted, VB6 and 64-bit VBA raise error [432 (`&H1B0`)](tb:Errors#432), *File name or class name not found during Automation operation*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *pathname* names a file that does not exist, and *class* is given, VB6 and 64-bit VBA raise error [432 (`&H1B0`)](tb:Errors#432), *File name or class name not found during Automation operation*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *class* is not a registered class name, VB6 and 64-bit VBA raise error [429 (`&H1AD`)](tb:Errors#429), *ActiveX component can't create object*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *pathname* is omitted and no instance of *class* is running, VB6 and 64-bit VBA raise error [429 (`&H1AD`)](tb:Errors#429), *ActiveX component can't create object*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When both arguments are omitted, VB6 and 64-bit VBA raise error [13 (`&HD`)](tb:Errors#13), *Type mismatch*, instead. diff --git a/docs/Reference/Default/VBA/Interaction/GetSetting.md b/docs/Reference/Default/VBA/Interaction/GetSetting.md index 1f456d6dd..4522c4ab8 100644 --- a/docs/Reference/Default/VBA/Interaction/GetSetting.md +++ b/docs/Reference/Default/VBA/Interaction/GetSetting.md @@ -42,7 +42,7 @@ The root of these registry settings is: `Computer\HKEY_CURRENT_USER\Software\VB | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | *appname*, *section* or *key* is a zero-length string | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | an argument is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *appname*, *section* or *key* is a zero-length string, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. diff --git a/docs/Reference/Default/VBA/Interaction/InputBox.md b/docs/Reference/Default/VBA/Interaction/InputBox.md index 4a620ce87..6d1cd24b5 100644 --- a/docs/Reference/Default/VBA/Interaction/InputBox.md +++ b/docs/Reference/Default/VBA/Interaction/InputBox.md @@ -64,16 +64,16 @@ When both *helpfile* and *context* are supplied, the user can press F1 to view t | [6 (`&H6`)](tb:Errors#6), *Overflow* | *xpos* or *ypos* is outside the range of a **Long** | | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | *helpfile* is given without *context* | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *helpfile* is **Null**, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *xpos* or *ypos* is outside -32,768 to 32,767 after rounding, and inside the range of a **Long**, VB6 and 64-bit VBA raise error [6 (`&H6`)](tb:Errors#6), *Overflow*; twinBASIC raises none. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *helpfile* is given without *context*, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *context* is given without *helpfile*, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*; twinBASIC raises none. diff --git a/docs/Reference/Default/VBA/Interaction/MsgBox.md b/docs/Reference/Default/VBA/Interaction/MsgBox.md index d18444d28..1a2ca9c51 100644 --- a/docs/Reference/Default/VBA/Interaction/MsgBox.md +++ b/docs/Reference/Default/VBA/Interaction/MsgBox.md @@ -34,7 +34,7 @@ The *buttons* argument is a combination of values from the [**VbMsgBoxStyle**](. > **MsgBox** passes *buttons* to Windows as it is, so any style Windows accepts can be used. VB6 and 64-bit VBA show a plain **OK** box for a value they do not know --- `vbCancelTryAgainContinue`, two icons combined, a default-button value of &H400 --- and raise no error. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > A *buttons* value whose button group Windows does not have (7 to 15, -1 included) shows no box: **MsgBox** returns 0 and raises no error. VB6 and 64-bit VBA show an **OK** box and return **vbOK**. The return value is one of the constants from the [**VbMsgBoxResult**](../Constants/VbMsgBoxResult) enumeration, identifying which button the user clicked. @@ -52,13 +52,13 @@ If the dialog box displays a **Cancel** button, pressing the ESC key has the sam | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *prompt*, *buttons*, *title* or *context* is **Null**
*helpfile* is **Null** | | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | *helpfile* is given without *context* | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *helpfile* is **Null**, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *helpfile* is given without *context*, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *context* is given without *helpfile*, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*; twinBASIC raises none. diff --git a/docs/Reference/Default/VBA/Interaction/Partition.md b/docs/Reference/Default/VBA/Interaction/Partition.md index 5cba9b3d4..edf219a52 100644 --- a/docs/Reference/Default/VBA/Interaction/Partition.md +++ b/docs/Reference/Default/VBA/Interaction/Partition.md @@ -53,13 +53,13 @@ Any argument may be a decimal value, but is rounded to the nearest even integer |---|---| | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | *start* is less than 0, *stop* is not greater than *start*, or *interval* is less than 1, after rounding
an argument is a string that is not a number
an argument is outside the range of a **LongLong** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *start* is less than 0, *stop* is not greater than *start*, or *interval* is less than 1, after rounding, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When an argument is a string that is not a number, VB6 and 64-bit VBA raise error [13 (`&HD`)](tb:Errors#13), *Type mismatch*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When an argument is outside the range of a **LongLong**, VB6 and 64-bit VBA raise error [6 (`&H6`)](tb:Errors#6), *Overflow*, instead. diff --git a/docs/Reference/Default/VBA/Interaction/RaiseEventByName.md b/docs/Reference/Default/VBA/Interaction/RaiseEventByName.md index 6644ab18c..bb36cca80 100644 --- a/docs/Reference/Default/VBA/Interaction/RaiseEventByName.md +++ b/docs/Reference/Default/VBA/Interaction/RaiseEventByName.md @@ -26,11 +26,11 @@ Returns a **Variant**. The returned value is the value left in the last `ByRef` If *procname* doesn't match any event declared on *object*, or the supplied arity doesn't match the event declaration, a run-time error occurs. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > An *object* that declares no events, such as a **Collection** or a **Scripting.Dictionary**, ends the program with an access violation, with no error to handle. Unsaved data is lost. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > An event that no handler is attached to raises error -2147467259 (`&H80004005`), *Unspecified error*. The **RaiseEvent** statement raises nothing for it. diff --git a/docs/Reference/Default/VBA/Interaction/RaiseEventByName2.md b/docs/Reference/Default/VBA/Interaction/RaiseEventByName2.md index 8ab7be53d..e5d9d54a2 100644 --- a/docs/Reference/Default/VBA/Interaction/RaiseEventByName2.md +++ b/docs/Reference/Default/VBA/Interaction/RaiseEventByName2.md @@ -26,11 +26,11 @@ Returns a **Variant**. The returned value is the value left in the last `ByRef` If *procname* doesn't match any event declared on *object*, or the supplied arity doesn't match the event declaration, a run-time error occurs. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > An *object* that declares no events, such as a **Collection**, ends the program with an access violation, with no error to handle. Unsaved data is lost. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > An event that no handler is attached to raises error -2147467259 (`&H80004005`), *Unspecified error*. The **RaiseEvent** statement raises nothing for it. diff --git a/docs/Reference/Default/VBA/Interaction/SaveSetting.md b/docs/Reference/Default/VBA/Interaction/SaveSetting.md index 24aff5b57..401685b00 100644 --- a/docs/Reference/Default/VBA/Interaction/SaveSetting.md +++ b/docs/Reference/Default/VBA/Interaction/SaveSetting.md @@ -41,7 +41,7 @@ The root of these registry settings is: `Computer\HKEY_CURRENT_USER\Software\VB | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | *appname*, *section* or *key* is a zero-length string | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | an argument is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *appname*, *section* or *key* is a zero-length string, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. diff --git a/docs/Reference/Default/VBA/Interaction/SendKeys.md b/docs/Reference/Default/VBA/Interaction/SendKeys.md index 160d759fe..8f1f6638b 100644 --- a/docs/Reference/Default/VBA/Interaction/SendKeys.md +++ b/docs/Reference/Default/VBA/Interaction/SendKeys.md @@ -70,7 +70,7 @@ To repeat a key, use the form `{key number}` --- for example, `"{LEFT 42}"` to p > **SendKeys** can't send keystrokes to applications that aren't running in Windows, and it cannot send the PRINT SCREEN key to any application: `{PRTSC}` sends the virtual key `VK_PRINT` (&H2A), not `VK_SNAPSHOT` (&H2C), as VB6 and 64-bit VBA do. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > A character that has no key on the keyboard layout sends the wrong key, and no error is raised. The string is first converted to the ANSI code page, so a character outside it becomes its nearest ANSI character (`Ā` becomes `A`, `☺` becomes `?`), and a character the layout has no key for is sent as the virtual key of its ANSI code (`é`, on a US layout, as &HE9, which types nothing or another character). VB6 and 64-bit VBA send such a character as the character itself, which types it in any window. @@ -81,13 +81,13 @@ To repeat a key, use the form `{key number}` --- for example, `"{LEFT 42}"` to p | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *string* is **Null** | | [-2147352567 (`&H80020009`)](tb:Errors#-2147352567), *Exception occurred.* | *string* has a brace that is not closed or not opened, an unknown key code (`{INS}` and `{F17}` included), a count in braces that is not a whole number from 0 to 8,175, unbalanced parentheses, or a modifier with nothing after it (`++`)
*wait* is **Null**
*wait* is a string that is not a **Boolean** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *string* has a brace that is not closed or not opened, an unknown key code (`{INS}` and `{F17}` included), a count in braces that is not a whole number from 0 to 8,175, unbalanced parentheses, or a modifier with nothing after it (`++`), VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *wait* is **Null**, VB6 and 64-bit VBA raise error [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *wait* is a string that is not a **Boolean**, VB6 and 64-bit VBA raise error [13 (`&HD`)](tb:Errors#13), *Type mismatch*, instead. diff --git a/docs/Reference/Default/VBA/Interaction/Shell.md b/docs/Reference/Default/VBA/Interaction/Shell.md index d0d6e27f3..c0100e962 100644 --- a/docs/Reference/Default/VBA/Interaction/Shell.md +++ b/docs/Reference/Default/VBA/Interaction/Shell.md @@ -35,16 +35,16 @@ If **Shell** successfully launches the named program, it returns the new process | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *pathname* or *windowstyle* is **Null** | | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | *pathname* is a zero-length string or spaces, names a folder or a file that is not a program, or is longer than 32,767 characters
the program, or a folder in its path, does not exist, or *pathname* opens a quotation mark it does not close
*windowstyle* is less than 0 or greater than 10 after rounding | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *pathname* is a zero-length string or spaces, names a folder or a file that is not a program, or is longer than 32,767 characters, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When the program, or a folder in its path, does not exist, or *pathname* opens a quotation mark it does not close, VB6 and 64-bit VBA raise error [53 (`&H35`)](tb:Errors#53), *File not found*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *windowstyle* is less than 0 or greater than 10 after rounding, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *windowstyle* is 10 after rounding (`SW_SHOWDEFAULT`, which a new program's start-up information may not hold), VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*; twinBASIC raises none. diff --git a/docs/Reference/Default/VBA/Interaction/Switch.md b/docs/Reference/Default/VBA/Interaction/Switch.md index 3158825b6..694789e8e 100644 --- a/docs/Reference/Default/VBA/Interaction/Switch.md +++ b/docs/Reference/Default/VBA/Interaction/Switch.md @@ -34,7 +34,7 @@ The argument list consists of pairs of conditions and values. Conditions are eva | [-2147467259 (`&H80004005`)](tb:Errors#-2147467259), *Unspecified error* | the arguments are not in pairs: their number is odd | | [13 (`&HD`)](tb:Errors#13), *Type mismatch* | an *expr* is a string that is not a **Boolean** or a number | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When the arguments are not in pairs: their number is odd, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. diff --git a/docs/Reference/Default/VBA/Math/Abs.md b/docs/Reference/Default/VBA/Math/Abs.md index 6e234ee49..f2261d688 100644 --- a/docs/Reference/Default/VBA/Math/Abs.md +++ b/docs/Reference/Default/VBA/Math/Abs.md @@ -21,10 +21,7 @@ The absolute value of a number is its unsigned magnitude. For example, `Abs(-1)` | Error | When | |---|---| -| [6 (`&H6`)](tb:Errors#6), *Overflow* | *number* is a **Currency** of -922,337,203,685,477.5808 | - -> [!NOTE] BETA 1010 -> When *number* is an **Integer** of -32,768 or a **Long** of -2,147,483,648 (twinBASIC returns a **Long** or a **Double**), VB6 and 64-bit VBA raise error [6 (`&H6`)](tb:Errors#6), *Overflow*; twinBASIC raises none. +| [6 (`&H6`)](tb:Errors#6), *Overflow* | *number* is a **Currency** of -922,337,203,685,477.5808
*number* is an **Integer** of -32,768 or a **Long** of -2,147,483,648 | diff --git a/docs/Reference/Default/VBA/Math/Cos.md b/docs/Reference/Default/VBA/Math/Cos.md index ebd446f79..ae5e2ad58 100644 --- a/docs/Reference/Default/VBA/Math/Cos.md +++ b/docs/Reference/Default/VBA/Math/Cos.md @@ -18,18 +18,16 @@ The **Cos** function takes an angle and returns the ratio of two sides of a righ To convert degrees to radians, multiply degrees by pi/180. To convert radians to degrees, multiply radians by 180/pi. -> [!WARNING] BETA 1010 -> **Cos** of a number of 9,223,372,036,854,775,808 (2^63) or more in magnitude returns the number itself, with no error: `Cos(1E+20)` is `1E+20`, outside the range -1 to 1. Check the magnitude of *number* before the call. - ### Common Errors | Error | When | |---|---| | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *number* is **Null** | +| [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument* | the magnitude of *number* is 9,223,372,036,854,775,808 (2^63) or more | -> [!NOTE] BETA 1010 -> When the magnitude of *number* is 9,223,372,000,000,000,000 or more, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*; twinBASIC raises none. +> [!NOTE] BETA 1034 +> When the magnitude of *number* is 9,223,372,000,000,000,000 or more but less than 2^63, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*; twinBASIC raises none. diff --git a/docs/Reference/Default/VBA/Math/Round.md b/docs/Reference/Default/VBA/Math/Round.md index cfdf72e16..d8e24aca7 100644 --- a/docs/Reference/Default/VBA/Math/Round.md +++ b/docs/Reference/Default/VBA/Math/Round.md @@ -28,9 +28,6 @@ Syntax: **Round(** *expression* [ **,** *numdecimalplaces* ] **)** | [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument* | *numdecimalplaces* rounds to a negative number
*numdecimalplaces* is greater than 22 and *expression* is a **Single**, **Double** or **Date**, or a string | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *numdecimalplaces* is **Null** | -> [!NOTE] BETA 1010 -> When *expression* is **Null** and *numdecimalplaces* is negative, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*; twinBASIC raises none. - ### Example diff --git a/docs/Reference/Default/VBA/Math/ShiftRotateLeft.md b/docs/Reference/Default/VBA/Math/ShiftRotateLeft.md index 9ec36d182..a4bf0076a 100644 --- a/docs/Reference/Default/VBA/Math/ShiftRotateLeft.md +++ b/docs/Reference/Default/VBA/Math/ShiftRotateLeft.md @@ -24,7 +24,7 @@ Unlike the [**\<<**](../../Core/LeftShift) operator, which discards the bits shi With constant arguments, the call is evaluated when the code is compiled, so it can be used in a [**Const**](../../Core/Const) declaration, an **Enum** member's value or a **Case** expression. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > **ShiftRotateLeft** ignores the names of named arguments and takes its arguments in the order they are written: `ShiftRotateLeft(ShiftAmount:=4, Number:=&H12345678)` rotates 4 left by `&H12345678` bits, and gives the **Integer** `&H400`. Write the arguments by position, *number* first. > [!NOTE] diff --git a/docs/Reference/Default/VBA/Math/ShiftRotateRight.md b/docs/Reference/Default/VBA/Math/ShiftRotateRight.md index 608686e70..ccef32ee4 100644 --- a/docs/Reference/Default/VBA/Math/ShiftRotateRight.md +++ b/docs/Reference/Default/VBA/Math/ShiftRotateRight.md @@ -24,7 +24,7 @@ Unlike the [**\>>**](../../Core/RightShift) operator, which discards the bits sh With constant arguments, the call is evaluated when the code is compiled, so it can be used in a [**Const**](../../Core/Const) declaration, an **Enum** member's value or a **Case** expression. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > **ShiftRotateRight** ignores the names of named arguments and takes its arguments in the order they are written: `ShiftRotateRight(ShiftAmount:=4, Number:=&H12345678)` rotates 4 right by `&H12345678` bits. Write the arguments by position, *number* first. > [!NOTE] diff --git a/docs/Reference/Default/VBA/Math/ShiftUnsignedRight.md b/docs/Reference/Default/VBA/Math/ShiftUnsignedRight.md index cbc430786..6a917b48d 100644 --- a/docs/Reference/Default/VBA/Math/ShiftUnsignedRight.md +++ b/docs/Reference/Default/VBA/Math/ShiftUnsignedRight.md @@ -24,7 +24,7 @@ A shift by as many bits as the type holds, or more, gives 0. A negative *shiftam With constant arguments, the call is evaluated when the code is compiled, so it can be used in a [**Const**](../../Core/Const) declaration, an **Enum** member's value or a **Case** expression. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > **ShiftUnsignedRight** ignores the names of named arguments and takes its arguments in the order they are written: `ShiftUnsignedRight(ShiftAmount:=4, Number:=&H12345678)` shifts 4 right by `&H12345678` bits, and gives 0. Write the arguments by position, *number* first. > [!NOTE] diff --git a/docs/Reference/Default/VBA/Math/Sin.md b/docs/Reference/Default/VBA/Math/Sin.md index bb670465b..75081c3b5 100644 --- a/docs/Reference/Default/VBA/Math/Sin.md +++ b/docs/Reference/Default/VBA/Math/Sin.md @@ -18,18 +18,16 @@ The **Sin** function takes an angle and returns the ratio of two sides of a righ To convert degrees to radians, multiply degrees by pi/180. To convert radians to degrees, multiply radians by 180/pi. -> [!WARNING] BETA 1010 -> **Sin** of a number of 9,223,372,036,854,775,808 (2^63) or more in magnitude returns the number itself, with no error: `Sin(1E+20)` is `1E+20`, outside the range -1 to 1. Check the magnitude of *number* before the call. - ### Common Errors | Error | When | |---|---| | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *number* is **Null** | +| [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument* | the magnitude of *number* is 9,223,372,036,854,775,808 (2^63) or more | -> [!NOTE] BETA 1010 -> When the magnitude of *number* is 9,223,372,000,000,000,000 or more, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*; twinBASIC raises none. +> [!NOTE] BETA 1034 +> When the magnitude of *number* is 9,223,372,000,000,000,000 or more but less than 2^63, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*; twinBASIC raises none. diff --git a/docs/Reference/Default/VBA/Math/Tan.md b/docs/Reference/Default/VBA/Math/Tan.md index b59e8967e..11c3a405a 100644 --- a/docs/Reference/Default/VBA/Math/Tan.md +++ b/docs/Reference/Default/VBA/Math/Tan.md @@ -26,10 +26,10 @@ To convert degrees to radians, multiply degrees by pi/180. To convert radians to | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *number* is **Null** | | [-2147348207 (`&H80021111`)](tb:Errors#-2147348207), *Automation error* | the magnitude of *number* is 9,223,372,036,854,775,808 (2^63) or more | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When the magnitude of *number* is 9,223,372,036,854,775,808 (2^63) or more, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*, instead. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When the magnitude of *number* is 9,223,372,000,000,000,000 or more but less than 2^63, VB6 and 64-bit VBA raise error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*; twinBASIC raises none. diff --git a/docs/Reference/Default/VBA/Strings/Filter.md b/docs/Reference/Default/VBA/Strings/Filter.md index 443961cb5..b7a816e85 100644 --- a/docs/Reference/Default/VBA/Strings/Filter.md +++ b/docs/Reference/Default/VBA/Strings/Filter.md @@ -48,7 +48,7 @@ The array returned by the **Filter** function contains only enough elements to c | [13 (`&HD`)](tb:Errors#13), *Type mismatch* | *sourcearray* is **Null**, or has more than one dimension | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *match*, *include* or *compare* is **Null**, or an element of *sourcearray* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *compare* is 2 (**vbDatabaseCompare**), VB6 raises error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*; twinBASIC raises none. diff --git a/docs/Reference/Default/VBA/Strings/Format.md b/docs/Reference/Default/VBA/Strings/Format.md index e1ab23d2a..246cb5232 100644 --- a/docs/Reference/Default/VBA/Strings/Format.md +++ b/docs/Reference/Default/VBA/Strings/Format.md @@ -58,7 +58,7 @@ The *firstWeekOfYear* argument has these settings. When formatting a number without specifying *format*, **Format** provides functionality similar to the **Str** function, although it is internationally aware. However, positive numbers formatted as strings using **Format** don't include a leading space reserved for the sign of the value; those converted using **Str** retain the leading space. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > A 64-bit build formats a negative zero of type **Single** or **Double** (a value that equals 0 and has its sign bit set) as `"-0"` when *format* is omitted. A 32-bit build and VB6 give `"0"`. When formatting a non-localized numeric string, use a user-defined numeric format to ensure the desired appearance. diff --git a/docs/Reference/Default/VBA/Strings/InStr.md b/docs/Reference/Default/VBA/Strings/InStr.md index a03718fb7..29e2f9230 100644 --- a/docs/Reference/Default/VBA/Strings/InStr.md +++ b/docs/Reference/Default/VBA/Strings/InStr.md @@ -59,10 +59,10 @@ The **InStrB** function is used with byte data contained in a string. Instead of | [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument* | *start* is less than 1
**InStr** is given a *start* greater than 1,073,741,824
**InStr** is given a *compare* that is less than 0, or greater than 1 and not a valid locale identifier
**InStr** is given a *compare* that is not a valid locale identifier, and *string1* or *string2* is zero-length | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *start* or *compare* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When **InStr** is given a *compare* that is not a valid locale identifier, and *string1* or *string2* is zero-length, VB6 and 64-bit VBA raise no error. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When **InStr** is given a *compare* of 2 (**vbDatabaseCompare**), VB6 raises error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*; twinBASIC raises none. diff --git a/docs/Reference/Default/VBA/Strings/Len.md b/docs/Reference/Default/VBA/Strings/Len.md index 89cc00293..bd6dbc5c6 100644 --- a/docs/Reference/Default/VBA/Strings/Len.md +++ b/docs/Reference/Default/VBA/Strings/Len.md @@ -28,7 +28,7 @@ One (and only one) of the two possible arguments must be specified. With user-de > [!NOTE] > **Len** may not be able to determine the actual number of storage bytes required when used with variable-length strings in user-defined data types. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > **Len** and **LenB** accept an array as *varname* and return a number that is not its size in bytes: 40 for a fixed-size one-dimensional array of any element type or length in a 32-bit build (48 in a 64-bit build), 48 (56) for two dimensions, and 4 (8) for a dynamic array. VB6 refuses an array here when the program is compiled. Multiply the element count by the size of one element instead: `(UBound(Buffer) - LBound(Buffer) + 1) * LenB(Buffer(LBound(Buffer)))`. ### Example diff --git a/docs/Reference/Default/VBA/Strings/StrComp.md b/docs/Reference/Default/VBA/Strings/StrComp.md index be61eda11..e494fff94 100644 --- a/docs/Reference/Default/VBA/Strings/StrComp.md +++ b/docs/Reference/Default/VBA/Strings/StrComp.md @@ -48,7 +48,7 @@ The *compare* argument settings are: | [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument* | *compare* is less than 0, or greater than 1 and not a valid locale identifier | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *compare* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *compare* is 2 (**vbDatabaseCompare**), VB6 raises error [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument*; twinBASIC raises none. diff --git a/docs/Reference/Default/VBA/Strings/StrConv.md b/docs/Reference/Default/VBA/Strings/StrConv.md index 4f8147892..b23ce43d6 100644 --- a/docs/Reference/Default/VBA/Strings/StrConv.md +++ b/docs/Reference/Default/VBA/Strings/StrConv.md @@ -57,7 +57,7 @@ When converting from a **Byte** array in ANSI format to a string, use the **StrC | [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument* | *conversion* is a value the table does not list, or combines **vbUnicode**, **vbFromUnicode**, **vbUTF8** or **vbFromUTF8** with another value
*conversion* includes **vbWide**, **vbNarrow**, **vbKatakana** or **vbHiragana** on a system whose locale is not East Asian
*conversion* includes **vbWide**, **vbNarrow**, **vbKatakana** or **vbHiragana** and *LCID* names an East Asian locale, on a system whose locale is not East Asian
*LCID* is not a valid locale identifier | | [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *conversion* or *LCID* is **Null** | -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When *conversion* includes **vbWide**, **vbNarrow**, **vbKatakana** or **vbHiragana** and *LCID* names an East Asian locale, on a system whose locale is not East Asian, VB6 and 64-bit VBA raise no error. diff --git a/docs/Reference/Default/VBA/Strings/String.md b/docs/Reference/Default/VBA/Strings/String.md index 200bb2c98..4c3622fc6 100644 --- a/docs/Reference/Default/VBA/Strings/String.md +++ b/docs/Reference/Default/VBA/Strings/String.md @@ -15,7 +15,7 @@ Syntax: **String$(** *number*, *character* **)**, **String(** *number*, *charact : *required* **Long**. Length of the returned string. *character* -: *required* **Variant**. Character code specifying the character or string expression whose first character is used to build the return string. +: *required* **Variant**. Character code specifying the character or string expression whose first character is used to build the return string. If *character* contains **Null**, **String** returns **Null**; **String$** raises error 94. The `$`-suffixed form returns a **String**; the unsuffixed form returns a **Variant** (**String**). @@ -27,10 +27,7 @@ When *character* is a number greater than 255, **String** converts the number to | Error | When | |---|---| | [5 (`&H5`)](tb:Errors#5), *Invalid procedure call or argument* | *number* is negative, or greater than 1,073,741,823
*character* is a zero-length string | -| [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *number* is **Null**, or **String$** is given a *character* that is **Null**
**String** is given a *character* that is **Null** | - -> [!NOTE] BETA 1010 -> When **String** is given a *character* that is **Null**, VB6 and 64-bit VBA raise no error. +| [94 (`&H5E`)](tb:Errors#94), *Invalid use of Null* | *number* is **Null**, or **String$** is given a *character* that is **Null** | diff --git a/docs/Reference/Default/VBA/TbExpressionService/AddCustomBinder.md b/docs/Reference/Default/VBA/TbExpressionService/AddCustomBinder.md index e595e9efd..7b241884a 100644 --- a/docs/Reference/Default/VBA/TbExpressionService/AddCustomBinder.md +++ b/docs/Reference/Default/VBA/TbExpressionService/AddCustomBinder.md @@ -23,7 +23,7 @@ Use **AddCustomBinder** when [**AddCustomBinderObject**](AddCustomBinderObject) The engine calls [**Bind**](Bind) on each registered custom binder during compilation, once per unresolved symbol. The first binder that returns a non-**Nothing** result wins. Multiple custom binders can be registered with one service, and they are consulted in registration order. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > A *customBinder* of **Nothing** ends the program with an access violation, in a 32-bit and a 64-bit build alike. ### Example diff --git a/docs/Reference/Default/VBA/TbExpressionService/AddCustomBinderObject.md b/docs/Reference/Default/VBA/TbExpressionService/AddCustomBinderObject.md index c0840774c..7073273ef 100644 --- a/docs/Reference/Default/VBA/TbExpressionService/AddCustomBinderObject.md +++ b/docs/Reference/Default/VBA/TbExpressionService/AddCustomBinderObject.md @@ -29,7 +29,7 @@ Member resolution is performed by name through the standard COM/IDispatch protoc Multiple objects can be bound to the same service, each under its own *name*. They are consulted in the order they were added. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > An *object* of **Nothing** ends the program with an access violation, in a 32-bit and a 64-bit build alike. diff --git a/docs/Reference/Default/VBA/TbExpressionService/Compile.md b/docs/Reference/Default/VBA/TbExpressionService/Compile.md index 9a386c3c8..40676644e 100644 --- a/docs/Reference/Default/VBA/TbExpressionService/Compile.md +++ b/docs/Reference/Default/VBA/TbExpressionService/Compile.md @@ -27,10 +27,10 @@ Compilation is the relatively expensive step; evaluation reuses the compiled for An expression that compiles can still fail when it is evaluated: `"1 / 0"` compiles, and [**Evaluate**](Evaluate) raises the error. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > Some malformed expressions are not refused: the part that cannot be read is left out, and no error is raised. `"2 +"`, `"2 Mod"`, `"(2"` and `"2)"` each compile to an expression whose value is 2. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > In a built program, the first error the expression service raises has the description *Automation error*, and every later one *Unspecified error*. In a run from the IDE, every one has the description *Automation error*. diff --git a/docs/Reference/Default/VBA/TbExpressionService/Evaluate.md b/docs/Reference/Default/VBA/TbExpressionService/Evaluate.md index 268389e28..7d2410683 100644 --- a/docs/Reference/Default/VBA/TbExpressionService/Evaluate.md +++ b/docs/Reference/Default/VBA/TbExpressionService/Evaluate.md @@ -22,10 +22,10 @@ Each call re-runs the expression against the current state of its bindings. If t A run-time error raised inside the expression --- division by zero, type mismatch, an invalid call into a bound object --- propagates out of **Evaluate** like any other run-time error. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > **Evaluate** raises -2147467259 for any error inside the expression: the error's own number and description are lost. `"1 / 0"` raises -2147467259, not 11, and a **Scripting.Dictionary**'s **Add** of a key it holds raises -2147467259, not 457. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > In a built program, the first error the expression service raises has the description *Automation error*, and every later one *Unspecified error*. In a run from the IDE, every one has the description *Automation error*. An error of a bound object's member has the description *Automation error* in both. diff --git a/docs/Reference/Default/VBRUN/ErrorCallstack/index.md b/docs/Reference/Default/VBRUN/ErrorCallstack/index.md index 6265d33d5..2005f5189 100644 --- a/docs/Reference/Default/VBRUN/ErrorCallstack/index.md +++ b/docs/Reference/Default/VBRUN/ErrorCallstack/index.md @@ -14,7 +14,7 @@ An **ErrorCallstack** object is a snapshot of the chain of procedures that were The snapshot is read through the **Callstack** property of an **ErrorContext** object, which is itself accessible from the structured error-handling machinery --- typically inside a `Catch` block or an **On Error** handler. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > twinBASIC code cannot obtain an **ErrorCallstack** object, because it cannot obtain the [**ErrorContext**](../ErrorContext) that returns one. The sample below compiles, but no code can supply its argument. ```tb check_build diff --git a/docs/Reference/Default/VBRUN/ErrorContext/index.md b/docs/Reference/Default/VBRUN/ErrorContext/index.md index 93c97b642..f8a7258c3 100644 --- a/docs/Reference/Default/VBRUN/ErrorContext/index.md +++ b/docs/Reference/Default/VBRUN/ErrorContext/index.md @@ -14,7 +14,7 @@ An **ErrorContext** object captures everything the runtime knows about a run-tim The error-identity properties (**Number**, **Description**, **Source**, **HelpFile**, **HelpContext**, **LastDLLError**) have the same meaning here as on the **Err** object --- see the [**ErrObject**](../../../Modules/ErrObject) module for a discussion of each. **State** and **Callstack** are unique to **ErrorContext** and reflect the structured error-handling machinery that has no equivalent on the legacy **Err** object. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > twinBASIC code cannot obtain an **ErrorContext** object. The **ErrEx** object that returns one is not available, and neither are the **Try**, **Catch** and **Finally** blocks that several [**State**](#state) values refer to. The **Err** object does not implement this interface. The VBRUN package declares the interface and its members, so code that uses them compiles, but nothing returns an object that implements them. > > ```tb check_build expect-error=TB5079 diff --git a/docs/Reference/Default/VBRUN/ErrorStackFrame/index.md b/docs/Reference/Default/VBRUN/ErrorStackFrame/index.md index 345df999b..e9ca0bd7a 100644 --- a/docs/Reference/Default/VBRUN/ErrorStackFrame/index.md +++ b/docs/Reference/Default/VBRUN/ErrorStackFrame/index.md @@ -12,7 +12,7 @@ has_toc: false An **ErrorStackFrame** describes one procedure that was active on the call stack at the moment a run-time error was raised --- the project it belongs to, the module that contains it, and its own name. Frames are produced by iterating an [**ErrorCallstack**](../ErrorCallstack) snapshot, which in turn is reachable from the [**Callstack**](../ErrorContext#callstack) property of an [**ErrorContext**](../ErrorContext). Every property is read-only. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > twinBASIC code cannot obtain an **ErrorStackFrame** object, because it cannot obtain the [**ErrorContext**](../ErrorContext) that leads to one. The sample below compiles, but no code can supply its argument. ```tb check_build diff --git a/docs/Reference/Literal-Keywords.md b/docs/Reference/Literal-Keywords.md index 801d13716..75d1b02e6 100644 --- a/docs/Reference/Literal-Keywords.md +++ b/docs/Reference/Literal-Keywords.md @@ -30,10 +30,10 @@ vba_attribution: true Test for it with [**Is**](../tB/Core/Is), `Items Is Nothing`, or with twinBASIC's [**IsNot**](../tB/Core/IsNot), `Items IsNot Nothing`. Two more forms are twinBASIC's own: an initializer in a declaration, `Dim Items As Collection = Nothing`, needs no **Set**, and [**ByVal Nothing**](../Features/Language/Pointers#byval-nothing) passes a null pointer for a **ByRef** interface parameter. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > **Nothing** assigned without **Set** to a variable that is not an object or a **Variant**, such as `Count = Nothing` for a **Long**, and **Nothing** compared with `=` or `<>`, such as `If Obj = Nothing`, compile and raise error [91](tb:Errors#91) when they run. VB6 and 64-bit VBA refuse them when they compile, with *Invalid use of object*. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > A **Variant** assigned **Nothing** without **Set**, `v = Nothing`, holds **Nothing**, as after `Set v = Nothing`, and no error is raised. VB6 and 64-bit VBA raise error [91](tb:Errors#91) and leave the **Variant** **Empty**. [**Object**](../tB/Core/Object#assigning) describes assigning and comparing object references. diff --git a/docs/Reference/Operators.md b/docs/Reference/Operators.md index d5958ce74..953d178e0 100644 --- a/docs/Reference/Operators.md +++ b/docs/Reference/Operators.md @@ -147,7 +147,7 @@ A result that does not fit its type raises error 6, *Overflow*. Some combination | **Date** `+` **Integer** | **Date** | | **String** `+` **Integer** | **Double**: `"34" + 6` is 40 | -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > An addition, subtraction, multiplication or division whose result is a declared **Single** raises no error when the result overflows: the **Single** holds infinity (`1.#INF`) instead. VB6 raises error 6, *Overflow*. Declare the variable **Double** where an overflow must be caught. Code compiled with LLVM has a similar defect for **Double**; see [Language support](../LLVM/Getting-Started#language-support). ### Variant operands diff --git a/docs/Reference/Run-time-Errors.md b/docs/Reference/Run-time-Errors.md index 05ab539a5..50053f367 100644 --- a/docs/Reference/Run-time-Errors.md +++ b/docs/Reference/Run-time-Errors.md @@ -32,11 +32,11 @@ Each number is given in decimal and in hexadecimal, as a 32-bit **Long**: 5 (`&H The message is the one that **Err.Description** gives for the error once it is raised. For the numbers from 1 to 65535, [**Error**](../tB/Modules/Conversion/Error)`(number)` gives the same text. For an HRESULT, **Error** gives *Application-defined or object-defined error*, as it does for any number without a message of its own, and the table shows both. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > For a number from 1 to 65535 that has no message of its own, **Error** gives *Application-defined or object-defined error* in twinBASIC and VB6. After `Err.Raise` of such a number, **Err.Description** in VB6 is that same text; in twinBASIC it is empty for a number below 747, and for 747 and above it is the Windows system message of that number, such as *Page fault was a transition fault.* for 747. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > `Err.Raise` of an HRESULT that has a VB error of the same meaning, such as `&H80070057` (`E_INVALIDARG`), raises the HRESULT in twinBASIC. VB6 and 64-bit VBA raise the VB error instead, 5 for that one. [**Raise**](../tB/Modules/ErrObject/Raise) lists them. ## Error numbers @@ -48,8 +48,8 @@ The messages are those of twinBASIC 32-bit and VB6, which agree for every number | Error | Message | Listed by | |---|---|---| | **3**{: #3 } (`&H3`) | *Return without GoSub* | --- | -| **5**{: #5 } (`&H5`) | *Invalid procedure call or argument* | [46 pages](#listed-5) | -| **6**{: #6 } (`&H6`) | *Overflow* | [27 pages](#listed-6) | +| **5**{: #5 } (`&H5`) | *Invalid procedure call or argument* | [48 pages](#listed-5) | +| **6**{: #6 } (`&H6`) | *Overflow* | [26 pages](#listed-6) | | **7**{: #7 } (`&H7`) | *Out of memory* | --- | | **9**{: #9 } (`&H9`) | *Subscript out of range* | [7 pages](#listed-9) | | **10**{: #10 } (`&HA`) | *This array is fixed or temporarily locked* | --- | @@ -137,7 +137,7 @@ The messages are those of twinBASIC 32-bit and VB6, which agree for every number | **-2147467263**{: #-2147467263 } (`&H80004001`) | *Not implemented*; **Error** gives *Application-defined or object-defined error* | [1 page](#listed--2147467263) | | **-2147467262**{: #-2147467262 } (`&H80004002`) | *No such interface supported*; **Error** gives *Application-defined or object-defined error* | [3 pages](#listed--2147467262) | | **-2147467261**{: #-2147467261 } (`&H80004003`) | *Invalid pointer*; **Error** gives *Application-defined or object-defined error* | [1 page](#listed--2147467261) | -| **-2147467259**{: #-2147467259 } (`&H80004005`) | *Unspecified error*; **Error** gives *Application-defined or object-defined error* | [56 pages](#listed--2147467259) | +| **-2147467259**{: #-2147467259 } (`&H80004005`) | *Unspecified error*; **Error** gives *Application-defined or object-defined error* | [55 pages](#listed--2147467259) | | **-2147418113**{: #-2147418113 } (`&H8000FFFF`) | *Catastrophic failure*; **Error** gives *Application-defined or object-defined error* | [4 pages](#listed--2147418113) | | **-2147352567**{: #-2147352567 } (`&H80020009`) | *Exception occurred.*; **Error** gives *Application-defined or object-defined error* | [2 pages](#listed--2147352567) | | **-2147352562**{: #-2147352562 } (`&H8002000E`) | *Invalid number of parameters.*; **Error** gives *Application-defined or object-defined error* | [2 pages](#listed--2147352562) | @@ -154,9 +154,9 @@ The messages are those of twinBASIC 32-bit and VB6, which agree for every number ## Pages that list each error -**5**{: #listed-5 } (`&H5`): [Add](../tB/Modules/Collection/Add), [Array](../tB/Modules/Information/Array), [Asc](../tB/Modules/Strings/Asc), [Chr](../tB/Modules/Strings/Chr), [ConvertIconToBitmap](../tB/Modules/HiddenModule/ConvertIconToBitmap), [CreateObject](../tB/Modules/Interaction/CreateObject), [CVErr](../tB/Modules/Conversion/CVErr), [DateAdd](../tB/Modules/DateTime/DateAdd), [DateDiff](../tB/Modules/DateTime/DateDiff), [DatePart](../tB/Modules/DateTime/DatePart), [DateSerial](../tB/Modules/DateTime/DateSerial), [Filter](../tB/Modules/Strings/Filter), [Format](../tB/Modules/Strings/Format), [FormatCurrency](../tB/Modules/Strings/FormatCurrency), [FormatDateTime](../tB/Modules/Strings/FormatDateTime), [FormatNumber](../tB/Modules/Strings/FormatNumber), [FormatPercent](../tB/Modules/Strings/FormatPercent), [FV](../tB/Modules/Financial/FV), [GetObject](../tB/Modules/Interaction/GetObject), [InStr](../tB/Modules/Strings/InStr), [InStrRev](../tB/Modules/Strings/InStrRev), [Item](../tB/Modules/Collection/Item), [Join](../tB/Modules/Strings/Join), [KeyCompareMode](../tB/Modules/Collection/KeyCompareMode), [Left](../tB/Modules/Strings/Left), [Log](../tB/Modules/Math/Log), [Mid](../tB/Modules/Strings/Mid), [MonthName](../tB/Modules/Strings/MonthName), [QBColor](../tB/Modules/Information/QBColor), [Raise](../tB/Modules/ErrObject/Raise), [Rate](../tB/Modules/Financial/Rate), [Remove](../tB/Modules/Collection/Remove), [Replace](../tB/Modules/Strings/Replace), [RGB](../tB/Modules/Information/RGB), [RGBA](../tB/Modules/Information/RGBA), [Right](../tB/Modules/Strings/Right), [Round](../tB/Modules/Math/Round), [Space](../tB/Modules/Strings/Space), [Split](../tB/Modules/Strings/Split), [Sqr](../tB/Modules/Math/Sqr), [StrComp](../tB/Modules/Strings/StrComp), [StrConv](../tB/Modules/Strings/StrConv), [String](../tB/Modules/Strings/String), [TranslateColor](../tB/Modules/Information/TranslateColor), [Weekday](../tB/Modules/DateTime/Weekday), [WeekdayName](../tB/Modules/Strings/WeekdayName) +**5**{: #listed-5 } (`&H5`): [Add](../tB/Modules/Collection/Add), [Array](../tB/Modules/Information/Array), [Asc](../tB/Modules/Strings/Asc), [Chr](../tB/Modules/Strings/Chr), [ConvertIconToBitmap](../tB/Modules/HiddenModule/ConvertIconToBitmap), [Cos](../tB/Modules/Math/Cos), [CreateObject](../tB/Modules/Interaction/CreateObject), [CVErr](../tB/Modules/Conversion/CVErr), [DateAdd](../tB/Modules/DateTime/DateAdd), [DateDiff](../tB/Modules/DateTime/DateDiff), [DatePart](../tB/Modules/DateTime/DatePart), [DateSerial](../tB/Modules/DateTime/DateSerial), [Filter](../tB/Modules/Strings/Filter), [Format](../tB/Modules/Strings/Format), [FormatCurrency](../tB/Modules/Strings/FormatCurrency), [FormatDateTime](../tB/Modules/Strings/FormatDateTime), [FormatNumber](../tB/Modules/Strings/FormatNumber), [FormatPercent](../tB/Modules/Strings/FormatPercent), [FV](../tB/Modules/Financial/FV), [GetObject](../tB/Modules/Interaction/GetObject), [InStr](../tB/Modules/Strings/InStr), [InStrRev](../tB/Modules/Strings/InStrRev), [Item](../tB/Modules/Collection/Item), [Join](../tB/Modules/Strings/Join), [KeyCompareMode](../tB/Modules/Collection/KeyCompareMode), [Left](../tB/Modules/Strings/Left), [Log](../tB/Modules/Math/Log), [Mid](../tB/Modules/Strings/Mid), [MonthName](../tB/Modules/Strings/MonthName), [QBColor](../tB/Modules/Information/QBColor), [Raise](../tB/Modules/ErrObject/Raise), [Rate](../tB/Modules/Financial/Rate), [Remove](../tB/Modules/Collection/Remove), [Replace](../tB/Modules/Strings/Replace), [RGB](../tB/Modules/Information/RGB), [RGBA](../tB/Modules/Information/RGBA), [Right](../tB/Modules/Strings/Right), [Round](../tB/Modules/Math/Round), [Sin](../tB/Modules/Math/Sin), [Space](../tB/Modules/Strings/Space), [Split](../tB/Modules/Strings/Split), [Sqr](../tB/Modules/Math/Sqr), [StrComp](../tB/Modules/Strings/StrComp), [StrConv](../tB/Modules/Strings/StrConv), [String](../tB/Modules/Strings/String), [TranslateColor](../tB/Modules/Information/TranslateColor), [Weekday](../tB/Modules/DateTime/Weekday), [WeekdayName](../tB/Modules/Strings/WeekdayName) -**6**{: #listed-6 } (`&H6`): [Abs](../tB/Modules/Math/Abs), [Bin](../tB/Modules/Conversion/Bin), [CBool](../tB/Modules/Conversion/CBool), [CByte](../tB/Modules/Conversion/CByte), [CCur](../tB/Modules/Conversion/CCur), [CDate](../tB/Modules/Conversion/CDate), [CDbl](../tB/Modules/Conversion/CDbl), [CDec](../tB/Modules/Conversion/CDec), [Choose](../tB/Modules/Interaction/Choose), [Chr](../tB/Modules/Strings/Chr), [CInt](../tB/Modules/Conversion/CInt), [CLng](../tB/Modules/Conversion/CLng), [CLngLng](../tB/Modules/Conversion/CLngLng), [CLngPtr](../tB/Modules/Conversion/CLngPtr), [CSng](../tB/Modules/Conversion/CSng), [CType](../tB/Modules/Conversion/CType), [DateAdd](../tB/Modules/DateTime/DateAdd), [DateDiff](../tB/Modules/DateTime/DateDiff), [Exp](../tB/Modules/Math/Exp), [FV](../tB/Modules/Financial/FV), [Hex](../tB/Modules/Conversion/Hex), [InputBox](../tB/Modules/Interaction/InputBox), [NPer](../tB/Modules/Financial/NPer), [Oct](../tB/Modules/Conversion/Oct), [Pmt](../tB/Modules/Financial/Pmt), [Val](../tB/Modules/Conversion/Val), [ValDec](../tB/Modules/Conversion/ValDec) +**6**{: #listed-6 } (`&H6`): [Abs](../tB/Modules/Math/Abs), [Bin](../tB/Modules/Conversion/Bin), [CBool](../tB/Modules/Conversion/CBool), [CByte](../tB/Modules/Conversion/CByte), [CCur](../tB/Modules/Conversion/CCur), [CDbl](../tB/Modules/Conversion/CDbl), [CDec](../tB/Modules/Conversion/CDec), [Choose](../tB/Modules/Interaction/Choose), [Chr](../tB/Modules/Strings/Chr), [CInt](../tB/Modules/Conversion/CInt), [CLng](../tB/Modules/Conversion/CLng), [CLngLng](../tB/Modules/Conversion/CLngLng), [CLngPtr](../tB/Modules/Conversion/CLngPtr), [CSng](../tB/Modules/Conversion/CSng), [CType](../tB/Modules/Conversion/CType), [DateAdd](../tB/Modules/DateTime/DateAdd), [DateDiff](../tB/Modules/DateTime/DateDiff), [Exp](../tB/Modules/Math/Exp), [FV](../tB/Modules/Financial/FV), [Hex](../tB/Modules/Conversion/Hex), [InputBox](../tB/Modules/Interaction/InputBox), [NPer](../tB/Modules/Financial/NPer), [Oct](../tB/Modules/Conversion/Oct), [Pmt](../tB/Modules/Financial/Pmt), [Val](../tB/Modules/Conversion/Val), [ValDec](../tB/Modules/Conversion/ValDec) **9**{: #listed-9 } (`&H9`): [Add](../tB/Modules/Collection/Add), [Array](../tB/Modules/Information/Array), [Item](../tB/Modules/Collection/Item), [LBound](../tB/Modules/Information/LBound), [ReDim](../tB/Core/ReDim), [Remove](../tB/Modules/Collection/Remove), [UBound](../tB/Modules/Information/UBound) @@ -188,7 +188,7 @@ The messages are those of twinBASIC 32-bit and VB6, which agree for every number **-2147467261**{: #listed--2147467261 } (`&H80004003`): [GetNameFromDispID](../tB/Modules/HiddenModule/GetNameFromDispID) -**-2147467259**{: #listed--2147467259 } (`&H80004005`): [AppActivate](../tB/Modules/Interaction/AppActivate), [Calendar](../tB/Modules/DateTime/Calendar), [CallByDispId](../tB/Modules/Interaction/CallByDispId), [CallByName](../tB/Modules/Interaction/CallByName), [ChDir](../tB/Modules/FileSystem/ChDir), [ChDrive](../tB/Modules/FileSystem/ChDrive), [Compile](../tB/Modules/TbExpressionService/Compile), [ConvertIconToBitmap](../tB/Modules/HiddenModule/ConvertIconToBitmap), [CreateStdPictureFromHandle](../tB/Modules/HiddenModule/CreateStdPictureFromHandle), [CurDir](../tB/Modules/FileSystem/CurDir), [Date](../tB/Modules/DateTime/Date), [DDB](../tB/Modules/Financial/DDB), [DeleteSetting](../tB/Modules/Interaction/DeleteSetting), [Dir](../tB/Modules/FileSystem/Dir), [Environ](../tB/Modules/Interaction/Environ), [EOF](../tB/Modules/FileSystem/EOF), [Eval](../tB/Modules/HiddenModule/Eval), [Evaluate](../tB/Modules/TbExpressionService/Evaluate), [FileAttr](../tB/Modules/FileSystem/FileAttr), [FileCopy](../tB/Modules/FileSystem/FileCopy), [FileDateTime](../tB/Modules/FileSystem/FileDateTime), [FileLen](../tB/Modules/FileSystem/FileLen), [FreeFile](../tB/Modules/FileSystem/FreeFile), [GetAllSettings](../tB/Modules/Interaction/GetAllSettings), [GetAttr](../tB/Modules/FileSystem/GetAttr), [GetObject](../tB/Modules/Interaction/GetObject), [GetSetting](../tB/Modules/Interaction/GetSetting), [Input, Input$](../tB/Modules/FileSystem/Input), [InputB, InputB$](../tB/Modules/FileSystem/InputB), [InputBox](../tB/Modules/Interaction/InputBox), [IPmt](../tB/Modules/Financial/IPmt), [IRR](../tB/Modules/Financial/IRR), [Kill](../tB/Modules/FileSystem/Kill), [LBound](../tB/Modules/Information/LBound), [Loc](../tB/Modules/FileSystem/Loc), [LOF](../tB/Modules/FileSystem/LOF), [MkDir](../tB/Modules/FileSystem/MkDir), [MsgBox](../tB/Modules/Interaction/MsgBox), [NPer](../tB/Modules/Financial/NPer), [Partition](../tB/Modules/Interaction/Partition), [Pmt](../tB/Modules/Financial/Pmt), [PPmt](../tB/Modules/Financial/PPmt), [PV](../tB/Modules/Financial/PV), [Rate](../tB/Modules/Financial/Rate), [ReDim](../tB/Core/ReDim), [RmDir](../tB/Modules/FileSystem/RmDir), [SaveSetting](../tB/Modules/Interaction/SaveSetting), [Seek](../tB/Modules/FileSystem/Seek), [SetAttr](../tB/Modules/FileSystem/SetAttr), [Shell](../tB/Modules/Interaction/Shell), [SLN](../tB/Modules/Financial/SLN), [Switch](../tB/Modules/Interaction/Switch), [SYD](../tB/Modules/Financial/SYD), [Time](../tB/Modules/DateTime/Time), [UBound](../tB/Modules/Information/UBound), [Width](../tB/Modules/FileSystem/Width) +**-2147467259**{: #listed--2147467259 } (`&H80004005`): [AppActivate](../tB/Modules/Interaction/AppActivate), [Calendar](../tB/Modules/DateTime/Calendar), [CallByDispId](../tB/Modules/Interaction/CallByDispId), [CallByName](../tB/Modules/Interaction/CallByName), [ChDir](../tB/Modules/FileSystem/ChDir), [ChDrive](../tB/Modules/FileSystem/ChDrive), [Compile](../tB/Modules/TbExpressionService/Compile), [ConvertIconToBitmap](../tB/Modules/HiddenModule/ConvertIconToBitmap), [CreateStdPictureFromHandle](../tB/Modules/HiddenModule/CreateStdPictureFromHandle), [CurDir](../tB/Modules/FileSystem/CurDir), [Date](../tB/Modules/DateTime/Date), [DDB](../tB/Modules/Financial/DDB), [DeleteSetting](../tB/Modules/Interaction/DeleteSetting), [Dir](../tB/Modules/FileSystem/Dir), [Environ](../tB/Modules/Interaction/Environ), [EOF](../tB/Modules/FileSystem/EOF), [Eval](../tB/Modules/HiddenModule/Eval), [Evaluate](../tB/Modules/TbExpressionService/Evaluate), [FileAttr](../tB/Modules/FileSystem/FileAttr), [FileCopy](../tB/Modules/FileSystem/FileCopy), [FileDateTime](../tB/Modules/FileSystem/FileDateTime), [FileLen](../tB/Modules/FileSystem/FileLen), [FreeFile](../tB/Modules/FileSystem/FreeFile), [GetAllSettings](../tB/Modules/Interaction/GetAllSettings), [GetAttr](../tB/Modules/FileSystem/GetAttr), [GetObject](../tB/Modules/Interaction/GetObject), [GetSetting](../tB/Modules/Interaction/GetSetting), [Input, Input$](../tB/Modules/FileSystem/Input), [InputB, InputB$](../tB/Modules/FileSystem/InputB), [InputBox](../tB/Modules/Interaction/InputBox), [IPmt](../tB/Modules/Financial/IPmt), [IRR](../tB/Modules/Financial/IRR), [Kill](../tB/Modules/FileSystem/Kill), [LBound](../tB/Modules/Information/LBound), [Loc](../tB/Modules/FileSystem/Loc), [LOF](../tB/Modules/FileSystem/LOF), [MkDir](../tB/Modules/FileSystem/MkDir), [MsgBox](../tB/Modules/Interaction/MsgBox), [NPer](../tB/Modules/Financial/NPer), [Partition](../tB/Modules/Interaction/Partition), [Pmt](../tB/Modules/Financial/Pmt), [PPmt](../tB/Modules/Financial/PPmt), [PV](../tB/Modules/Financial/PV), [Rate](../tB/Modules/Financial/Rate), [RmDir](../tB/Modules/FileSystem/RmDir), [SaveSetting](../tB/Modules/Interaction/SaveSetting), [Seek](../tB/Modules/FileSystem/Seek), [SetAttr](../tB/Modules/FileSystem/SetAttr), [Shell](../tB/Modules/Interaction/Shell), [SLN](../tB/Modules/Financial/SLN), [Switch](../tB/Modules/Interaction/Switch), [SYD](../tB/Modules/Financial/SYD), [Time](../tB/Modules/DateTime/Time), [UBound](../tB/Modules/Information/UBound), [Width](../tB/Modules/FileSystem/Width) **-2147418113**{: #listed--2147418113 } (`&H8000FFFF`): [CreateStdPictureFromHandle](../tB/Modules/HiddenModule/CreateStdPictureFromHandle), [IRR](../tB/Modules/Financial/IRR), [MIRR](../tB/Modules/Financial/MIRR), [NPV](../tB/Modules/Financial/NPV) diff --git a/errors/Core.yml b/errors/Core.yml index 6f214e5e5..02761134a 100644 --- a/errors/Core.yml +++ b/errors/Core.yml @@ -1,12 +1,10 @@ # Statements and operators of docs/Reference/Core/. Format: WIP.Errors.md. -beta: 1010 +beta: 1034 pages: Reference/Core/ReDim.md: rows: - - error: -2147467259 + - error: 9 when: an upper bound is less than its lower bound - vb6: 9 - bug: redim-inverted-bounds-error cases: - { decl: Dim X() As Long, run: ReDim X(0 To -1) } - { decl: Dim X() As Long, run: ReDim X(-1) } diff --git a/errors/VBA.Collection.yml b/errors/VBA.Collection.yml index beadc33c6..280cb7add 100644 --- a/errors/VBA.Collection.yml +++ b/errors/VBA.Collection.yml @@ -1,5 +1,5 @@ # The VBA Collection class. Format: WIP.Errors.md. -beta: 1010 +beta: 1034 pages: Reference/Default/VBA/Collection/Add.md: rows: diff --git a/errors/VBA.Compilation.yml b/errors/VBA.Compilation.yml index dfa3db489..d69cf5d09 100644 --- a/errors/VBA.Compilation.yml +++ b/errors/VBA.Compilation.yml @@ -1,7 +1,7 @@ # The VBA Compilation module. Format: WIP.Errors.md. # Every case is `only: tb`: VB6 and VBA have none of these procedures. Each member is a # compile-time intrinsic or a hint to the compiler, and raises nothing at run time. -beta: 1010 +beta: 1034 pages: Reference/Default/VBA/Compilation/CompilerVersion.md: quiet: diff --git a/errors/VBA.Conversion.yml b/errors/VBA.Conversion.yml index 0c0754eef..d6df37e85 100644 --- a/errors/VBA.Conversion.yml +++ b/errors/VBA.Conversion.yml @@ -1,5 +1,5 @@ # The VBA Conversion module. Format: WIP.Errors.md. -beta: 1010 +beta: 1034 pages: Reference/Default/VBA/Conversion/Bin.md: rows: @@ -99,13 +99,6 @@ pages: - CCur("-922337203685477.5808") Reference/Default/VBA/Conversion/CDate.md: rows: - - error: 6 - when: "*expression* is a **Decimal** less than -657,434 or 2,958,466 or more" - vb6: 13 - bug: cdate-string-decimal-out-of-range - cases: - - CDate(CDec(2958466)) - - CDate(CDec(-657435)) - error: 13 when: "*expression* is a string that is not a date, a time or a number (a zero-length string included), or a date after December 31, 9999" cases: @@ -113,6 +106,15 @@ pages: - CDate("") - CDate("13/13/2020") - CDate("1/1/10000") + - error: 13 + when: "*expression* is a **Decimal**, or a string that holds a number, less than -657,434 or 2,958,466 or more" + cases: + - CDate(CDec(2958466)) + - CDate(CDec(-657435)) + - CDate("2958466") + - CDate("-657435") + - CDate("3000000") + - CDate("1e10") - error: 94 when: "*expression* is **Null**" cases: [CDate(Null)] @@ -129,15 +131,6 @@ pages: - CDate(CLng(3000000)) - CDate(CSng(3000000)) - CDate(CCur(2958466)) - - error: 0 - when: "**CDate** is given a string that holds a number less than -657,434 or 2,958,466 or more" - vb6: 13 - bug: cdate-string-decimal-out-of-range - cases: - - CDate("2958466") - - CDate("-657435") - - CDate("3000000") - - CDate("1e10") quiet: - CDate(2958465) - CDate(2958465.9999) @@ -502,10 +495,8 @@ pages: - error: 94 when: "*string* is **Null**" cases: [Val(Null)] - - error: 0 - when: "**Val** is given a *string* that holds a number followed by the **Integer** (`%`), **Long** (`&`) or **Currency** (`@`) type-declaration character, and the number is outside the range of that type" - vb6: 6 - bug: val-suffix-overflow-no-error + - error: 6 + when: "*string* holds a number followed by the **Integer** (`%`), **Long** (`&`) or **Currency** (`@`) type-declaration character, and the number is outside the range of that type" cases: - Val("32768%") - Val("-32769%") diff --git a/errors/VBA.DateTime.yml b/errors/VBA.DateTime.yml index c066e0e3c..af64044b9 100644 --- a/errors/VBA.DateTime.yml +++ b/errors/VBA.DateTime.yml @@ -10,7 +10,7 @@ # Calendar is not implemented in twinBASIC (TB0009), and the owner chose not to report # it, so its cases are twinBASIC-only and have no VB6 NOTE. In VB6 vbCalHijri succeeds # and stays: a VB6 case that sets it must set vbCalGreg again. -beta: 1010 +beta: 1034 pages: Reference/Default/VBA/DateTime/Calendar.md: rows: diff --git a/errors/VBA.ErrObject.yml b/errors/VBA.ErrObject.yml index 70fb2c7d3..77aca1d2b 100644 --- a/errors/VBA.ErrObject.yml +++ b/errors/VBA.ErrObject.yml @@ -3,7 +3,7 @@ # Number is that number, so that it reports 0; a runtime that raises or keeps another # number reports that one. HelpFile, HelpContext and ReturnHResult have no cases: assigning # them fails twinBASIC's build (bugs/err-helpfile-helpcontext-codegen). -beta: 1010 +beta: 1034 pages: Reference/Default/VBA/ErrObject/Raise.md: rows: diff --git a/errors/VBA.FileSystem.yml b/errors/VBA.FileSystem.yml index 8148aaa42..4468a2af9 100644 --- a/errors/VBA.FileSystem.yml +++ b/errors/VBA.FileSystem.yml @@ -1,7 +1,7 @@ # The VBA FileSystem module. Format: WIP.Errors.md. Every case runs under the files harness, in a # folder of its own that is the current directory: a.txt (10 bytes), empty.txt, ro.txt # (read-only), hid.txt (hidden), the empty folder sub, and full, which holds f.txt. -beta: 1010 +beta: 1034 pages: Reference/Default/VBA/FileSystem/ChDir.md: harness: files diff --git a/errors/VBA.Financial.yml b/errors/VBA.Financial.yml index 6fd856999..00f25f2cf 100644 --- a/errors/VBA.Financial.yml +++ b/errors/VBA.Financial.yml @@ -1,5 +1,5 @@ # The VBA Financial module. Format: WIP.Errors.md. -beta: 1010 +beta: 1034 pages: Reference/Default/VBA/Financial/DDB.md: rows: diff --git a/errors/VBA.HiddenModule.yml b/errors/VBA.HiddenModule.yml index 4e4319e92..2b98f4f00 100644 --- a/errors/VBA.HiddenModule.yml +++ b/errors/VBA.HiddenModule.yml @@ -11,7 +11,7 @@ # Eval's row of errors raised while evaluating is bugs/expression-service-error-numbers (its # other -2147467259 rows are in no entry), and the crashes # bugs/nothing-argument-crashes. -beta: 1010 +beta: 1034 pages: Reference/Default/VBA/HiddenModule/AddClassInstanceFlag.md: rows: diff --git a/errors/VBA.Information.yml b/errors/VBA.Information.yml index aeab00d2a..aa2cd7284 100644 --- a/errors/VBA.Information.yml +++ b/errors/VBA.Information.yml @@ -1,5 +1,5 @@ # The VBA Information module. Format: WIP.Errors.md. -beta: 1010 +beta: 1034 pages: Reference/Default/VBA/Information/LBound.md: rows: diff --git a/errors/VBA.Interaction.yml b/errors/VBA.Interaction.yml index c4773fbfd..91b260f41 100644 --- a/errors/VBA.Interaction.yml +++ b/errors/VBA.Interaction.yml @@ -1,5 +1,5 @@ # The VBA Interaction module: its non-interactive procedures (batch 6a) and its interactive ones (batch 6b). Format: WIP.Errors.md. -beta: 1010 +beta: 1034 pages: Reference/Default/VBA/Interaction/Choose.md: rows: diff --git a/errors/VBA.Math.yml b/errors/VBA.Math.yml index 47f208b64..c464baf47 100644 --- a/errors/VBA.Math.yml +++ b/errors/VBA.Math.yml @@ -1,5 +1,5 @@ # The VBA Math module. Format: WIP.Errors.md. -beta: 1010 +beta: 1034 pages: Reference/Default/VBA/Math/Abs.md: rows: @@ -8,10 +8,8 @@ pages: cases: - { decl: Dim C As Currency, setup: C = -922337203685477.5807@ - 0.0001@, expr: Abs(C) } - { decl: Dim V As Variant, setup: V = -922337203685477.5807@ - 0.0001@, expr: Abs(V) } - - error: 0 - when: "*number* is an **Integer** of -32,768 or a **Long** of -2,147,483,648 (twinBASIC returns a **Long** or a **Double**)" - vb6: 6 - bug: abs-minimum-widens-type + - error: 6 + when: "*number* is an **Integer** of -32,768 or a **Long** of -2,147,483,648" cases: - { decl: Dim I As Integer, setup: I = -32768, expr: Abs(I) } - { decl: Dim L As Long, setup: L = -2147483647 - 1, expr: Abs(L) } @@ -40,15 +38,20 @@ pages: - error: 94 when: "*number* is **Null**" cases: [Cos(Null)] + - error: 5 + when: "the magnitude of *number* is 9,223,372,036,854,775,808 (2^63) or more" + cases: + - { decl: Dim X As Double, setup: X = 2 ^ 63, expr: Cos(X) } + - { decl: Dim X As Double, setup: X = -(2 ^ 63), expr: Cos(X) } + - Cos(1E+300) - error: 0 - when: "the magnitude of *number* is 9,223,372,000,000,000,000 or more" + when: "the magnitude of *number* is 9,223,372,000,000,000,000 or more but less than 2^63" vb6: 5 bug: trig-above-2-to-63 cases: - Cos(9.223372E+18) - - { decl: Dim X As Double, setup: X = 2 ^ 63, expr: Cos(X) } - - { decl: Dim X As Double, setup: X = -(2 ^ 63), expr: Cos(X) } - - Cos(1E+300) + - { decl: Dim X As Double, setup: X = 2 ^ 63 - 1024, expr: Cos(X) } + - { decl: Dim X As Double, setup: X = -(2 ^ 63) + 1024, expr: Cos(X) } quiet: - Cos(Empty) - Cos(1E+18) @@ -58,15 +61,20 @@ pages: - error: 94 when: "*number* is **Null**" cases: [Sin(Null)] + - error: 5 + when: "the magnitude of *number* is 9,223,372,036,854,775,808 (2^63) or more" + cases: + - { decl: Dim X As Double, setup: X = 2 ^ 63, expr: Sin(X) } + - { decl: Dim X As Double, setup: X = -(2 ^ 63), expr: Sin(X) } + - Sin(1E+300) - error: 0 - when: "the magnitude of *number* is 9,223,372,000,000,000,000 or more" + when: "the magnitude of *number* is 9,223,372,000,000,000,000 or more but less than 2^63" vb6: 5 bug: trig-above-2-to-63 cases: - Sin(9.223372E+18) - - { decl: Dim X As Double, setup: X = 2 ^ 63, expr: Sin(X) } - - { decl: Dim X As Double, setup: X = -(2 ^ 63), expr: Sin(X) } - - Sin(1E+300) + - { decl: Dim X As Double, setup: X = 2 ^ 63 - 1024, expr: Sin(X) } + - { decl: Dim X As Double, setup: X = -(2 ^ 63) + 1024, expr: Sin(X) } quiet: - Sin(Empty) - Sin(1E+18) @@ -176,6 +184,7 @@ pages: - Round(1.5, -1) - Round(1.5, -0.6) - Round(Empty, -1) + - Round(Null, -1) - { decl: Dim C As Currency, setup: C = 1.5, expr: 'Round(C, -1)' } - { decl: Dim D As Variant, setup: 'D = CDec("1.5")', expr: 'Round(D, -1)' } - error: 5 @@ -192,12 +201,6 @@ pages: cases: - Round(1.5, Null) - Round(Null, Null) - - error: 0 - when: "*expression* is **Null** and *numdecimalplaces* is negative" - vb6: 5 - bug: round-null-negative-digits - cases: - - Round(Null, -1) quiet: - Round(Null) - Round(Null, 23) diff --git a/errors/VBA.Strings.yml b/errors/VBA.Strings.yml index 613b0682f..931c8df74 100644 --- a/errors/VBA.Strings.yml +++ b/errors/VBA.Strings.yml @@ -1,5 +1,5 @@ # The VBA Strings module. Format: WIP.Errors.md. -beta: 1010 +beta: 1034 pages: Reference/Default/VBA/Strings/Asc.md: rows: @@ -600,13 +600,8 @@ pages: - String(Null, "a") - String$(Null, "a") - String$(3, Null) - - error: 94 - when: "**String** is given a *character* that is **Null**" - vb6: 0 - bug: string-null-character - cases: - - String(3, Null) quiet: + - String(3, Null) - String(0, "a") - String(3, "abc") - String(3, 65) diff --git a/errors/VBA.TbExpressionService.yml b/errors/VBA.TbExpressionService.yml index 87b48f1c4..fc8016556 100644 --- a/errors/VBA.TbExpressionService.yml +++ b/errors/VBA.TbExpressionService.yml @@ -8,7 +8,7 @@ # the probe module cannot declare, nor for AddCustomBinder Nothing and AddCustomBinderObject # with an object of Nothing, which crash (an access violation, 32-bit and 64-bit; the pages' # WARNINGs). -beta: 1010 +beta: 1034 pages: Reference/Default/VBA/TbExpressionService/Compile.md: harness: expression diff --git a/scripts/check_attribute_sweep.mjs b/scripts/check_attribute_sweep.mjs index 1729ebcbc..0390e3f07 100644 --- a/scripts/check_attribute_sweep.mjs +++ b/scripts/check_attribute_sweep.mjs @@ -850,7 +850,7 @@ for (const [app, want] of [ "Module, and to procedures, Declare statements and Enums in a Module", ["MODULE", "PROC_MODULE", "DECLARE", "ENUM"], ], - ["Function, Sub, Property Get in a Module", ["FUNC_MODULE", "SUB_MODULE", "PROPGET_MODULE"]], + ["Sub in a Module", ["SUB_MODULE"]], ["procedure in a Module", ["PROC_MODULE"]], ["Declare (API declaration), variable in a Class, procedure definitions", ["DECLARE", "VAR_CLASS", "PROC_ANY"]], [ diff --git a/test/addin/appdata.test.mjs b/test/addin/appdata.test.mjs index 62dcbf02e..11ac9ddc7 100644 --- a/test/addin/appdata.test.mjs +++ b/test/addin/appdata.test.mjs @@ -9,7 +9,7 @@ // That private APPDATA is what keeps the user's add-ins out of every other // lane, and these tests are what it rests on. // -// Each test states what BETA 1010 does. If one fails after an IDE update, the +// Each test states what BETA 1034 does. If one fails after an IDE update, the // IDE has changed: update P6 in WIP.HelpAddin.md and the lanes' APPDATA in // scripts/lib/tb-lane.mjs, and then this file. // diff --git a/test/addin/arch.test.mjs b/test/addin/arch.test.mjs index 26eecb4ca..c562a8fd2 100644 --- a/test/addin/arch.test.mjs +++ b/test/addin/arch.test.mjs @@ -6,7 +6,7 @@ // ArchProbeUser.dll. Every copy that loads prints one line: the bitness it was // built for, the executable of the process it runs in, and its own file. // -// Each test states what BETA 1010 does. If one fails after an IDE update, the +// Each test states what BETA 1034 does. If one fails after an IDE update, the // IDE has changed: update P7 in WIP.HelpAddin.md and the Add Ins page // (docs/IDE/AddIns/index.md), and then this file. // diff --git a/test/addin/detach.test.mjs b/test/addin/detach.test.mjs index 44b8318e7..f1095b9c3 100644 --- a/test/addin/detach.test.mjs +++ b/test/addin/detach.test.mjs @@ -14,7 +14,7 @@ // The tests also count the msedgewebview2.exe processes under that folder // before the IDE starts and after it ends. // -// Each test states what BETA 1010 does. If one fails after an IDE update, the +// Each test states what BETA 1034 does. If one fails after an IDE update, the // IDE has changed: update the plan for detaching the help pane in // WIP.HelpAddin.md, and then this file. // diff --git a/test/addin/entry.test.mjs b/test/addin/entry.test.mjs index e906d2d5a..69f692b01 100644 --- a/test/addin/entry.test.mjs +++ b/test/addin/entry.test.mjs @@ -13,7 +13,7 @@ // function called tbCreateCompilerAddin to tbCreateCompilerAddin_v3. This lane // checks both by what they do. // -// Each test states what BETA 1010 does. If one fails after an IDE update, the +// Each test states what BETA 1034 does. If one fails after an IDE update, the // IDE has changed: update P14 in WIP.HelpAddin.md, the entry point's NOTE on // the tbIDE package page (docs/Reference/Built-In/tbIDE/index.md) and the // messages on the Add Ins page (docs/IDE/AddIns/index.md), and then this file. diff --git a/test/addin/env.test.mjs b/test/addin/env.test.mjs index ca1989cd9..6151b5126 100644 --- a/test/addin/env.test.mjs +++ b/test/addin/env.test.mjs @@ -12,7 +12,7 @@ // variable left out reports it unset, so the first two read what the harness set // and not something the machine had. // -// Each test states what BETA 1010 does. If one fails after an IDE update, the +// Each test states what BETA 1034 does. If one fails after an IDE update, the // IDE has changed: update P10 in WIP.HelpAddin.md, the paragraph under "An // add-in under test opens nothing" in WIP.Harness.md, and every add-in that // checks the variable (they would open a real browser), and then this file. diff --git a/test/addin/ideserver.test.mjs b/test/addin/ideserver.test.mjs index 9c68662b8..9c941da6f 100644 --- a/test/addin/ideserver.test.mjs +++ b/test/addin/ideserver.test.mjs @@ -9,7 +9,7 @@ // so a relative URL is a path below the ide folder. The page fetches each file // the way a frame's src would be resolved. // -// Each test states what BETA 1010 does. If one fails after an IDE update, the +// Each test states what BETA 1034 does. If one fails after an IDE update, the // IDE has changed: update P13 and "Offline" in WIP.HelpAddin.md, and then this // file. // diff --git a/test/addin/keys.test.mjs b/test/addin/keys.test.mjs index e92bb8f26..901ce51da 100644 --- a/test/addin/keys.test.mjs +++ b/test/addin/keys.test.mjs @@ -3,7 +3,7 @@ // editor. The KeysProbe add-in (probes/keys) registers eight key strings and // prints "[KeysProbe] fired " to the DEBUG CONSOLE when one fires. // -// Each test states what BETA 1010 does. If one fails after an IDE update, the +// Each test states what BETA 1034 does. If one fails after an IDE update, the // IDE has changed: update P1 and P2 in WIP.HelpAddin.md, the NOTE on // docs/Reference/Built-In/tbIDE/KeyboardShortcuts.md and the filed report in // bugs/filed/ (twinbasic/twinbasic#2445), and then this file. diff --git a/test/addin/panes.test.mjs b/test/addin/panes.test.mjs index 6c9798ea3..8216172d8 100644 --- a/test/addin/panes.test.mjs +++ b/test/addin/panes.test.mjs @@ -11,7 +11,7 @@ // The last test is what ToolWindows.Add does with windows given no id, which // P9 turned up. // -// Each test states what BETA 1010 does. If one fails after an IDE update, the +// Each test states what BETA 1034 does. If one fails after an IDE update, the // IDE has changed: update P3, P4 and P12 in WIP.HelpAddin.md and the tbIDE // pages that rest on them (HtmlElement, HtmlElementProperties, HtmlElements, // ToolWindow, ToolWindows), and then this file. diff --git a/test/addin/parent.test.mjs b/test/addin/parent.test.mjs index c026cc5fc..473380a36 100644 --- a/test/addin/parent.test.mjs +++ b/test/addin/parent.test.mjs @@ -19,7 +19,7 @@ // e Dim f As Folder = RootFolder.Parent, then "read" // f the control: the project folder's Parent, printed by its Path // -// Each test states what BETA 1010 does: the +// Each test states what BETA 1034 does: the // compiler crashes in cases a to e, at the read of the root's Parent, before // anything is done with the value, and not in the control, f, where the // parent is the root. So the lane passes while the bug is there. If a test diff --git a/test/addin/references.test.mjs b/test/addin/references.test.mjs index a0b61ad5b..e77ad65e1 100644 --- a/test/addin/references.test.mjs +++ b/test/addin/references.test.mjs @@ -11,7 +11,7 @@ // add-in is loaded and removes it again, on the References page, as a person // does. // -// Each test states what BETA 1010 does. If one fails after an IDE update, the +// Each test states what BETA 1034 does. If one fails after an IDE update, the // IDE has changed: update P20 in WIP.HelpAddin.md, and the Folder page // (docs/Reference/Built-In/tbIDE/Folder.md), and then this file. // diff --git a/test/addin/reload.test.mjs b/test/addin/reload.test.mjs index d9333aa89..d2696651a 100644 --- a/test/addin/reload.test.mjs +++ b/test/addin/reload.test.mjs @@ -10,7 +10,7 @@ // Each also records in a file whether its Class_Terminate runs: the page ends // the compiler with taskkill /F (forceTerminate in main.js), so it cannot. // -// Each test states what BETA 1010 does. If one fails after an IDE update, the +// Each test states what BETA 1034 does. If one fails after an IDE update, the // IDE has changed: update P8 and P9 in WIP.HelpAddin.md, what the tbIDE // package page (docs/Reference/Built-In/tbIDE/index.md) says a compiler // restart does, and ToolWindows.Add's id on the ToolWindows page, and then diff --git a/test/addin/symbols.test.mjs b/test/addin/symbols.test.mjs index ec29fa98e..a07321fec 100644 --- a/test/addin/symbols.test.mjs +++ b/test/addin/symbols.test.mjs @@ -6,7 +6,7 @@ // as the code editor's intellisense does (getLiveDebugIntellisenseMonaco). // Only page script can ask any of them; the add-in API has no such call. // -// Each test states what BETA 1010 does. If one fails after an IDE update, the +// Each test states what BETA 1034 does. If one fails after an IDE update, the // IDE has changed: update P5 in WIP.HelpAddin.md, and then this file. // // Run it with addin-test.bat, which gives it a lane; on its own it is skipped. diff --git a/test/ide/assert.test.mjs b/test/ide/assert.test.mjs index 8d12730f9..cec9330bc 100644 --- a/test/ide/assert.test.mjs +++ b/test/ide/assert.test.mjs @@ -4,7 +4,7 @@ // line before and after the assertion and after the call, as the runner in // docs/Tutorials/Testing-with-Assert.md does. // -// Each test states what BETA 1010 does. If one fails after an IDE update, the +// Each test states what BETA 1034 does. If one fails after an IDE update, the // IDE has changed: update the NOTE under "Running the tests" in // Testing-with-Assert.md and the filed report of // twinbasic/twinbasic#2441 in bugs/filed/, and then this file. diff --git a/test/ide/break-into.test.mjs b/test/ide/break-into.test.mjs index abac52a1a..cb8cc6a78 100644 --- a/test/ide/break-into.test.mjs +++ b/test/ide/break-into.test.mjs @@ -11,7 +11,7 @@ // line (tb-lane.mjs), so a test that has asserted the crash clears the console, // and then restarts the compiler, which is what puts the IDE right again. // -// Each test states what BETA 1010 does. If one fails after an IDE update, the IDE +// Each test states what BETA 1034 does. If one fails after an IDE update, the IDE // has changed: update the entry "After Break Into Code stops in DoEvents, // evaluating anything ends the program with an access violation" in // BUGS-TO-REPORT.md, or its report in bugs/filed/, and then this file. diff --git a/test/ide/breakpoints.test.mjs b/test/ide/breakpoints.test.mjs index 219c9b8b0..797cb9cf1 100644 --- a/test/ide/breakpoints.test.mjs +++ b/test/ide/breakpoints.test.mjs @@ -6,7 +6,7 @@ // test sets and clears breakpoints as a person does (F9, Ctrl+Shift+F9, Ctrl+S), // restarts the compiler with the toolbar's button, reads the margin and runs Main. // -// Each test states what BETA 1010 does. If one fails after an IDE update, the IDE +// Each test states what BETA 1034 does. If one fails after an IDE update, the IDE // has changed. // // Run it with ide-test.bat, which gives it a lane; on its own it is skipped. diff --git a/test/ide/build-and-exit-silent.test.mjs b/test/ide/build-and-exit-silent.test.mjs index 86094c460..7e163fcb5 100644 --- a/test/ide/build-and-exit-silent.test.mjs +++ b/test/ide/build-and-exit-silent.test.mjs @@ -5,7 +5,7 @@ // desktop (launchOnDesktop), from the lane's own copy of the install, with the // lane's own APPDATA, and the project is packed into the lane's work folder. // -// Each test states what BETA 1010 does. If one fails after an IDE +// Each test states what BETA 1034 does. If one fails after an IDE // update, the switch has changed: update the entry in BUGS-TO-REPORT.md or // bugs/filed/, the notes that name it (scripts/tbbuild.mjs, WIP.Harness.md, // docs/Documentation/Tools-Compiler.md, docs/Features/Packages/Import-export tool.md), diff --git a/test/ide/building-hklm.test.mjs b/test/ide/building-hklm.test.mjs index 8ef9ad7ae..3a5618e91 100644 --- a/test/ide/building-hklm.test.mjs +++ b/test/ide/building-hklm.test.mjs @@ -13,7 +13,7 @@ // elevated, and write nothing; the last test checks that no key of the // fixture's exists in HKCU or HKLM. Nothing here writes the registry itself. // -// Each test states what BETA 1010 does. If one fails after an IDE update, the +// Each test states what BETA 1034 does. If one fails after an IDE update, the // IDE has changed: update the matching entry in BUGS-TO-REPORT.md or // bugs/filed/, and then this file. // diff --git a/test/ide/building-llvm-box.test.mjs b/test/ide/building-llvm-box.test.mjs index ca3d82581..6f1f93467 100644 --- a/test/ide/building-llvm-box.test.mjs +++ b/test/ide/building-llvm-box.test.mjs @@ -10,7 +10,7 @@ // the IDE, so the test opens one from the page, with the IDE's own // showModalNotification, at the build's first progress event. // -// Each test states what BETA 1010 does. If one fails after an IDE update, the +// Each test states what BETA 1034 does. If one fails after an IDE update, the // IDE has changed: update the matching entry in BUGS-TO-REPORT.md or // bugs/filed/, and then this file. // diff --git a/test/ide/code-find.test.mjs b/test/ide/code-find.test.mjs index 430f50622..eaf5693da 100644 --- a/test/ide/code-find.test.mjs +++ b/test/ide/code-find.test.mjs @@ -24,7 +24,7 @@ // proxy whose ChangeWindowSetting and RemoveRecentsList calls do nothing, as customising.test.mjs // does. Nothing here reaches SaveSetting, so lanes.mjs names no settings for this lane. // -// The tests share the lane's IDE in order. Each test states what BETA 1010 does; the tests named +// The tests share the lane's IDE in order. Each test states what BETA 1034 does; the tests named // "control" pass whether or not the defects are fixed. If another fails after an IDE update, the // IDE has changed: update the entry in BUGS-TO-REPORT.md, or its report in bugs/filed/, the // callout on the page of docs/IDE/Code/ that carries it, and then this file. diff --git a/test/ide/console-completion.test.mjs b/test/ide/console-completion.test.mjs index 0a7166e18..98b5b43fd 100644 --- a/test/ide/console-completion.test.mjs +++ b/test/ide/console-completion.test.mjs @@ -15,7 +15,7 @@ // first Ctrl+Space, and each test checks that the console has it before and // after: a list closed by a change of focus never reads as no list. // -// Each test states what BETA 1010 does. If one fails after an IDE update, the IDE +// Each test states what BETA 1034 does. If one fails after an IDE update, the IDE // has changed: update the entry "The Debug Console offers no completion for a name // typed after `?` and a space" in BUGS-TO-REPORT.md, or its report in bugs/filed/, // and then this file. diff --git a/test/ide/debugger.test.mjs b/test/ide/debugger.test.mjs index 245cf8745..730039a2a 100644 --- a/test/ide/debugger.test.mjs +++ b/test/ide/debugger.test.mjs @@ -5,7 +5,7 @@ // calls, and prints a line before and after each step, so the DEBUG CONSOLE // shows how far it got. // -// Each test states what BETA 1010 does. If one fails after an IDE update, +// Each test states what BETA 1034 does. If one fails after an IDE update, // the IDE has changed: update the Traps on docs/IDE/Menu/Debug.md and the // debugger entry in BUGS-TO-REPORT.md and twinbasic/twinbasic#2441's report in // bugs/filed/, and then this file. diff --git a/test/ide/debugging.test.mjs b/test/ide/debugging.test.mjs index 3bd111080..9f57b3118 100644 --- a/test/ide/debugging.test.mjs +++ b/test/ide/debugging.test.mjs @@ -33,7 +33,7 @@ // // The tests share the lane's IDE in order. The edits the last tests make (a letter typed at the end // of the @EDIT comment, a line that does not compile in Direct) are not undone. Each test states -// what BETA 1010 does; the tests named "control" pass whether or not the defects are fixed. If +// what BETA 1034 does; the tests named "control" pass whether or not the defects are fixed. If // another fails after an IDE update, the IDE has changed: update the entry in BUGS-TO-REPORT.md, or // its report in bugs/filed/, the callout on the page of docs/IDE/Debugging/ that carries it, and // then this file. diff --git a/test/ide/forms.test.mjs b/test/ide/forms.test.mjs index ae7175bbf..4617f8f83 100644 --- a/test/ide/forms.test.mjs +++ b/test/ide/forms.test.mjs @@ -56,7 +56,7 @@ // calls do nothing, as debugging.test.mjs does; the tests float the HISTORY and TOOLBOX panels. // Nothing here copies to the clipboard or reaches SaveSetting, so lanes.mjs names no settings. // -// The tests share the lane's IDE in order. Each test states what BETA 1010 does; the tests named +// The tests share the lane's IDE in order. Each test states what BETA 1034 does; the tests named // "control" pass whether or not the defects are fixed. If another fails after an IDE update, the // IDE has changed: update the entry in BUGS-TO-REPORT.md, or its report in bugs/filed/, the // callout on the page of docs/IDE/Forms/ or docs/IDE/Reports/ that carries it, and then this file. diff --git a/test/ide/memory.test.mjs b/test/ide/memory.test.mjs index 9cf1a3a52..ef20c6e6b 100644 --- a/test/ide/memory.test.mjs +++ b/test/ide/memory.test.mjs @@ -23,7 +23,7 @@ // RemoveRecentsList calls do nothing, as code-editor.test.mjs does. Nothing here reaches // SaveSetting, so lanes.mjs names no settings for this lane. // -// The tests share the lane's IDE in order. Each test states what BETA 1010 does; the tests named +// The tests share the lane's IDE in order. Each test states what BETA 1034 does; the tests named // "control" pass whether or not the defects are fixed. If another fails after an IDE update, the // IDE has changed: update the entry in BUGS-TO-REPORT.md, or its report in bugs/filed/, the // callout on docs/IDE/Memory.md that names it, and then this file. diff --git a/test/ide/restart-file-order.test.mjs b/test/ide/restart-file-order.test.mjs index 2dd8380ec..2b927a6c2 100644 --- a/test/ide/restart-file-order.test.mjs +++ b/test/ide/restart-file-order.test.mjs @@ -21,7 +21,7 @@ // counts is not known, so the lane leaves SPACING between one restart or switch and the next, and checks // the box before each build. // -// Each test states what BETA 1010 does. If one fails after an IDE update, the IDE has changed: update the +// Each test states what BETA 1034 does. If one fails after an IDE update, the IDE has changed: update the // entry "The IDE reverses the order of a project's files at each compiler restart, target switch and // Save, and the code of a build follows that order" in BUGS-TO-REPORT.md, or its report in bugs/filed/, // and then this file. diff --git a/test/ide/typing-after-open.test.mjs b/test/ide/typing-after-open.test.mjs index 7da6a8046..226b6b417 100644 --- a/test/ide/typing-after-open.test.mjs +++ b/test/ide/typing-after-open.test.mjs @@ -15,7 +15,7 @@ // setCursor in scripts/lib/tb-operate.mjs wait out the 700 ms (afterReveal) and // so hide the defect from every other lane. // -// The first test states what BETA 1010 does. If it fails after an IDE update and +// The first test states what BETA 1034 does. If it fails after an IDE update and // line 3 reads "xyz" in order, the defect is fixed: retire the entry, remove the // wait from afterReveal and this file, as the comment in // bugs/filed/typing-after-open-reversed/REPORT.md says. The control must hold diff --git a/test/ide/watches.test.mjs b/test/ide/watches.test.mjs index ae96f5e32..5dc07efcb 100644 --- a/test/ide/watches.test.mjs +++ b/test/ide/watches.test.mjs @@ -6,7 +6,7 @@ // error to the DEBUG CONSOLE. The watches are added with the page's // addNewWatchExpression, which the WATCHES panel's own input calls. // -// Each test states what BETA 1010 does. If one fails after an IDE update, the IDE +// Each test states what BETA 1034 does. If one fails after an IDE update, the IDE // has changed: for ? p, update the report "`? p` in the Debug Console, where `p` is // a user-defined type, fails with a codegen error and a linker error" // (bugs/console-print-udt-linker-error/), a follow-up to twinbasic/twinbasic#2485, diff --git a/test/llvm/README.md b/test/llvm/README.md new file mode 100644 index 000000000..198d255b6 --- /dev/null +++ b/test/llvm/README.md @@ -0,0 +1,55 @@ +# LLVM claims probe + +What the documentation says code compiled with LLVM does, run again on a new BETA. `src/` is a +project whose `Claims` module prints one line, `key: value`, for each claim; `run.mjs` runs it with +`tbrun --exe` in four modes, 32-bit and 64-bit, each with LLVM off and on for the whole project +(**Compiler Options (DEBUG)** and **(BUILD)**, as `tbrun --llvm` sets them), and compares every line +of the IDE run and of the built exe with `expect.json`. `gra64/` is a 64-bit build that must fail. + +It is outside every gate and CI: it needs Windows, a twinBASIC install (the newest +`twinBASIC_IDE_BETA_` on the Desktop, as `tbrun` finds it) and a licence that allows LLVM +(Professional or Ultimate). A run of all five takes about two minutes. + +The callouts on these pages that describe defects have reproducers of their own under `bugs/`, +which `bug_repro.mjs verify` runs; this probe holds the rest of what the pages say, and some details +of those defects that the reproducers do not check. + +## Running it + +```sh +node test/llvm/run.mjs # every mode; exit 0 when all print what expect.json says +node test/llvm/run.mjs --only win64 # one mode +node test/llvm/run.mjs --update # write what this build prints into expect.json +``` + +After a new BETA, run it, and for each difference it lists, change the page that states the claim +(below), then `--update`. Read the diff of `expect.json` before committing it: each changed line is +a changed claim. `expect.json`'s `beta` names the build its lines are from. + +A value in `expect.json` written `/.../` is a regular expression, for a value that changes from one +run to the next: the IDE run's `for-step-llvm`, whose loop variable ends anywhere from 28 to millions. +`--update` keeps such a pattern while the new value still matches it. + +## The keys and the pages + +How each procedure was compiled is read three ways, since no one way works in every mode: `assert` +says whether `Debug.Assert` evaluated its condition (only in an IDE run of code compiled without +LLVM), `return address` whether **InternalGetReturnAddress** returned 0 (under LLVM; 32-bit only), +and `overflow error` the error of a **Double** multiplication that overflows. + +| Key | Claim | Page | +|---|---|---| +| `default` | the project's own options apply | `docs/LLVM/Getting-Started.md`, *LLVM in twinBASIC* | +| `llvm`, `llvm-comma`, `llvm-space`, `llvm-comma-space` | `+llvm` turns LLVM on, the flags separated by commas, spaces, or both | `docs/LLVM/Getting-Started.md`, *Per-procedure LLVM options*; `docs/Reference/Attributes.md`, CompilerOptions | +| `optimize-only`, `optimize-size-only`, `cpu-only` | the other flags do not turn LLVM on, and the attribute replaces the project's options | the same | +| `empty` | `[CompilerOptions("")]` turns LLVM off, under LLVM for the whole project too | the same | +| `llvm-sse`, `llvm-sse2`, `all-cpu` | a CPU flag keeps LLVM on; the long sample compiles and runs; SSE2 changes the overflow | the same, and the **Double** WARNING under *Language support* | +| `assert-false-llvm-went-on`, `assert` in each line | **Debug.Assert** does not stop and does not evaluate its condition under LLVM, nor in a built exe | `docs/LLVM/Getting-Started.md`, *Language support*; `docs/Reference/Default/VBA/Debug.md` | +| `return address` in each line (32-bit) | **InternalGetReturnAddress** is not 0 without LLVM, in an IDE run and an exe, and 0 under LLVM | `docs/Reference/Default/VBA/HiddenModule/AddClassInstanceFlag.md` | +| mode `gra64` | a 64-bit build cannot generate code for a call to **InternalGetReturnAddress** | the same, its NOTE | +| `unroll-llvm` | **UnrollLoop** does not change what the loop does | `docs/Reference/Default/VBA/Compilation/UnrollLoop.md`; the LLVM page, *Unrolling a loop* | +| `double-llvm`, `double-llvm-sse2`, `double-llvm-checks`, `double-plain` | each operation's error, and what the variable holds after it (`kept`: its old value); **FloatingPointErrorChecks(True)** changes nothing | the **Double** WARNING under *Language support*; `docs/Reference/Data-Types/Double.md` | +| `single-llvm`, `single-plain` | the same for **Single**: under LLVM a **Single** division that overflows raises 6, and without LLVM an addition, subtraction, multiplication or division whose result is a **Single** raises nothing | `docs/Reference/Core/Divide.md`, `docs/Reference/Data-Types/Single.md`, `docs/Reference/Operators.md` | +| `for-step-llvm`, `for-step-plain` | a `For` loop whose **Step** is a `ByVal` parameter, with a later `ByRef ... As Boolean`, runs once with a wrong step: the count of passes, and the loop variable after it | the For...Next WARNING under *Language support*; `docs/Reference/Core/For-Next.md` | + +`single-plain`'s `/0` (no error, infinity) is not stated on any page yet: VB6 raises error 11 there. diff --git a/test/llvm/expect.json b/test/llvm/expect.json new file mode 100644 index 000000000..fe6a7080c --- /dev/null +++ b/test/llvm/expect.json @@ -0,0 +1,208 @@ +{ + "beta": 1034, + "modes": { + "win32": { + "ide": { + "default": "assert evaluated, return address set, overflow error 6", + "llvm": "assert skipped, return address 0, overflow error 0", + "llvm-comma": "assert skipped, return address 0, overflow error 0", + "llvm-space": "assert skipped, return address 0, overflow error 0", + "llvm-comma-space": "assert skipped, return address 0, overflow error 0", + "optimize-only": "assert evaluated, return address set, overflow error 6", + "optimize-size-only": "assert evaluated, return address set, overflow error 6", + "cpu-only": "assert evaluated, return address set, overflow error 6", + "empty": "assert evaluated, return address set, overflow error 6", + "llvm-sse": "assert skipped, return address 0, overflow error 0", + "llvm-sse2": "assert skipped, return address 0, overflow error 6", + "all-cpu": "assert skipped, return address 0, overflow error 6", + "assert-false-llvm-went-on": "True", + "unroll-llvm": "5050", + "double-llvm": "+ 0 inf; - 0 -inf; * 0 inf; ^ 0 0; / 6 kept; /0 11 kept;", + "double-llvm-sse2": "+ 6 kept; - 6 kept; * 6 kept; ^ 0 0; / 6 kept; /0 11 kept;", + "double-llvm-checks": "+ 0 inf; - 0 -inf; * 0 inf; ^ 0 0; / 6 kept; /0 11 kept;", + "double-plain": "+ 6 inf; - 6 -inf; * 6 inf; ^ 6 kept; / 6 inf; /0 11 inf;", + "single-llvm": "+ 0 inf; - 0 -inf; * 0 inf; ^ 0 inf; / 6 kept; /0 11 kept;", + "single-plain": "+ 0 inf; - 0 -inf; * 0 inf; ^ 6 kept; / 0 inf; /0 0 inf;", + "for-step-llvm": "/^1 \\d+$/", + "for-step-plain": "3 10" + }, + "exe": { + "default": "assert skipped, return address set, overflow error 6", + "llvm": "assert skipped, return address 0, overflow error 0", + "llvm-comma": "assert skipped, return address 0, overflow error 0", + "llvm-space": "assert skipped, return address 0, overflow error 0", + "llvm-comma-space": "assert skipped, return address 0, overflow error 0", + "optimize-only": "assert skipped, return address set, overflow error 6", + "optimize-size-only": "assert skipped, return address set, overflow error 6", + "cpu-only": "assert skipped, return address set, overflow error 6", + "empty": "assert skipped, return address set, overflow error 6", + "llvm-sse": "assert skipped, return address 0, overflow error 0", + "llvm-sse2": "assert skipped, return address 0, overflow error 6", + "all-cpu": "assert skipped, return address 0, overflow error 6", + "assert-false-llvm-went-on": "True", + "unroll-llvm": "5050", + "double-llvm": "+ 0 inf; - 0 -inf; * 0 inf; ^ 0 0; / 6 kept; /0 11 kept;", + "double-llvm-sse2": "+ 6 kept; - 6 kept; * 6 kept; ^ 0 0; / 6 kept; /0 11 kept;", + "double-llvm-checks": "+ 0 inf; - 0 -inf; * 0 inf; ^ 0 0; / 6 kept; /0 11 kept;", + "double-plain": "+ 6 inf; - 6 -inf; * 6 inf; ^ 6 kept; / 6 inf; /0 11 inf;", + "single-llvm": "+ 0 inf; - 0 -inf; * 0 inf; ^ 0 inf; / 6 kept; /0 11 kept;", + "single-plain": "+ 0 inf; - 0 -inf; * 0 inf; ^ 6 kept; / 0 inf; /0 0 inf;", + "for-step-llvm": "1 26", + "for-step-plain": "3 10" + } + }, + "win32-llvm": { + "ide": { + "default": "assert skipped, return address 0, overflow error 0", + "llvm": "assert skipped, return address 0, overflow error 0", + "llvm-comma": "assert skipped, return address 0, overflow error 0", + "llvm-space": "assert skipped, return address 0, overflow error 0", + "llvm-comma-space": "assert skipped, return address 0, overflow error 0", + "optimize-only": "assert evaluated, return address set, overflow error 6", + "optimize-size-only": "assert evaluated, return address set, overflow error 6", + "cpu-only": "assert evaluated, return address set, overflow error 6", + "empty": "assert evaluated, return address set, overflow error 6", + "llvm-sse": "assert skipped, return address 0, overflow error 0", + "llvm-sse2": "assert skipped, return address 0, overflow error 6", + "all-cpu": "assert skipped, return address 0, overflow error 6", + "assert-false-llvm-went-on": "True", + "unroll-llvm": "5050", + "double-llvm": "+ 0 inf; - 0 -inf; * 0 inf; ^ 0 0; / 6 kept; /0 11 kept;", + "double-llvm-sse2": "+ 6 kept; - 6 kept; * 6 kept; ^ 0 0; / 6 kept; /0 11 kept;", + "double-llvm-checks": "+ 0 inf; - 0 -inf; * 0 inf; ^ 0 0; / 6 kept; /0 11 kept;", + "double-plain": "+ 6 inf; - 6 -inf; * 6 inf; ^ 6 kept; / 6 inf; /0 11 inf;", + "single-llvm": "+ 0 inf; - 0 -inf; * 0 inf; ^ 0 inf; / 6 kept; /0 11 kept;", + "single-plain": "+ 0 inf; - 0 -inf; * 0 inf; ^ 6 kept; / 0 inf; /0 0 inf;", + "for-step-llvm": "/^1 \\d+$/", + "for-step-plain": "3 10" + }, + "exe": { + "default": "assert skipped, return address 0, overflow error 0", + "llvm": "assert skipped, return address 0, overflow error 0", + "llvm-comma": "assert skipped, return address 0, overflow error 0", + "llvm-space": "assert skipped, return address 0, overflow error 0", + "llvm-comma-space": "assert skipped, return address 0, overflow error 0", + "optimize-only": "assert skipped, return address set, overflow error 6", + "optimize-size-only": "assert skipped, return address set, overflow error 6", + "cpu-only": "assert skipped, return address set, overflow error 6", + "empty": "assert skipped, return address set, overflow error 6", + "llvm-sse": "assert skipped, return address 0, overflow error 0", + "llvm-sse2": "assert skipped, return address 0, overflow error 6", + "all-cpu": "assert skipped, return address 0, overflow error 6", + "assert-false-llvm-went-on": "True", + "unroll-llvm": "5050", + "double-llvm": "+ 0 inf; - 0 -inf; * 0 inf; ^ 0 0; / 6 kept; /0 11 kept;", + "double-llvm-sse2": "+ 6 kept; - 6 kept; * 6 kept; ^ 0 0; / 6 kept; /0 11 kept;", + "double-llvm-checks": "+ 0 inf; - 0 -inf; * 0 inf; ^ 0 0; / 6 kept; /0 11 kept;", + "double-plain": "+ 6 inf; - 6 -inf; * 6 inf; ^ 6 kept; / 6 inf; /0 11 inf;", + "single-llvm": "+ 0 inf; - 0 -inf; * 0 inf; ^ 0 inf; / 6 kept; /0 11 kept;", + "single-plain": "+ 0 inf; - 0 -inf; * 0 inf; ^ 6 kept; / 0 inf; /0 0 inf;", + "for-step-llvm": "1 26", + "for-step-plain": "3 10" + } + }, + "win64": { + "ide": { + "default": "assert evaluated, overflow error 6", + "llvm": "assert skipped, overflow error 6", + "llvm-comma": "assert skipped, overflow error 6", + "llvm-space": "assert skipped, overflow error 6", + "llvm-comma-space": "assert skipped, overflow error 6", + "optimize-only": "assert evaluated, overflow error 6", + "optimize-size-only": "assert evaluated, overflow error 6", + "cpu-only": "assert evaluated, overflow error 6", + "empty": "assert evaluated, overflow error 6", + "llvm-sse": "assert skipped, overflow error 6", + "llvm-sse2": "assert skipped, overflow error 6", + "all-cpu": "assert skipped, overflow error 6", + "assert-false-llvm-went-on": "True", + "unroll-llvm": "5050", + "double-llvm": "+ 6 kept; - 6 kept; * 6 kept; ^ 0 0; / 6 kept; /0 11 kept;", + "double-llvm-sse2": "+ 6 kept; - 6 kept; * 6 kept; ^ 0 0; / 6 kept; /0 11 kept;", + "double-llvm-checks": "+ 6 kept; - 6 kept; * 6 kept; ^ 0 0; / 6 kept; /0 11 kept;", + "double-plain": "+ 6 inf; - 6 -inf; * 6 inf; ^ 6 kept; / 6 inf; /0 11 inf;", + "single-llvm": "+ 6 kept; - 6 kept; * 6 kept; ^ 0 inf; / 6 kept; /0 11 kept;", + "single-plain": "+ 0 inf; - 0 -inf; * 0 inf; ^ 6 kept; / 0 inf; /0 0 inf;", + "for-step-llvm": "/^1 \\d+$/", + "for-step-plain": "3 10" + }, + "exe": { + "default": "assert skipped, overflow error 6", + "llvm": "assert skipped, overflow error 6", + "llvm-comma": "assert skipped, overflow error 6", + "llvm-space": "assert skipped, overflow error 6", + "llvm-comma-space": "assert skipped, overflow error 6", + "optimize-only": "assert skipped, overflow error 6", + "optimize-size-only": "assert skipped, overflow error 6", + "cpu-only": "assert skipped, overflow error 6", + "empty": "assert skipped, overflow error 6", + "llvm-sse": "assert skipped, overflow error 6", + "llvm-sse2": "assert skipped, overflow error 6", + "all-cpu": "assert skipped, overflow error 6", + "assert-false-llvm-went-on": "True", + "unroll-llvm": "5050", + "double-llvm": "+ 6 kept; - 6 kept; * 6 kept; ^ 0 0; / 6 kept; /0 11 kept;", + "double-llvm-sse2": "+ 6 kept; - 6 kept; * 6 kept; ^ 0 0; / 6 kept; /0 11 kept;", + "double-llvm-checks": "+ 6 kept; - 6 kept; * 6 kept; ^ 0 0; / 6 kept; /0 11 kept;", + "double-plain": "+ 6 inf; - 6 -inf; * 6 inf; ^ 6 kept; / 6 inf; /0 11 inf;", + "single-llvm": "+ 6 kept; - 6 kept; * 6 kept; ^ 0 inf; / 6 kept; /0 11 kept;", + "single-plain": "+ 0 inf; - 0 -inf; * 0 inf; ^ 6 kept; / 0 inf; /0 0 inf;", + "for-step-llvm": "1 21", + "for-step-plain": "3 10" + } + }, + "win64-llvm": { + "ide": { + "default": "assert skipped, overflow error 6", + "llvm": "assert skipped, overflow error 6", + "llvm-comma": "assert skipped, overflow error 6", + "llvm-space": "assert skipped, overflow error 6", + "llvm-comma-space": "assert skipped, overflow error 6", + "optimize-only": "assert evaluated, overflow error 6", + "optimize-size-only": "assert evaluated, overflow error 6", + "cpu-only": "assert evaluated, overflow error 6", + "empty": "assert evaluated, overflow error 6", + "llvm-sse": "assert skipped, overflow error 6", + "llvm-sse2": "assert skipped, overflow error 6", + "all-cpu": "assert skipped, overflow error 6", + "assert-false-llvm-went-on": "True", + "unroll-llvm": "5050", + "double-llvm": "+ 6 kept; - 6 kept; * 6 kept; ^ 0 0; / 6 kept; /0 11 kept;", + "double-llvm-sse2": "+ 6 kept; - 6 kept; * 6 kept; ^ 0 0; / 6 kept; /0 11 kept;", + "double-llvm-checks": "+ 6 kept; - 6 kept; * 6 kept; ^ 0 0; / 6 kept; /0 11 kept;", + "double-plain": "+ 6 inf; - 6 -inf; * 6 inf; ^ 6 kept; / 6 inf; /0 11 inf;", + "single-llvm": "+ 6 kept; - 6 kept; * 6 kept; ^ 0 inf; / 6 kept; /0 11 kept;", + "single-plain": "+ 0 inf; - 0 -inf; * 0 inf; ^ 6 kept; / 0 inf; /0 0 inf;", + "for-step-llvm": "/^1 \\d+$/", + "for-step-plain": "3 10" + }, + "exe": { + "default": "assert skipped, overflow error 6", + "llvm": "assert skipped, overflow error 6", + "llvm-comma": "assert skipped, overflow error 6", + "llvm-space": "assert skipped, overflow error 6", + "llvm-comma-space": "assert skipped, overflow error 6", + "optimize-only": "assert skipped, overflow error 6", + "optimize-size-only": "assert skipped, overflow error 6", + "cpu-only": "assert skipped, overflow error 6", + "empty": "assert skipped, overflow error 6", + "llvm-sse": "assert skipped, overflow error 6", + "llvm-sse2": "assert skipped, overflow error 6", + "all-cpu": "assert skipped, overflow error 6", + "assert-false-llvm-went-on": "True", + "unroll-llvm": "5050", + "double-llvm": "+ 6 kept; - 6 kept; * 6 kept; ^ 0 0; / 6 kept; /0 11 kept;", + "double-llvm-sse2": "+ 6 kept; - 6 kept; * 6 kept; ^ 0 0; / 6 kept; /0 11 kept;", + "double-llvm-checks": "+ 6 kept; - 6 kept; * 6 kept; ^ 0 0; / 6 kept; /0 11 kept;", + "double-plain": "+ 6 inf; - 6 -inf; * 6 inf; ^ 6 kept; / 6 inf; /0 11 inf;", + "single-llvm": "+ 6 kept; - 6 kept; * 6 kept; ^ 0 inf; / 6 kept; /0 11 kept;", + "single-plain": "+ 0 inf; - 0 -inf; * 0 inf; ^ 6 kept; / 0 inf; /0 0 inf;", + "for-step-llvm": "1 21", + "for-step-plain": "3 10" + } + }, + "gra64": { + "codegenError": true + } + } +} diff --git a/bugs/filed/string-null-character/src/Settings b/test/llvm/gra64/Settings similarity index 86% rename from bugs/filed/string-null-character/src/Settings rename to test/llvm/gra64/Settings index 99b8c6187..6bc2b3445 100644 --- a/bugs/filed/string-null-character/src/Settings +++ b/test/llvm/gra64/Settings @@ -1,12 +1,12 @@ { "configuration.inherits": "Defaults", - "project.appTitle": "StringNullCharacter", + "project.appTitle": "LlvmReturnAddress64", "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", "project.buildType": "Standard EXE", - "project.description": "Reproduces: `String` with a *character* of `Null` raises error 94, where VB6 returns `Null`", + "project.description": "A procedure that calls InternalGetReturnAddress, which a 64-bit build cannot generate code for; run by test/llvm/run.mjs.", "project.exportPathIsV2": true, - "project.id": "{5B37188A-FBCA-47E0-8FA0-6F64E51CE76F}", - "project.name": "StringNullCharacter", + "project.id": "{7B247400-0000-4000-9000-7B2474000E12}", + "project.name": "LlvmReturnAddress64", "project.optionExplicit": true, "project.references": [ { diff --git a/test/llvm/gra64/Sources/Probe.twin b/test/llvm/gra64/Sources/Probe.twin new file mode 100644 index 000000000..877c8fa1c --- /dev/null +++ b/test/llvm/gra64/Sources/Probe.twin @@ -0,0 +1,17 @@ +' A procedure that calls InternalGetReturnAddress: a 64-bit build cannot generate code for it, +' with or without LLVM, and the project does not build (the NOTE on +' docs/Reference/Default/VBA/HiddenModule/AddClassInstanceFlag.md). test/llvm/run.mjs builds it +' for 64-bit and expects the codegen error; ../src/ shows the 32-bit results. +Module Probe + + Private Function Where() As LongPtr + Where = InternalGetReturnAddress() + End Function + + [RunAfterBuild] + Public Sub Run() + Debug.Cls + Debug.Print "address nonzero: "; Where() <> 0 + End Sub + +End Module diff --git a/bugs/filed/abs-minimum-widens-type/src/Sources/Startup.twin b/test/llvm/gra64/Sources/Startup.twin similarity index 100% rename from bugs/filed/abs-minimum-widens-type/src/Sources/Startup.twin rename to test/llvm/gra64/Sources/Startup.twin diff --git a/test/llvm/run.mjs b/test/llvm/run.mjs new file mode 100644 index 000000000..4b398a01b --- /dev/null +++ b/test/llvm/run.mjs @@ -0,0 +1,137 @@ +// Runs the LLVM claims probe in every mode and compares what it prints with expect.json: what the +// documentation says code compiled with LLVM does, checked again on a new BETA. See README.md beside +// this file for the claims and the pages that state them. +// +// node test/llvm/run.mjs [--only ] [--update] [--ide ] +// +// Each mode is one tbrun --exe of src/: an IDE run, whose lines come before tbrun's "--- exe" line, +// and the built exe, whose lines come after it. gra64/ is built for 64-bit alone, and fails. +import { spawnSync } from "node:child_process"; +import { readFileSync, writeFileSync } from "node:fs"; +import path from "node:path"; +import { choiceOption, exitOnCrash, parseCli, printHelpAndExit, withUsageError } from "../../lib/cli.mjs"; + +const MODES = { + win32: [], + "win32-llvm": ["--llvm"], + win64: ["--arch", "win64"], + "win64-llvm": ["--arch", "win64", "--llvm"], +}; +const ALL = [...Object.keys(MODES), "gra64"]; + +const USAGE = `usage: node test/llvm/run.mjs [--only ] [--update] [--ide ] [-h, --help] + +Runs the LLVM claims probe (src/) with tbrun --exe in each mode, and compares every line it prints, +in the IDE run and in the built exe, with expect.json. The modes are win32 and win64, each with LLVM +off and on for the whole project (win32-llvm, win64-llvm), and gra64, a 64-bit build of gra64/ that +must fail with a codegen error. + + --only run one mode: ${ALL.join(", ")} + --update write what the run printed into expect.json, for the modes run; read the diff + before committing it, since each changed line is a changed claim + --ide twinBASIC.exe, as for tbrun (default $TB_IDE, else the newest on the Desktop) + -h, --help print this text and exit + +Exit codes: + 0 every mode printed what expect.json says + 1 a line differs from expect.json, is missing, or is new + 2 the command line was refused, or a mode could not be run`; + +exitOnCrash(); +const usageError = { format: (err) => `${err.message}\n${USAGE}` }; +const { values } = withUsageError( + () => + parseCli(process.argv.slice(2), { + options: { + only: { type: "string" }, + update: { type: "boolean" }, + ide: { type: "string" }, + help: { type: "boolean", short: "h" }, + }, + stopAt: ["help"], + }), + usageError, +); +if (values.help) printHelpAndExit(USAGE); +const only = + values.only && withUsageError(() => choiceOption(values.only, { option: "--only", choices: ALL }), usageError); + +const here = import.meta.dirname; +const repo = path.resolve(here, "..", ".."); +const expectPath = path.join(here, "expect.json"); +const expected = JSON.parse(readFileSync(expectPath, "utf8")); +const ideArgs = values.ide ? ["--ide", values.ide] : []; + +function tbrun(tree, args) { + const run = spawnSync(process.execPath, [path.join(repo, "scripts", "tbrun.mjs"), tree, ...args, ...ideArgs], { + cwd: repo, + encoding: "utf8", + }); + return { status: run.status, text: `${run.stdout ?? ""}${run.stderr ?? ""}` }; +} + +// An expected value written "/.../" is a regular expression, for a value that changes from run to +// run (the IDE run's for-step-llvm); any other is compared exactly. +const isPattern = (w) => typeof w === "string" && w.length > 1 && w.startsWith("/") && w.endsWith("/"); +const matches = (w, g) => (isPattern(w) ? typeof g === "string" && new RegExp(w.slice(1, -1)).test(g) : w === g); + +// What --update writes: the run's values, except that a pattern the new value still matches stays. +function merge(w, g) { + if (typeof g !== "object" || g === null) return isPattern(w) && matches(w, g) ? w : g; + return Object.fromEntries(Object.entries(g).map(([k, v]) => [k, merge(w?.[k], v)])); +} + +// "key: value" lines, by side: the IDE run's before tbrun's "--- exe" line, the exe's after it. +function sides(text) { + const got = { ide: {}, exe: {} }; + let side = "ide"; + for (const line of text.split(/\r?\n/)) { + if (/^--- exe/.test(line)) side = "exe"; + const m = /^([a-z0-9-]+): (.*)$/.exec(line); + if (m) got[side][m[1]] = m[2].trimEnd(); + } + return got; +} + +const differences = []; +let failed = false; +for (const mode of only ? [only] : ALL) { + process.stdout.write(`${mode}: `); + let got; + if (mode === "gra64") { + const run = tbrun(path.join(here, "gra64"), ["--arch", "win64"]); + got = { codegenError: /\[LINKER\] compilation \(codegen\) error detected in 'Probe\.Where'/.test(run.text) }; + } else { + const run = tbrun(path.join(here, "src"), ["--exe", ...MODES[mode]]); + got = sides(run.text); + if (run.status !== 0 || !Object.keys(got.ide).length || !Object.keys(got.exe).length) { + console.log(`tbrun exited ${run.status}`); + console.log(run.text.trim().split("\n").slice(-30).join("\n")); + failed = true; + continue; + } + } + const want = expected.modes[mode] ?? {}; + if (values.update) { + expected.modes[mode] = merge(want, got); + console.log("recorded"); + continue; + } + const before = differences.length; + const compare = (where, w, g) => { + for (const k of new Set([...Object.keys(w ?? {}), ...Object.keys(g ?? {})])) { + if (typeof (w?.[k] ?? g?.[k]) === "object") compare(`${where}${k}/`, w?.[k], g?.[k]); + else if (!matches(w?.[k], g?.[k])) + differences.push(`${mode} ${where}${k}: expected ${JSON.stringify(w?.[k])}, got ${JSON.stringify(g?.[k])}`); + } + }; + compare("", want, got); + console.log(differences.length === before ? "as expected" : `${differences.length - before} difference(s)`); +} + +if (values.update) { + writeFileSync(expectPath, `${JSON.stringify(expected, null, 2)}\n`); + console.log(`wrote ${path.relative(repo, expectPath)}`); +} +for (const d of differences) console.log(` ${d}`); +process.exit(failed ? 2 : differences.length ? 1 : 0); diff --git a/bugs/filed/round-null-negative-digits/src/Settings b/test/llvm/src/Settings similarity index 88% rename from bugs/filed/round-null-negative-digits/src/Settings rename to test/llvm/src/Settings index f08ea8e5f..1c2290450 100644 --- a/bugs/filed/round-null-negative-digits/src/Settings +++ b/test/llvm/src/Settings @@ -1,12 +1,12 @@ { "configuration.inherits": "Defaults", - "project.appTitle": "RoundNullNegativeDigits", + "project.appTitle": "LlvmClaims", "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", "project.buildType": "Standard EXE", - "project.description": "Reproduces: x", + "project.description": "The LLVM claims of the documentation, run in each mode by test/llvm/run.mjs (README.md beside it).", "project.exportPathIsV2": true, - "project.id": "{4FCE0798-9BD5-4F1D-BED0-666E98B31077}", - "project.name": "RoundNullNegativeDigits", + "project.id": "{7B247400-0000-4000-9000-7B2474000E11}", + "project.name": "LlvmClaims", "project.optionExplicit": true, "project.references": [ { diff --git a/test/llvm/src/Sources/Claims.twin b/test/llvm/src/Sources/Claims.twin new file mode 100644 index 000000000..e3083a8fd --- /dev/null +++ b/test/llvm/src/Sources/Claims.twin @@ -0,0 +1,421 @@ +' The claims the documentation makes about code compiled with LLVM, each printed as one line +' "key: value". test/llvm/run.mjs runs this project in 32-bit and 64-bit, with LLVM off and on +' for the whole project, in an IDE run and in the built exe, and compares every line with +' expect.json. README.md beside run.mjs names the page that states each claim. +' +' Which procedures LLVM compiled is read three ways, since no one way works everywhere: whether +' Debug.Assert evaluated its condition (it does only in an IDE run of code compiled without +' LLVM), whether InternalGetReturnAddress returned 0 (it does under LLVM; 32-bit only, since a +' 64-bit build cannot generate code for it: gra64/), and the error a Double multiplication that +' overflows raised (6 without LLVM; none with it, except in 32-bit code built for SSE2). +' +' Under LLVM for the whole project (run.mjs's llvm modes), a procedure that is not compiled +' with LLVM must not call one that is: an IDE run ends in an access violation (the WARNING +' under Per-procedure LLVM options). So MarkEvaluated, which every variant calls, is marked +' [CompilerOptions("")] too, and the variants call nothing else of this project. +Module Claims + + Private Sink As Double + Private Evaluated As Boolean + + [RunAfterBuild] + Public Sub Built() + Debug.Cls + Report + End Sub + + Public Sub Report() + Dim big As Double = 1E+308 + + ' Per-procedure LLVM options: which spellings turn LLVM on, and the long sample. + Out "default", Default(big) + Out "llvm", Llvm(big) + Out "llvm-comma", LlvmComma(big) + Out "llvm-space", LlvmSpace(big) + Out "llvm-comma-space", LlvmCommaSpace(big) + Out "optimize-only", OptimizeOnly(big) + Out "optimize-size-only", OptimizeSizeOnly(big) + Out "cpu-only", CpuOnly(big) + Out "empty", EmptyString(big) + Out "llvm-sse", LlvmSse(big) + Out "llvm-sse2", LlvmSse2(big) + Out "all-cpu", AllCpu(big) + + ' Debug.Assert under LLVM does not stop. + AssertFalseLlvm + Out "assert-false-llvm-went-on", True + + ' UnrollLoop does not change what the loop does. + Dim values(1 To 100) As Long + Dim n As Long + For n = 1 To 100 + values(n) = n + Next + Out "unroll-llvm", TotalLlvm(values) + + ' Arithmetic that overflows: each operation's error, and what the variable then holds. + Out "double-llvm", DoubleLlvm(big) + Out "double-llvm-sse2", DoubleLlvmSse2(big) + Out "double-llvm-checks", DoubleLlvmChecks(big) + Out "double-plain", DoublePlain(big) + Out "single-llvm", SingleLlvm(3E+38) + Out "single-plain", SinglePlain(3E+38) + + ' For...Next with a ByVal step and a later ByRef Boolean: the count of passes, and i after. + Dim flag As Boolean + Out "for-step-llvm", StepLlvm(3, flag) + Out "for-step-plain", StepPlain(3, flag) + End Sub + + Private Sub Out(ByVal key As String, ByVal value As Variant) + TbRun.Out key & ": " & CStr(value) + End Sub + + [CompilerOptions("")] + Private Function MarkEvaluated() As Boolean + Evaluated = True + MarkEvaluated = True + End Function + + ' The project's own options. + Private Function Default(ByVal x As Double) As String + Dim facts As String + Evaluated = False + Debug.Assert MarkEvaluated() + facts = IIf(Evaluated, "assert evaluated", "assert skipped") + #If Win64 = False Then + facts = facts & IIf(InternalGetReturnAddress() = 0, ", return address 0", ", return address set") + #End If + On Error Resume Next + Sink = x * 10 + facts = facts & ", overflow error " & Err.Number + Default = facts + End Function + + [CompilerOptions("+llvm")] + Private Function Llvm(ByVal x As Double) As String + Dim facts As String + Evaluated = False + Debug.Assert MarkEvaluated() + facts = IIf(Evaluated, "assert evaluated", "assert skipped") + #If Win64 = False Then + facts = facts & IIf(InternalGetReturnAddress() = 0, ", return address 0", ", return address set") + #End If + On Error Resume Next + Sink = x * 10 + facts = facts & ", overflow error " & Err.Number + Llvm = facts + End Function + + ' Commas. + [CompilerOptions("+llvm,+optimize")] + Private Function LlvmComma(ByVal x As Double) As String + Dim facts As String + Evaluated = False + Debug.Assert MarkEvaluated() + facts = IIf(Evaluated, "assert evaluated", "assert skipped") + #If Win64 = False Then + facts = facts & IIf(InternalGetReturnAddress() = 0, ", return address 0", ", return address set") + #End If + On Error Resume Next + Sink = x * 10 + facts = facts & ", overflow error " & Err.Number + LlvmComma = facts + End Function + + ' Spaces. + [CompilerOptions("+llvm +optimize")] + Private Function LlvmSpace(ByVal x As Double) As String + Dim facts As String + Evaluated = False + Debug.Assert MarkEvaluated() + facts = IIf(Evaluated, "assert evaluated", "assert skipped") + #If Win64 = False Then + facts = facts & IIf(InternalGetReturnAddress() = 0, ", return address 0", ", return address set") + #End If + On Error Resume Next + Sink = x * 10 + facts = facts & ", overflow error " & Err.Number + LlvmSpace = facts + End Function + + ' Commas and spaces. + [CompilerOptions("+llvm, +optimize")] + Private Function LlvmCommaSpace(ByVal x As Double) As String + Dim facts As String + Evaluated = False + Debug.Assert MarkEvaluated() + facts = IIf(Evaluated, "assert evaluated", "assert skipped") + #If Win64 = False Then + facts = facts & IIf(InternalGetReturnAddress() = 0, ", return address 0", ", return address set") + #End If + On Error Resume Next + Sink = x * 10 + facts = facts & ", overflow error " & Err.Number + LlvmCommaSpace = facts + End Function + + ' No +llvm. + [CompilerOptions("+optimize")] + Private Function OptimizeOnly(ByVal x As Double) As String + Dim facts As String + Evaluated = False + Debug.Assert MarkEvaluated() + facts = IIf(Evaluated, "assert evaluated", "assert skipped") + #If Win64 = False Then + facts = facts & IIf(InternalGetReturnAddress() = 0, ", return address 0", ", return address set") + #End If + On Error Resume Next + Sink = x * 10 + facts = facts & ", overflow error " & Err.Number + OptimizeOnly = facts + End Function + + ' No +llvm. + [CompilerOptions("+optimize,+optimizesize")] + Private Function OptimizeSizeOnly(ByVal x As Double) As String + Dim facts As String + Evaluated = False + Debug.Assert MarkEvaluated() + facts = IIf(Evaluated, "assert evaluated", "assert skipped") + #If Win64 = False Then + facts = facts & IIf(InternalGetReturnAddress() = 0, ", return address 0", ", return address set") + #End If + On Error Resume Next + Sink = x * 10 + facts = facts & ", overflow error " & Err.Number + OptimizeSizeOnly = facts + End Function + + ' No +llvm. + [CompilerOptions("+sse2")] + Private Function CpuOnly(ByVal x As Double) As String + Dim facts As String + Evaluated = False + Debug.Assert MarkEvaluated() + facts = IIf(Evaluated, "assert evaluated", "assert skipped") + #If Win64 = False Then + facts = facts & IIf(InternalGetReturnAddress() = 0, ", return address 0", ", return address set") + #End If + On Error Resume Next + Sink = x * 10 + facts = facts & ", overflow error " & Err.Number + CpuOnly = facts + End Function + + ' An empty string: LLVM off. + [CompilerOptions("")] + Private Function EmptyString(ByVal x As Double) As String + Dim facts As String + Evaluated = False + Debug.Assert MarkEvaluated() + facts = IIf(Evaluated, "assert evaluated", "assert skipped") + #If Win64 = False Then + facts = facts & IIf(InternalGetReturnAddress() = 0, ", return address 0", ", return address set") + #End If + On Error Resume Next + Sink = x * 10 + facts = facts & ", overflow error " & Err.Number + EmptyString = facts + End Function + + [CompilerOptions("+llvm,+sse")] + Private Function LlvmSse(ByVal x As Double) As String + Dim facts As String + Evaluated = False + Debug.Assert MarkEvaluated() + facts = IIf(Evaluated, "assert evaluated", "assert skipped") + #If Win64 = False Then + facts = facts & IIf(InternalGetReturnAddress() = 0, ", return address 0", ", return address set") + #End If + On Error Resume Next + Sink = x * 10 + facts = facts & ", overflow error " & Err.Number + LlvmSse = facts + End Function + + [CompilerOptions("+llvm,+sse2")] + Private Function LlvmSse2(ByVal x As Double) As String + Dim facts As String + Evaluated = False + Debug.Assert MarkEvaluated() + facts = IIf(Evaluated, "assert evaluated", "assert skipped") + #If Win64 = False Then + facts = facts & IIf(InternalGetReturnAddress() = 0, ", return address 0", ", return address set") + #End If + On Error Resume Next + Sink = x * 10 + facts = facts & ", overflow error " & Err.Number + LlvmSse2 = facts + End Function + + ' The long sample of the page. + [CompilerOptions("+llvm,+optimize,+optimizesize,+aes,+avx,+avx2,+bmi2,+fma,+fxsr,+lzcnt,+popcnt,+rdseed,+sha,+sse,+sse2,+sse3,+sse4.1,+sse4.2,+ssse3,+xsave,+xsavec,+xsaveopt,+xsaves")] + Private Function AllCpu(ByVal x As Double) As String + Dim facts As String + Evaluated = False + Debug.Assert MarkEvaluated() + facts = IIf(Evaluated, "assert evaluated", "assert skipped") + #If Win64 = False Then + facts = facts & IIf(InternalGetReturnAddress() = 0, ", return address 0", ", return address set") + #End If + On Error Resume Next + Sink = x * 10 + facts = facts & ", overflow error " & Err.Number + AllCpu = facts + End Function + + [CompilerOptions("+llvm")] + Private Sub AssertFalseLlvm() + Debug.Assert False + End Sub + + [CompilerOptions("+llvm,+optimize")] + Private Function TotalLlvm(Values() As Long) As Long + Dim i As Long + For i = LBound(Values) To UBound(Values) + UnrollLoop 8 + TotalLlvm += Values(i) + Next + End Function + + [CompilerOptions("+llvm")] + Private Function DoubleLlvm(ByVal x As Double) As String + Dim v As Double, r As String + Dim ten As Double = 10, tenth As Double = 0.1, two As Double = 2, zero As Double = 0 + On Error Resume Next + v = 1: Err.Clear: v = x + x + r = r & "+ " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 1E+308, "inf", IIf(v < -1E+308, "-inf", CStr(v))))) & "; " + v = 1: Err.Clear: v = -x - x + r = r & "- " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 1E+308, "inf", IIf(v < -1E+308, "-inf", CStr(v))))) & "; " + v = 1: Err.Clear: v = x * ten + r = r & "* " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 1E+308, "inf", IIf(v < -1E+308, "-inf", CStr(v))))) & "; " + v = 1: Err.Clear: v = x ^ two + r = r & "^ " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 1E+308, "inf", IIf(v < -1E+308, "-inf", CStr(v))))) & "; " + v = 1: Err.Clear: v = x / tenth + r = r & "/ " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 1E+308, "inf", IIf(v < -1E+308, "-inf", CStr(v))))) & "; " + v = 1: Err.Clear: v = x / zero + r = r & "/0 " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 1E+308, "inf", IIf(v < -1E+308, "-inf", CStr(v))))) & "; " + DoubleLlvm = r + End Function + + [CompilerOptions("+llvm,+sse2")] + Private Function DoubleLlvmSse2(ByVal x As Double) As String + Dim v As Double, r As String + Dim ten As Double = 10, tenth As Double = 0.1, two As Double = 2, zero As Double = 0 + On Error Resume Next + v = 1: Err.Clear: v = x + x + r = r & "+ " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 1E+308, "inf", IIf(v < -1E+308, "-inf", CStr(v))))) & "; " + v = 1: Err.Clear: v = -x - x + r = r & "- " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 1E+308, "inf", IIf(v < -1E+308, "-inf", CStr(v))))) & "; " + v = 1: Err.Clear: v = x * ten + r = r & "* " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 1E+308, "inf", IIf(v < -1E+308, "-inf", CStr(v))))) & "; " + v = 1: Err.Clear: v = x ^ two + r = r & "^ " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 1E+308, "inf", IIf(v < -1E+308, "-inf", CStr(v))))) & "; " + v = 1: Err.Clear: v = x / tenth + r = r & "/ " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 1E+308, "inf", IIf(v < -1E+308, "-inf", CStr(v))))) & "; " + v = 1: Err.Clear: v = x / zero + r = r & "/0 " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 1E+308, "inf", IIf(v < -1E+308, "-inf", CStr(v))))) & "; " + DoubleLlvmSse2 = r + End Function + + [CompilerOptions("+llvm")] + [FloatingPointErrorChecks(True)] + Private Function DoubleLlvmChecks(ByVal x As Double) As String + Dim v As Double, r As String + Dim ten As Double = 10, tenth As Double = 0.1, two As Double = 2, zero As Double = 0 + On Error Resume Next + v = 1: Err.Clear: v = x + x + r = r & "+ " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 1E+308, "inf", IIf(v < -1E+308, "-inf", CStr(v))))) & "; " + v = 1: Err.Clear: v = -x - x + r = r & "- " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 1E+308, "inf", IIf(v < -1E+308, "-inf", CStr(v))))) & "; " + v = 1: Err.Clear: v = x * ten + r = r & "* " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 1E+308, "inf", IIf(v < -1E+308, "-inf", CStr(v))))) & "; " + v = 1: Err.Clear: v = x ^ two + r = r & "^ " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 1E+308, "inf", IIf(v < -1E+308, "-inf", CStr(v))))) & "; " + v = 1: Err.Clear: v = x / tenth + r = r & "/ " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 1E+308, "inf", IIf(v < -1E+308, "-inf", CStr(v))))) & "; " + v = 1: Err.Clear: v = x / zero + r = r & "/0 " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 1E+308, "inf", IIf(v < -1E+308, "-inf", CStr(v))))) & "; " + DoubleLlvmChecks = r + End Function + + [CompilerOptions("")] + Private Function DoublePlain(ByVal x As Double) As String + Dim v As Double, r As String + Dim ten As Double = 10, tenth As Double = 0.1, two As Double = 2, zero As Double = 0 + On Error Resume Next + v = 1: Err.Clear: v = x + x + r = r & "+ " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 1E+308, "inf", IIf(v < -1E+308, "-inf", CStr(v))))) & "; " + v = 1: Err.Clear: v = -x - x + r = r & "- " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 1E+308, "inf", IIf(v < -1E+308, "-inf", CStr(v))))) & "; " + v = 1: Err.Clear: v = x * ten + r = r & "* " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 1E+308, "inf", IIf(v < -1E+308, "-inf", CStr(v))))) & "; " + v = 1: Err.Clear: v = x ^ two + r = r & "^ " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 1E+308, "inf", IIf(v < -1E+308, "-inf", CStr(v))))) & "; " + v = 1: Err.Clear: v = x / tenth + r = r & "/ " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 1E+308, "inf", IIf(v < -1E+308, "-inf", CStr(v))))) & "; " + v = 1: Err.Clear: v = x / zero + r = r & "/0 " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 1E+308, "inf", IIf(v < -1E+308, "-inf", CStr(v))))) & "; " + DoublePlain = r + End Function + + [CompilerOptions("+llvm")] + Private Function SingleLlvm(ByVal x As Single) As String + Dim v As Single, r As String + Dim ten As Single = 10, tenth As Single = 0.1, two As Single = 2, zero As Single = 0 + On Error Resume Next + v = 1: Err.Clear: v = x + x + r = r & "+ " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 3.4E+38, "inf", IIf(v < -3.4E+38, "-inf", CStr(v))))) & "; " + v = 1: Err.Clear: v = -x - x + r = r & "- " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 3.4E+38, "inf", IIf(v < -3.4E+38, "-inf", CStr(v))))) & "; " + v = 1: Err.Clear: v = x * ten + r = r & "* " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 3.4E+38, "inf", IIf(v < -3.4E+38, "-inf", CStr(v))))) & "; " + v = 1: Err.Clear: v = x ^ two + r = r & "^ " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 3.4E+38, "inf", IIf(v < -3.4E+38, "-inf", CStr(v))))) & "; " + v = 1: Err.Clear: v = x / tenth + r = r & "/ " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 3.4E+38, "inf", IIf(v < -3.4E+38, "-inf", CStr(v))))) & "; " + v = 1: Err.Clear: v = x / zero + r = r & "/0 " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 3.4E+38, "inf", IIf(v < -3.4E+38, "-inf", CStr(v))))) & "; " + SingleLlvm = r + End Function + + [CompilerOptions("")] + Private Function SinglePlain(ByVal x As Single) As String + Dim v As Single, r As String + Dim ten As Single = 10, tenth As Single = 0.1, two As Single = 2, zero As Single = 0 + On Error Resume Next + v = 1: Err.Clear: v = x + x + r = r & "+ " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 3.4E+38, "inf", IIf(v < -3.4E+38, "-inf", CStr(v))))) & "; " + v = 1: Err.Clear: v = -x - x + r = r & "- " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 3.4E+38, "inf", IIf(v < -3.4E+38, "-inf", CStr(v))))) & "; " + v = 1: Err.Clear: v = x * ten + r = r & "* " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 3.4E+38, "inf", IIf(v < -3.4E+38, "-inf", CStr(v))))) & "; " + v = 1: Err.Clear: v = x ^ two + r = r & "^ " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 3.4E+38, "inf", IIf(v < -3.4E+38, "-inf", CStr(v))))) & "; " + v = 1: Err.Clear: v = x / tenth + r = r & "/ " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 3.4E+38, "inf", IIf(v < -3.4E+38, "-inf", CStr(v))))) & "; " + v = 1: Err.Clear: v = x / zero + r = r & "/0 " & Err.Number & " " & IIf(v = 1, "kept", IIf(v = 0, "0", IIf(v > 3.4E+38, "inf", IIf(v < -3.4E+38, "-inf", CStr(v))))) & "; " + SinglePlain = r + End Function + + [CompilerOptions("+llvm")] + Private Function StepLlvm(ByVal x As Long, ByRef b As Boolean) As String + Dim i As Long, count As Long + For i = 1 To 7 Step x + count = count + 1 + Next + StepLlvm = count & " " & i + End Function + + [CompilerOptions("")] + Private Function StepPlain(ByVal x As Long, ByRef b As Boolean) As String + Dim i As Long, count As Long + For i = 1 To 7 Step x + count = count + 1 + Next + StepPlain = count & " " & i + End Function + +End Module diff --git a/test/llvm/src/Sources/Startup.twin b/test/llvm/src/Sources/Startup.twin new file mode 100644 index 000000000..e9a8e5f74 --- /dev/null +++ b/test/llvm/src/Sources/Startup.twin @@ -0,0 +1,8 @@ +Module Startup + + ' What the built exe runs; an IDE run gets the same lines from Claims.Built. + Public Sub Main() + Claims.Report + End Sub + +End Module From 0351f584b90e639f0630e8c959e576527ef5131a Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Mon, 2 Nov 2026 22:26:32 +0100 Subject: [PATCH 03/19] ArrayListLib 1.6.151.1 and WinReg 1.0.1.1 still do not compile on BETA 1034, and TBMANLIB 1.0.0.29 still fails in 64-bit only (same TB5001s): their callouts, the Experimental entries and the WIP notes name BETA 1034 --- WIP.ExamplesBuild.md | 2 +- WIP.md | 2 +- docs/Reference/Contributed/ArrayListLib/index.md | 2 +- docs/Reference/Contributed/TBMANLIB/index.md | 2 +- docs/Reference/Contributed/WinReg/index.md | 2 +- docs/Reference/Experimental.md | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/WIP.ExamplesBuild.md b/WIP.ExamplesBuild.md index 8fcfe58d8..b14fdeaba 100644 --- a/WIP.ExamplesBuild.md +++ b/WIP.ExamplesBuild.md @@ -338,7 +338,7 @@ the repository's to commit: four of the nine ship no licence file, and WinReg em and the Scripting Runtime itself. The WinReg landing page tells the reader the same. - **An error inside an embedded package is the package's, and fails nothing** (`splitPackageFaults`). WinReg 1.0.1.1 and ArrayListLib 1.6.151.1 do not compile on BETA - 1010: each passes a pointer to a `ByRef` structure parameter without `ByVal` (TB5001). The + 1034: each passes a pointer to a `ByRef` structure parameter without `ByVal` (TB5001). The template-alone build finds those rows; the run prints them as a note naming the package rather than as a template FAIL, every other template row still fails, and the samples are judged by their own rows as before. Their landing pages say so in a NOTE, and both sections diff --git a/WIP.md b/WIP.md index 2352e83d6..c60a3bb2a 100644 --- a/WIP.md +++ b/WIP.md @@ -40,7 +40,7 @@ sibling. History goes nowhere: git keeps it. Reference documentation is **complete** for all thirteen packages: VBA, VBRUN, VB, WebView2, Assert, CustomControls, CEF, WinEventLogLib, WinNamedPipesLib, WinServicesLib, tbIDE, WinNativeCommonCtls and AppGlobalClassObject. It is adapted from primary sources: Microsoft VBA-Docs (CC-BY-4.0) for the runtime library, `.twin` source for the twinBASIC-specific packages. The CEF and WebView2 packages also carry a tutorial set. -Twenty-seven **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: ArrayListLib, DetoursLib, FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerLib, tbFileSysTools, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsJson, tsPwGen, VBA_JSON, xCalendar, AutoCompleteLib and VBComDlg complete; BigNumberLib its main class only. TBMANLIB_ConsoleCore's console claims are checked by its test cases, each on a console screen buffer of its own with its input written by `WriteConsoleInput`; TBMANLIB_ConsoleComponents' follow the source, because the samples harness cannot capture a console. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-tests/xCalendar/probe/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. VBComDlg's `exclude_from_docs` names `CommonDialogBase`, and AutoCompleteLib's `AutoCompleteHandle`, each a module whose public procedures only its class calls. DetoursLib's names its demonstration module, `DetoursTest` (message boxes), which its landing page describes in a paragraph. Its test cases run in the IDE's own process, so each detours `MulDiv` of kernel32 and answers for itself only for one marked argument, passing every other call (another thread's included) to the original. `CreateDetourAtFuncAddress` does not check a *newAddress* of 0, and SlimDetours then reads address 0 inside a transaction that has the process's other threads suspended; no case tries it, since it would leave the IDE frozen, and the page states the missing check from the source alone. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modImpExp` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1010). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-tests/TBMANLIB_Dialog/probe/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. TBMANLIB_Dialog 1.1.0.0 has a page of its own for each class except cDialog, which keeps a page for each member, so `analyze` in audit mode lists the members of those one-page classes as undocumented; its `exclude_from_docs` names its test module, `TestModule`, and the seven COM interface declarations that cDialogEx calls inside (`IOleWindow`, `IModalWindow`, `IShellItem`, `IShellItemArray`, `IFileDialog`, `IFileOpenDialog`, `IFileSaveDialog`), which its landing page describes in a paragraph. cDialogEx 1.1.0.0 returns the name of the chosen file, not its path, ignores `InitialDir` and ignores a `Filter` that has no closing pipe, each documented as a WARNING. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. tbRegExp 1.9.0.0 raises `vbObjectError + 3016` from a search that uses up its step budget and returns nothing (1.8.0.0's `SearchAborted` is gone); the budget is 10 steps for each character squared, at least 1,000,000 and at most 100,000,000, for the search of one match, and `RegExp.SearchStepLimit` replaces it with a fixed number, documented on its own page and in a section of the landing page. On the dev box (BETA 1010, win32) `\d+x` costs 2.0 × n² steps (2.002 to 2.009 for 500 to 2,000 digits), so it finishes on 7,000 digits (16 s) and raises on 10,000 (16 s); `(a+)+b` raises in 0.2 s on 30 or 300 `a` but takes 16 s on 3,000; `a*` matches 1,000,000 characters whole in about half a second. Its pattern syntax has a page of its own, `PatternSyntax.md`. Each one that is not Experimental has a chapter in the book's Packages part; the Experimental ones are left out of the book. **ArrayListLib, GdiPlus, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_Xml, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner): ArrayListLib and WinReg do not compile on BETA 1010 (each documented from its source and a copy of it that builds; ArrayListLib's samples are compile-only, and WinReg's are not marked `check_build` at all, because under four lanes its template's compile outruns the 10 s limit: mark them again when a version of WinReg compiles; in a copy of ArrayListLib 1.6.151.1 with the arguments of its three `VSwap` calls passed `ByVal` and `LSetByVal` taking a `LongPtr`, a For Each over a list with no items fails, ToString of such a list never returns, Reverse keeps the middle pair of an even run in place and a second GetEnumerator over a list ignores its arguments; in a copy of WinReg's source with its four failing calls passed `ByVal`, ReadString, GetRegistryValueType and the first ConvertValueToString give wrong results), xCalendar has a grid that keeps the old month's dates when Value is set to another month, and text colour properties that have no effect, TBMANLIB 1.0.0.29 does not compile in a 64-bit build (TB5001 in cSubClass, where the window procedure is passed as a Long) and has many members that fail or give wrong results, TBMANLIB_ConsoleComponents cannot take a cConsole of the published TBMANLIB_ConsoleCore (it embeds a copy of it, the same package at 1.0.0.1, whose types the compiler treats as different; checked in a project made by the IDE's own import, in either order), TBMANLIB_ConsoleCore reads keys wrongly, TBMANLIB_Diagnostic cannot create its Dictionary and misplaces List items, TBMANLIB_Dialog 1.1.0.0 has classes that fail at their main task (cDialogFindReplace reports nothing, cDialogPageSetup ignores paper and orientation, cDialogEx returns names without their folder, cDialogPassword shows the password in its confirmation boxes; every defect confirmed by a second agent, owner), TBMANLIB_ListBoxHScroll 1.0.0.1 moves the thumb 20 px per arrow and one page per track click while the list box scrolls its contents by its own step, so thumb and text disagree (confirmed by a second agent, owner), TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text (and its XPath engine reads the step `..` and `and` with `or` wrongly). TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph, and the five types of its XPath engine (`XPathTokenType`, `XPathAxisType`, `XPathOperator`, `XPathStep`, `XPathPredicate`), which no public member takes or returns and which the mXPath page mentions in a paragraph; tsPwGen's names its five test modules and its six internal modules, and tsJson's its test and internal modules likewise, which their landing pages describe in a paragraph each. TBMANLIB_Diagnostic's `List` page has a NOTE labelled BETA 1010 under its `SetItem` sample: in a 32-bit IDE run `Err.Description` of the error 5 that `SetItem 0` raises on a list of one item (`Dim x As New List(Of String)`) is "Automation error", while the exe, a 64-bit IDE run and VB6 give "Invalid procedure call or argument" (`bugs/list-error5-automation-text`, with its case `ListTests.SetItem_AtZeroOnOneItemList_ErrorHasTheRuntimeText`). The samples harness runs in the IDE and has no per-sample way to state output that differs from an exe's, so that sample prints `Err.Number` only and the text is in the NOTE; when the entry is retired the NOTE goes and the sample prints `Err.Description` again, with the output `5: Invalid procedure call or argument`. See *Where things live* for how a package moves between the two lists. +Twenty-seven **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: ArrayListLib, DetoursLib, FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerLib, tbFileSysTools, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsJson, tsPwGen, VBA_JSON, xCalendar, AutoCompleteLib and VBComDlg complete; BigNumberLib its main class only. TBMANLIB_ConsoleCore's console claims are checked by its test cases, each on a console screen buffer of its own with its input written by `WriteConsoleInput`; TBMANLIB_ConsoleComponents' follow the source, because the samples harness cannot capture a console. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-tests/xCalendar/probe/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. VBComDlg's `exclude_from_docs` names `CommonDialogBase`, and AutoCompleteLib's `AutoCompleteHandle`, each a module whose public procedures only its class calls. DetoursLib's names its demonstration module, `DetoursTest` (message boxes), which its landing page describes in a paragraph. Its test cases run in the IDE's own process, so each detours `MulDiv` of kernel32 and answers for itself only for one marked argument, passing every other call (another thread's included) to the original. `CreateDetourAtFuncAddress` does not check a *newAddress* of 0, and SlimDetours then reads address 0 inside a transaction that has the process's other threads suspended; no case tries it, since it would leave the IDE frozen, and the page states the missing check from the source alone. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modImpExp` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1010). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-tests/TBMANLIB_Dialog/probe/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. TBMANLIB_Dialog 1.1.0.0 has a page of its own for each class except cDialog, which keeps a page for each member, so `analyze` in audit mode lists the members of those one-page classes as undocumented; its `exclude_from_docs` names its test module, `TestModule`, and the seven COM interface declarations that cDialogEx calls inside (`IOleWindow`, `IModalWindow`, `IShellItem`, `IShellItemArray`, `IFileDialog`, `IFileOpenDialog`, `IFileSaveDialog`), which its landing page describes in a paragraph. cDialogEx 1.1.0.0 returns the name of the chosen file, not its path, ignores `InitialDir` and ignores a `Filter` that has no closing pipe, each documented as a WARNING. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. tbRegExp 1.9.0.0 raises `vbObjectError + 3016` from a search that uses up its step budget and returns nothing (1.8.0.0's `SearchAborted` is gone); the budget is 10 steps for each character squared, at least 1,000,000 and at most 100,000,000, for the search of one match, and `RegExp.SearchStepLimit` replaces it with a fixed number, documented on its own page and in a section of the landing page. On the dev box (BETA 1010, win32) `\d+x` costs 2.0 × n² steps (2.002 to 2.009 for 500 to 2,000 digits), so it finishes on 7,000 digits (16 s) and raises on 10,000 (16 s); `(a+)+b` raises in 0.2 s on 30 or 300 `a` but takes 16 s on 3,000; `a*` matches 1,000,000 characters whole in about half a second. Its pattern syntax has a page of its own, `PatternSyntax.md`. Each one that is not Experimental has a chapter in the book's Packages part; the Experimental ones are left out of the book. **ArrayListLib, GdiPlus, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_Xml, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner): ArrayListLib and WinReg do not compile on BETA 1034 (each documented from its source and a copy of it that builds; ArrayListLib's samples are compile-only, and WinReg's are not marked `check_build` at all, because under four lanes its template's compile outruns the 10 s limit: mark them again when a version of WinReg compiles; in a copy of ArrayListLib 1.6.151.1 with the arguments of its three `VSwap` calls passed `ByVal` and `LSetByVal` taking a `LongPtr`, a For Each over a list with no items fails, ToString of such a list never returns, Reverse keeps the middle pair of an even run in place and a second GetEnumerator over a list ignores its arguments; in a copy of WinReg's source with its four failing calls passed `ByVal`, ReadString, GetRegistryValueType and the first ConvertValueToString give wrong results), xCalendar has a grid that keeps the old month's dates when Value is set to another month, and text colour properties that have no effect, TBMANLIB 1.0.0.29 does not compile in a 64-bit build (TB5001 in cSubClass, where the window procedure is passed as a Long) and has many members that fail or give wrong results, TBMANLIB_ConsoleComponents cannot take a cConsole of the published TBMANLIB_ConsoleCore (it embeds a copy of it, the same package at 1.0.0.1, whose types the compiler treats as different; checked in a project made by the IDE's own import, in either order), TBMANLIB_ConsoleCore reads keys wrongly, TBMANLIB_Diagnostic cannot create its Dictionary and misplaces List items, TBMANLIB_Dialog 1.1.0.0 has classes that fail at their main task (cDialogFindReplace reports nothing, cDialogPageSetup ignores paper and orientation, cDialogEx returns names without their folder, cDialogPassword shows the password in its confirmation boxes; every defect confirmed by a second agent, owner), TBMANLIB_ListBoxHScroll 1.0.0.1 moves the thumb 20 px per arrow and one page per track click while the list box scrolls its contents by its own step, so thumb and text disagree (confirmed by a second agent, owner), TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text (and its XPath engine reads the step `..` and `and` with `or` wrongly). TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph, and the five types of its XPath engine (`XPathTokenType`, `XPathAxisType`, `XPathOperator`, `XPathStep`, `XPathPredicate`), which no public member takes or returns and which the mXPath page mentions in a paragraph; tsPwGen's names its five test modules and its six internal modules, and tsJson's its test and internal modules likewise, which their landing pages describe in a paragraph each. TBMANLIB_Diagnostic's `List` page has a NOTE labelled BETA 1010 under its `SetItem` sample: in a 32-bit IDE run `Err.Description` of the error 5 that `SetItem 0` raises on a list of one item (`Dim x As New List(Of String)`) is "Automation error", while the exe, a 64-bit IDE run and VB6 give "Invalid procedure call or argument" (`bugs/list-error5-automation-text`, with its case `ListTests.SetItem_AtZeroOnOneItemList_ErrorHasTheRuntimeText`). The samples harness runs in the IDE and has no per-sample way to state output that differs from an exe's, so that sample prints `Err.Number` only and the text is in the NOTE; when the entry is retired the NOTE goes and the sample prints `Err.Description` again, with the output `5: Invalid procedure call or argument`. See *Where things live* for how a package moves between the two lists. The rest of this file is the maintenance guide for updating existing pages or adding new ones: package surface notes, page conventions, and the integrity check. diff --git a/docs/Reference/Contributed/ArrayListLib/index.md b/docs/Reference/Contributed/ArrayListLib/index.md index 68002acb2..904f93d23 100644 --- a/docs/Reference/Contributed/ArrayListLib/index.md +++ b/docs/Reference/Contributed/ArrayListLib/index.md @@ -19,7 +19,7 @@ The **ArrayListLib** contributed package is a list class for twinBASIC, in place This section documents version 1.6.151.1, which is the version on TWINSERV, published under the Unlicense. -> [!NOTE] 1.6.151.1, BETA 1010 +> [!NOTE] 1.6.151.1, BETA 1034 > The package does not compile, so a project that references it does not build (error TB5001, in a 32-bit and in a 64-bit build). **ArrayList** passes the addresses of two items, as **Long** values, to its private procedure **VSwap**, which declares both parameters as `ByRef` structures, in **Reverse** (`ArrayList.twin`, line 297) and in the two quicksort procedures that **Sort** calls (lines 585 and 602). **MemMoveBSTR**, a private procedure of the module **LibMemoryEx**, passes an address to **LSetByVal** in the same way (`Modules/LibMemoryEx.twin`, line 104), and **LSetByVal** declares its parameter as a `ByRef` structure that holds a string pointer. A pointer passed for a structure has to be passed `ByVal`. The behaviour described on these pages is that of the package with the two arguments of each **VSwap** call passed `ByVal`, and with **LSetByVal** taking the address as a **LongPtr** and putting it in a structure of its own. That copy builds in 32-bit and in 64-bit programs, and both give the same results. > [!WARNING] 1.6.151.1 diff --git a/docs/Reference/Contributed/TBMANLIB/index.md b/docs/Reference/Contributed/TBMANLIB/index.md index 71b8621a8..e6b3e9475 100644 --- a/docs/Reference/Contributed/TBMANLIB/index.md +++ b/docs/Reference/Contributed/TBMANLIB/index.md @@ -80,7 +80,7 @@ The references of a package are not visible to the project that uses it. A progr The package is built for 32-bit programs. The window procedure that **cSubClass** installs is passed to **SetWindowLong** as a **Long**, which a 64-bit program cannot hold. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > A project that embeds TBMANLIB and is built for 64-bit programs (**Win64**) does not compile. The compiler reports TB5001 in `cSubClass.twin`, *Validation of call to 'SetWindowLong' failed. Argument for 'dwNewLong': cannot coerce type 'Callback' to 'Long'*, even when the program uses nothing of the package. The package can be used in 32-bit programs only. The other components are available to 32-bit programs. diff --git a/docs/Reference/Contributed/WinReg/index.md b/docs/Reference/Contributed/WinReg/index.md index 4178fb0df..23633858c 100644 --- a/docs/Reference/Contributed/WinReg/index.md +++ b/docs/Reference/Contributed/WinReg/index.md @@ -15,7 +15,7 @@ The **WinReg** contributed package provides a Windows Registry helper class that The root keys, such as `HKEY_CURRENT_USER`, are members of **RegKeyspace**, so a project names them with **WinReg** alone. **WinReg** references the **WinDevLib** package, but a project does not see the names a package's own references declare. A project that names `BOOL` or `REGTYPES`, or calls `RegOpenKeyEx` or `RegCloseKey` and uses the `KEY_*` constants, as some examples here do, references **WinDevLib** as well. One that declares the **Dictionary** that [**EnumValues**](RegCls/EnumValues) returns references the Microsoft Scripting Runtime. -> [!NOTE] 1.0.1.1, BETA 1010 +> [!NOTE] 1.0.1.1, BETA 1034 > The package does not compile, so a project that references it does not build. **RegCls** declares `RegCreateKeyEx` and `RegEnumKeyEx` with a `ByRef` structure parameter, *lpSecurityAttributes* (`SECURITY_ATTRIBUTES`) and *lpftLastWriteTime* (`FILETIME`), and passes `VarPtr(0&)` for it in [**WriteString**](RegCls/WriteString), [**WriteDWORD**](RegCls/WriteDWORD), [**CreateKey**](RegCls/CreateKey) and [**DeleteKeyEx**](RegCls/DeleteKeyEx). A pointer passed for a structure has to be passed `ByVal` (error TB5001). `VarPtr(0&)` is also the address of a temporary **Long**, not a null pointer; `ByVal vbNullPtr` passes a null pointer. The behaviour described on these pages is that of the package with those four calls passed `ByVal`. > [!WARNING] 1.0.1.1 diff --git a/docs/Reference/Experimental.md b/docs/Reference/Experimental.md index 0c3ac0d2b..449bf71eb 100644 --- a/docs/Reference/Experimental.md +++ b/docs/Reference/Experimental.md @@ -12,7 +12,7 @@ Community-authored packages from TWINSERV that are not yet ready for use in a pr These packages are published through TWINSERV, as the [Contributed Packages](../Contributed/) are, and are documented in the same way. A package is listed here when its newest version does not compile in the current twinBASIC, or when it has defects that a program would meet. Each section says which version it documents and what does not work yet. A package moves to the Contributed Packages once a version of it works, and its pages keep their addresses. -- [**ArrayListLib**](../Contributed/ArrayListLib/) -- a list class in place of .NET's `ArrayList`. Version 1.6.151.1 does not compile in twinBASIC BETA 1010, and in a copy that does, a **For Each** over a list with no items fails, **ToString** of such a list never returns, **Reverse** leaves the two middle items of an even run in place, and a second **GetEnumerator** over the same list ignores its arguments. +- [**ArrayListLib**](../Contributed/ArrayListLib/) -- a list class in place of .NET's `ArrayList`. Version 1.6.151.1 does not compile in twinBASIC BETA 1034, and in a copy that does, a **For Each** over a list with no items fails, **ToString** of such a list never returns, **Reverse** leaves the two middle items of an even run in place, and a second **GetEnumerator** over the same list ignores its arguments. - [**GdiPlus**](../Contributed/GdiPlus/) -- Windows GDI+: lines, shapes, text and images drawn with pens, brushes, fonts, paths and transformations. Version 0.9.45.0 compiles, and has many members that give wrong results, fail or do not return: a colour given as a **Long** has its red and blue swapped and loses its alpha, saving an image with encoder parameters fails, the font collections cannot be created, and several path, metafile and effect members fail. - [**TBMANLIB**](../Contributed/TBMANLIB/) -- a toolkit of helpers: JSON, an HTTP client, logs, INI and CSV files, timers, strings, bytes, files and dictionaries. Version 1.0.0.29 compiles for 32-bit programs, does not compile in a 64-bit build, and has several members that fail or give wrong results. - [**TBMANLIB_Aes**](../Contributed/TBMANLIB_Aes/) -- AES encryption of text and byte arrays. Version 1.0.0.4 compiles, and its cipher modes are not the ones their names say: the default mode, CBC, runs ECB, and the output does not match the standard test vectors. @@ -22,5 +22,5 @@ These packages are published through TWINSERV, as the [Contributed Packages](../ - [**TBMANLIB_Dialog**](../Contributed/TBMANLIB_Dialog/) -- the Open, Save and folder dialogs (**cDialog**, **cDialogEx**), the Color, Font, Find, Replace, Page Setup and Print dialogs, and message, input, password, date and progress dialogs. Version 1.1.0.0 compiles, and several of its classes fail at their main task: the Find and Replace dialogs report nothing to the program, Page Setup ignores the paper size and orientation, **cDialogEx** returns file names without their folder, the password dialog shows the password in its confirmation boxes, and the date dialog keeps input it refused. **cDialog** works. - [**TBMANLIB_ListBoxHScroll**](../Contributed/TBMANLIB_ListBoxHScroll/) -- a horizontal scroll bar for a **ListBox**. Version 1.0.0.1 compiles, and its scroll arrows and track move the thumb by a different step than the list's contents, so the thumb runs ahead of the text and the arrows cannot reach the end of a long line. - [**TBMANLIB_Xml**](../Contributed/TBMANLIB_Xml/) -- XML documents parsed, edited and written, in twinBASIC code. Version 1.0.0.1 compiles, and its parser misreads some common XML, such as self-closing elements and entities in text, and does not return for some malformed text. Its XPath engine reads some valid expressions as others, such as the step `..` and `and` with `or`. -- [**WinReg**](../Contributed/WinReg/) -- registry keys and values, read, written, deleted and enumerated. Version 1.0.1.1 does not compile in twinBASIC BETA 1010, and three of its methods give wrong results: **ReadString** returns the text followed by NUL characters, **GetRegistryValueType** returns **REG_NONE** for a value that holds data, and one overload of **ConvertValueToString** finds no value. +- [**WinReg**](../Contributed/WinReg/) -- registry keys and values, read, written, deleted and enumerated. Version 1.0.1.1 does not compile in twinBASIC BETA 1034, and three of its methods give wrong results: **ReadString** returns the text followed by NUL characters, **GetRegistryValueType** returns **REG_NONE** for a value that holds data, and one overload of **ConvertValueToString** finds no value. - [**xCalendar**](../Contributed/xCalendar/) -- a calendar control, in place of the Calendar control of `MSCAL.OCX`. Version 1.0.17.0 compiles, and has defects, among them a grid that keeps the dates of the old month when **Value** is set to another month, and colour properties of the text that have no effect. From 9d8f19f3c9e3a10c0aa17da931a86c9d3b44137e Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Mon, 2 Nov 2026 22:43:56 +0100 Subject: [PATCH 04/19] Lane tests for two IDE NOTEs that had none, both holding on BETA 1034 and relabelled: code-editor asserts that ( on a selected completion gives Twice(() (Tab the control), packages that Import from file leaves its copy under Packages after Cancel with the project unchanged; lane headers name BETA 1034 --- docs/IDE/Code/IntelliSense.md | 2 +- docs/IDE/Projects/References-and-Packages.md | 2 +- test/ide/code-editor.test.mjs | 53 ++++++++++++++++++++ test/ide/packages.test.mjs | 27 +++++++++- 4 files changed, 81 insertions(+), 3 deletions(-) diff --git a/docs/IDE/Code/IntelliSense.md b/docs/IDE/Code/IntelliSense.md index 4b7c502a8..8b35c17c4 100644 --- a/docs/IDE/Code/IntelliSense.md +++ b/docs/IDE/Code/IntelliSense.md @@ -53,7 +53,7 @@ What an accepted entry adds: Esc, Left, Right and Backspace close the list. Left also moves the caret, and Backspace also deletes. Scrolling the editor with the mouse wheel closes the list too. -> [!NOTE] BETA 1005 +> [!NOTE] BETA 1034 > Typing `(` to accept a function from the list doubles the bracket: `Area` becomes `Area(()`. Accept the function with Tab or Enter instead. It gives `Area()` with the caret between the brackets, so type the arguments without the opening bracket. The Debug Console has the same list in its entry box. See [Debug Console](../Debugging/Debug-Console). diff --git a/docs/IDE/Projects/References-and-Packages.md b/docs/IDE/Projects/References-and-Packages.md index be42abbef..e809f9d23 100644 --- a/docs/IDE/Projects/References-and-Packages.md +++ b/docs/IDE/Projects/References-and-Packages.md @@ -112,7 +112,7 @@ The **Import from file...** button under the lists opens the Windows **Open** di > [!NOTE] BETA 1034 > A package imported with **Import from file...** is listed but not ticked, so the project does not use it. Tick it yourself after the import. -> [!NOTE] BETA 1033 +> [!NOTE] BETA 1034 > **Import from file...** puts a copy of the package under the project's `Packages` folder. Closing the dialog with **Cancel** leaves the copy there, and the project is not marked as changed. > [!NOTE] BETA 1034 diff --git a/test/ide/code-editor.test.mjs b/test/ide/code-editor.test.mjs index 6cf92a7fa..03c499aaf 100644 --- a/test/ide/code-editor.test.mjs +++ b/test/ide/code-editor.test.mjs @@ -56,6 +56,7 @@ import { pressKey, select, setCursor, + typeText, waitFor, } from "../../scripts/lib/tb-operate.mjs"; import { scenario } from "../addin/scenario.mjs"; @@ -440,6 +441,58 @@ scenario("the code editor and its panels", (lane) => { } }); + // A defect the twinBASIC developers already know of, so it has no entry in + // BUGS-TO-REPORT.md (WIP.IDE.md): docs/IDE/Code/IntelliSense.md carries its NOTE. The list + // opens as the name is typed in MODERN mode, which the test sets and puts back. It types on a + // line of its own after Debug.Print total, and removes the line again. + for (const [key, want] of [ + ["Tab", "Twice()"], + ["(", "Twice(()"], + ]) { + const control = key === "Tab"; + test(`${control ? "control: " : ""}${key} with Twice selected in the completion list gives ${want}`, async (t) => { + const mode = await c.evaluate(`liveIDEOptions.intellisenseMode`); + await c.evaluate(`liveIDEOptions.intellisenseMode = "MODERN"; changedIdeOptions();`); + t.after(() => c.evaluate(`liveIDEOptions.intellisenseMode = ${JSON.stringify(mode)}; changedIdeOptions();`)); + await openFile(c, EDITING); + const line = MAIN_PRINT + 1; + await c.evaluate(`(() => { + const m = editor.getModel(); + m.applyEdits([{ range: new monaco.Range(${MAIN_PRINT}, m.getLineMaxColumn(${MAIN_PRINT}), ${MAIN_PRINT}, m.getLineMaxColumn(${MAIN_PRINT})), text: "\\n " }]); + })()`); + t.after(() => + c.evaluate(`(() => { + const m = editor.getModel(); + m.applyEdits([{ range: new monaco.Range(${MAIN_PRINT}, m.getLineMaxColumn(${MAIN_PRINT}), ${line}, m.getLineMaxColumn(${line})), text: "" }]); + })()`), + ); + await setCursor(c, line, 9); + await typeText(c, "Twi"); + const open = await waitFor(c, (c) => c.evaluate(`!!context.activeDebugConsoleIntellisense.value`), { + timeout: 10 * 1000, + }); + assert.ok(open, "the completion list did not open"); + // Down selects the first row once the list has its rows; pressed sooner, it selects nothing. + const selected = await waitFor( + c, + async (c) => { + await pressKey(c, "ArrowDown"); + await sleep(150); + return c.evaluate( + `[...debugConsoleEntryIntellisenseBox.querySelectorAll("*")].some((e) => /selected/i.test(e.className || "") && /Twice/.test(e.textContent || ""))`, + ); + }, + { timeout: 5 * 1000 }, + ); + assert.ok(selected, "Twice was never selected in the list"); + await pressKey(c, key); + await sleep(500); + const text = (await editorState(c)).lineText.trim(); + t.diagnostic(`the line: ${JSON.stringify(text)}`); + assert.equal(text, want); + }); + } + test("control: Format Selection re-indents a selection that starts on a Select Case line", async (t) => { c.dialogs.length = 0; await select(c, { startLine: PLAIN_SELECT, startColumn: 1, endLine: PLAIN_END_SELECT, endColumn: 999 }); diff --git a/test/ide/packages.test.mjs b/test/ide/packages.test.mjs index 0a880ccf9..2e0baf961 100644 --- a/test/ide/packages.test.mjs +++ b/test/ide/packages.test.mjs @@ -12,7 +12,7 @@ // that nobody sees. OpenPackage puts the real hostAppObject back as it answers, // and every other read goes to the real HostReadFileDataBase64. // -// Each test states what BETA 1010 does. If a test fails after an IDE update, +// Each test states what BETA 1034 does. If a test fails after an IDE update, // the IDE has changed: update the matching entry in BUGS-TO-REPORT.md or bugs/filed/ and the // two Packages pages under docs/Features/Packages, and then this file. // @@ -135,6 +135,31 @@ scenario("Import from file... in the Packages dialog", (lane) => { const referenced = await c.evaluate(REFERENCES_JS); assert.ok(!referenced.includes("DocProbePkg"), "the imported package is now ticked"); }); + + // The NOTE on docs/IDE/Projects/References-and-Packages.md, under Import from file. + test("the import puts a copy of the package under Packages, and Cancel leaves it there with the project not marked changed", async (t) => { + // A folder's children are not listed until it is opened, so the copy is looked for by its path. + const packages = () => + c.evaluate(`["Packages/DocProbePkg", "Packages/DocProbePkg/Settings", "Packages/DocProbePkg/Sources"] + .filter((p) => !!fs.tree.resolvePath("twinbasic:/PackagesProbe/" + p))`); + const copied = await waitFor(c, async () => ((await packages()).includes("Packages/DocProbePkg") ? true : null), { + timeout: 10 * 1000, + }); + t.diagnostic(`under Packages: ${JSON.stringify(await packages())}; changed: ${await c.evaluate("projectIsDirty")}`); + assert.ok(copied, "there is no DocProbePkg under Packages after the import"); + await click(c, { css: "div", text: "Cancel" }); + assert.ok( + await waitFor(c, async () => !(await c.evaluate(`!!${CONFIG_JS}`)), { timeout: 20 * 1000 }), + "Cancel did not close the Settings editor", + ); + await new Promise((r) => setTimeout(r, 1000)); + const after = await packages(); + const startup = await c.evaluate(`!!fs.tree.resolvePath("twinbasic:/PackagesProbe/Sources/Startup.twin")`); + t.diagnostic(`after Cancel, under Packages: ${JSON.stringify(after)}; Sources/Startup.twin found: ${startup}`); + assert.ok(startup, "control: Sources/Startup.twin is not found after Cancel"); + assert.ok(after.includes("Packages/DocProbePkg"), "Cancel removed the copy under Packages"); + assert.equal(await c.evaluate("projectIsDirty"), false, "the project is marked changed"); + }); }); // --------------------------------------------------------------------------- From e7503e2e9653ca0d8cffa37cb16466b9af52c2f2 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Tue, 3 Nov 2026 18:40:14 +0100 Subject: [PATCH 05/19] Contributed callouts rechecked on BETA 1034 and relabelled from 1010/1005; the ReDim fix makes six GdiPlus, TBMANLIB and ConsoleComponents cases and two printed messages (cAes, cXml) error 9 Subscript out of range; xCalendar's text colours and its four DateAdd methods work on 1034 with the package unchanged, the colour defect and the DateAdd WARNING removed and the colours stated as prose with their cases; Authoring's label example and WIP's add-in lane count (18) brought up to date --- WIP.md | 4 +- docs/Documentation/Authoring.md | 8 ++-- docs/Documentation/Tools-IDE.md | 2 +- .../Contributed/Fmt/EscapeSequence/index.md | 2 +- .../Contributed/Fmt/StringValidation/index.md | 2 +- .../Contributed/GdiPlus/FontCollection.md | 2 +- .../Contributed/GdiPlus/GraphicsPath.md | 10 ++--- docs/Reference/Contributed/GdiPlus/Image.md | 4 +- .../GdiPlus/InstalledFontCollection.md | 2 +- docs/Reference/Contributed/GdiPlus/Matrix.md | 4 +- .../Contributed/GdiPlus/PathIterator.md | 6 +-- .../GdiPlus/PrivateFontCollection.md | 2 +- .../Contributed/GdiPlus/Types/GpPointF.md | 6 +-- .../Contributed/GdiPlus/Types/GpRect.md | 2 +- .../Contributed/GdiPlus/Types/GpRectF.md | 2 +- .../Contributed/GdiPlus/Types/GpSizeF.md | 4 +- .../GdiPlus/Types/PathPointType.md | 4 +- docs/Reference/Contributed/Generics/index.md | 4 +- .../OLEGuids/IUnknownUnrestricted.md | 2 +- .../Contributed/TBMANLIB/ToolsArray.md | 8 ++-- .../Contributed/TBMANLIB/cHttpClient.md | 2 +- .../Contributed/TBMANLIB_Aes/cAes.md | 4 +- .../TBMANLIB_ConsoleComponents/Helpers.md | 2 +- .../cConsoleLayout.md | 2 +- .../TBMANLIB_ConsoleComponents/index.md | 2 +- .../Contributed/TBMANLIB_Diagnostic/List.md | 2 +- .../Contributed/TBMANLIB_Diagnostic/index.md | 2 +- .../Contributed/TBMANLIB_Xml/cXml.md | 2 +- .../Contributed/tsJson/JsonOptions.md | 2 +- .../Contributed/tsPwGen/PwGenOptions.md | 2 +- docs/Reference/Contributed/xCalendar/index.md | 45 ++++++++----------- docs/Reference/Experimental.md | 2 +- .../GdiPlus/Sources/Br_PathIteratorTests.twin | 6 +-- .../GdiPlus/Sources/Br_PathTests.twin | 8 ++-- .../package-tests/GdiPlus/Sources/RunAll.twin | 12 ++--- .../TBMANLIB/Sources/ArrayTests.twin | 4 +- .../TBMANLIB/Sources/RunAll.twin | 4 +- test/package-tests/TBMANLIB/expect.json | 2 +- .../Sources/LayoutTests.twin | 4 +- test/package-tests/xCalendar/expect.json | 33 -------------- 40 files changed, 91 insertions(+), 131 deletions(-) diff --git a/WIP.md b/WIP.md index c60a3bb2a..914a21b89 100644 --- a/WIP.md +++ b/WIP.md @@ -40,7 +40,7 @@ sibling. History goes nowhere: git keeps it. Reference documentation is **complete** for all thirteen packages: VBA, VBRUN, VB, WebView2, Assert, CustomControls, CEF, WinEventLogLib, WinNamedPipesLib, WinServicesLib, tbIDE, WinNativeCommonCtls and AppGlobalClassObject. It is adapted from primary sources: Microsoft VBA-Docs (CC-BY-4.0) for the runtime library, `.twin` source for the twinBASIC-specific packages. The CEF and WebView2 packages also carry a tutorial set. -Twenty-seven **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: ArrayListLib, DetoursLib, FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerLib, tbFileSysTools, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsJson, tsPwGen, VBA_JSON, xCalendar, AutoCompleteLib and VBComDlg complete; BigNumberLib its main class only. TBMANLIB_ConsoleCore's console claims are checked by its test cases, each on a console screen buffer of its own with its input written by `WriteConsoleInput`; TBMANLIB_ConsoleComponents' follow the source, because the samples harness cannot capture a console. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-tests/xCalendar/probe/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. VBComDlg's `exclude_from_docs` names `CommonDialogBase`, and AutoCompleteLib's `AutoCompleteHandle`, each a module whose public procedures only its class calls. DetoursLib's names its demonstration module, `DetoursTest` (message boxes), which its landing page describes in a paragraph. Its test cases run in the IDE's own process, so each detours `MulDiv` of kernel32 and answers for itself only for one marked argument, passing every other call (another thread's included) to the original. `CreateDetourAtFuncAddress` does not check a *newAddress* of 0, and SlimDetours then reads address 0 inside a transaction that has the process's other threads suspended; no case tries it, since it would leave the IDE frozen, and the page states the missing check from the source alone. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modImpExp` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1010). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-tests/TBMANLIB_Dialog/probe/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. TBMANLIB_Dialog 1.1.0.0 has a page of its own for each class except cDialog, which keeps a page for each member, so `analyze` in audit mode lists the members of those one-page classes as undocumented; its `exclude_from_docs` names its test module, `TestModule`, and the seven COM interface declarations that cDialogEx calls inside (`IOleWindow`, `IModalWindow`, `IShellItem`, `IShellItemArray`, `IFileDialog`, `IFileOpenDialog`, `IFileSaveDialog`), which its landing page describes in a paragraph. cDialogEx 1.1.0.0 returns the name of the chosen file, not its path, ignores `InitialDir` and ignores a `Filter` that has no closing pipe, each documented as a WARNING. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. tbRegExp 1.9.0.0 raises `vbObjectError + 3016` from a search that uses up its step budget and returns nothing (1.8.0.0's `SearchAborted` is gone); the budget is 10 steps for each character squared, at least 1,000,000 and at most 100,000,000, for the search of one match, and `RegExp.SearchStepLimit` replaces it with a fixed number, documented on its own page and in a section of the landing page. On the dev box (BETA 1010, win32) `\d+x` costs 2.0 × n² steps (2.002 to 2.009 for 500 to 2,000 digits), so it finishes on 7,000 digits (16 s) and raises on 10,000 (16 s); `(a+)+b` raises in 0.2 s on 30 or 300 `a` but takes 16 s on 3,000; `a*` matches 1,000,000 characters whole in about half a second. Its pattern syntax has a page of its own, `PatternSyntax.md`. Each one that is not Experimental has a chapter in the book's Packages part; the Experimental ones are left out of the book. **ArrayListLib, GdiPlus, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_Xml, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner): ArrayListLib and WinReg do not compile on BETA 1034 (each documented from its source and a copy of it that builds; ArrayListLib's samples are compile-only, and WinReg's are not marked `check_build` at all, because under four lanes its template's compile outruns the 10 s limit: mark them again when a version of WinReg compiles; in a copy of ArrayListLib 1.6.151.1 with the arguments of its three `VSwap` calls passed `ByVal` and `LSetByVal` taking a `LongPtr`, a For Each over a list with no items fails, ToString of such a list never returns, Reverse keeps the middle pair of an even run in place and a second GetEnumerator over a list ignores its arguments; in a copy of WinReg's source with its four failing calls passed `ByVal`, ReadString, GetRegistryValueType and the first ConvertValueToString give wrong results), xCalendar has a grid that keeps the old month's dates when Value is set to another month, and text colour properties that have no effect, TBMANLIB 1.0.0.29 does not compile in a 64-bit build (TB5001 in cSubClass, where the window procedure is passed as a Long) and has many members that fail or give wrong results, TBMANLIB_ConsoleComponents cannot take a cConsole of the published TBMANLIB_ConsoleCore (it embeds a copy of it, the same package at 1.0.0.1, whose types the compiler treats as different; checked in a project made by the IDE's own import, in either order), TBMANLIB_ConsoleCore reads keys wrongly, TBMANLIB_Diagnostic cannot create its Dictionary and misplaces List items, TBMANLIB_Dialog 1.1.0.0 has classes that fail at their main task (cDialogFindReplace reports nothing, cDialogPageSetup ignores paper and orientation, cDialogEx returns names without their folder, cDialogPassword shows the password in its confirmation boxes; every defect confirmed by a second agent, owner), TBMANLIB_ListBoxHScroll 1.0.0.1 moves the thumb 20 px per arrow and one page per track click while the list box scrolls its contents by its own step, so thumb and text disagree (confirmed by a second agent, owner), TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text (and its XPath engine reads the step `..` and `and` with `or` wrongly). TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph, and the five types of its XPath engine (`XPathTokenType`, `XPathAxisType`, `XPathOperator`, `XPathStep`, `XPathPredicate`), which no public member takes or returns and which the mXPath page mentions in a paragraph; tsPwGen's names its five test modules and its six internal modules, and tsJson's its test and internal modules likewise, which their landing pages describe in a paragraph each. TBMANLIB_Diagnostic's `List` page has a NOTE labelled BETA 1010 under its `SetItem` sample: in a 32-bit IDE run `Err.Description` of the error 5 that `SetItem 0` raises on a list of one item (`Dim x As New List(Of String)`) is "Automation error", while the exe, a 64-bit IDE run and VB6 give "Invalid procedure call or argument" (`bugs/list-error5-automation-text`, with its case `ListTests.SetItem_AtZeroOnOneItemList_ErrorHasTheRuntimeText`). The samples harness runs in the IDE and has no per-sample way to state output that differs from an exe's, so that sample prints `Err.Number` only and the text is in the NOTE; when the entry is retired the NOTE goes and the sample prints `Err.Description` again, with the output `5: Invalid procedure call or argument`. See *Where things live* for how a package moves between the two lists. +Twenty-seven **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: ArrayListLib, DetoursLib, FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerLib, tbFileSysTools, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsJson, tsPwGen, VBA_JSON, xCalendar, AutoCompleteLib and VBComDlg complete; BigNumberLib its main class only. TBMANLIB_ConsoleCore's console claims are checked by its test cases, each on a console screen buffer of its own with its input written by `WriteConsoleInput`; TBMANLIB_ConsoleComponents' follow the source, because the samples harness cannot capture a console. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-tests/xCalendar/probe/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. VBComDlg's `exclude_from_docs` names `CommonDialogBase`, and AutoCompleteLib's `AutoCompleteHandle`, each a module whose public procedures only its class calls. DetoursLib's names its demonstration module, `DetoursTest` (message boxes), which its landing page describes in a paragraph. Its test cases run in the IDE's own process, so each detours `MulDiv` of kernel32 and answers for itself only for one marked argument, passing every other call (another thread's included) to the original. `CreateDetourAtFuncAddress` does not check a *newAddress* of 0, and SlimDetours then reads address 0 inside a transaction that has the process's other threads suspended; no case tries it, since it would leave the IDE frozen, and the page states the missing check from the source alone. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modImpExp` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1010). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-tests/TBMANLIB_Dialog/probe/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. TBMANLIB_Dialog 1.1.0.0 has a page of its own for each class except cDialog, which keeps a page for each member, so `analyze` in audit mode lists the members of those one-page classes as undocumented; its `exclude_from_docs` names its test module, `TestModule`, and the seven COM interface declarations that cDialogEx calls inside (`IOleWindow`, `IModalWindow`, `IShellItem`, `IShellItemArray`, `IFileDialog`, `IFileOpenDialog`, `IFileSaveDialog`), which its landing page describes in a paragraph. cDialogEx 1.1.0.0 returns the name of the chosen file, not its path, ignores `InitialDir` and ignores a `Filter` that has no closing pipe, each documented as a WARNING. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. tbRegExp 1.9.0.0 raises `vbObjectError + 3016` from a search that uses up its step budget and returns nothing (1.8.0.0's `SearchAborted` is gone); the budget is 10 steps for each character squared, at least 1,000,000 and at most 100,000,000, for the search of one match, and `RegExp.SearchStepLimit` replaces it with a fixed number, documented on its own page and in a section of the landing page. On the dev box (BETA 1010, win32) `\d+x` costs 2.0 × n² steps (2.002 to 2.009 for 500 to 2,000 digits), so it finishes on 7,000 digits (16 s) and raises on 10,000 (16 s); `(a+)+b` raises in 0.2 s on 30 or 300 `a` but takes 16 s on 3,000; `a*` matches 1,000,000 characters whole in about half a second. Its pattern syntax has a page of its own, `PatternSyntax.md`. Each one that is not Experimental has a chapter in the book's Packages part; the Experimental ones are left out of the book. **ArrayListLib, GdiPlus, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_Xml, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner): ArrayListLib and WinReg do not compile on BETA 1034 (each documented from its source and a copy of it that builds; ArrayListLib's samples are compile-only, and WinReg's are not marked `check_build` at all, because under four lanes its template's compile outruns the 10 s limit: mark them again when a version of WinReg compiles; in a copy of ArrayListLib 1.6.151.1 with the arguments of its three `VSwap` calls passed `ByVal` and `LSetByVal` taking a `LongPtr`, a For Each over a list with no items fails, ToString of such a list never returns, Reverse keeps the middle pair of an even run in place and a second GetEnumerator over a list ignores its arguments; in a copy of WinReg's source with its four failing calls passed `ByVal`, ReadString, GetRegistryValueType and the first ConvertValueToString give wrong results), xCalendar has a grid that keeps the old month's dates when Value is set to another month and presses near its right and bottom edges that select dates it does not show (its text colour properties work since BETA 1034, with the package unchanged; whether it stays Experimental is the owner's call), TBMANLIB 1.0.0.29 does not compile in a 64-bit build (TB5001 in cSubClass, where the window procedure is passed as a Long) and has many members that fail or give wrong results, TBMANLIB_ConsoleComponents cannot take a cConsole of the published TBMANLIB_ConsoleCore (it embeds a copy of it, the same package at 1.0.0.1, whose types the compiler treats as different; checked in a project made by the IDE's own import, in either order), TBMANLIB_ConsoleCore reads keys wrongly, TBMANLIB_Diagnostic cannot create its Dictionary and misplaces List items, TBMANLIB_Dialog 1.1.0.0 has classes that fail at their main task (cDialogFindReplace reports nothing, cDialogPageSetup ignores paper and orientation, cDialogEx returns names without their folder, cDialogPassword shows the password in its confirmation boxes; every defect confirmed by a second agent, owner), TBMANLIB_ListBoxHScroll 1.0.0.1 moves the thumb 20 px per arrow and one page per track click while the list box scrolls its contents by its own step, so thumb and text disagree (confirmed by a second agent, owner), TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text (and its XPath engine reads the step `..` and `and` with `or` wrongly). TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph, and the five types of its XPath engine (`XPathTokenType`, `XPathAxisType`, `XPathOperator`, `XPathStep`, `XPathPredicate`), which no public member takes or returns and which the mXPath page mentions in a paragraph; tsPwGen's names its five test modules and its six internal modules, and tsJson's its test and internal modules likewise, which their landing pages describe in a paragraph each. TBMANLIB_Diagnostic's `List` page has a NOTE labelled BETA 1034 under its `SetItem` sample: in a 32-bit IDE run `Err.Description` of the error 5 that `SetItem 0` raises on a list of one item (`Dim x As New List(Of String)`) is "Automation error", while the exe, a 64-bit IDE run and VB6 give "Invalid procedure call or argument" (`bugs/list-error5-automation-text`, with its case `ListTests.SetItem_AtZeroOnOneItemList_ErrorHasTheRuntimeText`). The samples harness runs in the IDE and has no per-sample way to state output that differs from an exe's, so that sample prints `Err.Number` only and the text is in the NOTE; when the entry is retired the NOTE goes and the sample prints `Err.Description` again, with the output `5: Invalid procedure call or argument`. See *Where things live* for how a package moves between the two lists. The rest of this file is the maintenance guide for updating existing pages or adding new ones: package surface notes, page conventions, and the integrity check. @@ -582,7 +582,7 @@ Why the report separates the wedged task from the merely blocked ones, and why - `examples.bat` — compiles the documentation's own twinBASIC code samples, every `tb` fence marked `check_build`, and reports the ones the compiler refuses against the line in the page they came from; a statement sample also marked `check_run` is built and run, and what it prints is checked against the page. Needs a twinBASIC install and Windows, so it is outside every gate and outside CI; ~190 s over the 2,660 samples marked (1,076 of them `check_run`). `--build` also builds each project that compiles clean, and `--llvm` builds it with LLVM (see [WIP.ExamplesBuild.md](WIP.ExamplesBuild.md)). Two modes need no compiler at all: `--census` classifies every fence and says how many classifiable ones are still unmarked, and `--report ` groups a saved `--propose --json` survey by diagnostic, section and unresolved name. `--propose` itself does compile. See [Compiling the reference's own code samples](#compiling-the-references-own-code-samples) and [WIP.ExamplesBuild.md](WIP.ExamplesBuild.md). - `package-tests.bat` --- runs the test projects of the contributed packages named (in any case: `tbregexp`), or of every one with `--all-packages`, which is never the default (`scripts/package_tests.mjs`): each project under `test/package-tests/` is staged with its package embedded and run with `tbrun --tests` as 32-bit and 64-bit, and each case is judged against `expect.json` as *documented*, *fixed* (the callout goes) or *changed* (a claim of the page is no longer true). `--update` writes `expect.json` from the run; read every entry it writes, since each is a claim that the package is defective. A case that never returns is caught and run alone. It also rewrites the generated `RunAll.twin`. Outside every gate and CI, as `examples.bat` is. The build publishes each project, referencing the package as a linked package and carrying none of its code, as `tB/Packages/Contributed//-tests.twinproj` in the online tree (the `packageTests` task, `builder/package-tests.mjs`, `_config.yml`'s `package_tests`; the publish allowlist exempts those paths, and the offline tree's links go to the website). -- `addin-test.bat` — tests IDE add-ins by operating an IDE: every lane in `test/addin/lanes.mjs` builds the add-ins it tests into a private copy of the install, opens a project and checks what the add-in does. Outside every gate and outside CI for the same reasons as `examples.bat`; the seventeen lanes: Samples 10 and 15, the thirteen probe lanes behind Stage 2's answers in [WIP.HelpAddin.md](WIP.HelpAddin.md), and the help add-in in `add-in/` twice, built with its committed copy of the symbol index, `add-in/Resources/SYMBOLS/symbols.json`, which a local build rewrites whenever the index changes. The `help` lane serves the built `docs/_site` on localhost to the add-in's pane; `help-offline` builds the add-in with a help archive of `docs/_site-offline` and the pane's pages come from the add-in's own server. So run `build.bat` first. Exit 0 every lane passed and the registry is as it was found, 1 a lane failed, 2 the harness failed, 3 the registry or a work folder was not put back. See [Driving the twinBASIC compiler](#driving-the-twinbasic-compiler) for its rules. +- `addin-test.bat` — tests IDE add-ins by operating an IDE: every lane in `test/addin/lanes.mjs` builds the add-ins it tests into a private copy of the install, opens a project and checks what the add-in does. Outside every gate and outside CI for the same reasons as `examples.bat`; the eighteen lanes: Samples 10 and 15, the fourteen probe lanes behind the answers in [WIP.HelpAddin.md](WIP.HelpAddin.md) (`keys-help` is `keys` with the help add-in loaded), and the help add-in in `add-in/` twice, built with its committed copy of the symbol index, `add-in/Resources/SYMBOLS/symbols.json`, which a local build rewrites whenever the index changes. The `help` lane serves the built `docs/_site` on localhost to the add-in's pane; `help-offline` builds the add-in with a help archive of `docs/_site-offline` and the pane's pages come from the add-in's own server. So run `build.bat` first. Exit 0 every lane passed and the registry is as it was found, 1 a lane failed, 2 the harness failed, 3 the registry or a work folder was not put back. See [Driving the twinBASIC compiler](#driving-the-twinbasic-compiler) for its rules. - `ide-test.bat` --- the same runner for scenarios that operate the IDE itself rather than an add-in: every lane in `test/ide/lanes.mjs`. Same exit codes, same standing outside every gate and outside CI, same rules. - `try-help-addin.bat` --- opens one IDE on the user's desktop with the help add-in built and loaded, to try it by hand, and puts the registry back once it is closed (see [Driving the twinBASIC compiler](#driving-the-twinbasic-compiler)). Needs `build.bat` first. Exit 0 closed and put back, 1 the add-in or the project did not compile, 2 the tool could not run, 3 not put back. - `node scripts/shoot_docs.mjs` --- writes the documentation's IDE screenshots (plan and owner's decisions: [WIP.Screenshots.md](WIP.Screenshots.md)). Each picture is a shot in a table, `out` its path under `docs/` (`--only` matches it), in a setup (the main three below; the full list is in WIP.Screenshots.md), each one IDE on a **private** desktop started only when a shot in it is selected: `help` (builds the add-in as `try-help-addin.bat` does, opens `test/addin/helpdemo`, `TB_DOCS_HELP_REAL_TITLEBAR=0`: the eight `docs/IDE/AddIns/Images/Help_.png`), `no-project` (`Lane.openNoProject` with `includeProjects`: the menus, cut out with alpha, the dialogs that need no project, and the empty panels, each floated on its own, after the dialogs) and `project` (the demo without the add-in: About, which needs the compiler's licence answer, and IDE Options, whose font lists need a project). One setup is not an IDE: `web` starts puppeteer's Chrome through `launchOnDesktop` on a private desktop of its own (never headless, never on the user's desktop; native-window occlusion and occluded-window backgrounding switched off, or a capture waits forever or comes back blank) for the two public pages the FAQ shows, taken once; it needs the network, as do `webpage` (the Webpage pane on `https://docs.twinbasic.com/`, its page frozen from the pane's own DevTools target and shown in a frame of the IDE's page at the pane's place, as Help_Window's docs frame is shown by srcdoc, so that the SVG can draw both: `scripts/lib/shot-freeze.mjs`) and the package-server setups, which never publish. Every IDE gets `--force-device-scale-factor=1` (`Lane`'s `browserArgs`), else the page follows the display's scaling; the page is overridden to 1280x880 at 2x, and `PAGE_DEFAULTS` puts the IDE's options, panel layouts and keyboard groups to their defaults and the recent lists to the tool's own **in the page only**, nothing written because the IDE has `IDE_FLAGS` (the one recent-list call they do not guard, `RemoveRecentsList`, is replaced first). Each picture is a `Page.captureScreenshot` with a `clip`; a page showing it gives it `{:width height}` of half its pixel size. **Every picture is taken in two themes by one IDE**: dark as `X.png`, then, after `backToPrepared` and `tbTheme_SwitchToLightMode`, Light (not Classic) as `X.light.png` beside it, which the build pairs by name ([WIP.Build.md](WIP.Build.md#pictures-in-two-themes)); a light picture with the dark one's pixels is not kept, and one more than 32 device pixels off the dark one's size fails (a wrong state, not the theme). A label's pill follows the theme unless the shot names a `tone`. A shot's `annotate` list (arrows, rings, badges...) is drawn by `scripts/lib/shot-annotate.mjs` as one SVG layer over the page before the capture and removed after it, the clip growing to hold it. A picture is rewritten only when its bytes differ, and a second run reports every one `unchanged`; a capture is not byte-stable, so `capture` captures back to back, with no rest, until one equals the file on disk or two agree, and, when the two that agree are not the file, goes on capturing further apart, for 5.5 s at most, until one is, keeping the last two that agreed, and a picture that differs from the file only by a capture's noise (16 grey levels, 0.5% of its pixels) is left as it is. **The reference is the picture at git `HEAD` first, then the file** (owner): compared with the file alone, a picture drifted from `HEAD` over the runs of one retake, each step within noise of the file before it. A capture equal to or within noise of `HEAD`'s picture leaves the file as `HEAD` has it, its SVG too (`HEAD`'s, or none), reported `restored` when it held anything else; one that is not, but is within noise of the file, leaves the file (an earlier run's real change); a picture `HEAD` lacks is compared with the file only (`pictureFate`, `scripts/lib/shot-revision.mjs`; the run's pictures at `HEAD` read in one `git cat-file --batch`). `--against ` takes another revision, `--against-disk` the file only. **`--cleanup`** (`--dry-run` to list) starts no IDE: it resets every PNG git reports modified that is within noise of `HEAD`'s, with its SVG, so that only real changes stay; one noise rule, `withinNoise` in `scripts/lib/shot-change.mjs`, for the capture, `--cleanup` and `sheet.mjs --against`. **The manifest, `test/shots/manifest.json`** (`scripts/lib/shot-manifest.mjs`; [WIP.ShotReview.md](WIP.ShotReview.md#remembered-overrides)), records the build of each picture a run writes, and holds each picture's allowances (its noise rule's limits and rectangles left out, its SVG's bar) and a review's decisions, each tied to the bytes it was made on; `decidePicture` and `decideSvg` (`scripts/lib/shot-policy.mjs`) judge with them, in a run, in `--cleanup` and in `replay.mjs`, so a run from a terminal and one from the review tool come out the same. It is read and written only for the default `--out`. **`--stage `** (a new or empty folder git ignores) writes nothing under `docs/`, to the bundle cache or to the manifest, and puts each capture, its SVG, bundle, difference pictures, strength maps and verdict there (`scripts/lib/shot-stage.mjs`), for the review tool to apply; **`--events`** prints a `@@shot-event` line per event, and then a line `stop` on stdin ends the run as at its normal end; `replay.mjs` takes both ([WIP.ShotReview.md](WIP.ShotReview.md#runs)). `--jobs N` (default 6) runs the setups at once, each in a slot of its own from `tbbroker`, a full run with `--svg` in about 12 minutes on BETA 1010 (`--jobs 1`: one at a time); `--diffs ` writes the old picture, the new one and an amplified difference map for each that changed, named with its theme. Settle by a condition, never a fixed sleep, and a picture that shows some state sets it itself rather than relying on the shots before it. The page's animations and carets are switched off through a style sheet put into the document and every shadow root; a picture that holds DEBUG CONSOLE lines has their times (and a CodeLens run's time taken) set in the page once the real ones show. **A running program's window** (setup `programs`, fixture `test/shots/programs`) is taken **once** (`once: true`, the dark pass only) with `captureWindow`, cropped to its visible frame, at the display's DPI, shown at its size at 96 DPI; its shot says `window: true` in the table, and a shot whose take and table disagree fails. **`--list`** starts no IDE: it prints the table as one line of JSON (`[{out, setup, once, window}]`), which the review tool's page of every picture reads. **The Windows user name must never be in a published picture**: the visible text of the page, text boxes included, is searched for it before the first capture and again when a capture is chosen, with the picture's SVG under `--svg` (a page can draw again in between: Project Settings puts a real path back over the one the shot set), and for a program's window the texts of the window and its children, and the run fails when it is there. The tool finds each name it presses F1 on by searching the demo's source, not by line. The add-in's detached window has a port from the broker (the help setup's slot carries one), since the add-in is told it before it binds it. **`--svg`** also draws each picture as SVG from the page as it stands at the capture (`scripts/svgshot/`: a DOM snapshot converted, its text set in per-picture subsets of the fonts the IDE drew it with, cut by `scripts/subset_font.py`, which needs Python with fontTools), compares it with the PNG, and writes `X.svg` or `X.light.svg` beside it only when no more than 0.25% of its pixels are far off; otherwise an older SVG is removed and the page shows the PNG; the SVG of a picture whose PNG is not rewritten stays as it is, with its bundle, unless a review accepts the new one (owner), but its bundle goes to the cache when the SVG drawn is the file's to the byte (owner, `bundleFollowsSvg`), and the SVG is written with it when the cached bundle cannot draw the picture again (owner, `staleBundle`) ([WIP.Build.md](WIP.Build.md#pictures-as-svg)). With `--svg` each picture's *bundle*, what its SVG is drawn from, is saved in the gitignored `.svgshot-bundles/` (`--bundles`; never under `docs/`, a bundle holds the user name), and **after a change to the converter, `node scripts/svgshot/replay.mjs` draws every SVG again from the bundles, with no IDE**, in about 20 s for the 370 (an SVG the same as its file is not compared again, `--recheck` compares all; the fonts' cuts are kept in `.svgshot-bundles/subsets/`; one browser per process, GPU off); it lists the pictures whose bundle cannot answer what the converter now asks, with the `--only` command that retakes them ([WIP.Screenshots.md](WIP.Screenshots.md#pictures-as-svg)). `node scripts/svgshot/sheet.mjs --out ` writes a page of the pictures with no SVG (`--all`: every picture), each drawn again from its bundle beside its PNG and difference map, worst first; it is drawn without the user-name check (owner: the sheet is the owner's alone), so `--out` is refused under `docs/` and anywhere git does not ignore; what it and replay share is `scripts/svgshot/redraw.mjs`, and its pages are `scripts/svgshot/sheets.mjs`, made of the pieces in `scripts/lib/shot-views.mjs`. `node scripts/svgshot/bench.mjs` tests the converter's replicas of what Chromium draws outside the DOM (scrollbars, drop-down lists) in Edge, with no IDE ([WIP.Screenshots.md](WIP.Screenshots.md#pictures-as-svg)). No wrapper; outside every gate and outside CI, as `try-help-addin.bat` is. Exit 0 every picture written or unchanged, 1 a picture failed, 2 the tool could not run, 3 not put back. Run `build.bat` first. diff --git a/docs/Documentation/Authoring.md b/docs/Documentation/Authoring.md index 24f80958a..78dbe605b 100644 --- a/docs/Documentation/Authoring.md +++ b/docs/Documentation/Authoring.md @@ -898,14 +898,14 @@ Use one callout per concern, and only for genuine notes: plain "why this is usef A callout that is true of one twinBASIC build, or of one version of a package, says so in its label: the rest of the `[!TYPE]` line. The page shows each item of the label as a tag after the callout's title. ```md -> [!WARNING] BETA 1005 -> **DateAdd** with the "m", "q" or "yyyy" interval does not stop at the end of a shorter month. +> [!WARNING] BETA 1034 +> Given a *rate* of -1, **NPV** returns NaN (not a number) and raises no error. ``` -- `BETA 1005` --- true of that build, the usual label of a defect. +- `BETA 1034` --- true of that build, the usual label of a defect. - `since BETA 984` --- true from that build on. - `1.0.0.4` --- true of that version of the package whose section the page is in, written with all four parts. -- `1.6.151.1, BETA 1005` --- both, separated by a comma. +- `1.6.151.1, BETA 1034` --- both, separated by a comma. - `timeless` --- true of every build and every version: a fact of Windows or COM, behaviour the package exists to reproduce, such as the Scripting Runtime's for a replacement of it, or a design decision the package's sources state. It stands alone, and the page shows no tag for it. The text then starts with the fact itself, not with "In twinBASIC BETA 1005, ..." or "In version 1.0.0.4, ...". If only part of a callout depends on the build, split it into two callouts. In a contributed package's section every callout has a label: the section's version for what the package does, `BETA ` for a fact of twinBASIC alone, or `timeless`. The build fails on a callout there without one, on any other label, and on a package version other than the one the section documents, its landing page's `indexed_from`. So when a section moves to a new version of its package, each callout labelled with the old one is checked again before the build passes. [`twin-index analyze`](Tools-Build#twin-index) lists those callouts, and after a new twinBASIC build, the callouts labelled with an older one. diff --git a/docs/Documentation/Tools-IDE.md b/docs/Documentation/Tools-IDE.md index 9aa6845db..dba9ad5e4 100644 --- a/docs/Documentation/Tools-IDE.md +++ b/docs/Documentation/Tools-IDE.md @@ -39,7 +39,7 @@ Exit codes: **0** every piece of every shipped theme loads; **1** at least one i Runs the add-in scenarios under `test/addin/`. Each scenario file is a `node:test` file and one **lane**, listed in `test/addin/lanes.mjs`; run it with [`addin-test.bat`](Tools#addin-testbat), because on its own a scenario skips itself. A lane runs in its own process, with its own slot from [`tbbroker`](Tools-Compiler#tbbroker) (a private desktop), work folder and private copy of the twinBASIC install, so a test add-in never loads into your own IDE and two lanes never share one. Each IDE's DevTools port is one Windows picks. -A scenario builds the add-ins it tests into its copy, opens a project and operates the IDE: it clicks, presses keys, types, and reads the add-ins' tool windows, message boxes, notifications, the code editor and the Debug Console. The seventeen lanes take about two minutes together: +A scenario builds the add-ins it tests into its copy, opens a project and operates the IDE: it clicks, presses keys, types, and reads the add-ins' tool windows, message boxes, notifications, the code editor and the Debug Console. The eighteen lanes take about two minutes together: - **Two sample lanes** operate the IDE's own sample add-ins, Sample 10 and Sample 15 (Global Search), end to end. - **Probe lanes** take what they need from `test/addin/probes/`. They check: diff --git a/docs/Reference/Contributed/Fmt/EscapeSequence/index.md b/docs/Reference/Contributed/Fmt/EscapeSequence/index.md index f37c8eadf..942191d02 100644 --- a/docs/Reference/Contributed/Fmt/EscapeSequence/index.md +++ b/docs/Reference/Contributed/Fmt/EscapeSequence/index.md @@ -29,7 +29,7 @@ esc.Execute text Debug.Print text ' aZZb ``` -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > [**Self**](Self) and the four property setters ([**AsciiBase**](AsciiBase), [**EscapeString**](EscapeString), [**IsAsciiCharacter**](IsAsciiCharacter) and [**ReplacementString**](ReplacementString) with **Let**) are declared **Friend**, for [**Create**](Create) to use. A project that references the package can call them too, so the properties of an instance can be changed after it is created. diff --git a/docs/Reference/Contributed/Fmt/StringValidation/index.md b/docs/Reference/Contributed/Fmt/StringValidation/index.md index 24ea37ac0..7af9ed790 100644 --- a/docs/Reference/Contributed/Fmt/StringValidation/index.md +++ b/docs/Reference/Contributed/Fmt/StringValidation/index.md @@ -14,7 +14,7 @@ Holds the small string tests and the case helper that the package's formatter is **StringValidation** is a standard module of five public functions. [**StringContains**](StringContains), [**StringContainsAny**](StringContainsAny), [**StringMatchesAny**](StringMatchesAny) and [**StringStartsWith**](StringStartsWith) return a **Boolean** about the text of a string, and [**CopyCapitalisation**](CopyCapitalisation) returns a string in the letter case of another. The formatter calls them to look for backslashes and escape sequences, to test whether a format string begins with `@` and to follow the case of an identifier. A project that references the package can call them too, by name or as `StringValidation.StringContains`. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > The package declares the module with `Option Private Module`, which marks it as internal to the package. A project that references the package can still call its functions. ```tb check_build check_run diff --git a/docs/Reference/Contributed/GdiPlus/FontCollection.md b/docs/Reference/Contributed/GdiPlus/FontCollection.md index b6772c78a..6ddae6dc0 100644 --- a/docs/Reference/Contributed/GdiPlus/FontCollection.md +++ b/docs/Reference/Contributed/GdiPlus/FontCollection.md @@ -13,7 +13,7 @@ The base class of the two classes that hold a set of font families: [**Installed A **FontCollection** is not created on its own. It gives its two derived classes the members that list the families in the collection. A variable declared **As FontCollection** can hold either of them, which is how the *fontCollection* parameter of [**FontFamily**](FontFamily) and of [**Font**](Font) accepts both. The class mirrors the GDI+ [FontCollection](https://learn.microsoft.com/en-us/windows/win32/api/gdiplusheaders/nl-gdiplusheaders-fontcollection) class. -> [!WARNING] 0.9.45.0, BETA 1010 +> [!WARNING] 0.9.45.0, BETA 1034 > Neither derived class can be created, so none of the members on this page can be used. See the warning on [**InstalledFontCollection**](InstalledFontCollection#creating) and on [**PrivateFontCollection**](PrivateFontCollection#creating). The rest of this page describes the members as the source defines them. ## Creating diff --git a/docs/Reference/Contributed/GdiPlus/GraphicsPath.md b/docs/Reference/Contributed/GdiPlus/GraphicsPath.md index 1dc029781..f9670ca04 100644 --- a/docs/Reference/Contributed/GdiPlus/GraphicsPath.md +++ b/docs/Reference/Contributed/GdiPlus/GraphicsPath.md @@ -45,7 +45,7 @@ Syntax: *path* **= GraphicsPath(** *points*(), *types*() [, *fillMode* ] **)** These forms create a path from existing point data: a [**PathData**](PathData), an array of [**TypedPoint**](Types/TypedPoint), or an array of **GpPointF** or **GpPoint** together with an array of [**PathPointType**](Types/PathPointType) of the same length. - + > [!WARNING] 0.9.45.0 > The forms that take point data do not create the path they are given. The call reports no error, and the path is empty: **PointCount** is 0, and a call of **GetPoints** on it raises an error. With an array of **GpPoint** the call records **OutOfMemory** in a 64-bit program and stops a 32-bit program with an access violation. Build the path with the **Add** methods instead. @@ -640,9 +640,9 @@ Syntax: *path*.**GetPoints(** *points*() **)** **As GpStatus** *points* : *required* A dynamic array of **GpPointF** or **GpPoint**. The call sizes it to the number of points, with indexes 1 to **PointCount**, and fills it. - + > [!NOTE] 0.9.45.0 -> On an empty path the call raises run-time error -2147467259, *Unspecified error*. Check **PointCount** first. +> On an empty path the call raises run-time error 9, *Subscript out of range*. Check **PointCount** first. ### GetTypes {: .no_toc } @@ -652,9 +652,9 @@ Syntax: *path*.**GetTypes(** *types*() **)** **As GpStatus** *types* : *required* A dynamic array of [**PathPointType**](Types/PathPointType). The call sizes it to the number of points, with indexes 1 to **PointCount**, and fills it. - + > [!NOTE] 0.9.45.0 -> On an empty path the call raises run-time error -2147467259, *Unspecified error*. Check **PointCount** first. +> On an empty path the call raises run-time error 9, *Subscript out of range*. Check **PointCount** first. ```tb check_build check_run diff --git a/docs/Reference/Contributed/GdiPlus/Image.md b/docs/Reference/Contributed/GdiPlus/Image.md index 55217bf44..5a1b26549 100644 --- a/docs/Reference/Contributed/GdiPlus/Image.md +++ b/docs/Reference/Contributed/GdiPlus/Image.md @@ -380,8 +380,8 @@ Syntax: *img*.**PropertyItem** **=** *item* The getter returns the property with the ID *propId* as an array of [**GpPropertyItem**](Types/GpPropertyItem) that holds one element, at index 1. The setter takes one **GpPropertyItem** and sets that property on the picture, adding it when it is not there. Its [**Id**](Types/GpPropertyItem), type, length and the address of its value must be filled in. - -> [!WARNING] BETA 1010 + +> [!WARNING] BETA 1034 > A property of an element of the array that **PropertyItem** returns, such as `items(1).TheASCII`, stops the process with an access violation, as it does for an element of any array of a user-defined type. An element can be read as a whole or by a field: `items(1).Id` works, and so does `Dim got As GpPropertyItem = items(1)` followed by `got.TheASCII`. **GetAllPropertyItems** returns its array in the same way, and the same holds for it. The setter, **PropertyCount**, **PropertyIdList** and **RemovePropertyItem** work. ### RemovePropertyItem diff --git a/docs/Reference/Contributed/GdiPlus/InstalledFontCollection.md b/docs/Reference/Contributed/GdiPlus/InstalledFontCollection.md index 698b582d1..469e15aa9 100644 --- a/docs/Reference/Contributed/GdiPlus/InstalledFontCollection.md +++ b/docs/Reference/Contributed/GdiPlus/InstalledFontCollection.md @@ -21,7 +21,7 @@ Syntax: *collection* **= InstalledFontCollection**( ) Syntax: *collection* **= New InstalledFontCollection** -> [!WARNING] 0.9.45.0, BETA 1010 +> [!WARNING] 0.9.45.0, BETA 1034 > Creating an **InstalledFontCollection** raises run-time error 438, *Object doesn't support this property or method*, and the result is **Nothing**. The statement `Debug.Assert TypeOf (Me) Is InstalledFontCollection` in the class raises it. Even with that statement removed, the constructor does not store the native collection it creates, so the object has no handle and every call on it fails with **InvalidParameter**. The class cannot be used until a version of the package corrects both. ```tb check_build diff --git a/docs/Reference/Contributed/GdiPlus/Matrix.md b/docs/Reference/Contributed/GdiPlus/Matrix.md index fec8f6876..4a59ca823 100644 --- a/docs/Reference/Contributed/GdiPlus/Matrix.md +++ b/docs/Reference/Contributed/GdiPlus/Matrix.md @@ -217,8 +217,8 @@ Syntax: *matrix*.**Scale(** *sx*, *sy*, [ *order* ] **)** **As GpStatus** Combines a scaling with the matrix. - -> [!WARNING] BETA 1010 + +> [!WARNING] BETA 1034 > A call of **Scale** written as a statement, with its arguments in parentheses and without **Call**, does nothing and raises no error: `m.Scale(2, 3)` leaves the matrix as it was. The same call written `m.Scale 2, 3`, `Call m.Scale(2, 3)` or `status = m.Scale(2, 3)` works. This is the case for a method of any class that is named **Scale**. ### Rotate diff --git a/docs/Reference/Contributed/GdiPlus/PathIterator.md b/docs/Reference/Contributed/GdiPlus/PathIterator.md index 799f1a0b3..10285e354 100644 --- a/docs/Reference/Contributed/GdiPlus/PathIterator.md +++ b/docs/Reference/Contributed/GdiPlus/PathIterator.md @@ -18,7 +18,7 @@ Each kind of iteration keeps its own position. The calls of **NextSubpath** step Every method exists twice. The *UDT form* has no arguments and returns a user-defined type: [**NextSubpath**](#nextsubpath) returns a [**SubPath**](Types/SubPath), [**NextMarker**](#nextmarker) a [**Marker**](Types/Marker), and so on. The *direct form* has the same name, takes variables for the results, and returns the number of points as a **Long**, as the C++ class does. In both forms the number of points is 0 when the iteration has come to its end. A call that fails records its status in **LastResult**, as every object of the package does. See [**GdiPlusBase**](GdiPlusBase). -> [!WARNING] 0.9.45.0, BETA 1010 +> [!WARNING] 0.9.45.0, BETA 1034 > **NextSubpathPath** and **NextMarkerPath** do not work. The package assigns a `New GraphicsPath` to a member of a user-defined type without `Set`, and twinBASIC reports a code generation error for it in **NextSubpathPath** whenever it compiles a project that uses **PathIterator**, though the other members still work when the program is run from the IDE. A program that calls the UDT form of either method stops at the call, and so does one that calls the direct form of **NextMarkerPath**. The direct form of **NextSubpathPath** returns 0 and a path that holds no GDI+ object. To get the points of a figure or a section, take the indexes from **NextSubpath** or **NextMarker** and read the points with **Enumerate**. ## Creating @@ -255,9 +255,9 @@ Syntax: *iterator*.**Enumerate(** *points*(), *types*(), [ *startIndex*, *endInd Copies the points and their types. The UDT form returns a [**TypedPoints**](Types/TypedPoints), a structure that holds an array of [**TypedPoint**](Types/TypedPoint) and the count in *resultCount*. The direct form sizes the two arrays, with indexes from 1, and returns the count. - + > [!NOTE] 0.9.45.0 -> On an empty path both forms raise run-time error -2147467259, *Unspecified error*. Check **Count** first. +> On an empty path both forms raise run-time error 9, *Subscript out of range*. Check **Count** first. ```tb check_build diff --git a/docs/Reference/Contributed/GdiPlus/PrivateFontCollection.md b/docs/Reference/Contributed/GdiPlus/PrivateFontCollection.md index 9080212c1..cb3db4fee 100644 --- a/docs/Reference/Contributed/GdiPlus/PrivateFontCollection.md +++ b/docs/Reference/Contributed/GdiPlus/PrivateFontCollection.md @@ -21,7 +21,7 @@ Syntax: *collection* **= PrivateFontCollection**( ) Syntax: *collection* **= New PrivateFontCollection** -> [!WARNING] 0.9.45.0, BETA 1010 +> [!WARNING] 0.9.45.0, BETA 1034 > Creating a **PrivateFontCollection** raises run-time error 438, *Object doesn't support this property or method*, and the result is **Nothing**. The statement `Debug.Assert TypeOf (Me) Is PrivateFontCollection` in the class raises it. Even with that statement removed, the constructor does not store the native collection it creates, so the object has no handle and **AddFontFile**, **AddMemoryFont** and the members of **FontCollection** fail with **InvalidParameter**. The class cannot be used until a version of the package corrects both. ## AddFontFile diff --git a/docs/Reference/Contributed/GdiPlus/Types/GpPointF.md b/docs/Reference/Contributed/GdiPlus/Types/GpPointF.md index 4f3b4d032..71b8f3804 100644 --- a/docs/Reference/Contributed/GdiPlus/Types/GpPointF.md +++ b/docs/Reference/Contributed/GdiPlus/Types/GpPointF.md @@ -86,7 +86,7 @@ Syntax: *point*.**Normalized** As **GpPointF** Returns the vector scaled to length 1. -> [!NOTE] 0.9.45.0, BETA 1010 +> [!NOTE] 0.9.45.0, BETA 1034 > A program that names **Normalized** does not link: the linker reports a code generation error in `Types.Normalized`. Use [**Normalize**](#normalize) on a copy of the point instead. ### Transposed @@ -154,8 +154,8 @@ These change the point they are called on. > [!NOTE] 0.9.45.0 > **Add** and **Sub** change the point they are called on. Before version 0.9.0.43 they returned the result and left the point alone. Use [**Sum**](#sum-diff) and [**Diff**](#sum-diff) to get a new point. - -> [!NOTE] 0.9.45.0, BETA 1010 + +> [!NOTE] 0.9.45.0, BETA 1034 > The compiler reads `p.Scale(2)` as the start of a `Scale` statement and reports a syntax error. Write `p.Scale 2` or `Call p.Scale(2)`. **Scale2**, **InvScale** and the other methods are not affected. > > ```tb check_build expect-error=TB5212 diff --git a/docs/Reference/Contributed/GdiPlus/Types/GpRect.md b/docs/Reference/Contributed/GdiPlus/Types/GpRect.md index 7906a2e37..05638f3aa 100644 --- a/docs/Reference/Contributed/GdiPlus/Types/GpRect.md +++ b/docs/Reference/Contributed/GdiPlus/Types/GpRect.md @@ -291,7 +291,7 @@ Syntax: *rect*.**InflatedByPt**( *point* As **GpPoint** ) As **GpRect** Return a copy of the rectangle moved or inflated as [**Offset**](#offset-offsetpt) and [**Inflate**](#inflate-inflatept) do. -> [!NOTE] 0.9.45.0, BETA 1010 +> [!NOTE] 0.9.45.0, BETA 1034 > A program that names any of these four methods does not link: the linker reports a code generation error in the method. Copy the rectangle and call **Offset**, **OffsetPt**, **Inflate** or **InflatePt** on the copy. ```tb check_build check_run diff --git a/docs/Reference/Contributed/GdiPlus/Types/GpRectF.md b/docs/Reference/Contributed/GdiPlus/Types/GpRectF.md index b173fb6c0..bc9d2a615 100644 --- a/docs/Reference/Contributed/GdiPlus/Types/GpRectF.md +++ b/docs/Reference/Contributed/GdiPlus/Types/GpRectF.md @@ -306,7 +306,7 @@ Syntax: *rect*.**InflatedByPt**( *point* As **GpPointF** ) As **GpRectF** Return a copy of the rectangle moved or inflated as [**Offset**](#offset-offsetpt) and [**Inflate**](#inflate-inflatept) do. -> [!NOTE] 0.9.45.0, BETA 1010 +> [!NOTE] 0.9.45.0, BETA 1034 > A program that names any of these four methods does not link: the linker reports a code generation error in the method. Copy the rectangle and call **Offset**, **OffsetPt**, **Inflate** or **InflatePt** on the copy. ```tb check_build check_run diff --git a/docs/Reference/Contributed/GdiPlus/Types/GpSizeF.md b/docs/Reference/Contributed/GdiPlus/Types/GpSizeF.md index 13611d372..8dbb6cd02 100644 --- a/docs/Reference/Contributed/GdiPlus/Types/GpSizeF.md +++ b/docs/Reference/Contributed/GdiPlus/Types/GpSizeF.md @@ -141,8 +141,8 @@ These change the size they are called on. > [!NOTE] 0.9.45.0 > **Add** and **Sub** change the size they are called on. Before version 0.9.0.43 they returned the result and left the size alone. Use [**Sum**](#sum-diff) and [**Diff**](#sum-diff) to get a new size. - -> [!NOTE] 0.9.45.0, BETA 1010 + +> [!NOTE] 0.9.45.0, BETA 1034 > The compiler reads `s.Scale(2)` as the start of a `Scale` statement and reports a syntax error. Write `s.Scale 2` or `Call s.Scale(2)`. **Scale2**, **InvScale** and the other methods are not affected. > > ```tb check_build expect-error=TB5212 diff --git a/docs/Reference/Contributed/GdiPlus/Types/PathPointType.md b/docs/Reference/Contributed/GdiPlus/Types/PathPointType.md index bec52acb0..376b460db 100644 --- a/docs/Reference/Contributed/GdiPlus/Types/PathPointType.md +++ b/docs/Reference/Contributed/GdiPlus/Types/PathPointType.md @@ -48,8 +48,8 @@ Debug.Print CLng(t.PointType) & " " & t.CloseSubpath & " " & t.PathMarker ' ``` - -> [!WARNING] BETA 1010 + +> [!WARNING] BETA 1034 > Reading a property of an element of an array, as in `types(i).CloseSubpath`, stops the program with an access violation. Copy the element to a variable and read the property of that. A type held in a variable or in a field of a **TypedPoint** is read without trouble. diff --git a/docs/Reference/Contributed/Generics/index.md b/docs/Reference/Contributed/Generics/index.md index f9cd702b0..957b0c659 100644 --- a/docs/Reference/Contributed/Generics/index.md +++ b/docs/Reference/Contributed/Generics/index.md @@ -103,7 +103,7 @@ End Module -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > **New** with a generic class from a package compiles only when some declaration in the project or the package names the same class with the same type arguments. A variable declared with the tuple's type is such a declaration. Without one, `Dim t As Any = New TupleOf2(Of String, Long)("a", 2)` fails with TB5074, *Could not bind to parameterized constructor of class 'TupleOf2'*, and so does **New** assigned to an **Object** or **Variant** variable or passed as an argument. A generic class declared in the project's own code is not affected. Assign the tuple to a variable of its own type first; that variable can then be assigned to an **Object** or **Variant** variable, through which the items are read late-bound: > > ```tb check_build check_run @@ -159,7 +159,7 @@ Three kinds of type cannot be used: > The constructor stores each item with `=`, not with **Set**, so creating a tuple of an object type fails. For **Object**, which compiles, **New** raises run-time error 91, *Object variable or With block variable not set*, even when the item passed is **Nothing**. A class whose default member takes an argument, such as **Collection**, fails to compile, with TB5092 *Missing argument 'Index'* reported in the package's source. -> [!WARNING] BETA 1010 +> [!WARNING] BETA 1034 > A tuple type with an array as a type argument, such as `TupleOf1(Of Long())`, crashes the compiler wherever it is written: in a declaration, a parameter, a function's result type or after **New**. The compiler restarts and crashes again, so nothing in the project compiles until the line is removed. The same happens with a generic class declared in a project's own code. ## TupleOf4 diff --git a/docs/Reference/Contributed/OLEGuids/IUnknownUnrestricted.md b/docs/Reference/Contributed/OLEGuids/IUnknownUnrestricted.md index b567d2d9f..7c4995740 100644 --- a/docs/Reference/Contributed/OLEGuids/IUnknownUnrestricted.md +++ b/docs/Reference/Contributed/OLEGuids/IUnknownUnrestricted.md @@ -32,7 +32,7 @@ A pointer that **QueryInterface** returns comes with a reference, which the call -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > When the interface is not supported, the COM contract sets *ppvObject* to 0. An object of a class that the project compiles --- a class, a form, a control --- returns `E_NOINTERFACE` and leaves the variable as it was. The runtime's own objects, such as a **Collection**, set it to 0, as every object of VB6 does. Set the variable to 0 before the call, and test the result rather than the pointer. ```tb check_run diff --git a/docs/Reference/Contributed/TBMANLIB/ToolsArray.md b/docs/Reference/Contributed/TBMANLIB/ToolsArray.md index 77c099031..238efd2c8 100644 --- a/docs/Reference/Contributed/TBMANLIB/ToolsArray.md +++ b/docs/Reference/Contributed/TBMANLIB/ToolsArray.md @@ -299,8 +299,8 @@ Debug.Print UBound(P) & " " & P(0) ``` - -A *position* of 0 raises the error *Unspecified error* (-2147467259), because the first part would be dimensioned with an upper bound of -1. + +A *position* of 0 raises run-time error 9, *Subscript out of range*, because the first part would be dimensioned with an upper bound of -1. ### SplitByteArrayByKeyword @@ -325,7 +325,7 @@ Returns a **Variant** array, from 0, of **Byte** arrays: the parts between the o > [!WARNING] 1.0.0.29 -> A keyword at the very start raises the error of a *position* of 0 in [**SplitByteArray**](#splitbytearray), *Unspecified error* (-2147467259). A keyword at the very end leaves a last part that is an array with one element holding 0, and not an empty array. +> A keyword at the very start raises the error of a *position* of 0 in [**SplitByteArray**](#splitbytearray), error 9, *Subscript out of range*. A keyword at the very end leaves a last part that is an array with one element holding 0, and not an empty array. ```tb check_build check_run Dim Bytes() As Byte @@ -343,7 +343,7 @@ Parts = ToolsArray.SplitByteArrayByKeyword(Bytes, ",") Debug.Print Err.Number ' Output: ' 2 0 0 -' -2147467259 +' 9 ``` ```tb check_build check_run diff --git a/docs/Reference/Contributed/TBMANLIB/cHttpClient.md b/docs/Reference/Contributed/TBMANLIB/cHttpClient.md index b49cb0b11..077d94064 100644 --- a/docs/Reference/Contributed/TBMANLIB/cHttpClient.md +++ b/docs/Reference/Contributed/TBMANLIB/cHttpClient.md @@ -63,7 +63,7 @@ Several error texts of the class are written in Chinese. The ones that are not a > The class ignores every certificate error: an expired certificate, one from an unknown authority and one issued for another name are all accepted. An HTTPS request therefore encrypts the traffic, but does not prove who the server is, and a program that sends a password or a key with it can send it to an impostor. -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > The compiler refuses an assignment written through a public dictionary field of an object, such as `Http.RequestHeaders("X-Key") = "1"`, with TB5090. Write the default member out: `Http.RequestHeaders.Item("X-Key") = "1"`. Reading `Http.RequestHeaders("X-Key")` is accepted. ## Request settings diff --git a/docs/Reference/Contributed/TBMANLIB_Aes/cAes.md b/docs/Reference/Contributed/TBMANLIB_Aes/cAes.md index 5263a5b2d..9c7c5e38f 100644 --- a/docs/Reference/Contributed/TBMANLIB_Aes/cAes.md +++ b/docs/Reference/Contributed/TBMANLIB_Aes/cAes.md @@ -880,7 +880,7 @@ The method does not raise an error. When the conversion fails, it prints `SetGcm > [!WARNING] 1.0.0.4 -> An empty string does not remove the additional data. The method fails for an empty string and keeps the data that was set before, and only the Debug Console shows the message `SetGcmAADFromString failed: Unspecified error`. The next GCM encryption then authenticates the old data. To remove the data, assign an array that was never sized to [**GcmAAD**](#gcmaad). +> An empty string does not remove the additional data. The method fails for an empty string and keeps the data that was set before, and only the Debug Console shows the message `SetGcmAADFromString failed: Subscript out of range`. The next GCM encryption then authenticates the old data. To remove the data, assign an array that was never sized to [**GcmAAD**](#gcmaad). ```tb check_build check_run projname=aes-class Dim aes As New cAes @@ -894,7 +894,7 @@ aad = aes.GcmAAD Debug.Print HexOf(aad) ' Output: ' 686561646572 -' SetGcmAADFromString failed: Unspecified error +' SetGcmAADFromString failed: Subscript out of range ' 686561646572 ``` diff --git a/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/Helpers.md b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/Helpers.md index af32bb312..517f58acc 100644 --- a/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/Helpers.md +++ b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/Helpers.md @@ -260,7 +260,7 @@ Debug.Print columns(0) & " " & columns(1) & " " & columns(2) The output is that of a console of 80 columns by 25 rows, which is also the size that a program without a console gets. -> [!NOTE] 1.0.0.1, BETA 1010 +> [!NOTE] 1.0.0.1, BETA 1034 > A program that references the packages TBMANLIB_ConsoleCore and TBMANLIB_ConsoleComponents cannot read what the members that give a **ConsoleRect** or a **ConsolePosition** return: the compiler refuses the members of the value and its assignment to a variable of the type. The members that give a **Long** or an array of **Long** compile. A **cLayoutHelper** cannot be made with the **cConsole** of the published package at all; see [The two packages](./#the-two-packages). diff --git a/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cConsoleLayout.md b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cConsoleLayout.md index f7b9b667c..f30f7dd61 100644 --- a/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cConsoleLayout.md +++ b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/cConsoleLayout.md @@ -201,7 +201,7 @@ Divides the root panel's content area into a grid of panels. Syntax: *object*.**CreateGrid** ( *cols* , *rows* , *namePrefix* ) **As Variant** *cols*, *rows* -: *required* **Long** values, the number of columns and rows of the grid. A value of 0 raises error -2147467259, *Unspecified error*. +: *required* **Long** values, the number of columns and rows of the grid. A value of 0 raises run-time error 9, *Subscript out of range*. *namePrefix* : *required* A **String**. The panels are named *namePrefix*`_0`, *namePrefix*`_1` and so on. diff --git a/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/index.md b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/index.md index 231348de8..bf347242f 100644 --- a/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/index.md +++ b/docs/Reference/Contributed/TBMANLIB_ConsoleComponents/index.md @@ -33,7 +33,7 @@ con.Restore ``` -> [!WARNING] 1.0.0.1, BETA 1010 +> [!WARNING] 1.0.0.1, BETA 1034 > The components that take a **cConsole** cannot be used with the published package [TBMANLIB_ConsoleCore](../TBMANLIB_ConsoleCore/). The package compiles, but the constructors of [**cProgressBar**](cProgressBar), [**cSparkline**](cSparkline), [**cVBar**](cVBar), [**cDataGrid**](cDataGrid), [**cConsoleLayout**](cConsoleLayout) and [**cLayoutHelper**](Helpers), and the members of [**cConsolePanel**](cConsolePanel) that take a **cConsoleBuffer**, raise error -2147467262, *No such interface supported*, when they are given an object of the published ConsoleCore, and a program cannot read the **ConsoleRect** and **ConsolePosition** values that the components return. The package carries a copy of ConsoleCore of its own, and the compiler treats the types of that copy as different from the types of the published package. [The two packages](#the-two-packages) has the details, and the one arrangement in which the components work. Only [**cConsoleTheme**](cConsoleTheme), the module **mConsoleThemeFactory**, [**cColorHelper**](Helpers#ccolorhelper-class) and the geometry of **cConsolePanel** work with the package as it is. A sample on these pages that makes a component with a **cConsole** raises this error in a project that references both packages; the output it shows is that of the code of the components compiled into the program's own project. * TOC diff --git a/docs/Reference/Contributed/TBMANLIB_Diagnostic/List.md b/docs/Reference/Contributed/TBMANLIB_Diagnostic/List.md index e2e8d0ff0..52d264636 100644 --- a/docs/Reference/Contributed/TBMANLIB_Diagnostic/List.md +++ b/docs/Reference/Contributed/TBMANLIB_Diagnostic/List.md @@ -94,7 +94,7 @@ Debug.Print blank.Count ``` -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > In a 32-bit run in the IDE, **Err.Description** of the error that **SetItem** raises here is *Automation error*, not *Invalid procedure call or argument*, when the list is declared with `Dim one As New List(Of String)`. **Err.Number** is 5 in every run. A built program, 32-bit or 64-bit, and a 64-bit run in the IDE give the text *Invalid procedure call or argument*, as VB6 does for the same call of **Collection.Add**. A program that tests the number is not affected. * TOC diff --git a/docs/Reference/Contributed/TBMANLIB_Diagnostic/index.md b/docs/Reference/Contributed/TBMANLIB_Diagnostic/index.md index b7e7794f5..267358363 100644 --- a/docs/Reference/Contributed/TBMANLIB_Diagnostic/index.md +++ b/docs/Reference/Contributed/TBMANLIB_Diagnostic/index.md @@ -20,7 +20,7 @@ The **TBMANLIB_Diagnostic** contributed package makes a diagnostic report of a W The package is the reusable part of woeoio's entry for [twinBASIC Monthly Challenge #1](../../../../Challenges/1), *Self-Contained Diagnostic Tool*. The entry is a console program that shows the values on a live screen, or writes a report when it is started with an argument such as `/json`. That program is not in the package; [**FormatterFactory.CreateFormatterFromArg**](FormatterFactory#createformatterfromarg) reads such an argument. -This section documents version 1.0.0.1, published on TWINSERV on 3 February 2026 under the MIT licence. The package compiles in BETA 1005, as a 32-bit and as a 64-bit program. The repository [woeoio/tbman](https://github.com/woeoio/tbman), the toolkit of the same author whose package is [**TBMANLIB**](../TBMANLIB/), does not hold the code of this package. +This section documents version 1.0.0.1, published on TWINSERV on 3 February 2026 under the MIT licence. The package compiles in BETA 1034, as a 32-bit and as a 64-bit program. The repository [woeoio/tbman](https://github.com/woeoio/tbman), the toolkit of the same author whose package is [**TBMANLIB**](../TBMANLIB/), does not hold the code of this package. The package has defects: the **Dictionary** class cannot be used, and several members give a wrong result without an error. [Known problems in version 1.0.0.1](#known-problems-in-version-1001) lists them. The sample below uses the parts that work. diff --git a/docs/Reference/Contributed/TBMANLIB_Xml/cXml.md b/docs/Reference/Contributed/TBMANLIB_Xml/cXml.md index 4417037f0..1a936081d 100644 --- a/docs/Reference/Contributed/TBMANLIB_Xml/cXml.md +++ b/docs/Reference/Contributed/TBMANLIB_Xml/cXml.md @@ -1422,7 +1422,7 @@ Close #f Debug.Print y.LoadFromFile(path) Kill path ' Output: -' LoadFromFile failed: Unspecified error +' LoadFromFile failed: Subscript out of range ' False ' True 0 ' ISO-8859-1 233 diff --git a/docs/Reference/Contributed/tsJson/JsonOptions.md b/docs/Reference/Contributed/tsJson/JsonOptions.md index 0f63a2657..399ed4846 100644 --- a/docs/Reference/Contributed/tsJson/JsonOptions.md +++ b/docs/Reference/Contributed/tsJson/JsonOptions.md @@ -46,7 +46,7 @@ A property of an options object checks nothing when it is set. The value is used -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > The class declares a **Private** constructor and the attribute `[COMCreatable(False)]`, so that only its presets create options objects. twinBASIC does not enforce a **Private** constructor, and a project that references the package can still create an object with **New**. Every property of such an object has the empty value of its type: **Indent** 0, an empty **Newline**, **Syntax** **jsRfc8259**, and a limit of 0, which means no limit. The text it writes has no line breaks and no indentation, but still a space after each colon. The presets are the way to get the defaults. > > ```tb check_build check_run diff --git a/docs/Reference/Contributed/tsPwGen/PwGenOptions.md b/docs/Reference/Contributed/tsPwGen/PwGenOptions.md index d0f1406da..a3862c1a6 100644 --- a/docs/Reference/Contributed/tsPwGen/PwGenOptions.md +++ b/docs/Reference/Contributed/tsPwGen/PwGenOptions.md @@ -48,7 +48,7 @@ A property of a recipe checks nothing when it is set. A value that cannot be use -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > The class declares a **Private** constructor and the attribute `[COMCreatable(False)]`, so that only its presets create recipes. twinBASIC does not enforce a **Private** constructor, and a project that references the package can still create an object with **New**. It is a recipe in which every property has the empty value of its type: **Length** 0, no class enabled, empty strings, **MinPerClass** 0, **Mode** **pgmRandom** and **RandomSource** **Nothing**. **Generate** returns an empty string for it, with the error `InvalidLength`, until the properties are set. The presets are the way to get the defaults. > > ```tb check_build check_run diff --git a/docs/Reference/Contributed/xCalendar/index.md b/docs/Reference/Contributed/xCalendar/index.md index 0e364ef32..5613749a3 100644 --- a/docs/Reference/Contributed/xCalendar/index.md +++ b/docs/Reference/Contributed/xCalendar/index.md @@ -15,7 +15,7 @@ The **xCalendar** contributed package provides a calendar control: a grid of the The package has one public class, **xCalendar**, a user control that draws itself with GDI calls. The class has 27 properties, 10 methods and 12 events, and four enumerations belong to it. This section documents version 1.0.17.0, published under the MIT licence. The package describes itself as "Calendar MSCAL replacement (Experimental, 32-bit and 64-bit)", and its 32-bit and 64-bit builds behave the same. -The package has defects: setting **Value** to a date in another month leaves the grid showing the old month, and the colour properties of the text have no effect. [Known problems in version 1.0.17.0](#known-problems-in-version-10170) lists them. +The package has defects: setting **Value** to a date in another month leaves the grid showing the old month, and setting **FirstDay** leaves the dates of the grid in the old order. [Known problems in version 1.0.17.0](#known-problems-in-version-10170) lists them. ```tb check_build Private Sub Form_Load() @@ -51,7 +51,7 @@ Debug.Print cal.Year ``` -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > `Dim cal As xCalendar` fails with TB5139, *[xCalendar] is a project or library, not a datatype*. > > ```tb check_build expect-error=TB5139 @@ -67,7 +67,7 @@ Debug.Print d, m ' 2 1 ``` -> [!NOTE] BETA 1010 +> [!NOTE] BETA 1034 > `ShortLength` and `LongLength` written alone fail with TB5073, *'ShortLength' is ambiguous*. > > ```tb check_build expect-error=TB5073 @@ -88,25 +88,23 @@ The two lists are scaled with the DPI of the display, by the [**DpiScale**](../. ### Title area - + The title area is present when **ShowTitle** or **ShowDateSelectors** is **True**, and has no height when both are **False**. Its height is the height of one line of text in **TitleFont**, plus **TitleMargin** above and below. The control works the height out when it is created and when its size changes, and again when **TitleFont** is assigned with **Set** or **TitleMargin** or **ShowDateSelectors** is set. Its background is **BackColor**. It holds two things. -- **The title** is the name of the shown month and the year, such as `October 2026`, in **TitleFont**, centred in the space to the left of the month list, or in the whole width of the control when the lists are not shown. A title wider than that space is cut off at both ends: see [Known problems](#known-problems-in-version-10170). **MonthLength** decides whether the month is written in full or as its first three letters. It is drawn only when **ShowTitle** is **True**. The month name comes from the [**MonthName**](../../../Modules/Strings/MonthName) function, so it is in the language of the system. +- **The title** is the name of the shown month and the year, such as `October 2026`, in **TitleFont** and **TitleFontColor**, centred in the space to the left of the month list, or in the whole width of the control when the lists are not shown. A title wider than that space is cut off at both ends: see [Known problems](#known-problems-in-version-10170). **MonthLength** decides whether the month is written in full or as its first three letters. It is drawn only when **ShowTitle** is **True**. The month name comes from the [**MonthName**](../../../Modules/Strings/MonthName) function, so it is in the language of the system. - **The date selectors** are two drop-down lists at the right edge, drawn only when **ShowDateSelectors** is **True**. At 96 DPI the month list is 80 pixels wide and holds the names of the twelve months, and the year list is 50 pixels wide and holds the years 1900 to 2100. Both are 21 pixels high, placed so that a list of that height is centred in the height of the title area. The year list ends 5 pixels from the right edge of the control, and the month list ends 5 pixels before the year list. At 144 DPI each of these sizes is 1.5 times as large: the lists are 120 and 75 pixels wide, and the year list ends 8 pixels from the right edge. The height of a drop-down list is fixed by its font, so at 144 DPI each list is 28 pixels high, not the 31 that the control asks for, and sits 2 pixels above the centre of the title area. When the control is resized the lists keep their distance from the right edge, and the title is centred again in the space to their left. Choosing an entry changes the shown month or year, keeps the selected day of the month, and raises **NewMonth** or **NewYear**. ### Row of day names - -The row is present when **ShowDays** is **True**. It has seven cells of the same width, filled with **DayBackColor**, each holding the name of a weekday centred, in **DayFont**. The first cell is the weekday that **FirstDay** names. **DayLength** decides whether a name is one letter, three letters or the whole name. The names come from the [**WeekdayName**](../../../Modules/Strings/WeekdayName) function, so they are in the language of the system. The height of the row is the height of the letter M in **DayFont**, plus **DaysMargin** above and below. + +The row is present when **ShowDays** is **True**. It has seven cells of the same width, filled with **DayBackColor**, each holding the name of a weekday centred, in **DayFont** and **DayFontColor**. The first cell is the weekday that **FirstDay** names. **DayLength** decides whether a name is one letter, three letters or the whole name. The names come from the [**WeekdayName**](../../../Modules/Strings/WeekdayName) function, so they are in the language of the system. The height of the row is the height of the letter M in **DayFont**, plus **DaysMargin** above and below. ### Grid of days - -The grid has six rows of seven cells, 42 days in all, whatever the month. The first cell holds the latest date, on or before the first day of the shown month, that falls on the weekday **FirstDay** names. A month therefore starts in the first row, and the cells before its first day hold the last days of the month before. Each cell shows the day of the month at its top left corner, in **GridFont**. The cell of the selected date, **Value**, is filled with a fixed warm grey (`&H82868E`) and has a black line along its left and top edges. - -All the text of the control is drawn in the same near-black, whatever the colour properties say: the days of the month before and the month after are not grey, and the title is not in **TitleFontColor**. See [Known problems](#known-problems-in-version-10170). + +The grid has six rows of seven cells, 42 days in all, whatever the month. The first cell holds the latest date, on or before the first day of the shown month, that falls on the weekday **FirstDay** names. A month therefore starts in the first row, and the cells before its first day hold the last days of the month before. Each cell shows the day of the month at its top left corner, in **GridFont**. The days of the shown month are in **GridFontColor**, and the days of the month before and the month after in a fixed grey (`&HAAAAAA`). The cell of the selected date, **Value**, is filled with a fixed warm grey (`&H82868E`) and has a black line along its left and top edges. **ShowVerticalGrid** and **ShowHorizontalGrid** switch the lines between the cells on and off, and **GridCellEffect** and **GridLinesColor** decide how the lines look. A black frame of one pixel always surrounds the row of day names and the grid, whichever lines are on. @@ -115,8 +113,8 @@ A cell is selected only when its date is equal to **Value**. A **Value** with a ### Disabled state - -When **Enabled** is set to **False** by the program, the two lists are disabled and drawn in grey, and the row of day names has the background `&HEEEEEE` in place of **DayBackColor**. A control that loads from a form with **Enabled** already **False** draws its lists as an enabled control does: see [Known problems](#known-problems-in-version-10170). With **Flat** lines, the grid lines are `&H5B5B5B` in place of **GridLinesColor**. **BackColor** is the same as for an enabled control, and the text keeps its colour. + +When **Enabled** is set to **False** by the program, the two lists are disabled and drawn in grey, and the row of day names has the background `&HEEEEEE` in place of **DayBackColor**. A control that loads from a form with **Enabled** already **False** draws its lists as an enabled control does: see [Known problems](#known-problems-in-version-10170). With **Flat** lines, the grid lines are `&H5B5B5B` in place of **GridLinesColor**. The title is drawn in a light grey (`&HBCBCBC`) in place of **TitleFontColor**, and the day names and the days of the shown month in grey (`&H999999`) in place of **DayFontColor** and **GridFontColor**. **BackColor** is the same as for an enabled control. A disabled control does not receive the mouse or the keyboard from the user, and the focus cannot be given to it. Code can still change its date, for example with **NextDay** or **Value**. @@ -177,11 +175,10 @@ Setting **Value**, **Day**, **Month** or **Year**, and calling the methods that ## Known problems in version 1.0.17.0 - + > [!WARNING] 1.0.17.0 > The package gives a wrong result without an error in these cases. > -> - **The colour properties of the text have no effect.** **DayFontColor**, **GridFontColor** and **TitleFontColor** are stored and returned, but the day names, the day numbers and the title are always drawn in the same near-black (`&H4`), and so are the days of the month before and the month after, which the control means to draw in grey. The disabled state has no effect on the text colour either. > - **Setting Value to a date in another month, or setting FirstDay, does not place the dates of the grid again.** After **Value** is set, the title and the two lists show the new month, but the grid still shows the dates of the month before, with no cell selected. After **FirstDay** is set, the day names move to the new order and the dates do not, so the names and the dates disagree. **Refresh** does not correct either. The dates are placed again when the selection is next moved, by a click on a cell, by **Today** or by one of the **Next** and **Previous** methods. > - **A press near the right edge or the bottom edge can select a date that the grid does not show.** In a control 255 pixels wide, a cell is 36.43 pixels, which the control takes as 36, and a press at 252 to 254 pixels from the left selects the date of a column after the last, the first date of the next row. In a control 292 pixels high, a press at 290 or 291 pixels from the top selects a date a row below the grid. > - **A flat grid is drawn near-black with the default GridLinesColor.** With **GridCellEffect** **Flat**, the default `&H80000010`, the system colour **vbButtonShadow**, is drawn as `&H10`, a near-black, and not as grey. A colour given as an RGB value, such as `RGB(255, 0, 0)`, is drawn as it is. @@ -241,7 +238,7 @@ The control keeps the object that is assigned, not a copy of it. Assigning a fon ### DayFontColor {: .no_toc } -The colour of the day names. **OLE_COLOR**. Default: black. The value is stored, and it has no effect on the colour of the text: see [Known problems](#known-problems-in-version-10170). +The colour of the day names. **OLE_COLOR**. Default: black. A disabled control draws them in grey: see [Disabled state](#disabled-state). ### DayLength {: .no_toc } @@ -288,7 +285,7 @@ The font of the day numbers in the grid. A **Font** object. Default: Arial, 8 po ### GridFontColor {: .no_toc } -The colour of the day numbers. **OLE_COLOR**. Default: dark blue (`&HA00000`, blue 160). The value is stored, and it has no effect on the colour of the text: see [Known problems](#known-problems-in-version-10170). +The colour of the numbers of the days of the shown month. **OLE_COLOR**. Default: dark blue (`&HA00000`, blue 160). The days of the month before and the month after are always grey, and a disabled control draws every day number in grey: see [Grid of days](#grid-of-days) and [Disabled state](#disabled-state). ### GridLinesColor {: .no_toc } @@ -348,7 +345,7 @@ The font of the title. A **Font** object. Default: Arial, 16 points. The remarks ### TitleFontColor {: .no_toc } -The colour of the title. **OLE_COLOR**. Default: orange (`&H80FF&`, red 255, green 128, blue 0). The value is stored, and it has no effect on the colour of the text: see [Known problems](#known-problems-in-version-10170). +The colour of the title. **OLE_COLOR**. Default: orange (`&H80FF&`, red 255, green 128, blue 0). A disabled control draws the title in a light grey: see [Disabled state](#disabled-state). ### TitleMargin {: .no_toc } @@ -399,10 +396,6 @@ Setting **Year** selects the same day and month in that year, shows the year, an A method is called as *object*.**Name**. None takes an argument or returns a value. The methods that move the selection show the month of the new date, update the two lists, and raise no event. They do not check that the new date is in the range of the year list, 1900 to 2100: past either end, **Value** and the title follow, and the year list is blank before 1900 and unchanged after 2100. - -> [!WARNING] BETA 1010 -> [**DateAdd**](../../../Modules/DateTime/DateAdd) with the interval `"m"`, `"q"` or `"yyyy"` does not stop at the end of a shorter month: the date overflows into the next month. **NextMonth**, **PreviousMonth**, **NextYear** and **PreviousYear** call **DateAdd**, so **NextMonth** from January 31, 2026 selects March 3, 2026, **PreviousMonth** from March 31, 2026 selects March 3, 2026, and **NextYear** from February 29, 2024 selects March 1, 2025. VB6 gives the last day of the shorter month in every such case: February 28, 2026 for the first one. - ### NextDay {: .no_toc } @@ -414,7 +407,7 @@ Selects the day after the selected one. {: .no_toc } -Selects the same day one month after the selected one, or the last day of that month when it has no such day, so January 31 gives February 28 or 29. The change stays: **NextMonth** from February 28 gives March 28, not March 31. In BETA 1010 the result differs for the 29th to the 31st: see the warning at the start of this section. +Selects the same day one month after the selected one, or the last day of that month when it has no such day, so January 31 gives February 28 or 29. The change stays: **NextMonth** from February 28 gives March 28, not March 31. ### NextWeek @@ -428,7 +421,7 @@ Selects the day seven days after the selected one. {: .no_toc } -Selects the same day one year after the selected one, or February 28 when the selected day is February 29 and the next year is not a leap year. In BETA 1010 the result differs for February 29: see the warning at the start of this section. +Selects the same day one year after the selected one, or February 28 when the selected day is February 29 and the next year is not a leap year. ### PreviousDay @@ -442,7 +435,7 @@ Selects the day before the selected one. {: .no_toc } -Selects the same day one month before the selected one. The remarks for [**NextMonth**](#nextmonth) apply, and so does the warning at the start of this section. +Selects the same day one month before the selected one. The remarks for [**NextMonth**](#nextmonth) apply. ### PreviousWeek @@ -456,7 +449,7 @@ Selects the day seven days before the selected one. {: .no_toc } -Selects the same day one year before the selected one. The remarks for [**NextYear**](#nextyear) apply, and so does the warning at the start of this section. +Selects the same day one year before the selected one. The remarks for [**NextYear**](#nextyear) apply. ### Refresh diff --git a/docs/Reference/Experimental.md b/docs/Reference/Experimental.md index 449bf71eb..449334c73 100644 --- a/docs/Reference/Experimental.md +++ b/docs/Reference/Experimental.md @@ -23,4 +23,4 @@ These packages are published through TWINSERV, as the [Contributed Packages](../ - [**TBMANLIB_ListBoxHScroll**](../Contributed/TBMANLIB_ListBoxHScroll/) -- a horizontal scroll bar for a **ListBox**. Version 1.0.0.1 compiles, and its scroll arrows and track move the thumb by a different step than the list's contents, so the thumb runs ahead of the text and the arrows cannot reach the end of a long line. - [**TBMANLIB_Xml**](../Contributed/TBMANLIB_Xml/) -- XML documents parsed, edited and written, in twinBASIC code. Version 1.0.0.1 compiles, and its parser misreads some common XML, such as self-closing elements and entities in text, and does not return for some malformed text. Its XPath engine reads some valid expressions as others, such as the step `..` and `and` with `or`. - [**WinReg**](../Contributed/WinReg/) -- registry keys and values, read, written, deleted and enumerated. Version 1.0.1.1 does not compile in twinBASIC BETA 1034, and three of its methods give wrong results: **ReadString** returns the text followed by NUL characters, **GetRegistryValueType** returns **REG_NONE** for a value that holds data, and one overload of **ConvertValueToString** finds no value. -- [**xCalendar**](../Contributed/xCalendar/) -- a calendar control, in place of the Calendar control of `MSCAL.OCX`. Version 1.0.17.0 compiles, and has defects, among them a grid that keeps the dates of the old month when **Value** is set to another month, and colour properties of the text that have no effect. +- [**xCalendar**](../Contributed/xCalendar/) -- a calendar control, in place of the Calendar control of `MSCAL.OCX`. Version 1.0.17.0 compiles, and has defects, among them a grid that keeps the dates of the old month when **Value** is set to another month, and presses near the right and bottom edges that select dates the grid does not show. diff --git a/test/package-tests/GdiPlus/Sources/Br_PathIteratorTests.twin b/test/package-tests/GdiPlus/Sources/Br_PathIteratorTests.twin index b4088d291..5b2ea0d4f 100644 --- a/test/package-tests/GdiPlus/Sources/Br_PathIteratorTests.twin +++ b/test/package-tests/GdiPlus/Sources/Br_PathIteratorTests.twin @@ -247,7 +247,7 @@ Module Br_PathIteratorTests End Sub [TestCase] - Public Sub Enumerate_EmptyPath_RaisesUnspecifiedError() + Public Sub Enumerate_EmptyPath_RaisesSubscriptOutOfRange() Expect.Begin Dim user As GdiPlusUser = GdiPlusUser() Dim it As PathIterator = PathIterator(New GraphicsPath()) @@ -263,8 +263,8 @@ Module Br_PathIteratorTests errDirect = Err.Number Err.Clear On Error GoTo 0 - Expect.AreEqual -2147467259, errUDT, "error raised by the UDT form of Enumerate on an empty path" - Expect.AreEqual -2147467259, errDirect, "error raised by the direct form of Enumerate on an empty path" + Expect.AreEqual 9, errUDT, "error raised by the UDT form of Enumerate on an empty path" + Expect.AreEqual 9, errDirect, "error raised by the direct form of Enumerate on an empty path" Expect.Done End Sub diff --git a/test/package-tests/GdiPlus/Sources/Br_PathTests.twin b/test/package-tests/GdiPlus/Sources/Br_PathTests.twin index 35bbbbbc8..a4e5dc55f 100644 --- a/test/package-tests/GdiPlus/Sources/Br_PathTests.twin +++ b/test/package-tests/GdiPlus/Sources/Br_PathTests.twin @@ -93,20 +93,20 @@ Module Br_PathTests End Sub [TestCase] - Public Sub GetPoints_EmptyPath_RaisesUnspecifiedError() + Public Sub GetPoints_EmptyPath_RaisesSubscriptOutOfRange() Expect.Begin Dim user As GdiPlusUser = GdiPlusUser() Dim p As GraphicsPath = New GraphicsPath() - Expect.AreEqual -2147467259, CaughtError(p, "points"), "error raised by GetPoints on an empty path" + Expect.AreEqual 9, CaughtError(p, "points"), "error raised by GetPoints on an empty path" Expect.Done End Sub [TestCase] - Public Sub GetTypes_EmptyPath_RaisesUnspecifiedError() + Public Sub GetTypes_EmptyPath_RaisesSubscriptOutOfRange() Expect.Begin Dim user As GdiPlusUser = GdiPlusUser() Dim p As GraphicsPath = New GraphicsPath() - Expect.AreEqual -2147467259, CaughtError(p, "types"), "error raised by GetTypes on an empty path" + Expect.AreEqual 9, CaughtError(p, "types"), "error raised by GetTypes on an empty path" Expect.Done End Sub diff --git a/test/package-tests/GdiPlus/Sources/RunAll.twin b/test/package-tests/GdiPlus/Sources/RunAll.twin index 6b83c34c8..5e626a84e 100644 --- a/test/package-tests/GdiPlus/Sources/RunAll.twin +++ b/test/package-tests/GdiPlus/Sources/RunAll.twin @@ -417,8 +417,8 @@ Module RunAll Expect.EndCase "Br_PathIteratorTests.Enumerate_OnlyOneIndex_IsRefused", Err.Number, Err.Description Expect.StartCase Err.Clear - Br_PathIteratorTests.Enumerate_EmptyPath_RaisesUnspecifiedError - Expect.EndCase "Br_PathIteratorTests.Enumerate_EmptyPath_RaisesUnspecifiedError", Err.Number, Err.Description + Br_PathIteratorTests.Enumerate_EmptyPath_RaisesSubscriptOutOfRange + Expect.EndCase "Br_PathIteratorTests.Enumerate_EmptyPath_RaisesSubscriptOutOfRange", Err.Number, Err.Description Expect.StartCase Err.Clear Br_PathIteratorTests.CopyData_DoesWhatEnumerateDoes @@ -449,12 +449,12 @@ Module RunAll Expect.EndCase "Br_PathTests.PathData_Get_ReturnsThePoints", Err.Number, Err.Description Expect.StartCase Err.Clear - Br_PathTests.GetPoints_EmptyPath_RaisesUnspecifiedError - Expect.EndCase "Br_PathTests.GetPoints_EmptyPath_RaisesUnspecifiedError", Err.Number, Err.Description + Br_PathTests.GetPoints_EmptyPath_RaisesSubscriptOutOfRange + Expect.EndCase "Br_PathTests.GetPoints_EmptyPath_RaisesSubscriptOutOfRange", Err.Number, Err.Description Expect.StartCase Err.Clear - Br_PathTests.GetTypes_EmptyPath_RaisesUnspecifiedError - Expect.EndCase "Br_PathTests.GetTypes_EmptyPath_RaisesUnspecifiedError", Err.Number, Err.Description + Br_PathTests.GetTypes_EmptyPath_RaisesSubscriptOutOfRange + Expect.EndCase "Br_PathTests.GetTypes_EmptyPath_RaisesSubscriptOutOfRange", Err.Number, Err.Description Expect.StartCase Err.Clear Br_PathTests.Types_OfTwoFiguresWithAClosedOneAndAMarker diff --git a/test/package-tests/TBMANLIB/Sources/ArrayTests.twin b/test/package-tests/TBMANLIB/Sources/ArrayTests.twin index cb125d17b..395da9102 100644 --- a/test/package-tests/TBMANLIB/Sources/ArrayTests.twin +++ b/test/package-tests/TBMANLIB/Sources/ArrayTests.twin @@ -180,13 +180,13 @@ Module ArrayTests End Sub [TestCase] - Public Sub SplitByteArray_PositionZero_RaisesUnspecifiedError() + Public Sub SplitByteArray_PositionZero_RaisesSubscriptOutOfRange() Expect.Begin Dim src() As Byte, parts As Variant src = Bytes("one,two") On Error Resume Next parts = ToolsArray.SplitByteArray(src, 0) - Expect.AreEqual -2147467259, Err.Number, "SplitByteArray with position 0 raises" + Expect.AreEqual 9, Err.Number, "SplitByteArray with position 0 raises" On Error GoTo 0 Expect.Done End Sub diff --git a/test/package-tests/TBMANLIB/Sources/RunAll.twin b/test/package-tests/TBMANLIB/Sources/RunAll.twin index 62560b1f2..eb1c1f50f 100644 --- a/test/package-tests/TBMANLIB/Sources/RunAll.twin +++ b/test/package-tests/TBMANLIB/Sources/RunAll.twin @@ -53,8 +53,8 @@ Module RunAll Expect.EndCase "ArrayTests.SplitByteArray_SplitsAtAPositionWithAnOffset", Err.Number, Err.Description Expect.StartCase Err.Clear - ArrayTests.SplitByteArray_PositionZero_RaisesUnspecifiedError - Expect.EndCase "ArrayTests.SplitByteArray_PositionZero_RaisesUnspecifiedError", Err.Number, Err.Description + ArrayTests.SplitByteArray_PositionZero_RaisesSubscriptOutOfRange + Expect.EndCase "ArrayTests.SplitByteArray_PositionZero_RaisesSubscriptOutOfRange", Err.Number, Err.Description Expect.StartCase Err.Clear ArrayTests.SplitByteArrayByKeyword_SplitsAtEachKeyword diff --git a/test/package-tests/TBMANLIB/expect.json b/test/package-tests/TBMANLIB/expect.json index d8f0f0011..19a77e2a0 100644 --- a/test/package-tests/TBMANLIB/expect.json +++ b/test/package-tests/TBMANLIB/expect.json @@ -11,7 +11,7 @@ "fails": "2 of 3 checks failed: first variable after the assignment to Extend: expected \"one\", got \"A\"; second variable after the assignment to Extend: expected \"two\", got \"C\"" }, "ArrayTests.SplitByteArrayByKeyword_KeywordAtStart_GivesAnEmptyFirstPart": { - "fails": "1 of 1 checks failed: split of ,a at a comma raised error -2147467259 Unspecified error: expected 0, got -2147467259" + "fails": "1 of 1 checks failed: split of ,a at a comma raised error 9 Subscript out of range: expected 0, got 9" }, "ArrayTests.SplitByteArrayByKeyword_KeywordAtEnd_GivesAnEmptyLastPart": { "fails": "1 of 3 checks failed: the last part of a, split at a comma has no elements: expected True, got False" diff --git a/test/package-tests/TBMANLIB_ConsoleComponents/Sources/LayoutTests.twin b/test/package-tests/TBMANLIB_ConsoleComponents/Sources/LayoutTests.twin index d5721166f..5c8d87c57 100644 --- a/test/package-tests/TBMANLIB_ConsoleComponents/Sources/LayoutTests.twin +++ b/test/package-tests/TBMANLIB_ConsoleComponents/Sources/LayoutTests.twin @@ -333,10 +333,10 @@ Module LayoutTests Dim layout As cConsoleLayout = New cConsoleLayout(con) On Error Resume Next layout.CreateGrid 0, 2, "c" - Expect.AreEqual -2147467259, Err.Number, "Err.Number of CreateGrid with 0 columns" + Expect.AreEqual 9, Err.Number, "Err.Number of CreateGrid with 0 columns" Err.Clear layout.CreateGrid 2, 0, "c" - Expect.AreEqual -2147467259, Err.Number, "Err.Number of CreateGrid with 0 rows" + Expect.AreEqual 9, Err.Number, "Err.Number of CreateGrid with 0 rows" On Error GoTo 0 Set layout = Nothing Set con = Nothing diff --git a/test/package-tests/xCalendar/expect.json b/test/package-tests/xCalendar/expect.json index cf49bace3..b06949160 100644 --- a/test/package-tests/xCalendar/expect.json +++ b/test/package-tests/xCalendar/expect.json @@ -1,30 +1,9 @@ { "version": "1.0.17.0", "cases": { - "ColourTests.TitleFontColor_ColoursTheTitle": { - "fails": "1 of 1 checks failed: the pixels of the colour given to TitleFontColor: expected True, got False" - }, - "ColourTests.TitleFontColor_DefaultColoursTheTitle": { - "fails": "1 of 1 checks failed: the pixels of the default TitleFontColor, orange: expected True, got False" - }, - "ColourTests.DayFontColor_ColoursTheDayNames": { - "fails": "1 of 1 checks failed: the pixels of the colour given to DayFontColor: expected True, got False" - }, - "ColourTests.GridFontColor_ColoursTheDayNumbers": { - "fails": "1 of 1 checks failed: the pixels of the colour given to GridFontColor: expected True, got False" - }, - "ColourTests.GridFont_DaysOfTheMonthsBeforeAndAfterAreGrey": { - "fails": "1 of 1 checks failed: the pixels of grey (&HAAAAAA) for the days of September and November in the grid for October 2026: expected True, got False" - }, "ColourTests.GridLinesColor_Flat_DefaultIsDrawnAsTheSystemColour": { "fails": "1 of 2 checks failed: the colour of a vertical line with the default GridLinesColor and GridCellEffect Flat: expected 10526880, got 16" }, - "EnabledTests.Enabled_False_DrawsTheTitleInGrey": { - "fails": "1 of 1 checks failed: the pixels of the grey (&HBCBCBC) of a disabled title: expected True, got False" - }, - "EnabledTests.Enabled_False_DrawsTheDayNamesAndNumbersInGrey": { - "fails": "1 of 1 checks failed: the pixels of the grey (&H999999) of disabled day names and day numbers: expected True, got False" - }, "GridTests.Value_InAnotherMonth_PlacesTheGrid": { "fails": "1 of 1 checks failed: the date of row 2, column 6 after Value is set to March 15, 2026: expected 3/15/2026, got 10/19/2025" }, @@ -55,18 +34,6 @@ "LayoutTests.Title_ThatIsWiderThanTheSpaceBesideTheLists_IsNotCutOff": { "fails": "1 of 1 checks failed: the title \"September 2026\" in the default font reaches the left edge of the control: expected False, got True" }, - "MethodTests.NextMonth_FromJanuary31_SelectsTheLastDayOfFebruary": { - "fails": "1 of 1 checks failed: Value after NextMonth with January 31, 2026 selected: expected 2/28/2026, got 3/3/2026" - }, - "MethodTests.PreviousMonth_FromMarch31_SelectsTheLastDayOfFebruary": { - "fails": "1 of 1 checks failed: Value after PreviousMonth with March 31, 2026 selected: expected 2/28/2026, got 3/3/2026" - }, - "MethodTests.NextYear_FromFebruary29_SelectsFebruary28": { - "fails": "1 of 1 checks failed: Value after NextYear with February 29, 2024 selected: expected 2/28/2025, got 3/1/2025" - }, - "MethodTests.PreviousYear_FromFebruary29_SelectsFebruary28": { - "fails": "1 of 1 checks failed: Value after PreviousYear with February 29, 2024 selected: expected 2/28/2023, got 3/1/2023" - }, "MouseTests.MouseDown_IsRaisedByAPress": { "fails": "2 of 2 checks failed: the MouseDown events raised by a left press on a day: expected 1, got 0; the MouseDown events raised by a right press on the title area: expected 1, got 0" }, From e04f8d4f1e14d211c4eceafefb15150cb52019af Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Tue, 3 Nov 2026 18:46:55 +0100 Subject: [PATCH 06/19] Compiler error codes in the search index: 36 TB5xxx codes and TB65535 marked on the section that explains each (index, with index_also for strong second pages), so a search for the code lands there; Attributes says which errors a misplaced attribute gives (TB5155, TB5182) and names TB5114 for a second RunAfterBuild --- docs/Features/Advanced/API-Declarations.md | 2 ++ docs/Features/Advanced/Classes-and-Modules.md | 3 +++ docs/Features/Advanced/Static-Linking.md | 2 +- .../Compiler-IDE/Compiler-Warnings.md | 2 +- docs/Features/Language/Generics.md | 2 ++ docs/Features/Language/Inheritance.md | 2 ++ docs/IDE/Code/Diagnostics.md | 1 + docs/Reference/Attributes.md | 23 ++++++++++--------- docs/Reference/COM-Interfaces/IErrorInfo.md | 1 + docs/Reference/Core/Const.md | 1 + docs/Reference/Core/Delegate.md | 1 + docs/Reference/Core/Graphics-Methods.md | 1 + docs/Reference/Core/LSet.md | 1 + docs/Reference/Core/New.md | 1 + docs/Reference/Data-Types.md | 2 +- docs/Reference/Data-Types/Date.md | 1 + docs/Reference/Data-Types/LongLong.md | 1 + docs/Reference/Default/VB/Report/index.md | 1 + .../Default/VBA/Compilation/UnrollLoop.md | 1 + docs/Reference/Default/VBA/Math/ByteSwap.md | 1 + .../Default/VBA/Math/ShiftRotateLeft.md | 1 + .../Default/VBA/Math/ShiftRotateRight.md | 1 + .../Default/VBA/Math/ShiftUnsignedRight.md | 1 + 23 files changed, 39 insertions(+), 14 deletions(-) diff --git a/docs/Features/Advanced/API-Declarations.md b/docs/Features/Advanced/API-Declarations.md index f11bb1a0d..ce6ffb21f 100644 --- a/docs/Features/Advanced/API-Declarations.md +++ b/docs/Features/Advanced/API-Declarations.md @@ -24,6 +24,7 @@ Both represent a fully Unicode operation, but the allows direct use of the `Stri > This does **not** change the underlying data types-- the `String` type is a `BSTR`, not an `LPWSTR`, so in the event an API returns a pre-allocated `LPWSTR`, rather than filling a buffer you have created, it will not provide a valid `String` type. This would be the case where an API parameter is given as `[out] LPWSTR *arg`. ## Calling Conventions +{: index="TB5258" } An API declaration, a procedure, a delegate or an interface member can name its calling convention. The keyword goes directly after the name. Without one, the convention is stdcall, as in VBA. @@ -64,6 +65,7 @@ End Function ``` ### ThisCall in interfaces +{: index="TB5004" } Some C++ interfaces are not COM interfaces, and their methods use thiscall rather than stdcall. RichEdit's `ITextServices` and `ITextHost` are examples. Mark each such member **ThisCall** in the **Interface** definition: diff --git a/docs/Features/Advanced/Classes-and-Modules.md b/docs/Features/Advanced/Classes-and-Modules.md index f371e6ef1..56fc6c60c 100644 --- a/docs/Features/Advanced/Classes-and-Modules.md +++ b/docs/Features/Advanced/Classes-and-Modules.md @@ -10,6 +10,7 @@ permalink: /Features/Advanced/Classes-and-Modules twinBASIC provides several enhancements for classes and modules. ## Parameterized Class Constructors +{: index="TB5073" index_also="TB5121" } A class can have a `Sub New` constructor, and the constructor can take arguments. The compiler treats the `Class_Initialize` event as another constructor, one that takes no arguments. [`New`](../../tB/Core/New) runs only the constructor that its arguments match --- it does not run `Sub New` and `Class_Initialize` one after the other: @@ -22,6 +23,7 @@ A class can have a `Sub New` constructor, and the constructor can take arguments > A `Sub New` declared `Private` is meant to make construction private, so that only code inside the class creates objects with it. This is not enforced: `New MyClass` in another module of the project, or in a project that references a package with the class, compiles and runs the private constructor. ### Example +{: index="TB5135" } For example a class can have: @@ -41,6 +43,7 @@ then created by `Dim mc As MyClass = New MyClass(123)`, which sets `MyClassVar` > A class that is not `Private` is exposed to COM, and COM creates objects without arguments. So a class whose `Sub New` takes arguments must be `Private`, have the `[COMCreatable(False)]` attribute, or also have a constructor that takes no arguments --- a `Class_Initialize()`, or a second `Sub New` with no parameters. Otherwise it fails to compile with TB5135. `Class_Initialize()` replaces `New` in callers of a compiled OCX. ### `Class_Initialize` or `Sub New`? +{: index="TB5053" } Both are constructors: code that runs once, as the object is created, before anything else can use it. Only one of them runs for any one object. `Class_Initialize` is inherited from VB6 and VBA, and follows COM's rule that an object is created without arguments; `Sub New` is twinBASIC's addition. They differ in what they can take and who can use them: diff --git a/docs/Features/Advanced/Static-Linking.md b/docs/Features/Advanced/Static-Linking.md index e667c581f..3a40e5f1d 100644 --- a/docs/Features/Advanced/Static-Linking.md +++ b/docs/Features/Advanced/Static-Linking.md @@ -22,7 +22,7 @@ A program uses a library in three steps: A package can carry the files and the statements, so that a project that references it needs neither; the [DetoursLib](../../tB/Packages/Contributed/DetoursLib/) package links SlimDetours this way. ## Syntax -{: index="Import Library" } +{: index="Import Library; TB5137" } **Import Library** "*path*" **As** *namespace* [ **Link** "*dependency*" [ **,** "*dependency*" ] ... ] diff --git a/docs/Features/Compiler-IDE/Compiler-Warnings.md b/docs/Features/Compiler-IDE/Compiler-Warnings.md index c3a7b7742..80e82b5eb 100644 --- a/docs/Features/Compiler-IDE/Compiler-Warnings.md +++ b/docs/Features/Compiler-IDE/Compiler-Warnings.md @@ -429,7 +429,7 @@ Debug.Print i ``` ### TB0033: Duplicate Definition -{: index="TB0033" } +{: index="TB0033; TB5134" } Reports a duplicate definition as a warning: *Duplicate definition, consider changing the name to be unique*. A name declared twice in one module, procedure, **Enum** or **Type** is error TB5134 instead. diff --git a/docs/Features/Language/Generics.md b/docs/Features/Language/Generics.md index 13eef8c0d..001337b0a 100644 --- a/docs/Features/Language/Generics.md +++ b/docs/Features/Language/Generics.md @@ -3,6 +3,7 @@ title: Generics parent: Language Syntax nav_order: 5 permalink: /Features/Language/Generics +index: TB5124 --- # Generics @@ -164,6 +165,7 @@ The function **Caster** introduces three type variables within its scope: * Finally, also in the body, the **intermediate** is cast to the result type of **String**, and returned. ### Operators in a generic body +{: index="TB5092" } The body of a generic procedure is compiled once for each type it is used with, as if it had been written out for that type. So the body can use an operator that works on every type it is called with: diff --git a/docs/Features/Language/Inheritance.md b/docs/Features/Language/Inheritance.md index dac1bedac..06e0691a1 100644 --- a/docs/Features/Language/Inheritance.md +++ b/docs/Features/Language/Inheritance.md @@ -58,6 +58,7 @@ You can see that the Honk method is only implemented by the parent class, then c This option supports full inheritance and OOP: `Protected` methods and variables accessible to derived classes (but not outside callers), `Overridable` and `Overrides` syntax, multiple inheritance, and explicit base class constructors. ### Example: Animal Class Hierarchy +{: index="TB5030; TB5068" index_also="TB5121; TB5135" } > [!IMPORTANT] > Every class in this example is declared `Private Class`, because each one has a `Sub New` that takes arguments. A class that is not `Private` is exposed to COM, and COM creates objects without arguments. So a public class --- base or derived --- whose `Sub New` has required arguments fails to compile with TB5135, unless it also has a constructor that takes none. There are three fixes: declare the class `Private`, add the `[COMCreatable(False)]` attribute, or add a constructor without arguments, such as `Class_Initialize`. Only the third lets the class be declared `As New`; without it, `As New` fails with TB5121. See [Parameterized Class Constructors](../Advanced/Classes-and-Modules#parameterized-class-constructors). @@ -205,6 +206,7 @@ Misty says: meow The classes come from Sample 23, which also has an `AnimalWatcher` class that handles the `Spoke` event, and a longer demonstration routine. To open it, choose **File → New Project**, then the **Samples** section, and pick the card **OOP Inheritance Example (Animals)**, the last one. The [New Project](../../tB/IDE/Project/New#samples) page lists every sample. ## Constructors in Derived Classes +{: index="TB5103" index_also="TB5073" } A class has two kinds of constructor: `Class_Initialize`, which takes no arguments, and `Sub New`, which can take arguments. [`Class_Initialize` or `Sub New`?](../Advanced/Classes-and-Modules#class_initialize-or-sub-new) compares them. When a derived class is created, the base class's constructor runs as well as the derived class's own, and it runs first. Which of the base's constructors runs depends on whether the derived class's constructor calls one: diff --git a/docs/IDE/Code/Diagnostics.md b/docs/IDE/Code/Diagnostics.md index 796999262..58173875b 100644 --- a/docs/IDE/Code/Diagnostics.md +++ b/docs/IDE/Code/Diagnostics.md @@ -33,6 +33,7 @@ Each counter is also a switch. Click it to show or hide the problems of that kin The commands `tbProblems_ToggleShowErrors`, `tbProblems_ToggleShowWarnings`, `tbProblems_ToggleShowHints` and `tbProblems_ToggleShowInfos` flip the same switches. They have no keys by default. ## The list +{: index="TB5000" } The list has one group for each file that has a problem to show. The group's header is the file's name, and pointing at it shows the path. Click the arrow at the left of the header, or double-click the header, to collapse or expand the group. diff --git a/docs/Reference/Attributes.md b/docs/Reference/Attributes.md index 0d3cc567f..a5fc68872 100644 --- a/docs/Reference/Attributes.md +++ b/docs/Reference/Attributes.md @@ -3,6 +3,7 @@ title: Attributes parent: Reference Section nav_order: 6 permalink: /tB/Core/Attributes +index: TB5155 --- # Attributes @@ -23,13 +24,13 @@ Multiple attributes can be specified in the same square braces, separated by com --- -The available attributes are listed below in alphabetic order. Not every attribute applies to every language element. The applicability of each attribute is given below its syntax. +The available attributes are listed below in alphabetic order. Not every attribute applies to every language element. The applicability of each attribute is given below its syntax. An attribute written where it does not apply is a compile error, depending on the place either TB5155, *This attribute is not supported in this context*, or TB5182, *Syntax error. No handler for this symbol*. * TOC {:toc} --- ## AllowUnpopulatedVtableEntry -{: #allowunpopulatedvtableentry } +{: #allowunpopulatedvtableentry index="TB65535" } Syntax: **[AllowUnpopulatedVtableEntry]** @@ -85,7 +86,7 @@ Applicable to: [**Class**](Class), [**Module**](Module), [procedure](../Gloss#pr Disables or enables array element access bounds checking within the scope of a class, module, or a single procedure/method. Used on performance-critical routines. **ArrayBoundsChecks(False)** turns the check off. **ArrayBoundsChecks(True)** turns it back on where the project setting [**Disable Array Bounds Checks**](../IDE/Project-Settings#disable-array-bounds-checks) has turned it off for the whole project. ## BindOnlyIfNoArguments (optional Bool) -{: #bindonlyifnoarguments } +{: #bindonlyifnoarguments index_also="TB5030" } Syntax: **[BindOnlyIfNoArguments** [ **( True** \| **False )** ] **]** @@ -191,7 +192,7 @@ On a **Class**, it marks the class as an ActiveX control (the `control` flag of library flag changes. --> ## COMCreatable (optional Bool) -{: #comcreatable } +{: #comcreatable index_also="TB5121; TB5135" } Syntax: **[COMCreatable** [ **( True** \| **False )** ] **]** @@ -374,7 +375,7 @@ When false, turns off breakpoints and stepping for the method, class or module. Interface it is accepted and a Stop in an implementing class still breaks. --> ## DebugOnly (optional Bool) -{: #debugonly } +{: #debugonly index="TB5162" } Syntax: **[DebugOnly** [ **( True** \| **False )** ] **]** @@ -543,7 +544,7 @@ Indicates that the interface exposes methods via **IDispatch** late-binding. Thi It is usually written combined with the other attributes on the interface rather than alone --- `[Hidden, DispInterface, COMExtensible]`. See [**LibraryId**](#libraryid) for what a generated **Library** module looks like. ## DllExport (optional String) -{: #dllexport } +{: #dllexport index="TB5163" } Syntax: **[DllExport** [ **("** export name **")** ] **]** @@ -597,7 +598,7 @@ Indicates that the interface exposes methods through the OLE VTable binding. The See [**LibraryId**](#libraryid) for what a generated **Library** module looks like. ## EnforceErrors (Warning code list) -{: #enforceerrors } +{: #enforceerrors index="TB5169" } Syntax: **[EnforceErrors** **(** **TBnnnn** [ **,** **TBmmmm** ]... **)** **]** @@ -606,7 +607,7 @@ Applicable to: [**Class**](Class), [**Interface**](Interface), [**Module**](Modu Reports the named warnings as errors within the class, interface, module or procedure the attribute is applied to. The codes are written bare, as for [IgnoreWarnings](#ignorewarnings). With `[EnforceErrors(TB0009)]` on a procedure, a call to a procedure marked [Unimplemented](#unimplemented) is an error where it would otherwise be warning TB0009. Written bare it is accepted and has no effect. With **True** or **False** in place of a code it is error TB5169, *Expected an error code in the format of TBxxxx*. ## EnforceWarnings (Warning code list) -{: #enforcewarnings } +{: #enforcewarnings index_also="TB5169" } Syntax: **[EnforceWarnings** **(** **TBnnnn** [ **,** **TBmmmm** ]... **)** **]** @@ -1090,7 +1091,7 @@ Public Declare PtrSafe Function SHGetDesktopFolder Lib "shell32" () As IShellFol ``` ## PropertyPage (String) -{: #propertypage } +{: #propertypage index="TB5259" index_also="TB5163" } Syntax: **[PropertyPage("** page name **")]** @@ -1202,7 +1203,7 @@ This is attribute has the same function as the [**restricted** MIDL attribute][M [MIDL restricted]: https://learn.microsoft.com/en-us/windows/win32/midl/restricted ## RunAfterBuild (optional Bool) -{: #runafterbuild } +{: #runafterbuild index="TB5114" index_also="TB5155" } Syntax: **[RunAfterBuild** [ **( True** \| **False )** ] **]** @@ -1225,7 +1226,7 @@ Public Sub Signed(Of T)() End Sub ``` -Only one **[RunAfterBuild]** is allowed per project. A second one is a compile error. A **[RunAfterBuild]** procedure in a package that the project references does not run, and does not count as the project's one. +Only one **[RunAfterBuild]** is allowed per project. A second one is compile error TB5114, *encountered too many [RunAfterBuild] attributes. Only allowed one per-project.* A **[RunAfterBuild]** procedure in a package that the project references does not run, and does not count as the project's one. ` marker on the line above it, which `check_package_tests.mjs` in `test.bat` enforces. **A NOTE or WARNING is only for a real difference or limit** (owner): a statement that describes how the package works stays prose, tied to its cases by a region, the marker on a line of its own above it and `` after it (owner). A difference from the object the package replaces that gives a different result silently (tbRegExp's `\1(a)`, `{,m}`, `$~` against VBScript) is a NOTE, not a WARNING (owner). A case checks what is correct, not what the package does (owner): a defect is a failing case, recorded in the project's `expect.json`, so the project tells the package's author what to fix. A section whose project is not written yet is in that gate's `PENDING` set. **A package that does not compile on the newest BETA gets no test project** (owner: ArrayListLib, WinReg): it stays in `PENDING` until a version of it compiles. One that compiles for one architecture only is tested on that one (owner: TBMANLIB, win32; `arches` in its `expect.json`, test/package-tests/README.md). `package-tests.bat` runs the cases; a case it reports *fixed* means the callout goes. **The landing page ends, after See Also, with `## Test cases`**: one or two sentences linking to `-tests.twinproj` (owner), which the build publishes beside the section without the package's code in it (owner; one exception, owner: TBMANLIB_ConsoleComponents' project carries verbatim copies of seven of its component files, in `Sources/ComponentSources/`, because the package's own components cannot be given a console object, and its drawing can be checked only on the copies): it references the package as a linked package, which the reader puts in `%APPDATA%\twinBASIC\packages` ([WIP.PackageTests.md](WIP.PackageTests.md)). Its samples compile in the `contributed-` sample template, whose `Settings` names the package's version: **change that version with `indexed_from`** ([WIP.ExamplesBuild.md](WIP.ExamplesBuild.md#template-projects)). **A contributed package is documented and its samples compiled against the newest twinBASIC BETA installed, never an older one** (owner): the version that matters is the package's, and a statement about the compiler (such as "does not compile on BETA 1010") names that newest build. **A contributed package that does not compile on that build, or has defects that a program would meet, is listed under Experimental Packages** (owner); a minor defect that the section documents does not make it Experimental (owner: VBComDlg, whose Replace All raises no event, stays Contributed; owner: nor does a fixed limit that the section states at the top of its landing page; owner: tsJson 1.0.15.0 stays Contributed, its defects confirmed by a second agent and each stated in a WARNING with a way round it). **A package moves from Contributed to Experimental for its defects only after a separate agent has reproduced each defect independently** and ruled out misuse, a wrong expectation, a twinBASIC defect and the harness (owner): `docs/Reference/Experimental.md`, at `/tB/Packages/Experimental/`, after Contributed Packages. Its landing page's `parent:` is `Experimental Packages`, its line is in `Experimental.md` and not in `Contributed.md`, and it has no chapter in the PDF book (owner: the book is for packages ready for use in a program): its entry is in `left_out:` of `docs/_book.yml`, in the list of Experimental packages, and the Experimental Packages page is left out with it. Its folder and URLs stay under `Contributed/`, so a package that is fixed moves back by changing only those three and the book (its entry leaves `left_out:` and its chapter is added after VBComDlg's), and its pages keep their addresses. A link from a book page to a left-out one opens the website. +- `docs/Reference/Contributed/` — the contributed packages, one folder each, landing page `Contributed.md` beside it. **A package's landing page opens, under its H1, with `**Author:**`** (owner): the publisher TWINSERV lists (`publisher` in `indexer/.packages/manifests/contributed.json`), or the author the package's own sources name when that is someone else (VBA_JSON: Tim Hall, published by WaynePhillipsEA). Next to the author, on the same line, `**Version:**`: the version the section documents, which is its `indexed_from` and changes with it (owner). Then `**On GitHub:**` with a link, when a repository holds the package, or the library the package says it is made from; search GitHub for the package's name and the author's account, and check that the repository holds the package's code (its history too: `TBMANLIB_*` were never in woeoio's `tbman`). A landing page with one or two sections has no `* TOC` list. A class with member pages is a folder, `/index.md` at `/tB/Packages/Contributed///` with its members beside it, so a member links to its class as `(.)`; a class documented on one page is `.md`. Each package that is not Experimental is a chapter of the book's Packages part in `_book.yml`. A section's `indexed_from` is the four-part TWINSERV version, and its snapshot is under `indexer/snapshots/contributed/`. **Every NOTE and WARNING of a section is backed by test cases** in the package's test project, `test/package-tests//` (owner; [its README](test/package-tests/README.md)), and names them in a `` marker on the line above it, which `check_package_tests.mjs` in `test.bat` enforces. **A NOTE or WARNING is only for a real difference or limit** (owner): a statement that describes how the package works stays prose, tied to its cases by a region, the marker on a line of its own above it and `` after it (owner). A difference from the object the package replaces that gives a different result silently (tbRegExp's `\1(a)`, `{,m}`, `$~` against VBScript) is a NOTE, not a WARNING (owner). A case checks what is correct, not what the package does (owner): a defect is a failing case, recorded in the project's `expect.json`, so the project tells the package's author what to fix. A section whose project is not written yet is in that gate's `PENDING` set. **A package that does not compile on the newest BETA gets no test project** (owner: ArrayListLib, WinReg): it stays in `PENDING` until a version of it compiles. One that compiles for one architecture only is tested on that one (owner: TBMANLIB, win32; `arches` in its `expect.json`, test/package-tests/README.md). `package-tests.bat` runs the cases; a case it reports *fixed* means the callout goes. **The landing page ends, after See Also, with `## Test cases`**: one or two sentences linking to `-tests.twinproj` (owner), which the build publishes beside the section without the package's code in it (owner; one exception, owner: TBMANLIB_ConsoleComponents' project carries verbatim copies of seven of its component files, in `Sources/ComponentSources/`, because the package's own components cannot be given a console object, and its drawing can be checked only on the copies): it references the package as a linked package, which the reader puts in `%APPDATA%\twinBASIC\packages` ([WIP.PackageTests.md](WIP.PackageTests.md)). Its samples compile in the `contributed-` sample template, whose `Settings` names the package's version: **change that version with `indexed_from`** ([WIP.ExamplesBuild.md](WIP.ExamplesBuild.md#template-projects)). **A contributed package is documented and its samples compiled against the newest twinBASIC BETA installed, never an older one** (owner): the version that matters is the package's, and a statement about the compiler (such as "does not compile on BETA 1010") names that newest build. **A contributed package that does not compile on that build, or has defects that a program would meet, is listed under Experimental Packages** (owner); a minor defect that the section documents does not make it Experimental (owner: VBComDlg, whose Replace All raises no event, stays Contributed; owner: nor does a fixed limit that the section states at the top of its landing page; owner: tsJson 1.0.15.0 stays Contributed, its defects confirmed by a second agent and each stated in a WARNING with a way round it; owner: xCalendar 1.0.17.0 moved back to Contributed once its text colours worked on BETA 1034, its grid and edge-press defects stated in a WARNING). **A package moves from Contributed to Experimental for its defects only after a separate agent has reproduced each defect independently** and ruled out misuse, a wrong expectation, a twinBASIC defect and the harness (owner): `docs/Reference/Experimental.md`, at `/tB/Packages/Experimental/`, after Contributed Packages. Its landing page's `parent:` is `Experimental Packages`, its line is in `Experimental.md` and not in `Contributed.md`, and it has no chapter in the PDF book (owner: the book is for packages ready for use in a program): its entry is in `left_out:` of `docs/_book.yml`, in the list of Experimental packages, and the Experimental Packages page is left out with it. Its folder and URLs stay under `Contributed/`, so a package that is fixed moves back by changing only those three and the book (its entry leaves `left_out:` and its chapter is added after VBComDlg's), and its pages keep their addresses. A link from a book page to a left-out one opens the website. - `docs/Downloads/index.md` — the Downloads page, `/Downloads`, a top-level page at `nav_order: 4` and in `_book.yml`'s `left_out:`. **Every file the documentation publishes for readers has a card or a row there**, and the page that documents it keeps its own link (owner). Cards are `
` inside `
` (styles at the end of `custom.scss`), with the theme's `.btn` for each file. The release job publishes every same-site link on the built page that has the `download` attribute, beside the zip and the PDF, so a new download needs no change to the workflow. `{{tbdocs:size:}}` gives a published file's size and `{{tbdocs:packageTestRows}}` the test projects' rows, both expanded before render; the help add-in's size is prose ("about 61 MB" since the pre-built search index, October 2026; it is also on the Help Add-In page, with the archive's "about 60 MB"), because its project embeds the offline tree, this page included, so it cannot be known before render. The PDF's release URL spells it `twinBASIC.Book.pdf`: GitHub turns the space into a dot, and the spaced name 404s. The footer's **Read Offline** link (`read_offline_link` in `_config.yml`) goes to the page's `#read-offline`. - `docs/Reference/Data-Types.md` and `docs/Reference/Data-Types/.md` — the intrinsic data types (owner, 2026-10-18): one page per type at `/tB/Core/` (`parent: Data Types`, `grand_parent: Reference Section`, since the Features page *New Data Types* has the same title), and the summary page, `/Reference/Data-Types`, with one `## ` section per type that links to it. The summary's `symbols:` names the types, and the symbol index points each at its section (kind `type`, the one URL outside `/tB/`), so keep those headings' text. `Long.md` is the model page. - `docs/Reference/Statements.md` — alphabetical index of language statements. diff --git a/docs/Reference/Contributed.md b/docs/Reference/Contributed.md index d284dc2c0..675a8d6c3 100644 --- a/docs/Reference/Contributed.md +++ b/docs/Reference/Contributed.md @@ -27,4 +27,5 @@ Each package is versioned apart from twinBASIC, and its section says which versi - [**tsPwGen**](tsPwGen/) -- passwords, PINs and passphrases, with their entropy known before they are generated - [**TwinTimerLib**](TwinTimerLib/) -- a Win32 timer class that reports the elapsed time - [**VBA_JSON**](VBA_JSON/) -- JSON parsing and serialization, with UTC and ISO 8601 date conversion -- [**VBComDlg**](VBComDlg/) -- the **CommonDialog** class, for the Windows common dialogs \ No newline at end of file +- [**VBComDlg**](VBComDlg/) -- the **CommonDialog** class, for the Windows common dialogs +- [**xCalendar**](xCalendar/) -- a calendar control, in place of the Calendar control of `MSCAL.OCX` \ No newline at end of file diff --git a/docs/Reference/Contributed/xCalendar/index.md b/docs/Reference/Contributed/xCalendar/index.md index 5613749a3..e60416b58 100644 --- a/docs/Reference/Contributed/xCalendar/index.md +++ b/docs/Reference/Contributed/xCalendar/index.md @@ -1,6 +1,6 @@ --- title: xCalendar Package -parent: Experimental Packages +parent: Contributed Packages has_toc: false indexed_from: 1.0.17.0 permalink: /tB/Packages/Contributed/xCalendar/ diff --git a/docs/Reference/Experimental.md b/docs/Reference/Experimental.md index 449334c73..6ec427cce 100644 --- a/docs/Reference/Experimental.md +++ b/docs/Reference/Experimental.md @@ -22,5 +22,4 @@ These packages are published through TWINSERV, as the [Contributed Packages](../ - [**TBMANLIB_Dialog**](../Contributed/TBMANLIB_Dialog/) -- the Open, Save and folder dialogs (**cDialog**, **cDialogEx**), the Color, Font, Find, Replace, Page Setup and Print dialogs, and message, input, password, date and progress dialogs. Version 1.1.0.0 compiles, and several of its classes fail at their main task: the Find and Replace dialogs report nothing to the program, Page Setup ignores the paper size and orientation, **cDialogEx** returns file names without their folder, the password dialog shows the password in its confirmation boxes, and the date dialog keeps input it refused. **cDialog** works. - [**TBMANLIB_ListBoxHScroll**](../Contributed/TBMANLIB_ListBoxHScroll/) -- a horizontal scroll bar for a **ListBox**. Version 1.0.0.1 compiles, and its scroll arrows and track move the thumb by a different step than the list's contents, so the thumb runs ahead of the text and the arrows cannot reach the end of a long line. - [**TBMANLIB_Xml**](../Contributed/TBMANLIB_Xml/) -- XML documents parsed, edited and written, in twinBASIC code. Version 1.0.0.1 compiles, and its parser misreads some common XML, such as self-closing elements and entities in text, and does not return for some malformed text. Its XPath engine reads some valid expressions as others, such as the step `..` and `and` with `or`. -- [**WinReg**](../Contributed/WinReg/) -- registry keys and values, read, written, deleted and enumerated. Version 1.0.1.1 does not compile in twinBASIC BETA 1034, and three of its methods give wrong results: **ReadString** returns the text followed by NUL characters, **GetRegistryValueType** returns **REG_NONE** for a value that holds data, and one overload of **ConvertValueToString** finds no value. -- [**xCalendar**](../Contributed/xCalendar/) -- a calendar control, in place of the Calendar control of `MSCAL.OCX`. Version 1.0.17.0 compiles, and has defects, among them a grid that keeps the dates of the old month when **Value** is set to another month, and presses near the right and bottom edges that select dates the grid does not show. +- [**WinReg**](../Contributed/WinReg/) -- registry keys and values, read, written, deleted and enumerated. Version 1.0.1.1 does not compile in twinBASIC BETA 1034, and three of its methods give wrong results: **ReadString** returns the text followed by NUL characters, **GetRegistryValueType** returns **REG_NONE** for a value that holds data, and one overload of **ConvertValueToString** finds no value. \ No newline at end of file diff --git a/docs/_book.yml b/docs/_book.yml index ab424c0ae..1565156a7 100644 --- a/docs/_book.yml +++ b/docs/_book.yml @@ -622,6 +622,13 @@ parts: landing_is_target: true outline_closed: true + - title: xCalendar Package + subtitle: A calendar control, in place of the Calendar control of MSCAL.OCX + landing_page: /tB/Packages/Contributed/xCalendar/ + page: /tB/Packages/Contributed/xCalendar/ + landing_is_target: true + outline_closed: true + - title: BigNumberLib Package subtitle: Arbitrary-precision signed integer arithmetic, up to 4096 bits landing_page: /tB/Packages/Contributed/BigNumberLib/ @@ -680,7 +687,6 @@ left_out: - /tB/Packages/Contributed/TBMANLIB_ListBoxHScroll/ - /tB/Packages/Contributed/TBMANLIB_Xml/ - /tB/Packages/Contributed/WinReg/ - - /tB/Packages/Contributed/xCalendar/ - reason: IDE pages that are still screenshots and the labels they show, or headings with nothing under them no_descent: true From 5d5c238390b11b9dd1e64a9ab0079cd68fdb6d76 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Tue, 3 Nov 2026 18:59:50 +0100 Subject: [PATCH 08/19] Diagnostics: a Common errors section for TB5001, TB5027, TB5079 and TB5182, with their messages on BETA 1034, as their search-index entry; BUGS entry tb5079-datatype-message-nested (a compile reproducer: since 1034 TB5079 for an unknown type nests its message in the one for a name) and its NOTE; WIP.Authoring states the TB code index rule --- BUGS-TO-REPORT.md | 41 ++++++++++++ WIP.Authoring.md | 11 ++++ .../tb5079-datatype-message-nested/repro.json | 10 +++ .../src/Settings | 61 ++++++++++++++++++ .../src/Sources/Startup.twin | 9 +++ .../tb5079-datatype-message-nested.twinproj | Bin 0 -> 2241 bytes docs/IDE/Code/Diagnostics.md | 21 ++++++ 7 files changed, 153 insertions(+) create mode 100644 bugs/tb5079-datatype-message-nested/repro.json create mode 100644 bugs/tb5079-datatype-message-nested/src/Settings create mode 100644 bugs/tb5079-datatype-message-nested/src/Sources/Startup.twin create mode 100644 bugs/tb5079-datatype-message-nested/tb5079-datatype-message-nested.twinproj diff --git a/BUGS-TO-REPORT.md b/BUGS-TO-REPORT.md index df92fd047..dc481eed3 100644 --- a/BUGS-TO-REPORT.md +++ b/BUGS-TO-REPORT.md @@ -11512,3 +11512,44 @@ Severity: medium. Code written to handle an error crashes the program instead, w +--- + +## TB5079 for an unknown type puts its whole message inside the message for an unknown name + +**Describe the bug** +Since BETA 1034, the error for a type name that does not exist reads *Unrecognized symbol 'Unrecognized datatype symbol 'NoSuchType''*. BETA 1033 reported *Unrecognized datatype symbol 'NoSuchType'*. The error for an unknown name in an expression is unchanged. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open `tb5079-datatype-message-nested.twinproj` (attached as `tb5079-datatype-message-nested.zip`). Its one source file, `Startup.twin`: + ``` + Module Startup + + Public Sub Main() + Dim t As NoSuchType + Dim n As Long + n = NoSuchName + End Sub + + End Module + ``` +2. See two errors in the DIAGNOSTICS panel: + ``` + TB5079 Unrecognized symbol 'Unrecognized datatype symbol 'NoSuchType'' + TB5079 Unrecognized symbol 'NoSuchName' + ``` + +**Expected behavior** +The first error reads `TB5079 Unrecognized datatype symbol 'NoSuchType'`, as in BETA 1033. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +`locale/en-gb.lang` has `TB5079: "Unrecognized symbol '{0}'"`. Since BETA 1034 the compiler takes the text of a message from the language pack by its code, and for an unknown type it fills `{0}` with the whole English message rather than the name. The two kinds of TB5079 need two strings in the pack, or the unknown-type message its own code. The messages are the same in a win32 and a win64 build. + +Severity: low; the message is longer and repeats itself, but still names the type. + + + diff --git a/WIP.Authoring.md b/WIP.Authoring.md index aa5bd8051..7fed8b2f9 100644 --- a/WIP.Authoring.md +++ b/WIP.Authoring.md @@ -144,6 +144,17 @@ summary line. That is the cue for a third. **Do not add `symbols:` to change how ranks or to add a keyword a page merely mentions**: the index is what the page documents, and a keyword such as `ElseIf` belongs on a page only when the page explains it. +### Compiler codes in the site search + +**Every `TBnnnn` code a page explains has a search-index entry** (owner): `index` on the +section that says best when the compiler reports it, `index_also` on a strong second page, as +`docs/Documentation/Authoring.md` (*Index entries for the site search*) describes. A warning's +entry is its own heading on Compiler Warnings. TB5001, TB5027, TB5079 and TB5182, which code +of every kind gives, have theirs on the Diagnostics page's *Common errors* section, and a code +that only a contributed package's page mentions gets none (owner). A new page that explains a +code takes its entry, or takes it over from a weaker page; after `build.bat`, +`node eval/site_search.mjs TBnnnn` shows where a search for it lands. + ### Cross-section linking Relative links resolve against the **rendered URL** (the page's `permalink:`), not the file path. Pages that share a URL folder can use bare names (`[Y](Y)`); crossing folders needs `../` to climb out. diff --git a/bugs/tb5079-datatype-message-nested/repro.json b/bugs/tb5079-datatype-message-nested/repro.json new file mode 100644 index 000000000..1f7c8e631 --- /dev/null +++ b/bugs/tb5079-datatype-message-nested/repro.json @@ -0,0 +1,10 @@ +{ + "mode": "compile", + "expect": { + "exit": 1, + "output": [ + "TB5079 Unrecognized symbol 'Unrecognized datatype symbol 'NoSuchType''", + "TB5079 Unrecognized symbol 'NoSuchName'" + ] + } +} diff --git a/bugs/tb5079-datatype-message-nested/src/Settings b/bugs/tb5079-datatype-message-nested/src/Settings new file mode 100644 index 000000000..4bca96c93 --- /dev/null +++ b/bugs/tb5079-datatype-message-nested/src/Settings @@ -0,0 +1,61 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "Tb5079DatatypeMessageNested", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Reproduces: TB5079 for an unknown type nests its message inside the message for an unknown name", + "project.exportPathIsV2": true, + "project.id": "{CB1845BF-9270-4AD1-AA74-342835964D71}", + "project.name": "Tb5079DatatypeMessageNested", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/bugs/tb5079-datatype-message-nested/src/Sources/Startup.twin b/bugs/tb5079-datatype-message-nested/src/Sources/Startup.twin new file mode 100644 index 000000000..c214e08be --- /dev/null +++ b/bugs/tb5079-datatype-message-nested/src/Sources/Startup.twin @@ -0,0 +1,9 @@ +Module Startup + + Public Sub Main() + Dim t As NoSuchType + Dim n As Long + n = NoSuchName + End Sub + +End Module diff --git a/bugs/tb5079-datatype-message-nested/tb5079-datatype-message-nested.twinproj b/bugs/tb5079-datatype-message-nested/tb5079-datatype-message-nested.twinproj new file mode 100644 index 0000000000000000000000000000000000000000..65490eaa15c168fb040ed287544441956d2b038b GIT binary patch literal 2241 zcmcImTW{h<6ej8R0&O1q)IN+*9(JV~8Dk(s>Vs`yg&GnRXi_Oem9ZxRZ@-2*`bu1*nGtj?a3+YQn)n2hu`5YI=l@l1pOa(NGd`?AD(m{8i z^A(H}KX?x@OYoN&T!o)&szf4#GJc%ls5bo+{p<+jN0o07)xOk8+|ZAVr$%Uq-*50Y z7GV-mlIxt*PHF_cNwSDoK&HupjD5ymzsbZ1_1T(e;wW+{rb)0u2Ff5u@IvtAUd+jl zx%#EQ&eaDzME@8Or&%5+HO%WMgS~1rw@J-!TbkMi=}u+1KsfBc*b-@QQfrXgY?>8^3Q9kPfHo;2kO(%gOO9@#_pKtB(C8Ir*-gOXNARUlc6 zRifTCg^I!iI^mqmT%Dvt}j?G6*TrEBY+@$dE$` zrO@;VdmIZMsT&KTYnjkPQdx*8Z9TVnX1Cp;(X+ayVLSb%;W%B}XxY|TtKI9^{cf{d zdKwB$|LIPMwEn?U3_Z#83z9P zoisr(iGtL~ggRkWBzko*AWoviI&C*(3BVYbX(2Cl?|eSLWjqv*YCfNC)a})6$DYrX z4n;(*hK?2oCLQ}F6HV7G>m?JlSuaF1N~z8q=Dt2qiA^>3-w6pGtt_cR%6KX@A=4cq zB|?U6(>=5LrqTBt*RWf|fzj)V*?FETBwI4|fYD={1Dv#)FOu#xd)Clx^^A_~IfmWuxrS?ZyN1(lo7T`i8w@?~|0msvW3r6I!jDK0 z`AQM7zzecq^0}oC1pkq1i1;=Q=wZD4zWg`zH%mm1|Bn8&98 z37ziY?18+>sDLIa-aSn7S?kf49N#0b#?w@%QVN+~I2fy1UooD(KzG@aE#U}KJhz2_ sKZyP6z}ww@Ej^@lnQ+b4w2>(KGiQMaY4I*(|6^>x9M4{Dmp+q!0uGX0CjbBd literal 0 HcmV?d00001 diff --git a/docs/IDE/Code/Diagnostics.md b/docs/IDE/Code/Diagnostics.md index 58173875b..8eeb0aae9 100644 --- a/docs/IDE/Code/Diagnostics.md +++ b/docs/IDE/Code/Diagnostics.md @@ -87,7 +87,28 @@ A problem also shows in other places: - The tab of the file is tinted, and so is its row in the Project Explorer. The tint follows the worst problem in the file. - Alt+F8 and Shift+Alt+F8 move through the problems of the current file; see [Navigating](Navigating#move-by-procedure-and-by-problem). +## Common errors +{: index="TB5001; TB5027; TB5079; TB5182" } + +Most errors are explained on the page of the statement, type or feature they concern, and a search for the code finds that page. These four come from code of every kind. Each is shown with the message it gives for an example. + +**TB5079**, *Unrecognized symbol 'Undefined1'* +: Nothing the code can reach declares the name. It is misspelt, it is a variable that was never declared while [**Option Explicit**](../../Core/Option) is on, it is **Private** to another module, or it belongs to a package or library that the project does not reference. For a project imported from VB6, see [Why do I see a lot of errors saying my variables are unrecognized?](../../../FAQ#unrecognized-variables). For a type name, as in `Dim t As NoSuchType`, the message is *Unrecognized datatype symbol 'NoSuchType'*. + +**TB5001**, *cannot coerce datatype LongLong to Long. Explicitly call CLng() if necessary.* +: A value has a type that twinBASIC does not convert to the type needed unless the code says so, such as a **LongLong** assigned to a **Long**. For an argument the message is *Validation of call to 'TakesLong' failed. Argument for 'n': cannot coerce type 'LongLong' to 'Long'*, which in a 64-bit build is also what a **LongPtr** passed for a **Long** parameter gives. Write the conversion out with **CLng** or another conversion function; see the conversions of [**LongLong**](../../Core/LongLong#conversions) and [**LongPtr**](../../Core/LongPtr#conversions). + +**TB5027**, *Unrecognized member 'NoSuchMember' on type '_Collection' [non-extensible object]* +: The name after a dot is not a member of the type or module before it, or is a member that is **Private** to it. For a module the message is *[Other] does not contain 'Hidden', or it is inaccessible in this context*. + +**TB5182**, *Syntax error. No handler for this symbol* +: A statement or an attribute where the language does not allow it. A line at the top level of a `.twin` file, outside a **Module** or **Class** block, is one case. An [attribute](../../Core/Attributes) written where it does not apply is another. + +> [!NOTE] BETA 1034 +> For a type name, TB5079 reads *Unrecognized symbol 'Unrecognized datatype symbol 'NoSuchType''*: the message for a type is put inside the message for a name. + ## See Also - [Navigating](Navigating) -- move from problem to problem in the editor - [IntelliSense](IntelliSense) -- hover shows a problem's message +- [Compiler Warnings](../../../Features/Compiler-IDE/Compiler-Warnings) -- every warning, by its code From 550e9b4ac92851e030403247e9fbb4330496b198 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Tue, 3 Nov 2026 19:00:15 +0100 Subject: [PATCH 09/19] Experimental.md: its final newline back, lost when xCalendar's line was removed --- docs/Reference/Experimental.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/Experimental.md b/docs/Reference/Experimental.md index 6ec427cce..03e0b9a5e 100644 --- a/docs/Reference/Experimental.md +++ b/docs/Reference/Experimental.md @@ -22,4 +22,4 @@ These packages are published through TWINSERV, as the [Contributed Packages](../ - [**TBMANLIB_Dialog**](../Contributed/TBMANLIB_Dialog/) -- the Open, Save and folder dialogs (**cDialog**, **cDialogEx**), the Color, Font, Find, Replace, Page Setup and Print dialogs, and message, input, password, date and progress dialogs. Version 1.1.0.0 compiles, and several of its classes fail at their main task: the Find and Replace dialogs report nothing to the program, Page Setup ignores the paper size and orientation, **cDialogEx** returns file names without their folder, the password dialog shows the password in its confirmation boxes, and the date dialog keeps input it refused. **cDialog** works. - [**TBMANLIB_ListBoxHScroll**](../Contributed/TBMANLIB_ListBoxHScroll/) -- a horizontal scroll bar for a **ListBox**. Version 1.0.0.1 compiles, and its scroll arrows and track move the thumb by a different step than the list's contents, so the thumb runs ahead of the text and the arrows cannot reach the end of a long line. - [**TBMANLIB_Xml**](../Contributed/TBMANLIB_Xml/) -- XML documents parsed, edited and written, in twinBASIC code. Version 1.0.0.1 compiles, and its parser misreads some common XML, such as self-closing elements and entities in text, and does not return for some malformed text. Its XPath engine reads some valid expressions as others, such as the step `..` and `and` with `or`. -- [**WinReg**](../Contributed/WinReg/) -- registry keys and values, read, written, deleted and enumerated. Version 1.0.1.1 does not compile in twinBASIC BETA 1034, and three of its methods give wrong results: **ReadString** returns the text followed by NUL characters, **GetRegistryValueType** returns **REG_NONE** for a value that holds data, and one overload of **ConvertValueToString** finds no value. \ No newline at end of file +- [**WinReg**](../Contributed/WinReg/) -- registry keys and values, read, written, deleted and enumerated. Version 1.0.1.1 does not compile in twinBASIC BETA 1034, and three of its methods give wrong results: **ReadString** returns the text followed by NUL characters, **GetRegistryValueType** returns **REG_NONE** for a value that holds data, and one overload of **ConvertValueToString** finds no value. From bc325393ece7d3d33ba09e0d8100abaa34f08c95 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Tue, 3 Nov 2026 19:14:25 +0100 Subject: [PATCH 10/19] Help add-in: the find box of the shortcut editors is the IDE's own search bar (objListSearchBar, objListSearchEntry, a close button), as the Project Explorer and the Toolbox open it, its entry without a type attribute so the dialog's input rule leaves it alone; a missed search rings the entry in red; the help lane asserts the bar's computed style against the IDE's classes and that the close button closes it --- add-in/Resources/SCRIPTS/textfind.js | 32 ++++++++++++++++------- docs/IDE/AddIns/Help.md | 4 +-- test/addin/help.test.mjs | 39 ++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 12 deletions(-) diff --git a/add-in/Resources/SCRIPTS/textfind.js b/add-in/Resources/SCRIPTS/textfind.js index 6c1920c4a..37c9e0ef0 100644 --- a/add-in/Resources/SCRIPTS/textfind.js +++ b/add-in/Resources/SCRIPTS/textfind.js @@ -138,21 +138,30 @@ var open = function (area) { close(false); var host = area.parentNode; if (getComputedStyle(host).position === "static") host.style.position = "relative"; + // The IDE's own search bar, as the Project Explorer and the Toolbox open it + // (styles.css): a strip hanging from the top right of the text area, with + // the entry and a close button. var bar = document.createElement("div"); - bar.className = "tbDocsFindBar"; - bar.style.cssText = - "position:absolute;top:4px;right:20px;z-index:10;display:flex;gap:4px;padding:3px;" + - "background:var(--themeGeneralPanelBackColor);border:var(--themePropertySheetValueBorderColor);" + - "box-shadow:0 2px 6px rgba(0,0,0,0.35)"; + bar.className = "objListSearchBar tbDocsFindBar"; + bar.style.zIndex = "10"; + // No type attribute: a text box all the same, and out of reach of the + // dialog's own rule for input[type="text"] (configEditor.css), which would + // give it the dialog's text-box colours over the search bar's. var input = document.createElement("input"); - input.type = "text"; + input.className = "objListSearchEntry"; input.placeholder = "Find"; input.setAttribute("aria-label", "Find"); input.value = state.query; - input.style.cssText = - "width:180px;font:inherit;color:var(--themeToolWindowBodyForeColor);" + - "background:var(--themeGeneralPanelBackColor);border:var(--themePropertySheetValueBorderColor);outline:none"; + var shut = document.createElement("div"); + shut.className = "codicon codicon-close objListSearchClose"; + shut.title = "Close"; + shut.setAttribute("role", "button"); + shut.setAttribute("aria-label", "Close"); + shut.onclick = function () { + close(true); + }; bar.appendChild(input); + bar.appendChild(shut); host.appendChild(bar); state.bar = bar; state.input = input; @@ -218,11 +227,14 @@ var down = function (e) { window.addEventListener("keydown", down, true); window.tbDocsTextFind = { down: down, bar: null, visible: visibleState }; +// A query with no match rings the entry in red, inside it, since the bar +// around it is red already. Set on every run, so a newer script's rule +// replaces an older one's. var style = document.getElementById("tbDocsFindStyle"); if (!style) { style = document.createElement("style"); style.id = "tbDocsFindStyle"; - style.textContent = ".tbDocsFindBar input.tbDocsFindMissing{border-color:#c03030 !important}"; document.head.appendChild(style); } +style.textContent = ".tbDocsFindBar input.tbDocsFindMissing{box-shadow:inset 0 0 0 2px #c03030}"; } diff --git a/docs/IDE/AddIns/Help.md b/docs/IDE/AddIns/Help.md index 4127e902e..eb684ae39 100644 --- a/docs/IDE/AddIns/Help.md +++ b/docs/IDE/AddIns/Help.md @@ -124,7 +124,7 @@ While this add-in is loaded, the keyboard shortcuts that other add-ins register The text of **Window > Keyboard Shortcuts > Manage Keyboard Shortcuts**, and of **Manage Panel Layouts**, has no find of its own. While this add-in is loaded, these keys work there, as in Visual Studio: Ctrl+F -: Opens a find box over the text, holding the selected text if it is on one line. +: Opens a find box at the top right of the text, like the search bar of the **Project Explorer**, holding the selected text if it is on one line. Its close button closes it, as Esc does. Enter, F3 : Selects the next match. **Shift+Enter** and **Shift+F3** select the one before. F3 and Shift+F3 also work after the find box is closed, with the last text searched for. @@ -135,7 +135,7 @@ Ctrl+F3 Esc : Closes the find box. The dialog stays open. -The search goes round past the end, and ignores case. The text scrolls to show each match. The setting **Find in the shortcut editors** turns this off. +The search goes round past the end, and ignores case. The text scrolls to show each match. A search that finds nothing draws a red ring inside the box where the text is typed, and leaves the selection where it was. The setting **Find in the shortcut editors** turns this off. ## Getting the add-in diff --git a/test/addin/help.test.mjs b/test/addin/help.test.mjs index 44357f9e2..570d48c1c 100644 --- a/test/addin/help.test.mjs +++ b/test/addin/help.test.mjs @@ -1164,6 +1164,30 @@ scenario("the help add-in: F1 and the help pane", (lane) => { `Ctrl+F opened no find box: ${JSON.stringify(await findBox())}`, ); assert.equal(await modals(), 1, "Ctrl+F closed the dialog"); + // The box is drawn as the IDE's own search bar, the one the Project Explorer and the + // Toolbox open: the same computed style as an element of the IDE's classes. + const look = await c.evaluate(`(() => { + const bar = document.querySelector(".tbDocsFindBar"); + const probe = document.createElement("div"); + probe.className = "objListSearchBar"; + const entry = document.createElement("input"); + entry.className = "objListSearchEntry"; + probe.appendChild(entry); + document.body.appendChild(probe); + const of = (e, names) => { const s = getComputedStyle(e); return Object.fromEntries(names.map((n) => [n, s[n]])); }; + const BAR = ["backgroundColor", "borderBottomLeftRadius", "borderBottomRightRadius", "paddingTop", "paddingLeft", "height", "display"]; + const ENTRY = ["backgroundColor", "borderTopWidth", "borderTopLeftRadius", "paddingTop"]; + const out = { + bar: of(bar, BAR), ide: of(probe, BAR), + entry: of(bar.querySelector("input"), ENTRY), ideEntry: of(entry, ENTRY), + close: bar.querySelector(".codicon-close.objListSearchClose") !== null, + }; + probe.remove(); + return out; + })()`); + assert.deepEqual(look.bar, look.ide, "the find box is not drawn as the IDE's search bar"); + assert.deepEqual(look.entry, look.ideEntry, "the find box's entry is not drawn as the IDE's"); + assert.ok(look.close, "the find box has no close button"); await typeText(c, WORD); await pressKey(c, "Enter"); await selectionIs(at[0], "Enter: the first match"); @@ -1211,6 +1235,21 @@ scenario("the help add-in: F1 and the help pane", (lane) => { assert.equal(await modals(), 1, "Esc closed the dialog"); assert.equal((await selection()).focused, true, "the text area has not got the focus after Esc"); + // Its close button closes it the same way. + await pressKey(c, "f", { ctrl: true }); + assert.ok(await waitFor(c, async () => (await findBox())?.focused === true), "Ctrl+F did not open the box again"); + const shut = await c.evaluate(`(() => { + const r = document.querySelector(".tbDocsFindBar .objListSearchClose").getBoundingClientRect(); + return { x: r.x + r.width / 2, y: r.y + r.height / 2 }; + })()`); + await clickAt(c, shut.x, shut.y); + assert.ok( + await waitFor(c, async () => (await findBox()) === null), + "the close button did not close the find box", + ); + assert.equal(await modals(), 1, "the close button closed the dialog"); + assert.equal((await selection()).focused, true, "the text area has not got the focus after the close button"); + // With the box closed, F3 and Shift+F3 go on with the last query. await keyDown("F3"); await selectionIs(at[2], "F3 with the box closed"); From 07c2892f6c6288bb8d6c7cb322fb0924c43d644d Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Tue, 3 Nov 2026 19:27:03 +0100 Subject: [PATCH 11/19] Help add-in: a picture of the find box over Manage Keyboard Shortcuts after a search for ctrl, shot Help_Find in shoot_docs' help setup (the text area keeps the focus so the match shows selected; the clip ends after twelve whole lines); one picture for both themes, the light pass having the dark one's pixels --- WIP.Screenshots.md | 2 +- WIP.md | 2 +- builder/page-baseline.json | 2 +- docs/Documentation/Tools-IDE.md | 2 +- docs/IDE/AddIns/Help.md | 2 ++ docs/IDE/AddIns/Images/Help_Find.png | Bin 0 -> 42120 bytes docs/IDE/AddIns/Images/Help_Find.svg | 1 + scripts/shoot_docs.mjs | 52 ++++++++++++++++++++++++++- test/shots/manifest.json | 3 ++ 9 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 docs/IDE/AddIns/Images/Help_Find.png create mode 100644 docs/IDE/AddIns/Images/Help_Find.svg diff --git a/WIP.Screenshots.md b/WIP.Screenshots.md index 064148178..b48031096 100644 --- a/WIP.Screenshots.md +++ b/WIP.Screenshots.md @@ -244,7 +244,7 @@ Exit codes: 0 every picture written or unchanged, 1 a picture failed, 2 the tool (`scripts/lib/shot-change.mjs`), and the sheet shows the `--cleanup` command beside its `git checkout` one. On the BETA 1033 retake its dry run puts back none of the 329 modified pictures. -- **Setups**: one IDE each --- `no-project` (menus, dialogs, panels), `help` (the add-in's eight, +- **Setups**: one IDE each --- `no-project` (menus, dialogs, panels), `help` (the add-in's nine, `test/addin/helpdemo`), `project`, `sample`, the `settings` setups, `glyphs`, `global-search` (Sample 15 exported from the install, built into the lane's copy as an add-in, its own project opened; once a search settles, its results list is told through its own `notifyChangedItem` diff --git a/WIP.md b/WIP.md index 6821d7328..5393eebbc 100644 --- a/WIP.md +++ b/WIP.md @@ -585,7 +585,7 @@ Why the report separates the wedged task from the merely blocked ones, and why - `addin-test.bat` — tests IDE add-ins by operating an IDE: every lane in `test/addin/lanes.mjs` builds the add-ins it tests into a private copy of the install, opens a project and checks what the add-in does. Outside every gate and outside CI for the same reasons as `examples.bat`; the eighteen lanes: Samples 10 and 15, the fourteen probe lanes behind the answers in [WIP.HelpAddin.md](WIP.HelpAddin.md) (`keys-help` is `keys` with the help add-in loaded), and the help add-in in `add-in/` twice, built with its committed copy of the symbol index, `add-in/Resources/SYMBOLS/symbols.json`, which a local build rewrites whenever the index changes. The `help` lane serves the built `docs/_site` on localhost to the add-in's pane; `help-offline` builds the add-in with a help archive of `docs/_site-offline` and the pane's pages come from the add-in's own server. So run `build.bat` first. Exit 0 every lane passed and the registry is as it was found, 1 a lane failed, 2 the harness failed, 3 the registry or a work folder was not put back. See [Driving the twinBASIC compiler](#driving-the-twinbasic-compiler) for its rules. - `ide-test.bat` --- the same runner for scenarios that operate the IDE itself rather than an add-in: every lane in `test/ide/lanes.mjs`. Same exit codes, same standing outside every gate and outside CI, same rules. - `try-help-addin.bat` --- opens one IDE on the user's desktop with the help add-in built and loaded, to try it by hand, and puts the registry back once it is closed (see [Driving the twinBASIC compiler](#driving-the-twinbasic-compiler)). Needs `build.bat` first. Exit 0 closed and put back, 1 the add-in or the project did not compile, 2 the tool could not run, 3 not put back. -- `node scripts/shoot_docs.mjs` --- writes the documentation's IDE screenshots (plan and owner's decisions: [WIP.Screenshots.md](WIP.Screenshots.md)). Each picture is a shot in a table, `out` its path under `docs/` (`--only` matches it), in a setup (the main three below; the full list is in WIP.Screenshots.md), each one IDE on a **private** desktop started only when a shot in it is selected: `help` (builds the add-in as `try-help-addin.bat` does, opens `test/addin/helpdemo`, `TB_DOCS_HELP_REAL_TITLEBAR=0`: the eight `docs/IDE/AddIns/Images/Help_.png`), `no-project` (`Lane.openNoProject` with `includeProjects`: the menus, cut out with alpha, the dialogs that need no project, and the empty panels, each floated on its own, after the dialogs) and `project` (the demo without the add-in: About, which needs the compiler's licence answer, and IDE Options, whose font lists need a project). One setup is not an IDE: `web` starts puppeteer's Chrome through `launchOnDesktop` on a private desktop of its own (never headless, never on the user's desktop; native-window occlusion and occluded-window backgrounding switched off, or a capture waits forever or comes back blank) for the two public pages the FAQ shows, taken once; it needs the network, as do `webpage` (the Webpage pane on `https://docs.twinbasic.com/`, its page frozen from the pane's own DevTools target and shown in a frame of the IDE's page at the pane's place, as Help_Window's docs frame is shown by srcdoc, so that the SVG can draw both: `scripts/lib/shot-freeze.mjs`) and the package-server setups, which never publish. Every IDE gets `--force-device-scale-factor=1` (`Lane`'s `browserArgs`), else the page follows the display's scaling; the page is overridden to 1280x880 at 2x, and `PAGE_DEFAULTS` puts the IDE's options, panel layouts and keyboard groups to their defaults and the recent lists to the tool's own **in the page only**, nothing written because the IDE has `IDE_FLAGS` (the one recent-list call they do not guard, `RemoveRecentsList`, is replaced first). Each picture is a `Page.captureScreenshot` with a `clip`; a page showing it gives it `{:width height}` of half its pixel size. **Every picture is taken in two themes by one IDE**: dark as `X.png`, then, after `backToPrepared` and `tbTheme_SwitchToLightMode`, Light (not Classic) as `X.light.png` beside it, which the build pairs by name ([WIP.Build.md](WIP.Build.md#pictures-in-two-themes)); a light picture with the dark one's pixels is not kept, and one more than 32 device pixels off the dark one's size fails (a wrong state, not the theme). A label's pill follows the theme unless the shot names a `tone`. A shot's `annotate` list (arrows, rings, badges...) is drawn by `scripts/lib/shot-annotate.mjs` as one SVG layer over the page before the capture and removed after it, the clip growing to hold it. A picture is rewritten only when its bytes differ, and a second run reports every one `unchanged`; a capture is not byte-stable, so `capture` captures back to back, with no rest, until one equals the file on disk or two agree, and, when the two that agree are not the file, goes on capturing further apart, for 5.5 s at most, until one is, keeping the last two that agreed, and a picture that differs from the file only by a capture's noise (16 grey levels, 0.5% of its pixels) is left as it is. **The reference is the picture at git `HEAD` first, then the file** (owner): compared with the file alone, a picture drifted from `HEAD` over the runs of one retake, each step within noise of the file before it. A capture equal to or within noise of `HEAD`'s picture leaves the file as `HEAD` has it, its SVG too (`HEAD`'s, or none), reported `restored` when it held anything else; one that is not, but is within noise of the file, leaves the file (an earlier run's real change); a picture `HEAD` lacks is compared with the file only (`pictureFate`, `scripts/lib/shot-revision.mjs`; the run's pictures at `HEAD` read in one `git cat-file --batch`). `--against ` takes another revision, `--against-disk` the file only. **`--cleanup`** (`--dry-run` to list) starts no IDE: it resets every PNG git reports modified that is within noise of `HEAD`'s, with its SVG, so that only real changes stay; one noise rule, `withinNoise` in `scripts/lib/shot-change.mjs`, for the capture, `--cleanup` and `sheet.mjs --against`. **The manifest, `test/shots/manifest.json`** (`scripts/lib/shot-manifest.mjs`; [WIP.ShotReview.md](WIP.ShotReview.md#remembered-overrides)), records the build of each picture a run writes, and holds each picture's allowances (its noise rule's limits and rectangles left out, its SVG's bar) and a review's decisions, each tied to the bytes it was made on; `decidePicture` and `decideSvg` (`scripts/lib/shot-policy.mjs`) judge with them, in a run, in `--cleanup` and in `replay.mjs`, so a run from a terminal and one from the review tool come out the same. It is read and written only for the default `--out`. **`--stage `** (a new or empty folder git ignores) writes nothing under `docs/`, to the bundle cache or to the manifest, and puts each capture, its SVG, bundle, difference pictures, strength maps and verdict there (`scripts/lib/shot-stage.mjs`), for the review tool to apply; **`--events`** prints a `@@shot-event` line per event, and then a line `stop` on stdin ends the run as at its normal end; `replay.mjs` takes both ([WIP.ShotReview.md](WIP.ShotReview.md#runs)). `--jobs N` (default 6) runs the setups at once, each in a slot of its own from `tbbroker`, a full run with `--svg` in about 12 minutes on BETA 1010 (`--jobs 1`: one at a time); `--diffs ` writes the old picture, the new one and an amplified difference map for each that changed, named with its theme. Settle by a condition, never a fixed sleep, and a picture that shows some state sets it itself rather than relying on the shots before it. The page's animations and carets are switched off through a style sheet put into the document and every shadow root; a picture that holds DEBUG CONSOLE lines has their times (and a CodeLens run's time taken) set in the page once the real ones show. **A running program's window** (setup `programs`, fixture `test/shots/programs`) is taken **once** (`once: true`, the dark pass only) with `captureWindow`, cropped to its visible frame, at the display's DPI, shown at its size at 96 DPI; its shot says `window: true` in the table, and a shot whose take and table disagree fails. **`--list`** starts no IDE: it prints the table as one line of JSON (`[{out, setup, once, window}]`), which the review tool's page of every picture reads. **The Windows user name must never be in a published picture**: the visible text of the page, text boxes included, is searched for it before the first capture and again when a capture is chosen, with the picture's SVG under `--svg` (a page can draw again in between: Project Settings puts a real path back over the one the shot set), and for a program's window the texts of the window and its children, and the run fails when it is there. The tool finds each name it presses F1 on by searching the demo's source, not by line. The add-in's detached window has a port from the broker (the help setup's slot carries one), since the add-in is told it before it binds it. **`--svg`** also draws each picture as SVG from the page as it stands at the capture (`scripts/svgshot/`: a DOM snapshot converted, its text set in per-picture subsets of the fonts the IDE drew it with, cut by `scripts/subset_font.py`, which needs Python with fontTools), compares it with the PNG, and writes `X.svg` or `X.light.svg` beside it only when no more than 0.25% of its pixels are far off; otherwise an older SVG is removed and the page shows the PNG; the SVG of a picture whose PNG is not rewritten stays as it is, with its bundle, unless a review accepts the new one (owner), but its bundle goes to the cache when the SVG drawn is the file's to the byte (owner, `bundleFollowsSvg`), and the SVG is written with it when the cached bundle cannot draw the picture again (owner, `staleBundle`) ([WIP.Build.md](WIP.Build.md#pictures-as-svg)). With `--svg` each picture's *bundle*, what its SVG is drawn from, is saved in the gitignored `.svgshot-bundles/` (`--bundles`; never under `docs/`, a bundle holds the user name), and **after a change to the converter, `node scripts/svgshot/replay.mjs` draws every SVG again from the bundles, with no IDE**, in about 20 s for the 370 (an SVG the same as its file is not compared again, `--recheck` compares all; the fonts' cuts are kept in `.svgshot-bundles/subsets/`; one browser per process, GPU off); it lists the pictures whose bundle cannot answer what the converter now asks, with the `--only` command that retakes them ([WIP.Screenshots.md](WIP.Screenshots.md#pictures-as-svg)). `node scripts/svgshot/sheet.mjs --out ` writes a page of the pictures with no SVG (`--all`: every picture), each drawn again from its bundle beside its PNG and difference map, worst first; it is drawn without the user-name check (owner: the sheet is the owner's alone), so `--out` is refused under `docs/` and anywhere git does not ignore; what it and replay share is `scripts/svgshot/redraw.mjs`, and its pages are `scripts/svgshot/sheets.mjs`, made of the pieces in `scripts/lib/shot-views.mjs`. `node scripts/svgshot/bench.mjs` tests the converter's replicas of what Chromium draws outside the DOM (scrollbars, drop-down lists) in Edge, with no IDE ([WIP.Screenshots.md](WIP.Screenshots.md#pictures-as-svg)). No wrapper; outside every gate and outside CI, as `try-help-addin.bat` is. Exit 0 every picture written or unchanged, 1 a picture failed, 2 the tool could not run, 3 not put back. Run `build.bat` first. +- `node scripts/shoot_docs.mjs` --- writes the documentation's IDE screenshots (plan and owner's decisions: [WIP.Screenshots.md](WIP.Screenshots.md)). Each picture is a shot in a table, `out` its path under `docs/` (`--only` matches it), in a setup (the main three below; the full list is in WIP.Screenshots.md), each one IDE on a **private** desktop started only when a shot in it is selected: `help` (builds the add-in as `try-help-addin.bat` does, opens `test/addin/helpdemo`, `TB_DOCS_HELP_REAL_TITLEBAR=0`: the nine `docs/IDE/AddIns/Images/Help_.png`), `no-project` (`Lane.openNoProject` with `includeProjects`: the menus, cut out with alpha, the dialogs that need no project, and the empty panels, each floated on its own, after the dialogs) and `project` (the demo without the add-in: About, which needs the compiler's licence answer, and IDE Options, whose font lists need a project). One setup is not an IDE: `web` starts puppeteer's Chrome through `launchOnDesktop` on a private desktop of its own (never headless, never on the user's desktop; native-window occlusion and occluded-window backgrounding switched off, or a capture waits forever or comes back blank) for the two public pages the FAQ shows, taken once; it needs the network, as do `webpage` (the Webpage pane on `https://docs.twinbasic.com/`, its page frozen from the pane's own DevTools target and shown in a frame of the IDE's page at the pane's place, as Help_Window's docs frame is shown by srcdoc, so that the SVG can draw both: `scripts/lib/shot-freeze.mjs`) and the package-server setups, which never publish. Every IDE gets `--force-device-scale-factor=1` (`Lane`'s `browserArgs`), else the page follows the display's scaling; the page is overridden to 1280x880 at 2x, and `PAGE_DEFAULTS` puts the IDE's options, panel layouts and keyboard groups to their defaults and the recent lists to the tool's own **in the page only**, nothing written because the IDE has `IDE_FLAGS` (the one recent-list call they do not guard, `RemoveRecentsList`, is replaced first). Each picture is a `Page.captureScreenshot` with a `clip`; a page showing it gives it `{:width height}` of half its pixel size. **Every picture is taken in two themes by one IDE**: dark as `X.png`, then, after `backToPrepared` and `tbTheme_SwitchToLightMode`, Light (not Classic) as `X.light.png` beside it, which the build pairs by name ([WIP.Build.md](WIP.Build.md#pictures-in-two-themes)); a light picture with the dark one's pixels is not kept, and one more than 32 device pixels off the dark one's size fails (a wrong state, not the theme). A label's pill follows the theme unless the shot names a `tone`. A shot's `annotate` list (arrows, rings, badges...) is drawn by `scripts/lib/shot-annotate.mjs` as one SVG layer over the page before the capture and removed after it, the clip growing to hold it. A picture is rewritten only when its bytes differ, and a second run reports every one `unchanged`; a capture is not byte-stable, so `capture` captures back to back, with no rest, until one equals the file on disk or two agree, and, when the two that agree are not the file, goes on capturing further apart, for 5.5 s at most, until one is, keeping the last two that agreed, and a picture that differs from the file only by a capture's noise (16 grey levels, 0.5% of its pixels) is left as it is. **The reference is the picture at git `HEAD` first, then the file** (owner): compared with the file alone, a picture drifted from `HEAD` over the runs of one retake, each step within noise of the file before it. A capture equal to or within noise of `HEAD`'s picture leaves the file as `HEAD` has it, its SVG too (`HEAD`'s, or none), reported `restored` when it held anything else; one that is not, but is within noise of the file, leaves the file (an earlier run's real change); a picture `HEAD` lacks is compared with the file only (`pictureFate`, `scripts/lib/shot-revision.mjs`; the run's pictures at `HEAD` read in one `git cat-file --batch`). `--against ` takes another revision, `--against-disk` the file only. **`--cleanup`** (`--dry-run` to list) starts no IDE: it resets every PNG git reports modified that is within noise of `HEAD`'s, with its SVG, so that only real changes stay; one noise rule, `withinNoise` in `scripts/lib/shot-change.mjs`, for the capture, `--cleanup` and `sheet.mjs --against`. **The manifest, `test/shots/manifest.json`** (`scripts/lib/shot-manifest.mjs`; [WIP.ShotReview.md](WIP.ShotReview.md#remembered-overrides)), records the build of each picture a run writes, and holds each picture's allowances (its noise rule's limits and rectangles left out, its SVG's bar) and a review's decisions, each tied to the bytes it was made on; `decidePicture` and `decideSvg` (`scripts/lib/shot-policy.mjs`) judge with them, in a run, in `--cleanup` and in `replay.mjs`, so a run from a terminal and one from the review tool come out the same. It is read and written only for the default `--out`. **`--stage `** (a new or empty folder git ignores) writes nothing under `docs/`, to the bundle cache or to the manifest, and puts each capture, its SVG, bundle, difference pictures, strength maps and verdict there (`scripts/lib/shot-stage.mjs`), for the review tool to apply; **`--events`** prints a `@@shot-event` line per event, and then a line `stop` on stdin ends the run as at its normal end; `replay.mjs` takes both ([WIP.ShotReview.md](WIP.ShotReview.md#runs)). `--jobs N` (default 6) runs the setups at once, each in a slot of its own from `tbbroker`, a full run with `--svg` in about 12 minutes on BETA 1010 (`--jobs 1`: one at a time); `--diffs ` writes the old picture, the new one and an amplified difference map for each that changed, named with its theme. Settle by a condition, never a fixed sleep, and a picture that shows some state sets it itself rather than relying on the shots before it. The page's animations and carets are switched off through a style sheet put into the document and every shadow root; a picture that holds DEBUG CONSOLE lines has their times (and a CodeLens run's time taken) set in the page once the real ones show. **A running program's window** (setup `programs`, fixture `test/shots/programs`) is taken **once** (`once: true`, the dark pass only) with `captureWindow`, cropped to its visible frame, at the display's DPI, shown at its size at 96 DPI; its shot says `window: true` in the table, and a shot whose take and table disagree fails. **`--list`** starts no IDE: it prints the table as one line of JSON (`[{out, setup, once, window}]`), which the review tool's page of every picture reads. **The Windows user name must never be in a published picture**: the visible text of the page, text boxes included, is searched for it before the first capture and again when a capture is chosen, with the picture's SVG under `--svg` (a page can draw again in between: Project Settings puts a real path back over the one the shot set), and for a program's window the texts of the window and its children, and the run fails when it is there. The tool finds each name it presses F1 on by searching the demo's source, not by line. The add-in's detached window has a port from the broker (the help setup's slot carries one), since the add-in is told it before it binds it. **`--svg`** also draws each picture as SVG from the page as it stands at the capture (`scripts/svgshot/`: a DOM snapshot converted, its text set in per-picture subsets of the fonts the IDE drew it with, cut by `scripts/subset_font.py`, which needs Python with fontTools), compares it with the PNG, and writes `X.svg` or `X.light.svg` beside it only when no more than 0.25% of its pixels are far off; otherwise an older SVG is removed and the page shows the PNG; the SVG of a picture whose PNG is not rewritten stays as it is, with its bundle, unless a review accepts the new one (owner), but its bundle goes to the cache when the SVG drawn is the file's to the byte (owner, `bundleFollowsSvg`), and the SVG is written with it when the cached bundle cannot draw the picture again (owner, `staleBundle`) ([WIP.Build.md](WIP.Build.md#pictures-as-svg)). With `--svg` each picture's *bundle*, what its SVG is drawn from, is saved in the gitignored `.svgshot-bundles/` (`--bundles`; never under `docs/`, a bundle holds the user name), and **after a change to the converter, `node scripts/svgshot/replay.mjs` draws every SVG again from the bundles, with no IDE**, in about 20 s for the 370 (an SVG the same as its file is not compared again, `--recheck` compares all; the fonts' cuts are kept in `.svgshot-bundles/subsets/`; one browser per process, GPU off); it lists the pictures whose bundle cannot answer what the converter now asks, with the `--only` command that retakes them ([WIP.Screenshots.md](WIP.Screenshots.md#pictures-as-svg)). `node scripts/svgshot/sheet.mjs --out ` writes a page of the pictures with no SVG (`--all`: every picture), each drawn again from its bundle beside its PNG and difference map, worst first; it is drawn without the user-name check (owner: the sheet is the owner's alone), so `--out` is refused under `docs/` and anywhere git does not ignore; what it and replay share is `scripts/svgshot/redraw.mjs`, and its pages are `scripts/svgshot/sheets.mjs`, made of the pieces in `scripts/lib/shot-views.mjs`. `node scripts/svgshot/bench.mjs` tests the converter's replicas of what Chromium draws outside the DOM (scrollbars, drop-down lists) in Edge, with no IDE ([WIP.Screenshots.md](WIP.Screenshots.md#pictures-as-svg)). No wrapper; outside every gate and outside CI, as `try-help-addin.bat` is. Exit 0 every picture written or unchanged, 1 a picture failed, 2 the tool could not run, 3 not put back. Run `build.bat` first. - **The help add-in's project file is written by every build, CI's included.** The `addinProject` task (`builder/addin-project.mjs`) packs `add-in/` into `tB/IDE/AddIns/downloads/tbDocsHelp.twinproj` in the **online tree only**, which the Help Add-In page offers as a download; `_config.yml`'s `addin_project` declares both ends, so a fixture config gets none. It waits for the `helpArchive` task. It packs the files `git ls-files` lists there, read from the working folder, never a `Resources/HELP/` file from the disk, with CRLF made LF outside `Resources/` (so a Windows and a Linux build write the same bytes), the `tB/symbols.json` the same build wrote in place of the committed copy, and **the zip that build's archive task wrote** as `Resources/HELP/site.zip`, passed in memory (a build that wrote none --- `--no-help-archive`, `--serve`, another `--dest`, a fixture --- packs without it, and the summary line says `without the offline archive`). The download is about 61 MB with the archive, 1 MB without. The offline tree holds no copy (the project carries that tree's archive, so a copy there would be circular), and its links to the file are rewritten to the website by the general rule in `offline-rewrite.mjs`: a URL the offline tree lacks but the online tree has becomes `site.url` + path, and is no miss (`computeWebsiteUrl`; the offline link check's `forbidExempt` excuses exactly those links, `isWebsiteOnlyLink`). The pack is `packFiles` from `scripts/impexp.mjs`, in memory (never staged through `%TEMP%`), the one module under `scripts/` that `builder/` may import (`biome.jsonc`); `check_tree_fresh.mjs` watches it and `add-in/` for that reason. The publish allowlist exempts the one path in the trees only (`publishPolicyFor`'s `generated`); a `.twinproj` anywhere else, or in `docs/`, is refused. `--serve` writes it into its one tree; `--dry-run` writes nothing. A failure (no git) fails the build with exit 1 and the index audit names the missing file. **Don't build the DLLs in CI from it**: that needs a twinBASIC install; the download is the project, and the reader builds it. Why each of these: [WIP.Build.md](WIP.Build.md#the-help-add-ins-project-file); `test/addin-project.test.mjs` is the gate. - **The help archive is the `helpArchive` task of the build's graph.** `builder/help-archive-step.mjs` writes `add-in/Resources/HELP/site.zip` from `docs/_site-offline` through `lib/help-archive.mjs`, once every task that writes into the offline tree has finished (`flushJoin`, `scss`, `searchIndex`, `writeOffline`; the archive task lists exactly those) and **before** the Gantt injection. **The archive leaves out `HELP_ARCHIVE_SKIP`** (`builder/help-archive-step.mjs`): the Documentation Development section (`BuildInfo.html` and `assets/images/gantt.svg` with it, so the chart never reaches the zip), Videos and their poster frames, Challenges, and **every download, which stays online** (owner): a file readers download goes in a `downloads` folder beside its page, and the step fails the build when the Downloads page offers a file the archive would hold. The add-in's server redirects any file the archive lacks to the website, because pages link to all of these; a new entry needs nothing else. It reads the file back to verify it. A mismatch fails the build (exit 1). After the Gantt injection `recheckHelpArchive` checks that the offline tree on disk lists the same files as the archive and differs from it only in `BuildInfo.html` and `gantt.svg`; a task that wrote into the offline tree late fails the build there. The help add-in embeds the zip as a resource and serves the pages from it with its own server; a build of the add-in without it uses the live site. The file is gitignored (about 60 MB, 10 MB of it the search index's `search-index.js`, deflated), so a build of the add-in from a clean clone is the online one until `build.bat` has run. The download carries the same zip (previous bullet). **`--no-help-archive` turns the step off, and neither CI workflow may pass it** (`check_ci_workflows.mjs` refuses it, in `ALLOWED.forbiddenBuildFlags`), because CI's published download would then hold no archive. It runs only for the guarded source tree (`docs`) built into the default `docs/_site-offline` (not another `--dest`, so `compare_trees` and `check_links_diff` leave it alone), never under `--serve` or `--dry-run`, never without an offline tree, and not keyed on `$CI`. There is no command-line tool for the archive: the `help-offline` lane makes its own with `writeHelpArchive`. The zip's format is fixed by the add-in's reader, which does no inflating ([WIP.HelpAddin.md](WIP.HelpAddin.md), Stage 4, increment 5). - `node scripts/bug_repro.mjs` --- the reproducer projects under `bugs//` for the entries of [BUGS-TO-REPORT.md](BUGS-TO-REPORT.md): `new`, `pack` (impexp, then the zip, written in Node), `compile`, `build` and `run` through `tbbuild` and `tbrun`, `vb6` (builds the optional `vb6/` VB6 project beside the twinBASIC one in a temp copy, runs its exe on a private desktop, and prints its `out.txt`; a project with a form or a user control is built without Unattended Execution, which VB6 refuses for it; `pack` zips its sources into `-vb6.zip`; VB6 is started only by `scripts/lib/vb6.mjs`, never from a shell; a graphical defect's `images` key makes `run` and `vb6` keep what the `PngDump` modules of `test/repro-templates/png/` draw as `images/-tb.png` and `-vb6.png`, with a `-compare.png` of both, tested by `test/png.test.mjs`), `file` (moves a filed entry and its reproducer to `bugs/filed//`; `file --marked` does every marked entry), and `verify`, which reads each `bugs/*/repro.json` and `bugs/filed/*/repro.json` and says whether the bug still reproduces on the newest beta; a `lane` reproducer names the `ide-test` or `addin-test` lane and tests that assert its bug, which `verify` runs last through that runner, a `probe` one a script under `scripts/`, and a `test` one its own `[TestCase]` Subs and compile cases through `tbrun --tests`, judged by the `PASS` lines of what is fixed and the `FAIL` lines of the bug (`new --template tests` starts one). **No reproducer should need a person at the IDE** (owner): a bug the IDE has to be operated for gets a lane test, and `manual` is left for what cannot be automated at all. It has no wrapper. **`verify` is run by a person only**, never by a gate or CI: it needs a twinBASIC install, like `examples.bat`. Each `tbbuild` and `tbrun` it starts takes a slot of its own from `tbbroker`; `--jobs N` runs N at once. Its command-line cases are in `scripts/lib/cli-cases.mjs`, and need no IDE. diff --git a/builder/page-baseline.json b/builder/page-baseline.json index 3cafbec66..462e57154 100644 --- a/builder/page-baseline.json +++ b/builder/page-baseline.json @@ -1,5 +1,5 @@ { "src": "docs", "pages": 1505, - "staticFiles": 1130 + "staticFiles": 1132 } diff --git a/docs/Documentation/Tools-IDE.md b/docs/Documentation/Tools-IDE.md index dba9ad5e4..b4ccbe1f4 100644 --- a/docs/Documentation/Tools-IDE.md +++ b/docs/Documentation/Tools-IDE.md @@ -106,7 +106,7 @@ Exit codes: **0** the IDE was closed, and the registry is as it was found; **1** Takes the documentation's screenshots of the IDE, so that one run regenerates them after a new BETA. Each picture belongs to a *setup*, one IDE brought to a known state. A setup's IDE is started only when one of its pictures is selected. Run `build.bat` first: the help setup's pane pages come from the built site, `docs/_site`, served on `localhost`. -- **help** builds the help add-in in `add-in/` into a private copy of the twinBASIC install and opens the demo project `test/addin/helpdemo`. It takes the eight pictures of the [Help Add-In](../../tB/IDE/AddIns/Help) page, `docs/IDE/AddIns/Images/Help_.png`. +- **help** builds the help add-in in `add-in/` into a private copy of the twinBASIC install and opens the demo project `test/addin/helpdemo`. It takes the nine pictures of the [Help Add-In](../../tB/IDE/AddIns/Help) page, `docs/IDE/AddIns/Images/Help_.png`. - **no-project** starts the IDE with no project open: the menus under `docs/IDE/Menu/Images/`, and the dialogs that need no project --- New / Open Project and its tabs, the Manage Keyboard Shortcuts and Manage Panel Layouts dialogs, and the ones the FAQ shows. - **project** opens the demo project without the add-in, for the dialogs that need a project open: About, whose licence line needs the compiler, and IDE Options, whose font lists do. - **sample**, **settings** and **glyphs** open `test/shots/sample`, a small Standard EXE made for the pictures. They take the panels and the editor with a project, Project Settings (with the settings files of `test/shots/settings`), and the icons the pages show inline. diff --git a/docs/IDE/AddIns/Help.md b/docs/IDE/AddIns/Help.md index eb684ae39..04808124d 100644 --- a/docs/IDE/AddIns/Help.md +++ b/docs/IDE/AddIns/Help.md @@ -137,6 +137,8 @@ Esc The search goes round past the end, and ignores case. The text scrolls to show each match. A search that finds nothing draws a red ring inside the box where the text is typed, and leaves the selection where it was. The setting **Find in the shortcut editors** turns this off. +![The top of the text of Manage Keyboard Shortcuts, with the find box hanging from its top right edge: a dark red bar holding a box with ctrl typed in it, and a close button. In the text, the first match, CTRL in the line {KEYDOWN}{CTRL}A, is selected.](Images/Help_Find.png){:width="795" height="184"} + ## Getting the add-in The add-in is a twinBASIC project, which the IDE builds into two DLLs, one for each bitness. diff --git a/docs/IDE/AddIns/Images/Help_Find.png b/docs/IDE/AddIns/Images/Help_Find.png new file mode 100644 index 0000000000000000000000000000000000000000..187979178a8da4059f5ae305a900ee85a762a069 GIT binary patch literal 42120 zcmb@uWn5O#*DY!x2qK__bcv*(C@CS0bT=X;DJ7kXv~;6%NO!k_($Xp2ozi*cWBgCt z`=0xLxX&kl;Ir9#t-0nLbBr;!uZ)xkIw}F`rAwF4pNk60UAlCg<tx>Kg~3Lw z)8g10rf+A~U6uDoMn<$;_9DnA{(k6gG`aiy{bbBV)1)o;rK7>`l8e@O_f~Eo{#RRl z7n4?}5w4kh7g+~kF+p?|-i@;$=-_lt;o3;**Aq5stv zS_LuT3$+adM@Iwa^%uq2vW>$YZg}uBS#5p&SNBhk7D zIJCQq_*OC;VO(4_J*Yq5n>%;ert|gn#X@RJ-zA=m z3JEFHZoH0xfk8=0Nl%|$Y|w@J`=R}(5qFJ4d>ZqPm-Sor-2VD#m!b^jQK~YJr-gBU zhV1+6=ze~F{Ot1^8)mOw-AAlkTaK`+>&!*xleYSNa@)l| zB24=C)rx|oS1IADI$M6Hts%4Haz|C3Vv|QbzW=uSZnUcE-c3~F{`z{J2wC)qLO&XE zD>du&`J<0CaTklh|NdXev)8vs%SvWmy_Wi*MiV zhGWat^rs6Ac?GSltuZ`!AoRli0(3iDoInhu-dp+dih&laSTcHj+;GI67Q% z1Et#%4~K^zNkKqANA7ewyjvGDAwK1Fw=F$St3FU!US6K>Uq}fpk1hO|{(#Hvw(~8S z-*b$D4qqziN>)FneQL8wY4K7BVQ{VM8GXzh0Ju0S6{w! zU*dcQ4RJX=_7yC}?4Fd#wLXzE`I_$yq3G!z69>Y}@(+fb>6n>|_}*VHD=T~P;spjN zk4B-6P>OH>V)3yukIr`HH-~b?B_yzahnZ*p@|Uqn+8|Q(k)LJ6yf1t+WWJ~~d=tr# zk(*X9>q|E2Gf3XmSQL?#mJSLGl#-NWVP)Ol+R9MM{TLDw^6wNT>sVT@D%G?-$@&rf zh>4kb3l|Aride1SnG%g_6|8CGqTq7n1n>O%dR%CzyN}68kv<;N3+L6`^3Hf}`oG)c zlTktT?&rl1w$|C$*ju=0;&idMvud8tg>5MAM7y<(jX57~O}o0fqBJ!$ zHX^}8^<>|B`t+ou^37)5MV;>63hVy<;bLFa-cNKw=EskDNw^8jPh7SxXPq7PN$<$d zXF9K7EI)NTt+4xmdsq^*Vorxx(s0sx^GVm2=gxX^N@Zn--d84cl4!^wI>kgqF^@bH zttGEJQbk>HcrQ;J%xgMaui~4;e+Ub<@ z#*&55v}GK4>w{onmT`0;nrIhBter1sv^_U*{}DIKfQ*pw-`D@~9=9 zQL`#ME-rw#9})LG^j{jrYIHnNlQDC0bLgIELp$}Fa@dcQqN$}ST z{Ak+Y%>OJgn%&$?h>xGBwlgOr{B#|iKu}ot_bNs8o8;FjjSTKPsHuIce1j-j8Fxq6 z*=u^zJ~$lh7{bX<^5=5GQw)ZEDN(?YaO!2vocRJ&|aT!|P^pnsf642@5 z8_~L4l|qV!oDU%(2b+_4h>h?Hjt{-fX%reSFP20=mws{9{2V{c{C5f66{FRv zJN5hifzZK0^Z4xGey&<&f8LpJqnDX^jcmQ;Z0*Sb{lH4?w%5=n^CoWl?h1Wz1;f5f zc@5QbttiCBsoa0pU)j#sxY&IUuRM*aU383~)YR1KgAH!C^BVEyz&Gc=NX5m)RgoVJ zW~GTkP`=XBOLVWStc;0?L7b-K(}!4yOXbJsbV%ri)m&f}J!Ess*Wx|xlTH6zePW)J zM1V$2Oq4grTwWFU`?OKL=jHe&z9Ov99}`C17th;O{JGLoNLOp}qs_`vM&k*hNG>Uc zZ!ISL-ant(Z*Epg_)?b=S z2aftjiVbpea-fpBySs1MPS&|VJ#HIy$LF%QATLiG^~Zm#mdT(|A%i(jMMt;Nh{0P@ zR>msFr`-}vZe(PX+;Weay8QL%peVX)+g9;?Q8ZWCM7J$*f$$PYL)I((nFd|)k{_RN z!1B^W_|34{z8n40ccQ3NYNmNczj;1Xe^(X`lPuQP1pGr?fpDy&W6CU=@U)Y3I>jtK zZS8mNnVCj`IA!LO6D3CS@2?3c=fB9+sASH2U1hzzbDF@GbhfH>5gNsSFdi4RZ_mdXP3)X%FWWMKhM*ww#ueVk+6ZH z2ARJ(h?K4Whb6lw7dQ7%p5~{}P>QHTi`Bv0j^tQgjHfe8OH0XekTJMcSNoKAYI<*7 z(b3T{9?AYGb_joORn>&}AE?#Dq zVs=~D*!Xyyw4Bp`!rpFw{Z81)!Fz8E-YOlWb7yDYMMf&v;295Ix~+7i^Dkfiw?$c5 zJm6qwpF*W(VJX%5?zhrzGLRLadlnoV+?OtWu-qqtO;D#W(dhnRbZN;@Bes1WgM@3i z#-W%&tKL;p_raYzVL`m->NvRU7(_{5Q9`D)~+ zPoF+eyvL?J+&mG-qAoQXSF<~F-;0nL-+64kBy$>_SiR5@BY#TnX;rh(8F#Xro)Ecw zr&_JlnAByrCr7n-!f`d13q$pJ>!$fy^7{==hn;&AbbR@##*!aZW)vhO`rRVleEq6F z-{gci0LgE86ShRcX=Kxm&oiy)XxKS8ZVX%mFlp7*&X!<@G8%H9eh_+p)jB9gVNGK)X=}H}zF_B41Go)OPc+ibp!% zP#DJQ-7dboyK0#zM6OZmxYrgzUlFQRW~%z*Ba6PHdpSy$goK!lMGl7b&|GU+k8I2R z!5mfjBz`1WhJI<&RCD&iLW{5NNabeZxApO*eBCZiCRJ5cpW3d`ly!FG-@XWtyjS_e zSfSd*={J^hY)p_oq!bI!D0))Y)zHw;(Qz|s+p0tK(X$t`D=Sq7T_iDrQ=v|MpCtnW z1LL0B;WDAg%lAPZKRsT}Z^5#7esOj%S%2+)lHV1^nh>G~T6IJYX@!N4Lh)j6CS$@b zS_%*3YmqKS+b^4rmaSKB)(MQ2St4!0UGI+!{(XkgY6&B#3c#auD?4gFH{I+l8yKv2m&Kz(*DnuY4bW|8M*n zGZfjzqh+rfz0kLD@oL3fID(2eiRG`<+i&YEcE;tZm4@P(TPc|`G3Afl-yVqOC-l9( zzw6){KO0Sb4`s9Ngb|g^d_rM?Kh))e%);0A$kGWsaZikHJCdM(S&ij*Q$k3#%FfO{ zH`qDpcA;rT&YrG~eQ~zww%xOuUmwVt!Bp>b!066jdBQIDK)p?~*qQev8T z6z9{Y?QPJdoCv(0QIEY*^qRdCAYx%Eq&4BASg}gTM`y=-0+*daVq;?`KN~+|A)f6l z*vOPm7xuqPejpl8H(qU5Z#C~H{A29?c~m>v!$v#p6P}&D zy@9%mTOs;k_a(QM>(Ao_9@U49%zE4PBn#~f5xhJQ2_Q)8=^wj~Ccm&Hci|b6uXW}4 zv}w>{y1^3`e}-+kNWVSWKkTyN!QLMJlNJ>l0a8fCSj%sDW465SoIBs9%2imDudwE_ zUc$^fwpgZ7_%+`2?c3Cqj?qzd>|1$PPj;6d+pei_%v&fa;WF|-3Vtw2XOZOjkmB*< zJIXB;w*0!Gp`itW6Bu#@)0)G?8i^D-?2fw`%(_o(5e>;f>3t~y&zQ#sGg&GwsO#-f zOq6EB<>mbN@+#-3AcyUwp4(5NQLPJjL8-M3fd#P^=h{~A!> zPC7$jR?5@Jj3s18$4r^};Cg;!AWQeXx!G;EM}+V~am@PXAsV~NulsqAwdmliUF-g$$So}8%P_sM!jPTRGNkduxC-ugF-ouAmu zrB0m&39+|u4k@nLTHOdGzNW(N8Q!l>TqMenuTrQ3Ra)pgNtuw%jLU3{ecq$F@Y#lZ zx)gEj<7CX;Q>x&}$zk?D_(4EmU=!WNQI}iW@m&eJ*EPsD4LPjtQPM#Yr6AsFzJ0E@ zqog?t0Z-B_a{3Fgxw9t+}oAqOjATCTqH6}OblG#z%Auo{r87Y?^)Ws!&0gGhOWXOvQe(RbI! zD&NctF_bYd)f5&J6t~Y3;NUH0yv??occoaP4WuO~vzX@J#ZhzLh(I>^?uXMRxaa5a zYhhZxL}|8wj%{j6qMKc!%~~ilewq4;7$G6yT8foWu7Ui(X0gE2!h2j?M4#H$JF9_d zAy46>wDf9`N2>12Ql!VOJ3ltDuBKr|TBZNS4VhYC_=)N8!`Pb~69k_xM%)?hdd@Zn zr8o_gzrC{wHDj24hc-oHgGXTh9rp5j44yB8!GhV%Do5P;1;h+P?D+st_905Ll%3W~ zhYCgt3LctDN~z#tVZDnBm5sYyNS?=EKP_*nf7ACn9i(W;#B-O>>DzhYGh@`dwfxC0%qRrLc7@v**<_fT2EC z9=u^&&2r)c_uA9=V!F~%uj4tvZZv<7`1FY|&-|&~2A6KrO@_%LeKhw=Nd%MBCYG2jfS-7G;}m%c4}=YA&~1={NfF_VUi7*M`sL+ae&pXFpo!WITP<8B43s z_cAHd3iUMyT5tKJtI04&L2z*kX1d8x-cO96MwU5v^!w8B{=01r4OiCrry+ghVc-LHY(T z>$=lQ-GVn))4qaivbQoo(75W6l@CqWoVE~k{l)o-iv4mgRpTwCAYLtdY!*9-IkLlv8Y##{0SYzRgfq~$ z>+0yBVsh4;AFtYfXL+0_WYZ;KeR^p5vHv5y}i9BPC4Hooz$aG4 z-Zjs%N+9;|^3r(@sb+4S6}7+T%%&wvY*Dwz;yzi!v&+=E0RfU`HoLs)m?~tt{Fl?c znKv-f13E1GyCTP?x+yn~0E>pdEqb{)P~-3`e2{K2f~Wp+mO^GS_fz%;!&6N15x%)x z1}>{P6xQfb;*V9W2|CyX@&s2&wsFy9*_{synjStsn?Djy{6a)b%=Y@Hho0oY>vTa+ z+3}%+-26I6gY6ks-R42Fp-x~qE7o`^BVZ$F>bIyd&oLVk1P)VB+z6&IQhOgRhF2-? zNLqtRL?yK7G%zBc>PG3vIV zCnMstHUykh;!QhExV^HvO3&nlE`Ezq9szkZX4+1y`=?jbUii?>MN`f{aKCzFQSwN) ziA+pB-?!60AV7#O#)+i*==@|eU#)ay>g#(vMvZ4tI@1yK$`(mxCMKgV)_tc$aWV09 z(3WTd)T=CF0)ilZFh+4|yv=ldj! zEazW8*dV;Z-m463s>#i*n!f9dd)oDpWfee>&qF;P3U7A`Qogp7@$0FsR$NTSs3rlV zyd?B>2NO>0)leB}{0?gzk9U_NAE;w~0qiR^^2*paOPGbDRLR1kpk@-aUb;K}^=R32 ziu-tyZ?wdO#Nwwux0iGL3eQ;9t10(F7MU(5_jaYprrdVZWBv_szFc@7V(hFaWRMJ{ zF@(=rT09>sH`n5@YMz*=`?7qg%nPjt|M4q^7d3~|joyJZBz}?+lsW?vZ zbongpPdhY;QG&E@F3w#9V=4^otgW$yMid0?MQ;xnXfw@YdgefRhqT!e4{hS4!y<72 z>JhhkO-;?uQWJ0a`5`SgA!pw&;N#e=)IplEW%?m*Mdc)F{f2-J}Eq=`|1^I;U3BU218?|gcSNKhn(m$DWW6;;~HAt#^MS6%S|hs)o5??C4G>iL8* zV$x|7*uw=Q=`fG>oDY7mye8R^suEq4b6;#i`6LfX$KqQgh+F~D=DKl zT?@=6YL2#>NoRAO#nX`Lla)t~rY=Z+Vil^hh0F}~7;WG^!R*XTaN@Mehf{&JoD{aX zeA?JNq-qBQXNcg_YCMj@oo{Kn!h)djJ&&MMoFjg8>=P8kwB^jmZ{y~jpTCL0d-~RY zAti{XAymITlJ@cD7eK1MLxAO`KM2yk($$?^Sh%myxVx*71|Ye!v(rE^xP}bfc{YHJ zfXyuD1#(YNTpTgu;k^G#q0~zT;#O8WKo!Us4(7xV`6&uY2Kf66UMS?KSVD{B^Pp{c zJKQ>8obRD*0V%QzYdm4>FaRmz-qh!&rVKkQ`V%yna1Ee2G&D6Kc?S}4zzM@O4ZWY~ zb1O3T!7cQYjq1(0l$+K=%AW_1VR5zzpLJ&J?T?z9NGqjb=+zY|OUH9%o@EmGP?K_I zGDt{EE8l+`W}#!kK#o9;6m+aiiab>5{$HtFT=R>dPyk`_L7B>uMoLLZiBcr6q@=_d z^cQExM31KEm>A7!+t;voLY8O|NmD;7gg)TXK+Auf+?^-8qN0M#`im4CUUcOJni{w< zQS|;*GWu|719f01g_~sU7Zw(FR;6OuL-Olfwr9SFQi((VWoc;9KPO2q)UWUjmz}yt zVUN(F`z^Fh%Nyg>1QGMCL8;O)&(K+*dprRM<<~&_OiM9O!(q8MO>}U7bCOrP;o9El zO*D%#GTD`h+NY>HrhEm-7xyHg?1XZIvgC4_=u7cbWyj~`riBg-+V+Q~`inDjGs<&L zyAA&~(Qwz3^>V=1Q}?pvv-S0L%lM1~ zUe_b%)9vQ(ypgPojAn4W4_{lX4L|zb`3vS3MA0P}|16<>VOe;PaB(Vf5Mki#Tx0da z-$b;(d4QQQ9hw~<@rsg?Cws9i1!blqMdp({Po7D+zejV`kamUaTHyn{Yu<5jIYiS~ zwlY(aD=cT9 ztfYh?1IMeU=aDfFEsJ1RJC+-Pjm1|}Q&WHcs}9!~wdTnin*eC@I3HTf2_`2NSmMhu z04EDfPgnOagTJpB(C}8Wwz)COH5xmhju~q`SY(y&4a;e$-Koy zMNjD3!!7{M^L_OF*rMO$k5BnYO%!Q~ertO>#3>+w;V6|~$h8ZXBb_IiXilD9DX05Q z=zVK`J-s;)OCSg>+|h|S6(;%=1d&$**d{Qus)@0#>t~SR{o36P?>g%UiB&`U+6{pQ zEvVzcM(*}ftusEm#mj*##f#HlU4T1J8a&agzF4WKs6;Vp>gekB46xhiOYUCp<;1Q# z-Dw97ak?D!h3qsG#AJJNNquY;P7MG~VN% zN%C~P7zbX>GQz~%`~vtHb#-;1B#8OGGzAbsGfJbNEa`LA+BxL?djVh+ft;Cdd8%<< zSyk1x3FOKR42g;&E|t^9+w!Oa?W8rE+wvqG4GX$S${<7khGdWaQ~)jV`dMngSTJ%y)a# z10Sqwy*h#>2u-peCT+ys1FisE953x;yc*6@C(Df@S1Px30-BkGuH# zMuUiGNWs&JP7%p#&wRpU?zDaumON#r;U&lj&T@Cx=q-D#zCN2~pk=fKeI&4PdSOxt zJh==^Uq&bv`$fY2pNJgXljLp{Sw#EC1vu@8{}7-~ylS`pGwnR(0nYcYq4mxM#iA7* z3c+`@kqa?-e{$rxo1ad-$B=o(<#FA5b3)TeA@Lj;tsb}AevP4lb}h$OEL|$D`?@u| z9RIu>n$qb~N>DeyhpKtq(S6tKDIAle`JKTso%fR^ZHcn?tgGBsZ~xx!_I23rxs+{- zdEXM6a?XnWgr|0lSFkiRc|k&)sCB9cz1Z!O1`@F+SDh^>!HSOQMU708+%g6}a-C*p z17lJtzHte_#8x+BJx6>fa6XianC=h0-MSf*wgLG(UNaKEwql;VVtDiRp zsW2`j>VtZhv=UNYGZYa_ZhsQ);pkCwBzmVB6b zIUUtCn5%yE%9S}9e&?q}$g<}V8{5S}J+QjpGk3n@X|>Rn{I17K1A3sr zoed~!B|ULDw&oL5phH4Jl8ooVHyV0(6?q3ItcQMnZKU|n?b2cWEdZi9YNc7fHRWXf z_SaX^O6iq+PVX}72nMzT%JmtlA?=nDR49Uo<96CNO(I}zxA;@|m*#)T1#D6#M@a|^ z3yX@10uYBx0$6;o-p%b#<`ztQTS+3HzoZp&q%dL&dXt8>&gGctFmnM_gN`ZAPv=w z17!zyx5LVYkB<-eZEI`mnPg62016#u+C{c>7EqdmL;@7D*iqj;X*p#u`y8T6Fr&z~!1DOg%te|uG$?NFuKNwQGo z_M*10ovSOy@U3!qJ^sdu<|sh|Yo>0k$v&Y<-t44rT0EsAdrR&ELS=#3K3(yZi(UQo zcf{F-ZlyW?>$B?Co8p%prvP>5*X|S8;u|c(Y4jx7<@nE{-TFnVNkl|su^h+`jMhNZ z5b#muiqJ3@1M3Fxb82b|DhY6st$ls+OCH09OWjFB`C8_ab!R}3??hlJ06N;4Yek_1 zaAmhPoTNDo3>EB=fT!;rYNZSrzG{9QotcFnpVUhXdw`{=s9@g<0;M;EVjx!?(r_s) zEv-suM1;-Sa3NqY%J=g1QONoMHotbE3*v!lX-8~L%!!FV7SHWufdT@D)tCr6@9EZ6 z&O6O0`PPgZVjZWQR{s3UKp;^eg96J9XYHqf_S3}rNQm#=w}*Ss>;Xl!-v z7!AB2SffDAy2Nb(^w#dKH8x;SAb8_+!D|4-};SI$&E{+aioKsL>)qLd0D5GE0}oeb!8Y0;M74 zb=9!bcaEzuGTa!ego@r#*xB+TP=Iq(T|L|>B*JBV|JSBnv)fT)+>!bQY2D!yS`K-= zi+o6AJL+NitR$At&jmdDOBfg?ZPyBm3PzS%a7Hh%3cpk9I~Pq!JNIv9^SX{D1fQM? zapP`-NQ^}&tf zE}e*#b$qO|*pfEH2rz$|5On}OmsVEjb}#~N+`RdOl{L`Er@KOHp*u+c0O8}GT$VH6 zh(`T`8^*_7j&|nB$;pSW%=kkq)*3W3JDV<@5NKgMT%ZkT!ph=s@>v-)a!@+$!TrE* zbG0AvZO4xvA>6c2^U&3wp?)qbfOxi9>E~;R&~*%@#7~@4)$%e!*#oKfP0rNvtC6(l zLXYJs=Oj&Rzhj(s?dgxs*v5$o#_lffs1x&4Ge13yrzzpp?ZdPu?`U%l2_RcTR~DBw zJl?$|#~XWnN!=qJk_03zgmTmJfrpy)&pOEVX!E3f2nYxO!4sTwL^r9X30|DN){fIq!H$>E`oJIGny^o8q6gU+bKRd)hp^y6b5oni^GU)jdq;MapbT67XhbW>zaR^(|M-RtoU-g;|8-oLn!! zX&_=KQ7`v&r9KY>M_ULDw04S&T#ma-aAFF!!lZflZYMdRWjuWOu>Nx(D=2#|8`S$w>!+LWe#MB*ZTScK!AnAfzRbma}0_9cjAw}J7?*EvLpS872UEI zfF$%P`HL@~5kvj(kW4Bt>`8|HhAb+Yn4<*D2)qqJ7?{GHQ5r4_#LxJkjvs#HsW8%V-kWjv4t zZPsX!zPP>pA>c=#BKL0AudT1oZ{>A#bol$Y z=dNdaLmnR2(&~zfi<_RT;Ns!}nBoWMpj8{bxFYwqCQ5?*g)bR?c0%$kk#eI ze_h1=y7+`#OEZvb#^Drg;w_BhAMmYov`gR??`_XW0}LP^X=rJ06o$t#VVcQL`RY zAOZezkjeFoDVR!4hJ@X{+8{MG=FO`hJK*yGMfD2&ihv$U>F*Na<7a=A6}-B!M*oqK zRBN`w@A@kB(Atz{QfwjniO5bp+eBE`IsLtH?zaN>4VpKhymu_*FI&{6%2W`H8mtW&J{Y}QO5;V7#J9E8B}Bd$qI7NE9Z4QcpVaKOTN^4 zo(^yY(5|sE39Q+bOdx}_aRhl`53rDQjE()Q1?E^*DA{-Nx3sY0w;7$PJ66BlJjcZ9&godyHh7kz~q;)0Ws<856xZx5=wKNQ> zT!rh|F(|81>snnIFXp za$IWdH7hjmc=tJL$odhR0U+*q0`H>4k5jT&EJpB586OzFT&wj^G4J5kP-9niSz z6G#H2{T7^f0IEJT5BJTN(Di&&;%DVg|2^pqc+#o$^?;g7+FwB-hyMC-P~AL^+ljTS z6p|mpix&~W@~R^mG@N~pcX#KptH zJ`UxoE4wIVLgJ+e+u7Y+gR%&vuHY+zMS9%3ora>(jnPtOyD>&n-$ z2W(yQW-(A$Kh+4FR3gOpEbY}Z<+-odzH{-?qVr|`SW z{CoenfEN*{pNVVeeUh!WPQ&`^i~Ix5?eG*?yoUsnmY3c^w$P2T$#DW$3Y-_hl89)X zhY0omzlgWM5v2by=mTVs9O z{`}xvv0eWe$K!Ggjv`J1TJwv-;^OuPv7y}y;Ka}gvZ~fx`W9i}6Clxszh`Q;df;aJ z6X#|q)z`=^5m~!)w)4EKT-6h=>n#n|yI%m1&&|q;p1WM~#(-y$RR?Mdjen3ib$?THmL`fA=Xszw#}Q;*fOao;Kb|`9~$k{MzwT z>8#C9>UKp5#Seqe7UQ`t=-WH`Gc+4fBNv4BioM1VIzLvHsH~HcJvBA;H#ZtGZiX$) zdJhkeE}jzxIyz%G_i%<6W%v;LNkM^d22dF}2GRzU^H_MMaX5 z{wq~#{wwQ;d)|x*b*ufyKX!0(B|SnOhIh{fnKzx8vuoQl4^^=Y#1@U$B`@!6vvlSd zCZ2C@H5{9>N+Y?ps#`<4-tovm$TnDlbrTenOfof4{3tn+16hoMYC@Zf6CHSoUC zIB$%%KkX~F`g)kTl(E%BXpYUZE7yF|)Miv&9W}*U#c4dsaO2WcTu!10==$M)!yf)+V{eN4cdAwh=SAXsAvobTI-nelC z9bI2gp9UyJwJK{`R4Y0vVgzEK#I6TZ$a-~o8{?VNmjX;bgbD9mKSWY{1bsR1B06T5 z{&W_#r~oHa03LzD)z{bObg=PSSNDBR>X)!{%&J?&hVbmGt6)YMt#dgcO20IOdH1dg zNIYQtt*WdX867=2I>LDwtw$?cK=P0*wiURDlHy`Q4l6wc1?(54i^MSWg80zg>*Upc zxx;54A0Pi~;l~GPV!$JWQEHYlQ1S zJz$gLyKJrucCPctzt!KW(V33)f3Y6_@BCAX(tpK2wg2OvmL&r9R<7Q5g?B$A4;T3JD1q6{@?vd@jXu zuU;*@y?k{p9a6}5^#IhJm-EA#5SeYAow%GF92~9guv(`t|ZO3Mvl0 zl7iL?2&yq9jyDS(kR@Ru-@kvK!0luU78w}VNxGwx0zD$wziQ#y@PB%+T#bM~2IeR% zH_u^v!fZ~=J^<6(oEW%7mYElCN1UhuSYYQ_o?zK;W3pYbYK}U}un-XrF%ip z7VQ4&;!QyqFgpr=h)%+Jh+@(rxqaLH0a-F&lCV=69v)YK$tdZ7Ldpz#D3Fnnld3dL zK)MA`bSH~25wjamCZ&^{tp;IPBDZzkKO!fM#C!RE7~k3Is#n1>LKxJBt`O)C+^nlf z<5K9*dH?Y#v+y@Uc7%pOQbgaOEYVFy^|ulSLj)Vq(#xRZf!O{^M~7n5CnTIu4|VgO z)piGzMJ%L&ve%<_6As9pF!w;I9Ez5}>lVmxW4jxf)EUIW$K&b~9t%fID}$n1-eR(ikw% z-$tQJ1g|Z$c=k?CANU1=Jrb#DX^p^R?sB|~<@t8W0NMdj_2B;}{nGj-5ufhn-G`Sv zTUuJcu!l0xh`Rj~%uNv2#tOEUmfL%KIBr2@*`OE)K7a_cSIm_AJ~pP|!clkY$BPOJ zZC|bkjB1CI{q_5jAN#?`tlj8k|2<^j*YgixmwP(%;GQ)J3CT+P{4y3Hp*lD&Yra2s zmq!vW(D+~WA;KRHmr>tmaw!5<(_s*#_K0p~2tW&7K#L8Am0dX39z0Nh!cu;1h483b z*w~a77CM|BZbQTd(*RL@0}VRrCstHRsKs-vSamcQ{qpfkcmxD~<1yfca)!p_V`Sv# zGn-*6@ao0*ZquB$YmgTc-ISfoO>!DaxegA;B$gv%Ec*6L=^l@_uy%x}+Fb-w@>eXPC zjY-l<734wnj8{I&!XjC#BgN@!wk4H-bP-a(z9lp)p3*pfwG+{Y3CuZAPrN)mRn^oW zR|t!+++%?Cxt-MyMo8hiqR;_A2O8J_;ZS!tgC;0+`SRtTDZh5#;kKoI2HuG?3li#6 z4i43`ug=WJeE;EDj0!OMW1^!C(>eY_w2a>g5dWVi8 zm|d`BJAnhXX(ke_#@yArkT)wP0L35s9fAukKut~{7x41A@(5;O%F1vZemHI{EPS3b zfgLa$2F^ZYhipT(shjHl{h1V^O{_N-79cLW0fPxU|Et3h=8}qS*VRhey1Fp5)Bt*N z2q;|!AK}dEPv}o!ilg!x7GVQ346RlUAYXe8qt&l_Q(FPtAnZ<8*xvp{3;jUdUHXRG z)6;{ZgRq04l5@7cnTyKLXVPI%_yh)Z<`jqN%8H6>*RQu(J$rWP3fb8ZVgdq4k)p>U zA|h|z`~s|jQmUHvge(y&%QvF{4aE*K8H}e)R8;y9#lx__cM7ofrmZT+y;;B$dhIn3 z=Pmt$FCg)%15Yc}ga-vA{pFo(UDBQcgeUox-{Q`iO>^FHhqCF!2&v$x&ZozVuIodnC4pSV36vm_0n4+ zWI`^R6$O}(3S<(R^KgAuRtg@$q&|2^r@fU@pzN%ft6<8a8Wx%2qI(AfLkc@5@Ylrk zlXW(xyPa-}#J@*}{$vcsAGH%(3;wRX&tJM?gU~4D9~VH%GX@(iOG&*bHO4`OQvd<+keNC3jW=*fFwyvwAR^>5F|R9;P4Ktr*C0@W zTomNNNI`L>M;dsG;Hlm9Q9RPuKTB?i3vL`pYg0mtQWq050)~z|ckbAfz_o6-VzGkI z^>SSVb!8Li3(lt76#n4UEdK{VbiO&a1qYWsG3>sCG|q-j8$8RPS*0K)$O2|0?jhz1 zm|liKtZOj@R>@HLKSoEtM2I2X1yJlj1eW2>f2i8_V{>Nl{VCaQH#m zcA5HenK&T4+;U`O1m}J%|F!$1Of=N0Lk~gncJ@H63M+!eKLe)=yJHHi zLA3=|-NM>huq5@&$om#Cs0>Uncsq*8d(8pn!+~z)m&clbRPdzBx9M*%&`86LzPLC) z^F}9(jEI=u+;ltMH42c1h6b1=(^P$^X32?z`C3eD?xq?v#^pS(P|$hs0Nuy= zvk$ZYcx{60E@mXqwg7sPfrI^-Fqk2Gb(+9-4)V9W)&!6yBbvjRFul9>vlPv##Bx@c zBaQP<^M{3V`&VYDx2Yja$pNz=E+$66?O0MHpE+Dt1w|AMj}fv9F~EG#5&LWWE84d%U-+S=WX1+V~ij%u6L-*0e$m2JWFhk{yvyh0!TSvr9+ zqZ>v|LBrs8>|h4>j9ufF<)cw${k&OVCgTzm z2hUE2!M~*{!(WF-|K-HQ1ehxzqa`IJ`Dfgm2ebhiJ5CmD2Vw>v@u}7Le`$;RplmqI zV&Gi*KO18R%fF-og<~ZY!-q#?HBerwtE!;M02PKZ`?dnv5~uY`9i8N`us)E`par2L zz4hBJ0k_X|=MJCOD{bvZ!a_3B0K6z?|2@>M98d*+A5c)?zkFZE|HU-%=Xn67KBpTv zQh=@ydc%NoS{fSBzpx3br@cWiTt_qojO3ac8grvyjsz*^U&Rz;3XyZU2#<(Rw6NG( zuSdW8@Q(uxuG`b@`Ro6$H;(+bp5VWFr%7tmgM=!Wu7Qa(XywkqM2Hwz{{5lbllUfn z@4_sVSUwJN@@Y62+0Y;UO^`s#uBBDEt&|SE$X3jbWYB_Ohc4&?$jHBr084gxU+xF*9?Y6SBjM%cmCOYL zUQeGs{m-JNjf#g#!}wPHug<547P+P6%P0`6(B~-p+ZNeO<*Z+s;o;cdB!JqEG6%P? zRrV2F5(tD(F)@3~eT&c%9qjHtfgafU%)!kB(=tncKg0!!Svyra3haqQSD|@=ea$)vZ6gq|9A+TlVC7!Gm&Rc+Ew6 zLV^}_SRp1py#Sz%zxiN+ZMx_KAi~9kh5c!7(upb?acq<_831p1I7BY8{5v);;@B-` zAbc7(W*N9)&d?Ugd2VTm#x4u8T~)c)or!t#%DDYXkkCVsplr+51Xs}h$kV7S`Wu10 znK18o>((vihzym&3+V>$L7+hk=B%u)rhI#s%`CRG)W&Rp5k`)| z{SHcite?JtLDwg?uEs`Ppl|@dN1=hsHCW;dmJl2zw~=(r&E3G=F-nKXl~nh%76$+( z0o5nl($O&nIKdW4j}4?9t8|&Ky}feHgPhh&7ANLcuiwPvB;RjX_Ba)9s<>SiWQP5H z6=YJGkMPC<=fCmfZVi+C3LH4w2XSOAS%6)b{4@;>4a*`SP5U-%@9x$>wSlo)SWqGS za-eP?#FHO_W)IkcJcb{S{4+2@s`akiR>IrHqEb@Aa~>WUpiJ3UL!$xGj?WB*NIgYt zXeQ%}4;P((X}RPpG~!YD69B%lY=G?-Y)UzR41h?SFc5Qbr_1fd8mlE|51 zgoJbSU#7hgNk-5eO;6W+lmuNn!c_*Sqdg|{7 z*;t{^pvh#9wgifnFV*z)^b-2GNm^`{pMZ$L~G2>@qI$0xQFtbfl^8nOX1~)_jOQotQ zX}X|UkjOVH=d6;iKYt#>XpZ2Qd%O>Z9ZJEoO=PL1rA11b0DU!L0G-EK1sf0K4Zywt zda^IxzN~-Z)eNuNVeZx(4gs%~NdooXeDeJ>ViLE62Qi78jKKYW{TY0yv+L^*P^lk1 zDg?+5?vd};J&ix#`$h#lQlvTB6fkjsvo}Y}H{rDquqcu-Ke2-VVZ*eU{aA%%8{@a; zW+wGAglrGhzdtW%Ne?{ozzqX#3`MVVKC%McIi$eHng6X14Kk8*2v6gP#WnAn`0mdz zCsoqKaEO$i*nsB^o!|-kn+1Own%!s6c+?*)5L#Ck7e8huFeRlWxm9ADEmJ8AZ#%-a z30KO6vBudTUdAo1U6dMV+(SdCP_I6`)^R$z>K>gB3Wxm%(mQL|kuMzr z_pls0V;+lSELa1BTl)93(vj;gAGvUZ;!RsgWdM(--3qD#%YxsZsa4TO2>q038Z<6J z&i7W}PXZIoIqZkeX?sUUESCc_tB!#|IrswRun&8IgUVGc7IwNq_C(ky+O&WbzoV5C zK#5B4XGua+8JG0_MKm`UI8Ba(F&kh9;GGZb@<>QE&|?q-zX2N4le!rg5!j4=qzi$& zzcK!y4c>KSG?0Z;r32}}o_?osY>enByz9prF^qF-oK1V0k|+_rNFiD6@aukPCNM^n zypEf-2f_3KL>xsg!!p}RzVr}*>3k;F#uggz28Ha2d(O*XyZ^932mX6t z+QMh|5WHNo9ys8Xp&x}>2KX=H$OtAw06_P6UE!@O$RHP#kH6s~ul%>PMFcY^NaNgW z@Py!?6z6~T{$(2Qv+x#|FFU|N8BXr@185C+r&&?J>$IOE2g4sT@XiO3yTH^2f~$Z$ zA1lj$`3KwN6Bzdv+CRc{+hB3N%X`o1t0!*1~6)qmgSM@i#S#Dspt7R*#77t|`+zV;IR20J zzB{hxe(zr?_hA@%!WFk8`fear7OZ_v`(7J=bdo|30lnvDQMK!rGWEfOx9B z0_VJPI>a*GNSVN|IF1gYH1*ckYxi6fILp6oT?0ilP#g%ztCgCTWFHrVv`n{0GAx-c zCq#ew=jTJ;4Jqp_F&Y(Fmfzf4MJcNiKVNCpDl((fG-Wc^?5qMbWrfqMw!R~)6X`@Z zG->nW;~n^{n%6uwSr!)`KR_#v()B@afbi6LXT|itew`_nts7;>9I9XHWN9OY8*Voq zH7I9M*1}CTD6a__)g_vJ#{7+mNBQ&O$DfNxB)tEGf<$YkPb38;2pP2~RyQX+zakRf zn>9*!%eCBxnxIbyRe&KQqC!u8yxN3&?7pk-H&A#Bon%A3h|f>5Q>tP>D#SEJyJBYQ zgzCYjt=Cb}s8z6jw15R@XrxLU$)e1U*k^!7lDP2n;Lf)_bjTO#!==pY1ba8qdELpn zcu(|ELql91Z&XUH^o_Ko=Y9c;pg}LW z%6#@opdvtLe>ES$KD@_62vLneTAcRUI=_CUGJw$qn=~ndEY?X$?OLURa3`nIl{|}G zj~-PeDFtssy>6l&kIy|IlLuoJ`W+uYfCgCb^)BzJ-IbEg zJr=p*wEPwg)wqxx9)*%O!#{_ZsWS8}l0XF-_Lj2q;1$>PAZjN`&jlTmn8dGiw;HkaKyh2H*=6 zDm2DtX7!-fP?rgZEFwYNt_Qa%W`-^3KU8+g&y$wqRj{$OB37t^cWUZsz>o3%dS))E zH*ek~cE&Qwr7L7cq5{Erc6E^~{c9fF(y0A@-`+v0s_@|A4?+q#`94gr1|$kg6f|4c zt{nxwSQpC|yM@Q7>59aWzIy=ifs&*lI^heL&wkSfNXf#Xuc@ttM%_w5>q(umKJmi59AneO&gKu8A zFpkg?v4AP+;(}>s!&HgK8*LwMmYsPKZ%(DwxWp!6Naqv5-?aY$6Q71%Z`;M03YEfX zxUFe-O`OQy{>Es;3#CjSma(ueEi(e|@vs19seT1P#5c6szheF{xi0n28_SbE+`;Xl zWDTT(_QL5OHzBNuOd212s;Oc^g5=E{TOZZ9k8dyCf?G%@_*{n0Ev=i9&v2b`->^ zP6}8I0Pv*pz$2vcy_wHIPav5Ia175^<$Ga2)WU;9w4@n|>ROr#w8Bxr#}FB? zn$Ff1$TAWEGIveUvNkE|_yq(6*a!%Um}{&Tkq^!a6d>HOVRl~^NfSo^z|qJUq2P5^ zrsjN&FFF7=JC0c9F*Ucdg{S*>8sEHnwd&-UY#@hQnu*=BUrG(x6M?zw*0O}(_k@j} zrT9!qC_aK5#B!bOo6z5F9(YY)PYX>X+XgoNxzj8jj)7K%bnS4bbU)LTD_6=|m1{P8 zezXV?g0KxTtP0OhYYY?h&}`!G!8v874$9`fSc9=} zEd~2N!2O!;$B^)GoJh3+yNlzGILErH)^n-_{GzaZeVQ-t+zk}2 z6K>$)@x^41U+6RZU#Xc4cwo@*f?oraeyXH>++3%ZS)%uqN++}wz`|!kg+C3*jK2@a z2J}l{U(8-RsofBf4f{5ErZXqae?h_MCXV2?C5f55jl?uI-&f1QA;Vc{#H2P|BM~3H7!>xS5s7>HMvS%roWWQ z^*T;(3xZT)qobpvSeBUM0{KtM6hip^iA-TaJ4wF`Gc#(2o{mnw;l-0#S$xt3mdnka z;+U+at~55AXJA#0|~s;&O`>s1iZ1nHGV3qFuCbU%(-*7@E*Wq zx_#`D-MDe*Y}Uxgp=>RJ|1i7&L>3*g`==f*_T+DOwh1~GUdSQt0NmHN->1KCI$qaD z3@56LY;0%VC^$@^#5SFm(eR3lj(&JtDJI)s5IXoanUZ2$f+GoCQVFqw_B02ngQL{% zzA{J9hVcJ`)|V4|1-Q8vRXxV(4w_mziRYIP6GkH|sSM|KgP53D^Ui=4p0?XiiGrjU zQO!fqhNTONd=?+2ExcQyRpM^|!1mITeb0Jlj-;)gP6SU=cofq$L}UFuP+0iomj^Zk`rsb&7;rpoihO}!ZjC=c#(E=%N-mDAg) zATUu3JrCl>t{{95Eu5RXJL_~nxk4kx3S}$2f?4dMY_7P7{7$Cj1 zv}kVz%IpU$F_twoHAM_t$pkB&seTGLe#o%LW%MvGE$qm33Pe|TrmRf1WWDXf8`gAM zRg!FYQm$b<*Sh7pUC)(&al*FujZMolQqgThv7!l900RT~385%dyV|$_E}-Y&UHg6N zO*HU7MgQ+!26kGNF}pBzv!z5+jS{m&=p{V{?#cW1ftBh*_KuFHy3vFDJ0=(IwV%I! z)wBjdp@nen8;3i;q? zr~3|}dmL7!kKN~syezE%O#G#upR8=B@kiYc^>%dZ_T$^Rb0-d6&Brtu7ZsKw96vpJzy0M+Z%XF<3Ih8-6>FYX?DiD+FwBW}zed z2(E@sak@W#o5V`;D)e6@P*AR$IpXLmq|w|SOGrL-if#qt=dY&M@YM~siF+^}H$jgJ zvr_BXH}9K9(D!i+pLg?7NwYb4kgpQx$|=E(4}Q&*}XeDhf5$zfw`yfqMB}Md2XW9yX?Sx2Z{gh!@#L zc+@g)aR(t6J4SCn#=OEB^Uo^lw9A(dJ4slx8@stp;Pf5gw0#86hD3He7|R+8*7>WH zFnTPGzu)Di7`zDxyP%*Q8V(3WU}C-av7ycr(FBJk^y;`F&}l8OeuV}W--8e21b`X{ z=f=os^lpP|lh8?D(%4bWJbrp%_Xk>ik+BH{#*!SSK3c#G(tO@js&w2LhJ6>PQeUYF6)UAun$(DO@? zQ4V!0QIiD7dBDpfH^<=lp@(cf6jy@Z=LajbG$<)4Ngjjry9EcYK9k=}*jKcCTh=}b zyMgeDO=O9Uh(6cIU6R5RqOv=iD;^CKebr;M$uW5m6~rCAltb7Vkr?G?_H*^yPO&T{ zFL+l3<8Q2N4%}K3B$Mu%N#yTjt;UrVL0dBV^skr6#*MfV5s6w`(vq?V{c}1Y>+&DJE8rs`~n+JawRx z3=IwO41|hqKew4;weq)1+qQ2mok=8|py{Ig{Ay{kaSIxaGr?0W>x~1(`j?)8U&z$V z3=*?VrGrg8T~*D>ui)j)q$ToH(ekuS;VA%quL%7u0Mm?J)k;wOgC~F`w5!-!wZ{5t zfT}aMYRwpD+Uo@)k`9>8Mb6I7{nWH{1o#E1T`BU*aBp~DY0c4F8xZgl9SJ@_Aik=r zv;cd}X*p<(4ULSDP=a~&D*_ImV*XU?o!FI7YYiWC|Wsfg=WJ2 z-e3TW&r+UnA5m81?(Bf_q=|@JL8({y_+OY+nDe*3v^fyAnC$j+x!Qr3@W$Xy$f|%b zM7Hnh=$}0*iw6IL9+j9rZfhc`q6Zz_zY==92A2TYSsn&*q$_@I20vA-*6;t5MS2Xm zSm5~%BNtXo%s`V4+Yy+&tltnRM*i99A!lf)sd>y)3)#51E?ZVXvWM1BkQ091ozc78 z@HB^=;Z}!5CPle~hfDqla-T*@qTHi#KxV!io~Lif!q5@%XNv+wS5kh;V_D~N240!< znMs?igD7Z%1{zV<&TKFX+P+-28Ti7$NQda|>q__W!GA^SYk?kps0Lh0GKhnJ_>ji> zjDB}Q2rFO=LJ8M&8DFYt8$gb13C$?Z*u`Jn9#!=Z477qOhn#7x%n>C`KHHl82M?Y# zml4Q#46|{~ngO<@c>b0y*Ttd*1dBzLUS=Ow;KMn`$jma4yhuS$?w1=KvmPWd-d{$} z*OF}kx465zKgh}DTbPjq;K^7f#8AS+0@)&8m>p{lE*mv1C^u(8;ePgjxCpgBVJ%LB zGqkaSk6=Y_NDy+ZzJ#Wbii!%!4GnOb6_8;+Z)aJ-NF@dw4n6!8{l_7=TSRKi$1+_1 zrXSHJ&_;EJtIcp1g}_S}5`l!h8GJwh4?cSUgtz;NguIaBb?rCs9xCs}n9m|`PK8y- z;VJ1;e}Bjn19f&7=fbJF$P7isOStTyezdi^tlE8&vbfH(k)N?+M(p=|qxeobjj(vn%Bt}4@?(Tpwa$fdYIO97TS~1pz65pCbz^UE=Kv~K4dXl5@`I%lezST1 z$gda^CZ@zCP|U0;$w2z*3sfU$OYs$`PuP`p{gRx}I>tnt=GuA_$6+{)*PcXAuW^&m zn@H09rZ-W{Y6AR4|1lm^Y3OzJXyfnBJ%S-_nYZ?CK@^A+jtnA!_>EjMMo!|pt1GWv zMrj%H$bdfn(NW4NO4Up3@U!`fmYyy`v4i2-P8Brp0=?t+-w~fYHxQMTEd!kiToyVV z0IzJDnB+Bx|-6+$mJy~c78M33*kvRK;WVxXV9DIT8}1ETBw3TgjNT~cL=IwXXj6Y zgJ%&v-f$^sa^4tX&|dA^aIZgjlx>sM2z*|q{Bm*}W{Go#4Y1fSY^>Xvd<*o$^bw`* zrg0j2P9+^XTiZ#D`B0FQ9OE;u@>zIwhhBXjntzLG2@+4DcP{+PCdJ>el)tG}{z~id z+x{9IcZI~{0IZK7l|)5TTK1fg}rgfflvtBrhostd}BX5DOZuOdzt zD<`M#9M!$>dL~?7{SQM*{+sXMuQu?!vu(_QfvO3U1?ufjq^12$kRs3gd4;yF?gcsb zVH_j)nB)8f-o&O2KG$iCP#47Cux+ zUgG+Vkf`ygV*CkuS6Wo;x4r6YUE$)6afKhvFBhn1_-55mqR6>LKF>99{{7L zQK6BPjNrL>@D~-s2jSoFkZ7z!OlZnBN63W<$JQS|*UTt?Txias^~W3W@xF8D>8E+` z5%wpA{&yE3MNE?;>YqHpYlDHj4VV%2fB?K~sBB&f?`FdFzg}aY*zRv)@{-di5!})- zZ*Sag?;r<9k_=1HGPlZR#Q|>_1O>L^OI{=I^S$@yY%e$%b)F#wL0ih*QD63ND&CZN zNciO158pdb_iMdPFa6q$1j~~6<$n;j&^`G>+_IhGw5!1s-8%p|W`qZJ_wLnSSUg)kkE{3%0*6t-2XhOi&#SbawavgO$Kb-?yjDm4{fIr1sC(C-}nPG2}BaLj?RjUt6xVW*N~?f z9vd{rUk9q;*v3>2KKC<5MxDpFkh+Y^K!#$Zn`plU-6?vg*qhuAaM&JCPe#+bJxLOg z=KC@3L22U3L-NAF`4S;A1`hpSHOq(kmeqI*>eYegY3uBqSLO?QtjlB($q5QVPLO`L zjRDjMEiGh&3hZ63cpH-FK@f^PKu!fY z4>`o2Ot;`pMhXOcjRhgXPw18{QybfdC=*}ivJYSe)$9#*{I8>Zz^~i{!Pe=7B>+_| zoDmdIpP@wd!6isN%|e21#`2Djdnk#upNMDtHZ}DqRer08h_1c?5X2@7f%;0~XrMOO zrgPFOLtBaXl8iz9h80z75C6g@Gfgb=*7P3y!=VyJseLPGS3tq`>&t+=&c)|HV!iSA z@%f=3x+an37qd8>k>WcJCY*Q=Ui3?2Usk4{ZupnNM?^208$VZor?K@#{#Q)(os46b z@kN0ZsF$atp$WqsYyWf|;sn2b{kk&Wf=C}IsX_;%2wgeF0ikMP!~`I*Cf6WqiJ`DS zvSD2h<8+QZwW*VYX=6&YNK`ebi2hfvc-~_U{Dx$Ky2CSVvnYySNt&aW!9ek z(pQ6#p7z`;A^ZEQ>b!7f!&!3kPyzbKiqEyHO@9;x%<7h#BQxXVmx?bPj>BJ%yc^HD zY>k9aR^$;Gr4btJIw~95R z0yn5uiUvXz#rOGPmem&jv2{g7N8WUC#)<2 zo}?>HL@r_gfE7NiE(gkMr2jC666-hKofDMBfTiLUk}C$+$VLPz(ymbDWcknljVu&- zL!8Nby^djqC_Lr6Lq1c*rX8no@$}6c>SF(L*>F(_6Gd)n~b5Q!&K0YdIhiDj`lhrXHqvkAW#Em z#G-Gub!|)F0lxYN5m!;WMxrdjxzv&(zSz+uwM{|n;yh`jo>deRvTut)TZ!$dFS5~T zg@A1^6latshEC5m*B(%u8M&4tjOBW){$2AI$sM(ZXX7)jA#}SfUK}V`#vw_oi%{8= zS-p;HntmYJpwsdOEQT9V&%!kei^d+{Z2Uw(7w^v_({Pd69IAmt`xW&VweLI7L>Mjh z@5_`8S`lf$v2JzY4y!wjzl1wMA(X4hy?FrSg(hQ1YO{lbYM`vRaW)^cvK?7Ylvibw+B$GeKziYh^fE$HUPj!9Luz7*@kD+ zJ2&0^9@IjF>JV61W#0|RmXMi4$F`xVEf4xo1Ysav@=(_;CIhaqC?bBd5r6QsjK&LP zpP*4Yz#=k*T2}hYJJq6s8EHcA-Ma_OG2fXvg2<;lb*0ubqiU)L3UF)LoxzwZ?Il?s zQro{pvq6CCqtpf5qTgCHH z!21b}1s!;kaIc5QQ&n;A1q7X{@b<@fEcl3SWhP}m{qQ`I^tnp=?39j_|^VM1DW#TJ$A+4E-@}!y6Z@AxXw_*nKLKk)!z`Rx$x%*K%#R| z*grGH=KB)96F3{Pud+Si?e-h^HI#21iLG6wU9-!MkQ3mCs`2dYCFDNF)bh3V`EzZh zm3WbzLg=Y+G~#Nk3%MNOrB``Wf-188W= z{N&&dM$01dT&2COtuLLn)z92@X^Bd5-S0 zdV@ac?AXzIi$*7{vO4wkK~gAan2QBWWS)-uj_7yk6i4CP>Y%~;$%W$Wtv)UU^F`#T zaXce;p!7v}N7I^aHV%$_vAz2HGdX2DnXa_m&>UwC?(erbPYV~eLxAwgOv?PcO`2z{ z4<5{^REtVD9TAa-pvbzruqvZ;8)QPoCwX8`KGL{BXFTZTNur#wLc=>LFy=4v+4DuP z%qEOsp-^(#tu;B2-684u(vqp z0r@@Q`z1BT2bR3c7f&NQF?zky;<|Wz-8+=A~ZMI#NIZMOlx?Ir`lyQbzTNV_a*#xwW>yXth~J{^#`Hb%O5}rh#d-+ z5P(P+?5>xsn~aT#fpo&#IMs|HU+C#U-?4se00;o*EB00e0cYx&Hn+ZQsUX1k&NTJa zPF%Yo4m%6Bb#7R%`P@?TXZu6jJ@u|&fY7FjlCK5U3-#zhCXV!OFi((I%{TsptNdGl zRql{~wOjJpZ^V343~%!9t-%)b8qgoCzb z&r#t0LbbFsiw@Y@S~tS3ZSsVz#t?TP_E+L^if51cC^U*(_xLFmnM%74Xn%klGjvP2 zkFe|(`6PY}DOc)NEi*bBm80*><;&ofQ!Y8NqyE@KWN~KxjGCKTnhV|uJjzK`@Dm>U z1gpWViXza_-x9WeyEj?JDVQU+c*~V9P;wV_?J?3qz+qW;#QwvFKfHZ=g0Q&XP?UEY ziqU^8dU4y2M}3U5b?xCW7^U@=wVvw0J&JI^<86XZeM#O8=tlKp_IJg|HVDO|5xgGe zn+c_Kzl?aA&i3t)%&jLO<%TwU87nn>aCEKV+ak*|qk#CrwT>|Hde5Giu!5>GCg@Od9i>`o%F`_qgJ zbex=4*@_mWdq?z+yp}rRYz3y*`JgMJYEk8TX&PXQeB%(Ptu*%))}xm06Z9ksena zA#6W?-V9&bKpc@N#Y)$j)+$JrMXHda{?Lod7arvh&cQBojxD_zs*nQG&G4Ii-N`QLB94eB7>Hvl!cdjHmq^ zN>!z?4{~*h+D1L;Qf7tT1JeN;_d{iLpnd2Wh=g&VrVu9JSx1z|UGxQSIIIZy*J|Ei zn6+Wt<8^Lu8B0fI2Ik{Q(7^=9x5?rb_d{XWa@~=mAT15i)EB%MR`ooHpJ?gzL4Pkp zM5Ha6&Ponz(ux8kG-Y|6_#yBA-L$NgR(=#;eE@hR80pdiew5WRN^rVO<~_#U5uyM; z#Vn$WlO*kVR}%VK+YM?H!SST^%jDKdHJ_(V1@15jeL+JKFvl@A>5*HH;*cphUcWvK zRCGEwDIdz|y14!fVh0vSF=Tl*l7!U(lck1j3trzEQkz*#A#Hv!iM|Ta8pwcd&Nblc zcT<%IqXF)Hto ze~1si^!oLh2LZTjZ*1v=Jef}vXGe!Rgmyt#Rx_z69e#Sz%4x3xvE7>i3WBt6&>*aT zG%qF*^Wd9?@O5Hzt=`DJquq`h9%3Wwq0`v*)4O^-W-$Ezt@ zYa*ycq|8im@p7fE;Fs9qcbAsphRFV}m`XN~zaHjU^FVk=B>RxoV@6jYDI%j_)vEAn zOd~G-nK$fy;^4;wyt;?&8&><}Jlaiivrt)NK2FslVzm&Fga+rjO~P&pUHhcyGof`p zA!WWDp+{{AWjO$y60KT7pVI2g0u(&nEy^rGy1xu2YE$Si=M%9FxNoICq*kP)ldX@% zX2&@V+raFG(2z*J?;VJB#K*$8tKWEeB!cf$;zE;EHY%^j9*$K8p;0}Xx%pQ@4fh=7 z4>hiE`>H)K6=m~4k%rkQ6Y+Xg*6NudoClFX1*DA~7D}H!B4!yBG<7@MGq&tCxzo^i zPKZ{gKyez$&G7gKPS)M;W4ad^n(rXeLT$3^r0arf?>g^dKVe5Z(k1`%IR^<2%XQ(U@u~pWgaf!xOsZbykaNpcG_%Fd`7y8IAdTHz@VLn~ z%O5v5!3i&`n;BE%D(KyyQ zUwQ_R1ijQe$@L%#y1Kf}*hzt1oG9s{8#bu1$09NKQ;)ovM#Goa4}Pto#?N^WHJ&ve zvV;tPlL!6Xkcd53DE{vvf_{_jSzZgV4A zuQDl9XHEp5(ZO=evG3DI{IUE3E_ORU{c+|s=m6;iI&S5GlvYDL+8VrIGhMr%}aMhv97?*PU-oKy0vw&vW@z6t5!KY6@;Tc4qTjvs8%tru$H*QD? zp9S0p^a3Y?Ec{0Ib1Ql!JX^9?Hi`(A7fSAh@`OW*DQd5*u%IpxsN=Tv+d+tNK;1rD zZ?7@{fixi)5)#To3k_zytbjOdbGMCIG^#bl3kvI(0bDe8iF zt?O%fD|gFh0Sovvplr6kdmEv9#jeyQOB%~CdbK5`$Rywlf&h&3s{srl-vgi2h*dlK z`fj7A3gVBHgFF3RZEUEzhQ_{}{wo=$1vR(Ih^%tk@NIc7vERl(e|nh0CKjFk*y!lJ zudh2>FErn9G?`pX4qTA-SxRUhso64ct8!# z{jVtWV$L%W@;n83Zs)EUT(bJLR)^Y(U!nBbN%LODkZH;nCVcM?4Xhh9u)(3Bq36%D zS5y9DE;ZNdU6~* zAc^V&Nv5k>$>BwzkWmI+$jQu*{qg{HIp3p0Cgf5bs%itA<_(>h3JVI%u~1~UyT+Ix z902I<=bLC^F^zO&-6kzjUJ;avMjOk}AWYX}p&Sdx@PV`&UIW7eTjg*%AZ+**LtQU% zR5_HuMj4?#r^&+A$WR3K=j9rKLCoq{k@jPaqcOlAF7sM?-cdq_j4j{aTz$Ec?m)1r zDj&a7Zz!G+^&W>Tgz3S>+(CCjoSB^FkLywPiTI@qExiTF@poiwii6M3*>T~7j6G84 zkWKmW=;m~Kpd{6o^i7eOj~O*#wF5T0zq>{aFBtZ?16AQ0QOb0~NRW|Kuf=DwB7d{4 z6LZFiF&Wu`2!Bv}Z7G0w8cGpjiJ-}jJz=7yjxRqVTI5&iG&R#B3_SWu3K#XFjI;n{ zYP216biB~H-Sk9|>WRf+n|uJK&X0cvX7>pWdGnU;r+tkWu1O^D^@(9&4-vDKS1vx5 zLP#2BnM<p7z!kWG`8g zWVyB`oqZ#^U+X!jHz6|f^;4N48gR1wAhOTHPIJnxpxMom6tyPi4UG#oqrn2SP;vs< zt(GttW0CcF=2gxq~(d29b&^b)$&9l z&d##&D*c$a?@6kB-02@be(YdPsP8yscaLkIGN-<5GZ9(a90`@V8kO19%$txWYhvP` zW6A80{~6x79?#$%5@3iN42azKf+!srAUJxY?hUOm3>)2tZPEB=wS;TH7rUOZEwx`6 zlsXb{at@gC7(kE58-pj1%~*Lt0%}JCZUmU18Q?84d)nRAmEgO)jDf=gCP}PU0dbM= zD+dev27D9V$nOIn2i^{P``z%*e%u)d#<~yyz-5A@Icp5B*^?N7jHg$rxMo0b18)q# zl~giY)_i9V^#N>udSuXZ;=~xBT__E5XMV`czWYdV3qtOS_mOimK<**P9u^^3{#O(d zqc%6I%8{7CS@Fus%5bv&VW@fNlEO*lK!ZXU;cGcYtNAa4c2wt+>P|rhgD17Q-jy-!Tg@=sgH>^e&JYnP z0INRmg;OqMtBjl=Lh7|xj-%*c_Flfa7sDgoB`{VwhZaHaqw&i9FBX>^0~c_9K1;*E zjQGAP?%au`3?3Pf>cV}rb3_M4%v(^T{GK|Fmi2M{{E-g^?SSPWSGd=T|6SsId zZU(%2_5miXhP;Ir2#1x~)=Wxbf?+t%bWP{doU{q0ZTCYcM!oLcT zPE%zi6O#*iW(*J5CsKxFh$%a$-d%@3c705%*60D_Bq4DP7cv0fVLNLZ8-B`7hkSwD zcSN%!G>t(j#qSv2nwgb#Lxj7ASldnlk(cm%KWhry#u)-yvFHMFo>Gk>q zBQ2@0Tu2Y_f);X4NyixQ)FfqnzYGFp5}6HloZFm{9Ty5j>Q5O<4eNVv%m+KK;boYt z&uFT{SG-<8V0vnb`_YR03vtE!e0{%1acyP1?f$Wgoeti@lWuRp54iTFM#-L=Tg&JP z`eQK-{g7SuX|Iuy5m&k#{_AA`6xy=7cr);HWmw}};M#ut`KDB`jkb!E>{;SU_Sv~8 zZ9SKKVCb&}<`^i?Z}q(LC3pLDwz>f=zIzQ4ECR)#$mi!L8~xw1m?S01EJgnlJ>5RK zf7H{}Nrep8UhL`!oXc_6!_YgD)W?FuVb%BZTIf$58DD!D*kQ^ErlK&bCwVyzVc8@&&Rg zqV{tVRva0|Y9(lSXCUTOiDJG7VP4jINqUTMA}Rv00Yk4aT=bEZ*|pbJu4Ll*QUIc^ zt~(|+LZ!H*1b4-W4R2UPGusE|^aVqS*i!MgsAfi9EZ4d%v+2j@R@`Ljror27FqA>8 zX!Mgs+Xx|LC9DXN&6ZV3G=deqA9$lztG`AD#z@Yt^+fcx{i!vVr+O<4Xy2*1R5>l7 zq5?~{A03L;k$#nSR)pNgct)~Q-KIUC#kubMAk}E72_`2P~ zyxkNek^Ui>`L|tLp=>NHl7Js99_b}XYR9}f)LVH6Mm<r0~kdBtkL1>4h- zT3YHAG1R_TUCrYV)>N1~-ysqT-ltMyqZ0AUibH*NTj^XN@Gdo?WVDPgzvF=Gk%)Q?&PD~5&%R0@%t0PO_A1H_W&;eh*pog$@<3zPn~<8E{FMomFt??s1^ zLZaojuYuM)ELgpwK$(Y!0yDD{*7`i?S~T|;`mmyY6ys-2&CKNFUUvaYwn z8*GLXz7R-uyjoK|=ihy9%yugF1YgF~oSZ=<$N-W-*>efql&gf<{2p{JpxuBWqkP-| zb?JR&V0hREVFpE&-yBd;p~^me>XbrSK1Ct$zHk8wHmExhx|hDr z_@M?BYxuLDxPSJP*wka{nY0iCaf43@mxL+$sGE37prty(P>}??=IKhrRN(X6p{olx z>%y4v95G`cREr$u7*CMr>3ba*kHXSMFZrPjLb`vt$s+TXng1H%^v`PWMot73Q@aXyi z7SIbN95gAhZ`N3ADtsSRb#!zjZv>2pz5<~SdF0x&uL}@P1R%=Q!qQmZ!_593g2Sa8 zcqi|>lEH|=);Hua@f03v#2Q}Idlu4wX#+Vm{_qLF5QH8FK(Ird50L0yh+%&D`tSr@ zh>OdV?9^I6`9^ScQS;TIEfc9an;*C?bE`;;U4h-+@%NmZp!CY`eh% zu}lhf*gP}X>+B5HBn*e{efy%w5Ur<`hDrIsQ6u$WFVW@`AxAY0^Lj-cF{_*JUib9{ zHhQrL8yDtQR^YKMyX*>M6{yKqKL6Sl$;tKVEU}V_Xf9R`qhZ+UG17G92!fySjb>EC znju2YO}FF`6LSGC4te?zcr6Qy3F8KU_Ry~dnh(Wv376BO9BbD`FFww#o%ICeqS(d88dz+b zP?@73pFlJH6Vei{Q~vi$4`B}x3%SZiX)Wz1V*ui+h2nD$^i%YWt#auc1YFwPB zXha3#^?3JIwAdvgaNzMF51dj zOj>*zr|+p;x5ja%ZA@^WrGr^0M#uNJoaiY#TmgvCtHE+m=I)h0X>WqGfk=P-jD9{Y zFfzFXU|{H)rpUS{8d01PV|aGmO=@3q$@{Q`0(^%kM&h9yf(o4Qgt@s%8lHxzB^gaW zI&M(-G-DhX%e{-1i$|eG3f+k$0Z_g_249#hPi13gr@0?oSJ#yVBchuIP4D2?$xs2# zQeg;JY09A)71gJ#)a9}%P+7~BITCEDbDImJTB z45mTZg>Vsd6bQ6%ib=*~>7X=9UDnC9G?Ai>X)h3l3d>E_C??1S z5g^@f08hiF|7!7=T0R8s8DjmUv$KE3ciwwrg$#Psu#k|wbm6%KF|T+sIvP#9Ty;34 zWO!ohs;ZQ8on9il>NDXW7+xZny}ChA*w!?2$sr1%bG6m5=Df%=qFH*@hx5{f?Mu@7 zcVXq2mRK^ea%>bt6)flc0s@G8<<71C2zHJ8jgrC?<|(2GpyA1Eit*(MFe~>Em0P#i2#3Cn1D+bq*mVBq-Wt8G_$* z;^a?!XYpQ;bsC-qP=cWsOJQ%_O3eDsPbE))KGWgTSdm9dIg8%SKPIt z%j*z9t2zs;u8FvFPU2wH*|TRos23k##{qFeiYJjFoS9HH{RJ5=hk7c^9tt+1+aR?H zzTCHJ?ZKEzK(rgJn9eoKvL~u zQ>LR=Q>5l1 zw0+OyQ1fD%Eiikfj=Da|`gp8nzK}@n{Tm+C_ydm@L8(2h3Y3#{XraBJV%?mn4!l`u zh|o65$}#+g3H4&lctl+CY)50G?o1D}#rMX;yqAsEtk2ojyh&1$rCb7eMAOpPQ6Vb2 zW9EJF07hfz2S?|uo94((I;r0+pYZ0;=M(M_Q-=7#R#EmiWLL?zFCRo`&b*aAPz*&T zVTEQo)SBN4X{J%O_CBFcB6Byru;HVeW!veGKA&AyiDBmNT_Zbq44N}zV-k@e|`7vaT>2`jL41Ew%*fkDUCMB^<{thUFg1~czBfaK8=hN zifKFo#|qZ2?j6w8LV4vxY45XaH_z|yxES;NYL>TbkN6464}f!2q?F4Ab?o|Kt5WJ>y;b;%}-2PeXAsP^zF0yhs!ScbdHwQA`t}!A*h-e*w&dMv6?V5bSj5Am%=Y7lOvWCDemZ&*N5oCoxTQ;v zWq}cZ-Ar`wrDh5Ub`#9#h_f1#zFsZ(?9cCu0*}A6UDG|FM2(H_(e4skwB@tuSN21o zm}`2KH!~R!;_wrWe`h(F!{NiCV@Z=;k00--e;G5A{%XZ6(I9G-?2jxis? ze92NdlyC+T$p@+>LbN; zU~OodA;p4Grvw$}kTpWO+i0I{8L$p2>n@V~XDQu;1JrpMn$JJ4Eu%O^>v_aY-wXL0 zf|O8N-A3t`t_1m1BZse(OUi*|x4<$rO$L_jkeoyCnUa_|Nr-@)*2iVx)4+XB3S-S- z7vBE>^nQ4d@bJQSFCH?TKtaUUZc;KW{#&p&MzzOBFJlQ6h8sQ(WN>Y_zA`Q)Y5%Oo zP`zh3Q!#}4kNst1GXG+KSp~v^7xtGa*RbDarlHz6L}OzP=8;9N8-gFGTI$4`>W78_ zpVtU91^Wxy(u6i@di)>XBMxa)AM9t zF-4m=AlQiCd27aA{w3h&*8nXZJiM#0aA3+(By-`75{LIMcb2WVGOl@gRcxn3%SoE~ zm1V#vWVDE3_1D6(mQ;q-_8n-Q3~01DdyFA}#Hgh;gu)Qx0#yDYa9Hr)4>W=L#N}x^ z{YC8e@s$g)-#T{Qn2X}1Y8>$3bg&}5lJQ~s5v=o&l{aoCHo)xW)hvA{LE)P^@$NXK zzJ{4odx`qfU0D0^14LJ5;EfQTRTY1Ja<#O~G4TWjrQT2YQ&@ON97op#q5#49aT}@b zl!U`>y1yLe+;l?MJ_EOE>-sPiK0;@_xB6cgLiyQ2Xf`!9^hvL!}Uiiop|<_DzdC@O{X+ z;`52kRKZ_a=kV`D(f=rdMFtF79g6Hjpn@j32ddFlvcA5IS%|JRQn%~|#pf3>JgSVx zvGOE5GfdEiSR^Z@E>~Rx{9qOz60ODPKbk_RknM=Co_cB4w+(xt_`@wVNhll`6Z<7) zWxX+?%2;E~W4J`k%a?zu#$l?kEFZN(r=D3(5436nc z#P|DRhSEWAL|UT6`;55WzupUlyyy$QD#lGe^9BuIiq;`O<1;k2h466Fx7a0cv9F~4FVJE<2d zNlin;w+Rbbl#_x5kamR)n1meE>PDJDIE(7MpLDMZ{G@kBJF#{TkV_42RcNLk=&C(kGjHn?PtWvE?ZkIM{V7eD7coPxIpt2U zf`oK?@@u)+uBUmGjWND|_&eDNe69i5X9vb^}i`ASE`cra9(S9o0oz_~AWXvofHR}hH`=oc!Qo2x6b zD_`Tt4cw=2SA7Zt`68i=#KcxeSP+ce@G$!OVrpMnB*Z+Tj`o9&R{xe{tTQsU#pCz!_Bv@s|3Kdfd<*Ur5lpLpqN@4_Aw72MY zuYKNPBDEu@eyCo{>cY{M@_Ngv^&cC@D+Et2!Y`#Rw$_|De(mQIUfY%szj0un{nwnE zogb!sQ`dyux4zp;L;UXRo4GSa_cvEq6%CHd-ud)(=D@Y3#P8P}=JA8#;QeHhty%Ba z=TlzWoJ!4!|AE`b;A`HM_m>_`O#Avjyk!5QgY!g2etxTidzr;EN%@qW+Hq9GpUYh7 zp2qRe#lg_s@@IKj=ZA)pWyEhk+R@_YaL4Ly?e~7X8J@-OEYeorsdhzWZ_xh%V;$a4 literal 0 HcmV?d00001 diff --git a/docs/IDE/AddIns/Images/Help_Find.svg b/docs/IDE/AddIns/Images/Help_Find.svg new file mode 100644 index 000000000..e77be81d2 --- /dev/null +++ b/docs/IDE/AddIns/Images/Help_Find.svg @@ -0,0 +1 @@ +Main()AsNewCollection"Widget""Sprocket""Gasket"AsObject=CreateObject("Scripting.Dictionary")"Widget",4.5"Sprocket",12"Gasket",0.75ResourcesSourcesInventory.twin{ "tbMisc_PreventDefaultKeyBehaviour": [ "{KEYDOWN}{CTRL}A", "{KEYDOWN}TAB" ], "tbEditor_SelectAll": [ "{KEYDOWN}{CTRL}A" ], "tbEditor_ClipboardCopy": [ "{KEYDOWN}{CTRL}C", "{KEYDOWN}{CTRL}INSERT" ], "tbEditor_ClipboardCut": [ "{KEYDOWN}{CTRL}X", "{KEYDOWN}{SHIFT}DELETE" ], "tbEditor_ClipboardPaste": [ "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}V" ], "tbEditor_Undo": [ "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}Z" ], "tbEditor_Redo": [ "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}Y" ], "tbEditor_DeleteLeft": [ "{KEYDOWN}{KEYDOWNREPEAT}BACKSPACE", "{KEYDOWN}{KEYDOWNREPEAT}{SHIFT}BACKSPACE" ], "tbEditor_DeleteRight": [ "{KEYDOWN}{KEYDOWNREPEAT}DELETE" ], "tbEditor_DeleteLeftWord": [ctrl diff --git a/scripts/shoot_docs.mjs b/scripts/shoot_docs.mjs index 7dd560a9a..52911952b 100644 --- a/scripts/shoot_docs.mjs +++ b/scripts/shoot_docs.mjs @@ -13204,6 +13204,56 @@ function helpTakes(ctx) { } }); + // The find box over the text of Manage Keyboard Shortcuts, after a search for ctrl: the + // top of the text area, the box at its top right and the first match selected. The text + // area keeps the focus for the capture (dialogShot would blur it), so the match shows as + // selected. + shoot("Find", () => + inDialog( + c, + { command: "tbKeyboardShortcuts_ShowManageKeyboardShortcuts", title: "Manage Keyboard", close: "Cancel" }, + async () => { + const AREA = ".modalDialogContainer textarea.genericMonospaceTextArea"; + const onArea = (expr) => + c.evaluate( + `(() => { const a = document.querySelector(${JSON.stringify(AREA)}); return a ? ${expr} : null; })()`, + ); + if (!(await onArea("(a.focus(), a.setSelectionRange(0, 0), (a.scrollTop = 0), true)"))) { + throw new Error("the dialog has no text area"); + } + await pressKey(c, "f", { ctrl: true }); + const boxFocused = () => + c.evaluate( + `document.activeElement !== null && document.activeElement === document.querySelector(".tbDocsFindBar input")`, + ); + if (!(await waitFor(c, boxFocused))) throw new Error("Ctrl+F opened no find box"); + await typeText(c, "ctrl"); + await pressKey(c, "Enter"); + const found = () => + onArea( + `document.activeElement === a && a.value.substring(a.selectionStart, a.selectionEnd).toLowerCase() === "ctrl"`, + ); + if (!(await waitFor(c, found))) throw new Error("Enter in the find box selected no match"); + await dialogStill(c); + // The text area's first LINES lines, whole: the find script's rect of the + // selected match gives the line's height and, by the match's line, the first + // line's top, in the text area's scrolling space (scrolled to its top). + const LINES = 12; + const { area, bar, end } = await onArea(`(() => { + const box = (e) => { const r = e.getBoundingClientRect(); return { x: r.x, y: r.y, width: r.width, height: r.height }; }; + const m = window.tbDocsTextFind.visible(a).rect; + const line = a.value.substring(0, a.selectionStart).split("\\n").length - 1; + const height = m.bottom - m.top; + const first = m.top - line * height; + return { area: box(a), bar: box(document.querySelector(".tbDocsFindBar")), end: a.getBoundingClientRect().y + a.clientTop + first + ${LINES} * height }; +})()`); + // The top of the text area, its border included, and the box. + const top = grow({ ...area, height: end - area.y }, 1); + return snap(c, "Find", snapOut(union(top, bar))); + }, + ), + ); + return takes; } @@ -13245,7 +13295,7 @@ const helpShot = (name) => ({ }); const SHOTS = [ - ...["Pane", "Hover", "Search", "Choices", "Description", "Settings", "Toolbar", "Window"].map(helpShot), + ...["Pane", "Hover", "Search", "Choices", "Description", "Settings", "Toolbar", "Window", "Find"].map(helpShot), aboutShot, servicesShot, servicesTooltipShot, diff --git a/test/shots/manifest.json b/test/shots/manifest.json index 508ed9a0e..0b1eb1807 100644 --- a/test/shots/manifest.json +++ b/test/shots/manifest.json @@ -17,6 +17,9 @@ ] } }, + "IDE/AddIns/Images/Help_Find.png": { + "build": 1034 + }, "IDE/AddIns/Images/Help_Hover.light.png": { "build": 1034 }, From 30988b50c8ed06e70b376b05164224c716434e80 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Tue, 3 Nov 2026 20:28:34 +0100 Subject: [PATCH 12/19] Callout evidence (items 15 and 8): every callout labelled BETA names what checks it in , one grammar with the contributed sections' (scripts/lib/test-markers.mjs), on 246 callouts; gate check_callout_evidence.mjs in test.bat and CI; recheck_callouts.mjs runs the evidence on a new build, relabels what holds and lists the rest; check_examples gains probe fences (a sample the reader never sees) and arch=win32,win64 runs; probes for Compiler-Warnings' TB0018/19/21 NOTEs and the FAQ's msvbvm60 redirects --- .github/actions/run-gates/action.yml | 7 + WIP.Authoring.md | 2 +- WIP.Errors.md | 5 +- WIP.ExamplesBuild.md | 13 + WIP.md | 22 +- builder/render.mjs | 16 +- docs/Documentation/Authoring.md | 61 ++- docs/Documentation/Building.md | 1 + docs/Documentation/Images/Tools-Groups.dot | 16 +- docs/Documentation/Images/Tools-Groups.svg | 458 +++++++++--------- docs/Documentation/Tools-Defects.md | 36 +- docs/Documentation/Tools-Samples.md | 5 + docs/Documentation/Tools-Toolchain-Tests.md | 22 +- docs/Documentation/Tools.md | 24 +- docs/Features/Advanced/API-Declarations.md | 2 + docs/Features/Advanced/Classes-and-Modules.md | 1 + docs/Features/Advanced/Static-Linking.md | 3 + .../Compiler-IDE/Compiler-Warnings.md | 67 +++ docs/Features/Language/UDTs.md | 2 + docs/IDE/Building/ActiveX-Registration.md | 2 + docs/IDE/Building/Build-Output.md | 2 + docs/IDE/Building/Build-and-Clean.md | 1 + docs/IDE/Building/LLVM-in-the-IDE.md | 1 + docs/IDE/Building/Package-Publishing.md | 3 + docs/IDE/Code/Diagnostics.md | 2 + docs/IDE/Code/Editor.md | 5 + docs/IDE/Code/Finding.md | 5 + docs/IDE/Code/IntelliSense.md | 1 + docs/IDE/Code/Navigating.md | 2 + docs/IDE/Code/Object-Browser.md | 1 + docs/IDE/Customising/Themes.md | 1 + docs/IDE/Customising/index.md | 1 + .../IDE/Debugging/Breakpoints-and-Stepping.md | 5 + docs/IDE/Debugging/Debug-Console.md | 3 + docs/IDE/Debugging/Inspecting.md | 3 + docs/IDE/Debugging/Memory.md | 4 + docs/IDE/Debugging/Running.md | 4 + docs/IDE/Forms/Arranging.md | 5 + docs/IDE/Forms/Controls.md | 1 + docs/IDE/Forms/Designer.md | 9 + docs/IDE/Forms/Properties.md | 2 + docs/IDE/Forms/Toolbox.md | 1 + docs/IDE/Menu/Format.md | 1 + docs/IDE/Projects/Adding-Files.md | 4 + docs/IDE/Projects/Files-and-Folders.md | 9 + docs/IDE/Projects/Project-Explorer.md | 2 + docs/IDE/Projects/References-and-Packages.md | 6 + docs/IDE/Projects/Saving-and-Exporting.md | 2 + docs/IDE/Reports/index.md | 1 + docs/LLVM/Getting-Started.md | 7 + docs/Miscellaneous/FAQs.md | 81 ++++ docs/Reference/Attributes.md | 5 + .../WinNativeCommonCtls/ListView/ListItem.md | 1 + .../WinNativeCommonCtls/ListView/index.md | 1 + .../Built-In/WinNativeCommonCtls/Slider.md | 1 + .../Built-In/tbIDE/FileSystemItem.md | 1 + docs/Reference/Built-In/tbIDE/HtmlElement.md | 1 + .../Built-In/tbIDE/HtmlElementProperties.md | 1 + .../Built-In/tbIDE/KeyboardShortcuts.md | 1 + docs/Reference/COM-Interfaces/IDispatch.md | 1 + docs/Reference/COM-Interfaces/IErrorInfo.md | 2 + docs/Reference/COM-Interfaces/IUnknown.md | 2 + docs/Reference/Compiler Constants.md | 2 +- .../Contributed/ArrayListLib/index.md | 1 + .../TBMANLIB_Dialog/cDialogPrint.md | 2 - .../cListboxHScroll/Show.md | 1 - .../cListboxHScroll/UnShow.md | 1 - .../cListboxHScroll/index.md | 1 - .../Contributed/TBMANLIB_RES/cRes/Read.md | 1 - .../TBMANLIB_RES/cRes/ReturnBase64.md | 1 - .../TBMANLIB_RES/cRes/ReturnPicture.md | 1 - .../Contributed/VBComDlg/CommonDialog.md | 1 - docs/Reference/Contributed/WinReg/index.md | 1 + .../Contributed/tsJson/JsonOptions.md | 1 - .../Contributed/tsPwGen/PwGenOptions.md | 2 - docs/Reference/Core/Const.md | 2 + docs/Reference/Core/Declare.md | 1 + docs/Reference/Core/Divide.md | 1 + docs/Reference/Core/Error.md | 1 + docs/Reference/Core/For-Each-Next.md | 1 + docs/Reference/Core/For-Next.md | 1 + docs/Reference/Core/Get.md | 1 + docs/Reference/Core/GoSub-Return.md | 1 + docs/Reference/Core/Graphics-Methods.md | 5 +- docs/Reference/Core/LSet.md | 2 + docs/Reference/Core/LeftShift.md | 1 + docs/Reference/Core/Line-Input.md | 1 + docs/Reference/Core/Minus.md | 1 + docs/Reference/Core/Multiply.md | 1 + docs/Reference/Core/Name.md | 1 + docs/Reference/Core/On-Error.md | 1 + docs/Reference/Core/Open.md | 1 + docs/Reference/Core/ParamArray.md | 1 + docs/Reference/Core/Plus.md | 1 + docs/Reference/Core/Print.md | 1 + docs/Reference/Core/Put.md | 1 + docs/Reference/Core/RSet.md | 2 + docs/Reference/Core/RightShift.md | 1 + docs/Reference/Core/Topic-Preprocessor.md | 2 +- docs/Reference/Core/Type.md | 4 + docs/Reference/Data-Types.md | 1 + docs/Reference/Data-Types/Date.md | 1 + docs/Reference/Data-Types/Double.md | 2 + docs/Reference/Data-Types/LongLong.md | 2 + docs/Reference/Data-Types/Object.md | 1 + docs/Reference/Data-Types/Single.md | 1 + docs/Reference/Default/VB/Data/index.md | 2 + docs/Reference/Default/VB/Form/index.md | 2 + docs/Reference/Default/VB/MultiFrame/index.md | 1 + docs/Reference/Default/VB/OLE/index.md | 1 + docs/Reference/Default/VB/PictureBox/index.md | 2 + .../Reference/Default/VBA/Conversion/CDate.md | 1 + .../Default/VBA/Conversion/CLngLng.md | 1 + docs/Reference/Default/VBA/Conversion/CStr.md | 1 + docs/Reference/Default/VBA/Conversion/Str.md | 1 + .../Default/VBA/DateTime/Calendar.md | 1 + .../Reference/Default/VBA/DateTime/DateAdd.md | 1 + .../Default/VBA/DateTime/DateDiff.md | 1 + .../Default/VBA/DateTime/DatePart.md | 2 + docs/Reference/Default/VBA/DateTime/index.md | 1 + .../Default/VBA/ErrObject/Description.md | 1 + .../Default/VBA/ErrObject/HelpContext.md | 2 +- .../Default/VBA/ErrObject/HelpFile.md | 2 +- .../Reference/Default/VBA/ErrObject/Number.md | 2 +- docs/Reference/Default/VBA/ErrObject/Raise.md | 6 +- .../Default/VBA/ErrObject/ReturnHResult.md | 2 +- .../Reference/Default/VBA/ErrObject/Source.md | 1 + .../Default/VBA/FileSystem/FileDateTime.md | 2 +- .../Default/VBA/FileSystem/FileLen.md | 2 +- docs/Reference/Default/VBA/FileSystem/Kill.md | 2 +- .../Default/VBA/FileSystem/SetAttr.md | 2 +- .../Reference/Default/VBA/FileSystem/Width.md | 2 +- docs/Reference/Default/VBA/Financial/IPmt.md | 2 +- docs/Reference/Default/VBA/Financial/IRR.md | 2 +- docs/Reference/Default/VBA/Financial/MIRR.md | 4 +- docs/Reference/Default/VBA/Financial/NPV.md | 4 +- docs/Reference/Default/VBA/Financial/PPmt.md | 2 +- docs/Reference/Default/VBA/Financial/Rate.md | 2 +- .../VBA/HiddenModule/AddClassInstanceFlag.md | 1 + .../VBA/HiddenModule/ConvertIconToBitmap.md | 2 +- .../Default/VBA/HiddenModule/Eval.md | 6 +- .../VBA/HiddenModule/GetInheritedOwner.md | 2 +- .../RuntimeCreateGetMessageHook.md | 2 +- .../Default/VBA/HiddenModule/vbaCastObj.md | 2 +- .../Default/VBA/Interaction/CallByName.md | 1 + .../Default/VBA/Interaction/MsgBox.md | 2 +- .../VBA/Interaction/RaiseEventByName.md | 3 +- .../VBA/Interaction/RaiseEventByName2.md | 3 +- .../Default/VBA/Interaction/SendKeys.md | 2 +- .../Default/VBA/Math/ShiftRotateLeft.md | 1 + .../Default/VBA/Math/ShiftRotateRight.md | 1 + .../Default/VBA/Math/ShiftUnsignedRight.md | 1 + docs/Reference/Default/VBA/Strings/Format.md | 1 + docs/Reference/Default/VBA/Strings/Len.md | 1 + .../TbExpressionService/AddCustomBinder.md | 2 +- .../AddCustomBinderObject.md | 2 +- .../VBA/TbExpressionService/Compile.md | 3 +- .../VBA/TbExpressionService/Evaluate.md | 3 +- .../Default/VBRUN/ErrorCallstack/index.md | 3 +- .../Default/VBRUN/ErrorContext/index.md | 5 +- .../Default/VBRUN/ErrorStackFrame/index.md | 3 +- docs/Reference/Literal-Keywords.md | 2 + docs/Reference/Operators.md | 1 + docs/Reference/Run-time-Errors.md | 3 +- scripts/bug_repro.mjs | 35 +- scripts/check_callout_evidence.mjs | 345 +++++++++++++ scripts/check_examples.mjs | 63 ++- scripts/check_package_tests.mjs | 18 +- scripts/lib/cli-cases.mjs | 9 + scripts/lib/example-batches.mjs | 56 +++ scripts/lib/example-run.mjs | 70 ++- scripts/lib/lane-report.mjs | 48 ++ scripts/lib/package-test-cases.mjs | 81 +--- scripts/lib/recheck.mjs | 108 +++++ scripts/lib/tb-fences.mjs | 51 +- scripts/lib/test-markers.mjs | 256 ++++++++++ scripts/recheck_callouts.mjs | 437 +++++++++++++++++ test.bat | 7 + test/package-tests/README.md | 3 + 179 files changed, 2277 insertions(+), 445 deletions(-) create mode 100644 scripts/check_callout_evidence.mjs create mode 100644 scripts/lib/lane-report.mjs create mode 100644 scripts/lib/recheck.mjs create mode 100644 scripts/lib/test-markers.mjs create mode 100644 scripts/recheck_callouts.mjs diff --git a/.github/actions/run-gates/action.yml b/.github/actions/run-gates/action.yml index a039572bd..0649a4a5f 100644 --- a/.github/actions/run-gates/action.yml +++ b/.github/actions/run-gates/action.yml @@ -249,6 +249,13 @@ runs: - name: Verify the package test markers and projects (check_package_tests.mjs) shell: bash run: node scripts/check_package_tests.mjs + # Every callout labelled BETA names what checks it in a marker, which + # scripts/recheck_callouts.mjs runs on a new build, and every marker under + # docs/ names a reproducer, lane, sample, error data file or LLVM probe mode + # that exists. Sources only: no browser, no built tree, no install. + - name: Verify the evidence markers of labelled callouts (check_callout_evidence.mjs) + shell: bash + run: node scripts/check_callout_evidence.mjs # Every page's Common Errors section is what scripts/errors.mjs write makes of the # run-time error data under errors/, and the data is in order. Sources # only: no twinBASIC, no VB6. diff --git a/WIP.Authoring.md b/WIP.Authoring.md index 7fed8b2f9..e4a203395 100644 --- a/WIP.Authoring.md +++ b/WIP.Authoring.md @@ -351,7 +351,7 @@ system remains a genuine consumer of the permalinks. - tbIDE → flat, one page per CoClass / Class under `docs/Reference/Built-In/tbIDE/.md`; nested enums fold onto their declaring class's page (no `Enumerations/` sub-folder). - WinNativeCommonCtls → single-file (`DTPicker`, `MonthView`, `ProgressBar`, `Slider`, `UpDown`) or folder-style (`ImageList/`, `ListView/`, `TreeView/`) when the control has sub-object companions. Module-level enums under `Enumerations/`; per-control nested enums fold onto the declaring control's page. - Pick `` from VBA's grouping (Information, Interaction, Strings, FileSystem, DateTime, Math, Financial, Conversion, ...) and the existing folders under `Reference//`. -2. **Flag tB deviations** with a `> [!NOTE]` callout (see next section). +2. **Flag tB deviations** with a `> [!NOTE]` callout (see next section). A callout labelled `BETA ` names what checks it in `` on the line above it: a reproducer, a lane, a sample's `id=` (a `probe` the reader never sees, when the check would mean nothing on the page), error data, an LLVM probe mode, or `none -- ` ([Authoring](docs/Documentation/Authoring.md#a-callout-names-what-checks-it); WIP.md, rule 6). 3. **Update the parent index** — turn an unlinked bullet into a link with a short blurb. Match the existing style of the page. If a new package is being added, also add a bullet to `docs/Reference/Built-In.md` (or `Default.md`) and bump the count on `docs/Reference/Packages.md` and `docs/Reference/index.md`. `Packages.md` itself only links to those two landing pages -- it does not enumerate packages. 4. **Add the page** to `Reference/Statements.md` or `Reference/Procedures and Functions.md` if it's a statement or callable and not already listed there. 5. **Run the [site integrity check](WIP.md#site-integrity-check)** after the batch and before committing. diff --git a/WIP.Errors.md b/WIP.Errors.md index 3a92fd98e..81e9e8fd5 100644 --- a/WIP.Errors.md +++ b/WIP.Errors.md @@ -512,7 +512,10 @@ What the probes must respect (BETA 1010; `errors.mjs` already does all of it): - **A case that crashes stops the probe, and every later case reports `none`** (`the probe stopped before it ended: exception 0xc0000005`), as a hang does. Explore one page per probe (a data file holding only that page), then one case per probe for a page that stops; a case - that crashes is a WARNING by hand, never a case. + that crashes is a WARNING by hand, never a case. A WARNING by hand names its reproducer in + `` on the line above it (and `errors ` when it cites the data's + rows too), as every callout labelled `BETA ` does (WIP.md, rule 6); the generated callouts + need none. - **The probe is one module**: a case cannot declare a class, an interface or a `Type`. A harness can hold a `Type`, an `Enum`, a `Declare` and procedures; a fact that needs a class (`GetNameFromDispID` on a twinBASIC class) is checked with `tbrun` on a tree of its own and diff --git a/WIP.ExamplesBuild.md b/WIP.ExamplesBuild.md index b14fdeaba..fc8985c52 100644 --- a/WIP.ExamplesBuild.md +++ b/WIP.ExamplesBuild.md @@ -124,6 +124,8 @@ a fence at all. | `check_build` | compile this sample | --- | | `check_run` | compile it, build it and run it, and compare what it prints with what the fence says it prints (a trailing comment on a `Debug.Print` line, up to a ` -- ` that starts a note, or the comment lines under `' Output:`, taken whole). Statement samples (`slot=sub`) only; `MsgBox`, `InputBox` and `End` are refused. See the dispatcher design below | --- | | `hidden` | context for the page's samples, compiled with them and rendered to nothing. Implies `check_build` | --- | +| `probe` | a sample of its own that is rendered to nothing: it checks a statement of the page (named by ``, which `check_callout_evidence.mjs` requires). Implies `check_build`; with `check_run` it is run. Needs `id=`; refused with `hidden` | --- | +| `arch=` | `win32`, `win64` or `win32,win64`: the architectures a `check_run` sample runs in. The win64 run is a copy, `@win64`, batched apart with its page's hidden fences and built for win64 (`splitArches`). Refused on a sample not run and on a `projname` or `concat_group` member | `win32` | | `slot=` | `file`, `module`, `sub`, `class` or `method` --- what to generate around it | inferred | | `inherits=` | the class the sample is code-behind *of*; forces the Class row | --- | | `project=` | which template project to build into | inferred from the page's path | @@ -554,6 +556,17 @@ not handle (`InStr(0, "abc", "a")` unhandled in a sample's body printed `[tbx-ru Invalid procedure call or argument`, and the next sample ran), and `Resume tbxNext` clears it before the next call. +**`arch=` copies a run sample, it does not switch a batch.** A sample that names win64 is +copied by `splitArches` (`lib/example-run.mjs`) into a sample of its own, `@win64`, with +the `hidden` fences and staged files of its page; the copies are batched by themselves, and +`stageBatch` hands each batch's `arch` to `compileProject`, which sets the build box on every +project (`setBuildTarget`), so a lane's IDE can open a win64 project after a win32 one. A copy is +judged under its own id, and the summary counts it with its sample. The check that this is a +64-bit run and not a relabelled 32-bit one: a `Declare` of msvbvm60's `rtcBeep`, which twinBASIC +does not redirect, ran in the win32 copy and raised `-353703418 Unable to load DLL` in the win64 +one (BETA 1034). `--json` writes `ids.passed` and `ids.unrun`, which `recheck_callouts.mjs` judges +a `sample` item by. + ### `vb6run --docs` **`node scripts/vb6run.mjs --docs` compares the same fences with VB6.** It reads the diff --git a/WIP.md b/WIP.md index 5393eebbc..adb54a620 100644 --- a/WIP.md +++ b/WIP.md @@ -252,6 +252,8 @@ The vocabulary tables further down cover word choice. The rules in this subsecti **A callout says what it applies to in its label, the rest of its `[!TYPE]` line, and never in its text** (owner's rule): `> [!WARNING] BETA 1010` for a fact of that twinBASIC build, `> [!NOTE] since BETA 984` for one true from that build on, `> [!WARNING] 1.0.0.4` for one of that version of the page's own package, and `> [!NOTE] 1.6.151.1, BETA 1010` for both. `BETA` is upper case and a package version is bare, all four parts. The text then starts with the fact: never "In twinBASIC BETA 1010, ...", "BETA 1010 has a defect: ..." or "In version 1.0.0.4, ...". The build draws each item as a tag after the title and fails on anything else after `[!TYPE]`, and on a package version other than the section's `indexed_from` (`lib/callout-labels.mjs`). A label belongs on what is true of one build or one version, a defect above all; a callout that holds as well as that something true of every build is split in two. **In a contributed package's section every callout has a label** (owner's rule), the section's version for what the package does, so that moving the section to a new version sends each one back to be checked; the build refuses one without. **`> [!WARNING] timeless`** (owner's rule) marks a callout no new build or version can change: a fact of Windows or COM, behaviour the package exists to reproduce (the Scripting Runtime's, for an FSO replacement), or a design decision the sources state. It stands alone, draws no tag, and is never listed to check again; the package's test cases still cover what the package itself does. + **Every callout labelled `BETA ` names what checks it** (owner, 2026-11-03), in `` on the line directly above it or a region around it, the grammar a contributed section's markers already had (`scripts/lib/test-markers.mjs`): `bug `, `lane :`, `sample ` (a fence with that `id=`), `errors `, `llvm `, or `none -- ` when no program can check it. A fixed bug's correct behaviour goes into an inline test, a `check_run` sample or the error data, not into prose alone. A statement that needs a run whose code would tell the reader nothing is checked by a **`probe`** fence: a sample the reader never sees, with an `id=` a marker names, and `arch=win32,win64` when the 64-bit half matters (`Tools-Samples.md#probes`; the FAQ's msvbvm60 list is the first). The callouts of a generated Common Errors section need no marker. `check_callout_evidence.mjs` in `test.bat` refuses a labelled callout without one, an item naming what does not exist, and a probe no marker names; `recheck_callouts.mjs` runs the evidence on a new build (below). + 7. **See Also.** Last section on the page, after `Example`; only a contributed package's landing page has one after it, `## Test cases`. Format: `- [Symbol](Symbol) ` where `` is the kind: statement, function, property, method, class, module, package. Pages with annotations use `- [Symbol](Symbol) -- short description` — the `--` source renders as a typographic dash via markdown-it's typographer (en-dash for `--`, em-dash for `---`). Don't write literal `—` in source; keep `--` for consistency across the docs. Order by conceptual proximity, not strict alphabetical. 8. **Name the fault directly; never build up to it.** Setting up a contrast and then withholding the point is coy, and it makes the reader parse the sentence twice to extract one fact. *Avoid:* "double-clicking it is the obvious shortcut, and it is the one that misleads"; "the specificity trap --- which is the one thing that reliably catches people out". *Use:* "double-clicking it is obvious, and wrong"; "the specificity trap: a rule that loses it applies in light mode and silently does not in dark". Say what the thing is and what it does, in that order, in one clause. The same applies to "and that is the one that…", "which is precisely the…" and "therein lies the…". @@ -494,11 +496,15 @@ as it is. The rules: mode. The bump fails the build until each callout labelled with the old version is checked against the new one and its label moved, or the callout removed: `analyze` lists them as the package's `recheck_callout` tasks. -- **After a new twinBASIC build, check again every callout labelled with an older one.** An - `analyze` of the whole reference (no `--package`) lists the callouts labelled `BETA ` below - the build the store's built-in packages come from, and writes them to - `indexer/.packages/callouts.json`. Each is checked on the new build: its label moves to it, or - the callout goes (rule 6 under *Plain-English prose*). +- **After a new twinBASIC build, check again every callout labelled with an older one**, with + `node scripts/recheck_callouts.mjs` (`--plan` first; [Tools-Defects](docs/Documentation/Tools-Defects.md#recheck-callouts)). + It runs what each callout's marker names, each check once, one tool after another, moves the + label of each callout whose evidence all holds, and lists the rest: *changed* (read the + callout, then fix or remove it, rule 6 under *Plain-English prose*), *not checked* (run again + alone with `--from ` before believing it) and *by hand* (`none`). A full run takes + hours; `--kinds` and `--only` cut it. An `analyze` of the whole reference (no `--package`) also + lists the callouts labelled `BETA ` below the build the store's built-in packages come from, + in `indexer/.packages/callouts.json`. - **A change to `scripts/lib/twin-api.mjs`, `scripts/lib/twin-declarations.mjs`, `indexer/lib/api-model.mjs` or `indexer/lib/vb6-preprocess.mjs`, or to `format` in `api.json`, regenerates the committed snapshots in the same commit**, with `sync --ide` @@ -577,7 +583,7 @@ Why the report separates the wedged task from the merely blocked ones, and why - `build.bat` — runs `node builder\tbdocs.mjs --src docs --check-audit-index` (which implies `--check`) and produces three trees in one pass: the online copy at `_site/`, a `file://`-browsable copy at `_site-offline/`, and the sparse pagedjs source at `_site-pdf/`. The offline pass adds ~700 ms and the PDF pass adds ~150 ms on top of the ~2 s online build. Toggle `also_build_offline` / `also_build_pdf` in `_config.yml` (or pass `--no-offline` / `--no-pdf`) to skip a sibling output. `--check` adds ~1.7 s and runs the link + integrity check over the HTML while it is still in worker memory; `build.bat --no-check` gets a plain build. A build also writes the help add-in's archive, `add-in/Resources/HELP/site.zip` (see the help-archive bullet below); `--no-help-archive` skips it, and CI never passes it. - `serve.bat` — runs `tbdocs --serve`: initial build, then a long-lived process with watcher, debounced rebuilds, and SSE-driven browser auto-reload. Writes to `docs/_serve/` (disjoint from `build.bat`'s `_site*/`) and skips the offline + PDF passes — so a one-off `build.bat` for the PDF or offline mirror doesn't disturb the live preview. Ctrl+C to stop. - `check.bat` — the gates that read the built site: a freshness check that refuses a stale tree (`scripts/check_tree_fresh.mjs`), the DOT diagram fit check (`scripts/check_dot_fit.mjs`), the a11y sample-coverage check (`scripts/pick_a11y_sample.mjs --check`), then the accessibility check (`scripts/check_a11y.mjs`). The link + integrity check runs in `build.bat`. ~37 s. -- `test.bat` — the tests the *toolchain* has to pass: the publish-allowlist self-test (`scripts/check_publish_policy.mjs`), the gate-list check (`scripts/check_gate_lists.mjs`), the CI-workflow roster check (`scripts/check_ci_workflows.mjs`), the lint gate (`scripts/check_lint.mjs`), the site-search unit tests (`node --test test/search.test.mjs`), the split search-index build against lunr's own (`node --test test/search-index.test.mjs`), the markdown-plugin unit tests (`node --test test/render.test.mjs`), the date-formatter unit tests (`node --test test/strftime.test.mjs`), the bug-reproducer picture tests (`node --test test/png.test.mjs`), the embedded-image tests (`node --test test/compact-image.test.mjs`), the SVG screenshots' one-line test (`node --test test/svgshot.test.mjs`), the screenshot review tool's tests (`node --test test/shot-review.test.mjs`), `check_examples.mjs`'s probes (`node --test test/example-batches.test.mjs`), the IDE-folder unit tests (`node --test test/ide-folders.test.mjs`), the broker tests (`node --test test/broker.test.mjs`, skipped unless `TBBROKER_EXE` names a built broker), the add-in project-file tests (`node --test test/addin-project.test.mjs`), the package-indexer tests (`node --test test/indexer.test.mjs`), the IDE `main.js` tool's tests (`node --test test/ide-js.test.mjs`), the regex-safety gate (`scripts/check_regex_safety.mjs`), the code-region gate (`scripts/check_code_regions.mjs`), the page-count drift-guard probes (`scripts/check_page_baseline.mjs`), the book-coverage probes (`scripts/check_book_coverage.mjs`), the package-test markers and projects (`scripts/check_package_tests.mjs`), the symbol-index probes (`scripts/check_symbol_index.mjs`), the twinBASIC-scanner probes (`scripts/check_twin_parsers.mjs`), the attribute-sweep probes (`scripts/check_attribute_sweep.mjs`), the command-line probes and cases (`scripts/check_cli.mjs`), the pdf-lib shim comparison (`scripts/check_pdf_shims_equiv.mjs`), the impexp parity check (`scripts/check_impexp_parity.mjs`), and the axe source-patch verification (`scripts/check_axe_patch_equiv.mjs`). ~23 s, of which the regex-safety gate is ~10 s and the impexp check ~4 s. See [What belongs in test.bat rather than check.bat](WIP.Build.md#what-belongs-in-testbat-rather-than-checkbat). +- `test.bat` — the tests the *toolchain* has to pass: the publish-allowlist self-test (`scripts/check_publish_policy.mjs`), the gate-list check (`scripts/check_gate_lists.mjs`), the CI-workflow roster check (`scripts/check_ci_workflows.mjs`), the lint gate (`scripts/check_lint.mjs`), the site-search unit tests (`node --test test/search.test.mjs`), the split search-index build against lunr's own (`node --test test/search-index.test.mjs`), the markdown-plugin unit tests (`node --test test/render.test.mjs`), the date-formatter unit tests (`node --test test/strftime.test.mjs`), the bug-reproducer picture tests (`node --test test/png.test.mjs`), the embedded-image tests (`node --test test/compact-image.test.mjs`), the SVG screenshots' one-line test (`node --test test/svgshot.test.mjs`), the screenshot review tool's tests (`node --test test/shot-review.test.mjs`), `check_examples.mjs`'s probes (`node --test test/example-batches.test.mjs`), the IDE-folder unit tests (`node --test test/ide-folders.test.mjs`), the broker tests (`node --test test/broker.test.mjs`, skipped unless `TBBROKER_EXE` names a built broker), the add-in project-file tests (`node --test test/addin-project.test.mjs`), the package-indexer tests (`node --test test/indexer.test.mjs`), the IDE `main.js` tool's tests (`node --test test/ide-js.test.mjs`), the regex-safety gate (`scripts/check_regex_safety.mjs`), the code-region gate (`scripts/check_code_regions.mjs`), the page-count drift-guard probes (`scripts/check_page_baseline.mjs`), the book-coverage probes (`scripts/check_book_coverage.mjs`), the package-test markers and projects (`scripts/check_package_tests.mjs`), the evidence markers of labelled callouts (`scripts/check_callout_evidence.mjs`), the symbol-index probes (`scripts/check_symbol_index.mjs`), the twinBASIC-scanner probes (`scripts/check_twin_parsers.mjs`), the attribute-sweep probes (`scripts/check_attribute_sweep.mjs`), the command-line probes and cases (`scripts/check_cli.mjs`), the pdf-lib shim comparison (`scripts/check_pdf_shims_equiv.mjs`), the impexp parity check (`scripts/check_impexp_parity.mjs`), and the axe source-patch verification (`scripts/check_axe_patch_equiv.mjs`). ~23 s, of which the regex-safety gate is ~10 s and the impexp check ~4 s. See [What belongs in test.bat rather than check.bat](WIP.Build.md#what-belongs-in-testbat-rather-than-checkbat). - `book.bat` — renders the PDF from `docs\_site-pdf\book.html` via `node book\render-book.mjs` into `docs\_pdf\twinBASIC Book.pdf`. Run `build.bat` first to populate `_site-pdf/`; `book.bat` refuses a tree older than its sources rather than rendering the previous book (see [The book refuses a stale source tree](WIP.Build.md#the-book-refuses-a-stale-source-tree)). - `examples.bat` — compiles the documentation's own twinBASIC code samples, every `tb` fence marked `check_build`, and reports the ones the compiler refuses against the line in the page they came from; a statement sample also marked `check_run` is built and run, and what it prints is checked against the page. Needs a twinBASIC install and Windows, so it is outside every gate and outside CI; ~190 s over the 2,660 samples marked (1,076 of them `check_run`). `--build` also builds each project that compiles clean, and `--llvm` builds it with LLVM (see [WIP.ExamplesBuild.md](WIP.ExamplesBuild.md)). Two modes need no compiler at all: `--census` classifies every fence and says how many classifiable ones are still unmarked, and `--report ` groups a saved `--propose --json` survey by diagnostic, section and unresolved name. `--propose` itself does compile. See [Compiling the reference's own code samples](#compiling-the-references-own-code-samples) and [WIP.ExamplesBuild.md](WIP.ExamplesBuild.md). @@ -591,6 +597,7 @@ Why the report separates the wedged task from the merely blocked ones, and why - `node scripts/bug_repro.mjs` --- the reproducer projects under `bugs//` for the entries of [BUGS-TO-REPORT.md](BUGS-TO-REPORT.md): `new`, `pack` (impexp, then the zip, written in Node), `compile`, `build` and `run` through `tbbuild` and `tbrun`, `vb6` (builds the optional `vb6/` VB6 project beside the twinBASIC one in a temp copy, runs its exe on a private desktop, and prints its `out.txt`; a project with a form or a user control is built without Unattended Execution, which VB6 refuses for it; `pack` zips its sources into `-vb6.zip`; VB6 is started only by `scripts/lib/vb6.mjs`, never from a shell; a graphical defect's `images` key makes `run` and `vb6` keep what the `PngDump` modules of `test/repro-templates/png/` draw as `images/-tb.png` and `-vb6.png`, with a `-compare.png` of both, tested by `test/png.test.mjs`), `file` (moves a filed entry and its reproducer to `bugs/filed//`; `file --marked` does every marked entry), and `verify`, which reads each `bugs/*/repro.json` and `bugs/filed/*/repro.json` and says whether the bug still reproduces on the newest beta; a `lane` reproducer names the `ide-test` or `addin-test` lane and tests that assert its bug, which `verify` runs last through that runner, a `probe` one a script under `scripts/`, and a `test` one its own `[TestCase]` Subs and compile cases through `tbrun --tests`, judged by the `PASS` lines of what is fixed and the `FAIL` lines of the bug (`new --template tests` starts one). **No reproducer should need a person at the IDE** (owner): a bug the IDE has to be operated for gets a lane test, and `manual` is left for what cannot be automated at all. It has no wrapper. **`verify` is run by a person only**, never by a gate or CI: it needs a twinBASIC install, like `examples.bat`. Each `tbbuild` and `tbrun` it starts takes a slot of its own from `tbbroker`; `--jobs N` runs N at once. Its command-line cases are in `scripts/lib/cli-cases.mjs`, and need no IDE. - `node scripts/vb6run.mjs ` and `node scripts/vb6run.mjs --docs [--only ]` --- build and run VB6 code, so that what a sample prints in twinBASIC can be compared with VB6: a stand-alone file of statements or a `.bas` module, or `--docs`, which builds the documentation's `check_run` fences in VB6 and says for each whether it prints what its page says (`same`, `differs`, `not VB6`, `error`, `refused`; a `projname=` group is built as a project of its own, its `slot=file` fences translated into VB6 `.cls` and `.bas` components). It has no wrapper and is outside every gate and outside CI: it needs VB6 (`--vb6`, `VB6_EXE`, or the standard install folder). **Never start `VB6.EXE` from a shell**: in Git Bash `/make` is rewritten as a path and VB6 answers with a modal box on the desktop; the tool spawns it from Node with an argument array, and a sample that calls `MsgBox` or `InputBox` or contains `End` is refused. Its `Debug.Print` rewrite and its file translation have probes in `test/example-batches.test.mjs`, and its command-line cases are in `scripts/lib/cli-cases.mjs`; both need no VB6. What it relies on is in [WIP.ExamplesBuild.md](WIP.ExamplesBuild.md). - `node scripts/errors.mjs run|generate|write` --- the run-time errors of the documented procedures, as data under `errors/` ([WIP.Errors.md](WIP.Errors.md), [Tools-Defects](docs/Documentation/Tools-Defects.md#errorsmjs)): `run` builds and runs a probe of the cases in twinBASIC 32-bit and 64-bit (`tbrun --exe`), VB6 and 64-bit VBA (in 64-bit Access, `scripts/lib/vba.mjs`) and compares each with the data, `write` writes each page's Errors section. Its gate is `check_errors.mjs`; `run` is by hand, never CI. +- `node scripts/recheck_callouts.mjs [--plan] [--kinds ] [--only ] [--dry-run] [--report ] [--from ]` --- after a new BETA, checks again every callout labelled with an older one by running what its marker names (`ide_test`/`addin_test` once per suite, judging lane reproducers from the same report through `scripts/lib/lane-report.mjs`, `bug_repro verify` for the other reproducers, `check_examples --json` for samples, `errors.mjs run` for tb and win64, `test/llvm/run.mjs --only`, `package_tests --json`), relabels what holds (a data file's `beta:` too, then `errors.mjs write`), and lists the rest. What it reads and decides is `scripts/lib/recheck.mjs`, probed by `check_callout_evidence.mjs`; every tool's output stays in `%TEMP%\recheck-callouts\\`. By hand, never CI. - `node scripts/ide_js.mjs ` --- reads, names and patches the IDE's minified `ide/main.js` ([Tools-Defects](docs/Documentation/Tools-Defects.md#ide_jsmjs)): `survey`, `units`, `list`, `name`, `seal`, `check`, `view`, `patch` and `shape`. It reads files and starts nothing; the rules for it are under [Driving the twinBASIC compiler](#driving-the-twinbasic-compiler) (*Fixing the IDE itself*). It has no wrapper and is outside every gate and CI, since it needs an install; its tests are `test/ide-js.test.mjs`, in `test.bat`, and its command-line cases are in `scripts/lib/cli-cases.mjs`. **After a new BETA, run `check` on it**, and `seal` the map once each `review` and `lost` name has been looked at. Three generators sit outside that loop and produce committed artifacts rather than build output — none runs during a build, and none is needed for one. `python scripts/build_fonts.py` rebuilds the subset webfaces under `docs/assets/fonts/` and needs a network connection; `node scripts/build_dot_metrics.mjs` regenerates `builder/inter-metrics.json` from those webfaces and needs only a browser. See [Typography](#typography). `node indexer/twin-index.mjs derive` regenerates `builder/package-api.json`, the packages' declared API that the build's symbol index (`tB/symbols.json`, for the IDE help add-in) is annotated from; it needs a twinBASIC install (or `--release`), so **run it when the reference is re-indexed against a newer build** and commit it with the pages and the snapshots. See [WIP.HelpAddin.md, Stage 3](WIP.HelpAddin.md#stage-3-the-symbol-index-generated-by-the-docs-build). @@ -605,7 +612,7 @@ build.bat && check.bat On the dev box that is ~4 s of build against ~37 s of check, of which the axe scan is ~20 s. [builder/PLAN-checks.md](builder/PLAN-checks.md) records how the link checker sits in the build's task graph and its cost; the axe follow-ons are designed there but not implemented. -**If the change touched `builder/`, `scripts/`, `lib/`, `book/`, `eval/`, `wisdom/`, `indexer/`, `test/`, `add-in/`, the site's scripts in `docs/assets/js/`, a wrapper or a workflow, run `test.bat` as well** --- another ~23 s. Twenty-six of its thirty-one gates cannot be affected by an edit under `docs/` at all. **Five can.** `check_errors.mjs` compares the reference pages' Common Errors sections with the data under `errors/`, so edit the data and run `node scripts/errors.mjs write`, never the section. `check_package_tests.mjs` reads the callouts of the contributed packages' sections. `check_lint.mjs` lints the site's two scripts in `docs/assets/js/` along with the tooling. `check_gate_lists.mjs` reads `README.md` and every page under `docs/Documentation/`, so an edit to any developer page that states a gate count can fail it. **`check_code_regions.mjs` has two halves, and a content edit reaches only one.** Its corpus sweep reads `DOCS_DIR`, which is `/docs`, and tokenises every markdown file in it, so a page that provokes a rewrite into *altering* a code region fails it --- that half is content-dependent. Its fixed probes are not: they run against their own sources whatever the tree holds, and they cover the **mirror** fault, where a rewrite silently stops firing. The sweep cannot see that fault, because text the rewrite skipped is masked and restored unchanged and every region still matches. So run `test.bat` after adding an unusual code construct --- a fence whose contents include a fence marker, a 4-space indented block, an admonition wrapping a fence --- and read the built page as well, because for the mirror fault the gate is asserting that the mask and the admonition scan still work rather than checking your page: +**If the change touched `builder/`, `scripts/`, `lib/`, `book/`, `eval/`, `wisdom/`, `indexer/`, `test/`, `add-in/`, the site's scripts in `docs/assets/js/`, a wrapper or a workflow, run `test.bat` as well** --- another ~23 s. Twenty-six of its thirty-two gates cannot be affected by an edit under `docs/` at all. **Six can.** `check_errors.mjs` compares the reference pages' Common Errors sections with the data under `errors/`, so edit the data and run `node scripts/errors.mjs write`, never the section. `check_package_tests.mjs` reads the callouts of the contributed packages' sections. `check_callout_evidence.mjs` reads every labelled callout and every marker, so a new callout labelled `BETA ` without its `` fails it. `check_lint.mjs` lints the site's two scripts in `docs/assets/js/` along with the tooling. `check_gate_lists.mjs` reads `README.md` and every page under `docs/Documentation/`, so an edit to any developer page that states a gate count can fail it. **`check_code_regions.mjs` has two halves, and a content edit reaches only one.** Its corpus sweep reads `DOCS_DIR`, which is `/docs`, and tokenises every markdown file in it, so a page that provokes a rewrite into *altering* a code region fails it --- that half is content-dependent. Its fixed probes are not: they run against their own sources whatever the tree holds, and they cover the **mirror** fault, where a rewrite silently stops firing. The sweep cannot see that fault, because text the rewrite skipped is masked and restored unchanged and every region still matches. So run `test.bat` after adding an unusual code construct --- a fence whose contents include a fence marker, a 4-space indented block, an admonition wrapping a fence --- and read the built page as well, because for the mirror fault the gate is asserting that the mask and the admonition scan still work rather than checking your page: ```sh build.bat && check.bat && test.bat @@ -652,6 +659,7 @@ wrapper: | `test.bat` | `check_code_regions` | no source or HTML rewrite altered a code region, and the rewrites over rendered HTML leave a raw `
` or `` as written; a rewrite still fires on prose beside a fence (the mirror fault); `lib/markdown.mjs` and `lib/frontmatter.mjs` pass their probes, and the block parse finds what the full parse finds; the count-name check skips code and names the file's line; `discover` warns about an unquoted frontmatter value that ends in `#`; the dash normaliser converts only prose |
 | `test.bat` | `check_regex_safety` | no regex in the tree can backtrack exponentially |
 | `test.bat` | `check_package_tests` | every NOTE and WARNING of a contributed section with a test project names its cases in a marker, every case is named, starts with `Expect.Begin` and ends with `Expect.Done`, `expect.json` is for the documented version, `RunAll.twin` is current; a section without a project is in `PENDING`, which only shrinks |
+| `test.bat` | `check_callout_evidence` | every callout labelled `BETA ` outside a generated errors section names what checks it in a marker or a region; every marker's item names a reproducer, a lane, one fence built as a sample with that `id=`, a file of `errors/` or a mode of the LLVM probe; no page outside a contributed section names a package case, and none has the old `` comment; every `probe` fence is named by a marker; and `scripts/lib/recheck.mjs` reads each tool's report and relabels as it should |
 | `test.bat` | `check_symbol_index` | the symbol index still places each kind of symbol, from fixtures |
 | `test.bat` | `check_twin_parsers` | every word of the shared modifier list reaches all three scanners of twinBASIC source; the census's declaration kinds and `parseTargets`' targets hold for the shapes each is known to misread |
 | `test.bat` | `check_attribute_sweep` | the sweep's site skeletons render and are each in a family of `Applicable to:` targets or listed as in none; its reading of a probe's diagnostics puts the refusals, the control's fold and a skeleton's own errors in the right states; every real `Applicable to:` line reads to its pinned targets; probes batch once each and never two of a once-per-project attribute; a cell missing a form is not reported refused; and the isolating runner, against a scripted fake in place of the IDE, finds a crash, a hang, a disturbed canary or a stray row and stops at each cap |
diff --git a/builder/render.mjs b/builder/render.mjs
index 91755c0ce..e14a526dd 100644
--- a/builder/render.mjs
+++ b/builder/render.mjs
@@ -340,13 +340,13 @@ export function createMarkdownIt(ctx) {
     highlight: ctx.highlighter ? (code, lang) => ctx.highlighter.render(code, lang) : undefined,
   });
 
-  // `hidden` as a whitespace-separated token anywhere after the language.
-  // Matched against the raw info string rather than parsed with
+  // `hidden` or `probe` as a whitespace-separated token anywhere after the
+  // language. Matched against the raw info string rather than parsed with
   // scripts/lib/tb-fences.mjs's parseInfo: builder/ must not depend on
-  // scripts/, and this is the one token of that markup the renderer cares
+  // scripts/, and these are the two tokens of that markup the renderer cares
   // about. The word has to stand alone, or a language called `hidden-x` or a
   // key like `id=hidden` would suppress a fence the author meant to publish.
-  const HIDDEN_FENCE_RE = /^\S+(?:\s+\S+)*?\s+hidden(?:\s|$)/;
+  const HIDDEN_FENCE_RE = /^\S+(?:\s+\S+)*?\s+(?:hidden|probe)(?:\s|$)/;
 
   // Override the fence renderer so our highlight callback's wrapper HTML
   // (which starts with ) is used verbatim. Without this,
@@ -364,9 +364,11 @@ export function createMarkdownIt(ctx) {
     // A fence marked `hidden` is context for the compile gate, not content:
     // scripts/check_examples.mjs builds it with the page's other samples so
     // that the declarations a sample assumes can live beside it rather than in
-    // a shared template, and the reader never sees it. Emitting nothing here
-    // keeps it out of the HTML, and therefore out of the search index, the
-    // offline mirror and the PDF book, all of which read the rendered string.
+    // a shared template, and the reader never sees it. A fence marked `probe`
+    // is a run the gate checks a statement of the page with, and the reader
+    // never sees it either. Emitting nothing here keeps both out of the HTML,
+    // and therefore out of the search index, the offline mirror and the PDF
+    // book, all of which read the rendered string.
     if (HIDDEN_FENCE_RE.test(tok.info ?? "")) return "";
     const html = options.highlight(tok.content, lang);
     if (tok.level > 0 || tok.meta?.nestedInBlock) {
diff --git a/docs/Documentation/Authoring.md b/docs/Documentation/Authoring.md
index 78dbe605b..8ad1cb42d 100644
--- a/docs/Documentation/Authoring.md
+++ b/docs/Documentation/Authoring.md
@@ -496,6 +496,9 @@ generated `Sub`. These keys override it when it guesses wrong:
 | `check_build` | Compile this sample. |
 | `check_run` | Compile it, build it and run it, then compare what it prints with what the page says it prints, as described below. Only a statement sample (`slot=sub`) can be run. A sample that raises an error, does not return, or prints something else is a finding. A sample that calls `MsgBox` or `InputBox`, or contains an `End` statement, is refused without being built. |
 | `hidden` | Context for the page's other samples that the reader never sees. Implies `check_build`. See below. |
+| `probe` | A sample of its own that the reader never sees, which checks a statement the page makes. Implies `check_build`; add `check_run` to run it. It needs an `id=`. See below. |
+| `id=` | A name for the sample that stays when the page changes, which a [marker](#a-callout-names-what-checks-it) names it by. |
+| `arch=win32,win64` | Run a `check_run` sample in a 32-bit and in a 64-bit project, or with `arch=win64` in a 64-bit one only. Without it a sample runs in a 32-bit project. Not for a member of a `projname` or `concat_group` group. |
 | `slot=file` / `slot=module` / `slot=sub` / `slot=class` / `slot=method` | What to generate around it, when the inference is wrong. `class` and `method` are the same two shapes inside a `Class` rather than a `Module`, for code-behind. The report names the slot it used. |
 | `inherits=` | The sample is code-behind *of* something --- `inherits=Form`, `inherits=MDIForm`. The wrapper becomes a `Class` that inherits it, so `Me.Caption` resolves against the real type. |
 | `project=` | Which template project to build into. The default follows the page: a page under `Reference/Built-In/` gets the one that references every package. `project=form` is for a `check_run` sample that needs a real form, to draw on or to read a property of: the project has an empty `Form1`, and the checker unloads every form a sample leaves loaded. Every fence of a `projname` group names the same project. |
@@ -628,6 +631,39 @@ event handler's name is built from. Hide what the reader brings from elsewhere:
 helper routines, the controls the form designer declares, and stand-ins for declarations
 that an external library provides.
 
+### A statement checked by a sample nobody sees
+
+A fence marked `probe` is rendered to nothing, as a `hidden` one is, but it is a sample of
+its own: it is compiled, and with `check_run` run, and judged like any other sample. It
+checks a statement the page makes in prose or in a callout, when the code that checks it
+would tell the reader nothing the statement does not. The FAQ says a `Declare` of
+msvbvm60's `GetMem4` works in a 64-bit build; a probe declares it and calls it in both
+architectures:
+
+````markdown
+
+A `Declare` of msvbvm60's `GetMem4` works in a 64-bit build too.
+
+
+```tb hidden slot=module
+Public Declare PtrSafe Sub GetMem4 Lib "msvbvm60" (ByVal Addr As LongPtr, RetVal As Long)
+```
+
+```tb probe check_run id=msvbvm60-getmem4 arch=win32,win64
+Dim l As Long, r As Long
+l = 7
+GetMem4 VarPtr(l), r
+Debug.Print r      ' 7
+```
+````
+
+A probe has an `id=`, and the marker around the statement names it, as
+[A callout names what checks it](#a-callout-names-what-checks-it) describes: `test.bat`
+fails on a probe no marker names, since a check nobody can see, tied to nothing a reader
+can see, checks nothing. A probe that must not compile takes `expect-error=` as any sample
+does, and one that must compile can turn a warning into an error with `[EnforceErrors]`,
+so that the warning, if it is drawn, fails the probe.
+
 ### One construct across several fences
 
 `concat_group=` joins every fence on the page that carries the name, in page order,
@@ -912,6 +948,29 @@ The text then starts with the fact itself, not with "In twinBASIC BETA 1005, ...
 
 A page states what the product does, never what the documentation's own checking could or could not do. "The samples harness cannot capture a console" is a fact about this repository's tools, not about the package, and has no place in a published page. A claim nobody has been able to check is written as the source supports it, or left out.
 
+### A callout names what checks it
+
+A callout labelled `BETA ` names, in an HTML comment on the line above it, the program that checks what it says, so that the claim can be checked again on the next build without anyone remembering how it was checked the first time:
+
+```md
+
+> [!WARNING] BETA 1034
+> A **Variant** *stringvar* that holds a **Boolean** is aligned within `-1` or `0` ...
+```
+
+The marker goes on the line directly above the callout, with no blank line between, and marks that callout alone. The same comment anywhere else opens a region, which `` closes: the paragraphs and callouts between them are what its items check. Regions do not nest. Each item, separated by commas, is one of:
+
+| Item | Names |
+|---|---|
+| `bug ` | The reproducer of a twinBASIC defect, under `bugs//` or `bugs/filed//`. It holds while the defect reproduces. |
+| `lane :` | A lane of the IDE tests (`ide`) or of the add-in tests (`addin`). It holds while every test of the lane passes. |
+| `sample ` | The sample with that `id=` under `docs/`: a `check_run` sample, a [probe](#a-statement-checked-by-a-sample-nobody-sees), or one that is only compiled. It holds while `examples.bat` finds nothing wrong with it. |
+| `errors ` | The run-time error data `errors/.yml`. |
+| `llvm ` | A mode of the LLVM claims probe, `test/llvm/run.mjs`. |
+| `none -- ` | Nothing a program can check. A person checks it again on each new build. |
+
+A contributed package's section names its own test cases too, as the next section says. The callouts of a generated Common Errors section need no marker: their data is what checks them. [`check_callout_evidence.mjs`](Tools-Toolchain-Tests#check-callout-evidence), in `test.bat`, fails a callout labelled `BETA ` with neither a marker nor a region around it, and an item that names nothing that exists. After a new build, [`recheck_callouts.mjs`](Tools-Defects#recheck-callouts) runs what each marker names, moves the label of each callout whose evidence still holds to the new build, and lists the rest to be read again.
+
 ### Callouts in a contributed package's section
 
 Every `> [!NOTE]` and `> [!WARNING]` in a contributed package's section is backed by test cases in the package's test project, `test/package-tests//`, and names them in an HTML comment on the line above it:
@@ -919,7 +978,7 @@ Every `> [!NOTE]` and `> [!WARNING]` in a contributed package's section is backe
     
     > [!NOTE]
 
-An item is a `Fixture.Sub` test case, `compile ` for a compile case, `probe ` for a scenario of the package's `probe/` harness, or `none -- ` for a callout no program can check. A case checks what is correct, so a defect of the package is a failing case.
+An item is a `Fixture.Sub` test case, `compile ` for a compile case, `probe ` for a scenario of the package's `probe/` harness, or `none -- ` for a callout no program can check. A marker there may also name an item of the table above, such as the reproducer of a defect of twinBASIC that a callout labelled `BETA ` describes. A case checks what is correct, so a defect of the package is a failing case.
 
 A callout is for a real difference or limit. A statement that only describes how the package works stays prose, and its cases are tied to it by a region: the same marker on a line of its own above the prose, and `` after it.
 
diff --git a/docs/Documentation/Building.md b/docs/Documentation/Building.md
index f0faed366..a7d457b9e 100644
--- a/docs/Documentation/Building.md
+++ b/docs/Documentation/Building.md
@@ -91,6 +91,7 @@ Do not drop `--check-audit-index` when typing the underlying build command: with
       && node scripts/check_page_baseline.mjs \
       && node scripts/check_book_coverage.mjs \
       && node scripts/check_package_tests.mjs \
+      && node scripts/check_callout_evidence.mjs \
       && node scripts/check_errors.mjs \
       && node scripts/check_symbol_index.mjs \
       && node scripts/check_twin_parsers.mjs \
diff --git a/docs/Documentation/Images/Tools-Groups.dot b/docs/Documentation/Images/Tools-Groups.dot
index ebec87dfc..2600cdb5f 100644
--- a/docs/Documentation/Images/Tools-Groups.dot
+++ b/docs/Documentation/Images/Tools-Groups.dot
@@ -135,9 +135,9 @@ digraph tools_groups {
         t23 [label="check_impexp_parity"]  t24 [label="check_axe_patch_equiv"]
         t25 [label="indexer.test"]          t26 [label="check_package_tests"]
         t27 [label="broker.test"]           t28 [label="ide-js.test"]
-        t29 [label="shot-review.test"]
+        t29 [label="shot-review.test"]      t30 [label="check_callout_evidence"]
         t1 -> t5 -> t9  -> t13 -> t17 -> t21 -> t25 -> t29
-        t2 -> t6 -> t10 -> t14 -> t18 -> t22 -> t26
+        t2 -> t6 -> t10 -> t14 -> t18 -> t22 -> t26 -> t30
         t3 -> t7 -> t11 -> t15 -> t19 -> t23 -> t27
         t4 -> t8 -> t12 -> t16 -> t20 -> t24 -> t28
         { rank=same; t5  t6  t7  t8  }
@@ -146,6 +146,7 @@ digraph tools_groups {
         { rank=same; t17 t18 t19 t20 }
         { rank=same; t21 t22 t23 t24 }
         { rank=same; t25 t26 t27 t28 }
+        { rank=same; t29 t30 }
     }
 
     // ---- row 4 ----
@@ -178,8 +179,9 @@ digraph tools_groups {
         label="Defect Reproducers"
         d1 [label="bug_repro"]
         d2 [label="vb6run"]
-        d3 [label="ide_js"]
-        d1 -> d2 -> d3
+        d3 [label="recheck_callouts"]
+        d4 [label="ide_js"]
+        d1 -> d2 -> d3 -> d4
     }
 
     // ---- rows ----
@@ -358,6 +360,8 @@ digraph tools_groups {
          tooltip="Runs unit tests for ide_js.mjs: the byte-exact round trip of a minified file, how a local's name carries to another build, and a fix patched in as the smallest edit."]
     t29 [URL="Tools-Toolchain-Tests#shot-review-test"
          tooltip="Runs unit tests for the screenshot review tool: its session, its pages, its writes under docs, and its server with a stand-in run and a commit in a scratch repository."]
+    t30 [URL="Tools-Toolchain-Tests#check-callout-evidence"
+         tooltip="Verifies that every callout labelled with a twinBASIC build names what checks it, and that every marker names a reproducer, lane, sample, error data file or LLVM probe mode that exists."]
 
     e1 [URL="Tools-Samples#check-examples"
         tooltip="Compiles the code samples marked check_build and reports each diagnostic against the page line it came from. Samples marked check_run are also run."]
@@ -393,6 +397,8 @@ digraph tools_groups {
         tooltip="Builds, runs and verifies the reproducer projects for the entries of BUGS-TO-REPORT.md, one folder for each entry."]
     d2 [URL="Tools-Defects#vb6run"
         tooltip="Builds and runs VB6 code, to compare what a documented sample prints in twinBASIC with what it prints in VB6."]
-    d3 [URL="Tools-Defects#ide-js"
+    d3 [URL="Tools-Defects#recheck-callouts"
+        tooltip="Checks again on a new twinBASIC build every callout labelled with an older one: runs what its marker names, moves the label of each that holds and lists the rest."]
+    d4 [URL="Tools-Defects#ide-js"
         tooltip="Reads, names and patches the minified JavaScript of the twinBASIC IDE, to test a fix for an IDE defect on a patched copy of the install and write its fix shape."]
 }
diff --git a/docs/Documentation/Images/Tools-Groups.svg b/docs/Documentation/Images/Tools-Groups.svg
index 41b2316bb..97ea5a708 100644
--- a/docs/Documentation/Images/Tools-Groups.svg
+++ b/docs/Documentation/Images/Tools-Groups.svg
@@ -1,71 +1,71 @@
 
 
-
-
+
+
 tools_groups
 
 cluster_wrappers
 
-
-Batch wrappers
+
+Batch wrappers
 
 
 
 
 cluster_build
 
-
-Build and Generation
+
+Build and Generation
 
 
 
 
 cluster_site
 
-
-Site Checks
+
+Site Checks
 
 
 
 
 cluster_compiler
 
-
-Compiler Harness
+
+Compiler Harness
 
 
 
 
 cluster_tests
 
-
-Toolchain Tests
+
+Toolchain Tests
 
 
 
-
+
 cluster_samples
-
-
-Code Samples and Attributes
+
+
+Code Samples and Attributes
 
 
 
-
+
 cluster_ide
-
-
-IDE Automation
+
+
+IDE Automation
 
 
 
-
+
 cluster_defects
-
-
-Defect Reproducers
+
+
+Defect Reproducers
 
 
 
@@ -73,8 +73,8 @@
 
 w1
 
-
-build.bat
+
+build.bat
 
 
 
@@ -82,8 +82,8 @@
 
 w2
 
-
-serve.bat
+
+serve.bat
 
 
 
@@ -91,8 +91,8 @@
 
 w3
 
-
-check.bat
+
+check.bat
 
 
 
@@ -100,8 +100,8 @@
 
 w4
 
-
-test.bat
+
+test.bat
 
 
 
@@ -109,8 +109,8 @@
 
 w5
 
-
-book.bat
+
+book.bat
 
 
 
@@ -118,8 +118,8 @@
 
 w6
 
-
-examples.bat
+
+examples.bat
 
 
 
@@ -127,8 +127,8 @@
 
 w7
 
-
-addin-test.bat
+
+addin-test.bat
 
 
 
@@ -136,8 +136,8 @@
 
 w8
 
-
-ide-test.bat
+
+ide-test.bat
 
 
 
@@ -145,8 +145,8 @@
 
 w9
 
-
-try-help-addin.bat
+
+try-help-addin.bat
 
 
 
@@ -154,8 +154,8 @@
 
 w10
 
-
-package-tests.bat
+
+package-tests.bat
 
 
 
@@ -163,8 +163,8 @@
 
 b1
 
-
-tbdocs
+
+tbdocs
 
 
 
@@ -172,8 +172,8 @@
 
 b2
 
-
-render-book
+
+render-book
 
 
 
@@ -181,8 +181,8 @@
 
 b3
 
-
-compare_trees
+
+compare_trees
 
 
 
@@ -190,8 +190,8 @@
 
 b4
 
-
-survey_tooling
+
+survey_tooling
 
 
 
@@ -199,8 +199,8 @@
 
 b5
 
-
-convert_em_dash_separators
+
+convert_em_dash_separators
 
 
 
@@ -208,8 +208,8 @@
 
 b6
 
-
-build_fonts.py
+
+build_fonts.py
 
 
 
@@ -217,8 +217,8 @@
 
 b7
 
-
-build_dot_metrics
+
+build_dot_metrics
 
 
 
@@ -226,8 +226,8 @@
 
 b8
 
-
-twin-index
+
+twin-index
 
 
 
@@ -235,8 +235,8 @@
 
 s1
 
-
-check_links
+
+check_links
 
 
 
@@ -244,8 +244,8 @@
 
 s2
 
-
-crawl_check
+
+crawl_check
 
 
 
@@ -253,8 +253,8 @@
 
 s3
 
-
-check_links_diff
+
+check_links_diff
 
 
 
@@ -262,8 +262,8 @@
 
 s4
 
-
-check_tree_fresh
+
+check_tree_fresh
 
 
 
@@ -271,8 +271,8 @@
 
 s5
 
-
-check_dot_fit
+
+check_dot_fit
 
 
 
@@ -280,8 +280,8 @@
 
 s6
 
-
-check_a11y
+
+check_a11y
 
 
 
@@ -289,8 +289,8 @@
 
 s7
 
-
-pick_a11y_sample
+
+pick_a11y_sample
 
 
 
@@ -298,8 +298,8 @@
 
 s8
 
-
-axe-scan
+
+axe-scan
 
 
 
@@ -307,8 +307,8 @@
 
 s9
 
-
-check_a11y_fingerprint
+
+check_a11y_fingerprint
 
 
 
@@ -316,8 +316,8 @@
 
 s10
 
-
-sweep_a11y
+
+sweep_a11y
 
 
 
@@ -325,8 +325,8 @@
 
 c1
 
-
-tbbuild
+
+tbbuild
 
 
 
@@ -334,8 +334,8 @@
 
 c2
 
-
-tbrun
+
+tbrun
 
 
 
@@ -343,8 +343,8 @@
 
 c3
 
-
-impexp
+
+impexp
 
 
 
@@ -352,8 +352,8 @@
 
 c4
 
-
-check_tb_registry
+
+check_tb_registry
 
 
 
@@ -361,8 +361,8 @@
 
 c7
 
-
-tbbroker
+
+tbbroker
 
 
 
@@ -370,8 +370,8 @@
 
 c6
 
-
-probe_build_twice
+
+probe_build_twice
 
 
 
@@ -380,8 +380,8 @@
 
 t1
 
-
-check_publish_policy
+
+check_publish_policy
 
 
 
@@ -389,8 +389,8 @@
 
 t2
 
-
-check_regex_safety
+
+check_regex_safety
 
 
 
@@ -398,8 +398,8 @@
 
 t3
 
-
-check_code_regions
+
+check_code_regions
 
 
 
@@ -407,8 +407,8 @@
 
 t4
 
-
-check_gate_lists
+
+check_gate_lists
 
 
 
@@ -416,8 +416,8 @@
 
 t5
 
-
-check_ci_workflows
+
+check_ci_workflows
 
 
 
@@ -425,8 +425,8 @@
 
 t6
 
-
-check_lint
+
+check_lint
 
 
 
@@ -434,8 +434,8 @@
 
 t7
 
-
-search.test
+
+search.test
 
 
 
@@ -443,8 +443,8 @@
 
 t8
 
-
-render.test
+
+render.test
 
 
 
@@ -452,8 +452,8 @@
 
 t9
 
-
-strftime.test
+
+strftime.test
 
 
 
@@ -461,8 +461,8 @@
 
 t10
 
-
-png.test
+
+png.test
 
 
 
@@ -470,8 +470,8 @@
 
 t11
 
-
-compact-image.test
+
+compact-image.test
 
 
 
@@ -479,8 +479,8 @@
 
 t12
 
-
-svgshot.test
+
+svgshot.test
 
 
 
@@ -488,8 +488,8 @@
 
 t13
 
-
-example-batches.test
+
+example-batches.test
 
 
 
@@ -497,8 +497,8 @@
 
 t14
 
-
-ide-folders.test
+
+ide-folders.test
 
 
 
@@ -506,8 +506,8 @@
 
 t15
 
-
-addin-project.test
+
+addin-project.test
 
 
 
@@ -515,8 +515,8 @@
 
 t16
 
-
-check_page_baseline
+
+check_page_baseline
 
 
 
@@ -524,8 +524,8 @@
 
 t17
 
-
-check_book_coverage
+
+check_book_coverage
 
 
 
@@ -533,8 +533,8 @@
 
 t18
 
-
-check_symbol_index
+
+check_symbol_index
 
 
 
@@ -542,8 +542,8 @@
 
 t19
 
-
-check_twin_parsers
+
+check_twin_parsers
 
 
 
@@ -551,8 +551,8 @@
 
 t20
 
-
-check_attribute_sweep
+
+check_attribute_sweep
 
 
 
@@ -560,8 +560,8 @@
 
 t21
 
-
-check_cli
+
+check_cli
 
 
 
@@ -569,8 +569,8 @@
 
 t22
 
-
-check_pdf_shims_equiv
+
+check_pdf_shims_equiv
 
 
 
@@ -578,8 +578,8 @@
 
 t23
 
-
-check_impexp_parity
+
+check_impexp_parity
 
 
 
@@ -587,8 +587,8 @@
 
 t24
 
-
-check_axe_patch_equiv
+
+check_axe_patch_equiv
 
 
 
@@ -596,8 +596,8 @@
 
 t25
 
-
-indexer.test
+
+indexer.test
 
 
 
@@ -605,8 +605,8 @@
 
 t26
 
-
-check_package_tests
+
+check_package_tests
 
 
 
@@ -614,8 +614,8 @@
 
 t27
 
-
-broker.test
+
+broker.test
 
 
 
@@ -623,8 +623,8 @@
 
 t28
 
-
-ide-js.test
+
+ide-js.test
 
 
 
@@ -632,161 +632,179 @@
 
 t29
 
-
-shot-review.test
+
+shot-review.test
 
 
 
-
+
 
+t30
+
+
+check_callout_evidence
+
+
+
+
+
 e1
-
-
-check_examples
+
+
+check_examples
 
 
 
 
-
+
 e2
-
-
-gen_attribute_probes
+
+
+gen_attribute_probes
 
 
 
 
-
+
 e3
-
-
-census_attributes
+
+
+census_attributes
 
 
 
 
-
+
 e4
-
-
-sweep_attributes
+
+
+sweep_attributes
 
 
 
 
-
+
 e5
-
-
-package_tests
+
+
+package_tests
 
 
 
 
-
+
 i1
-
-
-addin_test
+
+
+addin_test
 
 
 
 
-
+
 i2
-
-
-ide_test
+
+
+ide_test
 
 
 
 
-
+
 i3
-
-
-try_help_addin
+
+
+try_help_addin
 
 
 
 
-
+
 i4
-
-
-shoot_docs
+
+
+shoot_docs
 
 
 
 
-
+
 i5
-
-
-imagestrip
+
+
+imagestrip
 
 
 
 
-
+
 i8
-
-
-probe_theme_semicolon
+
+
+probe_theme_semicolon
 
 
 
 
-
+
 i7
-
-
-svgshot/replay
+
+
+svgshot/replay
 
 
 
 
-
+
 i9
-
-
-svgshot/sheet
+
+
+svgshot/sheet
 
 
 
 
-
+
 i10
-
-
-shot_review
+
+
+shot_review
 
 
 
 
-
+
 d1
-
-
-bug_repro
+
+
+bug_repro
 
 
 
 
-
+
 d2
-
-
-vb6run
+
+
+vb6run
 
 
 
 
-
+
 d3
-
-
-ide_js
+
+
+recheck_callouts
+
+
+
+
+
+d4
+
+
+ide_js
 
 
 
@@ -848,6 +866,7 @@
 
 
 
+
 
 
 
@@ -862,5 +881,6 @@
 
 
 
+
 
 
diff --git a/docs/Documentation/Tools-Defects.md b/docs/Documentation/Tools-Defects.md
index bd9b07d34..d2e6b6ed5 100644
--- a/docs/Documentation/Tools-Defects.md
+++ b/docs/Documentation/Tools-Defects.md
@@ -9,7 +9,7 @@ permalink: /Documentation/Development/Tools-Defects
 # Defect Reproducers
 {: .no_toc }
 
-Builds and verifies the reproducer projects for twinBASIC defects, runs VB6 code to compare what twinBASIC does with what VB6 does, and reads and patches the IDE's own JavaScript to test a fix for an IDE defect. The command-line conventions every tool shares are under [Command-line conventions](Tools#command-line-conventions).
+Builds and verifies the reproducer projects for twinBASIC defects, runs VB6 code to compare what twinBASIC does with what VB6 does, checks again on a new build every callout labelled with an older one, and reads and patches the IDE's own JavaScript to test a fix for an IDE defect. The command-line conventions every tool shares are under [Command-line conventions](Tools#command-line-conventions).
 
 * TOC goes here
 {:toc}
@@ -170,6 +170,40 @@ Measures the run-time errors each documented procedure raises, and writes each r
 
 Exit codes: **0** `run`, `messages`: every runtime agrees with the data; `generate`, `write`: done; **1** `run`, `messages`: a runtime disagrees with the data; **2** a refused command line, data with a problem, a runtime that could not be run or a probe that ended early, or a crash.
 
+## recheck_callouts.mjs
+{: #recheck-callouts }
+
+    node scripts/recheck_callouts.mjs [--only ] [--kinds ] [--ide ] [--plan] [--dry-run] [--report ] [--from ]
+
+Checks again, on a new twinBASIC build, every callout labelled `BETA ` with an older ``. Each such callout names what checks it in a marker on the line above it ([`check_callout_evidence.mjs`](Tools-Toolchain-Tests#check-callout-evidence) has the grammar and is its gate). The tool runs what the markers name, each thing once however many callouts name it, moves the label of each callout whose evidence all holds to the new build, and lists the rest for a person. A generated Common Errors section's callouts follow their data file, `errors/.yml`: when the data holds, its `beta:` moves and [`errors.mjs write`](#errors) writes the sections again.
+
+The build is the IDE's: `--ide`, else `TB_IDE`, else the newest `twinBASIC_IDE_BETA_` on the Desktop. The checks run one after another, through the tool that owns each, since runs at once give false failures under load:
+
+| Item | Run by | Holds when |
+|---|---|---|
+| `lane :` | `ide_test.mjs` or `addin_test.mjs`, once for each suite with every lane named | the lane passes |
+| `bug ` | [`bug_repro.mjs verify`](#bug-repro); a lane reproducer is judged from the same lane run, by the tests it names | the bug still reproduces |
+| `sample ` | [`check_examples.mjs --json`](Tools-Samples#check-examples), over the pages that hold the samples | the sample has no finding in each architecture it runs in |
+| `errors ` | `errors.mjs run`, for 32-bit and 64-bit twinBASIC | every case raises what the data says |
+| `llvm ` | `test/llvm/run.mjs --only ` | the mode prints what `expect.json` says |
+| a package's cases | [`package_tests.mjs --json`](Tools-Samples#package-tests) | each case is as documented |
+| `none`, a package's `probe` | nobody | never: a person checks it |
+
+A callout is relabelled only when every item of its marker holds. Otherwise it is listed under the worst of its items: **changed** (the evidence no longer says what the callout says, as when a bug no longer reproduces: read the callout, then fix or remove it), **not checked** (a check could not be run or its report read), or **by hand**. Each tool's whole output is kept under `%TEMP%\recheck-callouts\\`, and the list names the file for each check that did not hold.
+
+| Option | Effect |
+|---|---|
+| `--plan` | Prints the callouts selected and the checks they need, and runs nothing. |
+| `--dry-run` | Runs the checks and changes no file. |
+| `--only ` | Only the pages whose path under `docs/` matches. |
+| `--kinds ` | Runs only these kinds of check, comma-separated: `bug`, `lane`, `sample`, `errors`, `llvm`, `package`. A callout that needs another kind is not relabelled. |
+| `--report ` | Writes what was run and decided as JSON. |
+| `--from ` | Reads a report of an earlier run on the same build, and runs again only the checks that did not hold there. A result that surprises is run again alone before it is believed. |
+
+It needs a twinBASIC install, and a Professional or Ultimate licence when a callout names an `llvm` mode, so it is outside every gate and outside CI, and is run by a person when a new BETA is installed. A run over every callout takes hours, most of them in the reproducers and the package tests: `--kinds` and `--only` cut it into parts.
+
+Exit codes: **0** every selected callout and data file held and was relabelled, or `--plan`; **1** a callout or a data file is listed for a person; **2** a refused command line, no IDE, or a crash.
+
 ## ide_js.mjs
 {: #ide-js }
 
diff --git a/docs/Documentation/Tools-Samples.md b/docs/Documentation/Tools-Samples.md
index c2992d281..0bf001d54 100644
--- a/docs/Documentation/Tools-Samples.md
+++ b/docs/Documentation/Tools-Samples.md
@@ -80,6 +80,11 @@ The handler is reached only by an error the sample does not handle itself, so a
 
 The run's console output is captured as [`tbrun.mjs`](Tools-Compiler#tbrun) captures it and split by the markers, so each line is charged to the sample that printed it. A sample that raises an error, does not return, or prints something other than the page says is a finding at the page line that states the value. A run sample is refused without being built if it is not `slot=sub`, is marked `expect-error`, calls `MsgBox` or `InputBox`, or contains an `End` statement: a message box waits for a click on a desktop nobody sees, and `End` ends every sample after it.
 
+**A run sample can name the architectures it is run in.** `arch=win32,win64` runs it in a 32-bit and in a 64-bit project, and `arch=win64` in a 64-bit one only; without it a sample runs in a 32-bit one, as a new project is built. The 64-bit run is a copy of the sample, reported under its id with `@win64` after it, and batched apart, with its page's `hidden` context and staged files, in projects whose build target is win64. A sample in a `projname` or `concat_group` group, and one that is not run, may not ask: a sample that is only compiled is compiled the same for either target.
+
+**A probe is a sample the reader never sees.** A fence marked `probe` is not rendered, as a `hidden` one is not, but it is a sample of its own, built (and with `check_run` run and judged) as any other is, where a `hidden` fence is context that only travels with the page's samples. It checks a statement the page makes in prose or in a callout, whose code would tell the reader nothing: that a `Declare` of msvbvm60's `GetMem4` works in a 64-bit build, or that a conversion draws no warning, with the warning made an error by `[EnforceErrors]` in a probe that must compile. A probe has an `id=`, and a [marker](Tools-Toolchain-Tests#check-callout-evidence) beside the statement names it, ``; a probe no marker names fails `test.bat`. One marked `hidden` too, or with no `id=`, is refused.
+{: #probes }
+
     FAIL  docs/Reference/Default/VBA/Strings/InStr.md:77  (Reference/Default/VBA/Strings/InStr.md#3)
             prints "3", the page says "7"
 
diff --git a/docs/Documentation/Tools-Toolchain-Tests.md b/docs/Documentation/Tools-Toolchain-Tests.md
index 37a7e12d3..a8a7a99d9 100644
--- a/docs/Documentation/Tools-Toolchain-Tests.md
+++ b/docs/Documentation/Tools-Toolchain-Tests.md
@@ -422,12 +422,32 @@ A callout names its cases in an HTML comment on the line above it:
 
 The same comment anywhere else opens a region, which `` closes: it ties the paragraphs, lists, tables and samples between them, which describe how the package works, to the cases that check them. Regions do not nest, and a callout inside one is marked by it.
 
-Each item is a `Fixture.Sub` for a `[TestCase]`, `compile ` for a compile case, `probe ` for a scenario of the package's `probe/` harness, or `none -- ` for a callout no program can check. For a package with a test project, the gate fails when a callout has neither a marker nor a region around it, when a region is not closed or opens inside another, when an item names nothing that exists, when a case is named by no marker (a compile case of the package's `compile/` project included), when `compile/` holds a `[TestCase]`, when a `[TestCase]` does not start with `Expect.Begin` and end with `Expect.Done`, when `expect.json` is for another version than the section's `indexed_from` or names a case that does not exist, and when the generated `RunAll.twin` is out of date. A section with no test project yet is listed in the script's `PENDING` set; the gate fails on a name there with a project, and on a section neither there nor with one, so the list only shrinks.
+Each item is a `Fixture.Sub` for a `[TestCase]`, `compile ` for a compile case, `probe ` for a scenario of the package's `probe/` harness, or `none -- ` for a callout no program can check; an item any page's marker may hold, such as a reproducer under `bugs/`, is [`check_callout_evidence.mjs`](#check-callout-evidence)'s to check. For a package with a test project, the gate fails when a callout has neither a marker nor a region around it, when a region is not closed or opens inside another, when an item names nothing that exists, when a case is named by no marker (a compile case of the package's `compile/` project included), when `compile/` holds a `[TestCase]`, when a `[TestCase]` does not start with `Expect.Begin` and end with `Expect.Done`, when `expect.json` is for another version than the section's `indexed_from` or names a case that does not exist, and when the generated `RunAll.twin` is out of date. A section with no test project yet is listed in the script's `PENDING` set; the gate fails on a name there with a project, and on a section neither there nor with one, so the list only shrinks.
 
 Thirteen probes run first, on pages, sources and an `expect.json` built in memory: how a marker, a region and their items are read, the RunAll module, how `expect.json` is read and written, and how a case's result is judged.
 
 Exit codes: **0** every probe passed and every test project is in order, **1** a probe failed, or a section or test project is not in order, **2** the gate could not run: a refused command line, or a crash.
 
+## check_callout_evidence.mjs
+{: #check-callout-evidence }
+
+    node scripts/check_callout_evidence.mjs
+
+Verifies that every callout labelled `BETA `, which states what that twinBASIC build does, names what checks it, so that [`recheck_callouts.mjs`](Tools-Defects#recheck-callouts) can check it again on a new build. It reads only the sources --- the pages, the reproducers under `bugs/`, the lane lists, the error data and the LLVM probe's results --- so it needs no install, no browser and no built tree, and takes a few seconds.
+
+A callout names its evidence in the same HTML comment a contributed package's callout names its cases in, on the line above it, or by a region around it ([`check_package_tests.mjs`](#check-package-tests) has the grammar):
+
+    
+    > [!WARNING] BETA 1034
+
+Besides a contributed package's own cases, an item is `bug ` for a reproducer under `bugs/` or `bugs/filed/`, `lane :` for a lane of `test/ide/lanes.mjs` (`ide`) or `test/addin/lanes.mjs` (`addin`), `sample ` for the one `tb` fence under `docs/` with that `id=`, built by [`examples.bat`](Tools#examplesbat), `errors ` for the run-time error data `errors/.yml`, `llvm ` for a mode of the LLVM claims probe, `test/llvm/run.mjs`, or `none -- ` for a callout no program can check. The callouts of a generated Common Errors section need no marker: the data file the section's opening comment names is theirs.
+
+The gate fails when a callout labelled `BETA ` outside a generated section has neither a marker nor a region around it, when an item names nothing that exists, when a `sample` names a fence that is hidden context or an id two fences share, when a page outside a contributed package's section names a package's test case, when a region outside those sections is not closed or opens inside another, when a page still has the old `` comment, and when a fence marked [`probe`](Tools-Samples#probes), which the reader never sees, is named by no marker.
+
+Fourteen probes run first, on pages, an index of what exists and tool reports built in memory: how a marker, a region, an errors section and their items are read, that a marker in a fence or in indented code is text, what an item that names nothing is, and, for `recheck_callouts.mjs`, how each tool's report is read, how a callout's verdict is taken from its items, and how its label is moved.
+
+Exit codes: **0** every probe passed and every labelled callout and marker is in order, **1** a probe failed, or a callout or marker is not in order, **2** the gate could not run: a refused command line, or a crash.
+
 ## check_errors.mjs
 {: #check-errors }
 
diff --git a/docs/Documentation/Tools.md b/docs/Documentation/Tools.md
index f052fcf8c..f39d38c27 100644
--- a/docs/Documentation/Tools.md
+++ b/docs/Documentation/Tools.md
@@ -113,7 +113,7 @@ A local pass of [`check_a11y.mjs`](Tools-Site-Checks#check-a11y) does not predic
 
     test.bat
 
-The tests the toolchain has to pass. Thirty-one steps, each stopping the run if it fails:
+The tests the toolchain has to pass. Thirty-two steps, each stopping the run if it fails:
 
 1. [`scripts/check_publish_policy.mjs`](Tools-Toolchain-Tests#check-publish-policy) --- verifies the publish allowlist still refuses the types it should. Needs neither a browser nor a built tree.
 2. [`scripts/check_gate_lists.mjs`](Tools-Toolchain-Tests#check-gate-lists) --- verifies the two gate lists on this page still match the wrappers that run them.
@@ -138,14 +138,15 @@ The tests the toolchain has to pass. Thirty-one steps, each stopping the run if
 21. [`scripts/check_page_baseline.mjs`](Tools-Toolchain-Tests#check-page-baseline) --- verifies the page-count drift guard still refuses a fall.
 22. [`scripts/check_book_coverage.mjs`](Tools-Toolchain-Tests#check-book-coverage) --- verifies the build still warns about a page `docs/_book.yml` does not mention.
 23. [`scripts/check_package_tests.mjs`](Tools-Toolchain-Tests#check-package-tests) --- verifies every NOTE and WARNING of a contributed package's section names the test cases behind it, and that each package test project is in order.
-24. [`scripts/check_errors.mjs`](Tools-Toolchain-Tests#check-errors) --- verifies every page's Common Errors section is what the run-time error data under `errors/` writes, and that the data is in order.
-25. [`scripts/check_symbol_index.mjs`](Tools-Toolchain-Tests#check-symbol-index) --- verifies the symbol index still places each kind of symbol, and its drift guard still refuses a lost URL.
-26. [`scripts/check_twin_parsers.mjs`](Tools-Toolchain-Tests#check-twin-parsers) --- verifies the scanners of twinBASIC source and of the attribute reference still read the shapes each once misread.
-27. [`scripts/check_attribute_sweep.mjs`](Tools-Toolchain-Tests#check-attribute-sweep) --- verifies the logic of the attribute sweep: its site skeletons, how it reads a probe's diagnostics, how it batches probes, and how it compares the answers with `Attributes.md`.
-28. [`scripts/check_cli.mjs`](Tools-Toolchain-Tests#check-cli) --- verifies `lib/cli.mjs`, the command-line parser, and each tool's recorded command-line errors.
-29. [`scripts/check_pdf_shims_equiv.mjs`](Tools-Toolchain-Tests#check-pdf-shims-equiv) --- verifies the book's pdf-lib shims write what stock pdf-lib writes, patch the members of pdf-lib it lists, have each of them in their shim's table of targets, and run.
-30. [`scripts/check_impexp_parity.mjs`](Tools-Toolchain-Tests#check-impexp-parity) --- verifies the editions of the impexp tool pass the same built-in tests, and exit, print and write the same for one sequence of commands. Without Python it reports itself skipped and passes, except in CI. It compares the twinBASIC edition too, when `IMPEXP_EXE` names its built exe.
-31. [`scripts/check_axe_patch_equiv.mjs`](Tools-Toolchain-Tests#check-axe-patch-equiv) --- verifies the vendored axe source patch still produces identical colour values.
+24. [`scripts/check_callout_evidence.mjs`](Tools-Toolchain-Tests#check-callout-evidence) --- verifies every callout labelled with a twinBASIC build names what checks it, and that every marker names a reproducer, lane, sample, error data file or LLVM probe mode that exists.
+25. [`scripts/check_errors.mjs`](Tools-Toolchain-Tests#check-errors) --- verifies every page's Common Errors section is what the run-time error data under `errors/` writes, and that the data is in order.
+26. [`scripts/check_symbol_index.mjs`](Tools-Toolchain-Tests#check-symbol-index) --- verifies the symbol index still places each kind of symbol, and its drift guard still refuses a lost URL.
+27. [`scripts/check_twin_parsers.mjs`](Tools-Toolchain-Tests#check-twin-parsers) --- verifies the scanners of twinBASIC source and of the attribute reference still read the shapes each once misread.
+28. [`scripts/check_attribute_sweep.mjs`](Tools-Toolchain-Tests#check-attribute-sweep) --- verifies the logic of the attribute sweep: its site skeletons, how it reads a probe's diagnostics, how it batches probes, and how it compares the answers with `Attributes.md`.
+29. [`scripts/check_cli.mjs`](Tools-Toolchain-Tests#check-cli) --- verifies `lib/cli.mjs`, the command-line parser, and each tool's recorded command-line errors.
+30. [`scripts/check_pdf_shims_equiv.mjs`](Tools-Toolchain-Tests#check-pdf-shims-equiv) --- verifies the book's pdf-lib shims write what stock pdf-lib writes, patch the members of pdf-lib it lists, have each of them in their shim's table of targets, and run.
+31. [`scripts/check_impexp_parity.mjs`](Tools-Toolchain-Tests#check-impexp-parity) --- verifies the editions of the impexp tool pass the same built-in tests, and exit, print and write the same for one sequence of commands. Without Python it reports itself skipped and passes, except in CI. It compares the twinBASIC edition too, when `IMPEXP_EXE` names its built exe.
+32. [`scripts/check_axe_patch_equiv.mjs`](Tools-Toolchain-Tests#check-axe-patch-equiv) --- verifies the vendored axe source patch still produces identical colour values.
 
 POSIX:
 
@@ -172,6 +173,7 @@ POSIX:
       && node scripts/check_page_baseline.mjs \
       && node scripts/check_book_coverage.mjs \
       && node scripts/check_package_tests.mjs \
+      && node scripts/check_callout_evidence.mjs \
       && node scripts/check_errors.mjs \
       && node scripts/check_symbol_index.mjs \
       && node scripts/check_twin_parsers.mjs \
@@ -183,9 +185,9 @@ POSIX:
 
 Exit codes: **0** every step passed; otherwise the code of the step that stopped the run, as that step's entry gives it.
 
-**All but five cannot be affected by an edit confined to `docs/`**, which is why they are separate from `check.bat`. Run this one when the change touches `builder/`, `scripts/`, `lib/`, `book/`, `eval/`, `wisdom/`, `indexer/` or `test/`, the site's scripts in `docs/assets/js/`, the help add-in's source in `add-in/`, a contributed package's section, a wrapper, or a workflow. Both CI workflows run every step unconditionally.
+**All but six cannot be affected by an edit confined to `docs/`**, which is why they are separate from `check.bat`. Run this one when the change touches `builder/`, `scripts/`, `lib/`, `book/`, `eval/`, `wisdom/`, `indexer/` or `test/`, the site's scripts in `docs/assets/js/`, the help add-in's source in `add-in/`, a contributed package's section, a callout labelled with a twinBASIC build or its marker, a wrapper, or a workflow. Both CI workflows run every step unconditionally.
 
-The five exceptions are [`check_code_regions.mjs`](Tools-Toolchain-Tests#check-code-regions), [`check_errors.mjs`](Tools-Toolchain-Tests#check-errors), which compares the reference pages' Common Errors sections with their data, [`check_gate_lists.mjs`](Tools-Toolchain-Tests#check-gate-lists), which reads this page, [`check_lint.mjs`](Tools-Toolchain-Tests#check-lint), which lints the site's scripts in `docs/assets/js/`, and [`check_package_tests.mjs`](Tools-Toolchain-Tests#check-package-tests), which reads the callouts of the contributed packages' sections.
+The six exceptions are [`check_code_regions.mjs`](Tools-Toolchain-Tests#check-code-regions), [`check_errors.mjs`](Tools-Toolchain-Tests#check-errors), which compares the reference pages' Common Errors sections with their data, [`check_gate_lists.mjs`](Tools-Toolchain-Tests#check-gate-lists), which reads this page, [`check_lint.mjs`](Tools-Toolchain-Tests#check-lint), which lints the site's scripts in `docs/assets/js/`, [`check_package_tests.mjs`](Tools-Toolchain-Tests#check-package-tests), which reads the callouts of the contributed packages' sections, and [`check_callout_evidence.mjs`](Tools-Toolchain-Tests#check-callout-evidence), which reads every labelled callout and every marker.
 
 `check_code_regions.mjs` has two halves. Its corpus sweep tokenises every markdown file under `docs/`, so a page that provokes a rewrite into altering a code region fails it. Its fixed probes run against their own sources whatever the tree holds, and cover the *mirror* fault, where a rewrite silently stops firing; the sweep cannot see that one. After adding a page with an unusual code construct, run `test.bat` and read the built page too.
 
diff --git a/docs/Features/Advanced/API-Declarations.md b/docs/Features/Advanced/API-Declarations.md
index ce6ffb21f..3874059ac 100644
--- a/docs/Features/Advanced/API-Declarations.md
+++ b/docs/Features/Advanced/API-Declarations.md
@@ -99,6 +99,7 @@ End Class
 
 On a 32-bit build, a **ThisCall** implementation takes `this` in the ECX register and removes its own arguments from the stack, so native code calls it through the interface's vtable as it calls a C++ virtual method. An error raised in the implementation reaches the native caller as a failure **HRESULT**, as from any member without **[PreserveSig]**. Code compiled with LLVM does not pass the error on yet; see [Language support](../../LLVM/Getting-Started#language-support).
 
+
 > [!NOTE] BETA 1034
 > A class cannot implement a member marked [**[PreserveSig]**](../../tB/Core/Attributes#preservesig): the compiler reports the implementation as unmatched. Interfaces of this kind often return a value directly rather than an **HRESULT**, and are declared with **[PreserveSig]** for that reason. For a member that returns a 4-byte value, such as a **Long** or a C `BOOL`, declare it in the interface the class implements as a **Sub** without **[PreserveSig]**, and return the value with [**Err.ReturnHResult**](../../tB/Modules/ErrObject/ReturnHResult). Native code that calls this member as `long __thiscall Count()` gets 3 as its return value:
 >
@@ -124,6 +125,7 @@ An ActiveX DLL's type library records a **ThisCall** or **FastCall** member as s
 
 A callback passed to an API must use the convention the API calls it with. Declare a [**Delegate**](../../tB/Core/Delegate) with that convention, use it as the parameter type, and give the target procedure the same keyword. [**AddressOf**](../../tB/Core/AddressOf) of a procedure with a different convention is accepted with a warning only (TB0026).
 
+
 > [!NOTE] BETA 1034
 > On a 32-bit build, a call made from twinBASIC code *through* a **ThisCall** or **FastCall** delegate passes its arguments as stdcall does, and fails with *Bad DLL definition*. Calling the procedure directly works. **CDecl** delegates are not affected, and neither is a 64-bit build.
 
diff --git a/docs/Features/Advanced/Classes-and-Modules.md b/docs/Features/Advanced/Classes-and-Modules.md
index 56fc6c60c..b271ac7d8 100644
--- a/docs/Features/Advanced/Classes-and-Modules.md
+++ b/docs/Features/Advanced/Classes-and-Modules.md
@@ -19,6 +19,7 @@ A class can have a `Sub New` constructor, and the constructor can take arguments
 - A class that has both a `Class_Initialize` and a `Sub New` with no parameters cannot be created with a plain `New`. The call matches both, and fails with TB5073.
 - A class whose only `Sub New` has required arguments, with no `Class_Initialize`, cannot be declared [`As New`](../../tB/Core/New), which creates the object without arguments. The declaration fails with TB5121. If every argument of the `Sub New` is `Optional`, the class can be declared `As New`: the object is created by calling `Sub New` with the default values.
 
+
 > [!NOTE] BETA 1034
 > A `Sub New` declared `Private` is meant to make construction private, so that only code inside the class creates objects with it. This is not enforced: `New MyClass` in another module of the project, or in a project that references a package with the class, compiles and runs the private constructor.
 
diff --git a/docs/Features/Advanced/Static-Linking.md b/docs/Features/Advanced/Static-Linking.md
index 3a40e5f1d..7b815a386 100644
--- a/docs/Features/Advanced/Static-Linking.md
+++ b/docs/Features/Advanced/Static-Linking.md
@@ -10,6 +10,7 @@ index: static library
 
 Links compiled C code from `.obj`, `.lib` and `.a` files into the program, which calls its functions through **Declare** statements and needs no DLL beside it.
 
+
 > [!NOTE] BETA 1034
 > Static linking is experimental. It links code that needs nothing from the C runtime library beyond the few functions twinBASIC provides, and nothing from the C++ runtime. The SQLite amalgamation and SlimDetours link and work; most other C projects do not link without changes. [What can be linked](#what-can-be-linked) lists the limits.
 
@@ -105,9 +106,11 @@ Code links when every function it calls is in its own file or in one that its **
 - **Exception handling in a 32-bit build.** `__try` and `__except` need `__except_handler3`, which neither `"stdlib"` nor `"kernel32"` nor `"ntdll"` defines.
 - **Whole-program optimisation.** An object file compiled with `/GL` holds no machine code, and is compile error TB5059.
 
+
 > [!WARNING] BETA 1034
 > In a 64-bit build, code that uses `__try` and `__except` links without an error, but its handler never runs: an exception that it should handle ends the program, in the IDE and in a built exe.
 
+
 > [!WARNING] BETA 1034
 > An **Import Library** statement that names an import library crashes the compiler, in either architecture. An import library is a `.lib` that lists the functions of a DLL and holds no code, such as the Windows SDK's `user32.lib`. Name a Windows DLL in the **Link** list instead.
 
diff --git a/docs/Features/Compiler-IDE/Compiler-Warnings.md b/docs/Features/Compiler-IDE/Compiler-Warnings.md
index 80e82b5eb..57874368f 100644
--- a/docs/Features/Compiler-IDE/Compiler-Warnings.md
+++ b/docs/Features/Compiler-IDE/Compiler-Warnings.md
@@ -261,9 +261,27 @@ Public Sub Convert()
 End Sub
 ```
 
+
 > [!NOTE] BETA 1034
 > A member of another enumeration, written as a constant (`day = vbOK`), converts without a warning.
 
+```tb probe id=tb0019-enforced slot=module expect-error=TB0019
+[EnforceErrors(TB0019)]
+Public Sub ProbeOtherEnumVariable()
+    Dim answer As VbMsgBoxResult, day As VbDayOfWeek
+    answer = vbYes
+    day = answer
+End Sub
+```
+
+```tb probe id=tb0019-other-enum-constant slot=module
+[EnforceErrors(TB0018, TB0019, TB0021)]
+Public Sub ProbeOtherEnumConstant()
+    Dim day As VbDayOfWeek
+    day = vbOK
+End Sub
+```
+
 ### TB0020: Suspicious Interface Conversion
 {: index="TB0020" }
 
@@ -284,14 +302,61 @@ End Sub
 
 Reports an implicit conversion between an enumeration and a numeric type. It is at **IGNORE** by default.
 
+
 > [!NOTE] BETA 1034
 > None of these conversions draws TB0021. A whole number assigned to a variable of an enumeration, or an enumeration value assigned to a **Long**, converts without a warning. A **Double**, **Single**, **Currency** or **Variant** assigned to a variable of an enumeration draws [TB0019](#tb0019-implicit-enumeration-conversion), and an enumeration value assigned to an **Integer**, **Byte** or **Single** draws [TB0018](#tb0018-implicit-narrowing-conversion).
 
+```tb probe id=tb0021-not-drawn slot=module
+[EnforceErrors(TB0021)]
+Public Sub ProbeNoTB0021()
+    Dim day As VbDayOfWeek, l As Long, i As Integer, b As Byte
+    Dim d As Double, s As Single, c As Currency, v As Variant
+    day = 3
+    l = day
+    d = 2: s = 2: c = 2: v = 2
+    day = d
+    day = s
+    day = c
+    day = v
+    i = day
+    b = day
+    s = day
+End Sub
+```
+
+```tb probe id=tb0021-whole-number-and-long slot=module
+[EnforceErrors(TB0018, TB0019, TB0021)]
+Public Sub ProbeWholeNumberAndLong()
+    Dim day As VbDayOfWeek, l As Long
+    day = 3
+    l = day
+End Sub
+```
+
+```tb probe id=tb0019-from-double slot=module expect-error=TB0019
+[EnforceErrors(TB0019)]
+Public Sub ProbeDoubleToEnum()
+    Dim day As VbDayOfWeek, d As Double
+    d = 2
+    day = d
+End Sub
+```
+
+```tb probe id=tb0018-to-integer slot=module expect-error=TB0018
+[EnforceErrors(TB0018)]
+Public Sub ProbeEnumToInteger()
+    Dim day As VbDayOfWeek, i As Integer
+    day = vbMonday
+    i = day
+End Sub
+```
+
 ### TB0022: Suspicious Assignment
 {: index="TB0022" }
 
 An array assigned to an array of another element type, *suspicious array assignment; subtypes do not match*.
 
+
 > [!NOTE] BETA 1034
 > The message reads only *Suspicious array assignment*: the IDE cuts the text that `en-gb.lang` gives it at its semicolon. See [Language packs](../../tB/IDE/Customising/#language-packs).
 
@@ -438,6 +503,7 @@ Reports a duplicate definition as a warning: *Duplicate definition, consider cha
 
 A **Declare** of a function from a legacy system DLL that twinBASIC calls in its modern replacement instead, such as `OleCreatePictureIndirect`, which `olepro32.dll` exported and `oleaut32.dll` now does: *Use of legacy DLL OLEPRO32.DLL has been redirected to OLEAUT32.DLL*. Up to BETA 1033 the compiler reported it as [TB0015](#tb0015-declare-redirected-to-twinbasic). The Compiler Warnings list shows it at **HINT** by default.
 
+
 > [!NOTE] BETA 1034
 > TB0034 is reported as a warning whatever its level: at **IGNORE**, **HINT** or **ERROR** in the Compiler Warnings list, and under **IgnoreWarnings** or **EnforceErrors**. Its message leaves out the two names: *Use of legacy DLL has been redirected to*.
 
@@ -452,6 +518,7 @@ Private Declare PtrSafe Function OleCreatePictureIndirect Lib "olepro32" ( _
 
 A value whose type is known only at run time, an **Object** or a **Variant**, assigned or passed to a variable or parameter of a class or interface type: *Conversion from type Variant to type Font might fail at runtime. Use CType(Of Font)(expression) to avoid this message*. An item of a **Collection** is a **Variant**, and the result of a function declared **As Object** is an **Object**. When the value is not an object of that type, the conversion raises an error at run time, such as error 424, *Object required*, for a string. **CType(Of** _type_**)** makes the conversion explicit. A conversion from a known type that is not declared to support the target is [TB0020](#tb0020-suspicious-interface-conversion) instead. The Compiler Warnings list shows TB0035 at **WARNING** by default.
 
+
 > [!NOTE] BETA 1034
 > In a project whose settings give TB0035 no level, as a new project's do, TB0035 is not reported, although the Compiler Warnings list shows it at **WARNING**. Name it in an **EnforceWarnings** attribute, as below, to have it reported. For a conversion from an **Object**, its message shows `{0}`, `{1}` and `{2}` in place of the two types and the conversion.
 
diff --git a/docs/Features/Language/UDTs.md b/docs/Features/Language/UDTs.md
index ec8ef17f0..037d2b28a 100644
--- a/docs/Features/Language/UDTs.md
+++ b/docs/Features/Language/UDTs.md
@@ -58,9 +58,11 @@ End Type
 
 UDTs of these types are still stack allocated structs that can be used with standard Win32 APIs.
 
+
 > [!WARNING] BETA 1034
 > A method or property of a UDT called on an element of an array of the UDT, such as `a(1).Sum`, ends the program with an access violation, in 32-bit and 64-bit builds. A variable of the type, a UDT member of another UDT, and an element of an array declared inside a UDT are not affected. Copy the element to a variable first.
 
+
 > [!NOTE] BETA 1034
 > Assigning **Me** whole inside a method of a UDT, as in `Copy = Me` or `Return Me`, is a code generation error that fails the build once the method is called. Copy the members one by one.
 
diff --git a/docs/IDE/Building/ActiveX-Registration.md b/docs/IDE/Building/ActiveX-Registration.md
index 69ae8be2b..8411b3e8a 100644
--- a/docs/IDE/Building/ActiveX-Registration.md
+++ b/docs/IDE/Building/ActiveX-Registration.md
@@ -62,6 +62,7 @@ Its one button is **Continue Anyway**. The box does not appear when *Register DL
 
 ![The warning box the IDE shows as it opens a project that registers to HKEY_LOCAL_MACHINE while the IDE is not elevated, with one button.](Images/HKLM_Warning.png){:width="541" height="249"}
 
+
 > [!NOTE] BETA 1034
 > The warning opens twice each time you open the project, and also for a Standard EXE project that has *Register DLLs to HKLM* at **Yes**, although an EXE registers nothing. Choose **Continue Anyway** each time. To stop the box, set *Register DLLs to HKLM* or *Register DLL after build* to **No**.
 
@@ -88,6 +89,7 @@ For a DLL registered under `HKEY_LOCAL_MACHINE`, in an IDE that is not elevated,
 
 **Delete It And Continue** builds again and ignores the old build. The console prints `[BUILD] Starting...` again. **Cancel** stops there.
 
+
 > [!NOTE] BETA 1034
 > The box that opens at **Clean** has the same buttons, but **Delete It And Continue** there builds a new DLL instead of deleting the old one: the console prints `[LINKER] SUCCESS` and a failed `[REGISTER]` line. Choose **Cancel** at **Clean**: the box does not clean.
 
diff --git a/docs/IDE/Building/Build-Output.md b/docs/IDE/Building/Build-Output.md
index 81a082847..29a27e377 100644
--- a/docs/IDE/Building/Build-Output.md
+++ b/docs/IDE/Building/Build-Output.md
@@ -32,6 +32,7 @@ A build makes the folders of the path when they are missing, nested ones too. Th
 - An unknown variable such as `${Nope}` prints `[COMPILER] build path contains unrecognized variable.` and builds nothing.
 - `${IdePath}`, which the samples that build an add-in use, is the folder the IDE is installed in.
 
+
 > [!WARNING] BETA 1034
 > `${ProjectID}` in the path is replaced with nothing. A path of `${SourcePath}\Build\${ProjectID}_x.exe` builds a file called `_x.exe`, with no message, and two projects that use the same pattern overwrite each other's file. Do not use `${ProjectID}`; name the file with `${ProjectName}`.
 
@@ -60,6 +61,7 @@ The *Major*, *Minor*, *Build* and *Revision* settings of the [version resource](
 
 The type library of an ActiveX DLL or control has its own pair of settings, [*Type Library Version*](../Project-Settings#type-library-version), and its own *Auto-Increment*. A build prints `[BUILD] Auto-incremented type-library version number to: ` for it.
 
+
 > [!WARNING] BETA 1034
 > The number that *Auto-Increment* raises is changed in the IDE's memory only. The project is not marked as changed, the `Settings` file is not written, and the **Version** row of the **PACKAGE PUBLISHING** pane stays as it was. **Close Project** asks nothing, and the increments are lost: the next session builds again with the old numbers, and a package is published with a number that was used already. Set the numbers by hand in Project Settings before you build a version that you release or publish, and save the project.
 
diff --git a/docs/IDE/Building/Build-and-Clean.md b/docs/IDE/Building/Build-and-Clean.md
index f77f830a7..f0da6bc0d 100644
--- a/docs/IDE/Building/Build-and-Clean.md
+++ b/docs/IDE/Building/Build-and-Clean.md
@@ -49,6 +49,7 @@ A project made from a template, a sample or a folder, or imported from a VB6 pro
 
 The build does not go on after the dialog. Press **Build** again. The folder you chose is now `${SourcePath}`, so the default build path puts the file in a `Build` folder in it.
 
+
 > [!NOTE] BETA 1034
 > **Save** in this box makes the file you choose the project's file, but writes no `.twinproj` there, and nothing is built. The project stays changed, and **File → Close Project** still asks **You've made changes to this project.  Save now?** Press Ctrl+S after the dialog to write the file.
 
diff --git a/docs/IDE/Building/LLVM-in-the-IDE.md b/docs/IDE/Building/LLVM-in-the-IDE.md
index 9de370a8e..a52b698c2 100644
--- a/docs/IDE/Building/LLVM-in-the-IDE.md
+++ b/docs/IDE/Building/LLVM-in-the-IDE.md
@@ -42,6 +42,7 @@ When the compiler reports progress, the IDE opens a box titled **Compiling...**
 
 ![The Compiling... box of an LLVM build, its progress bar at the start.](Images/Compiling.png){:width="302" height="74"}
 
+
 > [!NOTE] BETA 1034
 > If another box opens while **Compiling...** is on screen, the end of the build closes that box instead of the progress box, so the other box is lost. The **Compiling...** box stays, with no button, and the IDE cannot be used. Restart the IDE.
 
diff --git a/docs/IDE/Building/Package-Publishing.md b/docs/IDE/Building/Package-Publishing.md
index ab1529ff9..780a9d8ff 100644
--- a/docs/IDE/Building/Package-Publishing.md
+++ b/docs/IDE/Building/Package-Publishing.md
@@ -74,9 +74,11 @@ The IDE saves the publisher name. It saves the password only when **Remember pas
 
 The password is stored in the IDE's settings in the registry with a simple obfuscation, which is not encryption. Anyone who can read your registry can recover it. Leave the box unticked on a machine that others use.
 
+
 > [!NOTE] BETA 1034
 > A remembered password that contains `~` comes back wrong: the form is filled in with a different password, and the login fails. Do not tick **Remember password on this device** for such a password, and type it each time.
 
+
 > [!NOTE] BETA 1034
 > A saved publisher name that contains a single quote, such as `O'Brien`, is cut at the quote in the pre-filled form (**USERNAME** shows `O`). A remembered password with a quote is cut in the same way. Type the whole name and password into the boxes.
 
@@ -105,6 +107,7 @@ The confirmation reads **IMPORTANT: Please check the following information befor
 | The login has ended | The sign-in box opens again. |
 | Anything else | **[PUBLISH] FAILED.  Server returned HTTPS code** and the number. |
 
+
 > [!NOTE] BETA 1034
 > After you press **OK** in a box that says **[PUBLISH] FAILED.  Server returned HTTPS code**, the IDE raises a script error (`t is not a function`) that only the IDE's developer tools show. Nothing else changes, and the failure is as the box said: check the number, then publish again.
 
diff --git a/docs/IDE/Code/Diagnostics.md b/docs/IDE/Code/Diagnostics.md
index 8eeb0aae9..a8cf8aea7 100644
--- a/docs/IDE/Code/Diagnostics.md
+++ b/docs/IDE/Code/Diagnostics.md
@@ -73,6 +73,7 @@ Right-click in the list for a menu of five items. The menu is in English whateve
 
 A copied row reads `{ERROR}` or `{WARNING}`, the path of the file, the position, and the code and message. **Copy All** and **Copy All Errors** begin with a line that names the IDE: `twinBASIC (` and the version, then `) compilation error report:`.
 
+
 > [!NOTE] BETA 1034
 > Each copied message contains the HTML tag `TBnnnn` around the code, and **Copy All** labels hints and information messages `{WARNING}`. Remove the tags from the pasted text by hand.
 
@@ -104,6 +105,7 @@ Most errors are explained on the page of the statement, type or feature they con
 **TB5182**, *Syntax error. No handler for this symbol*
 : A statement or an attribute where the language does not allow it. A line at the top level of a `.twin` file, outside a **Module** or **Class** block, is one case. An [attribute](../../Core/Attributes) written where it does not apply is another.
 
+
 > [!NOTE] BETA 1034
 > For a type name, TB5079 reads *Unrecognized symbol 'Unrecognized datatype symbol 'NoSuchType''*: the message for a type is put inside the message for a name.
 
diff --git a/docs/IDE/Code/Editor.md b/docs/IDE/Code/Editor.md
index 67b17e7d6..d5bc89b1f 100644
--- a/docs/IDE/Code/Editor.md
+++ b/docs/IDE/Code/Editor.md
@@ -26,6 +26,7 @@ Each open file has a tab. The tab shows the file's icon, its name and a close bu
 - Drag a tab to move it. A mark on the left or the right half of the tab under the pointer shows where it will land.
 - Click the middle mouse button on a tab to close it.
 
+
 > [!NOTE] BETA 1034
 > A tab is tinted only when the compiler reports its file while the tab is open. A tab opened after that report stays untinted until the compiler checks the file again, for example after the next edit to it. The Project Explorer row is tinted either way.
 
@@ -188,6 +189,7 @@ Typing, deleting and most commands act at every cursor. One Ctrl+Ctrl+D and Ctrl+Shift+L match whole words in the current file, including words inside strings and comments. Ctrl+Shift+L and **Select All Matches** (see [Finding](Finding#select-all-matches)) both give a cursor to each match.
 
+
 > [!NOTE] BETA 1034
 > Esc does not remove the extra cursors. Click in the text, or run any command that places the caret, to go back to one cursor.
 
@@ -259,6 +261,7 @@ Paste on a blank line, or with the caret at the end of the indent. Pasting at th
 
 **Rename Symbol** is on the right-click menu, which shows the key F2 beside it. F2 opens the [Object Browser](Object-Browser) instead, in the editor as everywhere else.
 
+
 > [!NOTE] BETA 1034
 > Rename Symbol does nothing: choosing it from the right-click menu opens no box and changes no name. To rename a name that is used in one file, use **Change All Occurrences**. For a name used in several files, use the [Find / Replace](Finding) panel with **Whole Word Only** and **Current Project**, and replace one match at a time, because **Replace All** does not work in project scope.
 
@@ -270,6 +273,7 @@ Paste on a blank line, or with the caret at the end of the indent. Pasting at th
 
 The formatter changes only the indent of each line. It trims the line and sets the indent to four spaces for each level, whatever **TAB Spaces Amount** is. It does not change the words, their case or the spacing inside the line. It adds a level after `Class`, `Module`, `Type`, `Enum`, `For`, `While`, `Do`, `With`, a block `If`, `#If`, `Sub`, `Function` and `Property`, and takes it away at the matching End. `ElseIf` and `Else` lines sit at the level of their `If`. A `Select Case` indents its `Case` lines one level and their bodies two. A continuation line is indented two levels deeper than its statement. The members of an interface get one level and no End.
 
+
 > [!WARNING] BETA 1034
 > **Format Selection** fails when the selection starts on a continuation line, which is a line after one that ends in ` _`. Nothing is formatted, and a box opens with the text *IDE startup failure*, followed by *JS Promise Rejection(1): Assignment to constant variable.* If the IDE was started by opening the project's `.twinproj` file, it then closes. With unsaved changes, it first asks whether to save them: choose **Save**, or **Cancel** to keep the IDE open. Save before you format, start the selection on the first line of the statement, or use **Format Document**.
 
@@ -306,6 +310,7 @@ A right-click in the editor moves the caret to the point, unless you click insid
 
 Shift+F10 opens the same menu from the keyboard.
 
+
 > [!NOTE] BETA 1034
 > Shift+F10 opens the menu at the top-left corner of the IDE window, not at the caret. Right-click in the editor to open it at the pointer.
 
diff --git a/docs/IDE/Code/Finding.md b/docs/IDE/Code/Finding.md
index 455caa51b..96ac054cb 100644
--- a/docs/IDE/Code/Finding.md
+++ b/docs/IDE/Code/Finding.md
@@ -120,6 +120,7 @@ A search that reaches the end of its scope with **Down** asks **End of search sc
 
 A search with **All**, a project search, and **Replace All** end with **The specified region has been searched.** After a **Replace All**, a second box says how many replacements were made: **N replacements were made.**, or **1 replacement was made.** With no scope ticked, the panel says **Scope not selected**.
 
+
 > [!NOTE] BETA 1034
 > After a search of **Selected Text**, moving the caret leaves **Selected Text** ticked but greyed out, and **Current File** is no longer ticked. **Find Next** then says at once that the end of the search scope has been reached. Tick **Current File** again to search on.
 
@@ -127,6 +128,7 @@ A search with **All**, a project search, and **Replace All** end with **The spec
 
 A plain search ignores case unless **Match Case** is ticked.
 
+
 > [!WARNING] BETA 1034
 > **Whole Word Only** never finds a match that starts at the very first character of the file. For example, searching for `Module` with **Whole Word Only** skips a `Module` on line 1, column 1. A **Replace All** with **Whole Word Only** leaves that word unchanged. Search without **Whole Word Only**, and check the first line yourself.
 
@@ -150,12 +152,15 @@ With **Match Regular Expressions**, **Find What** is a JavaScript regular expres
 
 If **Find What** is not in the `/pattern/flags` form, a box says **Invalid format. Expecting /pattern/flags**. If the pattern is not a valid expression, a box says **Invalid RegExp:**.
 
+
 > [!WARNING] BETA 1034
 > A regular expression that has a capital letter finds nothing unless it has the `i` flag. The search runs on the text of the file turned into small letters, and the pattern is left as written. `/End Sub/` finds nothing and reports **The specified region has been searched.**, while `/end sub/` and `/End Sub/i` find every `End Sub`. Write the pattern in small letters, or add the `i` flag.
 
+
 > [!NOTE] BETA 1034
 > A regular expression whose match spans a line break, such as `/^\s+end sub$/`, selects nothing. The selection is an empty one at the end of the line before the match, and **Find Next** stays there. Search for one line at a time.
 
+
 > [!NOTE] BETA 1034
 > The **Invalid RegExp:** box leaves out the reason. It shows the words and nothing after them.
 
diff --git a/docs/IDE/Code/IntelliSense.md b/docs/IDE/Code/IntelliSense.md
index 8b35c17c4..efe671e2c 100644
--- a/docs/IDE/Code/IntelliSense.md
+++ b/docs/IDE/Code/IntelliSense.md
@@ -53,6 +53,7 @@ What an accepted entry adds:
 
 Esc, Left, Right and Backspace close the list. Left also moves the caret, and Backspace also deletes. Scrolling the editor with the mouse wheel closes the list too.
 
+
 > [!NOTE] BETA 1034
 > Typing `(` to accept a function from the list doubles the bracket: `Area` becomes `Area(()`. Accept the function with Tab or Enter instead. It gives `Area()` with the caret between the brackets, so type the arguments without the opening bracket.
 
diff --git a/docs/IDE/Code/Navigating.md b/docs/IDE/Code/Navigating.md
index a2a840021..287ecbc8c 100644
--- a/docs/IDE/Code/Navigating.md
+++ b/docs/IDE/Code/Navigating.md
@@ -38,6 +38,7 @@ Where the caret lands:
 | A member of a package, or of the VBA library | A new tab with the package's source. The tab is read-only. |
 | A keyword, or text in a string | Nowhere. |
 
+
 > [!NOTE] BETA 1034
 > Ctrl+Up and Ctrl+Down, and Go To Definition for most names, land on the line after the declaration, not on the declaration itself. Press Up once to reach it.
 
@@ -99,6 +100,7 @@ The **OUTLINE** panel lists the symbols of the file you are editing. It is empty
 
 ![The Outline pane listing the two top level items of the open file, the module MainModule and the class GlobalSearchAddIn.](../Images/Outline_1.png){:width="300" height="96"}
 
+
 > [!NOTE] BETA 1034
 > The Outline lists only the top-level types of a file, such as the module or the classes. The procedures, fields and properties inside them are missing, rows have no expand arrow, and the filters of the **...** menu below change nothing you can see. To go to a procedure, use the [combo boxes](Editor#the-navigation-combo-boxes).
 
diff --git a/docs/IDE/Code/Object-Browser.md b/docs/IDE/Code/Object-Browser.md
index 6dd7bdb4a..6ce28020d 100644
--- a/docs/IDE/Code/Object-Browser.md
+++ b/docs/IDE/Code/Object-Browser.md
@@ -88,6 +88,7 @@ A member of a package, such as VBA's **Left**, opens the package's source in a r
 
 In a list, Up, Down, Home and End move the selection, and typing the first letters of a name selects the next item that starts with them. Letters typed within a second of each other make one name; after a pause, a letter starts again. Enter goes to the definition. Esc closes the Object Browser from anywhere in it, as **Close** and the title bar's **X** do, and the focus goes back to where it was.
 
+
 > [!NOTE] BETA 1034
 > Tab moves the focus from the search box to **Aa** and no further. Shift+Tab moves it from the search box to **Project / Library**, then to the copy button of the description, and no further. To use a list from the keyboard, click it first: its keys then work.
 
diff --git a/docs/IDE/Customising/Themes.md b/docs/IDE/Customising/Themes.md
index 2135bedca..953009c8b 100644
--- a/docs/IDE/Customising/Themes.md
+++ b/docs/IDE/Customising/Themes.md
@@ -70,6 +70,7 @@ MenuBackColor:                  red;
 SymbolClassTextDecoration:      underline;
 ```
 
+
 > [!WARNING] BETA 1034
 > A file named `Dark.theme` or `Light.theme` in `%APPDATA%\twinBASIC\themes` breaks the inheritance of every custom theme. The IDE ignores the file's contents, but still counts it, and so it never links the themes to their parents. Every custom theme, and **Classic (Light)** with them, is listed as `(undefined)` in place of **Light** or **Dark**. Choosing one gives the dark base with only that theme's own properties changed, and **Reload from disk** no longer applies the edits you make. No error is shown. To change one of the shipped themes, give your file another name and inherit from the shipped theme: `inherits: dark;`.
 
diff --git a/docs/IDE/Customising/index.md b/docs/IDE/Customising/index.md
index 733c63c77..61ac7fb5e 100644
--- a/docs/IDE/Customising/index.md
+++ b/docs/IDE/Customising/index.md
@@ -126,6 +126,7 @@ To write a pack, copy `en-gb.lang` and translate it. The `README.txt` in the IDE
 
 A pack is a text file of `Name: value;` pairs, as a [theme](Themes#the-format-of-a-theme-file) is, with `/* ... */` comments. Each value is a string in double quotes. Write two double quotes for one inside a string. A line break inside the quotes is kept. In a string, `{%Name}` stands for another string and `{%1}` and `{%2}` mark the places where the IDE fills in a value.
 
+
 > [!NOTE] BETA 1034
 > A `;` followed by a space, a tab or a line break ends the string even inside its quotes, and the rest of the string is lost: `"Save; then close"` reads *Save*. A `;` with no space after it stays in the string. `en-gb.lang` has one such string, the warning TB0022, so the warning reads only *Suspicious array assignment*.
 
diff --git a/docs/IDE/Debugging/Breakpoints-and-Stepping.md b/docs/IDE/Debugging/Breakpoints-and-Stepping.md
index 4a6d52671..afdb0da02 100644
--- a/docs/IDE/Debugging/Breakpoints-and-Stepping.md
+++ b/docs/IDE/Debugging/Breakpoints-and-Stepping.md
@@ -87,6 +87,7 @@ A procedure in another file opens in the editor when you step into it, as a prev
 
 At the `End Sub` of a procedure whose local variables hold objects, a step goes into a frame named `{class_final_destruction}` and then into the `Class_Terminate` procedures of those objects. A step off the `End Sub` of the `Click` procedure of a form lets the program run on, and the form waits for input again.
 
+
 > [!NOTE] BETA 1034
 > Once the program has stopped at a breakpoint, no step stops in a caller of the procedure it stopped in. Step Out, F8 at the `End Sub` and Step Over all run the caller on until the next breakpoint or the end of the program. Steps inside the procedure, and into the procedures it calls and back out to it, work. To stop in the caller, put a breakpoint there, or stop on the line of the call and step into the procedure.
 
@@ -168,6 +169,7 @@ Running any line in the console removes the error panel. The failing line stays
 - **Stop** does not end the program at an error. See [Traps](#traps).
 - **Search Online** opens a Google search in the default browser. The search text is `twinBASIC VB6`, the header and the description of the error.
 
+
 > [!NOTE] BETA 1034
 > **Search Online** puts the error text into the address as it is, without encoding it. An `&` in the description ends the search text at that point, so the search loses the rest, and a `"` ends the address. Copy the description from the panel and search for it in your browser instead.
 
@@ -184,6 +186,7 @@ To run on to the end instead, correct the value and press F5, or pres
 
 Edit and Continue is not supported at the error panel either: when you change the code, the console writes `[EDIT & CONTINUE] not yet supported.  Aborting.`. After you change the failing line, F5 starts a new run that runs the changed line.
 
+
 > [!WARNING] BETA 1034
 > An edit at the error panel does not abort the program, although the console says `Aborting`. The rest of the failing procedure is skipped, and the code after its call runs: its output appears in the console, and the run then ends. At a breakpoint the same edit does abort the program. Do not edit the code while the error panel is showing. End the run first, as the **Stop** warning under [Traps](#traps) describes, and then edit.
 
@@ -191,9 +194,11 @@ Edit and Continue is not supported at the error panel either: when you change th
 
 - **The panel disappears, and Ignore with it.** Running a line in the Debug Console, or clicking another procedure in the Call Stack, removes the panel. F5 brings it back only by running the failing line again. Set Next Statement onto the next line, then F5, does what **Ignore (Resume Next)** would have done.
 
+
 > [!NOTE] BETA 1034
 > A step key pressed on the failing line leaves a step waiting. After F8 on the failing line, **Ignore (Resume Next)** stops again on the next line instead of going on, and the next F5 is used up as well. Press F5 to go on, or use Set Next Statement as above instead of a step key.
 
+
 > [!WARNING] BETA 1034
 > **Stop** at an error ends only the procedure that failed. The procedure that called it goes on from its next line: if `Sub Main` called `FillTable`, the rest of `Sub Main` still runs. The panel's **Stop**, the toolbar's **Stop** and **Run → End** all do this. At a failed `Debug.Assert`, **Stop** ends only the check, and the test goes on past it as if the check had passed. To end the whole run, first move the arrow to a later line with Ctrl+F9, then press **Stop**. Away from an error --- at a breakpoint, after a step, or after Set Next Statement --- **Stop** ends the run.
 
diff --git a/docs/IDE/Debugging/Debug-Console.md b/docs/IDE/Debugging/Debug-Console.md
index 5fdc86b9d..aae330088 100644
--- a/docs/IDE/Debugging/Debug-Console.md
+++ b/docs/IDE/Debugging/Debug-Console.md
@@ -95,12 +95,15 @@ The input row is one line. There is no way to enter a multi-line construct.
 - The row has the IntelliSense of the editor, in the mode that [IDE Options](../Customising/IDE-Options#intellisense-modes) sets, and Ctrl+Space opens the list. It has a line for signature help of its own.
 - A [keyboard shortcut](../Customising/Keyboard-Shortcuts#the-text-of-a-group) can run a line of code in the console: `tbDebugConsole_ExecuteCode('? 6*7')` is the one command that takes an argument.
 
+
 > [!NOTE] BETA 1034
 > The input row offers no completion for a name typed after `?` and a space, and Ctrl+Space there opens no list. Type the name without the space, as `?total`, or start the line with the name.
 
+
 > [!NOTE] BETA 1034
 > In the **MODERN** [Intellisense mode](../Customising/IDE-Options#intellisense-modes), a space typed while the completion list is open accepts the list's first entry, and the space is lost. Typing `Print total` gives `Printertotal`, because the list offers `Printer` but not the keyword `Print`; `Call Touch(total)` and `Set obj = items` are changed in the same way. The console refuses the changed line. Press Escape to close the list before you type the space: the text you typed stays.
 
+
 > [!NOTE] BETA 1034
 > `? p`, where `p` is a variable of a user-defined type, fails with `(compile error: codegen error; check for compilation errors)`, and the console prints a `[LINKER] compilation (codegen) error` line. The [Variables](Inspecting#variables) panel and a watch show the same variable, so read it there.
 
diff --git a/docs/IDE/Debugging/Inspecting.md b/docs/IDE/Debugging/Inspecting.md
index 136347edd..fe1bdd9a3 100644
--- a/docs/IDE/Debugging/Inspecting.md
+++ b/docs/IDE/Debugging/Inspecting.md
@@ -63,9 +63,11 @@ Double-click a value to edit it. A text box replaces the value. Enter
 - A row that is a folder, an object, a user-defined type or an array, has no text box. Expand it and edit its parts.
 - A line typed in the Debug Console, such as `s = "it's"`, changes a variable too, and the panel shows the change.
 
+
 > [!WARNING] BETA 1034
 > The edit box cuts a string at an apostrophe, and leaving the box changes the variable. With `s` holding `it's`, double-click `s`, and the box holds only `"it`. A click on another row, or Enter, with nothing typed, then makes `s` the empty string. Escape leaves it as it was. Do not edit a string that contains an apostrophe in the panel. Assign it in the [Debug Console](Debug-Console) instead.
 
+
 > [!WARNING] BETA 1034
 > A **Date** or **Double** row shows a wrong value for a while after you edit it. A **Date** edited to `#1/2/2020#` shows `#12:00:00 AM#`, and a **Double** edited to `7.25` shows `0`. The variable holds the value you typed. The row shows it correctly after the next line you run in the console, or at the next stop. To check the value now, run `? d` in the console, where `d` is the variable.
 
@@ -145,6 +147,7 @@ A user-defined type shows `{user defined type, 8 bytes}` and one line for each f
 
 Pointing at a function call or at a property runs it, so a function with side effects changes the state of the program.
 
+
 > [!WARNING] BETA 1034
 > Pointing at a call to a **Sub** runs the Sub, and the popup shows `t.Bump:   0`, a value that means nothing. The Sub, and any event handlers it raises, runs once for each hover. Do not point at the name of a procedure that changes the program's state while the program is paused. To read a variable that such a procedure uses, point at the variable.
 
diff --git a/docs/IDE/Debugging/Memory.md b/docs/IDE/Debugging/Memory.md
index 9b6a14398..96389cc5a 100644
--- a/docs/IDE/Debugging/Memory.md
+++ b/docs/IDE/Debugging/Memory.md
@@ -38,11 +38,13 @@ The debugger evaluates the expression in the box as it does a watch, so the box
 - An **array**: the text is replaced with `VarPtr(`*expression*`)`, and the pane shows the array variable.
 - Any other result is a number, and **the number itself is the address**. The name of a **Long** variable therefore shows the memory at the address the variable *holds*, not the memory of the variable. That is what a **LongPtr** holding an address needs. For a variable's own bytes, enter `VarPtr(`*name*`)`.
 
+
 > [!NOTE] BETA 1034
 > A result that is not a whole number --- the name of a **Double**, a **Boolean** or a variable of a user-defined type --- gives no address and does not turn the box red. The rows stay as they were, and the DEBUG CONSOLE prints an *IDE internal error* such as `Uncaught SyntaxError: Cannot convert 1.5 to a BigInt`. Enter `VarPtr(`*name*`)` for such a variable.
 
 The pane starts its first row at that address exactly, and the cell at the address takes the keyboard focus. An expression that cannot be evaluated --- an unknown name, an empty box, anything entered while the program is not paused --- turns the box red and leaves the rows as they were.
 
+
 > [!NOTE] BETA 1034
 > A negative number is taken as an address too. With -8, the first row is labelled `&HFFFFFFF8` and the rows after it `&H00000008`, `&H00000018` and so on. None of them can be read.
 
@@ -86,6 +88,7 @@ Below, `s` holds "Hello twinBASIC". After `s` is entered, the box reads `StrPtr(
 
 `??` in the value cells, and `·` in the text column, mark memory the program cannot read.
 
+
 > [!NOTE] BETA 1034
 > `??` and `·` are also what a cell shows while its bytes are still being read, so unreadable memory looks the same as memory not read yet. Memory that keeps showing `??` cannot be read.
 
@@ -106,6 +109,7 @@ The formats of two and four bytes show each value as a number, so the hexadecima
 
 The second list, **Cols 8**, **Cols 12**, **Cols 16**, **Cols 24** or **Cols 32**, sets the number of values in a row. A row therefore covers more bytes in the larger formats: 16 in **Byte (Hex)** at **Cols 16**, 32 in an **Integer** format and 64 in a **Long** format. The address column steps by the same amount, and is written as `&H` and eight hexadecimal digits.
 
+
 > [!WARNING] BETA 1034
 > The address column of a 64-bit program shows only the low 32 bits of each address. The bytes shown are those at the whole address, but an address of `&H4232CFF438` is labelled `&H32CFF438`, which is another address. Do not copy an address from the column in a 64-bit program; print it with `Hex(VarPtr(`*name*`))` instead.
 
diff --git a/docs/IDE/Debugging/Running.md b/docs/IDE/Debugging/Running.md
index 4aaee49a3..119d3c531 100644
--- a/docs/IDE/Debugging/Running.md
+++ b/docs/IDE/Debugging/Running.md
@@ -45,6 +45,7 @@ At a run-time error, **End** ends only the procedure that failed; see [Traps](Br
 
 In a form that is waiting for input, and in a loop that holds only `DoEvents`, there is no line of yours to stop at. The IDE opens a file of the VB package, `IdleMessageLoopBreak.twin`, and stops in its procedure `IdleMessageLoopBreakpoint`. With a form waiting for input, a line typed in the console works there. Press F5 and the program stops again in the same file. Press it once more to run on.
 
+
 > [!WARNING] BETA 1034
 > A program that is stopped by **Break** in a loop that holds only `DoEvents` ends with a native access violation as soon as the IDE evaluates anything. A line typed in the console does it, even `? 1`, and so does any watch, because the IDE evaluates every watch at every stop. The DEBUG CONSOLE prints `NATIVE EXCEPTION: ACCESS_VIOLATION`, and the IDE stays in the paused state, with the arrow on the VB package's line and **Stop** and the step commands enabled, although the program has ended. Do not use **Break** on a loop that holds only `DoEvents`. Put a statement in the loop and a [breakpoint](Breakpoints-and-Stepping#breakpoints) on it instead. If the IDE is already in that state, **Restart the compiler** in the toolbar puts it right.
 
@@ -69,6 +70,7 @@ Start shows no dialog for a compile error, and it opens no panel. The [DIAGNOSTI
 - **In a procedure that `Main` calls.** The console shows `[LINKER] compilation (codegen) error detected in 'Startup.Main' at line #42`, where 42 is the line of the call. The editor opens `Main`'s module and stops at its `Sub` line with the error panel, titled **ERROR OCCURRED**. Nothing in `Main` has run.
 - **In `Main` itself.** No panel opens. The console shows `[LINKER] compilation (codegen) error detected in '{temp_component}.{temp_procedure}' at line #1`, then `(compile error: codegen error; check for compilation errors)`, and the program is idle again.
 
+
 > [!NOTE] BETA 1034
 > The panel for a compile error gives the description `COMPILATION (CODEGEN) ERROR AT LINE #42.  PLEASE REPORT THIS ERROR TO https://git.io/JMh6S`, and the line it names is the line of the call, not the line of the error. A compile error in your code is not a defect to report. Read the real error in the DIAGNOSTICS panel and fix it.
 
@@ -89,6 +91,7 @@ A preview is also a debug session. A breakpoint in `Form_Load` stops it, and a c
 - A caret at module level, outside a procedure: `RunCodeUnderCursor: failed to identify the procedure at the cursor position.`
 - With no editor active: `runCodeUnderCursor failed: no active text editor.`
 
+
 > [!NOTE] BETA 1034
 > F6 also gives `failed to identify the procedure at the cursor position.` when the caret is on the procedure's `End Sub` or `End Function` line, or on another of its lines at or past the column where that `End` line ends. A comment at the end of the `End` line moves that limit to where the comment starts. Nothing runs. Put the caret nearer the start of a line of the procedure.
 
@@ -119,6 +122,7 @@ A `Shell` that runs from a line you type in the console makes a box too, with no
 - **Opening a form designer.** The IDE tells the compiler to end the program if it is busy. A form program that is waiting for input ends, with `form terminate` and no `Form_Unload`. A loop with no `DoEvents` keeps running, and so does a program that is paused.
 - **Editing the code.** See the next section.
 
+
 > [!NOTE] BETA 1034
 > An `End` statement in a procedure that LLVM compiles ends the run, and then the console shows `restarting from MEMORY []`: the IDE restarts the compiler and compiles the project again. The IDE recovers by itself. Remove the `End` statement, or compile the procedure without LLVM, to avoid the restart.
 
diff --git a/docs/IDE/Forms/Arranging.md b/docs/IDE/Forms/Arranging.md
index 4e4a1ed6f..2170d5cbb 100644
--- a/docs/IDE/Forms/Arranging.md
+++ b/docs/IDE/Forms/Arranging.md
@@ -50,9 +50,11 @@ None of the controls is the reference. The first control you clicked has no spec
 
 **Center (Horizontal)** and **Center (Vertical)** have no keys. **To Grid** has none either. It rounds the **Left** and **Top** of each selected control down to the grid, and rounds the size down too: a control 24 pixels high becomes 20 with a 10-pixel grid. It acts at once, even when **Align Controls To Grid** is off.
 
+
 > [!NOTE] BETA 1034
 > **Center (Horizontal)** and **Center (Vertical)** do not centre the controls on the middle of the selection. They centre each control on the left edge, or the top edge, of the selection. Three controls 100 pixels wide at **Left** 10, 140 and 250 all end at **Left** -40, their middles on 10, and may end outside their container. To line up the middles of a group, align the edges and then type the **Left** or **Top** values in [PROPERTIES](Properties). **Center In Container** centres a group in its container correctly at a zoom of 100 %.
 
+
 > [!NOTE] BETA 1034
 > Each Alt+arrow also opens the IDE's menu bar when you release the keys. The controls are aligned all the same, and nothing is lost. If the menu bar is in the way, align with the **Format → Align** commands or the toolbar buttons instead.
 
@@ -71,6 +73,7 @@ None of the controls is the reference. The first control you clicked has no spec
 
 A command changes nothing when the controls already have the same size.
 
+
 > [!WARNING] BETA 1034
 > **Make Same Size** changes the controls on the surface and marks the form changed, but it does not reach the saved form. If you press Ctrl+S, close the tab and open the form again, the controls have their old sizes. A later change, such as moving a control with an arrow key, saves the new sizes with it. After you use **Make Same Size**, make such a change before you save, or type the **Width** and **Height** values in [PROPERTIES](Properties) instead.
 
@@ -100,6 +103,7 @@ For example, take three controls 100 pixels wide with **Left** 10, 140 and 250,
 
 The spacing commands compare positions that count from each control's own parent. Controls in different containers therefore give odd results. For example, **Remove** on a text box in a frame and a button on the form places the button at the text box's right edge as counted from the frame's corner, not from the form's.
 
+
 > [!WARNING] BETA 1034
 > **Horizontal Spacing** and **Vertical Spacing** move the controls on the surface, but do not mark the form changed and do not make an undo step. Ctrl+Z does not undo them. If you close the tab, the IDE asks no question, and the form opens again with the old positions. The new positions are saved only when a later real change follows, for example moving a control with an arrow key, because that change saves the whole surface. After you use these commands, make such a change before you save, and check the positions in [PROPERTIES](Properties). To go back, type the old **Left** or **Top** values there.
 
@@ -107,6 +111,7 @@ The spacing commands compare positions that count from each control's own parent
 
 **Center In Container (Horizontally)** and **Center In Container (Vertically)** move the selection as a group, so that the group's bounding box is centred in the container the controls share. The controls keep their positions relative to one another. They work for one control or for many, and need all of them to have the same parent. If they do not, nothing moves and a notification says so.
 
+
 > [!NOTE] BETA 1034
 > **Center In Container** reads the width of the container as it is drawn, so at a zoom other than 100 % it moves the controls to the wrong place. At 50 %, a text box in a frame ended at **Left** -9.98 instead of 40, and the group on the form moved by -80 instead of 20. Set the zoom to 100 % before you use the command.
 
diff --git a/docs/IDE/Forms/Controls.md b/docs/IDE/Forms/Controls.md
index a081fcaca..87007887d 100644
--- a/docs/IDE/Forms/Controls.md
+++ b/docs/IDE/Forms/Controls.md
@@ -124,6 +124,7 @@ There are three ways to make an array:
 
 The last two give the new controls the highest **Index** plus one, and turn a lone original with **Index** -1 into index 0. Choosing an event of a member in the [EVENTS tab](Properties#events) inserts a handler with an `Index As Integer` argument, such as `Private Sub Command2_GotFocus(Index As Integer)`.
 
+
 > [!NOTE] BETA 1034
 > The box that asks about the control array comes twice when you give a control the name of a single control. The first **YES** sets the other control's **Index** to 0 and leaves the renamed control's name unchanged, and the same box comes again. The second **YES** renames the control to `X(1)`. Answer **YES** both times. **NO** puts the old name back.
 
diff --git a/docs/IDE/Forms/Designer.md b/docs/IDE/Forms/Designer.md
index 374be4c43..13de6ebb0 100644
--- a/docs/IDE/Forms/Designer.md
+++ b/docs/IDE/Forms/Designer.md
@@ -40,6 +40,7 @@ The new files go into the folder that the Project Explorer has selected. The Pro
 
 Enter creates both files at once and opens the designer in a normal tab, not a preview tab. Esc, or an empty name, creates nothing. A name with a space is written in square brackets, such as `Class [My Form]`, both in the class and in the form's **Name** property. A Windows form, an MDI form and a report are bracketed for a punctuation mark too, such as the dot in `a.b`. A user control, a property page and a CustomControls form are bracketed for a space only.
 
+
 > [!NOTE] BETA 1034
 > The name box accepts a name that is not an identifier, such as `1bad`, and creates the files without a message. The class is then `Class 1bad`.
 
@@ -82,9 +83,11 @@ A file that the designer cannot use opens in one of three ways:
 - A file with a JSON error shows the notification *ERROR: bad JSON encountered when parsing tbform*.
 - A file without a `FormDesignerId` opens normally. The designer makes a new id for the session and does not write it to the file.
 
+
 > [!NOTE] BETA 1034
 > A read-only designer file still gets a tab, which is empty and selected. Close it.
 
+
 > [!NOTE] BETA 1034
 > A form file with a JSON error opens an empty designer with no controls, and PROPERTIES is empty. An edit there is refused, and Ctrl+S leaves the file as it was, so nothing is lost. Correct the JSON in a text editor outside the IDE, and open the form again.
 
@@ -110,6 +113,7 @@ From the code file, **View → Object Designer** and Shift+F7
 > [!NOTE] BETA 1034
 > A folder whose name contains `.twin`, such as `v1.twin.forms`, breaks the switch from the code to the designer, and the notification above appears. The switch from the designer to the code still works. Open the designer from the Project Explorer, or rename the folder.
 
@@ -177,9 +181,11 @@ With a designer focused, the **View** menu shows **Zoom In** and **Zoom Out** in
 ![frmMain in the designer at 50 %, drawn at half size with its grid hidden; the readout under it still gives 380 x 270.](Images/Designer_Zoom50.png){:width="197" height="173"}
 
 
+
 > [!NOTE] BETA 1034
 > **Zoom Out** stops at 50 %, although the box offers 25 %. Choose 25 % in the box.
 
+
 > [!WARNING] BETA 1034
 > The arrow keys also move the selected controls while the zoom box has the focus. With a control selected, Up pressed twice there changes the zoom by two steps and moves the control up by two steps. The form is marked changed, each press is an undo step, and a save keeps the new position. Nothing on the screen says the control moved. Check the positions of the controls before you save.
 
@@ -193,6 +199,7 @@ The lock is stored under the control's name, so it follows a rename, and it cove
 
 ![A form with Show Outlines on, so that a dashed outline surrounds every control. The command button Command1 is selected and locked: a grey lock icon shows on it and its grabbers are grey. The other buttons are unlocked.](Images/Designer_Locked.png){:width="387" height="333"}
 
+
 > [!WARNING] BETA 1034
 > Locking or unlocking controls does not mark the form changed. If you lock a control and close the tab, the IDE asks no question, and the lock is gone when you open the form again. The lock is saved only with the next real change. After you lock a control, make another change to the form, such as moving a control, before you save.
 
@@ -216,9 +223,11 @@ A designer tab shows the dot for unsaved changes as soon as the form changes. 
 > [!NOTE] BETA 1034
 > The tooltip of a form's entry in **HISTORY** ends with `[line: undefined]`, because a form change has no line. Nothing is lost.
 
+
 > [!NOTE] BETA 1034
 > A designer can be marked changed with no edit. Click a control, press Esc to select the form, and then click another control or only move the pointer over the form: the tab gets the dot, and closing asks to save. It does not happen every time. Every control is where it was, so saving or discarding gives the same form. Ctrl+Z then steps through two empty steps before it reaches your last real change.
 
diff --git a/docs/IDE/Forms/Properties.md b/docs/IDE/Forms/Properties.md
index daa1e697d..cc4749d9c 100644
--- a/docs/IDE/Forms/Properties.md
+++ b/docs/IDE/Forms/Properties.md
@@ -70,6 +70,7 @@ A property has the editor that suits its type. A value is committed as the table
 
 If the compiler is busy when you open a property page, the IDE says *Backend compiler seems to be busy. Check there isn't a modal dialog box open behind the IDE window.*
 
+
 > [!NOTE] BETA 1034
 > Ctrl+A does nothing in a property box. It does not select the text, and the selection of the controls on the form is unchanged. Text that you type is added to the text already in the box.
 
@@ -125,6 +126,7 @@ For example, a button at **Left** 10 and **Top** 50 pixels, 100 pixels wide and
 
 A value you type is taken in the same unit. Typing **Left** 450 on that form moves the button to 30 pixels. A font size, **Opacity**, **DrawWidth** and other properties that are not positions show as they are.
 
+
 > [!NOTE] BETA 1034
 > The pane shows **Left**, **Top**, **Width** and **Height** of a control on a CustomControls form multiplied by 15, as if the form were in twips, but takes the value you type as pixels. A button at **Left** 20 pixels shows 300. Typing 40 moves it to 40 pixels, and the box then shows 600. Do not type the value the pane shows. Type pixels, as the readouts on the surface show them.
 
diff --git a/docs/IDE/Forms/Toolbox.md b/docs/IDE/Forms/Toolbox.md
index 7e6522116..ae86c0f58 100644
--- a/docs/IDE/Forms/Toolbox.md
+++ b/docs/IDE/Forms/Toolbox.md
@@ -52,6 +52,7 @@ The magnifier in the title bar opens a search box under it. Typing a printable c
 
 ![The Toolbox with its search box holding Command, Text, beside three tool icons, the pointer, a text box and a command button, and More components below them. The menu of the ... button is open at the right, holding Show Control Names.](Images/Toolbox_Search.png){:width="377" height="129"}
 
+
 > [!NOTE] BETA 1034
 > A **floating** Toolbox lists every tool when a report is open, 24 controls and **More components**, where the docked Toolbox lists seven. A tool that a report does not allow is refused when you use it, so nothing is lost. Dock the Toolbox, or choose only the seven tools that a report allows.
 
diff --git a/docs/IDE/Menu/Format.md b/docs/IDE/Menu/Format.md
index 8a4dcb3c5..ca9419153 100644
--- a/docs/IDE/Menu/Format.md
+++ b/docs/IDE/Menu/Format.md
@@ -16,6 +16,7 @@ The Format menu holds the commands that line up and order the controls selected
 
 Every command acts on the selection in a form designer, so every command is greyed out until a designer is open with a selection. The commands need different numbers of controls: **Align**, **Make Same Size** and the two spacing submenus need two or more, **Make Equal** three or more, and the other commands one or more. The same commands are in the designer's [right-click menu](../../Forms/Controls#the-right-click-menu).
 
+
 > [!WARNING] BETA 1034
 > The spacing commands and **Make Same Size** can leave the new positions or sizes out of the saved form. [Arranging controls](../../Forms/Arranging) says what to do instead.
 
diff --git a/docs/IDE/Projects/Adding-Files.md b/docs/IDE/Projects/Adding-Files.md
index bd14252e0..8d68bcd22 100644
--- a/docs/IDE/Projects/Adding-Files.md
+++ b/docs/IDE/Projects/Adding-Files.md
@@ -67,6 +67,7 @@ The new file goes into the selected folder, or into the folder of the selected f
 
 **Add Folder** and **Add Other File** are not moved to `Sources`: a file made with **Add Other File** while a file in `Resources/CUSTOM` is selected is made in `Resources/CUSTOM`.
 
+
 > [!NOTE] BETA 1034
 > **Add Other File**, and **Paste**, in the `Packages` folder create nothing. The notification says *cannot create files inside the root resources folder*, which names the wrong folder. A file that was cut and then pasted there is deleted: see [Cut, copy and paste](Files-and-Folders#cut-copy-and-paste).
 
@@ -102,6 +103,7 @@ The `.twin` files are written as UTF-8 with Windows line endings. The `.bas` and
 
 A file with any extension but the three below is copied into the project unchanged. It goes into the selected folder, or the folder of the selected file, or `Sources` if nothing is selected. The IDE does not ask when a file of the same name is there already: the new file replaces it.
 
+
 > [!WARNING] BETA 1034
 > Importing a file with the name of a file already in the project crashes the compiler, whether or not the file is open. The Debug Console prints `NATIVE EXCEPTION: ACCESS_VIOLATION`, and the IDE restarts the compiler. When the file is open in a tab, the project then holds the imported text, but the tab keeps the old text. If you edit the tab and press Ctrl+S, the old text with your edit is saved over the import. Close the tab of a file before you import a file with its name.
 
@@ -115,6 +117,7 @@ The tree shows the project's folders and files but not its `Packages`, `Settings
 
 **Import** writes each ticked file at the same path in the current project, and makes the folders for it. A file already at that path is replaced with no question. If the import fails, a box says **Import failed. Please check the DEBUG CONSOLE for details of the errors that occurred.**
 
+
 > [!WARNING] BETA 1034
 > Importing a project's files replaces a file of the same path without a question, and closes the tab open on it first. Unsaved changes in that tab are lost, and so is the saved copy of the file, because the import also saves the project. Do not tick a file whose path is already in your project.
 
@@ -124,6 +127,7 @@ This is for taking files from another project. To open a whole VB6 project as a
 
 When every file of the command is done, the IDE saves the project, as if you had pressed Ctrl+S. For a project with no file yet, the **Save As** dialog opens instead. Cancelling it leaves the file imported and the project unsaved.
 
+
 > [!NOTE] BETA 1034
 > An import saves the project, with the unsaved changes of every open editor, although you did not ask for a save. **Cancel** in the **Import from twinproj...** dialog does the same, and then the IDE restarts its compiler.
 ## Resources
diff --git a/docs/IDE/Projects/Files-and-Folders.md b/docs/IDE/Projects/Files-and-Folders.md
index 05f68d762..00d1890a7 100644
--- a/docs/IDE/Projects/Files-and-Folders.md
+++ b/docs/IDE/Projects/Files-and-Folders.md
@@ -66,6 +66,7 @@ A name is refused, with a notification in the corner of the window, when it:
 
 The notification reads **Cannot rename: the new name entered is invalid.** The seven special folders, `Settings`, `Sources`, `Resources`, `References`, `Packages`, `Miscellaneous` and `ImportedTypeLibraries`, cannot be renamed at all: the notification is **Cannot rename: permission denied.**
 
+
 > [!NOTE] BETA 1034
 > A rename that changes only the case of a name, or nothing, is refused with the same message as an invalid name, so `legacy.bas` cannot be changed to `Legacy.bas` in the tree.
 
@@ -81,6 +82,7 @@ A form's designer file and its `.twin` file belong together. After you rename on
 
 The top row of the tree is the project. Renaming it changes the **Project Name** setting in the `Settings` file. The row, the title bar and the `.twinproj` file keep their names. A name that is not a valid project name gives **Cannot rename the project: the new name entered is invalid.**
 
+
 > [!NOTE] BETA 1034
 > Renaming the project also saves it, with the unsaved edits of the open editors, and the title bar's changed mark goes.
 
@@ -93,9 +95,11 @@ Drag a row, or the selected rows, onto a folder. The folder under the pointer is
 - The `Resources` folder accepts a dropped file, although **Paste** refuses to put one there.
 - A file dragged in from Windows is not accepted.
 
+
 > [!WARNING] BETA 1034
 > Dragging a folder onto one of its own files, or onto one of its own subfolders, is accepted with no message. The tree then nests the folder inside itself, about fifty levels deep. The next Ctrl+S prints the green `[DONE]` line in the Debug Console, and then an error, and it writes a project file that the IDE cannot open: opening it stops at *Please wait... 25.0%*. Never drop a folder onto anything inside it. If you did, do not save. Close the project with **Discard Changes** in the question that follows, and open it again.
 
+
 > [!NOTE] BETA 1034
 > Nothing stops a drag of a special folder. Dragging `Sources` onto `Miscellaneous` moves it there with no message, and the next save keeps it there. Do not drag the special folders. A drop of a file on `Packages` does nothing.
 
@@ -109,9 +113,11 @@ Drag a row, or the selected rows, onto a folder. The folder under the pointer is
 - The menu greys out **Cut** for a read-only row, such as a file of a package. Ctrl+X on a `References` entry still puts a cut on the clipboard.
 - The menu greys out **Cut** and **Copy** for a folder. Ctrl+C on a folder still puts its path on the clipboard.
 
+
 > [!WARNING] BETA 1034
 > A file that you cut and paste onto the `Resources` folder itself, or onto `Packages`, is deleted. The IDE shows the notification *cannot create files inside the root resources folder*, which does not say so. The file is gone from the whole tree, and the next save writes the project without it. Do not cut a file into either folder. If a file went, close the project with **Discard Changes** before you save, and the saved project still has it.
 
+
 > [!NOTE] BETA 1034
 > Pasting a file with no extension into a folder whose own name contains a period, such as `v1.2`, creates nothing and gives no message, when a file of that name is in the folder already. The IDE takes the file's extension from the last period of the whole path.
 
@@ -155,12 +161,15 @@ With the option off, the rows are deleted at once.
 
 Some folders cannot be deleted. For `Sources`, `References`, `Miscellaneous`, `ImportedTypeLibraries` and the compiler packages under `Packages`, the box is shown, but nothing is deleted and no message follows.
 
+
 > [!WARNING] BETA 1034
 > A file with unsaved changes in an open tab is deleted without a word about the changes. The box is the plain one, the tab closes, and the changes go with the file. A folder that holds such a file is the same. Look for the dot on a tab before you delete its file. The saved copy stays in the `.twinproj` file until the next save, so closing the project with **Discard Changes** brings the file back.
 
+
 > [!WARNING] BETA 1034
 > Deleting the project's own row, the top row of the tree, deletes every file in the project. The box reads like a file's: *Are you sure you want to delete* the project's path. After you confirm, the tree is empty, and the only message is an *IDE internal error* line in the Debug Console. The next Ctrl+S prints the green `[DONE]` line and writes a project with no files over the project file. Never delete the top row. If you did, do not save: close the project with **Discard Changes** and open it again.
 
+
 > [!NOTE] BETA 1034
 > Delete with nothing selected shows the box **Are you sure you want to delete the selected items?**, and deletes nothing. Choose **Cancel**. Deleting an entry under `References` removes its row from the tree, but not the reference: it stays in the project's settings. Remove a reference in [Project Settings](References-and-Packages#the-enabled-libraries-list).
 
diff --git a/docs/IDE/Projects/Project-Explorer.md b/docs/IDE/Projects/Project-Explorer.md
index f028de4cd..d2a17ce2c 100644
--- a/docs/IDE/Projects/Project-Explorer.md
+++ b/docs/IDE/Projects/Project-Explorer.md
@@ -100,6 +100,7 @@ These keys work while the tree has the focus.
 
 The tree scrolls to keep the selected row in view. Page Up, Page Down, Home and End do not move the selection.
 
+
 > [!NOTE] BETA 1034
 > Several keys of the tree do not work as they should.
 >
@@ -147,6 +148,7 @@ The **Search** button, or the first letter you type with a row selected, opens a
 
 ![The Project Explorer in its object view, with the search bar open at the top right of the list and Util typed in it. Under MyProject the Modules group lists only Util, beside its file Util.twin, and the Classes and Forms groups list nothing. The VBA, VBRUN and VB packages below are drawn open, and every group shows its full count.](Images/ObjectView_Search.png){:width="267" height="478"}
 
+
 > [!NOTE] BETA 1034
 > The object view has some defects.
 >
diff --git a/docs/IDE/Projects/References-and-Packages.md b/docs/IDE/Projects/References-and-Packages.md
index e809f9d23..18ba8fc4a 100644
--- a/docs/IDE/Projects/References-and-Packages.md
+++ b/docs/IDE/Projects/References-and-Packages.md
@@ -109,12 +109,15 @@ The **Import from file...** button under the lists opens the Windows **Open** di
 - On **Available COM References**, and on **Enabled Libraries**, it asks for a type library. The IDE copies the file into the project as `ImportedTypeLibraries/`, and adds it to the references at once, ticked.
 - On **Available Packages**, it asks for a `.twinpack` file. The package is added to the **Available Packages** list. [Importing a Package from a TWINPACK File](../../../Features/Packages/Importing-TWINPACK) gives the steps.
 
+
 > [!NOTE] BETA 1034
 > A package imported with **Import from file...** is listed but not ticked, so the project does not use it. Tick it yourself after the import.
 
+
 > [!NOTE] BETA 1034
 > **Import from file...** puts a copy of the package under the project's `Packages` folder. Closing the dialog with **Cancel** leaves the copy there, and the project is not marked as changed.
 
+
 > [!NOTE] BETA 1034
 > A type library added with **Import from file...** is drawn on the **Enabled Libraries** list as `[LINKED-PACKAGE]`, and on **Available COM References** it is listed twice, once from the project and once from the registered library, both ticked. Clearing the tick of either row removes the reference.
 
@@ -124,6 +127,7 @@ Choosing an **Add** item for a Windows form without the **WinNativeForms** packa
 
 The reference is listed under the package's own name, `WinNativeForms` or `CustomControlsPackage`, until the compiler has answered, and then under the name of the library, such as `twinBASIC - VB Compatibility Package (Forms)`.
 
+
 > [!NOTE] BETA 1034
 > In a project that has a file, the button also saves the project, although the box speaks only of restarting the compiler. A project with no file yet is not saved, and restarts from memory.
 
@@ -137,6 +141,7 @@ The foot of the dialog has, at the right, the number of settings changed, such a
 
 ![Project Settings on its General group, with a word added to its Project Description, and the box that its title bar's close button opened over it: WARNING: you made changes but haven't saved them. Close without saving?, with Yes, close and No, keep editing. The foot of the dialog behind it reads 2 changed, beside Apply Changes and Cancel.](Images/Settings_Close.png){:width="1202" height="838"}
 
+
 > [!WARNING] BETA 1034
 > The close button of the title bar does not ask when the only change is to the references. The dialog closes, the change is dropped, and nothing says so. The code that compares the settings leaves out the compiler warnings as well. Press **Apply Changes** before you close the dialog.
 
@@ -146,6 +151,7 @@ The `References` folder of the Project Explorer has one entry for each library t
 
 **Cut**, **Rename** and **Delete Permanently** are greyed out on the right-click menu for these entries.
 
+
 > [!NOTE] BETA 1034
 > The Delete key removes a `References` entry from the tree, although its menu item is greyed out. The reference itself stays in the project. To remove a reference, untick it in the **Enabled Libraries** list.
 
diff --git a/docs/IDE/Projects/Saving-and-Exporting.md b/docs/IDE/Projects/Saving-and-Exporting.md
index 05529394a..ae503501a 100644
--- a/docs/IDE/Projects/Saving-and-Exporting.md
+++ b/docs/IDE/Projects/Saving-and-Exporting.md
@@ -52,6 +52,7 @@ The compiler can also ask the IDE for another path: a box, **Failed to save the
 
 Some commands save the project besides **Save Project**: **Save Project As** saves after it chooses the path, and **Apply Changes** in [Project Settings](References-and-Packages#apply-changes-and-closing-the-dialog) saves when the compiler restarts. With the setting [Export After Save](../Project-Settings#export-after-save), a save runs **Export Project** first.
 
+
 > [!NOTE] BETA 1034
 > Several actions save the project although you did not ask. They are [**Import...**](Adding-Files#the-project-is-saved) of ordinary files; the **Import** and **Cancel** buttons of the **Import from twinproj...** dialog; [renaming the project](Files-and-Folders#the-project); and the button of a [Package needed](References-and-Packages#package-needed) box in a project that has a file. Importing ordinary files, **Cancel** in the import dialog and renaming the project write the unsaved changes of the open editors into the file as well.
 
@@ -120,6 +121,7 @@ To write only some of the files, use [**Export...**](Files-and-Folders#export-a-
 
 With [*Export After Save*](../Project-Settings#export-after-save) set, every save runs the export again before it writes the project, into *Export Path*, and empties that folder again.
 
+
 > [!NOTE] BETA 1034
 > When the `Settings` file has an *Export Path* but no `project.exportPathIsV2` entry, the project's name is joined to the path with no separator: `${SourcePath}\exp2` exports into `exp2ProjTry\` for a project named `ProjTry`.
 
diff --git a/docs/IDE/Reports/index.md b/docs/IDE/Reports/index.md
index 3a1f336e1..ff756d95b 100644
--- a/docs/IDE/Reports/index.md
+++ b/docs/IDE/Reports/index.md
@@ -65,6 +65,7 @@ A report holds windowless controls only. Its Toolbox lists seven tools: **Pointe
 
 A control belongs to the section where you draw it. A double-click on a tool places the control in the current section, and a control that you paste goes into the section that is shown. Drawing or pasting any other control gives the notification *Can't add* the control's type *to a report. Only windowless controls allowed.*, and adds nothing.
 
+
 > [!NOTE] BETA 1034
 > A floating Toolbox lists all 24 controls and **More components** when a report is open. A control that a report does not allow is refused when you use it, so nothing is lost. Dock the Toolbox, or use only the seven tools above.
 
diff --git a/docs/LLVM/Getting-Started.md b/docs/LLVM/Getting-Started.md
index 886311cb7..3e76e75f8 100644
--- a/docs/LLVM/Getting-Started.md
+++ b/docs/LLVM/Getting-Started.md
@@ -57,21 +57,27 @@ Building with LLVM does not currently work on Windows 7; Windows 10 or 11 is rec
 
 The main feature not yet supported is passing an error up to the calling procedure. A fix is planned.
 
+
 > [!WARNING] BETA 1034
 > An error that occurs in a procedure with no error handler is not passed to its caller when either of the two procedures is compiled with LLVM: the caller's own `On Error` statement does not catch it. A run in the IDE ends without a message. A built program stops as it does for an unhandled error when only the caller is compiled with LLVM. When the procedure that has no handler is compiled with LLVM, the program shows a box that names the procedure but no error, and then ends with an access violation. Without LLVM, and in VB6, the caller's handler catches the error.
 
+
 > [!NOTE] BETA 1034
 > An error handler in a procedure compiled with LLVM cannot read an error raised with [**Err.Raise**](../tB/Modules/ErrObject/Raise) in that procedure: **Err.Number** returns -353703423 (`&HEAEAEA01`), **Err.Source** is empty, and **Err.Description** is the generic "Application-defined or object-defined error". An error that a statement causes, such as a division by zero, is read correctly.
 
+
 > [!WARNING] BETA 1034
 > An exponentiation (`^`) that overflows raises no error in code compiled with LLVM, and its result is 0, where the same code compiled without LLVM raises error 6, *Overflow*, as VB6 does. In a 32-bit build, a **Double** addition, subtraction or multiplication that overflows raises no error either, and its result is infinity (`1.#INF` or `-1.#INF`), unless the procedure is compiled for SSE2: with `+sse2`, or with the flag of an instruction set that includes it, `+sse3`, `+ssse3`, `+sse4.1`, `+sse4.2`, `+avx`, `+avx2`, `+fma`, `+aes` or `+sha`. [**FloatingPointErrorChecks(True)**](../tB/Core/Attributes#floatingpointerrorchecks) does not change this. A division that overflows and a division by zero raise their errors, and so do an addition, subtraction and multiplication in a 64-bit build or compiled for SSE2. After any of these errors, under `On Error Resume Next`, the variable that was to receive the result keeps its old value; without LLVM it holds infinity.
 
+
 > [!WARNING] BETA 1034
 > A program built with LLVM ends with an access violation (0xC0000005) and no message box at the first [**GoSub**](../tB/Core/GoSub-Return) it runs, in a 32-bit and in a 64-bit build. A run in the IDE is not affected, and neither is a program built without LLVM. Replace the **GoSub** with a call to a **Private Sub**.
 
+
 > [!WARNING] BETA 1034
 > In a 64-bit build with LLVM, a call to a [**Declare**](../tB/Core/Declare)d procedure that passes `ByVal 0&` for its eighth parameter, declared `ByRef` with a 32-bit type such as **Long**, to give the procedure a NULL pointer, can end with an access violation (0xC0000005) and no message box, in the procedure. The eighth parameter of **WideCharToMultiByte** is one such case. Whether the call fails depends on what the stack held before it. The same override for the first to seventh parameters is right. Declare the parameter `ByVal ... As LongPtr` instead. It affects a built exe and a run in the IDE with LLVM turned on, and not a 32-bit build or a build without LLVM.
 
+
 > [!WARNING] BETA 1034
 > In a procedure that has a `ByVal` **Long** or **Single** parameter and, after it, a `ByRef` parameter declared `As Boolean` or `As Integer`, a [**For...Next**](../tB/Core/For-Next) loop whose **Step** is that `ByVal` parameter (or a variable set from it) runs with a wrong step, or with a wrong start or end when those are parameters too. The wrong value depends on where the caller's argument for the `ByRef` parameter is stored. In a built exe it is 20 in a 64-bit build and 25 in a 32-bit build when the argument is a local variable of the caller, and other values when it is a module-level variable or an array element; in a run in the IDE it varies more, up to millions. `For i = 1 To 7 Step x` with `x` equal to 3 and a local variable passed runs once, not three times. No error is raised, so the loop silently gives a wrong result. It affects a **Sub**, a **Function** and a class method, a built exe and a run in the IDE with LLVM turned on, in a 32-bit and in a 64-bit build, and not a build without LLVM. Whether a larger procedure shows it can depend on its other statements. A second parameter that is `ByRef ... As Long` (or **Byte**, **Single**, **Double**, **Currency**, **Date**, **String**, **Variant**, an Enum, a UDT, an array, an object or a pointer), `ByVal ... As Boolean`, a `ByRef ... As Boolean` placed before the step parameter, a step parameter of type **Integer**, **Double** or **Currency**, a `Do` loop, a loop with no **Step** and a loop with a literal **Step** are right. Declare the second parameter `ByVal`, or place it before the step parameter.
 
@@ -105,6 +111,7 @@ Public Sub DoNotOptimizeMe()
 End Sub
 ```
 
+
 > [!WARNING] BETA 1034
 > A run in the IDE with LLVM turned on for the whole project under **Compiler Options (DEBUG)** ends with an access violation when a procedure marked `[CompilerOptions("")]` calls a procedure that is compiled with LLVM, and `Sub Main` reaches the marked procedure: it calls it, or calls a procedure that does. A marked procedure that `Sub Main` does not reach is not affected, and neither is a built program.
 
diff --git a/docs/Miscellaneous/FAQs.md b/docs/Miscellaneous/FAQs.md
index 155d9be34..b3b0ef31b 100644
--- a/docs/Miscellaneous/FAQs.md
+++ b/docs/Miscellaneous/FAQs.md
@@ -120,7 +120,87 @@ Currently, all basic controls have reimplementations in twinBASIC that support U
 
 Most projects do not use these reverse engineered internals, but some do: most commonly, for self-subclassing and callbacks inside Forms/Classes/UserControls; and also for multithreading and inline assembly. These routines have native support in twinBASIC without requiring internals hacks, so replacing these small parts of a few programs is very simple: `AddressOf` is supported on class members, so you can use regular subclassing and callback methods as you would if they were in a .bas module. `CreateThread` can be called without any special steps. And tB supports [statically linked](Features/Advanced/Static-Linking) .obj files allowing incorporation of code from other languages (still experimental, and only for code that needs nothing from the C runtime), inline assembly in the form of `Emit()`/`EmitAny()` to insert instructions, and further support is planned in the future.
 
+
 Additionally, twinBASIC redirects the most common msvbvm60.dll (also msvbvm50.dll/vbe6.dll/vbe7.dll) functions used as `Declare` statements by users, all of which also work in x64, if you add the `PtrSafe` keyword like any other DLL definition. The following functions currently have redirects: `VarPtr, GetMem1, GetMem2, GetMem4, GetMem8, PutMem1, PutMem2, PutMem4, PutMem8, __vbaObjSet, __vbaObjSetAddRef, __vbaObjAddRef, __vbaCastObj, __vbaCopyBytes, __vbaCopyBytesZero, __vbaRefVarAry`, and `__vbaAryMove`. You may continue to use these with `Declare` statements to support the particular signatures you prefer. Further, declares for olepro32.dll are redirected to identical functions in oleaut32.dll, as olepro32 was deprecated by NT4 and doesn't have a 64bit version.
+
+
+```tb hidden slot=module
+Public Declare PtrSafe Function ArrPtr Lib "msvbvm60" Alias "VarPtr" (Ptr() As Any) As LongPtr
+Public Declare PtrSafe Sub GetMem1 Lib "msvbvm60" (ByVal Addr As LongPtr, RetVal As Byte)
+Public Declare PtrSafe Sub GetMem2 Lib "msvbvm60" (ByVal Addr As LongPtr, RetVal As Integer)
+Public Declare PtrSafe Sub GetMem4 Lib "msvbvm60" (ByVal Addr As LongPtr, RetVal As Long)
+Public Declare PtrSafe Sub GetMem8 Lib "msvbvm60" (ByVal Addr As LongPtr, RetVal As Currency)
+Public Declare PtrSafe Sub PutMem1 Lib "msvbvm60" (ByVal Addr As LongPtr, ByVal NewVal As Byte)
+Public Declare PtrSafe Sub PutMem2 Lib "msvbvm60" (ByVal Addr As LongPtr, ByVal NewVal As Integer)
+Public Declare PtrSafe Sub PutMem4 Lib "msvbvm60" (ByVal Addr As LongPtr, ByVal NewVal As Long)
+Public Declare PtrSafe Sub PutMem8 Lib "msvbvm60" (ByVal Addr As LongPtr, ByVal NewVal As Currency)
+Public Declare PtrSafe Function ObjSet Lib "msvbvm60" Alias "__vbaObjSet" (Dst As Object, ByVal Src As LongPtr) As LongPtr
+Public Declare PtrSafe Function ObjSetAddRef Lib "msvbvm60" Alias "__vbaObjSetAddref" (Dst As Object, ByVal Src As LongPtr) As LongPtr
+Public Declare PtrSafe Sub ObjAddRef Lib "msvbvm60" Alias "__vbaObjAddref" (ByVal Obj As LongPtr)
+Public Declare PtrSafe Function CastObj Lib "msvbvm60" Alias "__vbaCastObj" (ByVal Obj As LongPtr, ByVal IID As LongPtr) As LongPtr
+Public Declare PtrSafe Sub CopyBytes Lib "msvbvm60" Alias "__vbaCopyBytes" (ByVal Length As Long, ByVal Dst As LongPtr, ByVal Src As LongPtr)
+Public Declare PtrSafe Sub CopyBytesZero Lib "msvbvm60" Alias "__vbaCopyBytesZero" (ByVal Length As Long, ByVal Dst As LongPtr, ByVal Src As LongPtr)
+Public Declare PtrSafe Function RefVarAry Lib "msvbvm60" Alias "__vbaRefVarAry" (V As Variant) As LongPtr
+Public Declare PtrSafe Sub AryMove Lib "msvbvm60" Alias "__vbaAryMove" (Dst() As Any, Src() As Any)
+Public Declare PtrSafe Sub GetMem4From50 Lib "msvbvm50" Alias "GetMem4" (ByVal Addr As LongPtr, RetVal As Long)
+Public Declare PtrSafe Sub GetMem4From6 Lib "vbe6" Alias "GetMem4" (ByVal Addr As LongPtr, RetVal As Long)
+Public Declare PtrSafe Sub GetMem4From7 Lib "vbe7" Alias "GetMem4" (ByVal Addr As LongPtr, RetVal As Long)
+Public Declare PtrSafe Function OleTranslateColor Lib "olepro32" (ByVal Clr As Long, ByVal Palette As LongPtr, ColorRef As Long) As Long
+```
+
+```tb probe check_run id=msvbvm60-redirects arch=win32,win64
+Dim b As Byte, i As Integer, l As Long, cy As Currency
+Dim r1 As Byte, r2 As Integer, r4 As Long, r8 As Currency
+PutMem1 VarPtr(b), 200
+GetMem1 VarPtr(b), r1
+Debug.Print r1                                        ' 200
+PutMem2 VarPtr(i), -2
+GetMem2 VarPtr(i), r2
+Debug.Print r2                                        ' -2
+PutMem4 VarPtr(l), 123456789
+GetMem4 VarPtr(l), r4
+Debug.Print r4                                        ' 123456789
+PutMem8 VarPtr(cy), 1234.5678@
+GetMem8 VarPtr(cy), r8
+Debug.Print r8                                        ' 1234.5678
+r4 = 0: GetMem4From50 VarPtr(l), r4
+Debug.Print r4                                        ' 123456789
+r4 = 0: GetMem4From6 VarPtr(l), r4
+Debug.Print r4                                        ' 123456789
+r4 = 0: GetMem4From7 VarPtr(l), r4
+Debug.Print r4                                        ' 123456789
+Dim a(2) As Long
+Debug.Print ArrPtr(a) <> 0                            ' True
+Dim col As New Collection, o As Object, o2 As Object
+ObjAddRef ObjPtr(col)
+ObjSet o, ObjPtr(col)
+Debug.Print o Is col                                  ' True
+ObjSetAddRef o2, ObjPtr(col)
+Debug.Print o2 Is col                                 ' True
+Dim iid(15) As Byte
+iid(8) = &HC0: iid(15) = &H46
+Debug.Print CastObj(ObjPtr(col), VarPtr(iid(0))) <> 0 ' True
+Dim s As Long, d As Long
+s = 7
+CopyBytes 4, VarPtr(d), VarPtr(s)
+Debug.Print d                                         ' 7
+s = 9
+CopyBytesZero 4, VarPtr(d), VarPtr(s)
+Debug.Print d & " " & s                               ' 9 0
+Dim v As Variant
+v = Array(1, 2)
+Debug.Print RefVarAry(v) <> 0                         ' True
+Dim x() As Long, y() As Long
+ReDim y(2)
+y(1) = 5
+AryMove x, y
+Debug.Print x(1)                                      ' 5
+Dim colour As Long
+Debug.Print OleTranslateColor(&HFF&, 0, colour) & " " & colour   ' 0 255
+On Error Resume Next
+Debug.Print UBound(y)
+Debug.Print Err.Number                                ' 9
+```
 
 Other than those special cases, it's exceedingly rare for projects to depend on reverse engineered internals. So the vast majority of projects run with zero modification. 
 
@@ -288,6 +368,7 @@ In a built ActiveX DLL, these lines have the same effect as the twinBASIC attrib
 
 
 
+
 > [!WARNING] BETA 1034
 > Some `Attribute` lines have no effect, and nothing says so. `VB_HelpID` and `VB_VarHelpID` give no help context ID. `VB_VarMemberFlags` leaves a public field neither hidden nor nonbrowsable. `VB_MemberFlags = "200"` (the user-interface default) and `"14"` (the data-binding flags `bindable` and `displaybind`) set no flag. A component moved from VB6 loses these when it is built again. In a `.twin` file, `[Hidden]` on a public field does hide it.
 
diff --git a/docs/Reference/Attributes.md b/docs/Reference/Attributes.md index a5fc68872..33575ae67 100644 --- a/docs/Reference/Attributes.md +++ b/docs/Reference/Attributes.md @@ -295,6 +295,7 @@ The restriction to modules is not an oversight. Folding a call to a method would Calls are folded only when **Constant Function Folding** is set to **Yes** in [Project Settings](../IDE/Project-Settings#constant-function-folding). A **Property Get** in a module folds as a function does. On a **Sub** the attribute is accepted and has no effect. + > [!WARNING] BETA 1034 > The compiler also accepts the attribute on a **Declare Function**. With **Constant Function Folding** on, a call to such a function whose arguments are all constants crashes the compiler during the build, though the project compiles without an error. @@ -1056,6 +1057,7 @@ When set, a global instance of the class is created when the application starts. This attribute is equivalent to the `VB_PredeclaredId` attribute in VBx .cls files. + > [!NOTE] BETA 1034 > In an ActiveX build, the class is marked `appobject` in the type library as well as `predeclid`, which is what [AppObject](#appobject) gives it, so a client that references the library treats the members of the class as global names. @@ -1115,6 +1117,7 @@ End Property A property takes the page from its first accessor in the source, normally the **Property Get**, whichever kind of accessor that is. Written on a later accessor, the attribute has no effect, though its name is still checked; when two accessors name different pages, the control reports the first one's. On a **Sub**, a **Function** or a **Public** variable of the control, the control reports the page for that member's DISPID in the same way. In a class that is not a **UserControl**, or on an **Interface** member, the compiler accepts the attribute and it has no effect. The legacy line `Attribute Caption.VB_ProcData.VB_Invoke_Property = "CaptionPage"` inside the **Property Get** means the same. The argument must be a **String**: `[PropertyPage]` with no argument, or with a **Boolean** or a number, is TB5163, *Expected a single String argument*. On a procedure, a name that matches no property page in the project is error TB5259, *Failed to find named PropertyPage '...' for procedure '...'*. That includes an empty string and a name that differs only in case. A variable's name is not checked. + > [!NOTE] BETA 1034 > VB6 stores a property's page as `"PageName;Category"` in the legacy attribute, with the category the property sheet groups the property under. twinBASIC reads such a legacy line and drops what follows the semicolon. **[PropertyPage]** takes the whole string as the name, so `[PropertyPage("CaptionPage;Appearance")]` is TB5259. @@ -1263,9 +1266,11 @@ A procedure with the attribute in a package that the project references runs too the project called nothing in the package. The exe printed the package's line, then Main's. --> + > [!NOTE] BETA 1034 > The attribute is not allowed on a generic procedure, such as `Sub Pre(Of T)()`. The project compiles in the editor without an error, but building it fails with the message *Build FAILED due to invalid RunBeforeStartupObject procedure*, which names neither the procedure nor the reason. + > [!WARNING] BETA 1034 > The compiler accepts the attribute on more than one procedure, with no error or warning, and the built program runs only the last of them; the others never run. It also accepts the attribute on a procedure with parameters, which then runs with arbitrary values for its parameters, even for an **Optional** parameter that has a default. Give the attribute to one procedure, with no parameters. diff --git a/docs/Reference/Built-In/WinNativeCommonCtls/ListView/ListItem.md b/docs/Reference/Built-In/WinNativeCommonCtls/ListView/ListItem.md index f3ba5ec6a..2645a4355 100644 --- a/docs/Reference/Built-In/WinNativeCommonCtls/ListView/ListItem.md +++ b/docs/Reference/Built-In/WinNativeCommonCtls/ListView/ListItem.md @@ -32,6 +32,7 @@ Properties The background color used to render this row. **OLE_COLOR**. Default: `-1` (transparent --- defer to [**ListView.BackColor**](.#backcolor)). A change repaints the list at once. A value that is not a color raises error 380, *Invalid property value*, and the property keeps its color. + > [!NOTE] BETA 1034 > Once a color is assigned, assigning the default, `-1`, raises error 380, so the row cannot be made transparent again. diff --git a/docs/Reference/Built-In/WinNativeCommonCtls/ListView/index.md b/docs/Reference/Built-In/WinNativeCommonCtls/ListView/index.md index cef8f83ea..370db565b 100644 --- a/docs/Reference/Built-In/WinNativeCommonCtls/ListView/index.md +++ b/docs/Reference/Built-In/WinNativeCommonCtls/ListView/index.md @@ -205,6 +205,7 @@ The [**ImageList**](../ImageList/) used for small icons in **lvwSmallIcon**, **l Whether item-label text has an opaque background. A member of [**ListTextBackgroundConstants**](#listtextbackgroundconstants). Default: **lvwTransparent**. Changing it repaints the list at once. The opaque background is [**BackColor**](#backcolor), the color the list is already drawn in, so the two settings look the same. + > [!NOTE] BETA 1034 > **TextBackground** has no visible effect: the control has no **Picture** property, the background image an opaque text background would hide. diff --git a/docs/Reference/Built-In/WinNativeCommonCtls/Slider.md b/docs/Reference/Built-In/WinNativeCommonCtls/Slider.md index d45b853ed..29d4adfd8 100644 --- a/docs/Reference/Built-In/WinNativeCommonCtls/Slider.md +++ b/docs/Reference/Built-In/WinNativeCommonCtls/Slider.md @@ -53,6 +53,7 @@ Properties The background color of the control. **OLE_COLOR**. Default: **vbButtonFace**. + > [!NOTE] BETA 1034 > A change to **BackColor** does not repaint the control, and neither does invalidating it: the new color shows only when the user drags the thumb, or when the control is hidden and shown again. diff --git a/docs/Reference/Built-In/tbIDE/FileSystemItem.md b/docs/Reference/Built-In/tbIDE/FileSystemItem.md index 39c913e79..cc75826e2 100644 --- a/docs/Reference/Built-In/tbIDE/FileSystemItem.md +++ b/docs/Reference/Built-In/tbIDE/FileSystemItem.md @@ -37,6 +37,7 @@ The item's name (the last segment of its [**Path**](#path)). **String**, read-on The folder that contains this item. **As** [**Folder**](Folder). Read-only. The **Parent** of the project's folder, [**Project.RootFolder**](Project#rootfolder), is the file system's root, [**FileSystem.RootFolder**](FileSystem#rootfolder), whose **Path** is `twinbasic:/`. + > [!WARNING] BETA 1034 > Reading **Parent** of the file system's root crashes the compiler, and the add-in with it. The read itself crashes, before anything is done with the value, so neither `Is Nothing` nor **TypeName** can test it, and each compiler the IDE restarts crashes again when the add-in reaches the read. Tell the root by its **Path**, `twinbasic:/`, rather than by its **Parent**. diff --git a/docs/Reference/Built-In/tbIDE/HtmlElement.md b/docs/Reference/Built-In/tbIDE/HtmlElement.md index 69be930ec..f8011c6f5 100644 --- a/docs/Reference/Built-In/tbIDE/HtmlElement.md +++ b/docs/Reference/Built-In/tbIDE/HtmlElement.md @@ -99,6 +99,7 @@ raiseEvent(eventName, event, stopPropagation, ...customData) --- pass an event name (any string), the DOM `event` object, a boolean controlling propagation, and any number of trailing custom-data values. The addin then registers a listener with the same event name through **AddEventListener**, and the custom-data values arrive on the [**HtmlEventProperties**](HtmlEventProperties) as `eventInfo.customData0`, `eventInfo.customData1`, … (numerically indexed from zero). This pattern is used heavily in sample 13 (listview) and sample 15 (Global Search) to attach handlers to per-item buttons rendered inside a listview's HTML. + > [!NOTE] BETA 1034 > **raiseEvent** works only inside a `"listview"` or `"virtuallistview"` widget, or inside a widget added with [**CodeEditor.AddMonacoWidget**](CodeEditor#addmonacowidget). It passes the event to the nearest enclosing widget: in a list view, register the listener on the list view object, `.Properties.listview.AddEventListener("name", ...)`, as sample 15 does; in a code-editor widget, on the widget's own element. In plain tool-window HTML there is no such widget, and **raiseEvent** throws a `TypeError` in the IDE's page and the listener is not called. > diff --git a/docs/Reference/Built-In/tbIDE/HtmlElementProperties.md b/docs/Reference/Built-In/tbIDE/HtmlElementProperties.md index ed7cd2a54..82c984015 100644 --- a/docs/Reference/Built-In/tbIDE/HtmlElementProperties.md +++ b/docs/Reference/Built-In/tbIDE/HtmlElementProperties.md @@ -24,6 +24,7 @@ The shorthand reads at run time as a chain of `Item("name")` lookups against the > [!IMPORTANT] > This interface is **`[COMExtensible(True)]`**. Property names are resolved against the live DOM element at run time, not declared statically on the interface. The compiler does not validate names --- a typo (`.innerTxt = "..."` instead of `.innerText = "..."`) fails silently or throws at run time. The accepted set is **every DOM property of the underlying tag**, plus any custom-widget extensions; the reference does not enumerate it. + > [!NOTE] BETA 1034 > A property whose name starts with `on` is ignored. `.onclick = "..."` sets nothing, and the call returns without an error. Register a listener with [**HtmlElement.AddEventListener**](HtmlElement#addeventlistener) instead. diff --git a/docs/Reference/Built-In/tbIDE/KeyboardShortcuts.md b/docs/Reference/Built-In/tbIDE/KeyboardShortcuts.md index e39239fb7..0a6f8e9da 100644 --- a/docs/Reference/Built-In/tbIDE/KeyboardShortcuts.md +++ b/docs/Reference/Built-In/tbIDE/KeyboardShortcuts.md @@ -29,6 +29,7 @@ End Sub The shortcut fires when the key is released, wherever the focus is in the IDE's window --- in the code editor, in the DEBUG CONSOLE, or on nothing at all --- except while a dialog or the rename box is open. If the IDE has a command of its own on the same key, that command runs as well: F1, for one, also expands or collapses the signature help while it is showing. Shift+F12 has no command in the IDE's default keymap. + > [!NOTE] BETA 1034 > A shortcut that includes `{CTRL}` or `{ALT}` does not fire when the user presses it. The IDE matches an add-in's shortcut when the key is released, against a press of the same key less than half a second earlier, and it does not record a key pressed while Ctrl or Alt is held down. Such a shortcut fires only if the same key was also pressed on its own within that half second. Use a function key, alone or with `{SHIFT}`. While the [Help Add-In](../../IDE/AddIns/Help) is loaded, shortcuts with `{CTRL}` and `{ALT}` fire as the others do, for every add-in. diff --git a/docs/Reference/COM-Interfaces/IDispatch.md b/docs/Reference/COM-Interfaces/IDispatch.md index f7745a35e..f923778ed 100644 --- a/docs/Reference/COM-Interfaces/IDispatch.md +++ b/docs/Reference/COM-Interfaces/IDispatch.md @@ -231,6 +231,7 @@ The four [**VbCallType**](../../tB/Modules/Constants/VbCallType) values are the | The object is **Nothing** | 91, *Object variable or With block variable not set* | | **CallByName** on a value that is not an object | 424, *Object required* | + > [!WARNING] BETA 1034 > A call that fails inside **Invoke** can be made twice, so its side effects happen twice and nothing says so. VB6 makes the call once. A failed property assignment is followed by a second **Invoke** with *wFlags* 3. A call with more arguments than a **Sub** takes runs the **Sub**, then fails with error 13, and a late-bound statement does this twice. Code that implements **Invoke** must not change state before it can still fail. diff --git a/docs/Reference/COM-Interfaces/IErrorInfo.md b/docs/Reference/COM-Interfaces/IErrorInfo.md index 427459a72..b42d8554d 100644 --- a/docs/Reference/COM-Interfaces/IErrorInfo.md +++ b/docs/Reference/COM-Interfaces/IErrorInfo.md @@ -184,6 +184,7 @@ The object that **GetErrorInfo** returns then holds what the method gave **Raise **GetErrorInfo** empties the slot as COM requires, so a second read returns `Nothing`. + > [!WARNING] BETA 1034 > The object does not keep the values it was given, and no error tells the caller. It reads them from the **Err** object each time a method is called. [**Err.Clear**](../../tB/Modules/ErrObject/Clear) empties **Err**, so an object read after it returns empty strings and 0, and the next error changes the values to its own. COM requires an **IErrorInfo** to keep what was stored in it. Read the five properties before calling anything else that clears **Err** or raises an error. @@ -229,6 +230,7 @@ A method can return a failure **HRESULT** without calling **SetErrorInfo**, and | `&H80070005` (`E_ACCESSDENIED`) | -2147024891 | `Automation error` | | `&H8FFF0001` (no meaning) | -1879113727 | `Automation error` | + > [!WARNING] BETA 1034 > The slot is not always empty, and no error tells the caller. A handled error leaves in it an object that reads from **Err**, even when the error was handled in twinBASIC code that never read the slot. VB6 leaves the slot empty. When **Err** has been cleared in between, that object has an empty description, so **Description** of a failure that arrived without information is the standard text of the code: `Unspecified error` for `E_FAIL`, `Invalid procedure call or argument` for `&H800A0005` and `Access is denied.`, in the language of the system, for `E_ACCESSDENIED`. A program that must tell failures apart reads **Number** and does not rely on **Description** for a code that arrived without information. diff --git a/docs/Reference/COM-Interfaces/IUnknown.md b/docs/Reference/COM-Interfaces/IUnknown.md index f88f3b896..8d26a2c68 100644 --- a/docs/Reference/COM-Interfaces/IUnknown.md +++ b/docs/Reference/COM-Interfaces/IUnknown.md @@ -41,6 +41,7 @@ Syntax: *object*.**QueryInterface** *riid*, *ppvObject* Returns `S_OK` and a pointer with one reference added for the caller, or `E_NOINTERFACE` (`&H80004002`) and a null pointer. + > [!NOTE] BETA 1034 > An object of a class that the project compiles --- a class, a form, a control --- returns `E_NOINTERFACE` for an interface it does not support but leaves *ppvObject* as it was, instead of setting it to a null pointer. The runtime's own objects, such as a **Collection**, set it to a null pointer, as every object of VB6 does. A caller tests the result, not the pointer. @@ -163,6 +164,7 @@ A class never needs to implement **IUnknown**: the compiler gives every class an `Implements stdole.IUnknown` compiles, and the class works as before. A project's own **Interface** that carries the identifier of **IUnknown** and extends another interface also compiles, and a class can implement it. + > [!WARNING] BETA 1034 > That interface is unusable. **Set** to a variable of that type gives the object's ordinary **IUnknown** pointer, which has only the three methods of **IUnknown**. Calling a method the interface declares after them ends the program with an access violation, and any unsaved data is lost. The compiler gives no diagnostic. Give the identifier of **IUnknown** only to an interface declared **Extends Nothing** with the three methods of **IUnknown**, as in [Calling it from twinBASIC](#calling-it-from-twinbasic); otherwise use `stdole.IUnknown`. diff --git a/docs/Reference/Compiler Constants.md b/docs/Reference/Compiler Constants.md index f5eb7f52e..fa27a68b1 100644 --- a/docs/Reference/Compiler Constants.md +++ b/docs/Reference/Compiler Constants.md @@ -109,7 +109,7 @@ Or more simply, to decide whether to use `PtrSafe`, and then `DeclareWide` or ot > [!IMPORTANT] > 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 1034 > 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. diff --git a/docs/Reference/Contributed/ArrayListLib/index.md b/docs/Reference/Contributed/ArrayListLib/index.md index 904f93d23..ccd3cc661 100644 --- a/docs/Reference/Contributed/ArrayListLib/index.md +++ b/docs/Reference/Contributed/ArrayListLib/index.md @@ -19,6 +19,7 @@ The **ArrayListLib** contributed package is a list class for twinBASIC, in place This section documents version 1.6.151.1, which is the version on TWINSERV, published under the Unlicense. + > [!NOTE] 1.6.151.1, BETA 1034 > The package does not compile, so a project that references it does not build (error TB5001, in a 32-bit and in a 64-bit build). **ArrayList** passes the addresses of two items, as **Long** values, to its private procedure **VSwap**, which declares both parameters as `ByRef` structures, in **Reverse** (`ArrayList.twin`, line 297) and in the two quicksort procedures that **Sort** calls (lines 585 and 602). **MemMoveBSTR**, a private procedure of the module **LibMemoryEx**, passes an address to **LSetByVal** in the same way (`Modules/LibMemoryEx.twin`, line 104), and **LSetByVal** declares its parameter as a `ByRef` structure that holds a string pointer. A pointer passed for a structure has to be passed `ByVal`. The behaviour described on these pages is that of the package with the two arguments of each **VSwap** call passed `ByVal`, and with **LSetByVal** taking the address as a **LongPtr** and putting it in a structure of its own. That copy builds in 32-bit and in 64-bit programs, and both give the same results. diff --git a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogPrint.md b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogPrint.md index 1b7d003fb..34d3b305c 100644 --- a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogPrint.md +++ b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogPrint.md @@ -53,8 +53,6 @@ The class frees the printer data that Windows allocates for the dialog. The prin > [!IMPORTANT] 1.1.0.0 > With [**ReturnDC**](#returndc) **True**, which is the default, every **OK** creates a device context that the class never deletes. The program must delete it with the API function `DeleteDC` when it has finished with it, as the sample at the head of the page does, or set **ReturnDC** to **False** when it does not need the context. [**Reset**](#reset), and a later [**ShowDialog**](#showdialog) that returns **True**, replace the handle without deleting the old context. - - > [!NOTE] 1.1.0.0 > The page numbers and the number of copies are passed to Windows as 16-bit integers. A [**FromPage**](#frompage), [**ToPage**](#topage), [**MinPage**](#minpage), [**MaxPage**](#maxpage) or [**Copies**](#copies) that does not fit, one above 32,767 or one below -32,768, makes [**ShowDialog**](#showdialog) raise error 6, *Overflow*, before the dialog opens. diff --git a/docs/Reference/Contributed/TBMANLIB_ListBoxHScroll/cListboxHScroll/Show.md b/docs/Reference/Contributed/TBMANLIB_ListBoxHScroll/cListboxHScroll/Show.md index 0a2aae93e..480e94b28 100644 --- a/docs/Reference/Contributed/TBMANLIB_ListBoxHScroll/cListboxHScroll/Show.md +++ b/docs/Reference/Contributed/TBMANLIB_ListBoxHScroll/cListboxHScroll/Show.md @@ -33,7 +33,6 @@ After **Show** returns, the **ListBox** scrolls horizontally when any item is wi > [!IMPORTANT] timeless > **Show** must be called after the **ListBox** has been populated. If items are added or removed after the call, the horizontal extent is not updated automatically. Call **Show** again on the same control to recalculate the extent for the updated item list. - > [!NOTE] timeless diff --git a/docs/Reference/Contributed/TBMANLIB_ListBoxHScroll/cListboxHScroll/UnShow.md b/docs/Reference/Contributed/TBMANLIB_ListBoxHScroll/cListboxHScroll/UnShow.md index 46c6ac854..bc0dd5a8e 100644 --- a/docs/Reference/Contributed/TBMANLIB_ListBoxHScroll/cListboxHScroll/UnShow.md +++ b/docs/Reference/Contributed/TBMANLIB_ListBoxHScroll/cListboxHScroll/UnShow.md @@ -25,7 +25,6 @@ Syntax: *object*.**UnShow** > [!IMPORTANT] timeless > The class destructor (`Class_Terminate`) calls the internal `RemoveListBoxSubclass` helper automatically, so **UnShow** need only be called explicitly when the subclass must be removed before the **cListboxHScroll** instance goes out of scope. - ### Example diff --git a/docs/Reference/Contributed/TBMANLIB_ListBoxHScroll/cListboxHScroll/index.md b/docs/Reference/Contributed/TBMANLIB_ListBoxHScroll/cListboxHScroll/index.md index 5097419fc..66b54e84f 100644 --- a/docs/Reference/Contributed/TBMANLIB_ListBoxHScroll/cListboxHScroll/index.md +++ b/docs/Reference/Contributed/TBMANLIB_ListBoxHScroll/cListboxHScroll/index.md @@ -66,7 +66,6 @@ The scroll range is calculated from the widest item's pixel width measured with > [!IMPORTANT] timeless > The control's **hWnd** must be valid (the form must be loaded and the control fully created) before calling **Show**. Calling **Show** before the control's window exists returns silently without installing any scrolling behavior. - ### UnShow {: .no_toc } diff --git a/docs/Reference/Contributed/TBMANLIB_RES/cRes/Read.md b/docs/Reference/Contributed/TBMANLIB_RES/cRes/Read.md index e10168e23..7c748dcbf 100644 --- a/docs/Reference/Contributed/TBMANLIB_RES/cRes/Read.md +++ b/docs/Reference/Contributed/TBMANLIB_RES/cRes/Read.md @@ -26,7 +26,6 @@ Syntax: *object*.**Read**( *id*, *Type* ) **As cRes** > [!IMPORTANT] timeless > **Read** must be called before any retrieval method. The resource data is not populated at construction time; a retrieval method called without a prior **Read** will operate on an empty byte array and may raise a runtime error. - > [!NOTE] 1.0.0.4 diff --git a/docs/Reference/Contributed/TBMANLIB_RES/cRes/ReturnBase64.md b/docs/Reference/Contributed/TBMANLIB_RES/cRes/ReturnBase64.md index 94fc52657..f16765264 100644 --- a/docs/Reference/Contributed/TBMANLIB_RES/cRes/ReturnBase64.md +++ b/docs/Reference/Contributed/TBMANLIB_RES/cRes/ReturnBase64.md @@ -45,7 +45,6 @@ The following **Base64Prefix** values are defined: > [!IMPORTANT] 1.0.0.4 > **ReturnBase64** requires that MSXML2 is registered on the machine. It creates an `MSXML2.DOMDocument` COM object internally. This call will fail at run time on systems where MSXML2 is not available. - > [!NOTE] timeless diff --git a/docs/Reference/Contributed/TBMANLIB_RES/cRes/ReturnPicture.md b/docs/Reference/Contributed/TBMANLIB_RES/cRes/ReturnPicture.md index 3b2440050..92b8ac8d9 100644 --- a/docs/Reference/Contributed/TBMANLIB_RES/cRes/ReturnPicture.md +++ b/docs/Reference/Contributed/TBMANLIB_RES/cRes/ReturnPicture.md @@ -18,7 +18,6 @@ Syntax: *object*.**ReturnPicture**() **As IPictureDisp** > [!IMPORTANT] timeless > [**Read**](Read) must be called before **ReturnPicture**. An instance on which **Read** has not been called holds no image data for **LoadPicture** to read. - ### Example diff --git a/docs/Reference/Contributed/VBComDlg/CommonDialog.md b/docs/Reference/Contributed/VBComDlg/CommonDialog.md index 08f186358..a7bebf3a1 100644 --- a/docs/Reference/Contributed/VBComDlg/CommonDialog.md +++ b/docs/Reference/Contributed/VBComDlg/CommonDialog.md @@ -345,7 +345,6 @@ Syntax: *object*.**HookEvents** [ = *value* ] > [!IMPORTANT] timeless > **HookEvents** must be set to **True** before calling the corresponding `Show*` method. Setting it after the call has no effect on the current invocation. - ### InitDir {: .no_toc } diff --git a/docs/Reference/Contributed/WinReg/index.md b/docs/Reference/Contributed/WinReg/index.md index 23633858c..f868c0fb7 100644 --- a/docs/Reference/Contributed/WinReg/index.md +++ b/docs/Reference/Contributed/WinReg/index.md @@ -15,6 +15,7 @@ The **WinReg** contributed package provides a Windows Registry helper class that The root keys, such as `HKEY_CURRENT_USER`, are members of **RegKeyspace**, so a project names them with **WinReg** alone. **WinReg** references the **WinDevLib** package, but a project does not see the names a package's own references declare. A project that names `BOOL` or `REGTYPES`, or calls `RegOpenKeyEx` or `RegCloseKey` and uses the `KEY_*` constants, as some examples here do, references **WinDevLib** as well. One that declares the **Dictionary** that [**EnumValues**](RegCls/EnumValues) returns references the Microsoft Scripting Runtime. + > [!NOTE] 1.0.1.1, BETA 1034 > The package does not compile, so a project that references it does not build. **RegCls** declares `RegCreateKeyEx` and `RegEnumKeyEx` with a `ByRef` structure parameter, *lpSecurityAttributes* (`SECURITY_ATTRIBUTES`) and *lpftLastWriteTime* (`FILETIME`), and passes `VarPtr(0&)` for it in [**WriteString**](RegCls/WriteString), [**WriteDWORD**](RegCls/WriteDWORD), [**CreateKey**](RegCls/CreateKey) and [**DeleteKeyEx**](RegCls/DeleteKeyEx). A pointer passed for a structure has to be passed `ByVal` (error TB5001). `VarPtr(0&)` is also the address of a temporary **Long**, not a null pointer; `ByVal vbNullPtr` passes a null pointer. The behaviour described on these pages is that of the package with those four calls passed `ByVal`. diff --git a/docs/Reference/Contributed/tsJson/JsonOptions.md b/docs/Reference/Contributed/tsJson/JsonOptions.md index 399ed4846..cbd2bda6a 100644 --- a/docs/Reference/Contributed/tsJson/JsonOptions.md +++ b/docs/Reference/Contributed/tsJson/JsonOptions.md @@ -43,7 +43,6 @@ A property of an options object checks nothing when it is set. The value is used > ' Output: > ' 0|0|False||0|0 > ``` - > [!NOTE] BETA 1034 diff --git a/docs/Reference/Contributed/tsPwGen/PwGenOptions.md b/docs/Reference/Contributed/tsPwGen/PwGenOptions.md index a3862c1a6..56977fd8d 100644 --- a/docs/Reference/Contributed/tsPwGen/PwGenOptions.md +++ b/docs/Reference/Contributed/tsPwGen/PwGenOptions.md @@ -45,8 +45,6 @@ A property of a recipe checks nothing when it is set. A value that cannot be use > Debug.Print "[" & PwGenOptions.WordSeparator & "]" ' [] > ``` - - > [!NOTE] BETA 1034 > The class declares a **Private** constructor and the attribute `[COMCreatable(False)]`, so that only its presets create recipes. twinBASIC does not enforce a **Private** constructor, and a project that references the package can still create an object with **New**. It is a recipe in which every property has the empty value of its type: **Length** 0, no class enabled, empty strings, **MinPerClass** 0, **Mode** **pgmRandom** and **RandomSource** **Nothing**. **Generate** returns an empty string for it, with the error `InvalidLength`, until the properties are set. The presets are the way to get the defaults. diff --git a/docs/Reference/Core/Const.md b/docs/Reference/Core/Const.md index 9989bf6f7..e42c5950e 100644 --- a/docs/Reference/Core/Const.md +++ b/docs/Reference/Core/Const.md @@ -79,9 +79,11 @@ Debug.Print UBound(Primes) ' 4 Debug.Print Total ' 28 ``` + > [!WARNING] BETA 1034 > A module-level **Single** array constant whose element is too large for a **Single**, such as `Const Big() As Single = Array(1E+39)`, compiles without an error, and the element holds infinity. An array constant declared after it whose element calls **Sqr**, **Timer** or **Rnd** is then compile error **TB5001**, although it is correct. + > [!NOTE] BETA 1034 > In a module, an array constant declared after a procedure is compile error **TB5079**, *Unrecognized symbol*, in the procedures that follow it. The procedures before it see an empty array: reading an element raises error 9. Declare array constants above the module's first procedure. diff --git a/docs/Reference/Core/Declare.md b/docs/Reference/Core/Declare.md index 205f6ffc2..7ba500fa8 100644 --- a/docs/Reference/Core/Declare.md +++ b/docs/Reference/Core/Declare.md @@ -114,6 +114,7 @@ Declare Sub Second Lib "MyLib" () > - Fixed-length strings cannot appear in the argument list of a **Declare** statement; only variable-length strings can be passed to procedures. Fixed-length strings can appear as procedure arguments, but they are converted to variable-length strings before being passed. > - The **vbNullString** constant is used when calling external procedures, where the external procedure requires a string whose value is zero. This is not the same thing as a zero-length string (""). + > [!WARNING] BETA 1034 > In a 64-bit build with [LLVM](../../LLVM/Getting-Started), a call that passes `ByVal 0&` for the eighth parameter, declared `ByRef` with a 32-bit type such as **Long**, to give the procedure a NULL pointer, can end with an access violation (0xC0000005) and no message box. The eighth parameter of **WideCharToMultiByte** is one such case. Whether the call fails depends on what the stack held before it. The same override for the first to seventh parameters is right. Declare the parameter `ByVal ... As LongPtr` instead. A 32-bit build and a build without LLVM are not affected. diff --git a/docs/Reference/Core/Divide.md b/docs/Reference/Core/Divide.md index 1a416c0c1..080102518 100644 --- a/docs/Reference/Core/Divide.md +++ b/docs/Reference/Core/Divide.md @@ -36,6 +36,7 @@ A **Single** divided by a **Long**, **LongLong**, **Double**, **Currency** or ** Dividing by zero raises error 11, *Division by zero*, and zero divided by zero raises error 6, *Overflow*. Use [**\\**](IntegerDivide) for truncating-integer division and [**Mod**](Mod) for remainder. + > [!WARNING] BETA 1034 > A division whose result is a declared **Single** raises no error when the result overflows: the **Single** holds infinity (`1.#INF`) instead. VB6 raises error 6, *Overflow*. Compiled with LLVM, the division raises error 6 as it should. Declare the variable **Double** where an overflow must be caught. diff --git a/docs/Reference/Core/Error.md b/docs/Reference/Core/Error.md index e4a8d7d4b..4b6193f92 100644 --- a/docs/Reference/Core/Error.md +++ b/docs/Reference/Core/Error.md @@ -30,6 +30,7 @@ If *errornumber* is defined, the **Error** statement calls the error handler aft If no error handler exists or if none is enabled, an error message is created and displayed from the **Err** object properties. + > [!NOTE] BETA 1034 > An error that a class member raises with the **Error** statement reaches a caller that called the member late-bound --- through an **Object** variable or [**CallByName**](../Modules/Interaction/CallByName) --- as error -2147352567 (`&H80020009`), *Exception occurred.*, not as *errornumber*. VB6 raises *errornumber*. An early-bound caller receives *errornumber* unchanged. diff --git a/docs/Reference/Core/For-Each-Next.md b/docs/Reference/Core/For-Each-Next.md index 9f22d7fcb..ff708e331 100644 --- a/docs/Reference/Core/For-Each-Next.md +++ b/docs/Reference/Core/For-Each-Next.md @@ -50,6 +50,7 @@ The **For...Each...Next** statement cannot be used with an array of user-defined An object of a class can be the *group* when the class has a member marked [**[Enumerator]**](Attributes#enumerator) that supplies an enumerator. See [Custom Enumerators](../../Features/Language/Custom-Enumerators) for how to write one. + > [!WARNING] BETA 1034 > A **For Each** that cannot get an enumerator from *group* never ends when it runs under `On Error Resume Next`, or under an error handler that ends with **Resume Next**. That happens when *group* is **Nothing**, an object of a class with no **[Enumerator]** member, or an object whose **[Enumerator]** member returns **Nothing** or raises an error. The error resumes into the loop body, and **Next** raises error 91 and goes back to the body, again and again. Test *group* before the loop. VB6 runs the body once, then raises error 92 at **Next**, and the loop ends. diff --git a/docs/Reference/Core/For-Next.md b/docs/Reference/Core/For-Next.md index ba39450ca..26a2360fe 100644 --- a/docs/Reference/Core/For-Next.md +++ b/docs/Reference/Core/For-Next.md @@ -54,6 +54,7 @@ The *step* argument can be either positive or negative. The value of the *step* After all statements in the loop have executed, *step* is added to *counter*. At this point, either the statements in the loop execute again (based on the same test that caused the loop to execute initially), or the loop is exited and execution continues with the statement following the **Next** statement. + > [!WARNING] BETA 1034 > With [LLVM](../../LLVM/Getting-Started), a loop whose *step* is a `ByVal` **Long** or **Single** parameter of the procedure runs with a wrong step, or a wrong *start* or *end* when those are parameters too, when a later parameter of the procedure is declared `ByRef ... As Boolean` or `ByRef ... As Integer`. The wrong value depends on where the caller's argument for that parameter is stored: for a local variable, 20 in a 64-bit exe and 25 in a 32-bit exe, and other values in a run in the IDE. No error is raised. Declare that parameter `ByVal`, or place it before the *step* parameter. diff --git a/docs/Reference/Core/Get.md b/docs/Reference/Core/Get.md index b6f5f01ef..b69c5cf79 100644 --- a/docs/Reference/Core/Get.md +++ b/docs/Reference/Core/Get.md @@ -24,6 +24,7 @@ Syntax: *varname* : Valid variable name into which data is read. + > [!NOTE] BETA 1034 > **Get** raises run-time error -2147467259 (`&H80004005`) for a *filenumber* that is not open, where VB6 raises 52 (*Bad file name or number*). diff --git a/docs/Reference/Core/GoSub-Return.md b/docs/Reference/Core/GoSub-Return.md index a4f5a2d79..f169027d6 100644 --- a/docs/Reference/Core/GoSub-Return.md +++ b/docs/Reference/Core/GoSub-Return.md @@ -24,6 +24,7 @@ Use **GoSub** and **Return** anywhere in a procedure, but **GoSub** and the corr > [!NOTE] > **GoSub...Return** cannot enter or exit [**Sub**](Sub) procedures. + > [!WARNING] BETA 1034 > A program built with [LLVM](../../LLVM/Getting-Started) ends with an access violation (0xC0000005) and no message box at the first **GoSub** it runs, in a 32-bit and in a 64-bit build. A program built without LLVM, and a run in the IDE, are not affected. diff --git a/docs/Reference/Core/Graphics-Methods.md b/docs/Reference/Core/Graphics-Methods.md index 435e23eaf..5f2161646 100644 --- a/docs/Reference/Core/Graphics-Methods.md +++ b/docs/Reference/Core/Graphics-Methods.md @@ -122,13 +122,15 @@ The method must accept the whole argument list: six arguments for **Line**, eigh The plain call forms are not rewritten. `o.Line 99, 1, 2, 3, 4, 5` and `Call o.Line(99, 1, 2, 3, 4, 5)` pass their arguments through unchanged. A trailing comma is accepted after the second point of **Line** and after the point of **PSet**, but not after the radius of **Circle**, where it is error TB5075. + > [!WARNING] BETA 1034 > A call of **Scale** or **Line** with a single point in parentheses, such as `o.Scale (2, 3)`, `o.Scale(2, 3)` or `o.Line (2, 3)`, compiles and does nothing: the method is not called and no error is raised. VB6 reports a syntax error. Write the call with **Call**, or without parentheses: `Call o.Scale(2, 3)` or `o.Scale 2, 3`. + > [!NOTE] BETA 1034 > A call of **Scale** or **Line** with a single value in parentheses, such as `o.Scale(2)`, is error TB5212, because the compiler reads the graphics syntax from the method name. Write `Call o.Scale(2)` or `o.Scale 2`. > -> ```tb check_build expect-error=TB5212 slot=file +> ```tb check_build expect-error=TB5212 slot=file id=scale-single-value > Class ScalePlotter > Public Sub Scale(Optional ByVal Flags As Long, Optional ByVal X1 As Single, Optional ByVal Y1 As Single, _ > Optional ByVal X2 As Single, Optional ByVal Y2 As Single) @@ -167,6 +169,7 @@ The built-in surfaces read *Color* as a **Long**. When *Color* is left out they The line of a **Line** and the fill of **BF** use the same rule, as does the circle of **Circle** and the point of **PSet**. + > [!WARNING] BETA 1034 > System colours are read wrongly. VB6 takes the system colour index from the low 16 bits of the value, so that `&H80010003` draws system colour 3 and `&H80FF000F` draws system colour 15. twinBASIC draws both black, without raising an error, because it reads the value as a system colour only when it lies between `&H80000000` and `&H8000001E`. Pass a system colour as one of the `vb` constants, such as `vbButtonFace`, or as `&H800000nn` with nothing in the second and third bytes. diff --git a/docs/Reference/Core/LSet.md b/docs/Reference/Core/LSet.md index daef13b9b..7771bea75 100644 --- a/docs/Reference/Core/LSet.md +++ b/docs/Reference/Core/LSet.md @@ -41,11 +41,13 @@ LSet v = "ab" Debug.Print "[" & v & "]" ' [ab ] ``` + > [!WARNING] BETA 1034 > A **Variant** *stringvar* that holds a **Boolean** is aligned within `-1` or `0`, the text of its number, not within `True` or `False`: with `v = True`, `LSet v = "ab"` leaves `ab`, and with `v = False` it leaves `a`. VB6 leaves `ab ` and `ab `. **RSet** aligns within `True` and `False`. Assign the text to the **Variant** first, as `v = CStr(v)`. A **Variant** *stringvar* that holds an object raises error 450, *Wrong number of arguments or invalid property assignment*. + > [!WARNING] BETA 1034 > **LSet** on an element of a **Variant** that holds an array, such as `LSet v(0) = "zz"` with `v = Array("0123456789", "abcde")` or a **Variant** that was given its bounds with **ReDim**, changes nothing and raises no error. VB6 changes the element. An element of an array declared `Dim w(0 To 1) As Variant` works. To change an element of a **Variant** array, copy it to a **String** variable, use **LSet** on the variable and assign the variable back. diff --git a/docs/Reference/Core/LeftShift.md b/docs/Reference/Core/LeftShift.md index 64ce34a3b..9fe7949fe 100644 --- a/docs/Reference/Core/LeftShift.md +++ b/docs/Reference/Core/LeftShift.md @@ -41,6 +41,7 @@ An integer literal with no type suffix is an **Integer**, so `1 << 20` shifts a > > Convert such a value with [**CLng**](../Modules/Conversion/CLng) or [**CLngLng**](../Modules/Conversion/CLngLng) before shifting it. + > [!WARNING] BETA 1034 > Code compiled with LLVM cuts the shift count to the size of the shifted **Byte** or **Integer** before shifting: for a **Byte**, a count of 256 becomes 0 and 257 becomes 1. So a **Byte** shifted by 256 is unchanged, where the result should be 0, as it is without LLVM. An **Integer** shifted by 65,536 or more behaves the same way. A **Long** or **LongLong** is not affected. diff --git a/docs/Reference/Core/Line-Input.md b/docs/Reference/Core/Line-Input.md index b984b3650..abb395a02 100644 --- a/docs/Reference/Core/Line-Input.md +++ b/docs/Reference/Core/Line-Input.md @@ -24,6 +24,7 @@ The **Line Input #** statement reads from a file one character at a time until i Reading past the last line of the file raises run-time error 62 (*Input past end of file*), and leaves *varname* unchanged. Test for the end of the file with [**EOF**](../Modules/FileSystem/EOF) before each read. + > [!NOTE] BETA 1034 > **Line Input #** raises run-time error -2147467259 (`&H80004005`) for a *filenumber* that is not open, where VB6 raises 52 (*Bad file name or number*). diff --git a/docs/Reference/Core/Minus.md b/docs/Reference/Core/Minus.md index d6b9fa820..1957dd37f 100644 --- a/docs/Reference/Core/Minus.md +++ b/docs/Reference/Core/Minus.md @@ -39,6 +39,7 @@ The data type of *result* is usually the same as that of the most precise expres A declared (non-**Variant**) result that overflows raises error 6, *Overflow*. If one or both expressions are **Null** expressions, *result* is **Null**. If an expression is **Empty**, it is treated as 0. + > [!WARNING] BETA 1034 > A subtraction whose result is a declared **Single** raises no error when the result overflows: the **Single** holds infinity (`1.#INF`) instead. VB6 raises error 6, *Overflow*. The compound form **-=** does the same. Declare the variable **Double** where an overflow must be caught. diff --git a/docs/Reference/Core/Multiply.md b/docs/Reference/Core/Multiply.md index fd8b085da..fbf16607a 100644 --- a/docs/Reference/Core/Multiply.md +++ b/docs/Reference/Core/Multiply.md @@ -35,6 +35,7 @@ The data type of *result* is usually the same as that of the most precise expres A declared (non-**Variant**) result that overflows raises error 6, *Overflow*. If one or both expressions are **Null** expressions, *result* is **Null**. If an expression is **Empty**, it is treated as 0. + > [!WARNING] BETA 1034 > A multiplication whose result is a declared **Single** raises no error when the result overflows: the **Single** holds infinity (`1.#INF`) instead. VB6 raises error 6, *Overflow*. The compound form ***=** does the same. Declare the variable **Double** where an overflow must be caught. diff --git a/docs/Reference/Core/Name.md b/docs/Reference/Core/Name.md index 14c561007..99efad68a 100644 --- a/docs/Reference/Core/Name.md +++ b/docs/Reference/Core/Name.md @@ -22,6 +22,7 @@ The **Name** statement renames a file and moves it to a different directory or f Using **Name** on an open file produces an error. An open file must be closed before renaming it. **Name** arguments cannot include multiple-character (`*`) and single-character (`?`) wildcards. + > [!NOTE] BETA 1034 > Each of these failures of **Name** raises run-time error -2147467259 (`&H80004005`), where VB6 raises the numbered error shown: > diff --git a/docs/Reference/Core/On-Error.md b/docs/Reference/Core/On-Error.md index 0f0c48b7a..7f2477ca6 100644 --- a/docs/Reference/Core/On-Error.md +++ b/docs/Reference/Core/On-Error.md @@ -25,6 +25,7 @@ Syntax: Without an **On Error** statement, any run-time error that occurs is fatal; that is, an error message is displayed and execution stops. + > [!NOTE] BETA 1034 > In a built program, twinBASIC does not end the program after an unhandled error in an event procedure of a form, such as **Form_Load** or **Form_Activate**. It displays the error message, and once the message is closed the program goes on running: the form is shown and its later events run. VB6 ends the program. Until this is fixed, handle errors in a form's event procedures, and unload the form or end the program there when it cannot continue. diff --git a/docs/Reference/Core/Open.md b/docs/Reference/Core/Open.md index 4a9574f11..29434f4d3 100644 --- a/docs/Reference/Core/Open.md +++ b/docs/Reference/Core/Open.md @@ -41,6 +41,7 @@ If the file specified by *pathname* doesn't exist, it is created when a file is If the file is already opened by another process, and the specified type of access is not allowed, the **Open** operation fails and an error occurs. + > [!NOTE] BETA 1034 > Each of these failures of **Open** raises run-time error -2147467259 (`&H80004005`), where VB6 raises the numbered error shown: > diff --git a/docs/Reference/Core/ParamArray.md b/docs/Reference/Core/ParamArray.md index bf98f9bc0..2fc0c15a9 100644 --- a/docs/Reference/Core/ParamArray.md +++ b/docs/Reference/Core/ParamArray.md @@ -24,6 +24,7 @@ Syntax: When the procedure is called, each argument supplied in the call becomes a corresponding element of the **Variant** array. If no arguments are supplied for the **ParamArray** position, the array is empty. + > [!NOTE] BETA 1034 > When an argument is a **Variant** variable that holds an array, its element cannot be used as an array: `Args(0)(1)`, `Args(0)(1) = x`, and `b = Args(0)` where *b* is a dynamic array of any type raise run-time error -2147467259, *Unspecified error*, and so does passing `Args(0)` on to another **ParamArray** that indexes it; **Join** raises error 13. **LBound**, **UBound**, **IsArray**, **VarType** and **For Each** work on the element, and an argument that is an array variable or an expression is not affected. VB6 raises no error. Assign the element to a **Variant** first (`v = Args(0)`), or have the caller put the argument in parentheses, which passes a copy. diff --git a/docs/Reference/Core/Plus.md b/docs/Reference/Core/Plus.md index 352e4f054..7a92af175 100644 --- a/docs/Reference/Core/Plus.md +++ b/docs/Reference/Core/Plus.md @@ -58,6 +58,7 @@ For simple arithmetic addition involving only numeric expressions, the data type A declared (non-**Variant**) result that overflows raises error 6, *Overflow*. If one or both expressions are **Null** expressions, *result* is **Null**. If both expressions are **Empty**, *result* is an **Integer**. However, if only one expression is **Empty**, the other expression is returned unchanged as *result*. + > [!WARNING] BETA 1034 > An addition whose result is a declared **Single** raises no error when the result overflows: the **Single** holds infinity (`1.#INF`) instead. VB6 raises error 6, *Overflow*. The compound form **+=** does the same. Declare the variable **Double** where an overflow must be caught. diff --git a/docs/Reference/Core/Print.md b/docs/Reference/Core/Print.md index d2c95abf8..97cb6defe 100644 --- a/docs/Reference/Core/Print.md +++ b/docs/Reference/Core/Print.md @@ -55,6 +55,7 @@ An expression that reaches the end of its zone pushes the next one into the zone *filenumber* : Any valid file number. + > [!NOTE] BETA 1034 > **Print #** raises run-time error -2147467259 (`&H80004005`) for a *filenumber* that is not open, where VB6 raises 52 (*Bad file name or number*). diff --git a/docs/Reference/Core/Put.md b/docs/Reference/Core/Put.md index cd528ae23..a5871a1d6 100644 --- a/docs/Reference/Core/Put.md +++ b/docs/Reference/Core/Put.md @@ -21,6 +21,7 @@ Syntax: *varname* : Name of the variable containing data to be written to disk. + > [!NOTE] BETA 1034 > **Put** raises run-time error -2147467259 (`&H80004005`) for a *filenumber* that is not open, where VB6 raises 52 (*Bad file name or number*). It raises the same error for data longer than the record length of a **Random** file, where VB6 raises 59 (*Bad record length*). diff --git a/docs/Reference/Core/RSet.md b/docs/Reference/Core/RSet.md index f5611c345..404b08782 100644 --- a/docs/Reference/Core/RSet.md +++ b/docs/Reference/Core/RSet.md @@ -33,9 +33,11 @@ RSet v = "ab" Debug.Print "[" & v & "]" ' [ ab] ``` + > [!NOTE] BETA 1034 > A **Variant** *stringvar* that holds an object raises error 13, *Type mismatch*. VB6 raises error 450, *Wrong number of arguments or invalid property assignment*. + > [!WARNING] BETA 1034 > **RSet** on an element of a **Variant** that holds an array, such as `RSet v(1) = "zz"` with `v = Array("0123456789", "abcde")` or a **Variant** that was given its bounds with **ReDim**, changes nothing and raises no error. VB6 changes the element. An element of an array declared `Dim w(0 To 1) As Variant` works. To change an element of a **Variant** array, copy it to a **String** variable, use **RSet** on the variable and assign the variable back. diff --git a/docs/Reference/Core/RightShift.md b/docs/Reference/Core/RightShift.md index 391871717..98dbebf29 100644 --- a/docs/Reference/Core/RightShift.md +++ b/docs/Reference/Core/RightShift.md @@ -39,6 +39,7 @@ The data type of *result* matches the type of *number*, whatever the type of *co > > Convert such a value with [**CLng**](../Modules/Conversion/CLng) or [**CLngLng**](../Modules/Conversion/CLngLng) before shifting it. + > [!WARNING] BETA 1034 > Code compiled with LLVM cuts the shift count to the size of the shifted **Byte** or **Integer** before shifting: for a **Byte**, a count of 256 becomes 0 and 257 becomes 1. So a **Byte** shifted by 256 is unchanged, where the result should be 0, as it is without LLVM. An **Integer** shifted by 65,536 or more behaves the same way. A **Long** or **LongLong** is not affected. diff --git a/docs/Reference/Core/Topic-Preprocessor.md b/docs/Reference/Core/Topic-Preprocessor.md index a0098db1b..737187a8b 100644 --- a/docs/Reference/Core/Topic-Preprocessor.md +++ b/docs/Reference/Core/Topic-Preprocessor.md @@ -37,7 +37,7 @@ 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 1034 > 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`. diff --git a/docs/Reference/Core/Type.md b/docs/Reference/Core/Type.md index 4bca9f4d2..865f6f1cc 100644 --- a/docs/Reference/Core/Type.md +++ b/docs/Reference/Core/Type.md @@ -102,15 +102,19 @@ twinBASIC extends classic VBA's **Type** in several ways. UDTs remain stack-allo **Member procedures.** [**Sub**](Sub), [**Function**](Function), and [**Property**](Property) procedures may appear inside `Type ... End Type`, and are called through any variable of the type. Inside a member procedure, other members of the same UDT must be accessed with the explicit `Me.` prefix. + > [!WARNING] BETA 1034 > A member procedure called on an element of an array of the UDT, such as `a(1).Sum` or `a(1).Diff(a(2))`, ends the program with an access violation, in 32-bit and 64-bit builds. This holds for local, static, module-level, dynamic and parameter arrays, and in a **With** block. A variable of the type, a UDT member of another UDT, and an element of an array declared inside a UDT are not affected. Copy the element to a variable first. + > [!NOTE] BETA 1034 > Assigning **Me** whole inside a member procedure, as in `Copy = Me`, `Return Me` or `t = Me`, is a code generation error that fails the build once the procedure is called. `Me.X`, passing **Me** as an argument, and `Me = other` are not affected. Copy the members one by one. + > [!WARNING] BETA 1034 > A UDT larger than 4 bytes passed **ByVal** from an element of an array, as in `Take a(1)` for `Sub Take(ByVal p As Pair)`, damages the stack in a 32-bit build: the procedure receives the right value, and the calling procedure ends with an access violation when it returns. A UDT of 4 bytes or less, a variable, an element copied to a variable first, and a **ByRef** parameter are not affected, nor are 64-bit and LLVM builds. VB6 does not accept a UDT passed **ByVal**. + > [!NOTE] BETA 1034 > Assigning without **Set** to a member of a UDT whose type is a class, as in `h.o = 5` for a class with a default member, is a code generation error that fails the build. VB6 assigns the default member. Use `Set h.o = ...` to assign the object, or name the default member, as in `h.o.Value = 5`. diff --git a/docs/Reference/Data-Types.md b/docs/Reference/Data-Types.md index a7bd1226e..7e6b789f3 100644 --- a/docs/Reference/Data-Types.md +++ b/docs/Reference/Data-Types.md @@ -46,6 +46,7 @@ Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _ (ByRef Destination As Any, ByRef Source As Any, ByVal Length As LongPtr) ``` + > [!NOTE] BETA 1034 > A local variable, a **Static** variable, or a module or class field declared **As Any** compiles with no diagnostic; VB6 refuses each of them. The variable cannot be used: assigning to it is refused (TB5028), reading it into a **Variant** makes the build fail, and **TypeName** of it raises error 458 in a built program. Declare such a variable with a real type. A **Function**'s return type, a **Sub**'s parameter, a UDT field and an array **As Any** are refused (TB5079). diff --git a/docs/Reference/Data-Types/Date.md b/docs/Reference/Data-Types/Date.md index 454f1331d..a12c032b8 100644 --- a/docs/Reference/Data-Types/Date.md +++ b/docs/Reference/Data-Types/Date.md @@ -27,6 +27,7 @@ A **Date** is stored in 8 bytes as a **Double**. The integer part counts days fr A number or a date outside that range cannot be a **Date**, and an assignment, a conversion or an addition that would make one raises error 6, *Overflow*. + > [!WARNING] BETA 1034 > A **Date** outside the range is accepted with no error: `d = 2958466`, `CDate(-657435)`, `#12/31/9999# + 1` and an assignment of `1E+300` each store the number, and **CStr**, **Format** and **Year** of the value then raise error 5, *Invalid procedure call or argument*. VB6 raises error 6, *Overflow*, at the assignment. Compare a day number with -657,434 and 2,958,465 before assigning it to a **Date**. diff --git a/docs/Reference/Data-Types/Double.md b/docs/Reference/Data-Types/Double.md index 7e6bb0411..f8fa7e826 100644 --- a/docs/Reference/Data-Types/Double.md +++ b/docs/Reference/Data-Types/Double.md @@ -77,6 +77,7 @@ A result too large for the type raises error 6, *Overflow*: `1E+308 * 10` does, A **Double** can hold the IEEE values infinity and NaN, which can arrive from memory or from a COM component, but arithmetic never produces one: it raises an error instead. As in VB6, they print as `1.#INF`, `-1.#INF`, `1.#QNAN` and, for the indeterminate value, `-1.#IND`. A comparison with NaN does not follow the IEEE rules: `NaN = NaN` is True, as in VB6, and so is `NaN = 0`, so a comparison cannot tell a NaN from a number. + > [!NOTE] BETA 1034 > A 64-bit build prints infinity and NaN as `inf`, `-inf`, `nan` and `-nan(ind)`, and **Format** with a number picture such as `"0.00"` gives `inf.00` and `nan.00`. A 32-bit build and VB6 print `1.#INF` and `1.#QNAN`, and **Format** gives `1.#INF` and `1.#R`. Do not test or parse the text of these values. @@ -96,6 +97,7 @@ Debug.Print Err.Number ' 0 Debug.Print Value ' 0 ``` + > [!WARNING] BETA 1034 > An exponentiation (`^`) that overflows raises no error in code compiled with LLVM, and its result is 0. In a 32-bit build compiled with LLVM, and not for SSE2, a **Double** addition, subtraction or multiplication that overflows raises no error either, and its result is infinity. Code compiled without LLVM raises error 6, as VB6 does. See [Language support](../../LLVM/Getting-Started#language-support). diff --git a/docs/Reference/Data-Types/LongLong.md b/docs/Reference/Data-Types/LongLong.md index fe27540ad..7e162ab79 100644 --- a/docs/Reference/Data-Types/LongLong.md +++ b/docs/Reference/Data-Types/LongLong.md @@ -78,9 +78,11 @@ Big = CLngLng(1E+19) Debug.Print Err.Number ' 6 ``` + > [!WARNING] BETA 1034 > The constant 2^63, one past the largest **LongLong**, converts to -9223372036854775808 with no error: `CLngLng(2 ^ 63)`, `CLngLng(9223372036854775808#)` and `CLngLng(CSng(2 ^ 63))` all do, and so does `Big = 9223372036854775807`, because a whole-number literal without the `^` suffix is a **Double** that rounds to 2^63. A **Double** variable holding 2^63 raises error 6, and so does `1E+19`. VB6 has no **LongLong**; it raises error 6 for the first **Double** beyond the **Long** range. Write the largest value as `9223372036854775807^`. + > [!NOTE] BETA 1034 > In a 32-bit build, a constant expression with an operator after the `^`, such as `Big = 2 ^ 63 - 1024`, raises error 6 when its procedure has already converted a constant **Double** that does not fit a **Long** or a **LongLong**: `Small = 2 ^ 31`, `Big = CLngLng(1E+19)` and `Big = CLngLng(2 ^ 63)` all do. The value fits, and the statement stores it; the error is raised once, at the first such expression. A 64-bit build raises no error. A single literal, a single power such as `2 ^ 62` and arithmetic on a variable are not affected. A function whose body is the expression raises the error in the same way when a procedure that calls it holds such a conversion. Write the value as one literal, as in `Big = 9223372036854774784^`; `CLngLng` around the expression does not avoid the error. diff --git a/docs/Reference/Data-Types/Object.md b/docs/Reference/Data-Types/Object.md index 1e9821805..168382b57 100644 --- a/docs/Reference/Data-Types/Object.md +++ b/docs/Reference/Data-Types/Object.md @@ -50,6 +50,7 @@ An object reference is assigned with [**Set**](Set): `Set Item = New Collection` A reference can be assigned to a variable of a more specific type, `Set Items = Item`, which checks that the object supports that type. + > [!NOTE] BETA 1034 > Assigning an object to a variable of a class or interface it does not support raises the error -2147467262, the **HRESULT** `E_NOINTERFACE`, *No such interface supported*. VB6 raises error 13, *Type mismatch*. Test the object first with `If TypeOf Item Is Collection Then`, or have the handler accept both numbers. diff --git a/docs/Reference/Data-Types/Single.md b/docs/Reference/Data-Types/Single.md index 29f091c10..7493e2804 100644 --- a/docs/Reference/Data-Types/Single.md +++ b/docs/Reference/Data-Types/Single.md @@ -91,6 +91,7 @@ Debug.Print 0.1! + 0.2! = 0.3! ' True Debug.Print 0.1 + 0.2 = 0.3 ' False ``` + > [!WARNING] BETA 1034 > An addition, subtraction, multiplication or division whose result is a declared **Single** raises no error when the result overflows: the **Single** holds infinity (`1.#INF`) instead. VB6 raises error 6, *Overflow*. Declare the variable **Double** where an overflow must be caught. Code compiled with LLVM has a similar defect for **Double**; see [Language support](../../LLVM/Getting-Started#language-support). diff --git a/docs/Reference/Default/VB/Data/index.md b/docs/Reference/Default/VB/Data/index.md index 3cda23792..00c562b6a 100644 --- a/docs/Reference/Default/VB/Data/index.md +++ b/docs/Reference/Default/VB/Data/index.md @@ -85,6 +85,7 @@ Determines how the control's border is drawn by the OS. A member of [**Appearanc The fill colour of the band behind the [**Caption**](#caption), as an **OLE_COLOR**. Defaults to the system window-background colour. + > [!WARNING] BETA 1034 > Assigning a value that is not a colour, such as `-1`, raises no error and stores the value, so a later read returns it. Check a colour that comes from user input before assigning it. @@ -182,6 +183,7 @@ The **StdFont** used to render [**Caption**](#caption). The convenience properti The text colour for the caption, as an **OLE_COLOR**. Defaults to the system window-text colour. A disabled control draws the caption in the system grey-text colour instead. + > [!WARNING] BETA 1034 > Assigning a value that is not a colour, such as `-1`, raises no error and stores the value, so a later read returns it. Check a colour that comes from user input before assigning it. diff --git a/docs/Reference/Default/VB/Form/index.md b/docs/Reference/Default/VB/Form/index.md index e808669f5..3bb9a0497 100644 --- a/docs/Reference/Default/VB/Form/index.md +++ b/docs/Reference/Default/VB/Form/index.md @@ -149,6 +149,7 @@ When **False**, drawing primitives --- [**Cls**](#cls), [**Circle**](#circle), [ The background colour of the form's client area, as an **OLE_COLOR**. Defaults to the system 3-D face colour. Used as the fill colour for [**Cls**](#cls) and as the canvas behind [**Picture**](#picture). Setting it to a different colour erases everything drawn with the graphics methods and [**Print**](#print), as **Cls** does, but leaves [**CurrentX**](#currentx) and [**CurrentY**](#currenty) as they are. Setting it to the colour it already has erases nothing; on a [**PictureBox**](../PictureBox#backcolor) it erases the drawing all the same. A value that is not a colour, such as `-1`, raises error 380, *Invalid property value*, and the property keeps its colour. + > [!WARNING] BETA 1034 > When [**AutoRedraw**](#autoredraw) is **True**, setting a different **BackColor** also resets **CurrentX** and **CurrentY** to **0**. @@ -408,6 +409,7 @@ The form's opacity as a percentage (0--100, default 100). Values outside the ran A **StdPicture** drawn as the form's background. Painted before any drawing primitives or child controls. Assigning **Nothing** removes the background. + > [!WARNING] BETA 1034 > When [**AutoRedraw**](#autoredraw) is **True**, setting **Picture** resets [**CurrentX**](#currentx) and [**CurrentY**](#currenty) to **0**. diff --git a/docs/Reference/Default/VB/MultiFrame/index.md b/docs/Reference/Default/VB/MultiFrame/index.md index a66f3ff7d..714d9fbd1 100644 --- a/docs/Reference/Default/VB/MultiFrame/index.md +++ b/docs/Reference/Default/VB/MultiFrame/index.md @@ -78,6 +78,7 @@ The set of edges of the parent that the **MultiFrame**'s corresponding edges fol The background colour of the **MultiFrame**'s drawing surface, as an **OLE_COLOR**. Defaults to the system window-background colour. Visible only where the contained frames do not fully cover the extent --- e.g. when their **MultiFrameSize**s sum to less than 100%. + > [!WARNING] BETA 1034 > Assigning a value that is not a colour, such as `-1`, raises no error and stores the value, so a later read returns it. Check a colour that comes from user input before assigning it. diff --git a/docs/Reference/Default/VB/OLE/index.md b/docs/Reference/Default/VB/OLE/index.md index e273a910e..e336791fe 100644 --- a/docs/Reference/Default/VB/OLE/index.md +++ b/docs/Reference/Default/VB/OLE/index.md @@ -107,6 +107,7 @@ When **True** (default), right-clicking the container automatically pops up a me The background colour, as an **OLE_COLOR**. Defaults to the system window-background colour. + > [!WARNING] BETA 1034 > Assigning a value that is not a colour, such as `-1`, raises no error and stores the value, so a later read returns it. VB6 raises error 380, *Invalid property value*, and keeps the old colour. Check a colour that comes from user input before assigning it. diff --git a/docs/Reference/Default/VB/PictureBox/index.md b/docs/Reference/Default/VB/PictureBox/index.md index 1e8eb9757..bf96cabfa 100644 --- a/docs/Reference/Default/VB/PictureBox/index.md +++ b/docs/Reference/Default/VB/PictureBox/index.md @@ -103,6 +103,7 @@ When **True**, the control resizes itself to fit the assigned [**Picture**](#pic The background colour of the control's drawing surface, as an **OLE_COLOR**. Defaults to the system 3-D face colour. Setting it erases everything drawn with the graphics methods and [**Print**](#print), as [**Cls**](#cls) does, even when the colour is the one already set. Unlike **Cls**, it leaves [**CurrentX**](#currentx) and [**CurrentY**](#currenty) as they are. A value that is not a colour, such as `-1`, raises error 380, *Invalid property value*, and the property keeps its colour. + > [!WARNING] BETA 1034 > When [**AutoRedraw**](#autoredraw) is **True**, setting a different **BackColor** also resets **CurrentX** and **CurrentY** to **0**. @@ -370,6 +371,7 @@ A reference to the [**Form**](../Form) (or **UserControl**) that ultimately cont The picture displayed in the control's client area. **StdPicture**. **Default property.** Use **Set** to assign a new picture, **Nothing** to clear it. Assigning when [**AutoSize**](#autosize) is **True** resizes the control to fit. + > [!WARNING] BETA 1034 > When [**AutoRedraw**](#autoredraw) is **True**, setting **Picture** resets [**CurrentX**](#currentx) and [**CurrentY**](#currenty) to **0**. diff --git a/docs/Reference/Default/VBA/Conversion/CDate.md b/docs/Reference/Default/VBA/Conversion/CDate.md index f0f4b3b6c..1eac53d7f 100644 --- a/docs/Reference/Default/VBA/Conversion/CDate.md +++ b/docs/Reference/Default/VBA/Conversion/CDate.md @@ -26,6 +26,7 @@ Use the **IsDate** function to determine whether *expression* can be converted t A **Date** holds a day number from -657,434 (January 1, 100) to 2,958,465 (December 31, 9999), with the time of day as its fraction. + > [!WARNING] BETA 1034 > **CDate** of a number outside that range raises no error: `CDate(2958466)`, `CDate(-657435)` and `CDate(1E+300)` each return a **Date** that holds the number, and **CStr**, **Format** and **Year** of it then raise error 5, *Invalid procedure call or argument*. VB6 raises error 6, *Overflow*. Compare a day number with -657,434 and 2,958,465 before converting it, or use [**CVDate**](CVDate), which raises error 13 for a number outside the range. diff --git a/docs/Reference/Default/VBA/Conversion/CLngLng.md b/docs/Reference/Default/VBA/Conversion/CLngLng.md index 28896bdbc..1705c66b3 100644 --- a/docs/Reference/Default/VBA/Conversion/CLngLng.md +++ b/docs/Reference/Default/VBA/Conversion/CLngLng.md @@ -26,6 +26,7 @@ When the fractional part is exactly `0.5`, **CLngLng** always rounds it to the n > [!NOTE] > In VBA, **LongLong** (and therefore **CLngLng**) is restricted to 64-bit hosts. twinBASIC supports **LongLong** in both 32-bit and 64-bit modes --- see [Data Types](../../../Features/Language/Data-Types). + > [!WARNING] BETA 1034 > The constant 2^63, one past the largest **LongLong**, converts to -9223372036854775808 with no error: `CLngLng(2 ^ 63)` and `CLngLng(9223372036854775808#)` do, and so does `CLngLng(9223372036854775807)`, because a whole-number literal without the `^` suffix is a **Double** that rounds to 2^63. A **Double** variable holding 2^63 raises error 6, as the table below says. Write the largest value as `9223372036854775807^`. 64-bit VBA raises error 6 for each of these constants. diff --git a/docs/Reference/Default/VBA/Conversion/CStr.md b/docs/Reference/Default/VBA/Conversion/CStr.md index 3dcd06f84..24b3848fa 100644 --- a/docs/Reference/Default/VBA/Conversion/CStr.md +++ b/docs/Reference/Default/VBA/Conversion/CStr.md @@ -28,6 +28,7 @@ The return type is **String**. The result depends on the type of *expression*: **CStr** is the internationally aware alternative to [**Str**](Str) for converting a number to a string. **CStr** recognizes different decimal separators properly, depending on the system's locale setting. + > [!WARNING] BETA 1034 > A 64-bit build converts a negative zero of type **Single** or **Double** (a value that equals 0 and has its sign bit set) to `"-0"`. A 32-bit build and VB6 give `"0"`. The same happens when such a value is joined to a string with `&`. diff --git a/docs/Reference/Default/VBA/Conversion/Str.md b/docs/Reference/Default/VBA/Conversion/Str.md index 46a2d5603..eaa933244 100644 --- a/docs/Reference/Default/VBA/Conversion/Str.md +++ b/docs/Reference/Default/VBA/Conversion/Str.md @@ -25,6 +25,7 @@ When numbers are converted to strings, a leading space is always reserved for th Use the [**Format**](../Strings/Format) function to convert numeric values to be formatted as dates, times, or currency, or in other user-defined formats. Unlike **Str**, the **Format** function doesn't include a leading space for the sign of *number*. + > [!WARNING] BETA 1034 > A 64-bit build converts a negative zero of type **Single** or **Double** (a value that equals 0 and has its sign bit set) to `"-0"`, with no leading space. A 32-bit build and VB6 give `" 0"`. diff --git a/docs/Reference/Default/VBA/DateTime/Calendar.md b/docs/Reference/Default/VBA/DateTime/Calendar.md index c4eae318b..deb2ae39b 100644 --- a/docs/Reference/Default/VBA/DateTime/Calendar.md +++ b/docs/Reference/Default/VBA/DateTime/Calendar.md @@ -23,6 +23,7 @@ Syntax: **Calendar** [ **=** *calendartype* ] The **Calendar** property can only be set programmatically. The setting of **Calendar** affects the string returned by the [**Date$**](Date#date-1) property when the calendar is set to Hijri. + > [!NOTE] BETA 1034 > twinBASIC has not implemented **Calendar** yet. The compiler warns TB0009 at each use, **Calendar** always reads as **vbCalGreg**, and setting it raises an error, **vbCalGreg** included. diff --git a/docs/Reference/Default/VBA/DateTime/DateAdd.md b/docs/Reference/Default/VBA/DateTime/DateAdd.md index 636171d07..3c9d9876c 100644 --- a/docs/Reference/Default/VBA/DateTime/DateAdd.md +++ b/docs/Reference/Default/VBA/DateTime/DateAdd.md @@ -53,6 +53,7 @@ Debug.Print Format$(DateAdd("yyyy", 1, #2/29/2024#), "yyyy-mm-dd") ' 2025-02-28 **DateAdd** returns 28-Feb-95, not 31-Feb-95. If *date* is 31-Jan-96, it returns 29-Feb-96 because 1996 is a leap year. Subtracting a month, and adding or subtracting a quarter or a year, stops at the last day of the shorter month in the same way, as the last two lines show. + > [!WARNING] BETA 1034 > **DateAdd** with the "yyyy", "q" or "m" interval raises no error when the result would be before January 1, 100, and returns a date in the 1900s or 2000s instead: `DateAdd("yyyy", -1, #1/1/100#)` returns January 1, 1999, and `DateAdd("m", -1, #1/1/100#)` returns December 1, 1999. VB6 raises error 5. diff --git a/docs/Reference/Default/VBA/DateTime/DateDiff.md b/docs/Reference/Default/VBA/DateTime/DateDiff.md index 959e433d2..31c30b7da 100644 --- a/docs/Reference/Default/VBA/DateTime/DateDiff.md +++ b/docs/Reference/Default/VBA/DateTime/DateDiff.md @@ -66,6 +66,7 @@ To calculate the number of days between *date1* and *date2*, use either Day of Y If *interval* is Week ("ww"), however, **DateDiff** returns the number of calendar weeks between the two dates. It counts the number of Sundays between *date1* and *date2*. **DateDiff** counts *date2* if it falls on a Sunday, but it doesn't count *date1*, even if it does fall on a Sunday. + > [!WARNING] BETA 1034 > With the "ww" interval, **DateDiff** raises no error for a *firstdayofweek* outside 0 to 7, and what it returns follows no rule: a negative value acts as if taken modulo 7, 8 to 13 act as **vbSunday**, and a larger value matches no setting. VB6 raises error 5. diff --git a/docs/Reference/Default/VBA/DateTime/DatePart.md b/docs/Reference/Default/VBA/DateTime/DatePart.md index 3924f6cfd..1b9590668 100644 --- a/docs/Reference/Default/VBA/DateTime/DatePart.md +++ b/docs/Reference/Default/VBA/DateTime/DatePart.md @@ -64,9 +64,11 @@ Syntax: **DatePart** ( *interval*, *date* [, *firstdayofweek* [, *firstweekofyea The *firstdayofweek* argument affects calculations that use the "w" and "ww" interval symbols. + > [!WARNING] BETA 1034 > With the "w" or "ww" interval, **DatePart** raises no error for a *firstdayofweek* outside 0 to 7, and what it returns follows no rule: a negative value acts as if taken modulo 7, 8 acts as **vbSunday**, and a larger value matches no setting. `DatePart("w", #1/1/2000#, 255)` returns -2. VB6 raises error 5. + > [!WARNING] BETA 1034 > With the "ww" interval, **DatePart** goes wrong for a date in the first days of year 100 whose week belongs to year 99. With **vbFirstFullWeek** it never returns, and the program stops responding: `DatePart("ww", #1/1/100#, vbSunday, vbFirstFullWeek)` is one such call. With **vbFirstFourDays** it returns a wrong number: -98980 for January 1, 100. VB6 returns 52 for both. diff --git a/docs/Reference/Default/VBA/DateTime/index.md b/docs/Reference/Default/VBA/DateTime/index.md index 1f47d237e..a9f358052 100644 --- a/docs/Reference/Default/VBA/DateTime/index.md +++ b/docs/Reference/Default/VBA/DateTime/index.md @@ -62,6 +62,7 @@ Debug.Print DateDiff("d", #1/1/2026#, #5/9/2026#) ' 128 The [**Calendar**](Calendar) property selects the calendar --- **vbCalGreg** (Gregorian, the default) or **vbCalHijri** (Hijri) --- used by the rest of the module. The setting controls how **Date$** formats the system date, how arguments to **DateSerial**, **DateValue**, **DateAdd**, and **DateDiff** are interpreted, and how the parts returned by **DatePart**, **Year**, **Month**, **Day**, and **Weekday** are reported. + > [!NOTE] BETA 1034 > twinBASIC has not implemented **Calendar** yet: it always reads as **vbCalGreg**, and setting it raises an error. diff --git a/docs/Reference/Default/VBA/ErrObject/Description.md b/docs/Reference/Default/VBA/ErrObject/Description.md index 8eac12980..8bd892313 100644 --- a/docs/Reference/Default/VBA/ErrObject/Description.md +++ b/docs/Reference/Default/VBA/ErrObject/Description.md @@ -20,6 +20,7 @@ The **Description** setting consists of a short description of the error. Use th When generating a user-defined error, assign a short description of the error to the **Description** property. If **Description** isn't filled in and the value of [**Number**](Number) corresponds to a built-in run-time error, the string returned by the [**Error**](../Conversion/Error) function is placed in **Description** when the error is generated. + > [!WARNING] BETA 1034 > For a number that has no built-in message, **Raise** without a *description* leaves **Description** as an empty string, a Windows system message or `Automation error`, depending on the number, and raises no error. VBA and VB6 place `Application-defined or object-defined error` there. [**Raise**](Raise) has the table. diff --git a/docs/Reference/Default/VBA/ErrObject/HelpContext.md b/docs/Reference/Default/VBA/ErrObject/HelpContext.md index 1dd0a41fa..f2635b387 100644 --- a/docs/Reference/Default/VBA/ErrObject/HelpContext.md +++ b/docs/Reference/Default/VBA/ErrObject/HelpContext.md @@ -22,7 +22,7 @@ If both **HelpFile** and **HelpContext** are empty, the value of [**Number**](Nu Write routines to handle typical errors. When programming with an object, the object's Help file can improve error handling or display a meaningful message to the user when an error isn't recoverable. - + > [!NOTE] BETA 1034 > A statement that assigns **HelpContext**, such as `Err.HelpContext = 5`, compiles, but the build fails with a code-generation error in the procedure that holds it. [**Raise**](Raise) sets **HelpContext** without the problem, and so does an assignment through a variable declared **As Object** that holds **Err**. diff --git a/docs/Reference/Default/VBA/ErrObject/HelpFile.md b/docs/Reference/Default/VBA/ErrObject/HelpFile.md index b0fa79686..d3eede3b5 100644 --- a/docs/Reference/Default/VBA/ErrObject/HelpFile.md +++ b/docs/Reference/Default/VBA/ErrObject/HelpFile.md @@ -20,7 +20,7 @@ If a Help file is specified in **HelpFile**, it is automatically called when the Write routines to handle typical errors. When programming with an object, the object's Help file can improve error handling or display a meaningful message to the user when an error isn't recoverable. - + > [!NOTE] BETA 1034 > A statement that assigns **HelpFile**, such as `Err.HelpFile = "MyHelp.chm"`, compiles, but the build fails with a code-generation error in the procedure that holds it. [**Raise**](Raise) sets **HelpFile** without the problem, and so does an assignment through a variable declared **As Object** that holds **Err**. diff --git a/docs/Reference/Default/VBA/ErrObject/Number.md b/docs/Reference/Default/VBA/ErrObject/Number.md index 8c229c80d..1d9c6fe9d 100644 --- a/docs/Reference/Default/VBA/ErrObject/Number.md +++ b/docs/Reference/Default/VBA/ErrObject/Number.md @@ -25,7 +25,7 @@ Err.Raise Number:=vbObjectError + 1051, Source:="SomeClass" > [!NOTE] > **Number** accepts a number above 65535. VB6 and 64-bit VBA raise error 5, *Invalid procedure call or argument*, for one. - + > [!NOTE] BETA 1034 > **Number** keeps an **HRESULT** assigned to it as it is. VB6 and 64-bit VBA store the VB error of the same meaning for the ones that [**Raise**](Raise) turns into VB errors: after `Err.Number = &H80070057`, **Number** is 5 there, and in twinBASIC it is -2147024809. diff --git a/docs/Reference/Default/VBA/ErrObject/Raise.md b/docs/Reference/Default/VBA/ErrObject/Raise.md index bc5d992e3..262f4783f 100644 --- a/docs/Reference/Default/VBA/ErrObject/Raise.md +++ b/docs/Reference/Default/VBA/ErrObject/Raise.md @@ -44,13 +44,15 @@ A *description* of an empty string gives an empty **Description** for every numb When the error is raised in a method of a class and the caller handles it, an empty **Description** reaches the caller as `Application-defined or object-defined error`. The other texts arrive unchanged. + > [!WARNING] BETA 1034 > **Raise** differs from VBA and VB6 in three ways, and none of them raises an error. Without *source*, VBA sets **Source** to the name of the project, and twinBASIC leaves it empty. Without *description*, VBA gives every number that has no built-in message the text `Application-defined or object-defined error`, from 1 up to 65535, and twinBASIC gives an empty string or one of the texts above. And an omitted argument keeps the value left by an earlier error in VBA, and is reset in twinBASIC. A program that relies on **Err.Source** being the project name, or on **Err.Description** being non-empty, has to pass those arguments. + > [!NOTE] BETA 1034 > An error that a class member raises with a constant *number*, such as `Err.Raise 380`, reaches a caller that called the member late-bound --- through an **Object** variable or [**CallByName**](../Interaction/CallByName) --- as error -2147352567 (`&H80020009`), *Exception occurred.*, not as 380. VB6 raises 380. The number arrives unchanged when the member passes it in a variable, as in `Err.Raise Number`, and in every early-bound call. - + > [!NOTE] BETA 1034 > **Raise** raises an **HRESULT** *number* as it is. VB6 and 64-bit VBA raise many **HRESULT**s as the VB error of the same meaning instead, including these: > @@ -107,7 +109,7 @@ End Function This example raises errors without a source or a description, and prints what **Err** holds for each: -```tb check_run +```tb check_run id=err-raise-texts On Error Resume Next Err.Raise 5 Debug.Print "[" & Err.Source & "] [" & Err.Description & "]" ' [] [Invalid procedure call or argument] diff --git a/docs/Reference/Default/VBA/ErrObject/ReturnHResult.md b/docs/Reference/Default/VBA/ErrObject/ReturnHResult.md index 4e7b6ca26..c8d3adbd5 100644 --- a/docs/Reference/Default/VBA/ErrObject/ReturnHResult.md +++ b/docs/Reference/Default/VBA/ErrObject/ReturnHResult.md @@ -17,7 +17,7 @@ Syntax: **Err**.**ReturnHResult** **=** *value* A calling procedure can then use [**LastHresult**](LastHresult) to read the positive HRESULT that was returned from the method. Set **ReturnHResult** only when needed, typically just before the method exit point, so that the intended HRESULT is the one that's actually returned. - + > [!NOTE] BETA 1034 > Assigning **ReturnHResult** in a procedure of a module, rather than in a method of a class, compiles, but the build fails with a code-generation error in that procedure. diff --git a/docs/Reference/Default/VBA/ErrObject/Source.md b/docs/Reference/Default/VBA/ErrObject/Source.md index ae49f8456..b10a6dc83 100644 --- a/docs/Reference/Default/VBA/ErrObject/Source.md +++ b/docs/Reference/Default/VBA/ErrObject/Source.md @@ -24,6 +24,7 @@ When generating an error from user code, pass the source to [**Raise**](Raise). When an error occurs that twinBASIC raises itself, such as a division by zero, **Source** is an empty string too, in a standard module and in a class module alike. + > [!WARNING] BETA 1034 > It does not fill in **Source**, and raises no error. VBA and VB6 fill it in automatically with the name of the project, for an error that the runtime raises and for **Raise** without a source argument. A program that reads **Err.Source** to find where an error came from gets an empty string. diff --git a/docs/Reference/Default/VBA/FileSystem/FileDateTime.md b/docs/Reference/Default/VBA/FileSystem/FileDateTime.md index 3a742bd71..fa4988436 100644 --- a/docs/Reference/Default/VBA/FileSystem/FileDateTime.md +++ b/docs/Reference/Default/VBA/FileSystem/FileDateTime.md @@ -16,7 +16,7 @@ Syntax: **FileDateTime(** *pathname* **)** *pathname* : *required* String expression that specifies a file name. The *pathname* may include the directory or folder, and the drive. - + > [!WARNING] BETA 1034 > Given a *pathname* with a wildcard (`*` or `?`) that matches a file or a folder, **FileDateTime** returns the date and time of the first match, which may be a hidden file or a folder, and raises no error. VB6 and 64-bit VBA raise error 52, *Bad file name or number*. diff --git a/docs/Reference/Default/VBA/FileSystem/FileLen.md b/docs/Reference/Default/VBA/FileSystem/FileLen.md index cf1dbcaf0..2bdcd4de8 100644 --- a/docs/Reference/Default/VBA/FileSystem/FileLen.md +++ b/docs/Reference/Default/VBA/FileSystem/FileLen.md @@ -21,7 +21,7 @@ If the specified file is open when the **FileLen** function is called, the value > [!NOTE] > Use the [LOF](LOF) function to obtain the length of an open file. - + > [!WARNING] BETA 1034 > Given a *pathname* with a wildcard (`*` or `?`) that matches a file or a folder, **FileLen** returns the length of the first match, which may be a hidden file, or a folder, whose length is 0, and raises no error. VB6 and 64-bit VBA raise error 52, *Bad file name or number*. diff --git a/docs/Reference/Default/VBA/FileSystem/Kill.md b/docs/Reference/Default/VBA/FileSystem/Kill.md index dc85c2f4f..5970d24aa 100644 --- a/docs/Reference/Default/VBA/FileSystem/Kill.md +++ b/docs/Reference/Default/VBA/FileSystem/Kill.md @@ -18,7 +18,7 @@ Syntax: **Kill** *pathname* **Kill** supports the use of multiple-character (`*`) and single-character (`?`) wildcards to specify multiple files. - + > [!WARNING] BETA 1034 > **Kill** deletes a hidden or a system file, whether *pathname* names it or a wildcard matches it, and raises no error. VB6 and 64-bit VBA never delete such a file: they raise error 53, *File not found*, when *pathname* matches only hidden or system files, and otherwise delete the other files a wildcard matches and leave these. diff --git a/docs/Reference/Default/VBA/FileSystem/SetAttr.md b/docs/Reference/Default/VBA/FileSystem/SetAttr.md index b889a3387..9dbe07e2b 100644 --- a/docs/Reference/Default/VBA/FileSystem/SetAttr.md +++ b/docs/Reference/Default/VBA/FileSystem/SetAttr.md @@ -38,7 +38,7 @@ The *attributes* settings are: **SetAttr** sets the attributes of a file that is open, whatever mode it is open in, without an error. - + > [!WARNING] BETA 1034 > This differs from VB6 and 64-bit VBA, which raise run-time error 55 (*File already open*) for a file open for **Output**, **Append**, **Binary** or **Random**, and leave its attributes unchanged. They set them only for a file open for **Input**. So twinBASIC can make a file read-only while the program still has it open for writing. diff --git a/docs/Reference/Default/VBA/FileSystem/Width.md b/docs/Reference/Default/VBA/FileSystem/Width.md index b9d22c131..b3da4e8fd 100644 --- a/docs/Reference/Default/VBA/FileSystem/Width.md +++ b/docs/Reference/Default/VBA/FileSystem/Width.md @@ -23,7 +23,7 @@ Syntax: **Width #** *FileNumber* **,** *Width* > [!NOTE] > VB6 and 64-bit VBA take a *Width* from 0 to 255 only, and raise error 5, *Invalid procedure call or argument*, for one above 255. - + > [!WARNING] BETA 1034 > A negative *Width* raises no error, and **Print #** then starts a new line before each item after the first. VB6 and 64-bit VBA raise error 5, *Invalid procedure call or argument*. diff --git a/docs/Reference/Default/VBA/Financial/IPmt.md b/docs/Reference/Default/VBA/Financial/IPmt.md index c90a15149..59c68860b 100644 --- a/docs/Reference/Default/VBA/Financial/IPmt.md +++ b/docs/Reference/Default/VBA/Financial/IPmt.md @@ -35,7 +35,7 @@ The *rate* and *nper* arguments must be calculated by using payment periods expr For all arguments, cash paid out (such as deposits to savings) is represented by negative numbers; cash received (such as dividend checks) is represented by positive numbers. - + > [!WARNING] BETA 1034 > Given a *per* of 0 or less, or of *nper* + 1 or more, **IPmt** returns a value computed for that period, outside the annuity, and raises no error. VB6 and 64-bit VBA raise error 5, *Invalid procedure call or argument*. diff --git a/docs/Reference/Default/VBA/Financial/IRR.md b/docs/Reference/Default/VBA/Financial/IRR.md index 7fa486132..de5655589 100644 --- a/docs/Reference/Default/VBA/Financial/IRR.md +++ b/docs/Reference/Default/VBA/Financial/IRR.md @@ -23,7 +23,7 @@ The **IRR** function uses the order of values within the array to interpret the **IRR** is calculated by iteration. Starting with the value of *guess*, **IRR** cycles through the calculation until the result is accurate to within 0.00001 percent, and fails if it finds none in 20 tries. - + > [!WARNING] BETA 1034 > Given an array that has never been sized (`Dim A() As Double` with no **ReDim**, or one emptied by **Erase**), **IRR** ends the program with an access violation, in a 32-bit and a 64-bit build. VB6 and 64-bit VBA raise error 5, *Invalid procedure call or argument*. diff --git a/docs/Reference/Default/VBA/Financial/MIRR.md b/docs/Reference/Default/VBA/Financial/MIRR.md index 4605c7875..630467a51 100644 --- a/docs/Reference/Default/VBA/Financial/MIRR.md +++ b/docs/Reference/Default/VBA/Financial/MIRR.md @@ -29,11 +29,11 @@ The *finance_rate* and *reinvest_rate* arguments are percentages expressed as de The **MIRR** function uses the order of values within the array to interpret the order of payments and receipts. The payment and receipt values must be in the correct sequence. - + > [!WARNING] BETA 1034 > Given an array that has never been sized (`Dim A() As Double` with no **ReDim**, or one emptied by **Erase**), **MIRR** ends the program with an access violation, in a 32-bit and a 64-bit build. VB6 and 64-bit VBA raise error 5, *Invalid procedure call or argument*. - + > [!WARNING] BETA 1034 > **MIRR** returns a value and raises no error for arguments VB6 and 64-bit VBA refuse. Given *values* with one element it returns -1, and given a *finance_rate* or *reinvest_rate* of -1 it returns a rate; VB6 and 64-bit VBA raise error 5, *Invalid procedure call or argument*. Given *values* with no element less than 0 it returns infinity or NaN (not a number); VB6 and 64-bit VBA raise error 11, *Division by zero*. diff --git a/docs/Reference/Default/VBA/Financial/NPV.md b/docs/Reference/Default/VBA/Financial/NPV.md index 0b41a82a6..6154367aa 100644 --- a/docs/Reference/Default/VBA/Financial/NPV.md +++ b/docs/Reference/Default/VBA/Financial/NPV.md @@ -30,11 +30,11 @@ The net present value calculation is based on future cash flows. If the first ca The **NPV** function is similar to the [**PV**](PV) function (present value) except that the **PV** function allows cash flows to begin either at the end or the beginning of a period. Unlike the variable **NPV** cash flow values, **PV** cash flows must be fixed throughout the investment. - + > [!WARNING] BETA 1034 > Given an array that has never been sized (`Dim A() As Double` with no **ReDim**, or one emptied by **Erase**), **NPV** ends the program with an access violation, in a 32-bit and a 64-bit build. VB6 and 64-bit VBA raise error 5, *Invalid procedure call or argument*. - + > [!WARNING] BETA 1034 > Given a *rate* of -1, **NPV** returns NaN (not a number) and raises no error. VB6 and 64-bit VBA raise error 5, *Invalid procedure call or argument*. diff --git a/docs/Reference/Default/VBA/Financial/PPmt.md b/docs/Reference/Default/VBA/Financial/PPmt.md index 013abde51..03ff21be1 100644 --- a/docs/Reference/Default/VBA/Financial/PPmt.md +++ b/docs/Reference/Default/VBA/Financial/PPmt.md @@ -35,7 +35,7 @@ The *rate* and *nper* arguments must be calculated using payment periods express For all arguments, cash paid out (such as deposits to savings) is represented by negative numbers; cash received (such as dividend checks) is represented by positive numbers. - + > [!WARNING] BETA 1034 > Given a *per* of 0 or less, or of *nper* + 1 or more, **PPmt** returns a value computed for that period, outside the annuity, and raises no error. VB6 and 64-bit VBA raise error 5, *Invalid procedure call or argument*. diff --git a/docs/Reference/Default/VBA/Financial/Rate.md b/docs/Reference/Default/VBA/Financial/Rate.md index 060e045dd..af87a97c9 100644 --- a/docs/Reference/Default/VBA/Financial/Rate.md +++ b/docs/Reference/Default/VBA/Financial/Rate.md @@ -35,7 +35,7 @@ For all arguments, cash paid out (such as deposits to savings) is represented by **Rate** is calculated by iteration. Starting with the value of *guess*, **Rate** cycles through the calculation until the result is accurate to within 0.00001 percent, and fails if it finds none in 20 tries. If it fails from the default of 10 percent, try a different value for *guess*. - + > [!WARNING] BETA 1034 > Given a lump sum (a *pmt* of 0) over an even number of periods, **Rate** can return a rate below -1 and raise no error: `Rate(2, 0, 100, -121)` and `Rate(4, 0, 100, -146.41)` return -2.1. VB6 returns 0.1. diff --git a/docs/Reference/Default/VBA/HiddenModule/AddClassInstanceFlag.md b/docs/Reference/Default/VBA/HiddenModule/AddClassInstanceFlag.md index db46772ef..bfbb4812e 100644 --- a/docs/Reference/Default/VBA/HiddenModule/AddClassInstanceFlag.md +++ b/docs/Reference/Default/VBA/HiddenModule/AddClassInstanceFlag.md @@ -53,6 +53,7 @@ Syntax: **InternalGetReturnAddress()** **As LongPtr** The function is provided for code generated in debug mode. It uses special internal bindings, so it may not behave like a regular function. In a 32-bit build compiled without LLVM it returns a non-zero address, in an IDE run and in a built exe alike. In code compiled with LLVM it returns 0. + > [!NOTE] BETA 1034 > A procedure that calls **InternalGetReturnAddress** fails code generation in a 64-bit build, with or without LLVM, and the project does not build. diff --git a/docs/Reference/Default/VBA/HiddenModule/ConvertIconToBitmap.md b/docs/Reference/Default/VBA/HiddenModule/ConvertIconToBitmap.md index 82a155500..57fb773e7 100644 --- a/docs/Reference/Default/VBA/HiddenModule/ConvertIconToBitmap.md +++ b/docs/Reference/Default/VBA/HiddenModule/ConvertIconToBitmap.md @@ -18,7 +18,7 @@ Syntax: **ConvertIconToBitmap(** *IconPicture* [ **,** *BackColor* ] **)** **As The returned picture is a fresh bitmap-typed **stdole.StdPicture** with the icon rasterised on top of the chosen background. The original icon picture is unchanged. - + > [!WARNING] BETA 1034 > An *IconPicture* of **Nothing** ends the program with an access violation, in a 32-bit and a 64-bit build alike. diff --git a/docs/Reference/Default/VBA/HiddenModule/Eval.md b/docs/Reference/Default/VBA/HiddenModule/Eval.md index 30459488f..ed5815992 100644 --- a/docs/Reference/Default/VBA/HiddenModule/Eval.md +++ b/docs/Reference/Default/VBA/HiddenModule/Eval.md @@ -17,17 +17,19 @@ A fresh [**TbExpressionService**](../TbExpressionService/) is built for every ca For repeated evaluation of the same source, or for expressions that need to see application objects, construct the service explicitly and reuse a compiled [**ITbExpression**](../TbExpressionService/#itbexpression-interface). - + > [!WARNING] BETA 1034 > Some malformed expressions are not refused: the part that cannot be read is left out, and no error is raised. `Eval("2 +")`, `Eval("2 Mod")`, `Eval("(2")` and `Eval("2)")` each return 2. - + > [!NOTE] BETA 1034 > **Eval** raises -2147467259 for any error inside the expression: the error's own number and description are lost. `Eval("1 / 0")` raises -2147467259, not 11. + > [!NOTE] BETA 1034 > **Eval** also raises -2147467259 for an expression whose value is an **Error** value, such as `Eval("CVErr(5)")`, and for **Len** of **Null**, which outside an expression returns **Null**. + > [!NOTE] BETA 1034 > In a built program, the first error the expression service raises has the description *Automation error*, and every later one *Unspecified error*. In a run from the IDE, every one has the description *Automation error*. diff --git a/docs/Reference/Default/VBA/HiddenModule/GetInheritedOwner.md b/docs/Reference/Default/VBA/HiddenModule/GetInheritedOwner.md index d66c6232a..816756639 100644 --- a/docs/Reference/Default/VBA/HiddenModule/GetInheritedOwner.md +++ b/docs/Reference/Default/VBA/HiddenModule/GetInheritedOwner.md @@ -15,7 +15,7 @@ Syntax: **GetInheritedOwner(** *Value* **)** **As Object** For controls that participate in a control-container hierarchy, the inherited owner is the topmost owning object that supplies ambient settings --- typically the form. Returns **Nothing** when no inherited owner is set. - + > [!WARNING] BETA 1034 > A *Value* of **Nothing**, or an object that is not a control, ends the program with an access violation: **Nothing** and a **Collection** in a 32-bit and a 64-bit build, a **Scripting.Dictionary** in a 64-bit build. diff --git a/docs/Reference/Default/VBA/HiddenModule/RuntimeCreateGetMessageHook.md b/docs/Reference/Default/VBA/HiddenModule/RuntimeCreateGetMessageHook.md index fff6a733d..24c0558aa 100644 --- a/docs/Reference/Default/VBA/HiddenModule/RuntimeCreateGetMessageHook.md +++ b/docs/Reference/Default/VBA/HiddenModule/RuntimeCreateGetMessageHook.md @@ -12,7 +12,7 @@ Syntax: **RuntimeCreateGetMessageHook()** **As IGetMessageHook** The returned hook starts dormant. Subscribe a callback for one or more message types with [**RegisterMessage**](RegisterMessage), then call [**Start**](Start) to activate the subscriptions and [**Stop**](Stop) to remove them. - + > [!WARNING] BETA 1034 > Assigning the result to a **Variant** without **Set**, as in `V = RuntimeCreateGetMessageHook()`, ends the program with an access violation, in a 32-bit and a 64-bit build alike. Assign it with **Set**, or to a variable declared **As IGetMessageHook**. diff --git a/docs/Reference/Default/VBA/HiddenModule/vbaCastObj.md b/docs/Reference/Default/VBA/HiddenModule/vbaCastObj.md index f13169454..b58ebe36f 100644 --- a/docs/Reference/Default/VBA/HiddenModule/vbaCastObj.md +++ b/docs/Reference/Default/VBA/HiddenModule/vbaCastObj.md @@ -20,7 +20,7 @@ A direct wrapper over **IUnknown::QueryInterface**, as VB6's runtime function `_ *IID* is read as the address of a **GUID**. A **String** is not converted: its text is not an interface ID, as `"{00000000-0000-0000-C000-000000000046}"` is not. A **GUID** variable passed itself, rather than its address, ends a 32-bit program with an access violation. - + > [!WARNING] BETA 1034 > **vbaCastObj** raises no error when *Obj* does not have the interface, or when *IID* is a **String**: it returns **Nothing**. diff --git a/docs/Reference/Default/VBA/Interaction/CallByName.md b/docs/Reference/Default/VBA/Interaction/CallByName.md index 5022169b9..142a33c6d 100644 --- a/docs/Reference/Default/VBA/Interaction/CallByName.md +++ b/docs/Reference/Default/VBA/Interaction/CallByName.md @@ -27,6 +27,7 @@ Syntax: **CallByName(** *object* **,** *procname* **,** *calltype* [ **,** *args The return value is a **Variant** containing whatever the call returned. For methods that return nothing, or for property assignments, the result is **Empty**. + > [!NOTE] BETA 1034 > When the member called raises an error itself with a constant number, as in `Err.Raise 380`, or with the [**Error**](../../Core/Error) statement, **CallByName** raises error -2147352567 (`&H80020009`), *Exception occurred.*, not that number. VB6 raises the member's own number. A number the member passes in a variable, as in `Err.Raise Number`, and a run-time error such as a division by zero arrive unchanged. diff --git a/docs/Reference/Default/VBA/Interaction/MsgBox.md b/docs/Reference/Default/VBA/Interaction/MsgBox.md index 1a2ca9c51..12a7fd5e1 100644 --- a/docs/Reference/Default/VBA/Interaction/MsgBox.md +++ b/docs/Reference/Default/VBA/Interaction/MsgBox.md @@ -33,7 +33,7 @@ The *buttons* argument is a combination of values from the [**VbMsgBoxStyle**](. > [!NOTE] > **MsgBox** passes *buttons* to Windows as it is, so any style Windows accepts can be used. VB6 and 64-bit VBA show a plain **OK** box for a value they do not know --- `vbCancelTryAgainContinue`, two icons combined, a default-button value of &H400 --- and raise no error. - + > [!WARNING] BETA 1034 > A *buttons* value whose button group Windows does not have (7 to 15, -1 included) shows no box: **MsgBox** returns 0 and raises no error. VB6 and 64-bit VBA show an **OK** box and return **vbOK**. diff --git a/docs/Reference/Default/VBA/Interaction/RaiseEventByName.md b/docs/Reference/Default/VBA/Interaction/RaiseEventByName.md index bb36cca80..cd4c623a7 100644 --- a/docs/Reference/Default/VBA/Interaction/RaiseEventByName.md +++ b/docs/Reference/Default/VBA/Interaction/RaiseEventByName.md @@ -26,10 +26,11 @@ Returns a **Variant**. The returned value is the value left in the last `ByRef` If *procname* doesn't match any event declared on *object*, or the supplied arity doesn't match the event declaration, a run-time error occurs. + > [!WARNING] BETA 1034 > An *object* that declares no events, such as a **Collection** or a **Scripting.Dictionary**, ends the program with an access violation, with no error to handle. Unsaved data is lost. - + > [!NOTE] BETA 1034 > An event that no handler is attached to raises error -2147467259 (`&H80004005`), *Unspecified error*. The **RaiseEvent** statement raises nothing for it. diff --git a/docs/Reference/Default/VBA/Interaction/RaiseEventByName2.md b/docs/Reference/Default/VBA/Interaction/RaiseEventByName2.md index e5d9d54a2..8ceb71e51 100644 --- a/docs/Reference/Default/VBA/Interaction/RaiseEventByName2.md +++ b/docs/Reference/Default/VBA/Interaction/RaiseEventByName2.md @@ -26,10 +26,11 @@ Returns a **Variant**. The returned value is the value left in the last `ByRef` If *procname* doesn't match any event declared on *object*, or the supplied arity doesn't match the event declaration, a run-time error occurs. + > [!WARNING] BETA 1034 > An *object* that declares no events, such as a **Collection**, ends the program with an access violation, with no error to handle. Unsaved data is lost. - + > [!NOTE] BETA 1034 > An event that no handler is attached to raises error -2147467259 (`&H80004005`), *Unspecified error*. The **RaiseEvent** statement raises nothing for it. diff --git a/docs/Reference/Default/VBA/Interaction/SendKeys.md b/docs/Reference/Default/VBA/Interaction/SendKeys.md index 8f1f6638b..d8224bdfb 100644 --- a/docs/Reference/Default/VBA/Interaction/SendKeys.md +++ b/docs/Reference/Default/VBA/Interaction/SendKeys.md @@ -69,7 +69,7 @@ To repeat a key, use the form `{key number}` --- for example, `"{LEFT 42}"` to p > [!NOTE] > **SendKeys** can't send keystrokes to applications that aren't running in Windows, and it cannot send the PRINT SCREEN key to any application: `{PRTSC}` sends the virtual key `VK_PRINT` (&H2A), not `VK_SNAPSHOT` (&H2C), as VB6 and 64-bit VBA do. - + > [!WARNING] BETA 1034 > A character that has no key on the keyboard layout sends the wrong key, and no error is raised. The string is first converted to the ANSI code page, so a character outside it becomes its nearest ANSI character (`Ā` becomes `A`, `☺` becomes `?`), and a character the layout has no key for is sent as the virtual key of its ANSI code (`é`, on a US layout, as &HE9, which types nothing or another character). VB6 and 64-bit VBA send such a character as the character itself, which types it in any window. diff --git a/docs/Reference/Default/VBA/Math/ShiftRotateLeft.md b/docs/Reference/Default/VBA/Math/ShiftRotateLeft.md index e639a3c9d..a32571b80 100644 --- a/docs/Reference/Default/VBA/Math/ShiftRotateLeft.md +++ b/docs/Reference/Default/VBA/Math/ShiftRotateLeft.md @@ -25,6 +25,7 @@ Unlike the [**\<<**](../../Core/LeftShift) operator, which discards the bits shi With constant arguments, the call is evaluated when the code is compiled, so it can be used in a [**Const**](../../Core/Const) declaration, an **Enum** member's value or a **Case** expression. + > [!WARNING] BETA 1034 > **ShiftRotateLeft** ignores the names of named arguments and takes its arguments in the order they are written: `ShiftRotateLeft(ShiftAmount:=4, Number:=&H12345678)` rotates 4 left by `&H12345678` bits, and gives the **Integer** `&H400`. Write the arguments by position, *number* first. diff --git a/docs/Reference/Default/VBA/Math/ShiftRotateRight.md b/docs/Reference/Default/VBA/Math/ShiftRotateRight.md index cc18e663d..ac5e72eff 100644 --- a/docs/Reference/Default/VBA/Math/ShiftRotateRight.md +++ b/docs/Reference/Default/VBA/Math/ShiftRotateRight.md @@ -25,6 +25,7 @@ Unlike the [**\>>**](../../Core/RightShift) operator, which discards the bits sh With constant arguments, the call is evaluated when the code is compiled, so it can be used in a [**Const**](../../Core/Const) declaration, an **Enum** member's value or a **Case** expression. + > [!WARNING] BETA 1034 > **ShiftRotateRight** ignores the names of named arguments and takes its arguments in the order they are written: `ShiftRotateRight(ShiftAmount:=4, Number:=&H12345678)` rotates 4 right by `&H12345678` bits. Write the arguments by position, *number* first. diff --git a/docs/Reference/Default/VBA/Math/ShiftUnsignedRight.md b/docs/Reference/Default/VBA/Math/ShiftUnsignedRight.md index 7ca9caddd..4d460a74e 100644 --- a/docs/Reference/Default/VBA/Math/ShiftUnsignedRight.md +++ b/docs/Reference/Default/VBA/Math/ShiftUnsignedRight.md @@ -25,6 +25,7 @@ A shift by as many bits as the type holds, or more, gives 0. A negative *shiftam With constant arguments, the call is evaluated when the code is compiled, so it can be used in a [**Const**](../../Core/Const) declaration, an **Enum** member's value or a **Case** expression. + > [!WARNING] BETA 1034 > **ShiftUnsignedRight** ignores the names of named arguments and takes its arguments in the order they are written: `ShiftUnsignedRight(ShiftAmount:=4, Number:=&H12345678)` shifts 4 right by `&H12345678` bits, and gives 0. Write the arguments by position, *number* first. diff --git a/docs/Reference/Default/VBA/Strings/Format.md b/docs/Reference/Default/VBA/Strings/Format.md index 246cb5232..bff106756 100644 --- a/docs/Reference/Default/VBA/Strings/Format.md +++ b/docs/Reference/Default/VBA/Strings/Format.md @@ -58,6 +58,7 @@ The *firstWeekOfYear* argument has these settings. When formatting a number without specifying *format*, **Format** provides functionality similar to the **Str** function, although it is internationally aware. However, positive numbers formatted as strings using **Format** don't include a leading space reserved for the sign of the value; those converted using **Str** retain the leading space. + > [!WARNING] BETA 1034 > A 64-bit build formats a negative zero of type **Single** or **Double** (a value that equals 0 and has its sign bit set) as `"-0"` when *format* is omitted. A 32-bit build and VB6 give `"0"`. diff --git a/docs/Reference/Default/VBA/Strings/Len.md b/docs/Reference/Default/VBA/Strings/Len.md index bd6dbc5c6..098291dc7 100644 --- a/docs/Reference/Default/VBA/Strings/Len.md +++ b/docs/Reference/Default/VBA/Strings/Len.md @@ -28,6 +28,7 @@ One (and only one) of the two possible arguments must be specified. With user-de > [!NOTE] > **Len** may not be able to determine the actual number of storage bytes required when used with variable-length strings in user-defined data types. + > [!WARNING] BETA 1034 > **Len** and **LenB** accept an array as *varname* and return a number that is not its size in bytes: 40 for a fixed-size one-dimensional array of any element type or length in a 32-bit build (48 in a 64-bit build), 48 (56) for two dimensions, and 4 (8) for a dynamic array. VB6 refuses an array here when the program is compiled. Multiply the element count by the size of one element instead: `(UBound(Buffer) - LBound(Buffer) + 1) * LenB(Buffer(LBound(Buffer)))`. diff --git a/docs/Reference/Default/VBA/TbExpressionService/AddCustomBinder.md b/docs/Reference/Default/VBA/TbExpressionService/AddCustomBinder.md index 7b241884a..df5b7f74d 100644 --- a/docs/Reference/Default/VBA/TbExpressionService/AddCustomBinder.md +++ b/docs/Reference/Default/VBA/TbExpressionService/AddCustomBinder.md @@ -22,7 +22,7 @@ Use **AddCustomBinder** when [**AddCustomBinderObject**](AddCustomBinderObject) The engine calls [**Bind**](Bind) on each registered custom binder during compilation, once per unresolved symbol. The first binder that returns a non-**Nothing** result wins. Multiple custom binders can be registered with one service, and they are consulted in registration order. - + > [!WARNING] BETA 1034 > A *customBinder* of **Nothing** ends the program with an access violation, in a 32-bit and a 64-bit build alike. diff --git a/docs/Reference/Default/VBA/TbExpressionService/AddCustomBinderObject.md b/docs/Reference/Default/VBA/TbExpressionService/AddCustomBinderObject.md index 7073273ef..d70ee627f 100644 --- a/docs/Reference/Default/VBA/TbExpressionService/AddCustomBinderObject.md +++ b/docs/Reference/Default/VBA/TbExpressionService/AddCustomBinderObject.md @@ -28,7 +28,7 @@ Member resolution is performed by name through the standard COM/IDispatch protoc Multiple objects can be bound to the same service, each under its own *name*. They are consulted in the order they were added. - + > [!WARNING] BETA 1034 > An *object* of **Nothing** ends the program with an access violation, in a 32-bit and a 64-bit build alike. diff --git a/docs/Reference/Default/VBA/TbExpressionService/Compile.md b/docs/Reference/Default/VBA/TbExpressionService/Compile.md index 40676644e..1234a33dc 100644 --- a/docs/Reference/Default/VBA/TbExpressionService/Compile.md +++ b/docs/Reference/Default/VBA/TbExpressionService/Compile.md @@ -26,10 +26,11 @@ Compilation is the relatively expensive step; evaluation reuses the compiled for An expression that compiles can still fail when it is evaluated: `"1 / 0"` compiles, and [**Evaluate**](Evaluate) raises the error. - + > [!WARNING] BETA 1034 > Some malformed expressions are not refused: the part that cannot be read is left out, and no error is raised. `"2 +"`, `"2 Mod"`, `"(2"` and `"2)"` each compile to an expression whose value is 2. + > [!NOTE] BETA 1034 > In a built program, the first error the expression service raises has the description *Automation error*, and every later one *Unspecified error*. In a run from the IDE, every one has the description *Automation error*. diff --git a/docs/Reference/Default/VBA/TbExpressionService/Evaluate.md b/docs/Reference/Default/VBA/TbExpressionService/Evaluate.md index 7d2410683..b14c0961d 100644 --- a/docs/Reference/Default/VBA/TbExpressionService/Evaluate.md +++ b/docs/Reference/Default/VBA/TbExpressionService/Evaluate.md @@ -21,10 +21,11 @@ Each call re-runs the expression against the current state of its bindings. If t A run-time error raised inside the expression --- division by zero, type mismatch, an invalid call into a bound object --- propagates out of **Evaluate** like any other run-time error. - + > [!NOTE] BETA 1034 > **Evaluate** raises -2147467259 for any error inside the expression: the error's own number and description are lost. `"1 / 0"` raises -2147467259, not 11, and a **Scripting.Dictionary**'s **Add** of a key it holds raises -2147467259, not 457. + > [!NOTE] BETA 1034 > In a built program, the first error the expression service raises has the description *Automation error*, and every later one *Unspecified error*. In a run from the IDE, every one has the description *Automation error*. An error of a bound object's member has the description *Automation error* in both. diff --git a/docs/Reference/Default/VBRUN/ErrorCallstack/index.md b/docs/Reference/Default/VBRUN/ErrorCallstack/index.md index 2005f5189..a476f22b1 100644 --- a/docs/Reference/Default/VBRUN/ErrorCallstack/index.md +++ b/docs/Reference/Default/VBRUN/ErrorCallstack/index.md @@ -14,10 +14,11 @@ An **ErrorCallstack** object is a snapshot of the chain of procedures that were The snapshot is read through the **Callstack** property of an **ErrorContext** object, which is itself accessible from the structured error-handling machinery --- typically inside a `Catch` block or an **On Error** handler. + > [!NOTE] BETA 1034 > twinBASIC code cannot obtain an **ErrorCallstack** object, because it cannot obtain the [**ErrorContext**](../ErrorContext) that returns one. The sample below compiles, but no code can supply its argument. -```tb check_build +```tb check_build id=errorcallstack-argument Sub LogStackTrace(ByVal Stack As ErrorCallstack) Dim i As Long For i = 1 To Stack.Count diff --git a/docs/Reference/Default/VBRUN/ErrorContext/index.md b/docs/Reference/Default/VBRUN/ErrorContext/index.md index f8a7258c3..a376919d5 100644 --- a/docs/Reference/Default/VBRUN/ErrorContext/index.md +++ b/docs/Reference/Default/VBRUN/ErrorContext/index.md @@ -14,15 +14,16 @@ An **ErrorContext** object captures everything the runtime knows about a run-tim The error-identity properties (**Number**, **Description**, **Source**, **HelpFile**, **HelpContext**, **LastDLLError**) have the same meaning here as on the **Err** object --- see the [**ErrObject**](../../../Modules/ErrObject) module for a discussion of each. **State** and **Callstack** are unique to **ErrorContext** and reflect the structured error-handling machinery that has no equivalent on the legacy **Err** object. + > [!NOTE] BETA 1034 > twinBASIC code cannot obtain an **ErrorContext** object. The **ErrEx** object that returns one is not available, and neither are the **Try**, **Catch** and **Finally** blocks that several [**State**](#state) values refer to. The **Err** object does not implement this interface. The VBRUN package declares the interface and its members, so code that uses them compiles, but nothing returns an object that implements them. > -> ```tb check_build expect-error=TB5079 +> ```tb check_build expect-error=TB5079 id=errorcontext-errex > Dim context As ErrorContext > Set context = ErrEx > ``` > -> ```tb check_build expect-error=TB5079 +> ```tb check_build expect-error=TB5079 id=errorcontext-try > Try > Err.Raise 5 > Catch diff --git a/docs/Reference/Default/VBRUN/ErrorStackFrame/index.md b/docs/Reference/Default/VBRUN/ErrorStackFrame/index.md index e9ca0bd7a..9bab058a5 100644 --- a/docs/Reference/Default/VBRUN/ErrorStackFrame/index.md +++ b/docs/Reference/Default/VBRUN/ErrorStackFrame/index.md @@ -12,10 +12,11 @@ has_toc: false An **ErrorStackFrame** describes one procedure that was active on the call stack at the moment a run-time error was raised --- the project it belongs to, the module that contains it, and its own name. Frames are produced by iterating an [**ErrorCallstack**](../ErrorCallstack) snapshot, which in turn is reachable from the [**Callstack**](../ErrorContext#callstack) property of an [**ErrorContext**](../ErrorContext). Every property is read-only. + > [!NOTE] BETA 1034 > twinBASIC code cannot obtain an **ErrorStackFrame** object, because it cannot obtain the [**ErrorContext**](../ErrorContext) that leads to one. The sample below compiles, but no code can supply its argument. -```tb check_build +```tb check_build id=errorstackframe-argument Sub LogStackTrace(ByVal Stack As ErrorCallstack) Dim i As Long For i = 1 To Stack.Count diff --git a/docs/Reference/Literal-Keywords.md b/docs/Reference/Literal-Keywords.md index 75d1b02e6..a5ad97c5c 100644 --- a/docs/Reference/Literal-Keywords.md +++ b/docs/Reference/Literal-Keywords.md @@ -30,9 +30,11 @@ vba_attribution: true Test for it with [**Is**](../tB/Core/Is), `Items Is Nothing`, or with twinBASIC's [**IsNot**](../tB/Core/IsNot), `Items IsNot Nothing`. Two more forms are twinBASIC's own: an initializer in a declaration, `Dim Items As Collection = Nothing`, needs no **Set**, and [**ByVal Nothing**](../Features/Language/Pointers#byval-nothing) passes a null pointer for a **ByRef** interface parameter. + > [!NOTE] BETA 1034 > **Nothing** assigned without **Set** to a variable that is not an object or a **Variant**, such as `Count = Nothing` for a **Long**, and **Nothing** compared with `=` or `<>`, such as `If Obj = Nothing`, compile and raise error [91](tb:Errors#91) when they run. VB6 and 64-bit VBA refuse them when they compile, with *Invalid use of object*. + > [!WARNING] BETA 1034 > A **Variant** assigned **Nothing** without **Set**, `v = Nothing`, holds **Nothing**, as after `Set v = Nothing`, and no error is raised. VB6 and 64-bit VBA raise error [91](tb:Errors#91) and leave the **Variant** **Empty**. diff --git a/docs/Reference/Operators.md b/docs/Reference/Operators.md index 953d178e0..6c6237416 100644 --- a/docs/Reference/Operators.md +++ b/docs/Reference/Operators.md @@ -147,6 +147,7 @@ A result that does not fit its type raises error 6, *Overflow*. Some combination | **Date** `+` **Integer** | **Date** | | **String** `+` **Integer** | **Double**: `"34" + 6` is 40 | + > [!WARNING] BETA 1034 > An addition, subtraction, multiplication or division whose result is a declared **Single** raises no error when the result overflows: the **Single** holds infinity (`1.#INF`) instead. VB6 raises error 6, *Overflow*. Declare the variable **Double** where an overflow must be caught. Code compiled with LLVM has a similar defect for **Double**; see [Language support](../LLVM/Getting-Started#language-support). diff --git a/docs/Reference/Run-time-Errors.md b/docs/Reference/Run-time-Errors.md index 50053f367..9e964698c 100644 --- a/docs/Reference/Run-time-Errors.md +++ b/docs/Reference/Run-time-Errors.md @@ -32,10 +32,11 @@ Each number is given in decimal and in hexadecimal, as a 32-bit **Long**: 5 (`&H The message is the one that **Err.Description** gives for the error once it is raised. For the numbers from 1 to 65535, [**Error**](../tB/Modules/Conversion/Error)`(number)` gives the same text. For an HRESULT, **Error** gives *Application-defined or object-defined error*, as it does for any number without a message of its own, and the table shows both. + > [!NOTE] BETA 1034 > For a number from 1 to 65535 that has no message of its own, **Error** gives *Application-defined or object-defined error* in twinBASIC and VB6. After `Err.Raise` of such a number, **Err.Description** in VB6 is that same text; in twinBASIC it is empty for a number below 747, and for 747 and above it is the Windows system message of that number, such as *Page fault was a transition fault.* for 747. - + > [!NOTE] BETA 1034 > `Err.Raise` of an HRESULT that has a VB error of the same meaning, such as `&H80070057` (`E_INVALIDARG`), raises the HRESULT in twinBASIC. VB6 and 64-bit VBA raise the VB error instead, 5 for that one. [**Raise**](../tB/Modules/ErrObject/Raise) lists them. diff --git a/scripts/bug_repro.mjs b/scripts/bug_repro.mjs index f3e766162..69bd96f59 100644 --- a/scripts/bug_repro.mjs +++ b/scripts/bug_repro.mjs @@ -122,6 +122,7 @@ import { withUsageError, } from "../lib/cli.mjs"; import { REPO_ROOT } from "../lib/repo-paths.mjs"; +import { laneBlocks, testMark } from "./lib/lane-report.mjs"; import { keptIdeLines, killTree, launchOnDesktop, summaryLine, TARGETS } from "./lib/tb-ide.mjs"; import { BETA_FILE, @@ -157,6 +158,7 @@ import { reproZipFiles, runRepro, } from "./lib/vb6.mjs"; +import { SUITES } from "./lib/test-markers.mjs"; import { fileEntry, zipFiles } from "./lib/zip.mjs"; let tidy = null; @@ -648,12 +650,9 @@ const EXPECT_KEYS = { test: ["exit", "output", "absent"], }; -// The suites a lane reproducer names, as ":": the runner, and the -// lanes it has, by the name its --only matches and its report prints. -const SUITES = { - ide: { tool: "scripts/ide_test.mjs", lanes: "test/ide/lanes.mjs" }, - addin: { tool: "scripts/addin_test.mjs", lanes: "test/addin/lanes.mjs" }, -}; +// The suites a lane reproducer names, as ":" (SUITES: the runner, and +// the file that lists its lanes), and the lanes each has, by the name its --only +// matches and its report prints. const laneNames = {}; for (const [suite, { lanes }] of Object.entries(SUITES)) { const list = (await import(pathToFileURL(path.join(REPO_ROOT, lanes)).href)).default; @@ -1377,10 +1376,6 @@ function runProbeScript(repro) { }); } -// The spec reporter's marks for a test that passed and one that failed. -const PASSED = "\u{2714}"; -const FAILED = "\u{2716}"; - /** * Runs the lanes that lane reproducers name, each suite's once through its own * runner, and judges each reproducer by its tests: it reproduces when every one @@ -1422,14 +1417,7 @@ async function verifyLanes(slugs, repros) { const log = path.join(tmpdir(), "bugrepro", `verify-${suite}.log`); mkdirSync(path.dirname(log), { recursive: true }); writeFileSync(log, r.out); - const lines = r.out.split(/\r?\n/); - // A lane's report is the block from its "--- (): ..." line to the next lane's. - const block = (lane) => { - const start = lines.findIndex((l) => l.startsWith(`--- ${lane} (`)); - if (start < 0) return null; - const end = lines.findIndex((l, i) => i > start && l.startsWith("--- ")); - return lines.slice(start, end < 0 ? lines.length : end); - }; + const blocks = laneBlocks(r.out); // The runner's 2 is its harness failing, and 3 the registry or a work folder // not put back: either leaves no verdict to trust. const broken = r.code === null || r.code >= 2; @@ -1437,7 +1425,7 @@ async function verifyLanes(slugs, repros) { for (const slug of mine) { const repro = repros.get(slug); const lane = repro.lane.split(":")[1]; - const report = block(lane); + const report = blocks.get(lane)?.lines ?? null; const res = (status, detail) => results.push({ slug, status, detail: status === "reproduces" ? detail : `${detail} (see ${log})` }); if (broken) { @@ -1448,14 +1436,7 @@ async function verifyLanes(slugs, repros) { res("harness", `${tool} printed no report for the lane ${lane}`); continue; } - const mark = (test) => { - const found = report - .map((l) => l.trimStart()) - .filter((l) => l.startsWith(`${PASSED} ${test} (`) || l.startsWith(`${FAILED} ${test} (`)); - if (!found.length) return null; - return found.some((l) => l.startsWith(FAILED)) ? "failed" : "passed"; - }; - const marks = repro.tests.map((test) => ({ test, mark: mark(test) })); + const marks = repro.tests.map((test) => ({ test, mark: testMark(report, test) })); const missing = marks.filter((m) => !m.mark); const failed = marks.filter((m) => m.mark === "failed"); if (missing.length) diff --git a/scripts/check_callout_evidence.mjs b/scripts/check_callout_evidence.mjs new file mode 100644 index 000000000..e68efa3a7 --- /dev/null +++ b/scripts/check_callout_evidence.mjs @@ -0,0 +1,345 @@ +// Gate: every callout labelled with a twinBASIC build names what checks it, and every marker +// names something that exists. +// +// A callout labelled `BETA ` states what that build does, so a new build sends it back to be +// checked. The program that checks it is named in an HTML comment on the line above it: +// +// +// > [!WARNING] BETA 1034 +// +// or by a region around it (scripts/lib/test-markers.mjs has the grammar). The callouts of a +// generated Common Errors section need none: errors/.yml is theirs, as the section's opening +// comment says. scripts/recheck_callouts.mjs runs what the markers name on a new build, and moves +// each label or lists the callout for a person. +// +// It checks that: +// * every callout labelled BETA outside a generated errors section is marked; +// * every item of every marker under docs/ parses, and each one outside a contributed +// package's test project names something that exists: a reproducer under bugs/, a lane, one +// tb fence with that id built as a sample, a file of errors/, a mode of the LLVM probe; +// * a page outside a contributed package's section names no package test case, and a region +// outside those sections is closed and does not nest (check_package_tests.mjs checks the +// sections' own regions and cases); +// * no page has the old `` comment, which no tool read; +// * every fence marked `probe`, which the reader never sees, is named by some marker. +// It reads only the sources and needs no twinBASIC install. +// +// node scripts/check_callout_evidence.mjs + +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { exitOnCrash, parseCli, printHelpAndExit, withUsageError } from "../lib/cli.mjs"; +import { markdownFiles } from "../lib/markdown-files.mjs"; +import { packageTestNames } from "../lib/package-tests.mjs"; +import { DOCS_DIR, REPO_ROOT } from "../lib/repo-paths.mjs"; +import { createProbes } from "./lib/gate-probes.mjs"; +import { checkKey, packageCases, readVerify, relabelLine, sampleStatus, verdictOf } from "./lib/recheck.mjs"; +import { collectFences, PROBE_MARKER } from "./lib/tb-fences.mjs"; +import { evidenceIndex, itemProblem, kindOf, markersOf, PACKAGE_KINDS, parseMarker } from "./lib/test-markers.mjs"; + +exitOnCrash(); + +const USAGE = `usage: node scripts/check_callout_evidence.mjs [-h, --help] + +Checks that every callout labelled BETA names, in an HTML comment on the +line above it or a region around it, what checks it, and that every + marker under docs/ names a reproducer, lane, sample, error +data file or LLVM probe mode that exists. The callouts of a generated Common +Errors section are checked by their data and need no marker. + + -h, --help print this text and exit + +Exit codes: + 0 every probe passed, and every labelled callout and marker is in order + 1 a probe failed, or a callout or marker is not in order + 2 the gate could not run: a refused command line, or a crash`; + +if ( + withUsageError(() => + parseCli(process.argv.slice(2), { + options: { help: { type: "boolean", short: "h" } }, + stopAt: ["help"], + }), + ).values.help +) + printHelpAndExit(USAGE); + +const { check, report } = createProbes("check_callout_evidence", "see scripts/lib/test-markers.mjs"); + +// ---------------------------------------------------------------- probes + +{ + const page = [ + "Text.", + "", + "> [!WARNING] BETA 1034", + "> Wrong.", + "", + "> [!NOTE] BETA 1034", + "> Unmarked.", + "", + "```md", + "", + "> [!NOTE] BETA 1034", + "```", + "", + " ", + " > [!NOTE] BETA 1034", + "", + "", + "A paragraph the sample checks.", + "", + "> [!IMPORTANT] BETA 1034", + "> Inside the region.", + "", + "", + "", + "> [!NOTE] BETA 1034", + "> Generated.", + "", + ].join("\r\n"); + const { callouts: c, markers: mk, problems } = markersOf(page); + check( + "a marker or callout in a fence or in indented code is text", + c.length === 4 && mk.length === 2 && problems.length === 0, + JSON.stringify({ c, mk, problems }), + ); + check( + "a marker on the line above a callout marks it with its items, and its label is read", + c[0].marked && + c[0].markerLine === 2 && + JSON.stringify(c[0].evidence) === JSON.stringify([{ bug: "some-bug" }, { lane: "ide:forms" }]) && + c[0].label.items[0]?.kind === "beta" && + mk[0].region === false, + JSON.stringify(c[0]), + ); + check("a callout with no marker above it and no region around it is not marked", !c[1].marked, JSON.stringify(c[1])); + check( + "a callout of any type inside a region is marked by the region's items", + c[2].kind === "IMPORTANT" && c[2].marked && c[2].evidence?.[0]?.sample === "s1" && mk[1].region, + JSON.stringify([c[2], mk[1]]), + ); + check( + "a callout of a generated errors section names its data file", + c[3].errors === "VBA.Strings" && !c[3].marked && c[0].errors === null, + JSON.stringify(c[3]), + ); +} + +{ + const items = parseMarker( + "bug a-b, lane addin:help, sample x.y-1, errors VBA.Math, llvm win64-llvm, F.S, compile C, probe p1, bug Bad_Slug, lane web:x, wat", + ); + check( + "every kind of item is read, and a malformed one is bad", + JSON.stringify(items) === + JSON.stringify([ + { bug: "a-b" }, + { lane: "addin:help" }, + { sample: "x.y-1" }, + { errors: "VBA.Math" }, + { llvm: "win64-llvm" }, + { case: "F.S" }, + { compile: "C" }, + { probe: "p1" }, + { bad: "bug Bad_Slug" }, + { bad: "lane web:x" }, + { bad: "wat" }, + ]), + JSON.stringify(items), + ); + check( + "none keeps its reason and stands alone, and none without a reason is bad", + parseMarker("none -- a person, bug x")[0].none === "a person, bug x" && !!parseMarker("none")[0].bad, + JSON.stringify([parseMarker("none -- a person, bug x"), parseMarker("none")]), + ); + const fence = (rel, line, info) => ({ + rel, + line, + flags: new Set(info.split(" ").filter((w) => !w.includes("="))), + keys: new Map(info.split(" ").flatMap((w) => (w.includes("=") ? [w.split("=")] : []))), + }); + const index = { + bugs: new Map([["a-b", { filed: false }]]), + lanes: new Set(["ide:forms"]), + samples: new Map([ + ["ok", [fence("A.md", 3, "check_build check_run id=ok")]], + ["ctx", [fence("A.md", 9, "check_build hidden id=ctx")]], + ["two", [fence("A.md", 12, "check_build id=two"), fence("B.md", 4, "check_build id=two")]], + ]), + errors: new Set(["VBA.Math"]), + llvm: new Set(["win32"]), + }; + const verdicts = [ + { bug: "a-b" }, + { bug: "c-d" }, + { lane: "ide:forms" }, + { lane: "addin:nope" }, + { sample: "ok" }, + { sample: "ctx" }, + { sample: "two" }, + { sample: "none-such" }, + { errors: "VBA.Math" }, + { errors: "VBA.Nope" }, + { llvm: "win32" }, + { llvm: "win99" }, + { case: "F.S" }, + ].map((i) => itemProblem(i, index)); + check( + "an item that names nothing that exists, a fence that is context or two fences with one id is a problem", + verdicts.map((v) => (v ? "x" : ".")).join("") === ".x.x.xxx.x.x.", + JSON.stringify(verdicts, null, 1), + ); +} + +// recheck_callouts.mjs's reading of the tools' reports, and its relabelling. +{ + const read = readVerify( + [ + "running scripts/ide_test.mjs for forms", + "a-bug (filed #2482): reproduces -- run exit 0", + "b-bug: NO LONGER REPRODUCES -- compile: TB5079 gone", + "c-bug: manual", + "d-bug (filed): harness failed -- tbrun exit 2", + "3 reproduce, 1 no longer reproduce, 0 manual, 0 harness failed", + ].join("\r\n"), + ); + check( + "a reproducer's line from bug_repro verify gives its status, a filed one's included", + [...read].map(([s, r]) => `${s}=${r.status}`).join(" ") === "a-bug=holds b-bug=changed c-bug=hand d-bug=error", + JSON.stringify([...read]), + ); + const report = { + findings: [{ id: "p@win64", message: "raised error 53" }], + ids: { passed: ["s", "p", "p@win64", "u"], unrun: ["u"] }, + }; + const status = (ids) => sampleStatus(report, ids).status; + check( + "a sample holds when each of its runs passed with no finding, and not when one has one, was not built or not run", + status(["s"]) === "holds" && + status(["p", "p@win64"]) === "changed" && + status(["q"]) === "error" && + status(["u"]) === "error" && + sampleStatus(null, ["s"]).status === "error", + JSON.stringify([["s"], ["p", "p@win64"], ["q"], ["u"]].map((ids) => sampleStatus(report, ids))), + ); + const cases = packageCases( + { + packages: [ + { + package: "Pkg", + cases: [ + { name: "F.A", arch: "win32", verdict: "documented" }, + { name: "F.A", arch: "win64", verdict: "documented" }, + { name: "F.B", arch: "win32", verdict: "documented" }, + { name: "F.B", arch: "win64", verdict: "fixed" }, + ], + }, + ], + }, + "Pkg", + ); + check( + "a package case holds when it is as documented on every architecture", + cases("F.A").status === "holds" && cases("F.B").status === "changed" && cases("F.C").status === "error", + JSON.stringify([cases("F.A"), cases("F.B"), cases("F.C")]), + ); + check( + "a callout takes the worst status of its items, and none is no verdict to relabel on", + verdictOf(["holds", "holds"]) === "holds" && + verdictOf(["holds", "hand"]) === "hand" && + verdictOf(["hand", "error", "holds"]) === "error" && + verdictOf(["error", "changed"]) === "changed" && + verdictOf([]) === "error", + ); + check( + "a package's cases are one check, a probe of it another, and the rest are named by their item", + checkKey({ case: "F.A" }, "Pkg") === "package Pkg" && + checkKey({ compile: "C" }, "Pkg") === "package Pkg" && + checkKey({ probe: "c26" }, "Pkg") === "probe Pkg:c26" && + checkKey({ bug: "a-b" }, null) === "bug a-b" && + checkKey({ none: "why" }, null) === "none", + ); + check( + "relabelling moves the build, keeps the package version and the indent, and leaves since BETA and other lines alone", + relabelLine("> [!WARNING] BETA 1034", 1035) === "> [!WARNING] BETA 1035" && + relabelLine(" > [!NOTE] 1.0.0.4, BETA 1010", 1035) === " > [!NOTE] 1.0.0.4, BETA 1035" && + relabelLine("> [!NOTE] since BETA 984", 1035) === null && + relabelLine("> Some BETA 1034 text.", 1035) === null, + JSON.stringify([ + relabelLine("> [!WARNING] BETA 1034", 1035), + relabelLine(" > [!NOTE] 1.0.0.4, BETA 1010", 1035), + relabelLine("> [!NOTE] since BETA 984", 1035), + ]), + ); +} + +// ---------------------------------------------------------------- the corpus + +const problems = []; +const pages = await markdownFiles(DOCS_DIR); +const fences = await collectFences(DOCS_DIR); +const index = await evidenceIndex({ fences }); +const projects = new Set(packageTestNames(path.join(REPO_ROOT, "test", "package-tests"))); +const OLD_BUG = /^[ \t]*[ \t]*$/m; + +const named = new Set(); +let labelled = 0; +let marked = 0; +let generated = 0; +for (const rel of pages) { + const text = readFileSync(path.join(DOCS_DIR, rel), "utf8"); + const section = /^Reference\/Contributed\/([^/]+)\//.exec(rel)?.[1] ?? null; + // The package gate checks a section with a test project: its regions and its package items. + const packageGate = section !== null && projects.has(section); + const { callouts, markers, problems: regions } = markersOf(text); + if (!packageGate) problems.push(...regions.map((p) => `${rel} ${p}`)); + const old = OLD_BUG.exec(text); + if (old) { + const line = text.slice(0, old.index).split("\n").length; + problems.push(`${rel} line ${line}: is not a marker: write `); + } + for (const c of callouts) { + if (!c.label.items.some((i) => i.kind === "beta")) continue; + labelled++; + if (c.errors) { + generated++; + continue; + } + if (c.marked) marked++; + else + problems.push( + `${rel} line ${c.line}: the ${c.kind} labelled ${c.label.items.map((i) => i.text).join(", ")} names nothing that checks it: put on the line above it`, + ); + } + for (const mk of markers) { + const where = `${rel} line ${mk.line}`; + if (!mk.items.length && !packageGate) problems.push(`${where}: the marker names nothing`); + for (const item of mk.items) { + const kind = kindOf(item); + if (kind === "sample") named.add(item.sample); + if (PACKAGE_KINDS.has(kind)) { + if (section === null) problems.push(`${where}: ${kind} items name a contributed package's test cases`); + else if (!packageGate) problems.push(`${where}: ${section} has no test project for a ${kind} item to name`); + continue; + } + const problem = itemProblem(item, index); + if (problem) problems.push(`${where}: ${problem}`); + } + } +} +for (const f of fences) { + if (!f.flags.has(PROBE_MARKER)) continue; + const id = f.keys.get("id"); + if (!id) problems.push(`${f.rel} line ${f.line}: a probe has an id=, which a marker names`); + else if (!named.has(id)) + problems.push(`${f.rel} line ${f.line}: no marker names the probe ${id}: `); +} + +check( + `every labelled callout names what checks it (${labelled} labelled BETA : ${marked} marked, ${generated} in errors sections), and every marker names what exists`, + problems.length === 0, + problems.join("\n"), +); + +process.exitCode = report(); diff --git a/scripts/check_examples.mjs b/scripts/check_examples.mjs index 42b5954c5..7e1acce7e 100644 --- a/scripts/check_examples.mjs +++ b/scripts/check_examples.mjs @@ -98,10 +98,13 @@ import { } from "../lib/cli.mjs"; import { mapLines } from "../lib/markdown.mjs"; import { + ARCH_KEY, + ARCHES, CLASS_SLOTS, CONCAT_KEY, HIDDEN_MARKER, MARKER, + PROBE_MARKER, RUN_MARKER, SLOTS, classify, @@ -144,7 +147,9 @@ import { judgeOutput, parseRun, partitionRun, + probeRefusal, runRefusal, + splitArches, } from "./lib/example-run.mjs"; import { HarnessError, HARNESS_NAME, optionSeconds, readHarness, timeoutMs } from "./lib/harness-settings.mjs"; import { compileProject } from "./lib/tb-build.mjs"; @@ -400,8 +405,9 @@ function select(fences) { addFinding( fence, `unrecognised fence markup: ${fence.bad.join(" ")}`, - `known flags: ${MARKER}, ${RUN_MARKER}, ${HIDDEN_MARKER}; keys: slot=${SLOTS.join("|")}, ` + - `inherits=, project=, projname=, id=, expect-error=, resource=, inert=, ${CONCAT_KEY}=`, + `known flags: ${MARKER}, ${RUN_MARKER}, ${HIDDEN_MARKER}, ${PROBE_MARKER}; keys: slot=${SLOTS.join("|")}, ` + + `inherits=, project=, projname=, id=, expect-error=, resource=, inert=, ${CONCAT_KEY}=, ` + + `${ARCH_KEY}=${ARCHES.join("|")} or both, comma-separated`, ); continue; } @@ -494,8 +500,9 @@ function select(fences) { continue; } // A fence that asks to be RUN has to be one that can be: a statement - // sample, with nothing in it that waits for a person or ends the run. - const refusal = runRefusal(fence); + // sample, with nothing in it that waits for a person or ends the run. A + // probe has an id, and is not context. + const refusal = runRefusal(fence) ?? probeRefusal(fence); if (refusal) { addFinding(fence, refusal.message, refusal.detail); continue; @@ -691,7 +698,8 @@ function stageBatch(batch, work) { // also packs a tree holding an embedded package, as a resource= fence staged // under Packages/ makes. packTree(proj, dir); - return { proj, dir, map, runs }; + // A batch of win64 copies (splitArches) is built for that target, the rest for win32. + return { proj, dir, map, runs, arch: batch.arch ?? ARCHES[0] }; } // ------------------------------------------------------------------- building @@ -758,6 +766,7 @@ const timingBatch = (batch, staged) => samples: batch.fences.length, runs: staged.runs.length, canary: !!batch.canary, + arch: staged.arch, llvm: LLVM, build: BUILD, }); @@ -775,6 +784,7 @@ async function buildCanary(batch, session, work) { project: staged.proj, ide: IDE, session, + arch: staged.arch, build: BUILD || !!run, llvm: LLVM, run, @@ -807,6 +817,7 @@ async function buildStaged(staged, session) { project: staged.proj, ide: IDE, session, + arch: staged.arch, build: BUILD || !!run, llvm: LLVM, run, @@ -1232,12 +1243,17 @@ async function main() { : []), ] : makeBatches(restList, { batchSize, jobs }); + // A run sample that names win64 in `arch=` is also run as a copy of its own, + // `@win64`, in projects built for that target (splitArches). + const { win32: runList32, win64: runList64 } = splitArches(runList); // A run that builds always keeps a unit with its own `Sub Main` apart, as above. - const runBatches = makeBatches(runList, { - batchSize, - jobs, - alone: (members) => members.some((f) => DECLARES_MAIN.test(f.content)), - }).map((b) => ({ ...b, run: true })); + const runBatchesOf = (list, extra) => + makeBatches(list, { + batchSize, + jobs, + alone: (members) => members.some((f) => DECLARES_MAIN.test(f.content)), + }).map((b) => ({ ...b, run: true, ...extra })); + const runBatches = [...runBatchesOf(runList32, {}), ...runBatchesOf(runList64, { arch: ARCHES[1] })]; const canaryBatches = exclusions.canaries.flatMap((c) => { const { run, rest } = partitionRun(c.fences); return [ @@ -1271,7 +1287,7 @@ async function main() { (staged ? `, ${staged} staged file(s)` : ""), ); - const wantRun = exclusions.kept.filter(isRunFence).length; + const wantRun = [...runList32, ...runList64].filter(isRunFence).length; if (wantRun) { say(` ${wantRun} sample(s) marked \`${RUN_MARKER}\` are built and run, in ${runBatches.length} project(s)`); } @@ -1331,10 +1347,12 @@ async function main() { const passed = []; const canaryIds = new Set(exclusions.canaries.flatMap((c) => [...c.ids])); - for (const fence of selected) { + // The win64 copies are judged as samples of their own, by their own ids. + for (const fence of [...selected, ...runList64.filter(isRunFence)]) { if (fence.isResource) continue; // a staged file, not a sample + const baseId = fence.baseId ?? fence.id; // Not built, or judged as its canary's batches are. - if (exclusions.notBuilt.has(fence.id) || canaryIds.has(fence.id)) continue; + if (exclusions.notBuilt.has(baseId) || canaryIds.has(baseId)) continue; if (crashed.has(fence.id)) continue; // reported already, and never a pass const diags = (errorsById.get(fence.id) ?? []).filter((d) => d.severity === "ERROR"); @@ -1377,7 +1395,7 @@ async function main() { if (!diags.length) { if (!blamed.has(fence.id)) { passed.push(fence); - if (isRunFence(fence) && !exclusions.notRun.has(fence.id)) judgeRun(fence); + if (isRunFence(fence) && !exclusions.notRun.has(baseId)) judgeRun(fence); } continue; } @@ -1397,6 +1415,9 @@ async function main() { }); } + // The samples that passed, each win64 copy counted with its sample. + const passedSamples = passed.filter((f) => !f.baseId).length; + // A row inside a package a template embeds is the package's (splitPackageFaults). const embeddedFolders = new Set([...embeddedOf.values()].flat().map((p) => p.folder)); const { byPackage, own: ownFaults } = splitPackageFaults([...new Set(templateFaults)], embeddedFolders); @@ -1414,7 +1435,7 @@ async function main() { if (MODE_PROPOSE) { const unmarked = passed.filter((f) => !f.marked); say( - `\n${passed.length} of ${samples.length} sample(s) compile; ` + + `\n${passedSamples} of ${samples.length} sample(s) compile; ` + `${unmarked.length} of them are not yet marked \`${MARKER}\``, ); const byPage = new Map(); @@ -1451,21 +1472,24 @@ async function main() { // because a template that does not compile makes every sample in it fail. const real = findings.filter((f) => !f.advisory).length + (ownFaults.length ? 1 : 0); if (AS_JSON) { - console.log(JSON.stringify({ selected: samples.length, passed: passed.length, findings }, null, 2)); + // `ids` names the samples that passed and the run samples that were not run, for + // a caller that judges samples by id (scripts/recheck_callouts.mjs). + const ids = { passed: passed.map((f) => f.id), unrun: [...unrunIds] }; + console.log(JSON.stringify({ selected: samples.length, passed: passedSamples, findings, ids }, null, 2)); } else { reportFindings(); // The same grouping `--report` prints, from the run that just produced it: // a survey read page by page is 277 notes, and the question a survey is run // to answer is which of them share a cause. if (MODE_PROPOSE) { - summarise({ selected: samples.length, passed: passed.length, findings }); + summarise({ selected: samples.length, passed: passedSamples, findings }); } say( `\ncheck_examples: ${samples.length} sample(s), ` + (exclusions.notBuilt.size || exclusions.notRun.size ? `${exclusions.notBuilt.size} excluded from the build, ${exclusions.notRun.size} from the run, ` : "") + - `${passed.length} compile, ` + + `${passedSamples} compile, ` + `${real} finding(s), ${secs}s` + (real ? "" : " -- clean"), ); @@ -1492,6 +1516,7 @@ async function main() { // What the run samples did, for the summary line. const runStats = { ran: 0, matched: 0, unrun: 0 }; +const unrunIds = new Set(); const runNotes = []; /** @@ -1505,10 +1530,12 @@ function judgeRun(fence) { if (!out) { // Compiled clean, but its project had errors elsewhere, so nothing in it was built. runStats.unrun++; + unrunIds.add(fence.id); return; } if (!out.began) { runStats.unrun++; + unrunIds.add(fence.id); runNotes.push( ` note: ${where(fence)} (${fence.id}) was not run: ` + (out.culprit ? `${where(out.culprit)} did not return` : "the run ended before it"), diff --git a/scripts/check_package_tests.mjs b/scripts/check_package_tests.mjs index ed53ff525..20b53a7c9 100644 --- a/scripts/check_package_tests.mjs +++ b/scripts/check_package_tests.mjs @@ -10,7 +10,9 @@ // // with `Fixture.Sub` for a [TestCase], `compile ` for a compile case, `probe ` for a // scenario of the package's probe/ harness, or `none -- ` for a callout no program can -// check. This gate reads only the sources: the section's pages, the project's files and the +// check. A marker may also name what any page's marker can (scripts/lib/test-markers.mjs), a +// reproducer under bugs/ for a fact of twinBASIC, say: scripts/check_callout_evidence.mjs checks +// those items. This gate reads only the sources: the section's pages, the project's files and the // template that names the package's version. It needs no twinBASIC install; running the cases is // scripts/package_tests.mjs. // @@ -57,12 +59,11 @@ import { expectFrom, judge, malformedCases, - markersOf, outcomeOf, - parseMarker, projectCases, runAllText, } from "./lib/package-test-cases.mjs"; +import { kindOf, markersOf, PACKAGE_KINDS, parseMarker } from "./lib/test-markers.mjs"; exitOnCrash(); @@ -383,6 +384,7 @@ function checkPackage(pkg) { const { callouts, markers, problems: regionProblems } = markersOf(text); out.push(...regionProblems.map((p) => `${rel} ${p}`)); for (const c of callouts) { + if (c.kind !== "NOTE" && c.kind !== "WARNING") continue; if (!c.marked) out.push( `${rel} line ${c.line}: the ${c.kind} has no marker on the line above, nor a region around it`, @@ -391,12 +393,10 @@ function checkPackage(pkg) { for (const mk of markers) { const where = `${rel} line ${mk.line}`; if (!mk.items.length) out.push(`${where}: the marker names nothing`); - for (const item of mk.items) { - if (item.bad) - out.push( - `${where}: the marker's item "${item.bad}" is not Fixture.Sub, compile , probe or none -- `, - ); - else if (item.case && !tests.some((t) => `${t.fixture}.${t.name}` === item.case)) + // A bad item, and one that names something outside the project, are + // check_callout_evidence.mjs's to report. + for (const item of mk.items.filter((i) => PACKAGE_KINDS.has(kindOf(i)))) { + if (item.case && !tests.some((t) => `${t.fixture}.${t.name}` === item.case)) out.push(`${where}: no [TestCase] ${item.case}`); else if (item.compile && !compiles.some((x) => x.name === item.compile)) out.push(`${where}: no compile case ${item.compile}`); diff --git a/scripts/lib/cli-cases.mjs b/scripts/lib/cli-cases.mjs index 36b5244a3..a6792ab94 100644 --- a/scripts/lib/cli-cases.mjs +++ b/scripts/lib/cli-cases.mjs @@ -417,6 +417,7 @@ const HELP_TOOLS = { "scripts/check_axe_patch_equiv.mjs": null, "scripts/check_attribute_sweep.mjs": null, "scripts/check_book_coverage.mjs": null, + "scripts/check_callout_evidence.mjs": null, "scripts/check_ci_workflows.mjs": null, "scripts/check_errors.mjs": null, "scripts/check_cli.mjs": null, @@ -448,6 +449,7 @@ const HELP_TOOLS = { "scripts/pick_a11y_sample.mjs": null, "scripts/probe_build_twice.mjs": null, "scripts/probe_theme_semicolon.mjs": null, + "scripts/recheck_callouts.mjs": null, "scripts/shot_review.mjs": null, "scripts/survey_tooling.mjs": null, "scripts/sweep_a11y.mjs": null, @@ -505,6 +507,7 @@ const REFUSALS = { "scripts/check_axe_patch_equiv.mjs": ["patch"], "scripts/check_attribute_sweep.mjs": [null], "scripts/check_book_coverage.mjs": [null], + "scripts/check_callout_evidence.mjs": [null], "scripts/check_ci_workflows.mjs": [null], "scripts/check_cli.mjs": [null], "scripts/check_code_regions.mjs": [null], @@ -535,6 +538,7 @@ const REFUSALS = { "scripts/pick_a11y_sample.mjs": ["sweep"], "scripts/probe_build_twice.mjs": ["ide"], "scripts/probe_theme_semicolon.mjs": ["ide"], + "scripts/recheck_callouts.mjs": ["only"], "scripts/survey_tooling.mjs": ["root"], "scripts/sweep_a11y.mjs": ["out"], "scripts/sweep_attributes.mjs": ["out"], @@ -1040,6 +1044,11 @@ bad("eval/site_search.mjs", ["--n=1.5", "term"], NOT_COUNT("--n", 1.5) + "\n"); bad("eval/site_search.mjs", ["--composition", "term"], "--composition takes no search terms\n"); bad("eval/nav_hops.mjs", ["("], REGEX_REASON("", "(")); bad("eval/nav_hops.mjs", ["Reference", "[a-"], REGEX_REASON("", "[a-")); +bad( + "scripts/recheck_callouts.mjs", + ["--kinds", "bug,lanes"], + thenUsage("--kinds: lanes is not one of bug, lane, sample, errors, llvm, package", "scripts/recheck_callouts.mjs"), +); // build_corpus empties its --dest before it writes, so it refuses one that is or // contains the repository root, the folder it runs from or --repo, checked in diff --git a/scripts/lib/example-batches.mjs b/scripts/lib/example-batches.mjs index 85acd2883..3a09a955e 100644 --- a/scripts/lib/example-batches.mjs +++ b/scripts/lib/example-batches.mjs @@ -21,6 +21,7 @@ import { CONCAT_KEY, HIDDEN_MARKER, MARKER, + PROBE_MARKER, RUN_MARKER, SLOTS, classify, @@ -39,7 +40,9 @@ import { judgeOutput, parseRun, partitionRun, + probeRefusal, runRefusal, + splitArches, } from "./example-run.mjs"; /** check_examples.mjs's defaults for `--jobs` and `--batch`, which the probes batch with. */ @@ -1179,6 +1182,18 @@ const INFO_PROBES = [ (p) => p.flags.has(MARKER) && p.flags.has(HIDDEN_MARKER), ], ["another language is untouched", "js", (p) => p.lang === "js"], + [ + `${PROBE_MARKER} implies ${MARKER}, and is not run unless it says so`, + `tb ${PROBE_MARKER} id=p`, + (p) => p.flags.has(MARKER) && p.flags.has(PROBE_MARKER) && !p.flags.has(RUN_MARKER) && !p.bad.length, + ], + [ + "two architectures", + `tb ${RUN_MARKER} arch=win32,win64`, + (p) => p.keys.get("arch") === "win32,win64" && !p.bad.length, + ], + ["an unknown architecture is refused", `tb ${RUN_MARKER} arch=win65`, (p) => p.bad.length === 1], + ["an architecture named twice is refused", `tb ${RUN_MARKER} arch=win64,win64`, (p) => p.bad.length === 1], ]; /** @@ -1947,6 +1962,18 @@ export async function runProbes(say) { ], ["a plain statement sample", rfence('Debug.Print InStr(1, "abc", "b") \' 2\n'), false], ["a fence that is not run", rfence('MsgBox "hi"\n', { flags: [MARKER] }), false], + ["an arch= on a run sample", rfence("Debug.Print 1\n", { keys: [["arch", "win32,win64"]] }), false], + ["an arch= on a sample that is not run", rfence("Dim x\n", { flags: [MARKER], keys: [["arch", "win64"]] }), true], + [ + "an arch= on a group's member", + rfence("Debug.Print 1\n", { + keys: [ + ["arch", "win64"], + ["projname", "g"], + ], + }), + true, + ], ]) { expect( !!runRefusal(f) === refused, @@ -1958,6 +1985,35 @@ export async function runProbes(say) { "check_run runs statement samples (slot=sub) only", "run refusal: a non-sub slot does not say what check_run runs", ); + // A probe: a sample of its own with an id, never hidden context too. + for (const [name, f, refused] of [ + ["a probe with an id", rfence("Dim x\n", { flags: [MARKER, PROBE_MARKER], keys: [["id", "p"]] }), false], + ["a probe with no id", rfence("Dim x\n", { flags: [MARKER, PROBE_MARKER] }), true], + ["a hidden probe", rfence("Dim x\n", { flags: [MARKER, PROBE_MARKER, HIDDEN_MARKER], keys: [["id", "p"]] }), true], + ["a sample that is no probe", rfence("Dim x\n", { flags: [MARKER] }), false], + ]) { + expect( + !!probeRefusal(f) === refused, + `probe refusal: ${name} -> ${probeRefusal(f)?.message ?? "accepted"}, want ${refused ? "refused" : "accepted"}`, + ); + } + // The run list by architecture: a win64 copy of each sample that asks, with + // its page's hidden context; one that asks for win64 alone leaves win32. + { + const both = rfence("Debug.Print 1\n", { id: "A.md#1", keys: [["arch", "win32,win64"]] }); + const only64 = { ...rfence("Debug.Print 2\n", { id: "A.md#2", keys: [["arch", "win64"]] }) }; + const plain = { ...rfence("Debug.Print 3\n", { id: "B.md#1" }), rel: "B.md" }; + const ctxA = { ...rfence("Dim c\n", { id: "A.md#3", flags: [MARKER, HIDDEN_MARKER] }), rel: "R.md" }; + const ctxB = { ...rfence("Dim d\n", { id: "B.md#2", flags: [MARKER, HIDDEN_MARKER] }), rel: "B.md" }; + const { win32, win64 } = splitArches([both, only64, plain, ctxA, ctxB]); + const ids = (list) => list.map((f) => f.id).join(","); + expect( + ids(win32) === "A.md#1,B.md#1,A.md#3,B.md#2" && + ids(win64) === "A.md#1@win64,A.md#2@win64,A.md#3@win64" && + win64.every((f) => f.arch === "win64" && f.baseId === f.id.replace("@win64", "")), + `architectures: win32 ${ids(win32)}, win64 ${ids(win64)}`, + ); + } // What a fence says it prints, each line with its place on the page: the // fence opens at page line 10, so its first line is page line 11. diff --git a/scripts/lib/example-run.mjs b/scripts/lib/example-run.mjs index 6b15f441f..65d251bea 100644 --- a/scripts/lib/example-run.mjs +++ b/scripts/lib/example-run.mjs @@ -12,7 +12,16 @@ // // The probes for all of it are in example-batches.mjs's `runProbes`. -import { HIDDEN_MARKER, RUN_MARKER, partOf } from "./tb-fences.mjs"; +import { + ARCH_KEY, + ARCHES, + CONCAT_KEY, + HIDDEN_MARKER, + PROBE_MARKER, + RUN_MARKER, + archesOf, + partOf, +} from "./tb-fences.mjs"; import { logicalLines } from "./twin-api.mjs"; /** The generated module, Sub and file that call every run sample. */ @@ -61,6 +70,19 @@ export function runFenceProblem(content) { * null when it may (or does not ask to). The fence has its slot already. */ export function runRefusal(fence) { + if (fence.keys?.has(ARCH_KEY)) { + if (!fence.flags?.has(RUN_MARKER)) { + return { + message: `${ARCH_KEY}= names the architectures a \`${RUN_MARKER}\` sample is run in`, + detail: "a sample that is only compiled is compiled the same for either target", + }; + } + if (fence.keys.has("projname") || fence.keys.has(CONCAT_KEY)) { + return { + message: `${ARCH_KEY}= is for a sample that is run on its own, not a member of a projname= or ${CONCAT_KEY}= group`, + }; + } + } if (!fence.flags?.has(RUN_MARKER)) return null; if (fence.flags.has(HIDDEN_MARKER)) { return { @@ -84,6 +106,52 @@ export function runRefusal(fence) { return problem ? { message: `a \`${RUN_MARKER}\` sample ${problem}` } : null; } +/** + * Whether a fence marked `probe` is one, as a finding's message and detail, or + * null. A probe is a sample of its own that the reader never sees, so it is not + * `hidden` context too, and it has the `id=` a marker names it by. + */ +export function probeRefusal(fence) { + if (!fence.flags?.has(PROBE_MARKER)) return null; + if (fence.flags.has(HIDDEN_MARKER)) { + return { + message: `\`${PROBE_MARKER}\` and \`${HIDDEN_MARKER}\` contradict each other`, + detail: "a probe is a sample of its own; hidden context only travels with the page's samples", + }; + } + if (!fence.keys?.has("id")) { + return { + message: `a \`${PROBE_MARKER}\` has an id=`, + detail: "the marker beside the statement it checks names it: ", + }; + } + return null; +} + +/** + * The run list split by architecture: what is run in win32, and a copy of each + * run sample that asks for win64 (`arch=`), `@win64`, a sample of its own + * that is batched apart and built into projects of that target. The hidden + * context and the staged files of those samples' pages are copied with them. + * A run sample that names win64 alone is left out of the win32 list; the rest + * of the list, a group's members that are not run included, stays in it. + * + * @returns {{win32: object[], win64: object[]}} each copy carries `arch` and + * `baseId`, the id of the fence it copies + */ +export function splitArches(list) { + const [win32, win64] = ARCHES; + const asks = (f) => isRunFence(f) && archesOf(f).includes(win64); + const pages = new Set(list.filter(asks).map((f) => f.rel)); + const travels = (f) => f.isResource || f.flags?.has(HIDDEN_MARKER); + return { + win32: list.filter((f) => !isRunFence(f) || archesOf(f).includes(win32)), + win64: list + .filter((f) => asks(f) || (travels(f) && pages.has(f.rel))) + .map((f) => ({ ...f, id: `${f.id}@${win64}`, baseId: f.id, arch: win64 })), + }; +} + /** * Split the selection into what is built and run, and the rest. * diff --git a/scripts/lib/lane-report.mjs b/scripts/lib/lane-report.mjs new file mode 100644 index 000000000..7877b9125 --- /dev/null +++ b/scripts/lib/lane-report.mjs @@ -0,0 +1,48 @@ +// What a lane runner's report says (scripts/lib/lane-runner.mjs, behind ide_test.mjs and +// addin_test.mjs): each lane's verdict, and each test's mark within the lane. A lane's report is +// the block from its "--- (): in s" line to the next lane's, and +// node:test's spec reporter marks a test that passed and one that failed in it. +// +// scripts/bug_repro.mjs judges a lane reproducer by the marks of the tests it names, and +// scripts/recheck_callouts.mjs a `lane` item by its lane's verdict, both from this reading. + +/** The spec reporter's marks for a test that passed and one that failed. */ +export const PASSED = "\u{2714}"; +export const FAILED = "\u{2716}"; + +// A lane's first line. A lane that got no slot has no desktop in it. +const HEAD_RE = /^--- (\S+)(?: \([^)]*\))?: (passed|FAILED|TIMED OUT)\b/; + +/** + * Each lane's report: its verdict ("passed", "FAILED" or "TIMED OUT") and its lines, the first + * line included. + * + * @param {string} text the runner's whole output + * @returns {Map} + */ +export function laneBlocks(text) { + const lines = text.split(/\r?\n/); + const blocks = new Map(); + let current = null; + for (const line of lines) { + const m = HEAD_RE.exec(line); + if (m) { + current = { verdict: m[2], lines: [line] }; + blocks.set(m[1], current); + } else if (line.startsWith("--- ")) current = null; + else if (current) current.lines.push(line); + } + return blocks; +} + +/** + * A test's mark in a lane's lines: "failed" when any line marks it failed, "passed" when one + * marks it passed, null when none names it. + */ +export function testMark(lines, test) { + const found = lines + .map((l) => l.trimStart()) + .filter((l) => l.startsWith(`${PASSED} ${test} (`) || l.startsWith(`${FAILED} ${test} (`)); + if (!found.length) return null; + return found.some((l) => l.startsWith(FAILED)) ? "failed" : "passed"; +} diff --git a/scripts/lib/package-test-cases.mjs b/scripts/lib/package-test-cases.mjs index df051635f..87a46ddb7 100644 --- a/scripts/lib/package-test-cases.mjs +++ b/scripts/lib/package-test-cases.mjs @@ -1,7 +1,8 @@ // The package test projects' cases (test/package-tests//), as scripts/package_tests.mjs // runs them and scripts/check_package_tests.mjs checks them: the RunAll module generated from a -// project's [TestCase] Subs, what expect.json says a case does, how a result is judged against -// it, and the markers that tie each callout of a section's pages to the cases behind it. +// project's [TestCase] Subs, what expect.json says a case does, and how a result is judged +// against it. The markers that tie each callout of a section's pages to the cases behind it are +// read by scripts/lib/test-markers.mjs. // lib/package-tests.mjs puts a project together; test/package-tests/README.md describes it all. import { EXPECT_FILE, RUNALL_FILE } from "../../lib/package-tests.mjs"; @@ -153,82 +154,6 @@ export function archesOf(expect) { return Array.isArray(expect?.arches) ? ARCHES.filter((a) => expect.arches.includes(a)) : ARCHES; } -// ---------------------------------------------------------------- markers - -// A callout: a blockquote line `> [!NOTE]` or `> [!WARNING]`, indented or not. -const CALLOUT_RE = /^[ \t]*>[ \t]*\[!(NOTE|WARNING)\]/; -// A marker, ``, on a line of its own; and the end of a region. -const MARKER_RE = /^[ \t]*[ \t]*$/; -const END_RE = /^[ \t]*[ \t]*$/; -const FENCE_RE = /^[ \t]*(`{3,}|~{3,})/; - -/** - * The markers of a page, outside code fences. A marker on the line directly above a callout - * marks that callout. Any other marker opens a region, which `` closes: the - * paragraphs, lists, tables and samples between them state what the region's cases check. - * Regions do not nest, and a callout inside one is marked by it. - * - * @returns {{callouts: {line: number, kind: string, marked: boolean}[], - * markers: {line: number, items: object[]}[], problems: string[]}} `markers` holds - * every marker, a callout's and a region's alike; `items` parses one: { case: "Fixture.Sub" }, - * { compile: "Name" }, { probe: "id" } or { none: "reason" }, and { bad: "text" } for one it - * cannot read. `problems` names a region not closed, a region opened inside another and a - * close with no region. - */ -export function markersOf(text) { - const lines = text.split(/\r?\n/); - const callouts = []; - const markers = []; - const problems = []; - let fence = null; - let open = null; // the line of the region's marker - for (let i = 0; i < lines.length; i++) { - const f = FENCE_RE.exec(lines[i]); - if (f) { - if (!fence) fence = f[1][0]; - else if (f[1][0] === fence) fence = null; - continue; - } - if (fence) continue; - const m = MARKER_RE.exec(lines[i]); - if (m) { - markers.push({ line: i + 1, items: parseMarker(m[1]) }); - if (CALLOUT_RE.test(lines[i + 1] ?? "")) continue; // a callout's own marker - if (open !== null) problems.push(`line ${i + 1}: a region opens inside the region of line ${open}`); - open = i + 1; - continue; - } - if (END_RE.test(lines[i])) { - if (open === null) problems.push(`line ${i + 1}: closes no region`); - open = null; - continue; - } - const c = CALLOUT_RE.exec(lines[i]); - if (c) callouts.push({ line: i + 1, kind: c[1], marked: open !== null || MARKER_RE.test(lines[i - 1] ?? "") }); - } - if (open !== null) problems.push(`line ${open}: the region is not closed with `); - return { callouts, markers, problems }; -} - -/** The items of a marker: Fixture.Sub, compile , probe , none -- . */ -export function parseMarker(text) { - const none = /^none[ \t]*(?:--|:)?[ \t]*(.*)$/.exec(text); - if (none) return [none[1].trim() ? { none: none[1].trim() } : { bad: "none needs a reason" }]; - return text - .split(",") - .map((s) => s.trim()) - .filter(Boolean) - .map((s) => { - let m = /^(\w+)\.(\w+)$/.exec(s); - if (m) return { case: s }; - m = /^compile[ \t]+(\w+)$/.exec(s); - if (m) return { compile: m[1] }; - m = /^probe[ \t]+([\w,.-]+)$/.exec(s); - if (m) return { probe: m[1] }; - return { bad: s }; - }); -} - /** * The [TestCase] Subs whose first statement is not Expect.Begin or whose last is not * Expect.Done. diff --git a/scripts/lib/recheck.mjs b/scripts/lib/recheck.mjs new file mode 100644 index 000000000..7c3170fb5 --- /dev/null +++ b/scripts/lib/recheck.mjs @@ -0,0 +1,108 @@ +// What scripts/recheck_callouts.mjs reads and decides, apart from what it runs: the check each +// marker item stands for, the reading of each tool's report into a status per check, a callout's +// verdict from the statuses of its items, and the relabelling of a callout's first line. None of +// it needs an IDE; scripts/check_callout_evidence.mjs probes it. +// +// A check's status is one of: +// holds the evidence still says what the callout says: the bug reproduces, the lane +// passes, the sample prints what the page says, the data agrees, the case is as +// documented +// changed it no longer does: the callout is to be read again, and fixed or removed +// hand nothing runs it (`none`, a manual reproducer, a package's probe/ scenario) +// error it could not be run, or its report could not be read +// skipped its kind was not asked for (--kinds) + +import { kindOf } from "./test-markers.mjs"; + +/** A check's key: one per thing run, which several callouts may share. */ +export function checkKey(item, pkg) { + const kind = kindOf(item); + if (kind === "case" || kind === "compile") return `package ${pkg}`; + if (kind === "probe") return `probe ${pkg}:${item.probe}`; + if (kind === "none") return "none"; + return `${kind} ${item[kind]}`; +} + +/** The kind of a check's key, as --kinds names it. */ +export const keyKind = (key) => key.split(" ")[0]; + +/** The kinds a run can be limited to; `none` and a package's probe are never run. */ +export const RUN_KINDS = ["bug", "lane", "sample", "errors", "llvm", "package"]; + +// `bug_repro.mjs verify`'s line for a reproducer. +const VERIFY_RE = + /^([a-z0-9-]+)(?: \(filed[^)]*\))?: (reproduces|NO LONGER REPRODUCES|manual|harness failed)(?: -- (.*))?$/; +const VERIFY_STATUS = { + reproduces: "holds", + "NO LONGER REPRODUCES": "changed", + manual: "hand", + "harness failed": "error", +}; + +/** + * The status of each reproducer `bug_repro.mjs verify` reported. + * + * @returns {Map} + */ +export function readVerify(text) { + const out = new Map(); + for (const line of text.split(/\r?\n/)) { + const m = VERIFY_RE.exec(line.trim()); + if (m) out.set(m[1], { status: VERIFY_STATUS[m[2]], detail: m[2] + (m[3] ? ` -- ${m[3]}` : "") }); + } + return out; +} + +/** + * The status of a sample from `check_examples.mjs --json`'s report: it holds when each of its + * runs passed with no finding, its win64 copy's included when it asks for one. + * + * @param {{findings: object[], ids?: {passed: string[], unrun: string[]}}} report + * @param {string[]} runIds the ids the sample is judged under: its own, `@win64`, or both + */ +export function sampleStatus(report, runIds) { + if (!report?.ids) return { status: "error", detail: "check_examples wrote no ids" }; + const found = report.findings.filter((f) => runIds.includes(f.id)); + if (found.length) return { status: "changed", detail: found.map((f) => `${f.id}: ${f.message}`).join("; ") }; + const missing = runIds.filter((r) => !report.ids.passed.includes(r)); + if (missing.length) return { status: "error", detail: `not built: ${missing.join(", ")}` }; + const unrun = runIds.filter((r) => report.ids.unrun.includes(r)); + if (unrun.length) return { status: "error", detail: `built and not run: ${unrun.join(", ")}` }; + return { status: "holds", detail: runIds.length > 1 ? `${runIds.length} runs` : "" }; +} + +/** + * The status of the cases of one package from `package_tests.mjs --json`'s report: a case holds + * when it is as documented on every architecture it ran on. + * + * @returns {(name: string) => {status: string, detail: string}} + */ +export function packageCases(report, pkg) { + const entry = report?.packages?.find((p) => p.package === pkg); + return (name) => { + if (!entry) return { status: "error", detail: `package_tests reported nothing for ${pkg}` }; + const runs = entry.cases.filter((c) => c.name === name); + if (!runs.length) return { status: "error", detail: `package_tests reported nothing for ${name}` }; + const off = runs.filter((c) => c.verdict !== "documented"); + return off.length + ? { status: "changed", detail: off.map((c) => `${c.arch} ${c.verdict}`).join(", ") } + : { status: "holds", detail: "" }; + }; +} + +/** A callout's verdict from the statuses of its items: the worst of them. */ +export function verdictOf(statuses) { + for (const s of ["changed", "error", "skipped", "hand"]) if (statuses.includes(s)) return s; + return statuses.length ? "holds" : "error"; +} + +// A callout's first line with a BETA label, not `since BETA`. +const LABEL_RE = /^([ \t]*>[ \t]*\[!(?:NOTE|TIP|IMPORTANT|WARNING|CAUTION)\][^\n]*?)(?. The rest of the label, a package version, stays. + */ +export function relabelLine(line, build) { + return LABEL_RE.test(line) ? line.replace(LABEL_RE, `$1BETA ${build}`) : null; +} diff --git a/scripts/lib/tb-fences.mjs b/scripts/lib/tb-fences.mjs index eb5c8178d..643a92df5 100644 --- a/scripts/lib/tb-fences.mjs +++ b/scripts/lib/tb-fences.mjs @@ -95,8 +95,28 @@ export const RUN_MARKER = "check_run"; */ export const HIDDEN_MARKER = "hidden"; +/** + * A sample the reader never sees: builder/render.mjs emits nothing for it, as it + * does for `hidden`, but it is a sample of its own, built (and with `check_run` + * run and judged) as any other is, where a hidden fence is context that only + * travels with the page's samples. + * + * It checks a statement the page makes in prose or in a callout, whose code + * would tell the reader nothing the statement does not: that a Declare of + * msvbvm60's GetMem4 works in a 64-bit build (`check_run arch=win32,win64`), or + * that a conversion draws no warning (`[EnforceErrors(TB0021)]` in a sample + * that must compile), say. It has an `id=`, and a marker names it beside the + * statement it checks (``, scripts/lib/test-markers.mjs), + * which check_callout_evidence.mjs requires: a check nobody can see, tied to + * nothing a reader can see, would be a check of nothing. + */ +export const PROBE_MARKER = "probe"; + +/** The architectures a run sample may ask for with `arch=`; the first is the default. */ +export const ARCHES = ["win32", "win64"]; + /** Flags with no value. */ -const FLAGS = new Set([MARKER, RUN_MARKER, HIDDEN_MARKER]); +const FLAGS = new Set([MARKER, RUN_MARKER, HIDDEN_MARKER, PROBE_MARKER]); /** * Keys that take a value. @@ -116,8 +136,22 @@ const KEYS = new Set([ "resource", "inert", "concat_group", + "arch", ]); +/** + * `arch=win32,win64` builds and runs a run sample for each architecture it + * names, in a project of that target; without it a sample is built for win32 + * only, as a new project is. A statement that a call works in a 64-bit build + * is checked by a sample with `arch=win32,win64`, or `arch=win64` alone when + * the 32-bit half says nothing. Only a run sample may ask: a sample that is + * only compiled is compiled the same for either target. + */ +export const ARCH_KEY = "arch"; + +/** The architectures a fence is built for: its `arch=`, else the default. */ +export const archesOf = (fence) => fence.keys?.get(ARCH_KEY)?.split(",") ?? [ARCHES[0]]; + /** * `concat_group=` joins fences into ONE compilation unit, in page order, * before anything is classified. @@ -290,12 +324,19 @@ export function parseInfo(info) { bad.push(part); continue; } + if (key === ARCH_KEY) { + const arches = value.split(","); + if (!arches.every((a) => ARCHES.includes(a)) || new Set(arches).size !== arches.length) { + bad.push(part); + continue; + } + } keys.set(key, value); } - // These imply a build: a hidden fence that is not compiled is text nobody can - // read and nothing checks, and half a joined construct is not a unit anybody - // could have marked on its own. - if (flags.has(RUN_MARKER) || flags.has(HIDDEN_MARKER)) flags.add(MARKER); + // These imply a build: a hidden fence or a probe that is not compiled is text + // nobody can read and nothing checks, and half a joined construct is not a + // unit anybody could have marked on its own. + if (flags.has(RUN_MARKER) || flags.has(HIDDEN_MARKER) || flags.has(PROBE_MARKER)) flags.add(MARKER); if (keys.has(CONCAT_KEY)) flags.add(MARKER); return { lang, flags, keys, bad }; } diff --git a/scripts/lib/test-markers.mjs b/scripts/lib/test-markers.mjs new file mode 100644 index 000000000..879d5be67 --- /dev/null +++ b/scripts/lib/test-markers.mjs @@ -0,0 +1,256 @@ +// The markers that tie a page's statements to the programs that check them, one grammar for +// every page under docs/: +// +// +// > [!WARNING] BETA 1034 +// +// A marker on the line directly above a callout marks that callout. Anywhere else it opens a +// region, which `` closes: the prose between them is what its items check. Regions +// do not nest, and a callout inside one is marked by it. Markers inside a fence or an indented +// code block are text, not markers. +// +// The items, separated by commas: +// +// Fixture.Sub a [TestCase] of the section's package test project +// compile a compile case of that project +// probe a scenario of that project's probe/ harness +// bug a reproducer, bugs// or bugs/filed//, which +// `bug_repro.mjs verify` runs; it holds while the bug reproduces +// lane : a lane of test/ide/lanes.mjs (ide) or test/addin/lanes.mjs (addin); +// it holds while every test of the lane passes +// sample a tb fence under docs/ with `id=`, built by examples.bat: a +// `check_run` sample, a `probe` the reader never sees, or a `check_build` +// one; it holds while examples.bat finds nothing wrong with it +// errors the run-time error data errors/.yml, which `errors.mjs run` checks +// llvm a mode of the LLVM claims probe, test/llvm/run.mjs +// none -- nothing a program can check: a person checks it again +// +// The first three belong to a contributed package's section, which has a test project +// (scripts/check_package_tests.mjs checks them); the rest may be used on any page +// (scripts/check_callout_evidence.mjs checks them). scripts/recheck_callouts.mjs runs what the +// markers of the callouts labelled with an older twinBASIC build name, and moves each label to +// the new build or lists the callout for a person. + +import { existsSync, readdirSync, readFileSync } from "node:fs"; +import path from "node:path"; +import { pathToFileURL } from "node:url"; +import { parseCalloutLabel } from "../../lib/callout-labels.mjs"; +import { blockRegions } from "../../lib/markdown.mjs"; +import { REPO_ROOT } from "../../lib/repo-paths.mjs"; + +// A callout's first line, indented or not; its type and its label. +const CALLOUT_RE = /^[ \t]*>[ \t]*\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]([^\n]*)$/; +// A marker, ``, on a line of its own; and the end of a region. +const MARKER_RE = /^[ \t]*[ \t]*$/; +const END_RE = /^[ \t]*[ \t]*$/; +// The run-time error data's section, which scripts/errors.mjs write generates with its callouts: +// its opening comment names the data file, and the data is the callouts' evidence. +const ERRORS_OPEN_RE = /^[ \t]*$/; +const ERRORS_CLOSE_RE = /^[ \t]*$/; + +/** The item kinds that name a case of a contributed package's test project. */ +export const PACKAGE_KINDS = new Set(["case", "compile", "probe"]); + +/** The lane suites: the runner a `lane` item's lanes run in, and the file that lists them. */ +export const SUITES = { + ide: { tool: "scripts/ide_test.mjs", lanes: "test/ide/lanes.mjs" }, + addin: { tool: "scripts/addin_test.mjs", lanes: "test/addin/lanes.mjs" }, +}; + +/** + * The markers and callouts of a page, outside fences and indented code. + * + * @param {string} text + * @returns {{callouts: {line: number, kind: string, label: object, marked: boolean, + * evidence: object[] | null, markerLine: number | null, errors: string | null}[], + * markers: {line: number, items: object[], region: boolean}[], problems: string[]}} + * `label` is parseCalloutLabel's reading of the rest of the callout's first line. `evidence` + * holds the items of its marker or of the region it is in, and `markerLine` that marker's line; + * `errors` names the data file of the generated errors section it is in (`VBA.Strings` for + * errors/VBA.Strings.yml), whose data is its evidence. `markers` holds every marker, a + * callout's and a region's alike; `items` parses one (parseMarker). `problems` names a region + * not closed, a region opened inside another and a close with no region. + */ +export function markersOf(text) { + const lines = text.split(/\r\n?|\n/); + const inCode = []; + for (const r of blockRegions(text)) { + if (r.type === "html_block") continue; + for (let i = r.start; i < r.end; i++) inCode[i] = true; + } + const callouts = []; + const markers = []; + const problems = []; + let open = null; // the region's marker + let errors = null; // the data file of the errors section the line is in + for (let i = 0; i < lines.length; i++) { + if (inCode[i]) continue; + const e = ERRORS_OPEN_RE.exec(lines[i]); + if (e) { + errors = e[1]; + continue; + } + if (ERRORS_CLOSE_RE.test(lines[i])) { + errors = null; + continue; + } + const m = MARKER_RE.exec(lines[i]); + if (m) { + const own = CALLOUT_RE.test(lines[i + 1] ?? "") && !inCode[i + 1]; + const marker = { line: i + 1, items: parseMarker(m[1]), region: !own }; + markers.push(marker); + if (own) continue; // a callout's own marker + if (open !== null) problems.push(`line ${i + 1}: a region opens inside the region of line ${open.line}`); + open = marker; + continue; + } + if (END_RE.test(lines[i])) { + if (open === null) problems.push(`line ${i + 1}: closes no region`); + open = null; + continue; + } + const c = CALLOUT_RE.exec(lines[i]); + if (!c) continue; + const above = MARKER_RE.test(lines[i - 1] ?? "") && !inCode[i - 1] ? markers.at(-1) : null; + const by = above ?? open; + callouts.push({ + line: i + 1, + kind: c[1], + label: parseCalloutLabel(c[2]), + marked: by !== null, + evidence: by?.items ?? null, + markerLine: by?.line ?? null, + errors, + }); + } + if (open !== null) problems.push(`line ${open.line}: the region is not closed with `); + return { callouts, markers, problems }; +} + +/** + * The items of a marker, each an object with one key: { case: "Fixture.Sub" }, { compile }, + * { probe }, { bug }, { lane: "ide:" }, { sample }, { errors }, { llvm } or { none }, and + * { bad: "text" } for one the grammar does not know. + */ +export function parseMarker(text) { + const none = /^none[ \t]*(?:--|:)?[ \t]*(.*)$/.exec(text); + if (none) return [none[1].trim() ? { none: none[1].trim() } : { bad: "none needs a reason" }]; + return text + .split(",") + .map((s) => s.trim()) + .filter(Boolean) + .map((s) => { + if (/^\w+\.\w+$/.test(s)) return { case: s }; + const m = /^(compile|probe|bug|lane|sample|errors|llvm)[ \t]+(\S+)$/.exec(s); + if (!m) return { bad: s }; + const [, kind, value] = m; + const shape = { + compile: /^\w+$/, + probe: /^[\w,.-]+$/, + bug: /^[a-z0-9]+(?:-[a-z0-9]+)*$/, + lane: /^(?:ide|addin):[\w.-]+$/, + sample: /^[\w.-]+$/, + errors: /^[\w.-]+$/, + llvm: /^[\w-]+$/, + }[kind]; + return shape.test(value) ? { [kind]: value } : { bad: s }; + }); +} + +/** An item's kind, the one key of its object. */ +export const kindOf = (item) => Object.keys(item)[0]; + +/** An item as a marker writes it. */ +export function itemText(item) { + const kind = kindOf(item); + if (kind === "case") return item.case; + if (kind === "none") return `none -- ${item.none}`; + return `${kind} ${item[kind]}`; +} + +// ---------------------------------------------------------------- what exists + +/** + * What the items outside a package's test project can name, read once from the repository: the + * reproducers, the lanes of each suite, the explicit fence ids of docs/ with the fences, the error + * data files and the LLVM probe's modes. + * + * @param {{fences: object[]}} o `fences`: tb-fences' collectFences(docs), whose explicit ids + * (`id=` in the info string) a `sample` item may name + */ +export async function evidenceIndex({ fences, root = REPO_ROOT }) { + const dirs = (dir) => + existsSync(dir) + ? readdirSync(dir, { withFileTypes: true }) + .filter((d) => d.isDirectory()) + .map((d) => d.name) + : []; + const bugs = new Map(); + for (const slug of dirs(path.join(root, "bugs"))) { + if (existsSync(path.join(root, "bugs", slug, "repro.json"))) bugs.set(slug, { filed: false }); + } + for (const slug of dirs(path.join(root, "bugs", "filed"))) { + if (existsSync(path.join(root, "bugs", "filed", slug, "repro.json"))) bugs.set(slug, { filed: true }); + } + const lanes = new Set(); + for (const [suite, { lanes: file }] of Object.entries(SUITES)) { + const list = (await import(pathToFileURL(path.join(root, file)).href)).default; + for (const l of list) lanes.add(`${suite}:${l.name ?? path.basename(l.file, ".test.mjs")}`); + } + const samples = new Map(); + for (const f of fences) { + if (!f.keys?.has("id")) continue; + const id = f.keys.get("id"); + if (!samples.has(id)) samples.set(id, []); + samples.get(id).push(f); + } + const errorsDir = path.join(root, "errors"); + const errors = new Set( + existsSync(errorsDir) + ? readdirSync(errorsDir) + .filter((f) => f.endsWith(".yml") && f !== "messages.yml") + .map((f) => f.slice(0, -4)) + : [], + ); + const llvmFile = path.join(root, "test", "llvm", "expect.json"); + const llvm = new Set(existsSync(llvmFile) ? Object.keys(JSON.parse(readFileSync(llvmFile, "utf8")).modes ?? {}) : []); + return { bugs, lanes, samples, errors, llvm }; +} + +/** + * What is wrong with an item outside a package's test project, or null when it names something + * that exists. A package item (case, compile, probe) is the package gate's to check: null here. + * + * @param {object} item + * @param {object} index evidenceIndex's + */ +export function itemProblem(item, index) { + const kind = kindOf(item); + const value = item[kind]; + switch (kind) { + case "bad": + return `"${value}" is not an item: Fixture.Sub, compile , probe , bug , lane :, sample , errors , llvm or none -- `; + case "bug": + return index.bugs.has(value) ? null : `no reproducer bugs/${value}/ or bugs/filed/${value}/ with a repro.json`; + case "lane": + return index.lanes.has(value) ? null : `no lane ${value} in ${SUITES[value.split(":")[0]].lanes}`; + case "sample": { + const found = index.samples.get(value) ?? []; + if (!found.length) return `no tb fence with id=${value} under docs/`; + if (found.length > 1) + return `id=${value} is on ${found.length} fences: ${found.map((f) => `${f.rel} line ${f.line}`).join(", ")}`; + const [f] = found; + if (!f.flags.has("check_build") || f.flags.has("hidden")) + return `the fence with id=${value} (${f.rel} line ${f.line}) is not built as a sample of its own: mark it check_build, check_run or probe`; + return null; + } + case "errors": + return index.errors.has(value) ? null : `no error data errors/${value}.yml`; + case "llvm": + return index.llvm.has(value) + ? null + : `no mode ${value} in test/llvm/expect.json (it has ${[...index.llvm].join(", ")})`; + default: + return null; + } +} diff --git a/scripts/recheck_callouts.mjs b/scripts/recheck_callouts.mjs new file mode 100644 index 000000000..7e6765350 --- /dev/null +++ b/scripts/recheck_callouts.mjs @@ -0,0 +1,437 @@ +// Checks again, on a new twinBASIC build, every callout labelled with an older one: runs what each +// callout's marker names (scripts/lib/test-markers.mjs), moves the label of each callout whose +// evidence all holds to the new build, and lists the rest for a person. +// +// node scripts/recheck_callouts.mjs --plan # what it would run, and nothing else +// node scripts/recheck_callouts.mjs # run it all, relabel, list the rest +// +// The build is the IDE's: --ide, else $TB_IDE, else the newest twinBASIC_IDE_BETA_ on the +// Desktop. A callout is selected when its label is BETA with below that build; a callout +// of a generated Common Errors section is selected through its data file, errors/.yml, +// whose `beta:` is moved and whose sections `errors.mjs write` writes again when the data holds. +// +// Each check runs once, however many callouts name it, through the tool that owns it, one after +// another, since runs at once give false failures under load: +// +// lane ide_test.mjs or addin_test.mjs, once for each suite, with every lane named; a lane +// reproducer under bugs/ is judged here from the same report, by the tests it names +// bug bug_repro.mjs verify, for the other reproducers +// sample check_examples.mjs --json, over the pages that hold the samples +// errors errors.mjs run, for twinBASIC 32-bit and 64-bit (what VB6 does does not change) +// llvm test/llvm/run.mjs --only +// package package_tests.mjs --json, for the packages whose cases are named +// +// Every tool's output is kept under %TEMP%\recheck-callouts\\, and --report writes what was +// decided, which --from reads back to run again only what did not hold: a surprise is run again +// alone before it is believed. + +import { spawn } from "node:child_process"; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { exitOnCrash, parseCli, printHelpAndExit, regexOption, withUsageError, CliError } from "../lib/cli.mjs"; +import { markdownFiles } from "../lib/markdown-files.mjs"; +import { DOCS_DIR, REPO_ROOT } from "../lib/repo-paths.mjs"; +import { laneBlocks, testMark, FAILED } from "./lib/lane-report.mjs"; +import { + checkKey, + keyKind, + packageCases, + readVerify, + relabelLine, + RUN_KINDS, + sampleStatus, + verdictOf, +} from "./lib/recheck.mjs"; +import { archesOf, collectFences, RUN_MARKER } from "./lib/tb-fences.mjs"; +import { buildNumber, findIde, unsupportedIde } from "./lib/tb-install.mjs"; +import { itemText, kindOf, markersOf, SUITES } from "./lib/test-markers.mjs"; + +exitOnCrash(); + +const USAGE = `usage: node scripts/recheck_callouts.mjs [--only ] [--kinds ] [--ide ] [--plan] [--dry-run] [--report ] [--from ] [-h, --help] + +Checks again, on a twinBASIC build, every callout labelled BETA with an +older : runs what the marker above each one names, moves +the label of each callout whose evidence all holds to the build, and lists the +rest for a person. A generated Common Errors section's callouts follow their +data file, errors/.yml, whose beta: moves when the data holds. + + --only only the pages whose path under docs/ matches + --kinds run only these kinds of check, comma-separated: ${RUN_KINDS.join(", ")}; + a callout that needs another kind is not relabelled + --ide the build to check on (default: $TB_IDE, else the newest + twinBASIC_IDE_BETA_ on the Desktop) + --plan print the callouts and the checks, and run nothing + --dry-run run the checks, and change no file + --report write what was run and decided, as JSON + --from a report of an earlier run on the same build: a check that + held there is not run again + -h, --help print this text and exit + +Exit codes: + 0 every selected callout held, and was relabelled (or --plan) + 1 a callout is listed for a person: its evidence changed, could not be run, + or is checked by hand + 2 the command line was refused, no IDE was found, or a crash`; + +const usageError = { format: (err) => `${err.message}\n${USAGE}` }; +const { values } = withUsageError( + () => + parseCli(process.argv.slice(2), { + options: { + only: { type: "string" }, + kinds: { type: "string" }, + ide: { type: "string" }, + plan: { type: "boolean" }, + "dry-run": { type: "boolean" }, + report: { type: "string" }, + from: { type: "string" }, + help: { type: "boolean", short: "h" }, + }, + stopAt: ["help"], + }), + usageError, +); +if (values.help) printHelpAndExit(USAGE); +const { only, kinds } = withUsageError(() => { + const kinds = values.kinds ? values.kinds.split(",").map((k) => k.trim()) : RUN_KINDS; + const unknown = kinds.filter((k) => !RUN_KINDS.includes(k)); + if (unknown.length) + throw new CliError("value", `--kinds: ${unknown.join(", ")} is not one of ${RUN_KINDS.join(", ")}`, { + option: "--kinds", + }); + return { only: values.only ? regexOption(values.only, { option: "--only" }) : null, kinds: new Set(kinds) }; +}, usageError); + +const IDE = findIde(values.ide); +const tooOld = IDE && unsupportedIde(IDE); +if (tooOld) { + console.error(tooOld); + process.exit(2); +} +const BUILD = IDE ? buildNumber(IDE) : null; +if (!BUILD) { + console.error( + "no twinBASIC IDE found: pass --ide , set TB_IDE, or unpack a twinBASIC_IDE_BETA_ folder on your Desktop", + ); + process.exit(2); +} + +// ---------------------------------------------------------------- what is selected + +const pages = (await markdownFiles(DOCS_DIR)).filter((rel) => !only || only.test(rel)); +const callouts = []; +const errorFiles = new Map(); // name -> its beta +for (const rel of pages) { + const text = readFileSync(path.join(DOCS_DIR, rel), "utf8"); + const pkg = /^Reference\/Contributed\/([^/]+)\//.exec(rel)?.[1] ?? null; + for (const c of markersOf(text).callouts) { + const beta = c.label.items.find((i) => i.kind === "beta"); + if (!beta || Number(beta.value) >= BUILD) continue; + if (c.errors) { + errorFiles.set(c.errors, Number(beta.value)); + continue; + } + const items = c.evidence ?? []; + callouts.push({ + rel, + line: c.line, + kind: c.kind, + label: c.label.items.map((i) => i.text).join(", "), + items: items.map(itemText), + keys: items.length ? items.map((i) => checkKey(i, pkg)) : ["unmarked"], + cases: items.filter((i) => kindOf(i) === "case" || kindOf(i) === "compile").map((i) => i.case ?? i.compile), + pkg, + }); + } +} +for (const name of errorFiles.keys()) { + const file = path.join(REPO_ROOT, "errors", `${name}.yml`); + const beta = /^beta:[ \t]*(\d+)/m.exec(readFileSync(file, "utf8"))?.[1]; + if (Number(beta) >= BUILD) errorFiles.delete(name); // written already; the pages are behind +} + +// The checks, by key. +const checks = new Map(); +for (const c of callouts) for (const key of c.keys) if (!checks.has(key)) checks.set(key, null); +for (const name of errorFiles.keys()) checks.set(`errors ${name}`, null); + +// What an earlier run on this build found held is not run again. +if (values.from) { + const earlier = JSON.parse(readFileSync(values.from, "utf8")); + if (earlier.build !== BUILD) { + console.error(`recheck_callouts: ${values.from} is a run on BETA ${earlier.build}, not ${BUILD}`); + process.exit(2); + } + for (const [key, r] of Object.entries(earlier.checks ?? {})) + if (checks.has(key) && r.status === "holds") checks.set(key, { ...r, from: values.from }); +} +for (const key of checks.keys()) { + if (checks.get(key)) continue; + if (key === "none") checks.set(key, { status: "hand", detail: "none: a person checks it" }); + else if (key === "unmarked") checks.set(key, { status: "hand", detail: "the callout names nothing that checks it" }); + else if (keyKind(key) === "probe") + checks.set(key, { status: "hand", detail: "the package's probe/ harness is run by hand" }); + else if (!kinds.has(keyKind(key))) checks.set(key, { status: "skipped", detail: "not in --kinds" }); +} + +const todo = (kind) => + [...checks.keys()].filter((k) => keyKind(k) === kind && !checks.get(k)).map((k) => k.slice(kind.length + 1)); +const say = (s) => console.log(s); +say( + `recheck_callouts: BETA ${BUILD}: ${callouts.length} callout(s) labelled with an older build, in ` + + `${new Set(callouts.map((c) => c.rel)).size} page(s), and ${errorFiles.size} error data file(s)`, +); +for (const kind of RUN_KINDS) { + const list = todo(kind); + if (list.length) say(` ${kind}: ${list.length} to run${values.plan ? `: ${list.join(", ")}` : ""}`); +} +const reused = [...checks.values()].filter((r) => r?.from).length; +if (reused) say(` ${reused} check(s) held in ${values.from}, not run again`); +if (values.plan) process.exit(0); + +// ---------------------------------------------------------------- running + +const work = path.join(tmpdir(), "recheck-callouts", String(process.pid)); +mkdirSync(work, { recursive: true }); +let runs = 0; +const ideArgs = values.ide ? ["--ide", values.ide] : []; + +/** Runs `node ` to its end, keeps its output in the work folder, and returns it. */ +function runTool(tool, args, name) { + const log = path.join(work, `${String(++runs).padStart(2, "0")}-${name}.log`); + say(`running ${tool} ${args.join(" ")}`.slice(0, 200)); + const t0 = Date.now(); + return new Promise((resolve) => { + const child = spawn(process.execPath, [path.join(REPO_ROOT, tool), ...args], { + cwd: REPO_ROOT, + stdio: ["ignore", "pipe", "pipe"], + windowsHide: true, + }); + let out = ""; + let err = ""; + child.stdout.on("data", (d) => { + out += d; + }); + child.stderr.on("data", (d) => { + err += d; + }); + const done = (code) => { + writeFileSync(log, `${out}${err ? `\n--- stderr\n${err}` : ""}`); + say(` exit ${code} in ${Math.round((Date.now() - t0) / 1000)} s; output in ${log}`); + resolve({ code, out, err, log }); + }; + child.on("error", (e) => { + err += `could not start: ${e.message}`; + done(null); + }); + child.on("close", done); + }); +} +const set = (key, status, detail, log) => checks.set(key, { status, detail, log }); + +// Lanes, with the lane reproducers' lanes: one run of each suite. +const repros = new Map(); +for (const slug of todo("bug")) { + for (const dir of [path.join(REPO_ROOT, "bugs", slug), path.join(REPO_ROOT, "bugs", "filed", slug)]) { + const f = path.join(dir, "repro.json"); + if (existsSync(f)) repros.set(slug, JSON.parse(readFileSync(f, "utf8").replace(/^/, ""))); + } +} +const laneBugs = [...repros].filter(([, r]) => r.mode === "lane").map(([slug]) => slug); +const lanesWanted = new Set(todo("lane")); +for (const slug of laneBugs) lanesWanted.add(repros.get(slug).lane); +for (const [suite, { tool }] of Object.entries(SUITES)) { + const mine = [...lanesWanted].filter((l) => l.startsWith(`${suite}:`)).map((l) => l.slice(suite.length + 1)); + if (!mine.length) continue; + const r = await runTool(tool, ["--only", `^(${mine.join("|")})$`, ...ideArgs], `${suite}-lanes`); + const blocks = laneBlocks(r.out); + const broken = r.code === null || r.code >= 2; + for (const lane of mine) { + const key = `lane ${suite}:${lane}`; + const block = blocks.get(lane); + if (checks.has(key) && !checks.get(key)) { + if (broken || !block) set(key, "error", broken ? `${tool} exit ${r.code}` : "no report for the lane", r.log); + else if (block.verdict === "passed") set(key, "holds", "", r.log); + else { + const failed = block.lines.filter((l) => l.trimStart().startsWith(FAILED)).map((l) => l.trim()); + set(key, "changed", `${block.verdict}: ${failed.join("; ")}`, r.log); + } + } + for (const slug of laneBugs.filter((s) => repros.get(s).lane === `${suite}:${lane}`)) { + const marks = repros.get(slug).tests.map((t) => ({ t, mark: block ? testMark(block.lines, t) : null })); + const failed = marks.filter((m) => m.mark === "failed").map((m) => `"${m.t}"`); + if (broken || marks.some((m) => !m.mark)) + set(`bug ${slug}`, "error", `the lane did not report every test`, r.log); + else if (failed.length) set(`bug ${slug}`, "changed", `NO LONGER REPRODUCES: failed ${failed.join(", ")}`, r.log); + else set(`bug ${slug}`, "holds", `${marks.length} test(s) passed`, r.log); + } + } +} + +// The other reproducers. +const bugs = todo("bug"); +if (bugs.length) { + const r = await runTool("scripts/bug_repro.mjs", ["verify", ...bugs, ...ideArgs], "bugs"); + const read = readVerify(r.out); + for (const slug of bugs) { + const got = read.get(slug); + set(`bug ${slug}`, got?.status ?? "error", got?.detail ?? `bug_repro reported nothing (exit ${r.code})`, r.log); + } +} + +// Samples: check_examples over the pages that hold them. +const samples = todo("sample"); +if (samples.length) { + const fences = await collectFences(DOCS_DIR); + const byId = new Map(fences.filter((f) => f.keys.has("id")).map((f) => [f.keys.get("id"), f])); + const rels = [...new Set(samples.map((id) => byId.get(id)?.rel).filter(Boolean))]; + const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const r = await runTool( + "scripts/check_examples.mjs", + ["--only", `^(?:${rels.map(escapeRe).join("|")})$`, "--json", ...ideArgs], + "samples", + ); + let report = null; + try { + report = JSON.parse(r.out); + } catch {} + for (const id of samples) { + const f = byId.get(id); + const runIds = + f?.flags.has(RUN_MARKER) && archesOf(f).includes("win64") + ? [...(archesOf(f).includes("win32") ? [id] : []), `${id}@win64`] + : [id]; + const got = f ? sampleStatus(report, runIds) : { status: "error", detail: `no fence with id=${id}` }; + set(`sample ${id}`, got.status, got.detail, r.log); + } +} + +// Error data, for twinBASIC's two architectures. +for (const name of todo("errors")) { + const r = await runTool( + "scripts/errors.mjs", + ["run", `${name}.yml`, "--runtime", "tb", "--runtime", "win64", ...ideArgs], + `errors-${name}`, + ); + set( + `errors ${name}`, + r.code === 0 ? "holds" : r.code === 1 ? "changed" : "error", + `errors.mjs exit ${r.code}`, + r.log, + ); +} + +// The LLVM claims probe, one mode at a time. +for (const mode of todo("llvm")) { + const r = await runTool("test/llvm/run.mjs", ["--only", mode, ...ideArgs], `llvm-${mode}`); + set(`llvm ${mode}`, r.code === 0 ? "holds" : r.code === 1 ? "changed" : "error", `run.mjs exit ${r.code}`, r.log); +} + +// The contributed packages' cases. +const pkgs = todo("package"); +if (pkgs.length) { + const r = await runTool("scripts/package_tests.mjs", [...pkgs, "--json", ...ideArgs], "packages"); + let report = null; + try { + report = JSON.parse(r.out); + } catch {} + for (const pkg of pkgs) { + const judge = packageCases(report, pkg); + const mine = callouts.filter((c) => c.pkg === pkg).flatMap((c) => c.cases); + const off = [...new Set(mine)].map((n) => [n, judge(n)]).filter(([, j]) => j.status !== "holds"); + const worst = verdictOf(off.map(([, j]) => j.status).concat("holds")); + set(`package ${pkg}`, worst, off.map(([n, j]) => `${n}: ${j.detail}`).join("; "), r.log); + // A callout is judged by its own cases, not the package's worst. + for (const c of callouts.filter((c) => c.pkg === pkg && c.cases.length)) { + const own = c.cases.map((n) => judge(n)); + c.packageVerdict = { + status: verdictOf(own.map((j) => j.status)), + detail: own + .map((j) => j.detail) + .filter(Boolean) + .join("; "), + }; + } + } +} + +// ---------------------------------------------------------------- deciding + +for (const c of callouts) { + const statuses = c.keys.map((k) => + keyKind(k) === "package" && c.packageVerdict ? c.packageVerdict.status : (checks.get(k)?.status ?? "error"), + ); + c.verdict = verdictOf(statuses); +} +const held = callouts.filter((c) => c.verdict === "holds"); +const heldData = [...errorFiles.keys()].filter((n) => checks.get(`errors ${n}`)?.status === "holds"); + +if (!values["dry-run"]) { + const byFile = new Map(); + for (const c of held) { + if (!byFile.has(c.rel)) byFile.set(c.rel, []); + byFile.get(c.rel).push(c); + } + for (const [rel, list] of byFile) { + const file = path.join(DOCS_DIR, rel); + const text = readFileSync(file, "utf8"); + const eol = text.includes("\r\n") ? "\r\n" : "\n"; + const lines = text.split(/\r?\n/); + for (const c of list) { + const next = relabelLine(lines[c.line - 1], BUILD); + if (next === null) throw new Error(`docs/${rel} line ${c.line} is no longer a callout labelled BETA `); + lines[c.line - 1] = next; + } + writeFileSync(file, lines.join(eol)); + } + for (const name of heldData) { + const file = path.join(REPO_ROOT, "errors", `${name}.yml`); + writeFileSync(file, readFileSync(file, "utf8").replace(/^beta:[ \t]*\d+/m, `beta: ${BUILD}`)); + } + if (heldData.length) { + const r = await runTool("scripts/errors.mjs", ["write", ...heldData.map((n) => `${n}.yml`)], "errors-write"); + if (r.code !== 0) say(` errors.mjs write exited ${r.code}: the data files' beta is moved, the pages are not`); + } +} + +// ---------------------------------------------------------------- the report + +const verb = values["dry-run"] ? "would be relabelled" : "relabelled"; +say(`\n${held.length} callout(s) and ${heldData.length} error data file(s) ${verb} to BETA ${BUILD}`); +const HEADINGS = { + changed: "changed: the evidence no longer says what the callout says; read it, then fix or remove the callout", + error: "not checked: the evidence could not be run or read; run it again alone (--from)", + skipped: "not checked: its kind was not in --kinds", + hand: "by hand: a person checks it, then moves the label", +}; +for (const [verdict, heading] of Object.entries(HEADINGS)) { + const list = callouts.filter((c) => c.verdict === verdict); + if (!list.length) continue; + say(`\n${list.length} ${heading}:`); + for (const c of list) { + say(` docs/${c.rel}:${c.line} [${c.kind}] ${c.label}`); + c.keys.forEach((k, i) => { + const r = k === `package ${c.pkg}` && c.packageVerdict ? c.packageVerdict : checks.get(k); + if (r?.status !== "holds") say(` ${c.items[i] ?? k}: ${r?.status}${r?.detail ? ` -- ${r.detail}` : ""}`); + }); + } +} +const badData = [...errorFiles.keys()].filter((n) => !heldData.includes(n)); +if (badData.length) { + say(`\n${badData.length} error data file(s) not moved:`); + for (const n of badData) { + const r = checks.get(`errors ${n}`); + say(` errors/${n}.yml: ${r.status}${r.detail ? ` -- ${r.detail}` : ""}${r.log ? ` (${r.log})` : ""}`); + } +} +if (values.report) { + writeFileSync( + values.report, + `${JSON.stringify({ build: BUILD, dryRun: !!values["dry-run"], checks: Object.fromEntries(checks), callouts, errorData: Object.fromEntries([...errorFiles.keys()].map((n) => [n, checks.get(`errors ${n}`)])) }, null, 1)}\n`, + ); + say(`\nreport: ${values.report}`); +} +say(`tool output: ${work}`); +process.exitCode = callouts.every((c) => c.verdict === "holds") && !badData.length ? 0 : 1; diff --git a/test.bat b/test.bat index 358b881d7..1fad77548 100644 --- a/test.bat +++ b/test.bat @@ -191,6 +191,13 @@ node scripts/check_book_coverage.mjs @rem no tree, no install, well under a second. node scripts/check_package_tests.mjs @if errorlevel 1 goto :fail +@rem Every callout labelled BETA names what checks it in a marker, which +@rem scripts\recheck_callouts.mjs runs on a new build, and every marker under +@rem docs\ names a reproducer, lane, sample, error data file or LLVM probe +@rem mode that exists. Its probes run on inputs built in memory first. +@rem Sources only: no tree, no install, a few seconds. +node scripts/check_callout_evidence.mjs +@if errorlevel 1 goto :fail @rem Every page's Common Errors section is what scripts\errors.mjs write makes of the @rem run-time error data under errors\, and the data is in order. Its probes @rem run on inputs built in memory first. Sources only: no twinBASIC, no VB6. diff --git a/test/package-tests/README.md b/test/package-tests/README.md index a0dceb967..ffb17d4f7 100644 --- a/test/package-tests/README.md +++ b/test/package-tests/README.md @@ -96,6 +96,9 @@ A search that backtracks without end, such as `(a+)+b` on thirty `a`, gives up i An item is `Fixture.Sub`, `compile `, `probe ` (a scenario of `probe/`), or `none -- ` for a callout no program can check (a statement about the IDE, a licence). +A marker may also name what any page's marker can, such as `bug ` for the reproducer of a +twinBASIC defect behind a callout labelled `BETA ` (`scripts/lib/test-markers.mjs` has the +grammar, and `scripts/check_callout_evidence.mjs` checks those items). `node scripts/check_package_tests.mjs`, in `test.bat`, fails when a callout has neither a marker nor a region around it, a region is not closed or opens inside another, an item names nothing, a case is named by no marker, a case does not start with `Expect.Begin` and end with `Expect.Done`, From 14fd1d7391f8e5b15a04d222bd72ad13ea2bb529 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Tue, 3 Nov 2026 20:36:38 +0100 Subject: [PATCH 13/19] recheck_callouts: --dry-run read by its camel-cased name (it relabelled in a dry run); checked end to end on BETA 1034 with 15 callouts and VBA.HiddenModule.yml aged to 1033, all relabelled back byte for byte --- scripts/recheck_callouts.mjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/recheck_callouts.mjs b/scripts/recheck_callouts.mjs index 7e6765350..814466d57 100644 --- a/scripts/recheck_callouts.mjs +++ b/scripts/recheck_callouts.mjs @@ -368,7 +368,7 @@ for (const c of callouts) { const held = callouts.filter((c) => c.verdict === "holds"); const heldData = [...errorFiles.keys()].filter((n) => checks.get(`errors ${n}`)?.status === "holds"); -if (!values["dry-run"]) { +if (!values.dryRun) { const byFile = new Map(); for (const c of held) { if (!byFile.has(c.rel)) byFile.set(c.rel, []); @@ -398,7 +398,7 @@ if (!values["dry-run"]) { // ---------------------------------------------------------------- the report -const verb = values["dry-run"] ? "would be relabelled" : "relabelled"; +const verb = values.dryRun ? "would be relabelled" : "relabelled"; say(`\n${held.length} callout(s) and ${heldData.length} error data file(s) ${verb} to BETA ${BUILD}`); const HEADINGS = { changed: "changed: the evidence no longer says what the callout says; read it, then fix or remove the callout", @@ -429,7 +429,7 @@ if (badData.length) { if (values.report) { writeFileSync( values.report, - `${JSON.stringify({ build: BUILD, dryRun: !!values["dry-run"], checks: Object.fromEntries(checks), callouts, errorData: Object.fromEntries([...errorFiles.keys()].map((n) => [n, checks.get(`errors ${n}`)])) }, null, 1)}\n`, + `${JSON.stringify({ build: BUILD, dryRun: !!values.dryRun, checks: Object.fromEntries(checks), callouts, errorData: Object.fromEntries([...errorFiles.keys()].map((n) => [n, checks.get(`errors ${n}`)])) }, null, 1)}\n`, ); say(`\nreport: ${values.report}`); } From e235e20476451ed0ea4d0b80a96f0dd1996bf4d3 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Tue, 3 Nov 2026 20:38:42 +0100 Subject: [PATCH 14/19] WinReg: the none reason of its BETA NOTE says how to check it, since its samples are not built and examples.bat prints no note for it --- docs/Reference/Contributed/WinReg/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Reference/Contributed/WinReg/index.md b/docs/Reference/Contributed/WinReg/index.md index f868c0fb7..14e623cf8 100644 --- a/docs/Reference/Contributed/WinReg/index.md +++ b/docs/Reference/Contributed/WinReg/index.md @@ -15,7 +15,7 @@ The **WinReg** contributed package provides a Windows Registry helper class that The root keys, such as `HKEY_CURRENT_USER`, are members of **RegKeyspace**, so a project names them with **WinReg** alone. **WinReg** references the **WinDevLib** package, but a project does not see the names a package's own references declare. A project that names `BOOL` or `REGTYPES`, or calls `RegOpenKeyEx` or `RegCloseKey` and uses the `KEY_*` constants, as some examples here do, references **WinDevLib** as well. One that declares the **Dictionary** that [**EnumValues**](RegCls/EnumValues) returns references the Microsoft Scripting Runtime. - + > [!NOTE] 1.0.1.1, BETA 1034 > The package does not compile, so a project that references it does not build. **RegCls** declares `RegCreateKeyEx` and `RegEnumKeyEx` with a `ByRef` structure parameter, *lpSecurityAttributes* (`SECURITY_ATTRIBUTES`) and *lpftLastWriteTime* (`FILETIME`), and passes `VarPtr(0&)` for it in [**WriteString**](RegCls/WriteString), [**WriteDWORD**](RegCls/WriteDWORD), [**CreateKey**](RegCls/CreateKey) and [**DeleteKeyEx**](RegCls/DeleteKeyEx). A pointer passed for a structure has to be passed `ByVal` (error TB5001). `VarPtr(0&)` is also the address of a temporary **Long**, not a null pointer; `ByVal vbNullPtr` passes a null pointer. The behaviour described on these pages is that of the package with those four calls passed `ByVal`. From 7b6cc366838a34edceed1a24243f6e4e614dedeb Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Tue, 3 Nov 2026 21:16:37 +0100 Subject: [PATCH 15/19] Item 16: the 32-bit 'stall' was the IDE's error panel for -353703419 Bad DLL definition; captureRun reads the kept clears on every poll of the build and ends at an open error panel, tbrun names the error (exit 5); BUGS entry declare-dll-stack-not-checked (a DLL Declare with the wrong convention or parameters raises nothing and crashes at the caller's return, where a static library's function or a Delegate raises -353703419 and VB6's IDE 49), WARNING BETA 1034 on Declare and API-Declarations, the static-library error on Static-Linking --- BUGS-TO-REPORT.md | 86 ++++++++++++++++++ WIP.Harness.md | 9 ++ .../declare-dll-stack-not-checked.twinproj | Bin 0 -> 5862 bytes bugs/declare-dll-stack-not-checked/repro.json | 15 +++ .../src/Miscellaneous/basic.c | 13 +++ .../src/Miscellaneous/basic_32.obj | Bin 0 -> 1479 bytes .../src/Settings | 61 +++++++++++++ .../src/Sources/Startup.twin | 48 ++++++++++ .../vb6/Module1.bas | 34 +++++++ .../vb6/Probe.vbp | 7 ++ docs/Documentation/Tools-Compiler.md | 2 +- docs/Features/Advanced/API-Declarations.md | 8 +- docs/Features/Advanced/Static-Linking.md | 2 + docs/Reference/Core/Declare.md | 4 + docs/Reference/Core/Delegate.md | 2 +- scripts/lib/tb-run.mjs | 45 ++++++++- scripts/tbrun.mjs | 16 +++- 17 files changed, 341 insertions(+), 11 deletions(-) create mode 100644 bugs/declare-dll-stack-not-checked/declare-dll-stack-not-checked.twinproj create mode 100644 bugs/declare-dll-stack-not-checked/repro.json create mode 100644 bugs/declare-dll-stack-not-checked/src/Miscellaneous/basic.c create mode 100644 bugs/declare-dll-stack-not-checked/src/Miscellaneous/basic_32.obj create mode 100644 bugs/declare-dll-stack-not-checked/src/Settings create mode 100644 bugs/declare-dll-stack-not-checked/src/Sources/Startup.twin create mode 100644 bugs/declare-dll-stack-not-checked/vb6/Module1.bas create mode 100644 bugs/declare-dll-stack-not-checked/vb6/Probe.vbp diff --git a/BUGS-TO-REPORT.md b/BUGS-TO-REPORT.md index dc481eed3..cb322f055 100644 --- a/BUGS-TO-REPORT.md +++ b/BUGS-TO-REPORT.md @@ -11553,3 +11553,89 @@ Severity: low; the message is longer and repeats itself, but still names the typ +--- + +## In a 32-bit build, a Declare whose calling convention or parameters do not match the DLL function raises no error, and the procedure that called it ends with an access violation + +**Describe the bug** +In a 32-bit build, a `Declare` of a DLL function that names the wrong calling convention, or the wrong number of parameters, raises no error. The call returns, the stack is left unbalanced, and the procedure that made the call ends with an access violation as it returns. In the IDE the access violation is in the compiler's process, which restarts. The same mistake in a `Declare` of a statically linked function, or in a call through a `Delegate`, raises -353703419, *Bad DLL definition. Stack corruption detected.*, and the program goes on. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open `declare-dll-stack-not-checked.twinproj` (attached as `declare-dll-stack-not-checked.zip`); its build is win32. `labs` in `msvcrt.dll` and `add` in `basic_32.obj` (`basic.c`, beside it in Miscellaneous) are both `__cdecl`, and both are declared without `CDecl`: + ``` + Import Library "/Miscellaneous/basic_32.obj" As BASIC + + Module Startup + + Private Declare PtrSafe Function add Lib BASIC (ByVal a As Long, ByVal b As Long) As Long + Private Declare PtrSafe Function labs Lib "msvcrt" (ByVal x As Long) As Long + + Private Sub Report(ByVal what As String, ByVal value As Long) + If Err.Number <> 0 Then + Debug.Print what & ": error " & Err.Number & ", " & Err.Description + Else + Debug.Print what & " = " & value + End If + End Sub + + Private Sub CallStatic() + On Error Resume Next + Dim r As Long = add(2, 3) + Report "add(2, 3)", r + End Sub + + Private Sub CallDll() + On Error Resume Next + Dim r As Long = labs(-7) + Report "labs(-7)", r + End Sub + + Public Sub Main() + CallStatic + Debug.Print "returned from CallStatic" + CallDll + Debug.Print "returned from CallDll" + End Sub + + [RunAfterBuild] + Private Sub Built() + Debug.Cls + Main + End Sub + + End Module + ``` +2. Build it. The `[RunAfterBuild]` Sub runs `Main` in the IDE. +3. See the DEBUG CONSOLE, which has no `returned from CallDll` line: `CallDll` ends with an access violation as it returns, and the compiler restarts: + ``` + add(2, 3): error -353703419, Bad DLL definition. Stack corruption detected. + returned from CallStatic + labs(-7) = 7 + restarting from MEMORY [...] + ``` +4. Run the built exe. It ends with exit code -1073741819 (`0xC0000005`), and the Application event log records the exception at an address on the stack. + +**Expected behavior** +The call of `labs` raises an error, as the call of `add` does, and the program goes on. VB6 raises error 49, *Bad DLL calling convention*, for the same `Declare` in its IDE and in a P-Code exe. The VB6 project, attached as `declare-dll-stack-not-checked-vb6.zip` and compiled to P-Code, prints: +``` +labs(-7): error 49, Bad DLL calling convention +returned from CallDll +``` +Error 49 would suit the statically linked call too, which raises -353703419 (`&HEAEAEA05`). + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +Other mismatches tried, each in a 32-bit IDE run and exe, raise no error either, and end the procedure that made the call with the same access violation: `MulDiv` of kernel32, which is `__stdcall`, declared `CDecl` (the right value, called 1000 times); `MulDiv` declared with a parameter too few (a wrong value, -1 for `MulDiv(6, 4)`) or one too many (the right value), after one call each. In the IDE the access violation either ends the compiler's process, which restarts, or is reported as `NATIVE EXCEPTION: ACCESS_VIOLATION` and turns on TRACE-MODE. + +A statically linked call is checked both ways: a `__stdcall` function of the object declared `CDecl` raises -353703419 as well. So is a call through a `Delegate`: a `Delegate Function OneArg (ByVal x As Long) As Long`, without `CDecl`, given the address of `labs` from `GetProcAddress` raises -353703419 when it is called, and the program goes on, in the IDE and in the exe; the same delegate declared `CDecl` returns 7. A 64-bit build has one calling convention, so neither `Declare` in the reproducer is a mismatch there, and both calls return their values. + +VB6's native code checks nothing either: the same VB6 project compiled to native code ends with an access violation in OLEAUT32. The check is made by VB6's IDE and its P-Code. The reproducer behaves the same on BETA 1010. + +Severity: medium. A wrong `Declare` is the programmer's mistake, but nothing points to it: the program crashes in the procedure that made the call, after the call has returned, and in the IDE the compiler goes down with it. A VB6 programmer expects error 49 at the call. + + + diff --git a/WIP.Harness.md b/WIP.Harness.md index e3bc02de9..c79460a2f 100644 --- a/WIP.Harness.md +++ b/WIP.Harness.md @@ -707,6 +707,15 @@ Rules it follows: form loaded returns too: the IDE then prints `[DEBUGGER] Waiting for remaining forms to close...` after the sentinel, which `sentinelIndex` allows and `tbrun` prints (BETA 1010). `check_twin_parsers` has fixtures for both. +- **An unhandled error in plain code stops the run at the IDE's error panel**, which waits + for a person, so `captureRun` ends the wait once the panel is open and `tbrun` names the + error (exit 5). The page holds the panel's text in `activeDebuggingErrorHeader` and + `activeDebuggingError`; `clearDebuggingCallframe` resets `activeDebuggingFrameId` and not + the text, so a set frame is what says the panel is open (BETA 1034). Before this, + `tbrun` reported such a probe that printed nothing first as a build that "did not reach + [BUILD] Executing": the build, the probe's `Debug.Cls` and the stop all fell between two + polls, the console read as empty before and after, and the erased Executing line was + looked for only when the console changed. It is now looked for on every poll of the build. - **`tbrun --exe` exits 6 when the exe exited with a code other than 0**, when the event log records that it faulted, when it opened a box, or when it was still running at `--timeout` and was ended. A run that would exit 5 exits 5 first, since the diff --git a/bugs/declare-dll-stack-not-checked/declare-dll-stack-not-checked.twinproj b/bugs/declare-dll-stack-not-checked/declare-dll-stack-not-checked.twinproj new file mode 100644 index 0000000000000000000000000000000000000000..3306c4f2bc3e91cdc112390b631f4687befdff1e GIT binary patch literal 5862 zcmcIoU2Ggz6`sweiM>t06{;%2kFFNe+KFeq-d(SMT9Eh8!K(l8ItfzNxbt(@JIUH`Q-+N$ta9sr3-2(@Y-KuAcbD$ycd!c$Ph14ul;Q~1ukGrO~n z0|CU9cIVEy=bn4+cg{WM-1+#A9=)4TKB_26l^eF^@v3dt1I@U;>IRh-H?DINWBqmt z-4n{=_+M&wTrc3}=0S%qTe_!t7Wey%it-4(oKVi!1WYr{G4Y&W^w7uIA@g|9^&E(~!1SltjZ`X> zP7H>NCAr5w!=|#?r-cZI?+4q0KDO&xW=z6`3n9CZVz`bE0WHmA=XX5L1E_d(M!E%* zT)DkUT1=x=x3~R3>JNRXAL;D3V1fr|4cs(Fo7Q2?dC~Jh=^@Z@AsSohXui))Y+o)L z>{(8^R9~tPL;-AT@}|IMtgpp+-E!Q{IB6kp8<$9EosNAlCdOD(0t$1`2I9Cj$3WD_ zX>(-fgmMP|!uZ?yT*lR}4S2w>;6=-cl#=}2pRRo)sVJWeeIsLf+z57g_^M2z^Fw&c zKue7%8I$YXozZ$kzm3j&NB9<$k#dH7Ct||$Bz`{~&_q!dMzU{Z0+@TW?L@4PqVw}3 zd~9U>xubj^0^i#Y!8ZcFTMxlU*xd%--H?~Iblkd6fc`h|JPPcn(L?YZ*)N^a@sNl3 zs4XbiuhF{h7YmYr|8@L|NB9&d!x)>a))q^bmN%D|%JXUbRaQ3BgT%Gv>S}4FR!=j6 z_pkW9i{Fb%;xRmw%6#LJ&pp3!y{l`DQm0deNgB&8e7Ld6+nomDQ`h8;s;aJ4*3~^j z?fI%|x< z!|xRFyh^mMzm<56p5jF^4CD2bI5o0I;K^P*dMKYd-F*D1b8nt|cH#A#^~`T47VZ?^ zIzMv$kzc;}&4riWfBo(g^>>Y-zx?g_DVU_sEkLnp_Jq+3dHK#8*Pfu4`x7rtmUJiJw#Q z(PV;7>R^kXQF7;G;`Sf|-s0zz+%K6Rtf*~1SN$1<5k-+vSQU$te4NjfDcaSX{}k_kHX z)b@Xn{FdyE)-TS?6!PLl@F?*JKs!n%)+KA~|I_Gi_Yf}=xpjK zMpSf7*5csZvw{}l3ae0Qg(AgeTr*lC6EmA@yWD35rXK`&c94iQZ%4#1hVmq6aVlzz z>lqR|poQhj_l$vRL_{2uHC>PG&lbgs^E88+VY5lcwcQ<;O&Vi7?3xvLn+Ve zA}V4RCJ&ILxtU2%u)UULv`9Q{tp(r^DN$-s4e3iDX`th|JD%2NJ1$lvp>bt|sX}d{ zN{}od3pSt4Optt{R8aPykfpU8#vPNa4ZCTm>J0dm&vq@>7P*2v!3rV~RILaFm#Q@( zD17Dn9EOk;FO$XY0ZUH~l(mxsK(dn0L>YB9bvCu)nq8X{KIEB1A^WU*)~*(C78c%Y zJ@D#Ule5LBMkOD_Vp0wpD<53e;G86R*>!d%m?Y>CVLa+Qq<8@*$xln)Pe)7aM}lG! z2W8f~I@=Jo3$@y7X#vfui^@AvZCA6qyeCYGpq(!@S^iH+KE;!)O<6rpyX zy9S3^YRY%&WAjH%NO+LX!NRzQjE(mvuQ{YBR7Oz4>1L}aAN!+KtIfO!Agm6HkL4y< zJ|;lI%+kFfY}I=xWpDugdrinW#?+Z(6p2P2tVdV3aTy~_R&Z~N>(Iw2hC$5EX?c_p zK*bHO&yK(p87@5AeL~!l#|Qun;p`*EySmYJN=;mWL`v9_0;5Ao+7ms9t1nlo*nWR; z+M#@lNvEzn^JghzA~8|ctMh>Fe11GBo%|y5-eGDujoV?<+UcSsrC4e?NFY|=!{O6a z-qgA_`V(M7e7uH`nn7;gv;vz;h6h~S2WIG$_tv$bMN>u(We%nH8;ynlpwSpTT$kck zwKl)8J$hL3j27o<0STcpL-A)I5{bzEB(BGW%zf z_~6?fLL(ybJX@k0cRr`;h{i%!B!ymR+-teMOiEN}1FK!s`jlaCusRwN1`>_uGt;Fn z1guSUy$Iu!qIwtJUgnTNWFYAn+{7L+2Vu;>L8c3F@UJt+z{dDKB{G6@$-kV#-U6@7 z;|8-#+VEjzdZv=kmC9;qDqmC!i{%-$JXNf!Q-xf4aVDFaE6&yifpxGsni^&ac(X{e z{m(N4dblF))%H6$fmU#g9v*L^G;shq2Ct@uhv9s~!|71HY&M(A7V=pYo$#*`^@xF)WBM*YCQvUa6d zXf*Ii!?k%X6WDreO0jU{t5z#8S|Pt!Q|F5LDRpXUZhEd-C>3UMvk@ScAKd`M`W8|{ zHz2qygJ7o}tt@SZnnkExt*ouAFD=(L*m|k*Z0TZci}midOl6nL;&KT{bqgQ04p_JV z8(Va}wm%-jOu`%^NDw0eg=QbIh1A%*va~8n6g65sUpHqv4N=YqL=gMliFf*f;rl+&H#lmcDu~PZ}gN_dn zY{zzW)Le+Ks2TBD0HH8Kvggtz(`ibJqD=%GT5C9sZ@412U6rra w4vrwFa~lzyrrUpuNxQn05{EP$e)-LGeSXQY4Ag=jy#10&OO27DM{i zcQ=9k7cgrecPO}6Tur=d!QNJ3kVBCP#LLDfi~b(l0CNn6zlJ05GlC#=gd_WdiRf_j zV1Hi=OrgQymgUBwX!vk&FcNPOFugxvyoYg>a80m~P@j@0YK5XQSxQkQn9qmdNR+6- zXhs>Tds1CKO0~VB#sW<2 zp)@6?sNXNLKqu{{Db^Y2@+n1Dpe7s7Q&lNu6r*HzcPS}a)R|AWmrm*v9n=q6&^{jqM&y5KoBb(PVS^;BGVI<1m%Mmvp@r=9%b^Ub2 z!$;gBuiUo78!Hcbjw~kJmiIY6H1ZnLvKdALGWXDSeule`tF^I#)v#?K9++(nlV@w= zS|(!%>4K3JLS&mOy>UnLbn_wkVL2|nYnSK!GrPRI>R(>HB;Wb;aA8yYJ#+tf^ykf| zEs258r@!2Le&g=7uZKhJ9U*#Z{&U!MU}|6d?c)J>yCpJ%_#|Dub+L|HT*OL}(;77; zpYICc&QvT@Mn=l2R+ZTnXP;t?@*H;Ra&h)3mNU=IEeBxd>`yE|p2J+B43CDN5`yne zGNY6;?E|nF;_O1?c@Fy(<@fI1S^Fs`Qx6`V!(K%>_u>AFl}rP8cnM>t3={|G>3mX|_m0>Mcu> z=JT18wyD9gICF>v!*kg6C?{_7mu;;zKG56S(`{`mhB$8kjELuAHtLwSgPzR6&1fG1 UNsbjX&4i{P7)`Qhs`6g)7o%+rSpWb4 literal 0 HcmV?d00001 diff --git a/bugs/declare-dll-stack-not-checked/src/Settings b/bugs/declare-dll-stack-not-checked/src/Settings new file mode 100644 index 000000000..c54de66a1 --- /dev/null +++ b/bugs/declare-dll-stack-not-checked/src/Settings @@ -0,0 +1,61 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "DeclareDllStackNotChecked", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Reproduces: In a 32-bit build, a Declare whose calling convention or parameters do not match the DLL function raises no error, and the procedure that called it ends with an access violation", + "project.exportPathIsV2": true, + "project.id": "{C57C32AB-A136-4FB7-B16D-142BF702968E}", + "project.name": "DeclareDllStackNotChecked", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/bugs/declare-dll-stack-not-checked/src/Sources/Startup.twin b/bugs/declare-dll-stack-not-checked/src/Sources/Startup.twin new file mode 100644 index 000000000..b5f771d7b --- /dev/null +++ b/bugs/declare-dll-stack-not-checked/src/Sources/Startup.twin @@ -0,0 +1,48 @@ +' Build as win32. labs in msvcrt.dll and add in basic_32.obj are both __cdecl, and both are +' declared here without CDecl, so each call leaves 4 bytes per argument on the stack. +' basic_32.obj is basic.c compiled for x86 with: +' cl /nologo /c /Gw /Gy /GS- basic.c /Fo"basic_32.obj" +' The call of add raises -353703419, "Bad DLL definition. Stack corruption detected.", which +' the handler reports, and the program goes on. The call of labs raises nothing and returns 7, +' and CallDll then ends with an access violation as it returns, in the IDE and in the exe. +Import Library "/Miscellaneous/basic_32.obj" As BASIC + +Module Startup + + Private Declare PtrSafe Function add Lib BASIC (ByVal a As Long, ByVal b As Long) As Long + Private Declare PtrSafe Function labs Lib "msvcrt" (ByVal x As Long) As Long + + Private Sub Report(ByVal what As String, ByVal value As Long) + If Err.Number <> 0 Then + Debug.Print what & ": error " & Err.Number & ", " & Err.Description + Else + Debug.Print what & " = " & value + End If + End Sub + + Private Sub CallStatic() + On Error Resume Next + Dim r As Long = add(2, 3) + Report "add(2, 3)", r + End Sub + + Private Sub CallDll() + On Error Resume Next + Dim r As Long = labs(-7) + Report "labs(-7)", r + End Sub + + Public Sub Main() + CallStatic + Debug.Print "returned from CallStatic" + CallDll + Debug.Print "returned from CallDll" + End Sub + + [RunAfterBuild] + Private Sub Built() + Debug.Cls + Main + End Sub + +End Module diff --git a/bugs/declare-dll-stack-not-checked/vb6/Module1.bas b/bugs/declare-dll-stack-not-checked/vb6/Module1.bas new file mode 100644 index 000000000..2b740a420 --- /dev/null +++ b/bugs/declare-dll-stack-not-checked/vb6/Module1.bas @@ -0,0 +1,34 @@ +Attribute VB_Name = "Module1" +Option Explicit + +' The VB6 side of declare-dll-stack-not-checked: labs in msvcrt.dll is __cdecl, declared here +' without CDecl, as the twinBASIC reproducer declares it. Probe.vbp compiles to P-Code, which +' runs Declare calls as the VB6 IDE does: the call raises error 49, "Bad DLL calling +' convention", and the program goes on. Compiled to native code, VB6 checks nothing either. +' Build and run it with: node scripts/bug_repro.mjs vb6 declare-dll-stack-not-checked + +Private Declare Function labs Lib "msvcrt" (ByVal x As Long) As Long + +Private Sub CallDll() + Dim r As Long + On Error Resume Next + r = labs(-7) + If Err.Number <> 0 Then + Print #9, "labs(-7): error " & Err.Number & ", " & Err.Description + Else + Print #9, "labs(-7) = " & r + End If +End Sub + +Sub Main() + On Error GoTo Fail + Open App.Path & "\out.txt" For Output As #9 + CallDll + Print #9, "returned from CallDll" + Close #9 + Exit Sub +Fail: + On Error Resume Next + Print #9, "unhandled error " & Err.Number & ": " & Err.Description + Close #9 +End Sub diff --git a/bugs/declare-dll-stack-not-checked/vb6/Probe.vbp b/bugs/declare-dll-stack-not-checked/vb6/Probe.vbp new file mode 100644 index 000000000..a4424405d --- /dev/null +++ b/bugs/declare-dll-stack-not-checked/vb6/Probe.vbp @@ -0,0 +1,7 @@ +Type=Exe +Module=Module1; Module1.bas +Startup="Sub Main" +ExeName32="Probe.exe" +Name="Probe" +CompilationType=-1 +OptimizationType=0 diff --git a/docs/Documentation/Tools-Compiler.md b/docs/Documentation/Tools-Compiler.md index 2ad8b486a..26be457b3 100644 --- a/docs/Documentation/Tools-Compiler.md +++ b/docs/Documentation/Tools-Compiler.md @@ -222,7 +222,7 @@ clears what an earlier run in the same slot left behind. **A probe builds for th target `--arch` names**, whatever the IDE remembers, so a kept IDE switched to `win64` does not make later runs build 64-bit. -Exit codes: **0** the probe ran and its output was captured; **1** the project has compile errors (the diagnostics are printed); **2** a refused command line (a source folder that is missing or has no `Settings` file included), no IDE, an IDE that did not start, a compile that never settled or met its limit, a build that failed after a clean compile (an LLVM refusal anywhere in its log, an exe linked or not, and the build's limit included), a probe that never ran or stopped at a procedure that failed code generation, a missing or malformed `test/harness.json`, a `TB_` variable that is not a number of seconds greater than 0, an LLVM run on a Community or Personal licence, an `--exe` run with no exe built, `--tests` on a project with no case or with a `[TestCase]` it cannot call (one with parameters, a **Function**, or one outside a `[TestFixture]` module), or a crash; **3** no output: the console held none before the timeout, or the probe printed none after its last `Debug.Cls`; **4** the compiler crashed, or restarted twice, while compiling the project; **5** the probe ended before it returned, its output printed all the same; **6** under `--exe`, the exe exited with a code other than 0, the Application event log records that it faulted, it opened a box (which `tbrun` closed), or it was still running at its limit and was ended, its output, exit code, fault and boxes printed all the same; **7** under `--tests`, a case failed, every case's line printed all the same. A run that would exit 5 exits 5 whatever the exe did. +Exit codes: **0** the probe ran and its output was captured; **1** the project has compile errors (the diagnostics are printed); **2** a refused command line (a source folder that is missing or has no `Settings` file included), no IDE, an IDE that did not start, a compile that never settled or met its limit, a build that failed after a clean compile (an LLVM refusal anywhere in its log, an exe linked or not, and the build's limit included), a probe that never ran or stopped at a procedure that failed code generation, a missing or malformed `test/harness.json`, a `TB_` variable that is not a number of seconds greater than 0, an LLVM run on a Community or Personal licence, an `--exe` run with no exe built, `--tests` on a project with no case or with a `[TestCase]` it cannot call (one with parameters, a **Function**, or one outside a `[TestFixture]` module), or a crash; **3** no output: the console held none before the timeout, or the probe printed none after its last `Debug.Cls`; **4** the compiler crashed, or restarted twice, while compiling the project; **5** the probe ended before it returned, or stopped at a run-time error it does not handle (which `tbrun` names, with the file and line), its output printed all the same; **6** under `--exe`, the exe exited with a code other than 0, the Application event log records that it faulted, it opened a box (which `tbrun` closed), or it was still running at its limit and was ended, its output, exit code, fault and boxes printed all the same; **7** under `--tests`, a case failed, every case's line printed all the same. A run that would exit 5 exits 5 whatever the exe did. ## probe_build_twice.mjs {: #probe-build-twice } diff --git a/docs/Features/Advanced/API-Declarations.md b/docs/Features/Advanced/API-Declarations.md index 3874059ac..04038cc2b 100644 --- a/docs/Features/Advanced/API-Declarations.md +++ b/docs/Features/Advanced/API-Declarations.md @@ -35,7 +35,13 @@ An API declaration, a procedure, a delegate or an interface member can name its | **ThisCall** | thiscall | the first in the ECX register, the rest on the stack | the callee | | **FastCall** | fastcall | the first two in ECX and EDX, the rest on the stack | the callee | -The four follow the Microsoft C/C++ conventions of the same names: `__stdcall`, `__cdecl`, `__thiscall` and `__fastcall`. A declaration must name the convention its counterpart actually uses. A mismatch passes the arguments in the wrong places, leaves the stack unbalanced, and usually crashes. +The four follow the Microsoft C/C++ conventions of the same names: `__stdcall`, `__cdecl`, `__thiscall` and `__fastcall`. A declaration must name the convention its counterpart actually uses. A mismatch passes the arguments in the wrong places and leaves the stack unbalanced. + +In a 32-bit build, a call of a [statically linked](Static-Linking) function, and a call through a [**Delegate**](../../tB/Core/Delegate), check the stack afterwards. A wrong convention there raises run-time error -353703419 (`&HEAEAEA05`), *Bad DLL definition. Stack corruption detected.*, and the stack is put right, so a handler can go on. + + +> [!WARNING] BETA 1034 +> In a 32-bit build, a call of a DLL function through a **Declare** whose convention or number of parameters is wrong raises no error. The call returns, with a wrong value when a parameter is missing, and the procedure that made the call ends with an access violation as it returns. In an IDE run the access violation is in the compiler's process, which restarts. VB6 raises error 49, *Bad DLL calling convention*, at the call, in its IDE and in a P-Code exe; a VB6 exe compiled to native code checks nothing either. A 64-bit build has only one calling convention. There, all four keywords are accepted and have no effect. diff --git a/docs/Features/Advanced/Static-Linking.md b/docs/Features/Advanced/Static-Linking.md index 7b815a386..58e102ef3 100644 --- a/docs/Features/Advanced/Static-Linking.md +++ b/docs/Features/Advanced/Static-Linking.md @@ -65,6 +65,8 @@ The parameters work as they do for a DLL function, and **AddressOf** passes a tw The default calling convention of a C function is `__cdecl`, so its **Declare** names **CDecl**. A **Declare** without a convention keyword is stdcall (see [Calling Conventions](API-Declarations#calling-conventions)); a 64-bit build has only one convention, and ignores the keyword. +In a 32-bit build, a call through a **Declare** that names the wrong convention raises run-time error -353703419 (`&HEAEAEA05`), *Bad DLL definition. Stack corruption detected.* The call returns no value, the stack is put right, and [**On Error**](../../tB/Core/On-Error) can trap the error. + In a 32-bit build, the C compiler decorates the names in the object file. It puts an underscore before the name of a `__cdecl` or `__stdcall` function, which a **Declare** leaves out: twinBASIC adds it. A `__stdcall` function's name also ends with `@` and the size of its arguments, each rounded up to 4 bytes, and that suffix goes into **Alias**. `int __stdcall add_std(int a, short b)` is `_add_std@8` in a 32-bit file and `add_std` in a 64-bit one: ```tb inert=external diff --git a/docs/Reference/Core/Declare.md b/docs/Reference/Core/Declare.md index 7ba500fa8..38f7da8dc 100644 --- a/docs/Reference/Core/Declare.md +++ b/docs/Reference/Core/Declare.md @@ -118,6 +118,10 @@ Declare Sub Second Lib "MyLib" () > [!WARNING] BETA 1034 > In a 64-bit build with [LLVM](../../LLVM/Getting-Started), a call that passes `ByVal 0&` for the eighth parameter, declared `ByRef` with a 32-bit type such as **Long**, to give the procedure a NULL pointer, can end with an access violation (0xC0000005) and no message box. The eighth parameter of **WideCharToMultiByte** is one such case. Whether the call fails depends on what the stack held before it. The same override for the first to seventh parameters is right. Declare the parameter `ByVal ... As LongPtr` instead. A 32-bit build and a build without LLVM are not affected. + +> [!WARNING] BETA 1034 +> In a 32-bit build, a call of a DLL procedure whose **Declare** names the wrong calling convention, or the wrong number of parameters, raises no error. The call returns, with a wrong value when a parameter is missing, and the procedure that made the call ends with an access violation as it returns; in an IDE run the compiler's process ends with it and restarts. VB6 raises error 49, *Bad DLL calling convention*, at the call in its IDE and in a P-Code exe. A call of a statically linked function raises an error instead; see [Calling Conventions](../../Features/Advanced/API-Declarations#calling-conventions). + ### Example This example shows how the **Declare** statement is used at the module level of a standard module to declare a reference to an external procedure in a dynamic-link library (DLL). **Declare** statements can be placed in class modules when they are **Private**. diff --git a/docs/Reference/Core/Delegate.md b/docs/Reference/Core/Delegate.md index 76135409b..a96788dcd 100644 --- a/docs/Reference/Core/Delegate.md +++ b/docs/Reference/Core/Delegate.md @@ -26,7 +26,7 @@ Syntax: : The identifier naming the delegate type. Must be a valid twinBASIC identifier. *callconv* -: *optional* The calling convention of the procedures the delegate refers to: **CDecl**, **ThisCall** or **FastCall**. The default is stdcall. A C-runtime callback such as the comparator of `qsort` is **CDecl**. See [Calling Conventions](../../Features/Advanced/API-Declarations#calling-conventions), which also describes a defect in BETA 1010 that affects calls through **ThisCall** and **FastCall** delegates on 32-bit builds. +: *optional* The calling convention of the procedures the delegate refers to: **CDecl**, **ThisCall** or **FastCall**. The default is stdcall. A C-runtime callback such as the comparator of `qsort` is **CDecl**. See [Calling Conventions](../../Features/Advanced/API-Declarations#calling-conventions), which also describes a defect that affects calls through **ThisCall** and **FastCall** delegates on 32-bit builds. *arglist* : *optional* Parameter signature, written exactly as for a [**Sub**](Sub) or [**Function**](Function) --- comma-separated `[ ByVal | ByRef ] [ Optional ] *varname* [ As *type* ]` parts. diff --git a/scripts/lib/tb-run.mjs b/scripts/lib/tb-run.mjs index 12ae039ed..7bca18444 100644 --- a/scripts/lib/tb-run.mjs +++ b/scripts/lib/tb-run.mjs @@ -18,6 +18,23 @@ import { timing, timingsOn } from "./tb-timings.mjs"; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); +// The run-time error the IDE's error panel shows, or null. An error the program does not +// handle stops the run there, and the panel waits for a person (Try Again, Ignore, Stop). +// event_stopped (ide/main.js) puts the panel's text in activeDebuggingErrorHeader and +// activeDebuggingError, and the frame it stopped in in activeDebuggingFrameId; +// clearDebuggingCallframe, at a continue or the run's end, resets the frame and leaves the +// text, so the frame is what says the panel is open. A breakpoint or Stop has no text. +const STOPPED_JS = `(() => { + if (typeof activeDebuggingFrameId === "undefined" || activeDebuggingFrameId === undefined) return null; + if (typeof activeDebuggingError === "undefined" || !activeDebuggingError) return null; + return { + header: String(activeDebuggingErrorHeader ?? ""), + error: String(activeDebuggingError), + file: activeDebuggingFileNode ? activeDebuggingFileNode.getFullPath() : null, + line: activeDebuggingLine ?? null, + }; +})()`; + /** * The console's text as lines: blank lines trimmed off both ends, and nothing * else done. Reading the backing array rather than the pane means the header, @@ -46,7 +63,13 @@ export function strip(text, raw = null) { * `quietMs` counted from the later of Executing and its last change (a run that * prints nothing ends too), or at `runMs` after Executing. A caller whose Sub * ends with a marker line passes a `done` that looks for it; one that cannot - * says `() => false` and relies on the quiet period. + * says `() => false` and relies on the quiet period. A run stopped at the IDE's + * error panel by an error it does not handle never goes on, so it ends at once. + * + * Executing is looked for in what the clears erased on every poll of the build, + * not only when the console changes: a small project builds, runs its Sub's + * Debug.Cls and stops at a panel between two polls, and the console then reads + * as empty before the press and after. * * It throws, with a message that says why, when the IDE has no * `clearDebugConsole()` to wrap or no console to read, or the Build button @@ -59,11 +82,12 @@ export function strip(text, raw = null) { * @param {number} [o.runMs] default: its `compilerRun` (TB_COMPILER_RUN_TIMEOUT) * @param {number} [o.quietMs] default: its `compilerRunQuiet` (TB_COMPILER_RUN_QUIET) * @returns {Promise<{captured: string[], last: string, erased: string[], timedOut: boolean, - * ended: "done" | "quiet" | "failed" | "build-timeout" | "run-timeout", limitMs: number | null}>} + * ended: "done" | "quiet" | "stopped" | "failed" | "build-timeout" | "run-timeout", limitMs: number | null, + * stopped: {header: string, error: string, file: string | null, line: number | null} | null}>} * `captured` is the console after the run; `last` the same, unsplit; `erased` * every line a clear removed, oldest first; `timedOut` is true when the wait * ended at a limit, `ended` says how it ended, and `limitMs` is the limit that - * ended it + * ended it; `stopped` is the error panel's text and place when the run stopped there */ export async function captureRun( c, @@ -90,7 +114,8 @@ export async function captureRun( lastChange = Date.now(), failedAt = null, ended = null, - limitMs = null; + limitMs = null, + stopped = null; // When the build's first line, its link line and the Sub's Executing line were // first seen, in the console or in what a clear erased. `exec` ends the build // phase and starts the run's; the other two are for tb-timings.mjs only. @@ -136,6 +161,16 @@ export async function captureRun( ended = "done"; break; } + } else if (marks.exec === null) { + // A clear can erase the whole build between two polls: see the comment above. + await markLines(now); + } + if (marks.exec !== null) { + stopped = await c.evaluate(STOPPED_JS).catch(() => null); + if (stopped) { + ended = "stopped"; + break; + } } if (marks.exec === null) { // The build: it ends with Executing, or fails, or meets its limit. @@ -188,7 +223,7 @@ export async function captureRun( "a failure they erased cannot be ruled out", ); } - return { captured, last, erased: kept.flatMap((text) => text.split("\n")), timedOut, ended, limitMs }; + return { captured, last, erased: kept.flatMap((text) => text.split("\n")), timedOut, ended, limitMs, stopped }; } /** diff --git a/scripts/tbrun.mjs b/scripts/tbrun.mjs index 760c608f9..4076839f2 100644 --- a/scripts/tbrun.mjs +++ b/scripts/tbrun.mjs @@ -52,7 +52,8 @@ // test/harness.json missing or malformed -- 3 no output: the build produced none // in the console, or the probe ran and printed none after its last Debug.Cls -- 4 the // compiler crashed, or restarted twice, while compiling the project -- 5 the -// probe ended before it returned, its output printed all the same -- 6 with +// probe ended before it returned, or stopped at a run-time error it does not +// handle, its output printed all the same -- 6 with // --exe, the exe exited with a code other than 0, the event log records that it // faulted, it opened a box, or it was still running at its limit; its // output, exit code, fault and boxes printed all the same -- 7 with --tests, a @@ -257,8 +258,9 @@ Exit codes: 3 no output: the console held none, or the probe printed none after its last Debug.Cls 4 the compiler crashed, or restarted twice, while compiling the project - 5 the probe ended before it returned (End, or an error that ended the run); what - it printed is printed all the same + 5 the probe ended before it returned (End, or an error that ended the run), or + stopped at a run-time error it does not handle, which is named; what it + printed is printed all the same 6 --exe: the exe exited with a code other than 0, the event log records that it faulted, it opened a box (closed by tbrun, as an unhandled error opens one), or it was still running at its limit and was ended; its output, exit code, fault and @@ -717,6 +719,7 @@ if (values.json) { arch, lines: shown, returned, + stopped: capture.stopped, licence, llvmCache: leaseReport(llvmLease), exeRun, @@ -740,6 +743,13 @@ if (values.json) { // Only under --keep, where the pid is still alive and therefore actionable. if (values.keep && ideRun?.pid) for (const l of keptIdeLines(ideRun.pid)) console.log(l); } +// An error the probe does not handle stops it at the IDE's error panel, which waits for a +// person, so the probe never returns; the panel's text says why (captureRun). +if (capture.stopped && returned !== true) { + const s = capture.stopped; + const where = s.file ? `, in ${s.file}${s.line === null ? "" : ` at line ${s.line}`}` : ""; + die(5, `tbrun: the probe stopped at a run-time error it does not handle${where}: ${s.header}: ${s.error}`); +} if (returned === false) { die( 5, From 7c3b0a87a8e5176623ff6fb80e7c5d2172525b11 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Tue, 3 Nov 2026 21:54:46 +0100 Subject: [PATCH 16/19] Item 17: BUGS entries single-division-by-zero-no-error (a division by zero stored in a Single raises nothing, VB6 and 64-bit VBA raise 11 or 6; in the WARNING shared with #2476 on Divide, Single and Operators) and let-object-result-stored (a function's object result assigned to a Variant without Set is stored where VB6 raises 450 or 438; WARNING and a paragraph on Let); 64-bit VBA prints inf and -0 as a 64-bit build does, so infinity-text-64-bit is withdrawn, the -0 WARNINGs on CStr, Str and Format become NOTEs, Double states both per build with probes double-text-win32 and double-text-win64, NOTE on Round, rule in WIP.md --- BUGS-TO-REPORT.md | 189 +++++++++--------- WIP.md | 2 +- bugs/filed/negative-zero-text-win64/REPORT.md | 2 +- .../infinity-text-64-bit.twinproj | Bin 5220 -> 0 bytes bugs/infinity-text-64-bit/repro.json | 18 -- .../src/Sources/RegressionTests.twin | 73 ------- .../src/Sources/Startup.twin | 6 - bugs/infinity-text-64-bit/vb6/Module1.bas | 48 ----- .../let-object-result-stored.twinproj | Bin 0 -> 4002 bytes bugs/let-object-result-stored/repro.json | 14 ++ .../src/Settings | 136 ++++++------- .../src/Sources/Startup.twin | 46 +++++ bugs/let-object-result-stored/vb6/Module1.bas | 44 ++++ .../vb6/NoDefault.cls | 12 ++ bugs/let-object-result-stored/vb6/Probe.vbp | 9 + .../vb6/WithDefault.cls | 15 ++ .../repro.json | 15 ++ .../single-division-by-zero-no-error.twinproj | Bin 0 -> 3650 bytes .../src/Settings | 61 ++++++ .../src/Sources/Startup.twin | 28 +++ .../vb6/Module1.bas | 38 ++++ .../vb6/Probe.vbp | 0 docs/Reference/Core/Divide.md | 4 +- docs/Reference/Core/Let.md | 15 ++ docs/Reference/Data-Types/Double.md | 55 ++++- docs/Reference/Data-Types/Single.md | 4 +- docs/Reference/Default/VBA/Conversion/CStr.md | 6 +- docs/Reference/Default/VBA/Conversion/Str.md | 6 +- docs/Reference/Default/VBA/Math/Round.md | 4 + docs/Reference/Default/VBA/Strings/Format.md | 6 +- docs/Reference/Operators.md | 4 +- 31 files changed, 530 insertions(+), 330 deletions(-) delete mode 100644 bugs/infinity-text-64-bit/infinity-text-64-bit.twinproj delete mode 100644 bugs/infinity-text-64-bit/repro.json delete mode 100644 bugs/infinity-text-64-bit/src/Sources/RegressionTests.twin delete mode 100644 bugs/infinity-text-64-bit/src/Sources/Startup.twin delete mode 100644 bugs/infinity-text-64-bit/vb6/Module1.bas create mode 100644 bugs/let-object-result-stored/let-object-result-stored.twinproj create mode 100644 bugs/let-object-result-stored/repro.json rename bugs/{infinity-text-64-bit => let-object-result-stored}/src/Settings (71%) create mode 100644 bugs/let-object-result-stored/src/Sources/Startup.twin create mode 100644 bugs/let-object-result-stored/vb6/Module1.bas create mode 100644 bugs/let-object-result-stored/vb6/NoDefault.cls create mode 100644 bugs/let-object-result-stored/vb6/Probe.vbp create mode 100644 bugs/let-object-result-stored/vb6/WithDefault.cls create mode 100644 bugs/single-division-by-zero-no-error/repro.json create mode 100644 bugs/single-division-by-zero-no-error/single-division-by-zero-no-error.twinproj create mode 100644 bugs/single-division-by-zero-no-error/src/Settings create mode 100644 bugs/single-division-by-zero-no-error/src/Sources/Startup.twin create mode 100644 bugs/single-division-by-zero-no-error/vb6/Module1.bas rename bugs/{infinity-text-64-bit => single-division-by-zero-no-error}/vb6/Probe.vbp (100%) diff --git a/BUGS-TO-REPORT.md b/BUGS-TO-REPORT.md index cb322f055..6f91561c9 100644 --- a/BUGS-TO-REPORT.md +++ b/BUGS-TO-REPORT.md @@ -6021,95 +6021,6 @@ BETA 1007 gives the same results. Severity: low. Few programs write it, but the --- -## A 64-bit build prints infinity and NaN as `inf` and `nan`, and `Format` adds the picture's decimals - -**Describe the bug** -A `Double` or `Single` that holds infinity or NaN prints in VB6 as `1.#INF`, `-1.#INF`, `1.#QNAN` and `-1.#IND`, and `Format(x, "0.00")` gives `1.#INF` or `1.#R`. A 32-bit twinBASIC build prints exactly those. A 64-bit build prints the C runtime's text: `inf`, `-inf`, `nan` and `-nan(ind)`, and `Format(x, "0.00")` gives `inf.00` and `nan.00`. `CStr`, `Str`, concatenation and `Debug.Print` all give the same text. Arithmetic raises an error rather than make these values, so they come from memory, a COM component or a file; the reproducer copies their bytes into a variable. It was observed in IDE runs and in built exes. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `infinity-text-64-bit.twinproj` (attached as `infinity-text-64-bit.zip`). Its module `InfinityTextTests`, below, has one `[TestCase]` for each text, asserting what VB6 gives, and a control. -2. Run the test cases as a 64-bit build (`tbrun --tests --arch win64`). The first eight fail, with the 64-bit text as `Actual`; the control passes. As a 32-bit build all nine pass. -```vb -[TestFixture] -Module InfinityTextTests - - Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Any, ByRef Source As Any, ByVal Length As LongPtr) - - Private Function DoubleFromBits(ByVal Bits As LongLong) As Double - Dim result As Double - CopyMemory result, Bits, 8 - Return result - End Function - - Private Function SingleFromBits(ByVal Bits As Long) As Single - Dim result As Single - CopyMemory result, Bits, 4 - Return result - End Function - - [TestCase] - Public Sub CStrOfInfinity_Is1HashINF() - Assert.Strict.AreEqual "1.#INF", CStr(DoubleFromBits(&H7FF0000000000000^)) - End Sub - - [TestCase] - Public Sub CStrOfNegativeInfinity_IsMinus1HashINF() - Assert.Strict.AreEqual "-1.#INF", CStr(DoubleFromBits(&HFFF0000000000000^)) - End Sub - - [TestCase] - Public Sub CStrOfNaN_Is1HashQNAN() - Assert.Strict.AreEqual "1.#QNAN", CStr(DoubleFromBits(&H7FF8000000000000^)) - End Sub - - [TestCase] - Public Sub CStrOfIndeterminate_IsMinus1HashIND() - Assert.Strict.AreEqual "-1.#IND", CStr(DoubleFromBits(&HFFF8000000000000^)) - End Sub - - [TestCase] - Public Sub ConcatenatedInfinity_Is1HashINF() - Assert.Strict.AreEqual "x=1.#INF", "x=" & DoubleFromBits(&H7FF0000000000000^) - End Sub - - [TestCase] - Public Sub FormatOfInfinity_Is1HashINF() - Assert.Strict.AreEqual "1.#INF", Format(DoubleFromBits(&H7FF0000000000000^), "0.00") - End Sub - - [TestCase] - Public Sub FormatOfNaNWithPicture_Is1HashR() - Assert.Strict.AreEqual "1.#R", Format(DoubleFromBits(&H7FF8000000000000^), "0.00") - End Sub - - [TestCase] - Public Sub CStrOfSingleInfinity_Is1HashINF() - Assert.Strict.AreEqual "1.#INF", CStr(SingleFromBits(&H7F800000)) - End Sub - - [TestCase] - Public Sub CStrOfOrdinaryDouble_IsUnchanged() - Assert.Strict.AreEqual "1.5", CStr(1.5) - End Sub - -End Module -``` - -**Expected behavior** -The same text in a 64-bit build as in a 32-bit one and in VB6: `1.#INF`, `-1.#INF`, `1.#QNAN`, `-1.#IND`, and `1.#INF` or `1.#R` from `Format` with a picture. The reproducer's `vb6/` project prints them (attached as `infinity-text-64-bit-vb6.zip`). - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1010 - -**Additional context** -BETA 1007 gives the same results. The reproducer's `repro.json` names the 64-bit build (`"arch": "win64"`). Severity: low. A program meets these values rarely, but one that tests or parses the text, or shows `Format(x, "0.00")` of one, behaves differently in the two builds. - - - ---- - ## In a 32-bit build, a constant `Double` expression raises error 6 after a constant that overflows a `Long` or `LongLong` **Describe the bug** @@ -11639,3 +11550,103 @@ Severity: medium. A wrong `Declare` is the programmer's mistake, but nothing poi +--- + +## A division by zero whose result is a `Single` raises no error, where VB6 raises error 11 + +**Describe the bug** +A division by zero whose result is stored in a declared `Single` raises no error: `s = one / zero`, where all three are declared `Single`, leaves `Err.Number` at 0 and `s` at `1.#INF`, and `zero / zero` leaves `s` at `-1.#IND`. VB6 raises error 11, *Division by zero*, and error 6, *Overflow*, for zero divided by zero. It does not depend on the operands: a `Double` or a `Long` divided by zero and stored in a `Single` raises nothing either, and neither does `/=`, nor a comparison of a `Single` quotient, `If one / zero > 0 Then`. The same division stored in a `Double` raises 11, as in VB6. Observed in IDE runs and in built exes, 32-bit and 64-bit. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open `single-division-by-zero-no-error.twinproj` (attached as `single-division-by-zero-no-error.zip`). Its `Sub Main` runs each case under `On Error Resume Next`, for example: + ``` + Dim one As Single = 1! + Dim zero As Single = 0! + Dim s As Single + Dim n As Long + On Error Resume Next + s = 5!: Err.Clear: s = one / zero: n = Err.Number: Debug.Print "s = Single / 0 Err=" & n & " s=" & s + ``` +2. Run it (F5) and read the DEBUG CONSOLE: + ``` + s = Single / 0 Err=0 s=1.#INF + s = 0! / 0! Err=0 s=-1.#IND + s = Double / 0 Err=0 s=1.#INF + s = Long / 0 Err=0 s=1.#INF + s /= 0 Err=0 s=1.#INF + If Single / 0 > 0 Err=0 + d = Single / 0 Err=11 + d = Double / 0 Err=11 + d = 0# / 0# Err=6 + ``` + +**Expected behavior** +Error 11 for every division by zero, and error 6 for zero divided by zero, whatever the result is stored in, as in VB6. The VB6 project (attached as `single-division-by-zero-no-error-vb6.zip`) prints `Err=11` on every line but the two that divide zero by zero, which print `Err=6`; it has `s = s / 0` where twinBASIC has `s /= 0`. 64-bit VBA raises the same errors. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +BETA 1010 gives the same results. Compiled with LLVM (the whole project), every line raises 11 or 6, as in VB6, and the `Single` keeps its old value. A 64-bit build prints the values as `inf` and `-nan(ind)`, as 64-bit VBA does. + +Possibly the same cause as #2476, *A `Single` that overflows raises no error, where VB6 raises error 6*: in both, a `Single` is left holding infinity where a `Double` raises the error. This one is a division by zero, error 11, and it happens with `Double` and `Long` operands too. + +What matches VB6: `\` and `Mod` by zero raise 11 into a `Single`; a `Variant` target raises 11; VB6 also leaves infinity in the `Single` after the error, so only the error is missing. + +Severity: a silent wrong result. A division by zero that reaches an error handler in VB6 leaves infinity or NaN in the `Single` in twinBASIC, and the program goes on with it. + + + +--- + +## Assigning an object that a function returns to a `Variant` without `Set` stores the object when its default member cannot be read, where VB6 raises error 450 or 438 + +**Describe the bug** +An assignment of an object to a `Variant` without `Set` assigns the value of the object's default member. When the default member needs an argument, as a `Collection`'s `Item` does, VB6 raises error 450, *Wrong number of arguments or invalid property assignment*, and when the class has no default member, error 438, *Object doesn't support this property or method*; the `Variant` keeps its old value. twinBASIC raises an error when the object comes from a variable or from `New` of a twinBASIC class. When it comes from a function's result, from `New Collection` or from `CreateObject`, it raises no error and stores the object itself in the `Variant`, as `Set` would: after `v = NewCollection()`, where `NewCollection` returns a `Collection`, `TypeName(v)` is `Collection`. Observed in IDE runs and in built exes, 32-bit and 64-bit. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open `let-object-result-stored.twinproj` (attached as `let-object-result-stored.zip`). Its `Sub Main` makes each assignment under `On Error Resume Next` and prints `Err.Number` and `TypeName(v)`, for example: + ``` + v = Empty: Err.Clear: v = NewCollection(): Show "v = NewCollection() ", Err.Number, v + ``` + `NoDefault` is a class with no default member, `WithDefault` one whose default member, `Value`, returns 42, and `c` is a `Collection` variable. +2. Run it (F5) and read the DEBUG CONSOLE: + ``` + v = New Collection Err=0 Collection + v = NewCollection() Err=0 Collection + v = CreateObject(Dict) Err=0 Dictionary + v = NewNoDefault() Err=0 NoDefault + v = c (a Collection) Err=-2147352561 Empty + v = New NoDefault Err=438 Empty + v = NewWithDefault() Err=0 Long + ``` + +**Expected behavior** +Error 450 for each `Collection` and the `Dictionary`, and error 438 for `NoDefault` however it is made, with the `Variant` left `Empty`, as in VB6. The VB6 project (attached as `let-object-result-stored-vb6.zip`) prints: +``` +v = New Collection Err=450 Empty +v = NewCollection() Err=450 Empty +v = CreateObject(Dict) Err=450 Empty +v = NewNoDefault() Err=438 Empty +v = c (a Collection) Err=450 Empty +v = New NoDefault Err=438 Empty +v = NewWithDefault() Err=0 Long +``` +64-bit VBA raises 450 for `New Collection`, a function's `Collection`, a `Collection` variable and `CreateObject("Scripting.Dictionary")` as well. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +BETA 1010 gives the same results. What matches VB6: a default member that needs no argument is read from a function's result as from a variable (`v = NewWithDefault()` assigns the `Long` 42), and `New` of a twinBASIC class with no default member raises 438. The same assignment to a `Long` or a `String` (`l = c`), or to a `Variant` from a parenthesised variable (`v = (c)`), is refused at compile time with TB5092, *Missing argument 'Index'*, where VB6 refuses it with *Argument not optional*. + +Where the object is read but the error differs: a `Collection` in a variable raises -2147352561 (`&H8002000F`, `DISP_E_PARAMNOTOPTIONAL`), *Parameter not optional.*, where VB6 raises 450, and a `Scripting.Dictionary` in an `Object` variable -2147352562 (`DISP_E_BADPARAMCOUNT`). An element of a `Variant` array as the target, `va(0) = NewCollection()`, raises -2147352561 rather than storing the object; VB6 raises 450. `New` of a twinBASIC class whose default member needs an argument raises -2147352561, and the same object returned by a function is stored. + +Severity: a silent different result. A missing `Set` that VB6 reports at once goes unnoticed: the `Variant` holds the object, and code that used it as a value fails later, far from the mistake, or works by accident. The same assignment also behaves differently depending on whether the object comes from a variable or a function. + + + diff --git a/WIP.md b/WIP.md index adb54a620..957d9f70a 100644 --- a/WIP.md +++ b/WIP.md @@ -246,7 +246,7 @@ The vocabulary tables further down cover word choice. The rules in this subsecti One callout per concern; don't stack a NOTE and an IMPORTANT for the same point. - **Where twinBASIC and VB6 behave differently, say what VB6 does** (owner's rule), checked with `scripts/vb6run.mjs` or a reproducer's `vb6/` side, never from memory. Where twinBASIC's behaviour is the nominal one, the VB6 behaviour goes in a `> [!NOTE]`; inside a defect's WARNING it is one sentence, such as "VB6 raises error 6, *Overflow*." + **Where twinBASIC and VB6 behave differently, say what VB6 does** (owner's rule), checked with `scripts/vb6run.mjs` or a reproducer's `vb6/` side, never from memory. Where twinBASIC's behaviour is the nominal one, the VB6 behaviour goes in a `> [!NOTE]`; inside a defect's WARNING it is one sentence, such as "VB6 raises error 6, *Overflow*." **A 64-bit build answers to 64-bit VBA, not to VB6** (owner, 2026-10-22): what 64-bit VBA also does is no defect, and the page says the 64-bit build does as 64-bit VBA does (the text of infinity and of negative zero, `inf` and `-0`, on Double.md; `scripts/lib/vba.mjs` runs a module in 64-bit Access). **A known twinBASIC defect that silently gives a wrong result is a `> [!WARNING]` until a fixed build is released** (owner's rule): a wrong value with no error raised is data corruption the reader cannot see. A crash is a WARNING too (unsaved data is lost), and so is an error that hides a silent part (a procedure run twice before the error). A defect the reader cannot miss and that loses nothing --- an error raised, a statement refused --- is a `> [!NOTE]`. Name the build in either, in its label (next paragraph). **Once a fixed build is released, the callout goes** (owner's rule): the documentation is not a record of past defects, so the page then states the correct behaviour with no mention of the defect. The defect's BUGS-TO-REPORT entry says in its HTML comment which page carries the callout, so the callout is removed when the entry is retired. diff --git a/bugs/filed/negative-zero-text-win64/REPORT.md b/bugs/filed/negative-zero-text-win64/REPORT.md index c088b6699..bfd553eee 100644 --- a/bugs/filed/negative-zero-text-win64/REPORT.md +++ b/bugs/filed/negative-zero-text-win64/REPORT.md @@ -27,4 +27,4 @@ The same text in a 64-bit build as in a 32-bit one and in VB6: `0` from `CStr`, **Additional context** The reproducer's `repro.json` names the 64-bit build (`"arch": "win64"`). A positive zero and an ordinary negative number (`-1.5`) print the same in both builds. Severity: low. A negative zero is the result of `-x` for a zero `x`, and a COM or API result can hold one, so a program meets it, and one that shows or compares the text behaves differently in the two builds. - + diff --git a/bugs/infinity-text-64-bit/infinity-text-64-bit.twinproj b/bugs/infinity-text-64-bit/infinity-text-64-bit.twinproj deleted file mode 100644 index be7ea645103a191013eefc2ea5a43cf1811ea27e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5220 zcmcIo+ioK_5>;kzu{VBLAp4dFLOaOVfLhSax)_^>)}<36w(ON1JAgF_sBTK)q(y2; zwnu{DpV)ucpWCn4DzaOaJa)!2$>^coO%|&TPhD8_SO5L%|31t8Etkvn#e@rPRu^n( z>a{jEFn0g@GrYdY{T-hD`9dngqKnmn9rLlGiZgvLn9KbMKVIba;CaGz$l{m^CX+20 zR0lGj!}E-WztHKniodw&9r!b3S|%#oo`2!yQSAKP*+1a)XSpx!>ZxK{b15#EHo9ou za$y&v0{s1FH3nK5`>8X_lzlCDg~F(Xoub1tUvW@glonT#V%c*+~j z4(OmrF5C42p&}x5Z}%0cR=hDc1P!mvKRRKuYo_X z%iUm?><3cK!Q5MJW`IQ`^D$GXdci}JC}ve@)HNg%2jZYed{VDj%hp~h16aYBeF`d^ z6?oa_B08{`QLQt#zbue^saPuI56BzIg|?KWCSPe1v5?Pctcesy$N@+e$px@>L*s;L zLIK#EMvRPCs5F#|6;OvtxPmdD8CDqy8@hoAqCjtfXf8qqD`BH^W_U;xsXR04oK6@yPsXGJT%NGGR4W1rNb+v;PjT#Q{!%4a=;x@Z{P9Q;(*gQ77u~8=k zfI(}4H19PG889kRC2qI}xtC#k60=L>U%_ngI;neW+jt+&K8d$>ui=?^ZMbwGnjuzf zJU{`0gs|>EH|lhfMb6KCUH*>h8Pt(|@DR%4&$lU11W_mwMPISrzY}mu-W8gBfplPM z|AFJa{;m-OrOn?@2dPfv6pUwdz8BtrO(D{5*aplKE|L$znpVXp!3Mt@SUSjP#OnJ@ z9#Qm3Y(Ql5cOkMbB4(JHW9BkIxyfSpIY`rEcdD zncKbHoqQ;vRhcHr_76oaW(?Xioj)S^E}Kv|z;(WWa%nqMq>IG`H!(wrEGE`hi)~Ts zhcjwsDEso(btCwFHyX9ETON&G-k!NOYXAWq5hu9H9f63rBu-ukpOS}Rh!oI$&fUr>s z+uA*kP{nP#+Gy09rH@642 z6>#$y1jQyyF=(jKN!Q?-9rWYQPCms3r&Fa=tCl=Grre{Gz2Q))RI58|uqF=esuFGz zJ~NP~$35aFM#A+3SxaXZKn!%Fu!+vmXmkaa3;9-$M&~Pib$V5=jYir;GG>*ciN~93 z>b1w#7&fm~9$lkX^RbNkmTIRm({{rY#=!}FBNgbZq`JRL7)XM;5VwnZ|Mq%`@D@;36o; zc-_m0`9#cv)o-oQ4%$p>UuN@p&M0rQ4t=8w`t9KjXFQn>t!Ay^mrGr**=jbtTCH7y zDh6*oztQOSf<{p8wmw-pxQk4-^-o2&y)9)%p2zP82YUr>flzPY@fv-wJ}Gs2?q0g0 z1!-YjcR%l1fKwNDki2*a#FO3PU5mY?O5pJg>uoj5+kctZ;&1KkiaNu>}q#$I5r_AXkllb|Rc zr(dq0qBFZT*h$oD?^Hj4cmD5hXJ^Jg|M|mTTZNwrg~Bmar_+zrQ|B~_12tAcQXlA> z=UaGvqh*KTQb2QuHo@0siU z;m-3WzBBA1{6Rz_mR_F!#_&;W{F3|C7SOi}KO$GTQpL-Px@TM~p&~vt(0xZ>-{hXD zg@_dy!@HOfmlL6(AdD+PTyhB;bh)aK%WjiM>iQ8;3z`VMi-=EWE?`DN8cwO)+b+EW z8$OXPi)cg|2UT*vpd57EREBU$!3ubC4fnQogVNR61f9BH2_5q;O_CNVN5j}&hak*Z;@VMqA z4%h4d=I;t}A?Gnec;Pd{OT1<%pY}VBr)0bk_vP;D(hbP5JEZ~QI#!bL`5rOklNhrK z>BquQ&*=ljPjp1lOa}cwT`6=5C-{ubW9jN9!}x+qXjIk_v63hwYY)VhQsB%{ahJ;rrUHrruTCO4 z>1oUY|IAej6yJF?{U6s)lSvnOlgZBGnK6yE?dkWO$By(COo6Xh(x=MK>%=%|lz{zAJsw|E-^s@@Zu5tKcYiPGK*83{%Px99zWZ9I(lZ61w?kIK-Ky4Gc4IK8+3j|xWgqt1&938g zdaX`7k0l5AQT5Y@DxmZU-hWs^t6}Pu7$lv+o;V;3%(bS=VO5kawD(nzeX^5xCebR}Q2zAaPKEPZ8CUr!_a*igGE5?0sA5A9X zRdjWF)oe^Ak@7`AYZVntGb+u-YbuI{_1bGH(JGvZV5mVh1`}I%RAPRP+>b(n#hM8! zr82ImRfzNnIXOTE2i0z?)~nh*r_;3?^+DfmH|zWM{(k$Q-D`9jhqYD;2#XvMF5yPV zWE2NqNeUob%tP35N2B3I5?PAcwR1W;8y@%1$yvwwrSqYGvl&>44H*a}KLixnlm$$! zNaBE$2SSF?ZU!?0bB16*ObJ*QBgkqDGeGfOD*_?-;W v*M`sdOl(#eYu7g=eMv1DbHzehi6i 0\\s+Err=0$", + "^d = Single / 0\\s+Err=11$", + "^d = 0# / 0#\\s+Err=6$" + ] + } +} diff --git a/bugs/single-division-by-zero-no-error/single-division-by-zero-no-error.twinproj b/bugs/single-division-by-zero-no-error/single-division-by-zero-no-error.twinproj new file mode 100644 index 0000000000000000000000000000000000000000..512f502b95d76fc675c1397766203ee4198c6e63 GIT binary patch literal 3650 zcmcIn+iu%N5S7!b)cCPa?ZZSiasfh_)Gd~Q6c8yY5bBFyyLM5_XeekNlSDbSV>SmP~_a_?Aaaa=YM|qcdhVKp-?#D;W%I&{+=r?LVNxPlVV@E zQVNOM#q}*dJ}Uf(_uh0SWW@aA`Hb!Iq4XqYYGGI?d_ymf3hQ{^<7&i$zzdm(b2bzX zZNA5Q-y8kqjq@SCOVmaDIb=%2a+KFUO7vuQ^r7~vHI%Ov9+NimWE9UT(IpSH6FJiL z7%O-NZSd31VK|4sv3Izf2*p4$6$cS;1s)_2f%Ldy3PJ&l(AfdzjV5sE%|Tq~Fg`-L zUU~*Rj0A{~y#z1xA(}Aghq%a(J6tv}?-=+8NrbdwjIfc9;1fY8 z$E}5V$0A5W?22#<7$EN-Wd5nz%&XO+{Jg?n>d{i3qwaO@SQ<@txHB@pQ;g-(kaeT%&`ieO^wzz+0e~afL?HRsO<8H6^w{ul)*1H8pNiejd78 zuUA`3v`g1;MO=v+hLxjF?_!BP@Cyb!p0Mx^i@*)=T*)(Q!H^uoFy0cuDBcnV3m+dV zX>8;wA<@c2CgY_%y8l~JJ(%Vbh(w7#N~DJq=@Vl$%2FnCgZ9Zrww#N-nPhVDOvdAr z4pe$fj@k7R&4e{QkB(Rr;YqHR$Hmu095vUaVtFLO3qFpe7vZ$5@NmK;k5qXF${lv$ z;mAl~>=25`NAc6YcNt^Q-#ys%*UNj-6&Ff^o(fiF{V3Tez*u3s1 z3HH4yyZLkTx-CZ&9%05rGuCx&$NbWMZHA zmKA!i>q0w@#8h*RYD47=k*N_bWoKtFIOU-)F4bUgG*_nwr_K6cpdw!ctXheJ;gUVP6DQ}-ZM$e+VkECi4m(wQJIv7T54jYH>Ak{(ruXbcC}*~ z9j9#@^;*|8TFsheSXQgi>eSoySJmwd5Uw0CogtTH`jLpaQX&vsd_=HQM|-{Fw6YAf zGv{Eh-`jN$q2G3XZNG8PfxZFl_EFCP1NjFO8X+C>fJbvk6TtJXkW=+y39|&|B|!pW zCZG>8g`B1`j!%30I)jmAPpy`LJh4+@3YXr~=$$Tm24V{B9ccz_4)8;ke7$3knsqPe zj@7Dm?OMxd)*aj{otACb^;fTqcB5fdyY+3i>p1^E=B*3W5_;2aD(gd2A2^h&Sf1dg>C!77;}&GJZ_g4v4==>xZ7JH>c}Z{5 jvW&xsPgx~a?6;7QgwOD=haL{epS(G`)7~rH=vnz6?B3N9 literal 0 HcmV?d00001 diff --git a/bugs/single-division-by-zero-no-error/src/Settings b/bugs/single-division-by-zero-no-error/src/Settings new file mode 100644 index 000000000..b74a21968 --- /dev/null +++ b/bugs/single-division-by-zero-no-error/src/Settings @@ -0,0 +1,61 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "SingleDivisionByZeroNoError", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Reproduces: A division by zero whose result is a `Single` raises no error, where VB6 raises error 11", + "project.exportPathIsV2": true, + "project.id": "{EDBBE61A-45B6-4016-8541-C9250AEC95B1}", + "project.name": "SingleDivisionByZeroNoError", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/bugs/single-division-by-zero-no-error/src/Sources/Startup.twin b/bugs/single-division-by-zero-no-error/src/Sources/Startup.twin new file mode 100644 index 000000000..c8b97f2b2 --- /dev/null +++ b/bugs/single-division-by-zero-no-error/src/Sources/Startup.twin @@ -0,0 +1,28 @@ +' A division by zero whose result is a Single raises no error: each way of dividing +' into one, and the Double results that raise error 11 as they should. +Module Startup + + Public Sub Main() + Dim one As Single = 1! + Dim zero As Single = 0! + Dim dOne As Double = 1# + Dim dZero As Double = 0# + Dim lOne As Long = 1 + Dim lZero As Long = 0 + Dim s As Single + Dim d As Double + Dim n As Long + On Error Resume Next + s = 5!: Err.Clear: s = one / zero: n = Err.Number: Debug.Print "s = Single / 0 Err=" & n & " s=" & s + s = 5!: Err.Clear: s = zero / zero: n = Err.Number: Debug.Print "s = 0! / 0! Err=" & n & " s=" & s + s = 5!: Err.Clear: s = dOne / dZero: n = Err.Number: Debug.Print "s = Double / 0 Err=" & n & " s=" & s + s = 5!: Err.Clear: s = lOne / lZero: n = Err.Number: Debug.Print "s = Long / 0 Err=" & n & " s=" & s + s = 5!: Err.Clear: s /= zero: n = Err.Number: Debug.Print "s /= 0 Err=" & n & " s=" & s + n = -1: Err.Clear: If one / zero > 0 Then n = Err.Number Else n = Err.Number + Debug.Print "If Single / 0 > 0 Err=" & n + d = 5#: Err.Clear: d = one / zero: n = Err.Number: Debug.Print "d = Single / 0 Err=" & n + d = 5#: Err.Clear: d = dOne / dZero: n = Err.Number: Debug.Print "d = Double / 0 Err=" & n + d = 5#: Err.Clear: d = dZero / dZero: n = Err.Number: Debug.Print "d = 0# / 0# Err=" & n + End Sub + +End Module diff --git a/bugs/single-division-by-zero-no-error/vb6/Module1.bas b/bugs/single-division-by-zero-no-error/vb6/Module1.bas new file mode 100644 index 000000000..8c74c016e --- /dev/null +++ b/bugs/single-division-by-zero-no-error/vb6/Module1.bas @@ -0,0 +1,38 @@ +Attribute VB_Name = "Module1" +Option Explicit + +' The VB6 side of single-division-by-zero-no-error: every division by zero raises +' error 11, and zero divided by zero error 6, whatever the type of the result. +' Build and run it with: node scripts/bug_repro.mjs vb6 single-division-by-zero-no-error + +Sub Main() + Dim one As Single + Dim zero As Single + Dim dOne As Double + Dim dZero As Double + Dim lOne As Long + Dim lZero As Long + Dim s As Single + Dim d As Double + Dim n As Long + On Error GoTo Fail + Open App.Path & "\out.txt" For Output As #9 + one = 1!: zero = 0!: dOne = 1#: dZero = 0#: lOne = 1: lZero = 0 + On Error Resume Next + s = 5!: Err.Clear: s = one / zero: n = Err.Number: Print #9, "s = Single / 0 Err=" & n & " s=" & s + s = 5!: Err.Clear: s = zero / zero: n = Err.Number: Print #9, "s = 0! / 0! Err=" & n & " s=" & s + s = 5!: Err.Clear: s = dOne / dZero: n = Err.Number: Print #9, "s = Double / 0 Err=" & n & " s=" & s + s = 5!: Err.Clear: s = lOne / lZero: n = Err.Number: Print #9, "s = Long / 0 Err=" & n & " s=" & s + s = 5!: Err.Clear: s = s / zero: n = Err.Number: Print #9, "s = s / 0 Err=" & n & " s=" & s + n = -1: Err.Clear: If one / zero > 0 Then n = Err.Number Else n = Err.Number + Print #9, "If Single / 0 > 0 Err=" & n + d = 5#: Err.Clear: d = one / zero: n = Err.Number: Print #9, "d = Single / 0 Err=" & n + d = 5#: Err.Clear: d = dOne / dZero: n = Err.Number: Print #9, "d = Double / 0 Err=" & n + d = 5#: Err.Clear: d = dZero / dZero: n = Err.Number: Print #9, "d = 0# / 0# Err=" & n + Close #9 + Exit Sub +Fail: + On Error Resume Next + Print #9, "unhandled error " & Err.Number & ": " & Err.Description + Close #9 +End Sub diff --git a/bugs/infinity-text-64-bit/vb6/Probe.vbp b/bugs/single-division-by-zero-no-error/vb6/Probe.vbp similarity index 100% rename from bugs/infinity-text-64-bit/vb6/Probe.vbp rename to bugs/single-division-by-zero-no-error/vb6/Probe.vbp diff --git a/docs/Reference/Core/Divide.md b/docs/Reference/Core/Divide.md index 080102518..3ab8ce8b3 100644 --- a/docs/Reference/Core/Divide.md +++ b/docs/Reference/Core/Divide.md @@ -36,9 +36,9 @@ A **Single** divided by a **Long**, **LongLong**, **Double**, **Currency** or ** Dividing by zero raises error 11, *Division by zero*, and zero divided by zero raises error 6, *Overflow*. Use [**\\**](IntegerDivide) for truncating-integer division and [**Mod**](Mod) for remainder. - + > [!WARNING] BETA 1034 -> A division whose result is a declared **Single** raises no error when the result overflows: the **Single** holds infinity (`1.#INF`) instead. VB6 raises error 6, *Overflow*. Compiled with LLVM, the division raises error 6 as it should. Declare the variable **Double** where an overflow must be caught. +> A division whose result is a declared **Single** raises no error when the result overflows: the **Single** holds infinity (`1.#INF`) instead. VB6 raises error 6, *Overflow*. A division by zero stored in a declared **Single** raises no error either, whatever the types of its operands: the **Single** holds infinity, or NaN (`-1.#IND`) for zero divided by zero, where VB6 raises error 11, *Division by zero*, or error 6. Nor does a comparison of a **Single** divided by zero, as in `If x / 0! > 0 Then`. Compiled with LLVM, the division raises each error as it should. Store the quotient in a **Double** where the error must be caught. > [!NOTE] > **Decimal** can be a declared type in twinBASIC, so dividing by or into a declared **Decimal** gives a declared **Decimal**. In VBA, **Decimal** exists only inside a **Variant**. diff --git a/docs/Reference/Core/Let.md b/docs/Reference/Core/Let.md index 458eaef43..73c6ab1be 100644 --- a/docs/Reference/Core/Let.md +++ b/docs/Reference/Core/Let.md @@ -29,6 +29,21 @@ Assigning an expression of one numeric type to a variable of a different numeric **Let** statements can be used to assign one record variable to another only when both variables are of the same user-defined type. Use the **LSet** statement to assign record variables of different user-defined types. Use the [**Set**](Set) statement to assign object references to variables. +An object assigned without **Set** gives the value of its default member: assigning a **StdFont** to a **Variant** assigns its **Name**. An object whose class has no default member raises error 438, *Object doesn't support this property or method*. A default member that needs an argument, such as the **Item** of a **Collection**, is refused at compile time when the variable is not a **Variant**: TB5092, *Missing argument 'Index'*. + +```tb check_run +Dim Fnt As New StdFont +Dim V As Variant +Fnt.Name = "Arial" +V = Fnt ' no Set: the value of the default member, Name +Debug.Print TypeName(V) ' String +Debug.Print V ' Arial +``` + + +> [!WARNING] BETA 1034 +> An object whose default member needs an argument, such as a **Collection** or a **Scripting.Dictionary**, assigned to a **Variant** without **Set**, is stored in the **Variant** with no error when it comes from a function, from `New Collection` or from **CreateObject**; so is an object of a class with no default member that a function returns. VB6 raises error 450, *Wrong number of arguments or invalid property assignment*, or error 438, and leaves the **Variant** as it was. The same object in a variable raises -2147352561, *Parameter not optional.*, where VB6 raises 450. Use **Set** to assign an object. + ### Example This example assigns the values of expressions to variables by using the explicit **Let** statement. diff --git a/docs/Reference/Data-Types/Double.md b/docs/Reference/Data-Types/Double.md index f8fa7e826..32eb8b7a2 100644 --- a/docs/Reference/Data-Types/Double.md +++ b/docs/Reference/Data-Types/Double.md @@ -75,11 +75,58 @@ An operation on two **Double** values gives a **Double**. A **Double** combined A result too large for the type raises error 6, *Overflow*: `1E+308 * 10` does, and so does `1E+308 + 1E+308`. Dividing by zero raises error 11, *Division by zero*; `0 / 0` raises error 6, and `Sqr(-1)` raises error 5, *Invalid procedure call or argument*. A result too small for the type is 0, with no error. A **Variant** holding a **Double** raises error 6 on overflow too, where a **Variant** holding a smaller type widens. -A **Double** can hold the IEEE values infinity and NaN, which can arrive from memory or from a COM component, but arithmetic never produces one: it raises an error instead. As in VB6, they print as `1.#INF`, `-1.#INF`, `1.#QNAN` and, for the indeterminate value, `-1.#IND`. A comparison with NaN does not follow the IEEE rules: `NaN = NaN` is True, as in VB6, and so is `NaN = 0`, so a comparison cannot tell a NaN from a number. +A **Double** can hold the IEEE values infinity and NaN, which can arrive from memory or from a COM component, but **Double** arithmetic never produces one: it raises an error instead. A comparison with NaN does not follow the IEEE rules: `NaN = NaN` is True, as in VB6, and so is `NaN = 0`, so a comparison cannot tell a NaN from a number. A **Double** can also hold negative zero, which `Round(-0.5)`, `Fix(-0.5)` and `-x` for an *x* of 0 give; it equals 0. - -> [!NOTE] BETA 1034 -> A 64-bit build prints infinity and NaN as `inf`, `-inf`, `nan` and `-nan(ind)`, and **Format** with a number picture such as `"0.00"` gives `inf.00` and `nan.00`. A 32-bit build and VB6 print `1.#INF` and `1.#QNAN`, and **Format** gives `1.#INF` and `1.#R`. Do not test or parse the text of these values. + +How these values print depends on the build. A 32-bit build prints them as VB6 does: infinity and NaN as `1.#INF`, `-1.#INF`, `1.#QNAN` and, for the indeterminate value, `-1.#IND`, and negative zero as `0`; **Format** with a number picture such as `"0.00"` gives `1.#INF` and `1.#R`. A 64-bit build prints them as 64-bit VBA does: `inf`, `-inf`, `nan`, `-nan(ind)` and `-0`; **Format** gives `inf.00` and `nan.00`. Do not test or parse the text of these values. + + +```tb hidden slot=module +' Context for the probes below: a Double made from its eight bytes. +Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Any, ByRef Source As Any, ByVal Length As LongPtr) + +Public Function DoubleFromBits(ByVal Bits As LongLong) As Double + Dim Result As Double + CopyMemory Result, Bits, 8 + DoubleFromBits = Result +End Function +``` + +```tb probe check_run id=double-text-win32 +Dim Zero As Double, ZeroS As Single +Zero = -Zero +ZeroS = -ZeroS +Debug.Print CStr(Zero) ' 0 +Debug.Print "[" & Str(Zero) & "]" ' [ 0] +Debug.Print Format(Zero) ' 0 +Debug.Print "x" & Zero ' x0 +Debug.Print CStr(ZeroS) ' 0 +Debug.Print CStr(Round(-0.5)) ' 0 +Debug.Print CStr(DoubleFromBits(&H7FF0000000000000^)) ' 1.#INF +Debug.Print CStr(DoubleFromBits(&HFFF0000000000000^)) ' -1.#INF +Debug.Print CStr(DoubleFromBits(&H7FF8000000000000^)) ' 1.#QNAN +Debug.Print CStr(DoubleFromBits(&HFFF8000000000000^)) ' -1.#IND +Debug.Print Format(DoubleFromBits(&H7FF0000000000000^), "0.00") ' 1.#INF +Debug.Print Format(DoubleFromBits(&H7FF8000000000000^), "0.00") ' 1.#R +``` + +```tb probe check_run id=double-text-win64 arch=win64 +Dim Zero As Double, ZeroS As Single +Zero = -Zero +ZeroS = -ZeroS +Debug.Print CStr(Zero) ' -0 +Debug.Print "[" & Str(Zero) & "]" ' [-0] +Debug.Print Format(Zero) ' -0 +Debug.Print "x" & Zero ' x-0 +Debug.Print CStr(ZeroS) ' -0 +Debug.Print CStr(Round(-0.5)) ' -0 +Debug.Print CStr(DoubleFromBits(&H7FF0000000000000^)) ' inf +Debug.Print CStr(DoubleFromBits(&HFFF0000000000000^)) ' -inf +Debug.Print CStr(DoubleFromBits(&H7FF8000000000000^)) ' nan +Debug.Print CStr(DoubleFromBits(&HFFF8000000000000^)) ' -nan(ind) +Debug.Print Format(DoubleFromBits(&H7FF0000000000000^), "0.00") ' inf.00 +Debug.Print Format(DoubleFromBits(&H7FF8000000000000^), "0.00") ' nan.00 +``` ```tb check_run Dim Value As Double = 1E+308 diff --git a/docs/Reference/Data-Types/Single.md b/docs/Reference/Data-Types/Single.md index 7493e2804..47493688a 100644 --- a/docs/Reference/Data-Types/Single.md +++ b/docs/Reference/Data-Types/Single.md @@ -91,9 +91,9 @@ Debug.Print 0.1! + 0.2! = 0.3! ' True Debug.Print 0.1 + 0.2 = 0.3 ' False ``` - + > [!WARNING] BETA 1034 -> An addition, subtraction, multiplication or division whose result is a declared **Single** raises no error when the result overflows: the **Single** holds infinity (`1.#INF`) instead. VB6 raises error 6, *Overflow*. Declare the variable **Double** where an overflow must be caught. Code compiled with LLVM has a similar defect for **Double**; see [Language support](../../LLVM/Getting-Started#language-support). +> An addition, subtraction, multiplication or division whose result is a declared **Single** raises no error when the result overflows: the **Single** holds infinity (`1.#INF`) instead. VB6 raises error 6, *Overflow*. A division by zero stored in a declared **Single** raises no error either, whatever the types of its operands: the **Single** holds infinity, or NaN (`-1.#IND`) for zero divided by zero, where VB6 raises error 11, *Division by zero*, or error 6. Declare the variable **Double** where the error must be caught. Code compiled with LLVM has a similar defect for **Double**; see [Language support](../../LLVM/Getting-Started#language-support). A **Variant** holding a **Single** does not overflow: a result that does not fit becomes a **Double**. diff --git a/docs/Reference/Default/VBA/Conversion/CStr.md b/docs/Reference/Default/VBA/Conversion/CStr.md index 24b3848fa..39d7fbd82 100644 --- a/docs/Reference/Default/VBA/Conversion/CStr.md +++ b/docs/Reference/Default/VBA/Conversion/CStr.md @@ -28,9 +28,9 @@ The return type is **String**. The result depends on the type of *expression*: **CStr** is the internationally aware alternative to [**Str**](Str) for converting a number to a string. **CStr** recognizes different decimal separators properly, depending on the system's locale setting. - -> [!WARNING] BETA 1034 -> A 64-bit build converts a negative zero of type **Single** or **Double** (a value that equals 0 and has its sign bit set) to `"-0"`. A 32-bit build and VB6 give `"0"`. The same happens when such a value is joined to a string with `&`. + +> [!NOTE] +> A 64-bit build converts a negative zero of type **Single** or **Double** (a value that equals 0 and has its sign bit set) to `"-0"`, as 64-bit VBA does. A 32-bit build gives `"0"`, as VB6 does. The same happens when such a value is joined to a string with `&`. ### Common Errors diff --git a/docs/Reference/Default/VBA/Conversion/Str.md b/docs/Reference/Default/VBA/Conversion/Str.md index eaa933244..f18400076 100644 --- a/docs/Reference/Default/VBA/Conversion/Str.md +++ b/docs/Reference/Default/VBA/Conversion/Str.md @@ -25,9 +25,9 @@ When numbers are converted to strings, a leading space is always reserved for th Use the [**Format**](../Strings/Format) function to convert numeric values to be formatted as dates, times, or currency, or in other user-defined formats. Unlike **Str**, the **Format** function doesn't include a leading space for the sign of *number*. - -> [!WARNING] BETA 1034 -> A 64-bit build converts a negative zero of type **Single** or **Double** (a value that equals 0 and has its sign bit set) to `"-0"`, with no leading space. A 32-bit build and VB6 give `" 0"`. + +> [!NOTE] +> A 64-bit build converts a negative zero of type **Single** or **Double** (a value that equals 0 and has its sign bit set) to `"-0"`, with no leading space, as 64-bit VBA does. A 32-bit build gives `" 0"`, as VB6 does. > [!NOTE] > The **Str** function recognizes only the period (`.`) as a valid decimal separator. When different decimal separators may be used (for example, in international applications), use [**CStr**](CStr) to convert a number to a string. diff --git a/docs/Reference/Default/VBA/Math/Round.md b/docs/Reference/Default/VBA/Math/Round.md index d8e24aca7..23f7db692 100644 --- a/docs/Reference/Default/VBA/Math/Round.md +++ b/docs/Reference/Default/VBA/Math/Round.md @@ -20,6 +20,10 @@ Syntax: **Round(** *expression* [ **,** *numdecimalplaces* ] **)** > [!NOTE] > **Round** uses banker's rounding (round half to even): when the value is exactly half-way between two possible rounded results, it rounds toward the nearest even digit. For example, `Round(0.5, 0)` returns `0`, and `Round(1.5, 0)` returns `2`. + +> [!NOTE] +> `Round(-0.5)` returns negative zero, which equals 0. A 32-bit build prints it as `0`, as VB6 does, and a 64-bit build as `-0`, as 64-bit VBA does. See [Double](../../Core/Double#arithmetic-and-overflow). + ### Common Errors diff --git a/docs/Reference/Default/VBA/Strings/Format.md b/docs/Reference/Default/VBA/Strings/Format.md index bff106756..dcda5996c 100644 --- a/docs/Reference/Default/VBA/Strings/Format.md +++ b/docs/Reference/Default/VBA/Strings/Format.md @@ -58,9 +58,9 @@ The *firstWeekOfYear* argument has these settings. When formatting a number without specifying *format*, **Format** provides functionality similar to the **Str** function, although it is internationally aware. However, positive numbers formatted as strings using **Format** don't include a leading space reserved for the sign of the value; those converted using **Str** retain the leading space. - -> [!WARNING] BETA 1034 -> A 64-bit build formats a negative zero of type **Single** or **Double** (a value that equals 0 and has its sign bit set) as `"-0"` when *format* is omitted. A 32-bit build and VB6 give `"0"`. + +> [!NOTE] +> A 64-bit build formats a negative zero of type **Single** or **Double** (a value that equals 0 and has its sign bit set) as `"-0"` when *format* is omitted, as 64-bit VBA does. A 32-bit build gives `"0"`, as VB6 does. When formatting a non-localized numeric string, use a user-defined numeric format to ensure the desired appearance. diff --git a/docs/Reference/Operators.md b/docs/Reference/Operators.md index 6c6237416..1c64823d4 100644 --- a/docs/Reference/Operators.md +++ b/docs/Reference/Operators.md @@ -147,9 +147,9 @@ A result that does not fit its type raises error 6, *Overflow*. Some combination | **Date** `+` **Integer** | **Date** | | **String** `+` **Integer** | **Double**: `"34" + 6` is 40 | - + > [!WARNING] BETA 1034 -> An addition, subtraction, multiplication or division whose result is a declared **Single** raises no error when the result overflows: the **Single** holds infinity (`1.#INF`) instead. VB6 raises error 6, *Overflow*. Declare the variable **Double** where an overflow must be caught. Code compiled with LLVM has a similar defect for **Double**; see [Language support](../LLVM/Getting-Started#language-support). +> An addition, subtraction, multiplication or division whose result is a declared **Single** raises no error when the result overflows: the **Single** holds infinity (`1.#INF`) instead. VB6 raises error 6, *Overflow*. A division by zero stored in a declared **Single** raises no error either, whatever the types of its operands: the **Single** holds infinity, or NaN (`-1.#IND`) for zero divided by zero, where VB6 raises error 11, *Division by zero*, or error 6. Declare the variable **Double** where the error must be caught. Code compiled with LLVM has a similar defect for **Double**; see [Language support](../LLVM/Getting-Started#language-support). ### Variant operands From aa1d6d328dbb6f6ac84513b1a8d09326f1a9390a Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Tue, 3 Nov 2026 22:10:14 +0100 Subject: [PATCH 17/19] Item 18, first part: 12 callouts lose test: none. GdiPlus and Generics name their reproducers or samples; a compile case can name a row of a package's own source, so ArrayListLib, WinReg (compile/ alone) and TBMANLIB's win64 build check their does-not-compile callouts through package_tests --- WIP.Harness.md | 4 +- WIP.PackageTests.md | 7 +++ WIP.md | 2 +- docs/Documentation/Tools-Compiler.md | 2 +- .../Contributed/ArrayListLib/index.md | 2 +- docs/Reference/Contributed/GdiPlus/Image.md | 2 +- docs/Reference/Contributed/GdiPlus/Matrix.md | 2 +- .../Contributed/GdiPlus/Types/GpPointF.md | 6 +- .../Contributed/GdiPlus/Types/GpRect.md | 2 +- .../Contributed/GdiPlus/Types/GpRectF.md | 2 +- .../Contributed/GdiPlus/Types/GpSizeF.md | 4 +- .../GdiPlus/Types/PathPointType.md | 2 +- docs/Reference/Contributed/Generics/index.md | 2 +- docs/Reference/Contributed/TBMANLIB/index.md | 2 +- docs/Reference/Contributed/WinReg/index.md | 2 +- lib/package-tests.mjs | 13 ++++ scripts/check_callout_evidence.mjs | 7 ++- scripts/check_package_tests.mjs | 61 +++++++++++++++++++ scripts/lib/tb-tests.mjs | 36 +++++++++-- scripts/package_tests.mjs | 31 +++++++--- .../ArrayListLib/compile/PackageTests.twin | 11 ++++ test/package-tests/README.md | 6 ++ .../TBMANLIB/compile/CompileTests.twin | 3 + .../WinReg/compile/PackageTests.twin | 11 ++++ 24 files changed, 187 insertions(+), 35 deletions(-) create mode 100644 test/package-tests/ArrayListLib/compile/PackageTests.twin create mode 100644 test/package-tests/WinReg/compile/PackageTests.twin diff --git a/WIP.Harness.md b/WIP.Harness.md index c79460a2f..481f0d6a0 100644 --- a/WIP.Harness.md +++ b/WIP.Harness.md @@ -795,7 +795,9 @@ an `Integer` and `Err.Number` a `Long`. The tests here use `Assert.Strict`. **A compile-time defect is a compile case**: a line ending `' CASE : `, `: error` or `: none`, judged against the diagnostics of the compile (`judgeCompiles`). `error` is for a defect whose fix may choose its own diagnostic, so the case does not fail on a code it -cannot predict. A project with compile errors runs none of its `[TestCase]`s. +cannot predict. A project with compile errors runs none of its `[TestCase]`s. A comment +`' CASE : package :` names a row of an embedded package's sources, since +a package that does not compile has no line of the project's to end in a comment. ### Measuring LLVM diff --git a/WIP.PackageTests.md b/WIP.PackageTests.md index 00939a5c2..d974660c0 100644 --- a/WIP.PackageTests.md +++ b/WIP.PackageTests.md @@ -96,6 +96,13 @@ package embedded and judges with the rest. The published project stays `Sources/ compiles, so the author's Sub Main still runs every run-time case; the compile cases are the documentation's own re-check on each new build and version. +"This package does not compile" is a claim of the same kind, and it is true of the package's own +source, where no line can carry a comment of ours. So a compile case can name a row instead, with +`package :`, which pins the package's version as well as the build. ArrayListLib +and WinReg stay in `PENDING` (no test project until a version compiles, owner) with `compile/` +alone, which `check_package_tests.mjs` checks both ways: each case named by a marker, each marker +naming a case. The published project, and so `Sources/`, stays out of it. + ## Markers, and a gate that only tightens A callout names its cases in an HTML comment on the line above it. The marker lives with the diff --git a/WIP.md b/WIP.md index 957d9f70a..45db6315a 100644 --- a/WIP.md +++ b/WIP.md @@ -85,7 +85,7 @@ The rest of this file is the maintenance guide for updating existing pages or ad > would file the page as a *static file* and serve the raw markdown. Editors on > Windows add a BOM unasked. `lib/frontmatter.mjs` strips a leading BOM before parsing. - `docs/Reference/Built-In/tbIDE/` — IDE Extensibility package (this is the **addin SDK**). The package is type-only — it ships **public interfaces + CoClasses** that an addin DLL binds to; every implementation behind them lives in the twinBASIC IDE itself. The user-facing surface is one entry-point factory (`tbCreateCompilerAddin`) plus 23 CoClasses grouped by role: the addin contract (`AddIn`), the root API (`Host`), the loaded `Project`, the editors collection (`Editor` / `CodeEditor` / `Editors`), the virtual file system (`FileSystem` / `FileSystemItem` / `Folder` / `File`), the in-IDE UI surface (`Toolbar` / `Toolbars` / `Button` / `ToolWindow` / `ToolWindows`), the HTML DOM inside a tool window (`HtmlElement` / `HtmlElements` / `HtmlElementProperty` / `HtmlElementProperties` / `HtmlEventProperty` / `HtmlEventProperties`), the `DebugConsole`, `KeyboardShortcuts`, `Themes`, and the single concrete user-instantiable helper class `AddinTimer`. Flat layout — one page per CoClass / Class plus the index landing. -- `docs/Reference/Contributed/` — the contributed packages, one folder each, landing page `Contributed.md` beside it. **A package's landing page opens, under its H1, with `**Author:**`** (owner): the publisher TWINSERV lists (`publisher` in `indexer/.packages/manifests/contributed.json`), or the author the package's own sources name when that is someone else (VBA_JSON: Tim Hall, published by WaynePhillipsEA). Next to the author, on the same line, `**Version:**`: the version the section documents, which is its `indexed_from` and changes with it (owner). Then `**On GitHub:**` with a link, when a repository holds the package, or the library the package says it is made from; search GitHub for the package's name and the author's account, and check that the repository holds the package's code (its history too: `TBMANLIB_*` were never in woeoio's `tbman`). A landing page with one or two sections has no `* TOC` list. A class with member pages is a folder, `/index.md` at `/tB/Packages/Contributed///` with its members beside it, so a member links to its class as `(.)`; a class documented on one page is `.md`. Each package that is not Experimental is a chapter of the book's Packages part in `_book.yml`. A section's `indexed_from` is the four-part TWINSERV version, and its snapshot is under `indexer/snapshots/contributed/`. **Every NOTE and WARNING of a section is backed by test cases** in the package's test project, `test/package-tests//` (owner; [its README](test/package-tests/README.md)), and names them in a `` marker on the line above it, which `check_package_tests.mjs` in `test.bat` enforces. **A NOTE or WARNING is only for a real difference or limit** (owner): a statement that describes how the package works stays prose, tied to its cases by a region, the marker on a line of its own above it and `` after it (owner). A difference from the object the package replaces that gives a different result silently (tbRegExp's `\1(a)`, `{,m}`, `$~` against VBScript) is a NOTE, not a WARNING (owner). A case checks what is correct, not what the package does (owner): a defect is a failing case, recorded in the project's `expect.json`, so the project tells the package's author what to fix. A section whose project is not written yet is in that gate's `PENDING` set. **A package that does not compile on the newest BETA gets no test project** (owner: ArrayListLib, WinReg): it stays in `PENDING` until a version of it compiles. One that compiles for one architecture only is tested on that one (owner: TBMANLIB, win32; `arches` in its `expect.json`, test/package-tests/README.md). `package-tests.bat` runs the cases; a case it reports *fixed* means the callout goes. **The landing page ends, after See Also, with `## Test cases`**: one or two sentences linking to `-tests.twinproj` (owner), which the build publishes beside the section without the package's code in it (owner; one exception, owner: TBMANLIB_ConsoleComponents' project carries verbatim copies of seven of its component files, in `Sources/ComponentSources/`, because the package's own components cannot be given a console object, and its drawing can be checked only on the copies): it references the package as a linked package, which the reader puts in `%APPDATA%\twinBASIC\packages` ([WIP.PackageTests.md](WIP.PackageTests.md)). Its samples compile in the `contributed-` sample template, whose `Settings` names the package's version: **change that version with `indexed_from`** ([WIP.ExamplesBuild.md](WIP.ExamplesBuild.md#template-projects)). **A contributed package is documented and its samples compiled against the newest twinBASIC BETA installed, never an older one** (owner): the version that matters is the package's, and a statement about the compiler (such as "does not compile on BETA 1010") names that newest build. **A contributed package that does not compile on that build, or has defects that a program would meet, is listed under Experimental Packages** (owner); a minor defect that the section documents does not make it Experimental (owner: VBComDlg, whose Replace All raises no event, stays Contributed; owner: nor does a fixed limit that the section states at the top of its landing page; owner: tsJson 1.0.15.0 stays Contributed, its defects confirmed by a second agent and each stated in a WARNING with a way round it; owner: xCalendar 1.0.17.0 moved back to Contributed once its text colours worked on BETA 1034, its grid and edge-press defects stated in a WARNING). **A package moves from Contributed to Experimental for its defects only after a separate agent has reproduced each defect independently** and ruled out misuse, a wrong expectation, a twinBASIC defect and the harness (owner): `docs/Reference/Experimental.md`, at `/tB/Packages/Experimental/`, after Contributed Packages. Its landing page's `parent:` is `Experimental Packages`, its line is in `Experimental.md` and not in `Contributed.md`, and it has no chapter in the PDF book (owner: the book is for packages ready for use in a program): its entry is in `left_out:` of `docs/_book.yml`, in the list of Experimental packages, and the Experimental Packages page is left out with it. Its folder and URLs stay under `Contributed/`, so a package that is fixed moves back by changing only those three and the book (its entry leaves `left_out:` and its chapter is added after VBComDlg's), and its pages keep their addresses. A link from a book page to a left-out one opens the website. +- `docs/Reference/Contributed/` — the contributed packages, one folder each, landing page `Contributed.md` beside it. **A package's landing page opens, under its H1, with `**Author:**`** (owner): the publisher TWINSERV lists (`publisher` in `indexer/.packages/manifests/contributed.json`), or the author the package's own sources name when that is someone else (VBA_JSON: Tim Hall, published by WaynePhillipsEA). Next to the author, on the same line, `**Version:**`: the version the section documents, which is its `indexed_from` and changes with it (owner). Then `**On GitHub:**` with a link, when a repository holds the package, or the library the package says it is made from; search GitHub for the package's name and the author's account, and check that the repository holds the package's code (its history too: `TBMANLIB_*` were never in woeoio's `tbman`). A landing page with one or two sections has no `* TOC` list. A class with member pages is a folder, `/index.md` at `/tB/Packages/Contributed///` with its members beside it, so a member links to its class as `(.)`; a class documented on one page is `.md`. Each package that is not Experimental is a chapter of the book's Packages part in `_book.yml`. A section's `indexed_from` is the four-part TWINSERV version, and its snapshot is under `indexer/snapshots/contributed/`. **Every NOTE and WARNING of a section is backed by test cases** in the package's test project, `test/package-tests//` (owner; [its README](test/package-tests/README.md)), and names them in a `` marker on the line above it, which `check_package_tests.mjs` in `test.bat` enforces. **A NOTE or WARNING is only for a real difference or limit** (owner): a statement that describes how the package works stays prose, tied to its cases by a region, the marker on a line of its own above it and `` after it (owner). A difference from the object the package replaces that gives a different result silently (tbRegExp's `\1(a)`, `{,m}`, `$~` against VBScript) is a NOTE, not a WARNING (owner). A case checks what is correct, not what the package does (owner): a defect is a failing case, recorded in the project's `expect.json`, so the project tells the package's author what to fix. A section whose project is not written yet is in that gate's `PENDING` set. **A package that does not compile on the newest BETA gets no test project** (owner: ArrayListLib, WinReg): it stays in `PENDING` until a version of it compiles, and a `compile/` folder alone names the rows that refuse it, so its "does not compile" is checked. One that compiles for one architecture only is tested on that one (owner: TBMANLIB, win32; `arches` in its `expect.json`, test/package-tests/README.md), with a compile case for the other. `package-tests.bat` runs the cases; a case it reports *fixed* means the callout goes. **The landing page ends, after See Also, with `## Test cases`**: one or two sentences linking to `-tests.twinproj` (owner), which the build publishes beside the section without the package's code in it (owner; one exception, owner: TBMANLIB_ConsoleComponents' project carries verbatim copies of seven of its component files, in `Sources/ComponentSources/`, because the package's own components cannot be given a console object, and its drawing can be checked only on the copies): it references the package as a linked package, which the reader puts in `%APPDATA%\twinBASIC\packages` ([WIP.PackageTests.md](WIP.PackageTests.md)). Its samples compile in the `contributed-` sample template, whose `Settings` names the package's version: **change that version with `indexed_from`** ([WIP.ExamplesBuild.md](WIP.ExamplesBuild.md#template-projects)). **A contributed package is documented and its samples compiled against the newest twinBASIC BETA installed, never an older one** (owner): the version that matters is the package's, and a statement about the compiler (such as "does not compile on BETA 1010") names that newest build. **A contributed package that does not compile on that build, or has defects that a program would meet, is listed under Experimental Packages** (owner); a minor defect that the section documents does not make it Experimental (owner: VBComDlg, whose Replace All raises no event, stays Contributed; owner: nor does a fixed limit that the section states at the top of its landing page; owner: tsJson 1.0.15.0 stays Contributed, its defects confirmed by a second agent and each stated in a WARNING with a way round it; owner: xCalendar 1.0.17.0 moved back to Contributed once its text colours worked on BETA 1034, its grid and edge-press defects stated in a WARNING). **A package moves from Contributed to Experimental for its defects only after a separate agent has reproduced each defect independently** and ruled out misuse, a wrong expectation, a twinBASIC defect and the harness (owner): `docs/Reference/Experimental.md`, at `/tB/Packages/Experimental/`, after Contributed Packages. Its landing page's `parent:` is `Experimental Packages`, its line is in `Experimental.md` and not in `Contributed.md`, and it has no chapter in the PDF book (owner: the book is for packages ready for use in a program): its entry is in `left_out:` of `docs/_book.yml`, in the list of Experimental packages, and the Experimental Packages page is left out with it. Its folder and URLs stay under `Contributed/`, so a package that is fixed moves back by changing only those three and the book (its entry leaves `left_out:` and its chapter is added after VBComDlg's), and its pages keep their addresses. A link from a book page to a left-out one opens the website. - `docs/Downloads/index.md` — the Downloads page, `/Downloads`, a top-level page at `nav_order: 4` and in `_book.yml`'s `left_out:`. **Every file the documentation publishes for readers has a card or a row there**, and the page that documents it keeps its own link (owner). Cards are `
` inside `
` (styles at the end of `custom.scss`), with the theme's `.btn` for each file. The release job publishes every same-site link on the built page that has the `download` attribute, beside the zip and the PDF, so a new download needs no change to the workflow. `{{tbdocs:size:}}` gives a published file's size and `{{tbdocs:packageTestRows}}` the test projects' rows, both expanded before render; the help add-in's size is prose ("about 61 MB" since the pre-built search index, October 2026; it is also on the Help Add-In page, with the archive's "about 60 MB"), because its project embeds the offline tree, this page included, so it cannot be known before render. The PDF's release URL spells it `twinBASIC.Book.pdf`: GitHub turns the space into a dot, and the spaced name 404s. The footer's **Read Offline** link (`read_offline_link` in `_config.yml`) goes to the page's `#read-offline`. - `docs/Reference/Data-Types.md` and `docs/Reference/Data-Types/.md` — the intrinsic data types (owner, 2026-10-18): one page per type at `/tB/Core/` (`parent: Data Types`, `grand_parent: Reference Section`, since the Features page *New Data Types* has the same title), and the summary page, `/Reference/Data-Types`, with one `## ` section per type that links to it. The summary's `symbols:` names the types, and the symbol index points each at its section (kind `type`, the one URL outside `/tB/`), so keep those headings' text. `Long.md` is the model page. - `docs/Reference/Statements.md` — alphabetical index of language statements. diff --git a/docs/Documentation/Tools-Compiler.md b/docs/Documentation/Tools-Compiler.md index 26be457b3..65b33ff3a 100644 --- a/docs/Documentation/Tools-Compiler.md +++ b/docs/Documentation/Tools-Compiler.md @@ -181,7 +181,7 @@ lines follow the probe's output. **`--tests` runs the project's own test cases instead**, and builds nothing. A test case is a parameterless **Sub** marked [`[TestCase]`](../../tB/Core/Attributes#testcase) in a **Module** marked [`[TestFixture]`](../../tB/Core/Attributes#testfixture), checked with the [Assert package](../../tB/Packages/Assert/), in any `.twin` file under `Sources/`. `tbrun` runs each one in the compiler's test mode, where a failed assertion, an unhandled run-time error or an access violation ends only its case, and prints `PASS .`, or `FAIL .:` with the reason and the line it stopped on. The IDE has no way to run a test case. -A defect that is a compile-time diagnostic is a **compile case** instead: a line that ends in the comment `' CASE : TB5074` (one or more codes, separated by commas), `' CASE : error` or `' CASE : none`. It passes when the IDE reports each code named, any error, or nothing at all. Compile cases are judged first, and the project's diagnostics follow the case lines. A project with compile errors runs none of its test cases, and says so for each. [`bug_repro.mjs`](Tools-Defects#bug-repro) runs its `test` reproducers this way. +A defect that is a compile-time diagnostic is a **compile case** instead: a line that ends in the comment `' CASE : TB5074` (one or more codes, separated by commas), `' CASE : error` or `' CASE : none`. It passes when the IDE reports each code named, any error, or nothing at all. The comment `' CASE : package TB5001 Libs/cThing.twin:21` names a row of an embedded package's own source instead, for a package that does not compile; `win32` or `win64` before `package` is for [`package_tests.mjs`](Tools-Samples#package-tests), which runs such a case for that architecture alone. Compile cases are judged first, and the project's diagnostics follow the case lines. A project with compile errors runs none of its test cases, and says so for each. [`bug_repro.mjs`](Tools-Defects#bug-repro) runs its `test` reproducers this way. | Flag | Effect | |---|---| diff --git a/docs/Reference/Contributed/ArrayListLib/index.md b/docs/Reference/Contributed/ArrayListLib/index.md index ccd3cc661..af98598bb 100644 --- a/docs/Reference/Contributed/ArrayListLib/index.md +++ b/docs/Reference/Contributed/ArrayListLib/index.md @@ -19,7 +19,7 @@ The **ArrayListLib** contributed package is a list class for twinBASIC, in place This section documents version 1.6.151.1, which is the version on TWINSERV, published under the Unlicense. - + > [!NOTE] 1.6.151.1, BETA 1034 > The package does not compile, so a project that references it does not build (error TB5001, in a 32-bit and in a 64-bit build). **ArrayList** passes the addresses of two items, as **Long** values, to its private procedure **VSwap**, which declares both parameters as `ByRef` structures, in **Reverse** (`ArrayList.twin`, line 297) and in the two quicksort procedures that **Sort** calls (lines 585 and 602). **MemMoveBSTR**, a private procedure of the module **LibMemoryEx**, passes an address to **LSetByVal** in the same way (`Modules/LibMemoryEx.twin`, line 104), and **LSetByVal** declares its parameter as a `ByRef` structure that holds a string pointer. A pointer passed for a structure has to be passed `ByVal`. The behaviour described on these pages is that of the package with the two arguments of each **VSwap** call passed `ByVal`, and with **LSetByVal** taking the address as a **LongPtr** and putting it in a structure of its own. That copy builds in 32-bit and in 64-bit programs, and both give the same results. diff --git a/docs/Reference/Contributed/GdiPlus/Image.md b/docs/Reference/Contributed/GdiPlus/Image.md index 5a1b26549..f17f9d7d4 100644 --- a/docs/Reference/Contributed/GdiPlus/Image.md +++ b/docs/Reference/Contributed/GdiPlus/Image.md @@ -380,7 +380,7 @@ Syntax: *img*.**PropertyItem** **=** *item* The getter returns the property with the ID *propId* as an array of [**GpPropertyItem**](Types/GpPropertyItem) that holds one element, at index 1. The setter takes one **GpPropertyItem** and sets that property on the picture, adding it when it is not there. Its [**Id**](Types/GpPropertyItem), type, length and the address of its value must be filled in. - + > [!WARNING] BETA 1034 > A property of an element of the array that **PropertyItem** returns, such as `items(1).TheASCII`, stops the process with an access violation, as it does for an element of any array of a user-defined type. An element can be read as a whole or by a field: `items(1).Id` works, and so does `Dim got As GpPropertyItem = items(1)` followed by `got.TheASCII`. **GetAllPropertyItems** returns its array in the same way, and the same holds for it. The setter, **PropertyCount**, **PropertyIdList** and **RemovePropertyItem** work. diff --git a/docs/Reference/Contributed/GdiPlus/Matrix.md b/docs/Reference/Contributed/GdiPlus/Matrix.md index 4a59ca823..088f54a9a 100644 --- a/docs/Reference/Contributed/GdiPlus/Matrix.md +++ b/docs/Reference/Contributed/GdiPlus/Matrix.md @@ -217,7 +217,7 @@ Syntax: *matrix*.**Scale(** *sx*, *sy*, [ *order* ] **)** **As GpStatus** Combines a scaling with the matrix. - + > [!WARNING] BETA 1034 > A call of **Scale** written as a statement, with its arguments in parentheses and without **Call**, does nothing and raises no error: `m.Scale(2, 3)` leaves the matrix as it was. The same call written `m.Scale 2, 3`, `Call m.Scale(2, 3)` or `status = m.Scale(2, 3)` works. This is the case for a method of any class that is named **Scale**. diff --git a/docs/Reference/Contributed/GdiPlus/Types/GpPointF.md b/docs/Reference/Contributed/GdiPlus/Types/GpPointF.md index 71b8f3804..54a6724f4 100644 --- a/docs/Reference/Contributed/GdiPlus/Types/GpPointF.md +++ b/docs/Reference/Contributed/GdiPlus/Types/GpPointF.md @@ -85,7 +85,7 @@ Syntax: *point*.**Normalized** As **GpPointF** Returns the vector scaled to length 1. - + > [!NOTE] 0.9.45.0, BETA 1034 > A program that names **Normalized** does not link: the linker reports a code generation error in `Types.Normalized`. Use [**Normalize**](#normalize) on a copy of the point instead. @@ -154,11 +154,11 @@ These change the point they are called on. > [!NOTE] 0.9.45.0 > **Add** and **Sub** change the point they are called on. Before version 0.9.0.43 they returned the result and left the point alone. Use [**Sum**](#sum-diff) and [**Diff**](#sum-diff) to get a new point. - + > [!NOTE] 0.9.45.0, BETA 1034 > The compiler reads `p.Scale(2)` as the start of a `Scale` statement and reports a syntax error. Write `p.Scale 2` or `Call p.Scale(2)`. **Scale2**, **InvScale** and the other methods are not affected. > -> ```tb check_build expect-error=TB5212 +> ```tb check_build expect-error=TB5212 id=gppointf-scale-one-value > Dim p As GpPointF = GpPointF(3, 4) > p.Scale(2) > ``` diff --git a/docs/Reference/Contributed/GdiPlus/Types/GpRect.md b/docs/Reference/Contributed/GdiPlus/Types/GpRect.md index 05638f3aa..9c8d1acec 100644 --- a/docs/Reference/Contributed/GdiPlus/Types/GpRect.md +++ b/docs/Reference/Contributed/GdiPlus/Types/GpRect.md @@ -290,7 +290,7 @@ Syntax: *rect*.**InflatedByPt**( *point* As **GpPoint** ) As **GpRect** Return a copy of the rectangle moved or inflated as [**Offset**](#offset-offsetpt) and [**Inflate**](#inflate-inflatept) do. - + > [!NOTE] 0.9.45.0, BETA 1034 > A program that names any of these four methods does not link: the linker reports a code generation error in the method. Copy the rectangle and call **Offset**, **OffsetPt**, **Inflate** or **InflatePt** on the copy. diff --git a/docs/Reference/Contributed/GdiPlus/Types/GpRectF.md b/docs/Reference/Contributed/GdiPlus/Types/GpRectF.md index bc9d2a615..f663dfbe5 100644 --- a/docs/Reference/Contributed/GdiPlus/Types/GpRectF.md +++ b/docs/Reference/Contributed/GdiPlus/Types/GpRectF.md @@ -305,7 +305,7 @@ Syntax: *rect*.**InflatedByPt**( *point* As **GpPointF** ) As **GpRectF** Return a copy of the rectangle moved or inflated as [**Offset**](#offset-offsetpt) and [**Inflate**](#inflate-inflatept) do. - + > [!NOTE] 0.9.45.0, BETA 1034 > A program that names any of these four methods does not link: the linker reports a code generation error in the method. Copy the rectangle and call **Offset**, **OffsetPt**, **Inflate** or **InflatePt** on the copy. diff --git a/docs/Reference/Contributed/GdiPlus/Types/GpSizeF.md b/docs/Reference/Contributed/GdiPlus/Types/GpSizeF.md index 8dbb6cd02..298ec7889 100644 --- a/docs/Reference/Contributed/GdiPlus/Types/GpSizeF.md +++ b/docs/Reference/Contributed/GdiPlus/Types/GpSizeF.md @@ -141,11 +141,11 @@ These change the size they are called on. > [!NOTE] 0.9.45.0 > **Add** and **Sub** change the size they are called on. Before version 0.9.0.43 they returned the result and left the size alone. Use [**Sum**](#sum-diff) and [**Diff**](#sum-diff) to get a new size. - + > [!NOTE] 0.9.45.0, BETA 1034 > The compiler reads `s.Scale(2)` as the start of a `Scale` statement and reports a syntax error. Write `s.Scale 2` or `Call s.Scale(2)`. **Scale2**, **InvScale** and the other methods are not affected. > -> ```tb check_build expect-error=TB5212 +> ```tb check_build expect-error=TB5212 id=gpsizef-scale-one-value > Dim s As GpSizeF = GpSizeF(3, 4) > s.Scale(2) > ``` diff --git a/docs/Reference/Contributed/GdiPlus/Types/PathPointType.md b/docs/Reference/Contributed/GdiPlus/Types/PathPointType.md index 376b460db..cee94e36d 100644 --- a/docs/Reference/Contributed/GdiPlus/Types/PathPointType.md +++ b/docs/Reference/Contributed/GdiPlus/Types/PathPointType.md @@ -48,7 +48,7 @@ Debug.Print CLng(t.PointType) & " " & t.CloseSubpath & " " & t.PathMarker ' ``` - + > [!WARNING] BETA 1034 > Reading a property of an element of an array, as in `types(i).CloseSubpath`, stops the program with an access violation. Copy the element to a variable and read the property of that. A type held in a variable or in a field of a **TypedPoint** is read without trouble. diff --git a/docs/Reference/Contributed/Generics/index.md b/docs/Reference/Contributed/Generics/index.md index 957b0c659..68bc3c712 100644 --- a/docs/Reference/Contributed/Generics/index.md +++ b/docs/Reference/Contributed/Generics/index.md @@ -158,7 +158,7 @@ Three kinds of type cannot be used: > [!NOTE] 1.0.0.0 > The constructor stores each item with `=`, not with **Set**, so creating a tuple of an object type fails. For **Object**, which compiles, **New** raises run-time error 91, *Object variable or With block variable not set*, even when the item passed is **Nothing**. A class whose default member takes an argument, such as **Collection**, fails to compile, with TB5092 *Missing argument 'Index'* reported in the package's source. - + > [!WARNING] BETA 1034 > A tuple type with an array as a type argument, such as `TupleOf1(Of Long())`, crashes the compiler wherever it is written: in a declaration, a parameter, a function's result type or after **New**. The compiler restarts and crashes again, so nothing in the project compiles until the line is removed. The same happens with a generic class declared in a project's own code. diff --git a/docs/Reference/Contributed/TBMANLIB/index.md b/docs/Reference/Contributed/TBMANLIB/index.md index e6b3e9475..5af3af7d1 100644 --- a/docs/Reference/Contributed/TBMANLIB/index.md +++ b/docs/Reference/Contributed/TBMANLIB/index.md @@ -79,7 +79,7 @@ The references of a package are not visible to the project that uses it. A progr The package is built for 32-bit programs. The window procedure that **cSubClass** installs is passed to **SetWindowLong** as a **Long**, which a 64-bit program cannot hold. - + > [!WARNING] BETA 1034 > A project that embeds TBMANLIB and is built for 64-bit programs (**Win64**) does not compile. The compiler reports TB5001 in `cSubClass.twin`, *Validation of call to 'SetWindowLong' failed. Argument for 'dwNewLong': cannot coerce type 'Callback' to 'Long'*, even when the program uses nothing of the package. The package can be used in 32-bit programs only. diff --git a/docs/Reference/Contributed/WinReg/index.md b/docs/Reference/Contributed/WinReg/index.md index 14e623cf8..a9ca0cadc 100644 --- a/docs/Reference/Contributed/WinReg/index.md +++ b/docs/Reference/Contributed/WinReg/index.md @@ -15,7 +15,7 @@ The **WinReg** contributed package provides a Windows Registry helper class that The root keys, such as `HKEY_CURRENT_USER`, are members of **RegKeyspace**, so a project names them with **WinReg** alone. **WinReg** references the **WinDevLib** package, but a project does not see the names a package's own references declare. A project that names `BOOL` or `REGTYPES`, or calls `RegOpenKeyEx` or `RegCloseKey` and uses the `KEY_*` constants, as some examples here do, references **WinDevLib** as well. One that declares the **Dictionary** that [**EnumValues**](RegCls/EnumValues) returns references the Microsoft Scripting Runtime. - + > [!NOTE] 1.0.1.1, BETA 1034 > The package does not compile, so a project that references it does not build. **RegCls** declares `RegCreateKeyEx` and `RegEnumKeyEx` with a `ByRef` structure parameter, *lpSecurityAttributes* (`SECURITY_ATTRIBUTES`) and *lpftLastWriteTime* (`FILETIME`), and passes `VarPtr(0&)` for it in [**WriteString**](RegCls/WriteString), [**WriteDWORD**](RegCls/WriteDWORD), [**CreateKey**](RegCls/CreateKey) and [**DeleteKeyEx**](RegCls/DeleteKeyEx). A pointer passed for a structure has to be passed `ByVal` (error TB5001). `VarPtr(0&)` is also the address of a temporary **Long**, not a null pointer; `ByVal vbNullPtr` passes a null pointer. The behaviour described on these pages is that of the package with those four calls passed `ByVal`. diff --git a/lib/package-tests.mjs b/lib/package-tests.mjs index dc6888600..41fb9caed 100644 --- a/lib/package-tests.mjs +++ b/lib/package-tests.mjs @@ -56,6 +56,19 @@ export function packageTestNames(root) { .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)); } +/** + * The packages under `root` with compile cases and no test project, sorted: one that does not + * compile has no test project, and its compile/ alone says so. + */ +export function compileOnlyNames(root) { + if (!existsSync(root)) return []; + return readdirSync(root, { withFileTypes: true }) + .filter((d) => d.isDirectory() && existsSync(path.join(root, d.name, COMPILE_DIR))) + .filter((d) => !existsSync(path.join(root, d.name, "Sources"))) + .map((d) => d.name) + .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)); +} + /** The template's Settings, parsed. */ export function templateSettings(templatesDir, pkg) { const file = path.join(templatesDir, `contributed-${pkg}`, "Settings"); diff --git a/scripts/check_callout_evidence.mjs b/scripts/check_callout_evidence.mjs index e68efa3a7..943797d6b 100644 --- a/scripts/check_callout_evidence.mjs +++ b/scripts/check_callout_evidence.mjs @@ -30,7 +30,7 @@ import { readFileSync } from "node:fs"; import path from "node:path"; import { exitOnCrash, parseCli, printHelpAndExit, withUsageError } from "../lib/cli.mjs"; import { markdownFiles } from "../lib/markdown-files.mjs"; -import { packageTestNames } from "../lib/package-tests.mjs"; +import { compileOnlyNames, packageTestNames } from "../lib/package-tests.mjs"; import { DOCS_DIR, REPO_ROOT } from "../lib/repo-paths.mjs"; import { createProbes } from "./lib/gate-probes.mjs"; import { checkKey, packageCases, readVerify, relabelLine, sampleStatus, verdictOf } from "./lib/recheck.mjs"; @@ -281,6 +281,8 @@ const pages = await markdownFiles(DOCS_DIR); const fences = await collectFences(DOCS_DIR); const index = await evidenceIndex({ fences }); const projects = new Set(packageTestNames(path.join(REPO_ROOT, "test", "package-tests"))); +// A package that does not compile has compile cases and no project; the package gate checks those. +const compileOnly = new Set(compileOnlyNames(path.join(REPO_ROOT, "test", "package-tests"))); const OLD_BUG = /^[ \t]*[ \t]*$/m; const named = new Set(); @@ -320,7 +322,8 @@ for (const rel of pages) { if (kind === "sample") named.add(item.sample); if (PACKAGE_KINDS.has(kind)) { if (section === null) problems.push(`${where}: ${kind} items name a contributed package's test cases`); - else if (!packageGate) problems.push(`${where}: ${section} has no test project for a ${kind} item to name`); + else if (!packageGate && !(kind === "compile" && compileOnly.has(section))) + problems.push(`${where}: ${section} has no test project for a ${kind} item to name`); continue; } const problem = itemProblem(item, index); diff --git a/scripts/check_package_tests.mjs b/scripts/check_package_tests.mjs index 20b53a7c9..194c65cd1 100644 --- a/scripts/check_package_tests.mjs +++ b/scripts/check_package_tests.mjs @@ -105,6 +105,43 @@ const { check, report } = createProbes("check_package_tests", "see test/package- // ---------------------------------------------------------------- probes +{ + // A row of the package's own sources is not a row of the project's, and the reverse. + const body = [ + "Module M", + " ' CASE InThePackage: package TB5001 Libs/cThing.twin:21", + " ' CASE OnlyWin64: win64 package TB5001 cOther.twin:7", + " ' CASE OnAnotherLine: package TB5001 cOther.twin:8", + " ' CASE OtherCode: package TB5079 cOther.twin:7", + "End Module", + ].join("\n"); + const { compiles } = findCases([{ name: "M.twin", text: body }]); + check( + "a compile case that names a package's row takes its file, line and architecture from the comment", + compiles.length === 4 && + compiles[0].inPackage && + compiles[0].file === "Libs/cThing.twin" && + compiles[0].line === 21 && + !compiles[0].arch && + compiles[1].arch === "win64", + JSON.stringify(compiles), + ); + const judged = judgeCompiles(compiles, [ + "{ERROR} /Proj/Packages/Thing/Sources/Libs/cThing.twin [21,54]: TB5001 Validation of call", + "{ERROR} /Proj/Packages/Thing/Sources/cOther.twin [7,1]: TB5001 Validation of call", + ]); + check( + "a package's row passes on its file, line and code, and fails on any other", + judged.map((r) => r.pass).join() === "true,true,false,false", + JSON.stringify(judged), + ); + const [local] = judgeCompiles( + [{ name: "L", file: "cOther.twin", line: 7, codes: ["TB5001"], anyError: false }], + ["{ERROR} /Proj/Packages/Thing/Sources/cOther.twin [7,1]: TB5001 x"], + ); + check("a case of the project's own source is not met by a package's row", !local.pass, JSON.stringify(local)); +} + { // The compiler counts a lone CR as a line end (BETA 1010), so a compile case // is numbered the same whichever of the three ends the lines above it. @@ -291,6 +328,7 @@ for (const pkg of sections) { const has = projects.includes(pkg); if (PENDING.has(pkg)) { if (has) problems.push(`${pkg} has a test project: remove it from PENDING in scripts/check_package_tests.mjs`); + problems.push(...checkCompileOnly(pkg).map((p) => `${pkg}: ${p}`)); continue; } if (!has) { @@ -411,6 +449,29 @@ function checkPackage(pkg) { return out; } +// A package with no test project, which does not compile: its compile/ alone says so, each case +// named by a marker and each marker naming a case, and a marker names nothing else of a project. +function checkCompileOnly(pkg) { + const out = []; + const { tests, compiles, problems: bad } = projectCases(compileSources(ROOT, pkg)); + out.push(...bad.map((p) => `${COMPILE_DIR}/: ${p}`)); + if (tests.length) out.push(`${COMPILE_DIR}/ holds a [TestCase]: it holds compile cases only`); + const named = new Set(); + for (const rel of sectionPages(pkg)) { + for (const mk of markersOf(readFileSync(path.join(SECTIONS, pkg, rel), "utf8")).markers) { + const where = `${rel} line ${mk.line}`; + for (const item of mk.items.filter((i) => PACKAGE_KINDS.has(kindOf(i)))) { + if (!item.compile) + out.push(`${where}: the package has no test project, so a marker names no [TestCase] or probe`); + else if (!compiles.some((c) => c.name === item.compile)) out.push(`${where}: no compile case ${item.compile}`); + else named.add(item.compile); + } + } + } + for (const c of compiles) if (!named.has(c.name)) out.push(`the case ${c.name} is named by no marker of the section`); + return out; +} + // The section's pages, relative to its folder. function sectionPages(pkg) { const prefix = `${pkg}/`; diff --git a/scripts/lib/tb-tests.mjs b/scripts/lib/tb-tests.mjs index 0a58db2b4..bb543c92a 100644 --- a/scripts/lib/tb-tests.mjs +++ b/scripts/lib/tb-tests.mjs @@ -23,7 +23,12 @@ // `' CASE : error` or `' CASE : none`. It passes when the IDE // reports each code named on that line; for `error`, any error on it (for a // defect whose fix may choose its own diagnostic); for `none`, nothing at -// all on it. +// all on it. For a package that does not compile, a comment +// `' CASE : package TB5001 Libs/cThing.twin:21` names the row instead: +// a file under the package's Sources/ and a line, in any embedded package. +// `win32` or `win64` before `package` says the case holds for that +// architecture alone; scripts/package_tests.mjs runs it there, even one the +// package is not otherwise tested on. import { MODIFIERS } from "./twin-declarations.mjs"; @@ -35,6 +40,8 @@ const END_MODULE_RE = /^[ \t]*End[ \t]+Module\b/i; const SUB_RE = new RegExp(`^[ \\t]*(?:(?:${MODIFIERS})[ \\t]+)*(Sub|Function)[ \\t]+(\\w+)[ \\t]*(\\([^)]*\\))?`, "i"); const COMMENT_RE = /^[ \t]*(?:'|Rem\b)/i; const CASE_RE = /'[ \t]*CASE[ \t]+(\w+)[ \t]*:[ \t]*(none|error|TB\d+(?:[ \t]*,[ \t]*TB\d+)*)[ \t]*$/i; +const PACKAGE_CASE_RE = + /'[ \t]*CASE[ \t]+(\w+)[ \t]*:[ \t]*(?:(win32|win64)[ \t]+)?package[ \t]+(TB\d+)[ \t]+([\w./-]+):(\d+)[ \t]*$/i; // A diagnostic row as the IDE's PROBLEMS panel gives it to compileOutcome: // "{ERROR} /Project/Sources/Startup.twin [19,28]: TB5074 Cannot construct ...". const ROW_RE = /^\{(\w+)\}[ \t]+(\S+)[ \t]+\[(\d+),\d+\]:[ \t]+(TB\d+)/; @@ -42,6 +49,8 @@ const ROW_RE = /^\{(\w+)\}[ \t]+(\S+)[ \t]+\[(\d+),\d+\]:[ \t]+(TB\d+)/; // A row's file as findCases names it: its path under the project's Sources/ folder, in lower case, or // null for a file elsewhere, such as a package's. const underSources = (row) => /^\/[^/]+\/Sources\/(.+)$/i.exec(row)?.[1].toLowerCase() ?? null; +// The same under an embedded package's Sources/ folder. +const underPackage = (row) => /\/Packages\/[^/]+\/Sources\/(.+)$/i.exec(row)?.[1].toLowerCase() ?? null; const isOn = (arg) => arg === undefined || !/^\s*False\s*$/i.test(arg); @@ -51,10 +60,12 @@ const isOn = (arg) => arg === undefined || !/^\s*False\s*$/i.test(arg); * @param {{name: string, text: string}[]} files the .twin files, each named by its path under Sources/, such as * "Tests/Probe.twin" * @returns {{tests: {fixture: string, name: string, file: string, line: number}[], - * compiles: {name: string, file: string, line: number, codes: string[], anyError: boolean}[], + * compiles: {name: string, file: string, line: number, codes: string[], anyError: boolean, + * inPackage?: boolean, arch?: string}[], * problems: string[]}} - * `codes` is empty for `none` and for `error`, which sets `anyError`. `problems` names a [TestCase] the runner cannot - * call: one with parameters, a Function, or one outside a [TestFixture] module. + * `codes` is empty for `none` and for `error`, which sets `anyError`. `inPackage` says `file` and `line` are a row of + * an embedded package, and `arch` is the one architecture such a case holds for. `problems` names a [TestCase] the + * runner cannot call: one with parameters, a Function, or one outside a [TestFixture] module. */ export function findCases(files) { const tests = []; @@ -73,6 +84,18 @@ export function findCases(files) { const codes = what === "none" || what === "error" ? [] : what.split(",").map((c) => c.trim().toUpperCase()); compiles.push({ name: marker[1], file, line, codes, anyError: what === "error" }); } + const pkg = PACKAGE_CASE_RE.exec(raw); + if (pkg) { + compiles.push({ + name: pkg[1], + file: pkg[4], + line: Number(pkg[5]), + codes: [pkg[3].toUpperCase()], + anyError: false, + inPackage: true, + ...(pkg[2] ? { arch: pkg[2].toLowerCase() } : {}), + }); + } if (COMMENT_RE.test(raw) || !raw.trim()) return; let rest = raw; for (let m; (m = ATTRIBUTE_RE.exec(rest)); rest = rest.slice(m[0].length)) { @@ -109,8 +132,9 @@ export function findCases(files) { */ export function judgeCompiles(compiles, rows) { const reported = rows.map((r) => ROW_RE.exec(r)).filter(Boolean); - return compiles.map(({ name, file, line, codes, anyError }) => { - const rowsHere = reported.filter((m) => Number(m[3]) === line && underSources(m[2]) === file.toLowerCase()); + return compiles.map(({ name, file, line, codes, anyError, inPackage }) => { + const under = inPackage ? underPackage : underSources; + const rowsHere = reported.filter((m) => Number(m[3]) === line && under(m[2]) === file.toLowerCase()); const here = rowsHere.map((m) => m[4].toUpperCase()); const got = here.length ? [...new Set(here)].join(", ") : "none"; const pass = anyError diff --git a/scripts/package_tests.mjs b/scripts/package_tests.mjs index b033eb276..45a683741 100644 --- a/scripts/package_tests.mjs +++ b/scripts/package_tests.mjs @@ -29,7 +29,9 @@ // // A compile case that expects an error is in the package folder's compile/, a project of compile // cases only, because the compiler runs no [TestCase] in a project with errors; it is staged with -// the package embedded too, and its cases are judged with the rest. +// the package embedded too, and its cases are judged with the rest. A package that does not compile +// has that folder and no project: its cases name the rows of the package's own sources. A compile +// case that names an architecture is run on that one, whatever expect.json's arches says. // // RunAll.twin, which the project's Sub Main runs, is generated from the cases and expect.json: // the run writes it again when it is out of date, and says so. @@ -55,6 +57,7 @@ import { } from "../lib/cli.mjs"; import { COMPILE_DIR, + compileOnlyNames, compileSources, RESOURCES_DIR, resourceFiles, @@ -146,7 +149,7 @@ exitOnCrash(); // The packages to run: those named, each matched to its folder without regard to case, or all // of them with --all-packages. Neither is refused, and so are both, before anything starts. -const known = packageTestNames(ROOT); +const known = [...packageTestNames(ROOT), ...compileOnlyNames(ROOT)].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)); const knownList = known.length ? known.join(", ") : "none"; if (values.allPackages && positionals.length) die(2, `name packages or pass --all-packages, not both\n${USAGE}`); if (!values.allPackages && !positionals.length) { @@ -185,15 +188,18 @@ async function runPackage(pkg) { if (expect.version && expect.version !== version) { say(`${pkg}: expect.json is for version ${expect.version}, the template for ${version}`); } - let files = testSources(ROOT, pkg); + const hasProject = existsSync(path.join(ROOT, pkg, "Sources")); + let files = hasProject ? testSources(ROOT, pkg) : []; const { tests, compiles, problems } = projectCases(files); const compileFiles = compileSources(ROOT, pkg); const compileProject = projectCases(compileFiles); problems.push(...compileProject.problems.map((p) => `${COMPILE_DIR}/: ${p}`)); if (compileProject.tests.length) problems.push(`${COMPILE_DIR}/ holds a [TestCase]: it holds compile cases only`); if (problems.length) die(2, `${pkg}:\n${problems.map((p) => ` ${p}`).join("\n")}`); - refreshRunAll(pkg, version, tests, expect); - files = testSources(ROOT, pkg); + if (hasProject) { + refreshRunAll(pkg, version, tests, expect); + files = testSources(ROOT, pkg); + } const compileNames = compileProject.compiles.map((c) => c.name); const names = [...compiles.map((c) => c.name), ...compileNames, ...tests.map((t) => `${t.fixture}.${t.name}`)]; @@ -204,12 +210,17 @@ async function runPackage(pkg) { // A package tested on one architecture only (expect.json's arches) is run on that one. const arches = opts.arches.filter((a) => archesOf(expect).includes(a)); if (arches.length < opts.arches.length) say(` tested on ${archesOf(expect).join(", ")} only`); - for (const arch of arches) { - if (compileNames.length) { + // A compile case that names an architecture is for that one alone. + const pinned = new Map(compileProject.compiles.map((c) => [c.name, c.arch])); + const runsOn = (name, arch) => (pinned.get(name) ? pinned.get(name) === arch : arches.includes(arch)); + for (const arch of opts.arches) { + const compileHere = compileNames.filter((n) => runsOn(n, arch)); + if (compileHere.length) { const compileTree = stageTree(`${pkg}-${COMPILE_DIR}`, pkg, arch, template, compileFiles); - for (const r of tbrunCases(compileTree, arch, compileNames, opts.timeout)) + for (const r of tbrunCases(compileTree, arch, compileHere, opts.timeout)) outcomes.get(r.name)[arch] = outcomeOf(r); } + if (!hasProject || !arches.includes(arch)) continue; const tree = stageTree(pkg, pkg, arch, template, files, resourceFiles(ROOT, pkg)); const own = names.filter((n) => !compileNames.includes(n)); const hangs = own.filter((n) => expectationFor(expect, n, arch).kind === "hang"); @@ -244,7 +255,7 @@ async function runPackage(pkg) { const cases = []; for (const name of names) { - for (const arch of arches) { + for (const arch of opts.arches.filter((a) => runsOn(name, a))) { const expected = expectationFor(expect, name, arch); const got = outcomes.get(name)[arch] ?? { kind: "fail", detail: "no result" }; const verdict = judge(expected, got); @@ -264,7 +275,7 @@ async function runPackage(pkg) { const file = path.join(ROOT, pkg, EXPECT_JSON); writeFileSync(file, `${JSON.stringify(next, null, 2)}\n`, "utf8"); say(` wrote ${path.relative(REPO_ROOT, file).replaceAll(path.sep, "/")}`); - refreshRunAll(pkg, version, tests, next); + if (hasProject) refreshRunAll(pkg, version, tests, next); } return { package: pkg, version, cases }; } diff --git a/test/package-tests/ArrayListLib/compile/PackageTests.twin b/test/package-tests/ArrayListLib/compile/PackageTests.twin new file mode 100644 index 000000000..8c492955d --- /dev/null +++ b/test/package-tests/ArrayListLib/compile/PackageTests.twin @@ -0,0 +1,11 @@ +' ArrayListLib: the rows the compiler reports in the package's own sources, in a project that uses +' nothing of it. Each comment names one, ' CASE : package :. The package +' has no test project until a version of it compiles, so these cases are all there is. +Module CompileTests + + ' CASE Reverse_VSwapCallIsRefused: package TB5001 ArrayList.twin:297 + ' CASE QuickSort_FirstVSwapCallIsRefused: package TB5001 ArrayList.twin:585 + ' CASE QuickSort_SecondVSwapCallIsRefused: package TB5001 ArrayList.twin:602 + ' CASE MemMoveBSTR_LSetByValCallIsRefused: package TB5001 Modules/LibMemoryEx.twin:104 + +End Module diff --git a/test/package-tests/README.md b/test/package-tests/README.md index ffb17d4f7..34ed91895 100644 --- a/test/package-tests/README.md +++ b/test/package-tests/README.md @@ -66,6 +66,12 @@ End Module every other case. `package_tests.mjs` stages `compile/` with the package embedded and judges its cases with the rest; a marker names them as `compile `, as any compile case. It is not published: the published project is `Sources/` only, and compiles. +- **A package that does not compile says so in compile cases too**: a comment + `' CASE : package TB5001 Libs/cThing.twin:21` passes when the compiler reports that code at + that line of a file under the package's `Sources/`. `win64 package ...` (or `win32`) runs the + case for that architecture alone, even one `arches` leaves out (TBMANLIB, in a 64-bit build). A + package with no test project (ArrayListLib, WinReg) has `compile/` and nothing else; + `package_tests.mjs` runs it by name, and `--all-packages` includes it. - **Resources** a case reads go in the package folder's `Resources/`, laid out as an exported project keeps them (`/`, such as `BINARY/#101`). The runner stages them and the published project carries them, byte for byte. diff --git a/test/package-tests/TBMANLIB/compile/CompileTests.twin b/test/package-tests/TBMANLIB/compile/CompileTests.twin index bf90a10a3..fa34c525a 100644 --- a/test/package-tests/TBMANLIB/compile/CompileTests.twin +++ b/test/package-tests/TBMANLIB/compile/CompileTests.twin @@ -1,6 +1,9 @@ ' TBMANLIB: what the compiler refuses. Each line ends in a comment ' CASE : . Module CompileTests + ' A 64-bit build stops at the package's own source, so this is the one case run for win64. + ' CASE Win64_SubClassIsRefused: win64 package TB5001 Libs/SubClass/cSubClass.twin:21 + Public Sub RequestHeaders_AssignmentThroughTheDefaultMember() Dim Http As New cHttpClient Http.RequestHeaders("X-Key") = "1" ' CASE RequestHeadersDefaultMemberAssignment: TB5090 diff --git a/test/package-tests/WinReg/compile/PackageTests.twin b/test/package-tests/WinReg/compile/PackageTests.twin new file mode 100644 index 000000000..17aadccbc --- /dev/null +++ b/test/package-tests/WinReg/compile/PackageTests.twin @@ -0,0 +1,11 @@ +' WinReg: the rows the compiler reports in the package's own sources, in a project that uses nothing +' of it. Each comment names one, ' CASE : package :. The package has no test +' project until a version of it compiles, so these cases are all there is. +Module CompileTests + + ' CASE WriteString_RegCreateKeyExCallIsRefused: package TB5001 RegCls.twin:165 + ' CASE WriteDWORD_RegCreateKeyExCallIsRefused: package TB5001 RegCls.twin:192 + ' CASE CreateKey_RegCreateKeyExCallIsRefused: package TB5001 RegCls.twin:288 + ' CASE DeleteKeyEx_RegEnumKeyExCallIsRefused: package TB5001 RegCls.twin:313 + +End Module From ab7cb05fca89628dfd6915b160344fd904ad2dd4 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Tue, 3 Nov 2026 23:03:16 +0100 Subject: [PATCH 18/19] Item 18, second part: 8 more callouts lose test: none. package_tests runs exe/ cases in a built exe, with a server.mjs for them (GdiPlus start-up, cHttpClient's certificates); TimeZoneStandIn gives LocaleCollector's defect the same verdict all year; hidden samples check ListView TextBackground (templates wnc-form) and PropertyPage's semicolon (activex-control); test/static-linking and static markers check Static Linking's NOTE; makeBatches keeps each [RunAfterBuild] sample in a project of its own --- WIP.ExamplesBuild.md | 8 +- WIP.PackageTests.md | 13 ++ WIP.md | 6 +- docs/Documentation/Authoring.md | 1 + docs/Documentation/Tools-Defects.md | 1 + docs/Documentation/Tools-Samples.md | 4 +- docs/Documentation/Tools-Toolchain-Tests.md | 2 +- docs/Features/Advanced/Static-Linking.md | 2 +- docs/Reference/Attributes.md | 44 ++++- .../WinNativeCommonCtls/ListView/index.md | 56 +++++- .../Reference/Contributed/GdiPlus/Graphics.md | 4 +- docs/Reference/Contributed/GdiPlus/index.md | 2 +- .../Contributed/TBMANLIB/cHttpClient.md | 2 +- .../Contributed/TBMANLIB_Diagnostic/index.md | 2 +- lib/package-tests.mjs | 21 +- scripts/check_callout_evidence.mjs | 8 +- scripts/check_examples.mjs | 3 +- scripts/check_package_tests.mjs | 37 +++- scripts/lib/cli-cases.mjs | 5 +- scripts/lib/example-batches.mjs | 27 ++- scripts/lib/package-test-cases.mjs | 53 +++++ scripts/lib/recheck.mjs | 2 +- scripts/lib/test-markers.mjs | 26 ++- scripts/package_tests.mjs | 68 ++++++- scripts/recheck_callouts.mjs | 14 +- .../example-projects/activex-control/Settings | 61 ++++++ .../Sources/ProbeControl.tbform | 1 + .../activex-control/Sources/ProbePage.tbform | 1 + .../activex-control/Sources/ProbePage.twin | 15 ++ .../wnc-form/Sources/Form1.tbform | 94 +++++++++ .../wnc-form/Sources/Form1.twin | 15 ++ .../wnc-form/Sources/tbxStage.twin | 87 +++++++++ test/package-tests/Expect.twin | 20 +- .../GdiPlus/exe/StartupTests.twin | 47 +++++ test/package-tests/README.md | 9 + .../TBMANLIB/exe/HttpsTests.twin | 37 ++++ test/package-tests/TBMANLIB/exe/server.mjs | 46 +++++ test/package-tests/TBMANLIB/expect.json | 3 + .../Sources/CollectorTests.twin | 27 ++- .../TBMANLIB_Diagnostic/Sources/RunAll.twin | 10 +- .../Sources/TimeZoneStandIn.twin | 80 ++++++++ .../TBMANLIB_Diagnostic/expect.json | 5 +- test/static-linking/README.md | 40 ++++ test/static-linking/c/basic.c | 2 + test/static-linking/c/build.cmd | 15 ++ test/static-linking/c/cppnew.cpp | 10 + test/static-linking/c/crt.c | 22 +++ test/static-linking/c/crtmore.c | 14 ++ test/static-linking/expect.json | 77 ++++++++ .../links/Miscellaneous/basic_32.obj | Bin 0 -> 658 bytes .../links/Miscellaneous/basic_64.obj | Bin 0 -> 668 bytes .../links/Miscellaneous/crt_32.obj | Bin 0 -> 1308 bytes .../links/Miscellaneous/crt_64.obj | Bin 0 -> 1981 bytes test/static-linking/links/Settings | 75 +++++++ .../static-linking/links/Sources/Startup.twin | 30 +++ .../refused/Miscellaneous/cppnew_32.obj | Bin 0 -> 892 bytes .../refused/Miscellaneous/cppnew_64.obj | Bin 0 -> 1250 bytes .../refused/Miscellaneous/crtmore_32.obj | Bin 0 -> 2438 bytes .../refused/Miscellaneous/crtmore_64.obj | Bin 0 -> 5026 bytes test/static-linking/refused/Settings | 75 +++++++ .../refused/Sources/Startup.twin | 20 ++ test/static-linking/run.mjs | 183 ++++++++++++++++++ 62 files changed, 1478 insertions(+), 54 deletions(-) create mode 100644 test/example-projects/activex-control/Settings create mode 100644 test/example-projects/activex-control/Sources/ProbeControl.tbform create mode 100644 test/example-projects/activex-control/Sources/ProbePage.tbform create mode 100644 test/example-projects/activex-control/Sources/ProbePage.twin create mode 100644 test/example-projects/wnc-form/Sources/Form1.tbform create mode 100644 test/example-projects/wnc-form/Sources/Form1.twin create mode 100644 test/example-projects/wnc-form/Sources/tbxStage.twin create mode 100644 test/package-tests/GdiPlus/exe/StartupTests.twin create mode 100644 test/package-tests/TBMANLIB/exe/HttpsTests.twin create mode 100644 test/package-tests/TBMANLIB/exe/server.mjs create mode 100644 test/package-tests/TBMANLIB_Diagnostic/Sources/TimeZoneStandIn.twin create mode 100644 test/static-linking/README.md create mode 100644 test/static-linking/c/basic.c create mode 100644 test/static-linking/c/build.cmd create mode 100644 test/static-linking/c/cppnew.cpp create mode 100644 test/static-linking/c/crt.c create mode 100644 test/static-linking/c/crtmore.c create mode 100644 test/static-linking/expect.json create mode 100644 test/static-linking/links/Miscellaneous/basic_32.obj create mode 100644 test/static-linking/links/Miscellaneous/basic_64.obj create mode 100644 test/static-linking/links/Miscellaneous/crt_32.obj create mode 100644 test/static-linking/links/Miscellaneous/crt_64.obj create mode 100644 test/static-linking/links/Settings create mode 100644 test/static-linking/links/Sources/Startup.twin create mode 100644 test/static-linking/refused/Miscellaneous/cppnew_32.obj create mode 100644 test/static-linking/refused/Miscellaneous/cppnew_64.obj create mode 100644 test/static-linking/refused/Miscellaneous/crtmore_32.obj create mode 100644 test/static-linking/refused/Miscellaneous/crtmore_64.obj create mode 100644 test/static-linking/refused/Settings create mode 100644 test/static-linking/refused/Sources/Startup.twin create mode 100644 test/static-linking/run.mjs diff --git a/WIP.ExamplesBuild.md b/WIP.ExamplesBuild.md index fc8985c52..44a0024bc 100644 --- a/WIP.ExamplesBuild.md +++ b/WIP.ExamplesBuild.md @@ -311,11 +311,13 @@ already consumes. Three whole ones exist: | `packages` | the above plus all eleven other shipped packages | `Reference/Built-In/` and the package tutorials | | `form` | `console`, a delta | a `check_run` sample that needs a real form: drawing, `Cls`, `Print`, `Scale`, a form property. Never inferred: a fence says `project=form` | | `contributed-` | `console`, a delta: one `Settings` referencing one documented TWINSERV package, embedded | `Reference/Contributed//` | +| `wnc-form` | `packages`, a delta: `form`'s Form1 with a ListView, `ListView1`, on it | a `check_run` sample that looks at what a WinNativeCommonCtls control draws. Never inferred | +| `activex-control` | `console`, a delta: an ActiveX Control project with a property page, `ProbePage`, and the designer file of a UserControl, `ProbeControl` | a sample of a UserControl: its `slot=file` fence is the class, naming that designer id, and a `projname=` of its own keeps it the only one in its project. Never inferred | (The table is the three whole templates. The deltas over them --- `vb-private`, `cc-private`, -`wnc-private`, `cef-private`, `implicit`, `cef`, `webview2` --- are `TEMPLATE_BASE` in -`scripts/lib/example-batches.mjs`, with `form` and the `contributed-` ones, which -`TEMPLATE_BASE` gets from the directories so named.) +`wnc-private`, `wnc-form`, `activex-control`, `cef-private`, `implicit`, `cef`, `webview2` --- are +`TEMPLATE_BASE` in `scripts/lib/example-batches.mjs`, with `form` and the `contributed-` +ones, which `TEMPLATE_BASE` gets from the directories so named.) **`contributed-`: packages embedded, not committed.** A project uses a TWINSERV package by embedding its whole exported tree under `Packages//`, with a reference diff --git a/WIP.PackageTests.md b/WIP.PackageTests.md index d974660c0..b9ab87cfb 100644 --- a/WIP.PackageTests.md +++ b/WIP.PackageTests.md @@ -103,6 +103,19 @@ and WinReg stay in `PENDING` (no test project until a version compiles, owner) w alone, which `check_package_tests.mjs` checks both ways: each case named by a marker, each marker naming a case. The published project, and so `Sources/`, stays out of it. +A case in the IDE runs in the IDE's process, which has already started GDI+ and has no server that +speaks TLS. So `exe/` is a third project, run with `tbrun --exe`: its cases are ordinary +`[TestCase]`s, so a marker and `expect.json` need nothing new, and `recheck_callouts.mjs` reaches +them through `package_tests.mjs`. One process runs them all, which saves a build per case and makes +order matter: GDI+ stays started once a `GdiPlusUser` has started it, so the cases that need it +stopped come first. `TBMANLIB/exe/server.mjs` makes its certificate on every run, with the DER +written by hand, so that no private key is kept in the repository. + +A claim that depends on the date goes through a stand-in, not a case that reads the clock: +TBMANLIB_Diagnostic's `TimeZoneStandIn` overwrites the start of `GetTimeZoneInformation` for the +length of a call, in the IDE's process, as DetoursLib's cases do with `MulDiv`, so the defect shows +in every month and on any machine, with a standard-time case as its control. + ## Markers, and a gate that only tightens A callout names its cases in an HTML comment on the line above it. The marker lives with the diff --git a/WIP.md b/WIP.md index 45db6315a..acabbe465 100644 --- a/WIP.md +++ b/WIP.md @@ -85,7 +85,7 @@ The rest of this file is the maintenance guide for updating existing pages or ad > would file the page as a *static file* and serve the raw markdown. Editors on > Windows add a BOM unasked. `lib/frontmatter.mjs` strips a leading BOM before parsing. - `docs/Reference/Built-In/tbIDE/` — IDE Extensibility package (this is the **addin SDK**). The package is type-only — it ships **public interfaces + CoClasses** that an addin DLL binds to; every implementation behind them lives in the twinBASIC IDE itself. The user-facing surface is one entry-point factory (`tbCreateCompilerAddin`) plus 23 CoClasses grouped by role: the addin contract (`AddIn`), the root API (`Host`), the loaded `Project`, the editors collection (`Editor` / `CodeEditor` / `Editors`), the virtual file system (`FileSystem` / `FileSystemItem` / `Folder` / `File`), the in-IDE UI surface (`Toolbar` / `Toolbars` / `Button` / `ToolWindow` / `ToolWindows`), the HTML DOM inside a tool window (`HtmlElement` / `HtmlElements` / `HtmlElementProperty` / `HtmlElementProperties` / `HtmlEventProperty` / `HtmlEventProperties`), the `DebugConsole`, `KeyboardShortcuts`, `Themes`, and the single concrete user-instantiable helper class `AddinTimer`. Flat layout — one page per CoClass / Class plus the index landing. -- `docs/Reference/Contributed/` — the contributed packages, one folder each, landing page `Contributed.md` beside it. **A package's landing page opens, under its H1, with `**Author:**`** (owner): the publisher TWINSERV lists (`publisher` in `indexer/.packages/manifests/contributed.json`), or the author the package's own sources name when that is someone else (VBA_JSON: Tim Hall, published by WaynePhillipsEA). Next to the author, on the same line, `**Version:**`: the version the section documents, which is its `indexed_from` and changes with it (owner). Then `**On GitHub:**` with a link, when a repository holds the package, or the library the package says it is made from; search GitHub for the package's name and the author's account, and check that the repository holds the package's code (its history too: `TBMANLIB_*` were never in woeoio's `tbman`). A landing page with one or two sections has no `* TOC` list. A class with member pages is a folder, `/index.md` at `/tB/Packages/Contributed///` with its members beside it, so a member links to its class as `(.)`; a class documented on one page is `.md`. Each package that is not Experimental is a chapter of the book's Packages part in `_book.yml`. A section's `indexed_from` is the four-part TWINSERV version, and its snapshot is under `indexer/snapshots/contributed/`. **Every NOTE and WARNING of a section is backed by test cases** in the package's test project, `test/package-tests//` (owner; [its README](test/package-tests/README.md)), and names them in a `` marker on the line above it, which `check_package_tests.mjs` in `test.bat` enforces. **A NOTE or WARNING is only for a real difference or limit** (owner): a statement that describes how the package works stays prose, tied to its cases by a region, the marker on a line of its own above it and `` after it (owner). A difference from the object the package replaces that gives a different result silently (tbRegExp's `\1(a)`, `{,m}`, `$~` against VBScript) is a NOTE, not a WARNING (owner). A case checks what is correct, not what the package does (owner): a defect is a failing case, recorded in the project's `expect.json`, so the project tells the package's author what to fix. A section whose project is not written yet is in that gate's `PENDING` set. **A package that does not compile on the newest BETA gets no test project** (owner: ArrayListLib, WinReg): it stays in `PENDING` until a version of it compiles, and a `compile/` folder alone names the rows that refuse it, so its "does not compile" is checked. One that compiles for one architecture only is tested on that one (owner: TBMANLIB, win32; `arches` in its `expect.json`, test/package-tests/README.md), with a compile case for the other. `package-tests.bat` runs the cases; a case it reports *fixed* means the callout goes. **The landing page ends, after See Also, with `## Test cases`**: one or two sentences linking to `-tests.twinproj` (owner), which the build publishes beside the section without the package's code in it (owner; one exception, owner: TBMANLIB_ConsoleComponents' project carries verbatim copies of seven of its component files, in `Sources/ComponentSources/`, because the package's own components cannot be given a console object, and its drawing can be checked only on the copies): it references the package as a linked package, which the reader puts in `%APPDATA%\twinBASIC\packages` ([WIP.PackageTests.md](WIP.PackageTests.md)). Its samples compile in the `contributed-` sample template, whose `Settings` names the package's version: **change that version with `indexed_from`** ([WIP.ExamplesBuild.md](WIP.ExamplesBuild.md#template-projects)). **A contributed package is documented and its samples compiled against the newest twinBASIC BETA installed, never an older one** (owner): the version that matters is the package's, and a statement about the compiler (such as "does not compile on BETA 1010") names that newest build. **A contributed package that does not compile on that build, or has defects that a program would meet, is listed under Experimental Packages** (owner); a minor defect that the section documents does not make it Experimental (owner: VBComDlg, whose Replace All raises no event, stays Contributed; owner: nor does a fixed limit that the section states at the top of its landing page; owner: tsJson 1.0.15.0 stays Contributed, its defects confirmed by a second agent and each stated in a WARNING with a way round it; owner: xCalendar 1.0.17.0 moved back to Contributed once its text colours worked on BETA 1034, its grid and edge-press defects stated in a WARNING). **A package moves from Contributed to Experimental for its defects only after a separate agent has reproduced each defect independently** and ruled out misuse, a wrong expectation, a twinBASIC defect and the harness (owner): `docs/Reference/Experimental.md`, at `/tB/Packages/Experimental/`, after Contributed Packages. Its landing page's `parent:` is `Experimental Packages`, its line is in `Experimental.md` and not in `Contributed.md`, and it has no chapter in the PDF book (owner: the book is for packages ready for use in a program): its entry is in `left_out:` of `docs/_book.yml`, in the list of Experimental packages, and the Experimental Packages page is left out with it. Its folder and URLs stay under `Contributed/`, so a package that is fixed moves back by changing only those three and the book (its entry leaves `left_out:` and its chapter is added after VBComDlg's), and its pages keep their addresses. A link from a book page to a left-out one opens the website. +- `docs/Reference/Contributed/` — the contributed packages, one folder each, landing page `Contributed.md` beside it. **A package's landing page opens, under its H1, with `**Author:**`** (owner): the publisher TWINSERV lists (`publisher` in `indexer/.packages/manifests/contributed.json`), or the author the package's own sources name when that is someone else (VBA_JSON: Tim Hall, published by WaynePhillipsEA). Next to the author, on the same line, `**Version:**`: the version the section documents, which is its `indexed_from` and changes with it (owner). Then `**On GitHub:**` with a link, when a repository holds the package, or the library the package says it is made from; search GitHub for the package's name and the author's account, and check that the repository holds the package's code (its history too: `TBMANLIB_*` were never in woeoio's `tbman`). A landing page with one or two sections has no `* TOC` list. A class with member pages is a folder, `/index.md` at `/tB/Packages/Contributed///` with its members beside it, so a member links to its class as `(.)`; a class documented on one page is `.md`. Each package that is not Experimental is a chapter of the book's Packages part in `_book.yml`. A section's `indexed_from` is the four-part TWINSERV version, and its snapshot is under `indexer/snapshots/contributed/`. **Every NOTE and WARNING of a section is backed by test cases** in the package's test project, `test/package-tests//` (owner; [its README](test/package-tests/README.md)), and names them in a `` marker on the line above it, which `check_package_tests.mjs` in `test.bat` enforces. **A NOTE or WARNING is only for a real difference or limit** (owner): a statement that describes how the package works stays prose, tied to its cases by a region, the marker on a line of its own above it and `` after it (owner). A difference from the object the package replaces that gives a different result silently (tbRegExp's `\1(a)`, `{,m}`, `$~` against VBScript) is a NOTE, not a WARNING (owner). A case checks what is correct, not what the package does (owner): a defect is a failing case, recorded in the project's `expect.json`, so the project tells the package's author what to fix. A section whose project is not written yet is in that gate's `PENDING` set. **A package that does not compile on the newest BETA gets no test project** (owner: ArrayListLib, WinReg): it stays in `PENDING` until a version of it compiles, and a `compile/` folder alone names the rows that refuse it, so its "does not compile" is checked. One that compiles for one architecture only is tested on that one (owner: TBMANLIB, win32; `arches` in its `expect.json`, test/package-tests/README.md), with a compile case for the other. A case that the IDE's own process would change goes in the package folder's `exe/`, which `package_tests.mjs` runs in a built exe (GdiPlus: the IDE has started GDI+; TBMANLIB: an HTTPS server of its own). `package-tests.bat` runs the cases; a case it reports *fixed* means the callout goes. **The landing page ends, after See Also, with `## Test cases`**: one or two sentences linking to `-tests.twinproj` (owner), which the build publishes beside the section without the package's code in it (owner; one exception, owner: TBMANLIB_ConsoleComponents' project carries verbatim copies of seven of its component files, in `Sources/ComponentSources/`, because the package's own components cannot be given a console object, and its drawing can be checked only on the copies): it references the package as a linked package, which the reader puts in `%APPDATA%\twinBASIC\packages` ([WIP.PackageTests.md](WIP.PackageTests.md)). Its samples compile in the `contributed-` sample template, whose `Settings` names the package's version: **change that version with `indexed_from`** ([WIP.ExamplesBuild.md](WIP.ExamplesBuild.md#template-projects)). **A contributed package is documented and its samples compiled against the newest twinBASIC BETA installed, never an older one** (owner): the version that matters is the package's, and a statement about the compiler (such as "does not compile on BETA 1010") names that newest build. **A contributed package that does not compile on that build, or has defects that a program would meet, is listed under Experimental Packages** (owner); a minor defect that the section documents does not make it Experimental (owner: VBComDlg, whose Replace All raises no event, stays Contributed; owner: nor does a fixed limit that the section states at the top of its landing page; owner: tsJson 1.0.15.0 stays Contributed, its defects confirmed by a second agent and each stated in a WARNING with a way round it; owner: xCalendar 1.0.17.0 moved back to Contributed once its text colours worked on BETA 1034, its grid and edge-press defects stated in a WARNING). **A package moves from Contributed to Experimental for its defects only after a separate agent has reproduced each defect independently** and ruled out misuse, a wrong expectation, a twinBASIC defect and the harness (owner): `docs/Reference/Experimental.md`, at `/tB/Packages/Experimental/`, after Contributed Packages. Its landing page's `parent:` is `Experimental Packages`, its line is in `Experimental.md` and not in `Contributed.md`, and it has no chapter in the PDF book (owner: the book is for packages ready for use in a program): its entry is in `left_out:` of `docs/_book.yml`, in the list of Experimental packages, and the Experimental Packages page is left out with it. Its folder and URLs stay under `Contributed/`, so a package that is fixed moves back by changing only those three and the book (its entry leaves `left_out:` and its chapter is added after VBComDlg's), and its pages keep their addresses. A link from a book page to a left-out one opens the website. - `docs/Downloads/index.md` — the Downloads page, `/Downloads`, a top-level page at `nav_order: 4` and in `_book.yml`'s `left_out:`. **Every file the documentation publishes for readers has a card or a row there**, and the page that documents it keeps its own link (owner). Cards are `
` inside `
` (styles at the end of `custom.scss`), with the theme's `.btn` for each file. The release job publishes every same-site link on the built page that has the `download` attribute, beside the zip and the PDF, so a new download needs no change to the workflow. `{{tbdocs:size:}}` gives a published file's size and `{{tbdocs:packageTestRows}}` the test projects' rows, both expanded before render; the help add-in's size is prose ("about 61 MB" since the pre-built search index, October 2026; it is also on the Help Add-In page, with the archive's "about 60 MB"), because its project embeds the offline tree, this page included, so it cannot be known before render. The PDF's release URL spells it `twinBASIC.Book.pdf`: GitHub turns the space into a dot, and the spaced name 404s. The footer's **Read Offline** link (`read_offline_link` in `_config.yml`) goes to the page's `#read-offline`. - `docs/Reference/Data-Types.md` and `docs/Reference/Data-Types/.md` — the intrinsic data types (owner, 2026-10-18): one page per type at `/tB/Core/` (`parent: Data Types`, `grand_parent: Reference Section`, since the Features page *New Data Types* has the same title), and the summary page, `/Reference/Data-Types`, with one `## ` section per type that links to it. The summary's `symbols:` names the types, and the symbol index points each at its section (kind `type`, the one URL outside `/tB/`), so keep those headings' text. `Long.md` is the model page. - `docs/Reference/Statements.md` — alphabetical index of language statements. @@ -252,7 +252,7 @@ The vocabulary tables further down cover word choice. The rules in this subsecti **A callout says what it applies to in its label, the rest of its `[!TYPE]` line, and never in its text** (owner's rule): `> [!WARNING] BETA 1010` for a fact of that twinBASIC build, `> [!NOTE] since BETA 984` for one true from that build on, `> [!WARNING] 1.0.0.4` for one of that version of the page's own package, and `> [!NOTE] 1.6.151.1, BETA 1010` for both. `BETA` is upper case and a package version is bare, all four parts. The text then starts with the fact: never "In twinBASIC BETA 1010, ...", "BETA 1010 has a defect: ..." or "In version 1.0.0.4, ...". The build draws each item as a tag after the title and fails on anything else after `[!TYPE]`, and on a package version other than the section's `indexed_from` (`lib/callout-labels.mjs`). A label belongs on what is true of one build or one version, a defect above all; a callout that holds as well as that something true of every build is split in two. **In a contributed package's section every callout has a label** (owner's rule), the section's version for what the package does, so that moving the section to a new version sends each one back to be checked; the build refuses one without. **`> [!WARNING] timeless`** (owner's rule) marks a callout no new build or version can change: a fact of Windows or COM, behaviour the package exists to reproduce (the Scripting Runtime's, for an FSO replacement), or a design decision the sources state. It stands alone, draws no tag, and is never listed to check again; the package's test cases still cover what the package itself does. - **Every callout labelled `BETA ` names what checks it** (owner, 2026-11-03), in `` on the line directly above it or a region around it, the grammar a contributed section's markers already had (`scripts/lib/test-markers.mjs`): `bug `, `lane :`, `sample ` (a fence with that `id=`), `errors `, `llvm `, or `none -- ` when no program can check it. A fixed bug's correct behaviour goes into an inline test, a `check_run` sample or the error data, not into prose alone. A statement that needs a run whose code would tell the reader nothing is checked by a **`probe`** fence: a sample the reader never sees, with an `id=` a marker names, and `arch=win32,win64` when the 64-bit half matters (`Tools-Samples.md#probes`; the FAQ's msvbvm60 list is the first). The callouts of a generated Common Errors section need no marker. `check_callout_evidence.mjs` in `test.bat` refuses a labelled callout without one, an item naming what does not exist, and a probe no marker names; `recheck_callouts.mjs` runs the evidence on a new build (below). + **Every callout labelled `BETA ` names what checks it** (owner, 2026-11-03), in `` on the line directly above it or a region around it, the grammar a contributed section's markers already had (`scripts/lib/test-markers.mjs`): `bug `, `lane :`, `sample ` (a fence with that `id=`), `errors `, `llvm `, `static `, or `none -- ` when no program can check it. A fixed bug's correct behaviour goes into an inline test, a `check_run` sample or the error data, not into prose alone. A statement that needs a run whose code would tell the reader nothing is checked by a **`probe`** fence: a sample the reader never sees, with an `id=` a marker names, and `arch=win32,win64` when the 64-bit half matters (`Tools-Samples.md#probes`; the FAQ's msvbvm60 list is the first). The callouts of a generated Common Errors section need no marker. `check_callout_evidence.mjs` in `test.bat` refuses a labelled callout without one, an item naming what does not exist, and a probe no marker names; `recheck_callouts.mjs` runs the evidence on a new build (below). 7. **See Also.** Last section on the page, after `Example`; only a contributed package's landing page has one after it, `## Test cases`. Format: `- [Symbol](Symbol) ` where `` is the kind: statement, function, property, method, class, module, package. Pages with annotations use `- [Symbol](Symbol) -- short description` — the `--` source renders as a typographic dash via markdown-it's typographer (en-dash for `--`, em-dash for `---`). Don't write literal `—` in source; keep `--` for consistency across the docs. Order by conceptual proximity, not strict alphabetical. @@ -597,7 +597,7 @@ Why the report separates the wedged task from the merely blocked ones, and why - `node scripts/bug_repro.mjs` --- the reproducer projects under `bugs//` for the entries of [BUGS-TO-REPORT.md](BUGS-TO-REPORT.md): `new`, `pack` (impexp, then the zip, written in Node), `compile`, `build` and `run` through `tbbuild` and `tbrun`, `vb6` (builds the optional `vb6/` VB6 project beside the twinBASIC one in a temp copy, runs its exe on a private desktop, and prints its `out.txt`; a project with a form or a user control is built without Unattended Execution, which VB6 refuses for it; `pack` zips its sources into `-vb6.zip`; VB6 is started only by `scripts/lib/vb6.mjs`, never from a shell; a graphical defect's `images` key makes `run` and `vb6` keep what the `PngDump` modules of `test/repro-templates/png/` draw as `images/-tb.png` and `-vb6.png`, with a `-compare.png` of both, tested by `test/png.test.mjs`), `file` (moves a filed entry and its reproducer to `bugs/filed//`; `file --marked` does every marked entry), and `verify`, which reads each `bugs/*/repro.json` and `bugs/filed/*/repro.json` and says whether the bug still reproduces on the newest beta; a `lane` reproducer names the `ide-test` or `addin-test` lane and tests that assert its bug, which `verify` runs last through that runner, a `probe` one a script under `scripts/`, and a `test` one its own `[TestCase]` Subs and compile cases through `tbrun --tests`, judged by the `PASS` lines of what is fixed and the `FAIL` lines of the bug (`new --template tests` starts one). **No reproducer should need a person at the IDE** (owner): a bug the IDE has to be operated for gets a lane test, and `manual` is left for what cannot be automated at all. It has no wrapper. **`verify` is run by a person only**, never by a gate or CI: it needs a twinBASIC install, like `examples.bat`. Each `tbbuild` and `tbrun` it starts takes a slot of its own from `tbbroker`; `--jobs N` runs N at once. Its command-line cases are in `scripts/lib/cli-cases.mjs`, and need no IDE. - `node scripts/vb6run.mjs ` and `node scripts/vb6run.mjs --docs [--only ]` --- build and run VB6 code, so that what a sample prints in twinBASIC can be compared with VB6: a stand-alone file of statements or a `.bas` module, or `--docs`, which builds the documentation's `check_run` fences in VB6 and says for each whether it prints what its page says (`same`, `differs`, `not VB6`, `error`, `refused`; a `projname=` group is built as a project of its own, its `slot=file` fences translated into VB6 `.cls` and `.bas` components). It has no wrapper and is outside every gate and outside CI: it needs VB6 (`--vb6`, `VB6_EXE`, or the standard install folder). **Never start `VB6.EXE` from a shell**: in Git Bash `/make` is rewritten as a path and VB6 answers with a modal box on the desktop; the tool spawns it from Node with an argument array, and a sample that calls `MsgBox` or `InputBox` or contains `End` is refused. Its `Debug.Print` rewrite and its file translation have probes in `test/example-batches.test.mjs`, and its command-line cases are in `scripts/lib/cli-cases.mjs`; both need no VB6. What it relies on is in [WIP.ExamplesBuild.md](WIP.ExamplesBuild.md). - `node scripts/errors.mjs run|generate|write` --- the run-time errors of the documented procedures, as data under `errors/` ([WIP.Errors.md](WIP.Errors.md), [Tools-Defects](docs/Documentation/Tools-Defects.md#errorsmjs)): `run` builds and runs a probe of the cases in twinBASIC 32-bit and 64-bit (`tbrun --exe`), VB6 and 64-bit VBA (in 64-bit Access, `scripts/lib/vba.mjs`) and compares each with the data, `write` writes each page's Errors section. Its gate is `check_errors.mjs`; `run` is by hand, never CI. -- `node scripts/recheck_callouts.mjs [--plan] [--kinds ] [--only ] [--dry-run] [--report ] [--from ]` --- after a new BETA, checks again every callout labelled with an older one by running what its marker names (`ide_test`/`addin_test` once per suite, judging lane reproducers from the same report through `scripts/lib/lane-report.mjs`, `bug_repro verify` for the other reproducers, `check_examples --json` for samples, `errors.mjs run` for tb and win64, `test/llvm/run.mjs --only`, `package_tests --json`), relabels what holds (a data file's `beta:` too, then `errors.mjs write`), and lists the rest. What it reads and decides is `scripts/lib/recheck.mjs`, probed by `check_callout_evidence.mjs`; every tool's output stays in `%TEMP%\recheck-callouts\\`. By hand, never CI. +- `node scripts/recheck_callouts.mjs [--plan] [--kinds ] [--only ] [--dry-run] [--report ] [--from ]` --- after a new BETA, checks again every callout labelled with an older one by running what its marker names (`ide_test`/`addin_test` once per suite, judging lane reproducers from the same report through `scripts/lib/lane-report.mjs`, `bug_repro verify` for the other reproducers, `check_examples --json` for samples, `errors.mjs run` for tb and win64, `test/llvm/run.mjs --only`, `test/static-linking/run.mjs --only`, `package_tests --json`), relabels what holds (a data file's `beta:` too, then `errors.mjs write`), and lists the rest. What it reads and decides is `scripts/lib/recheck.mjs`, probed by `check_callout_evidence.mjs`; every tool's output stays in `%TEMP%\recheck-callouts\\`. By hand, never CI. - `node scripts/ide_js.mjs ` --- reads, names and patches the IDE's minified `ide/main.js` ([Tools-Defects](docs/Documentation/Tools-Defects.md#ide_jsmjs)): `survey`, `units`, `list`, `name`, `seal`, `check`, `view`, `patch` and `shape`. It reads files and starts nothing; the rules for it are under [Driving the twinBASIC compiler](#driving-the-twinbasic-compiler) (*Fixing the IDE itself*). It has no wrapper and is outside every gate and CI, since it needs an install; its tests are `test/ide-js.test.mjs`, in `test.bat`, and its command-line cases are in `scripts/lib/cli-cases.mjs`. **After a new BETA, run `check` on it**, and `seal` the map once each `review` and `lost` name has been looked at. Three generators sit outside that loop and produce committed artifacts rather than build output — none runs during a build, and none is needed for one. `python scripts/build_fonts.py` rebuilds the subset webfaces under `docs/assets/fonts/` and needs a network connection; `node scripts/build_dot_metrics.mjs` regenerates `builder/inter-metrics.json` from those webfaces and needs only a browser. See [Typography](#typography). `node indexer/twin-index.mjs derive` regenerates `builder/package-api.json`, the packages' declared API that the build's symbol index (`tB/symbols.json`, for the IDE help add-in) is annotated from; it needs a twinBASIC install (or `--release`), so **run it when the reference is re-indexed against a newer build** and commit it with the pages and the snapshots. See [WIP.HelpAddin.md, Stage 3](WIP.HelpAddin.md#stage-3-the-symbol-index-generated-by-the-docs-build). diff --git a/docs/Documentation/Authoring.md b/docs/Documentation/Authoring.md index 8ad1cb42d..3b2a53e3e 100644 --- a/docs/Documentation/Authoring.md +++ b/docs/Documentation/Authoring.md @@ -967,6 +967,7 @@ The marker goes on the line directly above the callout, with no blank line betwe | `sample ` | The sample with that `id=` under `docs/`: a `check_run` sample, a [probe](#a-statement-checked-by-a-sample-nobody-sees), or one that is only compiled. It holds while `examples.bat` finds nothing wrong with it. | | `errors ` | The run-time error data `errors/.yml`. | | `llvm ` | A mode of the LLVM claims probe, `test/llvm/run.mjs`. | +| `static ` | A mode of the static linking claims probe, `test/static-linking/run.mjs`. | | `none -- ` | Nothing a program can check. A person checks it again on each new build. | A contributed package's section names its own test cases too, as the next section says. The callouts of a generated Common Errors section need no marker: their data is what checks them. [`check_callout_evidence.mjs`](Tools-Toolchain-Tests#check-callout-evidence), in `test.bat`, fails a callout labelled `BETA ` with neither a marker nor a region around it, and an item that names nothing that exists. After a new build, [`recheck_callouts.mjs`](Tools-Defects#recheck-callouts) runs what each marker names, moves the label of each callout whose evidence still holds to the new build, and lists the rest to be read again. diff --git a/docs/Documentation/Tools-Defects.md b/docs/Documentation/Tools-Defects.md index d2e6b6ed5..94976763c 100644 --- a/docs/Documentation/Tools-Defects.md +++ b/docs/Documentation/Tools-Defects.md @@ -186,6 +186,7 @@ The build is the IDE's: `--ide`, else `TB_IDE`, else the newest `twinBASIC_IDE_B | `sample ` | [`check_examples.mjs --json`](Tools-Samples#check-examples), over the pages that hold the samples | the sample has no finding in each architecture it runs in | | `errors ` | `errors.mjs run`, for 32-bit and 64-bit twinBASIC | every case raises what the data says | | `llvm ` | `test/llvm/run.mjs --only ` | the mode prints what `expect.json` says | +| `static ` | `test/static-linking/run.mjs --only ` | the mode finds what `expect.json` says | | a package's cases | [`package_tests.mjs --json`](Tools-Samples#package-tests) | each case is as documented | | `none`, a package's `probe` | nobody | never: a person checks it | diff --git a/docs/Documentation/Tools-Samples.md b/docs/Documentation/Tools-Samples.md index 0bf001d54..7a2116b4e 100644 --- a/docs/Documentation/Tools-Samples.md +++ b/docs/Documentation/Tools-Samples.md @@ -129,7 +129,9 @@ For each package and architecture (both by default), the tool stages the project - **fixed** --- `expect.json` says it fails or never returns, and it passes: the page's callout and the entry go; - **changed** --- anything else: a claim of the page is no longer true, or the case is wrong. -A compile case that expects an error is in the package folder's `compile/`, a second project of compile cases only, because a project with compile errors runs none of its test cases. The tool stages it with the package embedded as well and judges its cases with the others; it is not published. +A compile case that expects an error is in the package folder's `compile/`, a second project of compile cases only, because a project with compile errors runs none of its test cases. The tool stages it with the package embedded as well and judges its cases with the others; it is not published. A package that does not compile has that folder and no project, and its cases name the rows the compiler reports in the package's own sources; the tool runs it like any other. + +A case that the IDE's own process would change is in the package folder's `exe/`, a third project, which the tool builds and runs with [`tbrun --exe`](Tools-Compiler#tbrun), every case in order in one process, and judges with the others. A `server.mjs` in that folder is started first, for the cases to call: it prints its port on its first line, and the exe reads the port from `PACKAGE_TEST_PORT`. This project is not published either. A case that never returns stops the compiler. The tool catches it, runs the cases after it again in a new IDE, and from then on, once `expect.json` says so, runs it alone with `--hang-timeout`. `--update` writes `expect.json` from the run, for the architectures run. The run also writes `RunAll.twin` again whenever the cases or `expect.json` change; [`check_package_tests.mjs`](Tools-Toolchain-Tests#check-package-tests) fails while it is out of date. diff --git a/docs/Documentation/Tools-Toolchain-Tests.md b/docs/Documentation/Tools-Toolchain-Tests.md index a8a7a99d9..a3e874ba3 100644 --- a/docs/Documentation/Tools-Toolchain-Tests.md +++ b/docs/Documentation/Tools-Toolchain-Tests.md @@ -440,7 +440,7 @@ A callout names its evidence in the same HTML comment a contributed package's ca > [!WARNING] BETA 1034 -Besides a contributed package's own cases, an item is `bug ` for a reproducer under `bugs/` or `bugs/filed/`, `lane :` for a lane of `test/ide/lanes.mjs` (`ide`) or `test/addin/lanes.mjs` (`addin`), `sample ` for the one `tb` fence under `docs/` with that `id=`, built by [`examples.bat`](Tools#examplesbat), `errors ` for the run-time error data `errors/.yml`, `llvm ` for a mode of the LLVM claims probe, `test/llvm/run.mjs`, or `none -- ` for a callout no program can check. The callouts of a generated Common Errors section need no marker: the data file the section's opening comment names is theirs. +Besides a contributed package's own cases, an item is `bug ` for a reproducer under `bugs/` or `bugs/filed/`, `lane :` for a lane of `test/ide/lanes.mjs` (`ide`) or `test/addin/lanes.mjs` (`addin`), `sample ` for the one `tb` fence under `docs/` with that `id=`, built by [`examples.bat`](Tools#examplesbat), `errors ` for the run-time error data `errors/.yml`, `llvm ` for a mode of the LLVM claims probe, `test/llvm/run.mjs`, `static ` for a mode of the static linking claims probe, `test/static-linking/run.mjs`, or `none -- ` for a callout no program can check. The callouts of a generated Common Errors section need no marker: the data file the section's opening comment names is theirs. The gate fails when a callout labelled `BETA ` outside a generated section has neither a marker nor a region around it, when an item names nothing that exists, when a `sample` names a fence that is hidden context or an id two fences share, when a page outside a contributed package's section names a package's test case, when a region outside those sections is not closed or opens inside another, when a page still has the old `` comment, and when a fence marked [`probe`](Tools-Samples#probes), which the reader never sees, is named by no marker. diff --git a/docs/Features/Advanced/Static-Linking.md b/docs/Features/Advanced/Static-Linking.md index 58e102ef3..110ba7732 100644 --- a/docs/Features/Advanced/Static-Linking.md +++ b/docs/Features/Advanced/Static-Linking.md @@ -10,7 +10,7 @@ index: static library Links compiled C code from `.obj`, `.lib` and `.a` files into the program, which calls its functions through **Declare** statements and needs no DLL beside it. - + > [!NOTE] BETA 1034 > Static linking is experimental. It links code that needs nothing from the C runtime library beyond the few functions twinBASIC provides, and nothing from the C++ runtime. The SQLite amalgamation and SlimDetours link and work; most other C projects do not link without changes. [What can be linked](#what-can-be-linked) lists the limits. diff --git a/docs/Reference/Attributes.md b/docs/Reference/Attributes.md index 33575ae67..5bd2fd962 100644 --- a/docs/Reference/Attributes.md +++ b/docs/Reference/Attributes.md @@ -1117,10 +1117,52 @@ End Property A property takes the page from its first accessor in the source, normally the **Property Get**, whichever kind of accessor that is. Written on a later accessor, the attribute has no effect, though its name is still checked; when two accessors name different pages, the control reports the first one's. On a **Sub**, a **Function** or a **Public** variable of the control, the control reports the page for that member's DISPID in the same way. In a class that is not a **UserControl**, or on an **Interface** member, the compiler accepts the attribute and it has no effect. The legacy line `Attribute Caption.VB_ProcData.VB_Invoke_Property = "CaptionPage"` inside the **Property Get** means the same. The argument must be a **String**: `[PropertyPage]` with no argument, or with a **Boolean** or a number, is TB5163, *Expected a single String argument*. On a procedure, a name that matches no property page in the project is error TB5259, *Failed to find named PropertyPage '...' for procedure '...'*. That includes an empty string and a name that differs only in case. A variable's name is not checked. - + > [!NOTE] BETA 1034 > VB6 stores a property's page as `"PageName;Category"` in the legacy attribute, with the category the property sheet groups the property under. twinBASIC reads such a legacy line and drops what follows the semicolon. **[PropertyPage]** takes the whole string as the name, so `[PropertyPage("CaptionPage;Appearance")]` is TB5259. +```tb probe projname=propertypage-semicolon project=activex-control slot=file id=propertypage-semicolon expect-error=TB5259 +[Description("")] +[FormDesignerId("7B247400-0000-4000-9000-7B2474000F51")] +[ClassId("7B247400-0000-4000-9000-7B2474000F55")] +[InterfaceId("7B247400-0000-4000-9000-7B2474000F56")] +[EventInterfaceId("7B247400-0000-4000-9000-7B2474000F57")] +[COMControl] +Class ProbeControl + [PropertyPage("ProbePage;Appearance")] + Public Property Get Caption() As String + End Property +End Class +``` + +```tb probe projname=propertypage-legacy-semicolon project=activex-control slot=file id=propertypage-legacy-semicolon +[Description("")] +[FormDesignerId("7B247400-0000-4000-9000-7B2474000F51")] +[ClassId("7B247400-0000-4000-9000-7B2474000F55")] +[InterfaceId("7B247400-0000-4000-9000-7B2474000F56")] +[EventInterfaceId("7B247400-0000-4000-9000-7B2474000F57")] +[COMControl] +Class ProbeControl + Public Property Get Caption() As String + Attribute Caption.VB_ProcData.VB_Invoke_Property = "ProbePage;Appearance" + End Property +End Class +``` + +```tb probe projname=propertypage-legacy-unknown project=activex-control slot=file id=propertypage-legacy-unknown expect-error=TB5259 +[Description("")] +[FormDesignerId("7B247400-0000-4000-9000-7B2474000F51")] +[ClassId("7B247400-0000-4000-9000-7B2474000F55")] +[InterfaceId("7B247400-0000-4000-9000-7B2474000F56")] +[EventInterfaceId("7B247400-0000-4000-9000-7B2474000F57")] +[COMControl] +Class ProbeControl + Public Property Get Caption() As String + Attribute Caption.VB_ProcData.VB_Invoke_Property = "NoSuchPage;Appearance" + End Property +End Class +``` + The pages the control offers as a whole are listed in its [**PropertyPages**](../Packages/VB/UserControl/#propertypages) property. + > [!NOTE] BETA 1034 > **TextBackground** has no visible effect: the control has no **Picture** property, the background image an opaque text background would hide. +```tb probe check_build projname=listview-textbackground project=wnc-form slot=file id=listview-picture +Module ListViewPicture + Private Declare PtrSafe Function GetDC Lib "user32" (ByVal hWnd As LongPtr) As LongPtr + Private Declare PtrSafe Function ReleaseDC Lib "user32" (ByVal hWnd As LongPtr, ByVal hDC As LongPtr) As Long + Private Declare PtrSafe Function PrintWindow Lib "user32" (ByVal hWnd As LongPtr, ByVal hDC As LongPtr, ByVal flags As Long) As Long + Private Declare PtrSafe Function CreateCompatibleDC Lib "gdi32" (ByVal hDC As LongPtr) As LongPtr + Private Declare PtrSafe Function CreateCompatibleBitmap Lib "gdi32" (ByVal hDC As LongPtr, ByVal cx As Long, ByVal cy As Long) As LongPtr + Private Declare PtrSafe Function SelectObject Lib "gdi32" (ByVal hDC As LongPtr, ByVal h As LongPtr) As LongPtr + Private Declare PtrSafe Function DeleteObject Lib "gdi32" (ByVal h As LongPtr) As Long + Private Declare PtrSafe Function DeleteDC Lib "gdi32" (ByVal hDC As LongPtr) As Long + Private Declare PtrSafe Function GetBitmapBits Lib "gdi32" (ByVal hBitmap As LongPtr, ByVal count As Long, ByRef bits As Any) As Long + + ' The pixels of a window, as a string that equals another only when every pixel does. + Public Function PictureOf(ByVal hWnd As LongPtr) As String + Const W As Long = 200, H As Long = 120 + Dim screen As LongPtr = GetDC(0) + Dim memory As LongPtr = CreateCompatibleDC(screen) + Dim bitmap As LongPtr = CreateCompatibleBitmap(screen, W, H) + Dim old As LongPtr = SelectObject(memory, bitmap) + PrintWindow hWnd, memory, 1 + SelectObject memory, old + Dim bits() As Byte + ReDim bits(W * H * 4 - 1) + GetBitmapBits bitmap, W * H * 4, bits(0) + DeleteObject bitmap + DeleteDC memory + ReleaseDC 0, screen + PictureOf = bits + End Function +End Module +``` + +```tb probe check_run projname=listview-textbackground project=wnc-form id=listview-textbackground +Load Form1 +Form1.Show +With Form1.ListView1 + .View = lvwList + .ListItems.Add , , "Alpha" + .ListItems.Add , , "Beta" + .Refresh + DoEvents + Dim plain As String = PictureOf(.hWnd) + .TextBackground = lvwOpaque + .Refresh + DoEvents + Debug.Print PictureOf(.hWnd) = plain ' True + .BackColor = vbRed + .Refresh + DoEvents + Debug.Print PictureOf(.hWnd) = plain ' False +End With +Unload Form1 +``` + ### View {: .no_toc } diff --git a/docs/Reference/Contributed/GdiPlus/Graphics.md b/docs/Reference/Contributed/GdiPlus/Graphics.md index 797218538..08ededf0e 100644 --- a/docs/Reference/Contributed/GdiPlus/Graphics.md +++ b/docs/Reference/Contributed/GdiPlus/Graphics.md @@ -36,7 +36,7 @@ A **Graphics** object is created with a package function of the same name as the A **Graphics** object does not own its target. The device context or window must stay valid for as long as the object exists. A **Graphics** object created for an image keeps a reference to the image, so the image is not released before the **Graphics** object. - + > [!NOTE] 0.9.45.0 > The package needs a [**GdiPlusUser**](GdiPlusUser) object to be alive for as long as any GDI+ object exists. A program creates one before the first **Graphics** object and keeps it. @@ -200,7 +200,7 @@ Sub Form_Resize() End Sub ``` - + > [!WARNING] 0.9.45.0 > A **GdiPlusUser** declared with **Dim ... As New** is not created until the variable is first used, so the GDI+ objects that are created before that fail with **GdiplusNotInitialized** (18). Declare the variable and set it with `Set mGdiPlus = GdiPlusUser()` in the **Load** event, as above. diff --git a/docs/Reference/Contributed/GdiPlus/index.md b/docs/Reference/Contributed/GdiPlus/index.md index e796dc243..d779347d0 100644 --- a/docs/Reference/Contributed/GdiPlus/index.md +++ b/docs/Reference/Contributed/GdiPlus/index.md @@ -146,7 +146,7 @@ A **GdiPlusUser** must exist while any GDI+ object is used. It starts GDI+, and Dim user As GdiPlusUser = GdiPlusUser() ``` - + > [!WARNING] 0.9.45.0 > A **GdiPlusUser** declared `Dim user As New GdiPlusUser` is not created until the variable is first used, so the GDI+ objects made before that fail with status 18. Use `Dim user As GdiPlusUser = GdiPlusUser()`, or `Set` the variable in the load handler. See [**GdiPlusUser**](GdiPlusUser). diff --git a/docs/Reference/Contributed/TBMANLIB/cHttpClient.md b/docs/Reference/Contributed/TBMANLIB/cHttpClient.md index 077d94064..b1e529ef8 100644 --- a/docs/Reference/Contributed/TBMANLIB/cHttpClient.md +++ b/docs/Reference/Contributed/TBMANLIB/cHttpClient.md @@ -58,7 +58,7 @@ Several error texts of the class are written in Chinese. The ones that are not a | 1 | no download path was found, or the file could not be read | **FinishDownloadFile**, **UploadFile** | - + > [!WARNING] 1.0.0.29 > The class ignores every certificate error: an expired certificate, one from an unknown authority and one issued for another name are all accepted. An HTTPS request therefore encrypts the traffic, but does not prove who the server is, and a program that sends a password or a key with it can send it to an impostor. diff --git a/docs/Reference/Contributed/TBMANLIB_Diagnostic/index.md b/docs/Reference/Contributed/TBMANLIB_Diagnostic/index.md index 267358363..6df91686f 100644 --- a/docs/Reference/Contributed/TBMANLIB_Diagnostic/index.md +++ b/docs/Reference/Contributed/TBMANLIB_Diagnostic/index.md @@ -140,7 +140,7 @@ The package has four modules whose contents a program has no need for: > - **[FormatBytes](FormatHelper#formatbytes) with no decimals leaves the decimal separator in the text**, as in `4. KB`. The `Page Size` item of [**CPUInfoCollector**](Collectors#cpuinfocollector) is written this way. > - **[NetworkInfoCollector](Collectors#networkinfocollector) lists the first adapter only in a 64-bit program.** It reads the adapters in steps of 640 bytes, and the records are 704 bytes apart in a 64-bit program. The summary may then have no `Primary IP`. A 32-bit program lists every adapter. - + > [!WARNING] 1.0.0.1 > **[LocaleCollector](Collectors#localecollector) reports the offset of standard time as `Timezone` all year.** In summer time the item is behind the clock by the daylight saving offset: `UTC+1` where the clock is at UTC+2, as in Central European Summer Time. diff --git a/lib/package-tests.mjs b/lib/package-tests.mjs index 41fb9caed..096912745 100644 --- a/lib/package-tests.mjs +++ b/lib/package-tests.mjs @@ -13,7 +13,9 @@ // // A compile case that expects an error is a compile error of its project, and the compiler runs // no [TestCase] in a project with errors. So those cases are a second project, the package -// folder's compile/, which holds compile cases only and is never published. +// folder's compile/, which holds compile cases only and is never published. A case that must run +// in a process of its own, because the IDE's has changed what it tests, is in a third, exe/, which +// is built and run as an exe and is never published either. import { createHash } from "node:crypto"; import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"; @@ -23,6 +25,7 @@ export const EXPECT_FILE = "Expect.twin"; export const RUNALL_FILE = "RunAll.twin"; export const EXPECT_JSON = "expect.json"; export const COMPILE_DIR = "compile"; +export const EXE_DIR = "exe"; export const RESOURCES_DIR = "Resources"; /** The file name of a package's published test project. */ @@ -137,6 +140,22 @@ export function compileSources(root, pkg) { return existsSync(dir) ? filesUnder(dir) : []; } +/** + * The exe project's source files: Expect.twin, then the .twin files of the package folder's exe/ in + * name order, each with LF line ends; none when the folder is absent. The folder may also hold a + * server.mjs for the cases to call, which is no source of the project. + * + * @returns {{name: string, text: string}[]} `name` is the path under exe/ + */ +export function exeSources(root, pkg) { + const dir = path.join(root, pkg, EXE_DIR); + if (!existsSync(dir)) return []; + return [ + { name: EXPECT_FILE, text: lf(readFileSync(path.join(root, EXPECT_FILE), "utf8")) }, + ...filesUnder(dir).filter((f) => f.name.endsWith(".twin")), + ]; +} + /** * The project's resources: the package folder's Resources/ (`/`, as an exported * project keeps them) in name order, byte for byte; none when the folder is absent. diff --git a/scripts/check_callout_evidence.mjs b/scripts/check_callout_evidence.mjs index 943797d6b..9610f8527 100644 --- a/scripts/check_callout_evidence.mjs +++ b/scripts/check_callout_evidence.mjs @@ -128,7 +128,7 @@ const { check, report } = createProbes("check_callout_evidence", "see scripts/li { const items = parseMarker( - "bug a-b, lane addin:help, sample x.y-1, errors VBA.Math, llvm win64-llvm, F.S, compile C, probe p1, bug Bad_Slug, lane web:x, wat", + "bug a-b, lane addin:help, sample x.y-1, errors VBA.Math, llvm win64-llvm, static links-win32, F.S, compile C, probe p1, bug Bad_Slug, lane web:x, wat", ); check( "every kind of item is read, and a malformed one is bad", @@ -139,6 +139,7 @@ const { check, report } = createProbes("check_callout_evidence", "see scripts/li { sample: "x.y-1" }, { errors: "VBA.Math" }, { llvm: "win64-llvm" }, + { static: "links-win32" }, { case: "F.S" }, { compile: "C" }, { probe: "p1" }, @@ -169,6 +170,7 @@ const { check, report } = createProbes("check_callout_evidence", "see scripts/li ]), errors: new Set(["VBA.Math"]), llvm: new Set(["win32"]), + static: new Set(["links-win32"]), }; const verdicts = [ { bug: "a-b" }, @@ -183,11 +185,13 @@ const { check, report } = createProbes("check_callout_evidence", "see scripts/li { errors: "VBA.Nope" }, { llvm: "win32" }, { llvm: "win99" }, + { static: "links-win32" }, + { static: "links-win99" }, { case: "F.S" }, ].map((i) => itemProblem(i, index)); check( "an item that names nothing that exists, a fence that is context or two fences with one id is a problem", - verdicts.map((v) => (v ? "x" : ".")).join("") === ".x.x.xxx.x.x.", + verdicts.map((v) => (v ? "x" : ".")).join("") === ".x.x.xxx.x.x.x.", JSON.stringify(verdicts, null, 1), ); } diff --git a/scripts/check_examples.mjs b/scripts/check_examples.mjs index 7e1acce7e..427827ef2 100644 --- a/scripts/check_examples.mjs +++ b/scripts/check_examples.mjs @@ -117,6 +117,7 @@ import { import { CANARIES, CANARY_FILES, + DECLARES_HOOK, DEFAULT_BATCH, DEFAULT_JOBS, applyExclusions, @@ -716,7 +717,7 @@ let tidy = null; // quiet period or limit from test/harness.json (LIMITS). // Whether a batch's own samples bring a [RunAfterBuild] Sub, whose compile a build // follows after the link (buildProject); a run batch's dispatcher is captureRun's. -const runsAfterBuild = (staged) => [...staged.map.values()].some((e) => /\[\s*RunAfterBuild\b/i.test(e.fence.content)); +const runsAfterBuild = (staged) => [...staged.map.values()].some((e) => DECLARES_HOOK.test(e.fence.content)); const runWait = () => ({ done: (lines) => lines.some((l) => l.trim() === RUN_DONE), runMs: LIMITS.compilerRun, diff --git a/scripts/check_package_tests.mjs b/scripts/check_package_tests.mjs index 194c65cd1..eb6f2f6c4 100644 --- a/scripts/check_package_tests.mjs +++ b/scripts/check_package_tests.mjs @@ -43,6 +43,8 @@ import { markdownFiles } from "../lib/markdown-files.mjs"; import { COMPILE_DIR, compileSources, + EXE_DIR, + exeSources, EXPECT_FILE, EXPECT_JSON, packageTestNames, @@ -55,6 +57,7 @@ import { REPO_ROOT } from "../lib/repo-paths.mjs"; import { createProbes } from "./lib/gate-probes.mjs"; import { findCases, judgeCompiles } from "./lib/tb-tests.mjs"; import { + exeOutcomes, expectationFor, expectFrom, judge, @@ -62,6 +65,7 @@ import { outcomeOf, projectCases, runAllText, + runExeText, } from "./lib/package-test-cases.mjs"; import { kindOf, markersOf, PACKAGE_KINDS, parseMarker } from "./lib/test-markers.mjs"; @@ -142,6 +146,30 @@ const { check, report } = createProbes("check_package_tests", "see test/package- check("a case of the project's own source is not met by a package's row", !local.pass, JSON.stringify(local)); } +{ + const text = runExeText([ + { fixture: "A", name: "One" }, + { fixture: "A", name: "Two" }, + ]); + check( + "the exe project's runner tells tbrun the build ended, and runs and prints each case in order", + /\[RunAfterBuild\]/.test(text) && + /Public Sub Main\(\)/.test(text) && + text.indexOf('TbRun.Out "CASE A.One: "') > 0 && + text.indexOf('TbRun.Out "CASE A.One: "') < text.indexOf('TbRun.Out "CASE A.Two: "'), + text, + ); + const got = exeOutcomes(["built", "CASE A.One: ", "CASE A.Two: 1 of 1 checks failed: x: expected 1, got 2"]); + check( + "a case's line with nothing after its name passed, and one with a report failed with it, its colons kept", + got.size === 2 && + got.get("A.One").kind === "pass" && + got.get("A.Two").kind === "fail" && + got.get("A.Two").detail === "1 of 1 checks failed: x: expected 1, got 2", + JSON.stringify([...got]), + ); +} + { // The compiler counts a lone CR as a line end (BETA 1010), so a compile case // is numbered the same whichever of the three ends the lines above it. @@ -374,6 +402,11 @@ function checkPackage(pkg) { const compileProject = projectCases(compileSources(ROOT, pkg)); out.push(...compileProject.problems.map((p) => `${COMPILE_DIR}/: ${p}`)); if (compileProject.tests.length) out.push(`${COMPILE_DIR}/ holds a [TestCase]: it holds compile cases only`); + const exeFiles = exeSources(ROOT, pkg); + const exeProject = projectCases(exeFiles); + out.push(...exeProject.problems.map((p) => `${EXE_DIR}/: ${p}`), ...malformedCases(exeFiles, exeProject.tests)); + if (exeProject.compiles.length) out.push(`${EXE_DIR}/ holds a compile case: they go in ${COMPILE_DIR}/`); + const allTests = [...tests, ...exeProject.tests]; const compiles = [...own, ...compileProject.compiles]; for (const c of compileProject.compiles) if (own.some((x) => x.name === c.name)) out.push(`two compile cases are named ${c.name}`); @@ -397,7 +430,7 @@ function checkPackage(pkg) { ) out.push(`${EXPECT_JSON}: arches is a non-empty list of win32 and win64, or absent for both`); } - const caseNames = new Set([...tests.map((t) => `${t.fixture}.${t.name}`), ...compiles.map((c) => c.name)]); + const caseNames = new Set([...allTests.map((t) => `${t.fixture}.${t.name}`), ...compiles.map((c) => c.name)]); for (const name of Object.keys(expect.cases ?? {})) { if (!caseNames.has(name)) out.push(`${EXPECT_JSON} names ${name}, which is not a case`); try { @@ -434,7 +467,7 @@ function checkPackage(pkg) { // A bad item, and one that names something outside the project, are // check_callout_evidence.mjs's to report. for (const item of mk.items.filter((i) => PACKAGE_KINDS.has(kindOf(i)))) { - if (item.case && !tests.some((t) => `${t.fixture}.${t.name}` === item.case)) + if (item.case && !allTests.some((t) => `${t.fixture}.${t.name}` === item.case)) out.push(`${where}: no [TestCase] ${item.case}`); else if (item.compile && !compiles.some((x) => x.name === item.compile)) out.push(`${where}: no compile case ${item.compile}`); diff --git a/scripts/lib/cli-cases.mjs b/scripts/lib/cli-cases.mjs index a6792ab94..c587831b5 100644 --- a/scripts/lib/cli-cases.mjs +++ b/scripts/lib/cli-cases.mjs @@ -1047,7 +1047,10 @@ bad("eval/nav_hops.mjs", ["Reference", "[a-"], REGEX_REASON("", "[a-" bad( "scripts/recheck_callouts.mjs", ["--kinds", "bug,lanes"], - thenUsage("--kinds: lanes is not one of bug, lane, sample, errors, llvm, package", "scripts/recheck_callouts.mjs"), + thenUsage( + "--kinds: lanes is not one of bug, lane, sample, errors, llvm, static, package", + "scripts/recheck_callouts.mjs", + ), ); // build_corpus empties its --dest before it writes, so it refuses one that is or diff --git a/scripts/lib/example-batches.mjs b/scripts/lib/example-batches.mjs index 3a09a955e..a0106f309 100644 --- a/scripts/lib/example-batches.mjs +++ b/scripts/lib/example-batches.mjs @@ -71,6 +71,8 @@ export const TEMPLATE_BASE = { "vb-private": "console", "cc-private": "packages", "wnc-private": "packages", + "wnc-form": "packages", + "activex-control": "console", "cef-private": "cef", implicit: "console", form: "console", @@ -217,11 +219,17 @@ function unitKey(fence) { return fence.keys.get("projname") ? `@${fence.keys.get("projname")}` : `#${fence.id}`; } +/** A sample that declares a [RunAfterBuild] Sub, of which a project may hold one (TB5114). */ +export const DECLARES_HOOK = /\[\s*RunAfterBuild\b/i; + /** * `alone`, given a unit's fences, says whether the unit is to be built in a * project of its own with no template Main (`noMain` on the batch): a run that * builds passes one for a unit declaring its own `Sub Main`, because a build, - * unlike a compile, fails on two. + * unlike a compile, fails on two. A unit that declares a [RunAfterBuild] Sub + * always gets a project of its own, with the template's Main: two of them in a + * batch are TB5114, and where a batch ends is no guard, since a new sample on + * any page moves it. */ export function makeBatches(fences, { batchSize = DEFAULT_BATCH, jobs = DEFAULT_JOBS, alone = null } = {}) { // A `hidden` fence is not a unit of its own: it is the PAGE's context, and @@ -298,7 +306,7 @@ export function makeBatches(fences, { batchSize = DEFAULT_BATCH, jobs = DEFAULT_ // written by hand, so there are never many. // A unit `alone` picks gets one too, built without the template's Main. const solo = !!alone?.(members); - if (key.startsWith("@") || solo) { + if (key.startsWith("@") || solo || members.some((f) => DECLARES_HOOK.test(f.content))) { const own = new Set(names); for (const rel of pages) for (const n of hiddenNamesFor(rel)) own.add(n); batches.push({ @@ -1465,6 +1473,15 @@ export async function runProbes(say) { if (soloShape !== "s2! s1,s3" && soloShape !== "s1,s3 s2!") { failures.push(`batching: a unit alone picks -> ${soloShape}, want s2 alone and marked`); } + const hook = { content: "[RunAfterBuild]\nSub Hook()\nEnd Sub\n" }; + const hooks = makeBatches([{ ...fake("h1"), ...hook }, { ...fake("h2"), ...hook }, fake("p1"), fake("p2")]); + const hookShape = hooks + .map((b) => `${b.fences.map((f) => f.id)}${b.noMain ? "!" : ""}`) + .sort() + .join(" "); + if (hookShape !== "h1 h2 p1,p2") { + failures.push(`batching: two samples with a [RunAfterBuild] -> ${hookShape}, want each alone, with Main`); + } const soleGroup = makeBatches([fake("g1", "g"), fake("g2", "g")])[0]; if (splitBatch(soleGroup) !== null) failures.push("split: a projname group was cut in half"); const mixed = makeBatches([ @@ -2342,6 +2359,12 @@ export async function runProbes(say) { consoleStage.filter((l) => l !== standIn).join("\n") === formStage.join("\n"), "templates: the form template's stage set is not the console's less `Public Form1 As Form`", ); + const packagesStage = declarationsOf(readTemplate("packages", "tbxStage.twin")); + const wncFormStage = declarationsOf(readTemplate("wnc-form", "tbxStage.twin")); + expect( + packagesStage.filter((l) => l !== standIn).join("\n") === wncFormStage.join("\n"), + "templates: the wnc-form template's stage set is not the packages template's less `Public Form1 As Form`", + ); const formClass = readTemplate("form", "Form1.twin"); const formDesigner = JSON.parse(readTemplate("form", "Form1.tbform")); const classId = /\[FormDesignerId\("([0-9A-F-]+)"\)\]/i.exec(formClass)?.[1]; diff --git a/scripts/lib/package-test-cases.mjs b/scripts/lib/package-test-cases.mjs index 87a46ddb7..5fe419c70 100644 --- a/scripts/lib/package-test-cases.mjs +++ b/scripts/lib/package-test-cases.mjs @@ -66,6 +66,59 @@ export function runAllText({ pkg, version, tests, expect }) { return lines.join("\n"); } +/** + * The text of the module that runs the exe project's cases, written into the staged copy: a built + * exe starts at its Sub Main, and only TbRun.Out reaches the run from there. Each case prints a line + * that exeOutcomes reads. The [RunAfterBuild] Sub does nothing but tell tbrun the build has ended. + * + * @param {{fixture: string, name: string}[]} tests + */ +export function runExeText(tests) { + const lines = [ + "Module RunExe", + "", + " [RunAfterBuild]", + " Public Sub Built()", + " Debug.Cls", + ' Debug.Print "built"', + " End Sub", + "", + " Public Sub Main()", + ' Expect.StartRun ""', + " On Error Resume Next", + ]; + for (const t of tests) { + const name = caseName(t); + lines.push( + " Expect.StartCase", + " Err.Clear", + ` ${name}`, + ` TbRun.Out "${EXE_LINE}${name}: " & Expect.Outcome(Err.Number, Err.Description)`, + ); + } + lines.push(" On Error GoTo 0", " End Sub", "", "End Module", ""); + return lines.join("\n"); +} + +const EXE_LINE = "CASE "; + +/** + * What the exe's lines say of its cases. + * + * @param {string[]} lines what tbrun --exe read from TbRun.Out + * @returns {Map} + */ +export function exeOutcomes(lines) { + const out = new Map(); + for (const line of lines) { + if (!line.startsWith(EXE_LINE)) continue; + const at = line.indexOf(":"); + const detail = normaliseDetail(line.slice(at + 1)); + out.set(line.slice(EXE_LINE.length, at), detail ? { kind: "fail", detail } : { kind: "pass", detail: "" }); + } + return out; +} + // Why RunAll skips a case on one architecture, or "": one that never returns would stop the run, // and one that ends in a native exception (a stack overflow, an access violation) may take the // reader's IDE or exe down with it, as it does a built exe. package_tests.mjs still runs both. diff --git a/scripts/lib/recheck.mjs b/scripts/lib/recheck.mjs index 7c3170fb5..648399792 100644 --- a/scripts/lib/recheck.mjs +++ b/scripts/lib/recheck.mjs @@ -27,7 +27,7 @@ export function checkKey(item, pkg) { export const keyKind = (key) => key.split(" ")[0]; /** The kinds a run can be limited to; `none` and a package's probe are never run. */ -export const RUN_KINDS = ["bug", "lane", "sample", "errors", "llvm", "package"]; +export const RUN_KINDS = ["bug", "lane", "sample", "errors", "llvm", "static", "package"]; // `bug_repro.mjs verify`'s line for a reproducer. const VERIFY_RE = diff --git a/scripts/lib/test-markers.mjs b/scripts/lib/test-markers.mjs index 879d5be67..7412a4eba 100644 --- a/scripts/lib/test-markers.mjs +++ b/scripts/lib/test-markers.mjs @@ -23,6 +23,7 @@ // one; it holds while examples.bat finds nothing wrong with it // errors the run-time error data errors/.yml, which `errors.mjs run` checks // llvm a mode of the LLVM claims probe, test/llvm/run.mjs +// static a mode of the static linking claims probe, test/static-linking/run.mjs // none -- nothing a program can check: a person checks it again // // The first three belong to a contributed package's section, which has a test project @@ -51,6 +52,9 @@ const ERRORS_CLOSE_RE = /^[ \t]*$/; /** The item kinds that name a case of a contributed package's test project. */ export const PACKAGE_KINDS = new Set(["case", "compile", "probe"]); +/** The claims probes: the item kinds that name a mode of one, and the folder that holds it. */ +export const CLAIM_PROBES = { llvm: "test/llvm", static: "test/static-linking" }; + /** The lane suites: the runner a `lane` item's lanes run in, and the file that lists them. */ export const SUITES = { ide: { tool: "scripts/ide_test.mjs", lanes: "test/ide/lanes.mjs" }, @@ -129,8 +133,8 @@ export function markersOf(text) { /** * The items of a marker, each an object with one key: { case: "Fixture.Sub" }, { compile }, - * { probe }, { bug }, { lane: "ide:" }, { sample }, { errors }, { llvm } or { none }, and - * { bad: "text" } for one the grammar does not know. + * { probe }, { bug }, { lane: "ide:" }, { sample }, { errors }, { llvm }, { static } or + * { none }, and { bad: "text" } for one the grammar does not know. */ export function parseMarker(text) { const none = /^none[ \t]*(?:--|:)?[ \t]*(.*)$/.exec(text); @@ -141,7 +145,7 @@ export function parseMarker(text) { .filter(Boolean) .map((s) => { if (/^\w+\.\w+$/.test(s)) return { case: s }; - const m = /^(compile|probe|bug|lane|sample|errors|llvm)[ \t]+(\S+)$/.exec(s); + const m = /^(compile|probe|bug|lane|sample|errors|llvm|static)[ \t]+(\S+)$/.exec(s); if (!m) return { bad: s }; const [, kind, value] = m; const shape = { @@ -152,6 +156,7 @@ export function parseMarker(text) { sample: /^[\w.-]+$/, errors: /^[\w.-]+$/, llvm: /^[\w-]+$/, + static: /^[\w-]+$/, }[kind]; return shape.test(value) ? { [kind]: value } : { bad: s }; }); @@ -212,9 +217,11 @@ export async function evidenceIndex({ fences, root = REPO_ROOT }) { .map((f) => f.slice(0, -4)) : [], ); - const llvmFile = path.join(root, "test", "llvm", "expect.json"); - const llvm = new Set(existsSync(llvmFile) ? Object.keys(JSON.parse(readFileSync(llvmFile, "utf8")).modes ?? {}) : []); - return { bugs, lanes, samples, errors, llvm }; + const modesOf = (folder) => { + const file = path.join(root, folder, "expect.json"); + return new Set(existsSync(file) ? Object.keys(JSON.parse(readFileSync(file, "utf8")).modes ?? {}) : []); + }; + return { bugs, lanes, samples, errors, llvm: modesOf(CLAIM_PROBES.llvm), static: modesOf(CLAIM_PROBES.static) }; } /** @@ -229,7 +236,7 @@ export function itemProblem(item, index) { const value = item[kind]; switch (kind) { case "bad": - return `"${value}" is not an item: Fixture.Sub, compile , probe , bug , lane :, sample , errors , llvm or none -- `; + return `"${value}" is not an item: Fixture.Sub, compile , probe , bug , lane :, sample , errors , llvm , static or none -- `; case "bug": return index.bugs.has(value) ? null : `no reproducer bugs/${value}/ or bugs/filed/${value}/ with a repro.json`; case "lane": @@ -247,9 +254,10 @@ export function itemProblem(item, index) { case "errors": return index.errors.has(value) ? null : `no error data errors/${value}.yml`; case "llvm": - return index.llvm.has(value) + case "static": + return index[kind].has(value) ? null - : `no mode ${value} in test/llvm/expect.json (it has ${[...index.llvm].join(", ")})`; + : `no mode ${value} in ${CLAIM_PROBES[kind]}/expect.json (it has ${[...index[kind]].join(", ")})`; default: return null; } diff --git a/scripts/package_tests.mjs b/scripts/package_tests.mjs index 45a683741..50a88e538 100644 --- a/scripts/package_tests.mjs +++ b/scripts/package_tests.mjs @@ -33,6 +33,11 @@ // has that folder and no project: its cases name the rows of the package's own sources. A compile // case that names an architecture is run on that one, whatever expect.json's arches says. // +// A [TestCase] that needs a process of its own, because the IDE's has already started something it +// tests, is in the package folder's exe/: the project is built and run with `tbrun --exe`, every +// case in order in one process, and judged with the rest. A server.mjs there is started first, +// prints its port, and is named to the exe by PACKAGE_TEST_PORT. +// // RunAll.twin, which the project's Sub Main runs, is generated from the cases and expect.json: // the run writes it again when it is out of date, and says so. // @@ -41,7 +46,7 @@ // Exit: 0 every case documented, 1 a case fixed or changed, 2 the harness failed or the command // line was refused. -import { spawnSync } from "node:child_process"; +import { spawn, spawnSync } from "node:child_process"; import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; @@ -59,6 +64,8 @@ import { COMPILE_DIR, compileOnlyNames, compileSources, + EXE_DIR, + exeSources, RESOURCES_DIR, resourceFiles, EXPECT_JSON, @@ -75,12 +82,14 @@ import { embeddedPackages } from "./lib/example-batches.mjs"; import { ARCHES, archesOf, + exeOutcomes, expectationFor, expectFrom, judge, outcomeOf, projectCases, runAllText, + runExeText, } from "./lib/package-test-cases.mjs"; import { unpackProject } from "./lib/tb-project.mjs"; @@ -195,6 +204,10 @@ async function runPackage(pkg) { const compileProject = projectCases(compileFiles); problems.push(...compileProject.problems.map((p) => `${COMPILE_DIR}/: ${p}`)); if (compileProject.tests.length) problems.push(`${COMPILE_DIR}/ holds a [TestCase]: it holds compile cases only`); + const exeFiles = exeSources(ROOT, pkg); + const exeProject = projectCases(exeFiles); + problems.push(...exeProject.problems.map((p) => `${EXE_DIR}/: ${p}`)); + if (exeProject.compiles.length) problems.push(`${EXE_DIR}/ holds a compile case: they go in ${COMPILE_DIR}/`); if (problems.length) die(2, `${pkg}:\n${problems.map((p) => ` ${p}`).join("\n")}`); if (hasProject) { refreshRunAll(pkg, version, tests, expect); @@ -202,7 +215,13 @@ async function runPackage(pkg) { } const compileNames = compileProject.compiles.map((c) => c.name); - const names = [...compiles.map((c) => c.name), ...compileNames, ...tests.map((t) => `${t.fixture}.${t.name}`)]; + const exeNames = exeProject.tests.map((t) => `${t.fixture}.${t.name}`); + const names = [ + ...compiles.map((c) => c.name), + ...compileNames, + ...tests.map((t) => `${t.fixture}.${t.name}`), + ...exeNames, + ]; const twice = names.filter((n, i) => names.indexOf(n) !== i); if (twice.length) die(2, `${pkg}: two cases are named ${[...new Set(twice)].join(", ")}`); say(`${pkg} ${version}: ${names.length} case${names.length === 1 ? "" : "s"}`); @@ -220,9 +239,19 @@ async function runPackage(pkg) { for (const r of tbrunCases(compileTree, arch, compileHere, opts.timeout)) outcomes.get(r.name)[arch] = outcomeOf(r); } + if (exeNames.length && arches.includes(arch)) { + const runner = { name: "RunExe.twin", text: runExeText(exeProject.tests) }; + const exeTree = stageTree(`${pkg}-${EXE_DIR}`, pkg, arch, template, [...exeFiles, runner]); + const server = await startServer(pkg); + try { + for (const [name, o] of exeCases(exeTree, arch, exeNames, server?.port)) outcomes.get(name)[arch] = o; + } finally { + server?.child.kill(); + } + } if (!hasProject || !arches.includes(arch)) continue; const tree = stageTree(pkg, pkg, arch, template, files, resourceFiles(ROOT, pkg)); - const own = names.filter((n) => !compileNames.includes(n)); + const own = names.filter((n) => !compileNames.includes(n) && !exeNames.includes(n)); const hangs = own.filter((n) => expectationFor(expect, n, arch).kind === "hang"); let rest = own.filter((n) => !hangs.includes(n)); // A case that does not return keeps the compiler busy, so tbrun reports the cases it did not @@ -337,6 +366,39 @@ function stageTree(label, pkg, arch, template, files, resources = []) { return tree; } +// The package's exe/server.mjs, when it has one, once it has printed its port. +function startServer(pkg) { + const file = path.join(ROOT, pkg, EXE_DIR, "server.mjs"); + if (!existsSync(file)) return null; + return new Promise((resolve, reject) => { + const child = spawn(process.execPath, [file], { stdio: ["ignore", "pipe", "inherit"], windowsHide: true }); + child.on("error", reject); + child.on("exit", (code) => reject(new Error(`${file} exited with code ${code} before it printed a port`))); + child.stdout.once("data", (d) => resolve({ child, port: String(d).trim() })); + }); +} + +// tbrun --exe --json on the exe project; each case's outcome, by name. Exit 6 is an exe that ended +// badly, which leaves the cases it did not reach with no line, and the reason in their detail. +function exeCases(tree, arch, names, port) { + const args = [TBRUN, tree, "--exe", "--json", "--arch", arch]; + if (values.ide) args.push("--ide", values.ide); + const env = port ? { ...process.env, PACKAGE_TEST_PORT: port } : process.env; + const r = spawnSync(process.execPath, args, { encoding: "utf8", maxBuffer: 64 * 1024 * 1024, env }); + let run = null; + try { + run = JSON.parse(r.stdout).exeRun; + } catch { + // below + } + if (!run || (r.status !== 0 && r.status !== 6)) { + throw new Error(`tbrun --exe exited ${r.status} on ${tree} (${arch}):\n${r.stderr || r.stdout}`); + } + const got = exeOutcomes(run.lines); + const end = run.timedOut ? "the exe was ended at its limit" : `the exe exited with code ${run.exitCode}`; + return names.map((n) => [n, got.get(n) ?? { kind: "fail", detail: `no result: ${end}` }]); +} + // tbrun --tests --json on some of the cases; its cases in the order asked. function tbrunCases(tree, arch, names, timeoutSecs) { const args = [TBRUN, tree, "--tests", "--json", "--arch", arch]; diff --git a/scripts/recheck_callouts.mjs b/scripts/recheck_callouts.mjs index 814466d57..87b38aba6 100644 --- a/scripts/recheck_callouts.mjs +++ b/scripts/recheck_callouts.mjs @@ -19,6 +19,7 @@ // sample check_examples.mjs --json, over the pages that hold the samples // errors errors.mjs run, for twinBASIC 32-bit and 64-bit (what VB6 does does not change) // llvm test/llvm/run.mjs --only +// static test/static-linking/run.mjs --only // package package_tests.mjs --json, for the packages whose cases are named // // Every tool's output is kept under %TEMP%\recheck-callouts\\, and --report writes what was @@ -45,7 +46,7 @@ import { } from "./lib/recheck.mjs"; import { archesOf, collectFences, RUN_MARKER } from "./lib/tb-fences.mjs"; import { buildNumber, findIde, unsupportedIde } from "./lib/tb-install.mjs"; -import { itemText, kindOf, markersOf, SUITES } from "./lib/test-markers.mjs"; +import { CLAIM_PROBES, itemText, kindOf, markersOf, SUITES } from "./lib/test-markers.mjs"; exitOnCrash(); @@ -323,10 +324,13 @@ for (const name of todo("errors")) { ); } -// The LLVM claims probe, one mode at a time. -for (const mode of todo("llvm")) { - const r = await runTool("test/llvm/run.mjs", ["--only", mode, ...ideArgs], `llvm-${mode}`); - set(`llvm ${mode}`, r.code === 0 ? "holds" : r.code === 1 ? "changed" : "error", `run.mjs exit ${r.code}`, r.log); +// The claims probes, one mode at a time. +for (const [kind, folder] of Object.entries(CLAIM_PROBES)) { + for (const mode of todo(kind)) { + const r = await runTool(`${folder}/run.mjs`, ["--only", mode, ...ideArgs], `${kind}-${mode}`); + const status = r.code === 0 ? "holds" : r.code === 1 ? "changed" : "error"; + set(`${kind} ${mode}`, status, `run.mjs exit ${r.code}`, r.log); + } } // The contributed packages' cases. diff --git a/test/example-projects/activex-control/Settings b/test/example-projects/activex-control/Settings new file mode 100644 index 000000000..7da2a041f --- /dev/null +++ b/test/example-projects/activex-control/Settings @@ -0,0 +1,61 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "Documentation samples", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "ActiveX Control", + "project.description": "Template for scripts/check_examples.mjs: the console template as an ActiveX Control project with a property page, for the samples of a UserControl. project.id, project.name and project.buildPath are rewritten in the staged copy, one per batch.", + "project.exportPathIsV2": true, + "project.id": "{7B247400-0000-4000-9000-7B2474000000}", + "project.name": "DocSamples", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/test/example-projects/activex-control/Sources/ProbeControl.tbform b/test/example-projects/activex-control/Sources/ProbeControl.tbform new file mode 100644 index 000000000..92e424d00 --- /dev/null +++ b/test/example-projects/activex-control/Sources/ProbeControl.tbform @@ -0,0 +1 @@ +[{"_clsid":"{33AD5010-6699-11CF-B70C-00AA0060D393}","_className":"UserControl","Name":"ProbeControl","Width":300,"Height":300,"FormDesignerId":"{7B247400-0000-4000-9000-7B2474000F51}"}] diff --git a/test/example-projects/activex-control/Sources/ProbePage.tbform b/test/example-projects/activex-control/Sources/ProbePage.tbform new file mode 100644 index 000000000..1578a8395 --- /dev/null +++ b/test/example-projects/activex-control/Sources/ProbePage.tbform @@ -0,0 +1 @@ +[{"_clsid":"{33AD5018-6699-11CF-B70C-00AA0060D393}","_className":"PropertyPage","Name":"ProbePage","Width":300,"Height":300,"FormDesignerId":"{7B247400-0000-4000-9000-7B2474000F50}"}] diff --git a/test/example-projects/activex-control/Sources/ProbePage.twin b/test/example-projects/activex-control/Sources/ProbePage.twin new file mode 100644 index 000000000..96cd80a5f --- /dev/null +++ b/test/example-projects/activex-control/Sources/ProbePage.twin @@ -0,0 +1,15 @@ +' A property page for the samples of a UserControl, as Add Windows PropertyPage writes one. The +' UserControl has only its designer file here, ProbeControl.tbform: its class is the sample, a +' `slot=file` fence that names the same designer id, each in a project of its own (`projname=`). + +[Description("")] +[FormDesignerId("7B247400-0000-4000-9000-7B2474000F50")] +[ClassId("7B247400-0000-4000-9000-7B2474000F52")] +[InterfaceId("7B247400-0000-4000-9000-7B2474000F53")] +[EventInterfaceId("7B247400-0000-4000-9000-7B2474000F54")] +Class ProbePage + + Sub New() + End Sub + +End Class diff --git a/test/example-projects/wnc-form/Sources/Form1.tbform b/test/example-projects/wnc-form/Sources/Form1.tbform new file mode 100644 index 000000000..8e964c5c6 --- /dev/null +++ b/test/example-projects/wnc-form/Sources/Form1.tbform @@ -0,0 +1,94 @@ +[ + { + "AlwaysShowKeyboardCues": false, + "Appearance": "vbAppear3d", + "AutoRedraw": false, + "BackColor": -2147483633, + "BorderStyle": "vbSizable", + "Caption": "Form1", + "ClipControls": true, + "ControlBox": true, + "DrawMode": "vbCopyPen", + "DrawStyle": "vbSolid", + "DrawWidth": 1, + "Enabled": true, + "FillColor": 0, + "FillStyle": "vbFSTransparent", + "FontBold": false, + "FontItalic": false, + "FontName": "Segoe UI", + "FontSize": 8, + "FontStrikethru": false, + "FontTransparent": true, + "FontUnderline": false, + "ForeColor": -2147483630, + "FormDesignerId": "{EAEAEAEA-EAEA-EAEA-EAEA-EAEAEAEAEA01}", + "HasDC": true, + "Height": 300, + "HelpContextID": 0, + "Icon": [], + "KeyPreview": false, + "Left": 0, + "LinkMode": "vbLinkNone", + "LinkTopic": null, + "MDIChild": false, + "MaxButton": true, + "MaxHeight": 0, + "MaxWidth": 0, + "MinButton": true, + "MinHeight": 0, + "MinWidth": 0, + "MouseIcon": [], + "MousePointer": "vbDefault", + "Moveable": true, + "Name": "Form1", + "NegotiateMenus": false, + "OLEDropMode": "vbOLEDropNone", + "Opacity": 100, + "Palette": [], + "PaletteMode": "vbPaletteModeHalftone", + "Picture": [], + "PictureDpiScaling": false, + "RightToLeft": false, + "ScaleHeight": 4500, + "ScaleLeft": 0, + "ScaleMode": "vbTwips", + "ScaleTop": 0, + "ScaleWidth": 4500, + "ShowInTaskbar": true, + "StartUpPosition": "vbStartUpScreen", + "TabFocusAutoSelect": false, + "Tag": null, + "Top": 0, + "TopMost": false, + "TransparencyKey": -1, + "Visible": true, + "WhatsThisButton": false, + "WhatsThisHelp": false, + "Width": 300, + "WindowState": "vbNormal", + "__IDEOptions": { + "alignToGrid": false, + "gridHeight": 10, + "gridWidth": 10, + "lockedControls": [], + "multiColoredGrabbers": false, + "showGrid": true, + "showOutlines": false + }, + "__lastUpdateMarker": 2061908507, + "_className": "Form", + "_clsid": "{33AD4F38-6699-11CF-B70C-00AA0060D393}", + "_children": [ + { + "Height": 120, + "Left": 8, + "Name": "ListView1", + "Top": 8, + "Width": 200, + "_className": "ListView", + "_clsid": "{ED6464B0-4324-4E2E-8A17-83A81F9D48DD}" + } + ] + } +] \ No newline at end of file diff --git a/test/example-projects/wnc-form/Sources/Form1.twin b/test/example-projects/wnc-form/Sources/Form1.twin new file mode 100644 index 000000000..acf8d72cb --- /dev/null +++ b/test/example-projects/wnc-form/Sources/Form1.twin @@ -0,0 +1,15 @@ +' A real form with one control on it, ListView1, for a sample that is run and looks at +' what a control of the WinNativeCommonCtls package draws. This is the `form` template's +' Form1 on the `packages` template (see WIP.ExamplesBuild.md). +' +' Form1.tbform is its designer file, and the two name the same designer id. + +[Description("")] +[FormDesignerId("EAEAEAEA-EAEA-EAEA-EAEA-EAEAEAEAEA01")] +[PredeclaredId] +Class Form1 + + Sub New() + End Sub + +End Class diff --git a/test/example-projects/wnc-form/Sources/tbxStage.twin b/test/example-projects/wnc-form/Sources/tbxStage.twin new file mode 100644 index 000000000..d02bac0a7 --- /dev/null +++ b/test/example-projects/wnc-form/Sources/tbxStage.twin @@ -0,0 +1,87 @@ +' The stage set for the package documentation -- see the console template's copy +' of this file for why it exists and why the list is written out rather than +' inferred. +' +' The VB half is the same; the rest are the control instances the +' Reference/Built-In and Tutorials pages assume on their forms. +' +' This is the `packages` template's file with ONE line fewer, as the `form` template's +' is the console's: `Public Form1 As Form`, since this template has a real Form1. + +Module tbxStage + + Public UserForm1 As Form + Public UserForm2 As Form + + Public Text1 As TextBox + Public Text2 As TextBox + Public TextBox1 As TextBox + + Public Label1 As Label + Public Timer1 As Timer + Public Command1 As CommandButton + Public Picture1 As PictureBox + Public PictureBox1 As PictureBox + + ' Browser controls -- CEF and WebView2. + Public CefBrowser1 As CefBrowser + Public WebView21 As WebView2 + + ' Windows Native Common Controls. + Public DTPicker1 As DTPicker + Public ImageList1 As ImageList + Public ListView1 As ListView + Public MonthView1 As MonthView + Public ProgressBar1 As ProgressBar + Public Slider1 As Slider + Public TreeView1 As TreeView + Public UpDown1 As UpDown + + ' The VB controls the package pages put on their forms alongside the + ' package's own, named as those pages name them. + Public List1 As ListBox + Public lblStatus As Label + Public lblClock As Label + Public lstNamedPipes As ListBox + Public MyForm As Form + + ' CustomControls -- the Waynes... instances the reference pages assume. + Public txtNotes As WaynesTextBox + Public lblHeader As WaynesLabel + Public lblTitle As WaynesLabel + Public btnGo As WaynesButton + Public btnDemo As WaynesButton + Public btnTab As WaynesButton + Public Grid1 As WaynesGrid + Public WaynesGrid1 As WaynesGrid + Public txtName As WaynesTextBox + Public sldProgress As WaynesSlider + Public sldVolume As WaynesSlider + Public Frame1 As WaynesFrame + Public pnlTree As WaynesFrame + Public pnlAside As WaynesFrame + Public pnlMain As WaynesFrame + Public pnlOne As WaynesFrame + Public pnlTwo As WaynesFrame + Public pnlThree As WaynesFrame + + ' tbIDE. An addin reaches the IDE through a Host, and opens tool windows; + ' both are what the addin SDK's pages are written against. + Public Host As tbIDE.Host + Public myToolWindow As tbIDE.ToolWindow + Public myWindow As tbIDE.ToolWindow + + ' The editor and DOM objects an addin has in hand by the time the samples + ' run: one comes from Host.Project.Editors, the others from a tool window's + ' HTML. The pages name them this way. + Public codeEditor As tbIDE.CodeEditor + Public element As tbIDE.HtmlElement + Public monacoDivElement As tbIDE.HtmlElement + + ' An addin keeps the toolbar buttons it created, so that its handlers can + ' reach them later; the Toolbar and Toolbars pages name theirs this way. + Public Button1 As tbIDE.Button + Public Button2 As tbIDE.Button + Public MyButton As tbIDE.Button + +End Module diff --git a/test/package-tests/Expect.twin b/test/package-tests/Expect.twin index 59f5c2f4c..11c775b79 100644 --- a/test/package-tests/Expect.twin +++ b/test/package-tests/Expect.twin @@ -78,22 +78,26 @@ Module Expect mReachedDone = False End Sub - ' What the case's call left: Done's report, or the error that ended it before Done. - Public Sub EndCase(ByVal Name As String, ByVal ErrNumber As Long, ByVal ErrDescription As String) - Dim outcome As String + ' What the case's call left: Done's report, or the error that ended it before Done. Nothing for a + ' case that passed. + Public Function Outcome(ByVal ErrNumber As Long, ByVal ErrDescription As String) As String If mReachedDone Then - outcome = mText + Outcome = mText ElseIf ErrNumber <> 0 Then - outcome = "run-time error " & ErrNumber & ": " & ErrDescription + Outcome = "run-time error " & ErrNumber & ": " & ErrDescription Else - outcome = "the case ended before Expect.Done" + Outcome = "the case ended before Expect.Done" End If - If Len(outcome) = 0 Then + End Function + + Public Sub EndCase(ByVal Name As String, ByVal ErrNumber As Long, ByVal ErrDescription As String) + Dim report As String = Outcome(ErrNumber, ErrDescription) + If Len(report) = 0 Then mPassed = mPassed + 1 Debug.Print "PASS " & Name Else mFailed = mFailed + 1 - Debug.Print "FAIL " & Name & ": " & outcome + Debug.Print "FAIL " & Name & ": " & report End If Begin End Sub diff --git a/test/package-tests/GdiPlus/exe/StartupTests.twin b/test/package-tests/GdiPlus/exe/StartupTests.twin new file mode 100644 index 000000000..4776eef2d --- /dev/null +++ b/test/package-tests/GdiPlus/exe/StartupTests.twin @@ -0,0 +1,47 @@ +' GdiPlusUser in a program that has not started GDI+. The IDE's process has, so in a test case run +' there a GDI+ object made with no GdiPlusUser works. These cases run in a built exe, in order, in +' one process, and GDI+ stays started until the process ends, so the two that need it stopped come +' first, and the second of them is the one that starts it. +[TestFixture] +Module StartupTests + + [TestCase] + Public Sub Bitmap_NoGdiPlusUser_FailsWithStatus18() + Expect.Begin + Dim bmp As Bitmap = Bitmap(8!, 8!) + Expect.AreEqual CLng(GdiplusNotInitialized), CLng(bmp.LastResult), "status of a Bitmap made with no GdiPlusUser" + Expect.Done + End Sub + + [TestCase] + Public Sub Bitmap_BeforeTheFirstUseOfADimAsNewUser_FailsWithStatus18() + Expect.Begin + Dim user As New GdiPlusUser + Dim before As Bitmap = Bitmap(8!, 8!) + Expect.AreEqual CLng(GdiplusNotInitialized), CLng(before.LastResult), "status of a Bitmap made before user is used" + Dim started As GpStatus = user.LastResult + Dim after As Bitmap = Bitmap(8!, 8!) + Expect.AreEqual CLng(Ok), CLng(after.LastResult), "status of a Bitmap made after user is first used" + Expect.Done + End Sub + + [TestCase] + Public Sub Bitmap_GdiPlusUserFirst_IsCreated() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Bitmap(8!, 8!) + Expect.AreEqual CLng(Ok), CLng(bmp.LastResult), "status of a Bitmap made after a GdiPlusUser" + Expect.Done + End Sub + + [TestCase] + Public Sub Bitmap_AfterSetOfAUser_IsCreated() + Expect.Begin + Dim user As GdiPlusUser + Set user = GdiPlusUser() + Dim bmp As Bitmap = Bitmap(8!, 8!) + Expect.AreEqual CLng(Ok), CLng(bmp.LastResult), "status of a Bitmap made after Set of a GdiPlusUser" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/README.md b/test/package-tests/README.md index 34ed91895..f19b76f7c 100644 --- a/test/package-tests/README.md +++ b/test/package-tests/README.md @@ -13,6 +13,8 @@ test/package-tests/ .twin [TestFixture] modules of [TestCase] Subs RunAll.twin generated: Sub Main runs every case expect.json how each case fails on the documented version + compile/ optional: compile cases that expect an error + exe/ optional: cases that run in a built exe, and a server.mjs for them probe/ optional: a harness for what only a built program shows ``` @@ -75,6 +77,13 @@ End Module - **Resources** a case reads go in the package folder's `Resources/`, laid out as an exported project keeps them (`/`, such as `BINARY/#101`). The runner stages them and the published project carries them, byte for byte. +- **A claim the IDE's own process changes goes in the package folder's `exe/`**: `[TestCase]` Subs + written as any others, which `package_tests.mjs` builds and runs with `tbrun --exe`, in file + order in one process, and judges with the rest, under the same names in `expect.json` and in the + markers (GdiPlus: the IDE has already started GDI+). `Debug.Print` writes nothing there. A + `server.mjs` in the folder is started first, prints its port on its first line, and the exe finds + the port in `PACKAGE_TEST_PORT` (TBMANLIB: an HTTPS server whose certificate fails every check). + It is not published, and it needs no `RunAll`. - A claim that needs a dialog operated or a form shown goes in the package's `probe/` harness, as TBMANLIB_Dialog's and xCalendar's do. diff --git a/test/package-tests/TBMANLIB/exe/HttpsTests.twin b/test/package-tests/TBMANLIB/exe/HttpsTests.twin new file mode 100644 index 000000000..5f670d3b4 --- /dev/null +++ b/test/package-tests/TBMANLIB/exe/HttpsTests.twin @@ -0,0 +1,37 @@ +' cHttpClient against a server whose certificate is expired, signed by no known authority and issued +' for another name. server.mjs beside this file is that server, on 127.0.0.1: a case in the IDE has +' no server to call, since the small server of the test project speaks plain HTTP. +[TestFixture] +Module HttpsTests + + Private Function Url() As String + Return "https://127.0.0.1:" & Environ$("PACKAGE_TEST_PORT") & "/" + End Function + + ' The control: without the package's options, Windows refuses this certificate. + [TestCase] + Public Sub WinHttpRequest_ByItself_RefusesTheCertificate() + Expect.Begin + Dim plain As Object = CreateObject("WinHttp.WinHttpRequest.5.1") + On Error Resume Next + plain.Open "GET", Url(), False + plain.Send + Dim number As Long = Err.Number + On Error GoTo 0 + Expect.IsTrue number <> 0, "a WinHttpRequest with its own settings raises an error for the certificate" + Expect.Done + End Sub + + [TestCase] + Public Sub Send_ACertificateThatProvesNothing_IsRefused() + Expect.Begin + Dim H As New cHttpClient + On Error Resume Next + H.SendGet Url() + Dim text As String = H.ReturnText() + On Error GoTo 0 + Expect.AreEqual "", text, "the body from a server whose certificate is expired, unknown and for another name" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/TBMANLIB/exe/server.mjs b/test/package-tests/TBMANLIB/exe/server.mjs new file mode 100644 index 000000000..b24c04844 --- /dev/null +++ b/test/package-tests/TBMANLIB/exe/server.mjs @@ -0,0 +1,46 @@ +// An HTTPS server on 127.0.0.1 for the cases of this folder: scripts/package_tests.mjs starts it and +// reads its port from the first line it prints. Its certificate fails every check a client can make, +// in one certificate: it expired in 2002, its authority is no one's, and it names another host. A +// client that takes the body it sends has checked none of them. +// +// The certificate is made here, on every run, so no private key is kept in the repository; Node makes +// keys and signatures, not certificates, so the certificate's DER is written by hand. + +import { generateKeyPairSync, sign } from "node:crypto"; +import { createServer } from "node:https"; + +const hex = (s) => Buffer.from(s, "hex"); + +function der(tag, ...parts) { + const body = Buffer.concat(parts); + const n = body.length; + return Buffer.concat([Buffer.from([tag, ...(n < 128 ? [n] : n < 256 ? [0x81, n] : [0x82, n >> 8, n & 255])]), body]); +} +const sequence = (...parts) => der(0x30, ...parts); +const utcTime = (s) => der(0x17, Buffer.from(s)); +// A name of one attribute, its common name (the object identifier 2.5.4.3). +const name = (cn) => sequence(der(0x31, sequence(hex("0603550403"), der(0x0c, Buffer.from(cn))))); + +const ECDSA_WITH_SHA256 = sequence(hex("06082a8648ce3d040302")); + +const { publicKey, privateKey } = generateKeyPairSync("ec", { namedCurve: "P-256" }); +const signed = sequence( + der(0xa0, hex("020102")), // version 3 + hex("020101"), // serial number 1 + ECDSA_WITH_SHA256, + name("An authority nobody trusts"), + sequence(utcTime("010101000000Z"), utcTime("020101000000Z")), + name("another-host.example"), + publicKey.export({ type: "spki", format: "der" }), +); +const certificate = sequence(signed, ECDSA_WITH_SHA256, der(0x03, hex("00"), sign("sha256", signed, privateKey))); +const lines = certificate.toString("base64").match(/.{1,64}/g); + +const server = createServer( + { + cert: `-----BEGIN CERTIFICATE-----\n${lines.join("\n")}\n-----END CERTIFICATE-----\n`, + key: privateKey.export({ type: "pkcs8", format: "pem" }), + }, + (_request, response) => response.end("the server's answer"), +); +server.listen(0, "127.0.0.1", () => console.log(server.address().port)); diff --git a/test/package-tests/TBMANLIB/expect.json b/test/package-tests/TBMANLIB/expect.json index 19a77e2a0..f2f7654dd 100644 --- a/test/package-tests/TBMANLIB/expect.json +++ b/test/package-tests/TBMANLIB/expect.json @@ -270,6 +270,9 @@ }, "Utf8Tests.DecodeToByteArray_ReturnsAllTheUtf16Bytes": { "fails": "2 of 2 checks failed: number of bytes of DecodeToByteArray for four characters: expected 8, got 4; bytes of DecodeToByteArray for cafe acute: expected \"63 00 61 00 66 00 E9 00\", got \"63 00 61 00\"" + }, + "HttpsTests.Send_ACertificateThatProvesNothing_IsRefused": { + "fails": "1 of 1 checks failed: the body from a server whose certificate is expired, unknown and for another name: expected \"\", got \"the server's answer\"" } } } diff --git a/test/package-tests/TBMANLIB_Diagnostic/Sources/CollectorTests.twin b/test/package-tests/TBMANLIB_Diagnostic/Sources/CollectorTests.twin index 499ca8a71..cca2e7d61 100644 --- a/test/package-tests/TBMANLIB_Diagnostic/Sources/CollectorTests.twin +++ b/test/package-tests/TBMANLIB_Diagnostic/Sources/CollectorTests.twin @@ -19,6 +19,31 @@ Module CollectorTests Expect.Done End Sub + ' The zone is TimeZoneStandIn's, so these two say the same in every month and on every machine. + [TestCase] + Public Sub LocaleCollector_InStandardTime_ReportsTheOffsetOfTheClock() + Expect.Begin + Expect.AreEqual "UTC+1", TimezoneItem(False), "Timezone item where the clock is at UTC+1, in standard time" + Expect.Done + End Sub + + [TestCase] + Public Sub LocaleCollector_InSummerTime_ReportsTheOffsetOfTheClock() + Expect.Begin + Expect.AreEqual "UTC+2", TimezoneItem(True), "Timezone item where the clock is at UTC+2, in summer time" + Expect.Done + End Sub + + Private Function TimezoneItem(ByVal Summer As Boolean) As String + Dim r As DiagnosticResult(Of KeyValuePair(Of String, String)) + TimeZoneStandIn.Start Summer + On Error Resume Next + Set r = Reports.CollectFrom(New LocaleCollector) + On Error GoTo 0 + TimeZoneStandIn.Finish + Return Reports.ValueOf(r, "Timezone") + End Function + ' ---- CPUInfoCollector [TestCase] @@ -55,7 +80,7 @@ Module CollectorTests Next i Dim r As DiagnosticResult(Of KeyValuePair(Of String, String)) Set r = Reports.CollectFrom(New NetworkInfoCollector) - Expect.AreEqual real, Reports.CountKeysLike(r, "Adapter #*"), "number of Adapter items in the full report, for " & real & " adapters that GetAdaptersInfo returns" + Expect.IsTrue Reports.CountKeysLike(r, "Adapter #*") = real, "the full report has an Adapter item for each adapter that GetAdaptersInfo returns" Expect.Done End Sub diff --git a/test/package-tests/TBMANLIB_Diagnostic/Sources/RunAll.twin b/test/package-tests/TBMANLIB_Diagnostic/Sources/RunAll.twin index 0dcea36e8..a96e8cf15 100644 --- a/test/package-tests/TBMANLIB_Diagnostic/Sources/RunAll.twin +++ b/test/package-tests/TBMANLIB_Diagnostic/Sources/RunAll.twin @@ -5,7 +5,7 @@ Module RunAll Public Sub Main() - Expect.StartRun "TBMANLIB_Diagnostic 1.0.0.1: 123 test cases" + Expect.StartRun "TBMANLIB_Diagnostic 1.0.0.1: 125 test cases" On Error Resume Next Expect.StartCase Err.Clear @@ -13,6 +13,14 @@ Module RunAll Expect.EndCase "CollectorTests.LocaleCollector_Items_ArePresent", Err.Number, Err.Description Expect.StartCase Err.Clear + CollectorTests.LocaleCollector_InStandardTime_ReportsTheOffsetOfTheClock + Expect.EndCase "CollectorTests.LocaleCollector_InStandardTime_ReportsTheOffsetOfTheClock", Err.Number, Err.Description + Expect.StartCase + Err.Clear + CollectorTests.LocaleCollector_InSummerTime_ReportsTheOffsetOfTheClock + Expect.EndCase "CollectorTests.LocaleCollector_InSummerTime_ReportsTheOffsetOfTheClock", Err.Number, Err.Description + Expect.StartCase + Err.Clear CollectorTests.CPUInfoCollector_PageSize_HasNoDecimalSeparator Expect.EndCase "CollectorTests.CPUInfoCollector_PageSize_HasNoDecimalSeparator", Err.Number, Err.Description Expect.StartCase diff --git a/test/package-tests/TBMANLIB_Diagnostic/Sources/TimeZoneStandIn.twin b/test/package-tests/TBMANLIB_Diagnostic/Sources/TimeZoneStandIn.twin new file mode 100644 index 000000000..ce86dc311 --- /dev/null +++ b/test/package-tests/TBMANLIB_Diagnostic/Sources/TimeZoneStandIn.twin @@ -0,0 +1,80 @@ +' TimeZoneStandIn: GetTimeZoneInformation answers for a zone of this module's, in summer time or in +' standard time, while a case runs, so that the case gives the same verdict in every month and on +' every machine. Start writes a jump to Answer over the first bytes of the Windows function, and +' Finish puts them back. A case calls Finish before anything can stop it; another thread that calls +' the function in between gets the same answer, and nothing else in the process changes. +Module TimeZoneStandIn + + Private Declare PtrSafe Function GetModuleHandleW Lib "kernel32" (ByVal lpModuleName As LongPtr) As LongPtr + Private Declare PtrSafe Function GetProcAddress Lib "kernel32" (ByVal hModule As LongPtr, ByVal lpProcName As String) As LongPtr + Private Declare PtrSafe Function VirtualProtect Lib "kernel32" (ByVal lpAddress As LongPtr, ByVal dwSize As LongPtr, ByVal flNewProtect As Long, ByRef lpflOldProtect As Long) As Long + Private Declare PtrSafe Function FlushInstructionCache Lib "kernel32" (ByVal hProcess As LongPtr, ByVal lpBaseAddress As LongPtr, ByVal dwSize As LongPtr) As Long + Private Declare PtrSafe Function GetCurrentProcess Lib "kernel32" () As LongPtr + Private Declare PtrSafe Sub CopyBytes Lib "kernel32" Alias "RtlMoveMemory" (ByVal Destination As LongPtr, ByVal Source As LongPtr, ByVal Length As LongPtr) + + Private Const PAGE_EXECUTE_READWRITE As Long = &H40 + Private Const TIME_ZONE_ID_STANDARD As Long = 1 + Private Const TIME_ZONE_ID_DAYLIGHT As Long = 2 + + ' Where the field of TIME_ZONE_INFORMATION is, after Bias at its start, and the size of it. + Private Const OFFSET_DAYLIGHT_BIAS As Long = 168 + Private Const SIZE_OF_INFORMATION As Long = 172 + + Private mTarget As LongPtr + Private mSaved() As Byte + Private mSummer As Boolean + + ' The zone is an hour east of Greenwich, an hour further in summer: UTC+1, and UTC+2 in summer. + Private Function Answer(ByVal Information As LongPtr) As Long + Dim blank(0 To SIZE_OF_INFORMATION - 1) As Byte + CopyBytes Information, VarPtr(blank(0)), SIZE_OF_INFORMATION + Dim bias As Long = -60 + Dim daylightBias As Long = -60 + CopyBytes Information, VarPtr(bias), 4 + CopyBytes Information + OFFSET_DAYLIGHT_BIAS, VarPtr(daylightBias), 4 + Answer = IIf(mSummer, TIME_ZONE_ID_DAYLIGHT, TIME_ZONE_ID_STANDARD) + End Function + + ' mov eax, target; jmp eax, or mov rax, target; jmp rax. + Private Function JumpTo(ByVal Target As LongPtr) As Byte() + Dim code() As Byte +#If Win64 Then + ReDim code(0 To 11) + code(0) = &H48 + code(1) = &HB8 + CopyBytes VarPtr(code(2)), VarPtr(Target), 8 + code(10) = &HFF + code(11) = &HE0 +#Else + ReDim code(0 To 6) + code(0) = &HB8 + CopyBytes VarPtr(code(1)), VarPtr(Target), 4 + code(5) = &HFF + code(6) = &HE0 +#End If + Return code + End Function + + Private Sub Overwrite(ByRef Code() As Byte) + Dim size As Long = UBound(Code) + 1 + Dim old As Long + VirtualProtect mTarget, size, PAGE_EXECUTE_READWRITE, old + CopyBytes mTarget, VarPtr(Code(0)), size + VirtualProtect mTarget, size, old, old + FlushInstructionCache GetCurrentProcess(), mTarget, size + End Sub + + Public Sub Start(ByVal Summer As Boolean) + mSummer = Summer + mTarget = GetProcAddress(GetModuleHandleW(StrPtr("kernel32")), "GetTimeZoneInformation") + Dim code() As Byte = JumpTo(AddressOf Answer) + ReDim mSaved(0 To UBound(code)) + CopyBytes VarPtr(mSaved(0)), mTarget, UBound(code) + 1 + Overwrite code + End Sub + + Public Sub Finish() + Overwrite mSaved + End Sub + +End Module diff --git a/test/package-tests/TBMANLIB_Diagnostic/expect.json b/test/package-tests/TBMANLIB_Diagnostic/expect.json index 0aca091fc..100e1886a 100644 --- a/test/package-tests/TBMANLIB_Diagnostic/expect.json +++ b/test/package-tests/TBMANLIB_Diagnostic/expect.json @@ -1,13 +1,16 @@ { "version": "1.0.0.1", "cases": { + "CollectorTests.LocaleCollector_InSummerTime_ReportsTheOffsetOfTheClock": { + "fails": "1 of 1 checks failed: Timezone item where the clock is at UTC+2, in summer time: expected \"UTC+2\", got \"UTC+1\"" + }, "CollectorTests.CPUInfoCollector_PageSize_HasNoDecimalSeparator": { "fails": "1 of 1 checks failed: Page Size item: expected \"4 KB\", got \"4. KB\"" }, "CollectorTests.NetworkInfoCollector_FullReport_ListsEveryAdapter": { "win32": "pass", "win64": { - "fails": "1 of 2 checks failed: number of Adapter items in the full report, for 8 adapters that GetAdaptersInfo returns: expected 8, got 1" + "fails": "1 of 2 checks failed: the full report has an Adapter item for each adapter that GetAdaptersInfo returns: expected True, got False" } }, "DictionaryTests.New_CreatesAnEmptyDictionary": { diff --git a/test/static-linking/README.md b/test/static-linking/README.md new file mode 100644 index 000000000..747568015 --- /dev/null +++ b/test/static-linking/README.md @@ -0,0 +1,40 @@ +# Static linking claims probe + +What the experimental NOTE of the Static Linking page says links and what does not, run again on a +new BETA. `run.mjs` runs each mode for 32-bit and 64-bit builds and compares what it finds with +`expect.json`. + +It is outside every gate and CI: it needs Windows and a twinBASIC install (the newest +`twinBASIC_IDE_BETA_` on the Desktop, as `tbrun` finds it). A run of all eight takes a few +minutes. It needs no compiler of C: the objects it links are committed. + +## Running it + +```sh +node test/static-linking/run.mjs # every mode; exit 0 when all are as expect.json says +node test/static-linking/run.mjs --only refused-win64 # one mode +node test/static-linking/run.mjs --update # write what this build finds into expect.json +``` + +After a new BETA, run it, and for each difference it lists, change the page, then `--update`. Read +the diff of `expect.json` before committing it: each changed line is a changed claim. +`recheck_callouts.mjs` runs it through the `static ` items of the NOTE's marker. + +## The modes + +| Mode | What it does | Claim | +|---|---|---| +| `links-` | `tbrun --exe` of `links/`: code that needs nothing, and code that needs only what `"stdlib"` provides (`memset`, `strcpy`, `memcpy`, `strlen`, `malloc`, `free`), in the IDE and in a built exe | it links code that needs nothing from the C runtime beyond the few functions twinBASIC provides | +| `refused-` | `tbbuild` of `refused/`, read for its errors: `sprintf`, `qsort`, `sin`, `floor`, `atoi` and C++ `new[]` and `delete[]` | it does not link the rest of the C runtime, or the C++ runtime | +| `sqlite-` | `tbrun --exe` of the SQLite sample in the install's `projects` folder, which reports through message boxes that tbrun closes and records | the SQLite amalgamation links and works | +| `detours-` | `package_tests.mjs DetoursLib`, whose project links SlimDetours: no case runs unless it links, and each case detours a function of Windows | SlimDetours links and works | + +A sample of DetoursLib would not check the last one: `check_examples.mjs` fails no sample for an +error inside an embedded package, so its compile says nothing about SlimDetours. + +## The objects + +`c/` holds the sources, and `c/build.cmd 32` and `c/build.cmd 64` write the objects into +`links/Miscellaneous/` and `refused/Miscellaneous/`, with the command line the page gives. They are +built by relative names, so that no path of the machine is written into them: `grep` them for your +user name before committing new ones. `run.mjs` does not use `c/`. diff --git a/test/static-linking/c/basic.c b/test/static-linking/c/basic.c new file mode 100644 index 000000000..26177ee92 --- /dev/null +++ b/test/static-linking/c/basic.c @@ -0,0 +1,2 @@ +/* Plain C: nothing from a runtime. */ +int add(int a, int b) { return a + b; } diff --git a/test/static-linking/c/build.cmd b/test/static-linking/c/build.cmd new file mode 100644 index 000000000..99dba53ab --- /dev/null +++ b/test/static-linking/c/build.cmd @@ -0,0 +1,15 @@ +@echo off +rem Builds the probe's object files for one architecture, with the command line the Static Linking +rem page gives, into the two projects beside this folder: build.cmd 32 ^| 64. Run it from any prompt +rem after a change to a .c file. The objects are committed, so the probe needs no compiler. They are +rem built in this folder, by relative names, so that no path of this machine is written into them. +setlocal +set ARCH=%1 +set VC=C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build +call "%VC%\vcvars%ARCH%.bat" >nul +cd /d "%~dp0" +set F=/nologo /c /Gw /Gy /GS- +cl %F% basic.c /Fo"..\links\Miscellaneous\basic_%ARCH%.obj" +cl %F% crt.c /Fo"..\links\Miscellaneous\crt_%ARCH%.obj" +cl %F% crtmore.c /Fo"..\refused\Miscellaneous\crtmore_%ARCH%.obj" +cl %F% cppnew.cpp /Fo"..\refused\Miscellaneous\cppnew_%ARCH%.obj" diff --git a/test/static-linking/c/cppnew.cpp b/test/static-linking/c/cppnew.cpp new file mode 100644 index 000000000..5e6988374 --- /dev/null +++ b/test/static-linking/c/cppnew.cpp @@ -0,0 +1,10 @@ +// C++ new and delete, from the C++ runtime. +extern "C" int cpp_new(int n) +{ + int *p = new int[n]; + int s = 0; + for (int i = 0; i < n; i++) p[i] = i; + for (int i = 0; i < n; i++) s += p[i]; + delete[] p; + return s; +} diff --git a/test/static-linking/c/crt.c b/test/static-linking/c/crt.c new file mode 100644 index 000000000..0ded66ddc --- /dev/null +++ b/test/static-linking/c/crt.c @@ -0,0 +1,22 @@ +/* The C runtime functions "stdlib" provides. */ +#include +#include + +int crt_strings(char *buf, int size) +{ + memset(buf, 0, size); + strcpy(buf, "abc"); + memcpy(buf + 3, "def", 4); + return (int)strlen(buf); +} + +int crt_malloc(int n) +{ + int *p = (int *)malloc(n * sizeof(int)); + int i, s = 0; + if (!p) return -1; + for (i = 0; i < n; i++) p[i] = i; + for (i = 0; i < n; i++) s += p[i]; + free(p); + return s; +} diff --git a/test/static-linking/c/crtmore.c b/test/static-linking/c/crtmore.c new file mode 100644 index 000000000..3d1bd29da --- /dev/null +++ b/test/static-linking/c/crtmore.c @@ -0,0 +1,14 @@ +/* C runtime functions "stdlib" does not provide. */ +#include +#include +#include + +int crt_sprintf(char *buf, int v) { return sprintf(buf, "v=%d", v); } + +static int cmp(const void *a, const void *b) { return *(const int *)a - *(const int *)b; } +int crt_qsort(int *v, int n) { qsort(v, n, sizeof(int), cmp); return v[0]; } + +double crt_sin(double x) { return sin(x); } +double crt_floor(double x) { return floor(x); } + +int crt_atoi(const char *s) { return atoi(s); } diff --git a/test/static-linking/expect.json b/test/static-linking/expect.json new file mode 100644 index 000000000..c0b8ea5a6 --- /dev/null +++ b/test/static-linking/expect.json @@ -0,0 +1,77 @@ +{ + "beta": 1034, + "modes": { + "links-win32": { + "ide": { + "add": "5", + "strings": "6", + "malloc": "45" + }, + "exe": { + "add": "5", + "strings": "6", + "malloc": "45" + } + }, + "links-win64": { + "ide": { + "add": "5", + "strings": "6", + "malloc": "45" + }, + "exe": { + "add": "5", + "strings": "6", + "malloc": "45" + } + }, + "refused-win32": { + "errors": [ + "TB5058 linker error: unrecognized symbol '??_U@YAPAXI@Z'", + "TB5058 linker error: unrecognized symbol '??_V@YAXPAX@Z'", + "TB5058 linker error: unrecognized symbol '?_OptionsStorage@?1??__local_stdio_printf_options@@9@9'", + "TB5058 linker error: unrecognized symbol '___stdio_common_vsprintf'", + "TB5058 linker error: unrecognized symbol '_atoi'", + "TB5058 linker error: unrecognized symbol '_floor' (Link With \"api-ms-win-crt-math-l1-1-0\"?)", + "TB5058 linker error: unrecognized symbol '_qsort'", + "TB5058 linker error: unrecognized symbol '_sin' (Link With \"api-ms-win-crt-math-l1-1-0\"?)" + ] + }, + "refused-win64": { + "errors": [ + "TB5058 linker error: unrecognized symbol '??_U@YAPEAX_K@Z'", + "TB5058 linker error: unrecognized symbol '??_V@YAXPEAX@Z'", + "TB5058 linker error: unrecognized symbol '?_OptionsStorage@?1??__local_stdio_printf_options@@9@9'", + "TB5058 linker error: unrecognized symbol '__stdio_common_vsprintf'", + "TB5058 linker error: unrecognized symbol 'atoi'", + "TB5058 linker error: unrecognized symbol 'floor' (Link With \"api-ms-win-crt-math-l1-1-0\"?)", + "TB5058 linker error: unrecognized symbol 'qsort'", + "TB5058 linker error: unrecognized symbol 'sin' (Link With \"api-ms-win-crt-math-l1-1-0\"?)" + ] + }, + "sqlite-win32": { + "exitCode": 0, + "boxes": [ + "Read record: 1, Hello, World1!", + "Read record: 2, Hello, World2!", + "Read record: 3, Hello, World3!" + ] + }, + "sqlite-win64": { + "exitCode": 0, + "boxes": [ + "Read record: 1, Hello, World1!", + "Read record: 2, Hello, World2!", + "Read record: 3, Hello, World3!" + ] + }, + "detours-win32": { + "ran": true, + "documented": true + }, + "detours-win64": { + "ran": true, + "documented": true + } + } +} diff --git a/test/static-linking/links/Miscellaneous/basic_32.obj b/test/static-linking/links/Miscellaneous/basic_32.obj new file mode 100644 index 0000000000000000000000000000000000000000..e6e646a6794f688c3b9618bf96986d9521785546 GIT binary patch literal 658 zcmeZaWMOa!eU-J1k%57S0Rr?=ic*tH%2J_R27Mr73ycG!xda&WQc{yj(^Z0DLMwob z$1o0*c3=>2(kn@=C{fAHgGq4%8Qnnpkb#0gK!RR!Ms~5830znK$eM!#!Cp+fr1>npeSKxQ~;8w03;3og>r#` literal 0 HcmV?d00001 diff --git a/test/static-linking/links/Miscellaneous/basic_64.obj b/test/static-linking/links/Miscellaneous/basic_64.obj new file mode 100644 index 0000000000000000000000000000000000000000..ad95de5bbce79f4fa08e9c5e2e4f33d8c55c9a4b GIT binary patch literal 668 zcmZ8e&r2IY7=7yol;B~dc=0sE1zXV7gLnu%B%3INCXF=lwx!+7PVDIZ2-%5>2bCg1 znnQap(jNT-JPNk-(w^Esptl|h-uw%6MzgZo7IY)&&g1#HFKk4y;^=*tgSY3BqPUM%k^2V*U+XU zp`3T>rFCbQI%1cHcCk?@EqD>_1uPV<;8ExZ77Hg<1zqrYxXr^&M{(@bcubk^yCI8u zvD0#6PT#C7TTyEZc|4zj4gA3q%;U6=2d;72jpC>+$kSKPNWCL?!1v`$Qi_5O_gNR{ zH%+5oH75pT(-=tBe-6I?m>q9V&&^!Uy#F%y_v7!$ix-uN%U{O_|J5TD3RIrAxOdvm zXg4>2jzXKc!dhDT1np?3>_om5c(MoFD`|(-2kV;hcob=O$7D;_l)s}$JFBMan(|g5 dd-$OPsM)UP-6<;QAQ(g1XJwAA5gA9J^jrOVgI)jt literal 0 HcmV?d00001 diff --git a/test/static-linking/links/Miscellaneous/crt_32.obj b/test/static-linking/links/Miscellaneous/crt_32.obj new file mode 100644 index 0000000000000000000000000000000000000000..b70a4ec1e9b82e7f2fe7709975dde2cc958b0fa9 GIT binary patch literal 1308 zcmZ8h-A~g{6hEZ{Dh?bGOe7&LV@64&Oi&X*lZ{acW|hg znurf*j3GYw!kh2ilvTwmsxy_Y@>Vc; zkGw>KfskOg`7hV=&LMscH{VrwO;uhrtcH!Fx@XWg;{0ZFgKL8o<`T-O6T7erv(`Eu zwBWPt_Wm*MtS9K3_DzfqJJ5OgX-6$M=^H!m;kkgrZoxLca6Q6x5vXP_!r$ef!s3C%HAN+jm zyxaTr&7bn|56@>;9mRXkUi9E0-P{7cZdQt}5=Bm+L-I*DJ_^oMcl` z+ygKWv%H8}Xo?7|*38l(Uq4N)*AlQY%Y}5ODIzSwezN`W769pO=8*4}riiGBkP_W@ zJ{p{vl$a9cmk=Xn#|{KhMAZNcL{T$!97K|0Rnz*ZSVr9aXH@!xlSx@H^&u&lgP29< zkW7~lMp{Q$le`-VN->0l_Vn=YaU*jE#)k$5hV(+`fQ}5h^zH^?mOr^MO_3i!t#xBL QLnMc{LH#I3kut{q2h1%FZ~y=R literal 0 HcmV?d00001 diff --git a/test/static-linking/links/Miscellaneous/crt_64.obj b/test/static-linking/links/Miscellaneous/crt_64.obj new file mode 100644 index 0000000000000000000000000000000000000000..18ad88756c88baea9edd5bcbb0de3c679ec342a0 GIT binary patch literal 1981 zcmaJ?U5Fc16h705yX)%KwA(7QScml5Mrx*6b}74yNRmzM6q0W2ZhSG)CX>l_w#j78 zOtxEpz!DHcEEQDLhXqB2(gzEr52bzBlos30cK=^a1P#+vI#jQ>|sq>B2z-p9yZyX@~tF=e1mQfz7!i7ZGgq z=_25d$(FWav6A8Ht{`}I3qVM;8H6DDQz!d%CykDXXVWn};Sk z)1(k7eL5J%vyr5O@x#wW(~0@S(b#kVtC{3{pqHFY#E(UjhvxzkMcBmh5tbK0*oFnzt(i&crm7W- zSwkyV&5T;HP8^z$%eh6EhG(`x4B%@Rg{NV)4xKistCg}@E?83V_*2sClBJjQS1_V9 zQ!Xv(MXf?>swdcxQoA>)sBsoiZvL=EX{`T314=#4f?viNFfub`zx%qR14sua8nFNogclurbf1`i^t0Z~oU|x5vSE4*i(8-nfY|S?dlD!`FlH znNqtwd21(5?8oAR&#``lqxwAaE6!Ge6mBxnXpY|{@36g_^W+8|0^^01T2qW(qpMQf zacA>g$J*`St;bP8@74!&8hbgCu^!{y<5wbZI@>&7*4iUZgWIexH_X+Jb%@iqUSxXLw-vQwbhgmKc2&XimtA$*_x78QkTvm#ZAHp z$e5btWlXEmr$MHub+RRS6)IXM8)H78IN4&cta{@Rrgf0{xP}ltS2B?^EOBhy?%Ub{ zKxQoLP_)wlkUiP(0rC{+w#nWaPyg%#;>dQ}4eyKK&9!s}qCl^fA};Hc-GGney-fopo^B7JwRZ|`ip*Y!%{7#Y{#->Uo5co5m{zCL~OuOnOt zZ0^X^MA$wLu5p+#ezG>>&DxlpF;2>M_S6fADN*80V%9S`c5aMS4a~rK<{f7IN7YMt LWUgmdOqfQ^y05V{{n9!6cvJv5G7!sf%qm(O7g**_r7mt-puZNQNaU2_1F4x z55OQCK~CnV?rhQ)g$XR)v`IS;ctK9m)%@DXd|P^r#rHO8XI}uOKt|$=t49Sni|F-tSk(7i?QhJ zt)N2Yhb0g0`-AZUV;KY=Z!G=#bofXHhOwFOTquHG3 z<7va(Fw8YAM^ky5CN;&db(&7EnAFPKnx4&?w2-)RS+-W!VG3^e1iW-FoPjaeap5>C zhMu!*E9FR+=0>Gh)-f{118ggWtxVQP(;VJQW#wY|93y>i`K|x$ziS{)g+9eg{MN-? zk4wsolwDHYBNZ;~kvdNY{Yi|LXbg>GOx4zfXSb9=`l~{R>Vu5zz7DWFxubo)8)h01*Nyy5h)_lg~hS)KWIB zw46!e9Key%=|D6JhywRuH#*(NO(F{1f8FSG9`7y++XJd-f)3phB1#mL=#Zz14&kZDE?$a=u=i$nY}W_>dGGz+@4f%WzPIY#Qt*v@ zpT5Wg)Id%|HK{4)td@772@MBldwE^%0#Vf_(^G87jyyx7`2J;&I2`Qpb=2X<-#c7a4E6^Decs;gp*q2VWe#yY!ZiV0B`OGX zg!=shp*baF&gq8FJJjQERO3oIrWt0~)Z<3T)KX?BMUK)K)r}e5mqk7`LgS0>1u^2(1fSqZQSITK)Q8+3d*8)R+ruYGxE^9V(*@NLT3-EVXg zB1v+VK4^J%0?bV?YJv1YIfjIzHy9JpnrKJ8SViRpD(cA&Ga*yGbnpoi*l_~05?DTg zEuNFfVV;}c&2C~%_rV_R?V-J8ip$oaRuOZ{E8AmRBGx>O%K=;X#UBqX&dGuE z37RO>TeeVN@E8ZkS=3Jz7FJ&tskv19y7u-@7MPaH7S8S8mkwWk-B@mT zx_@uk!P5c-4N6C8hfz~}E3dEcpzVXc2b&}_ zCUsfZiyx*jON?x&(JwQZpO$T!xX_wKO~m=Z51K4fV>EMFbTI}E5zo2ry{^EJlib&H zfA{x(=iYPQJ-5BwW(W*i8x55JG=pjWct)1=DcS16PIS6A**)vG%nSawJTy7n5V1!- zL`Pf23xK!Nugiz@hE&?-m7!D11MI*?#$oe$LkCv#LWs8@Cu{LY`gXip#7`jS>;`&U zkayAIpv&xbdKEabfV`_4=v5)_8gd*CQ)|6-Jcz@ak@J|vTW=kt*NdFQ271IBN6xVg z^jeU25;>Pw>&0Vw499H4t%vU|j*SJN>7rj68Pi%HwILWs@9HWZ;AZ-KzMb8|lN|%S z`+Ivj+v@Pq72IF9671{k-q#TnB6U7e_ygN1Y%$;{+tA$>?du9f4@yz}ppx$Fi1c(d z##L!DC8zb6uBhp#E^B&JqmI;=RMKNgdN`Vq6O)=8j|LS@l9S0;T2?2usFcxDYDN}Y z9`UO~qtFIVQ~;*j1$V;3FgpW<-=HF8R839jz6ZiPe0}4(l2WEo*Vm<{#+9U;0T?i@ zpQHH==4jTmkxs(0*WHG2Psj*X_8SA=nc$)!EE+!#=Bn`}YL!gChItsn5GqmBivc|0(Pcw$hb+Cy!Q9fN|)4sXIE8wHT(O{{$2XnLo^)_b&9T;`2SW-6c zy+G?9n+IH`;lM!D-GdoK51naS@ioDV zDa4%hj{;?*C>J=ecu817G8AR=KKAm|o^5fccO~xHUiE3!jrqhIO*f{`zjN-bxf4I^ z>pFA#Jj&6fB$mj>F4&o zu-)$eJG6(Z|EhiB$amq{g)hH;dw!~<<(t!gHqZ>n0up{ZekDFTn zVh-&h&rzMFk+V0?zqyt})q&?|KS<}mnUVL_a-57q^_5J?HNI@Ji(|QPglDInbn+AL z&#&ds*UEEL--t7}Ywzo8Ii-w4N0H|2`s4#V48$&uMXTOOr@X)+o$8N{{j^rc!#Gq& zDZ-@V#lOs`*a6BN84o@-6M#Asz^9EoZXu2W(qkbd3=y^QjFQ$91bR$lLQ^xkMKY0? zt|~TQy`>cHQZtsANUAE9E7R=g&szO*^DQYw1)P}^5&Av=B=!U-wKy4F)J*j!82kra CZss2V literal 0 HcmV?d00001 diff --git a/test/static-linking/refused/Miscellaneous/crtmore_64.obj b/test/static-linking/refused/Miscellaneous/crtmore_64.obj new file mode 100644 index 0000000000000000000000000000000000000000..810e99e15193bff57701dcad45faea3dcbb05491 GIT binary patch literal 5026 zcma)AeQZ2Cf>6lM5O99$Z)?xZJM3J>|Gj@V^R}&r~6b zu?2A53Ksx>CtwcfxVhr30^Ed-0h7wD;;n?9mjL(LJn{yG1;Yjv=6dyG1-7;O?y;-lG-98w6agf_P6?81Gwvd$EFezo;em*z; zCM|^<3wB0&d-@_z#3R}h>0F>Y{6J4jLXD4RlQ}J>rPW+SOBS?90UPm_OgcB5&h3rl zlc~`{G7;I4F2s|WOe~jFM+=d7UdyWaWOP%zR~>web+UVxGDraz*>ZLdJ2A=3`;K%x zuNKsl=Jq_Y&fPnrrL*a;KwNi^njJ}Jl6l6IBa|acxV3|DIaz2jB)dbCL3yPz*)O{k zXjUuZSCz{+4W-DG@$-^W49O$d<3Vc(Eq4g4J)vSy9#D$?a-UK>PYej;8$TfX*umPj zf^z#!C?8e>$_E|oaBo+}&ncz5AU0-CCwmC$PmwjpKsprC06Izbp`@a`brvJzX#9Mj z37n3DQ)22H8_u0>n=f`P!1D$Qi%txwgawBeyWP zwM~SvFO^4$2jz~?k$UK1-DmJ|P>L5(lT*28cpALmDmo=t0#9iLz}CX$WFDy9WZho5 zkuZXC5N6k*9Kl#Tx;0kxC5f>HYP_7xS{B-HT5*^sLSm}NAj}xIs=>nd&%ua+Pau>4zfrv& zYR=#mGCIj;XW%c9QSJeQGq{BeKl!A3iPh`DY2@I=(gGJ}a0?mtkSJsz0aaQXQ_+oiwX*nKqo^~;B^hxZ;|b}jtvn;#awu{v@5 zU!QlpyME{Kp)(Ipq~19E^soN3^$+jAeP!9l&l+vtGw)pa@q($rmjfs7{^6ZN1Djko zeqU(#ZTEBATech={^yp{%}=?u6^lRle$|#Q{(M~hwb6d?OzCsmT8|wVeEXXp9sAOB z?rZ6dug9YY+l{t?&mOwpz3ts~jp4ss=-OZZ+Dv)-a`mb^CI**hUwZV}ch28f`2N|k zg>9F9a9uW*!ykt<=S?S0Ogg2TH*snFsboy^`g}*3bV&D%sTprJ0ey^dkNt3p2PB7t z8zvU}!E^z1Iiyn}09y9LCPsHixE&brcYSwt^TVbcgD-_c!j}aj4*X^nJd0@AF_^0z z67D?4$^Ymy1TM$mSoTyy@udICX@<7ahkl+%u(cG&Cro;N8L8 z6oF@M^f1$dKL%KwrUzefEDOm~7|Ew|T8cb%Lnl>Grl{UXRbWAFv zs<7mGF~)RlL`5OkRr?^yszZ!M-7^pj4X}V@!+4BZqck z7N&wvR22hHo9>{4km$ix6+O5Yq6c?b^x&R~9(-Cv4?bg6%xgQk*Mo<@1z@ncSN|wG zX++pER*W#r{$X$j^g1NkeXy>deC2Nz>nWr{A&j841iY5ksn}*X=g>(9{NF(e6v7Bv zF7R4fN8da%Y_U!ghcUtkT6hW&wdzc*vpS0~g4QxYtNY^XGZyQT&LWJUwOr6T+_vk3 z#irI@brxX+txpJA?|u1Lv&H(4&LWJUwPKc5jj2^FaT;L+txpPCxP}(1L1z(0(7IF5 zn%cN_s@Bw6ud@gvXkiAk_UpZt{r|97TXYs-1T8#lTKk1s)uz^iI*TxZ7G^?G%VI@z z7GVS}%)^$}g+u$kyU5ha>MX(tT6h4qv|ec5ecEDuRc8?faEv_z9(@0>EPy{vtZ#2T z{5mA#x&PUka}N=S$>IB3TQ{~*rXUQ9rZO6SYVdxFWBWM5L!CpKF*?oro1h$xX4H5r z6D??ov>N3}G^)b4hMFrtCwIdyaaPSm$BZNkR!)aznCXsY7<}7A4G`Kq2cyOFsyv#5 z@27+;1d!>wzszR=O(6?sxpbJJsYA@B-fbV24iQ{77$30>q&sX7OzSoPHErGcO!=f{ nPaQp!^lc%i-4(@8JXrRyrsiXNlm4#OuCCi{kKe!9znT3XkJ?J7 literal 0 HcmV?d00001 diff --git a/test/static-linking/refused/Settings b/test/static-linking/refused/Settings new file mode 100644 index 000000000..cbfe933e8 --- /dev/null +++ b/test/static-linking/refused/Settings @@ -0,0 +1,75 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "StaticLinkingRefused", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "The static linking claims probe, test/static-linking: refused.", + "project.exportPathIsV2": true, + "project.id": "{7B247400-0000-4000-9000-7B2474000E11}", + "project.name": "StaticLinkingRefused", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + }, + { + "id": "{9847A10D-8987-44B2-8B87-EA77DEF7F1D9}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] Assert", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "Assert", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 24 + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/test/static-linking/refused/Sources/Startup.twin b/test/static-linking/refused/Sources/Startup.twin new file mode 100644 index 000000000..090884e6b --- /dev/null +++ b/test/static-linking/refused/Sources/Startup.twin @@ -0,0 +1,20 @@ +' What does not link: the rest of the C runtime, and C++ new and delete. Nothing here runs: run.mjs +' reads the errors the compiler reports for it. +#If Win64 Then + Import Library "/Miscellaneous/crtmore_64.obj" As CRTMORE Link "stdlib" + Import Library "/Miscellaneous/cppnew_64.obj" As CPPNEW Link "stdlib" +#Else + Import Library "/Miscellaneous/crtmore_32.obj" As CRTMORE Link "stdlib" + Import Library "/Miscellaneous/cppnew_32.obj" As CPPNEW Link "stdlib" +#End If + +Module Startup + + Private Declare PtrSafe Function crt_atoi CDecl Lib CRTMORE (ByVal s As String) As Long + Private Declare PtrSafe Function cpp_new CDecl Lib CPPNEW (ByVal n As Long) As Long + + Public Sub Main() + Debug.Print crt_atoi("1") + cpp_new(1) + End Sub + +End Module diff --git a/test/static-linking/run.mjs b/test/static-linking/run.mjs new file mode 100644 index 000000000..b4c08d8c6 --- /dev/null +++ b/test/static-linking/run.mjs @@ -0,0 +1,183 @@ +// Runs the static linking claims probe in every mode and compares what it finds with expect.json: +// what the Static Linking page's experimental NOTE says links and what does not, checked again on a +// new BETA. See README.md beside this file for the claims. +// +// node test/static-linking/run.mjs [--only ] [--update] [--ide ] +// +// Each mode names what it runs and the architecture: links- is one tbrun --exe of links/, whose +// lines come from the IDE run and from the built exe; refused- is one tbbuild of refused/, read +// for its errors; sqlite- is one tbrun --exe of the install's own sample that links SQLite, +// which is not copied here: it is 3.6 MB of objects, and what matters is that the sample still works; +// detours- is one run of the DetoursLib package's test project, which links SlimDetours: no case +// of it runs unless it links, and each detours a function of Windows. A sample of that package would +// say nothing, since check_examples fails no sample for an error inside an embedded package. +import { spawnSync } from "node:child_process"; +import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { choiceOption, exitOnCrash, parseCli, printHelpAndExit, withUsageError } from "../../lib/cli.mjs"; +import { findIde } from "../../scripts/lib/tb-install.mjs"; +import { packTree, unpackProject } from "../../scripts/lib/tb-project.mjs"; + +const ALL = ["links", "refused", "sqlite", "detours"].flatMap((kind) => [`${kind}-win32`, `${kind}-win64`]); + +const USAGE = `usage: node test/static-linking/run.mjs [--only ] [--update] [--ide ] [-h, --help] + +Runs the static linking claims probe in each mode and compares what it finds with expect.json: +links- builds and runs code that needs only "stdlib", refused- reads the errors for code +that needs more of the C runtime or the C++ runtime, sqlite- builds and runs the install's +sample that links SQLite, and detours- runs the test project of DetoursLib, which links +SlimDetours. + + --only run one mode: ${ALL.join(", ")} + --update write what the run found into expect.json, for the modes run; read the diff before + committing it, since each changed line is a changed claim + --ide twinBASIC.exe, as for tbrun (default $TB_IDE, else the newest on the Desktop) + -h, --help print this text and exit + +Exit codes: + 0 every mode found what expect.json says + 1 a value differs from expect.json, is missing, or is new + 2 the command line was refused, or a mode could not be run`; + +exitOnCrash(); +const usageError = { format: (err) => `${err.message}\n${USAGE}` }; +const { values } = withUsageError( + () => + parseCli(process.argv.slice(2), { + options: { + only: { type: "string" }, + update: { type: "boolean" }, + ide: { type: "string" }, + help: { type: "boolean", short: "h" }, + }, + stopAt: ["help"], + }), + usageError, +); +if (values.help) printHelpAndExit(USAGE); +const only = + values.only && withUsageError(() => choiceOption(values.only, { option: "--only", choices: ALL }), usageError); + +const here = import.meta.dirname; +const repo = path.resolve(here, "..", ".."); +const expectPath = path.join(here, "expect.json"); +const expected = JSON.parse(readFileSync(expectPath, "utf8")); +const ideArgs = values.ide ? ["--ide", values.ide] : []; +const work = mkdtempSync(path.join(tmpdir(), "static-linking-")); +process.on("exit", () => rmSync(work, { recursive: true, force: true })); + +function tool(name, args) { + const run = spawnSync(process.execPath, [path.join(repo, "scripts", name), ...args, ...ideArgs], { + cwd: repo, + encoding: "utf8", + maxBuffer: 64 * 1024 * 1024, + }); + return { status: run.status, stdout: run.stdout ?? "", text: `${run.stdout ?? ""}${run.stderr ?? ""}` }; +} + +function cannotRun(run) { + console.log(`exited ${run.status}`); + console.log(run.text.trim().split("\n").slice(-30).join("\n")); + return null; +} + +// "key: value" lines, by side: the IDE run's before tbrun's "--- exe" line, the exe's after it. +function links(arch) { + const run = tool("tbrun.mjs", [path.join(here, "links"), "--exe", "--arch", arch]); + if (run.status !== 0) return cannotRun(run); + const got = { ide: {}, exe: {} }; + let side = "ide"; + for (const line of run.text.split(/\r?\n/)) { + if (/^--- exe/.test(line)) side = "exe"; + const m = /^([a-z]+): (.*)$/.exec(line); + if (m) got[side][m[1]] = m[2].trimEnd(); + } + return got; +} + +// The errors of a compile, each as its code and message, sorted: the order is not the claim. +function refused(arch) { + const file = path.join(work, `refused-${arch}.twinproj`); + packTree(file, path.join(here, "refused")); + const run = tool("tbbuild.mjs", [file, "--arch", arch, "--json"]); + if (run.status !== 1) return cannotRun(run); + const errors = JSON.parse(run.stdout) + .diagnostics.filter((d) => String(d).startsWith("{ERROR}")) + .map((d) => String(d).replace(/^.*?\]: /, "")); + return { errors: [...new Set(errors)].sort() }; +} + +// The install's sample, unpacked with a [RunAfterBuild] Sub added, which tbrun needs to see that the +// build has ended. The sample reports through MsgBox, so what it did is in the boxes the exe opened: +// tbrun closes each one and says so with exit 6. +const BUILT = `Module Built + + [RunAfterBuild] + Public Sub TellsTheRunTheBuildEnded() + Debug.Cls + Debug.Print "built" + End Sub + +End Module +`; + +function sqlite(arch) { + const projects = path.join(path.dirname(findIde(values.ide)), "projects"); + const folder = readdirSync(projects).find((f) => /Static Library Example \(SQLITE3\)/.test(f)); + if (!folder) throw new Error(`no SQLite sample under ${projects}`); + const tree = path.join(work, `sqlite-${arch}`); + mkdirSync(tree, { recursive: true }); + unpackProject(path.join(projects, folder, "projectName.twinproj"), tree); + writeFileSync(path.join(tree, "Sources", "Built.twin"), BUILT); + const run = tool("tbrun.mjs", [tree, "--exe", "--json", "--arch", arch]); + if (run.status !== 6) return cannotRun(run); + const { exeRun } = JSON.parse(run.stdout); + return { exitCode: exeRun.exitCode, boxes: exeRun.dialogs.map((d) => d.text) }; +} + +// A defect of the package is its section's to document: here only that its cases ran, and did what +// its expect.json says. +function detours(arch) { + const run = tool("package_tests.mjs", ["DetoursLib", "--arch", arch, "--json"]); + if (run.status !== 0 && run.status !== 1) return cannotRun(run); + const { cases } = JSON.parse(run.stdout).packages[0]; + return { ran: cases.length > 0, documented: cases.every((c) => c.verdict === "documented") }; +} + +const RUN = { links, refused, sqlite, detours }; + +const differences = []; +let failed = false; +for (const mode of only ? [only] : ALL) { + process.stdout.write(`${mode}: `); + const [kind, arch] = mode.split("-"); + const got = RUN[kind](arch); + if (!got) { + failed = true; + continue; + } + const want = expected.modes[mode] ?? {}; + if (values.update) { + expected.modes[mode] = got; + console.log("recorded"); + continue; + } + const before = differences.length; + const compare = (where, w, g) => { + for (const k of new Set([...Object.keys(w ?? {}), ...Object.keys(g ?? {})])) { + if (typeof (w?.[k] ?? g?.[k]) === "object") compare(`${where}${k}/`, w?.[k], g?.[k]); + else if (w?.[k] !== g?.[k]) + differences.push(`${mode} ${where}${k}: expected ${JSON.stringify(w?.[k])}, got ${JSON.stringify(g?.[k])}`); + } + }; + compare("", want, got); + console.log(differences.length === before ? "as expected" : `${differences.length - before} difference(s)`); +} + +if (values.update) { + writeFileSync(expectPath, `${JSON.stringify(expected, null, 2)}\n`); + console.log(`wrote ${path.relative(repo, expectPath)}`); +} +for (const d of differences) console.log(` ${d}`); +process.exit(failed ? 2 : differences.length ? 1 : 0); From 3c0b18494ea73961b47deef31ace673424c0b0b6 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Tue, 3 Nov 2026 23:13:08 +0100 Subject: [PATCH 19/19] Item 18, last part: OpenFile's empty-path NOTE is checked by probe scenario 11e, on a desktop from tbbroker; the scenario ends the COM-started explorer.exe, which outlives the exe, only if it started after the call --- .../TBMANLIB_Dialog/cDialog/OpenFile.md | 2 +- .../TBMANLIB_Dialog/probe/README.md | 11 +-- .../TBMANLIB_Dialog/probe/src/Probe.twin | 9 +-- .../TBMANLIB_Dialog/probe/src/ProbeOF.twin | 72 +++++++++++++++++++ 4 files changed, 85 insertions(+), 9 deletions(-) create mode 100644 test/package-tests/TBMANLIB_Dialog/probe/src/ProbeOF.twin diff --git a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/OpenFile.md b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/OpenFile.md index 352c1b3f4..813668929 100644 --- a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/OpenFile.md +++ b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/OpenFile.md @@ -43,7 +43,7 @@ The Windows documentation of `ShellExecute` lists the other values. For a program, the verb `open` starts the program. For a document, it starts the program that Windows associates with the document. For a folder, it opens the folder in File Explorer. - + > [!NOTE] 1.1.0.0 > An empty *strFilePath* is not an error. `OpenFile("")` returns 42, which is above 32 and looks like success, and opens a File Explorer window on the current folder of the program. diff --git a/test/package-tests/TBMANLIB_Dialog/probe/README.md b/test/package-tests/TBMANLIB_Dialog/probe/README.md index 389ae19be..c2fab6fba 100644 --- a/test/package-tests/TBMANLIB_Dialog/probe/README.md +++ b/test/package-tests/TBMANLIB_Dialog/probe/README.md @@ -97,9 +97,12 @@ late. - `Probe.twin` declares the result of **OpenFile** in scenario 11 as a **Variant**: the method returns a **LongPtr** in 1.1.0.0, which a **Long** does not take in a 64-bit build. -- Never call **OpenFile** or **SelectAndOpenFile** on a path that exists, or on `""`: it starts - a program. `OpenFile("")` opens a File Explorer window, and the COM-started `explorer.exe` - behind it outlives the exe, with its window on the private desktop where nobody can close - it; it has to be ended by its pid. +- Never call **OpenFile** or **SelectAndOpenFile** on a path that exists: it starts a program. +- `OpenFile("")` opens a File Explorer window, and the COM-started `explorer.exe` behind it is + outside the exe's job, so it outlives the exe, with its window on the private desktop where + nobody can close it. Scenario `11e`, run with `probe.mjs 11e` and `--arch win64 11e`, is the one + place that calls it. It ends that process by its pid, in `src/ProbeOF.twin`, once it has seen that + the process started after the call, and looks for the window with `FindWindowW`, which sees no + desktop but its own. So the user's desktop and shell are out of its reach. - Never run the exe on the user's desktop, or a dialog in the IDE: the probe's `[RunAfterBuild]` Sub only prints. diff --git a/test/package-tests/TBMANLIB_Dialog/probe/src/Probe.twin b/test/package-tests/TBMANLIB_Dialog/probe/src/Probe.twin index 57fa83b02..53bcea818 100644 --- a/test/package-tests/TBMANLIB_Dialog/probe/src/Probe.twin +++ b/test/package-tests/TBMANLIB_Dialog/probe/src/Probe.twin @@ -20,9 +20,9 @@ ' 9 ShowBrowseForFolder: typing a folder, InitialDir, NewDialogStyle ' 9e ShowBrowseForFolder with NewDialogStyle = False, left alone ' 11 OpenFile on a nonexistent path -' (There is no OpenFile("") scenario: it returns 42 and opens a File Explorer window on the -' current directory, and the COM-started explorer.exe that shows it outlives the exe, with -' its window on the private desktop. Never call OpenFile on a path that exists, or on "".) +' 11e OpenFile(""): its result and the File Explorer window it opens on the current folder. The +' COM-started explorer.exe that shows it outlives the exe, with its window on the private +' desktop, so the scenario ends it (ProbeOF.twin). Never call OpenFile on a path that exists. ' 12 HideReadOnly = False and ReadOnly = True: the Open dialog's child windows ' 12b the default Open dialog, for comparison ' 12c HideReadOnly = False: click the Open button's arrow, which opens its drop-down @@ -115,7 +115,7 @@ Module Probe Debug.Print "hook ran in the IDE; the dialogs run only in the exe" End Sub - Private Const ScenarioIds As String = ",13,1,2,3,4,5,6,6d,7,8,9,9e,11,12,12b,12c,14,15,16,17,18,x0,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,y1,y2,y3,y4,c1,c2,c3,c4,c11,c12,c13,c14,c15,c16,c17,c21,c22,c23,c24,c25,c26,c27,c31,c32,c33,c34,c35,c41,c42,c43,c44,c45,c46,c47,c48,d0,d0b,d1,d2,d2b,d2c,d2t,d3,d4,d5,d10,d10b,d11,d11b,d11c,d12,d12d,d20,d20b,d21,d22,d22b,d23,d30,d31,d32,d33,d33b,d34,d34b,d40," + Private Const ScenarioIds As String = ",13,1,2,3,4,5,6,6d,7,8,9,9e,11,11e,12,12b,12c,14,15,16,17,18,x0,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,y1,y2,y3,y4,c1,c2,c3,c4,c11,c12,c13,c14,c15,c16,c17,c21,c22,c23,c24,c25,c26,c27,c31,c32,c33,c34,c35,c41,c42,c43,c44,c45,c46,c47,c48,d0,d0b,d1,d2,d2b,d2c,d2t,d3,d4,d5,d10,d10b,d11,d11b,d11c,d12,d12d,d20,d20b,d21,d22,d22b,d23,d30,d31,d32,d33,d33b,d34,d34b,d40," Public Sub Main() Dim sel As String = "," & Replace(Environ$("PROBE"), " ", "") & "," @@ -156,6 +156,7 @@ Module Probe Case "9": S9 Case "9e": S9e Case "11": S11 + Case "11e": ProbeOF.S11e Case "12": S12 Case "12b": S12b Case "12c": S12c diff --git a/test/package-tests/TBMANLIB_Dialog/probe/src/ProbeOF.twin b/test/package-tests/TBMANLIB_Dialog/probe/src/ProbeOF.twin new file mode 100644 index 000000000..a4185d5fa --- /dev/null +++ b/test/package-tests/TBMANLIB_Dialog/probe/src/ProbeOF.twin @@ -0,0 +1,72 @@ +' ProbeOF: the scenario 11e of the TBMANLIB_Dialog probe: OpenFile with an empty path. +' +' The call opens a File Explorer window on the current folder, in an explorer.exe that COM starts. +' That process is outside the job the exe runs in, so it outlives the run and leaves the broker's +' desktop in use. So the scenario ends it: FindWindowW looks at this desktop and no other, and the +' process is ended only if it started after the call, which no process of the user's did. + +Module ProbeOF + + Private Declare PtrSafe Function FindWindowW Lib "user32" (ByVal lpClassName As LongPtr, ByVal lpWindowName As LongPtr) As LongPtr + Private Declare PtrSafe Function GetWindowTextW Lib "user32" (ByVal hWnd As LongPtr, ByVal lpString As LongPtr, ByVal nMaxCount As Long) As Long + Private Declare PtrSafe Function GetWindowThreadProcessId Lib "user32" (ByVal hWnd As LongPtr, ByRef lpdwProcessId As Long) As Long + Private Declare PtrSafe Sub GetSystemTimeAsFileTime Lib "kernel32" (ByRef lpSystemTimeAsFileTime As LongLong) + Private Declare PtrSafe Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As LongPtr + Private Declare PtrSafe Function GetProcessTimes Lib "kernel32" (ByVal hProcess As LongPtr, ByRef lpCreationTime As LongLong, ByRef lpExitTime As LongLong, ByRef lpKernelTime As LongLong, ByRef lpUserTime As LongLong) As Long + Private Declare PtrSafe Function TerminateProcess Lib "kernel32" (ByVal hProcess As LongPtr, ByVal uExitCode As Long) As Long + Private Declare PtrSafe Function WaitForSingleObject Lib "kernel32" (ByVal hHandle As LongPtr, ByVal dwMilliseconds As Long) As Long + Private Declare PtrSafe Function CloseHandle Lib "kernel32" (ByVal hObject As LongPtr) As Long + Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) + + Private Const PROCESS_TERMINATE As Long = &H1 + Private Const PROCESS_QUERY_LIMITED_INFORMATION As Long = &H1000 + Private Const SYNCHRONIZE As Long = &H100000 + + Public Sub S11e() + Say "== 11e OpenFile with an empty path" + Dim called As LongLong + GetSystemTimeAsFileTime called + Dim d As New cDialog + Dim r As Variant ' OpenFile returns LongPtr in 1.1.0.0, which a Long does not take in a 64-bit build + r = d.OpenFile("") + Say " OpenFile("""") = " & r + + ' A new window is titled File Explorer, and takes the folder's name or its whole path, as the + ' user's Folder Options say, once it has opened the folder. + Dim folder As String = Mid$(CurDir$(), InStrRev(CurDir$(), "\") + 1) + Dim window As LongPtr, title As String, i As Long + For i = 1 To 150 + window = FindWindowW(StrPtr("CabinetWClass"), 0) + If window <> 0 Then title = TextOf(window) + If title = folder Or title = CurDir$() Then Exit For + Sleep 100 + Next + If window = 0 Then + Say " no File Explorer window on this desktop" + Exit Sub + End If + Say " a File Explorer window on this desktop, titled " & title + Say " the current folder is named " & folder + + Dim pid As Long + GetWindowThreadProcessId window, pid + Dim process As LongPtr = OpenProcess(PROCESS_TERMINATE Or PROCESS_QUERY_LIMITED_INFORMATION Or SYNCHRONIZE, 0, pid) + Dim created As LongLong, exited As LongLong, kernel As LongLong, user As LongLong + If process = 0 Or GetProcessTimes(process, created, exited, kernel, user) = 0 Then + Say " could not look at the window's process: left alone" + ElseIf created < called Then + Say " the window's process started before the call: left alone" + Else + TerminateProcess process, 0 + WaitForSingleObject process, 5000 + Say " the window's process started after the call, and is ended" + End If + If process <> 0 Then CloseHandle process + End Sub + + Private Function TextOf(ByVal window As LongPtr) As String + Dim buffer As String = String$(260, vbNullChar) + Return Left$(buffer, GetWindowTextW(window, StrPtr(buffer), 260)) + End Function + +End Module