Added [KnownAsImmutable] assembly attribute

This commit is contained in:
Carl de Billy 2018-07-24 16:00:55 -04:00
Родитель d380fdb536
Коммит eb748cf07e
6 изменённых файлов: 138 добавлений и 14 удалений

Просмотреть файл

@ -361,3 +361,17 @@ public class MyImmutable
> (`[GeneratedImmutable(GenerateEquality = false)]`) > (`[GeneratedImmutable(GenerateEquality = false)]`)
> won't have any effect in inherited class if the generation is active on the > won't have any effect in inherited class if the generation is active on the
> base class. > 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))]
```

Просмотреть файл

@ -0,0 +1,35 @@
// ******************************************************************
// Copyright <20> 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; }
}
}

Просмотреть файл

@ -60,7 +60,7 @@ namespace Uno.Helpers
private static ImmutableDictionary<(ITypeSymbol, bool), bool> _isImmutable = private static ImmutableDictionary<(ITypeSymbol, bool), bool> _isImmutable =
ImmutableDictionary<(ITypeSymbol, bool), bool>.Empty; ImmutableDictionary<(ITypeSymbol, bool), bool>.Empty;
public static bool IsImmutable(this ITypeSymbol type, bool treatArrayAsImmutable) public static bool IsImmutable(this ITypeSymbol type, bool treatArrayAsImmutable, IReadOnlyList<ITypeSymbol> knownAsImmutable)
{ {
bool GetIsImmutable((ITypeSymbol type, bool treatArrayAsImmutable) x) bool GetIsImmutable((ITypeSymbol type, bool treatArrayAsImmutable) x)
{ {
@ -92,7 +92,7 @@ namespace Uno.Helpers
if (t is IArrayTypeSymbol arrayType) if (t is IArrayTypeSymbol arrayType)
{ {
return arrayType.ElementType?.IsImmutable(asImmutable) ?? false; return arrayType.ElementType?.IsImmutable(asImmutable, knownAsImmutable) ?? false;
} }
var definitionType = t.GetDefinitionType(); var definitionType = t.GetDefinitionType();
@ -127,7 +127,7 @@ namespace Uno.Helpers
case "System.Collections.Immutable.ImmutableStack<T>": case "System.Collections.Immutable.ImmutableStack<T>":
{ {
var argumentParameter = (t as INamedTypeSymbol)?.TypeArguments.FirstOrDefault(); 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<TKey, TValue>": case "System.Collections.Immutable.IImmutableDictionary<TKey, TValue>":
case "System.Collections.Immutable.ImmutableDictionary<TKey, TValue>": case "System.Collections.Immutable.ImmutableDictionary<TKey, TValue>":
@ -135,11 +135,16 @@ namespace Uno.Helpers
{ {
var keyTypeParameter = (t as INamedTypeSymbol)?.TypeArguments.FirstOrDefault(); var keyTypeParameter = (t as INamedTypeSymbol)?.TypeArguments.FirstOrDefault();
var valueTypeParameter = (t as INamedTypeSymbol)?.TypeArguments.Skip(1).FirstOrDefault(); var valueTypeParameter = (t as INamedTypeSymbol)?.TypeArguments.Skip(1).FirstOrDefault();
return (keyTypeParameter == null || keyTypeParameter.IsImmutable(asImmutable)) return (keyTypeParameter == null || keyTypeParameter.IsImmutable(asImmutable, knownAsImmutable))
&& (valueTypeParameter == null || valueTypeParameter.IsImmutable(asImmutable)); && (valueTypeParameter == null || valueTypeParameter.IsImmutable(asImmutable, knownAsImmutable));
} }
} }
if (knownAsImmutable.Contains(definitionType))
{
return true;
}
switch (definitionType.GetType().Name) switch (definitionType.GetType().Name)
{ {
case "TupleTypeSymbol": case "TupleTypeSymbol":
@ -154,13 +159,13 @@ namespace Uno.Helpers
return asImmutable; return asImmutable;
} }
return IsImmutableByRequirements(t, treatArrayAsImmutable); return IsImmutableByRequirements(t, treatArrayAsImmutable, knownAsImmutable);
} }
return ImmutableInterlocked.GetOrAdd(ref _isImmutable, (type, treatArrayAsImmutable), GetIsImmutable); 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<ITypeSymbol> knownAsImmutable)
{ {
// Check if type is complying to immutable requirements // Check if type is complying to immutable requirements
// 1) all instance fields are readonly // 1) all instance fields are readonly
@ -169,11 +174,11 @@ namespace Uno.Helpers
foreach (var member in type.GetMembers()) 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 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 return false; // there's a non-readonly non-static property
} }
@ -183,7 +188,7 @@ namespace Uno.Helpers
return type.BaseType == null return type.BaseType == null
|| type.BaseType.SpecialType == SpecialType.System_Object || type.BaseType.SpecialType == SpecialType.System_Object
|| type.BaseType.SpecialType == SpecialType.System_ValueType || type.BaseType.SpecialType == SpecialType.System_ValueType
|| type.BaseType.IsImmutable(treatArrayAsImmutable); || type.BaseType.IsImmutable(treatArrayAsImmutable, knownAsImmutable);
} }
public static ITypeSymbol GetDefinitionType(this ITypeSymbol type) public static ITypeSymbol GetDefinitionType(this ITypeSymbol type)

Просмотреть файл

@ -17,6 +17,7 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
@ -46,6 +47,7 @@ namespace Uno
private INamedTypeSymbol _immutableBuilderAttributeSymbol; private INamedTypeSymbol _immutableBuilderAttributeSymbol;
private INamedTypeSymbol _immutableAttributeCopyIgnoreAttributeSymbol; private INamedTypeSymbol _immutableAttributeCopyIgnoreAttributeSymbol;
private INamedTypeSymbol _immutableGenerationOptionsAttributeSymbol; private INamedTypeSymbol _immutableGenerationOptionsAttributeSymbol;
private INamedTypeSymbol _immutableKnownAsImmutableAttributeSymbol;
private (bool generateOptionCode, bool treatArrayAsImmutable, bool generateEqualityByDefault, bool generateJsonNet) _generationOptions; private (bool generateOptionCode, bool treatArrayAsImmutable, bool generateEqualityByDefault, bool generateJsonNet) _generationOptions;
private bool _generateOptionCode = true; private bool _generateOptionCode = true;
@ -53,6 +55,8 @@ namespace Uno
private Regex[] _copyIgnoreAttributeRegexes; private Regex[] _copyIgnoreAttributeRegexes;
private IReadOnlyList<ITypeSymbol> _knownAsImmutableTypes;
/// <inheritdoc /> /// <inheritdoc />
public override void Execute(SourceGeneratorContext context) public override void Execute(SourceGeneratorContext context)
{ {
@ -63,6 +67,7 @@ namespace Uno
_immutableBuilderAttributeSymbol = context.Compilation.GetTypeByMetadataName("Uno.ImmutableBuilderAttribute"); _immutableBuilderAttributeSymbol = context.Compilation.GetTypeByMetadataName("Uno.ImmutableBuilderAttribute");
_immutableAttributeCopyIgnoreAttributeSymbol = context.Compilation.GetTypeByMetadataName("Uno.ImmutableAttributeCopyIgnoreAttribute"); _immutableAttributeCopyIgnoreAttributeSymbol = context.Compilation.GetTypeByMetadataName("Uno.ImmutableAttributeCopyIgnoreAttribute");
_immutableGenerationOptionsAttributeSymbol = context.Compilation.GetTypeByMetadataName("Uno.ImmutableGenerationOptionsAttribute"); _immutableGenerationOptionsAttributeSymbol = context.Compilation.GetTypeByMetadataName("Uno.ImmutableGenerationOptionsAttribute");
_immutableKnownAsImmutableAttributeSymbol = context.Compilation.GetTypeByMetadataName("Uno.KnownAsImmutableAttribute");
var generationData = EnumerateImmutableGeneratedEntities() var generationData = EnumerateImmutableGeneratedEntities()
.OrderBy(x => x.symbol.Name) .OrderBy(x => x.symbol.Name)
@ -70,6 +75,13 @@ namespace Uno
var immutableEntitiesToGenerate = generationData.Select(x => x.Item1).ToArray(); var immutableEntitiesToGenerate = generationData.Select(x => x.Item1).ToArray();
if (immutableEntitiesToGenerate.Length == 0)
{
return; // nothing to do
}
_knownAsImmutableTypes = EnumerateKnownAsImmutables();
_copyIgnoreAttributeRegexes = _copyIgnoreAttributeRegexes =
ExtractCopyIgnoreAttributes(context.Compilation.Assembly) ExtractCopyIgnoreAttributes(context.Compilation.Assembly)
.Concat(new[] {new Regex(@"^Uno\.Immutable"), new Regex(@"^Uno\.Equality")}) .Concat(new[] {new Regex(@"^Uno\.Immutable"), new Regex(@"^Uno\.Equality")})
@ -849,7 +861,7 @@ $@"public sealed class {symbolName}BuilderJsonConverterTo{symbolName}{genericArg
builder.AppendLineInvariant( builder.AppendLineInvariant(
$"#error {nameof(ImmutableGenerator)}: {typeSource} type {type} IS A BUILDER! It cannot be used in an immutable entity."); $"#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) if (type is IArrayTypeSymbol)
{ {
@ -950,5 +962,22 @@ $@"public sealed class {symbolName}BuilderJsonConverterTo{symbolName}{genericArg
where moduleAttribute != null where moduleAttribute != null
//where (bool) moduleAttribute.ConstructorArguments[0].Value //where (bool) moduleAttribute.ConstructorArguments[0].Value
select (type, moduleAttribute); select (type, moduleAttribute);
private IReadOnlyList<ITypeSymbol> 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<ITypeSymbol>()
.Distinct()
.ToImmutableArray();
return knownTypeAttributes;
}
} }
} }

Просмотреть файл

@ -21,7 +21,7 @@ namespace Uno
/// <summary> /// <summary>
/// Global settings for [GenerateImmutable] generator. /// Global settings for [GenerateImmutable] generator.
/// </summary> /// </summary>
[System.AttributeUsage(AttributeTargets.Assembly, Inherited = false, AllowMultiple = false)] [System.AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
public sealed class ImmutableGenerationOptionsAttribute : Attribute public sealed class ImmutableGenerationOptionsAttribute : Attribute
{ {
/// <summary> /// <summary>
@ -58,4 +58,4 @@ namespace Uno
/// </remarks> /// </remarks>
public bool GenerateNewtownsoftJsonNetConverters { get; set; } = true; public bool GenerateNewtownsoftJsonNetConverters { get; set; } = true;
} }
} }

Просмотреть файл

@ -0,0 +1,41 @@
// ******************************************************************
// Copyright <20> 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
{
/// <summary>
/// Define a type (usually external) as immutable
/// </summary>
[System.AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public sealed class KnownAsImmutableAttribute : Attribute
{
/// <summary>
/// The type known to be immutable
/// </summary>
public Type Type { get; }
/// <summary>
/// .ctor
/// </summary>
/// <param name="type"></param>
public KnownAsImmutableAttribute(Type type)
{
Type = type;
}
}
}