Skip to content

Commit

Permalink
Support nullable annotations on unconstrained type parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
cston committed Jul 16, 2020
1 parent 88bd81b commit 378b787
Show file tree
Hide file tree
Showing 55 changed files with 4,696 additions and 682 deletions.
40 changes: 34 additions & 6 deletions src/Compilers/CSharp/Portable/Binder/Binder_Constraints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ internal ImmutableArray<TypeParameterConstraintClause> BindTypeParameterConstrai
{
if (!reportedOverrideWithConstraints)
{
diagnostics.Add(ErrorCode.ERR_TypeConstraintsMustBeUniqueAndFirst, syntax.GetFirstToken().GetLocation());
reportTypeConstraintsMustBeUniqueAndFirst(syntax, diagnostics);
}

if (isForOverride && (constraints & (TypeParameterConstraintKind.ValueType | TypeParameterConstraintKind.ReferenceType)) != 0)
Expand Down Expand Up @@ -180,7 +180,7 @@ internal ImmutableArray<TypeParameterConstraintClause> BindTypeParameterConstrai
{
if (!reportedOverrideWithConstraints)
{
diagnostics.Add(ErrorCode.ERR_TypeConstraintsMustBeUniqueAndFirst, syntax.GetFirstToken().GetLocation());
reportTypeConstraintsMustBeUniqueAndFirst(syntax, diagnostics);
}

if (isForOverride && (constraints & (TypeParameterConstraintKind.ValueType | TypeParameterConstraintKind.ReferenceType)) != 0)
Expand Down Expand Up @@ -214,6 +214,29 @@ internal ImmutableArray<TypeParameterConstraintClause> BindTypeParameterConstrai

constraints |= TypeParameterConstraintKind.Constructor;
continue;
case SyntaxKind.DefaultConstraint:
CheckFeatureAvailability(syntax, MessageID.IDS_FeatureDefaultTypeParameterConstraint, diagnostics);

if (!isForOverride)
{
diagnostics.Add(ErrorCode.ERR_DefaultConstraintOverrideOnly, syntax.GetLocation());
}

if (i != 0)
{
if (!reportedOverrideWithConstraints)
{
reportTypeConstraintsMustBeUniqueAndFirst(syntax, diagnostics);
}

if (isForOverride && (constraints & (TypeParameterConstraintKind.ValueType | TypeParameterConstraintKind.ReferenceType)) != 0)
{
continue;
}
}

constraints |= TypeParameterConstraintKind.Default;
continue;
case SyntaxKind.TypeConstraint:
if (isForOverride)
{
Expand All @@ -239,7 +262,7 @@ internal ImmutableArray<TypeParameterConstraintClause> BindTypeParameterConstrai
case ConstraintContextualKeyword.Unmanaged:
if (i != 0)
{
diagnostics.Add(ErrorCode.ERR_TypeConstraintsMustBeUniqueAndFirst, typeSyntax.GetLocation());
reportTypeConstraintsMustBeUniqueAndFirst(typeSyntax, diagnostics);
continue;
}

Expand All @@ -253,7 +276,7 @@ internal ImmutableArray<TypeParameterConstraintClause> BindTypeParameterConstrai
case ConstraintContextualKeyword.NotNull:
if (i != 0)
{
diagnostics.Add(ErrorCode.ERR_TypeConstraintsMustBeUniqueAndFirst, typeSyntax.GetLocation());
reportTypeConstraintsMustBeUniqueAndFirst(typeSyntax, diagnostics);
}

constraints |= TypeParameterConstraintKind.NotNull;
Expand Down Expand Up @@ -292,6 +315,11 @@ static void reportOverrideWithConstraints(ref bool reportedOverrideWithConstrain
reportedOverrideWithConstraints = true;
}
}

static void reportTypeConstraintsMustBeUniqueAndFirst(CSharpSyntaxNode syntax, DiagnosticBag diagnostics)
{
diagnostics.Add(ErrorCode.ERR_TypeConstraintsMustBeUniqueAndFirst, syntax.GetLocation());
}
}

internal ImmutableArray<TypeParameterConstraintClause> GetDefaultTypeParameterConstraintClauses(TypeParameterListSyntax typeParameterList)
Expand Down Expand Up @@ -351,7 +379,7 @@ private static TypeParameterConstraintClause RemoveInvalidConstraints(
// since, in general, it may be difficult to support all invalid types.
// In the future, we may want to include some invalid types
// though so the public binding API has the most information.
if (Binder.IsValidConstraint(typeParameter.Name, syntax, constraintType, constraintClause.Constraints, constraintTypeBuilder, diagnostics))
if (IsValidConstraint(typeParameter.Name, syntax, constraintType, constraintClause.Constraints, constraintTypeBuilder, diagnostics))
{
CheckConstraintTypeVisibility(containingSymbol, syntax.Location, constraintType, diagnostics);
constraintTypeBuilder.Add(constraintType);
Expand Down Expand Up @@ -388,7 +416,7 @@ private static void CheckConstraintTypeVisibility(
/// Returns true if the constraint is valid. Otherwise
/// returns false and generates a diagnostic.
/// </summary>
internal static bool IsValidConstraint(
private static bool IsValidConstraint(
string typeParameterName,
TypeConstraintSyntax syntax,
TypeWithAnnotations type,
Expand Down
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1020,7 +1020,7 @@ private BoundPattern BindVarDesignation(
}
case SyntaxKind.SingleVariableDesignation:
{
var declType = TypeWithState.ForType(inputType).ToTypeWithAnnotations();
var declType = TypeWithState.ForType(inputType).ToTypeWithAnnotations(Compilation);
BindPatternDesignation(
designation: node, declType: declType, inputValEscape: inputValEscape, permitDesignations: permitDesignations,
typeSyntax: null, diagnostics: diagnostics, hasErrors: ref hasErrors,
Expand Down
19 changes: 16 additions & 3 deletions src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ NamespaceOrTypeOrAliasSymbolWithAnnotations bindNullable()

if (!ShouldCheckConstraints)
{
diagnostics.Add(new LazyUseSiteDiagnosticsInfoForNullableType(constructedType), syntax.GetLocation());
diagnostics.Add(new LazyUseSiteDiagnosticsInfoForNullableType(Compilation.LanguageVersion, constructedType), syntax.GetLocation());
}
else if (constructedType.IsNullableType())
{
Expand All @@ -521,9 +521,9 @@ NamespaceOrTypeOrAliasSymbolWithAnnotations bindNullable()
var location = syntax.Location;
type.CheckConstraints(new ConstraintsHelper.CheckConstraintsArgs(this.Compilation, this.Conversions, includeNullability: true, location, diagnostics));
}
else if (constructedType.Type.IsTypeParameterDisallowingAnnotation())
else if (GetNullableUnconstrainedTypeParameterDiagnosticIfNecessary(Compilation.LanguageVersion, constructedType) is { } diagnosticInfo)
{
diagnostics.Add(ErrorCode.ERR_NullableUnconstrainedTypeParameter, syntax.Location);
diagnostics.Add(diagnosticInfo, syntax.Location);
}

return constructedType;
Expand Down Expand Up @@ -571,6 +571,19 @@ NamespaceOrTypeOrAliasSymbolWithAnnotations createErrorType()
return TypeWithAnnotations.Create(CreateErrorType());
}
}

internal static CSDiagnosticInfo? GetNullableUnconstrainedTypeParameterDiagnosticIfNecessary(LanguageVersion languageVersion, in TypeWithAnnotations type)
{
if (type.Type.IsTypeParameterDisallowingAnnotation())
{
var requiredVersion = MessageID.IDS_FeatureDefaultTypeParameterConstraint.RequiredVersion();
if (requiredVersion > languageVersion)
{
return new CSDiagnosticInfo(ErrorCode.ERR_NullableUnconstrainedTypeParameter, new CSharpRequiredLanguageVersion(requiredVersion));
}
}
return null;
}
#nullable restore

private TypeWithAnnotations BindArrayType(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1493,12 +1493,14 @@ internal bool HasTopLevelNullabilityIdentityConversion(TypeWithAnnotations sourc
return true;
}

if (source.IsPossiblyNullableTypeTypeParameter() && !destination.IsPossiblyNullableTypeTypeParameter())
var sourceIsPossiblyNullableTypeParameter = IsPossiblyNullableTypeTypeParameter(source);
var destinationIsPossiblyNullableTypeParameter = IsPossiblyNullableTypeTypeParameter(destination);
if (sourceIsPossiblyNullableTypeParameter && !destinationIsPossiblyNullableTypeParameter)
{
return destination.NullableAnnotation.IsAnnotated();
}

if (destination.IsPossiblyNullableTypeTypeParameter() && !source.IsPossiblyNullableTypeTypeParameter())
if (destinationIsPossiblyNullableTypeParameter && !sourceIsPossiblyNullableTypeParameter)
{
return source.NullableAnnotation.IsAnnotated();
}
Expand All @@ -1523,14 +1525,21 @@ internal bool HasTopLevelNullabilityImplicitConversion(TypeWithAnnotations sourc
return true;
}

if (source.IsPossiblyNullableTypeTypeParameter() && !destination.IsPossiblyNullableTypeTypeParameter())
if (IsPossiblyNullableTypeTypeParameter(source) && !IsPossiblyNullableTypeTypeParameter(destination))
{
return false;
}

return !source.NullableAnnotation.IsAnnotated();
}

private static bool IsPossiblyNullableTypeTypeParameter(in TypeWithAnnotations typeWithAnnotations)
{
var type = typeWithAnnotations.Type;
return type is object &&
(type.IsPossiblyNullableReferenceTypeTypeParameter() || type.IsNullableTypeOrTypeParameter());
}

/// <summary>
/// Returns false if the source does not have an implicit conversion to the destination
/// because of either incompatible top level or nested nullability.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1541,9 +1541,9 @@ private bool ExactOrBoundsNullableInference(ExactOrBoundsKind kind, TypeWithAnno

return false;

// True if the type is nullable but not an unconstrained type parameter.
// True if the type is nullable.
bool isNullableOnly(TypeWithAnnotations type)
=> type.NullableAnnotation.IsAnnotated() && !type.Type.IsTypeParameterDisallowingAnnotation();
=> type.NullableAnnotation.IsAnnotated();
}

private bool ExactNullableInference(TypeWithAnnotations source, TypeWithAnnotations target, ref HashSet<DiagnosticInfo> useSiteDiagnostics)
Expand Down
13 changes: 11 additions & 2 deletions src/Compilers/CSharp/Portable/CSharpResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1363,7 +1363,7 @@
<value>The return type for ++ or -- operator must match the parameter type or be derived from the parameter type</value>
</data>
<data name="ERR_TypeConstraintsMustBeUniqueAndFirst" xml:space="preserve">
<value>The 'class', 'struct', 'unmanaged', and 'notnull' constraints cannot be combined or duplicated, and must be specified first in the constraints list.</value>
<value>The 'class', 'struct', 'unmanaged', 'notnull', and 'default' constraints cannot be combined or duplicated, and must be specified first in the constraints list.</value>
</data>
<data name="ERR_RefValBoundWithClass" xml:space="preserve">
<value>'{0}': cannot specify both a constraint class and the 'class' or 'struct' constraint</value>
Expand Down Expand Up @@ -5603,7 +5603,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<value>Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed.</value>
</data>
<data name="ERR_NullableUnconstrainedTypeParameter" xml:space="preserve">
<value>A nullable type parameter must be known to be a value type or non-nullable reference type. Consider adding a 'class', 'struct', or type constraint.</value>
<value>A nullable type parameter must be known to be a value type or non-nullable reference type. Please use language version '{0}' or greater, or consider adding a 'class', 'struct', or type constraint.</value>
</data>
<data name="ERR_NullableOptionNotAvailable" xml:space="preserve">
<value>Invalid '{0}' value: '{1}' for C# {2}. Please use language version '{3}' or greater.</value>
Expand Down Expand Up @@ -5731,6 +5731,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="IDS_FeatureNullPointerConstantPattern" xml:space="preserve">
<value>null pointer constant pattern</value>
</data>
<data name="IDS_FeatureDefaultTypeParameterConstraint" xml:space="preserve">
<value>default type parameter constraints</value>
</data>
<data name="ERR_WrongNumberOfSubpatterns" xml:space="preserve">
<value>Matching the tuple type '{0}' requires '{1}' subpatterns, but '{2}' subpatterns are present.</value>
</data>
Expand Down Expand Up @@ -6016,6 +6019,12 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ERR_OverrideValConstraintNotSatisfied" xml:space="preserve">
<value>Method '{0}' specifies a 'struct' constraint for type parameter '{1}', but corresponding type parameter '{2}' of overridden or explicitly implemented method '{3}' is not a non-nullable value type.</value>
</data>
<data name="ERR_OverrideDefaultConstraintNotSatisfied" xml:space="preserve">
<value>Method '{0}' specifies a 'default' constraint for type parameter '{1}', but corresponding type parameter '{2}' of overridden or explicitly implemented method '{3}' is constrained to a reference type or a value type.</value>
</data>
<data name="ERR_DefaultConstraintOverrideOnly" xml:space="preserve">
<value>The 'default' constraint is valid on override and explicit interface implementation methods only.</value>
</data>
<data name="IDS_OverrideWithConstraints" xml:space="preserve">
<value>constraints for override and explicit interface implementation methods</value>
</data>
Expand Down
2 changes: 2 additions & 0 deletions src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1834,6 +1834,8 @@ internal enum ErrorCode

ERR_StaticAnonymousFunctionCannotCaptureVariable = 8820,
ERR_StaticAnonymousFunctionCannotCaptureThis = 8821,
ERR_OverrideDefaultConstraintNotSatisfied = 8822,
ERR_DefaultConstraintOverrideOnly = 8823,

ERR_ExpressionTreeContainsWithExpression = 8849,
ERR_BadRecordDeclaration = 8850,
Expand Down
6 changes: 4 additions & 2 deletions src/Compilers/CSharp/Portable/Errors/LazyDiagnosticInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable enable

using System.Threading;

namespace Microsoft.CodeAnalysis.CSharp
{
internal abstract class LazyDiagnosticInfo : DiagnosticInfo
{
private DiagnosticInfo _lazyInfo;
private DiagnosticInfo? _lazyInfo;

protected LazyDiagnosticInfo()
: base(CSharp.MessageProvider.Instance, (int)ErrorCode.Unknown)
Expand All @@ -25,6 +27,6 @@ internal sealed override DiagnosticInfo GetResolvedInfo()
return _lazyInfo;
}

protected abstract DiagnosticInfo ResolveInfo();
protected abstract DiagnosticInfo? ResolveInfo();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,30 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable enable

using Microsoft.CodeAnalysis.CSharp.Symbols;

namespace Microsoft.CodeAnalysis.CSharp
{
internal sealed class LazyUseSiteDiagnosticsInfoForNullableType : LazyDiagnosticInfo
{
private readonly LanguageVersion _languageVersion;
private readonly TypeWithAnnotations _possiblyNullableTypeSymbol;

internal LazyUseSiteDiagnosticsInfoForNullableType(TypeWithAnnotations possiblyNullableTypeSymbol)
internal LazyUseSiteDiagnosticsInfoForNullableType(LanguageVersion languageVersion, TypeWithAnnotations possiblyNullableTypeSymbol)
{
_languageVersion = languageVersion;
_possiblyNullableTypeSymbol = possiblyNullableTypeSymbol;
}

protected override DiagnosticInfo ResolveInfo()
protected override DiagnosticInfo? ResolveInfo()
{
if (_possiblyNullableTypeSymbol.IsNullableType())
{
return _possiblyNullableTypeSymbol.Type.OriginalDefinition.GetUseSiteDiagnostic();
}
else if (_possiblyNullableTypeSymbol.Type.IsTypeParameterDisallowingAnnotation())
{
return new CSDiagnosticInfo(ErrorCode.ERR_NullableUnconstrainedTypeParameter);
}

return null;
return Binder.GetNullableUnconstrainedTypeParameterDiagnosticIfNecessary(_languageVersion, _possiblyNullableTypeSymbol);
}
}
}
2 changes: 2 additions & 0 deletions src/Compilers/CSharp/Portable/Errors/MessageID.cs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ internal enum MessageID
IDS_FeatureInitOnlySetters = MessageBase + 12781,
IDS_FeatureRecords = MessageBase + 12782,
IDS_FeatureNullPointerConstantPattern = MessageBase + 12783,
IDS_FeatureDefaultTypeParameterConstraint = MessageBase + 12784,
}

// Message IDs may refer to strings that need to be localized.
Expand Down Expand Up @@ -332,6 +333,7 @@ internal static LanguageVersion RequiredVersion(this MessageID feature)
case MessageID.IDS_FeatureInitOnlySetters: // semantic check
case MessageID.IDS_FeatureRecords:
case MessageID.IDS_FeatureStaticAnonymousFunction: // syntax check
case MessageID.IDS_FeatureDefaultTypeParameterConstraint:
return LanguageVersion.Preview;

// C# 8.0 features.
Expand Down
Loading

0 comments on commit 378b787

Please sign in to comment.