Expose collected superclass properties/methods in pkl:reflect (#1106)

This commit is contained in:
Jen Basch
2025-09-19 12:23:57 -07:00
committed by GitHub
parent d1171db3d5
commit 63f89fb679
7 changed files with 2997 additions and 16 deletions

View File

@@ -16,6 +16,7 @@
package org.pkl.core.ast.member;
import com.oracle.truffle.api.source.SourceSection;
import java.util.ArrayList;
import java.util.List;
import org.pkl.core.Member.SourceLocation;
import org.pkl.core.PClass;
@@ -67,8 +68,44 @@ public final class ClassProperty extends ClassMember {
return name.toString();
}
public VmTyped getMirror() {
return MirrorFactories.propertyFactory.create(this);
public static final class Mirror {
private final ClassProperty prop;
private final VmClass clazz;
Mirror(ClassProperty prop, VmClass clazz) {
this.prop = prop;
this.clazz = clazz;
}
public ClassProperty getProperty() {
return prop;
}
public List<VmTyped> getAllAnnotations() {
var annotations = new ArrayList<VmTyped>();
for (var klazz = clazz; klazz != null; klazz = klazz.getSuperclass()) {
var p = klazz.getDeclaredProperty(prop.getName());
if (p != null) {
annotations.addAll(p.getAnnotations());
}
}
return annotations;
}
public VmSet getAllModifierMirrors() {
var mods = 0;
for (var klazz = clazz; klazz != null; klazz = klazz.getSuperclass()) {
var parent = klazz.getDeclaredProperty(prop.getName());
if (parent != null) {
mods |= parent.getModifiers();
}
}
return VmModifier.getMirrors(mods, false);
}
}
public VmTyped getMirror(VmClass clazz) {
return MirrorFactories.propertyFactory.create(new Mirror(this, clazz));
}
public VmSet getModifierMirrors() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,7 +37,7 @@ public final class MirrorFactories {
public static final VmObjectFactory<VmTypeAlias> typeAliasFactory =
new VmObjectFactory<>(ReflectModule::getTypeAliasClass);
public static final VmObjectFactory<ClassProperty> propertyFactory =
public static final VmObjectFactory<ClassProperty.Mirror> propertyFactory =
new VmObjectFactory<>(ReflectModule::getPropertyClass);
public static final VmObjectFactory<ClassMethod> methodFactory =
@@ -146,8 +146,10 @@ public final class MirrorFactories {
.addProperty("superclass", VmClass::getSuperclassMirror)
.addProperty("supertype", VmClass::getSupertypeMirror)
.addMapProperty("properties", VmClass::getPropertyMirrors)
.addMapProperty("allProperties", VmClass::getAllPropertyMirrors)
.addTypedProperty("enclosingDeclaration", VmClass::getModuleMirror)
.addMapProperty("methods", VmClass::getMethodMirrors);
.addMapProperty("methods", VmClass::getMethodMirrors)
.addMapProperty("allMethods", VmClass::getAllMethodMirrors);
typeAliasFactory
.addTypedProperty(
@@ -164,24 +166,30 @@ public final class MirrorFactories {
propertyFactory
.addTypedProperty(
"location", property -> sourceLocationFactory.create(property.getHeaderSection()))
"location",
property -> sourceLocationFactory.create(property.getProperty().getHeaderSection()))
.addProperty(
"docComment",
property -> VmNull.lift(VmUtils.exportDocComment(property.getDocComment())))
.addListProperty("annotations", property -> VmList.create(property.getAnnotations()))
.addSetProperty("modifiers", ClassProperty::getModifierMirrors)
.addStringProperty("name", property -> property.getName().toString())
.addTypedProperty("type", ClassProperty::getTypeMirror)
property ->
VmNull.lift(VmUtils.exportDocComment(property.getProperty().getDocComment())))
.addListProperty(
"annotations", property -> VmList.create(property.getProperty().getAnnotations()))
.addListProperty("allAnnotations", property -> VmList.create(property.getAllAnnotations()))
.addSetProperty("modifiers", property -> property.getProperty().getModifierMirrors())
.addSetProperty("allModifiers", ClassProperty.Mirror::getAllModifierMirrors)
.addStringProperty("name", property -> property.getProperty().getName().toString())
.addTypedProperty("type", property -> property.getProperty().getTypeMirror())
.addProperty(
"defaultValue",
property ->
property.isAbstract()
|| property.isExternal()
|| property.getInitializer().isUndefined()
property.getProperty().isAbstract()
|| property.getProperty().isExternal()
|| property.getProperty().getInitializer().isUndefined()
? VmNull.withoutDefault()
:
// get default from prototype because it's cached there
VmUtils.readMember(property.getOwner(), property.getName()));
VmUtils.readMember(
property.getProperty().getOwner(), property.getProperty().getName()));
methodFactory
.addTypedProperty(

View File

@@ -575,7 +575,16 @@ public final class VmClass extends VmValue {
var builder = VmMap.builder();
for (var property : declaredProperties.getValues()) {
if (property.isLocal()) continue;
builder.add(property.getName().toString(), property.getMirror());
builder.add(property.getName().toString(), property.getMirror(this));
}
return builder.build();
}
public VmMap getAllPropertyMirrors() {
var builder = VmMap.builder();
for (var property : getAllProperties().getValues()) {
if (property.isLocal()) continue;
builder.add(property.getName().toString(), property.getMirror(this));
}
return builder.build();
}
@@ -589,6 +598,15 @@ public final class VmClass extends VmValue {
return builder.build();
}
public VmMap getAllMethodMirrors() {
var builder = VmMap.builder();
for (var method : getAllMethods().getValues()) {
if (method.isLocal()) continue;
builder.add(method.getName().toString(), method.getMirror());
}
return builder.build();
}
@Override
@TruffleBoundary
public PClass export() {

View File

@@ -0,0 +1,88 @@
amends ".../snippetTest.pkl"
import "pkl:reflect"
local class MyAnn extends Annotation
open local class A {
inheritedFromA: String
overriddenInBFromA: String
overriddenInCFromA: String
overriddenInBCFromA: String
fixed modA: String = ""
fixed modAOverrideB: String = ""
fixed modAOverrideC: String = ""
fixed modAOverrideBC: String = ""
fixed modAOverrideCB: String = ""
@Unlisted
annA: String
@Unlisted
annAOverrideB: String
@Unlisted
annAOverrideC: String
@Unlisted
annAOverrideBC: String
}
open local class B extends A {
inheritedFromB: String
overriddenInBFromA: Int
overriddenInCFromB: String
overriddenInBCFromA: Int
fixed modB: String = ""
hidden fixed modAOverrideB: Int = 0
hidden fixed modAOverrideBC: Int = 0
fixed modAOverrideCB: Int = 0
@Unlisted
annB: String
@MyAnn
annAOverrideB: String
@MyAnn
annAOverrideBC: String
}
local class C extends B {
fromC: String
overriddenInCFromA: Int
overriddenInCFromB: Int
overriddenInBCFromA: Boolean
fixed modC: String = ""
hidden fixed modAOverrideC: Int = 0
fixed modAOverrideBC: Boolean = false
hidden fixed modAOverrideCB: Boolean = false
@Unlisted
annC: String
@MyAnn
annAOverrideC: String
@AlsoKnownAs { names { "mergedAnnotations" } }
annAOverrideBC: String
}
local class DisplayProperty {
hidden property: reflect.Property
fixed name = property.name
fixed annotations = property.annotations.map((it) -> it.toString()).toListing()
fixed allAnnotations = property.allAnnotations.map((it) -> it.toString()).toListing()
fixed modifiers = property.modifiers.toListing()
fixed allModifiers = property.allModifiers.toListing()
fixed type = property.type.getPropertyOrNull("referent")?.getPropertyOrNull("name") ?? "unknown"
}
examples {
["C"] {
reflect.Class(C).allProperties.mapValues((_, it) -> new DisplayProperty { property = it }).toMapping()
}
["module"] {
new DisplayProperty { property = reflect.Class(module.getClass()).properties["output"] }
}
}
output { // override to test example "module", allModifiers should contain "hidden"
text = super.text + "\n// hello world"
}

View File

@@ -0,0 +1,243 @@
examples {
["C"] {
new {
["inheritedFromA"] {
name = "inheritedFromA"
annotations {}
allAnnotations {}
modifiers {}
allModifiers {}
type = "String"
}
["overriddenInBFromA"] {
name = "overriddenInBFromA"
annotations {}
allAnnotations {}
modifiers {}
allModifiers {}
type = "Int"
}
["overriddenInCFromA"] {
name = "overriddenInCFromA"
annotations {}
allAnnotations {}
modifiers {}
allModifiers {}
type = "Int"
}
["overriddenInBCFromA"] {
name = "overriddenInBCFromA"
annotations {}
allAnnotations {}
modifiers {}
allModifiers {}
type = "Boolean"
}
["modA"] {
name = "modA"
annotations {}
allAnnotations {}
modifiers {
"fixed"
}
allModifiers {
"fixed"
}
type = "String"
}
["modAOverrideB"] {
name = "modAOverrideB"
annotations {}
allAnnotations {}
modifiers {
"hidden"
"fixed"
}
allModifiers {
"hidden"
"fixed"
}
type = "Int"
}
["modAOverrideC"] {
name = "modAOverrideC"
annotations {}
allAnnotations {}
modifiers {
"hidden"
"fixed"
}
allModifiers {
"hidden"
"fixed"
}
type = "Int"
}
["modAOverrideBC"] {
name = "modAOverrideBC"
annotations {}
allAnnotations {}
modifiers {
"fixed"
}
allModifiers {
"hidden"
"fixed"
}
type = "Boolean"
}
["modAOverrideCB"] {
name = "modAOverrideCB"
annotations {}
allAnnotations {}
modifiers {
"hidden"
"fixed"
}
allModifiers {
"hidden"
"fixed"
}
type = "Boolean"
}
["annA"] {
name = "annA"
annotations {
"new Unlisted {}"
}
allAnnotations {
"new Unlisted {}"
}
modifiers {}
allModifiers {}
type = "String"
}
["annAOverrideB"] {
name = "annAOverrideB"
annotations {
"new MyAnn {}"
}
allAnnotations {
"new MyAnn {}"
"new Unlisted {}"
}
modifiers {}
allModifiers {}
type = "String"
}
["annAOverrideC"] {
name = "annAOverrideC"
annotations {
"new MyAnn {}"
}
allAnnotations {
"new MyAnn {}"
"new Unlisted {}"
}
modifiers {}
allModifiers {}
type = "String"
}
["annAOverrideBC"] {
name = "annAOverrideBC"
annotations {
"new AlsoKnownAs { names { \"mergedAnnotations\" } }"
}
allAnnotations {
"new AlsoKnownAs { names { \"mergedAnnotations\" } }"
"new MyAnn {}"
"new Unlisted {}"
}
modifiers {}
allModifiers {}
type = "String"
}
["inheritedFromB"] {
name = "inheritedFromB"
annotations {}
allAnnotations {}
modifiers {}
allModifiers {}
type = "String"
}
["overriddenInCFromB"] {
name = "overriddenInCFromB"
annotations {}
allAnnotations {}
modifiers {}
allModifiers {}
type = "Int"
}
["modB"] {
name = "modB"
annotations {}
allAnnotations {}
modifiers {
"fixed"
}
allModifiers {
"fixed"
}
type = "String"
}
["annB"] {
name = "annB"
annotations {
"new Unlisted {}"
}
allAnnotations {
"new Unlisted {}"
}
modifiers {}
allModifiers {}
type = "String"
}
["fromC"] {
name = "fromC"
annotations {}
allAnnotations {}
modifiers {}
allModifiers {}
type = "String"
}
["modC"] {
name = "modC"
annotations {}
allAnnotations {}
modifiers {
"fixed"
}
allModifiers {
"fixed"
}
type = "String"
}
["annC"] {
name = "annC"
annotations {
"new Unlisted {}"
}
allAnnotations {
"new Unlisted {}"
}
modifiers {}
allModifiers {}
type = "String"
}
}
}
["module"] {
new {
name = "output"
annotations {}
allAnnotations {}
modifiers {}
allModifiers {
"hidden"
}
type = "unknown"
}
}
}
// hello world