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()
{