Skip to content

Resolve variables at parse time#1429

Open
stackoverflow wants to merge 9 commits into
apple:mainfrom
stackoverflow:resolve-variables-at-parse-time
Open

Resolve variables at parse time#1429
stackoverflow wants to merge 9 commits into
apple:mainfrom
stackoverflow:resolve-variables-at-parse-time

Conversation

@stackoverflow
Copy link
Copy Markdown
Contributor

@stackoverflow stackoverflow commented Feb 18, 2026

This replaces ResolveVariableNode and ResolveMethodNode with their resolution. When we build the truffle node tree, we determine whether names resolve to:

  • lexical scope
  • base module
  • implicit this

Then, we use this information to directly construct the underlying nodes (ReadPropertyNode, ReadLocalPropertyNode, etc).

Additionally, AstBuilder determines whether the property access must be const or not.

This introduces a BaseModuleMembers registry, which gets generated as part of Java compilation.

Closes #1154

NOTE: this introduces a regression in the REPL. Currently, this works:

pkl0> foo { bar = baz }
pkl1> local baz = 3
pkl2> :f foo
new Dynamic {
  bar = 3
}

Now, :f foo produces an error, because baz is resolved to implicit this.

@stackoverflow stackoverflow force-pushed the resolve-variables-at-parse-time branch from b659dc3 to ccfff3d Compare April 16, 2026 13:24
@bioball bioball force-pushed the resolve-variables-at-parse-time branch 4 times, most recently from 09e24f7 to 059ab3c Compare May 11, 2026 20:21
unless the type is intentionally overridden via `extends`.

[source%tested,{pkl}]
[source%parsed,{pkl}]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Currently, amends "..." REPL statements don't do anything. In this PR, the header still mostly does nothing, but it will throw if the amends has a relative path.

This is because our REPL logic has changed; before the AstBuilder can visit an expression, it first needs to visit the module to collect names. In the process of visiting the module, it will validate this module header.

import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.ExpressionNode;

@NodeChild(value = "enclosingFrame", type = GetEnclosingFrameNode.class)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Optimization: move logic around finding the enclosing frame into its own node so that we don't keep re-executing the traversal logic every time we get a FrameSlotTypeException

new ReplResponse.InternalError(new IllegalStateException("Unexpected parse result")));
}
} catch (VmException e) {
// TODO: patch stack trace for constants
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is an existing TODO, not newly introduced by this PR (GitHub isn't able to track that the code just got shuffled around).

* A wrapper for Truffle's {@link FrameDescriptor.Builder}, but also lets us find the slot of a
* given {@link Identifier}.
*/
public class FrameDescriptorBuilder {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

When we resolve frame slot variables, we need to know the names of each slot. The FrameDescriptor.Builder API keeps these names inside private fields. So, we use our own builder abstraction that exposes the slot names.

@bioball bioball force-pushed the resolve-variables-at-parse-time branch from 059ab3c to 495ff89 Compare May 11, 2026 21:33
import org.pkl.parser.Parser;
import org.pkl.parser.syntax.Modifier.ModifierValue;

public final class BaseModuleMemberRegistryGenerator {
Copy link
Copy Markdown
Member

@bioball bioball May 11, 2026

Choose a reason for hiding this comment

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

This generates a class called BaseModuleMembers, and gets called as part of java compilation.

This generates a class that looks something like:

public final class BaseModuleMembers {
  private BaseModuleMembers() {
  }

  public static boolean hasProperty(String name) {
    return switch (name) {
      case "AlsoKnownAs",
        "Annotation",
        "Any",
        "BaseValueRenderer",
        "Boolean",
        "Bytes",
        "BytesRenderer",
        "Char",
        "Charset",
        "Class",
        "Collection",
        "Comparable",
        "ConvertProperty",
        // etc
        "YamlRenderer" -> true;
      default -> false;
    }
  }
}

This is used by SymbolTable to figure out if some name is defined on the base module or not.
This is much faster than looking up members on BaseModule.getModule().

@bioball bioball force-pushed the resolve-variables-at-parse-time branch 2 times, most recently from f0daa78 to 78b689a Compare May 11, 2026 21:41
@bioball bioball force-pushed the resolve-variables-at-parse-time branch from 78b689a to c5941de Compare May 11, 2026 22:08
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.

Resolve names at parse time

2 participants