diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs index e4cb98bbdd6c8..378659df8c64d 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs @@ -118,6 +118,17 @@ protected BoundExpression CreateConversion( return ConvertConditionalExpression((BoundUnconvertedConditionalOperator)source, destination, conversionIfTargetTyped: conversion, diagnostics); } + if (conversion.Kind == ConversionKind.InterpolatedString) + { + var unconvertedSource = (BoundUnconvertedInterpolatedString)source; + source = new BoundInterpolatedString( + unconvertedSource.Syntax, + unconvertedSource.Parts, + unconvertedSource.ConstantValue, + unconvertedSource.Type, + unconvertedSource.HasErrors); + } + if (source.Kind == BoundKind.UnconvertedSwitchExpression) { TypeSymbol? type = source.Type; diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 5b5b77f6c6019..f5875a434d382 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -374,6 +374,18 @@ internal BoundExpression BindToNaturalType(BoundExpression expression, BindingDi result = BindObjectCreationForErrorRecovery(expr, diagnostics); } break; + case BoundUnconvertedInterpolatedString unconvertedInterpolatedString: + { + // We determine the best method of emitting as a string (either via Concat, Format, or the builder pattern) + // during lowering, and it's not part of the publicly-visible API, unlike conversion to a builder type. + result = new BoundInterpolatedString( + unconvertedInterpolatedString.Syntax, + unconvertedInterpolatedString.Parts, + unconvertedInterpolatedString.ConstantValue, + unconvertedInterpolatedString.Type, + unconvertedInterpolatedString.HasErrors); + } + break; default: result = expression; break; diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_InterpolatedString.cs b/src/Compilers/CSharp/Portable/Binder/Binder_InterpolatedString.cs index 8cac49a57b338..963438c5abdf7 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_InterpolatedString.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_InterpolatedString.cs @@ -132,7 +132,7 @@ private BoundExpression BindInterpolatedString(InterpolatedStringExpressionSynta } Debug.Assert(isResultConstant == (resultConstant != null)); - return new BoundInterpolatedString(node, builder.ToImmutableAndFree(), resultConstant, stringType); + return new BoundUnconvertedInterpolatedString(node, builder.ToImmutableAndFree(), resultConstant, stringType); } } } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs index 91d809433cfda..09312a3c0b8d1 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs @@ -299,7 +299,7 @@ private BoundExpression BindExpressionForPatternContinued( BoundExpression convertedExpression = ConvertPatternExpression( inputType, patternExpression, expression, out constantValueOpt, hasErrors, diagnostics); - ConstantValueUtils.CheckLangVersionForConstantValue(expression, diagnostics); + ConstantValueUtils.CheckLangVersionForConstantValue(convertedExpression, diagnostics); if (!convertedExpression.HasErrors && !hasErrors) { diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversions.cs index 74b8f674901e3..f3d402007ac8d 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversions.cs @@ -78,7 +78,7 @@ public override Conversion GetMethodGroupFunctionPointerConversion(BoundMethodGr return conversion; } - protected override Conversion GetInterpolatedStringConversion(BoundInterpolatedString source, TypeSymbol destination, ref CompoundUseSiteInfo useSiteInfo) + protected override Conversion GetInterpolatedStringConversion(BoundUnconvertedInterpolatedString source, TypeSymbol destination, ref CompoundUseSiteInfo useSiteInfo) { // An interpolated string expression may be converted to the types // System.IFormattable and System.FormattableString diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs index 5aa09baf01618..556afd3655231 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs @@ -72,7 +72,7 @@ internal ConversionsBase WithNullability(bool includeNullability) protected abstract ConversionsBase CreateInstance(int currentRecursionDepth); - protected abstract Conversion GetInterpolatedStringConversion(BoundInterpolatedString source, TypeSymbol destination, ref CompoundUseSiteInfo useSiteInfo); + protected abstract Conversion GetInterpolatedStringConversion(BoundUnconvertedInterpolatedString source, TypeSymbol destination, ref CompoundUseSiteInfo useSiteInfo); internal AssemblySymbol CorLibrary { get { return corLibrary; } } @@ -931,8 +931,8 @@ private Conversion ClassifyImplicitBuiltInConversionFromExpression(BoundExpressi } break; - case BoundKind.InterpolatedString: - Conversion interpolatedStringConversion = GetInterpolatedStringConversion((BoundInterpolatedString)sourceExpression, destination, ref useSiteInfo); + case BoundKind.UnconvertedInterpolatedString: + Conversion interpolatedStringConversion = GetInterpolatedStringConversion((BoundUnconvertedInterpolatedString)sourceExpression, destination, ref useSiteInfo); if (interpolatedStringConversion.Exists) { return interpolatedStringConversion; diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/TypeConversions.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/TypeConversions.cs index b7f4f2a01c071..c3906596164d8 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/TypeConversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/TypeConversions.cs @@ -53,7 +53,7 @@ public override Conversion GetStackAllocConversion(BoundStackAllocArrayCreation throw ExceptionUtilities.Unreachable; } - protected override Conversion GetInterpolatedStringConversion(BoundInterpolatedString source, TypeSymbol destination, ref CompoundUseSiteInfo useSiteInfo) + protected override Conversion GetInterpolatedStringConversion(BoundUnconvertedInterpolatedString source, TypeSymbol destination, ref CompoundUseSiteInfo useSiteInfo) { // Conversions involving interpolated strings require a Binder. throw ExceptionUtilities.Unreachable; diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundExpression.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundExpression.cs index 22d65156255ca..fe75b32d33391 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundExpression.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundExpression.cs @@ -59,6 +59,7 @@ internal bool NeedsToBeConverted() case BoundKind.UnconvertedObjectCreationExpression: case BoundKind.UnconvertedConditionalOperator: case BoundKind.DefaultLiteral: + case BoundKind.UnconvertedInterpolatedString: return true; case BoundKind.StackAllocArrayCreation: // A BoundStackAllocArrayCreation is given a null type when it is in a @@ -286,9 +287,9 @@ public override Symbol? ExpressionSymbol } } - internal partial class BoundInterpolatedString + internal partial class BoundInterpolatedStringBase { - public override ConstantValue? ConstantValue + public sealed override ConstantValue? ConstantValue { get { return this.ConstantValueOpt; } } diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index 0f9ece1fc4f05..8a3bceb630e1c 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -2002,12 +2002,18 @@ - + + + + + + + diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs index e7c667ad0eb96..2fb2fa4d0994b 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs @@ -1047,7 +1047,7 @@ public override BoundNode VisitDynamicInvocation(BoundDynamicInvocation node) return null; } - public override BoundNode VisitInterpolatedString(BoundInterpolatedString node) + protected BoundNode VisitInterpolatedStringBase(BoundInterpolatedStringBase node) { foreach (var expr in node.Parts) { @@ -1056,6 +1056,16 @@ public override BoundNode VisitInterpolatedString(BoundInterpolatedString node) return null; } + public override BoundNode VisitInterpolatedString(BoundInterpolatedString node) + { + return VisitInterpolatedStringBase(node); + } + + public override BoundNode VisitUnconvertedInterpolatedString(BoundUnconvertedInterpolatedString node) + { + return VisitInterpolatedStringBase(node); + } + public override BoundNode VisitStringInsert(BoundStringInsert node) { VisitRvalue(node.Value); diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index b8203415bf38a..3e93f3c118b25 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -9362,6 +9362,14 @@ private static bool IsNullabilityMismatch(TypeSymbol type1, TypeSymbol type2) return result; } + public override BoundNode? VisitUnconvertedInterpolatedString(BoundUnconvertedInterpolatedString node) + { + // This is only involved with unbound lambdas or when visiting the source of a converted tuple literal + var result = base.VisitUnconvertedInterpolatedString(node); + SetResultType(node, TypeWithState.Create(node.Type, NullableFlowState.NotNull)); + return result; + } + public override BoundNode? VisitStringInsert(BoundStringInsert node) { var result = base.VisitStringInsert(node); diff --git a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs index 1423b578476ef..21696a558ceb1 100644 --- a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs @@ -199,6 +199,7 @@ internal enum BoundKind : byte QueryClause, TypeOrInstanceInitializers, NameOfOperator, + UnconvertedInterpolatedString, InterpolatedString, StringInsert, IsPatternExpression, @@ -7163,10 +7164,10 @@ public BoundNameOfOperator Update(BoundExpression argument, ConstantValue consta } } - internal sealed partial class BoundInterpolatedString : BoundExpression + internal abstract partial class BoundInterpolatedStringBase : BoundExpression { - public BoundInterpolatedString(SyntaxNode syntax, ImmutableArray parts, ConstantValue? constantValueOpt, TypeSymbol? type, bool hasErrors = false) - : base(BoundKind.InterpolatedString, syntax, type, hasErrors || parts.HasErrors()) + protected BoundInterpolatedStringBase(BoundKind kind, SyntaxNode syntax, ImmutableArray parts, ConstantValue? constantValueOpt, TypeSymbol? type, bool hasErrors = false) + : base(kind, syntax, type, hasErrors) { RoslynDebug.Assert(!parts.IsDefault, "Field 'parts' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); @@ -7179,6 +7180,43 @@ public BoundInterpolatedString(SyntaxNode syntax, ImmutableArray Parts { get; } public ConstantValue? ConstantValueOpt { get; } + } + + internal sealed partial class BoundUnconvertedInterpolatedString : BoundInterpolatedStringBase + { + public BoundUnconvertedInterpolatedString(SyntaxNode syntax, ImmutableArray parts, ConstantValue? constantValueOpt, TypeSymbol? type, bool hasErrors = false) + : base(BoundKind.UnconvertedInterpolatedString, syntax, parts, constantValueOpt, type, hasErrors || parts.HasErrors()) + { + + RoslynDebug.Assert(!parts.IsDefault, "Field 'parts' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); + + } + + [DebuggerStepThrough] + public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitUnconvertedInterpolatedString(this); + + public BoundUnconvertedInterpolatedString Update(ImmutableArray parts, ConstantValue? constantValueOpt, TypeSymbol? type) + { + if (parts != this.Parts || constantValueOpt != this.ConstantValueOpt || !TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) + { + var result = new BoundUnconvertedInterpolatedString(this.Syntax, parts, constantValueOpt, type, this.HasErrors); + result.CopyAttributes(this); + return result; + } + return this; + } + } + + internal sealed partial class BoundInterpolatedString : BoundInterpolatedStringBase + { + public BoundInterpolatedString(SyntaxNode syntax, ImmutableArray parts, ConstantValue? constantValueOpt, TypeSymbol? type, bool hasErrors = false) + : base(BoundKind.InterpolatedString, syntax, parts, constantValueOpt, type, hasErrors || parts.HasErrors()) + { + + RoslynDebug.Assert(!parts.IsDefault, "Field 'parts' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); + + } + [DebuggerStepThrough] public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitInterpolatedString(this); @@ -8321,6 +8359,8 @@ internal R VisitInternal(BoundNode node, A arg) return VisitTypeOrInstanceInitializers((BoundTypeOrInstanceInitializers)node, arg); case BoundKind.NameOfOperator: return VisitNameOfOperator((BoundNameOfOperator)node, arg); + case BoundKind.UnconvertedInterpolatedString: + return VisitUnconvertedInterpolatedString((BoundUnconvertedInterpolatedString)node, arg); case BoundKind.InterpolatedString: return VisitInterpolatedString((BoundInterpolatedString)node, arg); case BoundKind.StringInsert: @@ -8552,6 +8592,7 @@ internal abstract partial class BoundTreeVisitor public virtual R VisitQueryClause(BoundQueryClause node, A arg) => this.DefaultVisit(node, arg); public virtual R VisitTypeOrInstanceInitializers(BoundTypeOrInstanceInitializers node, A arg) => this.DefaultVisit(node, arg); public virtual R VisitNameOfOperator(BoundNameOfOperator node, A arg) => this.DefaultVisit(node, arg); + public virtual R VisitUnconvertedInterpolatedString(BoundUnconvertedInterpolatedString node, A arg) => this.DefaultVisit(node, arg); public virtual R VisitInterpolatedString(BoundInterpolatedString node, A arg) => this.DefaultVisit(node, arg); public virtual R VisitStringInsert(BoundStringInsert node, A arg) => this.DefaultVisit(node, arg); public virtual R VisitIsPatternExpression(BoundIsPatternExpression node, A arg) => this.DefaultVisit(node, arg); @@ -8757,6 +8798,7 @@ internal abstract partial class BoundTreeVisitor public virtual BoundNode? VisitQueryClause(BoundQueryClause node) => this.DefaultVisit(node); public virtual BoundNode? VisitTypeOrInstanceInitializers(BoundTypeOrInstanceInitializers node) => this.DefaultVisit(node); public virtual BoundNode? VisitNameOfOperator(BoundNameOfOperator node) => this.DefaultVisit(node); + public virtual BoundNode? VisitUnconvertedInterpolatedString(BoundUnconvertedInterpolatedString node) => this.DefaultVisit(node); public virtual BoundNode? VisitInterpolatedString(BoundInterpolatedString node) => this.DefaultVisit(node); public virtual BoundNode? VisitStringInsert(BoundStringInsert node) => this.DefaultVisit(node); public virtual BoundNode? VisitIsPatternExpression(BoundIsPatternExpression node) => this.DefaultVisit(node); @@ -9587,6 +9629,11 @@ internal abstract partial class BoundTreeWalker : BoundTreeVisitor this.Visit(node.Argument); return null; } + public override BoundNode? VisitUnconvertedInterpolatedString(BoundUnconvertedInterpolatedString node) + { + this.VisitList(node.Parts); + return null; + } public override BoundNode? VisitInterpolatedString(BoundInterpolatedString node) { this.VisitList(node.Parts); @@ -10744,6 +10791,12 @@ internal abstract partial class BoundTreeRewriter : BoundTreeVisitor TypeSymbol? type = this.VisitType(node.Type); return node.Update(argument, node.ConstantValueOpt, type); } + public override BoundNode? VisitUnconvertedInterpolatedString(BoundUnconvertedInterpolatedString node) + { + ImmutableArray parts = this.VisitList(node.Parts); + TypeSymbol? type = this.VisitType(node.Type); + return node.Update(parts, node.ConstantValueOpt, type); + } public override BoundNode? VisitInterpolatedString(BoundInterpolatedString node) { ImmutableArray parts = this.VisitList(node.Parts); @@ -12995,6 +13048,23 @@ public NullabilityRewriter(ImmutableDictionary parts = this.VisitList(node.Parts); + BoundUnconvertedInterpolatedString updatedNode; + + if (_updatedNullabilities.TryGetValue(node, out (NullabilityInfo Info, TypeSymbol? Type) infoAndType)) + { + updatedNode = node.Update(parts, node.ConstantValueOpt, infoAndType.Type); + updatedNode.TopLevelNullability = infoAndType.Info; + } + else + { + updatedNode = node.Update(parts, node.ConstantValueOpt, node.Type); + } + return updatedNode; + } + public override BoundNode? VisitInterpolatedString(BoundInterpolatedString node) { ImmutableArray parts = this.VisitList(node.Parts); @@ -14912,6 +14982,15 @@ private BoundTreeDumperNodeProducer() new TreeDumperNode("hasErrors", node.HasErrors, null) } ); + public override TreeDumperNode VisitUnconvertedInterpolatedString(BoundUnconvertedInterpolatedString node, object? arg) => new TreeDumperNode("unconvertedInterpolatedString", null, new TreeDumperNode[] + { + new TreeDumperNode("parts", null, from x in node.Parts select Visit(x, null)), + new TreeDumperNode("constantValueOpt", node.ConstantValueOpt, null), + new TreeDumperNode("type", node.Type, null), + new TreeDumperNode("isSuppressed", node.IsSuppressed, null), + new TreeDumperNode("hasErrors", node.HasErrors, null) + } + ); public override TreeDumperNode VisitInterpolatedString(BoundInterpolatedString node, object? arg) => new TreeDumperNode("interpolatedString", null, new TreeDumperNode[] { new TreeDumperNode("parts", null, from x in node.Parts select Visit(x, null)), diff --git a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs index 1efc166bf7e9b..8246e3af77eca 100644 --- a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs +++ b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs @@ -205,6 +205,8 @@ public CSharpOperationFactory(SemanticModel semanticModel) case BoundKind.TupleLiteral: case BoundKind.ConvertedTupleLiteral: return CreateBoundTupleOperation((BoundTupleExpression)boundNode); + case BoundKind.UnconvertedInterpolatedString: + throw ExceptionUtilities.Unreachable; case BoundKind.InterpolatedString: return CreateBoundInterpolatedStringExpressionOperation((BoundInterpolatedString)boundNode); case BoundKind.StringInsert: