diff --git a/src/Authoring/WinRT.SourceGenerator/AotOptimizer.cs b/src/Authoring/WinRT.SourceGenerator/AotOptimizer.cs index 0b473f14d..3f40f9ada 100644 --- a/src/Authoring/WinRT.SourceGenerator/AotOptimizer.cs +++ b/src/Authoring/WinRT.SourceGenerator/AotOptimizer.cs @@ -122,7 +122,7 @@ private static bool NeedVtableAttribute(SyntaxNode node) { return node is ClassDeclarationSyntax declaration && !declaration.Modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword) || m.IsKind(SyntaxKind.AbstractKeyword)) && - declaration.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword)) && + GeneratorHelper.IsPartial(declaration) && !GeneratorHelper.IsWinRTType(declaration); // Making sure it isn't an RCW we are projecting. } @@ -140,7 +140,7 @@ private static bool NeedCustomPropertyImplementation(SyntaxNode node) { return node is ClassDeclarationSyntax declaration && !declaration.Modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword) || m.IsKind(SyntaxKind.AbstractKeyword)) && - declaration.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword)) && + GeneratorHelper.IsPartial(declaration) && GeneratorHelper.HasBindableCustomPropertyAttribute(declaration); } diff --git a/src/Authoring/WinRT.SourceGenerator/CsWinRTDiagnosticStrings.Designer.cs b/src/Authoring/WinRT.SourceGenerator/CsWinRTDiagnosticStrings.Designer.cs index 352361083..ddfce497f 100644 --- a/src/Authoring/WinRT.SourceGenerator/CsWinRTDiagnosticStrings.Designer.cs +++ b/src/Authoring/WinRT.SourceGenerator/CsWinRTDiagnosticStrings.Designer.cs @@ -177,6 +177,15 @@ internal static string ArrayParamNotMarked_Text2 { } } + /// + /// Looks up a localized string similar to Class '{0}' has attribute GeneratedBindableCustomProperty but it or a parent type isn't marked partial. Type and any parent types should be marked partial to allow source generation for trimming and AOT compatibility.. + /// + internal static string BindableCustomPropertyClassNotMarkedPartial_Text { + get { + return ResourceManager.GetString("BindableCustomPropertyClassNotMarkedPartial_Text", resourceCulture); + } + } + /// /// Looks up a localized string similar to Class Constructor Rule. /// @@ -223,7 +232,7 @@ internal static string ClassNotMarkedPartial_Brief { } /// - /// Looks up a localized string similar to Class '{0}' implements WinRT interfaces but isn't marked partial. Type should be marked partial for trimming and AOT compatibility if passed across the WinRT ABI.. + /// Looks up a localized string similar to Class '{0}' implements WinRT interfaces but it or a parent type isn't marked partial. Type and any parent types should be marked partial for trimming and AOT compatibility if passed across the WinRT ABI.. /// internal static string ClassNotMarkedPartial_Text { get { diff --git a/src/Authoring/WinRT.SourceGenerator/CsWinRTDiagnosticStrings.resx b/src/Authoring/WinRT.SourceGenerator/CsWinRTDiagnosticStrings.resx index 8702c22f9..6f0ffe13c 100644 --- a/src/Authoring/WinRT.SourceGenerator/CsWinRTDiagnosticStrings.resx +++ b/src/Authoring/WinRT.SourceGenerator/CsWinRTDiagnosticStrings.resx @@ -113,8 +113,11 @@ Class is not marked partial - Class '{0}' implements WinRT interfaces but isn't marked partial. Type should be marked partial for trimming and AOT compatibility if passed across the WinRT ABI. + Class '{0}' implements WinRT interfaces but it or a parent type isn't marked partial. Type and any parent types should be marked partial for trimming and AOT compatibility if passed across the WinRT ABI. + + Class '{0}' has attribute GeneratedBindableCustomProperty but it or a parent type isn't marked partial. Type and any parent types should be marked partial to allow source generation for trimming and AOT compatibility. + Namespace is disjoint from main (winmd) namespace diff --git a/src/Authoring/WinRT.SourceGenerator/Helper.cs b/src/Authoring/WinRT.SourceGenerator/Helper.cs index 45faf0b15..459de7cab 100644 --- a/src/Authoring/WinRT.SourceGenerator/Helper.cs +++ b/src/Authoring/WinRT.SourceGenerator/Helper.cs @@ -488,7 +488,24 @@ static bool IsArgumentTypeParameter(ITypeSymbol argument) public static bool IsPartial(INamedTypeSymbol symbol) { - return symbol.DeclaringSyntaxReferences.Any(syntax => syntax.GetSyntax() is BaseTypeDeclarationSyntax declaration && declaration.Modifiers.Any(SyntaxKind.PartialKeyword)); + bool isPartial = true; + for (ITypeSymbol parent = symbol; parent is not null; parent = parent.ContainingType) + { + isPartial &= parent.DeclaringSyntaxReferences.Any( + syntax => syntax.GetSyntax() is BaseTypeDeclarationSyntax declaration && + declaration.Modifiers.Any(SyntaxKind.PartialKeyword)); + } + return isPartial; + } + + public static bool IsPartial(TypeDeclarationSyntax node) + { + bool isPartial = true; + for (TypeDeclarationSyntax parent = node; parent is not null; parent = parent.Parent as TypeDeclarationSyntax) + { + isPartial &= parent.Modifiers.Any(static m => m.IsKind(SyntaxKind.PartialKeyword)); + } + return isPartial; } public static bool HasPrivateclass(ITypeSymbol symbol) @@ -1028,7 +1045,7 @@ public static string GetAbiMarshalerType(string type, string abiType, TypeKind k public static string EscapeTypeNameForIdentifier(string typeName) { - return Regex.Replace(typeName, """[(\ |:<>,\.)]""", "_"); + return Regex.Replace(typeName, """[(\ |:<>,\.\-@)]""", "_"); } public readonly struct MappedType diff --git a/src/Authoring/WinRT.SourceGenerator/WinRTAotCodeFixer.cs b/src/Authoring/WinRT.SourceGenerator/WinRTAotCodeFixer.cs index c06f6c167..2a2ad6a5c 100644 --- a/src/Authoring/WinRT.SourceGenerator/WinRTAotCodeFixer.cs +++ b/src/Authoring/WinRT.SourceGenerator/WinRTAotCodeFixer.cs @@ -28,7 +28,8 @@ public sealed class WinRTAotDiagnosticAnalyzer : DiagnosticAnalyzer WinRTRules.ClassNotAotCompatibleOldProjectionWarning, WinRTRules.ClassNotAotCompatibleOldProjectionInfo, WinRTRules.ClassEnableUnsafeWarning, - WinRTRules.ClassEnableUnsafeInfo); + WinRTRules.ClassEnableUnsafeInfo, + WinRTRules.ClassWithBindableCustomPropertyNotPartial); public override ImmutableArray SupportedDiagnostics => _supportedDiagnostics; @@ -47,7 +48,8 @@ public override void Initialize(AnalysisContext context) bool isComponentProject = context.Options.AnalyzerConfigOptionsProvider.IsCsWinRTComponent(); var winrtTypeAttribute = context.Compilation.GetTypeByMetadataName("WinRT.WindowsRuntimeTypeAttribute"); var winrtExposedTypeAttribute = context.Compilation.GetTypeByMetadataName("WinRT.WinRTExposedTypeAttribute"); - if (winrtTypeAttribute is null || winrtExposedTypeAttribute is null) + var generatedBindableCustomPropertyAttribute = context.Compilation.GetTypeByMetadataName("WinRT.GeneratedBindableCustomPropertyAttribute"); + if (winrtTypeAttribute is null || winrtExposedTypeAttribute is null || generatedBindableCustomPropertyAttribute is null) { return; } @@ -115,6 +117,13 @@ public override void Initialize(AnalysisContext context) context.ReportDiagnostic(Diagnostic.Create(diagnosticDescriptor, namedType.Locations[0], namedType.Name, string.Join(", ", interfacesFromOldProjections))); } } + + // Make sure classes with the GeneratedBindableCustomProperty attribute are marked partial. + if (GeneratorHelper.HasAttributeWithType(namedType, generatedBindableCustomPropertyAttribute) && + !GeneratorHelper.IsPartial(namedType)) + { + context.ReportDiagnostic(Diagnostic.Create(WinRTRules.ClassWithBindableCustomPropertyNotPartial, namedType.Locations[0], namedType.Name)); + } } }, SymbolKind.NamedType); @@ -416,13 +425,21 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) private static async Task MakeTypePartial(Document document, ClassDeclarationSyntax @class, CancellationToken token) { - var newClass = @class.AddModifiers(SyntaxFactory.Token(SyntaxKind.PartialKeyword)); - var oldRoot = await document.GetSyntaxRootAsync(token).ConfigureAwait(false); if (oldRoot is null) return document; - var newRoot = oldRoot.ReplaceNode(@class, newClass); + var newRoot = oldRoot.ReplaceNodes(@class.AncestorsAndSelf().OfType(), + (_, typeDeclaration) => + { + if (!typeDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword)) + { + return typeDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.PartialKeyword)); + } + + return typeDeclaration; + }); + return document.WithSyntaxRoot(newRoot); } diff --git a/src/Authoring/WinRT.SourceGenerator/WinRTRules.cs b/src/Authoring/WinRT.SourceGenerator/WinRTRules.cs index db66b49b8..d244c9d8c 100644 --- a/src/Authoring/WinRT.SourceGenerator/WinRTRules.cs +++ b/src/Authoring/WinRT.SourceGenerator/WinRTRules.cs @@ -218,5 +218,12 @@ private static DiagnosticDescriptor MakeRule(string id, string title, string mes CsWinRTDiagnosticStrings.EnableUnsafe_Brief, CsWinRTDiagnosticStrings.EnableUnsafe_Text, false); + + public static DiagnosticDescriptor ClassWithBindableCustomPropertyNotPartial = MakeRule( + "CsWinRT1028", + CsWinRTDiagnosticStrings.ClassNotMarkedPartial_Brief, + CsWinRTDiagnosticStrings.BindableCustomPropertyClassNotMarkedPartial_Text, + false, + true); } } diff --git a/src/Tests/FunctionalTests/CCW/Program.cs b/src/Tests/FunctionalTests/CCW/Program.cs index 744c0cd68..f50015572 100644 --- a/src/Tests/FunctionalTests/CCW/Program.cs +++ b/src/Tests/FunctionalTests/CCW/Program.cs @@ -622,6 +622,17 @@ internal static IProperties2 GetGenericInstance() } } +class TestClass3 +{ + // Making sure it compiles if the parent class isn't partial, but the actual class is. +#pragma warning disable CsWinRT1028 // Class is not marked partial + partial class NestedTestClass2 : IProperties2 +#pragma warning restore CsWinRT1028 // Class is not marked partial + { + private int _value; + public int ReadWriteProperty { get => _value; set => _value = value; } + } +} sealed partial class CustomCommand : ICommand { public event EventHandler CanExecuteChanged; @@ -663,6 +674,28 @@ partial class LanguageDervied2 : Language public int Derived { get; set; } } +// Testing code compiles when not marked partial +[GeneratedBindableCustomProperty] +#pragma warning disable CsWinRT1028 // Class is not marked partial +class LanguageDervied3 : Language +#pragma warning restore CsWinRT1028 // Class is not marked partial +{ + public int Derived { get; set; } +} + +class ParentClass +{ + // Testing code compiles when not marked partial + [GeneratedBindableCustomProperty] +#pragma warning disable CsWinRT1028 // Class is not marked partial + partial class LanguageDervied3 : Language +#pragma warning restore CsWinRT1028 // Class is not marked partial + { + public int Derived { get; set; } + } +} + + [GeneratedBindableCustomPropertyAttribute] sealed partial class Language2 { diff --git a/src/Tests/FunctionalTests/Collections/Collections.csproj b/src/Tests/FunctionalTests/Collections/Collections.csproj index 4d63a770f..3559f5c97 100644 --- a/src/Tests/FunctionalTests/Collections/Collections.csproj +++ b/src/Tests/FunctionalTests/Collections/Collections.csproj @@ -9,10 +9,10 @@ - + - + diff --git a/src/Tests/FunctionalTests/TestLibrary/TestLibrary.csproj b/src/Tests/FunctionalTests/TestLibrary/Test-Library.csproj similarity index 100% rename from src/Tests/FunctionalTests/TestLibrary/TestLibrary.csproj rename to src/Tests/FunctionalTests/TestLibrary/Test-Library.csproj diff --git a/src/cswinrt.sln b/src/cswinrt.sln index 91412924b..6235db9b4 100644 --- a/src/cswinrt.sln +++ b/src/cswinrt.sln @@ -158,7 +158,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CCW", "Tests\FunctionalTest EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinAppSDK", "Projections\WinAppSDK\WinAppSDK.csproj", "{7B803846-91AE-4B98-AC93-D3FCFB2DE5AA}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestLibrary", "Tests\FunctionalTests\TestLibrary\TestLibrary.csproj", "{335D51AC-1DCF-4487-A2BD-34CE2F17B3C4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test-Library", "Tests\FunctionalTests\TestLibrary\Test-Library.csproj", "{335D51AC-1DCF-4487-A2BD-34CE2F17B3C4}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Windows.UI.Xaml", "Projections\Windows.UI.Xaml\Windows.UI.Xaml.csproj", "{E85F3614-79B6-4652-BDB0-64AF68874CE0}" EndProject