mirror of
https://github.com/apple/pkl.git
synced 2026-06-23 22:06:17 +02:00
Enforce that abstract members are implemented
This adds a check that abstract members must be implemented. If any members lack an implementation, an error is thrown describing the missing members. Also: we have a bug in `pkl:base`; class `Set` does not implement all members of class `Collection`.
This commit is contained in:
@@ -24,7 +24,7 @@ import org.pkl.core.ast.VmModifier;
|
||||
import org.pkl.core.runtime.*;
|
||||
|
||||
public final class ClassProperty extends ClassMember {
|
||||
private final @Nullable PropertyTypeNode typeNode;
|
||||
private @Nullable PropertyTypeNode typeNode;
|
||||
private final ObjectMember initializer;
|
||||
|
||||
public ClassProperty(
|
||||
@@ -64,6 +64,11 @@ public final class ClassProperty extends ClassMember {
|
||||
return VmModifier.getMirrors(mods, false);
|
||||
}
|
||||
|
||||
public void lateInitTypeNodeAndModifiers(@Nullable PropertyTypeNode typeNode, int modifiers) {
|
||||
this.typeNode = typeNode;
|
||||
this.modifiers = modifiers;
|
||||
}
|
||||
|
||||
public @Nullable PropertyTypeNode getTypeNode() {
|
||||
return typeNode;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ public abstract class Member {
|
||||
|
||||
protected final SourceSection headerSection;
|
||||
|
||||
protected final int modifiers;
|
||||
protected int modifiers;
|
||||
|
||||
protected final @Nullable Identifier name;
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.oracle.truffle.api.dsl.Idempotent;
|
||||
import com.oracle.truffle.api.frame.FrameDescriptor;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.*;
|
||||
import org.graalvm.collections.*;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
@@ -84,6 +85,13 @@ public final class VmClass extends VmValue {
|
||||
|
||||
private final Object allHiddenPropertyNamesLock = new Object();
|
||||
|
||||
@GuardedBy("finalizersLock")
|
||||
private @Nullable List<Runnable> __finalizers = null;
|
||||
|
||||
private final Object finalizersLock = new Object();
|
||||
|
||||
private final AtomicInteger uninitializedSuperclassCount = new AtomicInteger(0);
|
||||
|
||||
// Helps to overcome recursive initialization issues
|
||||
// between classes and annotations in pkl.base.
|
||||
@CompilationFinal private volatile boolean isInitialized;
|
||||
@@ -150,6 +158,83 @@ public final class VmClass extends VmValue {
|
||||
prototype.lateInitParent(superclass.getPrototype());
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
private void checkAbstractMembers() {
|
||||
if (this.isAbstract()) return;
|
||||
// minimize allocations in the non-error case
|
||||
if (!hasAbstractMember()) return;
|
||||
var abstractMembers = getAbstractMembers();
|
||||
if (abstractMembers.size() == 1) {
|
||||
var member = abstractMembers.get(0);
|
||||
var message =
|
||||
member instanceof ClassProperty
|
||||
? "noImplementationForAbstractProperty"
|
||||
: "noImplementationForAbstractMethod";
|
||||
throw new VmExceptionBuilder()
|
||||
.evalError(message, getDisplayName(), member.getName().toString())
|
||||
.withSourceSection(getHeaderSection())
|
||||
.build();
|
||||
}
|
||||
var memberList = new StringBuilder();
|
||||
var isFirst = true;
|
||||
for (var member : abstractMembers) {
|
||||
if (isFirst) {
|
||||
isFirst = false;
|
||||
} else {
|
||||
memberList.append('\n');
|
||||
}
|
||||
memberList.append("* ");
|
||||
if (member instanceof ClassProperty) {
|
||||
memberList.append("property ");
|
||||
} else {
|
||||
memberList.append("method ");
|
||||
}
|
||||
memberList.append('`').append(member.getName()).append('`');
|
||||
}
|
||||
throw new VmExceptionBuilder()
|
||||
.evalError("noImplementationForAbstractMembers", getDisplayName(), memberList.toString())
|
||||
.withSourceSection(getHeaderSection())
|
||||
.build();
|
||||
}
|
||||
|
||||
private boolean hasAbstractMember() {
|
||||
var propertyCursor = getAllProperties().getEntries();
|
||||
while (propertyCursor.advance()) {
|
||||
var property = propertyCursor.getValue();
|
||||
if (property.isAbstract()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
var methodCursor = getAllMethods().getEntries();
|
||||
while (methodCursor.advance()) {
|
||||
var method = methodCursor.getValue();
|
||||
if (method.isAbstract()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<ClassMember> getAbstractMembers() {
|
||||
assert this.superclass != null;
|
||||
var result = new ArrayList<ClassMember>();
|
||||
var propertyCursor = getAllProperties().getEntries();
|
||||
while (propertyCursor.advance()) {
|
||||
var property = propertyCursor.getValue();
|
||||
if (property.isAbstract()) {
|
||||
result.add(property);
|
||||
}
|
||||
}
|
||||
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());
|
||||
@@ -190,8 +275,46 @@ public final class VmClass extends VmValue {
|
||||
}
|
||||
}
|
||||
|
||||
private void onInitialized(Runnable runnable) {
|
||||
synchronized (finalizersLock) {
|
||||
if (this.__finalizers == null) {
|
||||
this.__finalizers = new ArrayList<>();
|
||||
}
|
||||
this.__finalizers.add(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
// Note: Superclasses may not have finished their initialization when this method is called.
|
||||
public void notifyInitialized() {
|
||||
var sc = superclass;
|
||||
var isAllInitialized = true;
|
||||
var uninitializedCount = 0;
|
||||
while (sc != null) {
|
||||
if (!sc.isInitialized) {
|
||||
sc.onInitialized(
|
||||
() -> {
|
||||
var count = uninitializedSuperclassCount.decrementAndGet();
|
||||
if (count == 0) {
|
||||
checkAbstractMembers();
|
||||
}
|
||||
});
|
||||
uninitializedCount++;
|
||||
isAllInitialized = false;
|
||||
}
|
||||
sc = sc.superclass;
|
||||
}
|
||||
uninitializedSuperclassCount.set(uninitializedCount);
|
||||
if (isAllInitialized) {
|
||||
checkAbstractMembers();
|
||||
}
|
||||
lock:
|
||||
synchronized (finalizersLock) {
|
||||
if (__finalizers == null) break lock;
|
||||
for (var finalizer : __finalizers) {
|
||||
finalizer.run();
|
||||
}
|
||||
this.__finalizers = null;
|
||||
}
|
||||
isInitialized = true;
|
||||
}
|
||||
|
||||
@@ -735,13 +858,27 @@ public final class VmClass extends VmValue {
|
||||
for (var property : EconomicMaps.getValues(declaredProperties)) {
|
||||
if (property.isLocal()) continue;
|
||||
|
||||
// A property is considered a class property definition
|
||||
// if it has a type annotation or has no superdefinition (ad-hoc case).
|
||||
// A property is considered a class property definition if any of the following are true:
|
||||
//
|
||||
// 1. It has a type annotation.
|
||||
// 2. It has no superdefinition.
|
||||
// 3. It is not abstract but its superclass is.
|
||||
//
|
||||
// Otherwise, it is considered an object property definition,
|
||||
// which means it affects the class prototype but not the class itself.
|
||||
// An example for the latter is when `Module.output` is overridden with `output { ... }`.
|
||||
if (property.getTypeNode() != null || !EconomicMaps.containsKey(result, property.getName())) {
|
||||
if (property.getTypeNode() != null) {
|
||||
EconomicMaps.put(result, property.getName(), property);
|
||||
} else {
|
||||
var existingProperty = EconomicMaps.get(result, property.getName());
|
||||
if (existingProperty != null && existingProperty.isAbstract() && !this.isAbstract()) {
|
||||
property.lateInitTypeNodeAndModifiers(
|
||||
existingProperty.getTypeNode(),
|
||||
existingProperty.getModifiers() & ~VmModifier.ABSTRACT);
|
||||
EconomicMaps.put(result, property.getName(), property);
|
||||
} else if (existingProperty == null) {
|
||||
EconomicMaps.put(result, property.getName(), property);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1176,3 +1176,13 @@ Cannot follow redirect from ''https:'' URL to ''http:'' URL.\
|
||||
\n\
|
||||
HTTP Request: `GET {0}`\n\
|
||||
Redirected to: `{1}`
|
||||
|
||||
noImplementationForAbstractProperty=\
|
||||
Class `{0}` should either be declared `abstract`, or should implement property `{1}`.
|
||||
|
||||
noImplementationForAbstractMethod=\
|
||||
Class `{0}` should either be declared `abstract`, or should implement method `{1}`.
|
||||
|
||||
noImplementationForAbstractMembers=\
|
||||
Class `{0}` should either be declared `abstract`, or implement the following members:\n\
|
||||
{1}
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
abstract module Foo
|
||||
|
||||
abstract bar: Int
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
abstract class AbstractProperty {
|
||||
abstract foo: Int
|
||||
}
|
||||
|
||||
abstract class AbstractMethod {
|
||||
abstract function foo(): Int
|
||||
}
|
||||
|
||||
abstract class AbstractPropertiesAndMethods {
|
||||
abstract foo: Int
|
||||
abstract bar: Int
|
||||
abstract function foo(): Int
|
||||
abstract function bar(): Int
|
||||
}
|
||||
|
||||
class MyClass extends AbstractProperty {
|
||||
}
|
||||
|
||||
foo: MyClass
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
abstract class AbstractMethod {
|
||||
abstract function foo(): Int
|
||||
}
|
||||
|
||||
class MyClass extends AbstractMethod {
|
||||
}
|
||||
|
||||
foo: MyClass
|
||||
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
abstract class AbstractPropertiesAndMethods {
|
||||
abstract foo: Int
|
||||
abstract bar: Int
|
||||
abstract function foo(): Int
|
||||
abstract function bar(): Int
|
||||
}
|
||||
|
||||
class MyClass extends AbstractPropertiesAndMethods {}
|
||||
|
||||
foo: MyClass
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
extends "../../input-helper/classes/AbstractModule.pkl"
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
–– Pkl Error ––
|
||||
Class `abstractMemberNotImplemented1#MyClass` should either be declared `abstract`, or should implement property `foo`.
|
||||
|
||||
xx | class MyClass extends AbstractProperty {
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at abstractMemberNotImplemented1#MyClass (file:///$snippetsDir/input/errors/abstractMemberNotImplemented1.pkl)
|
||||
|
||||
xx | foo: MyClass
|
||||
^^^^^^^
|
||||
at abstractMemberNotImplemented1 (file:///$snippetsDir/input/errors/abstractMemberNotImplemented1.pkl)
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
–– Pkl Error ––
|
||||
Class `abstractMemberNotImplemented2#MyClass` should either be declared `abstract`, or should implement method `foo`.
|
||||
|
||||
x | class MyClass extends AbstractMethod {
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at abstractMemberNotImplemented2#MyClass (file:///$snippetsDir/input/errors/abstractMemberNotImplemented2.pkl)
|
||||
|
||||
x | foo: MyClass
|
||||
^^^^^^^
|
||||
at abstractMemberNotImplemented2 (file:///$snippetsDir/input/errors/abstractMemberNotImplemented2.pkl)
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
–– Pkl Error ––
|
||||
Class `abstractMemberNotImplemented3#MyClass` should either be declared `abstract`, or implement the following members:
|
||||
* property `foo`
|
||||
* property `bar`
|
||||
* method `foo`
|
||||
* method `bar`
|
||||
|
||||
x | class MyClass extends AbstractPropertiesAndMethods {}
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at abstractMemberNotImplemented3#MyClass (file:///$snippetsDir/input/errors/abstractMemberNotImplemented3.pkl)
|
||||
|
||||
xx | foo: MyClass
|
||||
^^^^^^^
|
||||
at abstractMemberNotImplemented3 (file:///$snippetsDir/input/errors/abstractMemberNotImplemented3.pkl)
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
–– Pkl Error ––
|
||||
Class `abstractMemberNotImplemented4` should either be declared `abstract`, or should implement property `bar`.
|
||||
|
||||
x | extends "../../input-helper/classes/AbstractModule.pkl"
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at abstractMemberNotImplemented4 (file:///$snippetsDir/input/errors/abstractMemberNotImplemented4.pkl)
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
–– Pkl Error ––
|
||||
Class `abstractMethodsNotImplemented#MyClass` either needs to be abstract, or should implement property `foo`.
|
||||
|
||||
x | class MyClass extends MyAbstractClass {
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at abstractMethodsNotImplemented#MyClass (file:///$snippetsDir/input/errors/abstractMethodsNotImplemented.pkl)
|
||||
|
||||
x | foo: MyClass
|
||||
^^^^^^^
|
||||
at abstractMethodsNotImplemented (file:///$snippetsDir/input/errors/abstractMethodsNotImplemented.pkl)
|
||||
@@ -3562,6 +3562,18 @@ external class Set<out Element> extends Collection<Element> {
|
||||
|
||||
/// The difference of this set and [other].
|
||||
external function difference(other: Set): Set<Element>
|
||||
|
||||
function indexOf(_): Int = throw("Set does not implement `indexOf`")
|
||||
function indexOfOrNull(_): Int? = throw("Set does not implement `indexOfOrNull`")
|
||||
|
||||
function lastIndexOf(_): Int = throw("Set does not implement `lastIndexOf`")
|
||||
function lastIndexOfOrNull(_): Int? = throw("Set does not implement `lastIndexOfOrNull`")
|
||||
|
||||
function findIndex(_): Int = throw("Set does not implement `findIndex`")
|
||||
function findIndexOrNull(_): Int? = throw("Set does not implement `findIndexOrNull`")
|
||||
|
||||
function findLastIndex(_): Int = throw("Set does not implement `findLastIndex`")
|
||||
function findLastIndexOrNull(_): Int? = throw("Set does not implement `findLastIndexOrNull`")
|
||||
}
|
||||
|
||||
/// Creates a map containing the given alternating [keysAndValues].
|
||||
|
||||
Reference in New Issue
Block a user