Skip to content

Dynamic method dispatch (obj\method(args)) is statement-only -- can't be used as an expression / rvalue #62

@CoreyRDean

Description

@CoreyRDean

Summary

obj\method(args) dynamic dispatch only parses as a statement. The same call as an expression (e.g. on the right-hand side of an assignment, in a comparison, as a function-call argument) errors with Expecting end-of-file Got: ( or Expecting 'End Method' Got: (.

The static form TypeName::method(obj, args) works in both positions, so currently every method that returns a value must be called via the static form even when an instance reference is available.

Minimal repro

Strict

Type Foo
    Field val%
    Method get%()
        Return self\val
    End Method
End Type

Local f.Foo = New Foo()
f\val = 42

// Statement-level dynamic dispatch -- works:
f\get()        ' OK (return value discarded)

// Expression-level dynamic dispatch -- fails:
Local x% = f\get()
$ blitzcc dynexp.bb
Parsing...
dynexp.bb:17:17:17:17:Expecting end-of-file Got: (

Foo::get(f) in the same position compiles and works.

Asymmetry with the static form

Form As statement As expression
Foo::get(f) (static)
f\get() (dynamic)

MethodTest.bb demonstrates f\testMethod("buzz") only at statement level — the test suite doesn't exercise dynamic dispatch in expression position, which is presumably how the gap shipped without notice.

Why this matters

Consumers of method-with-return-value (the common case for getters, builders, value-formatters, hit-tests, row-chaining UI methods) currently can't use dynamic dispatch even when they have an instance in hand. They're forced into the static form even when the type name is otherwise redundant. Symptoms in real code:

// What I want to write:
Local entityName$ = self\threads\lookupName(kind, refID)
Local hit% = self\threads\renderChip(x, y, w, h, kind, refID, mx, my, clicked)

// What I have to write today:
Local entityName$ = Threads::lookupName(self\threads, kind, refID)
Local hit% = Threads::renderChip(self\threads, x, y, w, h, kind, refID, mx, my, clicked)

In the Loom alpha refactor, the second form propagates through every value-returning call site (row layout helpers, value formatters, lookup methods). Removing this gap would unlock consistent obj\method(args) style across an entire OO module.

Expected behaviour

obj\method(args) should parse anywhere a function/method call is valid — including expression positions. The static form already does; the dynamic form should too.

Where the gap likely lives

Method-call dispatch in src/blitzrc/compiler/parser.cpp around line 228 handles the backslash dispatch:

if (strictMode && toker->curr()=='\') {
    isField = true;
}
if( toker->lookAhead(2)=='(' && isField) {
    // ... method call ...
}

This branch is inside the statement-parsing path. The expression parser (exprnode.cpp / parser.cpp's parseExpr and friends) presumably needs the same dispatch added for the same shape — recognize ident\methodName(...) and emit a CallNode with the static-call name and the leading object as the first argument.

Repro environment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions