Skip to content

Classes at run time: DODEFAULT, Error methods, @var, class library search - #6

Merged
RandomChirp merged 2 commits into
FoxDevCommunity:mainfrom
jefflroberts:runtime-classes
Oct 5, 2026
Merged

RandomChirp merged 2 commits into
FoxDevCommunity:mainfrom
jefflroberts:runtime-classes

Conversation

@jefflroberts

Copy link
Copy Markdown
Collaborator

Found running a 60,000-line CodeMine application (Shutter Ace); each behaviour
measured in Visual FoxPro 9 first.

  • DODEFAULT() runs the parent class's code. The importer keeps an overridden
    .vcx method's ancestors' versions as Name#1, Name#2 (nearest first), each
    compiled with its own class's header; the session runs the next one up, or
    walks DEFINE CLASS parents from the class whose code is running, and
    answers .T. past the last. A program class inherits the methods it does not
    override, and answers Class in proper case.
  • An error raised inside an object's Error method - called directly or reached
    through DODEFAULT() - is dropped and the method carries on, and ERROR() and
    MESSAGE() there see the error that sent it (the session's last error).
  • @var passed to an object's method is the variable: it crosses as
    {"$ref", "$val"} and a method of FoxPro code writes the caller's variable.
    @m.x is a reference too.
  • The run-time class library search looks in the project's class-library
    folders before the Foundation Classes, SET CLASSLIB finds a project library
    by name, and a parent library that cannot be found is reported instead of
    silently dropping what the class inherits.
  • A non-visual class (a Custom) can be added to a container as a member.
  • Importer: a middle class's dotted overrides (pgfSteps.Page2.Name) reach the
    member an older ancestor made, so a third class keeps the pages and what is
    on them; a class's own property written in parentheses is an expression
    worked out when the object is made (nHkeyMachineRoot = ((2^31) + 2)).

Verification. The new tests in tests/runtime/defineClass.test.ts and tests/shared/vfpForm.test.ts, and the byref_mdot golden. The full VM suite and the runtime, shared and vfp TypeScript tests pass on this branch.

One of several pull requests split out of work on a CodeMine application (Shutter Ace); each stands on main on its own except where it says otherwise.

🤖 Generated with Claude Code

@RandomChirp RandomChirp left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of this checks out against the product: the whole DODEFAULT program in the test prints exactly what vfp9.exe prints (b<ax!>, faz, .T. past the last class, who=Cb, stmt;0, sas), @m.name and @name both reach a method by reference (Bump(@m.n) on 1 gives 2, then 3), Class answers Mythinghere for mYthingHere, and cargo test -p foxvm is green on a merge with main.

Two things I would change before merging.

  1. The Error-method rule in vm.rs handle() is wider than what the product does. It skips any error raised in a frame whose method is Error. I measured the two ways an Error method can be running:
  • Called by the program (o.Error(1098, "direct", 1), or an ancestor's reached with DODEFAULT from there): an ERROR command or a runtime error inside it is skipped, the method carries on at its next line, ON ERROR is not called, and ERROR() inside reads the skipped error (1098, or 12 for a missing variable). That is what the PR implements and the test expects.
  • Called by the runtime, because a method of the object raised an error: an error inside it (ERROR "x" or a missing variable, with ON ERROR set) is NOT skipped. It goes to the default handler, the dialog, and ON ERROR is not consulted. The program stops there unless somebody presses Ignore.

Probe for the second case, which stalls at the dialog in vfp9.exe:

ON ERROR ? "ON ERROR got", ERROR()
o = CREATEOBJECT("c2")
o.Boom()
? "back"
RETURN
DEFINE CLASS c2 AS Custom
  PROCEDURE Boom
    ? "boom", nosuchvar
  ENDPROC
  PROCEDURE Error(nError, cMethod, nLine)
    ? "in Error", nError
    ERROR "raised inside Error"
    ? "after ERROR"
  ENDPROC
ENDDEFINE

The VM already knows which case it is in: in_class_error holds the object while the runtime is running its Error method. Gating running_error_method on the object not being in in_class_error keeps the skip for the program-called case and lets the runtime-called case fall through to the default handler as the product does. The comment above the block should say the same, since it currently claims the runtime-called case was measured.

  1. Instr::DoDefault is dead after this change. The statement form now compiles to CallBuiltin, and the expression path in compiler.rs (the if name.upper == "DODEFAULT" block after the builtins lookup) is unreachable because DODEFAULT is in the builtins table. The variant in bytecode.rs, its handler in vm.rs and that compiler block can go, otherwise the generated instruction reference on the site documents an instruction nothing emits.

Review generated with Claude Code (Claude Fable 5.1), run against vfp9.exe and the test suites on a merge with main.

jefflroberts and others added 2 commits September 24, 2026 18:08
…arch

Found running a 60,000-line CodeMine application (Shutter Ace); each behaviour
measured in Visual FoxPro 9 first.

- DODEFAULT() runs the parent class's code. The importer keeps an overridden
  .vcx method's ancestors' versions as Name#1, Name#2 (nearest first), each
  compiled with its own class's header; the session runs the next one up, or
  walks DEFINE CLASS parents from the class whose code is running, and
  answers .T. past the last. A program class inherits the methods it does not
  override, and answers Class in proper case.
- An error raised inside an object's Error method - called directly or reached
  through DODEFAULT() - is dropped and the method carries on, and ERROR() and
  MESSAGE() there see the error that sent it (the session's last error).
- @var passed to an object's method is the variable: it crosses as
  {"$ref", "$val"} and a method of FoxPro code writes the caller's variable.
  @m.x is a reference too.
- The run-time class library search looks in the project's class-library
  folders before the Foundation Classes, SET CLASSLIB finds a project library
  by name, and a parent library that cannot be found is reported instead of
  silently dropping what the class inherits.
- A non-visual class (a Custom) can be added to a container as a member.
- Importer: a middle class's dotted overrides (pgfSteps.Page2.Name) reach the
  member an older ancestor made, so a third class keeps the pages and what is
  on them; a class's own property written in parentheses is an expression
  worked out when the object is made (nHkeyMachineRoot = ((2^31) + 2)).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ult instruction

Review on FoxDevCommunity#6 (measured in vfp9.exe): an error inside an Error method is skipped only
when the program called the method, or reached an ancestor's with DODEFAULT() from
there. When the runtime called Error because a method of the object failed, the error
goes to the default handler - the dialog - and ON ERROR is not asked. in_class_error
already holds the object in that case, so the skip is gated on it.

DODEFAULT is a builtin now, so Instr::DoDefault, its handler and the compiler block
that emitted it were unreachable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jefflroberts

Copy link
Copy Markdown
Collaborator Author

Thanks for measuring both of these. I've rebased on main and pushed the fixes:

  1. An error inside an Error method is now skipped only when the program called the method (or reached an ancestor's with DODEFAULT() from there). When the runtime called Error because a method of the object failed, the error isn't skipped: it goes to the default handler, and ON ERROR isn't called. This uses the in_class_error check, as you suggested. I rewrote the comment above that block to describe both cases, and added a test that runs your example program and checks that it stops at the error dialog with 1098 in C2.ERROR.
  2. I removed Instr::DoDefault, its handler in vm.rs and the compiler block that produced it.

cargo test -p foxvm and the vitest suite are green.

jefflroberts added a commit to jefflroberts/FoxDevStudio that referenced this pull request Sep 25, 2026
…nd moving to an Intel machine

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@RandomChirp
RandomChirp merged commit 46e5589 into FoxDevCommunity:main Oct 5, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants