From 9cbcb290bfc2a4369a5802e48761af46ff682e9c Mon Sep 17 00:00:00 2001 From: Fred Silberberg Date: Wed, 15 Dec 2021 15:22:16 -0800 Subject: [PATCH] Add support for nullable analysis in interpolated string handler constructors (#57780) Fixes https://github.com/dotnet/roslyn/issues/54583. We do not flow attribute post conditions back to the original slot of passed arguments or receivers, as without a proper in-order visit of the method parameters ensuring that the correct nullabilities are observed at the correct times isn't possible. --- .../Portable/Binder/Binder.ValueChecks.cs | 14 +- .../Portable/Binder/Binder_Expressions.cs | 22 +- .../Binder/Binder_InterpolatedString.cs | 23 +- .../Portable/Binder/Binder_Invocation.cs | 13 +- .../Portable/BoundTree/BoundNodeExtensions.cs | 10 + .../InterpolatedStringHandlerData.cs | 2 + .../Portable/FlowAnalysis/AbstractFlowPass.cs | 9 +- .../NullableWalker.DebugVerifier.cs | 25 +- .../Portable/FlowAnalysis/NullableWalker.cs | 133 +++- .../LocalRewriter/LocalRewriter_Call.cs | 7 +- .../LocalRewriter_StringInterpolation.cs | 7 +- .../Operations/CSharpOperationFactory.cs | 11 +- ...ationTests_IInterpolatedStringOperation.cs | 582 ++++++++++++++- .../Semantic/Semantics/InterpolationTests.cs | 26 +- .../Semantics/NullableReferenceTypesTests.cs | 672 +++++++++++++++++- .../Operations/ControlFlowGraphBuilder.cs | 46 +- 16 files changed, 1444 insertions(+), 158 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index 147df6c131d80..b61abacd57cd1 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -2798,12 +2798,7 @@ internal static uint GetValEscape(BoundExpression expr, uint scopeOfTheContainin if (conversion.ConversionKind == ConversionKind.InterpolatedStringHandler) { - var data = conversion.Operand switch - { - BoundInterpolatedString { InterpolationData: { } d } => d, - BoundBinaryOperator { InterpolatedStringHandlerData: { } d } => d, - _ => throw ExceptionUtilities.UnexpectedValue(conversion.Operand.Kind) - }; + var data = conversion.Operand.GetInterpolatedStringHandlerData(); return GetInterpolatedStringHandlerConversionEscapeScope(data, scopeOfTheContainingExpression); } @@ -3592,12 +3587,7 @@ private static bool CheckValEscape(ImmutableArray expressions, private static bool CheckInterpolatedStringHandlerConversionEscape(BoundExpression expression, uint escapeFrom, uint escapeTo, BindingDiagnosticBag diagnostics) { - var data = expression switch - { - BoundInterpolatedString { InterpolationData: { } d } => d, - BoundBinaryOperator { InterpolatedStringHandlerData: { } d } => d, - _ => throw ExceptionUtilities.UnexpectedValue(expression.Kind) - }; + var data = expression.GetInterpolatedStringHandlerData(); // We need to check to see if any values could potentially escape outside the max depth via the handler type. // Consider the case where a ref-struct handler saves off the result of one call to AppendFormatted, diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 3556cd37bd12f..11c4afb8019ea 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -2993,8 +2993,7 @@ private void CoerceArguments( MemberResolutionResult methodResult, ArrayBuilder arguments, BindingDiagnosticBag diagnostics, - TypeSymbol? receiverType, - uint receiverEscapeScope) + BoundExpression? receiver) where TMember : Symbol { var result = methodResult.Result; @@ -3012,7 +3011,7 @@ private void CoerceArguments( Debug.Assert(argument is BoundUnconvertedInterpolatedString or BoundBinaryOperator { IsUnconvertedInterpolatedStringAddition: true }); TypeWithAnnotations parameterTypeWithAnnotations = GetCorrespondingParameterTypeWithAnnotations(ref result, parameters, arg); reportUnsafeIfNeeded(methodResult, diagnostics, argument, parameterTypeWithAnnotations); - arguments[arg] = BindInterpolatedStringHandlerInMemberCall(argument, arguments, parameters, ref result, arg, receiverType, receiverEscapeScope, diagnostics); + arguments[arg] = BindInterpolatedStringHandlerInMemberCall(argument, arguments, parameters, ref result, arg, receiver, methodResult.LeastOverriddenMember.RequiresInstanceReceiver(), diagnostics); } // https://github.com/dotnet/roslyn/issues/37119 : should we create an (Identity) conversion when the kind is Identity but the types differ? else if (!kind.IsIdentity) @@ -4809,13 +4808,7 @@ private BoundExpression BindObjectInitializerMember( { if (argument is BoundConversion { Conversion.IsInterpolatedStringHandler: true, Operand: var operand }) { - var handlerPlaceholders = operand switch - { - BoundBinaryOperator { InterpolatedStringHandlerData: { } data } => data.ArgumentPlaceholders, - BoundInterpolatedString { InterpolationData: { } data } => data.ArgumentPlaceholders, - _ => throw ExceptionUtilities.UnexpectedValue(operand.Kind) - }; - + var handlerPlaceholders = operand.GetInterpolatedStringHandlerData().ArgumentPlaceholders; if (handlerPlaceholders.Any(placeholder => placeholder.ArgumentIndex == BoundInterpolatedStringArgumentPlaceholder.InstanceParameter)) { diagnostics.Add(ErrorCode.ERR_InterpolatedStringsReferencingInstanceCannotBeInObjectInitializers, argument.Syntax.Location); @@ -5745,7 +5738,7 @@ internal bool TryPerformConstructorOverloadResolution( if (succeededIgnoringAccessibility) { - this.CoerceArguments(result.ValidResult, analyzedArguments.Arguments, diagnostics, receiverType: null, receiverEscapeScope: Binder.ExternalScope); + this.CoerceArguments(result.ValidResult, analyzedArguments.Arguments, diagnostics, receiver: null); } // Fill in the out parameter with the result, if there was one; it might be inaccessible. @@ -8012,11 +8005,6 @@ private BoundExpression BindIndexerOrIndexedPropertyAccess( { MemberResolutionResult resolutionResult = overloadResolutionResult.ValidResult; PropertySymbol property = resolutionResult.Member; - RefKind? receiverRefKind = receiver.GetRefKind(); - uint receiverEscapeScope = property.RequiresInstanceReceiver && receiver != null - ? receiverRefKind?.IsWritableReference() == true ? GetRefEscape(receiver, LocalScopeDepth) : GetValEscape(receiver, LocalScopeDepth) - : Binder.ExternalScope; - this.CoerceArguments(resolutionResult, analyzedArguments.Arguments, diagnostics, receiver.Type, receiverEscapeScope); var isExpanded = resolutionResult.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm; var argsToParams = resolutionResult.Result.ArgsToParamsOpt; @@ -8028,6 +8016,8 @@ private BoundExpression BindIndexerOrIndexedPropertyAccess( receiver = ReplaceTypeOrValueReceiver(receiver, property.IsStatic, diagnostics); + this.CoerceArguments(resolutionResult, analyzedArguments.Arguments, diagnostics, receiver); + if (!gotError && receiver != null && receiver.Kind == BoundKind.ThisReference && receiver.WasCompilerGenerated) { gotError = IsRefOrOutThisParameterCaptured(syntax, diagnostics); diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_InterpolatedString.cs b/src/Compilers/CSharp/Portable/Binder/Binder_InterpolatedString.cs index 7880dbb41f6ea..a3ed2d0e89e43 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_InterpolatedString.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_InterpolatedString.cs @@ -808,8 +808,8 @@ private BoundExpression BindInterpolatedStringHandlerInMemberCall( ImmutableArray parameters, ref MemberAnalysisResult memberAnalysisResult, int interpolatedStringArgNum, - TypeSymbol? receiverType, - uint receiverEscapeScope, + BoundExpression? receiver, + bool requiresInstanceReceiver, BindingDiagnosticBag diagnostics) { Debug.Assert(unconvertedString is BoundUnconvertedInterpolatedString or BoundBinaryOperator { IsUnconvertedInterpolatedStringAddition: true }); @@ -916,9 +916,9 @@ private BoundExpression BindInterpolatedStringHandlerInMemberCall( switch (argumentIndex) { case BoundInterpolatedStringArgumentPlaceholder.InstanceParameter: - Debug.Assert(receiverType is not null); + Debug.Assert(receiver!.Type is not null); refKind = RefKind.None; - placeholderType = receiverType; + placeholderType = receiver.Type; break; case BoundInterpolatedStringArgumentPlaceholder.UnspecifiedParameter: { @@ -964,33 +964,40 @@ private BoundExpression BindInterpolatedStringHandlerInMemberCall( SyntaxNode placeholderSyntax; uint valSafeToEscapeScope; + bool isSuppressed; switch (argumentIndex) { case BoundInterpolatedStringArgumentPlaceholder.InstanceParameter: - placeholderSyntax = unconvertedString.Syntax; - valSafeToEscapeScope = receiverEscapeScope; + Debug.Assert(receiver != null); + valSafeToEscapeScope = requiresInstanceReceiver + ? receiver.GetRefKind().IsWritableReference() == true ? GetRefEscape(receiver, LocalScopeDepth) : GetValEscape(receiver, LocalScopeDepth) + : Binder.ExternalScope; + isSuppressed = receiver.IsSuppressed; + placeholderSyntax = receiver.Syntax; break; case BoundInterpolatedStringArgumentPlaceholder.UnspecifiedParameter: placeholderSyntax = unconvertedString.Syntax; valSafeToEscapeScope = Binder.ExternalScope; + isSuppressed = false; break; case >= 0: placeholderSyntax = arguments[argumentIndex].Syntax; valSafeToEscapeScope = GetValEscape(arguments[argumentIndex], LocalScopeDepth); + isSuppressed = arguments[argumentIndex].IsSuppressed; break; default: throw ExceptionUtilities.UnexpectedValue(argumentIndex); } argumentPlaceholdersBuilder.Add( - new BoundInterpolatedStringArgumentPlaceholder( + (BoundInterpolatedStringArgumentPlaceholder)(new BoundInterpolatedStringArgumentPlaceholder( placeholderSyntax, argumentIndex, valSafeToEscapeScope, placeholderType, hasErrors: argumentIndex == BoundInterpolatedStringArgumentPlaceholder.UnspecifiedParameter) - { WasCompilerGenerated = true }); + { WasCompilerGenerated = true }.WithSuppression(isSuppressed))); // We use the parameter refkind, rather than what the argument was actually passed with, because that will suppress duplicated errors // about arguments being passed with the wrong RefKind. The user will have already gotten an error about mismatched RefKinds or it will // be a place where refkinds are allowed to differ diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs index eac89f01308ad..05a1304c219ee 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs @@ -1022,11 +1022,7 @@ private BoundCall BindInvocationExpressionContinued( var receiver = ReplaceTypeOrValueReceiver(methodGroup.Receiver, !method.RequiresInstanceReceiver && !invokedAsExtensionMethod, diagnostics); - var receiverRefKind = receiver?.GetRefKind(); - uint receiverValEscapeScope = method.RequiresInstanceReceiver && receiver != null - ? receiverRefKind?.IsWritableReference() == true ? GetRefEscape(receiver, LocalScopeDepth) : GetValEscape(receiver, LocalScopeDepth) - : Binder.ExternalScope; - this.CoerceArguments(methodResult, analyzedArguments.Arguments, diagnostics, receiver?.Type, receiverValEscapeScope); + this.CoerceArguments(methodResult, analyzedArguments.Arguments, diagnostics, receiver); var expanded = methodResult.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm; var argsToParams = methodResult.Result.ArgsToParamsOpt; @@ -2029,12 +2025,7 @@ private BoundFunctionPointerInvocation BindFunctionPointerInvocation(SyntaxNode methodsBuilder.Free(); MemberResolutionResult methodResult = overloadResolutionResult.ValidResult; - CoerceArguments( - methodResult, - analyzedArguments.Arguments, - diagnostics, - receiverType: null, - receiverEscapeScope: Binder.ExternalScope); + CoerceArguments(methodResult, analyzedArguments.Arguments, diagnostics, receiver: null); var args = analyzedArguments.Arguments.ToImmutable(); var refKinds = analyzedArguments.RefKinds.ToImmutableOrNull(); diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodeExtensions.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundNodeExtensions.cs index cd58738867cd2..52cc65546c387 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodeExtensions.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodeExtensions.cs @@ -245,5 +245,15 @@ static void pushLeftNodes(BoundBinaryOperator binary, ArrayBuilder e switch + { + BoundBinaryOperator { InterpolatedStringHandlerData: { } d } => d, + BoundInterpolatedString { InterpolationData: { } d } => d, + BoundBinaryOperator or BoundInterpolatedString when !throwOnMissing => default, + BoundBinaryOperator or BoundInterpolatedString => throw ExceptionUtilities.Unreachable, + _ => throw ExceptionUtilities.UnexpectedValue(e.Kind), + }; } } diff --git a/src/Compilers/CSharp/Portable/BoundTree/InterpolatedStringHandlerData.cs b/src/Compilers/CSharp/Portable/BoundTree/InterpolatedStringHandlerData.cs index 03968639e19e2..2d4ed85c5b7a9 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/InterpolatedStringHandlerData.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/InterpolatedStringHandlerData.cs @@ -29,6 +29,8 @@ internal readonly struct InterpolatedStringHandlerData public readonly BoundInterpolatedStringHandlerPlaceholder ReceiverPlaceholder; + public bool IsDefault => Construction is null; + public InterpolatedStringHandlerData( TypeSymbol builderType, BoundExpression construction, diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs index a6cebb4193042..9515734437ab1 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs @@ -1114,7 +1114,7 @@ public override BoundNode VisitDynamicInvocation(BoundDynamicInvocation node) { } d => (d.Construction, d.UsesBoolReturns, d.HasTrailingHandlerValidityParameter) }; - VisitRvalue(construction); + VisitInterpolatedStringHandlerConstructor(construction); bool hasConditionalEvaluation = useBoolReturns || firstPartIsConditional; TLocalState? shortCircuitState = hasConditionalEvaluation ? State.Clone() : default; @@ -1128,6 +1128,11 @@ public override BoundNode VisitDynamicInvocation(BoundDynamicInvocation node) return null; } + + protected virtual void VisitInterpolatedStringHandlerConstructor(BoundExpression? constructor) + { + VisitRvalue(constructor); + } #nullable disable public override BoundNode VisitInterpolatedString(BoundInterpolatedString node) @@ -2459,7 +2464,7 @@ protected void VisitBinaryInterpolatedStringAddition(BoundBinaryOperator node) Debug.Assert(parts.Count >= 2); - VisitRvalue(data.Construction); + VisitInterpolatedStringHandlerConstructor(data.Construction); bool visitedFirst = false; bool hasTrailingHandlerValidityParameter = data.HasTrailingHandlerValidityParameter; diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.DebugVerifier.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.DebugVerifier.cs index 1c9e57e0f896d..932eaba8873c2 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.DebugVerifier.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.DebugVerifier.cs @@ -169,11 +169,6 @@ private void VerifyExpression(BoundExpression expression, bool overrideSkippedEx public override BoundNode? VisitBinaryOperator(BoundBinaryOperator node) { - if (node.InterpolatedStringHandlerData is { } data) - { - Visit(data.Construction); - } - VisitBinaryOperatorChildren(node); return null; } @@ -253,16 +248,6 @@ private void VisitBinaryOperatorChildren(BoundBinaryOperatorBase node) return null; } - public override BoundNode? VisitInterpolatedString(BoundInterpolatedString node) - { - if (node.InterpolationData is { Construction: var construction }) - { - Visit(construction); - } - base.VisitInterpolatedString(node); - return null; - } - public override BoundNode? VisitImplicitIndexerAccess(BoundImplicitIndexerAccess node) { Visit(node.Receiver); @@ -270,6 +255,16 @@ private void VisitBinaryOperatorChildren(BoundBinaryOperatorBase node) Visit(node.IndexerOrSliceAccess); return null; } + + public override BoundNode? VisitConversion(BoundConversion node) + { + if (node.ConversionKind == ConversionKind.InterpolatedStringHandler) + { + Visit(node.Operand.GetInterpolatedStringHandlerData().Construction); + } + + return base.VisitConversion(node); + } } #endif } diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 3a0bec4c4bc62..29567c7962c0e 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -205,7 +205,7 @@ public VisitArgumentResult(VisitResult visitResult, Optional stateFo /// private PooledDictionary? _methodGroupReceiverMapOpt; - private PooledDictionary? _resultForPlaceholdersOpt; + private PooledDictionary? _resultForPlaceholdersOpt; /// /// Variables instances for each lambda or local function defined within the analyzed region. @@ -480,13 +480,14 @@ private void AssertNoPlaceholderReplacements() } } - private void AddPlaceholderReplacement(BoundValuePlaceholderBase placeholder, BoundExpression expression, VisitResult result) + private void AddPlaceholderReplacement(BoundValuePlaceholderBase placeholder, BoundExpression? expression, VisitResult result) { #if DEBUG Debug.Assert(AreCloseEnough(placeholder.Type, result.RValueType.Type)); + Debug.Assert(expression != null || placeholder.Kind == BoundKind.InterpolatedStringArgumentPlaceholder); #endif - _resultForPlaceholdersOpt ??= PooledDictionary.GetInstance(); + _resultForPlaceholdersOpt ??= PooledDictionary.GetInstance(); _resultForPlaceholdersOpt.Add(placeholder, (expression, result)); } @@ -1965,7 +1966,8 @@ private void ReportNullableAssignmentIfNecessary( if (value == null || // This prevents us from giving undesired warnings for synthesized arguments for optional parameters, // but allows us to give warnings for synthesized assignments to record properties and for parameter default values at the declaration site. - (value.WasCompilerGenerated && assignmentKind == AssignmentKind.Argument) || + // Interpolated string handler argument placeholders _are_ compiler generated, but the user should be warned about them anyway. + (value.WasCompilerGenerated && assignmentKind == AssignmentKind.Argument && value.Kind != BoundKind.InterpolatedStringArgumentPlaceholder) || !ShouldReportNullableAssignment(targetType, valueType.State)) { return; @@ -3314,6 +3316,8 @@ private void VisitObjectElementInitializer(int containingSlot, BoundAssignmentOp var symbol = objectInitializer.MemberSymbol; if (!objectInitializer.Arguments.IsDefaultOrEmpty) { + // It is an error for an interpolated string to use the receiver of an object initializer indexer here, so we just use + // a default visit result VisitArguments(objectInitializer, objectInitializer.Arguments, objectInitializer.ArgumentRefKindsOpt, (PropertySymbol?)symbol, objectInitializer.ArgsToParamsOpt, objectInitializer.DefaultArguments, objectInitializer.Expanded); } @@ -3339,7 +3343,16 @@ private void VisitObjectElementInitializer(int containingSlot, BoundAssignmentOp private new void VisitCollectionElementInitializer(BoundCollectionElementInitializer node) { // Note: we analyze even omitted calls - (var reinferredMethod, _, _) = VisitArguments(node, node.Arguments, refKindsOpt: default, node.AddMethod, node.ArgsToParamsOpt, node.DefaultArguments, node.Expanded, node.InvokedAsExtensionMethod); + (var reinferredMethod, _, _) = VisitArguments( + node, + node.Arguments, + refKindsOpt: default, + node.AddMethod, + node.ArgsToParamsOpt, + node.DefaultArguments, + node.Expanded, + node.InvokedAsExtensionMethod); + Debug.Assert(reinferredMethod is object); if (node.ImplicitReceiverOpt != null) { @@ -4248,7 +4261,8 @@ private void LearnFromNonNullTest(BoundExpression expression, ref LocalState sta if (expression is BoundValuePlaceholderBase placeholder) { if (_resultForPlaceholdersOpt != null && - _resultForPlaceholdersOpt.TryGetValue(placeholder, out var value)) + _resultForPlaceholdersOpt.TryGetValue(placeholder, out var value) && + value.Replacement != null) { expression = value.Replacement; } @@ -5390,7 +5404,7 @@ private ImmutableArray VisitArguments( (ImmutableArray argumentsNoConversions, ImmutableArray conversions) = RemoveArgumentConversions(arguments, refKindsOpt); // Visit the arguments and collect results - ImmutableArray results = VisitArgumentsEvaluate(node.Syntax, argumentsNoConversions, refKindsOpt, parametersOpt, argsToParamsOpt, defaultArguments, expanded); + ImmutableArray results = VisitArgumentsEvaluate(argumentsNoConversions, refKindsOpt, parametersOpt, argsToParamsOpt, defaultArguments, expanded); // Re-infer method type parameters if (method?.IsGenericMethod == true) @@ -5416,6 +5430,7 @@ private ImmutableArray VisitArguments( bool parameterHasNotNullIfNotNull = !IsAnalyzingAttribute && !parametersOpt.IsDefault && parametersOpt.Any(p => !p.NotNullIfParameterNotNull.IsEmpty); var notNullParametersBuilder = parameterHasNotNullIfNotNull ? ArrayBuilder.GetInstance() : null; + var conversionResultsBuilder = ArrayBuilder.GetInstance(results.Length); if (!parametersOpt.IsDefault) { // Visit conversions, inbound assignments including pre-conditions @@ -5465,6 +5480,7 @@ private ImmutableArray VisitArguments( parameterType, parameterAnnotations, results[i], + conversionResultsBuilder, invokedAsExtensionMethod && i == 0); _disableDiagnostics = previousDisableDiagnostics; @@ -5481,6 +5497,8 @@ private ImmutableArray VisitArguments( } } + conversionResultsBuilder.Free(); + if (node is BoundCall { Method: { OriginalDefinition: LocalFunctionSymbol localFunction } }) { VisitLocalFunctionUse(localFunction); @@ -5628,7 +5646,6 @@ void markMembersAsNotNull(int receiverSlot, TypeSymbol type, string memberName, } private ImmutableArray VisitArgumentsEvaluate( - SyntaxNode syntax, ImmutableArray arguments, ImmutableArray refKindsOpt, ImmutableArray parametersOpt, @@ -5725,6 +5742,7 @@ private void VisitArgumentConversionAndInboundAssignmentsAndPreConditions( TypeWithAnnotations parameterType, FlowAnalysisAnnotations parameterAnnotations, VisitArgumentResult result, + ArrayBuilder? conversionResultsBuilder, bool extensionMethodThisArgument) { Debug.Assert(!this.IsConditionalState); @@ -5761,7 +5779,8 @@ private void VisitArgumentConversionAndInboundAssignmentsAndPreConditions( assignmentKind: AssignmentKind.Argument, parameterOpt: parameter, extensionMethodThisArgument: extensionMethodThisArgument, - stateForLambda: result.StateForLambda); + stateForLambda: result.StateForLambda, + previousArgumentConversionResults: conversionResultsBuilder); // If the parameter has annotations, we perform an additional check for nullable value types if (CheckDisallowedNullAssignment(stateAfterConversion, parameterAnnotations, argumentNoConversion.Syntax.Location)) @@ -5769,6 +5788,7 @@ private void VisitArgumentConversionAndInboundAssignmentsAndPreConditions( LearnFromNonNullTest(argumentNoConversion, ref State); } SetResultType(argumentNoConversion, stateAfterConversion, updateAnalyzedNullability: false); + conversionResultsBuilder?.Add(_visitResult); } break; case RefKind.Ref: @@ -5789,8 +5809,10 @@ private void VisitArgumentConversionAndInboundAssignmentsAndPreConditions( } } + conversionResultsBuilder?.Add(result.VisitResult); break; case RefKind.Out: + conversionResultsBuilder?.Add(result.VisitResult); break; default: throw ExceptionUtilities.UnexpectedValue(refKind); @@ -6992,8 +7014,7 @@ void reportBadDelegateParameter(BindingDiagnosticBag bag, MethodSymbol sourceInv } /// - /// Gets the conversion node for passing to , - /// if one should be passed. + /// Gets the conversion node for passing to VisitConversion, if one should be passed. /// private static BoundConversion? GetConversionIfApplicable(BoundExpression? conversionOpt, BoundExpression convertedNode) { @@ -7040,7 +7061,8 @@ private TypeWithState VisitConversion( bool extensionMethodThisArgument = false, Optional stateForLambda = default, bool trackMembers = false, - Location? diagnosticLocationOpt = null) + Location? diagnosticLocationOpt = null, + ArrayBuilder? previousArgumentConversionResults = null) { Debug.Assert(!trackMembers || !IsConditionalState); Debug.Assert(conversionOperand != null); @@ -7115,7 +7137,7 @@ private TypeWithState VisitConversion( break; case ConversionKind.InterpolatedStringHandler: - // https://github.com/dotnet/roslyn/issues/54583 Handle + visitInterpolatedStringHandlerConstructor(); resultState = NullableFlowState.NotNull; break; @@ -7509,6 +7531,61 @@ NullableFlowState visitNestedTargetTypedConstructs() throw ExceptionUtilities.UnexpectedValue(conversionOperand.Kind); } } + + void visitInterpolatedStringHandlerConstructor() + { + var handlerData = conversionOperand.GetInterpolatedStringHandlerData(throwOnMissing: false); + if (handlerData.IsDefault) + { + return; + } + + if (previousArgumentConversionResults == null) + { + Debug.Assert(handlerData.ArgumentPlaceholders.IsEmpty + || handlerData.ArgumentPlaceholders.Single().ArgumentIndex == BoundInterpolatedStringArgumentPlaceholder.TrailingConstructorValidityParameter); + VisitRvalue(handlerData.Construction); + return; + } + + bool addedPlaceholders = false; + foreach (var placeholder in handlerData.ArgumentPlaceholders) + { + switch (placeholder.ArgumentIndex) + { + case BoundInterpolatedStringArgumentPlaceholder.TrailingConstructorValidityParameter: + case BoundInterpolatedStringArgumentPlaceholder.UnspecifiedParameter: + // We presume that all instance parameters were dereferenced by calling the instance method this handler was passed to. This isn't strictly + // true: the handler constructor will be run before the receiver is dereferenced. However, if the dereference isn't safe, that will be a + // much better error to report than a mismatched argument nullability error. + case BoundInterpolatedStringArgumentPlaceholder.InstanceParameter: + break; + default: + if (previousArgumentConversionResults.Count > placeholder.ArgumentIndex) + { + // We intentionally do not give a replacement bound node for this placeholder, as we do not propagate any post conditions from the constructor + // to the original location of the node. This is because the nullable walker is not a true evaluation-order walker, and doing so would cause + // us to miss real warnings. + AddPlaceholderReplacement(placeholder, expression: null, previousArgumentConversionResults[placeholder.ArgumentIndex]); + addedPlaceholders = true; + } + break; + } + } + + VisitRvalue(handlerData.Construction); + + if (addedPlaceholders) + { + foreach (var placeholder in handlerData.ArgumentPlaceholders) + { + if (placeholder.ArgumentIndex < previousArgumentConversionResults.Count && placeholder.ArgumentIndex >= 0) + { + RemovePlaceholderReplacement(placeholder); + } + } + } + } } private TypeWithState VisitUserDefinedConversion( @@ -8281,7 +8358,7 @@ private void VisitDeconstructMethodArguments(ArrayBuilder.Empty, node.Constructor, argsToParamsOpt: node.ConstructorArgumentsToParamsOpt, defaultArguments: default, expanded: node.ConstructorExpanded, invokedAsExtensionMethod: false); + VisitArguments(node, node.ConstructorArguments, ImmutableArray.Empty, node.Constructor, argsToParamsOpt: node.ConstructorArgumentsToParamsOpt, defaultArguments: default, + expanded: node.ConstructorExpanded, invokedAsExtensionMethod: false); foreach (var assignment in node.NamedArguments) { Visit(assignment); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs index 6cdb0a410b1d8..e5c7af97c517c 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs @@ -455,12 +455,7 @@ ImmutableArray addInterpolationPlace { // Handler conversions are not supported in expression lambdas. Debug.Assert(!_inExpressionLambda); - var interpolationData = conversion.Operand switch - { - BoundInterpolatedString { InterpolationData: { } d } => d, - BoundBinaryOperator { InterpolatedStringHandlerData: { } d } => d, - _ => throw ExceptionUtilities.UnexpectedValue(conversion.Operand.Kind) - }; + var interpolationData = conversion.Operand.GetInterpolatedStringHandlerData(); var creation = (BoundObjectCreationExpression)interpolationData.Construction; if (interpolationData.ArgumentPlaceholders.Length > (interpolationData.HasTrailingHandlerValidityParameter ? 1 : 0)) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringInterpolation.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringInterpolation.cs index 11766c98d363c..ffb5979b910db 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringInterpolation.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringInterpolation.cs @@ -365,12 +365,7 @@ private static void AssertNoImplicitInterpolatedStringHandlerConversions(Immutab { if (arg is BoundConversion { Conversion: { Kind: ConversionKind.InterpolatedStringHandler }, ExplicitCastInCode: false, Operand: var operand }) { - var data = operand switch - { - BoundInterpolatedString { InterpolationData: { } d } => d, - BoundBinaryOperator { InterpolatedStringHandlerData: { } d } => d, - _ => throw ExceptionUtilities.UnexpectedValue(operand.Kind) - }; + var data = operand.GetInterpolatedStringHandlerData(); Debug.Assert(((BoundObjectCreationExpression)data.Construction).Arguments.All( a => a is BoundInterpolatedStringArgumentPlaceholder { ArgumentIndex: BoundInterpolatedStringArgumentPlaceholder.TrailingConstructorValidityParameter } or not BoundInterpolatedStringArgumentPlaceholder)); diff --git a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs index bbc1d254f453c..6d4c4b8241256 100644 --- a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs +++ b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs @@ -970,9 +970,8 @@ private IOperation CreateBoundConversionOperation(BoundConversion boundConversio if (boundConversion.ConversionKind == ConversionKind.InterpolatedStringHandler) { - // https://github.com/dotnet/roslyn/issues/54505 Support interpolation handlers in conversions Debug.Assert(!forceOperandImplicitLiteral); - Debug.Assert(boundOperand is BoundInterpolatedString { InterpolationData: not null } or BoundBinaryOperator { InterpolatedStringHandlerData: not null }); + Debug.Assert(!boundOperand.GetInterpolatedStringHandlerData().IsDefault); return CreateInterpolatedStringHandler(boundConversion); } @@ -2210,13 +2209,7 @@ private IInterpolatedStringHandlerCreationOperation CreateInterpolatedStringHand { Debug.Assert(conversion.Conversion.IsInterpolatedStringHandler); - InterpolatedStringHandlerData interpolationData = conversion.Operand switch - { - BoundInterpolatedString { InterpolationData: { } data } => data, - BoundBinaryOperator { InterpolatedStringHandlerData: { } data } => data, - _ => throw ExceptionUtilities.UnexpectedValue(conversion.Operand.Kind) - }; - + InterpolatedStringHandlerData interpolationData = conversion.Operand.GetInterpolatedStringHandlerData(); var construction = Create(interpolationData.Construction); var content = createContent(conversion.Operand); var isImplicit = conversion.WasCompilerGenerated || !conversion.ExplicitCastInCode; diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IInterpolatedStringOperation.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IInterpolatedStringOperation.cs index 57188b980ac25..99901da29b550 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IInterpolatedStringOperation.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IInterpolatedStringOperation.cs @@ -2114,8 +2114,8 @@ public CustomHandler(int literalLength, int formattedCount, C c) : this() ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""literal{c,1:format}""') InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') - IInterpolatedStringHandlerArgumentPlaceholderOperation (CallsiteReceiver) (OperationKind.InterpolatedStringHandlerArgumentPlaceholder, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'c') + IInterpolatedStringHandlerArgumentPlaceholderOperation (CallsiteReceiver) (OperationKind.InterpolatedStringHandlerArgumentPlaceholder, Type: null, IsImplicit) (Syntax: 'c') InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) Initializer: @@ -2941,7 +2941,7 @@ public CustomHandler(int literalLength, int formattedCount, C c, bool b, out boo ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""literal{c,1:format}""') InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'c') IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C, IsImplicit) (Syntax: 'c') InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) @@ -3229,7 +3229,7 @@ public CustomHandler(int literalLength, int formattedCount, C c, out bool succes ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""literal{c,1:format}""') InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'c') IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C, IsImplicit) (Syntax: 'c') InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) @@ -3371,7 +3371,7 @@ public CustomHandler(int literalLength, int formattedCount, C c, bool b, out boo ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""literal{c,1:format}""') InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'c') IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C, IsImplicit) (Syntax: 'c') InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) @@ -3526,7 +3526,7 @@ public CustomHandler(int literalLength, int formattedCount, C c, int i, out bool ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""literal{c,1:format}""') InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'c') IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C, IsImplicit) (Syntax: 'c') InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) @@ -3604,7 +3604,7 @@ public CustomHandler(int literalLength, int formattedCount, C c, int i, out bool ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""literal{c,2:format}""') InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,2:format}""') + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'c') IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C, IsImplicit) (Syntax: 'c') InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) @@ -3759,7 +3759,7 @@ public CustomHandler(int literalLength, int formattedCount, C c, bool b, out boo ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""literal{c,1:format}""') InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'c: $""litera ... ,1:format}""') + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'c') IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C, IsImplicit) (Syntax: 'c') InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) @@ -3942,8 +3942,8 @@ public CustomHandler(int literalLength, int formattedCount, C2 c, bool b) : this ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0, IsInvalid, IsImplicit) (Syntax: '$""literal""') InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsInvalid, IsImplicit) (Syntax: '$""literal""') - IInvalidOperation (OperationKind.Invalid, Type: null, IsInvalid, IsImplicit) (Syntax: '$""literal""') + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'C2') + IInvalidOperation (OperationKind.Invalid, Type: null, IsImplicit) (Syntax: 'C2') Children(0) InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) @@ -5280,7 +5280,7 @@ public void AppendFormatted([InterpolatedStringHandlerArgument("""")]CustomHandl ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0, IsImplicit) (Syntax: '$""Inner string""') InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""Inner string""') + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""{$""Inner string""}{2}""') IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""{$""Inner string""}{2}""') InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) @@ -5809,6 +5809,566 @@ public CustomHandler(int literalLength, int formattedCount, out int i) : this(li Block[B2] - Exit Predecessors: [B1] Statements (0) +"; + VerifyFlowGraphAndDiagnosticsForTest(new[] { code, InterpolatedStringHandlerArgumentAttribute, handler }, expectedFlowGraph, expectedDiagnostics); + } + + [Fact] + public void OutVariableDeclarationAsParameter_01() + { + var code = @" +using System; +using System.Runtime.CompilerServices; + +public class C +{ + public static void M1() + /**/{ + C.M(out int i, $""literal{1}""); + }/**/ + + public static void M(out int i, [InterpolatedStringHandlerArgument(""i"")]CustomHandler c) + { + i = 0; + } +} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, out int i) : this(literalLength, formattedCount) + { + i = 1; + } +} +"; + + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false); + + var expectedDiagnostics = DiagnosticDescription.None; + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + Locals: [System.Int32 i] + CaptureIds: [0] [1] + Block[B1] - Block + Predecessors: [B0] + Statements (5) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'i') + Value: + ILocalReferenceOperation: i (IsDeclaration: True) (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'i') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '$""literal{1}""') + Value: + IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount, out System.Int32 i)) (OperationKind.ObjectCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{1}""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{1}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 7, IsImplicit) (Syntax: '$""literal{1}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{1}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""literal{1}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: i) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'out int i') + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32, IsImplicit) (Syntax: 'i') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + IInvocationOperation ( void CustomHandler.AppendLiteral(System.String literal)) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 'literal') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{1}""') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literal) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'literal') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""literal"") (Syntax: 'literal') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IInvocationOperation ( void CustomHandler.AppendFormatted(System.Object o, [System.Int32 alignment = 0], [System.String format = null])) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: '{1}') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{1}""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: o) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '1') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: '1') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Boxing) + Operand: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.DefaultValue, Matching Parameter: alignment) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '{1}') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0, IsImplicit) (Syntax: '{1}') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.DefaultValue, Matching Parameter: format) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '{1}') + IDefaultValueOperation (OperationKind.DefaultValue, Type: System.String, Constant: null, IsImplicit) (Syntax: '{1}') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'C.M(out int ... teral{1}"");') + Expression: + IInvocationOperation (void C.M(out System.Int32 i, CustomHandler c)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'C.M(out int ... iteral{1}"")') + Instance Receiver: + null + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: i) (OperationKind.Argument, Type: null) (Syntax: 'out int i') + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32, IsImplicit) (Syntax: 'i') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null) (Syntax: '$""literal{1}""') + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{1}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B2] + Leaving: {R1} +} +Block[B2] - Exit + Predecessors: [B1] + Statements (0) +"; + VerifyFlowGraphAndDiagnosticsForTest(new[] { code, InterpolatedStringHandlerArgumentAttribute, handler }, expectedFlowGraph, expectedDiagnostics); + } + + [Fact] + public void OutVariableDeclarationAsParameter_02() + { + var code = @" +using System; +using System.Runtime.CompilerServices; + +public class C +{ + public static void M1(object o1, object o2) + /**/{ + C.M(out int i, o1 ?? o2, $""literal{1}""); + }/**/ + + public static void M(out int i, object o, [InterpolatedStringHandlerArgument(""i"")]CustomHandler c) + { + i = 0; + } +} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, out int i) : this(literalLength, formattedCount) + { + i = 1; + } +} +"; + + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false); + + var expectedDiagnostics = DiagnosticDescription.None; + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + Locals: [System.Int32 i] + CaptureIds: [0] [2] [3] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'i') + Value: + ILocalReferenceOperation: i (IsDeclaration: True) (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'i') + Next (Regular) Block[B2] + Entering: {R2} + .locals {R2} + { + CaptureIds: [1] + Block[B2] - Block + Predecessors: [B1] + Statements (1) + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'o1') + Value: + IParameterReferenceOperation: o1 (OperationKind.ParameterReference, Type: System.Object) (Syntax: 'o1') + Jump if True (Regular) to Block[B4] + IIsNullOperation (OperationKind.IsNull, Type: System.Boolean, IsImplicit) (Syntax: 'o1') + Operand: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Object, IsImplicit) (Syntax: 'o1') + Leaving: {R2} + Next (Regular) Block[B3] + Block[B3] - Block + Predecessors: [B2] + Statements (1) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'o1') + Value: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Object, IsImplicit) (Syntax: 'o1') + Next (Regular) Block[B5] + Leaving: {R2} + } + Block[B4] - Block + Predecessors: [B2] + Statements (1) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'o2') + Value: + IParameterReferenceOperation: o2 (OperationKind.ParameterReference, Type: System.Object) (Syntax: 'o2') + Next (Regular) Block[B5] + Block[B5] - Block + Predecessors: [B3] [B4] + Statements (4) + IFlowCaptureOperation: 3 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '$""literal{1}""') + Value: + IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount, out System.Int32 i)) (OperationKind.ObjectCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{1}""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{1}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 7, IsImplicit) (Syntax: '$""literal{1}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{1}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""literal{1}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: i) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'out int i') + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32, IsImplicit) (Syntax: 'i') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + IInvocationOperation ( void CustomHandler.AppendLiteral(System.String literal)) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 'literal') + Instance Receiver: + IFlowCaptureReferenceOperation: 3 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{1}""') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literal) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'literal') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""literal"") (Syntax: 'literal') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IInvocationOperation ( void CustomHandler.AppendFormatted(System.Object o, [System.Int32 alignment = 0], [System.String format = null])) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: '{1}') + Instance Receiver: + IFlowCaptureReferenceOperation: 3 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{1}""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: o) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '1') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: '1') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Boxing) + Operand: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.DefaultValue, Matching Parameter: alignment) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '{1}') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0, IsImplicit) (Syntax: '{1}') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.DefaultValue, Matching Parameter: format) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '{1}') + IDefaultValueOperation (OperationKind.DefaultValue, Type: System.String, Constant: null, IsImplicit) (Syntax: '{1}') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'C.M(out int ... teral{1}"");') + Expression: + IInvocationOperation (void C.M(out System.Int32 i, System.Object o, CustomHandler c)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'C.M(out int ... iteral{1}"")') + Instance Receiver: + null + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: i) (OperationKind.Argument, Type: null) (Syntax: 'out int i') + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32, IsImplicit) (Syntax: 'i') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: o) (OperationKind.Argument, Type: null) (Syntax: 'o1 ?? o2') + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: System.Object, IsImplicit) (Syntax: 'o1 ?? o2') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null) (Syntax: '$""literal{1}""') + IFlowCaptureReferenceOperation: 3 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{1}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B6] + Leaving: {R1} +} +Block[B6] - Exit + Predecessors: [B5] + Statements (0) +"; + VerifyFlowGraphAndDiagnosticsForTest(new[] { code, InterpolatedStringHandlerArgumentAttribute, handler }, expectedFlowGraph, expectedDiagnostics); + } + + [Fact] + public void OutVariableDeclarationAsParameter_03() + { + var code = @" +using System; +using System.Runtime.CompilerServices; + +public class C +{ + public static void M1() + /**/{ + C.M($"""", out int i, $""literal{1}""); + }/**/ + + public static void M(CustomHandler c1, out int i, [InterpolatedStringHandlerArgument(""i"")]CustomHandler c2) + { + i = 0; + } +} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, out int i) : this(literalLength, formattedCount) + { + i = 1; + } +} +"; + + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false); + + var expectedDiagnostics = DiagnosticDescription.None; + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + Locals: [System.Int32 i] + CaptureIds: [0] [1] [2] + Block[B1] - Block + Predecessors: [B0] + Statements (6) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '$""""') + Value: + IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount)) (OperationKind.ObjectCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""""') + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0, IsImplicit) (Syntax: '$""""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0, IsImplicit) (Syntax: '$""""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'i') + Value: + ILocalReferenceOperation: i (IsDeclaration: True) (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'i') + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '$""literal{1}""') + Value: + IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount, out System.Int32 i)) (OperationKind.ObjectCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{1}""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{1}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 7, IsImplicit) (Syntax: '$""literal{1}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{1}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""literal{1}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: i) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'out int i') + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Int32, IsImplicit) (Syntax: 'i') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + IInvocationOperation ( void CustomHandler.AppendLiteral(System.String literal)) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 'literal') + Instance Receiver: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{1}""') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literal) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'literal') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""literal"") (Syntax: 'literal') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IInvocationOperation ( void CustomHandler.AppendFormatted(System.Object o, [System.Int32 alignment = 0], [System.String format = null])) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: '{1}') + Instance Receiver: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{1}""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: o) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '1') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: '1') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Boxing) + Operand: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.DefaultValue, Matching Parameter: alignment) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '{1}') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0, IsImplicit) (Syntax: '{1}') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.DefaultValue, Matching Parameter: format) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '{1}') + IDefaultValueOperation (OperationKind.DefaultValue, Type: System.String, Constant: null, IsImplicit) (Syntax: '{1}') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'C.M($"""", ou ... teral{1}"");') + Expression: + IInvocationOperation (void C.M(CustomHandler c1, out System.Int32 i, CustomHandler c2)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'C.M($"""", ou ... iteral{1}"")') + Instance Receiver: + null + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c1) (OperationKind.Argument, Type: null) (Syntax: '$""""') + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: i) (OperationKind.Argument, Type: null) (Syntax: 'out int i') + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Int32, IsImplicit) (Syntax: 'i') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c2) (OperationKind.Argument, Type: null) (Syntax: '$""literal{1}""') + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{1}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B2] + Leaving: {R1} +} +Block[B2] - Exit + Predecessors: [B1] + Statements (0) +"; + VerifyFlowGraphAndDiagnosticsForTest(new[] { code, InterpolatedStringHandlerArgumentAttribute, handler }, expectedFlowGraph, expectedDiagnostics); + } + + [Fact] + public void OutVariableDeclarationAsParameter_04() + { + var code = @" +using System; +using System.Runtime.CompilerServices; + +public class C +{ + public static void M1(object o1, object o2) + /**/{ + C.M($""literal{1}"", out int i, o1 ?? o2); + }/**/ + + public static void M(CustomHandler c1, out int i, object o) + { + i = 0; + } +} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, out int i) : this(literalLength, formattedCount) + { + i = 1; + } +} +"; + + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false); + + var expectedDiagnostics = DiagnosticDescription.None; + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + Locals: [System.Int32 i] + CaptureIds: [0] [2] + Block[B1] - Block + Predecessors: [B0] + Statements (3) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '$""literal{1}""') + Value: + IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount)) (OperationKind.ObjectCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{1}""') + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{1}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 7, IsImplicit) (Syntax: '$""literal{1}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{1}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""literal{1}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + IInvocationOperation ( void CustomHandler.AppendLiteral(System.String literal)) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 'literal') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{1}""') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literal) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'literal') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""literal"") (Syntax: 'literal') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IInvocationOperation ( void CustomHandler.AppendFormatted(System.Object o, [System.Int32 alignment = 0], [System.String format = null])) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: '{1}') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{1}""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: o) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '1') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: '1') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Boxing) + Operand: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.DefaultValue, Matching Parameter: alignment) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '{1}') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0, IsImplicit) (Syntax: '{1}') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.DefaultValue, Matching Parameter: format) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '{1}') + IDefaultValueOperation (OperationKind.DefaultValue, Type: System.String, Constant: null, IsImplicit) (Syntax: '{1}') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B2] + Entering: {R2} + .locals {R2} + { + CaptureIds: [1] + Block[B2] - Block + Predecessors: [B1] + Statements (1) + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'o1') + Value: + IParameterReferenceOperation: o1 (OperationKind.ParameterReference, Type: System.Object) (Syntax: 'o1') + Jump if True (Regular) to Block[B4] + IIsNullOperation (OperationKind.IsNull, Type: System.Boolean, IsImplicit) (Syntax: 'o1') + Operand: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Object, IsImplicit) (Syntax: 'o1') + Leaving: {R2} + Next (Regular) Block[B3] + Block[B3] - Block + Predecessors: [B2] + Statements (1) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'o1') + Value: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Object, IsImplicit) (Syntax: 'o1') + Next (Regular) Block[B5] + Leaving: {R2} + } + Block[B4] - Block + Predecessors: [B2] + Statements (1) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'o2') + Value: + IParameterReferenceOperation: o2 (OperationKind.ParameterReference, Type: System.Object) (Syntax: 'o2') + Next (Regular) Block[B5] + Block[B5] - Block + Predecessors: [B3] [B4] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'C.M($""liter ... o1 ?? o2);') + Expression: + IInvocationOperation (void C.M(CustomHandler c1, out System.Int32 i, System.Object o)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'C.M($""liter ... , o1 ?? o2)') + Instance Receiver: + null + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c1) (OperationKind.Argument, Type: null) (Syntax: '$""literal{1}""') + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{1}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: i) (OperationKind.Argument, Type: null) (Syntax: 'out int i') + IDeclarationExpressionOperation (OperationKind.DeclarationExpression, Type: System.Int32) (Syntax: 'int i') + ILocalReferenceOperation: i (IsDeclaration: True) (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'i') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: o) (OperationKind.Argument, Type: null) (Syntax: 'o1 ?? o2') + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: System.Object, IsImplicit) (Syntax: 'o1 ?? o2') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B6] + Leaving: {R1} +} +Block[B6] - Exit + Predecessors: [B5] + Statements (0) "; VerifyFlowGraphAndDiagnosticsForTest(new[] { code, InterpolatedStringHandlerArgumentAttribute, handler }, expectedFlowGraph, expectedDiagnostics); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs index d8ed1fff4abb3..a4828a4c4ca28 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs @@ -9134,17 +9134,17 @@ public CustomHandler(int literalLength, int formattedCount, ref C c" + extraCons comp.VerifyDiagnostics(extraConstructorArg != "" ? new[] { + // (6,1): error CS1620: Argument 3 must be passed with the 'ref' keyword + // GetC(ref c).M($"literal" + $""); + Diagnostic(ErrorCode.ERR_BadArgRef, "GetC(ref c)").WithArguments("3", "ref").WithLocation(6, 1), // (6,15): error CS7036: There is no argument given that corresponds to the required formal parameter 'success' of 'CustomHandler.CustomHandler(int, int, ref C, out bool)' - // GetC(ref c).M($"literal"); - Diagnostic(ErrorCode.ERR_NoCorrespondingArgument, expression).WithArguments("success", "CustomHandler.CustomHandler(int, int, ref C, out bool)").WithLocation(6, 15), - // (6,15): error CS1620: Argument 3 must be passed with the 'ref' keyword - // GetC(ref c).M($"literal"); - Diagnostic(ErrorCode.ERR_BadArgRef, expression).WithArguments("3", "ref").WithLocation(6, 15) + // GetC(ref c).M($"literal" + $""); + Diagnostic(ErrorCode.ERR_NoCorrespondingArgument, expression).WithArguments("success", "CustomHandler.CustomHandler(int, int, ref C, out bool)").WithLocation(6, 15) } : new[] { - // (6,15): error CS1620: Argument 3 must be passed with the 'ref' keyword - // GetC(ref c).M($"literal"); - Diagnostic(ErrorCode.ERR_BadArgRef, expression).WithArguments("3", "ref").WithLocation(6, 15) + // (6,1): error CS1620: Argument 3 must be passed with the 'ref' keyword + // GetC(ref c).M($"literal" + $""); + Diagnostic(ErrorCode.ERR_BadArgRef, "GetC(ref c)").WithArguments("3", "ref").WithLocation(6, 1) }); } @@ -9176,9 +9176,9 @@ public CustomHandler(int literalLength, int formattedCount, ref C c) : this(lite var comp = CreateCompilation(new[] { code, InterpolatedStringHandlerArgumentAttribute, handler }); comp.VerifyDiagnostics( - // (5,10): error CS1620: Argument 3 must be passed with the 'ref' keyword - // GetC().M($"literal"); - Diagnostic(ErrorCode.ERR_BadArgRef, expression).WithArguments("3", "ref").WithLocation(5, 10) + // (5,1): error CS1620: Argument 3 must be passed with the 'ref' keyword + // GetC().M($"literal" + $""); + Diagnostic(ErrorCode.ERR_BadArgRef, "GetC()").WithArguments("3", "ref").WithLocation(5, 1) ); } @@ -9482,9 +9482,9 @@ public CustomHandler(int literalLength, int formattedCount, ref S s1, ref S s2) var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false); var comp = CreateCompilation(new[] { code, InterpolatedStringHandlerArgumentAttribute, handler }); comp.VerifyDiagnostics( - // (8,14): error CS1620: Argument 3 must be passed with the 'ref' keyword + // (8,1): error CS1620: Argument 3 must be passed with the 'ref' keyword // s1.M(ref s2, $""); - Diagnostic(ErrorCode.ERR_BadArgRef, expression).WithArguments("3", "ref").WithLocation(8, 14) + Diagnostic(ErrorCode.ERR_BadArgRef, "s1").WithArguments("3", "ref").WithLocation(8, 1) ); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index 9190ae101f525..9ea714ca2560b 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -64202,13 +64202,10 @@ public CustomHandler(int literalLength, int formattedCount, [MaybeNull] ref T t ", GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns, includeTrailingOutConstructorParameter: validityParameter), InterpolatedStringHandlerArgumentAttribute, MaybeNullAttributeDefinition }, parseOptions: TestOptions.RegularPreview); - // https://github.com/dotnet/roslyn/issues/54583 - // Should be a warning on `s.ToString()` - c.VerifyDiagnostics( - // (1000,9): warning CS8601: Possible null reference assignment. - // ref s, - Diagnostic(ErrorCode.WRN_NullReferenceAssignment, "s").WithLocation(1000, 9) - ); + // We are not propagating `MaybeNull` and similar post-condition attributes after the call to the CustomHandler + // constructor. This is because of aliasing issues: See the StringInterpolation_21-26 tests for scenarios where + // writes to a variable could effectively be missed if we did so. + c.VerifyDiagnostics(); } [Theory] @@ -64234,9 +64231,10 @@ public CustomHandler(int literalLength, int formattedCount, string s" + (validit ", GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns, includeTrailingOutConstructorParameter: validityParameter), InterpolatedStringHandlerArgumentAttribute }, parseOptions: TestOptions.RegularPreview); - // https://github.com/dotnet/roslyn/issues/54583 - // Should be a warning for the constructor parameter c.VerifyDiagnostics( + // (6,3): warning CS8604: Possible null reference argument for parameter 's' in 'CustomHandler.CustomHandler(int literalLength, int formattedCount, string s)'. + // M(s, $""); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "s").WithArguments("s", $"CustomHandler.CustomHandler(int literalLength, int formattedCount, string s{(validityParameter ? ", out bool success" : "")})").WithLocation(6, 3) ); } @@ -64377,6 +64375,662 @@ public CustomHandler(int literalLength, int formattedCount" + (validityParameter } } + [Fact, WorkItem(54583, "https://github.com/dotnet/roslyn/issues/54583")] + public void StringInterpolation_08() + { + var code = @" +using System.Runtime.CompilerServices; + +#nullable enable + +string? s = null; +M(s, $""""); // 1 +s = null; +M(s!, $""""); + +void M(T t1, [InterpolatedStringHandlerArgument(""t1"")] CustomHandler c) {} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, string t) : this() + { + } +} +"; + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false); + + var comp = CreateCompilation(new[] { code, handler, InterpolatedStringHandlerArgumentAttribute }); + comp.VerifyDiagnostics( + // (7,3): warning CS8604: Possible null reference argument for parameter 't' in 'CustomHandler.CustomHandler(int literalLength, int formattedCount, string t)'. + // M(s, $""); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "s").WithArguments("t", "CustomHandler.CustomHandler(int literalLength, int formattedCount, string t)").WithLocation(7, 3) + ); + } + + [Fact, WorkItem(54583, "https://github.com/dotnet/roslyn/issues/54583")] + public void StringInterpolation_09() + { + var code = @" +using System.Runtime.CompilerServices; + +#nullable enable + +C? c = null; +c.M($""""); + +public class C +{ + public void M([InterpolatedStringHandlerArgument("""")] CustomHandler c) {} +} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, C c) : this() + { + } +} +"; + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false); + + var comp = CreateCompilation(new[] { code, handler, InterpolatedStringHandlerArgumentAttribute }); + comp.VerifyDiagnostics( + // (7,1): warning CS8602: Dereference of a possibly null reference. + // c.M($""); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c").WithLocation(7, 1) + ); + } + + [Fact, WorkItem(54583, "https://github.com/dotnet/roslyn/issues/54583")] + public void StringInterpolation_10() + { + var code = @" +using System.Runtime.CompilerServices; + +#nullable enable + +C c = null!; +c.M($""""); // 1 +c!.M($""""); + +public class C +{ + public void M([InterpolatedStringHandlerArgument("""")] CustomHandler c) {} +} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, C c) : this() + { + } +} +"; + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false); + + var comp = CreateCompilation(new[] { code, handler, InterpolatedStringHandlerArgumentAttribute }); + comp.VerifyDiagnostics( + // (7,1): warning CS8620: Argument of type 'C' cannot be used for parameter 'c' of type 'C' in 'CustomHandler.CustomHandler(int literalLength, int formattedCount, C c)' due to differences in the nullability of reference types. + // c.M($""); // 1 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInArgument, "c").WithArguments("C", "C", "c", "CustomHandler.CustomHandler(int literalLength, int formattedCount, C c)").WithLocation(7, 1) + ); + } + + [Fact, WorkItem(54583, "https://github.com/dotnet/roslyn/issues/54583")] + public void StringInterpolation_11() + { + var code = @" +using System.Runtime.CompilerServices; + +#nullable enable + +M(out string? s1, $""""); + +void M(out string? t1, [InterpolatedStringHandlerArgument(""t1"")] CustomHandler c) => t1 = null; + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, out string? t1, out bool success) : this() + { + t1 = default; + success = true; + } +}"; + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false); + + var comp = CreateCompilation(new[] { code, handler, InterpolatedStringHandlerArgumentAttribute }); + comp.VerifyDiagnostics(); + } + + [Fact, WorkItem(54583, "https://github.com/dotnet/roslyn/issues/54583")] + public void StringInterpolation_12() + { + var code = @" +using System.Runtime.CompilerServices; + +#nullable enable + +M(out string s1, $""""); + +void M(out string? t1, [InterpolatedStringHandlerArgument(""t1"")] CustomHandler c) => t1 = null; + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, out string? t1, out bool success) : this() + { + t1 = default; + success = true; + } +}"; + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false); + + var comp = CreateCompilation(new[] { code, handler, InterpolatedStringHandlerArgumentAttribute }); + comp.VerifyDiagnostics( + // (6,7): warning CS8601: Possible null reference assignment. + // M(out string s1, $""); + Diagnostic(ErrorCode.WRN_NullReferenceAssignment, "string s1").WithLocation(6, 7), + // (6,7): warning CS8600: Converting null literal or possible null value to non-nullable type. + // M(out string s1, $""); + Diagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, "string s1").WithLocation(6, 7) + ); + } + + [Fact, WorkItem(54583, "https://github.com/dotnet/roslyn/issues/54583")] + public void StringInterpolation_13() + { + var code = @" +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +#nullable enable + +string s = """"; +C c = new C(); +c.M(s, $"""", c.ToString(), s.ToString()); + +public class C +{ + public void M(string s1, [InterpolatedStringHandlerArgument("""", ""s1"")] CustomHandler c1, string s2, string s3) { } +} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, [MaybeNull] C c, [MaybeNull] string s) : this() + { + } +}"; + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false); + + var comp = CreateCompilation(new[] { code, handler, InterpolatedStringHandlerArgumentAttribute, MaybeNullAttributeDefinition }); + comp.VerifyDiagnostics(); + } + + [Fact, WorkItem(54583, "https://github.com/dotnet/roslyn/issues/54583")] + public void StringInterpolation_14() + { + var code = @" +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +#nullable enable + +string? s = null; +M(s, $"""", s.ToString()); + +void M(string s1, [InterpolatedStringHandlerArgument(""s1"")] CustomHandler c1, string s2) { } + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, [NotNull] string? s) : this() + { + if (s == null) throw null!; + } +}"; + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false); + + var comp = CreateCompilation(new[] { code, handler, InterpolatedStringHandlerArgumentAttribute, NotNullAttributeDefinition }); + comp.VerifyDiagnostics( + // (8,3): warning CS8604: Possible null reference argument for parameter 's1' in 'void M(string s1, CustomHandler c1, string s2)'. + // M(s, $"", s.ToString()); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "s").WithArguments("s1", "void M(string s1, CustomHandler c1, string s2)").WithLocation(8, 3), + // (8,11): warning CS8602: Dereference of a possibly null reference. + // M(s, $"", s.ToString()); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(8, 11) + ); + } + + [Fact, WorkItem(54583, "https://github.com/dotnet/roslyn/issues/54583")] + public void StringInterpolation_15() + { + var code = @" +using System.Runtime.CompilerServices; + +#nullable enable + +M(GetNullableString(), $""""); + +void M(string? s1, [InterpolatedStringHandlerArgument(""s1"")] CustomHandler c1) { } + +string? GetNullableString() => null; + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, string s) : this() + { + if (s == null) throw null!; + } +}"; + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false); + + var comp = CreateCompilation(new[] { code, handler, InterpolatedStringHandlerArgumentAttribute }); + comp.VerifyDiagnostics( + // (6,3): warning CS8604: Possible null reference argument for parameter 's' in 'CustomHandler.CustomHandler(int literalLength, int formattedCount, string s)'. + // M(GetNullableString(), $""); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "GetNullableString()").WithArguments("s", "CustomHandler.CustomHandler(int literalLength, int formattedCount, string s)").WithLocation(6, 3) + ); + } + + [Fact, WorkItem(54583, "https://github.com/dotnet/roslyn/issues/54583")] + public void StringInterpolation_16() + { + var code = @" +using System.Runtime.CompilerServices; + +#nullable enable + +C c = new C(); +string? s = null; +_ = c[s, $""""]; // 1 +s = null; +_ = c[s!, $""""]; + +class C +{ + public string this[string? s1, [InterpolatedStringHandlerArgument(""s1"")] CustomHandler c] { get => throw null!; set => throw null!; } +} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, string t) : this() + { + } +} +"; + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false); + + var comp = CreateCompilation(new[] { code, handler, InterpolatedStringHandlerArgumentAttribute }); + comp.VerifyDiagnostics( + // (8,7): warning CS8604: Possible null reference argument for parameter 't' in 'CustomHandler.CustomHandler(int literalLength, int formattedCount, string t)'. + // _ = c[s, $""]; // 1 + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "s").WithArguments("t", "CustomHandler.CustomHandler(int literalLength, int formattedCount, string t)").WithLocation(8, 7) + ); + } + + [Fact, WorkItem(54583, "https://github.com/dotnet/roslyn/issues/54583")] + public void StringInterpolation_17() + { + var code = @" +using System.Runtime.CompilerServices; + +#nullable enable + +C? c = null; +_ = c[$""""]; + +public class C +{ + public string this[[InterpolatedStringHandlerArgument("""")] CustomHandler c] { get => throw null!; set => throw null!; } +} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, C c) : this() + { + } +} +"; + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false); + + var comp = CreateCompilation(new[] { code, handler, InterpolatedStringHandlerArgumentAttribute }); + comp.VerifyDiagnostics( + // (7,5): warning CS8602: Dereference of a possibly null reference. + // _ = c[$""]; + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c").WithLocation(7, 5) + ); + } + + [Fact, WorkItem(54583, "https://github.com/dotnet/roslyn/issues/54583")] + public void StringInterpolation_18() + { + var code = @" +using System.Runtime.CompilerServices; + +#nullable enable + +C c = null!; +_ = c[$""""]; // 1 +_ = c![$""""]; + +public class C +{ + public string this[[InterpolatedStringHandlerArgument("""")] CustomHandler c] { get => throw null!; set => throw null!; } +} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, C c) : this() + { + } +} +"; + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false); + + var comp = CreateCompilation(new[] { code, handler, InterpolatedStringHandlerArgumentAttribute }); + comp.VerifyDiagnostics( + // (7,5): warning CS8620: Argument of type 'C' cannot be used for parameter 'c' of type 'C' in 'CustomHandler.CustomHandler(int literalLength, int formattedCount, C c)' due to differences in the nullability of reference types. + // _ = c[$""]; // 1 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInArgument, "c").WithArguments("C", "C", "c", "CustomHandler.CustomHandler(int literalLength, int formattedCount, C c)").WithLocation(7, 5) + ); + } + + [Fact, WorkItem(54583, "https://github.com/dotnet/roslyn/issues/54583")] + public void StringInterpolation_19() + { + var code = @" +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +#nullable enable + +string s = """"; +C c = new C(); +_ = c[s, $"""", c.ToString(), s.ToString()]; + +public class C +{ + public string this[string s1, [InterpolatedStringHandlerArgument("""", ""s1"")] CustomHandler c1, string s2, string s3] => throw null!; +} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, [MaybeNull] C c, [MaybeNull] string s) : this() + { + } +}"; + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false); + + var comp = CreateCompilation(new[] { code, handler, InterpolatedStringHandlerArgumentAttribute, MaybeNullAttributeDefinition }); + comp.VerifyDiagnostics(); + } + + [Fact, WorkItem(54583, "https://github.com/dotnet/roslyn/issues/54583")] + public void StringInterpolation_20() + { + var code = @" +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +#nullable enable + +string? s = null; +C c = new C(); +_ = c[s, $"""", s.ToString()]; + +public class C +{ + public string this[string s1, [InterpolatedStringHandlerArgument(""s1"")] CustomHandler c1, string s2] => throw null!; +} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, [NotNull] string? s) : this() + { + if (s == null) throw null!; + } +}"; + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false); + + var comp = CreateCompilation(new[] { code, handler, InterpolatedStringHandlerArgumentAttribute, NotNullAttributeDefinition }); + comp.VerifyDiagnostics( + // (9,7): warning CS8604: Possible null reference argument for parameter 's1' in 'string C.this[string s1, CustomHandler c1, string s2]'. + // _ = c[s, $"", s.ToString()]; + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "s").WithArguments("s1", "string C.this[string s1, CustomHandler c1, string s2]").WithLocation(9, 7), + // (9,15): warning CS8602: Dereference of a possibly null reference. + // _ = c[s, $"", s.ToString()]; + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(9, 15) + ); + } + + [Fact, WorkItem(54583, "https://github.com/dotnet/roslyn/issues/54583")] + public void StringInterpolation_21() + { + var code = @" +#nullable enable +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +var c = new C(); +c.M(c = null, $""""); +c.ToString(); // 1 + +public class C +{ + public void M(object? o1, [InterpolatedStringHandlerArgument("""")] CustomHandler c) => throw null!; +} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, [NotNull] C? o) => throw null!; +}"; + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false); + + var comp = CreateCompilation(new[] { code, handler, InterpolatedStringHandlerArgumentAttribute, NotNullAttributeDefinition }); + comp.VerifyDiagnostics( + // (8,1): warning CS8602: Dereference of a possibly null reference. + // c.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c").WithLocation(8, 1) + ); + } + + [Fact, WorkItem(54583, "https://github.com/dotnet/roslyn/issues/54583")] + public void StringInterpolation_22() + { + var code = @" +#nullable enable +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +var c = new C(); +c.M($"""", c = null); +c.ToString(); // 1 + +public class C +{ + public void M([InterpolatedStringHandlerArgument("""")] CustomHandler c, object? o) => throw null!; +} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, [NotNull] C? o) => throw null!; +}"; + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false); + + var comp = CreateCompilation(new[] { code, handler, InterpolatedStringHandlerArgumentAttribute, NotNullAttributeDefinition }); + comp.VerifyDiagnostics( + // (8,1): warning CS8602: Dereference of a possibly null reference. + // c.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c").WithLocation(8, 1) + ); + } + + [Fact, WorkItem(54583, "https://github.com/dotnet/roslyn/issues/54583")] + public void StringInterpolation_23() + { + var code = @" +#nullable enable +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +object? o = new(); +M(o, $"""", o = null); +o.ToString(); // 1 + +void M(object? o1, [InterpolatedStringHandlerArgument(""o1"")] CustomHandler c, object? o2) => throw null!; + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, [NotNull] object? o) => throw null!; +}"; + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false); + + var comp = CreateCompilation(new[] { code, handler, InterpolatedStringHandlerArgumentAttribute, NotNullAttributeDefinition }); + comp.VerifyDiagnostics( + // (8,1): warning CS8602: Dereference of a possibly null reference. + // o.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "o").WithLocation(8, 1) + ); + } + + [Fact, WorkItem(54583, "https://github.com/dotnet/roslyn/issues/54583")] + public void StringInterpolation_24() + { + var code = @" +#nullable enable +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +object o = new(); +M(o, $"""", o = new()); +o.ToString(); + +void M(object? o1, [InterpolatedStringHandlerArgument(""o1"")] CustomHandler c, object? o2) => throw null!; + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, [MaybeNull] object? o) => throw null!; +}"; + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false); + + var comp = CreateCompilation(new[] { code, handler, InterpolatedStringHandlerArgumentAttribute, MaybeNullAttributeDefinition }); + comp.VerifyDiagnostics( + ); + } + + [Fact, WorkItem(54583, "https://github.com/dotnet/roslyn/issues/54583")] + public void StringInterpolation_25() + { + var code = @" +#nullable enable +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +var c = new C(); +c.M(c = new(), $""""); +c.ToString(); + +public class C +{ + public void M(object o, [InterpolatedStringHandlerArgument("""")] CustomHandler c) => throw null!; +} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, [MaybeNull] C? o) => throw null!; +}"; + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false); + + var comp = CreateCompilation(new[] { code, handler, InterpolatedStringHandlerArgumentAttribute, MaybeNullAttributeDefinition }); + comp.VerifyDiagnostics( + ); + } + + [Fact, WorkItem(54583, "https://github.com/dotnet/roslyn/issues/54583")] + public void StringInterpolation_26() + { + var code = @" +#nullable enable +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +var c = new C(); +c.M($"""", c = new()); +c.ToString(); + +public class C +{ + public void M([InterpolatedStringHandlerArgument("""")] CustomHandler c, object? o) => throw null!; +} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, [MaybeNull] C? o) => throw null!; +}"; + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false); + + var comp = CreateCompilation(new[] { code, handler, InterpolatedStringHandlerArgumentAttribute, MaybeNullAttributeDefinition }); + comp.VerifyDiagnostics( + ); + } + + [Fact, WorkItem(54583, "https://github.com/dotnet/roslyn/issues/54583")] + public void StringInterpolation_27() + { + var code = @" +#nullable enable +using System.Runtime.CompilerServices; + +string? s = null; +M("""", $""{s}""); + +void M(T t, CustomHandler c) => throw null!; + +[InterpolatedStringHandler] +public struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount) => throw null!; + public void AppendFormatted(T t) => throw null!; +}"; + var comp = CreateCompilation(new[] { code, InterpolatedStringHandlerAttribute }); + // Note: We are not flowing generic type reinference through the custom handler type to any AppendFormatted + // calls. The rest of this analysis is tracked by https://github.com/dotnet/roslyn/issues/54583, but it's + // viewed as low priority and we likely won't address it until we get a customer report of hitting this bug + // in real code as we don't view generic handlers as a common (or even uncommon) case. + comp.VerifyDiagnostics(); + } + + [Fact, WorkItem(54583, "https://github.com/dotnet/roslyn/issues/54583")] + public void StringInterpolation_28() + { + var code = @" +#nullable enable +using System.Runtime.CompilerServices; + +B b = new B(); +M(b, $""""); + +void M(A? a, [InterpolatedStringHandlerArgument(""a"")] CustomHandler c) => throw null!; + +public class A +{ +} + +public class B +{ + public static implicit operator A?(B b) => throw null!; +} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, A a) => throw null!; +}"; + + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false); + var comp = CreateCompilation(new[] { code, handler, InterpolatedStringHandlerArgumentAttribute }); + comp.VerifyDiagnostics( + // (6,3): warning CS8604: Possible null reference argument for parameter 'a' in 'CustomHandler.CustomHandler(int literalLength, int formattedCount, A a)'. + // M(b, $""); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "b").WithArguments("a", "CustomHandler.CustomHandler(int literalLength, int formattedCount, A a)").WithLocation(6, 3) + ); + } + [Fact] public void DelegateCreation_01() { diff --git a/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs b/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs index 61c5d7e174410..9911d367df9bd 100644 --- a/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs +++ b/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs @@ -2188,25 +2188,47 @@ private void VisitAndPushArguments(ImmutableArray arguments, { var previousInterpolatedStringHandlerContext = _currentInterpolatedStringHandlerArgumentContext; - if (arguments.SelectAsArray(predicate: arg => arg.Value is IInterpolatedStringHandlerCreationOperation, - selector: arg => (IInterpolatedStringHandlerCreationOperation)arg.Value) - is { IsDefaultOrEmpty: false } interpolatedStrings) + ArrayBuilder? interpolatedStringBuilder = null; + int lastIndexForSpilling = -1; + + for (int i = 0; i < arguments.Length; i++) { + if (arguments[i].Value is IInterpolatedStringHandlerCreationOperation creation) + { + lastIndexForSpilling = i; + interpolatedStringBuilder ??= ArrayBuilder.GetInstance(); + interpolatedStringBuilder.Add(creation); + } + } + + if (lastIndexForSpilling > -1) + { + Debug.Assert(interpolatedStringBuilder != null); _currentInterpolatedStringHandlerArgumentContext = new InterpolatedStringHandlerArgumentsContext( - interpolatedStrings, - _evalStack.Count - (instancePushed ? 1 : 0), + interpolatedStringBuilder.ToImmutableAndFree(), + startingStackDepth: _evalStack.Count - (instancePushed ? 1 : 0), hasReceiver: instancePushed); } - VisitAndPushArray(arguments, UnwrapArgument); - _currentInterpolatedStringHandlerArgumentContext = previousInterpolatedStringHandlerContext; - } + for (int i = 0; i < arguments.Length; i++) + { + // If there are declaration expressions in the arguments before an interpolated string handler, and that declaration + // expression is referenced by the handler constructor, we need to spill it to ensure the declaration doesn't end + // up in the tree twice. However, we don't want to generally introduce spilling for these declarations: that could + // have unexpected affects on consumers. So we limit the spilling to those indexes before the last interpolated string + // handler. We _could_ limit this further by only spilling declaration expressions if the handler in question actually + // referenced a specific declaration expression in the argument list, but we think that the difficulty in implementing + // this check is more complexity than this scenario needs. + var argument = arguments[i].Value switch + { + IDeclarationExpressionOperation declaration when i < lastIndexForSpilling => declaration.Expression, + var value => value + }; - private static readonly Func UnwrapArgument = UnwrapArgumentDoNotCaptureDirectly; + PushOperand(VisitRequired(argument)); + } - private static IOperation UnwrapArgumentDoNotCaptureDirectly(IArgumentOperation argument) - { - return argument.Value; + _currentInterpolatedStringHandlerArgumentContext = previousInterpolatedStringHandlerContext; } private IArgumentOperation RewriteArgumentFromArray(IOperation visitedArgument, int index, ImmutableArray args)