Skip to content

Handle T? constraint#27525

Merged
cston merged 22 commits intodotnet:features/NullableReferenceTypesfrom
cston:27289
Jun 28, 2018
Merged

Handle T? constraint#27525
cston merged 22 commits intodotnet:features/NullableReferenceTypesfrom
cston:27289

Conversation

@cston
Copy link
Contributor

@cston cston commented Jun 6, 2018

Binding of constraints is now split into two steps: early and late.

In the early step, constraint types are bound but with no additional checks. In the late step, constraint types are checked for validity and duplicates, and type parameter "bounds" (EffectiveBase and EffectiveInterface) are calculated.

Most TypeParameter properties simply ensure both steps have completed (as before), but IsValueType and IsReferenceType now only ensure the early step has completed.

Fixes #27289.

@cston cston requested a review from a team as a code owner June 6, 2018 17:51
@cston
Copy link
Contributor Author

cston commented Jun 7, 2018

@dotnet/roslyn-compiler please review. #Resolved

@jcouv jcouv added this to the 16.0 milestone Jun 7, 2018
@cston
Copy link
Contributor Author

cston commented Jun 12, 2018

@dotnet/roslyn-compiler please review. #Resolved

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Jun 12, 2018

@cston It looks like there are legitimate test failures #Resolved

@cston
Copy link
Contributor Author

cston commented Jun 13, 2018

@dotnet/roslyn-compiler please review, thanks.

_Describe the set of tests that affect flow state._

## `default`
`default(T)` is `T?` if `T` is a reference type. _Should `default(T?)` be an error?_
Copy link
Member

@jcouv jcouv Jun 14, 2018

Choose a reason for hiding this comment

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

reference type [](start = 33, length = 14)

It may be good to explain the meaning of T?, especially for unconstrained T.
Also, the phrasing may need to be adjusted "is a reference or unconstrained type".

Tangent: the null-state of null and the null-state of an instantiation (new) are trivial.

This spec change is unrelated to the PR. It may be better to have a separate PR for the spec change. #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a question about unconstrained type parameters.


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


internal static ImmutableArray<TypeParameterConstraintClause> MakeTypeParameterConstraints(
this SourceMethodSymbol containingSymbol,
internal static ImmutableArray<TypeParameterConstraintClause> MakeTypeParameterConstraintsEarly(
Copy link
Member

@jcouv jcouv Jun 14, 2018

Choose a reason for hiding this comment

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

MakeTypeParameterConstraintsEarly [](start = 70, length = 33)

Please add comments to clarify the purpose and rules around "early" vs. "late". #Closed

/// If a type parameter does not have constraints, the corresponding entry in the array is null.
/// </summary>
public abstract ImmutableArray<TypeParameterConstraintClause> TypeParameterConstraintClauses { get; }
public abstract ImmutableArray<TypeParameterConstraintClause> GetTypeParameterConstraintClauses(bool early);
Copy link
Member

@jcouv jcouv Jun 14, 2018

Choose a reason for hiding this comment

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

GetTypeParameterConstraintClauses [](start = 70, length = 33)

Please comment on early parameter #Closed

internal ImmutableArray<TypeSymbolWithAnnotations> GetConstraintTypesNoUseSiteDiagnostics(ConsList<TypeParameterSymbol> inProgress, bool early)
{
get
this.EnsureAllConstraintsAreResolved(early: true);
Copy link
Member

@jcouv jcouv Jun 14, 2018

Choose a reason for hiding this comment

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

EnsureAllConstraintsAreResolved [](start = 17, length = 31)

Is there a difference with just calling EnsureAllConstraintsAreResolved(early)? I would have assumed that calling with early : false would just do a superset of the work that calling with early : true does. #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The work for early: false is a superset of the early: true, but the early work should be completed for all type parameters before the late work is started for any type parameter. This split (two separate calls), needs to be done somewhere, and it was simpler to do here, in the caller of EnsuresAllConstraintsAreResolved rather than in several implementations of that abstract method. (This should be the only caller of that method that support early: false.)


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

Copy link
Member

@jcouv jcouv Jun 14, 2018

Choose a reason for hiding this comment

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

Thanks. Probably worth a comment #Closed

return this.GetConstraintTypes(inProgress, early);
}

internal ImmutableArray<TypeSymbolWithAnnotations> ConstraintTypesNoUseSiteDiagnostics => GetConstraintTypesNoUseSiteDiagnostics(ConsList<TypeParameterSymbol>.Empty, early: false);
Copy link
Member

@jcouv jcouv Jun 14, 2018

Choose a reason for hiding this comment

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

=> [](start = 94, length = 3)

Nit: consider breaking the line before => #Closed

/// order the callers query individual type parameters.
/// </summary>
internal abstract void EnsureAllConstraintsAreResolved();
internal abstract void EnsureAllConstraintsAreResolved(bool early);
Copy link
Member

@jcouv jcouv Jun 14, 2018

Choose a reason for hiding this comment

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

Please document early parameter #Closed

{
return true;
}
return IsReferenceTypeFromConstraintTypes(this.GetConstraintTypesNoUseSiteDiagnostics(inProgress, early: true), inProgress);
Copy link
Member

@jcouv jcouv Jun 14, 2018

Choose a reason for hiding this comment

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

early [](start = 110, length = 5)

Maybe early should be renamed to minValidation or limitedChecks? #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Perhaps, although early is the term used to differentiate phases of attribute binding so I re-used the term here.


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

@cston
Copy link
Contributor Author

cston commented Jun 25, 2018

@dotnet/roslyn-compiler please review.

var constraintTypes = currentBounds.ConstraintTypes;
if (!ContainingModule.UtilizesNullableReferenceTypes)
{
constraintTypes = constraintTypes.SelectAsArray(t => t.SetUnknownNullabilityForReferenceTypes());
Copy link
Member

@jcouv jcouv Jun 25, 2018

Choose a reason for hiding this comment

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

SetUnknownNullabilityForReferenceTypes [](start = 75, length = 38)

Can you add a PROTOTYPE marker: I suspect this will need to be revised in the presence of [NonNullTypes] in relevant scope. #Resolved

new C<A>();
}
}";
var comp2 = CreateCompilation(source2, parseOptions: TestOptions.Regular8, references: new[] { comp.EmitToImageReference() });
Copy link
Member

@jcouv jcouv Jun 25, 2018

Choose a reason for hiding this comment

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

CreateCompilation [](start = 24, length = 17)

Could you also verify diagnostics when compiling soure+source2? This also applies to other tests below. #Resolved

{
static void Main()
{
new B<A?>();
Copy link
Member

@jcouv jcouv Jun 25, 2018

Choose a reason for hiding this comment

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

B<A?> [](start = 12, length = 5)

I'd be tempted to also test permutations with a type derived from A, such as new B<AChild?>();, and so on. #Resolved

}

[Fact]
public void EmitAttribute_Constraint_Oblivious()
Copy link
Member

@jcouv jcouv Jun 25, 2018

Choose a reason for hiding this comment

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

EmitAttribute_Constraint_Oblivious [](start = 20, length = 34)

Please add a PROTOTYPE marker to test this with [NonNullTypes]. #Resolved

}

[Fact]
public void ConstraintErrorMultiplePartialDeclarations_01()
Copy link
Member

@jcouv jcouv Jun 25, 2018

Choose a reason for hiding this comment

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

ConstraintErrorMultiplePartialDeclarations_01 [](start = 20, length = 45)

Maybe test partial declarations with differences in ?. For instance, one with an I constraint, and the other with an I? constraint. #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See StaticNullChecking.PartialClassConstraintMismatch.


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

// (5,9): error CS0454: Circular constraint dependency involving 'T' and 'T'
// class E<T> where T : A, T? { }
Diagnostic(ErrorCode.ERR_CircularConstraint, "T").WithArguments("T", "T").WithLocation(5, 9));
}
Copy link
Member

@jcouv jcouv Jun 25, 2018

Choose a reason for hiding this comment

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

} [](start = 8, length = 1)

Is there any interesting scenario with nullability and possible unification on type arguments?
For instance, in where T : I<int>, I<U> we'll complain that U might unify with int.
But in where T : I<int>, I<U?> or where T : I<int?>, I<U?>, I think they cannot unify. #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added tests.


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

}

public static string ToTestDisplayString(this SymbolWithAnnotations symbol)
public static string ToTestDisplayString(this SymbolWithAnnotations symbol, bool includeNonNullable = false)
Copy link
Member

@jcouv jcouv Jun 25, 2018

Choose a reason for hiding this comment

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

ToTestDisplayString [](start = 29, length = 19)

Should this new extension method be moved to the "right" place (see comment on method above)? #ByDesign

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It looks like there are more callers of the extension method above than the extension method referenced in the comment. Perhaps the extension method in the comment should be replaced instead.


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

class E<T> where T : A, T? { }";
var comp = CreateCompilation(source, parseOptions: TestOptions.Regular8);
comp.VerifyDiagnostics(
// (4,30): error CS0701: 'T?' is not a valid constraint. A type used as a constraint must be an interface, a non-sealed class or a type parameter.
Copy link
Member

@jcouv jcouv Jun 25, 2018

Choose a reason for hiding this comment

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

error CS0701: 'T?' is not a valid constraint. A type used as a constraint must be an interface, a non-sealed class or a type parameter. [](start = 27, length = 135)

Should this diagnostic message be updated? #ByDesign

class C<V> where V : V?, V? { }";
var comp = CreateCompilation(source, parseOptions: TestOptions.Regular8);
comp.VerifyDiagnostics(
// (3,26): error CS0405: Duplicate constraint 'V' for type parameter 'V'
Copy link
Member

@jcouv jcouv Jun 25, 2018

Choose a reason for hiding this comment

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

V [](start = 63, length = 1)

Should we say "Duplicate constraint 'V?'..." instead? #Pending

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, top-level nullability should be included in the message. This requires allowing TypeSymbolWithAnnotations as a Diagnostic argument. I have that change in the next PR.


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

// (2,9): error CS0454: Circular constraint dependency involving 'U' and 'U'
// class B<U> where U : U?, U { }
Diagnostic(ErrorCode.ERR_CircularConstraint, "U").WithArguments("U", "U").WithLocation(2, 9));
}
Copy link
Member

@jcouv jcouv Jun 25, 2018

Choose a reason for hiding this comment

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

} [](start = 8, length = 1)

I don't remember the LDM decision on whether top-level nullability constraints need to agree. For instance, where T : I?, U where U : class. #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We're not checking nullability of constraints currently, but according to the notes from 4/25, nullability of constraints should agree. nullable-reference-types.md contains a reference to those notes for now.


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

// (8,40): error CS0701: 'T?' is not a valid constraint. A type used as a constraint must be an interface, a non-sealed class or a type parameter.
// void F3<T>() where T : struct, T? { }
Diagnostic(ErrorCode.ERR_BadBoundType, "T?").WithArguments("T?").WithLocation(8, 40),
// (9,32): error CS0246: The type or namespace name 'A' could not be found (are you missing a using directive or an assembly reference?)
Copy link
Member

@jcouv jcouv Jun 25, 2018

Choose a reason for hiding this comment

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

The type or namespace name 'A' [](start = 41, length = 30)

Is the error on missing type A intentional? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, not intentional.


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

void F3<T>() where T : struct, T? { }
void F4<T>() where T : A, T? { }
void F5<T, U>() where U : T? { }
void F6<T, U>() where T : class where U : T? { }
Copy link
Member

@jcouv jcouv Jun 25, 2018

Choose a reason for hiding this comment

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

F6 [](start = 13, length = 2)

It would be good to test the instantiations on F6 and F7 if they aren't already covered elsewhere.
Same in some tests below (like NullableTInConstraint_06).

Update: I think you're intentionally keeping that for a follow-up PR, which makes sense. #Resolved

### Null tests
_Describe the set of tests that affect flow state._

## `default`
Copy link
Member

@jcouv jcouv Jun 25, 2018

Choose a reason for hiding this comment

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

Please update the feature doc to explain T? constraints. #Resolved

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

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

LGTM with some test suggestions (iteration 17).

@jcouv jcouv self-assigned this Jun 25, 2018
_syntax.ConstraintClauses,
_syntax.Identifier.GetLocation(),
diagnostics);
lock (_declarationDiagnostics)
Copy link
Member

Choose a reason for hiding this comment

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

Consider another check to _lazyTypeParameterConstraints.IsDefault, as MakeTypeParameterConstraintsEarly is not a small function.

Copy link
Contributor Author

@cston cston Jun 28, 2018

Choose a reason for hiding this comment

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

If _lazyTypeParameterConstraints.IsDefault fails, another thread has already updated the field so there shouldn't be any lock contention from with that thread.

Will leave as is unless there are other concerns.


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

{
// Late step.
var diagnostics = DiagnosticBag.GetInstance();
var constraints = this.MakeTypeParameterConstraintsLate(TypeParameters, clauses, diagnostics);
Copy link
Member

Choose a reason for hiding this comment

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

Same suggestion on the check to _lazyTypeParameterConstraints.IsEarly() to avoid unnecessary lock contention.


var clauses = _lazyTypeParameterConstraints;
return (clauses.Length > 0) ? clauses[ordinal] : null;
return (clauses.Length > 0) ? clauses[ordinal] : TypeParameterConstraintClause.Empty;
Copy link
Member

@333fred 333fred Jun 27, 2018

Choose a reason for hiding this comment

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

We should probably add a comment to _lazyTypeParameterConstraints that multiple accesses like this are only safe if you copy it to a local variable first, as it's now a multi-stage initialization that could change between the first and second accesses. Same goes for the rest of the multi-stage initialization changes in this PR. #Resolved

Copy link
Member

@333fred 333fred left a comment

Choose a reason for hiding this comment

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

LGTM (commit 21), with some suggestions for followup. I am slightly concerned that future changes might introduce subtle threading issues with the double initialization strategy we're using, so I'd like us to at least comment about accessing those double-initialized fields now.

@cston cston merged commit a87a613 into dotnet:features/NullableReferenceTypes Jun 28, 2018
@cston cston deleted the 27289 branch June 28, 2018 05:49
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.

4 participants