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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
11 changes: 11 additions & 0 deletions pkl-core/src/main/java/org/pkl/core/PType.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ public String toString() {
}
};

/** The {@code this} type. */
public static final PType THIS =
new PType() {
@Serial private static final long serialVersionUID = 0L;

@Override
public String toString() {
return "this";
}
};

private PType() {}

public List<PType> getTypeArguments() {
Expand Down
97 changes: 96 additions & 1 deletion pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@
import org.pkl.core.ast.expression.primary.GetMemberKeyNode;
import org.pkl.core.ast.expression.primary.GetModuleNode;
import org.pkl.core.ast.expression.primary.GetOwnerNode;
import org.pkl.core.ast.expression.primary.GetReceiverClassNode;
import org.pkl.core.ast.expression.primary.GetReceiverNode;
import org.pkl.core.ast.expression.primary.GetTypeAliasModuleNode;
import org.pkl.core.ast.expression.primary.OuterNode;
Expand Down Expand Up @@ -207,6 +208,7 @@
import org.pkl.core.stdlib.registry.MemberRegistryFactory;
import org.pkl.core.util.CollectionUtils;
import org.pkl.core.util.EconomicMaps;
import org.pkl.core.util.ErrorMessages;
import org.pkl.core.util.IoUtils;
import org.pkl.core.util.Pair;
import org.pkl.parser.Span;
Expand Down Expand Up @@ -278,6 +280,7 @@
import org.pkl.parser.syntax.Type.NullableType;
import org.pkl.parser.syntax.Type.ParenthesizedType;
import org.pkl.parser.syntax.Type.StringConstantType;
import org.pkl.parser.syntax.Type.ThisType;
import org.pkl.parser.syntax.Type.UnionType;
import org.pkl.parser.syntax.Type.UnknownType;
import org.pkl.parser.syntax.TypeAlias;
Expand Down Expand Up @@ -369,7 +372,99 @@ public UnresolvedTypeNode visitNothingType(NothingType type) {

@Override
public UnresolvedTypeNode visitModuleType(ModuleType type) {
return new UnresolvedTypeNode.Module(createSourceSection(type));
var sourceSection = createSourceSection(type);
checkModuleType(type, sourceSection);
return new UnresolvedTypeNode.Module(sourceSection);
}

private void checkModuleType(ModuleType type, SourceSection sourceSection) {
// `class X extends module` is fine
if (type.parent() instanceof Class classNode && classNode.getSuperClass() == type) {
return;
}

String errorMessage = null;
Object[] errorArgs = new Object[] {};

// attempt to identify containing class/alias/annotation before checking const
// properties/methods
for (var scope = symbolTable.getCurrentScope();
scope != null && errorMessage == null;
scope = scope.getParent()) {
if (scope.isAnnotationScope()) {
errorMessage = "invalidModuleTypeInAnnotation";
} else if (scope.isClassScope()) {
errorMessage = "invalidModuleTypeInClass";
} else if (scope.isTypeAliasScope()) {
errorMessage = "invalidModuleTypeInTypeAlias";
}
}
for (var scope = symbolTable.getCurrentScope();
scope != null && errorMessage == null;
scope = scope.getParent()) {
if (!scope.getConstLevel().isConst()) {
continue;
}
if (scope.isPropertyScope()) {
errorMessage = "invalidModuleTypeInProperty";
errorArgs = new Object[] {scope.getQualifiedName()};
} else if (scope.isMethodScope()) {
errorMessage = "invalidModuleTypeInMethod";
errorArgs = new Object[] {scope.getQualifiedName()};
} else {
// all possibly const scopes should be covered in one of these loops
throw exceptionBuilder().unreachableCode().build();
}
}
if (errorMessage != null) {
VmContext.get(null)
.getLogger()
.warn(
ErrorMessages.create(errorMessage, errorArgs)
+ " This will be an error in a future release.",
VmUtils.createStackFrame(sourceSection, null));
}
Comment thread
HT154 marked this conversation as resolved.
}

@Override
public UnresolvedTypeNode visitThisType(ThisType type) {
var sourceSection = createSourceSection(type);
// need to pass explicit class name for property and method arg/return type annotations
// do not need: when in any object or at the module level (where `this` is the receiver's class)
org.pkl.core.runtime.Identifier className = null;
for (var scope = symbolTable.getCurrentScope(); scope != null; scope = scope.getParent()) {
if (scope.isObjectScope() || scope.isCustomThisScope()) {
break;
}
if (scope instanceof ClassScope foundClassScope) {
className = foundClassScope.getName();
break;
}
// it's still safe to break on ObjectScope because this is valid:
// typealias Foo = List(any((it) -> it == new Dynamic { it is this })) // this == Dynamic
if (scope.isTypeAliasScope()) {
throw exceptionBuilder()
.withSourceSection(sourceSection)
.evalError("invalidThisTypeInTypeAlias")
.build();
}
}

ExpressionNode getClassNode;
if (isBaseModule && className != null) {
getClassNode = new GetBaseModuleClassNode(className);
} else if (className == null) {
getClassNode = new GetReceiverClassNode(sourceSection);
} else if (className.isLocalProp()) {
getClassNode =
new ReadQualifiedLocalPropertyNode(
sourceSection, className, false, new GetModuleNode(sourceSection));
} else {
getClassNode =
ReadPropertyNodeGen.create(
sourceSection, className, false, new GetModuleNode(sourceSection));
}
return new UnresolvedTypeNode.This(sourceSection, getClassNode);
}

@Override
Expand Down
22 changes: 20 additions & 2 deletions pkl-core/src/main/java/org/pkl/core/ast/builder/SymbolTable.java
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,18 @@ public final Scope skipLambdaAndLetScopes() {
return curr;
}

public final boolean isAnnotationScope() {
return this instanceof AnnotationScope;
}

public final boolean isPropertyScope() {
return this instanceof PropertyScope;
}

public final boolean isMethodScope() {
return this instanceof MethodScope;
}

public final boolean isLetScope() {
return this instanceof LetExpressionScope;
}
Expand All @@ -422,6 +434,10 @@ public final boolean isClassScope() {
return this instanceof ClassScope;
}

public final boolean isObjectScope() {
return this instanceof ObjectScope;
}

public final boolean isClassMemberScope() {
var effectiveScope = skipLambdaAndLetScopes();
var parent = effectiveScope.parent;
Expand Down Expand Up @@ -454,6 +470,10 @@ public final boolean isForGeneratorScope() {
return this instanceof ForGeneratorScope;
}

public final boolean isTypeAliasScope() {
return this instanceof TypeAliasScope;
}

public ConstLevel getConstLevel() {
return constLevel;
}
Expand Down Expand Up @@ -1010,15 +1030,13 @@ public ClassScope(

@Override
public @Nullable VariableResolution doResolveProperty(String name, int levelsUp) {

var member = properties.get(name);
if (member == null) return null;
return new LexicalProperty(false, member.modifiers, levelsUp);
}

@Override
public @Nullable MethodResolution doResolveMethod(String name, int levelsUp) {

var member = methods.get(name);
if (member == null) return null;
return new LexicalMethod(false, isClosed, false, member.modifiers, levelsUp);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
@NodeInfo(shortName = "module")
public final class GetModuleNode extends ExpressionNode {

// NB: When used in an open module, this may resolve to instances of a regular class that extends
// the module.

public GetModuleNode(SourceSection sourceSection) {
super(sourceSection);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright © 2024-2026 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pkl.core.ast.expression.primary;

import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.runtime.VmUtils;

public final class GetReceiverClassNode extends ExpressionNode {

public GetReceiverClassNode(SourceSection sourceSection) {
super(sourceSection);
}

@Override
public Object executeGeneric(VirtualFrame frame) {
return VmUtils.getClass(VmUtils.getReceiver(frame));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ public Object executeGeneric(VirtualFrame frame) {
var defaultValue =
typeNode.createDefaultValue(frame, VmLanguage.get(this), sourceSection, qualifiedName);

// can't cache default value for `module` type in a non-final module because it's a self-type
// (the default value changes when inherited).
// can't cache default value for `module`/`this` types in a non-final modules/classes because
// they're self types (the default value changes when inherited).
if (typeNode.isFinalType() && defaultValue != null) {
unresolvedTypeNode = null;
this.defaultValue = defaultValue;
Expand Down
Loading
Loading