Skip to content

Commit

Permalink
Merge pull request godotengine#88371 from raulsntos/dotnet/generics-ii
Browse files Browse the repository at this point in the history
C#: Various fixes to generic scripts
  • Loading branch information
akien-mga committed Feb 19, 2024
2 parents 0164e49 + fe280ef commit 5936844
Show file tree
Hide file tree
Showing 12 changed files with 126 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,6 @@

namespace Godot.SourceGenerators.Sample
{
partial class Generic<T> : GodotObject
{
private int _field;
}

// Generic again but different generic parameters
partial class Generic<T, R> : GodotObject
{
private int _field;
}

// Generic again but without generic parameters
partial class Generic : GodotObject
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#pragma warning disable CS0169

namespace Godot.SourceGenerators.Sample
{
partial class Generic1T<T> : GodotObject
{
private int _field;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#pragma warning disable CS0169

namespace Godot.SourceGenerators.Sample
{
// Generic again but different generic parameters
partial class Generic2T<T, R> : GodotObject
{
private int _field;
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
Expand Down Expand Up @@ -47,9 +48,33 @@ public async void Generic()
{
var verifier = CSharpSourceGeneratorVerifier<ScriptPathAttributeGenerator>.MakeVerifier(
new string[] { "Generic.cs" },
new string[] { "Generic_ScriptPath.generated.cs" }
new string[] { "Generic(Of T)_ScriptPath.generated.cs" }
);
verifier.TestState.GeneratedSources.Add(MakeAssemblyScriptTypesGeneratedSource(new string[] { "global::Generic" }));
verifier.TestState.GeneratedSources.Add(MakeAssemblyScriptTypesGeneratedSource(new string[] { "global::Generic<>" }));
await verifier.RunAsync();
}

[Fact]
public async void GenericMultipleClassesSameName()
{
var verifier = CSharpSourceGeneratorVerifier<ScriptPathAttributeGenerator>.MakeVerifier(
Array.Empty<string>(),
new string[] { "Generic(Of T)_ScriptPath.generated.cs" }
);
verifier.TestState.Sources.Add(("Generic.cs", File.ReadAllText(Path.Combine(Constants.SourceFolderPath, "Generic.GD0003.cs"))));
verifier.TestState.GeneratedSources.Add(MakeAssemblyScriptTypesGeneratedSource(new string[] { "global::Generic<>", "global::Generic<,>", "global::Generic" }));
await verifier.RunAsync();
}

[Fact]
public async void NamespaceMultipleClassesSameName()
{
var verifier = CSharpSourceGeneratorVerifier<ScriptPathAttributeGenerator>.MakeVerifier(
Array.Empty<string>(),
new string[] { "NamespaceA.SameName_ScriptPath.generated.cs" }
);
verifier.TestState.Sources.Add(("SameName.cs", File.ReadAllText(Path.Combine(Constants.SourceFolderPath, "SameName.GD0003.cs"))));
verifier.TestState.GeneratedSources.Add(MakeAssemblyScriptTypesGeneratedSource(new string[] { "global::NamespaceA.SameName", "global::NamespaceB.SameName" }));
await verifier.RunAsync();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using Godot;
[ScriptPathAttribute("res://Generic.cs")]
partial class Generic
partial class Generic<T>
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Godot;
namespace NamespaceA {

[ScriptPathAttribute("res://SameName.cs")]
partial class SameName
{
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Godot;

partial class Generic<T> : GodotObject
{
private int _field;
}

// Generic again but different generic parameters
partial class {|GD0003:Generic|}<T, R> : GodotObject
{
private int _field;
}

// Generic again but without generic parameters
partial class {|GD0003:Generic|} : GodotObject
{
private int _field;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,3 @@ partial class Generic<T> : GodotObject
{
private int _field;
}

// Generic again but different generic parameters
partial class Generic<T, R> : GodotObject
{
private int _field;
}

// Generic again but without generic parameters
partial class Generic : GodotObject
{
private int _field;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Godot;

namespace NamespaceA
{
partial class SameName : GodotObject
{
private int _field;
}
}

// SameName again but different namespace
namespace NamespaceB
{
partial class {|GD0003:SameName|} : GodotObject
{
private int _field;
}
}
10 changes: 10 additions & 0 deletions modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@ TypeDeclarationSyntax outerTypeDeclSyntax
outerTypeDeclSyntax.SyntaxTree.FilePath));
}

public static readonly DiagnosticDescriptor MultipleClassesInGodotScriptRule =
new DiagnosticDescriptor(id: "GD0003",
title: "Found multiple classes with the same name in the same script file",
messageFormat: "Found multiple classes with the name '{0}' in the same script file",
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
"Found multiple classes with the same name in the same script file. A script file must only contain one class with a name that matches the file name.",
helpLinkUri: string.Format(_helpLinkFormat, "GD0003"));

public static readonly DiagnosticDescriptor ExportedMemberIsStaticRule =
new DiagnosticDescriptor(id: "GD0101",
title: "The exported member is static",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,10 @@ public void Execute(GeneratorExecutionContext context)
.GroupBy<(ClassDeclarationSyntax cds, INamedTypeSymbol symbol), INamedTypeSymbol>(x => x.symbol, SymbolEqualityComparer.Default)
.ToDictionary<IGrouping<INamedTypeSymbol, (ClassDeclarationSyntax cds, INamedTypeSymbol symbol)>, INamedTypeSymbol, IEnumerable<ClassDeclarationSyntax>>(g => g.Key, g => g.Select(x => x.cds), SymbolEqualityComparer.Default);

var usedPaths = new HashSet<string>();
foreach (var godotClass in godotClasses)
{
VisitGodotScriptClass(context, godotProjectDir,
VisitGodotScriptClass(context, godotProjectDir, usedPaths,
symbol: godotClass.Key,
classDeclarations: godotClass.Value);
}
Expand All @@ -74,6 +75,7 @@ public void Execute(GeneratorExecutionContext context)
private static void VisitGodotScriptClass(
GeneratorExecutionContext context,
string godotProjectDir,
HashSet<string> usedPaths,
INamedTypeSymbol symbol,
IEnumerable<ClassDeclarationSyntax> classDeclarations
)
Expand All @@ -93,8 +95,19 @@ IEnumerable<ClassDeclarationSyntax> classDeclarations
if (attributes.Length != 0)
attributes.Append("\n");

string scriptPath = RelativeToDir(cds.SyntaxTree.FilePath, godotProjectDir);
if (!usedPaths.Add(scriptPath))
{
context.ReportDiagnostic(Diagnostic.Create(
Common.MultipleClassesInGodotScriptRule,
cds.Identifier.GetLocation(),
symbol.Name
));
return;
}

attributes.Append(@"[ScriptPathAttribute(""res://");
attributes.Append(RelativeToDir(cds.SyntaxTree.FilePath, godotProjectDir));
attributes.Append(scriptPath);
attributes.Append(@""")]");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ public override void _ParseBegin(GodotObject godotObject)
continue;

string scriptPath = script.ResourcePath;

if (string.IsNullOrEmpty(scriptPath))
{
// Generic types used empty paths in older versions of Godot
// so we assume your project is out of sync.
AddCustomControl(new InspectorOutOfSyncWarning());
break;
}

if (scriptPath.StartsWith("csharp://"))
{
// This is a virtual path used by generic types, extract the real path.
Expand Down

0 comments on commit 5936844

Please sign in to comment.