Fix super method call inside let expression (#1383)

Fixes #1309

The issue was that super calls were blocked inside let expressions
because:
1. The compiler's isClassMemberScope() check didn't skip over lambda
scopes created by let expressions
2. The runtime's findSupermethod() didn't traverse past VmFunction
owners to find the actual class prototype

Changes:
- SymbolTable.java: Updated isClassMemberScope() to skip lambda scopes
before checking if the parent is a class or module scope
- InvokeSuperMethodNode.java: Updated findSupermethod() to skip
VmFunction owners when looking for the class prototype

Added regression tests covering:
- Super method calls inside let expressions
- Super property access inside let expressions
- Nested let expressions with super calls

---------

Co-authored-by: Jen Basch <jbasch@apple.com>
This commit is contained in:
Akshat Anand
2026-01-10 09:29:20 +05:30
committed by GitHub
parent 3595c03078
commit 9d385f2194
4 changed files with 56 additions and 2 deletions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved. * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -327,6 +327,8 @@ public final class SymbolTable {
} }
public final boolean isClassMemberScope() { public final boolean isClassMemberScope() {
var effectiveScope = skipLambdaScopes();
var parent = effectiveScope.parent;
if (parent == null) return false; if (parent == null) return false;
return parent.isClassScope() return parent.isClassScope()

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved. * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@ import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.ExpressionNode; import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.member.ClassMethod; import org.pkl.core.ast.member.ClassMethod;
import org.pkl.core.runtime.Identifier; import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.VmFunction;
import org.pkl.core.runtime.VmUtils; import org.pkl.core.runtime.VmUtils;
public abstract class InvokeSuperMethodNode extends ExpressionNode { public abstract class InvokeSuperMethodNode extends ExpressionNode {
@@ -66,6 +67,10 @@ public abstract class InvokeSuperMethodNode extends ExpressionNode {
protected ClassMethod findSupermethod(VirtualFrame frame) { protected ClassMethod findSupermethod(VirtualFrame frame) {
var owner = VmUtils.getOwner(frame); var owner = VmUtils.getOwner(frame);
while (owner instanceof VmFunction) {
owner = owner.getEnclosingOwner();
}
assert owner != null : "VmFunction always has a parent";
assert owner.isPrototype(); assert owner.isPrototype();
var superclass = owner.getVmClass().getSuperclass(); var superclass = owner.getVmClass().getSuperclass();

View File

@@ -0,0 +1,44 @@
open class A {
function foo() = "a"
}
class B extends A {
function foo() =
let (bar = "b")
super.foo() + bar
}
local b = new B {}
res1 = b.foo()
// Also test with property access
open class C {
value = "c"
}
class D extends C {
result =
let (x = "d")
super.value + x
}
local d = new D {}
res2 = d.result
// Test with nested let expressions
open class E {
function getValue() = "e"
}
class F extends E {
function getValue() =
let (x = "f")
let (y = " and " + x)
super.getValue() + y
}
local f = new F {}
res3 = f.getValue()

View File

@@ -0,0 +1,3 @@
res1 = "ab"
res2 = "cd"
res3 = "e and f"