Fix length of listings with computed index (#797)

Motivation:
The following expression evaluates to 2 instead of 1:
new Listing { "value" } { [0 + 0] = "override" }.length

Changes:
- fix length computation in EntriesLiteralNode
- improve `api/listing` tests
- make snippet test failures diffable in IntelliJ

Result:
- fixes https://github.com/apple/pkl/issues/780
- improved dev experience in IntelliJ
This commit is contained in:
translatenix
2024-11-13 10:29:37 -08:00
committed by GitHub
parent 3f91824dc2
commit 9faff5e551
4 changed files with 241 additions and 5 deletions
@@ -19,6 +19,7 @@ import java.nio.file.Path
import java.util.stream.Collectors import java.util.stream.Collectors
import kotlin.io.path.* import kotlin.io.path.*
import org.assertj.core.api.Assertions.fail import org.assertj.core.api.Assertions.fail
import org.opentest4j.AssertionFailedError
import org.pkl.commons.* import org.pkl.commons.*
object FileTestUtils { object FileTestUtils {
@@ -110,5 +111,11 @@ data class SnippetOutcome(val expectedOutFile: Path, val actual: String, val suc
} }
private fun failWithDiff(message: String): Nothing = private fun failWithDiff(message: String): Nothing =
throw PklAssertionFailedError(message, expected, actual) if (System.getProperty("sun.java.command", "").contains("intellij")) {
// IntelliJ only shows diffs for AssertionFailedError
throw AssertionFailedError(message, expected, actual)
} else {
// Gradle test logging/report only shows diffs for PklAssertionFailedError
throw PklAssertionFailedError(message, expected, actual)
}
} }
@@ -43,15 +43,15 @@ public abstract class EntriesLiteralNode extends SpecializedObjectLiteralNode {
@Children private final ExpressionNode[] keyNodes; @Children private final ExpressionNode[] keyNodes;
private final ObjectMember[] values; private final ObjectMember[] values;
public EntriesLiteralNode( protected EntriesLiteralNode(
SourceSection sourceSection, SourceSection sourceSection,
VmLanguage language, VmLanguage language,
// contains local properties and default property (if present)
// does *not* contain entries with constant keys to maintain definition order of entries
String qualifiedScopeName, String qualifiedScopeName,
boolean isCustomThisScope, boolean isCustomThisScope,
@Nullable FrameDescriptor parametersDescriptor, @Nullable FrameDescriptor parametersDescriptor,
UnresolvedTypeNode[] parameterTypes, UnresolvedTypeNode[] parameterTypes,
// contains local properties and default property (if present)
// does *not* contain entries with constant keys to maintain definition order of entries
UnmodifiableEconomicMap<Object, ObjectMember> members, UnmodifiableEconomicMap<Object, ObjectMember> members,
ExpressionNode[] keyNodes, ExpressionNode[] keyNodes,
ObjectMember[] values) { ObjectMember[] values) {
@@ -103,7 +103,8 @@ public abstract class EntriesLiteralNode extends SpecializedObjectLiteralNode {
frame.materialize(), frame.materialize(),
parent, parent,
createListMembers(frame, parent.getLength()), createListMembers(frame, parent.getLength()),
parent.getLength() + keyNodes.length); // `[x] = y` overrides existing element and doesn't increase length
parent.getLength());
} }
@Specialization @Specialization
@@ -27,12 +27,20 @@ local altered: Listing<Person> = (base) {
[0] { name = "Wood Pigeon" } [0] { name = "Wood Pigeon" }
} }
local computedIndex: Listing<Person> = (base) {
[computeIndex()] { name = "Wood Pigeon" }
}
local function computeIndex() = 0
facts { facts {
["isEmpty"] { ["isEmpty"] {
empty.isEmpty empty.isEmpty
empty2.isEmpty empty2.isEmpty
!base.isEmpty !base.isEmpty
!derived.isEmpty !derived.isEmpty
!altered.isEmpty
!computedIndex.isEmpty
} }
["lastIndex"] { ["lastIndex"] {
@@ -41,6 +49,8 @@ facts {
base.lastIndex == 2 base.lastIndex == 2
derived.lastIndex == 4 derived.lastIndex == 4
duplicate.lastIndex == 5 duplicate.lastIndex == 5
altered.lastIndex == 2
computedIndex.lastIndex == 2
} }
["isDistinct"] { ["isDistinct"] {
@@ -49,6 +59,8 @@ facts {
base.isDistinct base.isDistinct
derived.isDistinct derived.isDistinct
!duplicate.isDistinct !duplicate.isDistinct
altered.isDistinct
computedIndex.isDistinct
} }
["isDistinctBy()"] { ["isDistinctBy()"] {
@@ -57,18 +69,24 @@ facts {
base.isDistinctBy((it) -> it) base.isDistinctBy((it) -> it)
derived.isDistinctBy((it) -> it) derived.isDistinctBy((it) -> it)
!duplicate.isDistinctBy((it) -> it) !duplicate.isDistinctBy((it) -> it)
altered.isDistinctBy((it) -> it)
computedIndex.isDistinctBy((it) -> it)
empty.isDistinctBy((it) -> it.name) empty.isDistinctBy((it) -> it.name)
empty2.isDistinctBy((it) -> it.name) empty2.isDistinctBy((it) -> it.name)
base.isDistinctBy((it) -> it.name) base.isDistinctBy((it) -> it.name)
derived.isDistinctBy((it) -> it.name) derived.isDistinctBy((it) -> it.name)
!duplicate.isDistinctBy((it) -> it.name) !duplicate.isDistinctBy((it) -> it.name)
altered.isDistinctBy((it) -> it.name)
computedIndex.isDistinctBy((it) -> it.name)
empty.isDistinctBy((it) -> it.getClass()) empty.isDistinctBy((it) -> it.getClass())
empty2.isDistinctBy((it) -> it.getClass()) empty2.isDistinctBy((it) -> it.getClass())
!base.isDistinctBy((it) -> it.getClass()) !base.isDistinctBy((it) -> it.getClass())
!derived.isDistinctBy((it) -> it.getClass()) !derived.isDistinctBy((it) -> it.getClass())
!duplicate.isDistinctBy((it) -> it.getClass()) !duplicate.isDistinctBy((it) -> it.getClass())
!altered.isDistinctBy((it) -> it.getClass())
!computedIndex.isDistinctBy((it) -> it.getClass())
} }
["getOrNull"] { ["getOrNull"] {
@@ -85,24 +103,32 @@ facts {
module.catch(() -> empty.first) == "Expected a non-empty Listing." module.catch(() -> empty.first) == "Expected a non-empty Listing."
base.first == base[0] base.first == base[0]
derived.first == base[0] derived.first == base[0]
altered.first != base[0]
computedIndex.first == altered.first
} }
["firstOrNull"] { ["firstOrNull"] {
empty.firstOrNull == null empty.firstOrNull == null
base.firstOrNull == base[0] base.firstOrNull == base[0]
derived.firstOrNull == base[0] derived.firstOrNull == base[0]
altered.firstOrNull != base[0]
computedIndex.firstOrNull == altered.first
} }
["last"] { ["last"] {
module.catch(() -> empty.last) == "Expected a non-empty Listing." module.catch(() -> empty.last) == "Expected a non-empty Listing."
base.last == base[2] base.last == base[2]
derived.last == derived[4] derived.last == derived[4]
altered.last == base[2]
computedIndex.last == base[2]
} }
["lastOrNull"] { ["lastOrNull"] {
empty.lastOrNull == null empty.lastOrNull == null
base.lastOrNull == base[2] base.lastOrNull == base[2]
derived.lastOrNull == derived[4] derived.lastOrNull == derived[4]
altered.lastOrNull == base[2]
computedIndex.lastOrNull == base[2]
} }
["single"] { ["single"] {
@@ -135,6 +161,7 @@ facts {
derived.contains(base[1]) derived.contains(base[1])
derived.contains(derived[3]) derived.contains(derived[3])
!altered.contains(base[0]) !altered.contains(base[0])
!computedIndex.contains(base[0])
} }
} }
@@ -144,6 +171,17 @@ examples {
empty2.length empty2.length
base.length base.length
derived.length derived.length
altered.length
computedIndex.length
local elementsAndEntries = (base) {
new { name = "" }
[2] { name = "" }
[computeIndex()] { name = "" }
new { name = "" }
[1 + 0] { name = "" }
}
elementsAndEntries.length
} }
["toList()"] { ["toList()"] {
@@ -152,6 +190,8 @@ examples {
base.toList() base.toList()
derived.toList() derived.toList()
duplicate.toList() duplicate.toList()
altered.toList()
computedIndex.toList()
} }
["toSet()"] { ["toSet()"] {
@@ -160,6 +200,8 @@ examples {
base.toSet() base.toSet()
derived.toSet() derived.toSet()
duplicate.toSet() duplicate.toSet()
altered.toSet()
computedIndex.toSet()
} }
["distinct"] { ["distinct"] {
@@ -168,6 +210,8 @@ examples {
base.distinct base.distinct
derived.distinct derived.distinct
duplicate.distinct duplicate.distinct
altered.distinct
computedIndex.distinct
} }
["distinctBy()"] { ["distinctBy()"] {
@@ -176,36 +220,48 @@ examples {
base.distinctBy((it) -> it) base.distinctBy((it) -> it)
derived.distinctBy((it) -> it) derived.distinctBy((it) -> it)
duplicate.distinctBy((it) -> it) duplicate.distinctBy((it) -> it)
altered.distinctBy((it) -> it)
computedIndex.distinctBy((it) -> it)
empty.distinctBy((it) -> it.name) empty.distinctBy((it) -> it.name)
empty2.distinctBy((it) -> it.name) empty2.distinctBy((it) -> it.name)
base.distinctBy((it) -> it.name) base.distinctBy((it) -> it.name)
derived.distinctBy((it) -> it.name) derived.distinctBy((it) -> it.name)
duplicate.distinctBy((it) -> it.name) duplicate.distinctBy((it) -> it.name)
altered.distinctBy((it) -> it.name)
computedIndex.distinctBy((it) -> it.name)
empty.distinctBy((it) -> it.getClass()) empty.distinctBy((it) -> it.getClass())
empty2.distinctBy((it) -> it.getClass()) empty2.distinctBy((it) -> it.getClass())
base.distinctBy((it) -> it.getClass()) base.distinctBy((it) -> it.getClass())
derived.distinctBy((it) -> it.getClass()) derived.distinctBy((it) -> it.getClass())
duplicate.distinctBy((it) -> it.getClass()) duplicate.distinctBy((it) -> it.getClass())
altered.distinctBy((it) -> it.getClass())
computedIndex.distinctBy((it) -> it.getClass())
} }
["fold"] { ["fold"] {
empty.fold(List(), (l, e) -> l.add(e)) empty.fold(List(), (l, e) -> l.add(e))
base.fold(List(), (l, e) -> l.add(e)) base.fold(List(), (l, e) -> l.add(e))
derived.fold(List(), (l, e) -> l.add(e)) derived.fold(List(), (l, e) -> l.add(e))
altered.fold(List(), (l, e) -> l.add(e))
computedIndex.fold(List(), (l, e) -> l.add(e))
} }
["foldIndexed"] { ["foldIndexed"] {
empty.foldIndexed(List(), (i, l, e) -> l.add(Pair(i, e))) empty.foldIndexed(List(), (i, l, e) -> l.add(Pair(i, e)))
base.foldIndexed(List(), (i, l, e) -> l.add(Pair(i, e))) base.foldIndexed(List(), (i, l, e) -> l.add(Pair(i, e)))
derived.foldIndexed(List(), (i, l, e) -> l.add(Pair(i, e))) derived.foldIndexed(List(), (i, l, e) -> l.add(Pair(i, e)))
altered.foldIndexed(List(), (i, l, e) -> l.add(Pair(i, e)))
computedIndex.foldIndexed(List(), (i, l, e) -> l.add(Pair(i, e)))
} }
local baseNum = new Listing { 1; 2; 3 } local baseNum = new Listing { 1; 2; 3 }
local baseString = new Listing { "Pigeon"; "Barn Owl"; "Parrot" } local baseString = new Listing { "Pigeon"; "Barn Owl"; "Parrot" }
local derivedString = (baseString) { "Albatross"; "Elf Owl" } local derivedString = (baseString) { "Albatross"; "Elf Owl" }
local alteredString = (baseString) { [0] = "Wood Pigeon" }
local computedIndexString = (baseString) { [computeIndex()] = "Wood Pigeon" }
["join"] { ["join"] {
empty.join("") empty.join("")
@@ -215,5 +271,9 @@ examples {
baseString.join("---") baseString.join("---")
derivedString.join("") derivedString.join("")
derivedString.join("\n") derivedString.join("\n")
alteredString.join("")
alteredString.join("\n")
computedIndexString.join("")
computedIndexString.join("\n")
} }
} }
@@ -4,6 +4,8 @@ facts {
true true
true true
true true
true
true
} }
["lastIndex"] { ["lastIndex"] {
true true
@@ -11,6 +13,8 @@ facts {
true true
true true
true true
true
true
} }
["isDistinct"] { ["isDistinct"] {
true true
@@ -18,6 +22,8 @@ facts {
true true
true true
true true
true
true
} }
["isDistinctBy()"] { ["isDistinctBy()"] {
true true
@@ -35,6 +41,12 @@ facts {
true true
true true
true true
true
true
true
true
true
true
} }
["getOrNull"] { ["getOrNull"] {
true true
@@ -49,21 +61,29 @@ facts {
true true
true true
true true
true
true
} }
["firstOrNull"] { ["firstOrNull"] {
true true
true true
true true
true
true
} }
["last"] { ["last"] {
true true
true true
true true
true
true
} }
["lastOrNull"] { ["lastOrNull"] {
true true
true true
true true
true
true
} }
["single"] { ["single"] {
true true
@@ -91,6 +111,7 @@ facts {
true true
true true
true true
true
} }
} }
examples { examples {
@@ -99,6 +120,9 @@ examples {
0 0
3 3
5 5
3
3
5
} }
["toList()"] { ["toList()"] {
List() List()
@@ -134,6 +158,20 @@ examples {
}, new { }, new {
name = "Elf Owl" name = "Elf Owl"
}) })
List(new {
name = "Wood Pigeon"
}, new {
name = "Barn Owl"
}, new {
name = "Parrot"
})
List(new {
name = "Wood Pigeon"
}, new {
name = "Barn Owl"
}, new {
name = "Parrot"
})
} }
["toSet()"] { ["toSet()"] {
Set() Set()
@@ -167,6 +205,20 @@ examples {
}, new { }, new {
name = "Elf Owl" name = "Elf Owl"
}) })
Set(new {
name = "Wood Pigeon"
}, new {
name = "Barn Owl"
}, new {
name = "Parrot"
})
Set(new {
name = "Wood Pigeon"
}, new {
name = "Barn Owl"
}, new {
name = "Parrot"
})
} }
["distinct"] { ["distinct"] {
new {} new {}
@@ -216,6 +268,28 @@ examples {
name = "Elf Owl" name = "Elf Owl"
} }
} }
new {
new {
name = "Wood Pigeon"
}
new {
name = "Barn Owl"
}
new {
name = "Parrot"
}
}
new {
new {
name = "Wood Pigeon"
}
new {
name = "Barn Owl"
}
new {
name = "Parrot"
}
}
} }
["distinctBy()"] { ["distinctBy()"] {
new {} new {}
@@ -265,6 +339,28 @@ examples {
name = "Elf Owl" name = "Elf Owl"
} }
} }
new {
new {
name = "Wood Pigeon"
}
new {
name = "Barn Owl"
}
new {
name = "Parrot"
}
}
new {
new {
name = "Wood Pigeon"
}
new {
name = "Barn Owl"
}
new {
name = "Parrot"
}
}
new {} new {}
new {} new {}
new { new {
@@ -312,6 +408,28 @@ examples {
name = "Elf Owl" name = "Elf Owl"
} }
} }
new {
new {
name = "Wood Pigeon"
}
new {
name = "Barn Owl"
}
new {
name = "Parrot"
}
}
new {
new {
name = "Wood Pigeon"
}
new {
name = "Barn Owl"
}
new {
name = "Parrot"
}
}
new {} new {}
new {} new {}
new { new {
@@ -329,6 +447,16 @@ examples {
name = "Pigeon" name = "Pigeon"
} }
} }
new {
new {
name = "Wood Pigeon"
}
}
new {
new {
name = "Wood Pigeon"
}
}
} }
["fold"] { ["fold"] {
List() List()
@@ -350,6 +478,20 @@ examples {
}, new { }, new {
name = "Elf Owl" name = "Elf Owl"
}) })
List(new {
name = "Wood Pigeon"
}, new {
name = "Barn Owl"
}, new {
name = "Parrot"
})
List(new {
name = "Wood Pigeon"
}, new {
name = "Barn Owl"
}, new {
name = "Parrot"
})
} }
["foldIndexed"] { ["foldIndexed"] {
List() List()
@@ -371,6 +513,20 @@ examples {
}), Pair(4, new { }), Pair(4, new {
name = "Elf Owl" name = "Elf Owl"
})) }))
List(Pair(0, new {
name = "Wood Pigeon"
}), Pair(1, new {
name = "Barn Owl"
}), Pair(2, new {
name = "Parrot"
}))
List(Pair(0, new {
name = "Wood Pigeon"
}), Pair(1, new {
name = "Barn Owl"
}), Pair(2, new {
name = "Parrot"
}))
} }
["join"] { ["join"] {
"" ""
@@ -386,5 +542,17 @@ examples {
Albatross Albatross
Elf Owl Elf Owl
""" """
"Wood PigeonBarn OwlParrot"
"""
Wood Pigeon
Barn Owl
Parrot
"""
"Wood PigeonBarn OwlParrot"
"""
Wood Pigeon
Barn Owl
Parrot
"""
} }
} }