Skip to content

Paths: since #504, DirectoryPath("a").Parent / Name is "./a" and FilePath("x.txt").DirectoryPath / FileName is "./x.txt", because DirectoryPath's / operators use Path.Combine #539

Open

Description

@matt-edmondson

What's wrong

#504 changed the parent of a one-segment relative path, and the directory of a bare file name, from "" to ".". It also taught PooledStringBuilder.CombinePaths to drop a leading ".", so RelativeDirectoryPath's / operators still give back "a" and not "./a".

The primitive DirectoryPath type did not get that change. Its four / operators (Primitives/DirectoryPath.cs:88, :104, :120, :136) call Path.Combine(left.WeakString, right.WeakString), and Path.Combine(".", "a") is "./a". Before #504, the left side was "", and Path.Combine("", "a") returned "a", so this is a regression.

SemanticPath.MakeCanonical does not strip a leading ./, and path equality compares the stored string, so the rebuilt path is not equal to the original.

Reproduction

net10.0 build of main at ff736ff, Linux:

var dir = DirectoryPath.Create<DirectoryPath>("a");
dir.Parent / dir.Name;                 // "./a"  (equal to dir: False)

var file = FilePath.Create<FilePath>("x.txt");
file.DirectoryPath / file.FileName;    // "./x.txt" (equal to file: False)

// The Relative* types are fine, because they go through CombinePaths:
var rdir = RelativeDirectoryPath.Create<RelativeDirectoryPath>("a");
rdir.Parent / rdir.Name;               // "a" (equal: True)

DirectoryPath.Name documents that "Parent / Name is this directory again". DirectoryNameRebuildTests.DirectoryPath_ParentSlashName_IsTheSameDirectory tests only an absolute path, so it did not catch this. Code that rebuilds or re-parents a relative path, or uses the result as a dictionary key or in an equality check, now gets a different, unequal value.

This is not the ./..-suffix case in #503. Here the input is a plain single-segment path.

Suggested fix

  • Route DirectoryPath's / operators through PooledStringBuilder.CombinePaths, as the Relative* and Absolute* directory types already do. Alternatively, drop a leading "." left operand before calling Path.Combine.
  • Add tests:
    • DirectoryPath("a").Parent / Name == DirectoryPath("a")
    • FilePath("x.txt").DirectoryPath / FileName == FilePath("x.txt")
    • DirectoryPath(".") / RelativeFilePath("x.txt") is "x.txt"

Activity

  1. matt-edmondson commented on Nov 4, 2026

    @matt-edmondson
    ContributorAuthor

    Triage

    Category: Bug (regression from #504)
    Priority: High
    Area / suggested assignment: Semantics.Paths maintainer. The fix is in Primitives/DirectoryPath.cs.

    Why High: the wrong result is silent. On main, DirectoryPath("a").Parent / Name returns "./a", and FilePath("x.txt").DirectoryPath / FileName returns "./x.txt". Neither is equal to the original path. That breaks the round trip DirectoryPath.Name documents. It also breaks equality and dictionary-key lookups for any caller that rebuilds or re-parents a single-segment relative path, and nothing throws. It is a regression in a shipped library, while the fix is small.

    Verified: all four / operators in Semantics.Paths/Primitives/DirectoryPath.cs (lines 88, 104, 120, 136) still call Path.Combine(left.WeakString, right.WeakString) rather than PooledStringBuilder.CombinePaths, as the issue says.

    Duplicates / related:

    Next step: route DirectoryPath's / operators through PooledStringBuilder.CombinePaths, the way the Relative* and Absolute* types do. Then add the three regression tests listed in the issue. The existing DirectoryPath_ParentSlashName_IsTheSameDirectory covers only an absolute path, so it needs a relative case as well.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions