Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add NullableContextAttribute #36152

Merged
merged 25 commits into from
Jun 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion docs/features/nullable-metadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,25 +148,36 @@ for members that are inaccessible outside the assembly (`private` members, and a
if the assembly does not contain `InternalsVisibleToAttribute` attributes).

The compiler behavior is configured from a command-line flag.
For now a feature flag is used: `-feature:nullablePublicOnly`.
For now a feature flag is used: `-features:nullablePublicOnly`.

If private member attributes are dropped, the compiler will emit a `[module: NullablePublicOnly]` attribute.
The presence or absence of the `NullablePublicOnlyAttribute` can be used by tools to interpret
the nullability of private members that do not have an associated `NullableAttribute` attribute.

For members that do not have explicit accessibility in metadata
(specifically for parameters, type parameters, events, and properties),
the compiler uses the accessibility of the container to determine whether to emit nullable attributes.

```C#
namespace System.Runtime.CompilerServices
{
[System.AttributeUsage(AttributeTargets.Module, AllowMultiple = false)]
public sealed class NullablePublicOnlyAttribute : Attribute
{
public readonly bool IncludesInternals;
public NullablePublicOnlyAttribute(bool includesInternals)
{
IncludesInternals = includesInternals;
}
}
}
```

The `NullablePublicOnlyAttribute` type is for compiler use only - it is not permitted in source.
The type declaration is synthesized by the compiler if not already included in the compilation.

`IncludesInternal` is true if `internal` members are annotated in addition to `public` and `protected` members.

## Compatibility

The nullable metadata does not include an explicit version number.
Expand Down
22 changes: 22 additions & 0 deletions src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3305,12 +3305,34 @@ internal bool ShouldEmitNullableAttributes(Symbol symbol)
return true;
}

// For symbols that do not have explicit accessibility in metadata,
// use the accessibility of the container.
symbol = getExplicitAccessibilitySymbol(symbol);

if (!AccessCheck.IsEffectivelyPublicOrInternal(symbol, out bool isInternal))
{
return false;
}

return !isInternal || SourceAssembly.InternalsAreVisible;

static Symbol getExplicitAccessibilitySymbol(Symbol symbol)
{
while (true)
{
switch (symbol.Kind)
{
case SymbolKind.Parameter:
case SymbolKind.TypeParameter:
case SymbolKind.Property:
case SymbolKind.Event:
symbol = symbol.ContainingSymbol;
break;
default:
return symbol;
}
}
}
}

internal override AnalyzerDriver AnalyzerForLanguage(ImmutableArray<DiagnosticAnalyzer> analyzers, AnalyzerManager analyzerManager)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp
{
internal struct MostCommonNullableValueBuilder
{
private int _value0;
private int _value1;
private int _value2;

internal byte? MostCommonValue
{
get
{
int max;
byte b;
if (_value1 > _value0)
{
max = _value1;
b = 1;
}
else
{
max = _value0;
b = 0;
}
if (_value2 > max)
{
return 2;
}
return max == 0 ? (byte?)null : b;
}
}

internal void AddValue(byte value)
{
switch (value)
{
case 0:
_value0++;
break;
case 1:
_value1++;
break;
case 2:
_value2++;
break;
default:
throw ExceptionUtilities.UnexpectedValue(value);
}
}

internal void AddValue(byte? value)
{
if (value != null)
{
AddValue(value.GetValueOrDefault());
}
}

internal void AddValue(TypeWithAnnotations type)
{
var builder = ArrayBuilder<byte>.GetInstance();
type.AddNullableTransforms(builder);
AddValue(GetCommonValue(builder));
builder.Free();
}

/// <summary>
/// Returns the common value if all bytes are the same value.
/// Otherwise returns null.
/// </summary>
internal static byte? GetCommonValue(ArrayBuilder<byte> builder)
{
int n = builder.Count;
if (n == 0)
{
return null;
}
byte b = builder[0];
for (int i = 1; i < n; i++)
{
if (builder[i] != b)
{
return null;
}
}
return b;
}
}
}
57 changes: 50 additions & 7 deletions src/Compilers/CSharp/Portable/Emitter/Model/PEAssemblyBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ internal abstract class PEAssemblyBuilderBase : PEModuleBuilder, Cci.IAssemblyRe
private SynthesizedEmbeddedAttributeSymbol _lazyIsByRefLikeAttribute;
private SynthesizedEmbeddedAttributeSymbol _lazyIsUnmanagedAttribute;
private SynthesizedEmbeddedNullableAttributeSymbol _lazyNullableAttribute;
private SynthesizedEmbeddedAttributeSymbol _lazyNullablePublicOnlyAttribute;
private SynthesizedEmbeddedNullableContextAttributeSymbol _lazyNullableContextAttribute;
private SynthesizedEmbeddedNullablePublicOnlyAttributeSymbol _lazyNullablePublicOnlyAttribute;

/// <summary>
/// The behavior of the C# command-line compiler is as follows:
Expand Down Expand Up @@ -83,6 +84,7 @@ internal override ImmutableArray<NamedTypeSymbol> GetEmbeddedTypes(DiagnosticBag
builder.AddIfNotNull(_lazyIsUnmanagedAttribute);
builder.AddIfNotNull(_lazyIsByRefLikeAttribute);
builder.AddIfNotNull(_lazyNullableAttribute);
builder.AddIfNotNull(_lazyNullableContextAttribute);
builder.AddIfNotNull(_lazyNullablePublicOnlyAttribute);

return builder.ToImmutableAndFree();
Expand Down Expand Up @@ -195,17 +197,30 @@ internal override SynthesizedAttributeData SynthesizeNullableAttribute(WellKnown
return base.SynthesizeNullableAttribute(member, arguments);
}

internal override SynthesizedAttributeData SynthesizeNullablePublicOnlyAttribute()
internal override SynthesizedAttributeData SynthesizeNullableContextAttribute(ImmutableArray<TypedConstant> arguments)
{
if ((object)_lazyNullableContextAttribute != null)
{
return new SynthesizedAttributeData(
_lazyNullableContextAttribute.Constructors[0],
Copy link
Contributor

@AlekseyTs AlekseyTs Jun 21, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_lazyNullableContextAttribute.Constructors[0] [](start = 20, length = 45)

It feels like, either member should be taken into account here, or the parameter should be removed. #Closed

arguments,
ImmutableArray<KeyValuePair<string, TypedConstant>>.Empty);
}

return base.SynthesizeNullableContextAttribute(arguments);
}

internal override SynthesizedAttributeData SynthesizeNullablePublicOnlyAttribute(ImmutableArray<TypedConstant> arguments)
{
if ((object)_lazyNullablePublicOnlyAttribute != null)
{
return new SynthesizedAttributeData(
_lazyNullablePublicOnlyAttribute.Constructors[0],
ImmutableArray<TypedConstant>.Empty,
arguments,
ImmutableArray<KeyValuePair<string, TypedConstant>>.Empty);
}

return base.SynthesizeNullablePublicOnlyAttribute();
return base.SynthesizeNullablePublicOnlyAttribute(arguments);
}

protected override SynthesizedAttributeData TrySynthesizeIsReadOnlyAttribute()
Expand Down Expand Up @@ -292,12 +307,18 @@ private void CreateEmbeddedAttributesIfNeeded(DiagnosticBag diagnostics)
diagnostics);
}

if ((needsAttributes & EmbeddableAttributes.NullableContextAttribute) != 0)
{
CreateEmbeddedNullableContextAttributeIfNeeded(
ref _lazyNullableContextAttribute,
diagnostics);
}

if ((needsAttributes & EmbeddableAttributes.NullablePublicOnlyAttribute) != 0)
{
CreateEmbeddedAttributeIfNeeded(
CreateEmbeddedNullablePublicOnlyAttributeIfNeeded(
ref _lazyNullablePublicOnlyAttribute,
diagnostics,
AttributeDescription.NullablePublicOnlyAttribute);
diagnostics);
}
}

Expand All @@ -324,6 +345,28 @@ private void CreateEmbeddedNullableAttributeIfNeeded(
}
}

private void CreateEmbeddedNullableContextAttributeIfNeeded(
ref SynthesizedEmbeddedNullableContextAttributeSymbol symbol,
DiagnosticBag diagnostics)
{
if (symbol is null)
{
AddDiagnosticsForExistingAttribute(AttributeDescription.NullableContextAttribute, diagnostics);
symbol = new SynthesizedEmbeddedNullableContextAttributeSymbol(_sourceAssembly.DeclaringCompilation, diagnostics);
}
}

private void CreateEmbeddedNullablePublicOnlyAttributeIfNeeded(
ref SynthesizedEmbeddedNullablePublicOnlyAttributeSymbol symbol,
DiagnosticBag diagnostics)
{
if (symbol is null)
{
AddDiagnosticsForExistingAttribute(AttributeDescription.NullablePublicOnlyAttribute, diagnostics);
symbol = new SynthesizedEmbeddedNullablePublicOnlyAttributeSymbol(_sourceAssembly.DeclaringCompilation, diagnostics);
}
}

private void AddDiagnosticsForExistingAttribute(AttributeDescription description, DiagnosticBag diagnostics)
{
var attributeMetadataName = MetadataTypeName.FromFullName(description.FullName);
Expand Down
83 changes: 54 additions & 29 deletions src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1462,7 +1462,7 @@ internal SynthesizedAttributeData SynthesizeIsByRefLikeAttribute(Symbol symbol)
/// is a constructed type with a nullable reference type present in its type argument tree,
/// returns a synthesized NullableAttribute with encoded nullable transforms array.
/// </summary>
internal SynthesizedAttributeData SynthesizeNullableAttribute(Symbol symbol, TypeWithAnnotations type)
internal SynthesizedAttributeData SynthesizeNullableAttributeIfNecessary(Symbol symbol, byte? nullableContextValue, TypeWithAnnotations type)
{
if ((object)Compilation.SourceModule != symbol.ContainingModule)
{
Expand All @@ -1473,36 +1473,46 @@ internal SynthesizedAttributeData SynthesizeNullableAttribute(Symbol symbol, Typ
var flagsBuilder = ArrayBuilder<byte>.GetInstance();
type.AddNullableTransforms(flagsBuilder);

Debug.Assert(flagsBuilder.Any());
Debug.Assert(flagsBuilder.Contains(NullableAnnotationExtensions.NotAnnotatedAttributeValue) || flagsBuilder.Contains(NullableAnnotationExtensions.AnnotatedAttributeValue));

WellKnownMember constructor;
ImmutableArray<TypedConstant> arguments;
NamedTypeSymbol byteType = Compilation.GetSpecialType(SpecialType.System_Byte);
Debug.Assert((object)byteType != null);

if (flagsBuilder.All(flag => flag == NullableAnnotationExtensions.NotAnnotatedAttributeValue) || flagsBuilder.All(flag => flag == NullableAnnotationExtensions.AnnotatedAttributeValue))
SynthesizedAttributeData attribute;
if (!flagsBuilder.Any())
{
constructor = WellKnownMember.System_Runtime_CompilerServices_NullableAttribute__ctorByte;
arguments = ImmutableArray.Create(new TypedConstant(byteType, TypedConstantKind.Primitive, flagsBuilder[0]));
attribute = null;
}
else
{
var constantsBuilder = ArrayBuilder<TypedConstant>.GetInstance(flagsBuilder.Count);

foreach (byte flag in flagsBuilder)
Debug.Assert(flagsBuilder.All(f => f <= 2));
byte? commonValue = MostCommonNullableValueBuilder.GetCommonValue(flagsBuilder);
if (commonValue != null)
{
Debug.Assert(flag == NullableAnnotationExtensions.ObliviousAttributeValue || flag == NullableAnnotationExtensions.NotAnnotatedAttributeValue || flag == NullableAnnotationExtensions.AnnotatedAttributeValue);
constantsBuilder.Add(new TypedConstant(byteType, TypedConstantKind.Primitive, flag));
attribute = SynthesizeNullableAttributeIfNecessary(nullableContextValue, commonValue.GetValueOrDefault());
}
else
{
NamedTypeSymbol byteType = Compilation.GetSpecialType(SpecialType.System_Byte);
var byteArrayType = ArrayTypeSymbol.CreateSZArray(byteType.ContainingAssembly, TypeWithAnnotations.Create(byteType));
var value = flagsBuilder.SelectAsArray((flag, byteType) => new TypedConstant(byteType, TypedConstantKind.Primitive, flag), byteType);
attribute = SynthesizeNullableAttribute(
WellKnownMember.System_Runtime_CompilerServices_NullableAttribute__ctorTransformFlags,
ImmutableArray.Create(new TypedConstant(byteArrayType, value)));
}

var byteArray = ArrayTypeSymbol.CreateSZArray(byteType.ContainingAssembly, TypeWithAnnotations.Create(byteType));
constructor = WellKnownMember.System_Runtime_CompilerServices_NullableAttribute__ctorTransformFlags;
arguments = ImmutableArray.Create(new TypedConstant(byteArray, constantsBuilder.ToImmutableAndFree()));
}

flagsBuilder.Free();
return SynthesizeNullableAttribute(constructor, arguments);
return attribute;
}

internal SynthesizedAttributeData SynthesizeNullableAttributeIfNecessary(byte? nullableContextValue, byte nullableValue)
{
if (nullableValue == nullableContextValue ||
(nullableContextValue == null && nullableValue == 0))
{
return null;
}

NamedTypeSymbol byteType = Compilation.GetSpecialType(SpecialType.System_Byte);
return SynthesizeNullableAttribute(
WellKnownMember.System_Runtime_CompilerServices_NullableAttribute__ctorByte,
ImmutableArray.Create(new TypedConstant(byteType, TypedConstantKind.Primitive, nullableValue)));
}

internal virtual SynthesizedAttributeData SynthesizeNullableAttribute(WellKnownMember member, ImmutableArray<TypedConstant> arguments)
Expand All @@ -1512,10 +1522,30 @@ internal virtual SynthesizedAttributeData SynthesizeNullableAttribute(WellKnownM
return Compilation.TrySynthesizeAttribute(member, arguments, isOptionalUse: true);
}

internal virtual SynthesizedAttributeData SynthesizeNullablePublicOnlyAttribute()
internal SynthesizedAttributeData SynthesizeNullableContextAttribute(Symbol symbol, byte value)
{
var module = Compilation.SourceModule;
if ((object)module != symbol && (object)module != symbol.ContainingModule)
{
// For symbols that are not defined in the same compilation (like NoPia), don't synthesize this attribute.
return null;
}

return SynthesizeNullableContextAttribute(
ImmutableArray.Create(new TypedConstant(Compilation.GetSpecialType(SpecialType.System_Byte), TypedConstantKind.Primitive, value)));
}

internal virtual SynthesizedAttributeData SynthesizeNullableContextAttribute(ImmutableArray<TypedConstant> arguments)
{
// For modules, this attribute should be present. Only assemblies generate and embed this type.
return Compilation.TrySynthesizeAttribute(WellKnownMember.System_Runtime_CompilerServices_NullablePublicOnlyAttribute__ctor);
// https://github.com/dotnet/roslyn/issues/30062 Should not be optional.
return Compilation.TrySynthesizeAttribute(WellKnownMember.System_Runtime_CompilerServices_NullableContextAttribute__ctor, arguments, isOptionalUse: true);
}

internal virtual SynthesizedAttributeData SynthesizeNullablePublicOnlyAttribute(ImmutableArray<TypedConstant> arguments)
{
// For modules, this attribute should be present. Only assemblies generate and embed this type.
return Compilation.TrySynthesizeAttribute(WellKnownMember.System_Runtime_CompilerServices_NullablePublicOnlyAttribute__ctor, arguments);
}

protected virtual SynthesizedAttributeData TrySynthesizeIsReadOnlyAttribute()
Expand Down Expand Up @@ -1566,10 +1596,5 @@ internal void EnsureNullableAttributeExists()
{
EnsureEmbeddableAttributeExists(EmbeddableAttributes.NullableAttribute);
}

internal void EnsureNullablePublicOnlyAttributeExists()
{
EnsureEmbeddableAttributeExists(EmbeddableAttributes.NullablePublicOnlyAttribute);
}
}
}
Loading