Skip to content

Use struct for TypeSymbolWithAnnotations#28943

Merged
cston merged 16 commits intodotnet:features/NullableReferenceTypesfrom
cston:struct
Aug 2, 2018
Merged

Use struct for TypeSymbolWithAnnotations#28943
cston merged 16 commits intodotnet:features/NullableReferenceTypesfrom
cston:struct

Conversation

@cston
Copy link
Contributor

@cston cston commented Jul 30, 2018

The struct TypeSymbolWithAnnotations includes an allocation for less common cases only: for nullable type parameters; and when the type has associated custom modifiers.

@cston cston requested a review from a team as a code owner July 30, 2018 18:30
Debug.Assert(symbolOrTypeSymbolWithAnnotations != null);
Debug.Assert(!(symbolOrTypeSymbolWithAnnotations is TypeSymbol));
_symbolOrTypeSymbolWithAnnotations = symbolOrTypeSymbolWithAnnotations;
Debug.Assert(type == null != (symbol is null));
Copy link
Member

@jcouv jcouv Jul 30, 2018

Choose a reason for hiding this comment

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

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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)

Copy link
Member

Choose a reason for hiding this comment

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

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;
Copy link
Member

@jcouv jcouv Jul 31, 2018

Choose a reason for hiding this comment

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

_lazyType [](start = 24, length = 9)

This makes me nervous (race condition?) #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.

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) :
Copy link
Member

@jcouv jcouv Jul 31, 2018

Choose a reason for hiding this comment

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

NonNullTypesFalseContext.Instance [](start = 64, length = 33)

Feels like we should use the context from the containing method/symbol. #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.

NoReturnExpression is used as a placeholder only. And the TypeSymbol field is the only field used.


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

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 NonNullTypesUnusedContext.


In reply to: 206540166 [](ancestors = 206540166,206364611)

var returnType = inferredReturnType.Type;
if (returnType == null)
{
returnType = TypeSymbolWithAnnotations.CreateUnannotated(NonNullTypesFalseContext.Instance, LambdaSymbol.InferenceFailureReturnType);
Copy link
Member

@jcouv jcouv Jul 31, 2018

Choose a reason for hiding this comment

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

NonNullTypesFalseContext.Instance [](start = 73, length = 33)

Same here #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.

Similar to NoReturnExpression, this value is a placeholder only, with only TypeSymbol used.


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

Copy link
Member

Choose a reason for hiding this comment

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

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;
Copy link
Member

@jcouv jcouv Jul 31, 2018

Choose a reason for hiding this comment

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

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)
Copy link
Member

@jcouv jcouv Jul 31, 2018

Choose a reason for hiding this comment

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

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;
Copy link
Member

@jcouv jcouv Jul 31, 2018

Choose a reason for hiding this comment

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

NonNullTypesFalseContext.Instance [](start = 91, length = 33)

Can we pass a better context here too? #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.

Placeholder, for TypeSymbol only.


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

_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;
Copy link
Member

@jcouv jcouv Jul 31, 2018

Choose a reason for hiding this comment

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

return [](start = 12, length = 6)

Does anyone care for the return value? #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.

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));
Copy link
Member

@jcouv jcouv Jul 31, 2018

Choose a reason for hiding this comment

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

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);
Copy link
Member

@jcouv jcouv Jul 31, 2018

Choose a reason for hiding this comment

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

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);
Copy link
Member

@jcouv jcouv Jul 31, 2018

Choose a reason for hiding this comment

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

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;
Copy link
Member

@jcouv jcouv Jul 31, 2018

Choose a reason for hiding this comment

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

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;
Copy link
Member

@jcouv jcouv Jul 31, 2018

Choose a reason for hiding this comment

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

DefaultType and Implementation were not obvious. Maybe useful to rename and/or comment. #Closed

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.

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);
Copy link
Member

@jaredpar jaredpar Jul 31, 2018

Choose a reason for hiding this comment

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

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

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 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
Copy link
Member

@jaredpar jaredpar Jul 31, 2018

Choose a reason for hiding this comment

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

readonly struct? #Resolved

public override int GetHashCode(TypeSymbolWithAnnotations obj)
{
if (obj is null)
if (obj == null)
Copy link
Member

@jaredpar jaredpar Jul 31, 2018

Choose a reason for hiding this comment

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

The null check here is invalid now that TSWA is a struct #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.

This uses operator==(TypeSymbolWithAnnotations?, TypeSymbolWithAnnotations?). As @jcouv suggested, this might be easier to read as obj.IsDefault or some similar property.


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

{
return y is null;
return y == null;
}
Copy link
Member

@jaredpar jaredpar Jul 31, 2018

Choose a reason for hiding this comment

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

The null check is invalid now that TSWA is a struct #Resolved

@cston
Copy link
Contributor Author

cston commented Jul 31, 2018

@dotnet/roslyn-compiler please review.

private bool _isAnnotated;
private Extensions _extensions;

internal TypeSymbol DefaultType => _defaultType;
Copy link
Member

@jcouv jcouv Jul 31, 2018

Choose a reason for hiding this comment

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

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));
Copy link
Member

@jcouv jcouv Jul 31, 2018

Choose a reason for hiding this comment

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

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

Copy link
Contributor Author

@cston cston Jul 31, 2018

Choose a reason for hiding this comment

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

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)

Copy link
Member

Choose a reason for hiding this comment

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

Thanks for the clarification.


In reply to: 206635252 [](ancestors = 206635252,206631539)

}

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);
Copy link
Member

@jcouv jcouv Jul 31, 2018

Choose a reason for hiding this comment

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

=> [](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);
Copy link
Member

@jcouv jcouv Jul 31, 2018

Choose a reason for hiding this comment

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

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);
Copy link
Member

Choose a reason for hiding this comment

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

GetDebuggerDisplay [](start = 24, length = 18)

nit: would it be useful to display something about the extension too?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let's add that later, when it's more obvious what to display for _extension.


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

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 (iteration 7).
As we discussed, switching to IsNull will be even better. Thanks

}

private void AddNullableAnnotations(TypeSymbolWithAnnotations typeOpt)
private void AddNullableAnnotations(TypeSymbolWithAnnotations? typeOpt)
Copy link
Member

@jcouv jcouv Jul 31, 2018

Choose a reason for hiding this comment

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

TypeSymbolWithAnnotations? [](start = 44, length = 26)

Would it make sense to use a non-nullable TSWA, and use the default value for "nothing"? #Closed

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 Thanks! (iteration 10)

@jcouv jcouv self-assigned this Aug 1, 2018
/// <summary>
/// Set the fields of the builder.
/// </summary>
internal bool InterlockedInitialize(TypeSymbolWithAnnotations type)
Copy link
Member

@333fred 333fred Aug 1, 2018

Choose a reason for hiding this comment

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

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 :
Copy link
Member

@333fred 333fred Aug 1, 2018

Choose a reason for hiding this comment

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

Is this a valid case? Should we add an assert? #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.

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;
Copy link
Member

@333fred 333fred Aug 1, 2018

Choose a reason for hiding this comment

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

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

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 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
Copy link
Member

@333fred 333fred Aug 1, 2018

Choose a reason for hiding this comment

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

Consider renaming this file to match the type. #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.

Yes. I'll rename the file in a separate PR.


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

// another diagnostic for the same issue.
}
else if (!_alreadyReported[slot] && VariableType(symbol)?.IsErrorType() != true)
else if (!_alreadyReported[slot] && VariableType(symbol).IsErrorType() != true)
Copy link
Member

@333fred 333fred Aug 1, 2018

Choose a reason for hiding this comment

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

Should check for non-null and remove the != true #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.

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)

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 16)

@cston cston merged commit 14efbde into dotnet:features/NullableReferenceTypes Aug 2, 2018
@cston cston deleted the struct branch August 2, 2018 14:46
333fred added a commit to 333fred/roslyn that referenced this pull request Aug 6, 2018
… 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
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