Skip to content

Commit 9567678

Browse files
authored
Merge pull request scala#11662 from griggt/fix-#11644
Fix scala#11644: Fix broken dotty.tools.io.Path#parent
2 parents e64d3d1 + df1546a commit 9567678

File tree

5 files changed

+85
-4
lines changed

5 files changed

+85
-4
lines changed

compiler/src/dotty/tools/io/Path.scala

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,33 @@ class Path private[io] (val jpath: JPath) {
126126
/**
127127
* @return The path of the parent directory, or root if path is already root
128128
*/
129-
def parent: Directory = jpath.normalize.getParent match {
130-
case null => Directory(jpath)
131-
case parent => Directory(parent)
129+
def parent: Directory = {
130+
// We don't call JPath#normalize here because it may result in resolving
131+
// to a different path than intended, such as when the given path contains
132+
// a `..` component and the preceding name is a symbolic link.
133+
// https://docs.oracle.com/javase/8/docs/api/java/nio/file/Path.html#normalize--
134+
//
135+
// Paths ending with `..` or `.` are handled specially here as
136+
// JPath#getParent wants to simply strip away that last element.
137+
// For the `..` case, we should take care not to fall into the above trap
138+
// as the preceding name may be a symbolic link. This leaves little choice
139+
// (since we don't want to access the file system) but to return the given
140+
// path with `..` appended. By contrast, a `.` as the final path element
141+
// is always redundant and should be removed before computing the parent.
142+
if path.isEmpty then
143+
Directory("..")
144+
else if jpath.endsWith("..") then
145+
(this / "..").toDirectory
146+
else if jpath.endsWith(".") then
147+
jpath.getParent match // strip the trailing `.` element
148+
case null => Directory("..")
149+
case p => new Path(p).parent
150+
else jpath.getParent match
151+
case null =>
152+
if isAbsolute then toDirectory // it should be a root
153+
else Directory(".")
154+
case x =>
155+
Directory(x)
132156
}
133157
def parents: List[Directory] = {
134158
val p = parent
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package dotty.tools.io
2+
3+
import org.junit.Test
4+
5+
class PathTest {
6+
// Ref https://github.com/lampepfl/dotty/issues/11644#issuecomment-792457275
7+
@Test def parent(): Unit = {
8+
testParent(Path(""), Directory(".."))
9+
testParent(Path("."), Directory(".."))
10+
testParent(Path(".") / ".", Directory(".."))
11+
testParent(Path(".."), Directory("..") / "..")
12+
testParent(Path("..") / ".", Directory("..") / "..")
13+
testParent(Path("..") / "..", Directory("..") / ".." / "..")
14+
testParent(Path(".") / "..",
15+
Directory("..") / "..",
16+
Directory(".") / ".." / "..")
17+
18+
testParent(Path("foo") / ".", Directory("."))
19+
testParent(Path("foo") / ".." / "bar", Directory("foo") / "..")
20+
testParent(Path("foo") / ".." / "." / "bar", Directory("foo") / ".." / ".")
21+
22+
testParent(Path("foo.txt"), Directory("."))
23+
testParent(Path(".") / "foo.txt", Directory("."))
24+
testParent(Path(".") / "baz" / "bar" / "foo.txt",
25+
Directory(".") / "baz" / "bar",
26+
Directory("baz") / "bar")
27+
28+
for (root <- Path.roots) {
29+
testParent(root, root)
30+
testParent(root / ".", root)
31+
testParent(root / "..", root / ".." / "..")
32+
testParent(root / "foo" / ".", root)
33+
testParent(root / "foo.txt", root)
34+
testParent(root / "baz" / "bar" / "foo.txt", root / "baz" / "bar")
35+
testParent(root / "foo" / "bar" / "..", root / "foo" / "bar" / ".." / "..")
36+
}
37+
}
38+
39+
/** The parent of a path may have multiple valid non-canonical representations.
40+
* Here we test that the parent of the specified path is among a curated list
41+
* of representations we consider to be valid.
42+
*/
43+
private def testParent(path: Path, expected: Path*): Unit = {
44+
val actual = path.parent
45+
val some = if (expected.length > 1) " one of" else ""
46+
assert(expected.contains(actual),
47+
s"""expected$some: ${expected.mkString("<",">, <",">")} but was: <$actual>""")
48+
}
49+
}

project/scripts/bootstrapCmdTests

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ clear_out "$OUT"
3737
# check that `scalac -from-tasty` compiles and `scala` runs it
3838
echo "testing ./bin/scalac -from-tasty and scala -classpath"
3939
clear_out "$OUT1"
40+
./bin/scalac "$SOURCE" -d "$OUT"
4041
./bin/scalac -from-tasty -d "$OUT1" "$OUT/$TASTY"
4142
./bin/scala -classpath "$OUT1" "$MAIN" > "$tmp"
4243
test "$EXPECTED_OUTPUT" = "$(cat "$tmp")"
@@ -57,3 +58,9 @@ grep -qe "def main(args: scala.Array\[scala.Predef.String\]): scala.Unit =" "$tm
5758
echo "testing ./bin/scaladoc"
5859
clear_out "$OUT1"
5960
./bin/scaladoc -project Hello -d "$OUT1" "$OUT/out.jar"
61+
62+
# check compilation when the class/tasty files already exist in the current directory
63+
echo "testing i11644"
64+
cwd=$(pwd)
65+
clear_out "$OUT"
66+
(cd "$OUT" && "$cwd/bin/scalac" "$cwd/tests/pos/i11644.scala" && "$cwd/bin/scalac" "$cwd/tests/pos/i11644.scala")

project/scripts/cmdTestsCommon.inc.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ tmp=$(mktemp)
1515
clear_out()
1616
{
1717
local out="$1"
18-
rm -rf "$out/*"
18+
rm -rf "$out"/*
1919
}

tests/pos/i11644.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@main def hello = println("hello, world")

0 commit comments

Comments
 (0)