From eb748cf07e7b72e9d4503406ca2976f1fbb2f920 Mon Sep 17 00:00:00 2001 From: Carl de Billy Date: Tue, 24 Jul 2018 16:00:55 -0400 Subject: [PATCH] Added [KnownAsImmutable] assembly attribute --- doc/Immutable Generation.md | 14 +++++++ .../Given_ImmutableEntity.KnownAsImmutable.cs | 35 ++++++++++++++++ .../Helpers/TypeSymbolExtensions.cs | 25 ++++++----- src/Uno.CodeGen/ImmutableGenerator.cs | 33 ++++++++++++++- .../ImmutableGenerationOptionsAttribute.cs | 4 +- .../KnownAsImmutableAttribute.cs | 41 +++++++++++++++++++ 6 files changed, 138 insertions(+), 14 deletions(-) create mode 100644 src/Uno.CodeGen.Tests/Given_ImmutableEntity.KnownAsImmutable.cs create mode 100644 src/Uno.Immutables/KnownAsImmutableAttribute.cs diff --git a/doc/Immutable Generation.md b/doc/Immutable Generation.md index e83e54e..d1ea328 100644 --- a/doc/Immutable Generation.md +++ b/doc/Immutable Generation.md @@ -361,3 +361,17 @@ public class MyImmutable > (`[GeneratedImmutable(GenerateEquality = false)]`) > won't have any effect in inherited class if the generation is active on the > base class. + +## I want to reference external classes from my entities. + +I'm getting this error: + +``` csharp +#error: 'ImmutableGenerator: Property MyClass.SomeField type ExternalClass.SuperClass is not immutable. It cannot be used in an immutable entity.' +``` + +To fix this, put this attribute on your assembly: + +``` csharp +[assembly: Uno.KnownAsImmutable(typeof(ExternalClass.SuperClass))] +``` diff --git a/src/Uno.CodeGen.Tests/Given_ImmutableEntity.KnownAsImmutable.cs b/src/Uno.CodeGen.Tests/Given_ImmutableEntity.KnownAsImmutable.cs new file mode 100644 index 0000000..b4e3b19 --- /dev/null +++ b/src/Uno.CodeGen.Tests/Given_ImmutableEntity.KnownAsImmutable.cs @@ -0,0 +1,35 @@ +// ****************************************************************** +// Copyright � 2015-2018 nventive inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ****************************************************************** +using System.Net.Mail; +using Uno; + +[assembly: KnownAsImmutable(typeof(SmtpException))] + +namespace Uno.CodeGen.Tests +{ + partial class Given_ImmutableEntity + { + internal static readonly MyImmutableWithExternalKnownAsImmutable KnownAsImmutable = + MyImmutableWithExternalKnownAsImmutable.Default; + } + + [GeneratedImmutable] + internal partial class MyImmutableWithExternalKnownAsImmutable + { + private SmtpException Exception { get; } + } +} diff --git a/src/Uno.CodeGen/Helpers/TypeSymbolExtensions.cs b/src/Uno.CodeGen/Helpers/TypeSymbolExtensions.cs index 0f8b7f3..d2628ec 100644 --- a/src/Uno.CodeGen/Helpers/TypeSymbolExtensions.cs +++ b/src/Uno.CodeGen/Helpers/TypeSymbolExtensions.cs @@ -60,7 +60,7 @@ namespace Uno.Helpers private static ImmutableDictionary<(ITypeSymbol, bool), bool> _isImmutable = ImmutableDictionary<(ITypeSymbol, bool), bool>.Empty; - public static bool IsImmutable(this ITypeSymbol type, bool treatArrayAsImmutable) + public static bool IsImmutable(this ITypeSymbol type, bool treatArrayAsImmutable, IReadOnlyList knownAsImmutable) { bool GetIsImmutable((ITypeSymbol type, bool treatArrayAsImmutable) x) { @@ -92,7 +92,7 @@ namespace Uno.Helpers if (t is IArrayTypeSymbol arrayType) { - return arrayType.ElementType?.IsImmutable(asImmutable) ?? false; + return arrayType.ElementType?.IsImmutable(asImmutable, knownAsImmutable) ?? false; } var definitionType = t.GetDefinitionType(); @@ -127,7 +127,7 @@ namespace Uno.Helpers case "System.Collections.Immutable.ImmutableStack": { var argumentParameter = (t as INamedTypeSymbol)?.TypeArguments.FirstOrDefault(); - return argumentParameter == null || argumentParameter.IsImmutable(asImmutable); + return argumentParameter == null || argumentParameter.IsImmutable(asImmutable, knownAsImmutable); } case "System.Collections.Immutable.IImmutableDictionary": case "System.Collections.Immutable.ImmutableDictionary": @@ -135,11 +135,16 @@ namespace Uno.Helpers { var keyTypeParameter = (t as INamedTypeSymbol)?.TypeArguments.FirstOrDefault(); var valueTypeParameter = (t as INamedTypeSymbol)?.TypeArguments.Skip(1).FirstOrDefault(); - return (keyTypeParameter == null || keyTypeParameter.IsImmutable(asImmutable)) - && (valueTypeParameter == null || valueTypeParameter.IsImmutable(asImmutable)); + return (keyTypeParameter == null || keyTypeParameter.IsImmutable(asImmutable, knownAsImmutable)) + && (valueTypeParameter == null || valueTypeParameter.IsImmutable(asImmutable, knownAsImmutable)); } } + if (knownAsImmutable.Contains(definitionType)) + { + return true; + } + switch (definitionType.GetType().Name) { case "TupleTypeSymbol": @@ -154,13 +159,13 @@ namespace Uno.Helpers return asImmutable; } - return IsImmutableByRequirements(t, treatArrayAsImmutable); + return IsImmutableByRequirements(t, treatArrayAsImmutable, knownAsImmutable); } return ImmutableInterlocked.GetOrAdd(ref _isImmutable, (type, treatArrayAsImmutable), GetIsImmutable); } - private static bool IsImmutableByRequirements(ITypeSymbol type, bool treatArrayAsImmutable) + private static bool IsImmutableByRequirements(ITypeSymbol type, bool treatArrayAsImmutable, IReadOnlyList knownAsImmutable) { // Check if type is complying to immutable requirements // 1) all instance fields are readonly @@ -169,11 +174,11 @@ namespace Uno.Helpers foreach (var member in type.GetMembers()) { - if (member is IFieldSymbol f && !(f.IsStatic || f.IsReadOnly || f.IsImplicitlyDeclared) && f.Type.IsImmutable(treatArrayAsImmutable)) + if (member is IFieldSymbol f && !(f.IsStatic || f.IsReadOnly || f.IsImplicitlyDeclared) && f.Type.IsImmutable(treatArrayAsImmutable, knownAsImmutable)) { return false; // there's a non-readonly non-static field } - if (member is IPropertySymbol p && !(p.IsStatic || p.IsReadOnly || p.IsImplicitlyDeclared) && p.Type.IsImmutable(treatArrayAsImmutable)) + if (member is IPropertySymbol p && !(p.IsStatic || p.IsReadOnly || p.IsImplicitlyDeclared) && p.Type.IsImmutable(treatArrayAsImmutable, knownAsImmutable)) { return false; // there's a non-readonly non-static property } @@ -183,7 +188,7 @@ namespace Uno.Helpers return type.BaseType == null || type.BaseType.SpecialType == SpecialType.System_Object || type.BaseType.SpecialType == SpecialType.System_ValueType - || type.BaseType.IsImmutable(treatArrayAsImmutable); + || type.BaseType.IsImmutable(treatArrayAsImmutable, knownAsImmutable); } public static ITypeSymbol GetDefinitionType(this ITypeSymbol type) diff --git a/src/Uno.CodeGen/ImmutableGenerator.cs b/src/Uno.CodeGen/ImmutableGenerator.cs index b097ed8..fa3601f 100644 --- a/src/Uno.CodeGen/ImmutableGenerator.cs +++ b/src/Uno.CodeGen/ImmutableGenerator.cs @@ -17,6 +17,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; @@ -46,6 +47,7 @@ namespace Uno private INamedTypeSymbol _immutableBuilderAttributeSymbol; private INamedTypeSymbol _immutableAttributeCopyIgnoreAttributeSymbol; private INamedTypeSymbol _immutableGenerationOptionsAttributeSymbol; + private INamedTypeSymbol _immutableKnownAsImmutableAttributeSymbol; private (bool generateOptionCode, bool treatArrayAsImmutable, bool generateEqualityByDefault, bool generateJsonNet) _generationOptions; private bool _generateOptionCode = true; @@ -53,6 +55,8 @@ namespace Uno private Regex[] _copyIgnoreAttributeRegexes; + private IReadOnlyList _knownAsImmutableTypes; + /// public override void Execute(SourceGeneratorContext context) { @@ -63,6 +67,7 @@ namespace Uno _immutableBuilderAttributeSymbol = context.Compilation.GetTypeByMetadataName("Uno.ImmutableBuilderAttribute"); _immutableAttributeCopyIgnoreAttributeSymbol = context.Compilation.GetTypeByMetadataName("Uno.ImmutableAttributeCopyIgnoreAttribute"); _immutableGenerationOptionsAttributeSymbol = context.Compilation.GetTypeByMetadataName("Uno.ImmutableGenerationOptionsAttribute"); + _immutableKnownAsImmutableAttributeSymbol = context.Compilation.GetTypeByMetadataName("Uno.KnownAsImmutableAttribute"); var generationData = EnumerateImmutableGeneratedEntities() .OrderBy(x => x.symbol.Name) @@ -70,6 +75,13 @@ namespace Uno var immutableEntitiesToGenerate = generationData.Select(x => x.Item1).ToArray(); + if (immutableEntitiesToGenerate.Length == 0) + { + return; // nothing to do + } + + _knownAsImmutableTypes = EnumerateKnownAsImmutables(); + _copyIgnoreAttributeRegexes = ExtractCopyIgnoreAttributes(context.Compilation.Assembly) .Concat(new[] {new Regex(@"^Uno\.Immutable"), new Regex(@"^Uno\.Equality")}) @@ -849,7 +861,7 @@ $@"public sealed class {symbolName}BuilderJsonConverterTo{symbolName}{genericArg builder.AppendLineInvariant( $"#error {nameof(ImmutableGenerator)}: {typeSource} type {type} IS A BUILDER! It cannot be used in an immutable entity."); } - else if (!type.IsImmutable(_generationOptions.treatArrayAsImmutable)) + else if (!type.IsImmutable(_generationOptions.treatArrayAsImmutable, _knownAsImmutableTypes)) { if (type is IArrayTypeSymbol) { @@ -950,5 +962,22 @@ $@"public sealed class {symbolName}BuilderJsonConverterTo{symbolName}{genericArg where moduleAttribute != null //where (bool) moduleAttribute.ConstructorArguments[0].Value select (type, moduleAttribute); + + private IReadOnlyList EnumerateKnownAsImmutables() + { + var currentModuleAttributes = _context.Compilation.Assembly.GetAttributes(); + var referencedAssembliesAttributes = + _context.Compilation.SourceModule.ReferencedAssemblySymbols.SelectMany(a => a.GetAttributes()); + + var knownTypeAttributes = currentModuleAttributes + .Concat(referencedAssembliesAttributes) + .Where(a => a.AttributeClass.Equals(_immutableKnownAsImmutableAttributeSymbol)) + .Select(a => a.ConstructorArguments[0].Value) + .Cast() + .Distinct() + .ToImmutableArray(); + + return knownTypeAttributes; + } } -} \ No newline at end of file +} diff --git a/src/Uno.Immutables/ImmutableGenerationOptionsAttribute.cs b/src/Uno.Immutables/ImmutableGenerationOptionsAttribute.cs index 35a4129..7022be9 100644 --- a/src/Uno.Immutables/ImmutableGenerationOptionsAttribute.cs +++ b/src/Uno.Immutables/ImmutableGenerationOptionsAttribute.cs @@ -21,7 +21,7 @@ namespace Uno /// /// Global settings for [GenerateImmutable] generator. /// - [System.AttributeUsage(AttributeTargets.Assembly, Inherited = false, AllowMultiple = false)] + [System.AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)] public sealed class ImmutableGenerationOptionsAttribute : Attribute { /// @@ -58,4 +58,4 @@ namespace Uno /// public bool GenerateNewtownsoftJsonNetConverters { get; set; } = true; } -} \ No newline at end of file +} diff --git a/src/Uno.Immutables/KnownAsImmutableAttribute.cs b/src/Uno.Immutables/KnownAsImmutableAttribute.cs new file mode 100644 index 0000000..f724d60 --- /dev/null +++ b/src/Uno.Immutables/KnownAsImmutableAttribute.cs @@ -0,0 +1,41 @@ +// ****************************************************************** +// Copyright � 2015-2018 nventive inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ****************************************************************** +using System; + +namespace Uno +{ + /// + /// Define a type (usually external) as immutable + /// + [System.AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class KnownAsImmutableAttribute : Attribute + { + /// + /// The type known to be immutable + /// + public Type Type { get; } + + /// + /// .ctor + /// + /// + public KnownAsImmutableAttribute(Type type) + { + Type = type; + } + } +}