Skip to content

Commit

Permalink
WithDocumentAttributes
Browse files Browse the repository at this point in the history
  • Loading branch information
tmat committed Jul 17, 2024
1 parent 1ee365c commit a2a16e1
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 124 deletions.
18 changes: 6 additions & 12 deletions src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -387,21 +387,15 @@ public DocumentState UpdateSourceCodeKind(SourceCodeKind kind)
return UpdateParseOptionsAndSourceCodeKind(ParseOptions.WithKind(kind), onlyPreprocessorDirectiveChange: false);
}

return UpdateAttributes(Attributes.With(sourceCodeKind: kind));
return WithAttributes(Attributes.With(sourceCodeKind: kind));
}

public DocumentState UpdateName(string name)
=> UpdateAttributes(Attributes.With(name: name));

public DocumentState UpdateFilePath(string? path)
=> UpdateAttributes(Attributes.With(filePath: path));

public DocumentState UpdateFolders(IReadOnlyList<string> folders)
=> UpdateAttributes(Attributes.With(folders: folders));

private DocumentState UpdateAttributes(DocumentInfo.DocumentAttributes newAttributes)
public DocumentState WithAttributes(DocumentInfo.DocumentAttributes newAttributes)
{
Debug.Assert(newAttributes != Attributes);
if (ReferenceEquals(newAttributes, Attributes))
{
return this;
}

ITreeAndVersionSource? newTreeSource;

Expand Down
16 changes: 13 additions & 3 deletions src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1150,7 +1150,10 @@ public Solution WithDocumentName(DocumentId documentId, string name)
throw new ArgumentNullException(nameof(name));
}

return WithCompilationState(_compilationState.WithDocumentName(documentId, name));
return WithCompilationState(_compilationState.WithDocumentAttributes(
documentId,
name,
static (attributes, value) => attributes.With(name: value)));
}

/// <summary>
Expand All @@ -1163,7 +1166,10 @@ public Solution WithDocumentFolders(DocumentId documentId, IEnumerable<string>?

var collection = PublicContract.ToBoxedImmutableArrayWithNonNullItems(folders, nameof(folders));

return WithCompilationState(_compilationState.WithDocumentFolders(documentId, collection));
return WithCompilationState(_compilationState.WithDocumentAttributes(
documentId,
collection,
static (attributes, value) => attributes.With(folders: value)));
}

/// <summary>
Expand All @@ -1172,7 +1178,11 @@ public Solution WithDocumentFolders(DocumentId documentId, IEnumerable<string>?
public Solution WithDocumentFilePath(DocumentId documentId, string? filePath)
{
CheckContainsDocument(documentId);
return WithCompilationState(_compilationState.WithDocumentFilePath(documentId, filePath));

return WithCompilationState(_compilationState.WithDocumentAttributes(
documentId,
filePath,
static (attributes, value) => attributes.With(filePath: value)));
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -688,72 +688,62 @@ public SolutionCompilationState WithProjectAnalyzerReferences(
forkTracker: true);
}

/// <inheritdoc cref="SolutionState.WithDocumentName"/>
public SolutionCompilationState WithDocumentName(
DocumentId documentId, string name)
{
return UpdateDocumentState(
this.SolutionState.WithDocumentName(documentId, name), documentId);
}

/// <inheritdoc cref="SolutionState.WithDocumentFolders"/>
public SolutionCompilationState WithDocumentFolders(
DocumentId documentId, IReadOnlyList<string> folders)
{
return UpdateDocumentState(
this.SolutionState.WithDocumentFolders(documentId, folders), documentId);
}

/// <inheritdoc cref="SolutionState.WithDocumentFilePath"/>
public SolutionCompilationState WithDocumentFilePath(
DocumentId documentId, string? filePath)
/// <inheritdoc cref="SolutionState.WithDocumentAttributes{TValue}"/>
public SolutionCompilationState WithDocumentAttributes<TArg>(
DocumentId documentId,
TArg arg,
Func<DocumentInfo.DocumentAttributes, TArg, DocumentInfo.DocumentAttributes> updateAttributes)
{
return UpdateDocumentState(
this.SolutionState.WithDocumentFilePath(documentId, filePath), documentId);
SolutionState.WithDocumentAttributes(documentId, arg, updateAttributes), documentId);
}

internal SolutionCompilationState WithDocumentTexts(ImmutableArray<(DocumentId documentId, SourceText text)> texts, PreservationMode mode)
=> WithDocumentContents(
texts, SourceTextIsUnchanged,
static (documentState, text, mode) => documentState.UpdateText(text, mode),
data: mode);
=> UpdateDocumentsInMultipleProjects(
texts,
arg: mode,
updateDocument: static (oldDocumentState, text, mode) =>
SourceTextIsUnchanged(oldDocumentState, text) ? oldDocumentState : oldDocumentState.UpdateText(text, mode));

private static bool SourceTextIsUnchanged(DocumentState oldDocument, SourceText text, PreservationMode mode)
private static bool SourceTextIsUnchanged(DocumentState oldDocument, SourceText text)
=> oldDocument.TryGetText(out var oldText) && text == oldText;

private SolutionCompilationState WithDocumentContents<TContent, TData>(
ImmutableArray<(DocumentId documentId, TContent content)> texts,
Func<DocumentState, TContent, TData, bool> isUnchanged,
Func<DocumentState, TContent, TData, DocumentState> updateContent,
TData data)
private SolutionCompilationState UpdateDocumentsInMultipleProjects<TDocumentData, TArg>(
ImmutableArray<(DocumentId documentId, TDocumentData documentData)> updates,
TArg arg,
Func<DocumentState, TDocumentData, TArg, DocumentState> updateDocument)
{
return UpdateDocumentsInMultipleProjects(
texts.GroupBy(d => d.documentId.ProjectId).Select(g =>
{
var projectId = g.Key;
var projectState = this.SolutionState.GetRequiredProjectState(projectId);
using var _ = ArrayBuilder<DocumentState>.GetInstance(out var newDocumentStates);
foreach (var (documentId, content) in g)
{
var documentState = projectState.DocumentStates.GetRequiredState(documentId);
if (isUnchanged(documentState, content, data))
continue;
newDocumentStates.Add(updateContent(documentState, content, data));
}
return (projectId, newDocumentStates.ToImmutableAndClear());
}),
static (projectState, newDocumentStates) =>
{
return new TranslationAction.TouchDocumentsAction(
projectState,
projectState.UpdateDocuments(newDocumentStates, contentChanged: true),
newDocumentStates);
});
updates
.GroupBy(static d => d.documentId.ProjectId)
.Select(g =>
{
var projectId = g.Key;
var projectState = SolutionState.GetRequiredProjectState(projectId);
using var _ = ArrayBuilder<DocumentState>.GetInstance(out var newDocumentStates);
foreach (var (documentId, documentData) in updates)
{
var documentState = projectState.DocumentStates.GetRequiredState(documentId);
var newDocumentState = updateDocument(documentState, documentData, arg);
if (ReferenceEquals(documentState, newDocumentState))
continue;
newDocumentStates.Add(newDocumentState);
}
return (projectId, newDocumentStates.ToImmutableAndClear());
}),
GetUpdateDocumentsTranslationAction);
}

private static TranslationAction GetUpdateDocumentsTranslationAction(ProjectState projectState, ImmutableArray<DocumentState> newDocumentStates)
=> new TranslationAction.TouchDocumentsAction(
projectState,
projectState.UpdateDocuments(newDocumentStates, contentChanged: true),
newDocumentStates);

public SolutionCompilationState WithDocumentState(
DocumentState documentState)
{
Expand Down Expand Up @@ -807,32 +797,25 @@ public SolutionCompilationState WithAnalyzerConfigDocumentText(
/// <inheritdoc cref="Solution.WithDocumentSyntaxRoots(ImmutableArray{ValueTuple{DocumentId, SyntaxNode}}, PreservationMode)"/>
public SolutionCompilationState WithDocumentSyntaxRoots(ImmutableArray<(DocumentId documentId, SyntaxNode root)> syntaxRoots, PreservationMode mode)
{
return WithDocumentContents(
syntaxRoots, IsUnchanged,
static (documentState, root, mode) => documentState.UpdateTree(root, mode),
data: mode);

static bool IsUnchanged(DocumentState oldDocument, SyntaxNode root, PreservationMode _)
{
return oldDocument.TryGetSyntaxTree(out var oldTree) &&
oldTree.TryGetRoot(out var oldRoot) &&
oldRoot == root;
}
return UpdateDocumentsInMultipleProjects(
syntaxRoots,
arg: mode,
static (oldDocumentState, root, mode) =>
oldDocumentState.TryGetSyntaxTree(out var oldTree) && oldTree.TryGetRoot(out var oldRoot) && oldRoot == root
? oldDocumentState
: oldDocumentState.UpdateTree(root, mode));
}

public SolutionCompilationState WithDocumentContentsFrom(
ImmutableArray<(DocumentId documentId, DocumentState documentState)> documentIdsAndStates, bool forceEvenIfTreesWouldDiffer)
{
return WithDocumentContents(
return UpdateDocumentsInMultipleProjects(
documentIdsAndStates,
isUnchanged: static (oldDocumentState, documentState, forceEvenIfTreesWouldDiffer) =>
{
return oldDocumentState.TextAndVersionSource == documentState.TextAndVersionSource
&& oldDocumentState.TreeSource == documentState.TreeSource;
},
arg: forceEvenIfTreesWouldDiffer,
static (oldDocumentState, documentState, forceEvenIfTreesWouldDiffer) =>
oldDocumentState.UpdateTextAndTreeContents(documentState.TextAndVersionSource, documentState.TreeSource, forceEvenIfTreesWouldDiffer),
data: forceEvenIfTreesWouldDiffer);
oldDocumentState.TextAndVersionSource == documentState.TextAndVersionSource && oldDocumentState.TreeSource == documentState.TreeSource
? oldDocumentState
: oldDocumentState.UpdateTextAndTreeContents(documentState.TextAndVersionSource, documentState.TreeSource, forceEvenIfTreesWouldDiffer));
}

/// <inheritdoc cref="SolutionState.WithDocumentSourceCodeKind"/>
Expand Down Expand Up @@ -1696,7 +1679,7 @@ public SolutionCompilationState WithDocumentText(IEnumerable<DocumentId?> docume
// the same text (for example, when GetOpenDocumentInCurrentContextWithChanges) is called.
//
// The use of GetRequiredState mirrors what happens in WithDocumentTexts
if (!SourceTextIsUnchanged(documentState, text, mode))
if (!SourceTextIsUnchanged(documentState, text))
changedDocuments.Add((documentId, text));
}
}
Expand Down
43 changes: 8 additions & 35 deletions src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -944,53 +944,26 @@ public SolutionState WithFallbackAnalyzerOptions(ImmutableDictionary<string, Str
}

/// <summary>
/// Creates a new solution instance with the document specified updated to have the specified name.
/// Creates a new solution instance with an attribute of the document updated, if its value has changed.
/// </summary>
public StateChange WithDocumentName(DocumentId documentId, string name)
public StateChange WithDocumentAttributes<TArg>(
DocumentId documentId,
TArg arg,
Func<DocumentInfo.DocumentAttributes, TArg, DocumentInfo.DocumentAttributes> updateAttributes)
{
var oldDocument = GetRequiredDocumentState(documentId);
if (oldDocument.Attributes.Name == name)
{
var oldProject = GetRequiredProjectState(documentId.ProjectId);
return new(this, oldProject, oldProject);
}

return UpdateDocumentState(oldDocument.UpdateName(name), contentChanged: false);
}

/// <summary>
/// Creates a new solution instance with the document specified updated to be contained in
/// the sequence of logical folders.
/// </summary>
public StateChange WithDocumentFolders(DocumentId documentId, IReadOnlyList<string> folders)
{
var oldDocument = GetRequiredDocumentState(documentId);
if (oldDocument.Folders.SequenceEqual(folders))
{
var oldProject = GetRequiredProjectState(documentId.ProjectId);
return new(this, oldProject, oldProject);
}

return UpdateDocumentState(oldDocument.UpdateFolders(folders), contentChanged: false);
}

/// <summary>
/// Creates a new solution instance with the document specified updated to have the specified file path.
/// </summary>
public StateChange WithDocumentFilePath(DocumentId documentId, string? filePath)
{
var oldDocument = GetRequiredDocumentState(documentId);
if (oldDocument.FilePath == filePath)
var newDocument = oldDocument.WithAttributes(updateAttributes(oldDocument.Attributes, arg));
if (ReferenceEquals(oldDocument, newDocument))
{
var oldProject = GetRequiredProjectState(documentId.ProjectId);
return new(this, oldProject, oldProject);
}

return UpdateDocumentState(oldDocument.UpdateFilePath(filePath), contentChanged: false);
return UpdateDocumentState(newDocument, contentChanged: false);
}

/// <summary>
/// Creates a new solution instance with the document specified updated to have the text
/// specified.
/// </summary>
public StateChange WithDocumentText(DocumentId documentId, SourceText text, PreservationMode mode = PreservationMode.PreserveValue)
Expand Down

0 comments on commit a2a16e1

Please sign in to comment.