diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 534ce82d634bc..03ced82fb1ebf 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -235,6 +235,72 @@ internal BoundExpression BindValueAllowArgList(ExpressionSyntax node, Diagnostic return CheckValue(result, valueKind, diagnostics); } + /// + /// Attempts to compute the type of the field for an auto property + /// with an initializer. + /// + /// + /// Attempts to change the type of the backing field to be a more + /// specific type than the property. The JIT can use this + /// type information when inlining to potentially devirtualize calls. + /// + internal static TypeSymbolWithAnnotations BindBackingFieldTypeOfAutoPropWithInitializer( + Binder binder, + PropertySymbol propertySymbol, + TypeSymbolWithAnnotations propertyType, + EqualsValueClauseSyntax initializerOpt, + DiagnosticBag diagnostics) + { + binder = new ExecutableCodeBinder(initializerOpt, propertySymbol, new LocalScopeBinder(binder)); + return binder.BindAutoPropBackingFieldType(propertyType, initializerOpt, diagnostics); + } + + internal TypeSymbolWithAnnotations BindAutoPropBackingFieldType( + TypeSymbolWithAnnotations propertyType, + EqualsValueClauseSyntax initializerOpt, + DiagnosticBag diagnostics) + { + if (!propertyType.IsReferenceType || propertyType.IsDynamic()) + { + //If the property is a reference type, no casting is possible, + //so don't change the field type. + return propertyType; + } + + Binder initializerBinder = this.GetBinder(initializerOpt); + Debug.Assert(initializerBinder != null); + + BindValueKind valueKind; + ExpressionSyntax value; + IsInitializerRefKindValid(initializerOpt, initializerOpt, RefKind.None, diagnostics, out valueKind, out value); + BoundExpression initializer = BindPossibleArrayInitializer(value, propertyType.TypeSymbol, valueKind, diagnostics); + + if (initializer.IsLiteralNull()) + { + //Conversion from literal null to reference types is an implicit reference conversion. + return propertyType; + } + + if (initializer.Type == null || !initializer.Type.IsReferenceType || initializer.Type.IsDynamic()) + { + //If it is not a reference type, it can't be an implicit reference conversion. + return propertyType; + } + + HashSet useSiteDiagnostics = null; + var conversion = this.Conversions.ClassifyConversionFromExpression(initializer, propertyType.TypeSymbol, ref useSiteDiagnostics); + diagnostics.Add(initializer.Syntax, useSiteDiagnostics); + + if (conversion.Kind != ConversionKind.NoConversion && conversion.Kind != ConversionKind.ImplicitReference) + { + //If the conversion is anything other than an implicit reference conversion, + //such as boxing, don't change the field type. + return propertyType; + } + + return TypeSymbolWithAnnotations.Create(initializer.Type); + } + internal BoundFieldEqualsValue BindFieldInitializer( FieldSymbol field, EqualsValueClauseSyntax initializerOpt, diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs index e84661baf5553..65619d12a7530 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs @@ -18,6 +18,7 @@ internal sealed class SynthesizedBackingFieldSymbol : FieldSymbolWithAttributesA private readonly string _name; internal bool HasInitializer { get; } protected override DeclarationModifiers Modifiers { get; } + private TypeSymbolWithAnnotations.Builder _lazyType; public SynthesizedBackingFieldSymbol( SourcePropertySymbol property, @@ -54,7 +55,40 @@ public override ImmutableArray Locations => _property.Locations; internal override TypeSymbolWithAnnotations GetFieldType(ConsList fieldsBeingBound) - => _property.Type; + { + if (!_lazyType.IsNull) + return _lazyType.ToType(); + + + var propType = _property.Type; + if (!HasInitializer || !IsReadOnly) + { + _lazyType.InterlockedInitialize(propType); + } + else + { + //Attempt to make the type of the backing field the same as the expression. + //A more specific field type can enable the CLR to devirtualize method calls + //while inlining the accessor method. + + var diagnostics = DiagnosticBag.GetInstance(); + var syntax = (PropertyDeclarationSyntax)_property.SyntaxReference.GetSyntax(); + var binderFactory = this.DeclaringCompilation.GetBinderFactory(_property.SyntaxTree); + var binder = binderFactory.GetBinder(syntax.Initializer); + var result = Binder.BindBackingFieldTypeOfAutoPropWithInitializer(binder, _property, propType, syntax.Initializer, diagnostics); + if (result.IsNull) + { + result = propType; + } + if (_lazyType.InterlockedInitialize(result)) + { + this.AddDeclarationDiagnostics(diagnostics); + } + diagnostics.Free(); + } + + return _lazyType.ToType(); + } internal override bool HasPointerType => _property.HasPointerType; diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/PropertyTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/PropertyTests.cs index 5379ad3b3f073..ea49d30da5718 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/PropertyTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/PropertyTests.cs @@ -192,6 +192,165 @@ public void AutoWithInitializerInClass() Assert.Null(r.SetMethod); } + [Fact] + public void AutoWithInitializerWithTypeConversion() + { + string text; + + //no conversion + text = @"class C +{ + Int32 P { get; } = 2; +}"; + BackingFieldChecker(text, "Int32"); + + //no conversion + text = @" +class A { } +class C +{ + A P { get; } = new A(); +}"; + BackingFieldChecker(text, "A"); + + //boxing conversion + text = @" +class C +{ + Object P { get; } = 2; +}"; + BackingFieldChecker(text, "Object"); + + //---- Implicit reference conversions ---- + + //"From any reference_type to object and dynamic." + text = @" +class A { } +class C +{ + dynamic P { get; } = new A(); +}"; + BackingFieldChecker(text, "dynamic"); + + //"From any class_type S to any class_type T, provided S is derived from T." + text = @" +class A { } +class B : A { } +class C +{ + A P { get; } = new B(); +}"; + BackingFieldChecker(text, "A", "B"); + + //"From any class_type S to any interface_type T, provided S implements T." + text = @" +interface A { } +class B : A { } +class C +{ + A P { get; } = new B(); +}"; + BackingFieldChecker(text, "A", "B"); + + //"From an array_type S with an element type SE to an array_type T with an element type TE, provided ..." + text = @" +class A { } +class B : A { } +class C +{ + A[] P { get; } = new B[0]; +}"; + BackingFieldChecker(text, "A[]", "B[]"); + + //"From any array_type to System.Array and the interfaces it implements." + text = @" +class A { } +class C +{ + Array P { get; } = new A[0]; +}"; + BackingFieldChecker(text, "Array", "A[]"); + + //"From a single-dimensional array type S[] to System.Collections.Generic.IList and its base interfaces..." + text = @" +class A { } +class C +{ + IList P { get; } = new A[0]; +}"; + BackingFieldChecker(text, "IList", "A[]"); + + //"From a single-dimensional array type S[] to System.Collections.Generic.IList and its base interfaces..." + text = @" +delegate A(); +class C +{ + Delegate P { get; } = new A(() => {}); +}"; + BackingFieldChecker(text, "Delegate", "A"); + + //"From the null literal to any reference_type." + text = @" +class A { } +class C +{ + A P { get; } = null; +}"; + BackingFieldChecker(text, "A"); + + //Variance conversion + text = @" +class A { } +class B : A { } +interface I { } +class C +{ + I P { get; } = CreateB(); + static I CreateB() => null; +}"; + BackingFieldChecker(text, "I", "I"); + + //"Implicit conversions involving type parameters that are known to be reference types." + text = @" +interface I { } +class C where T : class, I, new() +{ + I P { get; } = new T(); +}"; + BackingFieldChecker(text, "I", "T"); + + + void BackingFieldChecker(string source, string expectedPropertyType, string expectedFieldType = null) + { + Assert.NotNull(source); + Assert.NotNull(expectedPropertyType); + + //If no field type, assume it is the same as the property type. + if (expectedFieldType == null) + expectedFieldType = expectedPropertyType; + + var comp = CreateCompilation(source); + var global = comp.GlobalNamespace; + var c = global.GetTypeMember("C"); + + var p = c.GetMember("P"); + Assert.Equal(expectedPropertyType, p.Type.ToString()); + + //find the field without depending on the exact name + FieldSymbol fieldSym = null; + foreach (var mem in c.GetMembers()) + { + if (mem.Kind == SymbolKind.Field) + { + fieldSym = mem as FieldSymbol; + break; + } + } + Assert.NotNull(fieldSym); + Assert.Equal(expectedFieldType, fieldSym.Type.ToString()); + } + } + [Fact] public void AutoWithInitializerInStruct1() {