Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 32 additions & 24 deletions pkl-core/src/main/java/org/pkl/core/ast/member/ClassNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -123,37 +123,45 @@ public VmClass executeGeneric(VirtualFrame frame) {
typeParameters,
prototype);

if (unresolvedSupertypeNode != null) {
var supertypeNode = unresolvedSupertypeNode.execute(frame);
var superclass = supertypeNode.getVmClass();
var localContext = VmLanguage.get(this).localContext.get();
localContext.beginClassInit(cachedClass);

checkSupertype(supertypeNode, superclass);
cachedClass.initSupertype(supertypeNode, superclass);
}
try {
if (unresolvedSupertypeNode != null) {
var supertypeNode = unresolvedSupertypeNode.execute(frame);
var superclass = supertypeNode.getVmClass();

// The superclass resolved above may not itself have completed the below initializations yet.
// That's because these initializations may have indirectly or directly triggered
// resolution of this class, in which case the `resolveSuperclass()` call above
// will have returned the partially initialized `cachedClass` of the superclass.
// As a consequence, initializations that require a fully initialized class hierarchy
// are done lazily in VmClass rather than here.
// A fully initialized class hierarchy is only required for initialization of internal caches,
// which is guaranteed to succeed (no impact on eager vs. lazy error reporting) and easy to
// defer.
checkSupertype(supertypeNode, superclass);
cachedClass.initSupertype(supertypeNode, superclass);
}

VmUtils.evaluateAnnotations(frame, annotationNodes, annotations);
// The superclass resolved above may not itself have completed the below initializations yet.
// That's because these initializations may have indirectly or directly triggered
// resolution of this class, in which case the `resolveSuperclass()` call above
// will have returned the partially initialized `cachedClass` of the superclass.
// As a consequence, initializations that require a fully initialized class hierarchy
// are done lazily in VmClass rather than here.
// A fully initialized class hierarchy is only required for initialization of internal caches,
// which is guaranteed to succeed (no impact on eager vs. lazy error reporting) and easy to
// defer.

for (var node : unresolvedPropertyNodes) {
cachedClass.addProperty(node.execute(frame, cachedClass));
}
VmUtils.evaluateAnnotations(frame, annotationNodes, annotations);

for (var node : unresolvedMethodNodes) {
cachedClass.addMethod(node.execute(frame, cachedClass));
}
for (var node : unresolvedPropertyNodes) {
cachedClass.addProperty(node.execute(frame, cachedClass));
}

cachedClass.notifyInitialized();
for (var node : unresolvedMethodNodes) {
cachedClass.addMethod(node.execute(frame, cachedClass));
}

return cachedClass;
cachedClass.onOwnClassInitialized();
localContext.endClassInit();
return cachedClass;
} catch (Throwable e) {
localContext.clearClassInitState();
throw e;
}
}

private void checkSupertype(TypeNode supertypeNode, @Nullable VmClass superclass) {
Expand Down
64 changes: 62 additions & 2 deletions pkl-core/src/main/java/org/pkl/core/runtime/VmClass.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.pkl.core.ast.*;
import org.pkl.core.ast.member.*;
import org.pkl.core.ast.type.TypeNode;
import org.pkl.core.runtime.VmExceptionBuilder.MultilineValue;
import org.pkl.core.util.CollectionUtils;
import org.pkl.core.util.EconomicMaps;
import org.pkl.core.util.LateInit;
Expand Down Expand Up @@ -150,6 +151,56 @@ public void initSupertype(TypeNode supertypeNode, VmClass superclass) {
prototype.lateInitParent(superclass.getPrototype());
}

@TruffleBoundary
private void checkAbstractMethods() {
if (this.isAbstract()) return;
// minimize allocations in the non-error case
if (!hasAbstractMethod()) return;
var abstractMethods = getAbstractMethods();
if (abstractMethods.size() == 1) {
throw new VmExceptionBuilder()
.evalError(
"noImplementationForAbstractMethod",
getDisplayName(),
abstractMethods.get(0).getName().toString())
.withSourceSection(getHeaderSection())
.build();
}
var methodList = new ArrayList<String>(abstractMethods.size());
for (var method : abstractMethods) {
methodList.add(method.getCallSignature());
}
throw new VmExceptionBuilder()
.evalError(
"noImplementationForAbstractMethods", getDisplayName(), MultilineValue.of(methodList))
.withSourceSection(getHeaderSection())
.build();
}

private boolean hasAbstractMethod() {
var methodCursor = getAllMethods().getEntries();
while (methodCursor.advance()) {
var method = methodCursor.getValue();
if (method.isAbstract()) {
return true;
}
}
return false;
}

private List<ClassMethod> getAbstractMethods() {
assert this.superclass != null;
var result = new ArrayList<ClassMethod>();
var methodCursor = getAllMethods().getEntries();
while (methodCursor.advance()) {
var method = methodCursor.getValue();
if (method.isAbstract()) {
result.add(method);
}
}
return result;
}

@TruffleBoundary
public void addProperty(ClassProperty property) {
prototype.addProperty(property.getInitializer());
Expand Down Expand Up @@ -190,11 +241,20 @@ public void addMethods(Iterable<ClassMethod> methods) {
}
}

// Note: Superclasses may not have finished their initialization when this method is called.
public void notifyInitialized() {
/**
* Called when this class itself has been initialized.
*
* <p>Superclasses may not have been initialized yet.
*/
public void onOwnClassInitialized() {
isInitialized = true;
}

/** Called when the entire class hierarchy is completely initialized, including superclasses. */
public void onFullyInitialized() {
checkAbstractMethods();
}

public int getTypeParameterCount() {
return typeParameters.size();
}
Expand Down
30 changes: 30 additions & 0 deletions pkl-core/src/main/java/org/pkl/core/runtime/VmLocalContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,22 @@
*/
package org.pkl.core.runtime;

import java.util.ArrayDeque;
import java.util.Deque;

/** A per-context thread-local value that can be used to influence execution. */
public class VmLocalContext {
private boolean shouldEagerTypecheck = false;

/** Whether we are currently inside a type test ({@code is} check). */
private boolean inTypeTest = false;

/** The number of classes currently being initialized. */
private int classDepth = 0;

/** The classes currently being initialized. */
private final Deque<VmClass> pendingClasses = new ArrayDeque<>();

/**
* Number of active {@link VmValueTracker} instances. Used to determine if instrumentation is
* already active.
Expand All @@ -48,6 +57,27 @@ public boolean isInTypeTest() {
return inTypeTest;
}

public void beginClassInit(VmClass vmClass) {
classDepth++;
pendingClasses.add(vmClass);
}

public void endClassInit() {
classDepth--;
if (classDepth > 0) {
return;
}
while (!pendingClasses.isEmpty()) {
var clazz = pendingClasses.pop();
clazz.onFullyInitialized();
}
}

public void clearClassInitState() {
pendingClasses.clear();
classDepth = 0;
}

public void enterTracker() {
activeTrackerDepth++;
instrumentationEverUsed = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1208,3 +1208,10 @@ invalidReferenceTypeAnnotationWithConstraint=\

cannotInstallPackageWithNoCache=\
Cannot install package to module cache dir when module cache is disabled.

noImplementationForAbstractMethod=\
Class `{0}` should either be declared `abstract`, or should implement method `{1}`.

noImplementationForAbstractMethods=\
Class `{0}` should either be declared `abstract`, or implement the following methods:\n\
{1}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
abstract module Foo

abstract function bar(): Int
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
abstract class AbstractMethod {
abstract function foo(): Int
}

class MyClass extends AbstractMethod {
}

foo: MyClass
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
abstract class AbstractMethods {
abstract function foo(): Int
abstract function bar(): Int
}

class MyClass extends AbstractMethods {
}

foo: MyClass
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
extends "../../input-helper/classes/AbstractModule.pkl"
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
abstract class AbstractMethod {
abstract function foo(): Int
}

abstract class AbstractIntermediate extends AbstractMethod

class MyClass extends AbstractIntermediate {
}

foo: MyClass
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
–– Pkl Error ––
Class `abstractMethodNotImplemented1#MyClass` should either be declared `abstract`, or should implement method `foo`.

x | class MyClass extends AbstractMethod {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at abstractMethodNotImplemented1 (file:///$snippetsDir/input/errors/abstractMethodNotImplemented1.pkl)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
–– Pkl Error ––
Class `abstractMethodNotImplemented2#MyClass` should either be declared `abstract`, or implement the following methods:
foo()
bar()
Comment on lines +2 to +4

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is a style change compared to #1690 to match how candidates in "Cannot find method" errors are displayed.


x | class MyClass extends AbstractMethods {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at abstractMethodNotImplemented2 (file:///$snippetsDir/input/errors/abstractMethodNotImplemented2.pkl)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
–– Pkl Error ––
Class `abstractMethodNotImplemented3` should either be declared `abstract`, or should implement method `bar`.

x | extends "../../input-helper/classes/AbstractModule.pkl"
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at abstractMethodNotImplemented3 (file:///$snippetsDir/input/errors/abstractMethodNotImplemented3.pkl)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
–– Pkl Error ––
Class `abstractMethodNotImplemented4#MyClass` should either be declared `abstract`, or should implement method `foo`.

x | class MyClass extends AbstractIntermediate {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at abstractMethodNotImplemented4 (file:///$snippetsDir/input/errors/abstractMethodNotImplemented4.pkl)
Loading