Use struct for TypeSymbolWithAnnotations#28943
Use struct for TypeSymbolWithAnnotations#28943cston merged 16 commits intodotnet:features/NullableReferenceTypesfrom
Conversation
| Debug.Assert(symbolOrTypeSymbolWithAnnotations != null); | ||
| Debug.Assert(!(symbolOrTypeSymbolWithAnnotations is TypeSymbol)); | ||
| _symbolOrTypeSymbolWithAnnotations = symbolOrTypeSymbolWithAnnotations; | ||
| Debug.Assert(type == null != (symbol is null)); |
There was a problem hiding this comment.
type == null [](start = 29, length = 12)
How does type == null work? (or _type != null below)
Found public static bool operator ==(TypeSymbolWithAnnotations? x, TypeSymbolWithAnnotations? y).
I wonder if having a type.IsDefault would be clearer. #Resolved
There was a problem hiding this comment.
I've tried both: IsNull and operator==(?, ?). The latter seemed slightly better since the result was closer to the existing code. But I'm open to either approach.
In reply to: 206357806 [](ancestors = 206357806)
There was a problem hiding this comment.
It's not blocking this PR (ie. up to you), but I do think IsNull or IsDefault would be easier to follow. Already Jared and I tripped on this.
In reply to: 206539416 [](ancestors = 206539416,206357806)
| type = type.WithTypeAndModifiers( | ||
| CustomModifierUtils.CopyTypeCustomModifiers(overriddenPropertyType.TypeSymbol, type.TypeSymbol, this.ContainingAssembly, nonNullTypesContext: this), | ||
| overriddenPropertyType.CustomModifiers); | ||
| _lazyType = default; |
There was a problem hiding this comment.
_lazyType [](start = 24, length = 9)
This makes me nervous (race condition?) #Closed
There was a problem hiding this comment.
This assignment is in the .ctor, so the field is not available to other threads.
In reply to: 206363023 [](ancestors = 206363023)
| var expression = node.ExpressionOpt; | ||
| var type = (expression is null) ? | ||
| NoReturnExpression : | ||
| TypeSymbolWithAnnotations.CreateUnannotated(NonNullTypesFalseContext.Instance, NoReturnExpression) : |
There was a problem hiding this comment.
NonNullTypesFalseContext.Instance [](start = 64, length = 33)
Feels like we should use the context from the containing method/symbol. #Closed
There was a problem hiding this comment.
NoReturnExpression is used as a placeholder only. And the TypeSymbol field is the only field used.
In reply to: 206364611 [](ancestors = 206364611)
There was a problem hiding this comment.
| var returnType = inferredReturnType.Type; | ||
| if (returnType == null) | ||
| { | ||
| returnType = TypeSymbolWithAnnotations.CreateUnannotated(NonNullTypesFalseContext.Instance, LambdaSymbol.InferenceFailureReturnType); |
There was a problem hiding this comment.
NonNullTypesFalseContext.Instance [](start = 73, length = 33)
Same here #Resolved
There was a problem hiding this comment.
Similar to NoReturnExpression, this value is a placeholder only, with only TypeSymbol used.
In reply to: 206364705 [](ancestors = 206364705)
There was a problem hiding this comment.
Still, I think we should consider it. It probably doesn't make a big difference, but it will simplify the upcoming scrub (to make sure we pass proper contexts everywhere).
In reply to: 206540785 [](ancestors = 206540785,206364705)
| case ConversionKind.ExplicitDynamic: | ||
| case ConversionKind.ImplicitDynamic: | ||
| isNullableIfReferenceType = operandType?.IsNullable; | ||
| isNullableIfReferenceType = operandType == null ? null : operandType.IsNullable; |
There was a problem hiding this comment.
operandType == null ? null : operandType.IsNullable [](start = 48, length = 51)
Seems equivalent to the original code Never mind #Closed
| var mapOrType = _mapOrType; | ||
| var type = mapOrType as TypeSymbolWithAnnotations; | ||
| if ((object)type != null) | ||
| if (mapOrType is TypeSymbolWithAnnotations) |
There was a problem hiding this comment.
if (mapOrType is TypeSymbolWithAnnotations) [](start = 16, length = 43)
nit: if (mapOrType is TypeSymbolWithAnnotations typeSymbolWithAnnotations) return typeSymbolWithAnnotations; #Closed
| _syntax = unboundLambda.Syntax; | ||
| _refKind = refKind; | ||
| _returnType = returnType ?? ReturnTypeIsBeingInferred; | ||
| _returnType = returnType == null ? TypeSymbolWithAnnotations.CreateUnannotated(NonNullTypesFalseContext.Instance, ReturnTypeIsBeingInferred) : returnType; |
There was a problem hiding this comment.
NonNullTypesFalseContext.Instance [](start = 91, length = 33)
Can we pass a better context here too? #Closed
There was a problem hiding this comment.
| _isAnnotated = type.IsAnnotated; | ||
| Interlocked.CompareExchange(ref _nonNullTypesContext, type.NonNullTypesContext, null); | ||
| Interlocked.CompareExchange(ref _implementation, type.Implementation, null); | ||
| return (object)Interlocked.CompareExchange(ref _defaultType, type.DefaultType, null) == null; |
There was a problem hiding this comment.
return [](start = 12, length = 6)
Does anyone care for the return value? #Closed
There was a problem hiding this comment.
Yes, several callers decide whether to perform additional checks based on the return value.
In reply to: 206366336 [](ancestors = 206366336)
|
|
||
| internal static TypeSymbolWithAnnotations CreateLazyNullableType(CSharpCompilation compilation, TypeSymbolWithAnnotations underlying) | ||
| { | ||
| return new TypeSymbolWithAnnotations(defaultType: underlying.DefaultType, nonNullTypesContext: null, isAnnotated: true, TypeSymbolWithAnnotationsImplementation.CreateLazy(compilation, underlying)); |
There was a problem hiding this comment.
null [](start = 107, length = 4)
The support for nonNullTypesContext = null is temporary. Could we avoid adding more scenarios that rely on it?
Actually, I think TypeSymbolWithAnnotations should not allow null nonNullTypesContext. See line 146 for solution.
Please add an assertion to the constructor too. Thanks #Closed
| Debug.Assert((object)typeSymbol != null); | ||
| Debug.Assert(!customModifiers.IsDefault); | ||
| Debug.Assert(!typeSymbol.IsNullableType() || isAnnotated); | ||
| Debug.Assert(nonNullTypesContext != null); |
There was a problem hiding this comment.
Debug.Assert(nonNullTypesContext != null); [](start = 16, length = 42)
I think we should keep this assert #Closed
| { | ||
| Debug.Assert((object)typeSymbol != null); | ||
| Debug.Assert(!customModifiers.IsDefault); | ||
| Debug.Assert(!typeSymbol.IsNullableType() || isAnnotated); |
There was a problem hiding this comment.
This assertions also seems useful to keep #Closed
| /// true for int?, T? where T : struct. | ||
| /// </summary> | ||
| public abstract bool IsAnnotated { get; } | ||
| public readonly bool IsAnnotated; |
There was a problem hiding this comment.
IsAnnotated [](start = 29, length = 11)
Let's move those fields to the top. Thanks #Closed
| internal struct TypeSymbolWithAnnotations : IFormattable | ||
| { | ||
| public sealed override string ToString() => TypeSymbol.ToString(); | ||
| internal readonly TypeSymbol DefaultType; |
There was a problem hiding this comment.
DefaultType and Implementation were not obvious. Maybe useful to rename and/or comment. #Closed
jcouv
left a comment
There was a problem hiding this comment.
Done with most files except SymbolWithAnnotations. I'll finish tomorrow.
| } | ||
| _isAnnotated = type.IsAnnotated; | ||
| Interlocked.CompareExchange(ref _nonNullTypesContext, type.NonNullTypesContext, null); | ||
| Interlocked.CompareExchange(ref _implementation, type.Implementation, null); |
There was a problem hiding this comment.
Don't think we can safely use CompareExchange here. The fields of a struct can always be assigned directly by way of simple struct assignment. Example
TSWABuilder t1 = someOtherTSWABuilder
The use of CompareExchange here is only safe if we also categorically disable assignment of these builder types. #ByDesign
There was a problem hiding this comment.
The builder is intended for use for lazy type calculations in Symbol implementations. Those callers are responsible for hiding the underlying field and ensuring access is otherwise thread-safe, as they are today.
In reply to: 206542229 [](ancestors = 206542229)
| /// </summary> | ||
| [DebuggerDisplay("{GetDebuggerDisplay(), nq}")] | ||
| internal abstract class TypeSymbolWithAnnotations : IFormattable | ||
| internal struct TypeSymbolWithAnnotations : IFormattable |
| public override int GetHashCode(TypeSymbolWithAnnotations obj) | ||
| { | ||
| if (obj is null) | ||
| if (obj == null) |
There was a problem hiding this comment.
The null check here is invalid now that TSWA is a struct #Resolved
| { | ||
| return y is null; | ||
| return y == null; | ||
| } |
There was a problem hiding this comment.
The null check is invalid now that TSWA is a struct #Resolved
|
@dotnet/roslyn-compiler please review. |
| private bool _isAnnotated; | ||
| private Extensions _extensions; | ||
|
|
||
| internal TypeSymbol DefaultType => _defaultType; |
There was a problem hiding this comment.
DefaultType [](start = 32, length = 11)
Consider xml doc on the builder's properties as well. That'll be useful for QuickInfo in code. #Closed
|
|
||
| internal static TypeSymbolWithAnnotations CreateLazyNullableType(CSharpCompilation compilation, TypeSymbolWithAnnotations underlying) | ||
| { | ||
| return new TypeSymbolWithAnnotations(defaultType: underlying._defaultType, nonNullTypesContext: underlying.NonNullTypesContext, isAnnotated: true, Extensions.CreateLazy(compilation, underlying)); |
There was a problem hiding this comment.
Extensions.CreateLazy(compilation, underlying) [](start = 159, length = 46)
It seems we're still allocating for every TSWA.
To save on allocations, I assumed we'd have null extensions for the common case. But that doesn't seem to be the current design. Would that be possible? #Closed
There was a problem hiding this comment.
We're allocating for every TypeSymbolWithAnnotations that represent either an annotated type parameter (this case) or a type with associated custom modifiers. For other cases, there shouldn't be allocations. (Those other cases use the shared Extensions.Default value.)
In reply to: 206631539 [](ancestors = 206631539)
There was a problem hiding this comment.
| } | ||
|
|
||
| public virtual void ReportDiagnosticsIfObsolete(Binder binder, SyntaxNode syntax, DiagnosticBag diagnostics) | ||
| public void ReportDiagnosticsIfObsolete(Binder binder, SyntaxNode syntax, DiagnosticBag diagnostics) => _extensions.ReportDiagnosticsIfObsolete(this, binder, syntax, diagnostics); |
There was a problem hiding this comment.
=> [](start = 109, length = 2)
nit: I don't think we've established a style, but I'd suggest breaking the lines with =>, especially the longer ones (if not all).
public void ReportDiagnosticsIfObsolete(Binder binder, SyntaxNode syntax, DiagnosticBag diagnostics)
=> _extensions.ReportDiagnosticsIfObsolete(this, binder, syntax, diagnostics);
``` #Resolved| return new LazyNullableTypeParameter(compilation, underlying); | ||
| } | ||
|
|
||
| public abstract TypeSymbol GetResolvedType(TypeSymbol defaultType); |
There was a problem hiding this comment.
public [](start = 12, length = 6)
nit: there's a mix of internal and public. Consider standardizing on one (my preference is internal, but that's up to you). #Resolved
| } | ||
|
|
||
| internal string GetDebuggerDisplay() => ToDisplayString(DebuggerDisplayFormat); | ||
| internal string GetDebuggerDisplay() => _defaultType is null ? "null" : ToDisplayString(DebuggerDisplayFormat); |
There was a problem hiding this comment.
GetDebuggerDisplay [](start = 24, length = 18)
nit: would it be useful to display something about the extension too?
There was a problem hiding this comment.
Let's add that later, when it's more obvious what to display for _extension.
In reply to: 206655276 [](ancestors = 206655276)
jcouv
left a comment
There was a problem hiding this comment.
LGTM (iteration 7).
As we discussed, switching to IsNull will be even better. Thanks
| } | ||
|
|
||
| private void AddNullableAnnotations(TypeSymbolWithAnnotations typeOpt) | ||
| private void AddNullableAnnotations(TypeSymbolWithAnnotations? typeOpt) |
There was a problem hiding this comment.
TypeSymbolWithAnnotations? [](start = 44, length = 26)
Would it make sense to use a non-nullable TSWA, and use the default value for "nothing"? #Closed
| /// <summary> | ||
| /// Set the fields of the builder. | ||
| /// </summary> | ||
| internal bool InterlockedInitialize(TypeSymbolWithAnnotations type) |
There was a problem hiding this comment.
We should add a comment that we're ok with caller A setting some fields and caller B setting other fields, and that it's not guaranteed that the exact same caller will set all fields of the builder.
. #Closed
| internal TypeSymbolWithAnnotations ToType() | ||
| { | ||
| return (object)_defaultType == null ? | ||
| default : |
There was a problem hiding this comment.
Is this a valid case? Should we add an assert? #ByDesign
There was a problem hiding this comment.
This is a valid case, when the TypeSymbolWithAnnotations is used to represent a null type.
In reply to: 207051084 [](ancestors = 207051084)
|
|
||
| /// <summary> | ||
| /// Returns: | ||
| /// false for string, int, T; |
There was a problem hiding this comment.
T [](start = 35, length = 1)
Can return true for unannotated T after adjusting in nullablewalker (and if we move up checking for unconstrained type parameters to initial binding, potentially before #Closed
There was a problem hiding this comment.
We may want a different field than IsAnnotated in that case, or a renamed field, since T was unannotated. Let's revisit this comment then.
In reply to: 207051401 [](ancestors = 207051401)
| /// </summary> | ||
| [DebuggerDisplay("{GetDebuggerDisplay(), nq}")] | ||
| internal abstract class TypeSymbolWithAnnotations : IFormattable | ||
| internal readonly struct TypeSymbolWithAnnotations : IFormattable |
There was a problem hiding this comment.
Consider renaming this file to match the type. #Closed
There was a problem hiding this comment.
| // another diagnostic for the same issue. | ||
| } | ||
| else if (!_alreadyReported[slot] && VariableType(symbol)?.IsErrorType() != true) | ||
| else if (!_alreadyReported[slot] && VariableType(symbol).IsErrorType() != true) |
There was a problem hiding this comment.
Should check for non-null and remove the != true #Closed
There was a problem hiding this comment.
Changed the != true, but did not add the null check since I think you can only get here with a symbol that has a type.
In reply to: 207056599 [](ancestors = 207056599)
… into unconstrained-type-parameter-nullability * dotnet/features/NullableReferenceTypes: Include nullability in CheckConstraints (dotnet#28959) Use struct for TypeSymbolWithAnnotations (dotnet#28943) Publish Linux Test Results Force retest Migrate Linux tests to VSTS Port determinism fixes Make sure TLS 1.2 is used to fetch from https://dot.net Move to language version 8 Make mutex creation more robust Disable icacls use on Helix Disable leak detection tests on x64 VSTS YAML file
The
struct TypeSymbolWithAnnotationsincludes an allocation for less common cases only: for nullable type parameters; and when the type has associated custom modifiers.