From f4094b554d6bf15aef155c4e76632f0f0971bb7c Mon Sep 17 00:00:00 2001 From: Raul Santos Date: Thu, 13 Feb 2025 22:10:26 +0100 Subject: [PATCH] [.NET] Disallow `[ExportToolButton]` on members thay may store the Callable Ensures the user doesn't store the Callable so the .NET assembly can be reloaded. --- .../ExportDiagnosticsTests.cs | 9 ++ ...stics_GD0108_ScriptProperties.generated.cs | 12 +- ...stics_GD0109_ScriptProperties.generated.cs | 12 +- ...stics_GD0110_ScriptProperties.generated.cs | 14 +-- ...stics_GD0111_ScriptProperties.generated.cs | 116 ++++++++++++++++++ .../Sources/ExportDiagnostics_GD0108.cs | 2 +- .../Sources/ExportDiagnostics_GD0109.cs | 2 +- .../Sources/ExportDiagnostics_GD0110.cs | 2 +- .../Sources/ExportDiagnostics_GD0111.cs | 29 +++++ .../AnalyzerReleases.Unshipped.md | 1 + .../Godot.SourceGenerators/Common.cs | 10 ++ .../Godot.SourceGenerators/GodotClasses.cs | 1 + .../ScriptPropertiesGenerator.cs | 89 ++++++++++++++ .../Attributes/ExportToolButtonAttribute.cs | 2 +- 14 files changed, 263 insertions(+), 38 deletions(-) create mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0111_ScriptProperties.generated.cs create mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0111.cs diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ExportDiagnosticsTests.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ExportDiagnosticsTests.cs index fe511d67ef4..77ade9e5bbb 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ExportDiagnosticsTests.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ExportDiagnosticsTests.cs @@ -101,4 +101,13 @@ public class ExportDiagnosticsTests new string[] { "ExportDiagnostics_GD0110_ScriptProperties.generated.cs" } ); } + + [Fact] + public async void ExportToolButtonStoringCallable() + { + await CSharpSourceGeneratorVerifier.Verify( + new string[] { "ExportDiagnostics_GD0111.cs" }, + new string[] { "ExportDiagnostics_GD0111_ScriptProperties.generated.cs" } + ); + } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0108_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0108_ScriptProperties.generated.cs index 77114a7c934..219ddb668f3 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0108_ScriptProperties.generated.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0108_ScriptProperties.generated.cs @@ -9,22 +9,12 @@ partial class ExportDiagnostics_GD0108 /// public new class PropertyName : global::Godot.Node.PropertyName { /// - /// Cached name for the 'MyButton' field. + /// Cached name for the 'MyButton' property. /// public new static readonly global::Godot.StringName @MyButton = "MyButton"; } /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] - protected override bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value) - { - if (name == PropertyName.@MyButton) { - this.@MyButton = global::Godot.NativeInterop.VariantUtils.ConvertTo(value); - return true; - } - return base.SetGodotClassPropertyValue(name, value); - } - /// - [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value) { if (name == PropertyName.@MyButton) { diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0109_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0109_ScriptProperties.generated.cs index fc4547f2c1f..807ad01fd67 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0109_ScriptProperties.generated.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0109_ScriptProperties.generated.cs @@ -9,22 +9,12 @@ partial class ExportDiagnostics_GD0109 /// public new class PropertyName : global::Godot.Node.PropertyName { /// - /// Cached name for the 'MyButton' field. + /// Cached name for the 'MyButton' property. /// public new static readonly global::Godot.StringName @MyButton = "MyButton"; } /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] - protected override bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value) - { - if (name == PropertyName.@MyButton) { - this.@MyButton = global::Godot.NativeInterop.VariantUtils.ConvertTo(value); - return true; - } - return base.SetGodotClassPropertyValue(name, value); - } - /// - [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value) { if (name == PropertyName.@MyButton) { diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0110_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0110_ScriptProperties.generated.cs index d1aac175571..c6984c53d26 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0110_ScriptProperties.generated.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0110_ScriptProperties.generated.cs @@ -9,26 +9,16 @@ partial class ExportDiagnostics_GD0110 /// public new class PropertyName : global::Godot.Node.PropertyName { /// - /// Cached name for the 'MyButton' field. + /// Cached name for the 'MyButton' property. /// public new static readonly global::Godot.StringName @MyButton = "MyButton"; } /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] - protected override bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value) - { - if (name == PropertyName.@MyButton) { - this.@MyButton = global::Godot.NativeInterop.VariantUtils.ConvertTo(value); - return true; - } - return base.SetGodotClassPropertyValue(name, value); - } - /// - [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value) { if (name == PropertyName.@MyButton) { - value = global::Godot.NativeInterop.VariantUtils.CreateFrom(this.@MyButton); + value = global::Godot.NativeInterop.VariantUtils.CreateFrom(this.@MyButton); return true; } return base.GetGodotClassPropertyValue(name, out value); diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0111_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0111_ScriptProperties.generated.cs new file mode 100644 index 00000000000..abeb54cd621 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0111_ScriptProperties.generated.cs @@ -0,0 +1,116 @@ +using Godot; +using Godot.NativeInterop; + +partial class ExportDiagnostics_GD0111 +{ +#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword + /// + /// Cached StringNames for the properties and fields contained in this class, for fast lookup. + /// + public new class PropertyName : global::Godot.Node.PropertyName { + /// + /// Cached name for the 'MyButtonGet' property. + /// + public new static readonly global::Godot.StringName @MyButtonGet = "MyButtonGet"; + /// + /// Cached name for the 'MyButtonGetSet' property. + /// + public new static readonly global::Godot.StringName @MyButtonGetSet = "MyButtonGetSet"; + /// + /// Cached name for the 'MyButtonGetWithBackingField' property. + /// + public new static readonly global::Godot.StringName @MyButtonGetWithBackingField = "MyButtonGetWithBackingField"; + /// + /// Cached name for the 'MyButtonGetSetWithBackingField' property. + /// + public new static readonly global::Godot.StringName @MyButtonGetSetWithBackingField = "MyButtonGetSetWithBackingField"; + /// + /// Cached name for the 'MyButtonOkWithCallableCreationExpression' property. + /// + public new static readonly global::Godot.StringName @MyButtonOkWithCallableCreationExpression = "MyButtonOkWithCallableCreationExpression"; + /// + /// Cached name for the 'MyButtonOkWithImplicitCallableCreationExpression' property. + /// + public new static readonly global::Godot.StringName @MyButtonOkWithImplicitCallableCreationExpression = "MyButtonOkWithImplicitCallableCreationExpression"; + /// + /// Cached name for the 'MyButtonOkWithCallableFromExpression' property. + /// + public new static readonly global::Godot.StringName @MyButtonOkWithCallableFromExpression = "MyButtonOkWithCallableFromExpression"; + /// + /// Cached name for the '_backingField' field. + /// + public new static readonly global::Godot.StringName @_backingField = "_backingField"; + } + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + protected override bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value) + { + if (name == PropertyName.@MyButtonGetSet) { + this.@MyButtonGetSet = global::Godot.NativeInterop.VariantUtils.ConvertTo(value); + return true; + } + if (name == PropertyName.@MyButtonGetSetWithBackingField) { + this.@MyButtonGetSetWithBackingField = global::Godot.NativeInterop.VariantUtils.ConvertTo(value); + return true; + } + if (name == PropertyName.@_backingField) { + this.@_backingField = global::Godot.NativeInterop.VariantUtils.ConvertTo(value); + return true; + } + return base.SetGodotClassPropertyValue(name, value); + } + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value) + { + if (name == PropertyName.@MyButtonGet) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom(this.@MyButtonGet); + return true; + } + if (name == PropertyName.@MyButtonGetSet) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom(this.@MyButtonGetSet); + return true; + } + if (name == PropertyName.@MyButtonGetWithBackingField) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom(this.@MyButtonGetWithBackingField); + return true; + } + if (name == PropertyName.@MyButtonGetSetWithBackingField) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom(this.@MyButtonGetSetWithBackingField); + return true; + } + if (name == PropertyName.@MyButtonOkWithCallableCreationExpression) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom(this.@MyButtonOkWithCallableCreationExpression); + return true; + } + if (name == PropertyName.@MyButtonOkWithImplicitCallableCreationExpression) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom(this.@MyButtonOkWithImplicitCallableCreationExpression); + return true; + } + if (name == PropertyName.@MyButtonOkWithCallableFromExpression) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom(this.@MyButtonOkWithCallableFromExpression); + return true; + } + if (name == PropertyName.@_backingField) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom(this.@_backingField); + return true; + } + return base.GetGodotClassPropertyValue(name, out value); + } + /// + /// Get the property information for all the properties declared in this class. + /// This method is used by Godot to register the available properties in the editor. + /// Do not call this method. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal new static global::System.Collections.Generic.List GetGodotPropertyList() + { + var properties = new global::System.Collections.Generic.List(); + properties.Add(new(type: (global::Godot.Variant.Type)25, name: PropertyName.@_backingField, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); + properties.Add(new(type: (global::Godot.Variant.Type)25, name: PropertyName.@MyButtonOkWithCallableCreationExpression, hint: (global::Godot.PropertyHint)39, hintString: "", usage: (global::Godot.PropertyUsageFlags)4, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)25, name: PropertyName.@MyButtonOkWithImplicitCallableCreationExpression, hint: (global::Godot.PropertyHint)39, hintString: "", usage: (global::Godot.PropertyUsageFlags)4, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)25, name: PropertyName.@MyButtonOkWithCallableFromExpression, hint: (global::Godot.PropertyHint)39, hintString: "", usage: (global::Godot.PropertyUsageFlags)4, exported: true)); + return properties; + } +#pragma warning restore CS0109 +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0108.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0108.cs index 6fde2e5957b..101e9b2c97c 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0108.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0108.cs @@ -4,5 +4,5 @@ using Godot.Collections; public partial class ExportDiagnostics_GD0108 : Node { [ExportToolButton("")] - public Callable {|GD0108:MyButton|}; + public Callable {|GD0108:MyButton|} => new Callable(); } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0109.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0109.cs index 80a04cd6413..099d424b2ea 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0109.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0109.cs @@ -5,5 +5,5 @@ using Godot.Collections; public partial class ExportDiagnostics_GD0109 : Node { [Export, ExportToolButton("")] - public Callable {|GD0109:MyButton|}; + public Callable {|GD0109:MyButton|} => new Callable(); } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0110.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0110.cs index 586bf6c19ee..fb687090fe2 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0110.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0110.cs @@ -5,5 +5,5 @@ using Godot.Collections; public partial class ExportDiagnostics_GD0110 : Node { [ExportToolButton("")] - public string {|GD0110:MyButton|}; + public int {|GD0110:MyButton|} => new(); } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0111.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0111.cs new file mode 100644 index 00000000000..6c7c59b7c1d --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0111.cs @@ -0,0 +1,29 @@ +using Godot; +using Godot.Collections; + +[Tool] +public partial class ExportDiagnostics_GD0111 : Node +{ + private Callable _backingField; + + [ExportToolButton("")] + public Callable {|GD0111:MyButtonGet|} { get; } + + [ExportToolButton("")] + public Callable {|GD0111:MyButtonGetSet|} { get; set; } + + [ExportToolButton("")] + public Callable {|GD0111:MyButtonGetWithBackingField|} { get => _backingField; } + + [ExportToolButton("")] + public Callable {|GD0111:MyButtonGetSetWithBackingField|} { get => _backingField; set => _backingField = value; } + + [ExportToolButton("")] + public Callable MyButtonOkWithCallableCreationExpression => new Callable(this, ""); + + [ExportToolButton("")] + public Callable MyButtonOkWithImplicitCallableCreationExpression => new(this, ""); + + [ExportToolButton("")] + public Callable MyButtonOkWithCallableFromExpression => Callable.From(null); +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/AnalyzerReleases.Unshipped.md b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/AnalyzerReleases.Unshipped.md index 90c84911770..aeb9a359d4e 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/AnalyzerReleases.Unshipped.md +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/AnalyzerReleases.Unshipped.md @@ -6,3 +6,4 @@ GD0003 | Usage | Error | ScriptPathAttributeGenerator, [Documentation](ht GD0108 | Usage | Error | ScriptPropertiesGenerator, [Documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/diagnostics/GD0108.html) GD0109 | Usage | Error | ScriptPropertiesGenerator, [Documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/diagnostics/GD0109.html) GD0110 | Usage | Error | ScriptPropertiesGenerator, [Documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/diagnostics/GD0110.html) +GD0111 | Usage | Error | ScriptPropertiesGenerator, [Documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/diagnostics/GD0111.html) diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs index 8da0818e42e..83ca004f91c 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs @@ -137,6 +137,16 @@ namespace Godot.SourceGenerators "The exported tool button is not a Callable. The '[ExportToolButton]' attribute is only supported on members of type Callable.", helpLinkUri: string.Format(_helpLinkFormat, "GD0110")); + public static readonly DiagnosticDescriptor ExportToolButtonMustBeExpressionBodiedProperty = + new DiagnosticDescriptor(id: "GD0111", + title: "The exported tool button must be an expression-bodied property", + messageFormat: "The exported tool button '{0}' must be an expression-bodied property", + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + "The exported tool button must be an expression-bodied property. The '[ExportToolButton]' attribute is only supported on expression-bodied properties with a 'new Callable(...)' or 'Callable.From(...)' expression.", + helpLinkUri: string.Format(_helpLinkFormat, "GD0111")); + public static readonly DiagnosticDescriptor SignalDelegateMissingSuffixRule = new DiagnosticDescriptor(id: "GD0201", title: "The name of the delegate must end with 'EventHandler'", diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs index f6de09e25bf..3264ec2b548 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs @@ -4,6 +4,7 @@ namespace Godot.SourceGenerators { public const string GodotObject = "Godot.GodotObject"; public const string Node = "Godot.Node"; + public const string Callable = "Godot.Callable"; public const string AssemblyHasScriptsAttr = "Godot.AssemblyHasScriptsAttribute"; public const string ExportAttr = "Godot.ExportAttribute"; public const string ExportCategoryAttr = "Godot.ExportCategoryAttribute"; diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs index a8033914e77..4d69611a70e 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Linq; using System.Text; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; @@ -465,6 +466,94 @@ namespace Godot.SourceGenerators return null; } + if (exportToolButtonAttr != null && propertySymbol != null) + { + if (!PropertyIsExpressionBodiedAndReturnsNewCallable(context.Compilation, propertySymbol)) + { + context.ReportDiagnostic(Diagnostic.Create( + Common.ExportToolButtonMustBeExpressionBodiedProperty, + propertySymbol.Locations.FirstLocationWithSourceTreeOrDefault(), + propertySymbol.ToDisplayString() + )); + return null; + } + + static bool PropertyIsExpressionBodiedAndReturnsNewCallable(Compilation compilation, IPropertySymbol? propertySymbol) + { + if (propertySymbol == null) + { + return false; + } + + var propertyDeclarationSyntax = propertySymbol.DeclaringSyntaxReferences + .Select(r => r.GetSyntax() as PropertyDeclarationSyntax).FirstOrDefault(); + if (propertyDeclarationSyntax == null || propertyDeclarationSyntax.Initializer != null) + { + return false; + } + + if (propertyDeclarationSyntax.AccessorList != null) + { + var accessors = propertyDeclarationSyntax.AccessorList.Accessors; + foreach (var accessor in accessors) + { + if (!accessor.IsKind(SyntaxKind.GetAccessorDeclaration)) + { + // Only getters are allowed. + return false; + } + + if (!ExpressionBodyReturnsNewCallable(compilation, accessor.ExpressionBody)) + { + return false; + } + } + } + else if (!ExpressionBodyReturnsNewCallable(compilation, propertyDeclarationSyntax.ExpressionBody)) + { + return false; + } + + return true; + } + + static bool ExpressionBodyReturnsNewCallable(Compilation compilation, ArrowExpressionClauseSyntax? expressionSyntax) + { + if (expressionSyntax == null) + { + return false; + } + + var semanticModel = compilation.GetSemanticModel(expressionSyntax.SyntaxTree); + + switch (expressionSyntax.Expression) + { + case ImplicitObjectCreationExpressionSyntax creationExpression: + // We already validate that the property type must be 'Callable' + // so we can assume this constructor is valid. + return true; + + case ObjectCreationExpressionSyntax creationExpression: + var typeSymbol = semanticModel.GetSymbolInfo(creationExpression.Type).Symbol as ITypeSymbol; + if (typeSymbol != null) + { + return typeSymbol.FullQualifiedNameOmitGlobal() == GodotClasses.Callable; + } + break; + + case InvocationExpressionSyntax invocationExpression: + var methodSymbol = semanticModel.GetSymbolInfo(invocationExpression).Symbol as IMethodSymbol; + if (methodSymbol != null && methodSymbol.Name == "From") + { + return methodSymbol.ContainingType.FullQualifiedNameOmitGlobal() == GodotClasses.Callable; + } + break; + } + + return false; + } + } + var memberType = propertySymbol?.Type ?? fieldSymbol!.Type; var memberVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(marshalType)!.Value; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportToolButtonAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportToolButtonAttribute.cs index 87a9e628f90..be5ed17675a 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportToolButtonAttribute.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportToolButtonAttribute.cs @@ -7,7 +7,7 @@ namespace Godot /// /// Exports the annotated as a clickable button. /// - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + [AttributeUsage(AttributeTargets.Property)] public sealed class ExportToolButtonAttribute : Attribute { ///