@@ -62,6 +62,51 @@ fileprivate func firstNonNil<T>(
6262  return  try await  defaultValue ( ) 
6363} 
6464
65+ /// Actor that caches realpaths for `sourceFilesWithSameRealpath`.
66+ fileprivate  actor  SourceFilesWithSameRealpathInferrer  { 
67+   private  let  buildSystemManager :  BuildSystemManager 
68+   private  var  realpathCache :  [ DocumentURI :  DocumentURI ]  =  [ : ] 
69+ 
70+   init ( buildSystemManager:  BuildSystemManager )  { 
71+     self . buildSystemManager =  buildSystemManager
72+   } 
73+ 
74+   private  func  realpath( of uri:  DocumentURI )  ->  DocumentURI  { 
75+     if  let  cached =  realpathCache [ uri]  { 
76+       return  cached
77+     } 
78+     let  value  =  uri. symlinkTarget ??  uri
79+     realpathCache [ uri]  =  value
80+     return  value
81+   } 
82+ 
83+   /// Returns the URIs of all source files in the project that have the same realpath as a document in `documents` but
84+   /// are not in `documents`.
85+   ///
86+   /// This is useful in the following scenario: A project has target A containing A.swift an target B containing B.swift
87+   /// B.swift is a symlink to A.swift. When A.swift is modified, both the dependencies of A and B need to be marked as
88+   /// having an out-of-date preparation status, not just A.
89+   package func  sourceFilesWithSameRealpath( as documents:  [ DocumentURI ] )  async  ->  [ DocumentURI ]  { 
90+     let  realPaths  =  Set ( documents. map  {  realpath ( of:  $0)  } ) 
91+     return  await  orLog ( " Determining source files with same realpath " )  { 
92+       var  result :  [ DocumentURI ]  =  [ ] 
93+       let  filesAndDirectories  =  try await  buildSystemManager. sourceFiles ( includeNonBuildableFiles:  true ) 
94+       for  file  in  filesAndDirectories. keys { 
95+         if  realPaths. contains ( realpath ( of:  file) )  && !documents. contains ( file)  { 
96+           result. append ( file) 
97+         } 
98+       } 
99+       return  result
100+     }  ??  [ ] 
101+   } 
102+ 
103+   func  filesDidChange( _ events:  [ FileEvent ] )  { 
104+     for  event  in  events { 
105+       realpathCache [ event. uri]  =  nil 
106+     } 
107+   } 
108+ } 
109+ 
65110/// Represents the configuration and state of a project or combination of projects being worked on
66111/// together.
67112///
@@ -86,6 +131,8 @@ package final class Workspace: Sendable, BuildSystemManagerDelegate {
86131  /// The build system manager to use for documents in this workspace.
87132  package let  buildSystemManager :  BuildSystemManager 
88133
134+   private  let  sourceFilesWithSameRealpathInferrer :  SourceFilesWithSameRealpathInferrer 
135+ 
89136  let  options :  SourceKitLSPOptions 
90137
91138  /// The source code index, if available.
@@ -126,6 +173,9 @@ package final class Workspace: Sendable, BuildSystemManagerDelegate {
126173    self . options =  options
127174    self . _uncheckedIndex =  ThreadSafeBox ( initialValue:  uncheckedIndex) 
128175    self . buildSystemManager =  buildSystemManager
176+     self . sourceFilesWithSameRealpathInferrer =  SourceFilesWithSameRealpathInferrer ( 
177+       buildSystemManager:  buildSystemManager
178+     ) 
129179    if  options. backgroundIndexingOrDefault,  let  uncheckedIndex, 
130180      await  buildSystemManager. initializationData? . prepareProvider ??  false 
131181    { 
@@ -316,6 +366,17 @@ package final class Workspace: Sendable, BuildSystemManagerDelegate {
316366  } 
317367
318368  package  func filesDidChange( _ events:  [ FileEvent] )  async  { 
369+     // First clear any cached realpaths in `sourceFilesWithSameRealpathInferrer`.
370+     await  sourceFilesWithSameRealpathInferrer. filesDidChange ( events) 
371+ 
372+     // Now infer any edits for source files that share the same realpath as one of the modified files.
373+     var  events  =  events
374+     events += 
375+       await  sourceFilesWithSameRealpathInferrer
376+       . sourceFilesWithSameRealpath ( as:  events. filter  {  $0. type ==  . changed } . map ( \. uri) ) 
377+       . map  {  FileEvent ( uri:  $0,  type:  . changed)  } 
378+ 
379+     // Notify all clients about the reported and inferred edits.
319380    await  buildSystemManager. filesDidChange ( events) 
320381    await  syntacticTestIndex. filesDidChange ( events) 
321382    await  semanticIndexManager? . filesDidChange ( events) 
0 commit comments