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

Use lightweight type constraint binding for IsValueType #48270

Closed
wants to merge 21 commits into from

Conversation

jcouv
Copy link
Member

@jcouv jcouv commented Oct 3, 2020

Fixes #41368 by building up on #47968

When binding type constraints for purpose of computing IsValueType, we don't need to bind the type arguments of the type constraints. We can bind the type constraints by using placeholder type arguments.
This allows making a fix to nullability rules for type substitution (also included in this PR), which we'd previously held off because of cycles.

We'll discuss whether this is better to take for 16.8 or 16.9. As the bootstrapping issue shows, the fix catches some nullability mismatches that were previously unreported, so it may be better to keep this change in early 16.9 so that VS and BCL can adjust accordingly (if needed).

Overview:

  • the central change is in BindGenericSimpleNamespaceOrTypeOrAliasSymbol in Binder_Symbols.cs
  • the fix for the substitution bug is in TypeWithAnnotations.SubstituteTypeCore
  • changed how we store _lazyTypeParameterConstraints (in NamedTypeSymbol and other) and _lazyBounds (in TypeParameterSymbol) is because bad constraints get filtered out. Otherwise, we had a bug where we were reporting diagnostics in the wrong phase (based on result of lightweight binding instead of full binding).

Note: there is a bootstrapping issue. The fix reveals some wrong annotations in Roslyn, but fixing them is a bit of a rabbit hole. Still working on that.


Update: putting this PR on hold for a short while. We think the nullability bug probably shouldn't be fixed by changing type substitution. So I'll take that part out of this PR.

@jcouv jcouv self-assigned this Oct 3, 2020
@jcouv jcouv force-pushed the constraint-cycle branch from 2f12174 to 36e873c Compare October 4, 2020 04:35
@jcouv jcouv force-pushed the constraint-cycle branch from 36e873c to 36cd621 Compare October 4, 2020 18:26
@jcouv jcouv marked this pull request as ready for review October 5, 2020 03:47
@jcouv jcouv requested review from a team as code owners October 5, 2020 03:47
return typeSymbol.Accept(ExpressionSyntaxGeneratorVisitor.Instance).WithAdditionalAnnotations(Simplifier.Annotation);
var expression = typeSymbol.Accept(ExpressionSyntaxGeneratorVisitor.Instance);
RoslynDebug.Assert(expression is not null);
return expression.WithAdditionalAnnotations(Simplifier.Annotation);
Copy link
Member

Choose a reason for hiding this comment

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

what happened here that needed this?

Copy link
Member Author

@jcouv jcouv Oct 5, 2020

Choose a reason for hiding this comment

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

@CyrusNajmabadi @tmat Please hold off on reviewing the IDE portion (it's not complete yet). I'll ping once the compiler portion is reviewed. Thanks
In short, ISerializerService.Deserialize is missing a ? annotation on the return type, and there are ripples from that. #Closed

@@ -30,7 +30,7 @@ internal interface ISerializerService : IWorkspaceService

void SerializeOptionSet(SerializableOptionSet options, ObjectWriter writer, CancellationToken cancellationToken);

T Deserialize<T>(WellKnownSynchronizationKind kind, ObjectReader reader, CancellationToken cancellationToken);
T? Deserialize<T>(WellKnownSynchronizationKind kind, ObjectReader reader, CancellationToken cancellationToken);
Copy link
Member

Choose a reason for hiding this comment

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

@tmat to confirm OOP serialization changes.

@jcouv jcouv requested a review from cston October 5, 2020 17:24
/// - an actual value, which may be from lightweight or full binding, depending on <see cref="TypeParameterBounds.UsedLightweightTypeConstraintBinding"/>
///
/// The distinction between <see cref="TypeParameterBounds.NullFromLightweightBinding"/> and null
/// helps us ensure that we do full binding at some point and thus produce diagnostics.
Copy link
Member

@cston cston Oct 5, 2020

Choose a reason for hiding this comment

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

What is an example where we need the distinction? (When would be have null from lightweight binding and produce diagnostics from full binding?) #ByDesign

Copy link
Member Author

@jcouv jcouv Oct 5, 2020

Choose a reason for hiding this comment

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

One scenario where the difference was visible was where T : Span<int>. The current diagnostic complains about Span<int> (and that constraint gets filtered out), but if we report the diagnostic from lightweight binding we'll complain about Span<T>. (see GenericArgsAndConstraints)
It's not a big difference, but also we may add more such scenarios, so I'm not sure we want to chance it. Also, if we decided to keep the previous design, we'd want to add comments, as this was surprising when I had to step through. #ByDesign

@@ -67,7 +67,7 @@ internal partial class Binder
Debug.Assert(ordinal < n);

(TypeParameterConstraintClause constraintClause, ArrayBuilder<TypeConstraintSyntax>? typeConstraintNodes) =
this.BindTypeParameterConstraints(typeParameterList.Parameters[ordinal], clause, isForOverride, canIgnoreNullableContext: canIgnoreNullableContext, diagnostics);
this.BindTypeParameterConstraints(typeParameterList.Parameters[ordinal], clause, isForOverride, canUseLightweightTypeConstraintBinding, diagnostics);
Copy link
Member

@cston cston Oct 5, 2020

Choose a reason for hiding this comment

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

canUseLightweightTypeConstraintBinding [](start = 120, length = 38)

Consider keeping the named argument since there are two bool parameters in a row. #Closed

@@ -119,7 +119,7 @@ internal partial class Binder
/// Bind and return a single type parameter constraint clause along with syntax nodes corresponding to type constraints.
/// </summary>
private (TypeParameterConstraintClause, ArrayBuilder<TypeConstraintSyntax>?) BindTypeParameterConstraints(
TypeParameterSyntax typeParameterSyntax, TypeParameterConstraintClauseSyntax constraintClauseSyntax, bool isForOverride, bool canIgnoreNullableContext, DiagnosticBag diagnostics)
TypeParameterSyntax typeParameterSyntax, TypeParameterConstraintClauseSyntax constraintClauseSyntax, bool isForOverride, bool canUseLightweightTypeConstraintBinding, DiagnosticBag diagnostics)
Copy link
Member

@cston cston Oct 5, 2020

Choose a reason for hiding this comment

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

canUseLightweightTypeConstraintBinding [](start = 138, length = 38)

Argument is not used. #Closed

@@ -1126,7 +1126,11 @@ private Symbol UnwrapAlias(Symbol symbol, out AliasSymbol alias, DiagnosticBag d
diagnostics, basesBeingResolved, qualifierOpt, node, plainName, node.Arity, options);
NamedTypeSymbol resultType;

if (isUnboundTypeExpr)
if ((Flags & BinderFlags.LightweightTypeConstraintBinding) != 0)
Copy link
Member

@cston cston Oct 5, 2020

Choose a reason for hiding this comment

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

(Flags & BinderFlags.LightweightTypeConstraintBinding) != 0 [](start = 16, length = 59)

Should this check be moved to the else block (after if (isUnboundTypeExpr))? #Closed

@@ -34,14 +38,20 @@ internal sealed class TypeParameterBounds
this.Interfaces = interfaces;
this.EffectiveBaseClass = effectiveBaseClass;
this.DeducedBaseType = deducedBaseType;
this.IgnoresNullableContext = ignoresNullableContext;
this.UsedLightweightTypeConstraintBinding = usedLightweightTypeConstraintBinding;
}

private TypeParameterBounds()
{
}
Copy link
Member

@cston cston Oct 5, 2020

Choose a reason for hiding this comment

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

Consider removing this constructor. #Closed

Copy link
Member Author

@jcouv jcouv Oct 5, 2020

Choose a reason for hiding this comment

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

It may be silly, but I didn't want to call passing an explicit usedLightweightTypeConstraintBinding: false value for the Unset constant, given that it has a very different meaning. #Closed

@@ -60,9 +60,9 @@ public static ImmutableArray<TypeWithAnnotations> CreateTypeArguments(ImmutableA
public static readonly ErrorTypeSymbol Instance = new UnboundArgumentErrorTypeSymbol(string.Empty, new CSDiagnosticInfo(ErrorCode.ERR_UnexpectedUnboundGenericName));

private readonly string _name;
private readonly DiagnosticInfo _errorInfo;
private readonly DiagnosticInfo? _errorInfo;
Copy link
Member

@cston cston Oct 5, 2020

Choose a reason for hiding this comment

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

DiagnosticInfo? [](start = 25, length = 15)

Is this change necessary? #Closed

Copy link
Member Author

@jcouv jcouv Oct 5, 2020

Choose a reason for hiding this comment

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

The change is correct (some instances are created with null) but not necessary. I'll undo so this PR is more focused. #Resolved

/// we'll just use these placeholders instead. That's good enough binding to compute
/// <see cref="TypeSymbol.IsValueType"/> with minimal binding.
/// </summary>
internal sealed class PlaceholderTypeArgumentSymbol : ErrorTypeSymbol
Copy link
Member

@cston cston Oct 5, 2020

Choose a reason for hiding this comment

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

PlaceholderTypeArgumentSymbol [](start = 26, length = 29)

Could we use UnboundArgumentErrorTypeSymbol instead? #ByDesign

Copy link
Member Author

@jcouv jcouv Oct 5, 2020

Choose a reason for hiding this comment

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

We could (and I did initially), but I felt that seeing a distinct type would be helpful to someone who ever sees this when debugging. It's clearly its own thing (I think having a distinct type is good).
I could try and share more code though... #ByDesign

{
if (!_lazyTypeParameterConstraints.HasValue(canIgnoreNullableContext))
// We're not using lightweight type constraint binding for local function because the risk of cycles is minimal (no overrides)
Copy link
Member

@333fred 333fred Oct 9, 2020

Choose a reason for hiding this comment

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

Man does this feel like a statement that's someone is going to look at in a few years and go "well that was wrong" #Resolved

Copy link
Contributor

Choose a reason for hiding this comment

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

I also prefer us not having this difference by comparison to other type parameters.


In reply to: 502597181 [](ancestors = 502597181)

@@ -253,25 +255,27 @@ private ImmutableArray<TypeParameterSymbol> MakeTypeParameters(DiagnosticBag dia
/// <summary>
/// Returns the constraint clause for the given type parameter.
/// </summary>
internal TypeParameterConstraintClause GetTypeParameterConstraintClause(bool canIgnoreNullableContext, int ordinal)
internal TypeParameterConstraintClause GetTypeParameterConstraintClause(bool canUseLightweightTypeConstraintBinding, int ordinal)
Copy link
Member

@333fred 333fred Oct 9, 2020

Choose a reason for hiding this comment

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

Consider nullable enabling this code. #Resolved

@@ -55,6 +56,33 @@ internal enum TypeParameterConstraintKind
AllNonNullableKinds = ReferenceType | ValueType | Constructor | Unmanaged,
}

internal sealed class TypeParameterConstraintClauses
Copy link
Member

@333fred 333fred Oct 9, 2020

Choose a reason for hiding this comment

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

Consider nullable enabling this. #Resolved

@jcouv
Copy link
Member Author

jcouv commented Oct 9, 2020

@cston Based on feedback from @AlekseyTs, I'm going to take a stab at simplifying the two-level caches. We'll go back to only caching the fully-bound constraints. IsValueType and IsReferenceType can be cached separately with less risk.
Let me know if you have a concern with that approach. #Closed

@cston
Copy link
Member

cston commented Oct 9, 2020

@cston Based on feedback from Aleksey, I'm going to take a stab at simplifying the two-level caches. We'll go back to only caching the fully-bound constraints. IsValueType and IsReferenceType can be cached separately with less risk.

Is there a reason to make that change in this PR? We're already binding with two levels based on canIgnoreNullableContext, so the change in this PR is limited to just those differences from binding type arguments.

If we want to change caching of IsValueType and IsReferenceType, that should be handled in a subsequent PR. #Closed

@jcouv
Copy link
Member Author

jcouv commented Oct 9, 2020

I'm fine to keep that for a subsequent PR. @AlekseyTs Would that be okay with you? #Closed

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Oct 10, 2020

Would that be okay with you?

I'd prefer reviewing one PR rather than two. Is the caching change going to be very disruptive? If it will be in a separate commit, it will be easy to review incrementally, if that is the reason. #Closed

/// </summary>
IgnoreNullableContext = 1u << 31,
LightweightTypeConstraintBinding = 1u << 31,
Copy link
Contributor

@AlekseyTs AlekseyTs Oct 12, 2020

Choose a reason for hiding this comment

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

LightweightTypeConstraintBinding [](start = 8, length = 32)

It looks like this flag is used in a function that is not specific to constraint binding, but rather it is our core code path to bind types in general. Let's keep the name of this flag indicating what that function should do, instead of reflecting mainly why. I suggest "SuppressTypeArgumentBinding", or something like that. #Resolved

@@ -113,9 +113,9 @@ internal enum BinderFlags : uint
InEEMethodBinder = 1 << 30,

/// <summary>
/// Assume '#nullable disable' context.
/// Skip binding type arguments in type constraints
Copy link
Contributor

@AlekseyTs AlekseyTs Oct 12, 2020

Choose a reason for hiding this comment

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

in type constraints [](start = 39, length = 20)

"Skip binding type arguments. For example, currently used when type constraints are bound in some scenarios."?
Also, consider describing what is used for type arguments when this flag is set. #Resolved

@@ -90,6 +90,14 @@ internal static class ConstraintsHelper
}

diagnosticsBuilder.Free();

if (bounds is null && canUseLightweightTypeConstraintBinding)
Copy link
Contributor

Choose a reason for hiding this comment

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

if (bounds is null && canUseLightweightTypeConstraintBinding) [](start = 12, length = 61)

Would we need this additional logic if we never cached "light-bound" constraints on a symbol?

@@ -55,6 +56,33 @@ internal enum TypeParameterConstraintKind
AllNonNullableKinds = ReferenceType | ValueType | Constructor | Unmanaged,
}

internal sealed class TypeParameterConstraintClauses
Copy link
Contributor

Choose a reason for hiding this comment

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

TypeParameterConstraintClauses [](start = 26, length = 30)

Would we need this type if we never cached "light-bound" constraints on a symbol? I would prefer us to never add it if we could do without.

@@ -468,6 +468,11 @@ internal TypeWithAnnotations SubstituteTypeCore(AbstractTypeMap typeMap)
return newTypeWithModifiers;
}

if (newTypeWithModifiers.Type is PlaceholderTypeArgumentSymbol)
Copy link
Contributor

@AlekseyTs AlekseyTs Oct 12, 2020

Choose a reason for hiding this comment

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

if (newTypeWithModifiers.Type is PlaceholderTypeArgumentSymbol) [](start = 12, length = 63)

Is this special case necessary? #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, without it we run into the following cycle:

  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Symbols.TypeParameterSymbol.IsValueType.get() Line 570 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbolExtensions.IsTypeParameterDisallowingAnnotationInCSharp8(Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol type) Line 68 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Symbols.TypeWithAnnotations.SubstituteTypeCore(Microsoft.CodeAnalysis.CSharp.Symbols.AbstractTypeMap typeMap) Line 499 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Symbols.TypeWithAnnotations.NonLazyType.SubstituteType(Microsoft.CodeAnalysis.CSharp.Symbols.TypeWithAnnotations type, Microsoft.CodeAnalysis.CSharp.Symbols.AbstractTypeMap typeMap) Line 920 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Symbols.TypeWithAnnotations.SubstituteType(Microsoft.CodeAnalysis.CSharp.Symbols.AbstractTypeMap typeMap) Line 434 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Symbols.AbstractTypeMap.SubstituteNamedType(Microsoft.CodeAnalysis.CSharp.Symbols.NamedTypeSymbol previous) Line 68 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Symbols.AbstractTypeMap.SubstituteNamedTypes(System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.CSharp.Symbols.NamedTypeSymbol> original) Line 416 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Symbols.SubstitutedNamedTypeSymbol.InterfacesNoUseSiteDiagnostics(Roslyn.Utilities.ConsList<Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol> basesBeingResolved) Line 154 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol.GetInterfaceInfo() Line 107 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol.GetAllInterfaces() Line 323 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol.AllInterfacesNoUseSiteDiagnostics.get() Line 206 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol.AllInterfacesWithDefinitionUseSiteDiagnostics(ref System.Collections.Generic.HashSet<Microsoft.CodeAnalysis.DiagnosticInfo> useSiteDiagnostics) Line 212 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Binder.GetBaseInterfaces(Microsoft.CodeAnalysis.CSharp.Symbols.NamedTypeSymbol type, Roslyn.Utilities.ConsList<Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol> basesBeingResolved, ref System.Collections.Generic.HashSet<Microsoft.CodeAnalysis.DiagnosticInfo> useSiteDiagnostics) Line 971 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Binder.LookupMembersInInterfaceOnly(Microsoft.CodeAnalysis.CSharp.LookupResult current, Microsoft.CodeAnalysis.CSharp.Symbols.NamedTypeSymbol type, string name, int arity, Roslyn.Utilities.ConsList<Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol> basesBeingResolved, Microsoft.CodeAnalysis.CSharp.LookupOptions options, Microsoft.CodeAnalysis.CSharp.Binder originalBinder, Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol accessThroughType, bool diagnose, ref System.Collections.Generic.HashSet<Microsoft.CodeAnalysis.DiagnosticInfo> useSiteDiagnostics) Line 962 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Binder.LookupMembersInInterface(Microsoft.CodeAnalysis.CSharp.LookupResult current, Microsoft.CodeAnalysis.CSharp.Symbols.NamedTypeSymbol type, string name, int arity, Roslyn.Utilities.ConsList<Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol> basesBeingResolved, Microsoft.CodeAnalysis.CSharp.LookupOptions options, Microsoft.CodeAnalysis.CSharp.Binder originalBinder, bool diagnose, ref System.Collections.Generic.HashSet<Microsoft.CodeAnalysis.DiagnosticInfo> useSiteDiagnostics) Line 1077 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Binder.LookupMembersInType(Microsoft.CodeAnalysis.CSharp.LookupResult result, Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol type, string name, int arity, Roslyn.Utilities.ConsList<Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol> basesBeingResolved, Microsoft.CodeAnalysis.CSharp.LookupOptions options, Microsoft.CodeAnalysis.CSharp.Binder originalBinder, bool diagnose, ref System.Collections.Generic.HashSet<Microsoft.CodeAnalysis.DiagnosticInfo> useSiteDiagnostics) Line 188 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Binder.LookupMembersInternal(Microsoft.CodeAnalysis.CSharp.LookupResult result, Microsoft.CodeAnalysis.CSharp.Symbols.NamespaceOrTypeSymbol nsOrType, string name, int arity, Roslyn.Utilities.ConsList<Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol> basesBeingResolved, Microsoft.CodeAnalysis.CSharp.LookupOptions options, Microsoft.CodeAnalysis.CSharp.Binder originalBinder, bool diagnose, ref System.Collections.Generic.HashSet<Microsoft.CodeAnalysis.DiagnosticInfo> useSiteDiagnostics) Line 174 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Binder.LookupSymbolsOrMembersInternal(Microsoft.CodeAnalysis.CSharp.LookupResult result, Microsoft.CodeAnalysis.CSharp.Symbols.NamespaceOrTypeSymbol qualifierOpt, string name, int arity, Roslyn.Utilities.ConsList<Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol> basesBeingResolved, Microsoft.CodeAnalysis.CSharp.LookupOptions options, bool diagnose, ref System.Collections.Generic.HashSet<Microsoft.CodeAnalysis.DiagnosticInfo> useSiteDiagnostics) Line 140 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Binder.LookupSymbolsSimpleName(Microsoft.CodeAnalysis.CSharp.LookupResult result, Microsoft.CodeAnalysis.CSharp.Symbols.NamespaceOrTypeSymbol qualifierOpt, string plainName, int arity, Roslyn.Utilities.ConsList<Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol> basesBeingResolved, Microsoft.CodeAnalysis.CSharp.LookupOptions options, bool diagnose, ref System.Collections.Generic.HashSet<Microsoft.CodeAnalysis.DiagnosticInfo> useSiteDiagnostics) Line 42 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Binder.BindNonGenericSimpleNamespaceOrTypeOrAliasSymbol(Microsoft.CodeAnalysis.CSharp.Syntax.IdentifierNameSyntax node, Microsoft.CodeAnalysis.DiagnosticBag diagnostics, Roslyn.Utilities.ConsList<Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol> basesBeingResolved, bool suppressUseSiteDiagnostics, Microsoft.CodeAnalysis.CSharp.Symbols.NamespaceOrTypeSymbol qualifierOpt) Line 871 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Binder.BindSimpleNamespaceOrTypeOrAliasSymbol(Microsoft.CodeAnalysis.CSharp.Syntax.SimpleNameSyntax syntax, Microsoft.CodeAnalysis.DiagnosticBag diagnostics, Roslyn.Utilities.ConsList<Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol> basesBeingResolved, bool suppressUseSiteDiagnostics, Microsoft.CodeAnalysis.CSharp.Symbols.NamespaceOrTypeSymbol qualifierOpt) Line 810 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Binder.BindQualifiedName(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax leftName, Microsoft.CodeAnalysis.CSharp.Syntax.SimpleNameSyntax rightName, Microsoft.CodeAnalysis.DiagnosticBag diagnostics, Roslyn.Utilities.ConsList<Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol> basesBeingResolved, bool suppressUseSiteDiagnostics) Line 1418 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Binder.BindNamespaceOrTypeOrAliasSymbol(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax syntax, Microsoft.CodeAnalysis.DiagnosticBag diagnostics, Roslyn.Utilities.ConsList<Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol> basesBeingResolved, bool suppressUseSiteDiagnostics) Line 423 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Binder.BindTypeOrAlias(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax syntax, Microsoft.CodeAnalysis.DiagnosticBag diagnostics, Roslyn.Utilities.ConsList<Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol> basesBeingResolved, bool suppressUseSiteDiagnostics) Line 317 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Binder.BindTypeOrAliasOrConstraintKeyword(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax syntax, Microsoft.CodeAnalysis.DiagnosticBag diagnostics, out Microsoft.CodeAnalysis.CSharp.Binder.ConstraintContextualKeyword keyword) Line 177 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Binder.BindTypeOrConstraintKeyword(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax syntax, Microsoft.CodeAnalysis.DiagnosticBag diagnostics, out Microsoft.CodeAnalysis.CSharp.Binder.ConstraintContextualKeyword keyword) Line 59 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Binder.BindTypeParameterConstraints(Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterSyntax typeParameterSyntax, Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterConstraintClauseSyntax constraintClauseSyntax, bool isForOverride, Microsoft.CodeAnalysis.DiagnosticBag diagnostics) Line 261 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Binder.BindTypeParameterConstraintClauses(Microsoft.CodeAnalysis.CSharp.Symbol containingSymbol, System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.CSharp.Symbols.TypeParameterSymbol> typeParameters, Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterListSyntax typeParameterList, Microsoft.CodeAnalysis.SyntaxList<Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterConstraintClauseSyntax> clauses, ref System.Collections.Generic.IReadOnlyDictionary<Microsoft.CodeAnalysis.CSharp.Symbols.TypeParameterSymbol, bool> isValueTypeOverride, Microsoft.CodeAnalysis.DiagnosticBag diagnostics, bool isForOverride) Line 68 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Symbols.SourceNamedTypeSymbol.MakeTypeParameterConstraints(bool canUseLightweightTypeConstraintBinding, Microsoft.CodeAnalysis.DiagnosticBag diagnostics) Line 333 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Symbols.SourceNamedTypeSymbol.GetTypeParameterConstraintClause(bool canUseLightweightTypeConstraintBinding, int ordinal) Line 265 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Symbols.SourceTypeParameterSymbol.GetTypeParameterConstraintClause(bool canUseLightweightTypeConstraintBinding) Line 573 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Symbols.SourceTypeParameterSymbol.GetDeclaredConstraints(bool canUseLightweightTypeConstraintBinding) Line 590 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Symbols.SourceTypeParameterSymbol.HasValueTypeConstraint.get() Line 513 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Symbols.TypeParameterSymbol.IsValueType.get() Line 570 C#


In reply to: 503331775 [](ancestors = 503331775)

}
else
{
newAnnotation = newTypeWithModifiers.NullableAnnotation;
newAnnotation = NullableAnnotation;
Copy link
Contributor

Choose a reason for hiding this comment

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

newAnnotation = NullableAnnotation; [](start = 20, length = 35)

If new annotation is oblivious, shouldn't that always win for typeSymbol.IsTypeParameterDisallowingAnnotationInCSharp8()?

Copy link
Member Author

Choose a reason for hiding this comment

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

Trying to answer this question, I tried to spec out the behavior of this method to make sure I understand it.

I wrote the matrix that I think we want: https://gist.github.com/jcouv/4dd5291e65a4cbffa83c03440427d557
Took a look with Chuck and made some small adjustments.
I would summarize this matrix as: (1) annotated original or substitution type yields annotated result, (2) otherwise, use the annotation from substitution type.

@AlekseyTs, does that look right to you too? I've included a legend to help decode that matrix, but would be happy to hop on a call to walk you through.


In reply to: 503339925 [](ancestors = 503339925)

@@ -489,18 +494,18 @@ internal TypeWithAnnotations SubstituteTypeCore(AbstractTypeMap typeMap)
{
newAnnotation = NullableAnnotation;
}
else if (NullableAnnotation != NullableAnnotation.Oblivious)
else if (NullableAnnotation.IsNotAnnotated())
Copy link
Contributor

Choose a reason for hiding this comment

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

else if (NullableAnnotation.IsNotAnnotated()) [](start = 12, length = 45)

Would it make sense to add a check if old and new annotations are equal, right before this condition?

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Oct 12, 2020

    private sealed class LazyNullableTypeParameter : Extensions

Do we still need this machinery? #ByDesign


Refers to: src/Compilers/CSharp/Portable/Symbols/TypeWithAnnotations.cs:937 in 618defa. [](commit_id = 618defa, deletion_comment = False)


public static ImmutableArray<TypeWithAnnotations> CreateTypeArguments(ImmutableArray<TypeParameterSymbol> typeParameters)
{
return typeParameters.SelectAsArray(_ => s_instance);
Copy link
Contributor

@AlekseyTs AlekseyTs Oct 12, 2020

Choose a reason for hiding this comment

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

SelectAsArray [](start = 34, length = 13)

Can we add a Repeate helper instead? All we are using from typeParameters is length.

Copy link
Member Author

Choose a reason for hiding this comment

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

We could, but I changed the code from populating an ArrayBuilder to using SelectAsArray in previous feedback. I don't think this is worth changing back again.


In reply to: 503346990 [](ancestors = 503346990)

Copy link
Contributor

Choose a reason for hiding this comment

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

I changed the code from populating an ArrayBuilder to using SelectAsArray in previous feedback. I don't think this is worth changing back again.

This isn't exactly what I am suggesting.


In reply to: 503602627 [](ancestors = 503602627,503346990)

}
}

internal override bool Equals(TypeSymbol t2, TypeCompareKind comparison, IReadOnlyDictionary<TypeParameterSymbol, bool>? isValueTypeOverrideOpt = null)
Copy link
Contributor

@AlekseyTs AlekseyTs Oct 12, 2020

Choose a reason for hiding this comment

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

Equals [](start = 31, length = 6)

Should we override GetHashCode? #Pending

Copy link
Member Author

Choose a reason for hiding this comment

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

I removed it based on earlier feedback.


In reply to: 503348130 [](ancestors = 503348130)

Copy link
Contributor

Choose a reason for hiding this comment

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

I removed it based on earlier feedback.

I think we should have it here.


In reply to: 503602842 [](ancestors = 503602842,503348130)


internal override bool Equals(TypeSymbol t2, TypeCompareKind comparison, IReadOnlyDictionary<TypeParameterSymbol, bool>? isValueTypeOverrideOpt = null)
{
return (object)t2 == this;
Copy link
Contributor

@AlekseyTs AlekseyTs Oct 12, 2020

Choose a reason for hiding this comment

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

(object)t2 == this [](start = 19, length = 18)

true? #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

If we returned true, then this symbol equals every other symbol.


In reply to: 503348621 [](ancestors = 503348621)

Copy link
Contributor

Choose a reason for hiding this comment

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

If we returned true, then this symbol equals every other symbol.

Right, I was thinking that all instances of this type should be considered equal to each other (since we have only one). But what I suggested clearly doesn't do that and it is probably not worth switching to a type check here.


In reply to: 503622221 [](ancestors = 503622221,503348621)

@@ -138511,7 +138566,14 @@ class B2 : A2
public override void F6<T>([DisallowNull] T t) { }
}";
comp = CreateCompilation(new[] { sourceB2, DisallowNullAttributeDefinition, NotNullAttributeDefinition }, references: new[] { refA });
comp.VerifyDiagnostics();
comp.VerifyDiagnostics(
// (14,26): warning CS8765: Nullability of type of parameter 't' doesn't match overridden member (possibly because of nullability attributes).
Copy link
Contributor

@AlekseyTs AlekseyTs Oct 12, 2020

Choose a reason for hiding this comment

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

// (14,26): warning CS8765: Nullability of type of parameter 't' doesn't match overridden member (possibly because of nullability attributes). [](start = 16, length = 142)

Is this warning caused by the attribute? Could we add an override without it to observe the fact? #Pending

@AlekseyTs
Copy link
Contributor

Done with review pass (iteration 20)

@jcouv
Copy link
Member Author

jcouv commented Oct 13, 2020

    private sealed class LazyNullableTypeParameter : Extensions

I tried removing it, but then we get into cycles. Example: static void M2<T, U>(U x, U? y) where U : T?

  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Symbols.TypeParameterSymbol.IsValueType.get() Line 570 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Symbols.TypeWithAnnotations.SetIsAnnotated(Microsoft.CodeAnalysis.CSharp.CSharpCompilation compilation) Line 196 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Binder.BindNamespaceOrTypeOrAliasSymbol.__bindNullable|941_1(ref Microsoft.CodeAnalysis.CSharp.Binder.<>c__DisplayClass941_0 value) Line 522 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Binder.BindNamespaceOrTypeOrAliasSymbol(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax syntax, Microsoft.CodeAnalysis.DiagnosticBag diagnostics, Roslyn.Utilities.ConsList<Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol> basesBeingResolved, bool suppressUseSiteDiagnostics) Line 406 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Binder.BindTypeOrAlias(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax syntax, Microsoft.CodeAnalysis.DiagnosticBag diagnostics, Roslyn.Utilities.ConsList<Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol> basesBeingResolved, bool suppressUseSiteDiagnostics) Line 317 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Binder.BindTypeOrAliasOrConstraintKeyword(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax syntax, Microsoft.CodeAnalysis.DiagnosticBag diagnostics, out Microsoft.CodeAnalysis.CSharp.Binder.ConstraintContextualKeyword keyword) Line 177 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Binder.BindTypeOrConstraintKeyword(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax syntax, Microsoft.CodeAnalysis.DiagnosticBag diagnostics, out Microsoft.CodeAnalysis.CSharp.Binder.ConstraintContextualKeyword keyword) Line 59 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Binder.BindTypeParameterConstraints(Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterSyntax typeParameterSyntax, Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterConstraintClauseSyntax constraintClauseSyntax, bool isForOverride, Microsoft.CodeAnalysis.DiagnosticBag diagnostics) Line 261 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Binder.BindTypeParameterConstraintClauses(Microsoft.CodeAnalysis.CSharp.Symbol containingSymbol, System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.CSharp.Symbols.TypeParameterSymbol> typeParameters, Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterListSyntax typeParameterList, Microsoft.CodeAnalysis.SyntaxList<Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterConstraintClauseSyntax> clauses, ref System.Collections.Generic.IReadOnlyDictionary<Microsoft.CodeAnalysis.CSharp.Symbols.TypeParameterSymbol, bool> isValueTypeOverride, Microsoft.CodeAnalysis.DiagnosticBag diagnostics, bool isForOverride) Line 68 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Symbols.ConstraintsHelper.MakeTypeParameterConstraints(Microsoft.CodeAnalysis.CSharp.Symbol containingSymbol, Microsoft.CodeAnalysis.CSharp.Binder binder, System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.CSharp.Symbols.TypeParameterSymbol> typeParameters, Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterListSyntax typeParameterList, Microsoft.CodeAnalysis.SyntaxList<Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterConstraintClauseSyntax> constraintClauses, bool canUseLightweightTypeConstraintBinding, Microsoft.CodeAnalysis.DiagnosticBag diagnostics) Line 350 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Symbols.SourceOrdinaryMethodSymbol.GetTypeParameterConstraintClauses(bool canUseLightweightTypeConstraintBinding) Line 296 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Symbols.SourceMethodTypeParameterSymbol.GetTypeParameterConstraintClause(bool canUseLightweightTypeConstraintBinding) Line 694 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Symbols.SourceMethodTypeParameterSymbol.GetDeclaredConstraints(bool canUseLightweightTypeConstraintBinding) Line 712 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Symbols.SourceMethodTypeParameterSymbol.HasValueTypeConstraint.get() Line 634 C#
  Microsoft.CodeAnalysis.CSharp.dll!Microsoft.CodeAnalysis.CSharp.Symbols.TypeParameterSymbol.IsValueType.get() Line 570 C#


In reply to: 707164189 [](ancestors = 707164189)


Refers to: src/Compilers/CSharp/Portable/Symbols/TypeWithAnnotations.cs:937 in 618defa. [](commit_id = 618defa, deletion_comment = False)

@AlekseyTs
Copy link
Contributor

    private sealed class LazyNullableTypeParameter : Extensions

I tried removing it, but then we get into cycles. Example: static void M2<T, U>(U x, U? y) where U : T?

Perhaps we should keep the part that ignores nullable context while constraints are bound?


In reply to: 707430606 [](ancestors = 707430606,707164189)


Refers to: src/Compilers/CSharp/Portable/Symbols/TypeWithAnnotations.cs:937 in 618defa. [](commit_id = 618defa, deletion_comment = False)

@AlekseyTs
Copy link
Contributor

        TypeParameterConstraintClause.AdjustConstraintTypes(containingSymbol, typeParameters, results, ref isValueTypeOverride);

It looks like here is yet another way to avoid cycles during constraint binding. Is it still needed?


Refers to: src/Compilers/CSharp/Portable/Binder/Binder_Constraints.cs:103 in 2273303. [](commit_id = 2273303, deletion_comment = False)

@jcouv
Copy link
Member Author

jcouv commented Oct 13, 2020

    private sealed class LazyNullableTypeParameter : Extensions

I thought about that. The stack trace above had that part of the code restored. The problem is that do need information about T to decide what T? means, but to do that we need to bind type constraints. That is still true when we do lightweight binding.


In reply to: 707443434 [](ancestors = 707443434,707430606,707164189)


Refers to: src/Compilers/CSharp/Portable/Symbols/TypeWithAnnotations.cs:937 in 618defa. [](commit_id = 618defa, deletion_comment = False)

@jcouv
Copy link
Member Author

jcouv commented Oct 13, 2020

        TypeParameterConstraintClause.AdjustConstraintTypes(containingSymbol, typeParameters, results, ref isValueTypeOverride);

Thanks, I'll take a look. That makes me think Equals also takes a dictionary for IsValueType information, I'll experiment to see if it is still needed.


In reply to: 707880199 [](ancestors = 707880199)


Refers to: src/Compilers/CSharp/Portable/Binder/Binder_Constraints.cs:103 in 2273303. [](commit_id = 2273303, deletion_comment = False)

@jcouv
Copy link
Member Author

jcouv commented Nov 11, 2020

Closing. This PR was subsumed by #49261

@jcouv jcouv closed this Nov 11, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Missing warnings on overriding a method with different nullability attributes
6 participants