Skip to content

Commit 341c50e

Browse files
committed
Enforce restrictions on referenced properties
1 parent b28a42d commit 341c50e

7 files changed

Lines changed: 63 additions & 23 deletions

File tree

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,36 @@
1+
/*
2+
* Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
116
package org.pkl.lsp
217

318
import org.pkl.lsp.ast.PklClass
419
import org.pkl.lsp.ast.PklModule
520
import org.pkl.lsp.type.Type
621

7-
class PklRefModule(project: Project): Component(project) {
8-
val module: PklModule?
9-
get() = project.stdlib.ref?.getModule()?.get()
22+
class PklRefModule(project: Project) : Component(project) {
23+
val module: PklModule?
24+
get() = project.stdlib.ref?.getModule()?.get()
1025

11-
val types: Map<String, Type> = buildMap {
12-
for (member in module?.members ?: emptyList()) {
13-
if (member is PklClass) {
14-
put(member.name, Type.Class.create(member))
15-
}
16-
}
26+
val types: Map<String, Type> = buildMap {
27+
for (member in module?.members ?: emptyList()) {
28+
if (member is PklClass) {
29+
put(member.name, Type.Class.create(member))
30+
}
1731
}
32+
}
1833

19-
// Will be `null` for versions < 0.32
20-
val referenceType: Type.Reference? by lazy { types["Reference"] as? Type.Reference }
34+
// Will be `null` for versions < 0.32
35+
val referenceType: Type.Reference? by lazy { types["Reference"] as? Type.Reference }
2136
}

src/main/kotlin/org/pkl/lsp/analyzers/TypeAnalyzer.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,10 @@ class TypeAnalyzer(project: Project) : Analyzer(project) {
4646
unaliased is Type.Reference &&
4747
type.containsConstrainedType(project.pklBaseModule, node.containingFile.pklProject)
4848
) {
49-
holder.addError(node, ErrorMessages.create("invalidReferenceTypeWithConstraint"))
49+
diagnosticsHolder.addError(
50+
node,
51+
ErrorMessages.create("invalidReferenceTypeWithConstraint"),
52+
)
5053
}
5154

5255
false

src/main/kotlin/org/pkl/lsp/ast/ModuleOrClass.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class PklModuleImpl(override val ctx: Node, override val virtualFile: VirtualFil
5050
header?.moduleExtendsAmendsClause?.moduleUri
5151
}
5252

53-
private val lock = Object()
53+
private val lock = Any()
5454

5555
// This is cached at the VirtualFile level
5656
override fun supermodule(context: PklProject?): PklModule? =

src/main/kotlin/org/pkl/lsp/type/Types.kt

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,28 @@ sealed class Type(val constraints: List<ConstraintExpr> = listOf()) {
560560
override fun withTypeArguments(arguments: List<Type>): Class =
561561
Reference(ctx, arguments, constraints)
562562

563+
// enforce restrictions! cannot reference:
564+
// - external or local properties
565+
// - properties of Listing or Mapping
566+
// - Dynamic.default
567+
// - properties of any external class
568+
// - any Module.output
569+
private fun isViable(
570+
prop: PklClassProperty,
571+
type: Type,
572+
base: PklBaseModule,
573+
context: PklProject?,
574+
): Boolean =
575+
!(prop.isExternal ||
576+
prop.isLocal ||
577+
(type as? Class)?.let {
578+
it == base.listingType ||
579+
it == base.mappingType ||
580+
(it == base.dynamicType && prop.name == "default") ||
581+
it.ctx.isExternal
582+
} ?: false ||
583+
(type.isSubtypeOf(base.moduleType, base, context) && prop.name == "output"))
584+
563585
override fun visitMembers(
564586
isProperty: Boolean,
565587
allowClasses: Boolean,
@@ -583,9 +605,9 @@ sealed class Type(val constraints: List<ConstraintExpr> = listOf()) {
583605
var isUnknown = false
584606
val candidates = mutableSetOf<PklType>()
585607

586-
walkCandidates(referent, base, context) { _, properties ->
608+
walkCandidates(referent, base, context) { type, properties ->
587609
for (prop in properties) {
588-
if (!(prop.isExternal || prop.isLocal) && prop.name == visitor.exactName) {
610+
if (isViable(prop, type, base, context) && prop.name == visitor.exactName) {
589611
val propType = prop.type ?: PklFakeUnknownTypeImpl(ctx.project)
590612
if (propType is PklUnknownType) {
591613
isUnknown = true
@@ -608,9 +630,9 @@ sealed class Type(val constraints: List<ConstraintExpr> = listOf()) {
608630
}
609631
else -> {
610632
val propertyCandidates = mutableMapOf<String, MutableSet<PklType>>()
611-
walkCandidates(referent, base, context) { _, properties ->
633+
walkCandidates(referent, base, context) { type, properties ->
612634
for (prop in properties) {
613-
if (!(prop.isExternal || prop.isLocal)) {
635+
if (isViable(prop, type, base, context)) {
614636
propertyCandidates
615637
.getOrPut(prop.name) { mutableSetOf() }
616638
.add(prop.type ?: PklFakeUnknownTypeImpl(ctx.project))

src/test/files/DiagnosticsSnippetTests/outputs/typecheck/nullabilityMismatch.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44
...foo
55
^^^
66
| Warning: Nullability mismatch.
7-
| Required: Collection|Map|Dynamic|Listing|Mapping|IntSeq|Bytes
7+
| Required: Collection | Map | Dynamic | Listing | Mapping | IntSeq | Bytes
88
| Actual: Listing<String>?
99
}
1010

1111
quz {
1212
for (elem in foo) {
1313
^^^
1414
| Warning: Nullability mismatch.
15-
| Required: Collection|Map|Dynamic|Listing|Mapping|IntSeq|Bytes
15+
| Required: Collection | Map | Dynamic | Listing | Mapping | IntSeq | Bytes
1616
| Actual: Listing<String>?
1717
"hi \(elem)"
1818
}

src/test/files/DiagnosticsSnippetTests/outputs/typecheck/spreadSyntax.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@
1010
...bar
1111
^^^
1212
| Warning: Nullability mismatch.
13-
| Required: Collection|Map|Dynamic|Listing|Mapping|IntSeq|Bytes
13+
| Required: Collection | Map | Dynamic | Listing | Mapping | IntSeq | Bytes
1414
| Actual: Mapping?
1515
}
1616

1717
prop3 {
1818
...myNum
1919
^^^^^
2020
| Error: Type mismatch.
21-
| Required: Collection|Map|Dynamic|Listing|Mapping|IntSeq|Bytes
21+
| Required: Collection | Map | Dynamic | Listing | Mapping | IntSeq | Bytes
2222
| Actual: Int
2323
}
2424

@@ -30,6 +30,6 @@
3030
...Bytes(1, 2, 3)
3131
^^^^^^^^^^^^^^
3232
| Error: Type mismatch.
33-
| Required: Collection<String>|Listing<String>|Dynamic
33+
| Required: Collection<String> | Listing<String> | Dynamic
3434
| Actual: Bytes
3535
}

src/test/files/DiagnosticsSnippetTests/outputs/typecheck/unions.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
foo: Int|String = true
22
^^^^
33
| Error: Type mismatch.
4-
| Required: Int|String
4+
| Required: Int | String
55
| Actual: Boolean
66

77
bar: Int = null

0 commit comments

Comments
 (0)