Skip to content

Commit

Permalink
Tests, adjustments, speclet update.
Browse files Browse the repository at this point in the history
  • Loading branch information
AlekseyTs committed Nov 16, 2018
1 parent 89ad74c commit 60ce669
Show file tree
Hide file tree
Showing 5 changed files with 470 additions and 46 deletions.
71 changes: 30 additions & 41 deletions docs/features/nullable-reference-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ In source, nullable reference types are annotated with `?`.
string? OptString; // may be null
Dictionary<string, object?>? OptDictionaryOptValues; // dictionary may be null, values may be null
```
A warning is reported when annotating a reference type or unconstrained generic type with `?` outside a `NonNullTypes(true)` context.
A warning is reported when annotating a reference type or unconstrained generic type with `?` outside a `#nullable` context.

In metadata, nullable reference types are annotated with a `[Nullable]` attribute.
```c#
Expand All @@ -22,54 +22,43 @@ namespace System.Runtime.CompilerServices
AllowMultiple = false)]
public sealed class NullableAttribute : Attribute
{
public NullableAttribute() { }
public NullableAttribute(bool[] b) { }
public NullableAttribute(byte b) { }
public NullableAttribute(byte[] b) { }
}
}
```
The parameter-less constructor is emitted for simple type references with top-level nullability and for type parameter definitions that have a `class?` constraint;
the constructor with `bool[]` parameter is emitted for type references with nested types and nullability.

Each type reference is accompanied by a NullableAttribute with an array of bytes, where 0 is Oblivious, 1 is NotAnnotated and 2 is Annotated.

To optimize trivial cases the attribute can be omitted, or instead can be replaced with an attribute that takes a single byte value rather than an array.

Trivial/optimized cases:
1) All parts are NotAnnotated – a NullableAttribute with a single value 1 (rather than an array of 1s)
2) All parts are Annotated - a NullableAttribute with a single value 2 (rather than an array of 2s)
3) All parts are Oblivious – the attribute is omitted, this matches how we interpret the lack of an attribute in legacy assemblies.
For completeness, we would also recognize a NullableAttribute with a single value 0 (rather than an array of 0s),
but compiler will never emit an attribute like this.

NullableAttribute(1) should be placed on a type parameter definition that has a `class!` constraint.
NullableAttribute(2) should be placed on a type parameter definition that has a `class?` constraint.
Other forms of NullableAttribute ar not emitted on type parameter definitions and are not specially recognized on them.

The `NullableAttribute` type declaration is synthesized by the compiler if it is not included in the compilation, but is needed to produce the output.

```c#
// C# representation of metadata
[Nullable]
[Nullable(2)]
string OptString; // string?
[Nullable(new[] { true, false, true })]
[Nullable(new[] { 2, 1, 2 })]
Dictionary<string, object> OptDictionaryOptValues; // Dictionary<string!, object?>?
string[] Oblivious1; // string~[]~
[Nullable(0)] string[] Oblivious2; // string~[]~
[Nullable(new[] { 0, 0 })] string[] Oblivious3; // string~[]~
[Nullable(1)] string[] NotNull1; // string![]!
[Nullable(new[] { 1, 1 })] string[] NotNull2; // string![]!
[Nullable(new[] { 0, 2 })] string[] ObliviousMaybeNull; // string?[]~
[Nullable(new[] { 1, 2 })] string[] NotNullMaybeNull; // string?[]!
```
The `NullableAttribute` type declaration is synthesized by the compiler if it is not included in the compilation.

Unannotated reference types are non-nullable or null-oblivious depending on whether the containing scope includes `[NonNullTypes]`.
```c#
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Delegate |
AttributeTargets.Enum |
AttributeTargets.Event |
AttributeTargets.Field |
AttributeTargets.Interface |
AttributeTargets.Method |
AttributeTargets.Module |
AttributeTargets.Property |
AttributeTargets.Struct,
AllowMultiple = false)]
internal sealed class NonNullTypesAttribute : Attribute
{
public NonNullTypesAttribute(bool enabled = true) { }
}
}
```
If there is no `[NonNullTypes]` attribute at any containing scope, including the module, reference types are null-oblivious.
```c#
[NonNullTypes(false)] string[] Oblivious; // string~[]~
[NonNullTypes(true)] string[] NotNull; // string![]!
[NonNullTypes(false), Nullable(new[] { false, true })] string[] ObliviousMaybeNull; // string?[]~
[NonNullTypes(true), Nullable(new[] { false, true })] string[] NotNullMaybeNull; // string?[]!
```
The `NonNullTypesAttribute` is always implicitly included in a compilation, but is only emitted if it is referenced in source. It cannot be referenced from metadata (assembly or module).
`NonNullTypesAttribute` can only be used in C# 8.0 compilations (or above).

## Declaration warnings
_Describe warnings reported for declarations in initial binding._

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1515,6 +1515,7 @@ internal SynthesizedAttributeData SynthesizeNullableAttribute(Symbol symbol, Typ

foreach (byte flag in flagsBuilder)
{
Debug.Assert(flag == (byte)NullableAnnotation.Unknown || flag == (byte)NullableAnnotation.NotNullable || flag == (byte)NullableAnnotation.Nullable);
constantsBuilder.Add(new TypedConstant(byteType, TypedConstantKind.Primitive, flag));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ private void AddNullableAnnotations(TypeSymbolWithAnnotations typeOpt)
}

if (format.MiscellaneousOptions.IncludesOption(SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier) &&
!typeOpt.IsNullableType() &&
!typeOpt.IsNullableType() && !typeOpt.IsValueType &&
(typeOpt.NullableAnnotation == NullableAnnotation.Nullable ||
(typeOpt.NullableAnnotation == NullableAnnotation.NullableBasedOnAnalysis && !typeOpt.TypeSymbol.IsUnconstrainedTypeParameter())))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ public string ToDisplayString(SymbolDisplayFormat format = null)
if (format != null)
{
if (format.MiscellaneousOptions.IncludesOption(SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier) &&
!IsNullableType() &&
!IsNullableType() && !IsValueType &&
(NullableAnnotation == NullableAnnotation.Nullable ||
(NullableAnnotation == NullableAnnotation.NullableBasedOnAnalysis && !TypeSymbol.IsUnconstrainedTypeParameter())))
{
Expand Down Expand Up @@ -833,13 +833,17 @@ public bool ApplyNullableTransforms(byte defaultTransformFlag, ImmutableArray<by
result = result.AsNotNullableReferenceType();
break;

default:
case NullableAnnotation.Unknown:
Debug.Assert((NullableAnnotation)transformFlag == NullableAnnotation.Unknown);
if (result.NullableAnnotation != NullableAnnotation.Unknown && (!result.NullableAnnotation.IsAnyNullable() || !oldTypeSymbol.IsNullableType()))
{
result = CreateNonLazyType(newTypeSymbol, NullableAnnotation.Unknown, result.CustomModifiers);
}
break;

default:
result = this;
return false;
}

return true;
Expand Down
Loading

0 comments on commit 60ce669

Please sign in to comment.