@@ -423,6 +423,163 @@ private struct URLTests {
423423        try   FileManager . default. removeItem ( at:  URL ( filePath:  " \( tempDirectory. path) /tmp-dir " ) ) 
424424    } 
425425
426+     #if FOUNDATION_FRAMEWORK || canImport(Darwin) 
427+     @Test   func  fileSystemRepresentations( )  throws  { 
428+         let  base  =  " /base/ " 
429+         let  pathNFC  =  " /caf \u{E9} " 
430+         let  relativeNFC  =  " caf \u{E9} " 
431+         let  pathNFD  =  " /cafe \u{301} " 
432+         let  relativeNFD  =  " cafe \u{301} " 
433+ 
434+         let  resolvedPathNFC  =  " /base/caf \u{E9} " 
435+         let  resolvedPathNFD  =  " /base/cafe \u{301} " 
436+         let  baseExtensionNFD  =  " /base.cafe \u{301} " 
437+         let  doubleCafeNFD  =  " /cafe \u{301} /cafe \u{301} " 
438+ 
439+         // URL(filePath:) should always convert the input to decomposed (NFD) representation
440+         let  baseURL  =  URL ( filePath:  base) 
441+         let  urlNFC  =  URL ( filePath:  pathNFC) 
442+         let  urlRelativeNFC  =  URL ( filePath:  relativeNFC,  relativeTo:  baseURL) 
443+         let  urlNFD  =  URL ( filePath:  pathNFD) 
444+         let  urlRelativeNFD  =  URL ( filePath:  relativeNFD,  relativeTo:  baseURL) 
445+ 
446+         func  equalBytes( _ p1:  UnsafePointer < CChar > ,  _ p2:  UnsafePointer < CChar > )  ->  Bool  { 
447+             return  strcmp ( p1,  p2)  ==  0 
448+         } 
449+ 
450+         // Compare bytes to ensure we have the right representation
451+         #expect( equalBytes ( urlNFC. path,  pathNFD) ) 
452+         #expect( equalBytes ( urlNFD. path,  pathNFD) ) 
453+         #expect( urlNFC ==  urlNFD) 
454+ 
455+         #expect( equalBytes ( urlRelativeNFC. path,  resolvedPathNFD) ) 
456+         #expect( equalBytes ( urlRelativeNFD. path,  resolvedPathNFD) ) 
457+         #expect( urlRelativeNFC ==  urlRelativeNFD) 
458+ 
459+         // withUnsafeFileSystemRepresentation should return a pointer to decomposed bytes
460+         try   urlNFC. withUnsafeFileSystemRepresentation  {  fsRep in 
461+             let  fsRep  =  try   #require( fsRep) 
462+             #expect( equalBytes ( fsRep,  pathNFD) ) 
463+         } 
464+ 
465+         try   urlNFD. withUnsafeFileSystemRepresentation  {  fsRep in 
466+             let  fsRep  =  try   #require( fsRep) 
467+             #expect( equalBytes ( fsRep,  pathNFD) ) 
468+         } 
469+ 
470+         try   urlRelativeNFC. withUnsafeFileSystemRepresentation  {  fsRep in 
471+             let  fsRep  =  try   #require( fsRep) 
472+             #expect( equalBytes ( fsRep,  resolvedPathNFD) ) 
473+         } 
474+ 
475+         try   urlRelativeNFD. withUnsafeFileSystemRepresentation  {  fsRep in 
476+             let  fsRep  =  try   #require( fsRep) 
477+             #expect( equalBytes ( fsRep,  resolvedPathNFD) ) 
478+         } 
479+ 
480+         // ...unless we specifically .init(fileURLWithFileSystemRepresentation:) with absolute NFC
481+         let  urlNFCFSR  =  URL ( fileURLWithFileSystemRepresentation:  pathNFC,  isDirectory:  false ,  relativeTo:  nil ) 
482+         let  urlNFDFSR  =  URL ( fileURLWithFileSystemRepresentation:  pathNFD,  isDirectory:  false ,  relativeTo:  nil ) 
483+ 
484+         #expect( equalBytes ( urlNFCFSR. path,  pathNFC) ) 
485+         #expect( equalBytes ( urlNFDFSR. path,  pathNFD) ) 
486+         #expect( urlNFCFSR !=  urlNFDFSR) 
487+ 
488+         try   urlNFCFSR. withUnsafeFileSystemRepresentation  {  fsRep in 
489+             let  fsRep  =  try   #require( fsRep) 
490+             #expect( equalBytes ( fsRep,  pathNFC) ) 
491+         } 
492+ 
493+         try   urlNFDFSR. withUnsafeFileSystemRepresentation  {  fsRep in 
494+             let  fsRep  =  try   #require( fsRep) 
495+             #expect( equalBytes ( fsRep,  pathNFD) ) 
496+         } 
497+ 
498+         // If we .init(fileURLWithFileSystemRepresentation:) with a relative path,
499+         // we store the given representation but must convert when returning it
500+         let  urlRelativeNFCFSR  =  URL ( fileURLWithFileSystemRepresentation:  relativeNFC,  isDirectory:  false ,  relativeTo:  baseURL) 
501+         let  urlRelativeNFDFSR  =  URL ( fileURLWithFileSystemRepresentation:  relativeNFD,  isDirectory:  false ,  relativeTo:  baseURL) 
502+ 
503+         #expect( equalBytes ( urlRelativeNFCFSR. path,  resolvedPathNFC) ) 
504+         #expect( equalBytes ( urlRelativeNFDFSR. path,  resolvedPathNFD) ) 
505+         #expect( urlRelativeNFCFSR !=  urlRelativeNFDFSR) 
506+ 
507+         try   urlRelativeNFCFSR. withUnsafeFileSystemRepresentation  {  fsRep in 
508+             let  fsRep  =  try   #require( fsRep) 
509+             #expect( equalBytes ( fsRep,  resolvedPathNFD) ) 
510+         } 
511+ 
512+         try   urlRelativeNFDFSR. withUnsafeFileSystemRepresentation  {  fsRep in 
513+             let  fsRep  =  try   #require( fsRep) 
514+             #expect( equalBytes ( fsRep,  resolvedPathNFD) ) 
515+         } 
516+ 
517+         // Appending a path component should convert to decomposed for file URLs
518+         let  baseWithNFCComponent  =  baseURL. appending ( path:  relativeNFC) 
519+         #expect( equalBytes ( baseWithNFCComponent. path,  resolvedPathNFD) ) 
520+ 
521+         let  baseWithNFDComponent  =  baseURL. appending ( path:  relativeNFD) 
522+         #expect( equalBytes ( baseWithNFDComponent. path,  resolvedPathNFD) ) 
523+         #expect( baseWithNFCComponent ==  baseWithNFDComponent) 
524+ 
525+         let  urlNFCWithNFCComponent  =  urlNFC. appending ( path:  relativeNFC) 
526+         let  urlNFCWithNFDComponent  =  urlNFC. appending ( path:  relativeNFD) 
527+         let  urlNFDWithNFCComponent  =  urlNFD. appending ( path:  relativeNFC) 
528+         let  urlNFDWithNFDComponent  =  urlNFD. appending ( path:  relativeNFD) 
529+         #expect( equalBytes ( urlNFCWithNFCComponent. path,  doubleCafeNFD) ) 
530+         #expect( equalBytes ( urlNFCWithNFDComponent. path,  doubleCafeNFD) ) 
531+         #expect( equalBytes ( urlNFDWithNFCComponent. path,  doubleCafeNFD) ) 
532+         #expect( equalBytes ( urlNFDWithNFDComponent. path,  doubleCafeNFD) ) 
533+         #expect( urlNFCWithNFCComponent ==  urlNFCWithNFDComponent) 
534+         #expect( urlNFCWithNFCComponent ==  urlNFDWithNFCComponent) 
535+         #expect( urlNFCWithNFCComponent ==  urlNFDWithNFDComponent) 
536+ 
537+         // Appending an extension should convert to decomposed for file URLs
538+         let  baseWithNFCExtension  =  baseURL. appendingPathExtension ( relativeNFC) 
539+         #expect( equalBytes ( baseWithNFCExtension. path,  baseExtensionNFD) ) 
540+ 
541+         let  baseWithNFDExtension  =  baseURL. appendingPathExtension ( relativeNFD) 
542+         #expect( equalBytes ( baseWithNFDExtension. path,  baseExtensionNFD) ) 
543+         #expect( baseWithNFCExtension ==  baseWithNFDExtension) 
544+ 
545+         // None of these conversions apply for initializing or appending to non-file URLs
546+         let  httpBase  =  try   #require( URL ( string:  " https://example.com/ " ) ) 
547+         let  httpRelativeNFC  =  try   #require( URL ( string:  relativeNFC,  relativeTo:  httpBase) ) 
548+         let  httpRelativeNFD  =  try   #require( URL ( string:  relativeNFD,  relativeTo:  httpBase) ) 
549+         let  httpWithNFCComponent  =  httpBase. appending ( path:  relativeNFC) 
550+         let  httpWithNFDComponent  =  httpBase. appending ( path:  relativeNFD) 
551+ 
552+         #expect( equalBytes ( httpRelativeNFC. path,  pathNFC) ) 
553+         #expect( equalBytes ( httpRelativeNFD. path,  pathNFD) ) 
554+         #expect( httpRelativeNFC !=  httpRelativeNFD) 
555+ 
556+         #expect( equalBytes ( httpWithNFCComponent. path,  pathNFC) ) 
557+         #expect( equalBytes ( httpWithNFDComponent. path,  pathNFD) ) 
558+         #expect( httpWithNFCComponent !=  httpWithNFDComponent) 
559+ 
560+         // Except when we explicitly get the file system representation
561+         try   httpRelativeNFC. withUnsafeFileSystemRepresentation  {  fsRep in 
562+             let  fsRep  =  try   #require( fsRep) 
563+             #expect( equalBytes ( fsRep,  pathNFD) ) 
564+         } 
565+ 
566+         try   httpRelativeNFD. withUnsafeFileSystemRepresentation  {  fsRep in 
567+             let  fsRep  =  try   #require( fsRep) 
568+             #expect( equalBytes ( fsRep,  pathNFD) ) 
569+         } 
570+ 
571+         try   httpWithNFCComponent. withUnsafeFileSystemRepresentation  {  fsRep in 
572+             let  fsRep  =  try   #require( fsRep) 
573+             #expect( equalBytes ( fsRep,  pathNFD) ) 
574+         } 
575+ 
576+         try   httpWithNFDComponent. withUnsafeFileSystemRepresentation  {  fsRep in 
577+             let  fsRep  =  try   #require( fsRep) 
578+             #expect( equalBytes ( fsRep,  pathNFD) ) 
579+         } 
580+     } 
581+     #endif 
582+ 
426583    #if os(Windows) 
427584    @Test   func  windowsDriveLetterPath( )  throws  { 
428585        var  url  =  URL ( filePath:  #"C:\test\path"# ,  directoryHint:  . notDirectory) 
0 commit comments