diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertyDefValGeneratorTests.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertyDefValGeneratorTests.cs index 7711bce1c79..4abff5812b9 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertyDefValGeneratorTests.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertyDefValGeneratorTests.cs @@ -22,6 +22,13 @@ public class ScriptPropertyDefValGeneratorTests ); } + [Fact] + public async void ExportedProperties2() + { + await CSharpSourceGeneratorVerifier.Verify( + "ExportedProperties2.cs", "ExportedProperties2_ScriptPropertyDefVal.generated.cs"); + } + [Fact] public async void ExportedComplexStrings() { diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties2_ScriptPropertyDefVal.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties2_ScriptPropertyDefVal.generated.cs new file mode 100644 index 00000000000..9cdb490b4f7 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties2_ScriptPropertyDefVal.generated.cs @@ -0,0 +1,25 @@ +partial class ExportedProperties2 +{ +#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword +#if TOOLS + /// + /// Get the default values for all properties declared in this class. + /// This method is used by Godot to determine the value that will be + /// used by the inspector when resetting properties. + /// Do not call this method. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal new static global::System.Collections.Generic.Dictionary GetGodotPropertyDefaultValues() + { + var values = new global::System.Collections.Generic.Dictionary(3); + int __Health_default_value = default; + values.Add(PropertyName.@Health, global::Godot.Variant.From(__Health_default_value)); + global::Godot.Resource __SubResource_default_value = default; + values.Add(PropertyName.@SubResource, global::Godot.Variant.From(__SubResource_default_value)); + string[] __Strings_default_value = default; + values.Add(PropertyName.@Strings, global::Godot.Variant.From(__Strings_default_value)); + return values; + } +#endif // TOOLS +#pragma warning restore CS0109 +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportedProperties2.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportedProperties2.cs new file mode 100644 index 00000000000..cd03d6432bb --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportedProperties2.cs @@ -0,0 +1,13 @@ +using Godot; +using System; + +[GlobalClass] +public partial class ExportedProperties2(int health, Resource subResource, string[] strings) : Resource +{ + [Export] + public int Health { get; set; } = health; + [Export] + public Resource SubResource { get; set; } = subResource; + [Export] + public string[] Strings { get; set; } = strings ?? System.Array.Empty(); +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs index 626f51ecae1..fff32a10800 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs @@ -215,7 +215,11 @@ namespace Godot.SourceGenerators if (propertyDeclarationSyntax.Initializer != null) { var sm = context.Compilation.GetSemanticModel(propertyDeclarationSyntax.Initializer.SyntaxTree); - value = propertyDeclarationSyntax.Initializer.Value.FullQualifiedSyntax(sm); + var initializerValue = propertyDeclarationSyntax.Initializer.Value; + if (!IsStaticallyResolvable(initializerValue, sm)) + value = "default"; + else + value = propertyDeclarationSyntax.Initializer.Value.FullQualifiedSyntax(sm); } else { @@ -418,6 +422,106 @@ namespace Godot.SourceGenerators context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8)); } + private static bool IsStaticallyResolvable(ExpressionSyntax expression, SemanticModel semanticModel) + { + // Handle literals (e.g., `10`, `"string"`, `true`, etc.) + if (expression is LiteralExpressionSyntax) + { + return true; + } + + // Handle identifiers (e.g., variable names) + if (expression is IdentifierNameSyntax identifier) + { + var symbolInfo = semanticModel.GetSymbolInfo(identifier).Symbol; + + // Ensure it's a static member + return symbolInfo is { IsStatic: true }; + } + + // Handle member access (e.g., `MyClass.StaticValue`) + if (expression is MemberAccessExpressionSyntax memberAccess) + { + var symbolInfo = semanticModel.GetSymbolInfo(memberAccess).Symbol; + + // Ensure it's referring to a static member + return symbolInfo is { IsStatic: true }; + } + + // Handle object creation expressions (e.g., `new Vector2(1.0f, 2.0f)`) + if (expression is ObjectCreationExpressionSyntax objectCreation) + { + // Recursively ensure all its arguments are self-contained + if (objectCreation.ArgumentList == null) + { + return true; + } + foreach (var argument in objectCreation.ArgumentList.Arguments) + { + if (!IsStaticallyResolvable(argument.Expression, semanticModel)) + { + return false; + } + } + + return true; + } + + if (expression is ImplicitObjectCreationExpressionSyntax) + { + return true; + } + + if (expression is InvocationExpressionSyntax invocationExpression) + { + // Resolve the method being invoked + var symbolInfo = semanticModel.GetSymbolInfo(invocationExpression).Symbol; + + if (symbolInfo is IMethodSymbol methodSymbol) + { + // Ensure the method is static + if (methodSymbol.IsStatic) + { + return true; + } + } + } + + if (expression is InterpolatedStringExpressionSyntax interpolatedString) + { + foreach (var content in interpolatedString.Contents) + { + if (content is not InterpolationSyntax interpolation) + { + continue; + } + // Analyze the expression inside `${...}` + var interpolatedExpression = interpolation.Expression; + + if (!IsStaticallyResolvable(interpolatedExpression, semanticModel)) + { + return false; + } + } + return true; + } + + if (expression is InitializerExpressionSyntax initializerExpressionSyntax) + { + foreach (var content in initializerExpressionSyntax.Expressions) + { + if (!IsStaticallyResolvable(content, semanticModel)) + { + return false; + } + } + return true; + } + + // Handle other expressions conservatively (e.g., method calls, instance references, etc.) + return false; + } + private static bool MemberHasNodeType(ITypeSymbol memberType, MarshalType marshalType) { if (marshalType == MarshalType.GodotObjectOrDerived)