stride3d/stride · error · NotImplementedException

Code upgrade for [ ] produced changes other than regular…

Error message

Code upgrade for [{projectChanges.NewProject.Name}] produced changes other than regular document text (added/removed docs, references, etc.); the runner persists changed .cs text only.

What it means

After applying Roslyn upgrades, EnsureNoUnsupportedChanges verifies that the workspace produced only regular document text changes. If a rule also added/removed documents or changed project/metadata/analyzer references, the runner cannot persist those, so it throws NotImplementedException to prevent silent loss of changes.

Solutions

  1. Rewrite the upgrade rule to only modify text of existing documents
  2. Manually apply the reference/document additions the rule wanted, then re-run only text edits
  3. Split the upgrade: automated text edits via the runner, manual steps for references/new files
  4. If you own the runner, extend persistence to handle non-text changes
Defensive patterns

Strategy: try-catch

Validate before calling

// dry-run rule against a sample project and inspect the resulting changes
var changes = upgradeRule.GetChanges(sampleProject);
if (changes.AddedDocuments.Any() || changes.AddedProjects.Any())
    logger.Warning("Rule produces non-text changes; runner will reject it.");

Type guard

static bool IsTextOnly(ProjectChanges changes) =>
    !changes.GetAddedDocuments().Any() && !changes.GetRemovedDocuments().Any() &&
    !changes.GetAddedProjectReferences().Any() && !changes.GetRemovedProjectReferences().Any();

Try / catch

try
{
    await RunUpgradesAsync(solution);
}
catch (NotImplementedException ex) when (ex.Message.Contains("produced changes other than regular document text"))
{
    logger.Error($"Upgrade rule too broad, apply its reference/new-file steps manually: {ex.Message}");
}

Prevention

When it happens

Trigger: RunAsync applies upgrades whose solution changes include added/removed documents, or added/removed project, metadata, or analyzer references on projectChanges.

Common situations: Custom upgrade rules that add new files or edit project references (e.g. adding NuGet/package references) instead of only rewriting existing .cs text; rules migrating APIs that require new dependency assemblies.

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/fbcab5b660526795. Report an issue: GitHub.

Appendix: source

Thrown at sources/engine/Stride.Assets/CodeUpgrade/RoslynCodeUpgradeRunner.cs:193

    // anything else would need workspace.TryApplyChanges() to reach the .csproj, so fail loud instead of dropping it.
    private static void EnsureNoUnsupportedChanges(ProjectChanges projectChanges)
    {
        if (projectChanges.GetAddedDocuments().Any()
            || projectChanges.GetRemovedDocuments().Any()
            || projectChanges.GetChangedAdditionalDocuments().Any()
            || projectChanges.GetAddedAdditionalDocuments().Any()
            || projectChanges.GetRemovedAdditionalDocuments().Any()
            || projectChanges.GetChangedAnalyzerConfigDocuments().Any()
            || projectChanges.GetAddedAnalyzerConfigDocuments().Any()
            || projectChanges.GetRemovedAnalyzerConfigDocuments().Any()
            || projectChanges.GetAddedMetadataReferences().Any()
            || projectChanges.GetRemovedMetadataReferences().Any()
            || projectChanges.GetAddedProjectReferences().Any()
            || projectChanges.GetRemovedProjectReferences().Any()
            || projectChanges.GetAddedAnalyzerReferences().Any()
            || projectChanges.GetRemovedAnalyzerReferences().Any())
        {
            throw new NotImplementedException(
                $"Code upgrade for [{projectChanges.NewProject.Name}] produced changes other than regular document " +
                "text (added/removed docs, references, etc.); the runner persists changed .cs text only.");
        }
    }

    private static bool PathsEqual(string? a, string? b)
    {
        if (a is null || b is null)
            return false;
        return string.Equals(Path.GetFullPath(a), Path.GetFullPath(b), StringComparison.OrdinalIgnoreCase);
    }
}

View on GitHub (pinned to 96fad776d2)