Skip to content

A method body can't portably refer to its own instance: field reads and sibling calls need this./self./self-> text in VariableReference, so no such body translates across targets #141

Open

Description

@matt-edmondson

What's wrong

The AST has no node for "the current instance" and no member-access expression. The only way for a method body to read a field, or call another method on its own object, is to put target-language text into VariableReference. The test suite does this itself, with new VariableReference("self.x") and new CallExpression(new VariableReference("self"), "Shift"). CompiledExemplar records the limitation:

a body that does says self->x in C and self.x in the other two, which is a difference no shared declaration can hold.

/// There are two groups, and the line between them is the receiver. Everything down to
/// <see cref="OriginTable"/> is what all three targets are checked against, and none of it contains a
/// body that reads a member — a body that does says <c>self-&gt;x</c> in C and <c>self.x</c> in the
/// other two, which is a difference no shared declaration can hold. Everything after it reads a
/// member, so it is shared by the two that spell that the same way and is not offered to C.
/// </para>

The generators already know each language's receiver. Python writes self into the signature, C and Go name the receiver self, and C#/JS write this. for member initialisers. Bodies just have no way to ask for it.

Failure scenario

Take a class Counter with a field count and a method add(n) whose body is count += helper(n), where helper is another instance method. Using a bare VariableReference("count") and CallExpression("helper"), every generator emits count += helper(n):

  • Python: UnboundLocalError on count, NameError on helper.
  • C: count and helper are undeclared, since the function is Counter_helper(self, n).
  • Rust, Go: unresolved names.

Changing the text to self.count fixes Python, Rust and Go, but breaks C# (no self), C++ (this->count), JS (this.count) and C (self->count). No single AST compiles in all seven targets, which undermines the premise of a language-agnostic AST. It is the same kind of gap that #55 closed for calls.

Suggested fix / acceptance criteria

  • Add a ThisExpression (or SelfReference) node, and a MemberAccessExpression(Receiver, MemberName) for field reads and writes. CallExpression.Receiver already covers calls.
  • Each generator spells them in its own way:
    • C# and JS: this.x
    • C++: this->x
    • Python, Rust and Go: self.x
    • C: self->x, with instance calls as Type_method(self, …)
  • The new nodes get the same support as existing ones: YAML serializer and deserializer, AstSchema/AstFields/AstNodeCatalog, and the node-exhaustiveness test.
  • Rewrite CompiledExemplar so its member-reading group uses the new nodes and is shared with C.

Activity

  1. matt-edmondson commented on Oct 5, 2026

    @matt-edmondson
    ContributorAuthor

    Triage


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions