diff --git a/azure-pipelines-official.yml b/azure-pipelines-official.yml index e4ebd077300af..264be2cdfc3e5 100644 --- a/azure-pipelines-official.yml +++ b/azure-pipelines-official.yml @@ -66,6 +66,9 @@ stages: - stage: build displayName: Build and Test + pool: + name: NetCore1ESPool-Svc-Internal + demands: ImageOverride -equals Build.Server.Amd64.VS2019 jobs: - ${{ if eq(variables['Build.SourceBranch'], 'refs/heads/release/dev17.1-vs-deps') }}: @@ -81,9 +84,6 @@ stages: - job: OfficialBuild displayName: Official Build timeoutInMinutes: 360 - pool: - name: NetCore1ESPool-Internal - demands: ImageOverride -equals Build.Server.Amd64.VS2019 steps: - powershell: Write-Host "##vso[task.setvariable variable=SourceBranchName]$('$(Build.SourceBranch)'.Substring('refs/heads/'.Length))" diff --git a/docs/Language Feature Status.md b/docs/Language Feature Status.md index abc411ef40450..003bb89aa1434 100644 --- a/docs/Language Feature Status.md +++ b/docs/Language Feature Status.md @@ -10,19 +10,20 @@ efforts behind them. | Feature | Branch | State | Developer | Reviewer | LDM Champ | | ------- | ------ | ----- | --------- | -------- | --------- | +| [Newlines in interpolations](https://github.com/dotnet/csharplang/issues/4935) | main | [Merged in 17.1p1](https://github.com/dotnet/roslyn/issues/57154) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [jcouv](https://github.com/jcouv), [chsienki](https://github.com/chsienki) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | +| [List patterns](https://github.com/dotnet/csharplang/issues/3435) | [list-patterns](https://github.com/dotnet/roslyn/tree/features/list-patterns) | [Merged in 17.1p2](https://github.com/dotnet/roslyn/issues/51289) | [alrz](https://github.com/alrz) | [jcouv](https://github.com/jcouv), [333fred](https://github.com/333fred) | [333fred](https://github.com/333fred) | +| [Parameter null-checking](https://github.com/dotnet/csharplang/issues/2145) | [param-nullchecking](https://github.com/dotnet/roslyn/tree/features/param-nullchecking) | [In Progress](https://github.com/dotnet/roslyn/issues/36024) | [RikkiGibson](https://github.com/RikkiGibson), [fayrose](https://github.com/fayrose) | [cston](https://github.com/cston), [chsienki](https://github.com/chsienki) | [jaredpar](https://github.com/jaredpar) | +| [Raw string literals](https://github.com/dotnet/csharplang/issues/4304) | [RawStringLiterals](https://github.com/dotnet/roslyn/tree/features/features/RawStringLiterals) | [In Progress](https://github.com/dotnet/roslyn/issues/55306) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [jcouv](https://github.com/jcouv) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | | [nameof(parameter)](https://github.com/dotnet/csharplang/issues/373) | main | [In Progress](https://github.com/dotnet/roslyn/issues/40524) | [jcouv](https://github.com/jcouv) | TBD | [jcouv](https://github.com/jcouv) | | [Relax ordering of `ref` and `partial` modifiers](https://github.com/dotnet/csharplang/issues/946) | [ref-partial](https://github.com/dotnet/roslyn/tree/features/ref-partial) | In Progress | [alrz](https://github.com/alrz) | [gafter](https://github.com/gafter) | [jcouv](https://github.com/jcouv) | -| [Parameter null-checking](https://github.com/dotnet/csharplang/issues/2145) | [param-nullchecking](https://github.com/dotnet/roslyn/tree/features/param-nullchecking) | [In Progress](https://github.com/dotnet/roslyn/issues/36024) | [RikkiGibson](https://github.com/RikkiGibson), [fayrose](https://github.com/fayrose) | [cston](https://github.com/cston), [chsienki](https://github.com/chsienki) | [jaredpar](https://github.com/jaredpar) | | [Generic attributes](https://github.com/dotnet/csharplang/issues/124) | [generic-attributes](https://github.com/dotnet/roslyn/tree/features/generic-attributes) | [Merged into 17.0p4 (preview langver)](https://github.com/dotnet/roslyn/issues/36285) | [AviAvni](https://github.com/AviAvni) | [RikkiGibson](https://github.com/RikkiGibson), [jcouv](https://github.com/jcouv) | [mattwar](https://github.com/mattwar) | | [Default in deconstruction](https://github.com/dotnet/roslyn/pull/25562) | [decon-default](https://github.com/dotnet/roslyn/tree/features/decon-default) | [Implemented](https://github.com/dotnet/roslyn/issues/25559) | [jcouv](https://github.com/jcouv) | [gafter](https://github.com/gafter) | [jcouv](https://github.com/jcouv) | -| [List patterns](https://github.com/dotnet/csharplang/issues/3435) | [list-patterns](https://github.com/dotnet/roslyn/tree/features/list-patterns) | [In Progress](https://github.com/dotnet/roslyn/issues/51289) | [alrz](https://github.com/alrz) | [jcouv](https://github.com/jcouv), [333fred](https://github.com/333fred) | [333fred](https://github.com/333fred) | -| [Raw string literals](https://github.com/dotnet/csharplang/issues/4304) | [RawStringLiterals](https://github.com/dotnet/roslyn/tree/features/features/RawStringLiterals) | [In Progress](https://github.com/dotnet/roslyn/issues/55306) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [jcouv](https://github.com/jcouv) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | | [Semi-auto-properties](https://github.com/dotnet/csharplang/issues/140) | [semi-auto-properties](https://github.com/dotnet/roslyn/tree/features/features/semi-auto-properties) | [In Progress](https://github.com/dotnet/roslyn/issues/57012) | [Youssef1313](https://github.com/Youssef1313) | TBD | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | -| [Required properties](https://github.com/dotnet/csharplang/issues/3630) | [required-properties](https://github.com/dotnet/roslyn/tree/features/features/required-properties) | [In Progress](https://github.com/dotnet/roslyn/issues/57046) | [333fred](https://github.com/333fred) | TBD | [333fred](https://github.com/333fred) | +| [Required members](https://github.com/dotnet/csharplang/issues/3630) | [required-members](https://github.com/dotnet/roslyn/tree/features/required-members) | [In Progress](https://github.com/dotnet/roslyn/issues/57046) | [333fred](https://github.com/333fred) | [jcouv](https://github.com/jcouv), [RikkiGibson](https://github.com/RikkiGibson) | [333fred](https://github.com/333fred) | | [Top Level statement attribute specifiers](https://github.com/dotnet/csharplang/issues/5045) | [main-attributes](https://github.com/dotnet/roslyn/tree/features/features/main-attributes) | [In Progress](https://github.com/dotnet/roslyn/issues/57047) | [chsienki](https://github.com/chsienki) | TBD | [jaredpar](https://github.com/jaredpar) | | [Primary Constructors](https://github.com/dotnet/csharplang/issues/2691) | [primary-constructors](https://github.com/dotnet/roslyn/tree/features/features/primary-constructors) | [In Progress](https://github.com/dotnet/roslyn/issues/57048) | TBD | TBD | [MadsTorgersen](https://github.com/MadsTorgersen) | | [Params Span + Stackalloc any array type](https://github.com/dotnet/csharplang/issues/1757) | [params-span](https://github.com/dotnet/roslyn/tree/features/features/params-span) | [In Progress](https://github.com/dotnet/roslyn/issues/57049) | [cston](https://github.com/cston) | TBD | [jaredpar](https://github.com/jaredpar) | -| [Newlines in interpolations](https://github.com/dotnet/csharplang/issues/4935) | main | [In Progress](https://github.com/dotnet/roslyn/issues/57154) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [jcouv](https://github.com/jcouv), TBD | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | + # C# 10.0 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 6315613559c09..85896ac039e85 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -6,25 +6,25 @@ 7e80445ee82adbf9a8e6ae601ac5e239d982afaa - + https://github.com/dotnet/source-build - 84050eb5821252b0e9753e2a54dbd978ed217bf9 + 90bdf447e1b97605f109b34243ab8c9f215308e9 - + https://github.com/dotnet/arcade - 18adc5b47acce8bb03948baf578fca442d1029d4 + 943d03f62955c771825dfa1f1bdeb8f853a2d7dd https://github.com/dotnet/roslyn 818313426323d979747781a17c78860c833776da - + https://github.com/dotnet/arcade - 18adc5b47acce8bb03948baf578fca442d1029d4 + 943d03f62955c771825dfa1f1bdeb8f853a2d7dd diff --git a/eng/Versions.props b/eng/Versions.props index f0ffd74e8c5fe..5f8c358c92b7d 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -237,7 +237,7 @@ 4.5.1 4.3.0 - 4.7.0 + 6.0.0 5.0.0 4.5.0 @@ -279,7 +279,7 @@ --> 5.0.0 5.0.0 - 5.0.0 + 6.0.0 true diff --git a/eng/targets/Services.props b/eng/targets/Services.props index cad3d601e7b63..c4421f0d7a042 100644 --- a/eng/targets/Services.props +++ b/eng/targets/Services.props @@ -15,7 +15,6 @@ - diff --git a/global.json b/global.json index 1c7d412b7817d..fd86f0793bb3e 100644 --- a/global.json +++ b/global.json @@ -12,7 +12,7 @@ "xcopy-msbuild": "16.10.0-preview2" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "7.0.0-beta.21610.4", - "Microsoft.DotNet.Helix.Sdk": "7.0.0-beta.21610.4" + "Microsoft.DotNet.Arcade.Sdk": "7.0.0-beta.21615.1", + "Microsoft.DotNet.Helix.Sdk": "7.0.0-beta.21615.1" } } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.QueryUnboundLambdaState.cs b/src/Compilers/CSharp/Portable/Binder/Binder.QueryUnboundLambdaState.cs index b750043fad8c2..a65b7103def98 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.QueryUnboundLambdaState.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.QueryUnboundLambdaState.cs @@ -32,6 +32,7 @@ public QueryUnboundLambdaState(Binder binder, RangeVariableMap rangeVariableMap, public override string ParameterName(int index) { return _parameters[index].Name; } public override bool ParameterIsDiscard(int index) { return false; } + public override bool ParameterIsNullChecked(int index) { return false; } public override SyntaxList ParameterAttributes(int index) => default; public override bool HasNames { get { return true; } } public override bool HasSignature { get { return true; } } 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/Binder/Binder_Lambda.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Lambda.cs index ce84a2241399c..68b5133be2f19 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Lambda.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Lambda.cs @@ -40,6 +40,7 @@ private UnboundLambda AnalyzeAnonymousFunction( ImmutableArray names = default; ImmutableArray refKinds = default; + ImmutableArray nullCheckedOpt = default; ImmutableArray types = default; RefKind returnRefKind = RefKind.None; TypeWithAnnotations returnType = default; @@ -63,6 +64,10 @@ private UnboundLambda AnalyzeAnonymousFunction( hasSignature = true; var simple = (SimpleLambdaExpressionSyntax)syntax; namesBuilder.Add(simple.Parameter.Identifier.ValueText); + if (isNullChecked(simple.Parameter)) + { + nullCheckedOpt = ImmutableArray.Create(true); + } break; case SyntaxKind.ParenthesizedLambdaExpression: // (T x, U y) => ... @@ -98,6 +103,7 @@ private UnboundLambda AnalyzeAnonymousFunction( var typesBuilder = ArrayBuilder.GetInstance(); var refKindsBuilder = ArrayBuilder.GetInstance(); + var nullCheckedBuilder = ArrayBuilder.GetInstance(); var attributesBuilder = ArrayBuilder>.GetInstance(); // In the batch compiler case we probably should have given a syntax error if the @@ -176,6 +182,7 @@ private UnboundLambda AnalyzeAnonymousFunction( namesBuilder.Add(p.Identifier.ValueText); typesBuilder.Add(type); refKindsBuilder.Add(refKind); + nullCheckedBuilder.Add(isNullChecked(p)); attributesBuilder.Add(syntax.Kind() == SyntaxKind.ParenthesizedLambdaExpression ? p.AttributeLists : default); } @@ -191,6 +198,11 @@ private UnboundLambda AnalyzeAnonymousFunction( refKinds = refKindsBuilder.ToImmutable(); } + if (nullCheckedBuilder.Contains(true)) + { + nullCheckedOpt = nullCheckedBuilder.ToImmutable(); + } + if (attributesBuilder.Any(a => a.Count > 0)) { parameterAttributes = attributesBuilder.ToImmutable(); @@ -198,6 +210,7 @@ private UnboundLambda AnalyzeAnonymousFunction( typesBuilder.Free(); refKindsBuilder.Free(); + nullCheckedBuilder.Free(); attributesBuilder.Free(); } @@ -208,7 +221,10 @@ private UnboundLambda AnalyzeAnonymousFunction( namesBuilder.Free(); - return UnboundLambda.Create(syntax, this, diagnostics.AccumulatesDependencies, returnRefKind, returnType, parameterAttributes, refKinds, types, names, discardsOpt, isAsync, isStatic); + return UnboundLambda.Create(syntax, this, diagnostics.AccumulatesDependencies, returnRefKind, returnType, parameterAttributes, refKinds, types, names, discardsOpt, nullCheckedOpt, isAsync, isStatic); + + static bool isNullChecked(ParameterSyntax parameter) + => parameter.ExclamationExclamationToken.IsKind(SyntaxKind.ExclamationExclamationToken); static ImmutableArray computeDiscards(SeparatedSyntaxList parameters, int underscoresCount) { diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index 95f7b6758aec2..55c775eec99ee 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -3497,7 +3497,7 @@ private BoundNode BindConstructorBody(ConstructorDeclarationSyntax constructor, bool thisInitializer = initializer?.IsKind(SyntaxKind.ThisConstructorInitializer) == true; if (!thisInitializer && - ContainingType.GetMembersUnordered().OfType().Any()) + hasAnyRecordConstructors()) { var constructorSymbol = (MethodSymbol)this.ContainingMember(); if (!constructorSymbol.IsStatic && @@ -3514,6 +3514,12 @@ private BoundNode BindConstructorBody(ConstructorDeclarationSyntax constructor, && thisInitializer && ContainingType.IsDefaultValueTypeConstructor(initializer); + if (skipInitializer && + hasAnyRecordConstructors()) + { + Error(diagnostics, ErrorCode.ERR_RecordStructConstructorCallsDefaultConstructor, initializer.ThisOrBaseKeyword); + } + // Using BindStatement to bind block to make sure we are reusing results of partial binding in SemanticModel return new BoundConstructorMethodBody(constructor, bodyBinder.GetDeclaredLocalsForScope(constructor), @@ -3524,6 +3530,9 @@ private BoundNode BindConstructorBody(ConstructorDeclarationSyntax constructor, null : bodyBinder.BindExpressionBodyAsBlock(constructor.ExpressionBody, constructor.Body == null ? diagnostics : BindingDiagnosticBag.Discarded)); + + bool hasAnyRecordConstructors() => + ContainingType.GetMembersUnordered().OfType().Any(); } internal virtual BoundExpressionStatement BindConstructorInitializer(ConstructorInitializerSyntax initializer, BindingDiagnosticBag diagnostics) 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/BoundTree/UnboundLambda.cs b/src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs index 91d12ba91a738..76baf682e82d8 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs @@ -383,6 +383,7 @@ public static UnboundLambda Create( ImmutableArray types, ImmutableArray names, ImmutableArray discardsOpt, + ImmutableArray nullCheckedOpt, bool isAsync, bool isStatic) { @@ -391,7 +392,7 @@ public static UnboundLambda Create( bool hasErrors = !types.IsDefault && types.Any(t => t.Type?.Kind == SymbolKind.ErrorType); var functionType = FunctionTypeSymbol.CreateIfFeatureEnabled(syntax, binder, static (binder, expr) => ((UnboundLambda)expr).Data.InferDelegateType()); - var data = new PlainUnboundLambdaState(binder, returnRefKind, returnType, parameterAttributes, names, discardsOpt, types, refKinds, isAsync, isStatic, includeCache: true); + var data = new PlainUnboundLambdaState(binder, returnRefKind, returnType, parameterAttributes, names, discardsOpt, nullCheckedOpt, types, refKinds, isAsync, isStatic, includeCache: true); var lambda = new UnboundLambda(syntax, data, functionType, withDependencies, hasErrors: hasErrors); data.SetUnboundLambda(lambda); functionType?.SetExpression(lambda.WithNoCache()); @@ -458,6 +459,7 @@ public TypeWithAnnotations InferReturnType(ConversionsBase conversions, NamedTyp public Location ParameterLocation(int index) { return Data.ParameterLocation(index); } public string ParameterName(int index) { return Data.ParameterName(index); } public bool ParameterIsDiscard(int index) { return Data.ParameterIsDiscard(index); } + public bool ParameterIsNullChecked(int index) { return Data.ParameterIsNullChecked(index); } } internal abstract class UnboundLambdaState @@ -517,6 +519,7 @@ internal UnboundLambdaState WithCaching(bool includeCache) public abstract MessageID MessageID { get; } public abstract string ParameterName(int index); public abstract bool ParameterIsDiscard(int index); + public abstract bool ParameterIsNullChecked(int index); public abstract SyntaxList ParameterAttributes(int index); public abstract bool HasSignature { get; } public abstract bool HasExplicitReturnType(out RefKind refKind, out TypeWithAnnotations returnType); @@ -1321,6 +1324,7 @@ internal sealed class PlainUnboundLambdaState : UnboundLambdaState private readonly ImmutableArray> _parameterAttributes; private readonly ImmutableArray _parameterNames; private readonly ImmutableArray _parameterIsDiscardOpt; + private readonly ImmutableArray _parameterIsNullCheckedOpt; private readonly ImmutableArray _parameterTypesWithAnnotations; private readonly ImmutableArray _parameterRefKinds; private readonly bool _isAsync; @@ -1333,6 +1337,7 @@ internal PlainUnboundLambdaState( ImmutableArray> parameterAttributes, ImmutableArray parameterNames, ImmutableArray parameterIsDiscardOpt, + ImmutableArray parameterIsNullCheckedOpt, ImmutableArray parameterTypesWithAnnotations, ImmutableArray parameterRefKinds, bool isAsync, @@ -1345,6 +1350,7 @@ internal PlainUnboundLambdaState( _parameterAttributes = parameterAttributes; _parameterNames = parameterNames; _parameterIsDiscardOpt = parameterIsDiscardOpt; + _parameterIsNullCheckedOpt = parameterIsNullCheckedOpt; _parameterTypesWithAnnotations = parameterTypesWithAnnotations; _parameterRefKinds = parameterRefKinds; _isAsync = isAsync; @@ -1414,6 +1420,11 @@ public override bool ParameterIsDiscard(int index) return _parameterIsDiscardOpt.IsDefault ? false : _parameterIsDiscardOpt[index]; } + public override bool ParameterIsNullChecked(int index) + { + return _parameterIsNullCheckedOpt.IsDefault ? false : _parameterIsNullCheckedOpt[index]; + } + public override RefKind RefKind(int index) { Debug.Assert(0 <= index && index < _parameterTypesWithAnnotations.Length); @@ -1429,7 +1440,7 @@ public override TypeWithAnnotations ParameterTypeWithAnnotations(int index) protected override UnboundLambdaState WithCachingCore(bool includeCache) { - return new PlainUnboundLambdaState(Binder, _returnRefKind, _returnType, _parameterAttributes, _parameterNames, _parameterIsDiscardOpt, _parameterTypesWithAnnotations, _parameterRefKinds, _isAsync, _isStatic, includeCache); + return new PlainUnboundLambdaState(Binder, _returnRefKind, _returnType, _parameterAttributes, _parameterNames, _parameterIsDiscardOpt, _parameterIsNullCheckedOpt, _parameterTypesWithAnnotations, _parameterRefKinds, _isAsync, _isStatic, includeCache); } protected override BoundExpression? GetLambdaExpressionBody(BoundBlock body) diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index a4f0dcd760e99..832ef7bcb07ea 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -363,6 +363,9 @@ type variance + + parameter null-checking + parameter @@ -6166,6 +6169,30 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Duplicate null suppression operator ('!') + + Incorrect parameter null checking syntax. Should be '!!'. + + + Parameter '{0}' can only have exclamation-point null checking in implementation methods. + + + Parameter '{0}' is a non-nullable value type and cannot be null-checked. + + + Parameter '{0}' is null-checked but is null by default. + + + Parameter is null-checked but is null by default. + + + By-reference parameter '{0}' cannot be null-checked. + + + Nullable type '{0}' is null-checked and will throw if null. + + + Nullable type is null-checked and will throw if null. + Type '{0}' cannot be embedded because it has a re-abstraction of a member from base interface. Consider setting the 'Embed Interop Types' property to false. @@ -6884,6 +6911,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ A lambda expression with attributes cannot be converted to an expression tree + + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + The operation may overflow '{0}' at runtime (use 'unchecked' syntax to override) diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs index b8bf3ced13628..875093a533ff0 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs @@ -1562,7 +1562,7 @@ internal override ISymbolInternal CommonGetSpecialTypeMember(SpecialMember speci internal TypeSymbol GetTypeByReflectionType(Type type, BindingDiagnosticBag diagnostics) { var result = Assembly.GetTypeByReflectionType(type, includeReferences: true); - if ((object)result == null) + if (result is null) { var errorType = new ExtendedErrorTypeSymbol(this, type.Name, 0, CreateReflectionTypeNotFoundError(type)); diagnostics.Add(errorType.ErrorInfo, NoLocation.Singleton); @@ -1590,9 +1590,9 @@ protected override ITypeSymbol? CommonScriptGlobalsType { if (HostObjectType != null && _lazyHostObjectTypeSymbol is null) { - TypeSymbol symbol = Assembly.GetTypeByReflectionType(HostObjectType, includeReferences: true); + TypeSymbol? symbol = Assembly.GetTypeByReflectionType(HostObjectType, includeReferences: true); - if ((object)symbol == null) + if (symbol is null) { MetadataTypeName mdName = MetadataTypeName.FromNamespaceAndTypeName(HostObjectType.Namespace ?? String.Empty, HostObjectType.Name, diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs index 533253c805e6b..a6615e7e8992e 100644 --- a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs +++ b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs @@ -1211,7 +1211,7 @@ forSemanticModel.Syntax is { } semanticModelSyntax && hasErrors = hasErrors || (hasBody && loweredBodyOpt.HasErrors) || diagsForCurrentMethod.HasAnyErrors(); SetGlobalErrorIfTrue(hasErrors); - + CSharpSyntaxNode syntax = methodSymbol.GetNonNullSyntaxNode(); // don't emit if the resulting method would contain initializers with errors if (!hasErrors && (hasBody || includeNonEmptyInitializersInBody)) { @@ -1290,12 +1290,29 @@ forSemanticModel.Syntax is { } semanticModelSyntax && { boundStatements = boundStatements.Concat(ImmutableArray.Create(loweredBodyOpt)); } - } + var factory = new SyntheticBoundNodeFactory(methodSymbol, syntax, compilationState, diagsForCurrentMethod); + + // Iterators handled in IteratorRewriter.cs + if (!methodSymbol.IsIterator) + { + var boundStatementsWithNullCheck = LocalRewriter.TryConstructNullCheckedStatementList(methodSymbol.Parameters, boundStatements, factory); + + if (!boundStatementsWithNullCheck.IsDefault) + { + boundStatements = boundStatementsWithNullCheck; + hasErrors = boundStatementsWithNullCheck.HasErrors() || diagsForCurrentMethod.HasAnyErrors(); + SetGlobalErrorIfTrue(hasErrors); + if (hasErrors) + { + _diagnostics.AddRange(diagsForCurrentMethod); + return; + } + } + } + } if (_emitMethodBodies && (!(methodSymbol is SynthesizedStaticConstructor cctor) || cctor.ShouldEmit(processedInitializers.BoundInitializers))) { - CSharpSyntaxNode syntax = methodSymbol.GetNonNullSyntaxNode(); - var boundBody = BoundStatementList.Synthesized(syntax, boundStatements); var emittedBody = GenerateMethodBody( diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs b/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs index 4a7a1bd74aa92..67ccd10bfc69a 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs @@ -1289,18 +1289,19 @@ internal Cci.IMethodReference Translate( } } +#nullable enable private Cci.IMethodReference Translate( MethodSymbol methodSymbol, SyntaxNode syntaxNodeOpt, DiagnosticBag diagnostics, bool needDeclaration) { - object reference; + object? reference; Cci.IMethodReference methodRef; NamedTypeSymbol container = methodSymbol.ContainingType; // Method of anonymous type being translated - if (container.IsAnonymousType) + if (container?.IsAnonymousType == true) { Debug.Assert(!needDeclaration); methodSymbol = AnonymousTypeManager.TranslateAnonymousTypeMethodSymbol(methodSymbol); @@ -1363,6 +1364,7 @@ private Cci.IMethodReference Translate( return methodSymbol.GetCciAdapter(); } +#nullable disable internal Cci.IMethodReference TranslateOverriddenMethodReference( MethodSymbol methodSymbol, @@ -1777,6 +1779,39 @@ internal void EnsureNativeIntegerAttributeExists() EnsureEmbeddableAttributeExists(EmbeddableAttributes.NativeIntegerAttribute); } +#nullable enable + /// + /// Creates the ThrowIfNull and Throw helpers if needed. + /// + /// + /// The ThrowIfNull and Throw helpers are modeled off of the helpers on ArgumentNullException. + /// https://github.com/dotnet/runtime/blob/22663769611ba89cd92d14cfcb76e287f8af2335/src/libraries/System.Private.CoreLib/src/System/ArgumentNullException.cs#L56-L69 + /// + internal MethodSymbol EnsureThrowIfNullFunctionExists(SyntaxNode syntaxNode, SyntheticBoundNodeFactory factory, DiagnosticBag? diagnostics) + { + var privateImplClass = GetPrivateImplClass(syntaxNode, diagnostics); + var throwIfNullAdapter = privateImplClass.GetMethod(PrivateImplementationDetails.SynthesizedThrowIfNullFunctionName); + if (throwIfNullAdapter is null) + { + TypeSymbol returnType = factory.SpecialType(SpecialType.System_Void); + TypeSymbol argumentType = factory.SpecialType(SpecialType.System_Object); + TypeSymbol paramNameType = factory.SpecialType(SpecialType.System_String); + var sourceModule = SourceModule; + + // use add-then-get pattern to ensure the symbol exists, and then ensure we use the single "canonical" instance added by whichever thread won the race. + privateImplClass.TryAddSynthesizedMethod(new SynthesizedThrowMethod(sourceModule, privateImplClass, returnType, paramNameType).GetCciAdapter()); + var actuallyAddedThrowMethod = (SynthesizedThrowMethod)privateImplClass.GetMethod(PrivateImplementationDetails.SynthesizedThrowFunctionName)!.GetInternalSymbol()!; + privateImplClass.TryAddSynthesizedMethod(new SynthesizedThrowIfNullMethod(sourceModule, privateImplClass, actuallyAddedThrowMethod, returnType, argumentType, paramNameType).GetCciAdapter()); + throwIfNullAdapter = privateImplClass.GetMethod(PrivateImplementationDetails.SynthesizedThrowIfNullFunctionName)!; + } + + var internalSymbol = (SynthesizedThrowIfNullMethod)throwIfNullAdapter.GetInternalSymbol()!; + // the ThrowMethod referenced by the ThrowIfNullMethod must be the same instance as the ThrowMethod contained in the PrivateImplementationDetails + Debug.Assert((object?)privateImplClass.GetMethod(PrivateImplementationDetails.SynthesizedThrowFunctionName)!.GetInternalSymbol() == internalSymbol.ThrowMethod); + return internalSymbol; + } +#nullable disable + public override IEnumerable GetAdditionalTopLevelTypeDefinitions(EmitContext context) { return GetAdditionalTopLevelTypes() diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 1aca126844b4a..7d18a53a3ea6a 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -2007,6 +2007,13 @@ internal enum ErrorCode // at runtime. ERR_CannotUseRefInUnmanagedCallersOnly = 8977, + ERR_IncorrectNullCheckSyntax = 8990, + ERR_MustNullCheckInImplementation = 8991, + ERR_NonNullableValueTypeIsNullChecked = 8992, + WRN_NullCheckedHasDefaultNull = 8993, + ERR_NullCheckingOnByRefParameter = 8994, + WRN_NullCheckingOnNullableType = 8995, + #endregion #region diagnostics introduced for C# 11.0 @@ -2016,6 +2023,7 @@ internal enum ErrorCode ERR_MisplacedSlicePattern = 8980, WRN_LowerCaseTypeName = 8981, + ERR_RecordStructConstructorCallsDefaultConstructor = 8982, #endregion diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs index 5b9956d3ae982..137316a152edc 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs @@ -455,6 +455,8 @@ internal static int GetWarningLevel(ErrorCode code) case ErrorCode.WRN_UndecoratedCancellationTokenParameter: case ErrorCode.WRN_NullabilityMismatchInTypeParameterNotNullConstraint: case ErrorCode.WRN_DisallowNullAttributeForbidsMaybeNullAssignment: + case ErrorCode.WRN_NullCheckedHasDefaultNull: + case ErrorCode.WRN_NullCheckingOnNullableType: case ErrorCode.WRN_ParameterConditionallyDisallowsNull: case ErrorCode.WRN_NullReferenceInitializer: case ErrorCode.WRN_ShouldNotReturn: diff --git a/src/Compilers/CSharp/Portable/Errors/MessageID.cs b/src/Compilers/CSharp/Portable/Errors/MessageID.cs index 9afceff25cbcb..d4869973012a7 100644 --- a/src/Compilers/CSharp/Portable/Errors/MessageID.cs +++ b/src/Compilers/CSharp/Portable/Errors/MessageID.cs @@ -240,6 +240,7 @@ internal enum MessageID IDS_FeatureNewLinesInInterpolations = MessageBase + 12813, IDS_FeatureListPattern = MessageBase + 12814, + IDS_ParameterNullChecking = MessageBase + 12815, } // Message IDs may refer to strings that need to be localized. @@ -353,6 +354,7 @@ internal static LanguageVersion RequiredVersion(this MessageID feature) case MessageID.IDS_FeatureGenericAttributes: // semantic check case MessageID.IDS_FeatureNewLinesInInterpolations: // semantic check case MessageID.IDS_FeatureListPattern: // semantic check + case MessageID.IDS_ParameterNullChecking: // syntax check return LanguageVersion.Preview; // C# 10.0 features. 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 7709892c6d048..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)); } @@ -1626,7 +1627,7 @@ private NullableFlowState GetDefaultState(ref LocalState state, int slot) { parameterType = parameter.TypeWithAnnotations; } - return GetParameterState(parameterType, parameter.FlowAnalysisAnnotations).State; + return GetParameterState(parameterType, parameter.FlowAnalysisAnnotations, parameter.IsNullChecked).State; } case SymbolKind.Field: case SymbolKind.Property: @@ -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; @@ -2044,7 +2046,11 @@ internal static bool AreParameterAnnotationsCompatible( { // pre-condition attributes // Check whether we can assign a value from overridden parameter to overriding - var valueState = GetParameterState(overriddenType, overriddenAnnotations); + var valueState = GetParameterState( + overriddenType, + overriddenAnnotations, + // We don't consider '!!' when deciding whether 'overridden' is compatible with 'override' + isNullChecked: false); if (isBadAssignment(valueState, overridingType, overridingAnnotations)) { return false; @@ -2470,7 +2476,7 @@ private void EnterParameter(ParameterSymbol parameter, TypeWithAnnotations param Debug.Assert(!IsConditionalState); if (slot > 0) { - var state = GetParameterState(parameterType, parameter.FlowAnalysisAnnotations).State; + var state = GetParameterState(parameterType, parameter.FlowAnalysisAnnotations, parameter.IsNullChecked).State; this.State[slot] = state; if (EmptyStructTypeCache.IsTrackableStructType(parameterType.Type)) { @@ -2503,8 +2509,13 @@ private void EnterParameter(ParameterSymbol parameter, TypeWithAnnotations param return null; } - internal static TypeWithState GetParameterState(TypeWithAnnotations parameterType, FlowAnalysisAnnotations parameterAnnotations) + internal static TypeWithState GetParameterState(TypeWithAnnotations parameterType, FlowAnalysisAnnotations parameterAnnotations, bool isNullChecked) { + if (isNullChecked) + { + return TypeWithState.Create(parameterType.Type, NullableFlowState.NotNull); + } + if ((parameterAnnotations & FlowAnalysisAnnotations.AllowNull) != 0) { return TypeWithState.Create(parameterType.Type, NullableFlowState.MaybeDefault); @@ -3305,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); } @@ -3330,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) { @@ -4239,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; } @@ -5381,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) @@ -5407,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 @@ -5456,6 +5480,7 @@ private ImmutableArray VisitArguments( parameterType, parameterAnnotations, results[i], + conversionResultsBuilder, invokedAsExtensionMethod && i == 0); _disableDiagnostics = previousDisableDiagnostics; @@ -5472,6 +5497,8 @@ private ImmutableArray VisitArguments( } } + conversionResultsBuilder.Free(); + if (node is BoundCall { Method: { OriginalDefinition: LocalFunctionSymbol localFunction } }) { VisitLocalFunctionUse(localFunction); @@ -5619,7 +5646,6 @@ void markMembersAsNotNull(int receiverSlot, TypeSymbol type, string memberName, } private ImmutableArray VisitArgumentsEvaluate( - SyntaxNode syntax, ImmutableArray arguments, ImmutableArray refKindsOpt, ImmutableArray parametersOpt, @@ -5716,6 +5742,7 @@ private void VisitArgumentConversionAndInboundAssignmentsAndPreConditions( TypeWithAnnotations parameterType, FlowAnalysisAnnotations parameterAnnotations, VisitArgumentResult result, + ArrayBuilder? conversionResultsBuilder, bool extensionMethodThisArgument) { Debug.Assert(!this.IsConditionalState); @@ -5752,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)) @@ -5760,6 +5788,7 @@ private void VisitArgumentConversionAndInboundAssignmentsAndPreConditions( LearnFromNonNullTest(argumentNoConversion, ref State); } SetResultType(argumentNoConversion, stateAfterConversion, updateAnalyzedNullability: false); + conversionResultsBuilder?.Add(_visitResult); } break; case RefKind.Ref: @@ -5780,8 +5809,10 @@ private void VisitArgumentConversionAndInboundAssignmentsAndPreConditions( } } + conversionResultsBuilder?.Add(result.VisitResult); break; case RefKind.Out: + conversionResultsBuilder?.Add(result.VisitResult); break; default: throw ExceptionUtilities.UnexpectedValue(refKind); @@ -6983,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) { @@ -7031,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); @@ -7106,7 +7137,7 @@ private TypeWithState VisitConversion( break; case ConversionKind.InterpolatedStringHandler: - // https://github.com/dotnet/roslyn/issues/54583 Handle + visitInterpolatedStringHandlerConstructor(); resultState = NullableFlowState.NotNull; break; @@ -7500,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( @@ -7977,7 +8063,7 @@ private void VisitThisOrBaseReference(BoundExpression node) var parameter = node.ParameterSymbol; int slot = GetOrCreateSlot(parameter); var parameterType = GetDeclaredParameterResult(parameter); - var typeWithState = GetParameterState(parameterType, parameter.FlowAnalysisAnnotations); + var typeWithState = GetParameterState(parameterType, parameter.FlowAnalysisAnnotations, parameter.IsNullChecked); SetResult(node, GetAdjustedResult(typeWithState, slot), parameterType); return null; } @@ -8272,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/Generated/CSharp.Generated.g4 b/src/Compilers/CSharp/Portable/Generated/CSharp.Generated.g4 index 886e950243cd3..7f8ef199e869d 100644 --- a/src/Compilers/CSharp/Portable/Generated/CSharp.Generated.g4 +++ b/src/Compilers/CSharp/Portable/Generated/CSharp.Generated.g4 @@ -157,7 +157,7 @@ parameter_list ; parameter - : attribute_list* modifier* type? (identifier_token | '__arglist') equals_value_clause? + : attribute_list* modifier* type? (identifier_token | '__arglist') '!!'? equals_value_clause? ; constructor_initializer diff --git a/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Internal.Generated.cs b/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Internal.Generated.cs index 3d49cf95a0d7e..248a299cfe280 100644 --- a/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Internal.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Internal.Generated.cs @@ -27732,12 +27732,13 @@ internal sealed partial class ParameterSyntax : BaseParameterSyntax internal readonly GreenNode? modifiers; internal readonly TypeSyntax? type; internal readonly SyntaxToken identifier; + internal readonly SyntaxToken? exclamationExclamationToken; internal readonly EqualsValueClauseSyntax? @default; - internal ParameterSyntax(SyntaxKind kind, GreenNode? attributeLists, GreenNode? modifiers, TypeSyntax? type, SyntaxToken identifier, EqualsValueClauseSyntax? @default, DiagnosticInfo[]? diagnostics, SyntaxAnnotation[]? annotations) + internal ParameterSyntax(SyntaxKind kind, GreenNode? attributeLists, GreenNode? modifiers, TypeSyntax? type, SyntaxToken identifier, SyntaxToken? exclamationExclamationToken, EqualsValueClauseSyntax? @default, DiagnosticInfo[]? diagnostics, SyntaxAnnotation[]? annotations) : base(kind, diagnostics, annotations) { - this.SlotCount = 5; + this.SlotCount = 6; if (attributeLists != null) { this.AdjustFlagsAndWidth(attributeLists); @@ -27755,6 +27756,11 @@ internal ParameterSyntax(SyntaxKind kind, GreenNode? attributeLists, GreenNode? } this.AdjustFlagsAndWidth(identifier); this.identifier = identifier; + if (exclamationExclamationToken != null) + { + this.AdjustFlagsAndWidth(exclamationExclamationToken); + this.exclamationExclamationToken = exclamationExclamationToken; + } if (@default != null) { this.AdjustFlagsAndWidth(@default); @@ -27762,11 +27768,11 @@ internal ParameterSyntax(SyntaxKind kind, GreenNode? attributeLists, GreenNode? } } - internal ParameterSyntax(SyntaxKind kind, GreenNode? attributeLists, GreenNode? modifiers, TypeSyntax? type, SyntaxToken identifier, EqualsValueClauseSyntax? @default, SyntaxFactoryContext context) + internal ParameterSyntax(SyntaxKind kind, GreenNode? attributeLists, GreenNode? modifiers, TypeSyntax? type, SyntaxToken identifier, SyntaxToken? exclamationExclamationToken, EqualsValueClauseSyntax? @default, SyntaxFactoryContext context) : base(kind) { this.SetFactoryContext(context); - this.SlotCount = 5; + this.SlotCount = 6; if (attributeLists != null) { this.AdjustFlagsAndWidth(attributeLists); @@ -27784,6 +27790,11 @@ internal ParameterSyntax(SyntaxKind kind, GreenNode? attributeLists, GreenNode? } this.AdjustFlagsAndWidth(identifier); this.identifier = identifier; + if (exclamationExclamationToken != null) + { + this.AdjustFlagsAndWidth(exclamationExclamationToken); + this.exclamationExclamationToken = exclamationExclamationToken; + } if (@default != null) { this.AdjustFlagsAndWidth(@default); @@ -27791,10 +27802,10 @@ internal ParameterSyntax(SyntaxKind kind, GreenNode? attributeLists, GreenNode? } } - internal ParameterSyntax(SyntaxKind kind, GreenNode? attributeLists, GreenNode? modifiers, TypeSyntax? type, SyntaxToken identifier, EqualsValueClauseSyntax? @default) + internal ParameterSyntax(SyntaxKind kind, GreenNode? attributeLists, GreenNode? modifiers, TypeSyntax? type, SyntaxToken identifier, SyntaxToken? exclamationExclamationToken, EqualsValueClauseSyntax? @default) : base(kind) { - this.SlotCount = 5; + this.SlotCount = 6; if (attributeLists != null) { this.AdjustFlagsAndWidth(attributeLists); @@ -27812,6 +27823,11 @@ internal ParameterSyntax(SyntaxKind kind, GreenNode? attributeLists, GreenNode? } this.AdjustFlagsAndWidth(identifier); this.identifier = identifier; + if (exclamationExclamationToken != null) + { + this.AdjustFlagsAndWidth(exclamationExclamationToken); + this.exclamationExclamationToken = exclamationExclamationToken; + } if (@default != null) { this.AdjustFlagsAndWidth(@default); @@ -27826,6 +27842,7 @@ internal ParameterSyntax(SyntaxKind kind, GreenNode? attributeLists, GreenNode? public override TypeSyntax? Type => this.type; /// Gets the identifier. public SyntaxToken Identifier => this.identifier; + public SyntaxToken? ExclamationExclamationToken => this.exclamationExclamationToken; public EqualsValueClauseSyntax? Default => this.@default; internal override GreenNode? GetSlot(int index) @@ -27835,7 +27852,8 @@ internal ParameterSyntax(SyntaxKind kind, GreenNode? attributeLists, GreenNode? 1 => this.modifiers, 2 => this.type, 3 => this.identifier, - 4 => this.@default, + 4 => this.exclamationExclamationToken, + 5 => this.@default, _ => null, }; @@ -27844,11 +27862,11 @@ internal ParameterSyntax(SyntaxKind kind, GreenNode? attributeLists, GreenNode? public override void Accept(CSharpSyntaxVisitor visitor) => visitor.VisitParameter(this); public override TResult Accept(CSharpSyntaxVisitor visitor) => visitor.VisitParameter(this); - public ParameterSyntax Update(Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList attributeLists, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList modifiers, TypeSyntax type, SyntaxToken identifier, EqualsValueClauseSyntax @default) + public ParameterSyntax Update(Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList attributeLists, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList modifiers, TypeSyntax type, SyntaxToken identifier, SyntaxToken exclamationExclamationToken, EqualsValueClauseSyntax @default) { - if (attributeLists != this.AttributeLists || modifiers != this.Modifiers || type != this.Type || identifier != this.Identifier || @default != this.Default) + if (attributeLists != this.AttributeLists || modifiers != this.Modifiers || type != this.Type || identifier != this.Identifier || exclamationExclamationToken != this.ExclamationExclamationToken || @default != this.Default) { - var newNode = SyntaxFactory.Parameter(attributeLists, modifiers, type, identifier, @default); + var newNode = SyntaxFactory.Parameter(attributeLists, modifiers, type, identifier, exclamationExclamationToken, @default); var diags = GetDiagnostics(); if (diags?.Length > 0) newNode = newNode.WithDiagnosticsGreen(diags); @@ -27862,15 +27880,15 @@ public ParameterSyntax Update(Microsoft.CodeAnalysis.Syntax.InternalSyntax.Synta } internal override GreenNode SetDiagnostics(DiagnosticInfo[]? diagnostics) - => new ParameterSyntax(this.Kind, this.attributeLists, this.modifiers, this.type, this.identifier, this.@default, diagnostics, GetAnnotations()); + => new ParameterSyntax(this.Kind, this.attributeLists, this.modifiers, this.type, this.identifier, this.exclamationExclamationToken, this.@default, diagnostics, GetAnnotations()); internal override GreenNode SetAnnotations(SyntaxAnnotation[]? annotations) - => new ParameterSyntax(this.Kind, this.attributeLists, this.modifiers, this.type, this.identifier, this.@default, GetDiagnostics(), annotations); + => new ParameterSyntax(this.Kind, this.attributeLists, this.modifiers, this.type, this.identifier, this.exclamationExclamationToken, this.@default, GetDiagnostics(), annotations); internal ParameterSyntax(ObjectReader reader) : base(reader) { - this.SlotCount = 5; + this.SlotCount = 6; var attributeLists = (GreenNode?)reader.ReadValue(); if (attributeLists != null) { @@ -27892,6 +27910,12 @@ internal ParameterSyntax(ObjectReader reader) var identifier = (SyntaxToken)reader.ReadValue(); AdjustFlagsAndWidth(identifier); this.identifier = identifier; + var exclamationExclamationToken = (SyntaxToken?)reader.ReadValue(); + if (exclamationExclamationToken != null) + { + AdjustFlagsAndWidth(exclamationExclamationToken); + this.exclamationExclamationToken = exclamationExclamationToken; + } var @default = (EqualsValueClauseSyntax?)reader.ReadValue(); if (@default != null) { @@ -27907,6 +27931,7 @@ internal override void WriteTo(ObjectWriter writer) writer.WriteValue(this.modifiers); writer.WriteValue(this.type); writer.WriteValue(this.identifier); + writer.WriteValue(this.exclamationExclamationToken); writer.WriteValue(this.@default); } @@ -35194,7 +35219,7 @@ public override CSharpSyntaxNode VisitBracketedParameterList(BracketedParameterL => node.Update((SyntaxToken)Visit(node.OpenBracketToken), VisitList(node.Parameters), (SyntaxToken)Visit(node.CloseBracketToken)); public override CSharpSyntaxNode VisitParameter(ParameterSyntax node) - => node.Update(VisitList(node.AttributeLists), VisitList(node.Modifiers), (TypeSyntax)Visit(node.Type), (SyntaxToken)Visit(node.Identifier), (EqualsValueClauseSyntax)Visit(node.Default)); + => node.Update(VisitList(node.AttributeLists), VisitList(node.Modifiers), (TypeSyntax)Visit(node.Type), (SyntaxToken)Visit(node.Identifier), (SyntaxToken)Visit(node.ExclamationExclamationToken), (EqualsValueClauseSyntax)Visit(node.Default)); public override CSharpSyntaxNode VisitFunctionPointerParameter(FunctionPointerParameterSyntax node) => node.Update(VisitList(node.AttributeLists), VisitList(node.Modifiers), (TypeSyntax)Visit(node.Type)); @@ -39436,7 +39461,7 @@ public BracketedParameterListSyntax BracketedParameterList(SyntaxToken openBrack return result; } - public ParameterSyntax Parameter(Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList attributeLists, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList modifiers, TypeSyntax? type, SyntaxToken identifier, EqualsValueClauseSyntax? @default) + public ParameterSyntax Parameter(Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList attributeLists, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList modifiers, TypeSyntax? type, SyntaxToken identifier, SyntaxToken? exclamationExclamationToken, EqualsValueClauseSyntax? @default) { #if DEBUG if (identifier == null) throw new ArgumentNullException(nameof(identifier)); @@ -39446,9 +39471,18 @@ public ParameterSyntax Parameter(Microsoft.CodeAnalysis.Syntax.InternalSyntax.Sy case SyntaxKind.ArgListKeyword: break; default: throw new ArgumentException(nameof(identifier)); } + if (exclamationExclamationToken != null) + { + switch (exclamationExclamationToken.Kind) + { + case SyntaxKind.ExclamationExclamationToken: + case SyntaxKind.None: break; + default: throw new ArgumentException(nameof(exclamationExclamationToken)); + } + } #endif - return new ParameterSyntax(SyntaxKind.Parameter, attributeLists.Node, modifiers.Node, type, identifier, @default, this.context); + return new ParameterSyntax(SyntaxKind.Parameter, attributeLists.Node, modifiers.Node, type, identifier, exclamationExclamationToken, @default, this.context); } public FunctionPointerParameterSyntax FunctionPointerParameter(Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList attributeLists, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList modifiers, TypeSyntax type) @@ -44441,7 +44475,7 @@ public static BracketedParameterListSyntax BracketedParameterList(SyntaxToken op return result; } - public static ParameterSyntax Parameter(Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList attributeLists, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList modifiers, TypeSyntax? type, SyntaxToken identifier, EqualsValueClauseSyntax? @default) + public static ParameterSyntax Parameter(Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList attributeLists, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList modifiers, TypeSyntax? type, SyntaxToken identifier, SyntaxToken? exclamationExclamationToken, EqualsValueClauseSyntax? @default) { #if DEBUG if (identifier == null) throw new ArgumentNullException(nameof(identifier)); @@ -44451,9 +44485,18 @@ public static ParameterSyntax Parameter(Microsoft.CodeAnalysis.Syntax.InternalSy case SyntaxKind.ArgListKeyword: break; default: throw new ArgumentException(nameof(identifier)); } + if (exclamationExclamationToken != null) + { + switch (exclamationExclamationToken.Kind) + { + case SyntaxKind.ExclamationExclamationToken: + case SyntaxKind.None: break; + default: throw new ArgumentException(nameof(exclamationExclamationToken)); + } + } #endif - return new ParameterSyntax(SyntaxKind.Parameter, attributeLists.Node, modifiers.Node, type, identifier, @default); + return new ParameterSyntax(SyntaxKind.Parameter, attributeLists.Node, modifiers.Node, type, identifier, exclamationExclamationToken, @default); } public static FunctionPointerParameterSyntax FunctionPointerParameter(Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList attributeLists, Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList modifiers, TypeSyntax type) diff --git a/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Main.Generated.cs b/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Main.Generated.cs index 87be16678a706..ce862c9412c74 100644 --- a/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Main.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Main.Generated.cs @@ -2014,7 +2014,7 @@ public partial class CSharpSyntaxRewriter : CSharpSyntaxVisitor => node.Update(VisitToken(node.OpenBracketToken), VisitList(node.Parameters), VisitToken(node.CloseBracketToken)); public override SyntaxNode? VisitParameter(ParameterSyntax node) - => node.Update(VisitList(node.AttributeLists), VisitList(node.Modifiers), (TypeSyntax?)Visit(node.Type), VisitToken(node.Identifier), (EqualsValueClauseSyntax?)Visit(node.Default)); + => node.Update(VisitList(node.AttributeLists), VisitList(node.Modifiers), (TypeSyntax?)Visit(node.Type), VisitToken(node.Identifier), VisitToken(node.ExclamationExclamationToken), (EqualsValueClauseSyntax?)Visit(node.Default)); public override SyntaxNode? VisitFunctionPointerParameter(FunctionPointerParameterSyntax node) => node.Update(VisitList(node.AttributeLists), VisitList(node.Modifiers), (TypeSyntax?)Visit(node.Type) ?? throw new ArgumentNullException("type")); @@ -5559,7 +5559,7 @@ public static BracketedParameterListSyntax BracketedParameterList(SeparatedSynta => SyntaxFactory.BracketedParameterList(SyntaxFactory.Token(SyntaxKind.OpenBracketToken), parameters, SyntaxFactory.Token(SyntaxKind.CloseBracketToken)); /// Creates a new ParameterSyntax instance. - public static ParameterSyntax Parameter(SyntaxList attributeLists, SyntaxTokenList modifiers, TypeSyntax? type, SyntaxToken identifier, EqualsValueClauseSyntax? @default) + public static ParameterSyntax Parameter(SyntaxList attributeLists, SyntaxTokenList modifiers, TypeSyntax? type, SyntaxToken identifier, SyntaxToken exclamationExclamationToken, EqualsValueClauseSyntax? @default) { switch (identifier.Kind()) { @@ -5567,12 +5567,22 @@ public static ParameterSyntax Parameter(SyntaxList attribut case SyntaxKind.ArgListKeyword: break; default: throw new ArgumentException(nameof(identifier)); } - return (ParameterSyntax)Syntax.InternalSyntax.SyntaxFactory.Parameter(attributeLists.Node.ToGreenList(), modifiers.Node.ToGreenList(), type == null ? null : (Syntax.InternalSyntax.TypeSyntax)type.Green, (Syntax.InternalSyntax.SyntaxToken)identifier.Node!, @default == null ? null : (Syntax.InternalSyntax.EqualsValueClauseSyntax)@default.Green).CreateRed(); + switch (exclamationExclamationToken.Kind()) + { + case SyntaxKind.ExclamationExclamationToken: + case SyntaxKind.None: break; + default: throw new ArgumentException(nameof(exclamationExclamationToken)); + } + return (ParameterSyntax)Syntax.InternalSyntax.SyntaxFactory.Parameter(attributeLists.Node.ToGreenList(), modifiers.Node.ToGreenList(), type == null ? null : (Syntax.InternalSyntax.TypeSyntax)type.Green, (Syntax.InternalSyntax.SyntaxToken)identifier.Node!, (Syntax.InternalSyntax.SyntaxToken?)exclamationExclamationToken.Node, @default == null ? null : (Syntax.InternalSyntax.EqualsValueClauseSyntax)@default.Green).CreateRed(); } + /// Creates a new ParameterSyntax instance. + public static ParameterSyntax Parameter(SyntaxList attributeLists, SyntaxTokenList modifiers, TypeSyntax? type, SyntaxToken identifier, EqualsValueClauseSyntax? @default) + => SyntaxFactory.Parameter(attributeLists, modifiers, type, identifier, default, @default); + /// Creates a new ParameterSyntax instance. public static ParameterSyntax Parameter(SyntaxToken identifier) - => SyntaxFactory.Parameter(default, default(SyntaxTokenList), default, identifier, default); + => SyntaxFactory.Parameter(default, default(SyntaxTokenList), default, identifier, default, default); /// Creates a new FunctionPointerParameterSyntax instance. public static FunctionPointerParameterSyntax FunctionPointerParameter(SyntaxList attributeLists, SyntaxTokenList modifiers, TypeSyntax type) diff --git a/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Syntax.Generated.cs b/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Syntax.Generated.cs index d0a2bef71eae4..a3893c47708be 100644 --- a/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Syntax.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Syntax.Generated.cs @@ -13163,14 +13163,23 @@ public override SyntaxTokenList Modifiers /// Gets the identifier. public SyntaxToken Identifier => new SyntaxToken(this, ((Syntax.InternalSyntax.ParameterSyntax)this.Green).identifier, GetChildPosition(3), GetChildIndex(3)); - public EqualsValueClauseSyntax? Default => GetRed(ref this.@default, 4); + public SyntaxToken ExclamationExclamationToken + { + get + { + var slot = ((Syntax.InternalSyntax.ParameterSyntax)this.Green).exclamationExclamationToken; + return slot != null ? new SyntaxToken(this, slot, GetChildPosition(4), GetChildIndex(4)) : default; + } + } + + public EqualsValueClauseSyntax? Default => GetRed(ref this.@default, 5); internal override SyntaxNode? GetNodeSlot(int index) => index switch { 0 => GetRedAtZero(ref this.attributeLists)!, 2 => GetRed(ref this.type, 2), - 4 => GetRed(ref this.@default, 4), + 5 => GetRed(ref this.@default, 5), _ => null, }; @@ -13179,18 +13188,18 @@ public override SyntaxTokenList Modifiers { 0 => this.attributeLists, 2 => this.type, - 4 => this.@default, + 5 => this.@default, _ => null, }; public override void Accept(CSharpSyntaxVisitor visitor) => visitor.VisitParameter(this); public override TResult? Accept(CSharpSyntaxVisitor visitor) where TResult : default => visitor.VisitParameter(this); - public ParameterSyntax Update(SyntaxList attributeLists, SyntaxTokenList modifiers, TypeSyntax? type, SyntaxToken identifier, EqualsValueClauseSyntax? @default) + public ParameterSyntax Update(SyntaxList attributeLists, SyntaxTokenList modifiers, TypeSyntax? type, SyntaxToken identifier, SyntaxToken exclamationExclamationToken, EqualsValueClauseSyntax? @default) { - if (attributeLists != this.AttributeLists || modifiers != this.Modifiers || type != this.Type || identifier != this.Identifier || @default != this.Default) + if (attributeLists != this.AttributeLists || modifiers != this.Modifiers || type != this.Type || identifier != this.Identifier || exclamationExclamationToken != this.ExclamationExclamationToken || @default != this.Default) { - var newNode = SyntaxFactory.Parameter(attributeLists, modifiers, type, identifier, @default); + var newNode = SyntaxFactory.Parameter(attributeLists, modifiers, type, identifier, exclamationExclamationToken, @default); var annotations = GetAnnotations(); return annotations?.Length > 0 ? newNode.WithAnnotations(annotations) : newNode; } @@ -13199,13 +13208,14 @@ public ParameterSyntax Update(SyntaxList attributeLists, Sy } internal override BaseParameterSyntax WithAttributeListsCore(SyntaxList attributeLists) => WithAttributeLists(attributeLists); - public new ParameterSyntax WithAttributeLists(SyntaxList attributeLists) => Update(attributeLists, this.Modifiers, this.Type, this.Identifier, this.Default); + public new ParameterSyntax WithAttributeLists(SyntaxList attributeLists) => Update(attributeLists, this.Modifiers, this.Type, this.Identifier, this.ExclamationExclamationToken, this.Default); internal override BaseParameterSyntax WithModifiersCore(SyntaxTokenList modifiers) => WithModifiers(modifiers); - public new ParameterSyntax WithModifiers(SyntaxTokenList modifiers) => Update(this.AttributeLists, modifiers, this.Type, this.Identifier, this.Default); + public new ParameterSyntax WithModifiers(SyntaxTokenList modifiers) => Update(this.AttributeLists, modifiers, this.Type, this.Identifier, this.ExclamationExclamationToken, this.Default); internal override BaseParameterSyntax WithTypeCore(TypeSyntax? type) => WithType(type); - public new ParameterSyntax WithType(TypeSyntax? type) => Update(this.AttributeLists, this.Modifiers, type, this.Identifier, this.Default); - public ParameterSyntax WithIdentifier(SyntaxToken identifier) => Update(this.AttributeLists, this.Modifiers, this.Type, identifier, this.Default); - public ParameterSyntax WithDefault(EqualsValueClauseSyntax? @default) => Update(this.AttributeLists, this.Modifiers, this.Type, this.Identifier, @default); + public new ParameterSyntax WithType(TypeSyntax? type) => Update(this.AttributeLists, this.Modifiers, type, this.Identifier, this.ExclamationExclamationToken, this.Default); + public ParameterSyntax WithIdentifier(SyntaxToken identifier) => Update(this.AttributeLists, this.Modifiers, this.Type, identifier, this.ExclamationExclamationToken, this.Default); + public ParameterSyntax WithExclamationExclamationToken(SyntaxToken exclamationExclamationToken) => Update(this.AttributeLists, this.Modifiers, this.Type, this.Identifier, exclamationExclamationToken, this.Default); + public ParameterSyntax WithDefault(EqualsValueClauseSyntax? @default) => Update(this.AttributeLists, this.Modifiers, this.Type, this.Identifier, this.ExclamationExclamationToken, @default); internal override BaseParameterSyntax AddAttributeListsCore(params AttributeListSyntax[] items) => AddAttributeLists(items); public new ParameterSyntax AddAttributeLists(params AttributeListSyntax[] items) => WithAttributeLists(this.AttributeLists.AddRange(items)); diff --git a/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs b/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs index 6c8dd1f29beda..47367286eeac1 100644 --- a/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs @@ -279,6 +279,8 @@ public static bool IsWarning(ErrorCode code) case ErrorCode.WRN_InterpolatedStringHandlerArgumentAttributeIgnoredOnLambdaParameters: case ErrorCode.WRN_CompileTimeCheckedOverflow: case ErrorCode.WRN_MethGrpToNonDel: + case ErrorCode.WRN_NullCheckedHasDefaultNull: + case ErrorCode.WRN_NullCheckingOnNullableType: case ErrorCode.WRN_LowerCaseTypeName: return true; default: diff --git a/src/Compilers/CSharp/Portable/LanguageVersion.cs b/src/Compilers/CSharp/Portable/LanguageVersion.cs index cc0d508de5d28..df9cebfcbbdd3 100644 --- a/src/Compilers/CSharp/Portable/LanguageVersion.cs +++ b/src/Compilers/CSharp/Portable/LanguageVersion.cs @@ -308,6 +308,9 @@ public static class LanguageVersionFacts /// Usages of TestOptions.RegularNext and LanguageVersionFacts.CSharpNext /// will be replaced with TestOptions.RegularN and LanguageVersion.CSharpN when language version N is introduced. /// + /// + /// Corresponds to Microsoft.CodeAnalysis.CSharp.Shared.Extensions.LanguageVersionExtensions.CSharpNext. + /// internal const LanguageVersion CSharpNext = LanguageVersion.Preview; /// diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs index 4081c24bf0db3..c8c175ea65d0d 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs @@ -101,7 +101,6 @@ public static BoundStatement Rewrite( // Presence of sequence points in the tree affects final IL, therefore, we always generate them. var localRewriter = new LocalRewriter(compilation, method, methodOrdinal, statement, containingType, factory, previousSubmissionFields, allowOmissionOfConditionalCalls, diagnostics, dynamicInstrumenter != null ? new DebugInfoInjector(dynamicInstrumenter) : DebugInfoInjector.Singleton); - statement.CheckLocalsDefined(); var loweredStatement = localRewriter.VisitStatement(statement); Debug.Assert(loweredStatement is { }); @@ -260,7 +259,13 @@ public override BoundNode VisitLambda(BoundLambda node) { _instrumenter = RemoveDynamicAnalysisInjectors(oldInstrumenter); } - return base.VisitLambda(node)!; + + var visited = (BoundLambda)base.VisitLambda(node)!; + if (RewriteNullChecking(visited.Body) is BoundBlock newBody) + { + visited = visited.Update(visited.UnboundLambda, visited.Symbol, newBody, visited.Diagnostics, visited.Binder, visited.Type); + } + return visited; } finally { @@ -311,6 +316,7 @@ static bool hasReturnTypeOrParameter(LocalFunctionSymbol localFunction, Func instead! /// If used, a unit-test with a missing member is absolutely a must have. /// - private static MethodSymbol UnsafeGetNullableMethod(SyntaxNode syntax, TypeSymbol nullableType, SpecialMember member, CSharpCompilation compilation, BindingDiagnosticBag diagnostics) + internal static MethodSymbol UnsafeGetNullableMethod(SyntaxNode syntax, TypeSymbol nullableType, SpecialMember member, CSharpCompilation compilation, BindingDiagnosticBag diagnostics) { var nullableType2 = nullableType as NamedTypeSymbol; Debug.Assert(nullableType2 is { }); 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_Conversion.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs index 533277236b35e..8bf9121b538ec 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs @@ -659,7 +659,7 @@ private BoundExpression RewriteTupleConversion( return _factory.MakeSequence(savedTuple.LocalSymbol, assignmentToTemp, result); } - private static bool NullableNeverHasValue(BoundExpression expression) + internal static bool NullableNeverHasValue(BoundExpression expression) { // CONSIDER: A sequence of side effects with an always-null expression as its value // CONSIDER: can be optimized also. Should we? @@ -674,7 +674,7 @@ private static bool NullableNeverHasValue(BoundExpression expression) /// which conversions appearing at the top of the expression have not been lowered. /// If this method is updated to recognize more complex patterns, callers should be reviewed. /// - private static BoundExpression? NullableAlwaysHasValue(BoundExpression expression) + internal static BoundExpression? NullableAlwaysHasValue(BoundExpression expression) { Debug.Assert(expression.Type is { }); if (!expression.Type.IsNullableType()) @@ -1108,7 +1108,7 @@ private BoundExpression RewriteLiftedUserDefinedConversion( MethodSymbol getValueOrDefault = UnsafeGetNullableMethod(syntax, boundTemp.Type, SpecialMember.System_Nullable_T_GetValueOrDefault); // temp.HasValue - BoundExpression condition = MakeNullableHasValue(syntax, boundTemp); + BoundExpression condition = _factory.MakeNullableHasValue(syntax, boundTemp); // temp.GetValueOrDefault() BoundCall callGetValueOrDefault = BoundCall.Synthesized(syntax, boundTemp, getValueOrDefault); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_FixedStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_FixedStatement.cs index 5ac883beffbae..35f386c718a42 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_FixedStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_FixedStatement.cs @@ -443,7 +443,7 @@ private BoundStatement InitializeFixedStatementStringLocal( BoundStatement localInit = InstrumentLocalDeclarationIfNecessary(localDecl, localSymbol, factory.Assignment(factory.Local(localSymbol), convertedStringTemp)); - BoundExpression notNullCheck = MakeNullCheck(factory.Syntax, factory.Local(localSymbol), BinaryOperatorKind.NotEqual); + BoundExpression notNullCheck = _factory.MakeNullCheck(factory.Syntax, factory.Local(localSymbol), BinaryOperatorKind.NotEqual); BoundExpression helperCall; MethodSymbol offsetMethod; @@ -497,7 +497,7 @@ private BoundStatement InitializeFixedStatementArrayLocal( BoundExpression arrayTempInit = factory.AssignmentExpression(factory.Local(pinnedTemp), initializerExpr); //(pinnedTemp = array) != null - BoundExpression notNullCheck = MakeNullCheck(factory.Syntax, arrayTempInit, BinaryOperatorKind.NotEqual); + BoundExpression notNullCheck = _factory.MakeNullCheck(factory.Syntax, arrayTempInit, BinaryOperatorKind.NotEqual); BoundExpression lengthCall; diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsOperator.cs index 61b83809b5156..891f53083c3ff 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsOperator.cs @@ -64,7 +64,7 @@ private BoundExpression MakeIsOperator( { // operand is a reference type with bound identity or implicit conversion // We can replace the "is" instruction with a null check - return MakeNullCheck(syntax, rewrittenOperand, BinaryOperatorKind.NotEqual); + return _factory.MakeNullCheck(syntax, rewrittenOperand, BinaryOperatorKind.NotEqual); } } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_NullChecking.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_NullChecking.cs new file mode 100644 index 0000000000000..406e106f62ad5 --- /dev/null +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_NullChecking.cs @@ -0,0 +1,105 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Diagnostics; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.PooledObjects; + +namespace Microsoft.CodeAnalysis.CSharp +{ + internal sealed partial class LocalRewriter + { + private BoundBlock? RewriteNullChecking(BoundBlock? block) + { + if (block is null) + { + return null; + } + + Debug.Assert(_factory.CurrentFunction is not null); + var statementList = TryConstructNullCheckedStatementList(_factory.CurrentFunction.Parameters, block.Statements, _factory); + if (statementList.IsDefault) + { + return null; + } + return _factory.Block(block.Locals, statementList); + } + + internal static ImmutableArray TryConstructNullCheckedStatementList(ImmutableArray parameters, + ImmutableArray existingStatements, + SyntheticBoundNodeFactory factory) + { + ArrayBuilder? statementList = null; + MethodSymbol? throwIfNullMethod = null; + foreach (ParameterSymbol param in parameters) + { + if (param.IsNullChecked) + { + var isNullCheckableValueType = param.Type.IsNullableTypeOrTypeParameter() || param.Type.IsPointerOrFunctionPointer(); + Debug.Assert(!param.Type.IsValueType || isNullCheckableValueType); + statementList ??= ArrayBuilder.GetInstance(); + var constructedIf = isNullCheckableValueType + ? ConstructDirectNullCheck(param, factory) + : ConstructNullCheckHelperCall(param, ref throwIfNullMethod, factory); + statementList.Add(constructedIf); + } + } + if (statementList is null) + { + return default; + } + + statementList.AddRange(existingStatements); + return statementList.ToImmutableAndFree(); + } + + private static BoundStatement ConstructNullCheckHelperCall(ParameterSymbol parameter, ref MethodSymbol? throwIfNullMethod, SyntheticBoundNodeFactory factory) + { + if (throwIfNullMethod is null) + { + var module = factory.ModuleBuilderOpt!; + var diagnosticSyntax = factory.CurrentFunction.GetNonNullSyntaxNode(); + var diagnostics = factory.Diagnostics.DiagnosticBag; + Debug.Assert(diagnostics is not null); + throwIfNullMethod = module.EnsureThrowIfNullFunctionExists(diagnosticSyntax, factory, diagnostics); + } + + var call = factory.Call( + receiver: null, + throwIfNullMethod, + arg0: factory.Convert(factory.SpecialType(SpecialType.System_Object), factory.Parameter(parameter)), + arg1: factory.StringLiteral(parameter.Name)); + return factory.HiddenSequencePoint(factory.ExpressionStatement(call)); + } + + private static BoundStatement ConstructDirectNullCheck(ParameterSymbol parameter, SyntheticBoundNodeFactory factory) + { + BoundExpression paramIsNullCondition; + var loweredLeft = factory.Parameter(parameter); + + if (loweredLeft.Type.IsNullableType()) + { + paramIsNullCondition = factory.Not(factory.MakeNullableHasValue(loweredLeft.Syntax, loweredLeft)); + } + else + { + // Examples of how we might get here: + // int* + // delegate*<...> + // T where T : int? (via some indirection) + Debug.Assert(parameter.Type.IsPointerOrFunctionPointer() + || (parameter.Type.IsNullableTypeOrTypeParameter() && !parameter.Type.IsNullableType())); + + paramIsNullCondition = factory.MakeNullCheck(loweredLeft.Syntax, loweredLeft, BinaryOperatorKind.Equal); + } + + var argumentName = ImmutableArray.Create(factory.StringLiteral(parameter.Name)); + BoundObjectCreationExpression ex = factory.New(factory.WellKnownMethod(WellKnownMember.System_ArgumentNullException__ctorString), argumentName); + BoundThrowStatement throwArgNullStatement = factory.Throw(ex); + + return factory.HiddenSequencePoint(factory.If(paramIsNullCondition, throwArgNullStatement)); + } + } +} diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_NullCoalescingOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_NullCoalescingOperator.cs index 18d2c97466700..c577d6cbb06f6 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_NullCoalescingOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_NullCoalescingOperator.cs @@ -150,7 +150,7 @@ private BoundExpression MakeNullCoalescingOperator( BoundLocal boundTemp = _factory.StoreToTemp(rewrittenLeft, out tempAssignment); // temp != null - BoundExpression nullCheck = MakeNullCheck(syntax, boundTemp, BinaryOperatorKind.NotEqual); + BoundExpression nullCheck = _factory.MakeNullCheck(syntax, boundTemp, BinaryOperatorKind.NotEqual); // MakeConversion(temp, rewrittenResultType) BoundExpression convertedLeft = GetConvertedLeftForNullCoalescingOperator(boundTemp, leftPlaceholder, leftConversion, rewrittenResultType); 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/Lowering/LocalRewriter/LocalRewriter_TupleBinaryOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_TupleBinaryOperator.cs index 160ce6a65293f..4f55bec376a45 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_TupleBinaryOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_TupleBinaryOperator.cs @@ -408,7 +408,7 @@ when expr.Type.IsNullableType() && o.Type is { } && o.Type.IsNullableType() && ! conversion.AssertUnderlyingConversionsChecked(); return makeNullableHasValue(o); default: - return MakeNullableHasValue(expr.Syntax, expr); + return _factory.MakeNullableHasValue(expr.Syntax, expr); } } } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs index c427e4b1f3be4..cae3f304a9ed7 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs @@ -199,7 +199,7 @@ private BoundExpression LowerLiftedUnaryOperator( MethodSymbol getValueOrDefault = UnsafeGetNullableMethod(syntax, boundTemp.Type, SpecialMember.System_Nullable_T_GetValueOrDefault); // temp.HasValue - BoundExpression condition = MakeNullableHasValue(syntax, boundTemp); + BoundExpression condition = _factory.MakeNullableHasValue(syntax, boundTemp); // temp.GetValueOrDefault() BoundExpression call_GetValueOrDefault = BoundCall.Synthesized(syntax, boundTemp, getValueOrDefault); @@ -643,7 +643,7 @@ private BoundExpression MakeUserDefinedIncrementOperator(BoundIncrementOperator MethodSymbol ctor = UnsafeGetNullableMethod(syntax, type, SpecialMember.System_Nullable_T__ctor); // temp.HasValue - BoundExpression condition = MakeNullableHasValue(node.Syntax, boundTemp); + BoundExpression condition = _factory.MakeNullableHasValue(node.Syntax, boundTemp); // temp.GetValueOrDefault() BoundExpression call_GetValueOrDefault = BoundCall.Synthesized(syntax, boundTemp, getValueOrDefault); @@ -804,7 +804,7 @@ private BoundExpression MakeLiftedDecimalIncDecOperator(SyntaxNode syntax, Binar MethodSymbol ctor = UnsafeGetNullableMethod(syntax, operand.Type, SpecialMember.System_Nullable_T__ctor); // x.HasValue - BoundExpression condition = MakeNullableHasValue(syntax, operand); + BoundExpression condition = _factory.MakeNullableHasValue(syntax, operand); // x.GetValueOrDefault() BoundExpression getValueCall = BoundCall.Synthesized(syntax, operand, getValueOrDefault); // op_Inc(x.GetValueOrDefault()) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs index 6197b910404b9..4a7083be57f42 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs @@ -372,7 +372,7 @@ private BoundStatement RewriteUsingStatementTryFinally( if (isNullableValueType) { // local.HasValue - ifCondition = MakeNullableHasValue(syntax, local); + ifCondition = _factory.MakeNullableHasValue(syntax, local); } else if (local.Type.IsValueType) { @@ -381,7 +381,7 @@ private BoundStatement RewriteUsingStatementTryFinally( else { // local != null - ifCondition = MakeNullCheck(syntax, local, BinaryOperatorKind.NotEqual); + ifCondition = _factory.MakeNullCheck(syntax, local, BinaryOperatorKind.NotEqual); } BoundStatement finallyStatement; diff --git a/src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs index ed32c02b22286..7baa20538ccd5 100644 --- a/src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs @@ -577,7 +577,12 @@ public override BoundNode VisitLoweredConditionalAccess(BoundLoweredConditionalA return null; } - if (method.ContainingType.IsAnonymousType) + if (method.ContainingType is null) + { + Debug.Assert(method is SynthesizedGlobalMethodSymbol); + return method; + } + else if (method.ContainingType.IsAnonymousType) { // Method of an anonymous type var newType = (NamedTypeSymbol)TypeMap.SubstituteType(method.ContainingType).AsTypeSymbolOnly(); diff --git a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs index 0e4ff51cc2c39..df7008cb75f7e 100644 --- a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs @@ -319,7 +319,9 @@ protected BoundStatement GenerateParameterStorage(LocalSymbol stateMachineVariab } } - return F.Block(bodyBuilder.ToImmutableAndFree()); + var builtBody = bodyBuilder.ToImmutableAndFree(); + ImmutableArray newBody = LocalRewriter.TryConstructNullCheckedStatementList(method.Parameters, builtBody, F); + return newBody.IsDefault ? F.Block(builtBody) : F.Block(ImmutableArray.Create(stateMachineVariable), newBody); } protected SynthesizedImplementationMethod OpenMethodImplementation( diff --git a/src/Compilers/CSharp/Portable/Lowering/SynthesizedMethodBaseSymbol.cs b/src/Compilers/CSharp/Portable/Lowering/SynthesizedMethodBaseSymbol.cs index bf757adc21c7d..f9a411264204e 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SynthesizedMethodBaseSymbol.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SynthesizedMethodBaseSymbol.cs @@ -138,7 +138,8 @@ private ImmutableArray MakeParameters() p.Name, // the synthesized parameter doesn't need to have the same ref custom modifiers as the base refCustomModifiers: default, - inheritAttributes ? p as SourceComplexParameterSymbol : null)); + inheritAttributes ? p as SourceComplexParameterSymbol : null, + isNullChecked: p.IsNullChecked)); } var extraSynthed = ExtraSynthesizedRefParameters; if (!extraSynthed.IsDefaultOrEmpty) diff --git a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs index 5f85105fd1806..a221190fe0d1e 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs @@ -1542,5 +1542,138 @@ internal ImmutableArray MakeTempsForDiscardArguments(ImmutableA return arguments; } + +#nullable disable + internal BoundExpression MakeNullCheck(SyntaxNode syntax, BoundExpression rewrittenExpr, BinaryOperatorKind operatorKind) + { + Debug.Assert((operatorKind == BinaryOperatorKind.Equal) || (operatorKind == BinaryOperatorKind.NotEqual) || + (operatorKind == BinaryOperatorKind.NullableNullEqual) || (operatorKind == BinaryOperatorKind.NullableNullNotEqual)); + + TypeSymbol exprType = rewrittenExpr.Type; + + // Don't even call this method if the expression cannot be nullable. + Debug.Assert( + (object)exprType == null || + exprType.IsNullableTypeOrTypeParameter() || + !exprType.IsValueType || + exprType.IsPointerOrFunctionPointer()); + + TypeSymbol boolType = Compilation.GetSpecialType(CodeAnalysis.SpecialType.System_Boolean); + + // Fold compile-time comparisons. + if (rewrittenExpr.ConstantValue != null) + { + switch (operatorKind) + { + case BinaryOperatorKind.Equal: + return Literal(ConstantValue.Create(rewrittenExpr.ConstantValue.IsNull, ConstantValueTypeDiscriminator.Boolean), boolType); + case BinaryOperatorKind.NotEqual: + return Literal(ConstantValue.Create(rewrittenExpr.ConstantValue.IsNull, ConstantValueTypeDiscriminator.Boolean), boolType); + } + } + + TypeSymbol objectType = SpecialType(CodeAnalysis.SpecialType.System_Object); + + if ((object)exprType != null) + { + if (exprType.Kind == SymbolKind.TypeParameter) + { + // Box type parameters. + rewrittenExpr = Convert(objectType, rewrittenExpr, Conversion.Boxing); + } + else if (exprType.IsNullableType()) + { + operatorKind |= BinaryOperatorKind.NullableNull; + } + } + if (operatorKind == BinaryOperatorKind.NullableNullEqual || operatorKind == BinaryOperatorKind.NullableNullNotEqual) + { + return RewriteNullableNullEquality(syntax, operatorKind, rewrittenExpr, Literal(ConstantValue.Null, objectType), boolType); + } + else + { + return Binary(operatorKind, boolType, rewrittenExpr, Null(objectType)); + } + } + + internal BoundExpression MakeNullableHasValue(SyntaxNode syntax, BoundExpression expression) + { + // https://github.com/dotnet/roslyn/issues/58335: consider restoring the 'private' accessibility of 'static LocalRewriter.UnsafeGetNullableMethod()' + return BoundCall.Synthesized(syntax, expression, LocalRewriter.UnsafeGetNullableMethod(syntax, expression.Type, CodeAnalysis.SpecialMember.System_Nullable_T_get_HasValue, Compilation, Diagnostics)); + } + + internal BoundExpression RewriteNullableNullEquality( + SyntaxNode syntax, + BinaryOperatorKind kind, + BoundExpression loweredLeft, + BoundExpression loweredRight, + TypeSymbol returnType) + { + // This handles the case where we have a nullable user-defined struct type compared against null, eg: + // + // struct S {} ... S? s = whatever; if (s != null) + // + // If S does not define an overloaded != operator then this is lowered to s.HasValue. + // + // If the type already has a user-defined or built-in operator then comparing to null is + // treated as a lifted equality operator. + + Debug.Assert(loweredLeft != null); + Debug.Assert(loweredRight != null); + Debug.Assert((object)returnType != null); + Debug.Assert(returnType.SpecialType == CodeAnalysis.SpecialType.System_Boolean); + Debug.Assert(loweredLeft.IsLiteralNull() != loweredRight.IsLiteralNull()); + + BoundExpression nullable = loweredRight.IsLiteralNull() ? loweredLeft : loweredRight; + + // If the other side is known to always be null then we can simply generate true or false, as appropriate. + + if (LocalRewriter.NullableNeverHasValue(nullable)) + { + return Literal(kind == BinaryOperatorKind.NullableNullEqual); + } + + BoundExpression nonNullValue = LocalRewriter.NullableAlwaysHasValue(nullable); + if (nonNullValue != null) + { + // We have something like "if (new int?(M()) != null)". We can optimize this to + // evaluate M() for its side effects and then result in true or false, as appropriate. + + // TODO: If the expression has no side effects then it can be optimized away here as well. + + return new BoundSequence( + syntax: syntax, + locals: ImmutableArray.Empty, + sideEffects: ImmutableArray.Create(nonNullValue), + value: Literal(kind == BinaryOperatorKind.NullableNullNotEqual), + type: returnType); + } + + // arr?.Length == null + var conditionalAccess = nullable as BoundLoweredConditionalAccess; + if (conditionalAccess != null && + (conditionalAccess.WhenNullOpt == null || conditionalAccess.WhenNullOpt.IsDefaultValue())) + { + BoundExpression whenNotNull = RewriteNullableNullEquality( + syntax, + kind, + conditionalAccess.WhenNotNull, + loweredLeft.IsLiteralNull() ? loweredLeft : loweredRight, + returnType); + + var whenNull = kind == BinaryOperatorKind.NullableNullEqual ? Literal(true) : null; + + return conditionalAccess.Update(conditionalAccess.Receiver, conditionalAccess.HasValueMethodOpt, whenNotNull, whenNull, conditionalAccess.Id, whenNotNull.Type); + } + + BoundExpression call = MakeNullableHasValue(syntax, nullable); + BoundExpression result = kind == BinaryOperatorKind.NullableNullNotEqual ? + call : + new BoundUnaryOperator(syntax, UnaryOperatorKind.BoolLogicalNegation, call, ConstantValue.NotAvailable, methodOpt: null, constrainedToTypeOpt: null, LookupResultKind.Viable, returnType); + + return result; + } + // https://github.com/dotnet/roslyn/issues/58335: Re-enable annotations +#nullable enable } } 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/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index 9eed125aec951..6d44f7e2a1545 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -4387,14 +4387,30 @@ private ParameterSyntax ParseParameter() TypeSyntax type; SyntaxToken name; + SyntaxToken exclamationExclamation = null; + SyntaxToken equals = null; if (this.CurrentToken.Kind != SyntaxKind.ArgListKeyword) { type = this.ParseType(mode: ParseTypeMode.Parameter); name = this.ParseIdentifierToken(); - - // When the user type "int goo[]", give them a useful error - if (this.CurrentToken.Kind == SyntaxKind.OpenBracketToken && this.PeekToken(1).Kind == SyntaxKind.CloseBracketToken) + if (this.CurrentToken.Kind == SyntaxKind.ExclamationToken) + { + exclamationExclamation = this.EatToken(); + if (this.CurrentToken.Kind == SyntaxKind.ExclamationEqualsToken) + { + var exclamationEquals = this.EatToken(); + var exclamation2 = SyntaxFactory.Token(exclamationEquals.GetLeadingTrivia(), SyntaxKind.ExclamationToken, null); + exclamationExclamation = MergeTokens(exclamationExclamation, exclamation2, SyntaxKind.ExclamationExclamationToken); + equals = SyntaxFactory.Token(null, SyntaxKind.EqualsToken, exclamationEquals.GetTrailingTrivia()); + } + else + { + exclamationExclamation = MergeTokens(exclamationExclamation, this.EatToken(SyntaxKind.ExclamationToken), SyntaxKind.ExclamationExclamationToken); + } + } + else if (this.CurrentToken.Kind == SyntaxKind.OpenBracketToken && this.PeekToken(1).Kind == SyntaxKind.CloseBracketToken) { + // When the user type "int goo[]", give them a useful error var open = this.EatToken(); var close = this.EatToken(); open = this.AddError(open, ErrorCode.ERR_BadArraySyntax); @@ -4409,16 +4425,24 @@ private ParameterSyntax ParseParameter() name = this.EatToken(SyntaxKind.ArgListKeyword); } - EqualsValueClauseSyntax def = null; if (this.CurrentToken.Kind == SyntaxKind.EqualsToken) { - var equals = this.EatToken(SyntaxKind.EqualsToken); + equals = this.EatToken(SyntaxKind.EqualsToken); + } + if (exclamationExclamation != null) + { + exclamationExclamation = (exclamationExclamation.Kind != SyntaxKind.ExclamationExclamationToken) + ? ConvertToMissingWithTrailingTrivia(exclamationExclamation, SyntaxKind.ExclamationExclamationToken) + : CheckFeatureAvailability(exclamationExclamation, MessageID.IDS_ParameterNullChecking); + } + EqualsValueClauseSyntax def = null; + if (!(equals is null)) + { var value = this.ParseExpressionCore(); def = _syntaxFactory.EqualsValueClause(equals, value: value); def = CheckFeatureAvailability(def, MessageID.IDS_FeatureOptionalParameter); } - - return _syntaxFactory.Parameter(attributes, modifiers.ToList(), type, name, def); + return _syntaxFactory.Parameter(attributes, modifiers.ToList(), type, name, exclamationExclamation, def); } finally { @@ -5778,7 +5802,7 @@ private ScanTypeArgumentListKind ScanTypeArgumentList(NameOptions options) case SyntaxKind.ExclamationEqualsToken: case SyntaxKind.BarToken: case SyntaxKind.CaretToken: - // These tokens are from 7.5.4.2 Grammar Ambiguities + // These tokens are from 7.5.4.2 Grammar Ambiguities return ScanTypeArgumentListKind.DefiniteTypeArgumentList; case SyntaxKind.AmpersandAmpersandToken: // e.g. `e is A && e` @@ -11439,11 +11463,8 @@ private bool ScanParenthesizedImplicitlyTypedLambda(Precedence precedence) { return false; } - // case 1: ( x , - if (this.PeekToken(1).Kind == SyntaxKind.IdentifierToken - && (!this.IsInQuery || !IsTokenQueryContextualKeyword(this.PeekToken(1))) - && this.PeekToken(2).Kind == SyntaxKind.CommaToken) + if (isParenVarCommaSyntax()) { // Make sure it really looks like a lambda, not just a tuple int curTk = 3; @@ -11467,11 +11488,22 @@ private bool ScanParenthesizedImplicitlyTypedLambda(Precedence precedence) } // case 2: ( x ) => - if (IsTrueIdentifier(this.PeekToken(1)) - && this.PeekToken(2).Kind == SyntaxKind.CloseParenToken - && this.PeekToken(3).Kind == SyntaxKind.EqualsGreaterThanToken) + if (IsTrueIdentifier(this.PeekToken(1))) { - return true; + // allow for a) => or a!!) => + var skipIndex = 2; + if (PeekToken(skipIndex).Kind == SyntaxKind.ExclamationToken + && this.PeekToken(skipIndex + 1).Kind == SyntaxKind.ExclamationToken) + { + skipIndex += 2; + } + + // Must have: ) => + if (this.PeekToken(skipIndex).Kind == SyntaxKind.CloseParenToken + && this.PeekToken(skipIndex + 1).Kind == SyntaxKind.EqualsGreaterThanToken) + { + return true; + } } // case 3: ( ) => @@ -11490,6 +11522,37 @@ private bool ScanParenthesizedImplicitlyTypedLambda(Precedence precedence) } return false; + + bool isParenVarCommaSyntax() + { + var next = this.PeekToken(1); + + // Ensure next token is a variable + if (next.Kind == SyntaxKind.IdentifierToken) + { + if (!this.IsInQuery || !IsTokenQueryContextualKeyword(next)) + { + // Variable must be directly followed by a comma if not followed by exclamation + var after = this.PeekToken(2); + // ( x , [...] + if (after.Kind == SyntaxKind.CommaToken) + { + return true; + } + // ( x!! , [...] + // https://github.com/dotnet/roslyn/issues/58335: https://github.com/dotnet/roslyn/pull/46520#discussion_r466650228 + if (after.Kind == SyntaxKind.ExclamationToken + && this.PeekToken(3).Kind == SyntaxKind.ExclamationToken + && after.GetTrailingTriviaWidth() == 0 + && this.PeekToken(3).GetLeadingTriviaWidth() == 0 + && this.PeekToken(4).Kind == SyntaxKind.CommaToken) + { + return true; + } + } + } + return false; + } } private bool ScanExplicitlyTypedLambda(Precedence precedence) @@ -11550,7 +11613,14 @@ private bool ScanExplicitlyTypedLambda(Precedence precedence) // eat the identifier this.EatToken(); } - + if (this.CurrentToken.Kind == SyntaxKind.ExclamationToken + && this.PeekToken(1).Kind == SyntaxKind.ExclamationToken + && this.CurrentToken.GetTrailingTriviaWidth() == 0 + && this.PeekToken(1).GetLeadingTriviaWidth() == 0) + { + this.EatToken(); + this.EatToken(); + } switch (this.CurrentToken.Kind) { case SyntaxKind.CommaToken: @@ -11785,6 +11855,32 @@ private bool IsPossibleLambdaExpression(Precedence precedence) return false; } + SyntaxKind token1 = this.PeekToken(1).Kind; + if (token1 == SyntaxKind.EqualsGreaterThanToken) + { + return true; + } + if (token1 == SyntaxKind.ExclamationToken + && this.PeekToken(2).Kind == SyntaxKind.ExclamationToken + && this.PeekToken(3).Kind == SyntaxKind.EqualsGreaterThanToken) + { + return true; + } + + // Broken case but error will be added in lambda function (!=>). + if (token1 == SyntaxKind.ExclamationEqualsToken && this.PeekToken(2).Kind == SyntaxKind.GreaterThanToken) + { + return true; + } + + // Broken case but error will be added in lambda function (!!=>). + if (token1 == SyntaxKind.ExclamationToken + && this.PeekToken(2).Kind == SyntaxKind.ExclamationEqualsToken + && this.PeekToken(3).Kind == SyntaxKind.GreaterThanToken) + { + return true; + } + var resetPoint = this.GetResetPoint(); try { @@ -12793,14 +12889,52 @@ LambdaExpressionSyntax parseLambdaExpressionWorker() else { var name = this.ParseIdentifierToken(); - var arrow = this.EatToken(SyntaxKind.EqualsGreaterThanToken); - arrow = CheckFeatureAvailability(arrow, MessageID.IDS_FeatureLambda); + SyntaxToken arrow, exclamationExclamation; + // Case x!! => + if (this.CurrentToken.Kind == SyntaxKind.ExclamationToken) + { + exclamationExclamation = this.EatToken(); + if (this.CurrentToken.Kind == SyntaxKind.ExclamationEqualsToken) + { + var exclamationEquals = this.EatToken(); + var exclamation2 = SyntaxFactory.Token(exclamationEquals.GetLeadingTrivia(), SyntaxKind.ExclamationToken, null); + exclamationExclamation = MergeTokens(exclamationExclamation, exclamation2, SyntaxKind.ExclamationExclamationToken); + + var equals = SyntaxFactory.Token(null, SyntaxKind.EqualsToken, exclamationEquals.GetTrailingTrivia()); + var greaterThan = this.EatToken(SyntaxKind.GreaterThanToken); + arrow = MergeTokens(equals, greaterThan, SyntaxKind.EqualsGreaterThanToken); + } + else + { + exclamationExclamation = MergeTokens(exclamationExclamation, this.EatToken(SyntaxKind.ExclamationToken), SyntaxKind.ExclamationExclamationToken); + arrow = this.EatToken(SyntaxKind.EqualsGreaterThanToken); + } + } + // Case x=>, x => + else + { + arrow = this.EatToken(SyntaxKind.EqualsGreaterThanToken); + exclamationExclamation = null; + } + + if (exclamationExclamation != null) + { + exclamationExclamation = (exclamationExclamation.Kind != SyntaxKind.ExclamationExclamationToken) + ? ConvertToMissingWithTrailingTrivia(exclamationExclamation, SyntaxKind.ExclamationExclamationToken) + : CheckFeatureAvailability(exclamationExclamation, MessageID.IDS_ParameterNullChecking); + } + + if (arrow != null) + { + arrow = (arrow.Kind != SyntaxKind.EqualsGreaterThanToken) + ? ConvertToMissingWithTrailingTrivia(arrow, SyntaxKind.EqualsGreaterThanToken) + : CheckFeatureAvailability(arrow, MessageID.IDS_FeatureLambda); + } var parameter = _syntaxFactory.Parameter( attributeLists: default, modifiers: default, - type: null, identifier: name, @default: null); + type: null, identifier: name, exclamationExclamationToken: exclamationExclamation, @default: null); var (block, expression) = ParseLambdaBody(); - return _syntaxFactory.SimpleLambdaExpression( attributes, modifiers, parameter, arrow, block, expression); } @@ -12923,7 +13057,18 @@ private ParameterSyntax ParseLambdaParameter() } SyntaxToken paramName = this.ParseIdentifierToken(); - var parameter = _syntaxFactory.Parameter(attributes, modifiers.ToList(), paramType, paramName, null); + SyntaxToken exclamationExclamation = null; + if (this.CurrentToken.Kind == SyntaxKind.ExclamationToken) + { + exclamationExclamation = MergeTokens(this.EatToken(SyntaxKind.ExclamationToken), this.EatToken(SyntaxKind.ExclamationToken), SyntaxKind.ExclamationExclamationToken); + } + if (exclamationExclamation != null) + { + exclamationExclamation = (exclamationExclamation.Kind != SyntaxKind.ExclamationExclamationToken) + ? ConvertToMissingWithTrailingTrivia(exclamationExclamation, SyntaxKind.ExclamationExclamationToken) + : CheckFeatureAvailability(exclamationExclamation, MessageID.IDS_ParameterNullChecking); + } + var parameter = _syntaxFactory.Parameter(attributes, modifiers.ToList(), paramType, paramName, exclamationExclamation, null); _pool.Free(modifiers); return parameter; } @@ -12967,7 +13112,8 @@ private bool ShouldParseLambdaParameterType(bool hasModifier) if (peek1.Kind != SyntaxKind.CommaToken && peek1.Kind != SyntaxKind.CloseParenToken && peek1.Kind != SyntaxKind.EqualsGreaterThanToken && - peek1.Kind != SyntaxKind.OpenBraceToken) + peek1.Kind != SyntaxKind.OpenBraceToken && + peek1.Kind != SyntaxKind.ExclamationToken) { return true; } @@ -13470,5 +13616,20 @@ internal TNode ConsumeUnexpectedTokens(TNode node) where TNode : CSharpSy node = this.AddTrailingSkippedSyntax(node, trailingTrash.Node); return node; } + + private SyntaxToken MergeTokens(SyntaxToken s1, SyntaxToken s2, SyntaxKind kind) + { + if (s1.GetTrailingTriviaWidth() == 0 && s2.GetLeadingTriviaWidth() == 0) + { + s1 = SyntaxFactory.Token(s1.GetLeadingTrivia(), kind, s2.GetTrailingTrivia()); + } + else + { + s2 = this.AddError(s2, ErrorCode.ERR_InvalidExprTerm, s2.Text); + s1 = AddTrailingSkippedSyntax(s1, s2); + } + + return s1; + } } } diff --git a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt index d00d1779438c3..4a6a172339b77 100644 --- a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt @@ -24,6 +24,10 @@ Microsoft.CodeAnalysis.CSharp.Syntax.LineDirectivePositionSyntax.WithCommaToken( Microsoft.CodeAnalysis.CSharp.Syntax.LineDirectivePositionSyntax.WithLine(Microsoft.CodeAnalysis.SyntaxToken line) -> Microsoft.CodeAnalysis.CSharp.Syntax.LineDirectivePositionSyntax Microsoft.CodeAnalysis.CSharp.Syntax.LineDirectivePositionSyntax.WithOpenParenToken(Microsoft.CodeAnalysis.SyntaxToken openParenToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.LineDirectivePositionSyntax *REMOVED*Microsoft.CodeAnalysis.CSharp.Syntax.LineDirectiveTriviaSyntax.File.get -> Microsoft.CodeAnalysis.SyntaxToken +Microsoft.CodeAnalysis.CSharp.Syntax.ParameterSyntax.ExclamationExclamationToken.get -> Microsoft.CodeAnalysis.SyntaxToken +Microsoft.CodeAnalysis.CSharp.Syntax.ParameterSyntax.Update(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.SyntaxToken exclamationExclamationToken, Microsoft.CodeAnalysis.CSharp.Syntax.EqualsValueClauseSyntax default) -> Microsoft.CodeAnalysis.CSharp.Syntax.ParameterSyntax +Microsoft.CodeAnalysis.CSharp.Syntax.ParameterSyntax.WithExclamationExclamationToken(Microsoft.CodeAnalysis.SyntaxToken exclamationExclamationToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.ParameterSyntax +Microsoft.CodeAnalysis.CSharp.SyntaxKind.ExclamationExclamationToken = 8285 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind Microsoft.CodeAnalysis.CSharp.Syntax.ListPatternSyntax Microsoft.CodeAnalysis.CSharp.Syntax.ListPatternSyntax.AddPatterns(params Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.ListPatternSyntax Microsoft.CodeAnalysis.CSharp.Syntax.ListPatternSyntax.CloseBracketToken.get -> Microsoft.CodeAnalysis.SyntaxToken @@ -139,6 +143,7 @@ static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetDeclaredSymbol(this Mic static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.FileScopedNamespaceDeclaration(Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax name) -> Microsoft.CodeAnalysis.CSharp.Syntax.FileScopedNamespaceDeclarationSyntax static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.FileScopedNamespaceDeclaration(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax name, Microsoft.CodeAnalysis.SyntaxList externs, Microsoft.CodeAnalysis.SyntaxList usings, Microsoft.CodeAnalysis.SyntaxList members) -> Microsoft.CodeAnalysis.CSharp.Syntax.FileScopedNamespaceDeclarationSyntax static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.FileScopedNamespaceDeclaration(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken namespaceKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax name, Microsoft.CodeAnalysis.SyntaxToken semicolonToken, Microsoft.CodeAnalysis.SyntaxList externs, Microsoft.CodeAnalysis.SyntaxList usings, Microsoft.CodeAnalysis.SyntaxList members) -> Microsoft.CodeAnalysis.CSharp.Syntax.FileScopedNamespaceDeclarationSyntax +static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.Parameter(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.SyntaxToken exclamationExclamationToken, Microsoft.CodeAnalysis.CSharp.Syntax.EqualsValueClauseSyntax default) -> Microsoft.CodeAnalysis.CSharp.Syntax.ParameterSyntax static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ListPattern(Microsoft.CodeAnalysis.SeparatedSyntaxList patterns = default(Microsoft.CodeAnalysis.SeparatedSyntaxList)) -> Microsoft.CodeAnalysis.CSharp.Syntax.ListPatternSyntax static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ListPattern(Microsoft.CodeAnalysis.SeparatedSyntaxList patterns, Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax designation) -> Microsoft.CodeAnalysis.CSharp.Syntax.ListPatternSyntax static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ListPattern(Microsoft.CodeAnalysis.SyntaxToken openBracketToken, Microsoft.CodeAnalysis.SeparatedSyntaxList patterns, Microsoft.CodeAnalysis.SyntaxToken closeBracketToken, Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax designation) -> Microsoft.CodeAnalysis.CSharp.Syntax.ListPatternSyntax diff --git a/src/Compilers/CSharp/Portable/Symbols/AssemblySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/AssemblySymbol.cs index 1f39620a8b0c2..b4e073eb84f30 100644 --- a/src/Compilers/CSharp/Portable/Symbols/AssemblySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/AssemblySymbol.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Reflection.PortableExecutable; @@ -565,12 +566,13 @@ internal NamedTypeSymbol GetPrimitiveType(Microsoft.Cci.PrimitiveTypeCode type) return GetSpecialType(SpecialTypes.GetTypeFromMetadataName(type)); } +#nullable enable /// /// Lookup a type within the assembly using the canonical CLR metadata name of the type. /// /// Type name. /// Symbol for the type or null if type cannot be found or is ambiguous. - public NamedTypeSymbol GetTypeByMetadataName(string fullyQualifiedMetadataName) + public NamedTypeSymbol? GetTypeByMetadataName(string fullyQualifiedMetadataName) { if (fullyQualifiedMetadataName == null) { @@ -606,16 +608,16 @@ public NamedTypeSymbol GetTypeByMetadataName(string fullyQualifiedMetadataName) /// In cases a type could not be found because of ambiguity, we return two of the candidates that caused the ambiguity. /// /// Null if the type can't be found. - internal NamedTypeSymbol GetTypeByMetadataName( + internal NamedTypeSymbol? GetTypeByMetadataName( string metadataName, bool includeReferences, bool isWellKnownType, out (AssemblySymbol, AssemblySymbol) conflicts, bool useCLSCompliantNameArityEncoding = false, - DiagnosticBag warnings = null, + DiagnosticBag? warnings = null, bool ignoreCorLibraryDuplicatedTypes = false) { - NamedTypeSymbol type; + NamedTypeSymbol? type; MetadataTypeName mdName; if (metadataName.IndexOf('+') >= 0) @@ -626,7 +628,7 @@ internal NamedTypeSymbol GetTypeByMetadataName( type = GetTopLevelTypeByMetadataName(ref mdName, assemblyOpt: null, includeReferences: includeReferences, isWellKnownType: isWellKnownType, conflicts: out conflicts, warnings: warnings, ignoreCorLibraryDuplicatedTypes: ignoreCorLibraryDuplicatedTypes); - for (int i = 1; (object)type != null && !type.IsErrorType() && i < parts.Length; i++) + for (int i = 1; type is object && !type.IsErrorType() && i < parts.Length; i++) { mdName = MetadataTypeName.FromTypeName(parts[i]); NamedTypeSymbol temp = type.LookupMetadataType(ref mdName); @@ -640,7 +642,7 @@ internal NamedTypeSymbol GetTypeByMetadataName( conflicts: out conflicts, warnings: warnings, ignoreCorLibraryDuplicatedTypes: ignoreCorLibraryDuplicatedTypes); } - return ((object)type == null || type.IsErrorType()) ? null : type; + return (type is null || type.IsErrorType()) ? null : type; } private static readonly char[] s_nestedTypeNameSeparators = new char[] { '+' }; @@ -652,7 +654,7 @@ internal NamedTypeSymbol GetTypeByMetadataName( /// The type to resolve. /// Use referenced assemblies for resolution. /// The resolved symbol if successful or null on failure. - internal TypeSymbol GetTypeByReflectionType(Type type, bool includeReferences) + internal TypeSymbol? GetTypeByReflectionType(Type type, bool includeReferences) { System.Reflection.TypeInfo typeInfo = type.GetTypeInfo(); @@ -663,8 +665,8 @@ internal TypeSymbol GetTypeByReflectionType(Type type, bool includeReferences) if (typeInfo.IsArray) { - TypeSymbol symbol = GetTypeByReflectionType(typeInfo.GetElementType(), includeReferences); - if ((object)symbol == null) + TypeSymbol? symbol = GetTypeByReflectionType(typeInfo.GetElementType()!, includeReferences); + if (symbol is null) { return null; } @@ -675,8 +677,8 @@ internal TypeSymbol GetTypeByReflectionType(Type type, bool includeReferences) } else if (typeInfo.IsPointer) { - TypeSymbol symbol = GetTypeByReflectionType(typeInfo.GetElementType(), includeReferences); - if ((object)symbol == null) + TypeSymbol? symbol = GetTypeByReflectionType(typeInfo.GetElementType()!, includeReferences); + if (symbol is null) { return null; } @@ -707,8 +709,8 @@ internal TypeSymbol GetTypeByReflectionType(Type type, bool includeReferences) } int i = nestedTypes.Count - 1; - var symbol = (NamedTypeSymbol)GetTypeByReflectionType(nestedTypes[i].AsType(), includeReferences); - if ((object)symbol == null) + var symbol = (NamedTypeSymbol?)GetTypeByReflectionType(nestedTypes[i].AsType(), includeReferences); + if (symbol is null) { return null; } @@ -719,13 +721,13 @@ internal TypeSymbol GetTypeByReflectionType(Type type, bool includeReferences) MetadataTypeName mdName = MetadataTypeName.FromTypeName(nestedTypes[i].Name, forcedArity: forcedArity); symbol = symbol.LookupMetadataType(ref mdName); - if ((object)symbol == null || symbol.IsErrorType()) + if (symbol is null || symbol.IsErrorType()) { return null; } symbol = ApplyGenericArguments(symbol, genericArguments, ref typeArgumentIndex, includeReferences); - if ((object)symbol == null) + if (symbol is null) { return null; } @@ -744,9 +746,9 @@ internal TypeSymbol GetTypeByReflectionType(Type type, bool includeReferences) typeInfo.Name, forcedArity: typeInfo.GenericTypeArguments.Length); - NamedTypeSymbol symbol = GetTopLevelTypeByMetadataName(ref mdName, assemblyId, includeReferences, isWellKnownType: false, conflicts: out var _); + NamedTypeSymbol? symbol = GetTopLevelTypeByMetadataName(ref mdName, assemblyId, includeReferences, isWellKnownType: false, conflicts: out var _); - if ((object)symbol == null || symbol.IsErrorType()) + if (symbol is null || symbol.IsErrorType()) { return null; } @@ -759,7 +761,7 @@ internal TypeSymbol GetTypeByReflectionType(Type type, bool includeReferences) } } - private NamedTypeSymbol ApplyGenericArguments(NamedTypeSymbol symbol, Type[] typeArguments, ref int currentTypeArgument, bool includeReferences) + private NamedTypeSymbol? ApplyGenericArguments(NamedTypeSymbol symbol, Type[] typeArguments, ref int currentTypeArgument, bool includeReferences) { int remainingTypeArguments = typeArguments.Length - currentTypeArgument; @@ -776,7 +778,7 @@ private NamedTypeSymbol ApplyGenericArguments(NamedTypeSymbol symbol, Type[] typ for (int i = 0; i < length; i++) { var argSymbol = GetTypeByReflectionType(typeArguments[currentTypeArgument++], includeReferences); - if ((object)argSymbol == null) + if (argSymbol is null) { return null; } @@ -786,13 +788,13 @@ private NamedTypeSymbol ApplyGenericArguments(NamedTypeSymbol symbol, Type[] typ return symbol.ConstructIfGeneric(typeArgumentSymbols.ToImmutableAndFree()); } - internal NamedTypeSymbol GetTopLevelTypeByMetadataName( + internal NamedTypeSymbol? GetTopLevelTypeByMetadataName( ref MetadataTypeName metadataName, - AssemblyIdentity assemblyOpt, + AssemblyIdentity? assemblyOpt, bool includeReferences, bool isWellKnownType, out (AssemblySymbol, AssemblySymbol) conflicts, - DiagnosticBag warnings = null, // this is set to collect ambiguity warning for well-known types before C# 7 + DiagnosticBag? warnings = null, // this is set to collect ambiguity warning for well-known types before C# 7 bool ignoreCorLibraryDuplicatedTypes = false) { // Type from this assembly always wins. @@ -805,7 +807,7 @@ internal NamedTypeSymbol GetTopLevelTypeByMetadataName( Debug.Assert(warnings is null || isWellKnownType); conflicts = default; - NamedTypeSymbol result; + NamedTypeSymbol? result; // First try this assembly result = GetTopLevelTypeByMetadataName(this, ref metadataName, assemblyOpt); @@ -816,7 +818,7 @@ internal NamedTypeSymbol GetTopLevelTypeByMetadataName( } // ignore any types of the same name that might be in referenced assemblies (prefer the current assembly): - if ((object)result != null || !includeReferences) + if (result is object || !includeReferences) { return result; } @@ -829,7 +831,7 @@ internal NamedTypeSymbol GetTopLevelTypeByMetadataName( !CorLibrary.IsMissing && !isWellKnownTypeBeforeCSharp7 && !ignoreCorLibraryDuplicatedTypes) { - NamedTypeSymbol corLibCandidate = GetTopLevelTypeByMetadataName(CorLibrary, ref metadataName, assemblyOpt); + NamedTypeSymbol? corLibCandidate = GetTopLevelTypeByMetadataName(CorLibrary, ref metadataName, assemblyOpt); skipCorLibrary = true; if (isValidCandidate(corLibCandidate, isWellKnownType)) @@ -863,7 +865,7 @@ internal NamedTypeSymbol GetTopLevelTypeByMetadataName( continue; } - NamedTypeSymbol candidate = GetTopLevelTypeByMetadataName(assembly, ref metadataName, assemblyOpt); + NamedTypeSymbol? candidate = GetTopLevelTypeByMetadataName(assembly, ref metadataName, assemblyOpt); if (!isValidCandidate(candidate, isWellKnownType)) { @@ -872,7 +874,7 @@ internal NamedTypeSymbol GetTopLevelTypeByMetadataName( Debug.Assert(!TypeSymbol.Equals(candidate, result, TypeCompareKind.ConsiderEverything)); - if ((object)result != null) + if (result is object) { // duplicate if (ignoreCorLibraryDuplicatedTypes) @@ -910,7 +912,7 @@ internal NamedTypeSymbol GetTopLevelTypeByMetadataName( assemblies.Free(); return result; - bool isValidCandidate(NamedTypeSymbol candidate, bool isWellKnownType) + bool isValidCandidate([NotNullWhen(true)] NamedTypeSymbol? candidate, bool isWellKnownType) { return candidate is not null && (!isWellKnownType || IsValidWellKnownType(candidate)) @@ -923,9 +925,9 @@ private bool IsInCorLib(NamedTypeSymbol type) return (object)type.ContainingAssembly == CorLibrary; } - private bool IsValidWellKnownType(NamedTypeSymbol result) + private bool IsValidWellKnownType(NamedTypeSymbol? result) { - if ((object)result == null || result.TypeKind == TypeKind.Error) + if (result is null || result.TypeKind == TypeKind.Error) { return false; } @@ -936,7 +938,7 @@ private bool IsValidWellKnownType(NamedTypeSymbol result) return result.DeclaredAccessibility == Accessibility.Public || IsSymbolAccessible(result, this); } - private static NamedTypeSymbol GetTopLevelTypeByMetadataName(AssemblySymbol assembly, ref MetadataTypeName metadataName, AssemblyIdentity assemblyOpt) + private static NamedTypeSymbol? GetTopLevelTypeByMetadataName(AssemblySymbol assembly, ref MetadataTypeName metadataName, AssemblyIdentity? assemblyOpt) { var result = assembly.LookupTopLevelMetadataType(ref metadataName, digThroughForwardedTypes: false); if (!IsAcceptableMatchForGetTypeByMetadataName(result)) @@ -956,6 +958,7 @@ private static bool IsAcceptableMatchForGetTypeByMetadataName(NamedTypeSymbol ca { return candidate.Kind != SymbolKind.ErrorType || !(candidate is MissingMetadataTypeSymbol); } +#nullable disable /// /// Lookup member declaration in predefined CorLib type in this Assembly. Only valid if this diff --git a/src/Compilers/CSharp/Portable/Symbols/FunctionPointers/FunctionPointerParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/FunctionPointers/FunctionPointerParameterSymbol.cs index ae0c21f607c30..8f77374f477ce 100644 --- a/src/Compilers/CSharp/Portable/Symbols/FunctionPointers/FunctionPointerParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/FunctionPointers/FunctionPointerParameterSymbol.cs @@ -66,6 +66,7 @@ internal int MethodHashCode() public override bool IsDiscard => false; public override bool IsParams => false; public override bool IsImplicitlyDeclared => true; + public override bool IsNullChecked => false; internal override MarshalPseudoCustomAttributeData? MarshallingInformation => null; internal override bool IsMetadataOptional => false; internal override bool IsMetadataIn => RefKind == RefKind.In; diff --git a/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs index 269c23303c5df..0155095c808a6 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs @@ -440,6 +440,7 @@ internal static bool IsParameterlessConstructor(this MethodSymbol method) return method.MethodKind == MethodKind.Constructor && method.ParameterCount == 0; } +#nullable enable /// /// Returns true if the method is the default constructor synthesized for struct types, and /// if is true, the constructor simply zero-inits the instance. @@ -453,7 +454,7 @@ internal static bool IsParameterlessConstructor(this MethodSymbol method) internal static bool IsDefaultValueTypeConstructor(this MethodSymbol method, bool requireZeroInit) { if (method.IsImplicitlyDeclared && - method.ContainingType.IsValueType && + method.ContainingType?.IsValueType == true && method.IsParameterlessConstructor()) { if (!requireZeroInit) @@ -469,6 +470,7 @@ internal static bool IsDefaultValueTypeConstructor(this MethodSymbol method, boo } return false; } +#nullable disable /// /// Indicates whether the method should be emitted. diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs index c81541d3876a1..7d06f22c26224 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs @@ -1061,6 +1061,8 @@ internal override IEnumerable GetCustomAttributesToEmit(PEM get { return null; } } + public override bool IsNullChecked => false; + public sealed override bool Equals(Symbol other, TypeCompareKind compareKind) { return other is NativeIntegerParameterSymbol nps ? diff --git a/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs index 00c9d489aade6..f1f0dc2642df8 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs @@ -167,6 +167,11 @@ public bool IsOptional /// internal abstract bool IsMetadataOptional { get; } + /// + /// True if the compiler will synthesize a null check for this parameter (the parameter is declared in source with a '!' following the parameter name). + /// + public abstract bool IsNullChecked { get; } + /// /// True if In flag is set in metadata. /// diff --git a/src/Compilers/CSharp/Portable/Symbols/PublicModel/AssemblySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/PublicModel/AssemblySymbol.cs index 46fa5f2f73c1b..e4c64a6df80c8 100644 --- a/src/Compilers/CSharp/Portable/Symbols/PublicModel/AssemblySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/PublicModel/AssemblySymbol.cs @@ -98,10 +98,12 @@ bool IAssemblySymbol.GivesAccessTo(IAssemblySymbol assemblyWantingAccess) return false; } - INamedTypeSymbol IAssemblySymbol.GetTypeByMetadataName(string metadataName) +#nullable enable + INamedTypeSymbol? IAssemblySymbol.GetTypeByMetadataName(string metadataName) { return UnderlyingAssemblySymbol.GetTypeByMetadataName(metadataName).GetPublicSymbol(); } +#nullable disable #region ISymbol Members diff --git a/src/Compilers/CSharp/Portable/Symbols/PublicModel/ParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/PublicModel/ParameterSymbol.cs index 1504282ae7e3c..49220458b4803 100644 --- a/src/Compilers/CSharp/Portable/Symbols/PublicModel/ParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/PublicModel/ParameterSymbol.cs @@ -62,6 +62,8 @@ IParameterSymbol IParameterSymbol.OriginalDefinition bool IParameterSymbol.IsParams => _underlying.IsParams; + bool IParameterSymbol.IsNullChecked => _underlying.IsNullChecked; + bool IParameterSymbol.IsOptional => _underlying.IsOptional; bool IParameterSymbol.IsThis => _underlying.IsThis; diff --git a/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyParameterSymbol.cs index e43f954117dc6..f2f793ded6c09 100644 --- a/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyParameterSymbol.cs @@ -90,6 +90,8 @@ public SignatureOnlyParameterSymbol( internal override ModuleSymbol ContainingModule { get { throw ExceptionUtilities.Unreachable; } } + public override bool IsNullChecked => false; + internal override ImmutableArray InterpolatedStringHandlerArgumentIndexes => throw ExceptionUtilities.Unreachable; internal override bool HasInterpolatedStringHandlerArgumentError => throw ExceptionUtilities.Unreachable; diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/LambdaParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/LambdaParameterSymbol.cs index 65173f0a90d00..40a8036052fd0 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/LambdaParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/LambdaParameterSymbol.cs @@ -20,15 +20,19 @@ public LambdaParameterSymbol( RefKind refKind, string name, bool isDiscard, + bool isNullChecked, ImmutableArray locations) : base(owner, ordinal, parameterType, refKind, name, locations, syntaxRef: null, isParams: false, isExtensionMethodThis: false) { _attributeLists = attributeLists; IsDiscard = isDiscard; + IsNullChecked = isNullChecked; } public override bool IsDiscard { get; } + public override bool IsNullChecked { get; } + internal override bool IsMetadataOptional { get { return false; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/LambdaSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/LambdaSymbol.cs index 99bcd710cec2f..9e7a253c67905 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/LambdaSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/LambdaSymbol.cs @@ -15,6 +15,7 @@ internal sealed class LambdaSymbol : SourceMethodSymbolWithAttributes private readonly Binder _binder; private readonly Symbol _containingSymbol; private readonly MessageID _messageID; + private readonly SyntaxNode _syntax; private readonly ImmutableArray _parameters; private RefKind _refKind; private TypeWithAnnotations _returnType; @@ -49,6 +50,7 @@ public LambdaSymbol( _binder = binder; _containingSymbol = containingSymbol; _messageID = unboundLambda.Data.MessageID; + _syntax = unboundLambda.Syntax; if (!unboundLambda.HasExplicitReturnType(out _refKind, out _returnType)) { _refKind = refKind; @@ -217,7 +219,7 @@ public override ImmutableArray Locations { get { - return ImmutableArray.Create(Syntax.Location); + return ImmutableArray.Create(_syntax.Location); } } @@ -229,7 +231,7 @@ internal Location DiagnosticLocation { get { - return Syntax switch + return _syntax switch { AnonymousMethodExpressionSyntax syntax => syntax.DelegateKeyword.GetLocation(), LambdaExpressionSyntax syntax => syntax.ArrowToken.GetLocation(), @@ -238,7 +240,7 @@ internal Location DiagnosticLocation } } - private bool HasExplicitReturnType => Syntax is ParenthesizedLambdaExpressionSyntax { ReturnType: not null }; + private bool HasExplicitReturnType => _syntax is ParenthesizedLambdaExpressionSyntax { ReturnType: not null }; public override ImmutableArray DeclaringSyntaxReferences { @@ -263,15 +265,13 @@ public override bool IsExtensionMethod get { return false; } } - internal SyntaxNode Syntax => syntaxReferenceOpt.GetSyntax(); - internal override Binder SignatureBinder => _binder; internal override Binder ParameterBinder => new WithLambdaParametersBinder(this, _binder); internal override OneOrMany> GetAttributeDeclarations() { - return Syntax is LambdaExpressionSyntax lambdaSyntax ? + return _syntax is LambdaExpressionSyntax lambdaSyntax ? OneOrMany.Create(lambdaSyntax.AttributeLists) : default; } @@ -281,6 +281,7 @@ internal void GetDeclarationDiagnostics(BindingDiagnosticBag addTo) foreach (var parameter in _parameters) { parameter.ForceComplete(locationOpt: null, cancellationToken: default); + ParameterHelpers.ReportParameterNullCheckingErrors(addTo.DiagnosticBag, parameter); } GetAttributes(); @@ -354,8 +355,7 @@ private ImmutableArray MakeParameters( var location = unboundLambda.ParameterLocation(p); var locations = location == null ? ImmutableArray.Empty : ImmutableArray.Create(location); - var parameter = new LambdaParameterSymbol(owner: this, attributeLists, type, ordinal: p, refKind, name, unboundLambda.ParameterIsDiscard(p), locations); - + var parameter = new LambdaParameterSymbol(owner: this, attributeLists, type, ordinal: p, refKind, name, unboundLambda.ParameterIsDiscard(p), unboundLambda.ParameterIsNullChecked(p), locations); builder.Add(parameter); } @@ -369,22 +369,17 @@ public sealed override bool Equals(Symbol symbol, TypeCompareKind compareKind) if ((object)this == symbol) return true; return symbol is LambdaSymbol lambda - && areEqual(lambda.syntaxReferenceOpt, syntaxReferenceOpt) + && lambda._syntax == _syntax && lambda._refKind == _refKind && TypeSymbol.Equals(lambda.ReturnType, this.ReturnType, compareKind) && ParameterTypesWithAnnotations.SequenceEqual(lambda.ParameterTypesWithAnnotations, compareKind, (p1, p2, compareKind) => p1.Equals(p2, compareKind)) && lambda.ContainingSymbol.Equals(ContainingSymbol, compareKind); - - static bool areEqual(SyntaxReference a, SyntaxReference b) - { - return (object)a.SyntaxTree == b.SyntaxTree && a.Span == b.Span; - } } public override int GetHashCode() { - return syntaxReferenceOpt.GetHashCode(); + return _syntax.GetHashCode(); } public override bool IsImplicitlyDeclared diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionSymbol.cs index 15e70522e70af..34d250feb9f6a 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionSymbol.cs @@ -193,6 +193,10 @@ private void ComputeParameters() ParameterHelpers.EnsureIsReadOnlyAttributeExists(compilation, parameters, diagnostics, modifyCompilation: false); ParameterHelpers.EnsureNativeIntegerAttributeExists(compilation, parameters, diagnostics, modifyCompilation: false); ParameterHelpers.EnsureNullableAttributeExists(compilation, this, parameters, diagnostics, modifyCompilation: false); + foreach (var parameter in parameters) + { + ParameterHelpers.ReportParameterNullCheckingErrors(diagnostics.DiagnosticBag, parameter); + } // Note: we don't need to warn on annotations used in #nullable disable context for local functions, as this is handled in binding already var isVararg = arglistToken.Kind() == SyntaxKind.ArgListKeyword; diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs b/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs index 7bd317ece2a34..d68437362abfb 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs @@ -138,6 +138,10 @@ private static ImmutableArray MakeParameters MakeParameters useSiteInfo) is null) + { + diagnostics.Add(useSiteInfo.DiagnosticInfo, location); + } + if (parameter.TypeWithAnnotations.NullableAnnotation.IsAnnotated() + || parameter.Type.IsNullableTypeOrTypeParameter()) + { + diagnostics.Add(ErrorCode.WRN_NullCheckingOnNullableType, location, parameter); + } + else if (parameter.Type.IsValueType && !parameter.Type.IsPointerOrFunctionPointer()) + { + diagnostics.Add(ErrorCode.ERR_NonNullableValueTypeIsNullChecked, location, parameter); + } + } + internal static ImmutableArray ConditionallyCreateInModifiers(RefKind refKind, bool addRefReadOnlyModifier, Binder binder, BindingDiagnosticBag diagnostics, SyntaxNode syntax) { if (addRefReadOnlyModifier && refKind == RefKind.In) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs index 7d71be4f09953..189055c6fdee8 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs @@ -72,6 +72,8 @@ internal override ConstantValue DefaultValueFromAttributes get { return _originalParam.DefaultValueFromAttributes; } } + public override bool IsNullChecked => _originalParam.IsNullChecked; + #region Forwarded public override TypeWithAnnotations TypeWithAnnotations diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs index 5289a0c1986f3..13259d75e75c3 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs @@ -142,6 +142,9 @@ internal override FlowAnalysisAnnotations FlowAnalysisAnnotations } } + public override bool IsNullChecked + => this.CSharpSyntaxNode?.ExclamationExclamationToken.Kind() == SyntaxKind.ExclamationExclamationToken; + private static FlowAnalysisAnnotations DecodeFlowAnalysisAttributes(ParameterWellKnownAttributeData attributeData) { if (attributeData == null) @@ -357,6 +360,11 @@ private ConstantValue MakeDefaultExpression(BindingDiagnosticBag diagnostics, ou } } + if (this.IsNullChecked && convertedExpression.ConstantValue?.IsNull == true) + { + diagnostics.Add(ErrorCode.WRN_NullCheckedHasDefaultNull, Locations.FirstOrNone(), this.Name); + } + // represent default(struct) by a Null constant: var value = convertedExpression.ConstantValue ?? ConstantValue.Null; return value; diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateMethodSymbol.cs index a9054d3b505e3..242ee106c736b 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateMethodSymbol.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Reflection.Metadata; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; @@ -97,12 +98,18 @@ internal static void AddDelegateMembers( diagnostics.Add(ErrorCode.ERR_BadVisDelegateReturn, delegateType.Locations[0], delegateType, invoke.ReturnType); } - foreach (var parameter in invoke.Parameters) + for (int i = 0; i < invoke.Parameters.Length; i++) { - if (!parameter.TypeWithAnnotations.IsAtLeastAsVisibleAs(delegateType, ref useSiteInfo)) + var parameterSymbol = invoke.Parameters[i]; + if (!parameterSymbol.TypeWithAnnotations.IsAtLeastAsVisibleAs(delegateType, ref useSiteInfo)) { // Inconsistent accessibility: parameter type '{1}' is less accessible than delegate '{0}' - diagnostics.Add(ErrorCode.ERR_BadVisDelegateParam, delegateType.Locations[0], delegateType, parameter.Type); + diagnostics.Add(ErrorCode.ERR_BadVisDelegateParam, delegateType.Locations[0], delegateType, parameterSymbol.Type); + } + var parameterSyntax = syntax.ParameterList.Parameters[i]; + if (parameterSyntax.ExclamationExclamationToken.Kind() == SyntaxKind.ExclamationExclamationToken) + { + diagnostics.Add(ErrorCode.ERR_MustNullCheckInImplementation, parameterSyntax.Identifier.GetLocation(), parameterSyntax.Identifier.ValueText); } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol_ImplementationChecks.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol_ImplementationChecks.cs index 1fc069af626dc..0911c864ce008 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol_ImplementationChecks.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol_ImplementationChecks.cs @@ -1293,7 +1293,7 @@ TypeWithAnnotations getNotNullIfNotNullOutputType(TypeWithAnnotations outputType { var overrideParam = overrideParameters[i + overrideParameterOffset]; var baseParam = baseParameters[i]; - if (notNullIfParameterNotNull.Contains(overrideParam.Name) && NullableWalker.GetParameterState(baseParam.TypeWithAnnotations, baseParam.FlowAnalysisAnnotations).IsNotNull) + if (notNullIfParameterNotNull.Contains(overrideParam.Name) && NullableWalker.GetParameterState(baseParam.TypeWithAnnotations, baseParam.FlowAnalysisAnnotations, baseParam.IsNullChecked).IsNotNull) { return outputType.AsNotAnnotated(); } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberMethodSymbol.cs index 6a733d192ad94..1f8ba976a7a61 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberMethodSymbol.cs @@ -838,6 +838,11 @@ internal override void AfterAddingTypeMembersChecks(ConversionsBase conversions, { compilation.EnsureNullableContextAttributeExists(diagnostics, location, modifyCompilation: true); } + + foreach (var parameter in Parameters) + { + ParameterHelpers.ReportParameterNullCheckingErrors(diagnostics.DiagnosticBag, parameter); + } } // Consider moving this state to SourceMethodSymbol to emit NullableContextAttributes diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceParameterSymbol.cs index 2fde8c4e4fe27..e1d73c7888ed7 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceParameterSymbol.cs @@ -74,7 +74,8 @@ public static SourceParameterSymbol Create( !isExtensionMethodThis && (syntax.Default == null) && (syntax.AttributeLists.Count == 0) && - !owner.IsPartialMethod()) + !owner.IsPartialMethod() && + syntax.ExclamationExclamationToken.Kind() == SyntaxKind.None) { return new SourceSimpleParameterSymbol(owner, parameterType, ordinal, refKind, name, locations); } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceParameterSymbolBase.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceParameterSymbolBase.cs index 58d6a49ad20ad..191d55d0d3c60 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceParameterSymbolBase.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceParameterSymbolBase.cs @@ -67,6 +67,7 @@ public sealed override AssemblySymbol ContainingAssembly internal abstract ConstantValue DefaultValueFromAttributes { get; } + internal override void AddSynthesizedAttributes(PEModuleBuilder moduleBuilder, ref ArrayBuilder attributes) { base.AddSynthesizedAttributes(moduleBuilder, ref attributes); diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceSimpleParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceSimpleParameterSymbol.cs index 4c55bcc0edc62..0710303b2e808 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceSimpleParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceSimpleParameterSymbol.cs @@ -31,6 +31,19 @@ internal override ConstantValue? ExplicitDefaultConstantValue get { return null; } } + public override bool IsNullChecked + { + get + { + var node = this.GetNonNullSyntaxNode(); + if (node is ParameterSyntax param) + { + return param.ExclamationExclamationToken.Kind() == SyntaxKind.ExclamationExclamationToken; + } + return false; + } + } + internal override bool IsMetadataOptional { get { return false; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/ThisParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/ThisParameterSymbol.cs index 45d1458bb5ba5..c4e7e44ae5516 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/ThisParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/ThisParameterSymbol.cs @@ -168,6 +168,8 @@ internal override MarshalPseudoCustomAttributeData MarshallingInformation get { return null; } } + public override bool IsNullChecked => false; + internal override ImmutableArray InterpolatedStringHandlerArgumentIndexes => ImmutableArray.Empty; internal override bool HasInterpolatedStringHandlerArgumentError => false; diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedIntrinsicOperatorSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedIntrinsicOperatorSymbol.cs index 047fcb25a065d..95de7c40285d0 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedIntrinsicOperatorSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedIntrinsicOperatorSymbol.cs @@ -469,7 +469,7 @@ public SynthesizedOperatorParameterSymbol( TypeSymbol type, int ordinal, string name - ) : base(container, TypeWithAnnotations.Create(type), ordinal, RefKind.None, name) + ) : base(container, TypeWithAnnotations.Create(type), ordinal, RefKind.None, name, isNullChecked: false) { } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedParameterSymbol.cs index 5deade334c61c..e9112667d293c 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedParameterSymbol.cs @@ -26,7 +26,8 @@ public SynthesizedParameterSymbolBase( TypeWithAnnotations type, int ordinal, RefKind refKind, - string name = "") + string name, + bool isNullChecked) { RoslynDebug.Assert(type.HasType); RoslynDebug.Assert(name != null); @@ -37,6 +38,7 @@ public SynthesizedParameterSymbolBase( _ordinal = ordinal; _refKind = refKind; _name = name; + IsNullChecked = isNullChecked; } public override TypeWithAnnotations TypeWithAnnotations => _type; @@ -131,6 +133,8 @@ public override ImmutableArray Locations get { return ImmutableArray.Empty; } } + public sealed override bool IsNullChecked { get; } + public override ImmutableArray DeclaringSyntaxReferences { get @@ -188,8 +192,9 @@ private SynthesizedParameterSymbol( TypeWithAnnotations type, int ordinal, RefKind refKind, - string name) - : base(container, type, ordinal, refKind, name) + string name, + bool IsNullChecked) + : base(container, type, ordinal, refKind, name, IsNullChecked) { } @@ -200,11 +205,12 @@ public static ParameterSymbol Create( RefKind refKind, string name = "", ImmutableArray refCustomModifiers = default, - SourceComplexParameterSymbol? baseParameterForAttributes = null) + SourceComplexParameterSymbol? baseParameterForAttributes = null, + bool isNullChecked = false) { if (refCustomModifiers.IsDefaultOrEmpty && baseParameterForAttributes is null) { - return new SynthesizedParameterSymbol(container, type, ordinal, refKind, name); + return new SynthesizedParameterSymbol(container, type, ordinal, refKind, name, isNullChecked); } return new SynthesizedComplexParameterSymbol( @@ -214,7 +220,8 @@ public static ParameterSymbol Create( refKind, name, refCustomModifiers.NullToEmpty(), - baseParameterForAttributes); + baseParameterForAttributes, + isNullChecked); } /// @@ -270,8 +277,9 @@ public SynthesizedComplexParameterSymbol( RefKind refKind, string name, ImmutableArray refCustomModifiers, - SourceComplexParameterSymbol? baseParameterForAttributes) - : base(container, type, ordinal, refKind, name) + SourceComplexParameterSymbol? baseParameterForAttributes, + bool isNullChecked) + : base(container, type, ordinal, refKind, name, isNullChecked) { Debug.Assert(!refCustomModifiers.IsDefault); Debug.Assert(!refCustomModifiers.IsEmpty || baseParameterForAttributes is object); diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedThrowIfNullMethod.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedThrowIfNullMethod.cs new file mode 100644 index 0000000000000..7fbac8500ce9d --- /dev/null +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedThrowIfNullMethod.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.CodeGen; + +namespace Microsoft.CodeAnalysis.CSharp.Symbols +{ + /// + /// Throws a System.ArgumentNullException if 'argument' is null. + /// + internal sealed class SynthesizedThrowIfNullMethod : SynthesizedGlobalMethodSymbol + { + internal MethodSymbol ThrowMethod { get; } + internal SynthesizedThrowIfNullMethod(SourceModuleSymbol containingModule, PrivateImplementationDetails privateImplType, MethodSymbol throwMethod, TypeSymbol returnType, TypeSymbol argumentParamType, TypeSymbol paramNameParamType) + : base(containingModule, privateImplType, returnType, PrivateImplementationDetails.SynthesizedThrowIfNullFunctionName) + { + ThrowMethod = throwMethod; + + this.SetParameters(ImmutableArray.Create( + SynthesizedParameterSymbol.Create(this, TypeWithAnnotations.Create(argumentParamType), ordinal: 0, RefKind.None, "argument"), + SynthesizedParameterSymbol.Create(this, TypeWithAnnotations.Create(paramNameParamType), ordinal: 1, RefKind.None, "paramName"))); + } + + internal override void GenerateMethodBody(TypeCompilationState compilationState, BindingDiagnosticBag diagnostics) + { + SyntheticBoundNodeFactory F = new SyntheticBoundNodeFactory(this, this.GetNonNullSyntaxNode(), compilationState, diagnostics); + F.CurrentFunction = this; + + try + { + ParameterSymbol argument = this.Parameters[0]; + ParameterSymbol paramName = this.Parameters[1]; + + //if (argument is null) + //{ + // Throw(paramName); + //} + + var body = F.Block( + ImmutableArray.Empty, + F.If( + F.Binary(BinaryOperatorKind.ObjectEqual, F.SpecialType(SpecialType.System_Boolean), + F.Parameter(argument), + F.Null(argument.Type)), + F.ExpressionStatement(F.Call(receiver: null, ThrowMethod, F.Parameter(paramName)))), + F.Return()); + + // NOTE: we created this block in its most-lowered form, so analysis is unnecessary + F.CloseMethod(body); + } + catch (SyntheticBoundNodeFactory.MissingPredefinedMember ex) + { + diagnostics.Add(ex.Diagnostic); + F.CloseMethod(F.ThrowNull()); + } + } + } +} diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedThrowMethod.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedThrowMethod.cs new file mode 100644 index 0000000000000..87d707e458399 --- /dev/null +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedThrowMethod.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.CodeGen; + +namespace Microsoft.CodeAnalysis.CSharp.Symbols +{ + /// + /// Throws a System.ArgumentNullException with the given 'paramName'. + /// + internal sealed class SynthesizedThrowMethod : SynthesizedGlobalMethodSymbol + { + internal SynthesizedThrowMethod(SourceModuleSymbol containingModule, PrivateImplementationDetails privateImplType, TypeSymbol returnType, TypeSymbol paramType) + : base(containingModule, privateImplType, returnType, PrivateImplementationDetails.SynthesizedThrowFunctionName) + { + this.SetParameters(ImmutableArray.Create(SynthesizedParameterSymbol.Create(this, TypeWithAnnotations.Create(paramType), 0, RefKind.None, "paramName"))); + } + + internal override void GenerateMethodBody(TypeCompilationState compilationState, BindingDiagnosticBag diagnostics) + { + SyntheticBoundNodeFactory F = new SyntheticBoundNodeFactory(this, this.GetNonNullSyntaxNode(), compilationState, diagnostics); + F.CurrentFunction = this; + + try + { + ParameterSymbol paramName = this.Parameters[0]; + + //throw new ArgumentNullException(paramName); + + var body = F.Block( + ImmutableArray.Empty, + F.Throw(F.New(F.WellKnownMethod(WellKnownMember.System_ArgumentNullException__ctorString), ImmutableArray.Create(F.Parameter(paramName))))); + + // NOTE: we created this block in its most-lowered form, so analysis is unnecessary + F.CloseMethod(body); + } + catch (SyntheticBoundNodeFactory.MissingPredefinedMember ex) + { + diagnostics.Add(ex.Diagnostic); + F.CloseMethod(F.ThrowNull()); + } + } + } +} diff --git a/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedParameterSymbol.cs index 512e978ab86c3..0d112e6ddfcea 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedParameterSymbol.cs @@ -154,6 +154,8 @@ public override string GetDocumentationCommentXml(CultureInfo preferredCulture = return _underlyingParameter.GetDocumentationCommentXml(preferredCulture, expandIncludes, cancellationToken); } + public sealed override bool IsNullChecked => UnderlyingParameter.IsNullChecked; + #endregion } } diff --git a/src/Compilers/CSharp/Portable/Syntax/ParameterSyntax.cs b/src/Compilers/CSharp/Portable/Syntax/ParameterSyntax.cs index 420409f8577e4..8097295cb2fe1 100644 --- a/src/Compilers/CSharp/Portable/Syntax/ParameterSyntax.cs +++ b/src/Compilers/CSharp/Portable/Syntax/ParameterSyntax.cs @@ -17,5 +17,10 @@ internal bool IsArgList return this.Type == null && this.Identifier.ContextualKind() == SyntaxKind.ArgListKeyword; } } + + public ParameterSyntax Update(SyntaxList attributeLists, SyntaxTokenList modifiers, TypeSyntax type, SyntaxToken identifier, EqualsValueClauseSyntax @default) + { + return Update(attributeLists, modifiers, type, identifier, exclamationExclamationToken: default, @default); + } } } diff --git a/src/Compilers/CSharp/Portable/Syntax/Syntax.xml b/src/Compilers/CSharp/Portable/Syntax/Syntax.xml index 6d240a68bd908..4aebdf0ee086d 100644 --- a/src/Compilers/CSharp/Portable/Syntax/Syntax.xml +++ b/src/Compilers/CSharp/Portable/Syntax/Syntax.xml @@ -4191,6 +4191,9 @@ + + + diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs index 6a7d6469fa54c..ecff495816ec2 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs @@ -143,6 +143,8 @@ public enum SyntaxKind : ushort PercentEqualsToken = 8283, /// Represents ??= token. QuestionQuestionEqualsToken = 8284, + /// Represents !! token. + ExclamationExclamationToken = 8285, // Keywords /// Represents . diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs index 59a3fbece6123..2b39e146d4f58 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs @@ -1384,6 +1384,8 @@ public static string GetText(SyntaxKind kind) return "??="; case SyntaxKind.DotDotToken: return ".."; + case SyntaxKind.ExclamationExclamationToken: + return "!!"; // Keywords case SyntaxKind.BoolKeyword: diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index 07114d053470f..51e3062746355 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -627,6 +627,11 @@ Argumenty s modifikátorem in se nedají použít v dynamicky volaných výrazech. + + Incorrect parameter null checking syntax. Should be '!!'. + Incorrect parameter null checking syntax. Should be '!!'. + + Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. Dědění ze záznamu se zapečetěným objektem Object.ToString se v jazyce C# {0} nepodporuje. Použijte prosím jazykovou verzi {1} nebo vyšší. @@ -847,6 +852,11 @@ Seznam parametrů může mít jenom částečná deklarace jednoho záznamu. + + Parameter '{0}' can only have exclamation-point null checking in implementation methods. + Parameter '{0}' can only have exclamation-point null checking in implementation methods. + + The 'new()' constraint cannot be used with the 'unmanaged' constraint Omezení new() nejde používat s omezením unmanaged. @@ -887,6 +897,11 @@ Nepovedlo se určit výstupní adresář. + + Parameter '{0}' is a non-nullable value type and cannot be null-checked. + Parameter '{0}' is a non-nullable value type and cannot be null-checked. + + Record member '{0}' must be private. Člen záznamu {0} musí být privátní. @@ -917,6 +932,11 @@ {0} musí povolovat přepisování, protože obsahující záznam není zapečetěný. + + By-reference parameter '{0}' cannot be null-checked. + By-reference parameter '{0}' cannot be null-checked. + + null is not a valid parameter name. To get access to the receiver of an instance method, use the empty string as the parameter name. null není platný název parametru. Pokud chcete získat přístup k příjemci instanční metody, použijte jako název parametru prázdný řetězec. @@ -1047,6 +1067,11 @@ Primární konstruktor je v konfliktu se syntetizovaně zkopírovaným konstruktorem. + + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + + Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'. Přiřazení odkazu {1} k {0} nelze provést, protože {1} má užší řídicí obor než {0}. @@ -1377,6 +1402,11 @@ se strukturami + + parameter null-checking + parameter null-checking + + The assembly '{0}' containing type '{1}' references .NET Framework, which is not supported. Sestavení {0}, které obsahuje typ {1}, se odkazuje na architekturu .NET Framework, což se nepodporuje. @@ -1507,6 +1537,26 @@ Converting method group to non-delegate type + + Parameter '{0}' is null-checked but is null by default. + Parameter '{0}' is null-checked but is null by default. + + + + Parameter is null-checked but is null by default. + Parameter is null-checked but is null by default. + + + + Nullable type '{0}' is null-checked and will throw if null. + Nullable type '{0}' is null-checked and will throw if null. + + + + Nullable type is null-checked and will throw if null. + Nullable type is null-checked and will throw if null. + + Parameter '{0}' must have a non-null value when exiting because parameter '{1}' is non-null. Parametr {0} musí mít při ukončení hodnotu jinou než null, protože parametr {1} není null. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 51607b966f869..f003de40a6536 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -627,6 +627,11 @@ Argumente mit dem Modifizierer "in" können nicht in dynamisch gebundenen Ausdrücken verwendet werden. + + Incorrect parameter null checking syntax. Should be '!!'. + Incorrect parameter null checking syntax. Should be '!!'. + + Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. Das Erben von einem Datensatz mit einem versiegelten "Object.ToString" wird in C# {0} nicht unterstützt. Verwenden Sie die Sprachversion "{1}" oder höher. @@ -847,6 +852,11 @@ Nur eine partielle Deklaration eines einzelnen Datensatzes darf eine Parameterliste aufweisen. + + Parameter '{0}' can only have exclamation-point null checking in implementation methods. + Parameter '{0}' can only have exclamation-point null checking in implementation methods. + + The 'new()' constraint cannot be used with the 'unmanaged' constraint Die new()-Einschränkung kann nicht mit der unmanaged-Einschränkung verwendet werden. @@ -887,6 +897,11 @@ Das Ausgabeverzeichnis konnte nicht bestimmt werden. + + Parameter '{0}' is a non-nullable value type and cannot be null-checked. + Parameter '{0}' is a non-nullable value type and cannot be null-checked. + + Record member '{0}' must be private. Der Datensatzmember "{0}" muss privat sein. @@ -917,6 +932,11 @@ "{0}" muss Überschreibungen zulassen, weil der enthaltende Datensatz nicht versiegelt ist. + + By-reference parameter '{0}' cannot be null-checked. + By-reference parameter '{0}' cannot be null-checked. + + null is not a valid parameter name. To get access to the receiver of an instance method, use the empty string as the parameter name. NULL ist kein gültiger Parametername. Um auf den Empfänger einer Instanzmethode zuzugreifen, verwenden Sie die leere Zeichenfolge als Parameternamen. @@ -1047,6 +1067,11 @@ Der primäre Konstruktor verursacht einen Konflikt mit dem synthetisierten Kopierkonstruktor. + + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + + Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'. ref-assign von "{1}" zu "{0}" ist nicht möglich, weil "{1}" einen geringeren Escapebereich als "{0}" aufweist. @@ -1377,6 +1402,11 @@ "with" in Strukturen + + parameter null-checking + parameter null-checking + + The assembly '{0}' containing type '{1}' references .NET Framework, which is not supported. Die Assembly "{0}" mit dem Typ "{1}" verweist auf das .NET Framework. Dies wird nicht unterstützt. @@ -1507,6 +1537,26 @@ Converting method group to non-delegate type + + Parameter '{0}' is null-checked but is null by default. + Parameter '{0}' is null-checked but is null by default. + + + + Parameter is null-checked but is null by default. + Parameter is null-checked but is null by default. + + + + Nullable type '{0}' is null-checked and will throw if null. + Nullable type '{0}' is null-checked and will throw if null. + + + + Nullable type is null-checked and will throw if null. + Nullable type is null-checked and will throw if null. + + Parameter '{0}' must have a non-null value when exiting because parameter '{1}' is non-null. Der Parameter "{0}" muss beim Beenden einen Wert ungleich NULL aufweisen, weil Parameter "{1}" nicht NULL ist. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index 5d1d440246ad6..2ef18a017050e 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -627,6 +627,11 @@ No se pueden usar argumentos con el modificador "in" en expresiones distribuidas dinámicamente. + + Incorrect parameter null checking syntax. Should be '!!'. + Incorrect parameter null checking syntax. Should be '!!'. + + Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. No se admite heredar desde un registro con 'Object.ToString' sellado en C# {0}. Utilice la versión de idioma '{1}' o superior. @@ -847,6 +852,11 @@ Solo una declaración parcial de un registro puede tener una lista de parámetros + + Parameter '{0}' can only have exclamation-point null checking in implementation methods. + Parameter '{0}' can only have exclamation-point null checking in implementation methods. + + The 'new()' constraint cannot be used with the 'unmanaged' constraint La restricción "new()" no se puede utilizar con la restricción "unmanaged" @@ -887,6 +897,11 @@ No se pudo determinar el directorio de salida. + + Parameter '{0}' is a non-nullable value type and cannot be null-checked. + Parameter '{0}' is a non-nullable value type and cannot be null-checked. + + Record member '{0}' must be private. El miembro de registro "{0}" debe ser privado. @@ -917,6 +932,11 @@ "{0}" debe permitir la invalidación porque el registro contenedor no está sellado. + + By-reference parameter '{0}' cannot be null-checked. + By-reference parameter '{0}' cannot be null-checked. + + null is not a valid parameter name. To get access to the receiver of an instance method, use the empty string as the parameter name. null no es un nombre de parámetro válido. Para obtener acceso al receptor de un método de instancia, use la cadena vacía como nombre del parámetro. @@ -1047,6 +1067,11 @@ El constructor principal está en conflicto con el constructor de copia sintetizado. + + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + + Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'. No se puede asignar referencia "{1}" a "{0}" porque "{1}" tiene un ámbito de escape más limitado que "{0}". @@ -1377,6 +1402,11 @@ con estrcutras + + parameter null-checking + parameter null-checking + + The assembly '{0}' containing type '{1}' references .NET Framework, which is not supported. El ensamblado "{0}" que contiene el tipo "{1}" hace referencia a .NET Framework, lo cual no se admite. @@ -1507,6 +1537,26 @@ Converting method group to non-delegate type + + Parameter '{0}' is null-checked but is null by default. + Parameter '{0}' is null-checked but is null by default. + + + + Parameter is null-checked but is null by default. + Parameter is null-checked but is null by default. + + + + Nullable type '{0}' is null-checked and will throw if null. + Nullable type '{0}' is null-checked and will throw if null. + + + + Nullable type is null-checked and will throw if null. + Nullable type is null-checked and will throw if null. + + Parameter '{0}' must have a non-null value when exiting because parameter '{1}' is non-null. El parámetro "{0}" debe tener un valor que no sea NULL al salir porque el parámetro "{1}" no es NULL. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 6b9e346b9683a..aa31d18909afa 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -627,6 +627,11 @@ Impossible d'utiliser les arguments avec le modificateur 'in' dans les expressions dispatchées dynamiquement. + + Incorrect parameter null checking syntax. Should be '!!'. + Incorrect parameter null checking syntax. Should be '!!'. + + Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. L’héritage d’un enregistrement avec un 'Object.ToString' scellé n’est pas pris en charge dans C# {0}. Veuillez utiliser la version linguistique '{1}' ou version supérieure. @@ -847,6 +852,11 @@ Seule une déclaration partielle d'un seul enregistrement peut avoir une liste de paramètres + + Parameter '{0}' can only have exclamation-point null checking in implementation methods. + Parameter '{0}' can only have exclamation-point null checking in implementation methods. + + The 'new()' constraint cannot be used with the 'unmanaged' constraint La contrainte 'new()' ne peut pas être utilisée avec la contrainte 'unmanaged' @@ -887,6 +897,11 @@ Impossible de déterminer le répertoire de sortie + + Parameter '{0}' is a non-nullable value type and cannot be null-checked. + Parameter '{0}' is a non-nullable value type and cannot be null-checked. + + Record member '{0}' must be private. Le membre d'enregistrement '{0}' doit être privé. @@ -917,6 +932,11 @@ '{0}' doit autoriser la substitution, car l'enregistrement contenant n'est pas sealed. + + By-reference parameter '{0}' cannot be null-checked. + By-reference parameter '{0}' cannot be null-checked. + + null is not a valid parameter name. To get access to the receiver of an instance method, use the empty string as the parameter name. null n'est pas un nom de paramètre valide. Pour avoir accès au récepteur d'une méthode d'instance, utilisez la chaîne vide comme nom de paramètre. @@ -1047,6 +1067,11 @@ Le constructeur principal est en conflit avec le constructeur de copie synthétisée. + + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + + Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'. Impossible d'effectuer une assignation par référence de '{1}' vers '{0}', car '{1}' a une portée de sortie plus limitée que '{0}'. @@ -1377,6 +1402,11 @@ avec sur structs + + parameter null-checking + parameter null-checking + + The assembly '{0}' containing type '{1}' references .NET Framework, which is not supported. L'assembly '{0}' contenant le type '{1}' référence le .NET Framework, ce qui n'est pas pris en charge. @@ -1507,6 +1537,26 @@ Converting method group to non-delegate type + + Parameter '{0}' is null-checked but is null by default. + Parameter '{0}' is null-checked but is null by default. + + + + Parameter is null-checked but is null by default. + Parameter is null-checked but is null by default. + + + + Nullable type '{0}' is null-checked and will throw if null. + Nullable type '{0}' is null-checked and will throw if null. + + + + Nullable type is null-checked and will throw if null. + Nullable type is null-checked and will throw if null. + + Parameter '{0}' must have a non-null value when exiting because parameter '{1}' is non-null. Le paramètre '{0}' doit avoir une valeur non null au moment de la sortie, car le paramètre '{1}' a une valeur non null. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 64b073f33de85..12da28eec7bc7 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -627,6 +627,11 @@ Non è possibile usare argomenti con il modificatore 'in' nelle espressioni inviate in modo dinamico. + + Incorrect parameter null checking syntax. Should be '!!'. + Incorrect parameter null checking syntax. Should be '!!'. + + Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. L'ereditarietà da un record con un 'Object.ToString' di tipo sealed non è supportata in C# {0}. Usare la versione '{1}' o successiva del linguaggio. @@ -847,6 +852,11 @@ Solo una dichiarazione parziale di singolo record può includere un elenco di parametri + + Parameter '{0}' can only have exclamation-point null checking in implementation methods. + Parameter '{0}' can only have exclamation-point null checking in implementation methods. + + The 'new()' constraint cannot be used with the 'unmanaged' constraint Non è possibile usare il vincolo 'new()' con il vincolo 'unmanaged' @@ -887,6 +897,11 @@ Non è stato possibile individuare la directory di output + + Parameter '{0}' is a non-nullable value type and cannot be null-checked. + Parameter '{0}' is a non-nullable value type and cannot be null-checked. + + Record member '{0}' must be private. Il membro del record '{0}' deve essere privato. @@ -917,6 +932,11 @@ '{0}' deve consentire l'override perché il record contenitore non è sealed. + + By-reference parameter '{0}' cannot be null-checked. + By-reference parameter '{0}' cannot be null-checked. + + null is not a valid parameter name. To get access to the receiver of an instance method, use the empty string as the parameter name. Null non è un nome di parametro valido. Per ottenere l'accesso al ricevitore di un metodo di istanza, usare la stringa vuota come nome del parametro. @@ -1047,6 +1067,11 @@ Il costruttore primario è in conflitto con il costruttore di copia sintetizzato. + + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + + Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'. Non è possibile assegnare '{1}' a '{0}' come ref perché l'ambito di escape di '{1}' è ridotto rispetto a quello di '{0}'. @@ -1377,6 +1402,11 @@ con struct + + parameter null-checking + parameter null-checking + + The assembly '{0}' containing type '{1}' references .NET Framework, which is not supported. L'assembly '{0}' che contiene il tipo '{1}' fa riferimento a .NET Framework, che non è supportato. @@ -1507,6 +1537,26 @@ Converting method group to non-delegate type + + Parameter '{0}' is null-checked but is null by default. + Parameter '{0}' is null-checked but is null by default. + + + + Parameter is null-checked but is null by default. + Parameter is null-checked but is null by default. + + + + Nullable type '{0}' is null-checked and will throw if null. + Nullable type '{0}' is null-checked and will throw if null. + + + + Nullable type is null-checked and will throw if null. + Nullable type is null-checked and will throw if null. + + Parameter '{0}' must have a non-null value when exiting because parameter '{1}' is non-null. Il parametro '{0}' deve avere un valore non Null quando viene terminato perché il parametro '{1}' è non Null. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index dde5f90342c10..3249543ebb564 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -627,6 +627,11 @@ 'in' 修飾子を持つ引数を、動的ディスパッチされる式で使用することはできません。 + + Incorrect parameter null checking syntax. Should be '!!'. + Incorrect parameter null checking syntax. Should be '!!'. + + Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. シールされた ' Object. ToString ' を含むレコードからの継承は、C# {0} ではサポートされていません。' {1} ' 以上の言語バージョンを使用してください。 @@ -847,6 +852,11 @@ 1 つのレコードの部分宣言のみがパラメーター リストを持つことができます + + Parameter '{0}' can only have exclamation-point null checking in implementation methods. + Parameter '{0}' can only have exclamation-point null checking in implementation methods. + + The 'new()' constraint cannot be used with the 'unmanaged' constraint new()' 制約は 'unmanaged' 制約と一緒には使用できません @@ -887,6 +897,11 @@ 出力ディレクトリを特定できませんでした + + Parameter '{0}' is a non-nullable value type and cannot be null-checked. + Parameter '{0}' is a non-nullable value type and cannot be null-checked. + + Record member '{0}' must be private. レコード メンバー '{0}' は private でなければなりません。 @@ -917,6 +932,11 @@ '{0}' ではオーバーライドを許可する必要があります。これが含まれているレコードが sealed ではないためです。 + + By-reference parameter '{0}' cannot be null-checked. + By-reference parameter '{0}' cannot be null-checked. + + null is not a valid parameter name. To get access to the receiver of an instance method, use the empty string as the parameter name. null は有効なパラメーター名ではありません。インスタンス メソッドのレシーバーへのアクセスを取得するには、パラメーター名として空の文字列を使用します。 @@ -1047,6 +1067,11 @@ プライマリ コンストラクターが、合成されたコピー コンストラクターと競合しています。 + + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + + Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'. '{1}' を '{0}' に ref 割り当てすることはできません。'{1}' のエスケープ スコープが '{0}' より狭いためです。 @@ -1377,6 +1402,11 @@ 構造体の場合 + + parameter null-checking + parameter null-checking + + The assembly '{0}' containing type '{1}' references .NET Framework, which is not supported. 型 '{1}' を含むアセンブリ '{0}' が .NET Framework を参照しています。これはサポートされていません。 @@ -1507,6 +1537,26 @@ Converting method group to non-delegate type + + Parameter '{0}' is null-checked but is null by default. + Parameter '{0}' is null-checked but is null by default. + + + + Parameter is null-checked but is null by default. + Parameter is null-checked but is null by default. + + + + Nullable type '{0}' is null-checked and will throw if null. + Nullable type '{0}' is null-checked and will throw if null. + + + + Nullable type is null-checked and will throw if null. + Nullable type is null-checked and will throw if null. + + Parameter '{0}' must have a non-null value when exiting because parameter '{1}' is non-null. パラメーター '{1}' が null 以外であるため、パラメーター '{0}' には、終了時に null 以外の値が含まれている必要があります。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index aed32c68daf8d..a9da4b08745aa 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -627,6 +627,11 @@ 동적으로 디스패치된 식에서 'in' 한정자가 있는 인수를 사용할 수 없습니다. + + Incorrect parameter null checking syntax. Should be '!!'. + Incorrect parameter null checking syntax. Should be '!!'. + + Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. 봉인된 'Object.ToString'이 있는 레코드에서 상속은 C# {0}에서 지원되지 않습니다. 언어 버전 '{1}'이상을 사용하세요. @@ -847,6 +852,11 @@ 단일 레코드 partial 선언에만 매개 변수 목록을 사용할 수 있습니다. + + Parameter '{0}' can only have exclamation-point null checking in implementation methods. + Parameter '{0}' can only have exclamation-point null checking in implementation methods. + + The 'new()' constraint cannot be used with the 'unmanaged' constraint new()' 제약 조건은 'unmanaged' 제약 조건과 함께 사용할 수 없습니다. @@ -887,6 +897,11 @@ 출력 디렉터리를 확인할 수 없습니다. + + Parameter '{0}' is a non-nullable value type and cannot be null-checked. + Parameter '{0}' is a non-nullable value type and cannot be null-checked. + + Record member '{0}' must be private. 레코드 멤버 '{0}'은(는) 프라이빗이어야 합니다. @@ -917,6 +932,11 @@ 포함된 레코드가 봉인되지 않았으므로 '{0}'은(는) 재정의를 허용해야 합니다. + + By-reference parameter '{0}' cannot be null-checked. + By-reference parameter '{0}' cannot be null-checked. + + null is not a valid parameter name. To get access to the receiver of an instance method, use the empty string as the parameter name. null은 유효한 매개 변수 이름이 아닙니다. 인스턴스 메소드의 수신자에 액세스하려면 빈 문자열을 매개 변수 이름으로 사용하세요. @@ -1047,6 +1067,11 @@ 기본 생성자가 합성된 복사 생성자와 충돌합니다. + + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + + Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'. '{1}'을(를) '{0}'에 참조 할당할 수 없습니다. '{1}'이(가) '{0}'보다 이스케이프 범위가 좁기 때문입니다. @@ -1377,6 +1402,11 @@ 구조체에서 사용 + + parameter null-checking + parameter null-checking + + The assembly '{0}' containing type '{1}' references .NET Framework, which is not supported. '{1}' 형식을 포함하는 '{0}' 어셈블리가 지원되지 않는 .NET Framework를 참조합니다. @@ -1507,6 +1537,26 @@ Converting method group to non-delegate type + + Parameter '{0}' is null-checked but is null by default. + Parameter '{0}' is null-checked but is null by default. + + + + Parameter is null-checked but is null by default. + Parameter is null-checked but is null by default. + + + + Nullable type '{0}' is null-checked and will throw if null. + Nullable type '{0}' is null-checked and will throw if null. + + + + Nullable type is null-checked and will throw if null. + Nullable type is null-checked and will throw if null. + + Parameter '{0}' must have a non-null value when exiting because parameter '{1}' is non-null. 매개 변수 '{1}'이(가) null이 아니므로 매개 변수 '{0}'은(는) 종료할 때 null이 아닌 값을 가져야 합니다. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index 483205326a977..3531567c78565 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -627,6 +627,11 @@ Nie można używać argumentów z modyfikatorem „in” w wyrażeniach przydzielanych dynamicznie. + + Incorrect parameter null checking syntax. Should be '!!'. + Incorrect parameter null checking syntax. Should be '!!'. + + Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. Dziedziczenie z rekordu z zapieczętowanym obiektem "Object.ToString" nie jest obsługiwane w języku C# {0}. Użyj wersji języka "{1}" lub nowszej. @@ -847,6 +852,11 @@ Tylko pojedyncza częściowa deklaracja rekordu może mieć listę parametrów + + Parameter '{0}' can only have exclamation-point null checking in implementation methods. + Parameter '{0}' can only have exclamation-point null checking in implementation methods. + + The 'new()' constraint cannot be used with the 'unmanaged' constraint Ograniczenie „new()” nie może być używane z ograniczeniem „unmanaged” @@ -887,6 +897,11 @@ Nie można było określić katalogu wyjściowego + + Parameter '{0}' is a non-nullable value type and cannot be null-checked. + Parameter '{0}' is a non-nullable value type and cannot be null-checked. + + Record member '{0}' must be private. Składowa rekordu „{0}” musi być prywatna. @@ -917,6 +932,11 @@ Element „{0}” musi zezwalać na przesłanianie, ponieważ zawierający go rekord nie jest zapieczętowany. + + By-reference parameter '{0}' cannot be null-checked. + By-reference parameter '{0}' cannot be null-checked. + + null is not a valid parameter name. To get access to the receiver of an instance method, use the empty string as the parameter name. Wartość null nie jest prawidłową nazwą parametru. Aby uzyskać dostęp do odbiorcy metody wystąpienia, użyj pustego ciągu jako nazwy parametru. @@ -1047,6 +1067,11 @@ Konstruktor podstawowy powoduje konflikt z konstruktorem syntetyzowanej kopii. + + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + + Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'. Nie można przypisać odwołania elementu „{1}” do elementu „{0}”, ponieważ element „{1}” ma węższy zakres wyjścia niż element „{0}”. @@ -1377,6 +1402,11 @@ przy użyciu struktur + + parameter null-checking + parameter null-checking + + The assembly '{0}' containing type '{1}' references .NET Framework, which is not supported. Zestaw „{0}” zawierający typ „{1}” odwołuje się do platformy .NET Framework, co nie jest obsługiwane. @@ -1507,6 +1537,26 @@ Converting method group to non-delegate type + + Parameter '{0}' is null-checked but is null by default. + Parameter '{0}' is null-checked but is null by default. + + + + Parameter is null-checked but is null by default. + Parameter is null-checked but is null by default. + + + + Nullable type '{0}' is null-checked and will throw if null. + Nullable type '{0}' is null-checked and will throw if null. + + + + Nullable type is null-checked and will throw if null. + Nullable type is null-checked and will throw if null. + + Parameter '{0}' must have a non-null value when exiting because parameter '{1}' is non-null. Parametr „{0}” musi mieć wartość inną niż null podczas kończenia działania, ponieważ parametr „{1}” ma wartość inną niż null. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 10d59ba048a94..9c6f7af393549 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -627,6 +627,11 @@ Os argumentos com o modificador 'in' não podem ser usados em expressões vinculadas dinamicamente. + + Incorrect parameter null checking syntax. Should be '!!'. + Incorrect parameter null checking syntax. Should be '!!'. + + Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. Herdar de um registro com um 'Object.ToString' selado não é compatível com C# {0}. Use a versão do idioma '{1}' ou superior. @@ -847,6 +852,11 @@ Apenas uma declaração parcial de registro único pode ter uma lista de parâmetros + + Parameter '{0}' can only have exclamation-point null checking in implementation methods. + Parameter '{0}' can only have exclamation-point null checking in implementation methods. + + The 'new()' constraint cannot be used with the 'unmanaged' constraint A restrição 'new()' não pode ser usada com a restrição 'unmanaged' @@ -887,6 +897,11 @@ Não foi possível determinar o diretório de saída + + Parameter '{0}' is a non-nullable value type and cannot be null-checked. + Parameter '{0}' is a non-nullable value type and cannot be null-checked. + + Record member '{0}' must be private. O membro do registro '{0}' precisa ser privado. @@ -917,6 +932,11 @@ '{0}' precisa permitir a substituição porque o registro contentor não está selado. + + By-reference parameter '{0}' cannot be null-checked. + By-reference parameter '{0}' cannot be null-checked. + + null is not a valid parameter name. To get access to the receiver of an instance method, use the empty string as the parameter name. null não é um nome de parâmetro válido. Para obter acesso ao receptor de um método de instância, use a cadeia de caracteres vazia como o nome do parâmetro. @@ -1047,6 +1067,11 @@ O construtor primário entra em conflito com o construtor de cópia sintetizado. + + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + + Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'. Não é possível atribuir ref '{1}' a '{0}' porque '{1}' tem um escopo de escape mais limitado que '{0}'. @@ -1377,6 +1402,11 @@ com estruturas + + parameter null-checking + parameter null-checking + + The assembly '{0}' containing type '{1}' references .NET Framework, which is not supported. O assembly '{0}' contendo o tipo '{1}' referencia o .NET Framework, mas não há suporte para isso. @@ -1507,6 +1537,26 @@ Converting method group to non-delegate type + + Parameter '{0}' is null-checked but is null by default. + Parameter '{0}' is null-checked but is null by default. + + + + Parameter is null-checked but is null by default. + Parameter is null-checked but is null by default. + + + + Nullable type '{0}' is null-checked and will throw if null. + Nullable type '{0}' is null-checked and will throw if null. + + + + Nullable type is null-checked and will throw if null. + Nullable type is null-checked and will throw if null. + + Parameter '{0}' must have a non-null value when exiting because parameter '{1}' is non-null. O parâmetro '{0}' precisa ter um valor não nulo durante a saída porque o parâmetro '{1}' não é nulo. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 0b973d15934a8..09ba053829a96 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -627,6 +627,11 @@ Аргументы с модификатором "in" невозможно использовать в динамически диспетчеризируемых выражениях. + + Incorrect parameter null checking syntax. Should be '!!'. + Incorrect parameter null checking syntax. Should be '!!'. + + Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. Наследование от записи с запечатанным Object. ToString не поддерживается в C# {0}. Используйте версию языка "{1}" или более позднюю. @@ -847,6 +852,11 @@ Только частичное объявление отдельной записи может иметь список параметров. + + Parameter '{0}' can only have exclamation-point null checking in implementation methods. + Parameter '{0}' can only have exclamation-point null checking in implementation methods. + + The 'new()' constraint cannot be used with the 'unmanaged' constraint Ограничение "new()" невозможно использовать вместе с ограничением "unmanaged" @@ -887,6 +897,11 @@ Не удалось определить выходной каталог + + Parameter '{0}' is a non-nullable value type and cannot be null-checked. + Parameter '{0}' is a non-nullable value type and cannot be null-checked. + + Record member '{0}' must be private. Элемент записи "{0}" должен быть закрытым. @@ -917,6 +932,11 @@ "{0}" должен допускать переопределение, поскольку содержащая его запись не является запечатанной. + + By-reference parameter '{0}' cannot be null-checked. + By-reference parameter '{0}' cannot be null-checked. + + null is not a valid parameter name. To get access to the receiver of an instance method, use the empty string as the parameter name. "Null" не является допустимым именем параметра. Для получения доступа к приемнику метода экземпляра используйте пустую строку в качестве имени параметра. @@ -1047,6 +1067,11 @@ Первичный конструктор конфликтует с синтезированным конструктором копий. + + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + + Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'. Не удается присвоить по ссылке "{1}" для "{0}", так как escape-область у "{1}" уже, чем у "{0}". @@ -1377,6 +1402,11 @@ с использованием структур + + parameter null-checking + parameter null-checking + + The assembly '{0}' containing type '{1}' references .NET Framework, which is not supported. Сборка "{0}", содержащая тип "{1}", ссылается на платформу .NET Framework, которая не поддерживается. @@ -1507,6 +1537,26 @@ Converting method group to non-delegate type + + Parameter '{0}' is null-checked but is null by default. + Parameter '{0}' is null-checked but is null by default. + + + + Parameter is null-checked but is null by default. + Parameter is null-checked but is null by default. + + + + Nullable type '{0}' is null-checked and will throw if null. + Nullable type '{0}' is null-checked and will throw if null. + + + + Nullable type is null-checked and will throw if null. + Nullable type is null-checked and will throw if null. + + Parameter '{0}' must have a non-null value when exiting because parameter '{1}' is non-null. При выходе параметр "{0}" должен иметь значение, отличное от NULL, так как параметр "{1}" имеет значение, отличное от NULL. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index d687ca30069f6..0598069f64e54 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -627,6 +627,11 @@ 'in' değiştiricisine sahip bağımsız değişkenler dinamik olarak dağıtılan ifadelerde kullanılamaz. + + Incorrect parameter null checking syntax. Should be '!!'. + Incorrect parameter null checking syntax. Should be '!!'. + + Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. Mühürlü bir 'Object.ToString' içeren bir kayıttan devralma işlemi C# {0} sürümünde desteklenmiyor. Lütfen '{1}' veya üstü bir dil sürümünü kullanın. @@ -847,6 +852,11 @@ Yalnızca tek kaydın kısmi bildiriminde parametre listesi olabilir + + Parameter '{0}' can only have exclamation-point null checking in implementation methods. + Parameter '{0}' can only have exclamation-point null checking in implementation methods. + + The 'new()' constraint cannot be used with the 'unmanaged' constraint 'new()' kısıtlaması, 'unmanaged' kısıtlamasıyla kullanılamaz @@ -887,6 +897,11 @@ Çıkış dizini belirlenemedi + + Parameter '{0}' is a non-nullable value type and cannot be null-checked. + Parameter '{0}' is a non-nullable value type and cannot be null-checked. + + Record member '{0}' must be private. '{0}' kayıt üyesi özel olmalıdır. @@ -917,6 +932,11 @@ '{0}', kapsayan kayıt mühürlü olmadığından geçersiz kılmaya izin vermelidir. + + By-reference parameter '{0}' cannot be null-checked. + By-reference parameter '{0}' cannot be null-checked. + + null is not a valid parameter name. To get access to the receiver of an instance method, use the empty string as the parameter name. null geçerli bir parametre adı değil. Örnek metodu alıcısı için erişim almak için parametre adı olarak boş dizeyi kullanın. @@ -1047,6 +1067,11 @@ Birincil oluşturucu, sentezlenmiş kopya oluşturucusuyla çakışıyor. + + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + + Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'. '{1}', '{0}' öğesinden daha dar bir kaçış kapsamı içerdiğinden '{0}' öğesine '{1}' ref ataması yapılamıyor. @@ -1377,6 +1402,11 @@ yapılar ile birlikte + + parameter null-checking + parameter null-checking + + The assembly '{0}' containing type '{1}' references .NET Framework, which is not supported. '{1}' türünü içeren '{0}' bütünleştirilmiş kodu, desteklenmeyen .NET Framework'e başvuruyor. @@ -1507,6 +1537,26 @@ Converting method group to non-delegate type + + Parameter '{0}' is null-checked but is null by default. + Parameter '{0}' is null-checked but is null by default. + + + + Parameter is null-checked but is null by default. + Parameter is null-checked but is null by default. + + + + Nullable type '{0}' is null-checked and will throw if null. + Nullable type '{0}' is null-checked and will throw if null. + + + + Nullable type is null-checked and will throw if null. + Nullable type is null-checked and will throw if null. + + Parameter '{0}' must have a non-null value when exiting because parameter '{1}' is non-null. '{1}' parametresi null olmadığından '{0}' parametresi çıkış yaparken null olmayan bir değere sahip olmalıdır. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index 4d8712f6779d2..0b34568dc2dca 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -627,6 +627,11 @@ 带有 "in" 修饰符的参数不能用于动态调度的表达式。 + + Incorrect parameter null checking syntax. Should be '!!'. + Incorrect parameter null checking syntax. Should be '!!'. + + Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. C# {0} 中不支持从包含密封 'Object.ToString' 的记录继承。请使用语言版本 '{1}’ 或更高版本。 @@ -847,6 +852,11 @@ 只有一个记录分部声明可以具有参数列表 + + Parameter '{0}' can only have exclamation-point null checking in implementation methods. + Parameter '{0}' can only have exclamation-point null checking in implementation methods. + + The 'new()' constraint cannot be used with the 'unmanaged' constraint "new()" 约束不能与 "unmanaged" 约束一起使用 @@ -887,6 +897,11 @@ 无法确定输出目录 + + Parameter '{0}' is a non-nullable value type and cannot be null-checked. + Parameter '{0}' is a non-nullable value type and cannot be null-checked. + + Record member '{0}' must be private. 记录成员“{0}”必须是非公开的。 @@ -917,6 +932,11 @@ “{0}”必须允许替代,因为包含的记录未密封。 + + By-reference parameter '{0}' cannot be null-checked. + By-reference parameter '{0}' cannot be null-checked. + + null is not a valid parameter name. To get access to the receiver of an instance method, use the empty string as the parameter name. null 不是有效的参数名称。若要获取对实例方法接收器的访问权限,请使用空字符串作为参数名。 @@ -1047,6 +1067,11 @@ 主构造函数与合成的复制构造函数冲突。 + + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + + Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'. 无法将“{1}”重新赋值为“{0}”,因为“{1}”具有比“{0}”更窄的转义范围。 @@ -1377,6 +1402,11 @@ 在结构上 + + parameter null-checking + parameter null-checking + + The assembly '{0}' containing type '{1}' references .NET Framework, which is not supported. 包含类型“{1}”的程序集“{0}”引用了 .NET Framework,而此操作不受支持。 @@ -1507,6 +1537,26 @@ Converting method group to non-delegate type + + Parameter '{0}' is null-checked but is null by default. + Parameter '{0}' is null-checked but is null by default. + + + + Parameter is null-checked but is null by default. + Parameter is null-checked but is null by default. + + + + Nullable type '{0}' is null-checked and will throw if null. + Nullable type '{0}' is null-checked and will throw if null. + + + + Nullable type is null-checked and will throw if null. + Nullable type is null-checked and will throw if null. + + Parameter '{0}' must have a non-null value when exiting because parameter '{1}' is non-null. 退出时参数“{0}”必须具有非 null 值,因为参数“{1}”是非 null。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 8a14e79f3c7d9..9c55bdd59ab77 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -627,6 +627,11 @@ 具有 'in' 修飾元的引數不可用於動態分派的運算式。 + + Incorrect parameter null checking syntax. Should be '!!'. + Incorrect parameter null checking syntax. Should be '!!'. + + Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. C # {0} 不支援從具有密封的 'Object.ToString' 的記錄繼承。請使用 '{1}' 或更高的語言版本。 @@ -847,6 +852,11 @@ 只有一筆記錄可以在部分宣告中包含參數清單 + + Parameter '{0}' can only have exclamation-point null checking in implementation methods. + Parameter '{0}' can only have exclamation-point null checking in implementation methods. + + The 'new()' constraint cannot be used with the 'unmanaged' constraint new()' 條件約束不能和 'unmanaged' 條件約束一起使用 @@ -887,6 +897,11 @@ 無法判斷輸出目錄 + + Parameter '{0}' is a non-nullable value type and cannot be null-checked. + Parameter '{0}' is a non-nullable value type and cannot be null-checked. + + Record member '{0}' must be private. 記錄成員 '{0}' 必須為私人。 @@ -917,6 +932,11 @@ '{0}' 必須允許覆寫,因為包含的記錄並未密封。 + + By-reference parameter '{0}' cannot be null-checked. + By-reference parameter '{0}' cannot be null-checked. + + null is not a valid parameter name. To get access to the receiver of an instance method, use the empty string as the parameter name. null 不是有效的參數名稱。若要取得執行個體方法接收器的存取權,請使用空字串做為參數名稱。 @@ -1047,6 +1067,11 @@ 主要建構函式與合成的複製建構函式相衝突。 + + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + + Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'. 不能將 '{1}' 參考指派至 '{0}',因為 '{1}' 的逸出範圍比 '{0}' 還要窄。 @@ -1377,6 +1402,11 @@ 在結構上 + + parameter null-checking + parameter null-checking + + The assembly '{0}' containing type '{1}' references .NET Framework, which is not supported. 包含類型 '{1}' 的組件 '{0}' 參考了 .NET Framework,此情形不受支援。 @@ -1507,6 +1537,26 @@ Converting method group to non-delegate type + + Parameter '{0}' is null-checked but is null by default. + Parameter '{0}' is null-checked but is null by default. + + + + Parameter is null-checked but is null by default. + Parameter is null-checked but is null by default. + + + + Nullable type '{0}' is null-checked and will throw if null. + Nullable type '{0}' is null-checked and will throw if null. + + + + Nullable type is null-checked and will throw if null. + Nullable type is null-checked and will throw if null. + + Parameter '{0}' must have a non-null value when exiting because parameter '{1}' is non-null. 因為參數 '{1}' 不是 null,所以參數 '{0}' 在結束時必須具有非 Null 值。 diff --git a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenNullCheckedParameterTests.cs b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenNullCheckedParameterTests.cs new file mode 100644 index 0000000000000..647511c5f8785 --- /dev/null +++ b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenNullCheckedParameterTests.cs @@ -0,0 +1,2033 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen +{ + public class CodeGenNullCheckedParameterTests : CSharpTestBase + { + [Fact] + public void TestIsNullChecked() + { + var source = @" +using System; +public class C +{ + public static void Main() { } + public void M(string input!!) { } +}"; + + // Release + var compilation = CompileAndVerify(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C.M", @" +{ + // Code size 12 (0xc) + .maxstack 2 + ~IL_0000: ldarg.1 + IL_0001: ldstr ""input"" + IL_0006: call ""ThrowIfNull"" + -IL_000b: ret +}", sequencePoints: "C.M"); + // Debug + compilation = CompileAndVerify(source, options: TestOptions.DebugExe, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C.M", @" +{ + // Code size 14 (0xe) + .maxstack 2 + ~IL_0000: ldarg.1 + IL_0001: ldstr ""input"" + IL_0006: call ""ThrowIfNull"" + IL_000b: nop + -IL_000c: nop + -IL_000d: ret +}", sequencePoints: "C.M"); + + compilation.VerifyIL("ThrowIfNull", @" +{ + // Code size 10 (0xa) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: brtrue.s IL_0009 + IL_0003: ldarg.1 + IL_0004: call ""Throw"" + IL_0009: ret +}"); + compilation.VerifyIL("Throw", @" +{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: newobj ""System.ArgumentNullException..ctor(string)"" + IL_0006: throw +}"); + if (ExecutionConditionUtil.IsCoreClr) + { + compilation.VerifyTypeIL("", @" +.class private auto ansi sealed '' + extends [netstandard]System.Object +{ + .custom instance void [netstandard]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( + 01 00 00 00 + ) + // Methods + .method assembly hidebysig static + void Throw ( + string paramName + ) cil managed + { + // Method begins at RVA 0x206b + // Code size 7 (0x7) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: newobj instance void [netstandard]System.ArgumentNullException::.ctor(string) + IL_0006: throw + } // end of method ''::Throw + .method assembly hidebysig static + void ThrowIfNull ( + object argument, + string paramName + ) cil managed + { + // Method begins at RVA 0x2073 + // Code size 10 (0xa) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: brtrue.s IL_0009 + IL_0003: ldarg.1 + IL_0004: call void ''::Throw(string) + IL_0009: ret + } // end of method ''::ThrowIfNull +} // end of class +"); + } + } + + [Fact] + public void TestNetModule() + { + var source = @" +using System; +public class C +{ + public static void Main() { } + public void M(string input!!) { } +}"; + var compilation = CompileAndVerify( + source, + options: TestOptions.DebugModule.WithModuleName("Module"), + parseOptions: TestOptions.RegularPreview, + // PeVerify under net472 gives the error: "The module was expected to contain an assembly manifest." + verify: Verification.Skipped); + compilation.VerifyIL("C.M", @" +{ + // Code size 14 (0xe) + .maxstack 2 + ~IL_0000: ldarg.1 + IL_0001: ldstr ""input"" + IL_0006: call ""ThrowIfNull"" + IL_000b: nop + -IL_000c: nop + -IL_000d: ret +}", sequencePoints: "C.M"); + + compilation.VerifyIL("ThrowIfNull", @" +{ + // Code size 10 (0xa) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: brtrue.s IL_0009 + IL_0003: ldarg.1 + IL_0004: call ""Throw"" + IL_0009: ret +}"); + compilation.VerifyIL("Throw", @" +{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: newobj ""System.ArgumentNullException..ctor(string)"" + IL_0006: throw +}"); + if (ExecutionConditionUtil.IsCoreClr) + { + compilation.VerifyTypeIL("", @" +.class private auto ansi sealed '' + extends [netstandard]System.Object +{ + .custom instance void [netstandard]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( + 01 00 00 00 + ) + // Methods + .method assembly hidebysig static + void Throw ( + string paramName + ) cil managed + { + // Method begins at RVA 0x206b + // Code size 7 (0x7) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: newobj instance void [netstandard]System.ArgumentNullException::.ctor(string) + IL_0006: throw + } // end of method ''::Throw + .method assembly hidebysig static + void ThrowIfNull ( + object argument, + string paramName + ) cil managed + { + // Method begins at RVA 0x2073 + // Code size 10 (0xa) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: brtrue.s IL_0009 + IL_0003: ldarg.1 + IL_0004: call void ''::Throw(string) + IL_0009: ret + } // end of method ''::ThrowIfNull +} // end of class +"); + } + } + + [Fact] + public void TestManyParamsOneNullChecked() + { + var source = @" +using System; +public class C +{ + public static void Main() { } + public void M(string x, string y!!) { } +} +"; + + // Release + var compilation = CompileAndVerify(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C.M", @" +{ + // Code size 12 (0xc) + .maxstack 2 + ~IL_0000: ldarg.2 + IL_0001: ldstr ""y"" + IL_0006: call ""ThrowIfNull"" + -IL_000b: ret +}", sequencePoints: "C.M"); + // Debug + compilation = CompileAndVerify(source, options: TestOptions.DebugExe, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C.M", @" +{ + // Code size 14 (0xe) + .maxstack 2 + ~IL_0000: ldarg.2 + IL_0001: ldstr ""y"" + IL_0006: call ""ThrowIfNull"" + IL_000b: nop + -IL_000c: nop + -IL_000d: ret +}", sequencePoints: "C.M"); + } + + [Fact] + public void TestNullCheckedParamWithOptionalStringParameter() + { + var source = @" +class C +{ + public static void Main() { } + void M(string name!! = ""rose"") { } +}"; + + // Release + var compilation = CompileAndVerify(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C.M", @" +{ + // Code size 12 (0xc) + .maxstack 2 + ~IL_0000: ldarg.1 + IL_0001: ldstr ""name"" + IL_0006: call ""ThrowIfNull"" + -IL_000b: ret +}", sequencePoints: "C.M"); + // Debug + compilation = CompileAndVerify(source, options: TestOptions.DebugExe, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C.M", @" +{ + // Code size 14 (0xe) + .maxstack 2 + ~IL_0000: ldarg.1 + IL_0001: ldstr ""name"" + IL_0006: call ""ThrowIfNull"" + IL_000b: nop + -IL_000c: nop + -IL_000d: ret +}", sequencePoints: "C.M"); + } + + [Fact] + public void TestNullCheckedOperator() + { + var source = @" +class Box +{ + public static void Main() { } + public static int operator+ (Box b!!, Box c) + { + return 2; + } +}"; + + // Release + var compilation = CompileAndVerify(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("int Box.op_Addition(Box, Box)", @" +{ + // Code size 13 (0xd) + .maxstack 2 + ~IL_0000: ldarg.0 + IL_0001: ldstr ""b"" + IL_0006: call ""ThrowIfNull"" + -IL_000b: ldc.i4.2 + IL_000c: ret +}", sequencePoints: "Box.op_Addition"); + // Debug + compilation = CompileAndVerify(source, options: TestOptions.DebugExe, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("int Box.op_Addition(Box, Box)", @" +{ + // Code size 19 (0x13) + .maxstack 2 + .locals init (int V_0) + ~IL_0000: ldarg.0 + IL_0001: ldstr ""b"" + IL_0006: call ""ThrowIfNull"" + IL_000b: nop + -IL_000c: nop + -IL_000d: ldc.i4.2 + IL_000e: stloc.0 + IL_000f: br.s IL_0011 + -IL_0011: ldloc.0 + IL_0012: ret +}", sequencePoints: "Box.op_Addition"); + } + + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/58322")] + public void TestNullCheckedArgListImplementation() + { + var source = @" +class C +{ + void M() + { + M2(__arglist(1!!, 'M')); + } + void M2(__arglist) + { + } + public static void Main() { } +}"; + + // Release + var compilation = CompileAndVerify(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C.M", @" +{ + // Code size 10 (0xa) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: ldc.i4.1 + IL_0002: ldc.i4.s 77 + IL_0004: call ""void C.M2(__arglist) with __arglist( int, char)"" + IL_0009: ret +}"); + // Debug + compilation = CompileAndVerify(source, options: TestOptions.DebugExe, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C.M", @" +{ + // Code size 12 (0xc) + .maxstack 3 + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: ldc.i4.1 + IL_0003: ldc.i4.s 77 + IL_0005: call ""void C.M2(__arglist) with __arglist( int, char)"" + IL_000a: nop + IL_000b: ret +}"); + } + + [Fact] + public void TestNullCheckedIndexedProperty() + { + var source = @" +class C +{ + public string this[string index!!] => null; + public static void Main() { } +}"; + + // Release + var compilation = CompileAndVerify(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C.this[string].get", @" +{ + // Code size 13 (0xd) + .maxstack 2 + ~IL_0000: ldarg.1 + IL_0001: ldstr ""index"" + IL_0006: call ""ThrowIfNull"" + -IL_000b: ldnull + IL_000c: ret +}", sequencePoints: "C.get_Item"); + + // Debug + compilation = CompileAndVerify(source, options: TestOptions.DebugExe, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C.this[string].get", @" +{ + // Code size 14 (0xe) + .maxstack 2 + ~IL_0000: ldarg.1 + IL_0001: ldstr ""index"" + IL_0006: call ""ThrowIfNull"" + IL_000b: nop + -IL_000c: ldnull + IL_000d: ret +}", sequencePoints: "C.get_Item"); + } + + [Fact] + public void TestNullCheckedIndexedGetterSetter() + { + var source = @" +using System; +class C +{ + private object[] items = {'h', ""hello""}; + public string this[object item!!] + { + get + { + return items[0].ToString(); + } + set + { + items[0] = value; + } + } + public static void Main() + { + C c = new C(); + Console.WriteLine((string)c[""world""] ?? ""didn't work""); + } +}"; + // Release + var compilation = CompileAndVerify(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C.this[object].set", @" +{ + // Code size 21 (0x15) + .maxstack 3 + ~IL_0000: ldarg.1 + IL_0001: ldstr ""item"" + IL_0006: call ""ThrowIfNull"" + -IL_000b: ldarg.0 + IL_000c: ldfld ""object[] C.items"" + IL_0011: ldc.i4.0 + IL_0012: ldarg.2 + IL_0013: stelem.ref + -IL_0014: ret +}", sequencePoints: "C.set_Item"); + + // Debug + compilation = CompileAndVerify(source, options: TestOptions.DebugExe, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C.this[object].set", @" +{ + // Code size 23 (0x17) + .maxstack 3 + ~IL_0000: ldarg.1 + IL_0001: ldstr ""item"" + IL_0006: call ""ThrowIfNull"" + IL_000b: nop + -IL_000c: nop + -IL_000d: ldarg.0 + IL_000e: ldfld ""object[] C.items"" + IL_0013: ldc.i4.0 + IL_0014: ldarg.2 + IL_0015: stelem.ref + -IL_0016: ret +}", sequencePoints: "C.set_Item"); + } + + [Fact] + public void TestNullCheckedIndexedSetter() + { + var source = @" +class C +{ + object this[object index!!] { set { } } + public static void Main() { } +}"; + // Release + var compilation = CompileAndVerify(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C.this[object].set", @" +{ + // Code size 12 (0xc) + .maxstack 2 + ~IL_0000: ldarg.1 + IL_0001: ldstr ""index"" + IL_0006: call ""ThrowIfNull"" + -IL_000b: ret +}", sequencePoints: "C.set_Item"); + + // Debug + compilation = CompileAndVerify(source, options: TestOptions.DebugExe, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C.this[object].set", @" +{ + // Code size 14 (0xe) + .maxstack 2 + ~IL_0000: ldarg.1 + IL_0001: ldstr ""index"" + IL_0006: call ""ThrowIfNull"" + IL_000b: nop + -IL_000c: nop + -IL_000d: ret +}", sequencePoints: "C.set_Item"); + } + + [Fact] + public void TestNullCheckedSubstitution1() + { + var source = @" +class A +{ + internal virtual void M(U u!!) where U : T { } +} +class B1 : A where T : class +{ + internal override void M(U u!!) { } +} +"; + var compilation = CompileAndVerify(source, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("B1.M(U)", @" +{ + // Code size 17 (0x11) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: box ""U"" + IL_0006: ldstr ""u"" + IL_000b: call ""ThrowIfNull"" + IL_0010: ret +}"); + compilation.VerifyIL("A.M(U)", @" +{ + // Code size 17 (0x11) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: box ""U"" + IL_0006: ldstr ""u"" + IL_000b: call ""ThrowIfNull"" + IL_0010: ret +}"); + } + + [Fact] + public void TestNullCheckedSubstitution2() + { + var source = @" +using System; + +class A +{ + internal virtual void M(U u!!) where U : T { } +} +class B3 : A where T : struct +{ + internal override void M(U u!!) { } +} + +class Program +{ + static void Main() + { + var b3 = new B3(); + try + { + b3.M(1); + Console.Write(1); + b3.M(null); + } + catch + { + Console.Write(2); + } + } +}"; + var compilation = CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: "12"); + compilation.VerifyIL("B3.M(U)", @" +{ + // Code size 20 (0x14) + .maxstack 1 + IL_0000: ldarg.1 + IL_0001: box ""U"" + IL_0006: brtrue.s IL_0013 + IL_0008: ldstr ""u"" + IL_000d: newobj ""System.ArgumentNullException..ctor(string)"" + IL_0012: throw + IL_0013: ret +}"); + } + + [Fact] + public void TestNullCheckedSubstitution3() + { + var source = @" +class A +{ + internal virtual void M(U u!!) where U : T { } +} +class B5 : A +{ + internal override void M(U u!!) { } +}"; + var compilation = CompileAndVerify(source, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("B5.M(U)", @" +{ + // Code size 17 (0x11) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: box ""U"" + IL_0006: ldstr ""u"" + IL_000b: call ""ThrowIfNull"" + IL_0010: ret +}"); + } + + [Fact] + public void TestNullCheckedSubstitution4() + { + var source = @" +class C +{ + void M(T value!!) where T : notnull { } +}"; + var compilation = CompileAndVerify(source, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C.M(T)", @" +{ + // Code size 17 (0x11) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: box ""T"" + IL_0006: ldstr ""value"" + IL_000b: call ""ThrowIfNull"" + IL_0010: ret +}"); + } + + + [Fact] + public void TestNullCheckingExpectedOutput() + { + var source = @" +using System; +class Program +{ + static void M(T t) + { + if (t is null) throw new ArgumentNullException(); + } + static void Main() + { + Invoke(() => M(new object())); + Invoke(() => M((object)null)); + Invoke(() => M((int?)1)); + Invoke(() => M((int?)null)); + } + static void Invoke(Action a) + { + try + { + a(); + Console.WriteLine(""ok""); + } + catch (Exception e) + { + Console.WriteLine(e.GetType()); + } + } +}"; + CompileAndVerify(source, expectedOutput: @" +ok +System.ArgumentNullException +ok +System.ArgumentNullException", parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void TestNullCheckingExpectedOutput2() + { + var source = @" +using System; +class Program +{ + static void M(int? i!!) + { + } + static void Main() + { + Invoke(() => M((int?)1)); + Invoke(() => M((int?)null)); + } + static void Invoke(Action a) + { + try + { + a(); + Console.WriteLine(""ok""); + } + catch (Exception e) + { + Console.WriteLine(e.GetType()); + } + } +}"; + CompileAndVerify(source, expectedOutput: @" +ok +System.ArgumentNullException", parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void TestNullCheckingExpectedOutput3() + { + var source = @" +using System; +class A +{ + internal virtual void M(U u!!) where U : T { } +} +class B1 : A where T : class +{ + internal override void M(U u!!) { } +} +class Program +{ + static void Main() + { + B1 b1 = new B1(); + Invoke(() => b1.M(""hello world"")); + Invoke(() => b1.M(null)); + } + static void Invoke(Action a) + { + try + { + a(); + Console.WriteLine(""ok""); + } + catch (Exception e) + { + Console.WriteLine(e.GetType()); + } + } +}"; + CompileAndVerify(source, expectedOutput: @" +ok +System.ArgumentNullException", parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void TestNullCheckingExpectedOutput4() + { + var source = @" +using System; +class A +{ + internal virtual void M(U u!!) where U : T { } +} +class B3 : A where T : struct +{ + internal override void M(U u!!) { } +} +class Program +{ + static void Main() + { + B3 b3 = new B3(); + Invoke(() => b3.M(false)); + Invoke(() => b3.M(null)); + } + static void Invoke(Action a) + { + try + { + a(); + Console.WriteLine(""ok""); + } + catch (Exception e) + { + Console.WriteLine(e.GetType()); + } + } +}"; + CompileAndVerify(source, expectedOutput: @" +ok +System.ArgumentNullException", parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void TestNullCheckingExpectedOutput5() + { + var source = @" +using System; +class A +{ + internal virtual void M(U u!!) where U : T { } +} +class B5 : A +{ + internal override void M(U u!!) { } +} +class Program +{ + static void Main() + { + B5 b5 = new B5(); + Invoke(() => b5.M(false)); + Invoke(() => b5.M(null)); + } + static void Invoke(Action a) + { + try + { + a(); + Console.WriteLine(""ok""); + } + catch (Exception e) + { + Console.WriteLine(e.GetType()); + } + } +}"; + CompileAndVerify(source, expectedOutput: @" +ok +System.ArgumentNullException", parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void NotNullCheckedLambdaParameter() + { + var source = @" +using System; +class C +{ + public void M() + { + Func func1 = x => x; + } +}"; + var compilation = CompileAndVerify(source, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C.<>c.b__0_0(string)", @" +{ + // Code size 2 (0x2) + .maxstack 1 + IL_0000: ldarg.1 + IL_0001: ret +}"); + } + + [Fact] + public void NullCheckedLambdaParameter() + { + var source = @" +using System; +class C +{ + public void M() + { + Func func1 = x!! => x; + } +}"; + var compilation = CompileAndVerify(source, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C.<>c.b__0_0(string)", @" +{ + // Code size 13 (0xd) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldstr ""x"" + IL_0006: call ""ThrowIfNull"" + IL_000b: ldarg.1 + IL_000c: ret +}"); + } + + [Fact] + public void NullCheckedLambdaWithMultipleParameters() + { + var source = @" +using System; +class C +{ + public void M() + { + Func func1 = (x!!, y) => x; + } +}"; + var compilation = CompileAndVerify(source, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C.<>c.b__0_0(string, string)", @" +{ + // Code size 13 (0xd) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldstr ""x"" + IL_0006: call ""ThrowIfNull"" + IL_000b: ldarg.1 + IL_000c: ret +}"); + } + + [Fact] + public void ManyNullCheckedLambdasTest() + { + var source = @" +using System; +class C +{ + public void M() + { + Func func1 = (x!!, y!!) => x; + } +}"; + var compilation = CompileAndVerify(source, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C.<>c.b__0_0(string, string)", @" +{ + // Code size 24 (0x18) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldstr ""x"" + IL_0006: call ""ThrowIfNull"" + IL_000b: ldarg.2 + IL_000c: ldstr ""y"" + IL_0011: call ""ThrowIfNull"" + IL_0016: ldarg.1 + IL_0017: ret +}"); + } + + [Fact] + public void NullCheckedDiscardTest() + { + var source = @" +using System; +class C +{ + public void M() + { + Func func1 = _!! => null; + } +}"; + var compilation = CompileAndVerify(source, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C.<>c.b__0_0(string)", @" +{ + // Code size 13 (0xd) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldstr ""_"" + IL_0006: call ""ThrowIfNull"" + IL_000b: ldnull + IL_000c: ret +}"); + } + + [Fact] + public void NullCheckedLambdaInsideFieldTest() + { + var source = @" +using System; +class C +{ + Func func1 = x!! => x; + public void M() + { + } +}"; + var compilation = CompileAndVerify(source, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C.<>c.<.ctor>b__2_0(string)", @" +{ + // Code size 13 (0xd) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldstr ""x"" + IL_0006: call ""ThrowIfNull"" + IL_000b: ldarg.1 + IL_000c: ret +}"); + } + + [Fact] + public void NullCheckedLocalFunction() + { + var source = @" +using System; +class C +{ + public void M() + { + InnerM(""hello world""); + void InnerM(string x!!) { } + } +}"; + var compilation = CompileAndVerify(source, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C.g__InnerM|0_0(string)", @" +{ + // Code size 12 (0xc) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldstr ""x"" + IL_0006: call ""ThrowIfNull"" + IL_000b: ret +}"); + } + + [Fact] + public void NullCheckedLocalFunctionWithManyParams() + { + var source = @" +using System; +class C +{ + public void M() + { + InnerM(""hello"", ""world""); + void InnerM(string x!!, string y) { } + } +}"; + var compilation = CompileAndVerify(source, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C.g__InnerM|0_0(string, string)", @" +{ + // Code size 12 (0xc) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldstr ""x"" + IL_0006: call ""ThrowIfNull"" + IL_000b: ret +}"); + } + + [Fact] + public void TestLocalFunctionWithManyNullCheckedParams() + { + var source = @" +using System; +class C +{ + public void M() + { + InnerM(""hello"", ""world""); + void InnerM(string x!!, string y!!) { } + } +}"; + var compilation = CompileAndVerify(source, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C.g__InnerM|0_0(string, string)", @" +{ + // Code size 23 (0x17) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldstr ""x"" + IL_0006: call ""ThrowIfNull"" + IL_000b: ldarg.1 + IL_000c: ldstr ""y"" + IL_0011: call ""ThrowIfNull"" + IL_0016: ret +}"); + } + + [Fact] + public void TestLocalFunctionWithShadowedNullCheckedInnerParam() + { + var source = @" +using System; +class C +{ + public void M(string x) + { + InnerM(""hello""); + void InnerM(string x!!) { } + } +}"; + var compilation = CompileAndVerify(source, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C.g__InnerM|0_0(string)", @" +{ + // Code size 12 (0xc) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldstr ""x"" + IL_0006: call ""ThrowIfNull"" + IL_000b: ret +}"); + compilation.VerifyIL("C.M(string)", @" +{ + // Code size 11 (0xb) + .maxstack 1 + IL_0000: ldstr ""hello"" + IL_0005: call ""void C.g__InnerM|0_0(string)"" + IL_000a: ret +}"); + } + + [Fact] + public void TestLocalFunctionWithShadowedNullCheckedOuterParam() + { + var source = @" +using System; +class C +{ + public void M(string x!!) + { + InnerM(""hello""); + void InnerM(string x) { } + } +}"; + var compilation = CompileAndVerify(source, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C.g__InnerM|0_0(string)", @" +{ + // Code size 1 (0x1) + .maxstack 0 + IL_0000: ret +}"); + compilation.VerifyIL("C.M(string)", @" +{ + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldstr ""x"" + IL_0006: call ""ThrowIfNull"" + IL_000b: ldstr ""hello"" + IL_0010: call ""void C.g__InnerM|0_0(string)"" + IL_0015: ret +}"); + } + + [Fact] + public void TestNullCheckedConstructors() + { + var source = @" +class C +{ + public C(string x!!) { } +}"; + var compilation = CompileAndVerify(source, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C..ctor(string)", @" +{ + // Code size 18 (0x12) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldstr ""x"" + IL_0006: call ""ThrowIfNull"" + IL_000b: ldarg.0 + IL_000c: call ""object..ctor()"" + IL_0011: ret +}"); + } + + [Fact] + public void TestNullCheckedConstructorWithBaseChain() + { + var source = @" +class B +{ + public B(string y) { } +} +class C : B +{ + public C(string x!!) : base(x) { } +}"; + var compilation = CompileAndVerify(source, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C..ctor(string)", @" +{ + // Code size 19 (0x13) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldstr ""x"" + IL_0006: call ""ThrowIfNull"" + IL_000b: ldarg.0 + IL_000c: ldarg.1 + IL_000d: call ""B..ctor(string)"" + IL_0012: ret +}"); + compilation.VerifyIL("B..ctor(string)", @" +{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: call ""object..ctor()"" + IL_0006: ret +}"); + } + + [Fact] + public void TestNullCheckedConstructorWithThisChain() + { + var source = @" +class C +{ + public C() { } + public C(string x!!) : this() { } +}"; + var compilation = CompileAndVerify(source, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C..ctor(string)", @" +{ + // Code size 18 (0x12) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldstr ""x"" + IL_0006: call ""ThrowIfNull"" + IL_000b: ldarg.0 + IL_000c: call ""C..ctor()"" + IL_0011: ret +}"); + } + + [Fact] + public void TestNullCheckedConstructorWithFieldInitializers() + { + var source = @" +class C +{ + int y = 5; + public C(string x!!) { } +}"; + var compilation = CompileAndVerify(source, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C..ctor(string)", @" +{ + // Code size 25 (0x19) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldstr ""x"" + IL_0006: call ""ThrowIfNull"" + IL_000b: ldarg.0 + IL_000c: ldc.i4.5 + IL_000d: stfld ""int C.y"" + IL_0012: ldarg.0 + IL_0013: call ""object..ctor()"" + IL_0018: ret +}"); + } + + [Fact] + public void TestNullCheckedExpressionBodyMethod() + { + var source = @" +class C +{ + object Local(object arg!!) => arg; + public static void Main() { } +}"; + var compilation = CompileAndVerify(source, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C.Local(object)", @" +{ + // Code size 13 (0xd) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldstr ""arg"" + IL_0006: call ""ThrowIfNull"" + IL_000b: ldarg.1 + IL_000c: ret +}"); + } + + [Fact] + public void TestNullChecked2ExpressionBodyLambdas() + { + var source = @" +using System; +class C +{ + public Func M(string s1!!) => s2!! => s2 + s1; +}"; + var compilation = CompileAndVerify(source, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C.<>c__DisplayClass0_0.b__0(string)", @" +{ + // Code size 24 (0x18) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldstr ""s2"" + IL_0006: call ""ThrowIfNull"" + IL_000b: ldarg.1 + IL_000c: ldarg.0 + IL_000d: ldfld ""string C.<>c__DisplayClass0_0.s1"" + IL_0012: call ""string string.Concat(string, string)"" + IL_0017: ret +}"); + compilation.VerifyIL("C.M(string)", @" +{ + // Code size 35 (0x23) + .maxstack 3 + IL_0000: ldarg.1 + IL_0001: ldstr ""s1"" + IL_0006: call ""ThrowIfNull"" + IL_000b: newobj ""C.<>c__DisplayClass0_0..ctor()"" + IL_0010: dup + IL_0011: ldarg.1 + IL_0012: stfld ""string C.<>c__DisplayClass0_0.s1"" + IL_0017: ldftn ""string C.<>c__DisplayClass0_0.b__0(string)"" + IL_001d: newobj ""System.Func..ctor(object, System.IntPtr)"" + IL_0022: ret +}"); + compilation.VerifyIL("C.<>c__DisplayClass0_0..ctor()", @" +{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: call ""object..ctor()"" + IL_0006: ret +}"); + } + + [Fact] + public void TestNullCheckedIterator() + { + var source = @" +using System.Collections.Generic; +class C +{ + IEnumerable GetChars(string s!!) + { + foreach (var c in s) + { + yield return c; + } + } + public static void Main() + { + C c = new C(); + IEnumerable e = c.GetChars(""hello""); + + } +}"; + var compilation = CompileAndVerify(source, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("C.GetChars(string)", @" +{ + // Code size 26 (0x1a) + .maxstack 3 + IL_0000: ldc.i4.s -2 + IL_0002: newobj ""C.d__0..ctor(int)"" + IL_0007: ldarg.1 + IL_0008: ldstr ""s"" + IL_000d: call ""ThrowIfNull"" + IL_0012: dup + IL_0013: ldarg.1 + IL_0014: stfld ""string C.d__0.<>3__s"" + IL_0019: ret +}"); + } + + [Fact] + public void TestNullCheckedAsyncIterator() + { + var source = @" +using System.Collections.Generic; +using System.Threading.Tasks; + +class C +{ + async IAsyncEnumerable GetChars(string s!!, Task t) + { + foreach (var c in s) + { + await t; + yield return c; + } + } +}"; + var compilation = CompileAndVerify(CreateCompilationWithTasksExtensions(new[] { source, AsyncStreamsTypes }, parseOptions: TestOptions.RegularPreview)); + compilation.VerifyIL("C.GetChars", @" +{ + // Code size 33 (0x21) + .maxstack 3 + IL_0000: ldc.i4.s -2 + IL_0002: newobj ""C.d__0..ctor(int)"" + IL_0007: ldarg.1 + IL_0008: ldstr ""s"" + IL_000d: call ""ThrowIfNull"" + IL_0012: dup + IL_0013: ldarg.1 + IL_0014: stfld ""string C.d__0.<>3__s"" + IL_0019: dup + IL_001a: ldarg.2 + IL_001b: stfld ""System.Threading.Tasks.Task C.d__0.<>3__t"" + IL_0020: ret +}"); + } + + [Fact] + public void TestNullCheckedIteratorInLocalFunction() + { + var source = @" +using System.Collections.Generic; +class Iterators +{ + void Use() + { + IEnumerable e = GetChars(""hello""); + IEnumerable GetChars(string s!!) + { + foreach (var c in s) + { + yield return c; + } + } + } + +}"; + var compilation = CompileAndVerify(source, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("Iterators.g__GetChars|0_0(string)", @" +{ + // Code size 26 (0x1a) + .maxstack 3 + IL_0000: ldc.i4.s -2 + IL_0002: newobj ""Iterators.<g__GetChars|0_0>d..ctor(int)"" + IL_0007: ldarg.0 + IL_0008: ldstr ""s"" + IL_000d: call ""ThrowIfNull"" + IL_0012: dup + IL_0013: ldarg.0 + IL_0014: stfld ""string Iterators.<g__GetChars|0_0>d.<>3__s"" + IL_0019: ret +}"); + } + + [Fact] + public void TestNullCheckedEnumeratorInLocalFunction() + { + var source = @" +using System.Collections.Generic; +class Iterators +{ + void Use() + { + IEnumerator e = GetChars(""hello""); + IEnumerator GetChars(string s!!) + { + foreach (var c in s) + { + yield return c; + } + } + } + +}"; + var compilation = CompileAndVerify(source, parseOptions: TestOptions.RegularPreview); + compilation.VerifyIL("Iterators.g__GetChars|0_0(string)", @" +{ + // Code size 25 (0x19) + .maxstack 3 + IL_0000: ldc.i4.0 + IL_0001: newobj ""Iterators.<g__GetChars|0_0>d..ctor(int)"" + IL_0006: ldarg.0 + IL_0007: ldstr ""s"" + IL_000c: call ""ThrowIfNull"" + IL_0011: dup + IL_0012: ldarg.0 + IL_0013: stfld ""string Iterators.<g__GetChars|0_0>d.s"" + IL_0018: ret +}"); + } + + [Fact] + public void TestNullCheckedIteratorExecution() + { + var source = @" +using System; +using System.Collections.Generic; +class Program +{ + IEnumerable GetChars(string s!!) + { + foreach (var c in s) + { + yield return c; + } + } + static void Main() + { + Program p = new Program(); + var c = Invoke(() => p.GetChars(string.Empty)); + var e = Invoke(() => c.GetEnumerator()); + Invoke(() => e.MoveNext()); + c = Invoke(() => p.GetChars(null)); + } + static T Invoke(Func f) + { + T t = default; + try + { + t = f(); + Console.WriteLine(""ok""); + } + catch (Exception e) + { + Console.WriteLine(e.GetType()); + } + return t; + } +}"; + CompileAndVerify(source, expectedOutput: @" +ok +ok +ok +System.ArgumentNullException", parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void TestNullCheckedLambdaWithMissingType() + { + var source = +@" +using System; +class Program +{ + public static void Main() + { + Func func = x!! => x; + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.MakeMemberMissing(WellKnownMember.System_ArgumentNullException__ctorString); + comp.MakeTypeMissing(WellKnownType.System_ArgumentNullException); + comp.VerifyDiagnostics( + // (7,37): error CS0656: Missing compiler required member 'System.ArgumentNullException..ctor' + // Func func = x!! => x; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "x").WithArguments("System.ArgumentNullException", ".ctor").WithLocation(7, 37)); + } + + [Fact] + public void TestNullCheckedLocalFunctionWithMissingType() + { + var source = +@" +class Program +{ + public static void Main() + { + M(""ok""); + void M(string x!!) { } + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.MakeMemberMissing(WellKnownMember.System_ArgumentNullException__ctorString); + comp.MakeTypeMissing(WellKnownType.System_ArgumentNullException); + comp.VerifyDiagnostics( + // (7,23): error CS0656: Missing compiler required member 'System.ArgumentNullException..ctor' + // void M(string x!!) { } + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "x").WithArguments("System.ArgumentNullException", ".ctor").WithLocation(7, 23)); + } + + [Fact] + public void TestEmptyNullCheckedIterator1() + { + var source = @" +using System.Collections.Generic; +class C +{ + public static void Main() { } + static IEnumerable GetChars(string s!!) + { + yield break; + } +}"; + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview).VerifyIL("C.GetChars(string)", @" +{ + // Code size 19 (0x13) + .maxstack 3 + IL_0000: ldc.i4.s -2 + IL_0002: newobj ""C.d__1..ctor(int)"" + IL_0007: ldarg.0 + IL_0008: ldstr ""s"" + IL_000d: call ""ThrowIfNull"" + IL_0012: ret +}"); + } + + [Fact] + public void TestEmptyNullCheckedIterator2() + { + var source = @" +using System.Collections.Generic; +class C +{ + public static void Main() { } + static IEnumerator GetChars(string s!!) + { + yield break; + } +}"; + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview).VerifyIL("C.GetChars(string)", @" +{ + // Code size 18 (0x12) + .maxstack 3 + IL_0000: ldc.i4.0 + IL_0001: newobj ""C.d__1..ctor(int)"" + IL_0006: ldarg.0 + IL_0007: ldstr ""s"" + IL_000c: call ""ThrowIfNull"" + IL_0011: ret +}"); + } + + [Fact] + public void TestNullCheckedParams() + { + var source = @" +class C +{ + public static void Main() { } + public void M(params int[] number!!) {} +}"; + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview).VerifyIL("C.M(params int[])", @" +{ + // Code size 12 (0xc) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldstr ""number"" + IL_0006: call ""ThrowIfNull"" + IL_000b: ret +}"); + } + + [Theory] + [InlineData("")] + [InlineData("where T : class")] + [InlineData("where T : notnull")] + public void TestTypeParameter(string constraint) + { + var source = @" +class C +{ + public void M(T t!!) " + constraint + @" + { + } +}"; + var verifier = CompileAndVerify(source); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C.M", @" +{ + // Code size 17 (0x11) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: box ""T"" + IL_0006: ldstr ""t"" + IL_000b: call ""ThrowIfNull"" + IL_0010: ret +}"); + } + + [Fact] + public void TestTypeParameter_StructConstrained() + { + var source = @" +class C +{ + public void M(T? t!!) where T : struct + { + } +}"; + var verifier = CompileAndVerify(source); + verifier.VerifyDiagnostics( + // (4,25): warning CS8995: Nullable type 'T?' is null-checked and will throw if null. + // public void M(T? t!!) where T : struct + Diagnostic(ErrorCode.WRN_NullCheckingOnNullableType, "t").WithArguments("T?").WithLocation(4, 25)); + verifier.VerifyIL("C.M", @" +{ + // Code size 21 (0x15) + .maxstack 1 + IL_0000: ldarga.s V_1 + IL_0002: call ""bool T?.HasValue.get"" + IL_0007: brtrue.s IL_0014 + IL_0009: ldstr ""t"" + IL_000e: newobj ""System.ArgumentNullException..ctor(string)"" + IL_0013: throw + IL_0014: ret +}"); + verifier.VerifyMissing("ThrowIfNull"); + verifier.VerifyMissing("Throw"); + } + + [Fact] + public void TestNullableValueType() + { + var source = @" +class C +{ + public void M(int? i!!) // 1 + { + } +}"; + var verifier = CompileAndVerify(source); + verifier.VerifyDiagnostics( + // (4,24): warning CS8995: Nullable type 'int?' is null-checked and will throw if null. + // public void M(int? i!!) // 1 + Diagnostic(ErrorCode.WRN_NullCheckingOnNullableType, "i").WithArguments("int?").WithLocation(4, 24)); + verifier.VerifyIL("C.M(int?)", @" +{ + // Code size 21 (0x15) + .maxstack 1 + IL_0000: ldarga.s V_1 + IL_0002: call ""bool int?.HasValue.get"" + IL_0007: brtrue.s IL_0014 + IL_0009: ldstr ""i"" + IL_000e: newobj ""System.ArgumentNullException..ctor(string)"" + IL_0013: throw + IL_0014: ret +}"); + verifier.VerifyMissing("ThrowIfNull"); + verifier.VerifyMissing("Throw"); + } + + [Fact] + public void NullCheckedPointer() + { + var source = @" +using System; + + +class C +{ + static unsafe void M1(void* ptr!!) { } + static unsafe void M2(int* ptr!!) { } + static unsafe void M3(delegate* funcPtr!!) { } + + public static void M0(int x) { } + + public static unsafe void Main() + { + int x = 1; + try + { + M1(&x); + Console.Write(1); + M1(null); + } + catch + { + Console.Write(2); + } + + try + { + M2(&x); + Console.Write(3); + M2(null); + } + catch + { + Console.Write(4); + } + + try + { + M3(&M0); + Console.Write(5); + M3(null); + } + catch + { + Console.Write(6); + } + } +}"; + var verifier = CompileAndVerify(source, options: TestOptions.UnsafeDebugExe, verify: Verification.Skipped, expectedOutput: "123456"); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C.M1", @" +{ + // Code size 16 (0x10) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: brtrue.s IL_000e + IL_0003: ldstr ""ptr"" + IL_0008: newobj ""System.ArgumentNullException..ctor(string)"" + IL_000d: throw + IL_000e: nop + IL_000f: ret +}"); + verifier.VerifyIL("C.M2", @" +{ + // Code size 16 (0x10) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: brtrue.s IL_000e + IL_0003: ldstr ""ptr"" + IL_0008: newobj ""System.ArgumentNullException..ctor(string)"" + IL_000d: throw + IL_000e: nop + IL_000f: ret +}"); + verifier.VerifyIL("C.M3", @" +{ + // Code size 16 (0x10) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: brtrue.s IL_000e + IL_0003: ldstr ""funcPtr"" + IL_0008: newobj ""System.ArgumentNullException..ctor(string)"" + IL_000d: throw + IL_000e: nop + IL_000f: ret +}"); + verifier.VerifyMissing("ThrowIfNull"); + verifier.VerifyMissing("Throw"); + } + + [Fact] + public void UserDefinedConversion() + { + var source = @" +class C +{ + public static bool operator ==(C c1, C c2) => false; + public static bool operator !=(C c1, C c2) => false; + public override bool Equals(object obj) => false; + public override int GetHashCode() => 0; + + public static void M(C c!!) + { + if (c == null) { System.Console.Write(1); } + } +} +"; + var verifier = CompileAndVerify(source); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C.M", @" +{ + // Code size 27 (0x1b) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldstr ""c"" + IL_0006: call ""ThrowIfNull"" + IL_000b: ldarg.0 + IL_000c: ldnull + IL_000d: call ""bool C.op_Equality(C, C)"" + IL_0012: brfalse.s IL_001a + IL_0014: ldc.i4.1 + IL_0015: call ""void System.Console.Write(int)"" + IL_001a: ret +}"); + } + + [Fact] + public void Override_NullCheckedDifference() + { + var source = +@" +#nullable enable +class Base +{ + public virtual void M1(string x!!) { } + public virtual void M2(string x) { } +} + +class Derived : Base +{ + public override void M1(string x) { } + public override void M2(string x!!) { } +}"; + var verifier = CompileAndVerify(source); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Base.M1", @" +{ + // Code size 12 (0xc) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldstr ""x"" + IL_0006: call ""ThrowIfNull"" + IL_000b: ret +}"); + verifier.VerifyIL("Base.M2", @" +{ + // Code size 1 (0x1) + .maxstack 0 + IL_0000: ret +}"); + verifier.VerifyIL("Derived.M1", @" +{ + // Code size 1 (0x1) + .maxstack 0 + IL_0000: ret +}"); + verifier.VerifyIL("Derived.M2", @" +{ + // Code size 12 (0xc) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldstr ""x"" + IL_0006: call ""ThrowIfNull"" + IL_000b: ret +}"); + } + + [Fact] + public void Implementation_NullCheckedDifference1() + { + var source = +@" +#nullable enable +interface Base +{ + public void M1(string x!!) { } + public void M2(string x) { } +} + +class Derived : Base +{ + public void M1(string x) { } + public void M2(string x!!) { } +}"; + var verifier = CompileAndVerify(source, verify: Verification.Skipped, targetFramework: TargetFramework.NetCoreApp); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Base.M1", @" +{ + // Code size 12 (0xc) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldstr ""x"" + IL_0006: call ""ThrowIfNull"" + IL_000b: ret +}"); + verifier.VerifyIL("Base.M2", @" +{ + // Code size 1 (0x1) + .maxstack 0 + IL_0000: ret +}"); + verifier.VerifyIL("Derived.M1", @" +{ + // Code size 1 (0x1) + .maxstack 0 + IL_0000: ret +}"); + verifier.VerifyIL("Derived.M2", @" +{ + // Code size 12 (0xc) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldstr ""x"" + IL_0006: call ""ThrowIfNull"" + IL_000b: ret +}"); + } + + [Fact] + public void Implementation_NullCheckedDifference2() + { + var source = +@" +#nullable enable +interface Base +{ + void M1(string x); + void M2(string x); +} + +class Derived : Base +{ + public void M1(string x!!) { } + void Base.M2(string x!!) { } +}"; + var verifier = CompileAndVerify(source); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Derived.M1", @" +{ + // Code size 12 (0xc) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldstr ""x"" + IL_0006: call ""ThrowIfNull"" + IL_000b: ret +}"); + verifier.VerifyIL("Derived.Base.M2", @" +{ + // Code size 12 (0xc) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldstr ""x"" + IL_0006: call ""ThrowIfNull"" + IL_000b: ret +}"); + } + + [Fact] + public void RecordPrimaryConstructor() + { + var source = +@" +using System; + +Console.Write(new Record(""1"", ""0"").Prop1); +try +{ + new Record(null, ""a""); +} +catch (ArgumentNullException) +{ + Console.Write(2); +} + +Console.Write(new RecordStruct(""3"", ""0"").Prop1); +try +{ + new RecordStruct(""b"", null); +} +catch (ArgumentNullException) +{ + Console.Write(4); +} + +record Record(string Prop1!!, string Prop2!!); +record struct RecordStruct(string Prop1!!, string Prop2!!); +"; + var verifier = CompileAndVerify(new[] { source, IsExternalInitTypeDefinition }, verify: Verification.Skipped, expectedOutput: "1234"); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Record..ctor(string, string)", @" +{ + // Code size 43 (0x2b) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldstr ""Prop1"" + IL_0006: call ""ThrowIfNull"" + IL_000b: ldarg.2 + IL_000c: ldstr ""Prop2"" + IL_0011: call ""ThrowIfNull"" + IL_0016: ldarg.0 + IL_0017: ldarg.1 + IL_0018: stfld ""string Record.k__BackingField"" + IL_001d: ldarg.0 + IL_001e: ldarg.2 + IL_001f: stfld ""string Record.k__BackingField"" + IL_0024: ldarg.0 + IL_0025: call ""object..ctor()"" + IL_002a: ret +}"); + + verifier.VerifyIL("RecordStruct..ctor(string, string)", @" +{ + // Code size 37 (0x25) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldstr ""Prop1"" + IL_0006: call ""ThrowIfNull"" + IL_000b: ldarg.2 + IL_000c: ldstr ""Prop2"" + IL_0011: call ""ThrowIfNull"" + IL_0016: ldarg.0 + IL_0017: ldarg.1 + IL_0018: stfld ""string RecordStruct.k__BackingField"" + IL_001d: ldarg.0 + IL_001e: ldarg.2 + IL_001f: stfld ""string RecordStruct.k__BackingField"" + IL_0024: ret +}"); + } + } +} 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/IOperation/IOperation/IOperationTests_NullCheckedParameters.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_NullCheckedParameters.cs new file mode 100644 index 0000000000000..297a80c6d18f6 --- /dev/null +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_NullCheckedParameters.cs @@ -0,0 +1,2647 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.FlowAnalysis; +using Microsoft.CodeAnalysis.Operations; +using Microsoft.CodeAnalysis.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.CSharp.UnitTests +{ + public class IOperationTests_NullCheckedParameters : SemanticModelTestBase + { + [CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow)] + [Fact] + public void NullCheckedMethodDeclarationIOp() + { + var source = @" +public class C +{ + public void M(string input!!) { } +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + + compilation.VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var node1 = tree.GetRoot().DescendantNodes().OfType().Single(); + + compilation.VerifyOperationTree(node1, expectedOperationTree: @" +IMethodBodyOperation (OperationKind.MethodBody, Type: null) (Syntax: 'public void ... nput!!) { }') + BlockBody: + IBlockOperation (0 statements) (OperationKind.Block, Type: null) (Syntax: '{ }') + ExpressionBody: + null"); + + VerifyFlowGraph(compilation, node1, expectedFlowGraph: @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] +Block[B1] - Block + Predecessors: [B0] + Statements (0) + Jump if False (Regular) to Block[B3] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsImplicit) (Syntax: 'public void ... nput!!) { }') + Left: + IParameterReferenceOperation: input (OperationKind.ParameterReference, Type: System.String, IsImplicit) (Syntax: 'public void ... nput!!) { }') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: null, IsImplicit) (Syntax: 'public void ... nput!!) { }') + Next (Regular) Block[B2] +Block[B2] - Block + Predecessors: [B1] + Statements (0) + Next (Throw) Block[null] + IObjectCreationOperation (Constructor: System.ArgumentNullException..ctor(System.String paramName)) (OperationKind.ObjectCreation, Type: System.ArgumentNullException, IsImplicit) (Syntax: 'public void ... nput!!) { }') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: paramName) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'public void ... nput!!) { }') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""input"", IsImplicit) (Syntax: 'public void ... nput!!) { }') + 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 +Block[B3] - Exit + Predecessors: [B1] + Statements (0)"); + } + + [Fact] + public void TestIOp_OneNullCheckedManyParams() + { + var source = @" +public class C +{ + public void M(string x, string y!!) { } +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + + compilation.VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var node1 = tree.GetRoot().DescendantNodes().OfType().Single(); + + compilation.VerifyOperationTree(node1, expectedOperationTree: @" + IMethodBodyOperation (OperationKind.MethodBody, Type: null) (Syntax: 'public void ... ng y!!) { }') + BlockBody: + IBlockOperation (0 statements) (OperationKind.Block, Type: null) (Syntax: '{ }') + ExpressionBody: + null"); + + VerifyFlowGraph(compilation, node1, expectedFlowGraph: @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] +Block[B1] - Block + Predecessors: [B0] + Statements (0) + Jump if False (Regular) to Block[B3] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsImplicit) (Syntax: 'public void ... ng y!!) { }') + Left: + IParameterReferenceOperation: y (OperationKind.ParameterReference, Type: System.String, IsImplicit) (Syntax: 'public void ... ng y!!) { }') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: null, IsImplicit) (Syntax: 'public void ... ng y!!) { }') + Next (Regular) Block[B2] +Block[B2] - Block + Predecessors: [B1] + Statements (0) + Next (Throw) Block[null] + IObjectCreationOperation (Constructor: System.ArgumentNullException..ctor(System.String paramName)) (OperationKind.ObjectCreation, Type: System.ArgumentNullException, IsImplicit) (Syntax: 'public void ... ng y!!) { }') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: paramName) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'public void ... ng y!!) { }') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""y"", IsImplicit) (Syntax: 'public void ... ng y!!) { }') + 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 +Block[B3] - Exit + Predecessors: [B1] + Statements (0)"); + } + + [Fact] + public void TestIOp_OneNullCheckedParamWithStringOpt() + { + var source = @" +public class C +{ + public void M(string name!! = ""rose"") { } +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + + compilation.VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var node1 = tree.GetRoot().DescendantNodes().OfType().Single(); + + compilation.VerifyOperationTree(node1, expectedOperationTree: @" + IMethodBodyOperation (OperationKind.MethodBody, Type: null) (Syntax: 'public void ... ""rose"") { }') + BlockBody: + IBlockOperation (0 statements) (OperationKind.Block, Type: null) (Syntax: '{ }') + ExpressionBody: + null"); + + VerifyFlowGraph(compilation, node1, expectedFlowGraph: @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] +Block[B1] - Block + Predecessors: [B0] + Statements (0) + Jump if False (Regular) to Block[B3] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsImplicit) (Syntax: 'public void ... ""rose"") { }') + Left: + IParameterReferenceOperation: name (OperationKind.ParameterReference, Type: System.String, IsImplicit) (Syntax: 'public void ... ""rose"") { }') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: null, IsImplicit) (Syntax: 'public void ... ""rose"") { }') + Next (Regular) Block[B2] +Block[B2] - Block + Predecessors: [B1] + Statements (0) + Next (Throw) Block[null] + IObjectCreationOperation (Constructor: System.ArgumentNullException..ctor(System.String paramName)) (OperationKind.ObjectCreation, Type: System.ArgumentNullException, IsImplicit) (Syntax: 'public void ... ""rose"") { }') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: paramName) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'public void ... ""rose"") { }') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""name"", IsImplicit) (Syntax: 'public void ... ""rose"") { }') + 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 +Block[B3] - Exit + Predecessors: [B1] + Statements (0)"); + } + + [Fact] + public void TestIOp_NullCheckedOperator() + { + var source = @" +public class Box +{ + public static int operator+ (Box b!!, Box c) + { + return 2; + } +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + + compilation.VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var node1 = tree.GetRoot().DescendantNodes().OfType().Single(); + + compilation.VerifyOperationTree(node1, expectedOperationTree: @" + IMethodBodyOperation (OperationKind.MethodBody, Type: null) (Syntax: 'public stat ... }') + BlockBody: + IBlockOperation (1 statements) (OperationKind.Block, Type: null) (Syntax: '{ ... }') + IReturnOperation (OperationKind.Return, Type: null) (Syntax: 'return 2;') + ReturnedValue: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 2) (Syntax: '2') + ExpressionBody: + null"); + + VerifyFlowGraph(compilation, node1, expectedFlowGraph: @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] +Block[B1] - Block + Predecessors: [B0] + Statements (0) + Jump if False (Regular) to Block[B3] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsImplicit) (Syntax: 'public stat ... }') + Left: + IParameterReferenceOperation: b (OperationKind.ParameterReference, Type: Box, IsImplicit) (Syntax: 'public stat ... }') + Right: + ILiteralOperation (OperationKind.Literal, Type: Box, Constant: null, IsImplicit) (Syntax: 'public stat ... }') + Next (Regular) Block[B2] +Block[B2] - Block + Predecessors: [B1] + Statements (0) + Next (Throw) Block[null] + IObjectCreationOperation (Constructor: System.ArgumentNullException..ctor(System.String paramName)) (OperationKind.ObjectCreation, Type: System.ArgumentNullException, IsImplicit) (Syntax: 'public stat ... }') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: paramName) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'public stat ... }') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""b"", IsImplicit) (Syntax: 'public stat ... }') + 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 +Block[B3] - Block + Predecessors: [B1] + Statements (0) + Next (Return) Block[B4] + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 2) (Syntax: '2') +Block[B4] - Exit + Predecessors: [B3] + Statements (0)"); + } + + [Fact] + public void TestIOp_NullCheckedIndexedProperty() + { + // https://github.com/dotnet/roslyn/issues/58320 + var source = @" +public class C +{ + public string this[string index!!] => null; +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + + compilation.VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var node1 = tree.GetRoot().DescendantNodes().OfType().Single(); + compilation.VerifyOperationTree(node1, expectedOperationTree: @" + IBlockOperation (1 statements) (OperationKind.Block, Type: null) (Syntax: '=> null') + IReturnOperation (OperationKind.Return, Type: null, IsImplicit) (Syntax: 'null') + ReturnedValue: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.String, Constant: null, IsImplicit) (Syntax: 'null') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + Operand: + ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null')"); + + VerifyFlowGraph(compilation, node1, expectedFlowGraph: @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] +Block[B1] - Block + Predecessors: [B0] + Statements (0) + Next (Return) Block[B2] + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.String, Constant: null, IsImplicit) (Syntax: 'null') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') +Block[B2] - Exit + Predecessors: [B1] + Statements (0)"); + } + + [Fact] + public void TestIOp_NullCheckedIndexedGetterSetter() + { + var source = @" +public class C +{ + private object[] items = {'h', ""hello""}; + public string this[object item!!] + { + /**/get + { + return items[0].ToString(); + }/**/ + set + { + items[0] = value; + } + } +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + + compilation.VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var node1 = tree.GetRoot().DescendantNodes().OfType().ElementAt(1); + compilation.VerifyOperationTree(node1, expectedOperationTree: @" + IMethodBodyOperation (OperationKind.MethodBody, Type: null) (Syntax: 'set ... }') + BlockBody: + IBlockOperation (1 statements) (OperationKind.Block, Type: null) (Syntax: '{ ... }') + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'items[0] = value;') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Object) (Syntax: 'items[0] = value') + Left: + IArrayElementReferenceOperation (OperationKind.ArrayElementReference, Type: System.Object) (Syntax: 'items[0]') + Array reference: + IFieldReferenceOperation: System.Object[] C.items (OperationKind.FieldReference, Type: System.Object[]) (Syntax: 'items') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'items') + Indices(1): + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0) (Syntax: '0') + Right: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'value') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + Operand: + IParameterReferenceOperation: value (OperationKind.ParameterReference, Type: System.String) (Syntax: 'value') + ExpressionBody: + null"); + var expected = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] +Block[B1] - Block + Predecessors: [B0] + Statements (0) + Jump if False (Regular) to Block[B3] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsImplicit) (Syntax: 'get ... }') + Left: + IParameterReferenceOperation: item (OperationKind.ParameterReference, Type: System.Object, IsImplicit) (Syntax: 'get ... }') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Object, Constant: null, IsImplicit) (Syntax: 'get ... }') + Next (Regular) Block[B2] +Block[B2] - Block + Predecessors: [B1] + Statements (0) + Next (Throw) Block[null] + IObjectCreationOperation (Constructor: System.ArgumentNullException..ctor(System.String paramName)) (OperationKind.ObjectCreation, Type: System.ArgumentNullException, IsImplicit) (Syntax: 'get ... }') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: paramName) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'get ... }') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""item"", IsImplicit) (Syntax: 'get ... }') + 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 +Block[B3] - Block + Predecessors: [B1] + Statements (0) + Next (Return) Block[B4] + IInvocationOperation (virtual System.String System.Object.ToString()) (OperationKind.Invocation, Type: System.String) (Syntax: 'items[0].ToString()') + Instance Receiver: + IArrayElementReferenceOperation (OperationKind.ArrayElementReference, Type: System.Object) (Syntax: 'items[0]') + Array reference: + IFieldReferenceOperation: System.Object[] C.items (OperationKind.FieldReference, Type: System.Object[]) (Syntax: 'items') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'items') + Indices(1): + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0) (Syntax: '0') + Arguments(0) +Block[B4] - Exit + Predecessors: [B3] + Statements (0)"; + VerifyFlowGraphAndDiagnosticsForTest(source, expected, DiagnosticDescription.None, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void TestIOp_NullCheckedIndexedGetterExpression() + { + var source = @" +public class C +{ + private object[] items = {'h', ""hello""}; + public string this[object item!!] + { + /**/get => items[0].ToString();/**/ + set + { + items[0] = value; + } + } +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + var tree = compilation.SyntaxTrees.Single(); + var node1 = tree.GetRoot().DescendantNodes().OfType().ElementAt(1); + compilation.VerifyOperationTree(node1, expectedOperationTree: @" + IMethodBodyOperation (OperationKind.MethodBody, Type: null) (Syntax: 'set ... }') + BlockBody: + IBlockOperation (1 statements) (OperationKind.Block, Type: null) (Syntax: '{ ... }') + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'items[0] = value;') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Object) (Syntax: 'items[0] = value') + Left: + IArrayElementReferenceOperation (OperationKind.ArrayElementReference, Type: System.Object) (Syntax: 'items[0]') + Array reference: + IFieldReferenceOperation: System.Object[] C.items (OperationKind.FieldReference, Type: System.Object[]) (Syntax: 'items') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'items') + Indices(1): + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0) (Syntax: '0') + Right: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'value') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + Operand: + IParameterReferenceOperation: value (OperationKind.ParameterReference, Type: System.String) (Syntax: 'value') + ExpressionBody: + null"); + var expected = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] +Block[B1] - Block + Predecessors: [B0] + Statements (0) + Jump if False (Regular) to Block[B3] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsImplicit) (Syntax: 'get => item ... ToString();') + Left: + IParameterReferenceOperation: item (OperationKind.ParameterReference, Type: System.Object, IsImplicit) (Syntax: 'get => item ... ToString();') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Object, Constant: null, IsImplicit) (Syntax: 'get => item ... ToString();') + Next (Regular) Block[B2] +Block[B2] - Block + Predecessors: [B1] + Statements (0) + Next (Throw) Block[null] + IObjectCreationOperation (Constructor: System.ArgumentNullException..ctor(System.String paramName)) (OperationKind.ObjectCreation, Type: System.ArgumentNullException, IsImplicit) (Syntax: 'get => item ... ToString();') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: paramName) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'get => item ... ToString();') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""item"", IsImplicit) (Syntax: 'get => item ... ToString();') + 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 +Block[B3] - Block + Predecessors: [B1] + Statements (0) + Next (Return) Block[B4] + IInvocationOperation (virtual System.String System.Object.ToString()) (OperationKind.Invocation, Type: System.String) (Syntax: 'items[0].ToString()') + Instance Receiver: + IArrayElementReferenceOperation (OperationKind.ArrayElementReference, Type: System.Object) (Syntax: 'items[0]') + Array reference: + IFieldReferenceOperation: System.Object[] C.items (OperationKind.FieldReference, Type: System.Object[]) (Syntax: 'items') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'items') + Indices(1): + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0) (Syntax: '0') + Arguments(0) +Block[B4] - Exit + Predecessors: [B3] + Statements (0)"; + + VerifyFlowGraphAndDiagnosticsForTest(source, expected, DiagnosticDescription.None, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void TestIOp_NullCheckedIndexedSetter() + { + var source = @" +public class C +{ + public string this[object item!!] { /**/set { }/**/ } +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + + compilation.VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var node1 = tree.GetRoot().DescendantNodes().OfType().Single(); + compilation.VerifyOperationTree(node1, expectedOperationTree: @" + IMethodBodyOperation (OperationKind.MethodBody, Type: null) (Syntax: 'set { }') + BlockBody: + IBlockOperation (0 statements) (OperationKind.Block, Type: null) (Syntax: '{ }') + ExpressionBody: + null"); + var expected = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] +Block[B1] - Block + Predecessors: [B0] + Statements (0) + Jump if False (Regular) to Block[B3] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsImplicit) (Syntax: 'set { }') + Left: + IParameterReferenceOperation: item (OperationKind.ParameterReference, Type: System.Object, IsImplicit) (Syntax: 'set { }') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Object, Constant: null, IsImplicit) (Syntax: 'set { }') + Next (Regular) Block[B2] +Block[B2] - Block + Predecessors: [B1] + Statements (0) + Next (Throw) Block[null] + IObjectCreationOperation (Constructor: System.ArgumentNullException..ctor(System.String paramName)) (OperationKind.ObjectCreation, Type: System.ArgumentNullException, IsImplicit) (Syntax: 'set { }') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: paramName) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'set { }') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""item"", IsImplicit) (Syntax: 'set { }') + 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 +Block[B3] - Exit + Predecessors: [B1] + Statements (0)"; + VerifyFlowGraphAndDiagnosticsForTest(source, expected, DiagnosticDescription.None, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void TestIOp_NullCheckedLambda() + { + var source = @" +using System; +class C +{ + public void M() + { + Func func1 = x!! => x; + } +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + + compilation.VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var node1 = tree.GetRoot().DescendantNodes().OfType().Single(); + compilation.VerifyOperationTree(node1, expectedOperationTree: @" +IMethodBodyOperation (OperationKind.MethodBody, Type: null) (Syntax: 'public void ... }') + BlockBody: + IBlockOperation (1 statements, 1 locals) (OperationKind.Block, Type: null) (Syntax: '{ ... }') + Locals: Local_1: System.Func func1 + IVariableDeclarationGroupOperation (1 declarations) (OperationKind.VariableDeclarationGroup, Type: null) (Syntax: 'Func x;') + IVariableDeclarationOperation (1 declarators) (OperationKind.VariableDeclaration, Type: null) (Syntax: 'Func x') + Declarators: + IVariableDeclaratorOperation (Symbol: System.Func func1) (OperationKind.VariableDeclarator, Type: null) (Syntax: 'func1 = x!! => x') + Initializer: + IVariableInitializerOperation (OperationKind.VariableInitializer, Type: null) (Syntax: '= x!! => x') + IDelegateCreationOperation (OperationKind.DelegateCreation, Type: System.Func, IsImplicit) (Syntax: 'x!! => x') + Target: + IAnonymousFunctionOperation (Symbol: lambda expression) (OperationKind.AnonymousFunction, Type: null) (Syntax: 'x!! => x') + IBlockOperation (1 statements) (OperationKind.Block, Type: null, IsImplicit) (Syntax: 'x') + IReturnOperation (OperationKind.Return, Type: null, IsImplicit) (Syntax: 'x') + ReturnedValue: + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.String) (Syntax: 'x') + Initializer: + null + ExpressionBody: + null"); + + VerifyFlowGraph(compilation, node1, expectedFlowGraph: @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + Locals: [System.Func func1] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Func, IsImplicit) (Syntax: 'func1 = x!! => x') + Left: + ILocalReferenceOperation: func1 (IsDeclaration: True) (OperationKind.LocalReference, Type: System.Func, IsImplicit) (Syntax: 'func1 = x!! => x') + Right: + IDelegateCreationOperation (OperationKind.DelegateCreation, Type: System.Func, IsImplicit) (Syntax: 'x!! => x') + Target: + IFlowAnonymousFunctionOperation (Symbol: lambda expression) (OperationKind.FlowAnonymousFunction, Type: null) (Syntax: 'x!! => x') + { + Block[B0#A0] - Entry + Statements (0) + Next (Regular) Block[B1#A0] + Block[B1#A0] - Block + Predecessors: [B0#A0] + Statements (0) + Jump if False (Regular) to Block[B3#A0] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsImplicit) (Syntax: 'x!! => x') + Left: + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.String, IsImplicit) (Syntax: 'x!! => x') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: null, IsImplicit) (Syntax: 'x!! => x') + Next (Regular) Block[B2#A0] + Block[B2#A0] - Block + Predecessors: [B1#A0] + Statements (0) + Next (Throw) Block[null] + IObjectCreationOperation (Constructor: System.ArgumentNullException..ctor(System.String paramName)) (OperationKind.ObjectCreation, Type: System.ArgumentNullException, IsImplicit) (Syntax: 'x!! => x') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: paramName) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'x!! => x') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""x"", IsImplicit) (Syntax: 'x!! => x') + 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 + Block[B3#A0] - Block + Predecessors: [B1#A0] + Statements (0) + Next (Return) Block[B4#A0] + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.String) (Syntax: 'x') + Block[B4#A0] - Exit + Predecessors: [B3#A0] + Statements (0) + } + Next (Regular) Block[B2] + Leaving: {R1} +} +Block[B2] - Exit + Predecessors: [B1] + Statements (0)"); + } + + [Fact] + public void TestIOp_NullCheckedInLambdaWithManyParameters() + { + var source = @" +using System; +class C +{ + public void M() + { + Func func1 = (x!!, y) => x; + } +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + + compilation.VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var node1 = tree.GetRoot().DescendantNodes().OfType().Single(); + compilation.VerifyOperationTree(node1, expectedOperationTree: @" + IMethodBodyOperation (OperationKind.MethodBody, Type: null) (Syntax: 'public void ... }') + BlockBody: + IBlockOperation (1 statements, 1 locals) (OperationKind.Block, Type: null) (Syntax: '{ ... }') + Locals: Local_1: System.Func func1 + IVariableDeclarationGroupOperation (1 declarations) (OperationKind.VariableDeclarationGroup, Type: null) (Syntax: 'Func x;') + IVariableDeclarationOperation (1 declarators) (OperationKind.VariableDeclaration, Type: null) (Syntax: 'Func x') + Declarators: + IVariableDeclaratorOperation (Symbol: System.Func func1) (OperationKind.VariableDeclarator, Type: null) (Syntax: 'func1 = (x!!, y) => x') + Initializer: + IVariableInitializerOperation (OperationKind.VariableInitializer, Type: null) (Syntax: '= (x!!, y) => x') + IDelegateCreationOperation (OperationKind.DelegateCreation, Type: System.Func, IsImplicit) (Syntax: '(x!!, y) => x') + Target: + IAnonymousFunctionOperation (Symbol: lambda expression) (OperationKind.AnonymousFunction, Type: null) (Syntax: '(x!!, y) => x') + IBlockOperation (1 statements) (OperationKind.Block, Type: null, IsImplicit) (Syntax: 'x') + IReturnOperation (OperationKind.Return, Type: null, IsImplicit) (Syntax: 'x') + ReturnedValue: + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.String) (Syntax: 'x') + Initializer: + null + ExpressionBody: + null"); + + VerifyFlowGraph(compilation, node1, expectedFlowGraph: @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + Locals: [System.Func func1] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Func, IsImplicit) (Syntax: 'func1 = (x!!, y) => x') + Left: + ILocalReferenceOperation: func1 (IsDeclaration: True) (OperationKind.LocalReference, Type: System.Func, IsImplicit) (Syntax: 'func1 = (x!!, y) => x') + Right: + IDelegateCreationOperation (OperationKind.DelegateCreation, Type: System.Func, IsImplicit) (Syntax: '(x!!, y) => x') + Target: + IFlowAnonymousFunctionOperation (Symbol: lambda expression) (OperationKind.FlowAnonymousFunction, Type: null) (Syntax: '(x!!, y) => x') + { + Block[B0#A0] - Entry + Statements (0) + Next (Regular) Block[B1#A0] + Block[B1#A0] - Block + Predecessors: [B0#A0] + Statements (0) + Jump if False (Regular) to Block[B3#A0] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsImplicit) (Syntax: '(x!!, y) => x') + Left: + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.String, IsImplicit) (Syntax: '(x!!, y) => x') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: null, IsImplicit) (Syntax: '(x!!, y) => x') + Next (Regular) Block[B2#A0] + Block[B2#A0] - Block + Predecessors: [B1#A0] + Statements (0) + Next (Throw) Block[null] + IObjectCreationOperation (Constructor: System.ArgumentNullException..ctor(System.String paramName)) (OperationKind.ObjectCreation, Type: System.ArgumentNullException, IsImplicit) (Syntax: '(x!!, y) => x') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: paramName) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '(x!!, y) => x') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""x"", IsImplicit) (Syntax: '(x!!, y) => x') + 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 + Block[B3#A0] - Block + Predecessors: [B1#A0] + Statements (0) + Next (Return) Block[B4#A0] + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.String) (Syntax: 'x') + Block[B4#A0] - Exit + Predecessors: [B3#A0] + Statements (0) + } + Next (Regular) Block[B2] + Leaving: {R1} +} +Block[B2] - Exit + Predecessors: [B1] + Statements (0)"); + } + + [Fact] + public void TestIOp_NullCheckedUnnamedVariableInLambda() + { + var source = @" +using System; +class C +{ + public void M() + { + Func func1 = _!! => null; + } +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + + compilation.VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var node1 = tree.GetRoot().DescendantNodes().OfType().Single(); + compilation.VerifyOperationTree(node1, expectedOperationTree: @" + IMethodBodyOperation (OperationKind.MethodBody, Type: null) (Syntax: 'public void ... }') + BlockBody: + IBlockOperation (1 statements, 1 locals) (OperationKind.Block, Type: null) (Syntax: '{ ... }') + Locals: Local_1: System.Func func1 + IVariableDeclarationGroupOperation (1 declarations) (OperationKind.VariableDeclarationGroup, Type: null) (Syntax: 'Func null;') + IVariableDeclarationOperation (1 declarators) (OperationKind.VariableDeclaration, Type: null) (Syntax: 'Func null') + Declarators: + IVariableDeclaratorOperation (Symbol: System.Func func1) (OperationKind.VariableDeclarator, Type: null) (Syntax: 'func1 = _!! => null') + Initializer: + IVariableInitializerOperation (OperationKind.VariableInitializer, Type: null) (Syntax: '= _!! => null') + IDelegateCreationOperation (OperationKind.DelegateCreation, Type: System.Func, IsImplicit) (Syntax: '_!! => null') + Target: + IAnonymousFunctionOperation (Symbol: lambda expression) (OperationKind.AnonymousFunction, Type: null) (Syntax: '_!! => null') + IBlockOperation (1 statements) (OperationKind.Block, Type: null, IsImplicit) (Syntax: 'null') + IReturnOperation (OperationKind.Return, Type: null, IsImplicit) (Syntax: 'null') + ReturnedValue: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.String, Constant: null, IsImplicit) (Syntax: 'null') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + Operand: + ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') + Initializer: + null + ExpressionBody: + null"); + + VerifyFlowGraph(compilation, node1, expectedFlowGraph: @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + Locals: [System.Func func1] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Func, IsImplicit) (Syntax: 'func1 = _!! => null') + Left: + ILocalReferenceOperation: func1 (IsDeclaration: True) (OperationKind.LocalReference, Type: System.Func, IsImplicit) (Syntax: 'func1 = _!! => null') + Right: + IDelegateCreationOperation (OperationKind.DelegateCreation, Type: System.Func, IsImplicit) (Syntax: '_!! => null') + Target: + IFlowAnonymousFunctionOperation (Symbol: lambda expression) (OperationKind.FlowAnonymousFunction, Type: null) (Syntax: '_!! => null') + { + Block[B0#A0] - Entry + Statements (0) + Next (Regular) Block[B1#A0] + Block[B1#A0] - Block + Predecessors: [B0#A0] + Statements (0) + Jump if False (Regular) to Block[B3#A0] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsImplicit) (Syntax: '_!! => null') + Left: + IParameterReferenceOperation: _ (OperationKind.ParameterReference, Type: System.String, IsImplicit) (Syntax: '_!! => null') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: null, IsImplicit) (Syntax: '_!! => null') + Next (Regular) Block[B2#A0] + Block[B2#A0] - Block + Predecessors: [B1#A0] + Statements (0) + Next (Throw) Block[null] + IObjectCreationOperation (Constructor: System.ArgumentNullException..ctor(System.String paramName)) (OperationKind.ObjectCreation, Type: System.ArgumentNullException, IsImplicit) (Syntax: '_!! => null') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: paramName) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '_!! => null') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""_"", IsImplicit) (Syntax: '_!! => null') + 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 + Block[B3#A0] - Block + Predecessors: [B1#A0] + Statements (0) + Next (Return) Block[B4#A0] + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.String, Constant: null, IsImplicit) (Syntax: 'null') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') + Block[B4#A0] - Exit + Predecessors: [B3#A0] + Statements (0) + } + Next (Regular) Block[B2] + Leaving: {R1} +} +Block[B2] - Exit + Predecessors: [B1] + Statements (0)"); + } + + [Fact] + public void TestIOp_NullCheckedTwoExpressionBodyLambdas() + { + var source = @" +using System; +class C +{ + public Func M(string s1!!) => s2!! => s2 + s1; +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + + compilation.VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var node1 = tree.GetRoot().DescendantNodes().OfType().Single(); + compilation.VerifyOperationTree(node1, expectedOperationTree: @" + IMethodBodyOperation (OperationKind.MethodBody, Type: null) (Syntax: 'public Func ... => s2 + s1;') + BlockBody: + null + ExpressionBody: + IBlockOperation (1 statements) (OperationKind.Block, Type: null) (Syntax: '=> s2!! => s2 + s1') + IReturnOperation (OperationKind.Return, Type: null, IsImplicit) (Syntax: 's2!! => s2 + s1') + ReturnedValue: + IDelegateCreationOperation (OperationKind.DelegateCreation, Type: System.Func, IsImplicit) (Syntax: 's2!! => s2 + s1') + Target: + IAnonymousFunctionOperation (Symbol: lambda expression) (OperationKind.AnonymousFunction, Type: null) (Syntax: 's2!! => s2 + s1') + IBlockOperation (1 statements) (OperationKind.Block, Type: null, IsImplicit) (Syntax: 's2 + s1') + IReturnOperation (OperationKind.Return, Type: null, IsImplicit) (Syntax: 's2 + s1') + ReturnedValue: + IBinaryOperation (BinaryOperatorKind.Add) (OperationKind.Binary, Type: System.String) (Syntax: 's2 + s1') + Left: + IParameterReferenceOperation: s2 (OperationKind.ParameterReference, Type: System.String) (Syntax: 's2') + Right: + IParameterReferenceOperation: s1 (OperationKind.ParameterReference, Type: System.String) (Syntax: 's1')"); + + VerifyFlowGraph(compilation, node1, expectedFlowGraph: @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] +Block[B1] - Block + Predecessors: [B0] + Statements (0) + Jump if False (Regular) to Block[B3] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsImplicit) (Syntax: 'public Func ... => s2 + s1;') + Left: + IParameterReferenceOperation: s1 (OperationKind.ParameterReference, Type: System.String, IsImplicit) (Syntax: 'public Func ... => s2 + s1;') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: null, IsImplicit) (Syntax: 'public Func ... => s2 + s1;') + Next (Regular) Block[B2] +Block[B2] - Block + Predecessors: [B1] + Statements (0) + Next (Throw) Block[null] + IObjectCreationOperation (Constructor: System.ArgumentNullException..ctor(System.String paramName)) (OperationKind.ObjectCreation, Type: System.ArgumentNullException, IsImplicit) (Syntax: 'public Func ... => s2 + s1;') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: paramName) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'public Func ... => s2 + s1;') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""s1"", IsImplicit) (Syntax: 'public Func ... => s2 + s1;') + 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 +Block[B3] - Block + Predecessors: [B1] + Statements (0) + Next (Return) Block[B4] + IDelegateCreationOperation (OperationKind.DelegateCreation, Type: System.Func, IsImplicit) (Syntax: 's2!! => s2 + s1') + Target: + IFlowAnonymousFunctionOperation (Symbol: lambda expression) (OperationKind.FlowAnonymousFunction, Type: null) (Syntax: 's2!! => s2 + s1') + { + Block[B0#A0] - Entry + Statements (0) + Next (Regular) Block[B1#A0] + Block[B1#A0] - Block + Predecessors: [B0#A0] + Statements (0) + Jump if False (Regular) to Block[B3#A0] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsImplicit) (Syntax: 's2!! => s2 + s1') + Left: + IParameterReferenceOperation: s2 (OperationKind.ParameterReference, Type: System.String, IsImplicit) (Syntax: 's2!! => s2 + s1') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: null, IsImplicit) (Syntax: 's2!! => s2 + s1') + Next (Regular) Block[B2#A0] + Block[B2#A0] - Block + Predecessors: [B1#A0] + Statements (0) + Next (Throw) Block[null] + IObjectCreationOperation (Constructor: System.ArgumentNullException..ctor(System.String paramName)) (OperationKind.ObjectCreation, Type: System.ArgumentNullException, IsImplicit) (Syntax: 's2!! => s2 + s1') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: paramName) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 's2!! => s2 + s1') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""s2"", IsImplicit) (Syntax: 's2!! => s2 + s1') + 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 + Block[B3#A0] - Block + Predecessors: [B1#A0] + Statements (0) + Next (Return) Block[B4#A0] + IBinaryOperation (BinaryOperatorKind.Add) (OperationKind.Binary, Type: System.String) (Syntax: 's2 + s1') + Left: + IParameterReferenceOperation: s2 (OperationKind.ParameterReference, Type: System.String) (Syntax: 's2') + Right: + IParameterReferenceOperation: s1 (OperationKind.ParameterReference, Type: System.String) (Syntax: 's1') + Block[B4#A0] - Exit + Predecessors: [B3#A0] + Statements (0) + } +Block[B4] - Exit + Predecessors: [B3] + Statements (0)"); + } + + [Fact] + public void TestIOp_NullCheckedLambdaInField() + { + var source = @" +using System; +class C +{ + Func func1 = x!! => x; + public C() + { + } +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + + compilation.VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var node1 = tree.GetRoot().DescendantNodes().OfType().Single(); + var node2 = tree.GetRoot().DescendantNodes().OfType().Single(); + compilation.VerifyOperationTree(node1, expectedOperationTree: @" + IAnonymousFunctionOperation (Symbol: lambda expression) (OperationKind.AnonymousFunction, Type: null) (Syntax: 'x!! => x') + IBlockOperation (1 statements) (OperationKind.Block, Type: null, IsImplicit) (Syntax: 'x') + IReturnOperation (OperationKind.Return, Type: null, IsImplicit) (Syntax: 'x') + ReturnedValue: + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.String) (Syntax: 'x')"); + + VerifyFlowGraph(compilation, node2, expectedFlowGraph: @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] +Block[B1] - Block + Predecessors: [B0] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Func, IsImplicit) (Syntax: '= x!! => x') + Left: + IFieldReferenceOperation: System.Func C.func1 (OperationKind.FieldReference, Type: System.Func, IsImplicit) (Syntax: '= x!! => x') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: '= x!! => x') + Right: + IDelegateCreationOperation (OperationKind.DelegateCreation, Type: System.Func, IsImplicit) (Syntax: 'x!! => x') + Target: + IFlowAnonymousFunctionOperation (Symbol: lambda expression) (OperationKind.FlowAnonymousFunction, Type: null) (Syntax: 'x!! => x') + { + Block[B0#A0] - Entry + Statements (0) + Next (Regular) Block[B1#A0] + Block[B1#A0] - Block + Predecessors: [B0#A0] + Statements (0) + Jump if False (Regular) to Block[B3#A0] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsImplicit) (Syntax: 'x!! => x') + Left: + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.String, IsImplicit) (Syntax: 'x!! => x') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: null, IsImplicit) (Syntax: 'x!! => x') + Next (Regular) Block[B2#A0] + Block[B2#A0] - Block + Predecessors: [B1#A0] + Statements (0) + Next (Throw) Block[null] + IObjectCreationOperation (Constructor: System.ArgumentNullException..ctor(System.String paramName)) (OperationKind.ObjectCreation, Type: System.ArgumentNullException, IsImplicit) (Syntax: 'x!! => x') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: paramName) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'x!! => x') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""x"", IsImplicit) (Syntax: 'x!! => x') + 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 + Block[B3#A0] - Block + Predecessors: [B1#A0] + Statements (0) + Next (Return) Block[B4#A0] + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.String) (Syntax: 'x') + Block[B4#A0] - Exit + Predecessors: [B3#A0] + Statements (0) + } + Next (Regular) Block[B2] +Block[B2] - Exit + Predecessors: [B1] + Statements (0) +"); + } + + [Fact] + public void TestIOp_NullCheckedLocalFunction() + { + var source = @" +class C +{ + public void M() + { + InnerM(""hello world""); + void InnerM(string x!!) { } + } +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + + compilation.VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var node1 = tree.GetRoot().DescendantNodes().OfType().Single(); + var node2 = tree.GetRoot().DescendantNodes().OfType().Single(); + compilation.VerifyOperationTree(node1, expectedOperationTree: @" + ILocalFunctionOperation (Symbol: void InnerM(System.String x)) (OperationKind.LocalFunction, Type: null) (Syntax: 'void InnerM ... ng x!!) { }') + IBlockOperation (1 statements) (OperationKind.Block, Type: null) (Syntax: '{ }') + IReturnOperation (OperationKind.Return, Type: null, IsImplicit) (Syntax: '{ }') + ReturnedValue: + null"); + VerifyFlowGraph(compilation, node2, @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + Methods: [void InnerM(System.String x)] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'InnerM(""hello world"");') + Expression: + IInvocationOperation (void InnerM(System.String x)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'InnerM(""hello world"")') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: x) (OperationKind.Argument, Type: null) (Syntax: '""hello world""') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""hello world"") (Syntax: '""hello world""') + 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} + + { void InnerM(System.String x) + + Block[B0#0R1] - Entry + Statements (0) + Next (Regular) Block[B1#0R1] + Block[B1#0R1] - Block + Predecessors: [B0#0R1] + Statements (0) + Jump if False (Regular) to Block[B3#0R1] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsImplicit) (Syntax: 'void InnerM ... ng x!!) { }') + Left: + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.String, IsImplicit) (Syntax: 'void InnerM ... ng x!!) { }') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: null, IsImplicit) (Syntax: 'void InnerM ... ng x!!) { }') + Next (Regular) Block[B2#0R1] + Block[B2#0R1] - Block + Predecessors: [B1#0R1] + Statements (0) + Next (Throw) Block[null] + IObjectCreationOperation (Constructor: System.ArgumentNullException..ctor(System.String paramName)) (OperationKind.ObjectCreation, Type: System.ArgumentNullException, IsImplicit) (Syntax: 'void InnerM ... ng x!!) { }') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: paramName) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'void InnerM ... ng x!!) { }') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""x"", IsImplicit) (Syntax: 'void InnerM ... ng x!!) { }') + 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 + Block[B3#0R1] - Exit + Predecessors: [B1#0R1] + Statements (0) + } +} +Block[B2] - Exit + Predecessors: [B1] + Statements (0)"); + } + + [Fact] + public void TestIOp_NullCheckedManyParamsInLocalFunction() + { + var source = @" +class C +{ + public void M() + { + InnerM(""hello"", ""world""); + void InnerM(string x!!, string y!!) { } + } +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + + compilation.VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var node1 = tree.GetRoot().DescendantNodes().OfType().Single(); + var node2 = tree.GetRoot().DescendantNodes().OfType().Single(); + compilation.VerifyOperationTree(node1, expectedOperationTree: @" + ILocalFunctionOperation (Symbol: void InnerM(System.String x, System.String y)) (OperationKind.LocalFunction, Type: null) (Syntax: 'void InnerM ... ng y!!) { }') + IBlockOperation (1 statements) (OperationKind.Block, Type: null) (Syntax: '{ }') + IReturnOperation (OperationKind.Return, Type: null, IsImplicit) (Syntax: '{ }') + ReturnedValue: + null"); + + VerifyFlowGraph(compilation, node2, @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + Methods: [void InnerM(System.String x, System.String y)] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'InnerM(""hel ... ""world"");') + Expression: + IInvocationOperation (void InnerM(System.String x, System.String y)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'InnerM(""hel ... , ""world"")') + Instance Receiver: + null + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: x) (OperationKind.Argument, Type: null) (Syntax: '""hello""') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""hello"") (Syntax: '""hello""') + 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: y) (OperationKind.Argument, Type: null) (Syntax: '""world""') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""world"") (Syntax: '""world""') + 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} + + { void InnerM(System.String x, System.String y) + + Block[B0#0R1] - Entry + Statements (0) + Next (Regular) Block[B1#0R1] + Block[B1#0R1] - Block + Predecessors: [B0#0R1] + Statements (0) + Jump if False (Regular) to Block[B3#0R1] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsImplicit) (Syntax: 'void InnerM ... ng y!!) { }') + Left: + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.String, IsImplicit) (Syntax: 'void InnerM ... ng y!!) { }') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: null, IsImplicit) (Syntax: 'void InnerM ... ng y!!) { }') + Next (Regular) Block[B2#0R1] + Block[B2#0R1] - Block + Predecessors: [B1#0R1] + Statements (0) + Next (Throw) Block[null] + IObjectCreationOperation (Constructor: System.ArgumentNullException..ctor(System.String paramName)) (OperationKind.ObjectCreation, Type: System.ArgumentNullException, IsImplicit) (Syntax: 'void InnerM ... ng y!!) { }') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: paramName) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'void InnerM ... ng y!!) { }') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""x"", IsImplicit) (Syntax: 'void InnerM ... ng y!!) { }') + 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 + Block[B3#0R1] - Block + Predecessors: [B1#0R1] + Statements (0) + Jump if False (Regular) to Block[B5#0R1] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsImplicit) (Syntax: 'void InnerM ... ng y!!) { }') + Left: + IParameterReferenceOperation: y (OperationKind.ParameterReference, Type: System.String, IsImplicit) (Syntax: 'void InnerM ... ng y!!) { }') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: null, IsImplicit) (Syntax: 'void InnerM ... ng y!!) { }') + Next (Regular) Block[B4#0R1] + Block[B4#0R1] - Block + Predecessors: [B3#0R1] + Statements (0) + Next (Throw) Block[null] + IObjectCreationOperation (Constructor: System.ArgumentNullException..ctor(System.String paramName)) (OperationKind.ObjectCreation, Type: System.ArgumentNullException, IsImplicit) (Syntax: 'void InnerM ... ng y!!) { }') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: paramName) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'void InnerM ... ng y!!) { }') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""y"", IsImplicit) (Syntax: 'void InnerM ... ng y!!) { }') + 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 + Block[B5#0R1] - Exit + Predecessors: [B3#0R1] + Statements (0) + } +} +Block[B2] - Exit + Predecessors: [B1] + Statements (0)"); + } + + [Fact] + public void TestIOp_OuterNullCheckedShadowedParameter() + { + var source = @" +class C +{ + public void M(string x!!) + { + InnerM(""hello""); + void InnerM(string x) { } + } +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + + compilation.VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var node1 = tree.GetRoot().DescendantNodes().OfType().Single(); + compilation.VerifyOperationTree(node1, expectedOperationTree: @" + IMethodBodyOperation (OperationKind.MethodBody, Type: null) (Syntax: 'public void ... }') + BlockBody: + IBlockOperation (2 statements) (OperationKind.Block, Type: null) (Syntax: '{ ... }') + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'InnerM(""hello"");') + Expression: + IInvocationOperation (void InnerM(System.String x)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'InnerM(""hello"")') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: x) (OperationKind.Argument, Type: null) (Syntax: '""hello""') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""hello"") (Syntax: '""hello""') + 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) + ILocalFunctionOperation (Symbol: void InnerM(System.String x)) (OperationKind.LocalFunction, Type: null) (Syntax: 'void InnerM ... ring x) { }') + IBlockOperation (1 statements) (OperationKind.Block, Type: null) (Syntax: '{ }') + IReturnOperation (OperationKind.Return, Type: null, IsImplicit) (Syntax: '{ }') + ReturnedValue: + null + ExpressionBody: + null"); + + VerifyFlowGraph(compilation, node1, expectedFlowGraph: @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] +Block[B1] - Block + Predecessors: [B0] + Statements (0) + Jump if False (Regular) to Block[B3] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsImplicit) (Syntax: 'public void ... }') + Left: + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.String, IsImplicit) (Syntax: 'public void ... }') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: null, IsImplicit) (Syntax: 'public void ... }') + Entering: {R1} + Next (Regular) Block[B2] +Block[B2] - Block + Predecessors: [B1] + Statements (0) + Next (Throw) Block[null] + IObjectCreationOperation (Constructor: System.ArgumentNullException..ctor(System.String paramName)) (OperationKind.ObjectCreation, Type: System.ArgumentNullException, IsImplicit) (Syntax: 'public void ... }') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: paramName) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'public void ... }') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""x"", IsImplicit) (Syntax: 'public void ... }') + 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 +.locals {R1} +{ + Methods: [void InnerM(System.String x)] + Block[B3] - Block + Predecessors: [B1] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'InnerM(""hello"");') + Expression: + IInvocationOperation (void InnerM(System.String x)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'InnerM(""hello"")') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: x) (OperationKind.Argument, Type: null) (Syntax: '""hello""') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""hello"") (Syntax: '""hello""') + 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[B4] + Leaving: {R1} + + { void InnerM(System.String x) + + Block[B0#0R1] - Entry + Statements (0) + Next (Regular) Block[B1#0R1] + Block[B1#0R1] - Exit + Predecessors: [B0#0R1] + Statements (0) + } +} +Block[B4] - Exit + Predecessors: [B3] + Statements (0)"); + } + + [Fact] + public void TestIOp_InnerNullCheckedShadowedParameter() + { + var source = @" +class C +{ + public void M(string x) + { + InnerM(""hello""); + void InnerM(string x!!) { } + } +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + + compilation.VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var node1 = tree.GetRoot().DescendantNodes().OfType().Single(); + var node2 = tree.GetRoot().DescendantNodes().OfType().Single(); + compilation.VerifyOperationTree(node1, expectedOperationTree: @" + ILocalFunctionOperation (Symbol: void InnerM(System.String x)) (OperationKind.LocalFunction, Type: null) (Syntax: 'void InnerM ... ng x!!) { }') + IBlockOperation (1 statements) (OperationKind.Block, Type: null) (Syntax: '{ }') + IReturnOperation (OperationKind.Return, Type: null, IsImplicit) (Syntax: '{ }') + ReturnedValue: + null"); + VerifyFlowGraph(compilation, node2, @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + Methods: [void InnerM(System.String x)] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'InnerM(""hello"");') + Expression: + IInvocationOperation (void InnerM(System.String x)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'InnerM(""hello"")') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: x) (OperationKind.Argument, Type: null) (Syntax: '""hello""') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""hello"") (Syntax: '""hello""') + 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} + + { void InnerM(System.String x) + + Block[B0#0R1] - Entry + Statements (0) + Next (Regular) Block[B1#0R1] + Block[B1#0R1] - Block + Predecessors: [B0#0R1] + Statements (0) + Jump if False (Regular) to Block[B3#0R1] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsImplicit) (Syntax: 'void InnerM ... ng x!!) { }') + Left: + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.String, IsImplicit) (Syntax: 'void InnerM ... ng x!!) { }') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: null, IsImplicit) (Syntax: 'void InnerM ... ng x!!) { }') + Next (Regular) Block[B2#0R1] + Block[B2#0R1] - Block + Predecessors: [B1#0R1] + Statements (0) + Next (Throw) Block[null] + IObjectCreationOperation (Constructor: System.ArgumentNullException..ctor(System.String paramName)) (OperationKind.ObjectCreation, Type: System.ArgumentNullException, IsImplicit) (Syntax: 'void InnerM ... ng x!!) { }') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: paramName) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'void InnerM ... ng x!!) { }') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""x"", IsImplicit) (Syntax: 'void InnerM ... ng x!!) { }') + 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 + Block[B3#0R1] - Exit + Predecessors: [B1#0R1] + Statements (0) + } +} +Block[B2] - Exit + Predecessors: [B1] + Statements (0)"); + } + + [Fact] + public void TestIOp_NullCheckedConstructor() + { + var source = @" +class C +{ + public C(string x!!) { } +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + + compilation.VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var node1 = tree.GetRoot().DescendantNodes().OfType().Single(); + compilation.VerifyOperationTree(node1, expectedOperationTree: @" + IConstructorBodyOperation (OperationKind.ConstructorBody, Type: null) (Syntax: 'public C(string x!!) { }') + Initializer: + null + BlockBody: + IBlockOperation (0 statements) (OperationKind.Block, Type: null) (Syntax: '{ }') + ExpressionBody: + null"); + + VerifyFlowGraph(compilation, node1, expectedFlowGraph: @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] +Block[B1] - Block + Predecessors: [B0] + Statements (0) + Jump if False (Regular) to Block[B3] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsImplicit) (Syntax: 'public C(string x!!) { }') + Left: + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.String, IsImplicit) (Syntax: 'public C(string x!!) { }') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: null, IsImplicit) (Syntax: 'public C(string x!!) { }') + Next (Regular) Block[B2] +Block[B2] - Block + Predecessors: [B1] + Statements (0) + Next (Throw) Block[null] + IObjectCreationOperation (Constructor: System.ArgumentNullException..ctor(System.String paramName)) (OperationKind.ObjectCreation, Type: System.ArgumentNullException, IsImplicit) (Syntax: 'public C(string x!!) { }') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: paramName) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'public C(string x!!) { }') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""x"", IsImplicit) (Syntax: 'public C(string x!!) { }') + 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 +Block[B3] - Exit + Predecessors: [B1] + Statements (0)"); + } + + [Fact] + public void TestIOp_NullCheckedConstructorWithThisChain() + { + var source = @" +class C +{ + public C() { } + public C(string x!!) : this() { } +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + + compilation.VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var node1 = tree.GetRoot().DescendantNodes().OfType().ElementAt(1); + compilation.VerifyOperationTree(node1, expectedOperationTree: @" + IConstructorBodyOperation (OperationKind.ConstructorBody, Type: null) (Syntax: 'public C(st ... this() { }') + Initializer: + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null, IsImplicit) (Syntax: ': this()') + Expression: + IInvocationOperation ( C..ctor()) (OperationKind.Invocation, Type: System.Void) (Syntax: ': this()') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: ': this()') + Arguments(0) + BlockBody: + IBlockOperation (0 statements) (OperationKind.Block, Type: null) (Syntax: '{ }') + ExpressionBody: + null"); + + VerifyFlowGraph(compilation, node1, expectedFlowGraph: @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] +Block[B1] - Block + Predecessors: [B0] + Statements (0) + Jump if False (Regular) to Block[B3] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsImplicit) (Syntax: 'public C(st ... this() { }') + Left: + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.String, IsImplicit) (Syntax: 'public C(st ... this() { }') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: null, IsImplicit) (Syntax: 'public C(st ... this() { }') + Next (Regular) Block[B2] +Block[B2] - Block + Predecessors: [B1] + Statements (0) + Next (Throw) Block[null] + IObjectCreationOperation (Constructor: System.ArgumentNullException..ctor(System.String paramName)) (OperationKind.ObjectCreation, Type: System.ArgumentNullException, IsImplicit) (Syntax: 'public C(st ... this() { }') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: paramName) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'public C(st ... this() { }') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""x"", IsImplicit) (Syntax: 'public C(st ... this() { }') + 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 +Block[B3] - Block + Predecessors: [B1] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null, IsImplicit) (Syntax: ': this()') + Expression: + IInvocationOperation ( C..ctor()) (OperationKind.Invocation, Type: System.Void) (Syntax: ': this()') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: ': this()') + Arguments(0) + Next (Regular) Block[B4] +Block[B4] - Exit + Predecessors: [B3] + Statements (0)"); + } + + [Fact] + public void TestIOp_NullCheckedConstructorWithBaseChain() + { + var source = @" +class B +{ + public B(string y) { } +} +class C : B +{ + public C(string x!!) : base(x) { } +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + + compilation.VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var node1 = tree.GetRoot().DescendantNodes().OfType().ElementAt(1); + compilation.VerifyOperationTree(node1, expectedOperationTree: @" + IConstructorBodyOperation (OperationKind.ConstructorBody, Type: null) (Syntax: 'public C(st ... base(x) { }') + Initializer: + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null, IsImplicit) (Syntax: ': base(x)') + Expression: + IInvocationOperation ( B..ctor(System.String y)) (OperationKind.Invocation, Type: System.Void) (Syntax: ': base(x)') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: B, IsImplicit) (Syntax: ': base(x)') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: y) (OperationKind.Argument, Type: null) (Syntax: 'x') + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.String) (Syntax: 'x') + 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) + BlockBody: + IBlockOperation (0 statements) (OperationKind.Block, Type: null) (Syntax: '{ }') + ExpressionBody: + null"); + + VerifyFlowGraph(compilation, node1, expectedFlowGraph: @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] +Block[B1] - Block + Predecessors: [B0] + Statements (0) + Jump if False (Regular) to Block[B3] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsImplicit) (Syntax: 'public C(st ... base(x) { }') + Left: + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.String, IsImplicit) (Syntax: 'public C(st ... base(x) { }') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: null, IsImplicit) (Syntax: 'public C(st ... base(x) { }') + Next (Regular) Block[B2] +Block[B2] - Block + Predecessors: [B1] + Statements (0) + Next (Throw) Block[null] + IObjectCreationOperation (Constructor: System.ArgumentNullException..ctor(System.String paramName)) (OperationKind.ObjectCreation, Type: System.ArgumentNullException, IsImplicit) (Syntax: 'public C(st ... base(x) { }') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: paramName) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'public C(st ... base(x) { }') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""x"", IsImplicit) (Syntax: 'public C(st ... base(x) { }') + 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 +Block[B3] - Block + Predecessors: [B1] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null, IsImplicit) (Syntax: ': base(x)') + Expression: + IInvocationOperation ( B..ctor(System.String y)) (OperationKind.Invocation, Type: System.Void) (Syntax: ': base(x)') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: B, IsImplicit) (Syntax: ': base(x)') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: y) (OperationKind.Argument, Type: null) (Syntax: 'x') + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.String) (Syntax: 'x') + 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[B4] +Block[B4] - Exit + Predecessors: [B3] + Statements (0)"); + } + + [Fact] + public void TestIOp_NullCheckedConstructorWithFieldInitializers() + { + var source = @" +class C +{ + int y = 5; + public C(string x!!) { y++; } +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + + compilation.VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var node1 = tree.GetRoot().DescendantNodes().OfType().Single(); + compilation.VerifyOperationTree(node1, expectedOperationTree: @" + IConstructorBodyOperation (OperationKind.ConstructorBody, Type: null) (Syntax: 'public C(st ... !) { y++; }') + Initializer: + null + BlockBody: + IBlockOperation (1 statements) (OperationKind.Block, Type: null) (Syntax: '{ y++; }') + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'y++;') + Expression: + IIncrementOrDecrementOperation (Postfix) (OperationKind.Increment, Type: System.Int32) (Syntax: 'y++') + Target: + IFieldReferenceOperation: System.Int32 C.y (OperationKind.FieldReference, Type: System.Int32) (Syntax: 'y') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'y') + ExpressionBody: + null"); + + VerifyFlowGraph(compilation, node1, expectedFlowGraph: @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] +Block[B1] - Block + Predecessors: [B0] + Statements (0) + Jump if False (Regular) to Block[B3] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsImplicit) (Syntax: 'public C(st ... !) { y++; }') + Left: + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.String, IsImplicit) (Syntax: 'public C(st ... !) { y++; }') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: null, IsImplicit) (Syntax: 'public C(st ... !) { y++; }') + Next (Regular) Block[B2] +Block[B2] - Block + Predecessors: [B1] + Statements (0) + Next (Throw) Block[null] + IObjectCreationOperation (Constructor: System.ArgumentNullException..ctor(System.String paramName)) (OperationKind.ObjectCreation, Type: System.ArgumentNullException, IsImplicit) (Syntax: 'public C(st ... !) { y++; }') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: paramName) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'public C(st ... !) { y++; }') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""x"", IsImplicit) (Syntax: 'public C(st ... !) { y++; }') + 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 +Block[B3] - Block + Predecessors: [B1] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'y++;') + Expression: + IIncrementOrDecrementOperation (Postfix) (OperationKind.Increment, Type: System.Int32) (Syntax: 'y++') + Target: + IFieldReferenceOperation: System.Int32 C.y (OperationKind.FieldReference, Type: System.Int32) (Syntax: 'y') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'y') + Next (Regular) Block[B4] +Block[B4] - Exit + Predecessors: [B3] + Statements (0)"); + } + + [Fact] + public void TestIOp_NullCheckedExpressionBodyMethod() + { + var source = @" +class C +{ + object Local(object arg!!) => arg; +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + + compilation.VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var node1 = tree.GetRoot().DescendantNodes().OfType().Single(); + compilation.VerifyOperationTree(node1, expectedOperationTree: @" + IMethodBodyOperation (OperationKind.MethodBody, Type: null) (Syntax: 'object Loca ... !!) => arg;') + BlockBody: + null + ExpressionBody: + IBlockOperation (1 statements) (OperationKind.Block, Type: null) (Syntax: '=> arg') + IReturnOperation (OperationKind.Return, Type: null, IsImplicit) (Syntax: 'arg') + ReturnedValue: + IParameterReferenceOperation: arg (OperationKind.ParameterReference, Type: System.Object) (Syntax: 'arg')"); + + VerifyFlowGraph(compilation, node1, expectedFlowGraph: @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] +Block[B1] - Block + Predecessors: [B0] + Statements (0) + Jump if False (Regular) to Block[B3] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsImplicit) (Syntax: 'object Loca ... !!) => arg;') + Left: + IParameterReferenceOperation: arg (OperationKind.ParameterReference, Type: System.Object, IsImplicit) (Syntax: 'object Loca ... !!) => arg;') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Object, Constant: null, IsImplicit) (Syntax: 'object Loca ... !!) => arg;') + Next (Regular) Block[B2] +Block[B2] - Block + Predecessors: [B1] + Statements (0) + Next (Throw) Block[null] + IObjectCreationOperation (Constructor: System.ArgumentNullException..ctor(System.String paramName)) (OperationKind.ObjectCreation, Type: System.ArgumentNullException, IsImplicit) (Syntax: 'object Loca ... !!) => arg;') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: paramName) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'object Loca ... !!) => arg;') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""arg"", IsImplicit) (Syntax: 'object Loca ... !!) => arg;') + 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 +Block[B3] - Block + Predecessors: [B1] + Statements (0) + Next (Return) Block[B4] + IParameterReferenceOperation: arg (OperationKind.ParameterReference, Type: System.Object) (Syntax: 'arg') +Block[B4] - Exit + Predecessors: [B3] + Statements (0)"); + } + + [Fact] + public void TestIOp_NullCheckedIterator() + { + var source = @" +using System.Collections.Generic; +class C +{ + IEnumerable GetChars(string s!!) + { + foreach (var c in s) + { + yield return c; + } + } + public static void Main() + { + C c = new C(); + IEnumerable e = c.GetChars(""hello""); + } +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + + compilation.VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var node1 = tree.GetRoot().DescendantNodes().OfType().ElementAt(0); + compilation.VerifyOperationTree(node1, expectedOperationTree: @" + IMethodBodyOperation (OperationKind.MethodBody, Type: null) (Syntax: 'IEnumerable ... }') + BlockBody: + IBlockOperation (1 statements) (OperationKind.Block, Type: null) (Syntax: '{ ... }') + IForEachLoopOperation (LoopKind.ForEach, Continue Label Id: 0, Exit Label Id: 1) (OperationKind.Loop, Type: null) (Syntax: 'foreach (va ... }') + Locals: Local_1: System.Char c + LoopControlVariable: + IVariableDeclaratorOperation (Symbol: System.Char c) (OperationKind.VariableDeclarator, Type: null) (Syntax: 'var') + Initializer: + null + Collection: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.String, IsImplicit) (Syntax: 's') + Conversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IParameterReferenceOperation: s (OperationKind.ParameterReference, Type: System.String) (Syntax: 's') + Body: + IBlockOperation (1 statements) (OperationKind.Block, Type: null) (Syntax: '{ ... }') + IReturnOperation (OperationKind.YieldReturn, Type: null) (Syntax: 'yield return c;') + ReturnedValue: + ILocalReferenceOperation: c (OperationKind.LocalReference, Type: System.Char) (Syntax: 'c') + NextVariables(0) + ExpressionBody: + null"); + + VerifyFlowGraph(compilation, node1, expectedFlowGraph: @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] +Block[B1] - Block + Predecessors: [B0] + Statements (0) + Jump if False (Regular) to Block[B3] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsImplicit) (Syntax: 'IEnumerable ... }') + Left: + IParameterReferenceOperation: s (OperationKind.ParameterReference, Type: System.String, IsImplicit) (Syntax: 'IEnumerable ... }') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: null, IsImplicit) (Syntax: 'IEnumerable ... }') + Entering: {R1} + Next (Regular) Block[B2] +Block[B2] - Block + Predecessors: [B1] + Statements (0) + Next (Throw) Block[null] + IObjectCreationOperation (Constructor: System.ArgumentNullException..ctor(System.String paramName)) (OperationKind.ObjectCreation, Type: System.ArgumentNullException, IsImplicit) (Syntax: 'IEnumerable ... }') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: paramName) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'IEnumerable ... }') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""s"", IsImplicit) (Syntax: 'IEnumerable ... }') + 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 +.locals {R1} +{ + CaptureIds: [0] + Block[B3] - Block + Predecessors: [B1] + Statements (1) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 's') + Value: + IInvocationOperation ( System.CharEnumerator System.String.GetEnumerator()) (OperationKind.Invocation, Type: System.CharEnumerator, IsImplicit) (Syntax: 's') + Instance Receiver: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.String, IsImplicit) (Syntax: 's') + Conversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Identity) + Operand: + IParameterReferenceOperation: s (OperationKind.ParameterReference, Type: System.String) (Syntax: 's') + Arguments(0) + Next (Regular) Block[B4] + Entering: {R2} {R3} + .try {R2, R3} + { + Block[B4] - Block + Predecessors: [B3] [B5] + Statements (0) + Jump if False (Regular) to Block[B9] + IInvocationOperation ( System.Boolean System.CharEnumerator.MoveNext()) (OperationKind.Invocation, Type: System.Boolean, IsImplicit) (Syntax: 's') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.CharEnumerator, IsImplicit) (Syntax: 's') + Arguments(0) + Finalizing: {R5} + Leaving: {R3} {R2} {R1} + Next (Regular) Block[B5] + Entering: {R4} + .locals {R4} + { + Locals: [System.Char c] + Block[B5] - Block + Predecessors: [B4] + Statements (2) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: null, IsImplicit) (Syntax: 'var') + Left: + ILocalReferenceOperation: c (IsDeclaration: True) (OperationKind.LocalReference, Type: System.Char, IsImplicit) (Syntax: 'var') + Right: + IPropertyReferenceOperation: System.Char System.CharEnumerator.Current { get; } (OperationKind.PropertyReference, Type: System.Char, IsImplicit) (Syntax: 'var') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.CharEnumerator, IsImplicit) (Syntax: 's') + IReturnOperation (OperationKind.YieldReturn, Type: null) (Syntax: 'yield return c;') + ReturnedValue: + ILocalReferenceOperation: c (OperationKind.LocalReference, Type: System.Char) (Syntax: 'c') + Next (Regular) Block[B4] + Leaving: {R4} + } + } + .finally {R5} + { + Block[B6] - Block + Predecessors (0) + Statements (0) + Jump if True (Regular) to Block[B8] + IIsNullOperation (OperationKind.IsNull, Type: System.Boolean, IsImplicit) (Syntax: 's') + Operand: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.CharEnumerator, IsImplicit) (Syntax: 's') + Next (Regular) Block[B7] + Block[B7] - Block + Predecessors: [B6] + Statements (1) + IInvocationOperation (virtual void System.IDisposable.Dispose()) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 's') + Instance Receiver: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.IDisposable, IsImplicit) (Syntax: 's') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.CharEnumerator, IsImplicit) (Syntax: 's') + Arguments(0) + Next (Regular) Block[B8] + Block[B8] - Block + Predecessors: [B6] [B7] + Statements (0) + Next (StructuredExceptionHandling) Block[null] + } +} +Block[B9] - Exit + Predecessors: [B4] + Statements (0)"); + } + + [Fact] + public void TestIOp_NullCheckedIteratorInLocalFunction() + { + var source = @" +using System.Collections.Generic; +class Iterators +{ + void Use() + { + IEnumerable e = GetChars(""hello""); + IEnumerable GetChars(string s!!) + { + foreach (var c in s) + { + yield return c; + } + } + } +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + + compilation.VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var node1 = tree.GetRoot().DescendantNodes().OfType().Single(); + var node2 = tree.GetRoot().DescendantNodes().OfType().Single(); + compilation.VerifyOperationTree(node1, expectedOperationTree: @" + ILocalFunctionOperation (Symbol: System.Collections.Generic.IEnumerable GetChars(System.String s)) (OperationKind.LocalFunction, Type: null) (Syntax: 'IEnumerable ... }') + IBlockOperation (2 statements) (OperationKind.Block, Type: null) (Syntax: '{ ... }') + IForEachLoopOperation (LoopKind.ForEach, Continue Label Id: 0, Exit Label Id: 1) (OperationKind.Loop, Type: null) (Syntax: 'foreach (va ... }') + Locals: Local_1: System.Char c + LoopControlVariable: + IVariableDeclaratorOperation (Symbol: System.Char c) (OperationKind.VariableDeclarator, Type: null) (Syntax: 'var') + Initializer: + null + Collection: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.String, IsImplicit) (Syntax: 's') + Conversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IParameterReferenceOperation: s (OperationKind.ParameterReference, Type: System.String) (Syntax: 's') + Body: + IBlockOperation (1 statements) (OperationKind.Block, Type: null) (Syntax: '{ ... }') + IReturnOperation (OperationKind.YieldReturn, Type: null) (Syntax: 'yield return c;') + ReturnedValue: + ILocalReferenceOperation: c (OperationKind.LocalReference, Type: System.Char) (Syntax: 'c') + NextVariables(0) + IReturnOperation (OperationKind.YieldBreak, Type: null, IsImplicit) (Syntax: '{ ... }') + ReturnedValue: + null"); + + VerifyFlowGraph(compilation, node2, @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + Locals: [System.Collections.Generic.IEnumerable e] + Methods: [System.Collections.Generic.IEnumerable GetChars(System.String s)] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Collections.Generic.IEnumerable, IsImplicit) (Syntax: 'e = GetChars(""hello"")') + Left: + ILocalReferenceOperation: e (IsDeclaration: True) (OperationKind.LocalReference, Type: System.Collections.Generic.IEnumerable, IsImplicit) (Syntax: 'e = GetChars(""hello"")') + Right: + IInvocationOperation (System.Collections.Generic.IEnumerable GetChars(System.String s)) (OperationKind.Invocation, Type: System.Collections.Generic.IEnumerable) (Syntax: 'GetChars(""hello"")') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: s) (OperationKind.Argument, Type: null) (Syntax: '""hello""') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""hello"") (Syntax: '""hello""') + 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} + + { System.Collections.Generic.IEnumerable GetChars(System.String s) + + Block[B0#0R1] - Entry + Statements (0) + Next (Regular) Block[B1#0R1] + Block[B1#0R1] - Block + Predecessors: [B0#0R1] + Statements (0) + Jump if False (Regular) to Block[B3#0R1] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsImplicit) (Syntax: 'IEnumerable ... }') + Left: + IParameterReferenceOperation: s (OperationKind.ParameterReference, Type: System.String, IsImplicit) (Syntax: 'IEnumerable ... }') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: null, IsImplicit) (Syntax: 'IEnumerable ... }') + Entering: {R1#0R1} + Next (Regular) Block[B2#0R1] + Block[B2#0R1] - Block + Predecessors: [B1#0R1] + Statements (0) + Next (Throw) Block[null] + IObjectCreationOperation (Constructor: System.ArgumentNullException..ctor(System.String paramName)) (OperationKind.ObjectCreation, Type: System.ArgumentNullException, IsImplicit) (Syntax: 'IEnumerable ... }') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: paramName) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'IEnumerable ... }') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""s"", IsImplicit) (Syntax: 'IEnumerable ... }') + 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 + .locals {R1#0R1} + { + CaptureIds: [0] + Block[B3#0R1] - Block + Predecessors: [B1#0R1] + Statements (1) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 's') + Value: + IInvocationOperation ( System.CharEnumerator System.String.GetEnumerator()) (OperationKind.Invocation, Type: System.CharEnumerator, IsImplicit) (Syntax: 's') + Instance Receiver: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.String, IsImplicit) (Syntax: 's') + Conversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Identity) + Operand: + IParameterReferenceOperation: s (OperationKind.ParameterReference, Type: System.String) (Syntax: 's') + Arguments(0) + Next (Regular) Block[B4#0R1] + Entering: {R2#0R1} {R3#0R1} + .try {R2#0R1, R3#0R1} + { + Block[B4#0R1] - Block + Predecessors: [B3#0R1] [B5#0R1] + Statements (0) + Jump if False (Regular) to Block[B9#0R1] + IInvocationOperation ( System.Boolean System.CharEnumerator.MoveNext()) (OperationKind.Invocation, Type: System.Boolean, IsImplicit) (Syntax: 's') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.CharEnumerator, IsImplicit) (Syntax: 's') + Arguments(0) + Finalizing: {R5#0R1} + Leaving: {R3#0R1} {R2#0R1} {R1#0R1} + Next (Regular) Block[B5#0R1] + Entering: {R4#0R1} + .locals {R4#0R1} + { + Locals: [System.Char c] + Block[B5#0R1] - Block + Predecessors: [B4#0R1] + Statements (2) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: null, IsImplicit) (Syntax: 'var') + Left: + ILocalReferenceOperation: c (IsDeclaration: True) (OperationKind.LocalReference, Type: System.Char, IsImplicit) (Syntax: 'var') + Right: + IPropertyReferenceOperation: System.Char System.CharEnumerator.Current { get; } (OperationKind.PropertyReference, Type: System.Char, IsImplicit) (Syntax: 'var') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.CharEnumerator, IsImplicit) (Syntax: 's') + IReturnOperation (OperationKind.YieldReturn, Type: null) (Syntax: 'yield return c;') + ReturnedValue: + ILocalReferenceOperation: c (OperationKind.LocalReference, Type: System.Char) (Syntax: 'c') + Next (Regular) Block[B4#0R1] + Leaving: {R4#0R1} + } + } + .finally {R5#0R1} + { + Block[B6#0R1] - Block + Predecessors (0) + Statements (0) + Jump if True (Regular) to Block[B8#0R1] + IIsNullOperation (OperationKind.IsNull, Type: System.Boolean, IsImplicit) (Syntax: 's') + Operand: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.CharEnumerator, IsImplicit) (Syntax: 's') + Next (Regular) Block[B7#0R1] + Block[B7#0R1] - Block + Predecessors: [B6#0R1] + Statements (1) + IInvocationOperation (virtual void System.IDisposable.Dispose()) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 's') + Instance Receiver: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.IDisposable, IsImplicit) (Syntax: 's') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.CharEnumerator, IsImplicit) (Syntax: 's') + Arguments(0) + Next (Regular) Block[B8#0R1] + Block[B8#0R1] - Block + Predecessors: [B6#0R1] [B7#0R1] + Statements (0) + Next (StructuredExceptionHandling) Block[null] + } + } + Block[B9#0R1] - Exit + Predecessors: [B4#0R1] + Statements (0) + } +} +Block[B2] - Exit + Predecessors: [B1] + Statements (0)"); + } + + [Fact] + public void TestIOp_NullCheckedEmptyIterator() + { + var source = @" +using System.Collections.Generic; +class C +{ + public static void Main() { } + static IEnumerable GetChars(string s!!) + { + yield break; + } +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + + compilation.VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var node1 = tree.GetRoot().DescendantNodes().OfType().ElementAt(1); + compilation.VerifyOperationTree(node1, expectedOperationTree: @" + IMethodBodyOperation (OperationKind.MethodBody, Type: null) (Syntax: 'static IEnu ... }') + BlockBody: + IBlockOperation (1 statements) (OperationKind.Block, Type: null) (Syntax: '{ ... }') + IReturnOperation (OperationKind.YieldBreak, Type: null) (Syntax: 'yield break;') + ReturnedValue: + null + ExpressionBody: + null"); + + VerifyFlowGraph(compilation, node1, expectedFlowGraph: @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] +Block[B1] - Block + Predecessors: [B0] + Statements (0) + Jump if False (Regular) to Block[B3] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsImplicit) (Syntax: 'static IEnu ... }') + Left: + IParameterReferenceOperation: s (OperationKind.ParameterReference, Type: System.String, IsImplicit) (Syntax: 'static IEnu ... }') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: null, IsImplicit) (Syntax: 'static IEnu ... }') + Next (Regular) Block[B2] +Block[B2] - Block + Predecessors: [B1] + Statements (0) + Next (Throw) Block[null] + IObjectCreationOperation (Constructor: System.ArgumentNullException..ctor(System.String paramName)) (OperationKind.ObjectCreation, Type: System.ArgumentNullException, IsImplicit) (Syntax: 'static IEnu ... }') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: paramName) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'static IEnu ... }') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""s"", IsImplicit) (Syntax: 'static IEnu ... }') + 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 +Block[B3] - Exit + Predecessors: [B1] + Statements (0)"); + } + + [Fact] + public void TestIOp_NullCheckedEmptyIteratorReturningIEnumerator() + { + var source = @" +using System.Collections.Generic; +class C +{ + public static void Main() { } + static IEnumerator GetChars(string s!!) + { + yield break; + } +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + + compilation.VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var node1 = tree.GetRoot().DescendantNodes().OfType().ElementAt(1); + compilation.VerifyOperationTree(node1, expectedOperationTree: @" + IMethodBodyOperation (OperationKind.MethodBody, Type: null) (Syntax: 'static IEnu ... }') + BlockBody: + IBlockOperation (1 statements) (OperationKind.Block, Type: null) (Syntax: '{ ... }') + IReturnOperation (OperationKind.YieldBreak, Type: null) (Syntax: 'yield break;') + ReturnedValue: + null + ExpressionBody: + null"); + + VerifyFlowGraph(compilation, node1, expectedFlowGraph: @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] +Block[B1] - Block + Predecessors: [B0] + Statements (0) + Jump if False (Regular) to Block[B3] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsImplicit) (Syntax: 'static IEnu ... }') + Left: + IParameterReferenceOperation: s (OperationKind.ParameterReference, Type: System.String, IsImplicit) (Syntax: 'static IEnu ... }') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: null, IsImplicit) (Syntax: 'static IEnu ... }') + Next (Regular) Block[B2] +Block[B2] - Block + Predecessors: [B1] + Statements (0) + Next (Throw) Block[null] + IObjectCreationOperation (Constructor: System.ArgumentNullException..ctor(System.String paramName)) (OperationKind.ObjectCreation, Type: System.ArgumentNullException, IsImplicit) (Syntax: 'static IEnu ... }') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: paramName) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'static IEnu ... }') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""s"", IsImplicit) (Syntax: 'static IEnu ... }') + 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 +Block[B3] - Exit + Predecessors: [B1] + Statements (0)"); + } + + [Fact] + public void TestNullCheckedLambdaWithMissingType() + { + var source = +@" +using System; +class Program +{ + public static void Main() + { + Func func = x!! => x; + } +} + +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.MakeMemberMissing(WellKnownMember.System_ArgumentNullException__ctorString); + comp.MakeTypeMissing(WellKnownType.System_ArgumentNullException); + comp.VerifyDiagnostics( + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "x").WithArguments("System.ArgumentNullException", ".ctor").WithLocation(7, 37)); + var tree = comp.SyntaxTrees.Single(); + var node1 = tree.GetRoot().DescendantNodes().OfType().Single(); + comp.VerifyOperationTree(node1, expectedOperationTree: @" +IMethodBodyOperation (OperationKind.MethodBody, Type: null, IsInvalid) (Syntax: 'public stat ... }') + BlockBody: + IBlockOperation (1 statements, 1 locals) (OperationKind.Block, Type: null, IsInvalid) (Syntax: '{ ... }') + Locals: Local_1: System.Func func + IVariableDeclarationGroupOperation (1 declarations) (OperationKind.VariableDeclarationGroup, Type: null, IsInvalid) (Syntax: 'Func x;') + IVariableDeclarationOperation (1 declarators) (OperationKind.VariableDeclaration, Type: null, IsInvalid) (Syntax: 'Func x') + Declarators: + IVariableDeclaratorOperation (Symbol: System.Func func) (OperationKind.VariableDeclarator, Type: null, IsInvalid) (Syntax: 'func = x!! => x') + Initializer: + IVariableInitializerOperation (OperationKind.VariableInitializer, Type: null, IsInvalid) (Syntax: '= x!! => x') + IDelegateCreationOperation (OperationKind.DelegateCreation, Type: System.Func, IsInvalid, IsImplicit) (Syntax: 'x!! => x') + Target: + IAnonymousFunctionOperation (Symbol: lambda expression) (OperationKind.AnonymousFunction, Type: null, IsInvalid) (Syntax: 'x!! => x') + IBlockOperation (1 statements) (OperationKind.Block, Type: null, IsImplicit) (Syntax: 'x') + IReturnOperation (OperationKind.Return, Type: null, IsImplicit) (Syntax: 'x') + ReturnedValue: + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.String) (Syntax: 'x') + Initializer: + null + ExpressionBody: + null"); + VerifyFlowGraph(comp, node1, expectedFlowGraph: @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + Locals: [System.Func func] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Func, IsInvalid, IsImplicit) (Syntax: 'func = x!! => x') + Left: + ILocalReferenceOperation: func (IsDeclaration: True) (OperationKind.LocalReference, Type: System.Func, IsInvalid, IsImplicit) (Syntax: 'func = x!! => x') + Right: + IDelegateCreationOperation (OperationKind.DelegateCreation, Type: System.Func, IsInvalid, IsImplicit) (Syntax: 'x!! => x') + Target: + IFlowAnonymousFunctionOperation (Symbol: lambda expression) (OperationKind.FlowAnonymousFunction, Type: null, IsInvalid) (Syntax: 'x!! => x') + { + Block[B0#A0] - Entry + Statements (0) + Next (Regular) Block[B1#A0] + Block[B1#A0] - Block + Predecessors: [B0#A0] + Statements (0) + Jump if False (Regular) to Block[B3#A0] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsInvalid, IsImplicit) (Syntax: 'x!! => x') + Left: + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.String, IsInvalid, IsImplicit) (Syntax: 'x!! => x') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: null, IsInvalid, IsImplicit) (Syntax: 'x!! => x') + Next (Regular) Block[B2#A0] + Block[B2#A0] - Block + Predecessors: [B1#A0] + Statements (0) + Next (Throw) Block[null] + IInvalidOperation (OperationKind.Invalid, Type: null, IsInvalid, IsImplicit) (Syntax: 'x!! => x') + Children(1): + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""x"", IsInvalid, IsImplicit) (Syntax: 'x!! => x') + Block[B3#A0] - Block + Predecessors: [B1#A0] + Statements (0) + Next (Return) Block[B4#A0] + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.String) (Syntax: 'x') + Block[B4#A0] - Exit + Predecessors: [B3#A0] + Statements (0) + } + Next (Regular) Block[B2] + Leaving: {R1} +} +Block[B2] - Exit + Predecessors: [B1] + Statements (0)"); + } + + [Fact] + public void TestNullCheckedLocalFunctionWithMissingType() + { + var source = +@" +class Program +{ + public static void Main() + { + M(""ok""); + void M(string x!!) { } + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.MakeMemberMissing(WellKnownMember.System_ArgumentNullException__ctorString); + comp.MakeTypeMissing(WellKnownType.System_ArgumentNullException); + comp.VerifyDiagnostics( + // (7,23): error CS0656: Missing compiler required member 'System.ArgumentNullException..ctor' + // void M(string x!!) { } + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "x").WithArguments("System.ArgumentNullException", ".ctor").WithLocation(7, 23)); + var tree = comp.SyntaxTrees.Single(); + var node1 = tree.GetRoot().DescendantNodes().OfType().Single(); + var node2 = tree.GetRoot().DescendantNodes().OfType().Single(); + comp.VerifyOperationTree(node1, expectedOperationTree: @" + ILocalFunctionOperation (Symbol: void M(System.String x)) (OperationKind.LocalFunction, Type: null, IsInvalid) (Syntax: 'void M(string x!!) { }') + IBlockOperation (1 statements) (OperationKind.Block, Type: null) (Syntax: '{ }') + IReturnOperation (OperationKind.Return, Type: null, IsImplicit) (Syntax: '{ }') + ReturnedValue: + null"); + VerifyFlowGraph(comp, node2, @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + Methods: [void M(System.String x)] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'M(""ok"");') + Expression: + IInvocationOperation (void M(System.String x)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'M(""ok"")') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: x) (OperationKind.Argument, Type: null) (Syntax: '""ok""') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""ok"") (Syntax: '""ok""') + 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} + + { void M(System.String x) + + Block[B0#0R1] - Entry + Statements (0) + Next (Regular) Block[B1#0R1] + Block[B1#0R1] - Block + Predecessors: [B0#0R1] + Statements (0) + Jump if False (Regular) to Block[B3#0R1] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsInvalid, IsImplicit) (Syntax: 'void M(string x!!) { }') + Left: + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.String, IsInvalid, IsImplicit) (Syntax: 'void M(string x!!) { }') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: null, IsInvalid, IsImplicit) (Syntax: 'void M(string x!!) { }') + Next (Regular) Block[B2#0R1] + Block[B2#0R1] - Block + Predecessors: [B1#0R1] + Statements (0) + Next (Throw) Block[null] + IInvalidOperation (OperationKind.Invalid, Type: null, IsInvalid, IsImplicit) (Syntax: 'void M(string x!!) { }') + Children(1): + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""x"", IsInvalid, IsImplicit) (Syntax: 'void M(string x!!) { }') + Block[B3#0R1] - Exit + Predecessors: [B1#0R1] + Statements (0) + } +} +Block[B2] - Exit + Predecessors: [B1] + Statements (0)"); + } + + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/58335: MakeMemberMissing doesn't work as expected with our method of obtaining Nullable.HasValue in this scenario")] + public void TestNullCheckedMethodWithMissingHasValue() + { + var source = +@" +class Program +{ + public void Method(int? x!!) { } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.MakeMemberMissing(SpecialMember.System_Nullable_T_get_HasValue); + comp.VerifyDiagnostics( + // (4,29): warning CS8721: Nullable value type 'int?' is null-checked and will throw if null. + // public void Method(int? x!!) { } + Diagnostic(ErrorCode.WRN_NullCheckingOnNullableType, "x").WithArguments("int?").WithLocation(4, 29)); + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Single(); + comp.VerifyOperationTree(node, expectedOperationTree: @" + IMethodBodyOperation (OperationKind.MethodBody, Type: null) (Syntax: 'public void ... t? x!!) { }') + BlockBody: + IBlockOperation (0 statements) (OperationKind.Block, Type: null) (Syntax: '{ }') + ExpressionBody: + null"); + VerifyFlowGraph(comp, node, @" + Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Block[B1] - Block + Predecessors: [B0] + Statements (0) + Jump if True (Regular) to Block[B3] + IInvalidOperation (OperationKind.Invalid, Type: System.Boolean, IsImplicit) (Syntax: 'public void ... t? x!!) { }') + Children(1): + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.Int32?, IsImplicit) (Syntax: 'public void ... t? x!!) { }') + Next (Regular) Block[B2] + Block[B2] - Block + Predecessors: [B1] + Statements (0) + Next (Throw) Block[null] + IObjectCreationOperation (Constructor: System.ArgumentNullException..ctor(System.String paramName)) (OperationKind.ObjectCreation, Type: System.ArgumentNullException, IsImplicit) (Syntax: 'public void ... t? x!!) { }') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: paramName) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'public void ... t? x!!) { }') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""x"", IsImplicit) (Syntax: 'public void ... t? x!!) { }') + 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 + Block[B3] - Exit + Predecessors: [B1] + Statements (0)"); + } + + [Fact] + public void TestNoNullChecksInBlockOperation() + { + // https://github.com/dotnet/roslyn/issues/58320 + var source = @" +public class C +{ + public void M(string input!!) + /**/{ }/**/ +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + + compilation.VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var node1 = tree.GetRoot().DescendantNodes().OfType().Single(); + + compilation.VerifyOperationTree(node1, expectedOperationTree: @" +IBlockOperation (0 statements) (OperationKind.Block, Type: null) (Syntax: '{ }') +"); + var output = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] +Block[B1] - Block + Predecessors: [B0] + Statements (0) + Jump if False (Regular) to Block[B3] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsImplicit) (Syntax: 'public void ... */{ }') + Left: + IParameterReferenceOperation: input (OperationKind.ParameterReference, Type: System.String, IsImplicit) (Syntax: 'public void ... */{ }') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: null, IsImplicit) (Syntax: 'public void ... */{ }') + Next (Regular) Block[B2] +Block[B2] - Block + Predecessors: [B1] + Statements (0) + Next (Throw) Block[null] + IObjectCreationOperation (Constructor: System.ArgumentNullException..ctor(System.String paramName)) (OperationKind.ObjectCreation, Type: System.ArgumentNullException, IsImplicit) (Syntax: 'public void ... */{ }') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: paramName) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'public void ... */{ }') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""input"", IsImplicit) (Syntax: 'public void ... */{ }') + 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 +Block[B3] - Exit + Predecessors: [B1] + Statements (0)"; + VerifyFlowGraphAndDiagnosticsForTest(compilation, expectedFlowGraph: output, DiagnosticDescription.None); + } + + [Fact] + public void TestNullCheckedBaseCallOrdering() + { + var source = @" +public class B +{ + public B(string x) { } +} +public class C : B +{ + public C(string param!!) : base(param ?? """") { } +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + + compilation.VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var node1 = tree.GetRoot().DescendantNodes().OfType().ElementAt(1); + compilation.VerifyOperationTree(node1, expectedOperationTree: @" +IConstructorBodyOperation (OperationKind.ConstructorBody, Type: null) (Syntax: 'public C(st ... ?? """") { }') + Initializer: + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null, IsImplicit) (Syntax: ': base(param ?? """")') + Expression: + IInvocationOperation ( B..ctor(System.String x)) (OperationKind.Invocation, Type: System.Void) (Syntax: ': base(param ?? """")') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: B, IsImplicit) (Syntax: ': base(param ?? """")') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: x) (OperationKind.Argument, Type: null) (Syntax: 'param ?? """"') + ICoalesceOperation (OperationKind.Coalesce, Type: System.String) (Syntax: 'param ?? """"') + Expression: + IParameterReferenceOperation: param (OperationKind.ParameterReference, Type: System.String) (Syntax: 'param') + ValueConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Identity) + WhenNull: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: """") (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) + BlockBody: + IBlockOperation (0 statements) (OperationKind.Block, Type: null) (Syntax: '{ }') + ExpressionBody: + null"); + + VerifyFlowGraph(compilation, node1, expectedFlowGraph: @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] +Block[B1] - Block + Predecessors: [B0] + Statements (0) + Jump if False (Regular) to Block[B3] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsImplicit) (Syntax: 'public C(st ... ?? """") { }') + Left: + IParameterReferenceOperation: param (OperationKind.ParameterReference, Type: System.String, IsImplicit) (Syntax: 'public C(st ... ?? """") { }') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: null, IsImplicit) (Syntax: 'public C(st ... ?? """") { }') + Entering: {R1} + Next (Regular) Block[B2] +Block[B2] - Block + Predecessors: [B1] + Statements (0) + Next (Throw) Block[null] + IObjectCreationOperation (Constructor: System.ArgumentNullException..ctor(System.String paramName)) (OperationKind.ObjectCreation, Type: System.ArgumentNullException, IsImplicit) (Syntax: 'public C(st ... ?? """") { }') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: paramName) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'public C(st ... ?? """") { }') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""param"", IsImplicit) (Syntax: 'public C(st ... ?? """") { }') + 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 +.locals {R1} +{ + CaptureIds: [0] [2] + Block[B3] - Block + Predecessors: [B1] + Statements (1) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: ': base(param ?? """")') + Value: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: B, IsImplicit) (Syntax: ': base(param ?? """")') + Next (Regular) Block[B4] + Entering: {R2} + .locals {R2} + { + CaptureIds: [1] + Block[B4] - Block + Predecessors: [B3] + Statements (1) + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'param') + Value: + IParameterReferenceOperation: param (OperationKind.ParameterReference, Type: System.String) (Syntax: 'param') + Jump if True (Regular) to Block[B6] + IIsNullOperation (OperationKind.IsNull, Type: System.Boolean, IsImplicit) (Syntax: 'param') + Operand: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.String, IsImplicit) (Syntax: 'param') + Leaving: {R2} + Next (Regular) Block[B5] + Block[B5] - Block + Predecessors: [B4] + Statements (1) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'param') + Value: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.String, IsImplicit) (Syntax: 'param') + Next (Regular) Block[B7] + Leaving: {R2} + } + Block[B6] - Block + Predecessors: [B4] + Statements (1) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '""""') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: """") (Syntax: '""""') + Next (Regular) Block[B7] + Block[B7] - Block + Predecessors: [B5] [B6] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null, IsImplicit) (Syntax: ': base(param ?? """")') + Expression: + IInvocationOperation ( B..ctor(System.String x)) (OperationKind.Invocation, Type: System.Void) (Syntax: ': base(param ?? """")') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: B, IsImplicit) (Syntax: ': base(param ?? """")') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: x) (OperationKind.Argument, Type: null) (Syntax: 'param ?? """"') + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: System.String, IsImplicit) (Syntax: 'param ?? """"') + 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[B8] + Leaving: {R1} +} +Block[B8] - Exit + Predecessors: [B7] + Statements (0)"); + } + } +} 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/NullCheckedParameterTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullCheckedParameterTests.cs new file mode 100644 index 0000000000000..f6e02cac45ba2 --- /dev/null +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullCheckedParameterTests.cs @@ -0,0 +1,1133 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.CSharp.UnitTests +{ + /// + /// Tests related to binding (but not lowering) null-checked variables and lambdas. + /// + public class NullCheckedParameterTests : CompilingTestBase + { + [Fact] + public void NullCheckedDelegateDeclaration() + { + var source = @" +delegate void Del(string x!!, int y); +class C +{ + Del d = delegate(string k!!, int j) { /* ... */ }; +}"; + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics( + // (2,26): error CS8714: Parameter 'x' can only have exclamation-point null checking in implementation methods. + // delegate void Del(int x!!, int y); + Diagnostic(ErrorCode.ERR_MustNullCheckInImplementation, "x").WithArguments("x").WithLocation(2, 26)); + } + + [Fact] + public void NullCheckedAbstractMethod() + { + var source = @" +abstract class C +{ + abstract public int M(string x!!); +}"; + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics( + // (4,34): error CS8714: Parameter 'x' can only have exclamation-point null checking in implementation methods. + // abstract public int M(int x!!); + Diagnostic(ErrorCode.ERR_MustNullCheckInImplementation, "x").WithArguments("x").WithLocation(4, 34)); + } + + [Fact] + public void NullCheckedInterfaceMethod() + { + var source = @" +interface C +{ + public int M(string x!!); +}"; + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics( + // (4,25): error CS8714: Parameter 'x' can only have exclamation-point null checking in implementation methods. + // public int M(int x!!); + Diagnostic(ErrorCode.ERR_MustNullCheckInImplementation, "x").WithArguments("x").WithLocation(4, 25)); + } + + [ConditionalFact(typeof(MonoOrCoreClrOnly))] + public void NullCheckedInterfaceMethod2() + { + var source = @" +interface C +{ + public void M(string x!!) { } +}"; + var compilation = CreateCompilation(source, options: TestOptions.DebugDll, targetFramework: TargetFramework.StandardLatest, + parseOptions: TestOptions.RegularPreview); + compilation.VerifyDiagnostics(); + } + + [Fact] + public void NullCheckedAutoProperty() + { + var source = @" +abstract class C +{ + string FirstName!! { get; set; } +}"; + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics( + // (4,12): warning CS0169: The field 'C.FirstName' is never used + // string FirstName!! { get; set; } + Diagnostic(ErrorCode.WRN_UnreferencedField, "FirstName").WithArguments("C.FirstName").WithLocation(4, 12), + // (4,21): error CS1003: Syntax error, ',' expected + // string FirstName!! { get; set; } + Diagnostic(ErrorCode.ERR_SyntaxError, "!").WithArguments(",", "!").WithLocation(4, 21), + // (4,26): error CS1002: ; expected + // string FirstName!! { get; set; } + Diagnostic(ErrorCode.ERR_SemicolonExpected, "get").WithLocation(4, 26), + // (4,29): error CS1519: Invalid token ';' in class, struct, or interface member declaration + // string FirstName!! { get; set; } + Diagnostic(ErrorCode.ERR_InvalidMemberDecl, ";").WithArguments(";").WithLocation(4, 29), + // (4,29): error CS1519: Invalid token ';' in class, struct, or interface member declaration + // string FirstName!! { get; set; } + Diagnostic(ErrorCode.ERR_InvalidMemberDecl, ";").WithArguments(";").WithLocation(4, 29), + // (4,34): error CS1519: Invalid token ';' in class, struct, or interface member declaration + // string FirstName!! { get; set; } + Diagnostic(ErrorCode.ERR_InvalidMemberDecl, ";").WithArguments(";").WithLocation(4, 34), + // (4,34): error CS1519: Invalid token ';' in class, struct, or interface member declaration + // string FirstName!! { get; set; } + Diagnostic(ErrorCode.ERR_InvalidMemberDecl, ";").WithArguments(";").WithLocation(4, 34), + // (5,1): error CS1022: Type or namespace definition, or end-of-file expected + // } + Diagnostic(ErrorCode.ERR_EOFExpected, "}").WithLocation(5, 1)); + } + + [Fact] + public void NullCheckedPartialMethod() + { + var source = @" +partial class C +{ + partial void M(string x!!); +}"; + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics( + // (4,27): error CS8717: Parameter 'x' can only have exclamation-point null checking in implementation methods. + // partial void M(string x!!); + Diagnostic(ErrorCode.ERR_MustNullCheckInImplementation, "x").WithArguments("x").WithLocation(4, 27)); + } + + [Fact] + public void NullCheckedBadSyntax() + { + var source = @" +#pragma warning disable CS8893 +partial class C +{ + void M0(string name !!=null) { } + void M1(string name! !=null) { } + void M2(string name!!= null) { } + void M3(string name ! !=null) { } + void M4(string name ! ! =null) { } + void M5(string name! ! =null) { } + void M6(string name! != null) { } + void M7(string name! + != null) { } +}"; + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics( + // (5,20): warning CS8993: Parameter 'name' is null-checked but is null by default. + // void M0(string name !!=null) { } + Diagnostic(ErrorCode.WRN_NullCheckedHasDefaultNull, "name").WithArguments("name").WithLocation(5, 20), + // (6,20): warning CS8993: Parameter 'name' is null-checked but is null by default. + // void M1(string name! !=null) { } + Diagnostic(ErrorCode.WRN_NullCheckedHasDefaultNull, "name").WithArguments("name").WithLocation(6, 20), + // (6,26): error CS1525: Invalid expression term '!' + // void M1(string name! !=null) { } + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "!").WithArguments("!").WithLocation(6, 26), + // (7,20): warning CS8993: Parameter 'name' is null-checked but is null by default. + // void M2(string name!!= null) { } + Diagnostic(ErrorCode.WRN_NullCheckedHasDefaultNull, "name").WithArguments("name").WithLocation(7, 20), + // (8,20): warning CS8993: Parameter 'name' is null-checked but is null by default. + // void M3(string name ! !=null) { } + Diagnostic(ErrorCode.WRN_NullCheckedHasDefaultNull, "name").WithArguments("name").WithLocation(8, 20), + // (8,27): error CS1525: Invalid expression term '!' + // void M3(string name ! !=null) { } + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "!").WithArguments("!").WithLocation(8, 27), + // (9,20): warning CS8993: Parameter 'name' is null-checked but is null by default. + // void M4(string name ! ! =null) { } + Diagnostic(ErrorCode.WRN_NullCheckedHasDefaultNull, "name").WithArguments("name").WithLocation(9, 20), + // (9,27): error CS1525: Invalid expression term '!' + // void M4(string name ! ! =null) { } + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "!").WithArguments("!").WithLocation(9, 27), + // (10,20): warning CS8993: Parameter 'name' is null-checked but is null by default. + // void M5(string name! ! =null) { } + Diagnostic(ErrorCode.WRN_NullCheckedHasDefaultNull, "name").WithArguments("name").WithLocation(10, 20), + // (10,26): error CS1525: Invalid expression term '!' + // void M5(string name! ! =null) { } + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "!").WithArguments("!").WithLocation(10, 26), + // (11,20): warning CS8993: Parameter 'name' is null-checked but is null by default. + // void M6(string name! != null) { } + Diagnostic(ErrorCode.WRN_NullCheckedHasDefaultNull, "name").WithArguments("name").WithLocation(11, 20), + // (11,26): error CS1525: Invalid expression term '!' + // void M6(string name! != null) { } + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "!").WithArguments("!").WithLocation(11, 26), + // (12,20): warning CS8993: Parameter 'name' is null-checked but is null by default. + // void M7(string name! + Diagnostic(ErrorCode.WRN_NullCheckedHasDefaultNull, "name").WithArguments("name").WithLocation(12, 20), + // (13,5): error CS1525: Invalid expression term '!' + // != null) { } + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "!").WithArguments("!").WithLocation(13, 5)); + } + + [Fact] + public void NullCheckedInterfaceProperty() + { + var source = @" +interface C +{ + public string this[string index!!] { get; set; } +}"; + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics( + // (4,31): error CS8714: Parameter 'index' can only have exclamation-point null checking in implementation methods. + // public string this[int index!!] { get; set; } + Diagnostic(ErrorCode.ERR_MustNullCheckInImplementation, "index").WithArguments("index").WithLocation(4, 31)); + } + + [Fact] + public void NullCheckedAbstractProperty() + { + var source = @" +abstract class C +{ + public abstract string this[string index!!] { get; } +}"; + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics( + // (4,40): error CS8714: Parameter 'index' can only have exclamation-point null checking in implementation methods. + // public abstract string this[int index!!] { get; } + Diagnostic(ErrorCode.ERR_MustNullCheckInImplementation, "index").WithArguments("index").WithLocation(4, 40)); + } + + [Fact] + public void NullCheckedIndexedProperty() + { + var source = @" +class C +{ + public string this[string index!!] => null; +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics(); + var m = comp.GlobalNamespace.GetTypeMember("C").GetMember("this[]"); + Assert.True(((SourceParameterSymbol)m.Parameters[0]).IsNullChecked); + } + + [Fact] + public void NullCheckedExternMethod() + { + var source = @" +using System.Runtime.InteropServices; +class C +{ + [DllImport(""User32.dll"")] + public static extern int M(string x!!); +}"; + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics( + // (6,39): error CS8714: Parameter 'x' can only have exclamation-point null checking in implementation methods. + // public static extern int M(int x!!); + Diagnostic(ErrorCode.ERR_MustNullCheckInImplementation, "x").WithArguments("x").WithLocation(6, 39)); + } + + [Fact] + public void NullCheckedMethodDeclaration() + { + var source = @" +class C +{ + void M(string name!!) { } + void M2(string x) { } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics(); + var m = comp.GlobalNamespace.GetTypeMember("C").GetMember("M"); + Assert.True(((SourceParameterSymbol)m.Parameters[0]).IsNullChecked); + + var m2 = comp.GlobalNamespace.GetTypeMember("C").GetMember("M2"); + Assert.False(((SourceParameterSymbol)m2.Parameters[0]).IsNullChecked); + } + + [Fact] + public void FailingNullCheckedArgList() + { + var source = @" +class C +{ + void M() + { + M2(__arglist(1, 'M')); + } + void M2(__arglist!!) + { + } +}"; + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics( + // (8,22): error CS1003: Syntax error, ',' expected + // void M2(__arglist!!) + Diagnostic(ErrorCode.ERR_SyntaxError, "!").WithArguments(",", "!").WithLocation(8, 22)); + } + + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/58335")] + public void NullCheckedArgList() + { + var source = @" +class C +{ + void M() + { + M2(__arglist(1!!, 'M')); + } + void M2(__arglist) + { + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics(); + } + + [Fact] + public void NullCheckedMethodValidationWithOptionalNullParameter() + { + var source = @" +class C +{ + void M(string name!! = null) { } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (4,19): warning CS8719: Parameter 'name' is null-checked but is null by default. + // void M(string name!! = null) { } + Diagnostic(ErrorCode.WRN_NullCheckedHasDefaultNull, "name").WithArguments("name").WithLocation(4, 19)); + var m = comp.GlobalNamespace.GetTypeMember("C").GetMember("M"); + Assert.True(((SourceParameterSymbol)m.Parameters[0]).IsNullChecked); + } + + [Fact] + public void NullCheckedMethodValidationWithOptionalStringLiteralParameter() + { + var source = @" +class C +{ + void M(string name!! = ""rose"") { } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics(); + var m = comp.GlobalNamespace.GetTypeMember("C").GetMember("M"); + Assert.True(((SourceParameterSymbol)m.Parameters[0]).IsNullChecked); + } + + [Fact] + public void NullCheckedMethodValidationWithOptionalParameterSplitBySpace() + { + var source = @" +class C +{ + void M(string name!! =null) { } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (4,19): warning CS8719: Parameter 'name' is null-checked but is null by default. + // void M(string name!! =null) { } + Diagnostic(ErrorCode.WRN_NullCheckedHasDefaultNull, "name").WithArguments("name").WithLocation(4, 19)); + var m = comp.GlobalNamespace.GetTypeMember("C").GetMember("M"); + Assert.True(((SourceParameterSymbol)m.Parameters[0]).IsNullChecked); + } + + [Fact] + public void NullCheckedConstructor() + { + var source = @" +class C +{ + public C(string name!!) { } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics(); + var m = comp.GlobalNamespace.GetTypeMember("C").GetMember(".ctor"); + Assert.True(((SourceParameterSymbol)m.Parameters[0]).IsNullChecked); + } + + [Fact] + public void NullCheckedOperator() + { + var source = @" +class Box +{ + public static int operator+ (Box b!!, Box c) + { + return 2; + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics(); + var m = comp.GlobalNamespace.GetTypeMember("Box").GetMember("op_Addition"); + Assert.True(((SourceParameterSymbol)m.Parameters[0]).IsNullChecked); + Assert.False(((SourceParameterSymbol)m.Parameters[1]).IsNullChecked); + } + + [Fact] + public void NullCheckedLambdaParameter() + { + var source = @" +using System; +class C +{ + public void M() + { + Func func1 = x!! => x + ""1""; + } +}"; + var tree = Parse(source, options: TestOptions.RegularPreview); + var comp = CreateCompilation(tree); + comp.VerifyDiagnostics(); + + var model = (CSharpSemanticModel)comp.GetSemanticModel(tree); + SimpleLambdaExpressionSyntax node = comp.GlobalNamespace.GetTypeMember("C") + .GetMember("M") + .GetNonNullSyntaxNode() + .DescendantNodes() + .OfType() + .Single(); + var methodSymbol = (IMethodSymbol)model.GetSymbolInfo(node).Symbol!; + Assert.True(methodSymbol.Parameters[0].IsNullChecked); + } + + [Fact] + public void NullCheckedLambdaWithMultipleParameters() + { + var source = @" +using System; +class C +{ + public void M() + { + Func func1 = (x!!, y) => x == y; + } +}"; + var tree = Parse(source, options: TestOptions.RegularPreview); + var comp = CreateCompilation(tree); + comp.VerifyDiagnostics(); + + var model = (CSharpSemanticModel)comp.GetSemanticModel(tree); + ParenthesizedLambdaExpressionSyntax node = comp.GlobalNamespace.GetTypeMember("C") + .GetMember("M") + .GetNonNullSyntaxNode() + .DescendantNodes() + .OfType() + .Single(); + var methodSymbol = (IMethodSymbol)model.GetSymbolInfo(node).Symbol!; + Assert.True(methodSymbol.Parameters[0].IsNullChecked); + Assert.False(methodSymbol.Parameters[1].IsNullChecked); + } + + [Fact] + public void NullCheckedLambdaSingleParameterInParentheses() + { + var source = @" +using System; +class C +{ + public void M() + { + Func func1 = (x!!) => x; + } +}"; + var tree = Parse(source, options: TestOptions.RegularPreview); + var comp = CreateCompilation(tree); + comp.VerifyDiagnostics(); + + CSharpSemanticModel model = (CSharpSemanticModel)comp.GetSemanticModel(tree); + Syntax.ParenthesizedLambdaExpressionSyntax node = comp.GlobalNamespace.GetTypeMember("C") + .GetMember("M") + .GetNonNullSyntaxNode() + .DescendantNodes() + .OfType() + .Single(); + var methodSymbol = (IMethodSymbol)model.GetSymbolInfo(node).Symbol!; + Assert.True(methodSymbol.Parameters[0].IsNullChecked); + } + + [Fact] + public void NullCheckedLambdaSingleParameterNoSpaces() + { + var source = @" +using System; +class C +{ + public void M() + { + Func func1 = x!!=> x; + } +}"; + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics(); + } + + [Fact] + public void NullCheckedLambdaBadSyntax() + { + var source = @" +using System; +class C +{ + public void M() + { + Func func0 = x!=> x; + Func func1 = x !=> x; + Func func2 = x != > x; + Func func3 = x! => x; + Func func4 = x ! => x; + Func func5 = x !!=> x; + Func func6 = x !!= > x; + Func func7 = x !! => x; + Func func8 = x! !=> x; + Func func9 = x! ! => x; + } +}"; + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics( + // (7,39): error CS1003: Syntax error, '=>' expected + // Func func0 = x!=> x; + Diagnostic(ErrorCode.ERR_SyntaxError, "!=").WithArguments("=>", "!=").WithLocation(7, 39), + // (7,39): error CS1525: Invalid expression term '!=' + // Func func0 = x!=> x; + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "!=").WithArguments("!=").WithLocation(7, 39), + // (7,41): error CS1525: Invalid expression term '>' + // Func func0 = x!=> x; + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ">").WithArguments(">").WithLocation(7, 41), + // (8,40): error CS1003: Syntax error, '=>' expected + // Func func1 = x !=> x; + Diagnostic(ErrorCode.ERR_SyntaxError, "!=").WithArguments("=>", "!=").WithLocation(8, 40), + // (8,40): error CS1525: Invalid expression term '!=' + // Func func1 = x !=> x; + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "!=").WithArguments("!=").WithLocation(8, 40), + // (8,42): error CS1525: Invalid expression term '>' + // Func func1 = x !=> x; + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ">").WithArguments(">").WithLocation(8, 42), + // (9,40): error CS1003: Syntax error, '=>' expected + // Func func2 = x != > x; + Diagnostic(ErrorCode.ERR_SyntaxError, "!=").WithArguments("=>", "!=").WithLocation(9, 40), + // (9,40): error CS1525: Invalid expression term '!=' + // Func func2 = x != > x; + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "!=").WithArguments("!=").WithLocation(9, 40), + // (9,43): error CS1525: Invalid expression term '>' + // Func func2 = x != > x; + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ">").WithArguments(">").WithLocation(9, 43), + // (10,38): error CS0103: The name 'x' does not exist in the current context + // Func func3 = x! => x; + Diagnostic(ErrorCode.ERR_NameNotInContext, "x").WithArguments("x").WithLocation(10, 38), + // (10,41): error CS1003: Syntax error, ',' expected + // Func func3 = x! => x; + Diagnostic(ErrorCode.ERR_SyntaxError, "=>").WithArguments(",", "=>").WithLocation(10, 41), + // (10,44): error CS1002: ; expected + // Func func3 = x! => x; + Diagnostic(ErrorCode.ERR_SemicolonExpected, "x").WithLocation(10, 44), + // (10,44): error CS0103: The name 'x' does not exist in the current context + // Func func3 = x! => x; + Diagnostic(ErrorCode.ERR_NameNotInContext, "x").WithArguments("x").WithLocation(10, 44), + // (10,44): error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement + // Func func3 = x! => x; + Diagnostic(ErrorCode.ERR_IllegalStatement, "x").WithLocation(10, 44), + // (11,38): error CS0103: The name 'x' does not exist in the current context + // Func func4 = x ! => x; + Diagnostic(ErrorCode.ERR_NameNotInContext, "x").WithArguments("x").WithLocation(11, 38), + // (11,42): error CS1003: Syntax error, ',' expected + // Func func4 = x ! => x; + Diagnostic(ErrorCode.ERR_SyntaxError, "=>").WithArguments(",", "=>").WithLocation(11, 42), + // (11,45): error CS1002: ; expected + // Func func4 = x ! => x; + Diagnostic(ErrorCode.ERR_SemicolonExpected, "x").WithLocation(11, 45), + // (11,45): error CS0103: The name 'x' does not exist in the current context + // Func func4 = x ! => x; + Diagnostic(ErrorCode.ERR_NameNotInContext, "x").WithArguments("x").WithLocation(11, 45), + // (11,45): error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement + // Func func4 = x ! => x; + Diagnostic(ErrorCode.ERR_IllegalStatement, "x").WithLocation(11, 45), + // (13,44): error CS1525: Invalid expression term '>' + // Func func6 = x !!= > x; + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ">").WithArguments(">").WithLocation(13, 44), + // (15,41): error CS1525: Invalid expression term '!' + // Func func8 = x! !=> x; + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "!").WithArguments("!").WithLocation(15, 41), + // (16,41): error CS1525: Invalid expression term '!' + // Func func9 = x! ! => x; + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "!").WithArguments("!").WithLocation(16, 41)); + } + + [Fact] + public void NullCheckedLambdaSingleTypedParameterInParentheses() + { + var source = @" +using System; +class C +{ + public void M() + { + Func func1 = (string x!!) => x; + } +}"; + var tree = Parse(source, options: TestOptions.RegularPreview); + var comp = CreateCompilation(tree); + comp.VerifyDiagnostics(); + + CSharpSemanticModel model = (CSharpSemanticModel)comp.GetSemanticModel(tree); + ParenthesizedLambdaExpressionSyntax node = comp.GlobalNamespace.GetTypeMember("C") + .GetMember("M") + .GetNonNullSyntaxNode() + .DescendantNodes() + .OfType() + .Single(); + var methodSymbol = (IMethodSymbol)model.GetSymbolInfo(node).Symbol!; + Assert.True(methodSymbol.Parameters[0].IsNullChecked); + } + + [Fact] + public void NullCheckedLambdaManyTypedParametersInParentheses() + { + var source = @" +using System; +class C +{ + public void M() + { + Func func1 = (string x!!, string y) => x; + } +}"; + var tree = Parse(source, options: TestOptions.RegularPreview); + var comp = CreateCompilation(tree); + comp.VerifyDiagnostics(); + + CSharpSemanticModel model = (CSharpSemanticModel)comp.GetSemanticModel(tree); + ParenthesizedLambdaExpressionSyntax node = comp.GlobalNamespace.GetTypeMember("C") + .GetMember("M") + .GetNonNullSyntaxNode() + .DescendantNodes() + .OfType() + .Single(); + var methodSymbol = (IMethodSymbol)model.GetSymbolInfo(node).Symbol!; + Assert.True(methodSymbol.Parameters[0].IsNullChecked); + Assert.False(methodSymbol.Parameters[1].IsNullChecked); + } + + [Fact] + public void LambdaManyNullCheckedParametersInParentheses() + { + var source = @" +using System; +class C +{ + public void M() + { + Func func1 = (string x!!, string y!!) => x; + } +}"; + var tree = Parse(source, options: TestOptions.RegularPreview); + var comp = CreateCompilation(tree); + comp.VerifyDiagnostics(); + + CSharpSemanticModel model = (CSharpSemanticModel)comp.GetSemanticModel(tree); + ParenthesizedLambdaExpressionSyntax node = comp.GlobalNamespace.GetTypeMember("C") + .GetMember("M") + .GetNonNullSyntaxNode() + .DescendantNodes() + .OfType() + .Single(); + var methodSymbol = (IMethodSymbol)model.GetSymbolInfo(node).Symbol!; + Assert.True(methodSymbol.Parameters[0].IsNullChecked); + Assert.True(methodSymbol.Parameters[1].IsNullChecked); + } + + [Fact] + public void NullCheckedDiscard() + { + var source = @" +using System; +class C +{ + public void M() + { + Func func1 = (_!!) => 42; + } +}"; + var tree = Parse(source, options: TestOptions.RegularPreview); + var comp = CreateCompilation(tree); + comp.VerifyDiagnostics(); + + CSharpSemanticModel model = (CSharpSemanticModel)comp.GetSemanticModel(tree); + ParenthesizedLambdaExpressionSyntax node = comp.GlobalNamespace.GetTypeMember("C") + .GetMember("M") + .GetNonNullSyntaxNode() + .DescendantNodes() + .OfType() + .Single(); + var methodSymbol = (IMethodSymbol)model.GetSymbolInfo(node).Symbol!; + Assert.True(methodSymbol.Parameters[0].IsNullChecked); + } + + [Fact] + public void TestNullCheckedOutString() + { + var source = @" +class C +{ + public static void Main() { } + public void M(out string x!!) + { + x = ""hello world""; + } +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + compilation.VerifyDiagnostics( + // (5,31): error CS8720: By-reference parameter 'x' cannot be null-checked. + // public void M(out string x!!) + Diagnostic(ErrorCode.ERR_NullCheckingOnByRefParameter, "!!").WithArguments("x").WithLocation(5, 31)); + } + + [Fact] + public void TestNullCheckedRefString() + { + var source = @" +class C +{ + public static void Main() { } + public void M(ref string x!!) + { + x = ""hello world""; + } +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + compilation.VerifyDiagnostics( + // (5,31): error CS8720: By-reference parameter 'x' cannot be null-checked. + // public void M(ref string x!!) + Diagnostic(ErrorCode.ERR_NullCheckingOnByRefParameter, "!!").WithArguments("x").WithLocation(5, 31)); + } + + [Fact] + public void TestNullCheckedInString() + { + var source = @" +class C +{ + public static void Main() { } + public void M(in string x!!) { } +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + compilation.VerifyDiagnostics( + // (5,30): error CS8720: By-reference parameter 'x' cannot be null-checked. + // public void M(in string x!!) { } + Diagnostic(ErrorCode.ERR_NullCheckingOnByRefParameter, "!!").WithArguments("x").WithLocation(5, 30)); + } + + [Fact] + public void TestNullCheckedGenericWithDefault() + { + var source = @" +class C +{ + static void M1(T t!! = default) { } + static void M2(T? t!! = default) where T : struct { } + static void M3(T t!! = default) where T : class { } +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + compilation.VerifyDiagnostics( + // (5,26): warning CS8721: Nullable value type 'T?' is null-checked and will throw if null. + // static void M2(T? t!! = default) where T : struct { } + Diagnostic(ErrorCode.WRN_NullCheckingOnNullableType, "t").WithArguments("T?").WithLocation(5, 26), + // (6,25): warning CS8719: Parameter 't' is null-checked but is null by default. + // static void M3(T t!! = default) where T : class { } + Diagnostic(ErrorCode.WRN_NullCheckedHasDefaultNull, "t").WithArguments("t").WithLocation(6, 25)); + } + + [Fact] + public void TestNullableInteraction() + { + var source = @" +class C +{ + static void M(int? i!!) { } + public static void Main() { } +}"; + // Release + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + compilation.VerifyDiagnostics( + // (4,24): error CS8721: Nullable value type 'int?' is null-checked and will throw if null. + // static void M(int? i!!) { } + Diagnostic(ErrorCode.WRN_NullCheckingOnNullableType, "i").WithArguments("int?").WithLocation(4, 24)); + } + + [Fact] + public void TestNullableGenericsImplementingGenericAbstractClass() + { + var source = @" +abstract class A +{ + internal abstract void F(U u) where U : T; +} +class B1 : A +{ + internal override void F(U u!! = default) { } +} +class B2 : A +{ + internal override void F(U u!! = default) { } +} +class B3 : A +{ + internal override void F(U u!! = default) { } +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + compilation.VerifyDiagnostics( + // (12,35): warning CS8719: Parameter 'u' is null-checked but is null by default. + // internal override void F(U u!! = default) { } + Diagnostic(ErrorCode.WRN_NullCheckedHasDefaultNull, "u").WithArguments("u").WithLocation(12, 35), + // (16,35): warning CS8721: Nullable value type 'U' is null-checked and will throw if null. + // internal override void F(U u!! = default) { } + Diagnostic(ErrorCode.WRN_NullCheckingOnNullableType, "u").WithArguments("U").WithLocation(16, 35)); + } + + [Fact] + public void NoGeneratedNullCheckIfNonNullableTest() + { + var source = @" +using System; +class C +{ + public void M() + { + Func func1 = x!! => x; + } +}"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + compilation.VerifyDiagnostics( + // (7,32): error CS8718: Parameter 'int' is a non-nullable value type and therefore cannot be null-checked. + // Func func1 = x!! => x; + Diagnostic(ErrorCode.ERR_NonNullableValueTypeIsNullChecked, "x").WithArguments("int").WithLocation(7, 32)); + } + + [Fact] + public void TestNullCheckedParamWithOptionalNullParameter() + { + var source = @" +class C +{ + public static void Main() { } + void M(string name!! = null) { } +}"; + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics( + // (5,19): warning CS8719: Parameter 'name' is null-checked but is null by default. + // void M(string name!! = null) { } + Diagnostic(ErrorCode.WRN_NullCheckedHasDefaultNull, "name").WithArguments("name").WithLocation(5, 19)); + } + + [Fact] + public void TestManyNullCheckedArgs() + { + var source = @" +class C +{ + public void M(int x!!, string y!!) { } + public static void Main() { } +}"; + + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics( + // (4,23): error CS8718: Parameter 'int' is a non-nullable value type and therefore cannot be null-checked. + // public void M(int x!!, string y!!) { } + Diagnostic(ErrorCode.ERR_NonNullableValueTypeIsNullChecked, "x").WithArguments("int").WithLocation(4, 23)); + } + + [Fact] + public void TestNullCheckedSubstitutionWithDiagnostic1() + { + var source = @" +class A +{ + internal virtual void M(U u!!) where U : T { } +} +class B2 : A where T : struct +{ + internal override void M(U u!!) { } +}"; + + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics( + // (8,35): error CS8718: Parameter 'U' is a non-nullable value type and therefore cannot be null-checked. + // internal override void M(U u!!) { } + Diagnostic(ErrorCode.ERR_NonNullableValueTypeIsNullChecked, "u").WithArguments("U").WithLocation(8, 35)); + } + + [Fact] + public void TestNullCheckedSubstitutionWithDiagnostic2() + { + var source = @" +class A +{ + internal virtual void M(U u!!) where U : T { } +} +class B4 : A +{ + internal override void M(U u!!) { } +}"; + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics( + // (8,35): error CS8718: Parameter 'U' is a non-nullable value type and therefore cannot be null-checked. + // internal override void M(U u!!) { } + Diagnostic(ErrorCode.ERR_NonNullableValueTypeIsNullChecked, "u").WithArguments("U").WithLocation(8, 35)); + } + + [Fact] + public void TestNullCheckedSubstitutionWithDiagnostic3() + { + var source = @" +class C +{ + void M(T value!!) where T : unmanaged { } +}"; + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics( + // (4,17): error CS8718: Parameter 'T' is a non-nullable value type and cannot be null-checked. + // void M(T value!!) where T : unmanaged { } + Diagnostic(ErrorCode.ERR_NonNullableValueTypeIsNullChecked, "value").WithArguments("T").WithLocation(4, 17)); + } + + [Fact] + public void TestNullCheckedNullableValueTypeInLocalFunction() + { + var source = @" +class C +{ + public static void Main() + { + M((int?)5); + void M(int? x!!) { } + } +}"; + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics( + // (7,21): warning CS8721: Nullable value type 'int?' is null-checked and will throw if null. + // void M(int? x!!) { } + Diagnostic(ErrorCode.WRN_NullCheckingOnNullableType, "x").WithArguments("int?").WithLocation(7, 21)); + } + + [Fact] + public void TestNullCheckedParameterWithDefaultNullValueInLocalFunction() + { + var source = @" +class C +{ + public static void Main() + { + M(""ok""); + void M(string x!! = null) { } + } +}"; + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics( + // (7,23): warning CS8719: Parameter 'x' is null-checked but is null by default. + // void M(string x!! = null) { } + Diagnostic(ErrorCode.WRN_NullCheckedHasDefaultNull, "x").WithArguments("x").WithLocation(7, 23)); + } + + [Fact] + public void TestNullCheckedWithMissingType() + { + var source = +@" +class Program +{ + static void Main(string[] args!!) { } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.MakeMemberMissing(WellKnownMember.System_ArgumentNullException__ctorString); + comp.MakeTypeMissing(WellKnownType.System_ArgumentNullException); + comp.VerifyDiagnostics( + // (4,31): error CS0656: Missing compiler required member 'System.ArgumentNullException..ctor' + // static void Main(string[] args!!) { } + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "args").WithArguments("System.ArgumentNullException", ".ctor").WithLocation(4, 31)); + } + + [Fact] + public void TestNullCheckedMethodParameterWithWrongLanguageVersion() + { + var source = +@" +class Program +{ + void M(string x!!) { } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp6)); + comp.VerifyDiagnostics( + // (4,20): error CS8652: The feature 'parameter null-checking' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // void M(string x!!) { } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "!!").WithArguments("parameter null-checking").WithLocation(4, 20)); + } + + [Fact] + public void TestNullCheckedLambdaParameterWithWrongLanguageVersion() + { + var source = +@" +using System; +class Program +{ + void M() + { + Func func = x!! => x; + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp6)); + comp.VerifyDiagnostics( + // (7,38): error CS8652: The feature 'parameter null-checking' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // Func func = x!! => x; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "!!").WithArguments("parameter null-checking").WithLocation(7, 38)); + } + + [Fact] + public void TestNullCheckedLambdaParametersWithWrongLanguageVersion() + { + var source = +@" +using System; +class Program +{ + void M() + { + Func func = (x!!, y) => x; + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp6)); + comp.VerifyDiagnostics( + // (7,47): error CS8652: The feature 'parameter null-checking' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // Func func = (x!!, y) => x; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "!!").WithArguments("parameter null-checking").WithLocation(7, 47)); + } + + [Fact] + public void TestNullCheckedParameterUpdatesFlowState1() + { + var source = +@" +#nullable enable + +class Program +{ + string M(string? s!!) // 1 + { + return s; + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (6,22): warning CS8995: Nullable type 'string?' is null-checked and will throw if null. + // string M(string? s!!) // 1 + Diagnostic(ErrorCode.WRN_NullCheckingOnNullableType, "s").WithArguments("string?").WithLocation(6, 22)); + } + + [Fact] + public void TestNullCheckedParameterUpdatesFlowState2() + { + var source = +@" +#nullable enable + +class Program +{ + int M(int? x!!) // 1 + { + return x.Value; + } +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (6,16): warning CS8995: Nullable type 'int?' is null-checked and will throw if null. + // int M(int? x!!) // 1 + Diagnostic(ErrorCode.WRN_NullCheckingOnNullableType, "x").WithArguments("int?").WithLocation(6, 16)); + } + + [Fact] + public void TestNullCheckedParameterUpdatesFlowState3() + { + var source = +@" +#nullable enable + +using System.Diagnostics.CodeAnalysis; + +class Program +{ + void M1(string? s1, string? s2!!) // 1 + { + s1.ToString(); // 2 + s2.ToString(); + } + + static void M2(T x1, T y1!!) + { + x1.ToString(); // 3 + y1.ToString(); + } + static void M3([AllowNull] T x2, [AllowNull] T y2!!) + { + x2.ToString(); // 4 + y2.ToString(); + } + static void M4([DisallowNull] T x3, [DisallowNull] T y3!!) + { + x3.ToString(); + y3.ToString(); + } +}"; + var comp = CreateCompilation(new[] { source, AllowNullAttributeDefinition, DisallowNullAttributeDefinition }, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (8,33): warning CS8995: Nullable type 'string?' is null-checked and will throw if null. + // void M1(string? s1, string? s2!!) // 1 + Diagnostic(ErrorCode.WRN_NullCheckingOnNullableType, "s2").WithArguments("string?").WithLocation(8, 33), + // (10,9): warning CS8602: Dereference of a possibly null reference. + // s1.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s1").WithLocation(10, 9), + // (16,9): warning CS8602: Dereference of a possibly null reference. + // x1.ToString(); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x1").WithLocation(16, 9), + // (21,9): warning CS8602: Dereference of a possibly null reference. + // x2.ToString(); // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x2").WithLocation(21, 9)); + } + + [Fact] + public void TestNullCheckedParameterDoesNotAffectNullableVarianceChecks() + { + var source = +@" +#nullable enable + +class Base +{ + public virtual void M1(string? s) { } + public virtual void M2(string? s!!) { } // 1 +} + +class Derived : Base +{ + public override void M1(string s) { } // 2 + public override void M2(string s) { } // 3 +}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics( + // (7,36): warning CS8995: Nullable type 'string?' is null-checked and will throw if null. + // public virtual void M2(string? s!!) { } // 1 + Diagnostic(ErrorCode.WRN_NullCheckingOnNullableType, "s").WithArguments("string?").WithLocation(7, 36), + // (12,26): warning CS8765: Nullability of type of parameter 's' doesn't match overridden member (possibly because of nullability attributes). + // public override void M1(string s) { } // 2 + Diagnostic(ErrorCode.WRN_TopLevelNullabilityMismatchInParameterTypeOnOverride, "M1").WithArguments("s").WithLocation(12, 26), + // (13,26): warning CS8765: Nullability of type of parameter 's' doesn't match overridden member (possibly because of nullability attributes). + // public override void M2(string s) { } // 3 + Diagnostic(ErrorCode.WRN_TopLevelNullabilityMismatchInParameterTypeOnOverride, "M2").WithArguments("s").WithLocation(13, 26) + ); + } + } +} 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/CSharp/Test/Semantic/Semantics/RecordStructTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs index acb8b434c79ae..a9c28b9af2914 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs @@ -10616,5 +10616,349 @@ public Value(Value original) : this(42) { } var comp = CreateCompilation(src); CompileAndVerify(comp, expectedOutput: "Value { I = 42 }"); } + + [Fact] + public void ExplicitConstructors_01() + { + var source = +@"using static System.Console; +record struct S1 +{ +} +record struct S2 +{ + public S2() { } +} +record struct S3 +{ + public S3(object o) { } +} +class Program +{ + static void Main() + { + WriteLine(new S1()); + WriteLine(new S2()); + WriteLine(new S3()); + WriteLine(new S3(null)); + } +}"; + var verifier = CompileAndVerify(source, expectedOutput: +@"S1 { } +S2 { } +S3 { } +S3 { } +"); + verifier.VerifyMissing("S1..ctor()"); + verifier.VerifyIL("S2..ctor()", +@"{ + // Code size 1 (0x1) + .maxstack 0 + IL_0000: ret +}"); + verifier.VerifyMissing("S3..ctor()"); + verifier.VerifyIL("S3..ctor(object)", +@"{ + // Code size 1 (0x1) + .maxstack 0 + IL_0000: ret +}"); + } + + [Fact] + public void ExplicitConstructors_02() + { + var source = +@"record struct S1 +{ + public S1(object o) { } +} +record struct S2() +{ + public S2(object o) { } +} +record struct S3(char A) +{ + public S3(object o) { } +} +record struct S4(char A, char B) +{ + public S4(object o) { } +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (7,12): error CS8862: A constructor declared in a record with parameter list must have 'this' constructor initializer. + // public S2(object o) { } + Diagnostic(ErrorCode.ERR_UnexpectedOrMissingConstructorInitializerInRecord, "S2").WithLocation(7, 12), + // (11,12): error CS8862: A constructor declared in a record with parameter list must have 'this' constructor initializer. + // public S3(object o) { } + Diagnostic(ErrorCode.ERR_UnexpectedOrMissingConstructorInitializerInRecord, "S3").WithLocation(11, 12), + // (15,12): error CS8862: A constructor declared in a record with parameter list must have 'this' constructor initializer. + // public S4(object o) { } + Diagnostic(ErrorCode.ERR_UnexpectedOrMissingConstructorInitializerInRecord, "S4").WithLocation(15, 12)); + } + + [Fact] + public void ExplicitConstructors_03() + { + var source = +@"using static System.Console; +record struct S1 +{ + public S1(object o) : this() { } +} +record struct S2() +{ + public S2(object o) : this() { } +} +class Program +{ + static void Main() + { + WriteLine(new S1()); + WriteLine(new S2()); + } +}"; + CompileAndVerify(source, expectedOutput: +@"S1 { } +S2 { } +"); + } + + [Fact] + public void ExplicitConstructors_04() + { + var source = +@"using static System.Console; +record struct S0 +{ + internal object F = 0; + public S0() { } +} +record struct S1 +{ + internal object F = 1; + public S1(object o) : this() { F = o; } +} +record struct S2() +{ + internal object F = 2; + public S2(object o) : this() { F = o; } +} +class Program +{ + static void Main() + { + WriteLine(new S0().F); + WriteLine(new S1().F); + WriteLine(new S1(-1).F); + WriteLine(new S2().F); + WriteLine(new S2(-2).F); + } +}"; + CompileAndVerify(source, expectedOutput: +@"0 + +-1 +2 +-2 +"); + } + + [Fact] + [WorkItem(58328, "https://github.com/dotnet/roslyn/issues/58328")] + public void ExplicitConstructors_05() + { + var source = +@"record struct S3(char A) +{ + public S3(object o) : this() { } +} +record struct S4(char A, char B) +{ + public S4(object o) : this() { } +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (3,27): error CS8982: A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + // public S3(object o) : this() { } + Diagnostic(ErrorCode.ERR_RecordStructConstructorCallsDefaultConstructor, "this").WithLocation(3, 27), + // (7,27): error CS8982: A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + // public S4(object o) : this() { } + Diagnostic(ErrorCode.ERR_RecordStructConstructorCallsDefaultConstructor, "this").WithLocation(7, 27)); + } + + [Fact] + [WorkItem(58328, "https://github.com/dotnet/roslyn/issues/58328")] + public void ExplicitConstructors_06() + { + var source = +@"record struct S3(char A) +{ + internal object F = 3; + public S3(object o) : this() { F = o; } +} +record struct S4(char A, char B) +{ + internal object F = 4; + public S4(object o) : this() { F = o; } +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (4,27): error CS8982: A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + // public S3(object o) : this() { F = o; } + Diagnostic(ErrorCode.ERR_RecordStructConstructorCallsDefaultConstructor, "this").WithLocation(4, 27), + // (9,27): error CS8982: A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + // public S4(object o) : this() { F = o; } + Diagnostic(ErrorCode.ERR_RecordStructConstructorCallsDefaultConstructor, "this").WithLocation(9, 27)); + } + + [Fact] + public void ExplicitConstructors_07() + { + var source = +@"using static System.Console; +record struct S1 +{ + public S1(object o) : this() { } + public S1() { } +} +record struct S3(char A) +{ + public S3(object o) : this() { } + public S3() : this('a') { } +} +record struct S4(char A, char B) +{ + public S4(object o) : this() { } + public S4() : this('a', 'b') { } +} +class Program +{ + static void Main() + { + WriteLine(new S1()); + WriteLine(new S1(1)); + WriteLine(new S3()); + WriteLine(new S3(3)); + WriteLine(new S4()); + WriteLine(new S4(4)); + } +}"; + var verifier = CompileAndVerify(source, expectedOutput: +@"S1 { } +S1 { } +S3 { A = a } +S3 { A = a } +S4 { A = a, B = b } +S4 { A = a, B = b } +"); + verifier.VerifyIL("S1..ctor()", +@"{ + // Code size 1 (0x1) + .maxstack 0 + IL_0000: ret +}"); + verifier.VerifyIL("S1..ctor(object)", +@"{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: call ""S1..ctor()"" + IL_0006: ret +}"); + verifier.VerifyIL("S3..ctor()", +@"{ + // Code size 9 (0x9) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.s 97 + IL_0003: call ""S3..ctor(char)"" + IL_0008: ret +}"); + verifier.VerifyIL("S3..ctor(object)", +@"{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: call ""S3..ctor()"" + IL_0006: ret +}"); + verifier.VerifyIL("S4..ctor()", +@"{ + // Code size 11 (0xb) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: ldc.i4.s 97 + IL_0003: ldc.i4.s 98 + IL_0005: call ""S4..ctor(char, char)"" + IL_000a: ret +}"); + verifier.VerifyIL("S4..ctor(object)", +@"{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: call ""S4..ctor()"" + IL_0006: ret +}"); + } + + [Fact] + public void ExplicitConstructors_08() + { + var source = +@"record struct S2() +{ + public S2(object o) : this() { } + public S2() { } +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (3,27): error CS2121: The call is ambiguous between the following methods or properties: 'S2.S2()' and 'S2.S2()' + // public S2(object o) : this() { } + Diagnostic(ErrorCode.ERR_AmbigCall, "this").WithArguments("S2.S2()", "S2.S2()").WithLocation(3, 27), + // (4,12): error CS2111: Type 'S2' already defines a member called 'S2' with the same parameter types + // public S2() { } + Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "S2").WithArguments("S2", "S2").WithLocation(4, 12), + // (4,12): error CS8862: A constructor declared in a record with parameter list must have 'this' constructor initializer. + // public S2() { } + Diagnostic(ErrorCode.ERR_UnexpectedOrMissingConstructorInitializerInRecord, "S2").WithLocation(4, 12)); + } + + [Fact] + public void ExplicitConstructors_09() + { + var source = +@"record struct S1 +{ + public S1(object o) : base() { } +} +record struct S2() +{ + public S2(object o) : base() { } +} +record struct S3(char A) +{ + public S3(object o) : base() { } +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (3,12): error CS0522: 'S1': structs cannot call base class constructors + // public S1(object o) : base() { } + Diagnostic(ErrorCode.ERR_StructWithBaseConstructorCall, "S1").WithArguments("S1").WithLocation(3, 12), + // (7,12): error CS0522: 'S2': structs cannot call base class constructors + // public S2(object o) : base() { } + Diagnostic(ErrorCode.ERR_StructWithBaseConstructorCall, "S2").WithArguments("S2").WithLocation(7, 12), + // (7,27): error CS8862: A constructor declared in a record with parameter list must have 'this' constructor initializer. + // public S2(object o) : base() { } + Diagnostic(ErrorCode.ERR_UnexpectedOrMissingConstructorInitializerInRecord, "base").WithLocation(7, 27), + // (11,12): error CS0522: 'S3': structs cannot call base class constructors + // public S3(object o) : base() { } + Diagnostic(ErrorCode.ERR_StructWithBaseConstructorCall, "S3").WithArguments("S3").WithLocation(11, 12), + // (11,27): error CS8862: A constructor declared in a record with parameter list must have 'this' constructor initializer. + // public S3(object o) : base() { } + Diagnostic(ErrorCode.ERR_UnexpectedOrMissingConstructorInitializerInRecord, "base").WithLocation(11, 27)); + } } } diff --git a/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs b/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs index 7bc0e88624703..2160aa0efaca0 100644 --- a/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs @@ -7963,5 +7963,33 @@ End Sub SymbolDisplayPartKind.ParameterName, SymbolDisplayPartKind.Punctuation); } + + [Fact] + public void TestNullCheckedParameter() + { + var source = @" +class C +{ + void M(string s!!) + { + } +} +"; + + var comp = CreateCompilation(source); + var methodSymbol = comp.GetMember("C.M").GetPublicSymbol(); + + Verify(methodSymbol.ToDisplayParts(s_memberSignatureDisplayFormat), "void C.M(string s)", + SymbolDisplayPartKind.Keyword, + SymbolDisplayPartKind.Space, + SymbolDisplayPartKind.ClassName, + SymbolDisplayPartKind.Punctuation, + SymbolDisplayPartKind.MethodName, + SymbolDisplayPartKind.Punctuation, + SymbolDisplayPartKind.Keyword, + SymbolDisplayPartKind.Space, + SymbolDisplayPartKind.ParameterName, + SymbolDisplayPartKind.Punctuation); + } } } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolEqualityTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolEqualityTests.cs index 5c42667207182..5759e5fd9fd23 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolEqualityTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolEqualityTests.cs @@ -4,10 +4,12 @@ #nullable disable +using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Operations; using Roslyn.Test.Utilities; using Xunit; @@ -911,6 +913,62 @@ public static void M(A t) ); } + [Fact] + [WorkItem(58226, "https://github.com/dotnet/roslyn/issues/58226")] + public void LambdaSymbol() + { + var source = +@" +#nullable enable + +using System; +using System.Linq; + +M1(args => string.Join("" "", args.Select(a => a!.ToString()))); +void M1(Func? f) { } +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + + var syntaxTree = comp.SyntaxTrees[0]; + var root = syntaxTree.GetRoot(); + + var lambdaSyntax = root.DescendantNodes().OfType().First(); + var semanticModel1 = comp.GetSemanticModel(syntaxTree); + var semanticModel2 = comp.GetSemanticModel(syntaxTree); + + var lambdaSymbol = (IMethodSymbol)semanticModel1.GetSymbolInfo(lambdaSyntax).Symbol; + var p1 = lambdaSymbol.Parameters.Single(); + + var p2 = semanticModel2.GetDeclaredSymbol(lambdaSyntax.Parameter); + VerifyEquality(p1, p2, expectedIncludeNullability: true); + } + + [Fact] + [WorkItem(58226, "https://github.com/dotnet/roslyn/issues/58226")] + public void LambdaSymbol_02() + { + var source = +@"class Program +{ + static void Main() + { + var q = from i in new int[] { 4, 5 } where /*pos*/ + } +}"; + var comp = CreateCompilation(source); + var syntaxTree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(syntaxTree); + var syntaxNode = syntaxTree.GetRoot().DescendantNodes(). + OfType().Single(); + var operation = model.GetOperation(syntaxNode); + var lambdas = operation.Descendants().OfType(). + Select(op => op.Symbol.GetSymbol()).ToImmutableArray(); + Assert.Equal(2, lambdas.Length); + Assert.Equal(lambdas[0].SyntaxRef.Span, lambdas[1].SyntaxRef.Span); + Assert.NotEqual(lambdas[0], lambdas[1]); + } + private void VerifyEquality(ISymbol symbol1, ISymbol symbol2, bool expectedIncludeNullability) { // Symbol.Equals diff --git a/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs b/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs index aa08cc788a9cb..ffc6e4ad098c9 100644 --- a/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs +++ b/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs @@ -270,6 +270,8 @@ public void WarningLevel_2() case ErrorCode.WRN_CallerMemberNamePreferredOverCallerArgumentExpression: case ErrorCode.WRN_CallerArgumentExpressionAttributeHasInvalidParameterName: case ErrorCode.WRN_CallerArgumentExpressionAttributeSelfReferential: + case ErrorCode.WRN_NullCheckedHasDefaultNull: + case ErrorCode.WRN_NullCheckingOnNullableType: Assert.Equal(1, ErrorFacts.GetWarningLevel(errorCode)); break; case ErrorCode.WRN_MainIgnored: @@ -411,6 +413,8 @@ public void NullableWarnings() // Nullable-unrelated warnings in the C# 8 range should be added to this array. var nullableUnrelatedWarnings = new[] { + ErrorCode.WRN_NullCheckingOnNullableType, + ErrorCode.WRN_NullCheckedHasDefaultNull, ErrorCode.WRN_MissingNonNullTypesContextForAnnotation, ErrorCode.WRN_MissingNonNullTypesContextForAnnotationInGeneratedCode, ErrorCode.WRN_ImplicitCopyInReadOnlyMember, diff --git a/src/Compilers/CSharp/Test/Syntax/Generated/Syntax.Test.xml.Generated.cs b/src/Compilers/CSharp/Test/Syntax/Generated/Syntax.Test.xml.Generated.cs index f2e6069cccaf3..caa4938c8e8ad 100644 --- a/src/Compilers/CSharp/Test/Syntax/Generated/Syntax.Test.xml.Generated.cs +++ b/src/Compilers/CSharp/Test/Syntax/Generated/Syntax.Test.xml.Generated.cs @@ -581,7 +581,7 @@ private static Syntax.InternalSyntax.BracketedParameterListSyntax GenerateBracke => InternalSyntaxFactory.BracketedParameterList(InternalSyntaxFactory.Token(SyntaxKind.OpenBracketToken), new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SeparatedSyntaxList(), InternalSyntaxFactory.Token(SyntaxKind.CloseBracketToken)); private static Syntax.InternalSyntax.ParameterSyntax GenerateParameter() - => InternalSyntaxFactory.Parameter(new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), null, InternalSyntaxFactory.Identifier("Identifier"), null); + => InternalSyntaxFactory.Parameter(new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), null, InternalSyntaxFactory.Identifier("Identifier"), null, null); private static Syntax.InternalSyntax.FunctionPointerParameterSyntax GenerateFunctionPointerParameter() => InternalSyntaxFactory.FunctionPointerParameter(new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), GenerateIdentifierName()); @@ -3166,6 +3166,7 @@ public void TestParameterFactoryAndProperties() Assert.Equal(default, node.Modifiers); Assert.Null(node.Type); Assert.Equal(SyntaxKind.IdentifierToken, node.Identifier.Kind); + Assert.Null(node.ExclamationExclamationToken); Assert.Null(node.Default); AttachAndCheckDiagnostics(node); @@ -10509,7 +10510,7 @@ private static BracketedParameterListSyntax GenerateBracketedParameterList() => SyntaxFactory.BracketedParameterList(SyntaxFactory.Token(SyntaxKind.OpenBracketToken), new SeparatedSyntaxList(), SyntaxFactory.Token(SyntaxKind.CloseBracketToken)); private static ParameterSyntax GenerateParameter() - => SyntaxFactory.Parameter(new SyntaxList(), new SyntaxTokenList(), default(TypeSyntax), SyntaxFactory.Identifier("Identifier"), default(EqualsValueClauseSyntax)); + => SyntaxFactory.Parameter(new SyntaxList(), new SyntaxTokenList(), default(TypeSyntax), SyntaxFactory.Identifier("Identifier"), default(SyntaxToken), default(EqualsValueClauseSyntax)); private static FunctionPointerParameterSyntax GenerateFunctionPointerParameter() => SyntaxFactory.FunctionPointerParameter(new SyntaxList(), new SyntaxTokenList(), GenerateIdentifierName()); @@ -13094,8 +13095,9 @@ public void TestParameterFactoryAndProperties() Assert.Equal(default, node.Modifiers); Assert.Null(node.Type); Assert.Equal(SyntaxKind.IdentifierToken, node.Identifier.Kind()); + Assert.Equal(SyntaxKind.None, node.ExclamationExclamationToken.Kind()); Assert.Null(node.Default); - var newNode = node.WithAttributeLists(node.AttributeLists).WithModifiers(node.Modifiers).WithType(node.Type).WithIdentifier(node.Identifier).WithDefault(node.Default); + var newNode = node.WithAttributeLists(node.AttributeLists).WithModifiers(node.Modifiers).WithType(node.Type).WithIdentifier(node.Identifier).WithExclamationExclamationToken(node.ExclamationExclamationToken).WithDefault(node.Default); Assert.Equal(node, newNode); } diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/DeclarationParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/DeclarationParsingTests.cs index efa6a08fb337a..6d98f3a528743 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/DeclarationParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/DeclarationParsingTests.cs @@ -7239,6 +7239,780 @@ class C where T : struct? {} EOF(); } + [Fact] + public void TestMethodDeclarationNullValidation() + { + UsingStatement(@"void M(string name!!) { }", options: TestOptions.RegularPreview); + N(SyntaxKind.LocalFunctionStatement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "name"); + N(SyntaxKind.ExclamationExclamationToken); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + + [Fact] + public void TestOptParamMethodDeclarationWithNullValidation() + { + UsingStatement(@"void M(string name!! = null) { }", options: TestOptions.RegularPreview); + N(SyntaxKind.LocalFunctionStatement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "name"); + N(SyntaxKind.ExclamationExclamationToken); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NullLiteralExpression); + { + N(SyntaxKind.NullKeyword); + } + } + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + + [Fact] + public void TestOptParamMethodDeclarationWithNullValidationNoSpaces() + { + UsingStatement(@"void M(string name!!=null) { }", options: TestOptions.RegularPreview); + N(SyntaxKind.LocalFunctionStatement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "name"); + N(SyntaxKind.ExclamationExclamationToken); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NullLiteralExpression); + { + N(SyntaxKind.NullKeyword); + } + } + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + + [Fact] + public void TestNullCheckedArgList() + { + UsingStatement(@"void M(__arglist!) { }", options: TestOptions.RegularPreview, + // (1,17): error CS1003: Syntax error, ',' expected + // void M(__arglist!) { } + Diagnostic(ErrorCode.ERR_SyntaxError, "!").WithArguments(",", "!").WithLocation(1, 17)); + N(SyntaxKind.LocalFunctionStatement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.ArgListKeyword); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + + + [Fact] + public void TestNullCheckedArgWithLeadingSpace() + { + UsingStatement(@"void M(string name !!=null) { }", options: TestOptions.RegularPreview); + N(SyntaxKind.LocalFunctionStatement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "name"); + N(SyntaxKind.ExclamationExclamationToken); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NullLiteralExpression); + { + N(SyntaxKind.NullKeyword); + } + } + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + + [Fact] + public void TestNullCheckedArgWithLeadingNewLine() + { + UsingStatement(@"void M(string name!!=null) { }", options: TestOptions.RegularPreview); + N(SyntaxKind.LocalFunctionStatement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "name"); + N(SyntaxKind.ExclamationExclamationToken); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NullLiteralExpression); + { + N(SyntaxKind.NullKeyword); + } + } + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + + [Fact] + public void TestNullCheckedArgWithTrailingSpace() + { + UsingStatement(@"void M(string name!!= null) { }", options: TestOptions.RegularPreview); + N(SyntaxKind.LocalFunctionStatement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "name"); + N(SyntaxKind.ExclamationExclamationToken); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NullLiteralExpression); + { + N(SyntaxKind.NullKeyword); + } + } + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + + [Fact] + public void TestNullCheckedArgWithTrailingNewLine() + { + UsingStatement(@"void M(string name!!=null) { }", options: TestOptions.RegularPreview); + N(SyntaxKind.LocalFunctionStatement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "name"); + N(SyntaxKind.ExclamationExclamationToken); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NullLiteralExpression); + { + N(SyntaxKind.NullKeyword); + } + } + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + + [Fact] + public void TestNullCheckedArgWithSpaceInbetween() + { + UsingStatement(@"void M(string name! !=null) { }", options: TestOptions.RegularPreview, + // (1,21): error CS1525: Invalid expression term '!' + // void M(string name! !=null) { } + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "!").WithArguments("!").WithLocation(1, 21)); + N(SyntaxKind.LocalFunctionStatement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "name"); + M(SyntaxKind.ExclamationExclamationToken); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NullLiteralExpression); + { + N(SyntaxKind.NullKeyword); + } + } + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + + [Fact] + public void TestNullCheckedArgWithSpaceAfterParam() + { + UsingStatement(@"void M(string name !!=null) { }", options: TestOptions.RegularPreview); + N(SyntaxKind.LocalFunctionStatement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "name"); + N(SyntaxKind.ExclamationExclamationToken); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NullLiteralExpression); + { + N(SyntaxKind.NullKeyword); + } + } + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + + [Fact] + public void TestNullCheckedArgWithSpaceAfterBangs() + { + UsingStatement(@"void M(string name! ! =null) { }", options: TestOptions.RegularPreview, + // (1,21): error CS1525: Invalid expression term '!' + // void M(string name! ! =null) { } + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "!").WithArguments("!").WithLocation(1, 21)); + N(SyntaxKind.LocalFunctionStatement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "name"); + M(SyntaxKind.ExclamationExclamationToken); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NullLiteralExpression); + { + N(SyntaxKind.NullKeyword); + } + } + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + + [Fact] + public void TestNullCheckedArgWithSpaceBeforeBangs() + { + UsingStatement(@"void M(string name ! !=null) { }", options: TestOptions.RegularPreview, + // (1,22): error CS1525: Invalid expression term '!' + // void M(string name ! !=null) { } + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "!").WithArguments("!").WithLocation(1, 22)); + N(SyntaxKind.LocalFunctionStatement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "name"); + M(SyntaxKind.ExclamationExclamationToken); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NullLiteralExpression); + { + N(SyntaxKind.NullKeyword); + } + } + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + + [Fact] + public void TestNullCheckedArgWithSpaceAfterEquals() + { + UsingStatement(@"void M(string name!!= null) { }", options: TestOptions.RegularPreview); + N(SyntaxKind.LocalFunctionStatement); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "name"); + N(SyntaxKind.ExclamationExclamationToken); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NullLiteralExpression); + { + N(SyntaxKind.NullKeyword); + } + } + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + + [Fact] + public void TestNullCheckedMethod() + { + UsingTree(@" +class C +{ + public void M(string x!!) { } +}", options: TestOptions.RegularPreview); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "x"); + N(SyntaxKind.ExclamationExclamationToken); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + } + + [Fact] + public void TestNullCheckedConstructor() + { + UsingTree(@" +class C +{ + public C(string x!!) { } +}", options: TestOptions.RegularPreview); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.ConstructorDeclaration); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "x"); + N(SyntaxKind.ExclamationExclamationToken); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + } + + + [Fact] + public void TestNullCheckedOperator() + { + UsingTree(@" +class Box +{ + public static int operator+ (Box b!!, Box c) + { + return 2; + } +}", options: TestOptions.RegularPreview); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.OperatorDeclaration); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.StaticKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.OperatorKeyword); + N(SyntaxKind.PlusToken); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.IdentifierToken); + N(SyntaxKind.ExclamationExclamationToken); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.ReturnStatement); + { + N(SyntaxKind.ReturnKeyword); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.EndOfFileToken); + } + + [Fact] + public void TestAnonymousDelegateNullChecking() + { + UsingTree(@" +delegate void Del(int x!!); +Del d = delegate(int k!!) { /* ... */ };", options: TestOptions.RegularPreview); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.DelegateDeclaration); + { + N(SyntaxKind.DelegateKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "Del"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.IdentifierToken, "x"); + N(SyntaxKind.ExclamationExclamationToken); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.GlobalStatement); + { + N(SyntaxKind.LocalDeclarationStatement); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Del"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "d"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.AnonymousMethodExpression); + { + N(SyntaxKind.DelegateKeyword); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.IdentifierToken, "k"); + N(SyntaxKind.ExclamationExclamationToken); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + } + } + N(SyntaxKind.SemicolonToken); + } + } + N(SyntaxKind.EndOfFileToken); + } + } + [Fact, WorkItem(30102, "https://github.com/dotnet/roslyn/issues/30102")] public void IncompleteGenericInBaseList1() { diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/LambdaParameterParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/LambdaParameterParsingTests.cs index de2bf0dd18253..63f1721dfc3ff 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/LambdaParameterParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/LambdaParameterParsingTests.cs @@ -4,6 +4,9 @@ #nullable disable +using System; +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; using Xunit.Abstractions; @@ -663,5 +666,1225 @@ public void Arglist_03() } EOF(); } + + [Fact] + public void TestLambdaWithNullValidation() + { + UsingDeclaration("Func func1 = x!! => x + \"1\";", options: TestOptions.RegularPreview); + N(SyntaxKind.FieldDeclaration); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.GenericName); + N(SyntaxKind.IdentifierToken, "Func"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "func1"); + } + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.SimpleLambdaExpression); + { + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierToken); + N(SyntaxKind.ExclamationExclamationToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.AddExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.PlusToken); + N(SyntaxKind.StringLiteralExpression); + { + N(SyntaxKind.StringLiteralToken); + } + } + } + } + N(SyntaxKind.SemicolonToken); + } + } + + [Fact] + public void TestLambdaWithNullValidationParams() + { + UsingDeclaration("Func func1 = (x!!, y) => x == y;", options: TestOptions.RegularPreview); + N(SyntaxKind.FieldDeclaration); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.GenericName); + N(SyntaxKind.IdentifierToken, "Func"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.BoolKeyword); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "func1"); + } + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierToken, "x"); + N(SyntaxKind.ExclamationExclamationToken); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierToken, "y"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.EqualsExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.EqualsEqualsToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken); + } + } + } + } + N(SyntaxKind.SemicolonToken); + } + } + + [Fact] + public void TestNullCheckedSingleParamInParens() + { + UsingDeclaration("Func func1 = (x!!) => x;", options: TestOptions.RegularPreview); + N(SyntaxKind.FieldDeclaration); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.GenericName); + N(SyntaxKind.IdentifierToken, "Func"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "func1"); + } + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierToken, "x"); + N(SyntaxKind.ExclamationExclamationToken); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken); + } + } + } + N(SyntaxKind.SemicolonToken); + } + } + + [Fact] + public void TestNullCheckedSingleParamNoSpaces() + { + UsingDeclaration("Func func1 = x!!=>x;", options: TestOptions.RegularPreview, expectedErrors: new DiagnosticDescription[0]); + N(SyntaxKind.FieldDeclaration); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.GenericName); + N(SyntaxKind.IdentifierToken, "Func"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "func1"); + } + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.SimpleLambdaExpression); + { + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierToken, "x"); + N(SyntaxKind.ExclamationExclamationToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken); + } + } + } + N(SyntaxKind.SemicolonToken); + } + } + + [Fact] + public void TestNullCheckedTypedSingleParamInParen() + { + UsingDeclaration("Func func1 = (int x!!) => x;", options: TestOptions.RegularPreview); + N(SyntaxKind.FieldDeclaration); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.GenericName); + N(SyntaxKind.IdentifierToken, "Func"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "func1"); + } + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.IdentifierToken, "x"); + N(SyntaxKind.ExclamationExclamationToken); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken); + } + } + } + N(SyntaxKind.SemicolonToken); + } + } + + [Fact] + public void TestNullCheckedTypedManyParams() + { + UsingDeclaration("Func func1 = (int x!!, int y) => x;", options: TestOptions.RegularPreview); + N(SyntaxKind.FieldDeclaration); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.GenericName); + N(SyntaxKind.IdentifierToken, "Func"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "func1"); + } + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.IdentifierToken, "x"); + N(SyntaxKind.ExclamationExclamationToken); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.IdentifierToken, "y"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken); + } + } + } + N(SyntaxKind.SemicolonToken); + } + } + + [Fact] + public void TestManyNullCheckedTypedParams() + { + UsingDeclaration("Func func1 = (int x!!, int y!!) => x;", options: TestOptions.RegularPreview); + N(SyntaxKind.FieldDeclaration); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.GenericName); + N(SyntaxKind.IdentifierToken, "Func"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "func1"); + } + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.IdentifierToken, "x"); + N(SyntaxKind.ExclamationExclamationToken); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.IdentifierToken, "y"); + N(SyntaxKind.ExclamationExclamationToken); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken); + } + } + } + N(SyntaxKind.SemicolonToken); + } + } + + [Fact] + public void TestNullCheckedNoParams() + { + UsingDeclaration("Func func1 = (!!) => 42;", options: TestOptions.RegularPreview, expectedErrors: new DiagnosticDescription[] + { + // (1,22): error CS1525: Invalid expression term ')' + // Func func1 = (!!) => 42; + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ")").WithArguments(")").WithLocation(1, 22), + // (1,24): error CS1003: Syntax error, ',' expected + // Func func1 = (!!) => 42; + Diagnostic(ErrorCode.ERR_SyntaxError, "=>").WithArguments(",", "=>").WithLocation(1, 24) + }); + N(SyntaxKind.FieldDeclaration); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Func"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "func1"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.ParenthesizedExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.LogicalNotExpression); + { + N(SyntaxKind.ExclamationToken); + N(SyntaxKind.LogicalNotExpression); + { + N(SyntaxKind.ExclamationToken); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + } + } + N(SyntaxKind.CloseParenToken); + } + } + } + } + N(SyntaxKind.SemicolonToken); + } + } + + [Fact] + public void TestNullCheckedDiscard() + { + UsingDeclaration("Func func1 = (_!!) => 42;", options: TestOptions.RegularPreview); + N(SyntaxKind.FieldDeclaration); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.GenericName); + N(SyntaxKind.IdentifierToken, "Func"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "func1"); + } + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierToken, "_"); + N(SyntaxKind.ExclamationExclamationToken); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken); + } + } + } + N(SyntaxKind.SemicolonToken); + } + } + + [Fact] + public void TestNullCheckedSyntaxCorrection0() + { + UsingDeclaration("Func func0 = x!=> x;", options: TestOptions.RegularPreview, + // (1,31): error CS1003: Syntax error, '=>' expected + // Func func0 = x!=> x; + Diagnostic(ErrorCode.ERR_SyntaxError, "!=").WithArguments("=>", "!=").WithLocation(1, 31), + // (1,31): error CS1525: Invalid expression term '!=' + // Func func0 = x!=> x; + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "!=").WithArguments("!=").WithLocation(1, 31), + // (1,33): error CS1525: Invalid expression term '>' + // Func func0 = x!=> x; + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ">").WithArguments(">").WithLocation(1, 33)); + N(SyntaxKind.FieldDeclaration); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Func"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "func0"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.SimpleLambdaExpression); + { + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierToken, "x"); + } + M(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.NotEqualsExpression); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.ExclamationEqualsToken); + N(SyntaxKind.GreaterThanExpression); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.GreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + } + } + } + } + } + } + N(SyntaxKind.SemicolonToken); + } + } + + [Fact] + public void TestNullCheckedSyntaxCorrection1() + { + UsingDeclaration("Func func1 = x !=> x;", options: TestOptions.RegularPreview, + // (1,32): error CS1003: Syntax error, '=>' expected + // Func func1 = x !=> x; + Diagnostic(ErrorCode.ERR_SyntaxError, "!=").WithArguments("=>", "!=").WithLocation(1, 32), + // (1,32): error CS1525: Invalid expression term '!=' + // Func func1 = x !=> x; + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "!=").WithArguments("!=").WithLocation(1, 32), + // (1,34): error CS1525: Invalid expression term '>' + // Func func1 = x !=> x; + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ">").WithArguments(">").WithLocation(1, 34)); + N(SyntaxKind.FieldDeclaration); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Func"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "func1"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.SimpleLambdaExpression); + { + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierToken, "x"); + } + M(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.NotEqualsExpression); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.ExclamationEqualsToken); + N(SyntaxKind.GreaterThanExpression); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.GreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + } + } + } + } + } + } + N(SyntaxKind.SemicolonToken); + } + } + + [Fact] + public void TestNullCheckedSyntaxCorrection2() + { + UsingDeclaration("Func func2 = x != > x;", options: TestOptions.RegularPreview, + // (1,32): error CS1003: Syntax error, '=>' expected + // Func func2 = x != > x; + Diagnostic(ErrorCode.ERR_SyntaxError, "!=").WithArguments("=>", "!=").WithLocation(1, 32), + // (1,32): error CS1525: Invalid expression term '!=' + // Func func2 = x != > x; + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "!=").WithArguments("!=").WithLocation(1, 32), + // (1,35): error CS1525: Invalid expression term '>' + // Func func2 = x != > x; + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ">").WithArguments(">").WithLocation(1, 35)); + N(SyntaxKind.FieldDeclaration); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Func"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "func2"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.SimpleLambdaExpression); + { + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierToken, "x"); + } + M(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.NotEqualsExpression); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.ExclamationEqualsToken); + N(SyntaxKind.GreaterThanExpression); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.GreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + } + } + } + } + } + } + N(SyntaxKind.SemicolonToken); + } + } + + [Fact] + public void TestNullCheckedSyntaxCorrection3() + { + UsingDeclaration("Func func3 = x! => x;", options: TestOptions.RegularPreview, + // (1,33): error CS1003: Syntax error, ',' expected + // Func func3 = x! => x; + Diagnostic(ErrorCode.ERR_SyntaxError, "=>").WithArguments(",", "=>").WithLocation(1, 33)); + N(SyntaxKind.FieldDeclaration); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Func"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "func3"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.SuppressNullableWarningExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.ExclamationToken); + } + } + } + } + N(SyntaxKind.SemicolonToken); + } + } + + [Fact] + public void TestNullCheckedSyntaxCorrection4() + { + UsingDeclaration("Func func4 = x ! => x;", options: TestOptions.RegularPreview, + // (1,34): error CS1003: Syntax error, ',' expected + // Func func4 = x ! => x; + Diagnostic(ErrorCode.ERR_SyntaxError, "=>").WithArguments(",", "=>").WithLocation(1, 34)); + N(SyntaxKind.FieldDeclaration); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Func"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "func4"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.SuppressNullableWarningExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.ExclamationToken); + } + } + } + } + N(SyntaxKind.SemicolonToken); + } + } + + [Fact] + public void TestNullCheckedSyntaxCorrection5() + { + UsingDeclaration("Func func5 = x !!=> x;", options: TestOptions.RegularPreview); + N(SyntaxKind.FieldDeclaration); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Func"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "func5"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.SimpleLambdaExpression); + { + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierToken, "x"); + N(SyntaxKind.ExclamationExclamationToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + } + } + } + } + N(SyntaxKind.SemicolonToken); + } + } + + [Fact] + public void TestNullCheckedSyntaxCorrection6() + { + UsingDeclaration("Func func6 = x !!= > x;", options: TestOptions.RegularPreview, + // (1,36): error CS1525: Invalid expression term '>' + // Func func6 = x !!= > x; + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ">").WithArguments(">").WithLocation(1, 36)); + N(SyntaxKind.FieldDeclaration); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Func"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "func6"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.SimpleLambdaExpression); + { + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierToken, "x"); + N(SyntaxKind.ExclamationExclamationToken); + } + M(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + } + } + } + } + N(SyntaxKind.SemicolonToken); + } + } + + [Fact] + public void TestNullCheckedSyntaxCorrection7() + { + UsingDeclaration("Func func7 = x!! => x;", options: TestOptions.RegularPreview); + N(SyntaxKind.FieldDeclaration); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Func"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "func7"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.SimpleLambdaExpression); + { + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierToken, "x"); + N(SyntaxKind.ExclamationExclamationToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + } + } + } + } + N(SyntaxKind.SemicolonToken); + } + } + + [Fact] + public void TestNullCheckedSyntaxCorrection8() + { + UsingDeclaration("Func func8 = x! !=> x;", options: TestOptions.RegularPreview, + // (1,33): error CS1525: Invalid expression term '!' + // Func func8 = x! !=> x; + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "!").WithArguments("!").WithLocation(1, 33)); + N(SyntaxKind.FieldDeclaration); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Func"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "func8"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.SimpleLambdaExpression); + { + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierToken, "x"); + M(SyntaxKind.ExclamationExclamationToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + } + } + } + } + N(SyntaxKind.SemicolonToken); + } + } + + [Fact] + public void TestNullCheckedSyntaxCorrection9() + { + UsingDeclaration("Func func9 = x! ! => x;", options: TestOptions.RegularPreview, + // (1,33): error CS1525: Invalid expression term '!' + // Func func9 = x! ! => x; + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "!").WithArguments("!").WithLocation(1, 33)); + N(SyntaxKind.FieldDeclaration); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Func"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "func9"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.SimpleLambdaExpression); + { + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierToken, "x"); + M(SyntaxKind.ExclamationExclamationToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + } + } + } + } + N(SyntaxKind.SemicolonToken); + } + } + + [Fact] + public void AsyncAwaitInLambda() + { + UsingStatement(@"F(async () => await Task.FromResult(4));"); + N(SyntaxKind.ExpressionStatement); + { + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "F"); + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.AsyncKeyword); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.AwaitExpression); + { + N(SyntaxKind.AwaitKeyword); + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.SimpleMemberAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Task"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "FromResult"); + } + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "4"); + } + } + N(SyntaxKind.CloseParenToken); + } + } + } + } + } + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } } } diff --git a/src/Compilers/Core/Portable/CodeGen/PrivateImplementationDetails.cs b/src/Compilers/Core/Portable/CodeGen/PrivateImplementationDetails.cs index dfdf3bf89e5cd..664c23bb91ed3 100644 --- a/src/Compilers/Core/Portable/CodeGen/PrivateImplementationDetails.cs +++ b/src/Compilers/Core/Portable/CodeGen/PrivateImplementationDetails.cs @@ -14,7 +14,6 @@ using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CodeGen @@ -29,6 +28,9 @@ internal sealed class PrivateImplementationDetails : DefaultTypeDef, Cci.INamesp // value, and data field offsets are unique within the method, not across all methods. internal const string SynthesizedStringHashFunctionName = "ComputeStringHash"; + internal const string SynthesizedThrowIfNullFunctionName = "ThrowIfNull"; + internal const string SynthesizedThrowFunctionName = "Throw"; + private readonly CommonPEModuleBuilder _moduleBuilder; //the module builder private readonly Cci.ITypeReference _systemObject; //base type private readonly Cci.ITypeReference _systemValueType; //base for nested structs diff --git a/src/Compilers/Core/Portable/Microsoft.CodeAnalysis.csproj b/src/Compilers/Core/Portable/Microsoft.CodeAnalysis.csproj index 8535f767ca531..2e8b0ce4a4915 100644 --- a/src/Compilers/Core/Portable/Microsoft.CodeAnalysis.csproj +++ b/src/Compilers/Core/Portable/Microsoft.CodeAnalysis.csproj @@ -62,6 +62,7 @@ + diff --git a/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs b/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs index 354c4cc660e09..9911d367df9bd 100644 --- a/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs +++ b/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs @@ -127,6 +127,7 @@ public static ControlFlowGraph Create(IOperation body, ControlFlowGraph? parent case OperationKind.AnonymousFunction: Debug.Assert(captureIdDispenser != null); var anonymousFunction = (IAnonymousFunctionOperation)body; + builder.VisitNullChecks(anonymousFunction, anonymousFunction.Symbol.Parameters); builder.VisitStatement(anonymousFunction.Body); break; default: @@ -1380,7 +1381,6 @@ private void UnconditionalBranch(BasicBlockBuilder nextBlock) public override IOperation? VisitBlock(IBlockOperation operation, int? captureIdForResult) { StartVisitingStatement(operation); - EnterRegion(new RegionBuilder(ControlFlowRegionKind.LocalLifetime, locals: operation.Locals)); VisitStatements(operation.Operations); LeaveRegion(); @@ -1481,6 +1481,12 @@ bool visitPossibleUsingDeclarationInLabel(ILabeledOperation labelOperation) EnterRegion(new RegionBuilder(ControlFlowRegionKind.LocalLifetime, locals: operation.Locals)); + // https://github.com/dotnet/roslyn/issues/58335: this implementation doesn't handle record primary constructors + if (operation.SemanticModel!.GetDeclaredSymbol(operation.Syntax) is IMethodSymbol method) + { + VisitNullChecks(operation, method.Parameters); + } + if (operation.Initializer != null) { VisitStatement(operation.Initializer); @@ -1496,10 +1502,156 @@ bool visitPossibleUsingDeclarationInLabel(ILabeledOperation labelOperation) { StartVisitingStatement(operation); + // https://github.com/dotnet/roslyn/issues/58335: do we need to use SemanticModel here? + var member = operation.SemanticModel!.GetDeclaredSymbol(operation.Syntax); + Debug.Assert(captureIdForResult is null); + VisitNullChecks(operation, ((IMethodSymbol)member!).Parameters); + VisitMethodBodyBaseOperation(operation); return FinishVisitingStatement(operation); } + private void VisitNullChecks(IOperation operation, ImmutableArray parameters) + { + var temp = _currentStatement; + foreach (var param in parameters) + { + if (param.IsNullChecked) + { + // https://github.com/dotnet/roslyn/issues/58335: do we need to use SemanticModel here? + var check = GenerateNullCheckForParameter(param, operation.Syntax, ((Operation)operation).OwningSemanticModel!); + _currentStatement = check; + VisitConditional(check, captureIdForResult: null); + } + } + _currentStatement = temp; + } + + private ConditionalOperation GenerateNullCheckForParameter(IParameterSymbol parameter, SyntaxNode syntax, SemanticModel semanticModel) + { + Debug.Assert(parameter.Language == LanguageNames.CSharp); + var paramReference = new ParameterReferenceOperation(parameter, semanticModel, syntax, parameter.Type, isImplicit: true); + var boolType = _compilation.GetSpecialType(SpecialType.System_Boolean); + + IOperation conditionOp; + if (ITypeSymbolHelpers.IsNullableType(parameter.Type)) + { + // https://github.com/dotnet/roslyn/issues/58335: is there a better way to get the HasValue symbol here? + // This way doesn't work with compilation.MakeMemberMissing for testing + var nullableHasValueProperty = parameter.Type.GetMembers(nameof(Nullable.HasValue)).FirstOrDefault() as IPropertySymbol; + var nullableHasValueGet = nullableHasValueProperty?.GetMethod; + if (nullableHasValueGet is null) + { + conditionOp = new UnaryOperation( + UnaryOperatorKind.Not, + new InvalidOperation( + ImmutableArray.Create(paramReference), + semanticModel, + syntax, + boolType, + constantValue: null, + isImplicit: true), + isLifted: false, + isChecked: false, + operatorMethod: null, + semanticModel, + syntax, + boolType, + constantValue: null, + isImplicit: true); + } + else + { + conditionOp = new UnaryOperation( + UnaryOperatorKind.Not, + new InvocationOperation( + targetMethod: nullableHasValueGet, + instance: paramReference, + isVirtual: false, + arguments: ImmutableArray.Empty, + semanticModel, + syntax, + boolType, + isImplicit: true), + isLifted: false, + isChecked: false, + operatorMethod: null, + semanticModel, + syntax, + boolType, + constantValue: null, + isImplicit: true); + } + } + else + { + conditionOp = new BinaryOperation( + BinaryOperatorKind.Equals, + paramReference, + new LiteralOperation(semanticModel, syntax, parameter.Type, ConstantValue.Null, isImplicit: true), + isLifted: false, + isChecked: false, + isCompareText: false, + operatorMethod: null, + unaryOperatorMethod: null, + semanticModel, + syntax, + boolType, + constantValue: null, + isImplicit: true); + } + + var paramNameLiteral = new LiteralOperation(semanticModel, syntax, _compilation.GetSpecialType(SpecialType.System_String), ConstantValue.Create(parameter.Name), isImplicit: true); + var argumentNullExceptionMethod = (IMethodSymbol?)_compilation.CommonGetWellKnownTypeMember(WellKnownMember.System_ArgumentNullException__ctorString)?.GetISymbol(); + var argumentNullExceptionType = argumentNullExceptionMethod?.ContainingType; + + // Occurs when a member is missing. + IOperation argumentNullExceptionObject; + if (argumentNullExceptionMethod is null) + { + argumentNullExceptionObject = new InvalidOperation( + children: ImmutableArray.Create((IOperation)paramNameLiteral), + semanticModel, + syntax, + argumentNullExceptionType, + constantValue: null, + isImplicit: true); + } + else + { + argumentNullExceptionObject = new ObjectCreationOperation( + argumentNullExceptionMethod, + initializer: null, + ImmutableArray.Create( + new ArgumentOperation( + ArgumentKind.Explicit, + parameter: argumentNullExceptionMethod.Parameters[0], + value: paramNameLiteral, + OperationFactory.IdentityConversion, + OperationFactory.IdentityConversion, + semanticModel, + syntax, + isImplicit: true)), + semanticModel, + syntax, + argumentNullExceptionType, + constantValue: null, + isImplicit: true); + } + + IOperation whenTrue = new ExpressionStatementOperation( + new ThrowOperation( + argumentNullExceptionObject, + semanticModel, + syntax, + argumentNullExceptionType, + isImplicit: true), + semanticModel, + syntax, + isImplicit: true); + return new ConditionalOperation(conditionOp, whenTrue, whenFalse: null, isRef: false, semanticModel, syntax, boolType, constantValue: null, isImplicit: true); + } + private void VisitMethodBodyBaseOperation(IMethodBodyBaseOperation operation) { Debug.Assert(_currentStatement == operation); @@ -2036,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) @@ -6131,6 +6305,7 @@ IOperation visitAndCaptureInitializer(IPropertySymbol initializedProperty, IOper private IOperation? VisitLocalFunctionAsRoot(ILocalFunctionOperation operation) { Debug.Assert(_currentStatement == null); + VisitNullChecks(operation, operation.Symbol.Parameters); VisitMethodBodies(operation.Body, operation.IgnoredBody); return null; } diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index ff766de8af0e0..02161a15fe460 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -76,6 +76,7 @@ Microsoft.CodeAnalysis.IncrementalValueProvider.IncrementalValueProvider Microsoft.CodeAnalysis.IncrementalValueProviderExtensions Microsoft.CodeAnalysis.IncrementalValuesProvider Microsoft.CodeAnalysis.IncrementalValuesProvider.IncrementalValuesProvider() -> void +Microsoft.CodeAnalysis.IParameterSymbol.IsNullChecked.get -> bool Microsoft.CodeAnalysis.ISymbol.MetadataToken.get -> int Microsoft.CodeAnalysis.ISyntaxContextReceiver Microsoft.CodeAnalysis.ISyntaxContextReceiver.OnVisitSyntaxNode(Microsoft.CodeAnalysis.GeneratorSyntaxContext context) -> void diff --git a/src/Compilers/Core/Portable/Symbols/IParameterSymbol.cs b/src/Compilers/Core/Portable/Symbols/IParameterSymbol.cs index 7c77dbe8962c6..32ac67218109f 100644 --- a/src/Compilers/Core/Portable/Symbols/IParameterSymbol.cs +++ b/src/Compilers/Core/Portable/Symbols/IParameterSymbol.cs @@ -91,5 +91,10 @@ public interface IParameterSymbol : ISymbol /// source or metadata. /// new IParameterSymbol OriginalDefinition { get; } + + /// + /// True if the compiler will synthesize a null check for this parameter (the parameter is declared in source with a !! following the parameter name). + /// + bool IsNullChecked { get; } } } diff --git a/src/Compilers/Core/Portable/WellKnownMember.cs b/src/Compilers/Core/Portable/WellKnownMember.cs index 7df1c2d23a983..b8ce5205655ec 100644 --- a/src/Compilers/Core/Portable/WellKnownMember.cs +++ b/src/Compilers/Core/Portable/WellKnownMember.cs @@ -505,6 +505,8 @@ internal enum WellKnownMember System_Threading_CancellationTokenSource__Token, System_Threading_CancellationTokenSource__Dispose, + System_ArgumentNullException__ctorString, + System_Runtime_CompilerServices_NativeIntegerAttribute__ctor, System_Runtime_CompilerServices_NativeIntegerAttribute__ctorTransformFlags, diff --git a/src/Compilers/Core/Portable/WellKnownMembers.cs b/src/Compilers/Core/Portable/WellKnownMembers.cs index 5d91cc3c17988..00f6ae1bcf8a4 100644 --- a/src/Compilers/Core/Portable/WellKnownMembers.cs +++ b/src/Compilers/Core/Portable/WellKnownMembers.cs @@ -3472,7 +3472,15 @@ static WellKnownMembers() (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Threading_CancellationTokenSource - WellKnownType.ExtSentinel), // DeclaringTypeId 0, // Arity 0, // Method Signature - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, // Return Type + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, // Return Type, + + // System_ArgumentNullException__ctorString + (byte)MemberFlags.Constructor, // Flags + (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_ArgumentNullException - WellKnownType.ExtSentinel), // DeclaringTypeId + 0, // Arity + 1, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, // Return Type + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, // Argument // System_Runtime_CompilerServices_NativeIntegerAttribute__ctor (byte)MemberFlags.Constructor, // Flags @@ -3963,6 +3971,7 @@ static WellKnownMembers() "CreateLinkedTokenSource", // System_Threading_CancellationTokenSource__CreateLinkedTokenSource "Token", // System_Threading_CancellationTokenSource__Token "Dispose", // System_Threading_CancellationTokenSource__Dispose + ".ctor", // System_ArgumentNullException__ctorString ".ctor", // System_Runtime_CompilerServices_NativeIntegerAttribute__ctor ".ctor", // System_Runtime_CompilerServices_NativeIntegerAttribute__ctorTransformFlags "Append", // System_Text_StringBuilder__AppendString diff --git a/src/Compilers/Core/Portable/WellKnownTypes.cs b/src/Compilers/Core/Portable/WellKnownTypes.cs index 40b5d73a187a7..1ee9254677987 100644 --- a/src/Compilers/Core/Portable/WellKnownTypes.cs +++ b/src/Compilers/Core/Portable/WellKnownTypes.cs @@ -315,8 +315,9 @@ internal enum WellKnownType System_Runtime_CompilerServices_DefaultInterpolatedStringHandler, - NextAvailable, + System_ArgumentNullException, + NextAvailable, // Remember to update the AllWellKnownTypes tests when making changes here } @@ -623,6 +624,7 @@ internal static class WellKnownTypes "System.Text.StringBuilder", "System.Runtime.CompilerServices.DefaultInterpolatedStringHandler", + "System.ArgumentNullException", }; private static readonly Dictionary s_nameToTypeIdMap = new Dictionary((int)Count); @@ -675,7 +677,7 @@ private static void AssertEnumAndTableInSync() typeIdName = typeIdName.Substring(0, separator); } - Debug.Assert(name == typeIdName, "Enum name and type name must match"); + Debug.Assert(name == typeIdName, $"Enum name ({typeIdName}) and type name ({name}) must match at {i}"); } Debug.Assert((int)WellKnownType.ExtSentinel == 255); diff --git a/src/Compilers/VisualBasic/Portable/Symbols/ParameterSymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/ParameterSymbol.vb index 9a726b552ca70..9104a8864e97f 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/ParameterSymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/ParameterSymbol.vb @@ -359,6 +359,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols End Get End Property + Private ReadOnly Property IsNullChecked As Boolean Implements IParameterSymbol.IsNullChecked + Get + Return False + End Get + End Property + Public Overrides Sub Accept(visitor As SymbolVisitor) visitor.VisitParameter(Me) End Sub diff --git a/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs b/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs index de4c4969865b7..e036949a7be05 100644 --- a/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs +++ b/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs @@ -4470,6 +4470,19 @@ class C { }", Namespace("NS")); } + [Theory] + [CombinatorialData] + public async Task NullCheckedParameterClassification(TestHost testHost) + { + await TestAsync( +@" +class C +{ + void M(string s!!) { } +}", + testHost); + } + [Theory] [CombinatorialData] [WorkItem(57184, "https://github.com/dotnet/roslyn/issues/57184")] diff --git a/src/EditorFeatures/CSharpTest/CodeActions/EnableNullable/EnableNullableTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/EnableNullable/EnableNullableTests.cs index ff0f0d28ef2b9..c81023d17436b 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/EnableNullable/EnableNullableTests.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/EnableNullable/EnableNullableTests.cs @@ -5,6 +5,7 @@ using System; using System.Globalization; using System.Linq; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp; @@ -25,8 +26,8 @@ public class EnableNullableTests var project = solution.GetRequiredProject(projectId); var document = project.Documents.First(); - // Only the input solution contains '#nullable enable' - if (!document.GetTextSynchronously(CancellationToken.None).ToString().Contains("#nullable enable")) + // Only the input solution contains '#nullable enable' or '#nullable enable' in the first document + if (!Regex.IsMatch(document.GetTextSynchronously(CancellationToken.None).ToString(), "#nullable ?enable")) { var compilationOptions = (CSharpCompilationOptions)solution.GetRequiredProject(projectId).CompilationOptions!; solution = solution.WithProjectCompilationOptions(projectId, compilationOptions.WithNullableContextOptions(NullableContextOptions.Enable)); @@ -35,16 +36,43 @@ public class EnableNullableTests return solution; }; - [Fact] - public async Task EnabledOnNullableEnable() + private static readonly Func s_enableNullableInFixedSolutionFromRestoreKeyword = + (solution, projectId) => + { + var project = solution.GetRequiredProject(projectId); + var document = project.Documents.First(); + + // Only the input solution contains '#nullable restore' or '#nullable restore' in the first document + if (!Regex.IsMatch(document.GetTextSynchronously(CancellationToken.None).ToString(), "#nullable ?restore")) + { + var compilationOptions = (CSharpCompilationOptions)solution.GetRequiredProject(projectId).CompilationOptions!; + solution = solution.WithProjectCompilationOptions(projectId, compilationOptions.WithNullableContextOptions(NullableContextOptions.Enable)); + } + + return solution; + }; + + private static readonly Func s_enableNullableInFixedSolutionFromDisableKeyword = + s_enableNullableInFixedSolutionFromRestoreKeyword; + + [Theory] + [InlineData("$$#nullable enable")] + [InlineData("#$$nullable enable")] + [InlineData("#null$$able enable")] + [InlineData("#nullable$$ enable")] + [InlineData("#nullable $$ enable")] + [InlineData("#nullable $$enable")] + [InlineData("#nullable ena$$ble")] + [InlineData("#nullable enable$$")] + public async Task EnabledOnNullableEnable(string directive) { - var code1 = @" -#nullable enable$$ + var code1 = $@" +{directive} class Example -{ +{{ string? value; -} +}} "; var code2 = @" class Example2 @@ -573,17 +601,217 @@ public async Task DisabledForUnsupportedLanguageVersion(LanguageVersion language }.RunAsync(); } - [Fact] - public async Task DisabledOnNullableDisable() + [Theory] + [InlineData("$$#nullable restore")] + [InlineData("#$$nullable restore")] + [InlineData("#null$$able restore")] + [InlineData("#nullable$$ restore")] + [InlineData("#nullable $$ restore")] + [InlineData("#nullable $$restore")] + [InlineData("#nullable res$$tore")] + [InlineData("#nullable restore$$")] + public async Task EnabledOnNullableRestore(string directive) { - var code = @" -#nullable disable$$ + var code1 = $@" +{directive} + +class Example +{{ + string value; +}} +"; + var code2 = @" +class Example2 +{ + string value; +} +"; + var code3 = @" +class Example3 +{ +#nullable enable + string? value; +#nullable restore +} +"; + var code4 = @" +#nullable disable + +class Example4 +{ + string value; +} +"; + + var fixedDirective = directive.Replace("$$", "").Replace("restore", "disable"); + + var fixedCode1 = $@" +{fixedDirective} + +class Example +{{ + string value; +}} +"; + var fixedCode2 = @" +#nullable disable + +class Example2 +{ + string value; +} +"; + var fixedCode3 = @" +#nullable disable + +class Example3 +{ +#nullable restore + string? value; +#nullable disable +} +"; + var fixedCode4 = @" +#nullable disable + +class Example4 +{ + string value; +} "; await new VerifyCS.Test { - TestCode = code, - FixedCode = code, + TestState = + { + Sources = + { + code1, + code2, + code3, + code4, + }, + }, + FixedState = + { + Sources = + { + fixedCode1, + fixedCode2, + fixedCode3, + fixedCode4, + }, + }, + SolutionTransforms = { s_enableNullableInFixedSolutionFromRestoreKeyword }, + }.RunAsync(); + } + + [Theory] + [InlineData("$$#nullable disable")] + [InlineData("#$$nullable disable")] + [InlineData("#null$$able disable")] + [InlineData("#nullable$$ disable")] + [InlineData("#nullable $$ disable")] + [InlineData("#nullable $$disable")] + [InlineData("#nullable dis$$able")] + [InlineData("#nullable disable$$")] + public async Task EnabledOnNullableDisable(string directive) + { + var code1 = $@" +{directive} + +class Example +{{ + string value; +}} + +#nullable restore +"; + var code2 = @" +class Example2 +{ + string value; +} +"; + var code3 = @" +class Example3 +{ +#nullable enable + string? value; +#nullable restore +} +"; + var code4 = @" +#nullable disable + +class Example4 +{ + string value; +} +"; + + var fixedDirective = directive.Replace("$$", ""); + + var fixedCode1 = $@" +{fixedDirective} + +class Example +{{ + string value; +}} + +#nullable disable +"; + var fixedCode2 = @" +#nullable disable + +class Example2 +{ + string value; +} +"; + var fixedCode3 = @" +#nullable disable + +class Example3 +{ +#nullable restore + string? value; +#nullable disable +} +"; + var fixedCode4 = @" +#nullable disable + +class Example4 +{ + string value; +} +"; + + await new VerifyCS.Test + { + TestState = + { + Sources = + { + code1, + code2, + code3, + code4, + }, + }, + FixedState = + { + Sources = + { + fixedCode1, + fixedCode2, + fixedCode3, + fixedCode4, + }, + }, + SolutionTransforms = { s_enableNullableInFixedSolutionFromDisableKeyword }, }.RunAsync(); } } diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/UpgradeProject/UpgradeProjectTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/UpgradeProject/UpgradeProjectTests.cs index e70f8bdc1b63a..65f10ef18f768 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/UpgradeProject/UpgradeProjectTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/UpgradeProject/UpgradeProjectTests.cs @@ -1086,5 +1086,22 @@ void M(bool b) expected: LanguageVersion.CSharp9, new CSharpParseOptions(LanguageVersion.CSharp8)); } + + [Fact, WorkItem(57154, "https://github.com/dotnet/roslyn/issues/57154")] + public async Task UpgradeProjectForNewLinesInInterpolations() + { + await TestLanguageVersionUpgradedAsync(@" +class Test +{ + void M() + { + var v = $""x{ + 1 + 1 + [|}|]y""; + } +}", + expected: LanguageVersion.Preview, + new CSharpParseOptions(LanguageVersion.CSharp8)); + } } } diff --git a/src/EditorFeatures/CSharpTest/EmbeddedLanguages/RegularExpressions/CSharpRegexParserTests_RealWorld.cs b/src/EditorFeatures/CSharpTest/EmbeddedLanguages/RegularExpressions/CSharpRegexParserTests_RealWorld.cs index 821df0bdec67c..041ccb8e63503 100644 --- a/src/EditorFeatures/CSharpTest/EmbeddedLanguages/RegularExpressions/CSharpRegexParserTests_RealWorld.cs +++ b/src/EditorFeatures/CSharpTest/EmbeddedLanguages/RegularExpressions/CSharpRegexParserTests_RealWorld.cs @@ -20,7 +20,7 @@ public void TestDeepAlternation() { // Tree is too large to convert to string and still fit within dll limits. So we just validate that // we were able to parse and get a real tree back. - var (_, tree, _) = JustParseTree(@"@""(((http|ftp|https):\/{2})+(([0-9a-z_-]+\.)+(zw|zuerich|zone|zm|zip|zero|zara|zappos|za|yun|yt|youtube|you|yokohama|yoga|yodobashi|ye|yandex|yamaxun|yahoo|yachts|xyz|xxx|xin|xfinity|xerox|xbox|wtf|wtc|ws|wow|world|works|work|woodside|wolterskluwer|wme|winners|wine|windows|win|williamhill|wiki|wien|whoswho|wf|weir|wedding|wed|website|weber|webcam|weatherchannel|weather|watches|watch|wang|walter|walmart|wales|vu|voyage|voting|vote|volvo|volkswagen|vodka|vn|vlaanderen|vivo|viva|vision|visa|virgin|vip|vin|villas|viking|vig|video|vi|vg|vet|verisign|ventures|vegas|ve|vc|vanguard|vana|vacations|va|uz|uy|us|ups|uol|university|unicom|uk|ug|ubs|ubank|ua|tz|tw|tvs|tv|tunes|tui|tube|tt|trv|trust|travelersinsurance|travelers|travelchannel|travel|training|trading|trade|tr|tp|toys|toyota|town|tours|total|toshiba|toray|top|tools|tokyo|today|to|tn|tmall|tm|tl|tkmaxx|tk|tjx|tjmaxx|tj|tirol|tires|tips|tiffany|tickets|tiaa|theatre|theater|thd|th|tg|tf|teva|tennis|temasek|tel|technology|tech|team|tdk|td|tci|tc|taxi|tax|tattoo|tatar|tatamotors|target|taobao|talk|taipei|tab|sz|systems|symantec|sydney|sy|sx|swiss|swiftcover|swatch|sv|suzuki|surgery|surf|support|supply|supplies|sucks|su|style|study|studio|stream|store|storage|stockholm|stcgroup|stc|statefarm|statebank|star|staples|stada|st|ss|srl|sr|spreadbetting|spot|sport|space|sony|song|solutions|solar|sohu|software|softbank|social|soccer|so|sncf|sn|smile|smart|sm|sling|sl|skype|sky|skin|ski|sk|sj|site|singles|sina|silk|si|shriram|showtime|show|shopping|shop|shoes|shia|shell|shaw|sharp|shangrila|sh|sg|sfr|sexy|sex|sew|seven|ses|services|sener|select|seek|security|secure|seat|search|se|sd|scot|scor|scjohnson|science|schwarz|school|scholarships|schmidt|schaeffler|scb|sca|sc|sbs|sbi|sb|saxo|save|sas|sap|sanofi|sandvikcoromant|sandvik|samsung|samsclub|salon|sale|sakura|safety|safe|saarland|sa|ryukyu|rwe|rw|run|ruhr|rugby|ru|rs|room|rogers|rodeo|rocks|rocher|ro|rmit|rip|rio|ril|rightathome|ricoh|richardli|rich|rexroth|reviews|review|restaurant|rest|republican|report|repair|rentals|rent|ren|reliance|reit|rehab|redumbrella|redstone|red|recipes|realty|realtor|realestate|read|re|raid|radio|racing|qvc|quest|quebec|qpon|qa|py|pwc|pw|pub|pt|ps|prudential|pru|protection|property|properties|promo|progressive|prof|productions|prod|pro|prime|press|praxi|pramerica|pr|post|porn|politie|poker|pohl|pnc|pn|pm|plus|plumbing|playstation|play|place|pl|pk|pizza|pioneer|pink|ping|pin|pid|pictures|pictet|pics|physio|photos|photography|photo|phone|philips|phd|pharmacy|ph|pg|pfizer|pf|pet|pe|pccw|pay|party|parts|partners|pars|paris|panasonic|page|pa|ovh|ott|otsuka|osaka|origins|orientexpress|organic|org|orange|oracle|open|ooo|onyourside|online|onl|ong|one|omega|om|ollo|oldnavy|olayangroup|olayan|okinawa|office|off|observer|obi|nz|nyc|nu|ntt|nrw|nra|nr|np|nowtv|nowruz|now|norton|northwesternmutual|nokia|no|nl|nissay|nissan|ninja|nikon|nike|nico|ni|nhk|ngo|ng|nfl|nf|nexus|nextdirect|next|news|newholland|new|neustar|network|netflix|netbank|net|nec|ne|nc|nba|navy|natura|nationwide|name|nagoya|nab|na|mz|my|mx|mw|mv|mutuelle|mutual|museum|mu|mtr|mtpc|mtn|mt|msd|ms|mr|mq|mp|movie|mov|motorcycles|moto|moscow|mortgage|mormon|monster|money|monash|mom|moi|mobile|mobi|mo|mn|mma|mm|mls|mlb|ml|mk|mitsubishi|mit|mint|mini|mil|microsoft|miami|mh|mg|metlife|merckmsd|menu|men|memorial|meme|melbourne|meet|media|med|me|md|mckinsey|mc|mba|mattel|maserati|marshalls|marriott|markets|marketing|market|map|mango|management|man|makeup|maif|madrid|macys|ma|ly|lv|luxury|luxe|lupin|lundbeck|lu|ltd|lt|ls|lr|lplfinancial|lpl|love|lotto|lotte|london|lol|loft|locus|locker|loans|loan|llp|llc|lk|lixil|living|live|lipsy|link|linde|lincoln|limo|limited|lilly|like|lighting|lifestyle|lifeinsurance|life|lidl|li|lgbt|lexus|lego|legal|lefrak|leclerc|lease|lds|lc|lb|lawyer|law|latrobe|latino|lat|lasalle|lanxess|landrover|land|lancia|lancaster|lamer|lamborghini|lacaixa|la|kz|kyoto|ky|kw|kuokgroup|kred|krd|kr|kpn|kpmg|kp|kosher|komatsu|koeln|kn|km|kiwi|kitchen|kindle|kim|kia|ki|kh|kg|kfh|kerryproperties|kerrylogistics|kerryhotels|ke|kddi|juniper|jprs|jpmorgan|jp|joy|jot|joburg|jobs|jo|jnj|jmp|jm|jll|jio|jewelry|jeep|je|jcp|jcb|java|jaguar|iveco|itv|itau|it|istanbul|ist|ismaili|is|irish|ir|iq|ipiranga|io|investments|intuit|international|intel|int|insure|insurance|institute|ink|ing|info|infiniti|industries|inc|in|imdb|imamat|im|il|ikano|iinet|ifm|ieee|ie|id|icu|ice|icbc|ibm|ευ|hyundai|hyatt|hughes|hu|ht|hsbc|hr|how|house|hotmail|hotels|hot|hosting|host|hospital|horse|honda|homesense|homes|homegoods|homedepot|holiday|holdings|hockey|hn|hm|hkt|hk|hiv|hitachi|hisamitsu|hiphop|hgtv|hermes|here|helsinki|help|healthcare|health|hdfcbank|hdfc|hbo|hangout|hamburg|hair|gy|gw|guru|guitars|guide|guge|gucci|guardian|gu|gt|gs|group|grocery|gripe|green|graphics|grainger|gr|gq|gp|gov|got|gop|google|goog|goodyear|goo|golf|goldpoint|gold|godaddy|gn|gmx|gmo|gmail|gm|globo|global|gle|glass|glade|gl|giving|gives|gifts|gift|gi|gh|ggee|gg|gf|george|genting|gent|gea|ge|gdn|gd|gbiz|gb|gay|garden|gap|games|game|gallup|gallo|gallery|gal|ga|fyi|furniture|fund|fun|fujixerox|fujitsu|ftr|frontier|frontdoor|frogans|frl|fresenius|free|fr|fox|foundation|forum|forsale|forex|ford|football|foodnetwork|food|foo|fo|fm|fly|flsmidth|flowers|florist|flir|flights|flickr|fk|fj|fitness|fit|fishing|fish|firmdale|firestone|fire|financial|finance|final|film|fido|fidelity|fiat|fi|ferrero|ferrari|feedback|fedex|fast|fashion|farmers|farm|fans|fan|family|faith|fairwinds|fail|fage|extraspace|express|exposed|expert|exchange|events|eus|eurovision|eu|etisalat|et|esurance|estate|esq|es|erni|ericsson|er|equipment|epson|enterprises|engineering|engineer|energy|emerck|email|eg|ee|education|edu|edeka|eco|ec|eat|earth|dz|dvr|dvag|durban|dupont|dunlop|duck|dubai|dtv|drive|download|dot|doosan|domains|dog|doctor|docs|do|dnp|dm|dk|dj|diy|dish|discover|discount|directory|direct|digital|diet|diamonds|dhl|dev|design|dentist|dental|democrat|delta|deloitte|dell|delivery|degree|deals|dealer|deal|de|dds|dclk|day|datsun|dating|date|data|dance|dad|dabur|cz|cyou|cymru|cy|cx|cw|cv|cuisinella|cu|csc|cruises|cruise|crs|crown|cricket|creditunion|creditcard|credit|cr|cpa|courses|coupons|coupon|country|corsica|coop|cool|cookingchannel|cooking|contractors|contact|consulting|construction|condos|comsec|computer|compare|company|community|commbank|comcast|com|cologne|college|coffee|codes|coach|co|cn|cm|clubmed|club|cloud|clothing|clinique|clinic|click|cleaning|claims|cl|ck|cityeats|city|citic|citi|citadel|cisco|circle|cipriani|ci|church|chrome|christmas|chintai|cheap|chat|chase|charity|channel|chanel|ch|cg|cfd|cfa|cf|cern|ceo|center|ceb|cd|cc|cbs|cbre|cbn|cba|catholic|catering|cat|casino|cash|caseih|case|cars|careers|career|care|cards|caravan|car|capitalone|capital|capetown|canon|cancerresearch|camp|camera|cam|calvinklein|call|cal|cab|ca|bzh|bz|by|bw|bv|buzz|buy|business|builders|build|bugatti|budapest|bt|bs|brussels|brother|broker|broadway|bridgestone|bradesco|br|box|boutique|bot|boston|bostik|bosch|booking|book|boo|bond|bofa|boehringer|boats|bo|bnpparibas|bn|bmw|bms|bm|blue|bloomberg|blog|blockbuster|blackfriday|black|bj|biz|bio|bingo|bing|bike|bid|bible|bi|bharti|bh|bg|bf|bet|bestbuy|best|berlin|bentley|beer|beauty|beats|be|bd|bcn|bcg|bbva|bbt|bbc|bb|bayern|bauhaus|basketball|baseball|bargains|barefoot|barclays|barclaycard|barcelona|bar|bank|band|bananarepublic|banamex|baidu|baby|ba|azure|az|axa|ax|aws|aw|avianca|autos|auto|author|auspost|audio|audible|audi|auction|au|attorney|athleta|at|associates|asia|asda|as|arte|art|army|archi|aramco|arab|ar|aquarelle|aq|apple|app|apartments|aol|ao|anz|anquan|android|analytics|an|amsterdam|amica|amfam|amex|americanfamily|americanexpress|am|alstom|alsace|ally|allstate|allfinanz|alipay|alibaba|alfaromeo|al|akdn|airtel|airforce|airbus|aigo|aig|ai|agency|agakhan|ag|africa|afl|afamilycompany|af|aetna|aero|aeg|ae|adult|ads|adac|ad|actor|aco|accountants|accountant|accenture|academy|ac|abudhabi|able|abc|abbvie|abbott|abb|abarth|aarp|aaa)(:[0-9]+)?((\/([~0-9a-zA-Z\#\+\%@\.\/_-]+))?(\?[0-9a-zA-Z\+\%@\/&\[\];=_-]+)?)?))""", + var (_, tree, _) = JustParseTree(@"@""(((http|ftp|https):\/{2})+(([0-9a-z_-]+\.)+(zw|zuerich|zone|zm|zip|zero|zara|zappos|za|yun|yt|youtube|you|yokohama|yoga|yodobashi|ye|yandex|yamaxun|yahoo|yachts|xyz|xxx|xin|xfinity|xerox|xbox|wta|wtc|ws|wow|world|works|work|woodside|wolterskluwer|wme|winners|wine|windows|win|williamhill|wiki|wien|whoswho|wf|weir|wedding|wed|website|weber|webcam|weatherchannel|weather|watches|watch|wang|walter|walmart|wales|vu|voyage|voting|vote|volvo|volkswagen|vodka|vn|vlaanderen|vivo|viva|vision|visa|virgin|vip|vin|villas|viking|vig|video|vi|vg|vet|verisign|ventures|vegas|ve|vc|vanguard|vana|vacations|va|uz|uy|us|ups|uol|university|unicom|uk|ug|ubs|ubank|ua|tz|tw|tvs|tv|tunes|tui|tube|tt|trv|trust|travelersinsurance|travelers|travelchannel|travel|training|trading|trade|tr|tp|toys|toyota|town|tours|total|toshiba|toray|top|tools|tokyo|today|to|tn|tmall|tm|tl|tkmaxx|tk|tjx|tjmaxx|tj|tirol|tires|tips|tiffany|tickets|tiaa|theatre|theater|thd|th|tg|tf|teva|tennis|temasek|tel|technology|tech|team|tdk|td|tci|tc|taxi|tax|tattoo|tatar|tatamotors|target|taobao|talk|taipei|tab|sz|systems|symantec|sydney|sy|sx|swiss|swiftcover|swatch|sv|suzuki|surgery|surf|support|supply|supplies|sun|su|style|study|studio|stream|store|storage|stockholm|stcgroup|stc|statefarm|statebank|star|staples|stada|st|ss|srl|sr|spreadbetting|spot|sport|space|sony|song|solutions|solar|sohu|software|softbank|social|soccer|so|sncf|sn|smile|smart|sm|sling|sl|skype|sky|skin|ski|sk|sj|site|singles|sina|silk|si|shriram|showtime|show|shopping|shop|shoes|shia|shell|shaw|sharp|shangrila|sh|sg|sfr|sexy|sex|sew|seven|ses|services|sener|select|seek|security|secure|seat|search|se|sd|scot|scor|scjohnson|science|schwarz|school|scholarships|schmidt|schaeffler|scb|sca|sc|sbs|sbi|sb|saxo|save|sas|sap|sanofi|sandvikcoromant|sandvik|samsung|samsclub|salon|sale|sakura|safety|safe|saarland|sa|ryukyu|rwe|rw|run|ruhr|rugby|ru|rs|room|rogers|rodeo|rocks|rocher|ro|rmit|rip|rio|ril|rightathome|ricoh|richardli|rich|rexroth|reviews|review|restaurant|rest|republican|report|repair|rentals|rent|ren|reliance|reit|rehab|redumbrella|redstone|red|recipes|realty|realtor|realestate|read|re|raid|radio|racing|qvc|quest|quebec|qpon|qa|py|pwc|pw|pub|pt|ps|prudential|pru|protection|property|properties|promo|progressive|prof|productions|prod|pro|prime|press|praxi|pramerica|pr|post|porn|politie|poker|pohl|pnc|pn|pm|plus|plumbing|playstation|play|place|pl|pk|pizza|pioneer|pink|ping|pin|pid|pictures|pictet|pics|physio|photos|photography|photo|phone|philips|phd|pharmacy|ph|pg|pfizer|pf|pet|pe|pccw|pay|party|parts|partners|pars|paris|panasonic|page|pa|ovh|ott|otsuka|osaka|origins|orientexpress|organic|org|orange|oracle|open|ooo|onyourside|online|onl|ong|one|omega|om|ollo|oldnavy|olayangroup|olayan|okinawa|office|off|observer|obi|nz|nyc|nu|ntt|nrw|nra|nr|np|nowtv|nowruz|now|norton|northwesternmutual|nokia|no|nl|nissay|nissan|ninja|nikon|nike|nico|ni|nhk|ngo|ng|nfl|nf|nexus|nextdirect|next|news|newholland|new|neustar|network|netflix|netbank|net|nec|ne|nc|nba|navy|natura|nationwide|name|nagoya|nab|na|mz|my|mx|mw|mv|mutuelle|mutual|museum|mu|mtr|mtpc|mtn|mt|msd|ms|mr|mq|mp|movie|mov|motorcycles|moto|moscow|mortgage|mormon|monster|money|monash|mom|moi|mobile|mobi|mo|mn|mma|mm|mls|mlb|ml|mk|mitsubishi|mit|mint|mini|mil|microsoft|miami|mh|mg|metlife|merckmsd|menu|men|memorial|meme|melbourne|meet|media|med|me|md|mckinsey|mc|mba|mattel|maserati|marshalls|marriott|markets|marketing|market|map|mango|management|man|makeup|maif|madrid|macys|ma|ly|lv|luxury|luxe|lupin|lundbeck|lu|ltd|lt|ls|lr|lplfinancial|lpl|love|lotto|lotte|london|lol|loft|locus|locker|loans|loan|llp|llc|lk|lixil|living|live|lipsy|link|linde|lincoln|limo|limited|lilly|like|lighting|lifestyle|lifeinsurance|life|lidl|li|lgbt|lexus|lego|legal|lefrak|leclerc|lease|lds|lc|lb|lawyer|law|latrobe|latino|lat|lasalle|lanxess|landrover|land|lancia|lancaster|lamer|lamborghini|lacaixa|la|kz|kyoto|ky|kw|kuokgroup|kred|krd|kr|kpn|kpmg|kp|kosher|komatsu|koeln|kn|km|kiwi|kitchen|kindle|kim|kia|ki|kh|kg|kfh|kerryproperties|kerrylogistics|kerryhotels|ke|kddi|juniper|jprs|jpmorgan|jp|joy|jot|joburg|jobs|jo|jnj|jmp|jm|jll|jio|jewelry|jeep|je|jcp|jcb|java|jaguar|iveco|itv|itau|it|istanbul|ist|ismaili|is|irish|ir|iq|ipiranga|io|investments|intuit|international|intel|int|insure|insurance|institute|ink|ing|info|infiniti|industries|inc|in|imdb|imamat|im|il|ikano|iinet|ifm|ieee|ie|id|icu|ice|icbc|ibm|ευ|hyundai|hyatt|hughes|hu|ht|hsbc|hr|how|house|hotmail|hotels|hot|hosting|host|hospital|horse|honda|homesense|homes|homegoods|homedepot|holiday|holdings|hockey|hn|hm|hkt|hk|hiv|hitachi|hisamitsu|hiphop|hgtv|hermes|here|helsinki|help|healthcare|health|hdfcbank|hdfc|hbo|hangout|hamburg|hair|gy|gw|guru|guitars|guide|guge|gucci|guardian|gu|gt|gs|group|grocery|gripe|green|graphics|grainger|gr|gq|gp|gov|got|gop|google|goog|goodyear|goo|golf|goldpoint|gold|godaddy|gn|gmx|gmo|gmail|gm|globo|global|gle|glass|glade|gl|giving|gives|gifts|gift|gi|gh|ggee|gg|gf|george|genting|gent|gea|ge|gdn|gd|gbiz|gb|garage|garden|gap|games|game|gallup|gallo|gallery|gal|ga|fyi|furniture|fund|fun|fujixerox|fujitsu|ftr|frontier|frontdoor|frogans|frl|fresenius|free|fr|fox|foundation|forum|forsale|forex|ford|football|foodnetwork|food|foo|fo|fm|fly|flsmidth|flowers|florist|flir|flights|flickr|fk|fj|fitness|fit|fishing|fish|firmdale|firestone|fire|financial|finance|final|film|fido|fidelity|fiat|fi|ferrero|ferrari|feedback|fedex|fast|fashion|farmers|farm|fans|fan|family|faith|fairwinds|fail|fage|extraspace|express|exposed|expert|exchange|events|eus|eurovision|eu|etisalat|et|esurance|estate|esq|es|erni|ericsson|er|equipment|epson|enterprises|engineering|engineer|energy|emerck|email|eg|ee|education|edu|edeka|eco|ec|eat|earth|dz|dvr|dvag|durban|dupont|dunlop|duck|dubai|dtv|drive|download|dot|doosan|domains|dog|doctor|docs|do|dnp|dm|dk|dj|diy|dish|discover|discount|directory|direct|digital|diet|diamonds|dhl|dev|design|dentist|dental|democrat|delta|deloitte|dell|delivery|degree|deals|dealer|deal|de|dds|dclk|day|datsun|dating|date|data|dance|dad|dabur|cz|cyou|cymru|cy|cx|cw|cv|cuisinella|cu|csc|cruises|cruise|crs|crown|cricket|creditunion|creditcard|credit|cr|cpa|courses|coupons|coupon|count|corsica|coop|cool|cookingchannel|cooking|contractors|contact|consulting|construction|condos|comsec|computer|compare|company|community|commbank|comcast|com|cologne|college|coffee|codes|coach|co|cn|cm|clubmed|club|cloud|clothing|clinique|clinic|click|cleaning|claims|cl|ck|cityeats|city|citic|citi|citadel|cisco|circle|cipriani|ci|church|chrome|christmas|chintai|cheap|chat|chase|charity|channel|chanel|ch|cg|cfd|cfa|cf|cern|ceo|center|ceb|cd|cc|cbs|cbre|cbn|cba|catholic|catering|cat|casino|cash|caseih|case|cars|careers|career|care|cards|caravan|car|captainone|captain|capetown|canon|cancerresearch|camp|camera|cam|calvinklein|call|cal|cab|ca|bzh|bz|by|bw|bv|buzz|buy|business|builders|build|bugatti|budapest|bt|bs|brussels|brother|broker|broadway|bridgestone|bradesco|br|box|boutique|bot|boston|bostik|bosch|booking|book|boo|bond|bofa|boehringer|boats|bo|bnpparibas|bn|bmw|bms|bm|blue|bloomberg|blog|blockbuster|blackfriday|black|bj|biz|bio|bingo|bing|bike|bid|bible|bi|bharti|bh|bg|bf|bet|bestbuy|best|berlin|bentley|beer|beauty|beats|be|bd|bcn|bcg|bbva|bbt|bbc|bb|bayern|bauhaus|basketball|baseball|bargains|barefoot|barclays|barclaycard|barcelona|bar|bank|band|bananarepublic|banamex|baidu|baby|ba|azure|az|axa|ax|aws|aw|avianca|autos|auto|author|auspost|audio|audible|audi|auction|au|attorney|athleta|at|associates|asia|asda|as|arte|art|army|archi|aramco|arab|ar|aquarelle|aq|apple|app|apartments|aol|ao|anz|anquan|android|analytics|an|amsterdam|amica|amfam|amex|americanfamily|americanexpress|am|alstom|alsace|ally|allstate|allfinanz|alipay|alibaba|alfaromeo|al|akdn|airtel|airforce|airbus|aigo|aig|ai|agency|agakhan|ag|africa|afl|afamilycompany|af|aetna|aero|aeg|ae|adult|ads|adac|ad|actor|aco|accountants|accountant|accenture|academy|ac|abudhabi|able|abc|abbvie|abbott|abb|abarth|aarp|aaa)(:[0-9]+)?((\/([~0-9a-zA-Z\#\+\%@\.\/_-]+))?(\?[0-9a-zA-Z\+\%@\/&\[\];=_-]+)?)?))""", RegexOptions.None, conversionFailureOk: false); Assert.NotNull(tree); Assert.Empty(tree.Diagnostics); diff --git a/src/EditorFeatures/CSharpTest/ImplementAbstractClass/ImplementAbstractClassTests.cs b/src/EditorFeatures/CSharpTest/ImplementAbstractClass/ImplementAbstractClassTests.cs index 6fa2a987c3ce0..5bcd6dca784d2 100644 --- a/src/EditorFeatures/CSharpTest/ImplementAbstractClass/ImplementAbstractClassTests.cs +++ b/src/EditorFeatures/CSharpTest/ImplementAbstractClass/ImplementAbstractClassTests.cs @@ -1488,7 +1488,7 @@ class Derived : Base void Goo() { } public override int Prop => throw new System.NotImplementedException(); -}", options: Option(ImplementTypeOptions.InsertionBehavior, ImplementTypeInsertionBehavior.AtTheEnd)); +}", options: Option(ImplementTypeOptions.Metadata.InsertionBehavior, ImplementTypeInsertionBehavior.AtTheEnd)); } [WorkItem(17274, "https://github.com/dotnet/roslyn/issues/17274")] @@ -1665,7 +1665,7 @@ class C : AbstractClass public override int ReadWriteProp { get; set; } public override int WriteOnlyProp { set => throw new System.NotImplementedException(); } }", parameters: new TestParameters(options: Option( - ImplementTypeOptions.PropertyGenerationBehavior, + ImplementTypeOptions.Metadata.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferAutoProperties))); } diff --git a/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests.cs b/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests.cs index 107b93ca985e5..66ffe34f3a66f 100644 --- a/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests.cs +++ b/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests.cs @@ -7287,7 +7287,7 @@ void M() { } }", Options = { - { ImplementTypeOptions.InsertionBehavior, ImplementTypeInsertionBehavior.AtTheEnd }, + { ImplementTypeOptions.Metadata.InsertionBehavior, ImplementTypeInsertionBehavior.AtTheEnd }, }, }.RunAsync(); } @@ -7462,7 +7462,7 @@ class Class : IInterface }", Options = { - { ImplementTypeOptions.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferAutoProperties }, + { ImplementTypeOptions.Metadata.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferAutoProperties }, }, }.RunAsync(); } diff --git a/src/EditorFeatures/CSharpTest/InitializeParameter/AddParameterCheckTests.cs b/src/EditorFeatures/CSharpTest/InitializeParameter/AddParameterCheckTests.cs index a2ded4cb3e282..7470bfef5aabb 100644 --- a/src/EditorFeatures/CSharpTest/InitializeParameter/AddParameterCheckTests.cs +++ b/src/EditorFeatures/CSharpTest/InitializeParameter/AddParameterCheckTests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.Shared.Extensions; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Testing; using Roslyn.Test.Utilities; @@ -30,6 +31,77 @@ public async Task TestEmptyFile() [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)] public async Task TestSimpleReferenceType() + { + await new VerifyCS.Test + { + LanguageVersion = LanguageVersionExtensions.CSharpNext, + TestCode = @" +using System; + +class C +{ + public C([||]string s) + { + } +}", + FixedCode = @" +using System; + +class C +{ + public C(string s!!) + { + } +}" + }.RunAsync(); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)] + public async Task TestSimpleReferenceType_AlreadyNullChecked1() + { + var testCode = @" +using System; + +class C +{ + public C([||]string s!!) + { + } +}"; + await new VerifyCS.Test + { + LanguageVersion = LanguageVersionExtensions.CSharpNext, + TestCode = testCode, + FixedCode = testCode + }.RunAsync(); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)] + public async Task TestSimpleReferenceType_AlreadyNullChecked2() + { + var testCode = @" +using System; + +class C +{ + public C([||]string s) + { + if (s is null) + { + throw new ArgumentNullException(nameof(s)); + } + } +}"; + await new VerifyCS.Test + { + LanguageVersion = LanguageVersionExtensions.CSharpNext, + TestCode = testCode, + FixedCode = testCode + }.RunAsync(); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)] + public async Task TestSimpleReferenceType_CSharp8() { await VerifyCS.VerifyRefactoringAsync( @" @@ -204,8 +276,11 @@ class C await VerifyCS.VerifyRefactoringAsync(code, code); } - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)] - public async Task TestNotOnPartialMethodDefinition1() + [Theory] + [Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)] + [InlineData(LanguageVersionExtensions.CSharpNext)] + [InlineData(LanguageVersion.CSharp8)] + public async Task TestNotOnPartialMethodDefinition1(LanguageVersion languageVersion) { var code = @" using System; @@ -218,7 +293,12 @@ partial void M(string s) { } }"; - await VerifyCS.VerifyRefactoringAsync(code, code); + await new VerifyCS.Test + { + LanguageVersion = languageVersion, + TestCode = code, + FixedCode = code + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)] @@ -243,8 +323,11 @@ public partial void M(string s) }.RunAsync(); } - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)] - public async Task TestNotOnPartialMethodDefinition2() + [Theory] + [Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)] + [InlineData(LanguageVersionExtensions.CSharpNext)] + [InlineData(LanguageVersion.CSharp8)] + public async Task TestNotOnPartialMethodDefinition2(LanguageVersion languageVersion) { var code = @" using System; @@ -257,7 +340,12 @@ partial void M(string s) partial void M([||]string s); }"; - await VerifyCS.VerifyRefactoringAsync(code, code); + await new VerifyCS.Test + { + LanguageVersion = languageVersion, + TestCode = code, + FixedCode = code + }.RunAsync(); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)] @@ -284,6 +372,37 @@ public partial void M(string s) [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)] public async Task TestOnPartialMethodImplementation1() + { + await new VerifyCS.Test + { + LanguageVersion = LanguageVersionExtensions.CSharpNext, + TestCode = @" +using System; + +partial class C +{ + partial void M(string s); + + partial void M([||]string s) + { + } +}", + FixedCode = @" +using System; + +partial class C +{ + partial void M(string s); + + partial void M(string s!!) + { + } +}" + }.RunAsync(); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)] + public async Task TestOnPartialMethodImplementation1_CSharp8() { await VerifyCS.VerifyRefactoringAsync( @" @@ -351,6 +470,37 @@ public partial void M(string s) [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)] public async Task TestOnPartialMethodImplementation2() + { + await new VerifyCS.Test + { + LanguageVersion = LanguageVersionExtensions.CSharpNext, + TestCode = @" +using System; + +partial class C +{ + partial void M([||]string s) + { + } + + partial void M(string s); +}", + FixedCode = @" +using System; + +partial class C +{ + partial void M(string s!!) + { + } + + partial void M(string s); +}" + }.RunAsync(); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)] + public async Task TestOnPartialMethodImplementation2_CSharp9() { await VerifyCS.VerifyRefactoringAsync( @" diff --git a/src/EditorFeatures/CSharpTest/SimplifyTypeNames/SimplifyTypeNamesTests.cs b/src/EditorFeatures/CSharpTest/SimplifyTypeNames/SimplifyTypeNamesTests.cs index 7cf9243983f6d..e10f174cc8304 100644 --- a/src/EditorFeatures/CSharpTest/SimplifyTypeNames/SimplifyTypeNamesTests.cs +++ b/src/EditorFeatures/CSharpTest/SimplifyTypeNames/SimplifyTypeNamesTests.cs @@ -6213,6 +6213,54 @@ static void Main(string[] args) }"); } + [WorkItem(57767, "https://github.com/dotnet/roslyn/issues/57767")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsSimplifyTypeNames)] + public async Task TestInvocationOffOfFunctionPointerInvocationResult1() + { + await TestMissingInRegularAndScriptAsync( +@"using System.Runtime.CompilerServices; + +public ref struct A +{ + private void Goo() + { + } + + public readonly unsafe ref struct B + { + private readonly void* a; + + public void Dispose() + { + [|((delegate*)(delegate*)&Unsafe.As)(ref *(byte*)a)|].Goo(); + } + } +}"); + } + + [WorkItem(57767, "https://github.com/dotnet/roslyn/issues/57767")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsSimplifyTypeNames)] + public async Task TestInvocationOffOfFunctionPointerInvocationResult2() + { + await TestMissingInRegularAndScriptAsync( +@"public struct A +{ + private void Goo() + { + } + + public readonly unsafe struct B + { + private readonly void* a; + + public void Dispose() + { + [|((delegate*)&Unsafe.As)(ref *(byte*)a)|].Goo(); + } + } +}"); + } + private async Task TestWithPredefinedTypeOptionsAsync(string code, string expected, int index = 0) => await TestInRegularAndScript1Async(code, expected, index, new TestParameters(options: PreferIntrinsicTypeEverywhere)); diff --git a/src/EditorFeatures/CSharpTest/Structure/MetadataAsSource/InvalidIdentifierStructureTests.cs b/src/EditorFeatures/CSharpTest/Structure/MetadataAsSource/InvalidIdentifierStructureTests.cs index 6b5a188ebef8e..531afa24ce496 100644 --- a/src/EditorFeatures/CSharpTest/Structure/MetadataAsSource/InvalidIdentifierStructureTests.cs +++ b/src/EditorFeatures/CSharpTest/Structure/MetadataAsSource/InvalidIdentifierStructureTests.cs @@ -29,8 +29,8 @@ public class InvalidIdentifierStructureTests : AbstractSyntaxStructureProviderTe internal override async Task> GetBlockSpansWorkerAsync(Document document, int position) { var outliningService = document.GetLanguageService(); - - return (await outliningService.GetBlockStructureAsync(document, CancellationToken.None)).Spans; + var options = BlockStructureOptions.From(document.Project); + return (await outliningService.GetBlockStructureAsync(document, options, CancellationToken.None)).Spans; } [WorkItem(1174405, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1174405")] diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptBlockStructureService.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptBlockStructureService.cs index 33be68f1bfd40..941da3d4923c1 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptBlockStructureService.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptBlockStructureService.cs @@ -26,7 +26,7 @@ public VSTypeScriptBlockStructureService(IVSTypeScriptBlockStructureServiceImple public override string Language => InternalLanguageNames.TypeScript; - public override async Task GetBlockStructureAsync(Document document, CancellationToken cancellationToken) + public override async Task GetBlockStructureAsync(Document document, BlockStructureOptions options, CancellationToken cancellationToken) { var blockStructure = await _impl.GetBlockStructureAsync(document, cancellationToken).ConfigureAwait(false); diff --git a/src/EditorFeatures/Core/Implementation/Classification/SemanticClassificationUtilities.cs b/src/EditorFeatures/Core/Implementation/Classification/SemanticClassificationUtilities.cs index 3533f6f6bd12d..6bd34a453593f 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/SemanticClassificationUtilities.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/SemanticClassificationUtilities.cs @@ -147,8 +147,12 @@ private static async Task ClassifySpansAsync( { using var _ = ArrayBuilder.GetInstance(out var classifiedSpans); - await AddSemanticClassificationsAsync( - document, snapshotSpan.Span.ToTextSpan(), classificationService, classifiedSpans, cancellationToken: cancellationToken).ConfigureAwait(false); + await classificationService.AddSemanticClassificationsAsync( + document, + snapshotSpan.Span.ToTextSpan(), + ClassificationOptions.From(document.Project), + classifiedSpans, + cancellationToken).ConfigureAwait(false); foreach (var span in classifiedSpans) context.AddTag(ClassificationUtilities.Convert(typeMap, snapshotSpan.Snapshot, span)); @@ -165,56 +169,5 @@ await AddSemanticClassificationsAsync( throw ExceptionUtilities.Unreachable; } } - - private static async Task AddSemanticClassificationsAsync( - Document document, - TextSpan textSpan, - IClassificationService classificationService, - ArrayBuilder classifiedSpans, - CancellationToken cancellationToken) - { - var workspaceStatusService = document.Project.Solution.Workspace.Services.GetRequiredService(); - - // Importantly, we do not await/wait on the fullyLoadedStateTask. We do not want to ever be waiting on work - // that may end up touching the UI thread (As we can deadlock if GetTagsSynchronous waits on us). Instead, - // we only check if the Task is completed. Prior to that we will assume we are still loading. Once this - // task is completed, we know that the WaitUntilFullyLoadedAsync call will have actually finished and we're - // fully loaded. - var isFullyLoadedTask = workspaceStatusService.IsFullyLoadedAsync(cancellationToken); - var isFullyLoaded = isFullyLoadedTask.IsCompleted && isFullyLoadedTask.GetAwaiter().GetResult(); - - // If we're not fully loaded try to read from the cache instead so that classifications appear up to date. - // New code will not be semantically classified, but will eventually when the project fully loads. - if (await TryAddSemanticClassificationsFromCacheAsync(document, textSpan, classifiedSpans, isFullyLoaded, cancellationToken).ConfigureAwait(false)) - return; - - var options = ClassificationOptions.From(document.Project); - await classificationService.AddSemanticClassificationsAsync( - document, textSpan, options, classifiedSpans, cancellationToken).ConfigureAwait(false); - } - - private static async Task TryAddSemanticClassificationsFromCacheAsync( - Document document, - TextSpan textSpan, - ArrayBuilder classifiedSpans, - bool isFullyLoaded, - CancellationToken cancellationToken) - { - // Don't use the cache if we're fully loaded. We should just compute values normally. - if (isFullyLoaded) - return false; - - var semanticCacheService = document.Project.Solution.Workspace.Services.GetService(); - if (semanticCacheService == null) - return false; - - var result = await semanticCacheService.GetCachedSemanticClassificationsAsync( - document, textSpan, cancellationToken).ConfigureAwait(false); - if (result.IsDefault) - return false; - - classifiedSpans.AddRange(result); - return true; - } } } diff --git a/src/EditorFeatures/Core/Implementation/Structure/AbstractStructureTaggerProvider.cs b/src/EditorFeatures/Core/Implementation/Structure/AbstractStructureTaggerProvider.cs index d08e0b5fe7d33..0b0e2db7167b8 100644 --- a/src/EditorFeatures/Core/Implementation/Structure/AbstractStructureTaggerProvider.cs +++ b/src/EditorFeatures/Core/Implementation/Structure/AbstractStructureTaggerProvider.cs @@ -143,8 +143,9 @@ protected sealed override async Task ProduceTagsAsync( if (outliningService == null) return; + var options = BlockStructureOptions.From(document.Project); var blockStructure = await outliningService.GetBlockStructureAsync( - documentSnapshotSpan.Document, cancellationToken).ConfigureAwait(false); + documentSnapshotSpan.Document, options, cancellationToken).ConfigureAwait(false); ProcessSpans( context, documentSnapshotSpan.SnapshotSpan, outliningService, diff --git a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs index 8b05e633a8ab5..e3642b0df912a 100644 --- a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs +++ b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs @@ -158,6 +158,34 @@ public async Task TestGetFixesAsyncForFixableAndNonFixableAnalyzersAsync() Assert.True(analyzerWithoutFix.ReceivedCallback); } + [Fact, WorkItem(1450689, "https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1450689")] + public async Task TestGetFixesAsyncForDocumentDiagnosticAnalyzerAsync() + { + // TS has special DocumentDiagnosticAnalyzer that report 0 SupportedDiagnostics. + // We need to ensure that we don't skip these document analyzers + // when computing the diagnostics/code fixes for "Normal" priority bucket, which + // normally only execute those analyzers which report at least one fixable supported diagnostic. + var documentDiagnosticAnalyzer = new MockAnalyzerReference.MockDocumentDiagnosticAnalyzer(reportedDiagnosticIds: ImmutableArray.Empty); + Assert.Empty(documentDiagnosticAnalyzer.SupportedDiagnostics); + + var analyzers = ImmutableArray.Create(documentDiagnosticAnalyzer); + var codeFix = new MockFixer(); + var analyzerReference = new MockAnalyzerReference(codeFix, analyzers); + + // Verify no callbacks received at initialization. + Assert.False(documentDiagnosticAnalyzer.ReceivedCallback); + + var tuple = ServiceSetup(codeFix, includeConfigurationFixProviders: false); + using var workspace = tuple.workspace; + GetDocumentAndExtensionManager(tuple.analyzerService, workspace, out var document, out var extensionManager, analyzerReference); + + // Verify both analyzers are executed when GetFixesAsync is invoked with 'CodeActionRequestPriority.Normal'. + _ = await tuple.codeFixService.GetFixesAsync(document, TextSpan.FromBounds(0, 0), + priority: CodeActionRequestPriority.Normal, isBlocking: false, + addOperationScope: _ => null, cancellationToken: CancellationToken.None); + Assert.True(documentDiagnosticAnalyzer.ReceivedCallback); + } + [Fact] public async Task TestGetCodeFixWithExceptionInRegisterMethod_Diagnostic() { @@ -401,6 +429,18 @@ public override ImmutableArray GetAnalyzersForAllLanguages() public ImmutableArray GetFixers() => Fixer != null ? ImmutableArray.Create(Fixer) : ImmutableArray.Empty; + private static ImmutableArray CreateSupportedDiagnostics(ImmutableArray<(string id, string category)> reportedDiagnosticIdsWithCategories) + { + var builder = ArrayBuilder.GetInstance(); + foreach (var (diagnosticId, category) in reportedDiagnosticIdsWithCategories) + { + var descriptor = new DiagnosticDescriptor(diagnosticId, "MockDiagnostic", "MockDiagnostic", category, DiagnosticSeverity.Warning, isEnabledByDefault: true); + builder.Add(descriptor); + } + + return builder.ToImmutableAndFree(); + } + public class MockDiagnosticAnalyzer : DiagnosticAnalyzer { public MockDiagnosticAnalyzer(ImmutableArray<(string id, string category)> reportedDiagnosticIdsWithCategories) @@ -423,18 +463,6 @@ public MockDiagnosticAnalyzer() public bool ReceivedCallback { get; private set; } - private static ImmutableArray CreateSupportedDiagnostics(ImmutableArray<(string id, string category)> reportedDiagnosticIdsWithCategories) - { - var builder = ArrayBuilder.GetInstance(); - foreach (var (diagnosticId, category) in reportedDiagnosticIdsWithCategories) - { - var descriptor = new DiagnosticDescriptor(diagnosticId, "MockDiagnostic", "MockDiagnostic", category, DiagnosticSeverity.Warning, isEnabledByDefault: true); - builder.Add(descriptor); - } - - return builder.ToImmutableAndFree(); - } - public override ImmutableArray SupportedDiagnostics { get; } public override void Initialize(AnalysisContext context) @@ -450,6 +478,38 @@ public override void Initialize(AnalysisContext context) }); } } + + public class MockDocumentDiagnosticAnalyzer : DocumentDiagnosticAnalyzer + { + public MockDocumentDiagnosticAnalyzer(ImmutableArray<(string id, string category)> reportedDiagnosticIdsWithCategories) + => SupportedDiagnostics = CreateSupportedDiagnostics(reportedDiagnosticIdsWithCategories); + + public MockDocumentDiagnosticAnalyzer(ImmutableArray reportedDiagnosticIds) + : this(reportedDiagnosticIds.SelectAsArray(id => (id, "InternalCategory"))) + { + } + + public MockDocumentDiagnosticAnalyzer() + : this(ImmutableArray.Create(MockFixer.Id)) + { + } + + public bool ReceivedCallback { get; private set; } + + public override ImmutableArray SupportedDiagnostics { get; } + + public override Task> AnalyzeSyntaxAsync(Document document, CancellationToken cancellationToken) + { + ReceivedCallback = true; + return Task.FromResult(ImmutableArray.Empty); + } + + public override Task> AnalyzeSemanticsAsync(Document document, CancellationToken cancellationToken) + { + ReceivedCallback = true; + return Task.FromResult(ImmutableArray.Empty); + } + } } internal class TestErrorLogger : IErrorLoggerService diff --git a/src/EditorFeatures/Test/Structure/BlockStructureServiceTests.cs b/src/EditorFeatures/Test/Structure/BlockStructureServiceTests.cs index ece4a2307e221..99919f10f2e7b 100644 --- a/src/EditorFeatures/Test/Structure/BlockStructureServiceTests.cs +++ b/src/EditorFeatures/Test/Structure/BlockStructureServiceTests.cs @@ -97,8 +97,9 @@ private static async Task> GetSpansFromWorkspaceAsync( var hostDocument = workspace.Documents.First(); var document = workspace.CurrentSolution.GetDocument(hostDocument.Id); var outliningService = document.GetLanguageService(); + var options = BlockStructureOptions.From(document.Project); - var structure = await outliningService.GetBlockStructureAsync(document, CancellationToken.None); + var structure = await outliningService.GetBlockStructureAsync(document, options, CancellationToken.None); return structure.Spans; } } diff --git a/src/EditorFeatures/VisualBasic/ImplementAbstractClass/ImplementAbstractClassCommandHandler.vb b/src/EditorFeatures/VisualBasic/ImplementAbstractClass/ImplementAbstractClassCommandHandler.vb index 4796e2a8ed7f6..b2904e3fca185 100644 --- a/src/EditorFeatures/VisualBasic/ImplementAbstractClass/ImplementAbstractClassCommandHandler.vb +++ b/src/EditorFeatures/VisualBasic/ImplementAbstractClass/ImplementAbstractClassCommandHandler.vb @@ -7,6 +7,7 @@ Imports System.Diagnostics.CodeAnalysis Imports System.Threading Imports Microsoft.CodeAnalysis.Editor.VisualBasic.Utilities.CommandHandlers Imports Microsoft.CodeAnalysis.ImplementAbstractClass +Imports Microsoft.CodeAnalysis.ImplementType Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Microsoft.VisualStudio.Commanding @@ -32,21 +33,22 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.ImplementAbstractClass Protected Overrides Function TryGetNewDocument( document As Document, - typeSyntax As TypeSyntax, + options As ImplementTypeOptions, + TypeSyntax As TypeSyntax, cancellationToken As CancellationToken ) As Document - If typeSyntax.Parent.Kind <> SyntaxKind.InheritsStatement Then + If TypeSyntax.Parent.Kind <> SyntaxKind.InheritsStatement Then Return Nothing End If - Dim classBlock = TryCast(typeSyntax.Parent.Parent, ClassBlockSyntax) + Dim classBlock = TryCast(TypeSyntax.Parent.Parent, ClassBlockSyntax) If classBlock Is Nothing Then Return Nothing End If Dim updatedDocument = ImplementAbstractClassData.TryImplementAbstractClassAsync( - document, classBlock, classBlock.ClassStatement.Identifier, cancellationToken).WaitAndGetResult(cancellationToken) + document, classBlock, classBlock.ClassStatement.Identifier, options, cancellationToken).WaitAndGetResult(cancellationToken) If updatedDocument IsNot Nothing AndAlso updatedDocument.GetTextChangesAsync(document, cancellationToken).WaitAndGetResult(cancellationToken).Count = 0 Then Return Nothing diff --git a/src/EditorFeatures/VisualBasic/ImplementInterface/ImplementInterfaceCommandHandler.vb b/src/EditorFeatures/VisualBasic/ImplementInterface/ImplementInterfaceCommandHandler.vb index d89a89fdf71e9..a93f14150b908 100644 --- a/src/EditorFeatures/VisualBasic/ImplementInterface/ImplementInterfaceCommandHandler.vb +++ b/src/EditorFeatures/VisualBasic/ImplementInterface/ImplementInterfaceCommandHandler.vb @@ -7,6 +7,7 @@ Imports System.Diagnostics.CodeAnalysis Imports System.Threading Imports Microsoft.CodeAnalysis.Editor.VisualBasic.Utilities.CommandHandlers Imports Microsoft.CodeAnalysis.ImplementInterface +Imports Microsoft.CodeAnalysis.ImplementType Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Microsoft.VisualStudio.Commanding @@ -32,6 +33,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.ImplementInterface Protected Overrides Function TryGetNewDocument( document As Document, + options As ImplementTypeOptions, typeSyntax As TypeSyntax, cancellationToken As CancellationToken ) As Document @@ -43,6 +45,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.ImplementInterface Dim service = document.GetLanguageService(Of IImplementInterfaceService)() Dim updatedDocument = service.ImplementInterfaceAsync( document, + options, typeSyntax.Parent, cancellationToken).WaitAndGetResult(cancellationToken) If updatedDocument.GetTextChangesAsync(document, cancellationToken).WaitAndGetResult(cancellationToken).Count = 0 Then diff --git a/src/EditorFeatures/VisualBasic/Utilities/CommandHandlers/AbstractImplementAbstractClassOrInterfaceCommandHandler.vb b/src/EditorFeatures/VisualBasic/Utilities/CommandHandlers/AbstractImplementAbstractClassOrInterfaceCommandHandler.vb index 275b1febc61c0..3c7167f549ea6 100644 --- a/src/EditorFeatures/VisualBasic/Utilities/CommandHandlers/AbstractImplementAbstractClassOrInterfaceCommandHandler.vb +++ b/src/EditorFeatures/VisualBasic/Utilities/CommandHandlers/AbstractImplementAbstractClassOrInterfaceCommandHandler.vb @@ -6,6 +6,7 @@ Imports System.Threading Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Editor.Implementation.EndConstructGeneration Imports Microsoft.CodeAnalysis.Formatting +Imports Microsoft.CodeAnalysis.ImplementType Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.Simplification Imports Microsoft.CodeAnalysis.Text @@ -38,6 +39,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.Utilities.CommandHandlers Protected MustOverride Overloads Function TryGetNewDocument( document As Document, + options As ImplementTypeOptions, typeSyntax As TypeSyntax, cancellationToken As CancellationToken) As Document @@ -104,14 +106,6 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.Utilities.CommandHandlers End Function Private Overloads Function TryExecute(args As ReturnKeyCommandArgs, cancellationToken As CancellationToken) As Boolean - Dim textSnapshot = args.SubjectBuffer.CurrentSnapshot - Dim text = textSnapshot.AsText() - - Dim document = textSnapshot.GetOpenDocumentInCurrentContextWithChanges() - If document Is Nothing Then - Return False - End If - If Not _globalOptions.GetOption(FeatureOnOffOptions.AutomaticInsertionOfAbstractOrInterfaceMembers, LanguageNames.VisualBasic) Then Return False End If @@ -126,8 +120,15 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.Utilities.CommandHandlers Return False End If + Dim textSnapshot = args.SubjectBuffer.CurrentSnapshot + Dim document = textSnapshot.GetOpenDocumentInCurrentContextWithChanges() + If document Is Nothing Then + Return False + End If + Dim syntaxRoot = document.GetSyntaxRootSynchronously(cancellationToken) Dim token = syntaxRoot.FindTokenOnLeftOfPosition(caretPosition) + Dim text = textSnapshot.AsText() If text.Lines.IndexOf(token.SpanStart) <> text.Lines.IndexOf(caretPosition) Then Return False @@ -162,7 +163,8 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.Utilities.CommandHandlers Return False End If - Dim newDocument = TryGetNewDocument(document, identifier, cancellationToken) + Dim options = ImplementTypeOptions.From(document.Project) + Dim newDocument = TryGetNewDocument(document, options, identifier, cancellationToken) If newDocument Is Nothing Then Return False diff --git a/src/EditorFeatures/VisualBasicTest/ImplementAbstractClass/ImplementAbstractClassTests.vb b/src/EditorFeatures/VisualBasicTest/ImplementAbstractClass/ImplementAbstractClassTests.vb index b013da74084b6..74bd618c3f1b0 100644 --- a/src/EditorFeatures/VisualBasicTest/ImplementAbstractClass/ImplementAbstractClassTests.vb +++ b/src/EditorFeatures/VisualBasicTest/ImplementAbstractClass/ImplementAbstractClassTests.vb @@ -631,7 +631,7 @@ Class C End Set End Property End Class", parameters:=New TestParameters(options:=[Option]( - ImplementTypeOptions.PropertyGenerationBehavior, + ImplementTypeOptions.Metadata.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferAutoProperties))) End Function End Class diff --git a/src/EditorFeatures/VisualBasicTest/ImplementInterface/ImplementInterfaceTests.vb b/src/EditorFeatures/VisualBasicTest/ImplementInterface/ImplementInterfaceTests.vb index 235274b2ef184..f915762663037 100644 --- a/src/EditorFeatures/VisualBasicTest/ImplementInterface/ImplementInterfaceTests.vb +++ b/src/EditorFeatures/VisualBasicTest/ImplementInterface/ImplementInterfaceTests.vb @@ -4647,7 +4647,7 @@ class Class End Set End Property end class", parameters:=New TestParameters(options:=[Option]( - ImplementTypeOptions.PropertyGenerationBehavior, + ImplementTypeOptions.Metadata.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferAutoProperties))) End Function End Class diff --git a/src/EditorFeatures/VisualBasicTest/Semantics/SpeculationAnalyzerTests.vb b/src/EditorFeatures/VisualBasicTest/Semantics/SpeculationAnalyzerTests.vb index cc2a613742e2b..7bdbad8ebe871 100644 --- a/src/EditorFeatures/VisualBasicTest/Semantics/SpeculationAnalyzerTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Semantics/SpeculationAnalyzerTests.vb @@ -15,6 +15,10 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Semantics Public Sub SpeculationAnalyzerExtensionMethodExplicitInvocation() + ' We consider a change here to be a change in semantics as an instance call became a static call. In + ' practice this is fine as the only thing that makes this change i complexification, and we don't test for + ' semantics changed after that as the purpose of complexification is to put us in a safe place to make + ' changes that won't break semantics. Test( Module Oombr <System.Runtime.CompilerServices.Extension> @@ -25,7 +29,7 @@ Module Oombr Call [|5.Vain()|] End Sub End Module - .Value, "Vain(5)", False) + .Value, "Vain(5)", semanticChanges:=True) End Sub diff --git a/src/EditorFeatures/VisualBasicTest/SimplifyTypeNames/SimplifyTypeNamesTests.vb b/src/EditorFeatures/VisualBasicTest/SimplifyTypeNames/SimplifyTypeNamesTests.vb index 6ede97efd4a8e..dccafe36519ac 100644 --- a/src/EditorFeatures/VisualBasicTest/SimplifyTypeNames/SimplifyTypeNamesTests.vb +++ b/src/EditorFeatures/VisualBasicTest/SimplifyTypeNames/SimplifyTypeNamesTests.vb @@ -977,7 +977,7 @@ End Namespace") Public Async Function TestSimplifyTypeInScriptCode() As Task Await TestAsync( "Imports System -[|System.Console.WriteLine(0)|]", +[|System.Console|].WriteLine(0)", "Imports System Console.WriteLine(0)", parseOptions:=TestOptions.Script) diff --git a/src/EditorFeatures/VisualBasicTest/Structure/MetadataAsSource/InvalidIdentifierStructureTests.vb b/src/EditorFeatures/VisualBasicTest/Structure/MetadataAsSource/InvalidIdentifierStructureTests.vb index ed68b90fb82a6..bcbeef95fe486 100644 --- a/src/EditorFeatures/VisualBasicTest/Structure/MetadataAsSource/InvalidIdentifierStructureTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Structure/MetadataAsSource/InvalidIdentifierStructureTests.vb @@ -30,8 +30,9 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Outlining.Metadata Friend Overrides Async Function GetBlockSpansWorkerAsync(document As Document, position As Integer) As Task(Of ImmutableArray(Of BlockSpan)) Dim outliningService = document.GetLanguageService(Of BlockStructureService)() + Dim options = BlockStructureOptions.From(document.Project) - Return (Await outliningService.GetBlockStructureAsync(document, CancellationToken.None)).Spans + Return (Await outliningService.GetBlockStructureAsync(document, options, CancellationToken.None)).Spans End Function diff --git a/src/EditorFeatures/VisualBasicTest/Structure/OverallStructureTests.vb b/src/EditorFeatures/VisualBasicTest/Structure/OverallStructureTests.vb index fc63045eeba3c..28fc10b63a6fc 100644 --- a/src/EditorFeatures/VisualBasicTest/Structure/OverallStructureTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Structure/OverallStructureTests.vb @@ -19,8 +19,9 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Outlining Friend Overrides Async Function GetBlockSpansWorkerAsync(document As Document, position As Integer) As Task(Of ImmutableArray(Of BlockSpan)) Dim outliningService = document.GetLanguageService(Of BlockStructureService)() + Dim options = BlockStructureOptions.From(document.Project) - Return (Await outliningService.GetBlockStructureAsync(document, CancellationToken.None)).Spans + Return (Await outliningService.GetBlockStructureAsync(document, options, CancellationToken.None)).Spans End Function diff --git a/src/Features/CSharp/Portable/CodeRefactorings/EnableNullable/EnableNullableCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/CodeRefactorings/EnableNullable/EnableNullableCodeRefactoringProvider.cs index 4781ec960fc92..706522c837a47 100644 --- a/src/Features/CSharp/Portable/CodeRefactorings/EnableNullable/EnableNullableCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/CodeRefactorings/EnableNullable/EnableNullableCodeRefactoringProvider.cs @@ -52,8 +52,11 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte if (token.IsKind(SyntaxKind.EndOfDirectiveToken)) token = root.FindToken(textSpan.Start - 1, findInsideTrivia: true); - if (!token.IsKind(SyntaxKind.EnableKeyword) || !token.Parent.IsKind(SyntaxKind.NullableDirectiveTrivia)) + if (!token.IsKind(SyntaxKind.EnableKeyword, SyntaxKind.RestoreKeyword, SyntaxKind.DisableKeyword, SyntaxKind.NullableKeyword, SyntaxKind.HashToken) + || !token.Parent.IsKind(SyntaxKind.NullableDirectiveTrivia, out NullableDirectiveTriviaSyntax? nullableDirectiveTrivia)) + { return; + } context.RegisterRefactoring( new MyCodeAction((purpose, cancellationToken) => EnableNullableReferenceTypesAsync(document.Project, purpose, cancellationToken))); diff --git a/src/Features/CSharp/Portable/ImplementInterface/CSharpImplementInterfaceCodeFixProvider.cs b/src/Features/CSharp/Portable/ImplementInterface/CSharpImplementInterfaceCodeFixProvider.cs index 89c738968cc5f..ffaa2974eb11c 100644 --- a/src/Features/CSharp/Portable/ImplementInterface/CSharpImplementInterfaceCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/ImplementInterface/CSharpImplementInterfaceCodeFixProvider.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.ImplementInterface; +using Microsoft.CodeAnalysis.ImplementType; using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.CSharp.ImplementInterface @@ -50,10 +51,11 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) var service = document.GetRequiredLanguageService(); var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var options = ImplementTypeOptions.From(document.Project); var actions = token.Parent.GetAncestorsOrThis() .Where(_interfaceName) - .Select(n => service.GetCodeActions(document, model, n, cancellationToken)) + .Select(n => service.GetCodeActions(document, options, model, n, cancellationToken)) .FirstOrDefault(a => !a.IsEmpty); if (actions.IsDefaultOrEmpty) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpAddParameterCheckCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpAddParameterCheckCodeRefactoringProvider.cs index e11ffcac31b0b..fe936807d8232 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpAddParameterCheckCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpAddParameterCheckCodeRefactoringProvider.cs @@ -2,18 +2,19 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Composition; -using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using System.Threading; using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.CSharp.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.Shared.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.InitializeParameter; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Extensions; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Microsoft.CodeAnalysis.CSharp.InitializeParameter @@ -40,8 +41,8 @@ protected override bool IsFunctionDeclaration(SyntaxNode node) protected override SyntaxNode GetBody(SyntaxNode functionDeclaration) => InitializeParameterHelpers.GetBody(functionDeclaration); - protected override void InsertStatement(SyntaxEditor editor, SyntaxNode functionDeclaration, bool returnsVoid, SyntaxNode statementToAddAfterOpt, StatementSyntax statement) - => InitializeParameterHelpers.InsertStatement(editor, functionDeclaration, returnsVoid, statementToAddAfterOpt, statement); + protected override void InsertStatement(SyntaxEditor editor, SyntaxNode functionDeclaration, bool returnsVoid, SyntaxNode? statementToAddAfter, StatementSyntax statement) + => InitializeParameterHelpers.InsertStatement(editor, functionDeclaration, returnsVoid, statementToAddAfter, statement); protected override bool IsImplicitConversion(Compilation compilation, ITypeSymbol source, ITypeSymbol destination) => InitializeParameterHelpers.IsImplicitConversion(compilation, source, destination); @@ -92,5 +93,22 @@ protected override StatementSyntax CreateParameterCheckIfStatement(DocumentOptio statement: ifTrueStatement, @else: null); } + + protected override Document? TryAddNullCheckToParameterDeclaration(Document document, ParameterSyntax parameterSyntax, CancellationToken cancellationToken) + { + var tree = parameterSyntax.SyntaxTree; + var options = (CSharpParseOptions)tree.Options; + if (options.LanguageVersion < LanguageVersionExtensions.CSharpNext) + { + return null; + } + + // We expect the syntax tree to already be in memory since we already have a node from the tree + var syntaxRoot = tree.GetRoot(cancellationToken); + syntaxRoot = syntaxRoot.ReplaceNode( + parameterSyntax, + parameterSyntax.WithExclamationExclamationToken(Token(SyntaxKind.ExclamationExclamationToken))); + return document.WithSyntaxRoot(syntaxRoot); + } } } diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromParameterCodeRefactoringProvider.cs index 4bae31bbfc49e..d163b5a58e249 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromParameterCodeRefactoringProvider.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Composition; using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis.CodeRefactorings; @@ -33,11 +31,11 @@ public CSharpInitializeMemberFromParameterCodeRefactoringProvider() protected override bool IsFunctionDeclaration(SyntaxNode node) => InitializeParameterHelpers.IsFunctionDeclaration(node); - protected override SyntaxNode TryGetLastStatement(IBlockOperation blockStatementOpt) - => InitializeParameterHelpers.TryGetLastStatement(blockStatementOpt); + protected override SyntaxNode? TryGetLastStatement(IBlockOperation? blockStatement) + => InitializeParameterHelpers.TryGetLastStatement(blockStatement); - protected override void InsertStatement(SyntaxEditor editor, SyntaxNode functionDeclaration, bool returnsVoid, SyntaxNode statementToAddAfterOpt, StatementSyntax statement) - => InitializeParameterHelpers.InsertStatement(editor, functionDeclaration, returnsVoid, statementToAddAfterOpt, statement); + protected override void InsertStatement(SyntaxEditor editor, SyntaxNode functionDeclaration, bool returnsVoid, SyntaxNode? statementToAddAfter, StatementSyntax statement) + => InitializeParameterHelpers.InsertStatement(editor, functionDeclaration, returnsVoid, statementToAddAfter, statement); protected override bool IsImplicitConversion(Compilation compilation, ITypeSymbol source, ITypeSymbol destination) => InitializeParameterHelpers.IsImplicitConversion(compilation, source, destination); diff --git a/src/Features/CSharp/Portable/InitializeParameter/InitializeParameterHelpers.cs b/src/Features/CSharp/Portable/InitializeParameter/InitializeParameterHelpers.cs index 120a71e15ceac..6555c2fc0ef56 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/InitializeParameterHelpers.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/InitializeParameterHelpers.cs @@ -44,16 +44,16 @@ public static SyntaxNode GetBody(SyntaxNode functionDeclaration) public static bool IsImplicitConversion(Compilation compilation, ITypeSymbol source, ITypeSymbol destination) => compilation.ClassifyConversion(source: source, destination: destination).IsImplicit; - public static SyntaxNode? TryGetLastStatement(IBlockOperation blockStatementOpt) - => blockStatementOpt?.Syntax is BlockSyntax block + public static SyntaxNode? TryGetLastStatement(IBlockOperation? blockStatement) + => blockStatement?.Syntax is BlockSyntax block ? block.Statements.LastOrDefault() - : blockStatementOpt?.Syntax; + : blockStatement?.Syntax; public static void InsertStatement( SyntaxEditor editor, SyntaxNode functionDeclaration, bool returnsVoid, - SyntaxNode statementToAddAfterOpt, + SyntaxNode? statementToAddAfterOpt, StatementSyntax statement) { var body = GetBody(functionDeclaration); diff --git a/src/Features/CSharp/Portable/UpgradeProject/CSharpUpgradeProjectCodeFixProvider.cs b/src/Features/CSharp/Portable/UpgradeProject/CSharpUpgradeProjectCodeFixProvider.cs index bdc0f9bcb06dd..42295cd9112da 100644 --- a/src/Features/CSharp/Portable/UpgradeProject/CSharpUpgradeProjectCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/UpgradeProject/CSharpUpgradeProjectCodeFixProvider.cs @@ -50,6 +50,7 @@ public CSharpUpgradeProjectCodeFixProvider() "CS8912", // error CS8912: Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. "CS8704", // error CS8704: 'Test1' does not implement interface member 'I1.M1()'. 'Test1.M1()' cannot implicitly implement a non-public member in C# 9.0. Please use language version 'preview' or greater. "CS8957", // error CS8957: Conditional expression is not valid in language version '8.0' because a common type was not found between 'int' and ''. To use a target-typed conversion, upgrade to language version '9.0' or greater. + "CS8967", // error CS8967: Newlines inside a non-verbatim interpolated string are not supported in C# 8.0. Please use language version preview or greater. }); public override string UpgradeThisProjectResource => CSharpFeaturesResources.Upgrade_this_project_to_csharp_language_version_0; diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs index 239a1327c5863..6bb483dfd0bd5 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs @@ -148,11 +148,8 @@ public async Task TryGetAsync(ArrayBuilder list, Cancellat using var _3 = ArrayBuilder.GetInstance(out var semanticDocumentBasedAnalyzers); foreach (var stateSet in _stateSets) { - if (_shouldIncludeDiagnostic != null && - !_owner.DiagnosticAnalyzerInfoCache.GetDiagnosticDescriptors(stateSet.Analyzer).Any(a => _shouldIncludeDiagnostic(a.Id))) - { + if (!ShouldIncludeAnalyzer(stateSet.Analyzer, _shouldIncludeDiagnostic, _owner)) continue; - } if (!await TryAddCachedDocumentDiagnosticsAsync(stateSet, AnalysisKind.Syntax, list, cancellationToken).ConfigureAwait(false)) syntaxAnalyzers.Add(stateSet.Analyzer); @@ -186,6 +183,30 @@ public async Task TryGetAsync(ArrayBuilder list, Cancellat { throw ExceptionUtilities.Unreachable; } + + // Local functions + static bool ShouldIncludeAnalyzer( + DiagnosticAnalyzer analyzer, + Func? shouldIncludeDiagnostic, + DiagnosticIncrementalAnalyzer owner) + { + // Special case DocumentDiagnosticAnalyzer to never skip these document analyzers + // based on 'shouldIncludeDiagnostic' predicate. More specifically, TS has special document + // analyzer which report 0 supported diagnostics, but we always want to execute it. + if (analyzer is DocumentDiagnosticAnalyzer) + { + return true; + } + + // Skip analyzer if none of its reported diagnostics should be included. + if (shouldIncludeDiagnostic != null && + !owner.DiagnosticAnalyzerInfoCache.GetDiagnosticDescriptors(analyzer).Any(a => shouldIncludeDiagnostic(a.Id))) + { + return false; + } + + return true; + } } /// diff --git a/src/Features/Core/Portable/ImplementAbstractClass/AbstractImplementAbstractClassCodeFixProvider.cs b/src/Features/Core/Portable/ImplementAbstractClass/AbstractImplementAbstractClassCodeFixProvider.cs index ae201af95dac2..6295abf449ee6 100644 --- a/src/Features/Core/Portable/ImplementAbstractClass/AbstractImplementAbstractClassCodeFixProvider.cs +++ b/src/Features/Core/Portable/ImplementAbstractClass/AbstractImplementAbstractClassCodeFixProvider.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.ImplementType; using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.ImplementAbstractClass @@ -40,8 +41,9 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) if (classNode == null) return; + var options = ImplementTypeOptions.From(document.Project); var data = await ImplementAbstractClassData.TryGetDataAsync( - document, classNode, GetClassIdentifier(classNode), cancellationToken).ConfigureAwait(false); + document, classNode, GetClassIdentifier(classNode), options, cancellationToken).ConfigureAwait(false); if (data == null) return; diff --git a/src/Features/Core/Portable/ImplementAbstractClass/ImplementAbstractClassData.cs b/src/Features/Core/Portable/ImplementAbstractClass/ImplementAbstractClassData.cs index 1ba4b7426c990..fd78fc5a477c8 100644 --- a/src/Features/Core/Portable/ImplementAbstractClass/ImplementAbstractClassData.cs +++ b/src/Features/Core/Portable/ImplementAbstractClass/ImplementAbstractClassData.cs @@ -21,9 +21,10 @@ namespace Microsoft.CodeAnalysis.ImplementAbstractClass { - internal class ImplementAbstractClassData + internal sealed class ImplementAbstractClassData { private readonly Document _document; + private readonly ImplementTypeOptions _options; private readonly SyntaxNode _classNode; private readonly SyntaxToken _classIdentifier; private readonly ImmutableArray<(INamedTypeSymbol type, ImmutableArray members)> _unimplementedMembers; @@ -32,11 +33,12 @@ internal class ImplementAbstractClassData public readonly INamedTypeSymbol AbstractClassType; public ImplementAbstractClassData( - Document document, SyntaxNode classNode, SyntaxToken classIdentifier, + Document document, ImplementTypeOptions options, SyntaxNode classNode, SyntaxToken classIdentifier, INamedTypeSymbol classType, INamedTypeSymbol abstractClassType, ImmutableArray<(INamedTypeSymbol type, ImmutableArray members)> unimplementedMembers) { _document = document; + _options = options; _classNode = classNode; _classIdentifier = classIdentifier; ClassType = classType; @@ -45,7 +47,7 @@ public ImplementAbstractClassData( } public static async Task TryGetDataAsync( - Document document, SyntaxNode classNode, SyntaxToken classIdentifier, CancellationToken cancellationToken) + Document document, SyntaxNode classNode, SyntaxToken classIdentifier, ImplementTypeOptions options, CancellationToken cancellationToken) { var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); if (semanticModel.GetDeclaredSymbol(classNode, cancellationToken) is not INamedTypeSymbol classType) @@ -70,14 +72,14 @@ public ImplementAbstractClassData( return null; return new ImplementAbstractClassData( - document, classNode, classIdentifier, + document, options, classNode, classIdentifier, classType, abstractClassType, unimplementedMembers); } public static async Task TryImplementAbstractClassAsync( - Document document, SyntaxNode classNode, SyntaxToken classIdentifier, CancellationToken cancellationToken) + Document document, SyntaxNode classNode, SyntaxToken classIdentifier, ImplementTypeOptions options, CancellationToken cancellationToken) { - var data = await TryGetDataAsync(document, classNode, classIdentifier, cancellationToken).ConfigureAwait(false); + var data = await TryGetDataAsync(document, classNode, classIdentifier, options, cancellationToken).ConfigureAwait(false); if (data == null) return null; @@ -88,14 +90,8 @@ public async Task ImplementAbstractClassAsync( ISymbol? throughMember, bool? canDelegateAllMembers, CancellationToken cancellationToken) { var compilation = await _document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); - - var options = await _document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); - var propertyGenerationBehavior = options.GetOption(ImplementTypeOptions.PropertyGenerationBehavior); - - var memberDefinitions = GenerateMembers(compilation, throughMember, propertyGenerationBehavior, cancellationToken); - - var insertionBehavior = options.GetOption(ImplementTypeOptions.InsertionBehavior); - var groupMembers = insertionBehavior == ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind; + var memberDefinitions = GenerateMembers(compilation, throughMember, _options.PropertyGenerationBehavior, cancellationToken); + var groupMembers = _options.InsertionBehavior == ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind; // If we're implementing through one of our members, but we can't delegate all members // through it, then give an error message on the class decl letting the user know. @@ -109,6 +105,7 @@ public async Task ImplementAbstractClassAsync( FeaturesResources.Base_classes_contain_inaccessible_unimplemented_members))); } + var options = await _document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); var updatedClassNode = CodeGenerator.AddMemberDeclarations( classNodeToAddMembersTo, memberDefinitions, diff --git a/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.CodeAction.cs b/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.CodeAction.cs index 8d308dfc95efa..8b1dce2749de2 100644 --- a/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.CodeAction.cs +++ b/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.CodeAction.cs @@ -32,6 +32,7 @@ internal partial class ImplementInterfaceCodeAction : CodeAction private readonly bool _onlyRemaining; protected readonly ISymbol ThroughMember; protected readonly Document Document; + protected readonly ImplementTypeOptions Options; protected readonly State State; protected readonly AbstractImplementInterfaceService Service; private readonly string _equivalenceKey; @@ -39,6 +40,7 @@ internal partial class ImplementInterfaceCodeAction : CodeAction internal ImplementInterfaceCodeAction( AbstractImplementInterfaceService service, Document document, + ImplementTypeOptions options, State state, bool explicitly, bool abstractly, @@ -47,6 +49,7 @@ internal ImplementInterfaceCodeAction( { Service = service; Document = document; + Options = options; State = state; Abstractly = abstractly; _onlyRemaining = onlyRemaining; @@ -58,42 +61,47 @@ internal ImplementInterfaceCodeAction( public static ImplementInterfaceCodeAction CreateImplementAbstractlyCodeAction( AbstractImplementInterfaceService service, Document document, + ImplementTypeOptions options, State state) { - return new ImplementInterfaceCodeAction(service, document, state, explicitly: false, abstractly: true, onlyRemaining: true, throughMember: null); + return new ImplementInterfaceCodeAction(service, document, options, state, explicitly: false, abstractly: true, onlyRemaining: true, throughMember: null); } public static ImplementInterfaceCodeAction CreateImplementCodeAction( AbstractImplementInterfaceService service, Document document, + ImplementTypeOptions options, State state) { - return new ImplementInterfaceCodeAction(service, document, state, explicitly: false, abstractly: false, onlyRemaining: true, throughMember: null); + return new ImplementInterfaceCodeAction(service, document, options, state, explicitly: false, abstractly: false, onlyRemaining: true, throughMember: null); } public static ImplementInterfaceCodeAction CreateImplementExplicitlyCodeAction( AbstractImplementInterfaceService service, Document document, + ImplementTypeOptions options, State state) { - return new ImplementInterfaceCodeAction(service, document, state, explicitly: true, abstractly: false, onlyRemaining: false, throughMember: null); + return new ImplementInterfaceCodeAction(service, document, options, state, explicitly: true, abstractly: false, onlyRemaining: false, throughMember: null); } public static ImplementInterfaceCodeAction CreateImplementThroughMemberCodeAction( AbstractImplementInterfaceService service, Document document, + ImplementTypeOptions options, State state, ISymbol throughMember) { - return new ImplementInterfaceCodeAction(service, document, state, explicitly: false, abstractly: false, onlyRemaining: false, throughMember: throughMember); + return new ImplementInterfaceCodeAction(service, document, options, state, explicitly: false, abstractly: false, onlyRemaining: false, throughMember: throughMember); } public static ImplementInterfaceCodeAction CreateImplementRemainingExplicitlyCodeAction( AbstractImplementInterfaceService service, Document document, + ImplementTypeOptions options, State state) { - return new ImplementInterfaceCodeAction(service, document, state, explicitly: true, abstractly: false, onlyRemaining: true, throughMember: null); + return new ImplementInterfaceCodeAction(service, document, options, state, explicitly: true, abstractly: false, onlyRemaining: true, throughMember: null); } public override string Title @@ -189,18 +197,15 @@ protected async Task GetUpdatedDocumentAsync( var compilation = await result.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); var isComImport = unimplementedMembers.Any(t => t.type.IsComImport); - var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); - var propertyGenerationBehavior = options.GetOption(ImplementTypeOptions.PropertyGenerationBehavior); var memberDefinitions = GenerateMembers( - compilation, unimplementedMembers, propertyGenerationBehavior); + compilation, unimplementedMembers, Options.PropertyGenerationBehavior); // Only group the members in the destination if the user wants that *and* // it's not a ComImport interface. Member ordering in ComImport interfaces // matters, so we don't want to much with them. - var insertionBehavior = options.GetOption(ImplementTypeOptions.InsertionBehavior); var groupMembers = !isComImport && - insertionBehavior == ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind; + Options.InsertionBehavior == ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind; return await CodeGenerator.AddMemberDeclarationsAsync( result.Project.Solution, classOrStructType, diff --git a/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.DisposePatternCodeAction.cs b/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.DisposePatternCodeAction.cs index c23cdfc4759c7..ccc4517226375 100644 --- a/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.DisposePatternCodeAction.cs +++ b/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.DisposePatternCodeAction.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.ImplementType; using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -88,27 +89,30 @@ private class ImplementInterfaceWithDisposePatternCodeAction : ImplementInterfac public ImplementInterfaceWithDisposePatternCodeAction( AbstractImplementInterfaceService service, Document document, + ImplementTypeOptions options, State state, bool explicitly, bool abstractly, - ISymbol? throughMember) : base(service, document, state, explicitly, abstractly, onlyRemaining: !explicitly, throughMember) + ISymbol? throughMember) : base(service, document, options, state, explicitly, abstractly, onlyRemaining: !explicitly, throughMember) { } public static ImplementInterfaceWithDisposePatternCodeAction CreateImplementWithDisposePatternCodeAction( AbstractImplementInterfaceService service, Document document, + ImplementTypeOptions options, State state) { - return new ImplementInterfaceWithDisposePatternCodeAction(service, document, state, explicitly: false, abstractly: false, throughMember: null); + return new ImplementInterfaceWithDisposePatternCodeAction(service, document, options, state, explicitly: false, abstractly: false, throughMember: null); } public static ImplementInterfaceWithDisposePatternCodeAction CreateImplementExplicitlyWithDisposePatternCodeAction( AbstractImplementInterfaceService service, Document document, + ImplementTypeOptions options, State state) { - return new ImplementInterfaceWithDisposePatternCodeAction(service, document, state, explicitly: true, abstractly: false, throughMember: null); + return new ImplementInterfaceWithDisposePatternCodeAction(service, document, options, state, explicitly: true, abstractly: false, throughMember: null); } public override string Title diff --git a/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.cs b/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.cs index fec05251bb2e4..b551def32576f 100644 --- a/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.cs +++ b/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.ImplementType; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -35,7 +36,7 @@ protected AbstractImplementInterfaceService() protected abstract SyntaxNode AddCommentInsideIfStatement(SyntaxNode ifDisposingStatement, SyntaxTriviaList trivia); protected abstract SyntaxNode CreateFinalizer(SyntaxGenerator generator, INamedTypeSymbol classType, string disposeMethodDisplayString); - public async Task ImplementInterfaceAsync(Document document, SyntaxNode node, CancellationToken cancellationToken) + public async Task ImplementInterfaceAsync(Document document, ImplementTypeOptions options, SyntaxNode node, CancellationToken cancellationToken) { using (Logger.LogBlock(FunctionId.Refactoring_ImplementInterface, cancellationToken)) { @@ -49,20 +50,20 @@ public async Task ImplementInterfaceAsync(Document document, SyntaxNod // While implementing just one default action, like in the case of pressing enter after interface name in VB, // choose to implement with the dispose pattern as that's the Dev12 behavior. var action = ShouldImplementDisposePattern(state, explicitly: false) - ? ImplementInterfaceWithDisposePatternCodeAction.CreateImplementWithDisposePatternCodeAction(this, document, state) - : ImplementInterfaceCodeAction.CreateImplementCodeAction(this, document, state); + ? ImplementInterfaceWithDisposePatternCodeAction.CreateImplementWithDisposePatternCodeAction(this, document, options, state) + : ImplementInterfaceCodeAction.CreateImplementCodeAction(this, document, options, state); return await action.GetUpdatedDocumentAsync(cancellationToken).ConfigureAwait(false); } } - public ImmutableArray GetCodeActions(Document document, SemanticModel model, SyntaxNode node, CancellationToken cancellationToken) + public ImmutableArray GetCodeActions(Document document, ImplementTypeOptions options, SemanticModel model, SyntaxNode node, CancellationToken cancellationToken) { var state = State.Generate(this, document, model, node, cancellationToken); - return GetActions(document, state).ToImmutableArray(); + return GetActions(document, options, state).ToImmutableArray(); } - private IEnumerable GetActions(Document document, State state) + private IEnumerable GetActions(Document document, ImplementTypeOptions options, State state) { if (state == null) { @@ -71,38 +72,38 @@ private IEnumerable GetActions(Document document, State state) if (state.MembersWithoutExplicitOrImplicitImplementationWhichCanBeImplicitlyImplemented.Length > 0) { - yield return ImplementInterfaceCodeAction.CreateImplementCodeAction(this, document, state); + yield return ImplementInterfaceCodeAction.CreateImplementCodeAction(this, document, options, state); if (ShouldImplementDisposePattern(state, explicitly: false)) { - yield return ImplementInterfaceWithDisposePatternCodeAction.CreateImplementWithDisposePatternCodeAction(this, document, state); + yield return ImplementInterfaceWithDisposePatternCodeAction.CreateImplementWithDisposePatternCodeAction(this, document, options, state); } var delegatableMembers = GetDelegatableMembers(state); foreach (var member in delegatableMembers) { - yield return ImplementInterfaceCodeAction.CreateImplementThroughMemberCodeAction(this, document, state, member); + yield return ImplementInterfaceCodeAction.CreateImplementThroughMemberCodeAction(this, document, options, state, member); } if (state.ClassOrStructType.IsAbstract) { - yield return ImplementInterfaceCodeAction.CreateImplementAbstractlyCodeAction(this, document, state); + yield return ImplementInterfaceCodeAction.CreateImplementAbstractlyCodeAction(this, document, options, state); } } if (state.MembersWithoutExplicitImplementation.Length > 0) { - yield return ImplementInterfaceCodeAction.CreateImplementExplicitlyCodeAction(this, document, state); + yield return ImplementInterfaceCodeAction.CreateImplementExplicitlyCodeAction(this, document, options, state); if (ShouldImplementDisposePattern(state, explicitly: true)) { - yield return ImplementInterfaceWithDisposePatternCodeAction.CreateImplementExplicitlyWithDisposePatternCodeAction(this, document, state); + yield return ImplementInterfaceWithDisposePatternCodeAction.CreateImplementExplicitlyWithDisposePatternCodeAction(this, document, options, state); } } if (AnyImplementedImplicitly(state)) { - yield return ImplementInterfaceCodeAction.CreateImplementRemainingExplicitlyCodeAction(this, document, state); + yield return ImplementInterfaceCodeAction.CreateImplementRemainingExplicitlyCodeAction(this, document, options, state); } } diff --git a/src/Features/Core/Portable/ImplementInterface/IImplementInterfaceService.cs b/src/Features/Core/Portable/ImplementInterface/IImplementInterfaceService.cs index 6d740d31ddabf..6d572fbbcfd53 100644 --- a/src/Features/Core/Portable/ImplementInterface/IImplementInterfaceService.cs +++ b/src/Features/Core/Portable/ImplementInterface/IImplementInterfaceService.cs @@ -9,12 +9,13 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.ImplementType; namespace Microsoft.CodeAnalysis.ImplementInterface { internal interface IImplementInterfaceService : ILanguageService { - Task ImplementInterfaceAsync(Document document, SyntaxNode node, CancellationToken cancellationToken); - ImmutableArray GetCodeActions(Document document, SemanticModel model, SyntaxNode node, CancellationToken cancellationToken); + Task ImplementInterfaceAsync(Document document, ImplementTypeOptions options, SyntaxNode node, CancellationToken cancellationToken); + ImmutableArray GetCodeActions(Document document, ImplementTypeOptions options, SemanticModel model, SyntaxNode node, CancellationToken cancellationToken); } } diff --git a/src/Features/Core/Portable/ImplementType/ImplementTypeInsertionBehavior.cs b/src/Features/Core/Portable/ImplementType/ImplementTypeInsertionBehavior.cs new file mode 100644 index 0000000000000..c88ea79f906e9 --- /dev/null +++ b/src/Features/Core/Portable/ImplementType/ImplementTypeInsertionBehavior.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.ImplementType +{ + internal enum ImplementTypeInsertionBehavior + { + WithOtherMembersOfTheSameKind = 0, + AtTheEnd = 1, + } +} diff --git a/src/Features/Core/Portable/ImplementType/ImplementTypeOptions.cs b/src/Features/Core/Portable/ImplementType/ImplementTypeOptions.cs index 25799f248a500..2a543b075ce87 100644 --- a/src/Features/Core/Portable/ImplementType/ImplementTypeOptions.cs +++ b/src/Features/Core/Portable/ImplementType/ImplementTypeOptions.cs @@ -2,39 +2,53 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; +using System.Composition; +using System.Collections.Immutable; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options.Providers; namespace Microsoft.CodeAnalysis.ImplementType { - internal enum ImplementTypeInsertionBehavior + internal readonly record struct ImplementTypeOptions( + ImplementTypeInsertionBehavior InsertionBehavior, + ImplementTypePropertyGenerationBehavior PropertyGenerationBehavior) { - WithOtherMembersOfTheSameKind = 0, - AtTheEnd = 1, - } + public static ImplementTypeOptions From(Project project) + => From(project.Solution.Options, project.Language); - internal enum ImplementTypePropertyGenerationBehavior - { - PreferThrowingProperties = 0, - PreferAutoProperties = 1, - } + public static ImplementTypeOptions From(OptionSet options, string language) + => new( + InsertionBehavior: options.GetOption(Metadata.InsertionBehavior, language), + PropertyGenerationBehavior: options.GetOption(Metadata.PropertyGenerationBehavior, language)); - internal static class ImplementTypeOptions - { - public static readonly PerLanguageOption2 InsertionBehavior = - new( - nameof(ImplementTypeOptions), - nameof(InsertionBehavior), - defaultValue: ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind, - storageLocation: new RoamingProfileStorageLocation( - $"TextEditor.%LANGUAGE%.{nameof(ImplementTypeOptions)}.{nameof(InsertionBehavior)}")); - - public static readonly PerLanguageOption2 PropertyGenerationBehavior = - new( - nameof(ImplementTypeOptions), - nameof(PropertyGenerationBehavior), - defaultValue: ImplementTypePropertyGenerationBehavior.PreferThrowingProperties, - storageLocation: new RoamingProfileStorageLocation( - $"TextEditor.%LANGUAGE%.{nameof(ImplementTypeOptions)}.{nameof(PropertyGenerationBehavior)}")); + [ExportSolutionOptionProvider, Shared] + internal sealed class Metadata : IOptionProvider + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public Metadata() + { + } + + public ImmutableArray Options { get; } = ImmutableArray.Create( + InsertionBehavior, + PropertyGenerationBehavior); + + private const string FeatureName = "ImplementTypeOptions"; + + public static readonly PerLanguageOption2 InsertionBehavior = + new(FeatureName, + "InsertionBehavior", + defaultValue: ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind, + storageLocation: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.ImplementTypeOptions.InsertionBehavior")); + public static readonly PerLanguageOption2 PropertyGenerationBehavior = + new(FeatureName, + "PropertyGenerationBehavior", + defaultValue: ImplementTypePropertyGenerationBehavior.PreferThrowingProperties, + storageLocation: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.ImplementTypeOptions.PropertyGenerationBehavior")); + } } } diff --git a/src/Features/Core/Portable/ImplementType/ImplementTypeOptionsProvider.cs b/src/Features/Core/Portable/ImplementType/ImplementTypeOptionsProvider.cs deleted file mode 100644 index 937942765e476..0000000000000 --- a/src/Features/Core/Portable/ImplementType/ImplementTypeOptionsProvider.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using System.Composition; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Options.Providers; - -namespace Microsoft.CodeAnalysis.ImplementType -{ - [ExportSolutionOptionProvider, Shared] - internal class ImplementTypeOptionsProvider : IOptionProvider - { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public ImplementTypeOptionsProvider() - { - } - - public ImmutableArray Options { get; } = ImmutableArray.Create( - ImplementTypeOptions.InsertionBehavior); - } -} diff --git a/src/Features/Core/Portable/ImplementType/ImplementTypePropertyGenerationBehavior.cs b/src/Features/Core/Portable/ImplementType/ImplementTypePropertyGenerationBehavior.cs new file mode 100644 index 0000000000000..525c2f8f2ca5e --- /dev/null +++ b/src/Features/Core/Portable/ImplementType/ImplementTypePropertyGenerationBehavior.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.ImplementType +{ + internal enum ImplementTypePropertyGenerationBehavior + { + PreferThrowingProperties = 0, + PreferAutoProperties = 1, + } +} diff --git a/src/Features/Core/Portable/InitializeParameter/AbstractAddParameterCheckCodeRefactoringProvider.cs b/src/Features/Core/Portable/InitializeParameter/AbstractAddParameterCheckCodeRefactoringProvider.cs index 7d2826e900fbd..21c83ad5ae0dd 100644 --- a/src/Features/Core/Portable/InitializeParameter/AbstractAddParameterCheckCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/InitializeParameter/AbstractAddParameterCheckCodeRefactoringProvider.cs @@ -48,6 +48,7 @@ protected AbstractAddParameterCheckCodeRefactoringProvider() protected abstract bool PrefersThrowExpression(DocumentOptionSet options); protected abstract string EscapeResourceString(string input); protected abstract TStatementSyntax CreateParameterCheckIfStatement(DocumentOptionSet options, TExpressionSyntax condition, TStatementSyntax ifTrueStatement); + protected abstract Document? TryAddNullCheckToParameterDeclaration(Document document, TParameterSyntax parameterSyntax, CancellationToken cancellationToken); protected override async Task> GetRefactoringsForAllParametersAsync( Document document, SyntaxNode functionDeclaration, IMethodSymbol methodSymbol, @@ -80,8 +81,13 @@ protected override async Task> GetRefactoringsForAllP } protected override async Task> GetRefactoringsForSingleParameterAsync( - Document document, IParameterSymbol parameter, SyntaxNode functionDeclaration, IMethodSymbol methodSymbol, - IBlockOperation? blockStatementOpt, CancellationToken cancellationToken) + Document document, + TParameterSyntax parameterSyntax, + IParameterSymbol parameter, + SyntaxNode functionDeclaration, + IMethodSymbol methodSymbol, + IBlockOperation? blockStatementOpt, + CancellationToken cancellationToken) { var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); @@ -95,7 +101,7 @@ protected override async Task> GetRefactoringsForSing using var _ = ArrayBuilder.GetInstance(out var result); result.Add(new MyCodeAction( FeaturesResources.Add_null_check, - c => AddNullCheckAsync(document, parameter, functionDeclaration, methodSymbol, blockStatementOpt, c), + c => AddNullCheckAsync(document, parameterSyntax, parameter, functionDeclaration, methodSymbol, blockStatementOpt, c), nameof(FeaturesResources.Add_null_check))); // Also, if this was a string, offer to add the special checks to @@ -134,11 +140,12 @@ private async Task UpdateDocumentForRefactoringAsync( continue; var generator = SyntaxGenerator.GetGenerator(document); - var parameterNodes = generator.GetParameters(functionDeclaration); + var parameterNodes = (IReadOnlyList)generator.GetParameters(functionDeclaration); var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - var parameter = GetParameterAtOrdinal(index, parameterNodes, semanticModel, cancellationToken); + var (parameterSyntax, parameter) = GetParameterAtOrdinal(index, parameterNodes, semanticModel, cancellationToken); if (parameter == null) continue; + Contract.ThrowIfNull(parameterSyntax); var syntaxFacts = document.GetRequiredLanguageService(); @@ -155,25 +162,25 @@ private async Task UpdateDocumentForRefactoringAsync( } // For all other parameters, add null check - updates document - document = await AddNullCheckAsync(document, parameter, functionDeclaration, + document = await AddNullCheckAsync(document, parameterSyntax, parameter, functionDeclaration, (IMethodSymbol)parameter.ContainingSymbol, blockStatementOpt, cancellationToken).ConfigureAwait(false); } return document; } - private static IParameterSymbol? GetParameterAtOrdinal(int index, IReadOnlyList parameterNodes, SemanticModel semanticModel, CancellationToken cancellationToken) + private static (TParameterSyntax?, IParameterSymbol?) GetParameterAtOrdinal(int index, IReadOnlyList parameterNodes, SemanticModel semanticModel, CancellationToken cancellationToken) { foreach (var parameterNode in parameterNodes) { var parameter = (IParameterSymbol)semanticModel.GetRequiredDeclaredSymbol(parameterNode, cancellationToken); if (index == parameter.Ordinal) { - return parameter; + return (parameterNode, parameter); } } - return null; + return default; } private static bool ContainsNullCoalesceCheck( @@ -257,6 +264,11 @@ protected bool ParameterValidForNullCheck(Document document, IParameterSymbol pa return false; } + if (parameter.IsNullChecked) + { + return false; + } + var syntaxFacts = document.GetRequiredLanguageService(); // Look for an existing "if (p == null)" statement, or "p ?? throw" check. If we already @@ -312,19 +324,27 @@ private static bool IsNullCheck(IOperation operand1, IOperation operand2, IParam private async Task AddNullCheckAsync( Document document, + TParameterSyntax parameterSyntax, IParameterSymbol parameter, SyntaxNode functionDeclaration, IMethodSymbol method, IBlockOperation? blockStatementOpt, CancellationToken cancellationToken) { - // First see if we can convert a statement of the form "this.s = s" into "this.s = s ?? throw ...". - var documentOpt = await TryAddNullCheckToAssignmentAsync( + // First see if we can adopt the '!!' parameter null checking syntax. + var modifiedDocument = TryAddNullCheckToParameterDeclaration(document, parameterSyntax, cancellationToken); + if (modifiedDocument != null) + { + return modifiedDocument; + } + + // Then see if we can convert a statement of the form "this.s = s" into "this.s = s ?? throw ...". + modifiedDocument = await TryAddNullCheckToAssignmentAsync( document, parameter, blockStatementOpt, cancellationToken).ConfigureAwait(false); - if (documentOpt != null) + if (modifiedDocument != null) { - return documentOpt; + return modifiedDocument; } // If we can't, then just offer to add an "if (s == null)" statement. diff --git a/src/Features/Core/Portable/InitializeParameter/AbstractInitializeMemberFromParameterCodeRefactoringProviderMemberCreation.cs b/src/Features/Core/Portable/InitializeParameter/AbstractInitializeMemberFromParameterCodeRefactoringProviderMemberCreation.cs index c39837e34e550..87079e49d8f8e 100644 --- a/src/Features/Core/Portable/InitializeParameter/AbstractInitializeMemberFromParameterCodeRefactoringProviderMemberCreation.cs +++ b/src/Features/Core/Portable/InitializeParameter/AbstractInitializeMemberFromParameterCodeRefactoringProviderMemberCreation.cs @@ -39,7 +39,7 @@ internal abstract partial class AbstractInitializeMemberFromParameterCodeRefacto where TStatementSyntax : SyntaxNode where TExpressionSyntax : SyntaxNode { - protected abstract SyntaxNode TryGetLastStatement(IBlockOperation? blockStatementOpt); + protected abstract SyntaxNode? TryGetLastStatement(IBlockOperation? blockStatementOpt); protected abstract Accessibility DetermineDefaultFieldAccessibility(INamedTypeSymbol containingType); @@ -53,8 +53,13 @@ protected override Task> GetRefactoringsForAllParamet } protected override async Task> GetRefactoringsForSingleParameterAsync( - Document document, IParameterSymbol parameter, SyntaxNode constructorDeclaration, IMethodSymbol method, - IBlockOperation? blockStatementOpt, CancellationToken cancellationToken) + Document document, + TParameterSyntax parameterSyntax, + IParameterSymbol parameter, + SyntaxNode constructorDeclaration, + IMethodSymbol method, + IBlockOperation? blockStatementOpt, + CancellationToken cancellationToken) { // Only supported for constructor parameters. if (method.MethodKind != MethodKind.Constructor) diff --git a/src/Features/Core/Portable/InitializeParameter/AbstractInitializeParameterCodeRefactoringProvider.cs b/src/Features/Core/Portable/InitializeParameter/AbstractInitializeParameterCodeRefactoringProvider.cs index 74c5f574f2da0..9046a16c44144 100644 --- a/src/Features/Core/Portable/InitializeParameter/AbstractInitializeParameterCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/InitializeParameter/AbstractInitializeParameterCodeRefactoringProvider.cs @@ -52,6 +52,7 @@ protected abstract Task> GetRefactoringsForAllParamet protected abstract Task> GetRefactoringsForSingleParameterAsync( Document document, + TParameterSyntax parameterSyntax, IParameterSymbol parameter, SyntaxNode functionDeclaration, IMethodSymbol methodSymbol, @@ -60,7 +61,7 @@ protected abstract Task> GetRefactoringsForSinglePara protected abstract void InsertStatement( SyntaxEditor editor, SyntaxNode functionDeclaration, bool returnsVoid, - SyntaxNode? statementToAddAfterOpt, TStatementSyntax statement); + SyntaxNode? statementToAddAfter, TStatementSyntax statement); public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { @@ -115,7 +116,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte // Ok. Looks like the selected parameter could be refactored. Defer to subclass to // actually determine if there are any viable refactorings here. var refactorings = await GetRefactoringsForSingleParameterAsync( - document, parameter, functionDeclaration, methodSymbol, blockStatementOpt, cancellationToken).ConfigureAwait(false); + document, selectedParameter, parameter, functionDeclaration, methodSymbol, blockStatementOpt, cancellationToken).ConfigureAwait(false); context.RegisterRefactorings(refactorings, context.Span); } diff --git a/src/Features/Core/Portable/SemanticClassificationCache/SemanticClassificationCacheService.cs b/src/Features/Core/Portable/SemanticClassificationCache/SemanticClassificationCacheService.cs deleted file mode 100644 index 77fd467c7519c..0000000000000 --- a/src/Features/Core/Portable/SemanticClassificationCache/SemanticClassificationCacheService.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using System.Composition; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Classification; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Remote; -using Microsoft.CodeAnalysis.Storage; -using Microsoft.CodeAnalysis.Text; - -namespace Microsoft.CodeAnalysis.SemanticClassificationCache -{ - [ExportWorkspaceService(typeof(ISemanticClassificationCacheService), ServiceLayer.Editor), Shared] - internal class SemanticClassificationCacheService : ISemanticClassificationCacheService - { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public SemanticClassificationCacheService() - { - } - - public async Task> GetCachedSemanticClassificationsAsync( - Document document, - TextSpan textSpan, - CancellationToken cancellationToken) - { - var client = await RemoteHostClient.TryGetClientAsync(document.Project.Solution.Workspace, cancellationToken).ConfigureAwait(false); - if (client == null) - { - // We don't do anything if we fail to get the external process. That's the case when something has gone - // wrong, or the user is explicitly choosing to run inproc only. In neither of those cases do we want - // to bog down the VS process with the work to semantically classify files. - return default; - } - - var (documentKey, checksum) = await SemanticClassificationCacheUtilities.GetDocumentKeyAndChecksumAsync( - document, cancellationToken).ConfigureAwait(false); - - var database = document.Project.Solution.Options.GetPersistentStorageDatabase(); - - var classifiedSpans = await client.TryInvokeAsync( - (service, cancellationToken) => service.GetCachedSemanticClassificationsAsync(documentKey, textSpan, checksum, database, cancellationToken), - cancellationToken).ConfigureAwait(false); - - if (!classifiedSpans.HasValue || classifiedSpans.Value == null) - return default; - - using var _ = ArrayBuilder.GetInstance(out var result); - classifiedSpans.Value.Rehydrate(result); - return result.ToImmutable(); - } - } -} diff --git a/src/Features/Core/Portable/Structure/BlockStructureOptions.cs b/src/Features/Core/Portable/Structure/BlockStructureOptions.cs index a952ea35c3539..7cfed666438f4 100644 --- a/src/Features/Core/Portable/Structure/BlockStructureOptions.cs +++ b/src/Features/Core/Portable/Structure/BlockStructureOptions.cs @@ -22,6 +22,17 @@ internal record struct BlockStructureOptions( int MaximumBannerLength, bool IsMetadataAsSource) { + public static readonly BlockStructureOptions Default = + new(ShowBlockStructureGuidesForCommentsAndPreprocessorRegions: Metadata.ShowBlockStructureGuidesForCommentsAndPreprocessorRegions.DefaultValue, + ShowBlockStructureGuidesForDeclarationLevelConstructs: Metadata.ShowBlockStructureGuidesForDeclarationLevelConstructs.DefaultValue, + ShowBlockStructureGuidesForCodeLevelConstructs: Metadata.ShowBlockStructureGuidesForCodeLevelConstructs.DefaultValue, + ShowOutliningForCommentsAndPreprocessorRegions: Metadata.ShowOutliningForCommentsAndPreprocessorRegions.DefaultValue, + ShowOutliningForDeclarationLevelConstructs: Metadata.ShowOutliningForDeclarationLevelConstructs.DefaultValue, + ShowOutliningForCodeLevelConstructs: Metadata.ShowOutliningForCodeLevelConstructs.DefaultValue, + CollapseRegionsWhenCollapsingToDefinitions: Metadata.CollapseRegionsWhenCollapsingToDefinitions.DefaultValue, + MaximumBannerLength: Metadata.MaximumBannerLength.DefaultValue, + IsMetadataAsSource: false); + public static BlockStructureOptions From(Project project) => From(project.Solution.Options, project.Language, isMetadataAsSource: project.Solution.Workspace.Kind == WorkspaceKind.MetadataAsSource); diff --git a/src/Features/Core/Portable/Structure/BlockStructureService.cs b/src/Features/Core/Portable/Structure/BlockStructureService.cs index f0c5bd48f30b1..ceb7609380933 100644 --- a/src/Features/Core/Portable/Structure/BlockStructureService.cs +++ b/src/Features/Core/Portable/Structure/BlockStructureService.cs @@ -25,6 +25,6 @@ public static BlockStructureService GetService(Document document) /// public abstract string Language { get; } - public abstract Task GetBlockStructureAsync(Document document, CancellationToken cancellationToken); + public abstract Task GetBlockStructureAsync(Document document, BlockStructureOptions options, CancellationToken cancellationToken); } } diff --git a/src/Features/Core/Portable/Structure/BlockStructureServiceWithProviders.cs b/src/Features/Core/Portable/Structure/BlockStructureServiceWithProviders.cs index 7543d0317b84b..68d58fa6d0a96 100644 --- a/src/Features/Core/Portable/Structure/BlockStructureServiceWithProviders.cs +++ b/src/Features/Core/Portable/Structure/BlockStructureServiceWithProviders.cs @@ -47,9 +47,12 @@ private ImmutableArray GetImportedProviders() public override async Task GetBlockStructureAsync( Document document, + BlockStructureOptions options, CancellationToken cancellationToken) { - var context = await CreateContextAsync(document, cancellationToken).ConfigureAwait(false); + var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + var context = CreateContext(syntaxTree, options, cancellationToken); + return GetBlockStructure(context, _providers); } @@ -62,13 +65,6 @@ public BlockStructure GetBlockStructure( return GetBlockStructure(context, _providers); } - private static async Task CreateContextAsync(Document document, CancellationToken cancellationToken) - { - var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - var options = BlockStructureOptions.From(document.Project); - return CreateContext(syntaxTree, options, cancellationToken); - } - private static BlockStructureContext CreateContext( SyntaxTree syntaxTree, in BlockStructureOptions options, diff --git a/src/Features/LanguageServer/Protocol/Handler/FoldingRanges/FoldingRangesHandler.cs b/src/Features/LanguageServer/Protocol/Handler/FoldingRanges/FoldingRangesHandler.cs index 9b3cfad98143f..c35f190587167 100644 --- a/src/Features/LanguageServer/Protocol/Handler/FoldingRanges/FoldingRangesHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/FoldingRanges/FoldingRangesHandler.cs @@ -44,7 +44,8 @@ public FoldingRangesHandler() return Array.Empty(); } - var blockStructure = await blockStructureService.GetBlockStructureAsync(document, cancellationToken).ConfigureAwait(false); + var options = BlockStructureOptions.From(document.Project); + var blockStructure = await blockStructureService.GetBlockStructureAsync(document, options, cancellationToken).ConfigureAwait(false); if (blockStructure == null) { return Array.Empty(); diff --git a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs index 9e0d42bfb117b..7d24e2c02b2c5 100644 --- a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs +++ b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs @@ -4,10 +4,10 @@ using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Classification; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; @@ -119,7 +119,7 @@ static SemanticTokensHelpers() index++; } - foreach (var roslynTokenType in SemanticTokensHelpers.RoslynCustomTokenTypes) + foreach (var roslynTokenType in RoslynCustomTokenTypes) { TokenTypeToIndex.Add(roslynTokenType, index); index++; @@ -133,6 +133,7 @@ static SemanticTokensHelpers() Document document, Dictionary tokenTypesToIndex, LSP.Range? range, + bool includeSyntacticClassifications, CancellationToken cancellationToken) { var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); @@ -149,19 +150,57 @@ static SemanticTokensHelpers() var isFinalized = document.Project.TryGetCompilation(out var compilation) && compilation == semanticModel.Compilation; document = frozenDocument; - var options = ClassificationOptions.From(document.Project); - var classifiedSpans = Classifier.GetClassifiedSpans(document.Project.Solution.Workspace.Services, semanticModel, textSpan, options, cancellationToken); - Contract.ThrowIfNull(classifiedSpans, "classifiedSpans is null"); + var classifiedSpans = await GetClassifiedSpansForDocumentAsync( + document, textSpan, includeSyntacticClassifications, cancellationToken).ConfigureAwait(false); // Multi-line tokens are not supported by VS (tracked by https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1265495). // Roslyn's classifier however can return multi-line classified spans, so we must break these up into single-line spans. - var updatedClassifiedSpans = ConvertMultiLineToSingleLineSpans(text, classifiedSpans.ToArray()); + var updatedClassifiedSpans = ConvertMultiLineToSingleLineSpans(text, classifiedSpans); // TO-DO: We should implement support for streaming if LSP adds support for it: // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1276300 return (ComputeTokens(text.Lines, updatedClassifiedSpans, tokenTypesToIndex), isFinalized); } + private static async Task GetClassifiedSpansForDocumentAsync( + Document document, + TextSpan textSpan, + bool includeSyntacticClassifications, + CancellationToken cancellationToken) + { + var classificationService = document.GetRequiredLanguageService(); + using var _ = ArrayBuilder.GetInstance(out var classifiedSpans); + + // Case 1 - Generated Razor documents: + // In Razor, the C# syntax classifier does not run on the client. This means we need to return both + // syntactic and semantic classifications. + // Case 2 - C# and VB documents: + // In C#/VB, the syntax classifier runs on the client. This means we only need to return semantic + // classifications. + // + // Ideally, Razor will eventually run the classifier on their end so we can get rid of this special + // casing: https://github.com/dotnet/razor-tooling/issues/5850 + if (includeSyntacticClassifications) + { + // `removeAdditiveSpans` will remove token modifiers such as 'static', which we want to include in LSP. + // `fillInClassifiedSpanGaps` includes whitespace in the results, which we don't care about in LSP. + // Therefore, we set both optional parameters to false. + var spans = await ClassifierHelper.GetClassifiedSpansAsync( + document, textSpan, cancellationToken, removeAdditiveSpans: false, fillInClassifiedSpanGaps: false).ConfigureAwait(false); + classifiedSpans.AddRange(spans); + } + else + { + var options = ClassificationOptions.From(document.Project); + await classificationService.AddSemanticClassificationsAsync( + document, textSpan, options, classifiedSpans, cancellationToken).ConfigureAwait(false); + } + + // Classified spans are not guaranteed to be returned in a certain order so we sort them to be safe. + classifiedSpans.Sort(ClassifiedSpanComparer.Instance); + return classifiedSpans.ToArray(); + } + private static ClassifiedSpan[] ConvertMultiLineToSingleLineSpans(SourceText text, ClassifiedSpan[] classifiedSpans) { using var _ = ArrayBuilder.GetInstance(out var updatedClassifiedSpans); @@ -371,5 +410,12 @@ private static int GetTokenTypeIndex(string classificationType, Dictionary + { + public static readonly ClassifiedSpanComparer Instance = new(); + + public int Compare(ClassifiedSpan x, ClassifiedSpan y) => x.TextSpan.CompareTo(y.TextSpan); + } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensRangeHandler.cs b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensRangeHandler.cs index e06cb63ec89e6..d2ff7b10ab2ae 100644 --- a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensRangeHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensRangeHandler.cs @@ -50,8 +50,11 @@ public SemanticTokensRangeHandler() // document request, so caching range results is unnecessary since the whole document // handler will cache the results anyway. var (tokensData, isFinalized) = await SemanticTokensHelpers.ComputeSemanticTokensDataAsync( - context.Document, SemanticTokensHelpers.TokenTypeToIndex, - request.Range, cancellationToken).ConfigureAwait(false); + context.Document, + SemanticTokensHelpers.TokenTypeToIndex, + request.Range, + includeSyntacticClassifications: context.Document.IsRazorDocument(), + cancellationToken).ConfigureAwait(false); return new RoslynSemanticTokens { Data = tokensData, IsFinalized = isFinalized }; } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/SemanticTokens/AbstractSemanticTokensTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/SemanticTokens/AbstractSemanticTokensTests.cs index d1765e5924b10..3b8315a864103 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/SemanticTokens/AbstractSemanticTokensTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/SemanticTokens/AbstractSemanticTokensTests.cs @@ -7,9 +7,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; -using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.LanguageServer.Protocol; using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; diff --git a/src/Features/LanguageServer/ProtocolUnitTests/SemanticTokens/SemanticTokensRangeTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/SemanticTokens/SemanticTokensRangeTests.cs index 5c9189f1c3ad0..f7306bacc96e1 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/SemanticTokens/SemanticTokensRangeTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/SemanticTokens/SemanticTokensRangeTests.cs @@ -4,7 +4,9 @@ #nullable enable +using System; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Classification; using Microsoft.CodeAnalysis.LanguageServer.Handler.SemanticTokens; @@ -28,6 +30,30 @@ static class C { } var range = new LSP.Range { Start = new Position(0, 0), End = new Position(2, 0) }; var results = await RunGetSemanticTokensRangeAsync(testLspServer, testLspServer.GetLocations("caret").First(), range); + // Everything is colorized syntactically, so we shouldn't be returning any semantic results. + var expectedResults = new LSP.SemanticTokens + { + Data = Array.Empty() + }; + + Assert.Equal(expectedResults.Data, results.Data); + } + + [Fact] + public async Task TestGetSemanticTokensRange_FullDoc_RazorAsync() + { + // Razor docs should be returning semantic + syntactic reuslts. + var markup = +@"{|caret:|}// Comment +static class C { } +"; + using var testLspServer = await CreateTestLspServerAsync(markup); + + var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); + var range = new LSP.Range { Start = new Position(0, 0), End = new Position(2, 0) }; + var (results, _) = await SemanticTokensHelpers.ComputeSemanticTokensDataAsync( + document, SemanticTokensHelpers.TokenTypeToIndex, range, includeSyntacticClassifications: true, CancellationToken.None); + var expectedResults = new LSP.SemanticTokens { Data = new int[] @@ -42,21 +68,24 @@ static class C { } }, }; - await VerifyNoMultiLineTokens(testLspServer, results.Data!).ConfigureAwait(false); - Assert.Equal(expectedResults.Data, results.Data); + await VerifyNoMultiLineTokens(testLspServer, results).ConfigureAwait(false); + Assert.Equal(expectedResults.Data, results); } [Fact] - public async Task TestGetSemanticTokensRange_PartialDocAsync() + public async Task TestGetSemanticTokensRange_PartialDoc_RazorAsync() { + // Razor docs should be returning semantic + syntactic reuslts. var markup = @"{|caret:|}// Comment static class C { } "; using var testLspServer = await CreateTestLspServerAsync(markup); + var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); var range = new LSP.Range { Start = new Position(1, 0), End = new Position(2, 0) }; - var results = await RunGetSemanticTokensRangeAsync(testLspServer, testLspServer.GetLocations("caret").First(), range); + var (results, _) = await SemanticTokensHelpers.ComputeSemanticTokensDataAsync( + document, SemanticTokensHelpers.TokenTypeToIndex, range, includeSyntacticClassifications: true, CancellationToken.None); var expectedResults = new LSP.SemanticTokens { @@ -71,21 +100,25 @@ static class C { } }, }; - await VerifyNoMultiLineTokens(testLspServer, results.Data!).ConfigureAwait(false); - Assert.Equal(expectedResults.Data, results.Data); + await VerifyNoMultiLineTokens(testLspServer, results).ConfigureAwait(false); + Assert.Equal(expectedResults.Data, results); } [Fact] - public async Task TestGetSemanticTokensRange_MultiLineCommentAsync() + public async Task TestGetSemanticTokensRange_MultiLineComment_RazorAsync() { + // Testing as a Razor doc so we get both syntactic + semantic results; otherwise the results would be empty. var markup = @"{|caret:|}class C { /* one two three */ } "; - var range = new LSP.Range { Start = new Position(0, 0), End = new Position(3, 0) }; using var testLspServer = await CreateTestLspServerAsync(markup); - var results = await RunGetSemanticTokensRangeAsync(testLspServer, testLspServer.GetLocations("caret").First(), range); + + var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); + var range = new LSP.Range { Start = new Position(0, 0), End = new Position(3, 0) }; + var (results, _) = await SemanticTokensHelpers.ComputeSemanticTokensDataAsync( + document, SemanticTokensHelpers.TokenTypeToIndex, range, includeSyntacticClassifications: true, CancellationToken.None); var expectedResults = new LSP.SemanticTokens { @@ -102,8 +135,8 @@ public async Task TestGetSemanticTokensRange_MultiLineCommentAsync() }, }; - await VerifyNoMultiLineTokens(testLspServer, results.Data!).ConfigureAwait(false); - Assert.Equal(expectedResults.Data, results.Data); + await VerifyNoMultiLineTokens(testLspServer, results).ConfigureAwait(false); + Assert.Equal(expectedResults.Data, results); } [Fact] @@ -125,6 +158,42 @@ void M() var range = new LSP.Range { Start = new Position(0, 0), End = new Position(9, 0) }; var results = await RunGetSemanticTokensRangeAsync(testLspServer, testLspServer.GetLocations("caret").First(), range); + var expectedResults = new LSP.SemanticTokens + { + Data = new int[] + { + // Line | Char | Len | Token type | Modifier + 4, 8, 3, SemanticTokensHelpers.TokenTypeToIndex[ClassificationTypeNames.Keyword], 0, // 'var' + 1, 4, 2, SemanticTokensHelpers.TokenTypeToIndex[ClassificationTypeNames.StringEscapeCharacter], 0, // '""' + }, + }; + + await VerifyNoMultiLineTokens(testLspServer, results.Data!).ConfigureAwait(false); + Assert.Equal(expectedResults.Data, results.Data); + } + + [Fact] + public async Task TestGetSemanticTokensRange_StringLiteral_RazorAsync() + { + var markup = +@"{|caret:|}class C +{ + void M() + { + var x = @""one +two """" +three""; + } +} +"; + + using var testLspServer = await CreateTestLspServerAsync(markup); + + var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); + var range = new LSP.Range { Start = new Position(0, 0), End = new Position(9, 0) }; + var (results, _) = await SemanticTokensHelpers.ComputeSemanticTokensDataAsync( + document, SemanticTokensHelpers.TokenTypeToIndex, range, includeSyntacticClassifications: true, CancellationToken.None); + var expectedResults = new LSP.SemanticTokens { Data = new int[] @@ -151,8 +220,72 @@ void M() }, }; - await VerifyNoMultiLineTokens(testLspServer, results.Data!).ConfigureAwait(false); - Assert.Equal(expectedResults.Data, results.Data); + await VerifyNoMultiLineTokens(testLspServer, results).ConfigureAwait(false); + Assert.Equal(expectedResults.Data, results); + } + + [Fact] + public async Task TestGetSemanticTokensRange_Regex_RazorAsync() + { + var markup = +@"{|caret:|}using System.Text.RegularExpressions; + +class C +{ + void M() + { + var x = new Regex(""(abc)*""); + } +} +"; + + using var testLspServer = await CreateTestLspServerAsync(markup); + + var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); + var range = new LSP.Range { Start = new Position(0, 0), End = new Position(9, 0) }; + var (results, _) = await SemanticTokensHelpers.ComputeSemanticTokensDataAsync( + document, SemanticTokensHelpers.TokenTypeToIndex, range, includeSyntacticClassifications: true, CancellationToken.None); + + var expectedResults = new LSP.SemanticTokens + { + Data = new int[] + { + // Line | Char | Len | Token type | Modifier + 0, 0, 5, SemanticTokensHelpers.TokenTypeToIndex[LSP.SemanticTokenTypes.Keyword], 0, // 'using' + 0, 6, 6, SemanticTokensHelpers.TokenTypeToIndex[LSP.SemanticTokenTypes.Variable], 0, // 'System' + 0, 6, 1, SemanticTokensHelpers.TokenTypeToIndex[LSP.SemanticTokenTypes.Operator], 0, // '.' + 0, 1, 4, SemanticTokensHelpers.TokenTypeToIndex[LSP.SemanticTokenTypes.Variable], 0, // 'Text' + 0, 4, 1, SemanticTokensHelpers.TokenTypeToIndex[LSP.SemanticTokenTypes.Operator], 0, // '.' + 0, 1, 18, SemanticTokensHelpers.TokenTypeToIndex[ClassificationTypeNames.NamespaceName], 0, // 'RegularExpressions' + 0, 18, 1, SemanticTokensHelpers.TokenTypeToIndex[ClassificationTypeNames.Punctuation], 0, // ';' + 2, 0, 5, SemanticTokensHelpers.TokenTypeToIndex[LSP.SemanticTokenTypes.Keyword], 0, // 'class' + 0, 6, 1, SemanticTokensHelpers.TokenTypeToIndex[ClassificationTypeNames.ClassName], 0, // 'C' + 1, 0, 1, SemanticTokensHelpers.TokenTypeToIndex[ClassificationTypeNames.Punctuation], 0, // '{' + 1, 1, 4, SemanticTokensHelpers.TokenTypeToIndex[LSP.SemanticTokenTypes.Keyword], 0, // 'void' + 0, 5, 1, SemanticTokensHelpers.TokenTypeToIndex[ClassificationTypeNames.MethodName], 0, // 'M' + 0, 1, 1, SemanticTokensHelpers.TokenTypeToIndex[ClassificationTypeNames.Punctuation], 0, // '(' + 0, 1, 1, SemanticTokensHelpers.TokenTypeToIndex[ClassificationTypeNames.Punctuation], 0, // ')' + 1, 1, 1, SemanticTokensHelpers.TokenTypeToIndex[ClassificationTypeNames.Punctuation], 0, // '{' + 1, 2, 3, SemanticTokensHelpers.TokenTypeToIndex[ClassificationTypeNames.Keyword], 0, // 'var' + 0, 4, 1, SemanticTokensHelpers.TokenTypeToIndex[ClassificationTypeNames.LocalName], 0, // 'x' + 0, 2, 1, SemanticTokensHelpers.TokenTypeToIndex[LSP.SemanticTokenTypes.Operator], 0, // '=' + 0, 2, 3, SemanticTokensHelpers.TokenTypeToIndex[LSP.SemanticTokenTypes.Keyword], 0, // 'new' + 0, 4, 5, SemanticTokensHelpers.TokenTypeToIndex[LSP.SemanticTokenTypes.Variable], 0, // 'Regex' + 0, 5, 1, SemanticTokensHelpers.TokenTypeToIndex[ClassificationTypeNames.Punctuation], 0, // '(' + 0, 1, 8, SemanticTokensHelpers.TokenTypeToIndex[LSP.SemanticTokenTypes.String], 0, // '"(abc)*"' + 0, 1, 1, SemanticTokensHelpers.TokenTypeToIndex[ClassificationTypeNames.RegexGrouping], 0, // '(' + 0, 1, 3, SemanticTokensHelpers.TokenTypeToIndex[ClassificationTypeNames.RegexText], 0, // 'abc' + 0, 3, 1, SemanticTokensHelpers.TokenTypeToIndex[ClassificationTypeNames.RegexGrouping], 0, // ')' + 0, 1, 1, SemanticTokensHelpers.TokenTypeToIndex[ClassificationTypeNames.RegexQuantifier], 0, // '*' + 0, 2, 1, SemanticTokensHelpers.TokenTypeToIndex[ClassificationTypeNames.Punctuation], 0, // ')' + 0, 1, 1, SemanticTokensHelpers.TokenTypeToIndex[ClassificationTypeNames.Punctuation], 0, // ';' + 1, 4, 1, SemanticTokensHelpers.TokenTypeToIndex[ClassificationTypeNames.Punctuation], 0, // } + 1, 0, 1, SemanticTokensHelpers.TokenTypeToIndex[ClassificationTypeNames.Punctuation], 0, // } + } + }; + + await VerifyNoMultiLineTokens(testLspServer, results).ConfigureAwait(false); + Assert.Equal(expectedResults.Data, results); } } } diff --git a/src/Features/VisualBasic/Portable/ImplementInterface/VisualBasicImplementInterfaceCodeFixProvider.vb b/src/Features/VisualBasic/Portable/ImplementInterface/VisualBasicImplementInterfaceCodeFixProvider.vb index 4570e4732a071..4d4081405d9db 100644 --- a/src/Features/VisualBasic/Portable/ImplementInterface/VisualBasicImplementInterfaceCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/ImplementInterface/VisualBasicImplementInterfaceCodeFixProvider.vb @@ -7,6 +7,7 @@ Imports System.Composition Imports System.Diagnostics.CodeAnalysis Imports Microsoft.CodeAnalysis.CodeFixes Imports Microsoft.CodeAnalysis.ImplementInterface +Imports Microsoft.CodeAnalysis.ImplementType Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.ImplementInterface @@ -57,9 +58,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ImplementInterface Return End If + Dim options = ImplementTypeOptions.From(document.Project) Dim service = document.GetLanguageService(Of IImplementInterfaceService)() Dim actions = service.GetCodeActions( document, + options, Await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(False), typeNode, cancellationToken) diff --git a/src/Features/VisualBasic/Portable/InitializeParameter/VisualBasicAddParameterCheckCodeRefactoringProvider.vb b/src/Features/VisualBasic/Portable/InitializeParameter/VisualBasicAddParameterCheckCodeRefactoringProvider.vb index 0aab6c4a37fdc..20c4080b8f690 100644 --- a/src/Features/VisualBasic/Portable/InitializeParameter/VisualBasicAddParameterCheckCodeRefactoringProvider.vb +++ b/src/Features/VisualBasic/Portable/InitializeParameter/VisualBasicAddParameterCheckCodeRefactoringProvider.vb @@ -4,6 +4,7 @@ Imports System.Composition Imports System.Diagnostics.CodeAnalysis +Imports System.Threading Imports Microsoft.CodeAnalysis.CodeRefactorings Imports Microsoft.CodeAnalysis.Editing Imports Microsoft.CodeAnalysis.InitializeParameter @@ -62,5 +63,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.InitializeParameter elseIfBlocks:=Nothing, elseBlock:=Nothing) End Function + + Protected Overrides Function TryAddNullCheckToParameterDeclaration(document As Document, parameterSyntax As ParameterSyntax, cancellationToken As CancellationToken) As Document + Return Nothing + End Function End Class End Namespace diff --git a/src/Tools/ExternalAccess/FSharp/Internal/Structure/FSharpBlockStructureService.cs b/src/Tools/ExternalAccess/FSharp/Internal/Structure/FSharpBlockStructureService.cs index 588bc7fb239ef..5826b8fdda73d 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/Structure/FSharpBlockStructureService.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/Structure/FSharpBlockStructureService.cs @@ -29,7 +29,7 @@ public FSharpBlockStructureService(IFSharpBlockStructureService service) public override string Language => LanguageNames.FSharp; - public override async Task GetBlockStructureAsync(Document document, CancellationToken cancellationToken) + public override async Task GetBlockStructureAsync(Document document, BlockStructureOptions options, CancellationToken cancellationToken) { var blockStructure = await _service.GetBlockStructureAsync(document, cancellationToken).ConfigureAwait(false); if (blockStructure != null) diff --git a/src/Tools/ExternalAccess/OmniSharp/Completion/OmniSharpCompletionOptions.cs b/src/Tools/ExternalAccess/OmniSharp/Completion/OmniSharpCompletionOptions.cs new file mode 100644 index 0000000000000..ff99d9b65b090 --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/Completion/OmniSharpCompletionOptions.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.Completion; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Completion +{ + internal readonly record struct OmniSharpCompletionOptions( + bool ShowItemsFromUnimportedNamespaces) + { + internal CompletionOptions ToCompletionOptions() + => CompletionOptions.Default with { ShowItemsFromUnimportedNamespaces = ShowItemsFromUnimportedNamespaces }; + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp/Completion/OmniSharpCompletionService.cs b/src/Tools/ExternalAccess/OmniSharp/Completion/OmniSharpCompletionService.cs index 7413e21d5d498..fb5c4d40c432b 100644 --- a/src/Tools/ExternalAccess/OmniSharp/Completion/OmniSharpCompletionService.cs +++ b/src/Tools/ExternalAccess/OmniSharp/Completion/OmniSharpCompletionService.cs @@ -7,24 +7,35 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion; -using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Completion { internal static class OmniSharpCompletionService { - public static Task<(CompletionList? completionList, bool expandItemsAvailable)> GetCompletionsAsync( + public static async ValueTask ShouldTriggerCompletionAsync( this CompletionService completionService, Document document, int caretPosition, CompletionTrigger trigger, ImmutableHashSet? roles, + OmniSharpCompletionOptions options, CancellationToken cancellationToken) - => completionService.GetCompletionsInternalAsync(document, caretPosition, CompletionOptions.Default, trigger, roles, cancellationToken); + { + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + return completionService.ShouldTriggerCompletion(document.Project, document.Project.LanguageServices, text, caretPosition, trigger, options.ToCompletionOptions(), roles); + } - public static string? GetProviderName(this CompletionItem completionItem) => completionItem.ProviderName; + public static Task<(CompletionList? completionList, bool expandItemsAvailable)> GetCompletionsAsync( + this CompletionService completionService, + Document document, + int caretPosition, + CompletionTrigger trigger, + ImmutableHashSet? roles, + OmniSharpCompletionOptions options, + CancellationToken cancellationToken) + => completionService.GetCompletionsInternalAsync(document, caretPosition, options.ToCompletionOptions(), trigger, roles, cancellationToken); - public static bool? IncludeItemsFromUnimportedNamespaces(Document document) - => document.Project.Solution.Options.GetOption(CompletionOptions.Metadata.ShowItemsFromUnimportedNamespaces, document.Project.Language); + public static string? GetProviderName(this CompletionItem completionItem) => completionItem.ProviderName; } } diff --git a/src/Tools/ExternalAccess/OmniSharp/ImplementType/OmniSharpImplementTypeOptions.cs b/src/Tools/ExternalAccess/OmniSharp/ImplementType/OmniSharpImplementTypeOptions.cs index 121e0f7f342ec..6a7c48179da23 100644 --- a/src/Tools/ExternalAccess/OmniSharp/ImplementType/OmniSharpImplementTypeOptions.cs +++ b/src/Tools/ExternalAccess/OmniSharp/ImplementType/OmniSharpImplementTypeOptions.cs @@ -10,16 +10,16 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.ImplementType internal static class OmniSharpImplementTypeOptions { public static OmniSharpImplementTypeInsertionBehavior GetInsertionBehavior(OptionSet options, string language) - => (OmniSharpImplementTypeInsertionBehavior)options.GetOption(ImplementTypeOptions.InsertionBehavior, language); + => (OmniSharpImplementTypeInsertionBehavior)options.GetOption(ImplementTypeOptions.Metadata.InsertionBehavior, language); public static OptionSet SetInsertionBehavior(OptionSet options, string language, OmniSharpImplementTypeInsertionBehavior value) - => options.WithChangedOption(ImplementTypeOptions.InsertionBehavior, language, (ImplementTypeInsertionBehavior)value); + => options.WithChangedOption(ImplementTypeOptions.Metadata.InsertionBehavior, language, (ImplementTypeInsertionBehavior)value); public static OmniSharpImplementTypePropertyGenerationBehavior GetPropertyGenerationBehavior(OptionSet options, string language) - => (OmniSharpImplementTypePropertyGenerationBehavior)options.GetOption(ImplementTypeOptions.PropertyGenerationBehavior, language); + => (OmniSharpImplementTypePropertyGenerationBehavior)options.GetOption(ImplementTypeOptions.Metadata.PropertyGenerationBehavior, language); public static OptionSet SetPropertyGenerationBehavior(OptionSet options, string language, OmniSharpImplementTypePropertyGenerationBehavior value) - => options.WithChangedOption(ImplementTypeOptions.PropertyGenerationBehavior, language, (ImplementTypePropertyGenerationBehavior)value); + => options.WithChangedOption(ImplementTypeOptions.Metadata.PropertyGenerationBehavior, language, (ImplementTypePropertyGenerationBehavior)value); } internal enum OmniSharpImplementTypeInsertionBehavior diff --git a/src/Tools/ExternalAccess/OmniSharp/Rename/OmniSharpRenameOptions.cs b/src/Tools/ExternalAccess/OmniSharp/Rename/OmniSharpRenameOptions.cs new file mode 100644 index 0000000000000..647202d6d5bdc --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/Rename/OmniSharpRenameOptions.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.Rename; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp +{ + internal readonly record struct OmniSharpRenameOptions( + bool RenameInComments, + bool RenameInStrings, + bool RenameOverloads) + { + internal RenameOptionSet ToRenameOptions() + => new( + RenameOverloads: RenameOverloads, + RenameInStrings: RenameInStrings, + RenameInComments: RenameInComments, + RenameFile: false); + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp/Rename/OmniSharpRenamer.cs b/src/Tools/ExternalAccess/OmniSharp/Rename/OmniSharpRenamer.cs new file mode 100644 index 0000000000000..d62aced448ddb --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/Rename/OmniSharpRenamer.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Rename; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp +{ + internal static class OmniSharpRenamer + { + public static Task RenameSymbolAsync( + Solution solution, + ISymbol symbol, + string newName, + OmniSharpRenameOptions options, + ImmutableHashSet? nonConflictSymbols, + CancellationToken cancellationToken) + => Renamer.RenameSymbolAsync(solution, symbol, newName, options.ToRenameOptions(), nonConflictSymbols, cancellationToken); + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockStructureOptions.cs b/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockStructureOptions.cs index 3a85176e96456..52b0b27612bd5 100644 --- a/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockStructureOptions.cs +++ b/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockStructureOptions.cs @@ -3,17 +3,19 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; -using System.Text; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Structure; namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Structure { - internal static class OmniSharpBlockStructureOptions + internal readonly record struct OmniSharpBlockStructureOptions( + bool ShowBlockStructureGuidesForCommentsAndPreprocessorRegions, + bool ShowOutliningForCommentsAndPreprocessorRegions) { - public static readonly PerLanguageOption ShowBlockStructureGuidesForCommentsAndPreprocessorRegions = (PerLanguageOption)BlockStructureOptions.Metadata.ShowBlockStructureGuidesForCommentsAndPreprocessorRegions; - - public static readonly PerLanguageOption ShowOutliningForCommentsAndPreprocessorRegions = (PerLanguageOption)BlockStructureOptions.Metadata.ShowOutliningForCommentsAndPreprocessorRegions; + internal BlockStructureOptions ToBlockStructureOptions() + => BlockStructureOptions.Default with + { + ShowBlockStructureGuidesForCommentsAndPreprocessorRegions = ShowBlockStructureGuidesForCommentsAndPreprocessorRegions, + ShowOutliningForCommentsAndPreprocessorRegions = ShowOutliningForCommentsAndPreprocessorRegions, + }; } } diff --git a/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockStructureService.cs b/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockStructureService.cs index 7b56f4a38cf46..8b353d48bbda0 100644 --- a/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockStructureService.cs +++ b/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockStructureService.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Structure; @@ -13,10 +11,10 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Structure { internal static class OmniSharpBlockStructureService { - public static async Task GetBlockStructureAsync(Document document, CancellationToken cancellationToken) + public static async Task GetBlockStructureAsync(Document document, OmniSharpBlockStructureOptions options, CancellationToken cancellationToken) { var service = document.GetRequiredLanguageService(); - var blockStructure = await service.GetBlockStructureAsync(document, cancellationToken).ConfigureAwait(false); + var blockStructure = await service.GetBlockStructureAsync(document, options.ToBlockStructureOptions(), cancellationToken).ConfigureAwait(false); if (blockStructure != null) { return new OmniSharpBlockStructure(blockStructure.Spans.SelectAsArray(x => new OmniSharpBlockSpan(x.Type, x.IsCollapsible, x.TextSpan, x.HintSpan, x.BannerText, x.AutoCollapse, x.IsDefaultCollapsed))); diff --git a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml.cs b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml.cs index 039e54099fa28..f71c20f945332 100644 --- a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml.cs +++ b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml.cs @@ -117,11 +117,11 @@ public AdvancedOptionPageControl(OptionStore optionStore, IComponentModel compon BindToOption(DontPutOutOrRefOnStruct, ExtractMethodOptions.DontPutOutOrRefOnStruct, LanguageNames.CSharp); - BindToOption(with_other_members_of_the_same_kind, ImplementTypeOptions.InsertionBehavior, ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind, LanguageNames.CSharp); - BindToOption(at_the_end, ImplementTypeOptions.InsertionBehavior, ImplementTypeInsertionBehavior.AtTheEnd, LanguageNames.CSharp); + BindToOption(with_other_members_of_the_same_kind, ImplementTypeOptions.Metadata.InsertionBehavior, ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind, LanguageNames.CSharp); + BindToOption(at_the_end, ImplementTypeOptions.Metadata.InsertionBehavior, ImplementTypeInsertionBehavior.AtTheEnd, LanguageNames.CSharp); - BindToOption(prefer_throwing_properties, ImplementTypeOptions.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferThrowingProperties, LanguageNames.CSharp); - BindToOption(prefer_auto_properties, ImplementTypeOptions.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferAutoProperties, LanguageNames.CSharp); + BindToOption(prefer_throwing_properties, ImplementTypeOptions.Metadata.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferThrowingProperties, LanguageNames.CSharp); + BindToOption(prefer_auto_properties, ImplementTypeOptions.Metadata.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferAutoProperties, LanguageNames.CSharp); BindToOption(Report_invalid_placeholders_in_string_dot_format_calls, ValidateFormatStringOption.ReportInvalidPlaceholdersInStringDotFormatCalls, LanguageNames.CSharp); diff --git a/src/VisualStudio/CSharp/Test/CodeModel/AbstractFileCodeElementTests.cs b/src/VisualStudio/CSharp/Test/CodeModel/AbstractFileCodeElementTests.cs index 37c6513339b0b..d97aad4ea707d 100644 --- a/src/VisualStudio/CSharp/Test/CodeModel/AbstractFileCodeElementTests.cs +++ b/src/VisualStudio/CSharp/Test/CodeModel/AbstractFileCodeElementTests.cs @@ -23,14 +23,14 @@ namespace Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.CodeModel public abstract class AbstractFileCodeElementTests : IDisposable { private readonly string _contents; - private (TestWorkspace workspace, VisualStudioWorkspace extraWorkspaceToDisposeButNotUse, FileCodeModel fileCodeModel)? _workspaceAndCodeModel; + private (TestWorkspace workspace, FileCodeModel fileCodeModel)? _workspaceAndCodeModel; protected AbstractFileCodeElementTests(string contents) { _contents = contents; } - public (TestWorkspace workspace, VisualStudioWorkspace extraWorkspaceToDisposeButNotUse, FileCodeModel fileCodeModel) WorkspaceAndCodeModel + public (TestWorkspace workspace, FileCodeModel fileCodeModel) WorkspaceAndCodeModel { get { @@ -43,11 +43,6 @@ protected TestWorkspace GetWorkspace() return WorkspaceAndCodeModel.workspace; } - private VisualStudioWorkspace GetExtraWorkspaceToDisposeButNotUse() - { - return WorkspaceAndCodeModel.extraWorkspaceToDisposeButNotUse; - } - protected FileCodeModel GetCodeModel() { return WorkspaceAndCodeModel.fileCodeModel; @@ -62,7 +57,7 @@ protected Microsoft.CodeAnalysis.Project GetCurrentProject() protected Microsoft.CodeAnalysis.Document GetCurrentDocument() => GetCurrentProject().Documents.Single(); - protected static (TestWorkspace workspace, VisualStudioWorkspace extraWorkspaceToDisposeButNotUse, FileCodeModel fileCodeModel) CreateWorkspaceAndFileCodeModelAsync(string file) + protected static (TestWorkspace workspace, FileCodeModel fileCodeModel) CreateWorkspaceAndFileCodeModelAsync(string file) => FileCodeModelTestHelpers.CreateWorkspaceAndFileCodeModel(file); protected CodeElement GetCodeElement(params object[] path) @@ -86,7 +81,6 @@ protected CodeElement GetCodeElement(params object[] path) public void Dispose() { - GetExtraWorkspaceToDisposeButNotUse().Dispose(); GetWorkspace().Dispose(); } diff --git a/src/VisualStudio/CSharp/Test/CodeModel/FileCodeModelTestHelpers.cs b/src/VisualStudio/CSharp/Test/CodeModel/FileCodeModelTestHelpers.cs index a5a104ec42953..c481819cb2de3 100644 --- a/src/VisualStudio/CSharp/Test/CodeModel/FileCodeModelTestHelpers.cs +++ b/src/VisualStudio/CSharp/Test/CodeModel/FileCodeModelTestHelpers.cs @@ -13,6 +13,7 @@ using Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel; using Microsoft.VisualStudio.LanguageServices.Implementation.Interop; using Microsoft.VisualStudio.LanguageServices.UnitTests; +using Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel; using Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel.Mocks; using static Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel.CodeModelTestHelpers; @@ -26,20 +27,22 @@ internal static class FileCodeModelTestHelpers // finalizer complaining we didn't clean it up. Catching AVs is of course not safe, but this is balancing // "probably not crash" as an improvement over "will crash when the finalizer throws." [HandleProcessCorruptedStateExceptions] - public static (TestWorkspace workspace, VisualStudioWorkspace extraWorkspaceToDisposeButNotUse, EnvDTE.FileCodeModel fileCodeModel) CreateWorkspaceAndFileCodeModel(string file) + public static (TestWorkspace workspace, EnvDTE.FileCodeModel fileCodeModel) CreateWorkspaceAndFileCodeModel(string file) { - var workspace = TestWorkspace.CreateCSharp(file, composition: VisualStudioTestCompositions.LanguageServices); + var workspace = TestWorkspace.CreateCSharp(file, composition: CodeModelTestHelpers.Composition); try { var project = workspace.CurrentSolution.Projects.Single(); var document = project.Documents.Single().Id; + var serviceProvider = workspace.ExportProvider.GetExportedValue(); var componentModel = new MockComponentModel(workspace.ExportProvider); - var serviceProvider = new MockServiceProvider(componentModel); WrapperPolicy.s_ComWrapperFactory = MockComWrapperFactory.Instance; - var visualStudioWorkspaceMock = new MockVisualStudioWorkspace(workspace); + var visualStudioWorkspaceMock = workspace.ExportProvider.GetExportedValue(); + visualStudioWorkspaceMock.SetWorkspace(workspace); + var threadingContext = workspace.ExportProvider.GetExportedValue(); var listenerProvider = workspace.ExportProvider.GetExportedValue(); @@ -48,15 +51,11 @@ public static (TestWorkspace workspace, VisualStudioWorkspace extraWorkspaceToDi serviceProvider, project.LanguageServices, visualStudioWorkspaceMock, - new ProjectCodeModelFactory( - visualStudioWorkspaceMock, - serviceProvider, - threadingContext, - listenerProvider)); + workspace.ExportProvider.GetExportedValue()); - var codeModel = FileCodeModel.Create(state, null, document, new MockTextManagerAdapter()).Handle; + var codeModel = FileCodeModel.Create(state, null, document, isSourceGeneratorOutput: false, new MockTextManagerAdapter()).Handle; - return (workspace, visualStudioWorkspaceMock, codeModel); + return (workspace, codeModel); } catch { diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs index df60f4ed036e7..e66d827f69592 100644 --- a/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs +++ b/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs @@ -129,6 +129,30 @@ public async Task TestNullFilePaths() Assert.Null(await storage.ReadStreamAsync(document, streamName)); } + [Theory, CombinatorialData, WorkItem(1436188, "https://devdiv.visualstudio.com/DevDiv/_queries/edit/1436188")] + public async Task CacheDirectoryInPathWithSingleQuote(Size size, bool withChecksum, [CombinatorialRange(0, Iterations)] int iteration) + { + _ = iteration; + using var folderRoot = new DisposableDirectory(new TempRoot()); + var folder = folderRoot.CreateDirectory(PersistentFolderPrefix + "'" + Guid.NewGuid()); + var solution = CreateOrOpenSolution(folder); + + var streamName1 = "PersistentService_Solution_WriteReadDifferentInstances1"; + var streamName2 = "PersistentService_Solution_WriteReadDifferentInstances2"; + + await using (var storage = await GetStorageAsync(solution, folder)) + { + Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); + Assert.True(await storage.WriteStreamAsync(streamName2, EncodeString(GetData2(size)), GetChecksum2(withChecksum))); + } + + await using (var storage = await GetStorageAsync(solution, folder)) + { + Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName1, GetChecksum1(withChecksum)))); + Assert.Equal(GetData2(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName2, GetChecksum2(withChecksum)))); + } + } + [Theory, CombinatorialData] public async Task PersistentService_Solution_WriteReadDifferentInstances(Size size, bool withChecksum, [CombinatorialRange(0, Iterations)] int iteration) { @@ -911,9 +935,10 @@ private void DoSimultaneousWrites(Func write) Assert.Empty(exceptions); } - protected Solution CreateOrOpenSolution(bool nullPaths = false) + protected Solution CreateOrOpenSolution(TempDirectory? persistentFolder = null, bool nullPaths = false) { - var solutionFile = _persistentFolder.CreateOrOpenFile("Solution1.sln").WriteAllText(""); + persistentFolder ??= _persistentFolder; + var solutionFile = persistentFolder.CreateOrOpenFile("Solution1.sln").WriteAllText(""); var info = SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Create(), solutionFile.Path); @@ -922,12 +947,12 @@ protected Solution CreateOrOpenSolution(bool nullPaths = false) var solution = workspace.CurrentSolution; - var projectFile = _persistentFolder.CreateOrOpenFile("Project1.csproj").WriteAllText(""); + var projectFile = persistentFolder.CreateOrOpenFile("Project1.csproj").WriteAllText(""); solution = solution.AddProject(ProjectInfo.Create(ProjectId.CreateNewId(), VersionStamp.Create(), "Project1", "Project1", LanguageNames.CSharp, filePath: nullPaths ? null : projectFile.Path)); var project = solution.Projects.Single(); - var documentFile = _persistentFolder.CreateOrOpenFile("Document1.cs").WriteAllText(""); + var documentFile = persistentFolder.CreateOrOpenFile("Document1.cs").WriteAllText(""); solution = solution.AddDocument(DocumentInfo.Create(DocumentId.CreateNewId(project.Id), "Document1", filePath: nullPaths ? null : documentFile.Path)); @@ -939,12 +964,14 @@ protected Solution CreateOrOpenSolution(bool nullPaths = false) internal async Task GetStorageAsync( Solution solution, + TempDirectory? persistentFolder = null, IPersistentStorageFaultInjector? faultInjector = null, bool throwOnFailure = true) { // If we handed out one for a previous test, we need to shut that down first + persistentFolder ??= _persistentFolder; _storageService?.GetTestAccessor().Shutdown(); - var configuration = new MockPersistentStorageConfiguration(solution.Id, _persistentFolder.Path, throwOnFailure); + var configuration = new MockPersistentStorageConfiguration(solution.Id, persistentFolder.Path, throwOnFailure); _storageService = GetStorageService((IMefHostExportProvider)solution.Workspace.Services.HostServices, configuration, faultInjector, _persistentFolder.Path); var storage = await _storageService.GetStorageAsync(SolutionKey.ToSolutionKey(solution), CancellationToken.None); diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs index d6845be5adaea..ef6a87a205418 100644 --- a/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs +++ b/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs @@ -46,7 +46,7 @@ public async Task TestCrashInNewConnection() // Because instantiating the connection will fail, we will not get back // a working persistent storage. We are testing a fault recovery code path. - await using (var storage = await GetStorageAsync(solution, faultInjector, throwOnFailure: false)) + await using (var storage = await GetStorageAsync(solution, faultInjector: faultInjector, throwOnFailure: false)) using (var memStream = new MemoryStream()) using (var streamWriter = new StreamWriter(memStream)) { diff --git a/src/VisualStudio/Core/Def/Implementation/CodeModel/IProjectCodeModel.cs b/src/VisualStudio/Core/Def/Implementation/CodeModel/IProjectCodeModel.cs index f44f00467ae71..90aaf9f3fae69 100644 --- a/src/VisualStudio/Core/Def/Implementation/CodeModel/IProjectCodeModel.cs +++ b/src/VisualStudio/Core/Def/Implementation/CodeModel/IProjectCodeModel.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using EnvDTE; using EnvDTE80; using Microsoft.VisualStudio.LanguageServices.Implementation.Interop; @@ -12,7 +10,8 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel { internal interface IProjectCodeModel { - EnvDTE.FileCodeModel GetOrCreateFileCodeModel(string filePath, object parent); + EnvDTE.FileCodeModel GetOrCreateFileCodeModel(string filePath, object? parent); + EnvDTE.FileCodeModel CreateFileCodeModel(CodeAnalysis.SourceGeneratedDocument sourceGeneratedDocument); EnvDTE.CodeModel GetOrCreateRootCodeModel(Project parent); void OnSourceFileRemoved(string fileName); void OnSourceFileRenaming(string filePath, string newFilePath); diff --git a/src/VisualStudio/Core/Def/Implementation/CodeModel/IProjectCodeModelFactory.cs b/src/VisualStudio/Core/Def/Implementation/CodeModel/IProjectCodeModelFactory.cs index 92b743c787098..93372ba19cc87 100644 --- a/src/VisualStudio/Core/Def/Implementation/CodeModel/IProjectCodeModelFactory.cs +++ b/src/VisualStudio/Core/Def/Implementation/CodeModel/IProjectCodeModelFactory.cs @@ -15,5 +15,6 @@ internal interface IProjectCodeModelFactory { IProjectCodeModel CreateProjectCodeModel(ProjectId id, ICodeModelInstanceFactory codeModelInstanceFactory); EnvDTE.FileCodeModel GetOrCreateFileCodeModel(ProjectId id, string filePath); + EnvDTE.FileCodeModel CreateFileCodeModel(SourceGeneratedDocument sourceGeneratedDocument); } } diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractPackage.cs b/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractPackage.cs index 2e697d6d82423..654e8721e475e 100644 --- a/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractPackage.cs +++ b/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractPackage.cs @@ -2,9 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - +using System; using System.Threading; +using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.Shell; using Task = System.Threading.Tasks.Task; @@ -12,6 +12,26 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService { internal abstract class AbstractPackage : AsyncPackage { + private IComponentModel? _componentModel_doNotAccessDirectly; + + internal IComponentModel ComponentModel + { + get + { + Assumes.Present(_componentModel_doNotAccessDirectly); + return _componentModel_doNotAccessDirectly; + } + } + + protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) + { + await base.InitializeAsync(cancellationToken, progress).ConfigureAwait(true); + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + _componentModel_doNotAccessDirectly = (IComponentModel)await GetServiceAsync(typeof(SComponentModel)).ConfigureAwait(true); + Assumes.Present(_componentModel_doNotAccessDirectly); + } + protected async Task LoadComponentsInUIContextOnceSolutionFullyLoadedAsync(CancellationToken cancellationToken) { // UIContexts can be "zombied" if UIContexts aren't supported because we're in a command line build or in other scenarios. diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractPackage`2.cs b/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractPackage`2.cs index 25c72e285146c..f7546e274dc53 100644 --- a/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractPackage`2.cs +++ b/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractPackage`2.cs @@ -19,6 +19,7 @@ using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Threading; +using Roslyn.Utilities; using Task = System.Threading.Tasks.Task; namespace Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService @@ -31,7 +32,6 @@ internal abstract partial class AbstractPackage : Ab private PackageInstallerService _packageInstallerService; private VisualStudioSymbolSearchService _symbolSearchService; - private IComponentModel _componentModel_doNotAccessDirectly; protected AbstractPackage() { @@ -102,19 +102,6 @@ protected override async Task LoadComponentsAsync(CancellationToken cancellation _symbolSearchService?.Connect(this.RoslynLanguageName); } - internal IComponentModel ComponentModel - { - get - { - ThreadHelper.ThrowIfNotOnUIThread(); - - if (_componentModel_doNotAccessDirectly == null) - _componentModel_doNotAccessDirectly = (IComponentModel)GetService(typeof(SComponentModel)); - - return _componentModel_doNotAccessDirectly; - } - } - protected abstract void RegisterMiscellaneousFilesWorkspaceInformation(MiscellaneousFilesWorkspace miscellaneousFilesWorkspace); protected abstract IEnumerable CreateEditorFactories(); diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs index 67bb64547ceae..3005c1111fee6 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs @@ -340,13 +340,20 @@ public override EnvDTE.FileCodeModel GetFileCodeModel(DocumentId documentId) throw new ArgumentNullException(nameof(documentId)); } - var documentFilePath = GetFilePath(documentId); - if (documentFilePath == null) + var document = _threadingContext.JoinableTaskFactory.Run(() => CurrentSolution.GetDocumentAsync(documentId, includeSourceGenerated: true).AsTask()); + if (document == null) { throw new ArgumentException(ServicesVSResources.The_given_DocumentId_did_not_come_from_the_Visual_Studio_workspace, nameof(documentId)); } - return _projectCodeModelFactory.Value.GetOrCreateFileCodeModel(documentId.ProjectId, documentFilePath); + if (document is SourceGeneratedDocument sourceGeneratedDocument) + { + return _projectCodeModelFactory.Value.CreateFileCodeModel(sourceGeneratedDocument); + } + else + { + return _projectCodeModelFactory.Value.GetOrCreateFileCodeModel(documentId.ProjectId, document.FilePath); + } } internal override bool TryApplyChanges( @@ -1417,7 +1424,7 @@ protected override void Dispose(bool finalize) base.Dispose(finalize); } - public void EnsureEditableDocuments(IEnumerable documents) + public virtual void EnsureEditableDocuments(IEnumerable documents) { var queryEdit = (IVsQueryEditQuerySave2)ServiceProvider.GlobalProvider.GetService(typeof(SVsQueryEditQuerySave)); diff --git a/src/VisualStudio/Core/Def/Implementation/SemanticClassificationCache/SemanticClassificationCacheIncrementalAnalyzerProvider.cs b/src/VisualStudio/Core/Def/Implementation/SemanticClassificationCache/SemanticClassificationCacheIncrementalAnalyzerProvider.cs deleted file mode 100644 index 6ddffb87c7390..0000000000000 --- a/src/VisualStudio/Core/Def/Implementation/SemanticClassificationCache/SemanticClassificationCacheIncrementalAnalyzerProvider.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Composition; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Classification; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Remote; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.SolutionCrawler; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.SemanticClassificationCache -{ - [ExportIncrementalAnalyzerProvider(nameof(SemanticClassificationCacheIncrementalAnalyzerProvider), new[] { WorkspaceKind.Host }), Shared] - internal class SemanticClassificationCacheIncrementalAnalyzerProvider : IIncrementalAnalyzerProvider - { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public SemanticClassificationCacheIncrementalAnalyzerProvider() - { - } - - public IIncrementalAnalyzer? CreateIncrementalAnalyzer(Workspace workspace) - { - if (workspace is not VisualStudioWorkspace) - return null; - - return new SemanticClassificationCacheIncrementalAnalyzer(); - } - - private class SemanticClassificationCacheIncrementalAnalyzer : IncrementalAnalyzerBase - { - public override async Task AnalyzeDocumentAsync(Document document, SyntaxNode bodyOpt, InvocationReasons reasons, CancellationToken cancellationToken) - { - // only process C# and VB. OOP does not contain files for other languages. - if (document.Project.Language is not (LanguageNames.CSharp or LanguageNames.VisualBasic)) - return; - - // Only cache classifications for open files. This keeps our CPU/memory usage low, but hits the common - // case of ensuring we cache classifications for the files the user edits so that they're ready the next - // time they open VS. - if (!document.IsOpen()) - return; - - var solution = document.Project.Solution; - var client = await RemoteHostClient.TryGetClientAsync(solution.Workspace, cancellationToken).ConfigureAwait(false); - if (client == null) - { - // We don't do anything if we fail to get the external process. That's the case when something has gone - // wrong, or the user is explicitly choosing to run inproc only. In neither of those cases do we want - // to bog down the VS process with the work to semantically classify files. - return; - } - - var statusService = document.Project.Solution.Workspace.Services.GetRequiredService(); - - // If we're not fully loaded, then we don't want to cache classifications. The classifications we have - // will likely not be accurate. And, if we shutdown after that, we'll have cached incomplete classifications. - var isFullyLoaded = await statusService.IsFullyLoadedAsync(cancellationToken).ConfigureAwait(false); - if (!isFullyLoaded) - return; - - // Call the project overload. We don't need to sync the full solution just to classify this document. - await client.TryInvokeAsync( - document.Project, - (service, solutionInfo, cancellationToken) => service.CacheSemanticClassificationsAsync(solutionInfo, document.Id, cancellationToken), - cancellationToken).ConfigureAwait(false); - } - } - } -} diff --git a/src/VisualStudio/Core/Def/RoslynPackage.cs b/src/VisualStudio/Core/Def/RoslynPackage.cs index 5890c9ad8e5d6..5cfd84c835325 100644 --- a/src/VisualStudio/Core/Def/RoslynPackage.cs +++ b/src/VisualStudio/Core/Def/RoslynPackage.cs @@ -64,7 +64,6 @@ internal sealed class RoslynPackage : AbstractPackage private static RoslynPackage? _lazyInstance; private VisualStudioWorkspace? _workspace; - private IComponentModel? _componentModel; private RuleSetEventHandler? _ruleSetEventHandler; private ColorSchemeApplier? _colorSchemeApplier; private IDisposable? _solutionEventMonitor; @@ -151,14 +150,12 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - _componentModel = (IComponentModel)await GetServiceAsync(typeof(SComponentModel)).ConfigureAwait(true); cancellationToken.ThrowIfCancellationRequested(); - Assumes.Present(_componentModel); // Ensure the options persisters are loaded since we have to fetch options from the shell - LoadOptionPersistersAsync(_componentModel, cancellationToken).Forget(); + LoadOptionPersistersAsync(this.ComponentModel, cancellationToken).Forget(); - _workspace = _componentModel.GetService(); + _workspace = this.ComponentModel.GetService(); // Fetch the session synchronously on the UI thread; if this doesn't happen before we try using this on // the background thread then we will experience hangs like we see in this bug: @@ -179,7 +176,7 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke TrackBulkFileOperations(); - var settingsEditorFactory = _componentModel.GetService(); + var settingsEditorFactory = this.ComponentModel.GetService(); RegisterEditorFactory(settingsEditorFactory); } @@ -304,14 +301,6 @@ private async Task LoadCallstackExplorerMenusAsync(CancellationToken cancellatio StackTraceExplorerCommandHandler.Initialize(menuCommandService, this); } - internal IComponentModel ComponentModel - { - get - { - return _componentModel ?? throw new InvalidOperationException($"Cannot use {nameof(RoslynPackage)}.{nameof(ComponentModel)} prior to initialization."); - } - } - protected override void Dispose(bool disposing) { DisposeVisualStudioServices(); diff --git a/src/VisualStudio/Core/Impl/CodeModel/CodeModelProjectCache.cs b/src/VisualStudio/Core/Impl/CodeModel/CodeModelProjectCache.cs index 4e09bb2da9a63..27e166f65b086 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/CodeModelProjectCache.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/CodeModelProjectCache.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics; @@ -23,19 +21,20 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel /// internal sealed partial class CodeModelProjectCache { - private readonly CodeModelState _state; private readonly ProjectId _projectId; private readonly ICodeModelInstanceFactory _codeModelInstanceFactory; private readonly Dictionary _cache = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly object _cacheGate = new object(); - private EnvDTE.CodeModel _rootCodeModel; + private EnvDTE.CodeModel? _rootCodeModel; private bool _zombied; + internal CodeModelState State { get; } + internal CodeModelProjectCache(IThreadingContext threadingContext, ProjectId projectId, ICodeModelInstanceFactory codeModelInstanceFactory, ProjectCodeModelFactory projectFactory, IServiceProvider serviceProvider, HostLanguageServices languageServices, VisualStudioWorkspace workspace) { - _state = new CodeModelState(threadingContext, serviceProvider, languageServices, workspace, projectFactory); + State = new CodeModelState(threadingContext, serviceProvider, languageServices, workspace, projectFactory); _projectId = projectId; _codeModelInstanceFactory = codeModelInstanceFactory; } @@ -85,7 +84,7 @@ internal CodeModelProjectCache(IThreadingContext threadingContext, ProjectId pro return cacheEntry?.ComHandle; } - public ComHandle GetOrCreateFileCodeModel(string filePath, object parent) + public ComHandle GetOrCreateFileCodeModel(string filePath, object? parent) { // First try { @@ -101,7 +100,7 @@ internal CodeModelProjectCache(IThreadingContext threadingContext, ProjectId pro } // Check that we know about this file! - var documentId = _state.Workspace.CurrentSolution.GetDocumentIdsWithFilePath(filePath).Where(id => id.ProjectId == _projectId).FirstOrDefault(); + var documentId = State.Workspace.CurrentSolution.GetDocumentIdsWithFilePath(filePath).Where(id => id.ProjectId == _projectId).FirstOrDefault(); if (documentId == null) { // Matches behavior of native (C#) implementation @@ -109,7 +108,7 @@ internal CodeModelProjectCache(IThreadingContext threadingContext, ProjectId pro } // Create object (outside of lock) - var newFileCodeModel = FileCodeModel.Create(_state, parent, documentId, new TextManagerAdapter()); + var newFileCodeModel = FileCodeModel.Create(State, parent, documentId, isSourceGeneratorOutput: false, new TextManagerAdapter()); var newCacheEntry = new CacheEntry(newFileCodeModel); // Second try (object might have been added by another thread at this point!) @@ -143,7 +142,7 @@ public EnvDTE.CodeModel GetOrCreateRootCodeModel(EnvDTE.Project parent) if (_rootCodeModel == null) { - _rootCodeModel = RootCodeModel.Create(_state, parent, _projectId); + _rootCodeModel = RootCodeModel.Create(State, parent, _projectId); } return _rootCodeModel; diff --git a/src/VisualStudio/Core/Impl/CodeModel/FileCodeModel.cs b/src/VisualStudio/Core/Impl/CodeModel/FileCodeModel.cs index 5cc19423c729b..64ed02d5cba6c 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/FileCodeModel.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/FileCodeModel.cs @@ -31,19 +31,21 @@ public sealed partial class FileCodeModel : AbstractCodeModelObject, EnvDTE.File { internal static ComHandle Create( CodeModelState state, - object parent, + object? parent, DocumentId documentId, + bool isSourceGeneratorOutput, ITextManagerAdapter textManagerAdapter) { - return new FileCodeModel(state, parent, documentId, textManagerAdapter).GetComHandle(); + return new FileCodeModel(state, parent, documentId, isSourceGeneratorOutput, textManagerAdapter).GetComHandle(); } - private readonly ComHandle _parentHandle; + private readonly ComHandle _parentHandle; /// /// Don't use directly. Instead, call . /// private DocumentId? _documentId; + private readonly bool _isSourceGeneratedOutput; // Note: these are only valid when the underlying file is being renamed. Do not use. private ProjectId? _incomingProjectId; @@ -65,16 +67,18 @@ public sealed partial class FileCodeModel : AbstractCodeModelObject, EnvDTE.File private FileCodeModel( CodeModelState state, - object parent, + object? parent, DocumentId documentId, + bool isSourceGeneratedOutput, ITextManagerAdapter textManagerAdapter) : base(state) { RoslynDebug.AssertNotNull(documentId); RoslynDebug.AssertNotNull(textManagerAdapter); - _parentHandle = new ComHandle(parent); + _parentHandle = new ComHandle(parent); _documentId = documentId; + _isSourceGeneratedOutput = isSourceGeneratedOutput; TextManagerAdapter = textManagerAdapter; _codeElementTable = new CleanableWeakComHandleTable(state.ThreadingContext); @@ -262,6 +266,14 @@ internal T GetOrCreateCodeElement(SyntaxNode node) private void InitializeEditor() { + // If this is a source generated file, we can't edit it, so just block this at the very start. + // E_FAIL is probably as a good as anything else, is and is also what we use files that go missing + // so it's consistent for "this file isn't something you can use." + if (_isSourceGeneratedOutput) + { + throw Exceptions.ThrowEFail(); + } + _editCount++; if (_editCount == 1) @@ -388,6 +400,11 @@ internal bool TryGetDocument([NotNullWhen(true)] out Document? document) { document = _previousDocument; } + else if (_isSourceGeneratedOutput) + { + document = State.ThreadingContext.JoinableTaskFactory.Run( + () => Workspace.CurrentSolution.GetSourceGeneratedDocumentAsync(GetDocumentId(), CancellationToken.None).AsTask()); + } else { // HACK HACK HACK: Ensure we've processed all files being opened before we let designers work further. @@ -609,15 +626,11 @@ public EnvDTE.CodeElements CodeElements get { return NamespaceCollection.Create(this.State, this, this, SyntaxNodeKey.Empty); } } -#nullable disable - - public EnvDTE.ProjectItem Parent + public EnvDTE.ProjectItem? Parent { get { return _parentHandle.Object as EnvDTE.ProjectItem; } } -#nullable restore - public void Remove(object element) { var codeElement = ComAggregate.TryGetManagedObject(element); diff --git a/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModel.cs b/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModel.cs index c21a61210ea52..822d05949532a 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModel.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModel.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; -using EnvDTE; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.VisualStudio.LanguageServices.Implementation.Interop; @@ -110,5 +109,12 @@ public void OnSourceFileRenaming(string filePath, string newFilePath) EnvDTE.FileCodeModel IProjectCodeModel.GetOrCreateFileCodeModel(string filePath, object parent) => this.GetOrCreateFileCodeModel(filePath, parent).Handle; + + public EnvDTE.FileCodeModel CreateFileCodeModel(SourceGeneratedDocument sourceGeneratedDocument) + { + // Unlike for "regular" documents, we make no effort to cache these between callers or hold them for longer lifetimes with + // events. + return FileCodeModel.Create(GetCodeModelCache().State, parent: null, sourceGeneratedDocument.Id, isSourceGeneratorOutput: true, new TextManagerAdapter()).Handle; + } } } diff --git a/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs b/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs index 1a545d92168cc..daeb7f6e733d7 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeGeneration; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.SolutionCrawler; @@ -38,7 +39,7 @@ internal sealed class ProjectCodeModelFactory : ForegroundThreadAffinitizedObjec private readonly AsyncBatchingWorkQueue _documentsToFireEventsFor; [ImportingConstructor] - [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public ProjectCodeModelFactory( VisualStudioWorkspace visualStudioWorkspace, [Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider, @@ -217,6 +218,9 @@ public ProjectCodeModel TryGetProjectCodeModel(ProjectId id) public EnvDTE.FileCodeModel GetOrCreateFileCodeModel(ProjectId id, string filePath) => GetProjectCodeModel(id).GetOrCreateFileCodeModel(filePath).Handle; + public EnvDTE.FileCodeModel CreateFileCodeModel(SourceGeneratedDocument sourceGeneratedDocument) + => GetProjectCodeModel(sourceGeneratedDocument.Project.Id).CreateFileCodeModel(sourceGeneratedDocument); + public void ScheduleDeferredCleanupTask(Action a) { _ = _threadingContext.RunWithShutdownBlockAsync(async cancellationToken => diff --git a/src/VisualStudio/Core/Test/CodeModel/CSharp/FileCodeModelTests.vb b/src/VisualStudio/Core/Test/CodeModel/CSharp/FileCodeModelTests.vb index 36936fc3a43db..71de849d3e84f 100644 --- a/src/VisualStudio/Core/Test/CodeModel/CSharp/FileCodeModelTests.vb +++ b/src/VisualStudio/Core/Test/CodeModel/CSharp/FileCodeModelTests.vb @@ -2,6 +2,7 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. +Imports System.Runtime.InteropServices Imports System.Threading.Tasks Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces @@ -1013,12 +1014,12 @@ class D Using originalWorkspaceAndFileCodeModel = CreateCodeModelTestState(GetWorkspaceDefinition(oldCode)) - Using changedworkspace = TestWorkspace.Create(changedDefinition, composition:=VisualStudioTestCompositions.LanguageServices) + Using changedWorkspace = TestWorkspace.Create(changedDefinition, composition:=CodeModelTestHelpers.Composition) Dim originalDocument = originalWorkspaceAndFileCodeModel.Workspace.CurrentSolution.GetDocument(originalWorkspaceAndFileCodeModel.Workspace.Documents(0).Id) Dim originalTree = Await originalDocument.GetSyntaxTreeAsync() - Dim changeDocument = changedworkspace.CurrentSolution.GetDocument(changedworkspace.Documents(0).Id) + Dim changeDocument = changedWorkspace.CurrentSolution.GetDocument(changedWorkspace.Documents(0).Id) Dim changeTree = Await changeDocument.GetSyntaxTreeAsync() Dim codeModelEvent = originalWorkspaceAndFileCodeModel.CodeModelService.CollectCodeModelEvents(originalTree, changeTree) @@ -1255,6 +1256,36 @@ class C End Sub) End Sub + + Public Sub CanAccessPartialPartsInSourceGeneratedFiles() + Dim code = + + CommonReferences="true"> + partial class C { } + partial class C { void M() { } } + + + + Using state = CreateCodeModelTestState(code) + Dim workspace = state.VisualStudioWorkspace + Dim fileCodeModel = state.FileCodeModel + + Dim codeClass = DirectCast(Assert.Single(fileCodeModel.CodeElements()), EnvDTE80.CodeClass2) + Dim parts = codeClass.Parts.Cast(Of EnvDTE.CodeClass).ToList() + Assert.Equal(2, parts.Count) + + ' Grab the part that isn't the one we started with + Dim generatedPart = Assert.Single(parts, Function(p) p IsNot codeClass) + + ' Confirm we can inspect members + Dim member = DirectCast(Assert.Single(generatedPart.Members), EnvDTE.CodeFunction) + Assert.Equal("M", member.Name) + + ' We are unable to change things in generated files + Assert.Throws(Of COMException)(Sub() member.AddParameter("Test", "System.Object")) + End Using + End Sub + Protected Overrides ReadOnly Property LanguageName As String Get Return LanguageNames.CSharp diff --git a/src/VisualStudio/Core/Test/CodeModel/VisualBasic/FileCodeModelTests.vb b/src/VisualStudio/Core/Test/CodeModel/VisualBasic/FileCodeModelTests.vb index af340d4c8154c..bceb730aa5bed 100644 --- a/src/VisualStudio/Core/Test/CodeModel/VisualBasic/FileCodeModelTests.vb +++ b/src/VisualStudio/Core/Test/CodeModel/VisualBasic/FileCodeModelTests.vb @@ -1011,7 +1011,7 @@ End Class Using originalWorkspaceAndFileCodeModel = CreateCodeModelTestState(GetWorkspaceDefinition(oldCode)) - Using changedworkspace = TestWorkspace.Create(changedDefinition, composition:=VisualStudioTestCompositions.LanguageServices) + Using changedworkspace = TestWorkspace.Create(changedDefinition, composition:=CodeModelTestHelpers.Composition) Dim originalDocument = originalWorkspaceAndFileCodeModel.Workspace.CurrentSolution.GetDocument(originalWorkspaceAndFileCodeModel.Workspace.Documents(0).Id) Dim originalTree = Await originalDocument.GetSyntaxTreeAsync() diff --git a/src/VisualStudio/Core/Test/ObjectBrowser/AbstractObjectBrowserTests.vb b/src/VisualStudio/Core/Test/ObjectBrowser/AbstractObjectBrowserTests.vb index 79bccbd8ab95c..8a8566c54110f 100644 --- a/src/VisualStudio/Core/Test/ObjectBrowser/AbstractObjectBrowserTests.vb +++ b/src/VisualStudio/Core/Test/ObjectBrowser/AbstractObjectBrowserTests.vb @@ -3,13 +3,13 @@ ' See the LICENSE file in the project root for more information. Imports System.Runtime.ExceptionServices -Imports Microsoft.CodeAnalysis.Editor.UnitTests Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.Test.Utilities Imports Microsoft.VisualStudio.ComponentModelHost +Imports Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel Imports Microsoft.VisualStudio.LanguageServices.Implementation.Library.ObjectBrowser +Imports Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel Imports Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel.Mocks -Imports Microsoft.VisualStudio.LanguageServices.UnitTests.ObjectBrowser.Mocks Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ObjectBrowser <[UseExportProvider]> @@ -38,14 +38,15 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ObjectBrowser Friend Function CreateLibraryManager(definition As XElement) As TestState - Dim workspace = TestWorkspace.Create(definition, composition:=VisualStudioTestCompositions.LanguageServices) + Dim workspace = TestWorkspace.Create(definition, composition:=CodeModelTestHelpers.Composition) Dim result As TestState = Nothing Try - Dim vsWorkspace = New MockVisualStudioWorkspace(workspace) + Dim vsWorkspace = New MockVisualStudioWorkspace(workspace.ExportProvider) + vsWorkspace.SetWorkspace(workspace) Dim mockComponentModel = New MockComponentModel(workspace.ExportProvider) mockComponentModel.ProvideService(Of VisualStudioWorkspace)(vsWorkspace) - Dim mockServiceProvider = New MockServiceProvider(mockComponentModel) + Dim mockServiceProvider = workspace.ExportProvider.GetExportedValue(Of MockServiceProvider) Dim libraryManager = CreateLibraryManager(mockServiceProvider, mockComponentModel, vsWorkspace) result = New TestState(workspace, vsWorkspace, libraryManager) diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/SolutionExplorerInProcess.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/SolutionExplorerInProcess.cs index 6dac30e8125d1..3192bc829da18 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/SolutionExplorerInProcess.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/SolutionExplorerInProcess.cs @@ -462,7 +462,7 @@ private static string CreateTemporaryPath() private async Task GetProjectAsync(string nameOrFileName, CancellationToken cancellationToken) { - await JoinableTaskFactory.SwitchToMainThreadAsync(); + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); var dte = await GetRequiredGlobalServiceAsync(cancellationToken); var solution = (EnvDTE80.Solution2)dte.Solution; diff --git a/src/VisualStudio/TestUtilities2/CodeModel/CodeModelTestHelpers.vb b/src/VisualStudio/TestUtilities2/CodeModel/CodeModelTestHelpers.vb index bc4b23cdde4b6..88ab88337f277 100644 --- a/src/VisualStudio/TestUtilities2/CodeModel/CodeModelTestHelpers.vb +++ b/src/VisualStudio/TestUtilities2/CodeModel/CodeModelTestHelpers.vb @@ -6,14 +6,11 @@ Imports System.Runtime.CompilerServices Imports System.Runtime.ExceptionServices Imports System.Runtime.InteropServices Imports EnvDTE -Imports EnvDTE80 Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.Editor Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.Shared.TestHooks Imports Microsoft.CodeAnalysis.Test.Utilities -Imports Microsoft.VisualStudio.ComponentModelHost Imports Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel Imports Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel.ExternalElements Imports Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel.InternalElements @@ -26,6 +23,11 @@ Imports Roslyn.Test.Utilities Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel Friend Module CodeModelTestHelpers + Public ReadOnly Composition As TestComposition = VisualStudioTestCompositions.LanguageServices.AddParts( + GetType(MockServiceProvider), + GetType(MockVisualStudioWorkspace), + GetType(ProjectCodeModelFactory)) + Public SystemWindowsFormsPath As String Public SystemDrawingPath As String @@ -44,13 +46,10 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel Public Function CreateCodeModelTestState(definition As XElement) As CodeModelTestState - Dim workspace = TestWorkspace.Create(definition, composition:=VisualStudioTestCompositions.LanguageServices) + Dim workspace = TestWorkspace.Create(definition, composition:=Composition) Dim result As CodeModelTestState = Nothing Try - Dim mockComponentModel = New MockComponentModel(workspace.ExportProvider) - Dim mockServiceProvider = New MockServiceProvider(mockComponentModel) - Dim mockVisualStudioWorkspace = New MockVisualStudioWorkspace(workspace) WrapperPolicy.s_ComWrapperFactory = MockComWrapperFactory.Instance ' The Code Model test infrastructure assumes that a test workspace only ever contains a single project. @@ -59,31 +58,33 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel Dim threadingContext = workspace.ExportProvider.GetExportedValue(Of IThreadingContext) Dim listenerProvider = workspace.ExportProvider.GetExportedValue(Of AsynchronousOperationListenerProvider)() + Dim visualStudioWorkspace = workspace.ExportProvider.GetExportedValue(Of MockVisualStudioWorkspace)() + visualStudioWorkspace.SetWorkspace(workspace) Dim state = New CodeModelState( threadingContext, - mockServiceProvider, + workspace.ExportProvider.GetExportedValue(Of MockServiceProvider), project.LanguageServices, - mockVisualStudioWorkspace, - New ProjectCodeModelFactory( - mockVisualStudioWorkspace, - mockServiceProvider, - threadingContext, - listenerProvider)) + visualStudioWorkspace, + workspace.ExportProvider.GetExportedValue(Of ProjectCodeModelFactory)) Dim projectCodeModel = DirectCast(state.ProjectCodeModelFactory.CreateProjectCodeModel(project.Id, Nothing), ProjectCodeModel) + Dim firstFileCodeModel As ComHandle(Of EnvDTE80.FileCodeModel2, Implementation.CodeModel.FileCodeModel)? = Nothing + For Each document In project.Documents ' Note that a parent is not specified below. In Visual Studio, this would normally be an EnvDTE.Project instance. Dim fcm = projectCodeModel.GetOrCreateFileCodeModel(document.FilePath, parent:=Nothing) fcm.Object.TextManagerAdapter = New MockTextManagerAdapter() - mockVisualStudioWorkspace.SetFileCodeModel(document.Id, fcm) + + If Not firstFileCodeModel.HasValue Then + firstFileCodeModel = fcm + End If Next Dim root = New ComHandle(Of EnvDTE.CodeModel, RootCodeModel)(RootCodeModel.Create(state, Nothing, project.Id)) - Dim firstFCM = mockVisualStudioWorkspace.GetFileCodeModelComHandle(project.DocumentIds.First()) - result = New CodeModelTestState(workspace, mockVisualStudioWorkspace, root, firstFCM, state.CodeModelService) + result = New CodeModelTestState(workspace, state.Workspace, root, firstFileCodeModel.Value, state.CodeModelService) Finally If result Is Nothing Then workspace.Dispose() @@ -93,28 +94,6 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel Return result End Function - Public Class MockServiceProvider - Implements IServiceProvider - - Private ReadOnly _componentModel As MockComponentModel - - Public Sub New(componentModel As MockComponentModel) - _componentModel = componentModel - End Sub - - Public Function GetService(serviceType As Type) As Object Implements IServiceProvider.GetService - If serviceType = GetType(SComponentModel) Then - Return Me._componentModel - End If - - If serviceType = GetType(EnvDTE.IVsExtensibility) Then - Return Nothing - End If - - Throw New NotImplementedException($"No service exists for {serviceType.FullName}") - End Function - End Class - Friend Class MockComWrapperFactory Implements IComWrapperFactory diff --git a/src/VisualStudio/TestUtilities2/CodeModel/CodeModelTestState.vb b/src/VisualStudio/TestUtilities2/CodeModel/CodeModelTestState.vb index cc5803a6da2f3..753f06f4b851e 100644 --- a/src/VisualStudio/TestUtilities2/CodeModel/CodeModelTestState.vb +++ b/src/VisualStudio/TestUtilities2/CodeModel/CodeModelTestState.vb @@ -86,7 +86,12 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel If Not Me._disposedValue Then If disposing Then - VisualStudioWorkspace.Dispose() + ' Ensure the existing project is removed from the ProjectCodeModelFactory; we otherwise later might try updating any state + ' for it. + Dim projectId = Workspace.CurrentSolution.ProjectIds.Single() + Dim projectCodeModel = Workspace.ExportProvider.GetExportedValue(Of ProjectCodeModelFactory)().GetProjectCodeModel(projectId) + projectCodeModel.OnProjectClosed() + Workspace.Dispose() End If End If diff --git a/src/VisualStudio/TestUtilities2/CodeModel/Mocks/MockVisualStudioWorkspace.vb b/src/VisualStudio/TestUtilities2/CodeModel/Mocks/MockVisualStudioWorkspace.vb index ab14e0881f40a..cdb6b50a0a2a0 100644 --- a/src/VisualStudio/TestUtilities2/CodeModel/Mocks/MockVisualStudioWorkspace.vb +++ b/src/VisualStudio/TestUtilities2/CodeModel/Mocks/MockVisualStudioWorkspace.vb @@ -2,35 +2,51 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. +Imports System.ComponentModel.Composition Imports System.Threading Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.FindSymbols +Imports Microsoft.CodeAnalysis.Host +Imports Microsoft.CodeAnalysis.Host.Mef +Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.VisualStudio.Composition Imports Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel Imports Microsoft.VisualStudio.LanguageServices.Implementation.Interop Imports Microsoft.VisualStudio.LanguageServices.Implementation.Library.ObjectBrowser.Lists Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel.Mocks + + + + Friend Class MockVisualStudioWorkspace - Inherits VisualStudioWorkspace + Inherits VisualStudioWorkspaceImpl - Private ReadOnly _workspace As TestWorkspace - Private ReadOnly _fileCodeModels As New Dictionary(Of DocumentId, ComHandle(Of EnvDTE80.FileCodeModel2, FileCodeModel)) + Private _workspace As TestWorkspace + + + + Public Sub New(exportProvider As ExportProvider) + MyBase.New(exportProvider, exportProvider.GetExportedValue(Of MockServiceProvider)) + + End Sub - Public Sub New(workspace As TestWorkspace) - MyBase.New(workspace.Services.HostServices) + Public Sub SetWorkspace(testWorkspace As TestWorkspace) + _workspace = testWorkspace + SetCurrentSolution(testWorkspace.CurrentSolution) - _workspace = workspace - SetCurrentSolution(workspace.CurrentSolution) + ' HACK: ensure this service is created so it can be used during disposal + Me.Services.GetService(Of IWorkspaceEventListenerService)() End Sub Public Overrides Function CanApplyChange(feature As ApplyChangesKind) As Boolean Return _workspace.CanApplyChange(feature) End Function - Protected Overrides Sub OnDocumentTextChanged(document As Document) - Assert.True(_workspace.TryApplyChanges(_workspace.CurrentSolution.WithDocumentText(document.Id, document.GetTextAsync().Result))) + Protected Overrides Sub ApplyDocumentTextChanged(documentId As DocumentId, newText As SourceText) + Assert.True(_workspace.TryApplyChanges(_workspace.CurrentSolution.WithDocumentText(documentId, newText))) SetCurrentSolution(_workspace.CurrentSolution) End Sub @@ -44,22 +60,10 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel.Mocks SetCurrentSolution(_workspace.CurrentSolution) End Sub - Public Overrides Function GetHierarchy(projectId As ProjectId) As Microsoft.VisualStudio.Shell.Interop.IVsHierarchy - Return Nothing - End Function - - Friend Overrides Function GetProjectGuid(projectId As ProjectId) As Guid - Return Guid.Empty - End Function - Friend Overrides Function OpenInvisibleEditor(documentId As DocumentId) As IInvisibleEditor Return New MockInvisibleEditor(documentId, _workspace) End Function - Public Overrides Function GetFileCodeModel(documentId As DocumentId) As EnvDTE.FileCodeModel - Return _fileCodeModels(documentId).Handle - End Function - Public Overrides Function TryGoToDefinition(symbol As ISymbol, project As Project, cancellationToken As CancellationToken) As Boolean Throw New NotImplementedException() End Function @@ -80,17 +84,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel.Mocks Throw New NotImplementedException() End Function - Friend Sub SetFileCodeModel(id As DocumentId, fileCodeModel As ComHandle(Of EnvDTE80.FileCodeModel2, FileCodeModel)) - _fileCodeModels.Add(id, fileCodeModel) + Public Overrides Sub EnsureEditableDocuments(documents As IEnumerable(Of DocumentId)) + ' Nothing to do here End Sub - - Friend Function GetFileCodeModelComHandle(id As DocumentId) As ComHandle(Of EnvDTE80.FileCodeModel2, FileCodeModel) - Return _fileCodeModels(id) - End Function - - Friend Overrides Function TryGetRuleSetPathForProject(projectId As ProjectId) As String - Throw New NotImplementedException() - End Function End Class Public Class MockInvisibleEditor diff --git a/src/VisualStudio/TestUtilities2/MockServiceProvider.vb b/src/VisualStudio/TestUtilities2/MockServiceProvider.vb new file mode 100644 index 0000000000000..45444d9342d49 --- /dev/null +++ b/src/VisualStudio/TestUtilities2/MockServiceProvider.vb @@ -0,0 +1,69 @@ +' Licensed to the .NET Foundation under one or more agreements. +' The .NET Foundation licenses this file to you under the MIT license. +' See the LICENSE file in the project root for more information. + +Imports System.ComponentModel.Composition +Imports Microsoft.CodeAnalysis.Host.Mef +Imports Microsoft.VisualStudio.ComponentModelHost +Imports Microsoft.VisualStudio.Shell +Imports Microsoft.VisualStudio.Shell.Interop +Imports Moq + +Namespace Microsoft.VisualStudio.LanguageServices.UnitTests + + + + + Friend Class MockServiceProvider + Implements IServiceProvider + Implements SVsServiceProvider ' The shell service provider actually implements this too for people using that type directly + Implements IAsyncServiceProvider + + Private ReadOnly _exportProvider As Composition.ExportProvider + Private ReadOnly _fileChangeEx As MockVsFileChangeEx = New MockVsFileChangeEx + + Public MockMonitorSelection As IVsMonitorSelection + + + + Public Sub New(exportProvider As Composition.ExportProvider) + _exportProvider = exportProvider + End Sub + + Public Function GetService(serviceType As Type) As Object Implements IServiceProvider.GetService + Select Case serviceType + Case GetType(SVsSolution), GetType(SVsShell) + ' Return a loose mock that just is a big no-op + Dim solutionMock As New Mock(Of IVsSolution2)(MockBehavior.Loose) + Return solutionMock.Object + + Case GetType(SComponentModel) + Return GetComponentModelMock() + + Case GetType(SVsShellMonitorSelection) + Return MockMonitorSelection + + Case GetType(SVsXMLMemberIndexService) + Return New MockXmlMemberIndexService + + Case GetType(SVsSmartOpenScope) + Return New MockVsSmartOpenScope + + Case GetType(SVsFileChangeEx) + Return _fileChangeEx + + Case Else + Throw New Exception($"{NameOf(MockServiceProvider)} does not implement {serviceType.FullName}.") + End Select + End Function + + Public Function GetServiceAsync(serviceType As Type) As Task(Of Object) Implements IAsyncServiceProvider.GetServiceAsync + Return System.Threading.Tasks.Task.FromResult(GetService(serviceType)) + End Function + + Friend Function GetComponentModelMock() As IComponentModel + Return New MockComponentModel(_exportProvider) + End Function + End Class + +End Namespace diff --git a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/MockVsFileChangeEx.vb b/src/VisualStudio/TestUtilities2/MockVsFileChangeEx.vb similarity index 99% rename from src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/MockVsFileChangeEx.vb rename to src/VisualStudio/TestUtilities2/MockVsFileChangeEx.vb index 36a02aba67183..a2b4911de42e0 100644 --- a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/MockVsFileChangeEx.vb +++ b/src/VisualStudio/TestUtilities2/MockVsFileChangeEx.vb @@ -9,7 +9,7 @@ Imports Microsoft.VisualStudio.Shell Imports Microsoft.VisualStudio.Shell.Interop Imports Task = System.Threading.Tasks.Task -Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Framework +Namespace Microsoft.VisualStudio.LanguageServices.UnitTests Friend Class MockVsFileChangeEx Implements IVsFileChangeEx Implements IVsAsyncFileChangeEx diff --git a/src/VisualStudio/TestUtilities2/MockVsSmartOpenScope.vb b/src/VisualStudio/TestUtilities2/MockVsSmartOpenScope.vb new file mode 100644 index 0000000000000..98b6f43488357 --- /dev/null +++ b/src/VisualStudio/TestUtilities2/MockVsSmartOpenScope.vb @@ -0,0 +1,15 @@ +' Licensed to the .NET Foundation under one or more agreements. +' The .NET Foundation licenses this file to you under the MIT license. +' See the LICENSE file in the project root for more information. + +Imports Microsoft.VisualStudio.Shell.Interop + +Namespace Microsoft.VisualStudio.LanguageServices.UnitTests + Friend Class MockVsSmartOpenScope + Implements IVsSmartOpenScope + + Public Function OpenScope(wszScope As String, dwOpenFlags As UInteger, ByRef riid As Guid, ByRef ppIUnk As Object) As Integer Implements IVsSmartOpenScope.OpenScope + Throw New NotImplementedException() + End Function + End Class +End Namespace diff --git a/src/VisualStudio/TestUtilities2/MockXmlMemberIndexService.vb b/src/VisualStudio/TestUtilities2/MockXmlMemberIndexService.vb new file mode 100644 index 0000000000000..cca50f6df23f9 --- /dev/null +++ b/src/VisualStudio/TestUtilities2/MockXmlMemberIndexService.vb @@ -0,0 +1,19 @@ +' Licensed to the .NET Foundation under one or more agreements. +' The .NET Foundation licenses this file to you under the MIT license. +' See the LICENSE file in the project root for more information. + +Imports Microsoft.VisualStudio.Shell.Interop + +Namespace Microsoft.VisualStudio.LanguageServices.UnitTests + Friend Class MockXmlMemberIndexService + Implements IVsXMLMemberIndexService + + Public Function CreateXMLMemberIndex(pszBinaryName As String, ByRef ppIndex As IVsXMLMemberIndex) As Integer Implements IVsXMLMemberIndexService.CreateXMLMemberIndex + Throw New NotImplementedException() + End Function + + Public Function GetMemberDataFromXML(pszXML As String, ByRef ppObj As IVsXMLMemberData) As Integer Implements IVsXMLMemberIndexService.GetMemberDataFromXML + Throw New NotImplementedException() + End Function + End Class +End Namespace diff --git a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb index 64c3608cd7200..8cae746e36b85 100644 --- a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb +++ b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb @@ -16,7 +16,6 @@ Imports Microsoft.CodeAnalysis.FindSymbols Imports Microsoft.CodeAnalysis.Host.Mef Imports Microsoft.CodeAnalysis.Shared.TestHooks Imports Microsoft.CodeAnalysis.Test.Utilities -Imports Microsoft.VisualStudio.ComponentModelHost Imports Microsoft.VisualStudio.Composition Imports Microsoft.VisualStudio.LanguageServices.Implementation Imports Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel @@ -28,7 +27,6 @@ Imports Microsoft.VisualStudio.LanguageServices.Implementation.TaskList Imports Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel Imports Microsoft.VisualStudio.Shell Imports Microsoft.VisualStudio.Shell.Interop -Imports Moq Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Framework @@ -167,61 +165,6 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr Return Workspace.CurrentSolution.Projects.Single().CompilationOptions End Function - - - - Friend Class MockServiceProvider - Implements IServiceProvider - Implements SVsServiceProvider ' The shell service provider actually implements this too for people using that type directly - Implements IAsyncServiceProvider - - Private ReadOnly _exportProvider As Composition.ExportProvider - Private ReadOnly _fileChangeEx As MockVsFileChangeEx = New MockVsFileChangeEx - - Public MockMonitorSelection As IVsMonitorSelection - - - - Public Sub New(exportProvider As Composition.ExportProvider) - _exportProvider = exportProvider - End Sub - - Public Function GetService(serviceType As Type) As Object Implements IServiceProvider.GetService - Select Case serviceType - Case GetType(SVsSolution) - ' Return a loose mock that just is a big no-op - Dim solutionMock As New Mock(Of IVsSolution2)(MockBehavior.Loose) - Return solutionMock.Object - - Case GetType(SComponentModel) - Return GetComponentModelMock() - - Case GetType(SVsShellMonitorSelection) - Return MockMonitorSelection - - Case GetType(SVsXMLMemberIndexService) - Return New MockXmlMemberIndexService - - Case GetType(SVsSmartOpenScope) - Return New MockVsSmartOpenScope - - Case GetType(SVsFileChangeEx) - Return _fileChangeEx - - Case Else - Throw New Exception($"{NameOf(MockServiceProvider)} does not implement {serviceType.FullName}.") - End Select - End Function - - Public Function GetServiceAsync(serviceType As Type) As Task(Of Object) Implements IAsyncServiceProvider.GetServiceAsync - Return System.Threading.Tasks.Task.FromResult(GetService(serviceType)) - End Function - - Friend Function GetComponentModelMock() As IComponentModel - Return New MockComponentModel(_exportProvider) - End Function - End Class - Private Class MockShellMonitorSelection Implements IVsMonitorSelection @@ -262,18 +205,6 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr End Function End Class - Private Class MockXmlMemberIndexService - Implements IVsXMLMemberIndexService - - Public Function CreateXMLMemberIndex(pszBinaryName As String, ByRef ppIndex As IVsXMLMemberIndex) As Integer Implements IVsXMLMemberIndexService.CreateXMLMemberIndex - Throw New NotImplementedException() - End Function - - Public Function GetMemberDataFromXML(pszXML As String, ByRef ppObj As IVsXMLMemberData) As Integer Implements IVsXMLMemberIndexService.GetMemberDataFromXML - Throw New NotImplementedException() - End Function - End Class - Friend Async Function GetFileChangeServiceAsync() As Task(Of MockVsFileChangeEx) ' Ensure we've pushed everything to the file change watcher Dim fileChangeProvider = ExportProvider.GetExportedValue(Of FileChangeWatcherProvider) @@ -292,13 +223,5 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr Dim service = Await GetFileChangeServiceAsync() service.FireStaleUpdate(path, unsubscribingAction) End Function - - Private Class MockVsSmartOpenScope - Implements IVsSmartOpenScope - - Public Function OpenScope(wszScope As String, dwOpenFlags As UInteger, ByRef riid As Guid, ByRef ppIUnk As Object) As Integer Implements IVsSmartOpenScope.OpenScope - Throw New NotImplementedException() - End Function - End Class End Class End Namespace diff --git a/src/VisualStudio/TestUtilities2/VisualStudioTestCompositions.vb b/src/VisualStudio/TestUtilities2/VisualStudioTestCompositions.vb index e8090351abb28..3502426d2846f 100644 --- a/src/VisualStudio/TestUtilities2/VisualStudioTestCompositions.vb +++ b/src/VisualStudio/TestUtilities2/VisualStudioTestCompositions.vb @@ -26,6 +26,6 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests GetType(VisualStudioRemoteHostClientProvider.Factory), ' Do not use ServiceHub in VS unit tests, run services locally. GetType(IStreamingFindUsagesPresenter), ' TODO: should we be using the actual implementation (https://github.com/dotnet/roslyn/issues/46380)? GetType(HACK_ThemeColorFixer), - GetType(INotificationService)) + GetType(Implementation.Notification.VSNotificationServiceFactory)) End Class End Namespace diff --git a/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml.vb b/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml.vb index 26b84d62be62b..9855802da5742 100644 --- a/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml.vb +++ b/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml.vb @@ -133,11 +133,11 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options BindToOption(DontPutOutOrRefOnStruct, ExtractMethodOptions.DontPutOutOrRefOnStruct, LanguageNames.VisualBasic) ' Implement Interface or Abstract Class - BindToOption(with_other_members_of_the_same_kind, ImplementTypeOptions.InsertionBehavior, ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind, LanguageNames.VisualBasic) - BindToOption(at_the_end, ImplementTypeOptions.InsertionBehavior, ImplementTypeInsertionBehavior.AtTheEnd, LanguageNames.VisualBasic) + BindToOption(with_other_members_of_the_same_kind, ImplementTypeOptions.Metadata.InsertionBehavior, ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind, LanguageNames.VisualBasic) + BindToOption(at_the_end, ImplementTypeOptions.Metadata.InsertionBehavior, ImplementTypeInsertionBehavior.AtTheEnd, LanguageNames.VisualBasic) - BindToOption(prefer_throwing_properties, ImplementTypeOptions.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferThrowingProperties, LanguageNames.VisualBasic) - BindToOption(prefer_auto_properties, ImplementTypeOptions.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferAutoProperties, LanguageNames.VisualBasic) + BindToOption(prefer_throwing_properties, ImplementTypeOptions.Metadata.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferThrowingProperties, LanguageNames.VisualBasic) + BindToOption(prefer_auto_properties, ImplementTypeOptions.Metadata.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferAutoProperties, LanguageNames.VisualBasic) ' Inline hints BindToOption(DisplayAllHintsWhilePressingAltF1, InlineHintsViewOptions.DisplayAllHintsWhilePressingAltF1) diff --git a/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs b/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs index 18b829f4323b6..a19b4808c5618 100644 --- a/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs +++ b/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs @@ -5775,6 +5775,28 @@ static object F() await AssertFormatAsync(expectedCode, code); } + [Fact] + [Trait(Traits.Feature, Traits.Features.Formatting)] + public async Task SpacingInNullCheckedParameter() + { + var code = +@"class C +{ + static object F(string s !!) + { + } +}"; + var expectedCode = +@"class C +{ + static object F(string s!!) + { + } +}"; + + await AssertFormatAsync(expectedCode, code); + } + [Fact] [WorkItem(545335, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545335")] [Trait(Traits.Feature, Traits.Features.Formatting)] diff --git a/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs b/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs index df7ed80a101c7..28ade4ec636e7 100644 --- a/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs +++ b/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs @@ -7,11 +7,13 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Extensions; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.ReassignedVariable; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Storage; using Microsoft.CodeAnalysis.Text; -using Microsoft.CodeAnalysis.ReassignedVariable; namespace Microsoft.CodeAnalysis.Classification { @@ -46,11 +48,19 @@ public async Task AddSemanticClassificationsAsync(Document document, TextSpan te var client = await RemoteHostClient.TryGetClientAsync(document.Project, cancellationToken).ConfigureAwait(false); if (client != null) { + // We have an oop connection. If we're not fully loaded, see if we can retrieve a previously cached set + // of classifications from the server. Note: this must be a separate call (instead of being part of + // service.GetSemanticClassificationsAsync below) as we want to try to read in the cached + // classifications without doing any syncing to the OOP process. + var isFullyLoaded = IsFullyLoaded(document, cancellationToken); + if (await TryGetCachedClassificationsAsync(document, textSpan, result, client, isFullyLoaded, cancellationToken).ConfigureAwait(false)) + return; + // Call the project overload. Semantic classification only needs the current project's information // to classify properly. var classifiedSpans = await client.TryInvokeAsync( document.Project, - (service, solutionInfo, cancellationToken) => service.GetSemanticClassificationsAsync(solutionInfo, document.Id, textSpan, options, cancellationToken), + (service, solutionInfo, cancellationToken) => service.GetSemanticClassificationsAsync(solutionInfo, document.Id, textSpan, options, isFullyLoaded, cancellationToken), cancellationToken).ConfigureAwait(false); // if the remote call fails do nothing (error has already been reported) @@ -64,6 +74,45 @@ await AddSemanticClassificationsInCurrentProcessAsync( } } + private static bool IsFullyLoaded(Document document, CancellationToken cancellationToken) + { + var workspaceStatusService = document.Project.Solution.Workspace.Services.GetRequiredService(); + + // Importantly, we do not await/wait on the fullyLoadedStateTask. We do not want to ever be waiting on work + // that may end up touching the UI thread (As we can deadlock if GetTagsSynchronous waits on us). Instead, + // we only check if the Task is completed. Prior to that we will assume we are still loading. Once this + // task is completed, we know that the WaitUntilFullyLoadedAsync call will have actually finished and we're + // fully loaded. + var isFullyLoadedTask = workspaceStatusService.IsFullyLoadedAsync(cancellationToken); + var isFullyLoaded = isFullyLoadedTask.IsCompleted && isFullyLoadedTask.GetAwaiter().GetResult(); + return isFullyLoaded; + } + + private static async Task TryGetCachedClassificationsAsync( + Document document, TextSpan textSpan, ArrayBuilder result, + RemoteHostClient client, bool isFullyLoaded, CancellationToken cancellationToken) + { + // Only try to get cached classifications if we're not fully loaded yet. + if (isFullyLoaded) + return false; + + var (documentKey, checksum) = await SemanticClassificationCacheUtilities.GetDocumentKeyAndChecksumAsync( + document, cancellationToken).ConfigureAwait(false); + var database = document.Project.Solution.Options.GetPersistentStorageDatabase(); + + var cachedSpans = await client.TryInvokeAsync( + document.Project, + (service, solutionInfo, cancellationToken) => service.GetCachedSemanticClassificationsAsync(documentKey, textSpan, checksum, database, cancellationToken), + cancellationToken).ConfigureAwait(false); + + // if the remote call fails do nothing (error has already been reported) + if (!cachedSpans.HasValue || cachedSpans.Value == null) + return false; + + cachedSpans.Value.Rehydrate(result); + return true; + } + public static async Task AddSemanticClassificationsInCurrentProcessAsync( Document document, TextSpan textSpan, ClassificationOptions options, ArrayBuilder result, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/Classification/ClassifierHelper.cs b/src/Workspaces/Core/Portable/Classification/ClassifierHelper.cs index ff39972f21cdc..8c2b3354b4eba 100644 --- a/src/Workspaces/Core/Portable/Classification/ClassifierHelper.cs +++ b/src/Workspaces/Core/Portable/Classification/ClassifierHelper.cs @@ -21,7 +21,11 @@ internal static class ClassifierHelper /// fails. /// public static async Task> GetClassifiedSpansAsync( - Document document, TextSpan span, CancellationToken cancellationToken) + Document document, + TextSpan span, + CancellationToken cancellationToken, + bool removeAdditiveSpans = true, + bool fillInClassifiedSpanGaps = true) { var classificationService = document.GetLanguageService(); if (classificationService == null) @@ -46,13 +50,17 @@ public static async Task> GetClassifiedSpansAsync // MergeClassifiedSpans will ultimately filter multiple classifications for the same // span down to one. We know that additive classifications are there just to - // provide additional information about the true classification. We will remove - // additive ClassifiedSpans until we have support for additive classifications + // provide additional information about the true classification. By default, we will + // remove additive ClassifiedSpans until we have support for additive classifications // in classified spans. https://github.com/dotnet/roslyn/issues/32770 - RemoveAdditiveSpans(syntaxSpans); - RemoveAdditiveSpans(semanticSpans); + // The exception to this is LSP, which expects the additive spans. + if (removeAdditiveSpans) + { + RemoveAdditiveSpans(syntaxSpans); + RemoveAdditiveSpans(semanticSpans); + } - var classifiedSpans = MergeClassifiedSpans(syntaxSpans, semanticSpans, span); + var classifiedSpans = MergeClassifiedSpans(syntaxSpans, semanticSpans, span, fillInClassifiedSpanGaps); return classifiedSpans; } @@ -67,7 +75,10 @@ private static void RemoveAdditiveSpans(ArrayBuilder spans) } private static ImmutableArray MergeClassifiedSpans( - ArrayBuilder syntaxSpans, ArrayBuilder semanticSpans, TextSpan widenedSpan) + ArrayBuilder syntaxSpans, + ArrayBuilder semanticSpans, + TextSpan widenedSpan, + bool fillInClassifiedSpanGaps) { // The spans produced by the language services may not be ordered // (indeed, this happens with semantic classification as different @@ -90,6 +101,11 @@ private static ImmutableArray MergeClassifiedSpans( AdjustSpans(syntaxSpans, widenedSpan); AdjustSpans(semanticSpans, widenedSpan); + if (!fillInClassifiedSpanGaps) + { + return MergeParts(syntaxSpans, semanticSpans); + } + // The classification service will only produce classifications for // things it knows about. i.e. there will be gaps in what it produces. // Fill in those gaps so we have *all* parts of the span @@ -169,7 +185,7 @@ private static ImmutableArray MergeParts( // Take all the syntax parts. However, if any have been overridden by a // semantic part, then choose that one. - var finalParts = ArrayBuilder.GetInstance(); + using var _ = ArrayBuilder.GetInstance(out var finalParts); var lastReplacementIndex = 0; for (int i = 0, n = syntaxParts.Count; i < n; i++) { @@ -177,7 +193,7 @@ private static ImmutableArray MergeParts( // See if we can find a semantic part to replace this syntax part. var replacementIndex = semanticParts.FindIndex( - lastReplacementIndex, t => t.TextSpan == syntaxPartAndSpan.TextSpan); + lastReplacementIndex, t => t.TextSpan.OverlapsWith(syntaxPartAndSpan.TextSpan)); // Take the semantic part if it's just 'text'. We want to keep it if // the semantic classifier actually produced an interesting result @@ -189,6 +205,12 @@ private static ImmutableArray MergeParts( if (replacementIndex >= 0) { + // There may be multiple semantic parts corresponding to a single + // syntactic part, so we might need to go through a syntactic part + // multiple times to verify. For example, this is the case with + // verbatim string literals containing string escape characters. + i--; + // If we found a semantic replacement, update the lastIndex. // That way we can start searching from that point instead // of checking all the elements each time. @@ -196,7 +218,7 @@ private static ImmutableArray MergeParts( } } - return finalParts.ToImmutableAndFree(); + return finalParts.ToImmutable(); } private static bool IsClassifiedAsText(ClassifiedSpan partAndSpan) diff --git a/src/Workspaces/Core/Portable/Classification/IRemoteSemanticClassificationCacheService.cs b/src/Workspaces/Core/Portable/Classification/IRemoteSemanticClassificationCacheService.cs deleted file mode 100644 index 90e4bb72a743f..0000000000000 --- a/src/Workspaces/Core/Portable/Classification/IRemoteSemanticClassificationCacheService.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Remote; -using Microsoft.CodeAnalysis.Serialization; -using Microsoft.CodeAnalysis.Storage; -using Microsoft.CodeAnalysis.Text; - -namespace Microsoft.CodeAnalysis.Classification -{ - /// - /// Remote stubs used for a host to request cached semantic classifications from an OOP server. - /// - internal interface IRemoteSemanticClassificationCacheService - { - ValueTask CacheSemanticClassificationsAsync( - PinnedSolutionInfo solutionInfo, DocumentId documentId, CancellationToken cancellationToken); - - /// - /// Tries to get cached semantic classifications for the specified document and the specified . Will return an empty array not able to. - /// - /// Pass in . This will ensure that the cached - /// classifications are only returned if they match the content the file currently has. - ValueTask GetCachedSemanticClassificationsAsync( - DocumentKey documentKey, - TextSpan textSpan, - Checksum checksum, - StorageDatabase database, - CancellationToken cancellationToken); - } -} diff --git a/src/Workspaces/Core/Portable/Classification/IRemoteSemanticClassificationService.cs b/src/Workspaces/Core/Portable/Classification/IRemoteSemanticClassificationService.cs index 11ef8568e7a5f..7e259c5846709 100644 --- a/src/Workspaces/Core/Portable/Classification/IRemoteSemanticClassificationService.cs +++ b/src/Workspaces/Core/Portable/Classification/IRemoteSemanticClassificationService.cs @@ -9,6 +9,8 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; +using Microsoft.CodeAnalysis.Serialization; +using Microsoft.CodeAnalysis.Storage; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -17,7 +19,20 @@ namespace Microsoft.CodeAnalysis.Classification internal interface IRemoteSemanticClassificationService { ValueTask GetSemanticClassificationsAsync( - PinnedSolutionInfo solutionInfo, DocumentId documentId, TextSpan span, ClassificationOptions options, CancellationToken cancellationToken); + PinnedSolutionInfo solutionInfo, DocumentId documentId, TextSpan span, ClassificationOptions options, bool isFullyLoaded, CancellationToken cancellationToken); + + /// + /// Tries to get cached semantic classifications for the specified document and the specified . Will return an empty array not able to. + /// + /// Pass in . This will ensure that the cached + /// classifications are only returned if they match the content the file currently has. + ValueTask GetCachedSemanticClassificationsAsync( + DocumentKey documentKey, + TextSpan textSpan, + Checksum checksum, + StorageDatabase database, + CancellationToken cancellationToken); } /// diff --git a/src/Workspaces/Core/Portable/Classification/ISemanticClassificationCacheService.cs b/src/Workspaces/Core/Portable/Classification/ISemanticClassificationCacheService.cs deleted file mode 100644 index 1c78b56fe107a..0000000000000 --- a/src/Workspaces/Core/Portable/Classification/ISemanticClassificationCacheService.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using System.Composition; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Classification -{ - /// - /// Service that can retrieve semantic classifications for a document cached during a previous session. This is - /// intended to help populate semantic classifications for a host during the time while a solution is loading and - /// semantics may be incomplete or unavailable. - /// - internal interface ISemanticClassificationCacheService : IWorkspaceService - { - /// - /// Tries to get cached semantic classifications for the specified document and the specified . Will return a default array not able to. An empty array indicates that there - /// were cached classifications, but none that intersected the provided . - /// - Task> GetCachedSemanticClassificationsAsync( - Document document, TextSpan textSpan, CancellationToken cancellationToken); - } - - [ExportWorkspaceService(typeof(ISemanticClassificationCacheService)), Shared] - internal class DefaultSemanticClassificationCacheService : ISemanticClassificationCacheService - { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public DefaultSemanticClassificationCacheService() - { - } - - public Task> GetCachedSemanticClassificationsAsync(Document document, TextSpan textSpan, CancellationToken cancellationToken) - => SpecializedTasks.Default>(); - } -} diff --git a/src/Features/Core/Portable/SemanticClassificationCache/SemanticClassificationCacheUtilities.cs b/src/Workspaces/Core/Portable/Classification/SemanticClassificationCacheUtilities.cs similarity index 97% rename from src/Features/Core/Portable/SemanticClassificationCache/SemanticClassificationCacheUtilities.cs rename to src/Workspaces/Core/Portable/Classification/SemanticClassificationCacheUtilities.cs index 126284e57dae4..a5c61f104dc28 100644 --- a/src/Features/Core/Portable/SemanticClassificationCache/SemanticClassificationCacheUtilities.cs +++ b/src/Workspaces/Core/Portable/Classification/SemanticClassificationCacheUtilities.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Storage; -namespace Microsoft.CodeAnalysis.SemanticClassificationCache +namespace Microsoft.CodeAnalysis.Classification { internal static class SemanticClassificationCacheUtilities { diff --git a/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationParameterSymbol.cs b/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationParameterSymbol.cs index 5c24c75230aaa..f296c8ee1fda6 100644 --- a/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationParameterSymbol.cs +++ b/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationParameterSymbol.cs @@ -65,6 +65,7 @@ public override TResult Accept(SymbolVisitor visitor) public ImmutableArray CustomModifiers => ImmutableArray.Create(); + public bool IsNullChecked => false; public bool IsDiscard => false; } } diff --git a/src/Workspaces/Core/Portable/Rename/RenameOptions.cs b/src/Workspaces/Core/Portable/Rename/RenameOptions.cs index cac76215e79ac..8d4cfa0164ebe 100644 --- a/src/Workspaces/Core/Portable/Rename/RenameOptions.cs +++ b/src/Workspaces/Core/Portable/Rename/RenameOptions.cs @@ -22,21 +22,12 @@ public static class RenameOptions public static Option PreviewChanges { get; } = new Option(nameof(RenameOptions), nameof(PreviewChanges), defaultValue: false); } - internal struct RenameOptionSet + internal readonly record struct RenameOptionSet( + bool RenameOverloads, + bool RenameInStrings, + bool RenameInComments, + bool RenameFile) { - public readonly bool RenameOverloads; - public readonly bool RenameInStrings; - public readonly bool RenameInComments; - public readonly bool RenameFile; - - public RenameOptionSet(bool renameOverloads, bool renameInStrings, bool renameInComments, bool renameFile) - { - RenameOverloads = renameOverloads; - RenameInStrings = renameInStrings; - RenameInComments = renameInComments; - RenameFile = renameFile; - } - internal static RenameOptionSet From(Solution solution) => From(solution, options: null); diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue`2.cs b/src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue`2.cs index debca352c369c..68649d2c11e1d 100644 --- a/src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue`2.cs +++ b/src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue`2.cs @@ -121,6 +121,23 @@ public void AddWork(IEnumerable items) return; + void AddItemsToBatch(IEnumerable items) + { + // no equality comparer. We want to process all items. + if (_equalityComparer == null) + { + _nextBatch.AddRange(items); + return; + } + + // We're deduping items. Only add the item if it's the first time we've seen it. + foreach (var item in items) + { + if (_uniqueItems.Add(item)) + _nextBatch.Add(item); + } + } + async Task ContinueAfterDelay(Task lastTask) { using var _ = _asyncListener.BeginAsyncOperation(nameof(AddWork)); @@ -142,23 +159,6 @@ public void AddWork(IEnumerable items) return _updateTask; } - private void AddItemsToBatch(IEnumerable items) - { - // no equality comparer. We want to process all items. - if (_equalityComparer == null) - { - _nextBatch.AddRange(items); - return; - } - - // We're deduping items. Only add the item if it's the first time we've seen it. - foreach (var item in items) - { - if (_uniqueItems.Add(item)) - _nextBatch.Add(item); - } - } - private ValueTask ProcessNextBatchAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/Interop/SqlConnection.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/Interop/SqlConnection.cs index 7a191298ed363..3d69836813508 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/Interop/SqlConnection.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/Interop/SqlConnection.cs @@ -126,8 +126,14 @@ public static SqlConnection Create(IPersistentStorageFaultInjector? faultInjecto // uri of the DB on disk we're associating this in-memory cache with. This throws on at least OSX for // reasons that aren't fully understood yet. If more details/fixes emerge in that linked issue, we can // ideally remove this and perform the attachment uniformly on all platforms. + + // From: https://www.sqlite.org/lang_expr.html + // + // A string constant is formed by enclosing the string in single quotes ('). A single quote within the + // string can be encoded by putting two single quotes in a row - as in Pascal. C-style escapes using the + // backslash character are not supported because they are not standard SQL. var attachString = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? $"attach database '{new Uri(databasePath).AbsoluteUri}?mode=memory&cache=shared' as {Database.WriteCache.GetName()};" + ? $"attach database '{new Uri(databasePath.Replace("'", "''")).AbsoluteUri}?mode=memory&cache=shared' as {Database.WriteCache.GetName()};" : $"attach database 'file::memory:?cache=shared' as {Database.WriteCache.GetName()};"; connection.ExecuteCommand(attachString); diff --git a/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs b/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs index 21c9d5fce8591..240d73168bc28 100644 --- a/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs +++ b/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs @@ -194,7 +194,6 @@ public InProcRemoteServices(HostWorkspaceServices workspaceServices, TraceListen RegisterRemoteBrokeredService(new RemoteTodoCommentsDiscoveryService.Factory()); RegisterRemoteBrokeredService(new RemoteDiagnosticAnalyzerService.Factory()); RegisterRemoteBrokeredService(new RemoteSemanticClassificationService.Factory()); - RegisterRemoteBrokeredService(new RemoteSemanticClassificationCacheService.Factory()); RegisterRemoteBrokeredService(new RemoteDocumentHighlightsService.Factory()); RegisterRemoteBrokeredService(new RemoteEncapsulateFieldService.Factory()); RegisterRemoteBrokeredService(new RemoteRenamerService.Factory()); diff --git a/src/Workspaces/Remote/Core/RemoteWorkspacesResources.resx b/src/Workspaces/Remote/Core/RemoteWorkspacesResources.resx index 711236fa3ac77..32b3079cc1b49 100644 --- a/src/Workspaces/Remote/Core/RemoteWorkspacesResources.resx +++ b/src/Workspaces/Remote/Core/RemoteWorkspacesResources.resx @@ -171,9 +171,6 @@ Semantic classification - - Semantic classification cache - Symbol finder diff --git a/src/Workspaces/Remote/Core/ServiceDescriptors.cs b/src/Workspaces/Remote/Core/ServiceDescriptors.cs index d973eb1283769..339d2fd811c94 100644 --- a/src/Workspaces/Remote/Core/ServiceDescriptors.cs +++ b/src/Workspaces/Remote/Core/ServiceDescriptors.cs @@ -57,7 +57,6 @@ internal sealed class ServiceDescriptors (typeof(IRemoteProjectTelemetryService), typeof(IRemoteProjectTelemetryService.ICallback)), (typeof(IRemoteDiagnosticAnalyzerService), null), (typeof(IRemoteSemanticClassificationService), null), - (typeof(IRemoteSemanticClassificationCacheService), null), (typeof(IRemoteDocumentHighlightsService), null), (typeof(IRemoteEncapsulateFieldService), null), (typeof(IRemoteRenamerService), null), diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.cs.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.cs.xlf index bd78855426cfc..ac955b2f7261d 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.cs.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.cs.xlf @@ -112,11 +112,6 @@ Sémantická klasifikace - - Semantic classification cache - Mezipaměť sémantické klasifikace - - Asset provider Poskytovatel prostředků diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.de.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.de.xlf index 168888fab7707..487df947a2b1c 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.de.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.de.xlf @@ -112,11 +112,6 @@ Semantische Klassifizierung - - Semantic classification cache - Cache für semantische Klassifizierung - - Asset provider Objektanbieter diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.es.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.es.xlf index 1bca7a507182e..7be5777a98d58 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.es.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.es.xlf @@ -112,11 +112,6 @@ Clasificación semántica - - Semantic classification cache - Caché de clasificación semántica - - Asset provider Proveedor de recursos diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.fr.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.fr.xlf index 5007994441e27..36cfd27dca1cc 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.fr.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.fr.xlf @@ -112,11 +112,6 @@ Classification sémantique - - Semantic classification cache - Cache de classification sémantique - - Asset provider Fournisseur de ressources diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.it.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.it.xlf index f7db4bda55c9e..bd450c678f700 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.it.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.it.xlf @@ -112,11 +112,6 @@ Classificazione semantica - - Semantic classification cache - Cache della classificazione semantica - - Asset provider Provider di asset diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ja.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ja.xlf index 10af9837d06e0..9849446cea3ed 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ja.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ja.xlf @@ -112,11 +112,6 @@ セマンティック分類 - - Semantic classification cache - セマンティック分類キャッシュ - - Asset provider 資産プロバイダー diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ko.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ko.xlf index d68a4690eeaae..3fd9514bdf002 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ko.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ko.xlf @@ -112,11 +112,6 @@ 의미 체계 분류 - - Semantic classification cache - 의미 체계 분류 캐시 - - Asset provider 자산 공급자 diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pl.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pl.xlf index ee0d9b1ce3ec1..f90d53513925b 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pl.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pl.xlf @@ -112,11 +112,6 @@ Klasyfikacja semantyczna - - Semantic classification cache - Pamięć podręczna klasyfikacji semantycznej - - Asset provider Dostawca elementów zawartości diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pt-BR.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pt-BR.xlf index ebe44d5dc63bb..cc29c449693f8 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pt-BR.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pt-BR.xlf @@ -112,11 +112,6 @@ Classificação semântica - - Semantic classification cache - Cache de classificação semântica - - Asset provider Provedor de ativos diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ru.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ru.xlf index 51136013069e6..d003842fccfa2 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ru.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ru.xlf @@ -112,11 +112,6 @@ Семантическая классификация - - Semantic classification cache - Кэш семантической классификации - - Asset provider Поставщик ресурсов diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.tr.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.tr.xlf index 4ce0b9d6f5773..ba76ab9632328 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.tr.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.tr.xlf @@ -112,11 +112,6 @@ Anlamsal sınıflandırma - - Semantic classification cache - Anlamsal sınıflandırma önbelleği - - Asset provider Varlık sağlayıcısı diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hans.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hans.xlf index 69f687e043e73..048d4167d327d 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hans.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hans.xlf @@ -112,11 +112,6 @@ 语义分类 - - Semantic classification cache - 语义分类缓存 - - Asset provider 资产提供商 diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hant.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hant.xlf index 37e5eda58f50a..8c4a36ea93c31 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hant.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hant.xlf @@ -112,11 +112,6 @@ 語意分類 - - Semantic classification cache - 語意分類快取 - - Asset provider 資產提供者 diff --git a/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.cs b/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.cs index ba4d67a0683a1..6a355eb37769a 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.cs @@ -69,7 +69,7 @@ protected BrokeredServiceBase(in ServiceConstructionArguments arguments) SolutionAssetSource = new SolutionAssetSource(ServiceBrokerClient); } - public void Dispose() + public virtual void Dispose() => ServiceBrokerClient.Dispose(); public RemoteWorkspace GetWorkspace() diff --git a/src/Workspaces/Remote/ServiceHub/Services/SemanticClassificationCache/RemoteSemanticClassificationCacheService.cs b/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.Caching.cs similarity index 82% rename from src/Workspaces/Remote/ServiceHub/Services/SemanticClassificationCache/RemoteSemanticClassificationCacheService.cs rename to src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.Caching.cs index 9e551a09c059d..19d06e10d79b0 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SemanticClassificationCache/RemoteSemanticClassificationCacheService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.Caching.cs @@ -2,35 +2,27 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Classification; +using Microsoft.CodeAnalysis.Editor.Shared.Tagging; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.SemanticClassificationCache; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Storage; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Remote { - internal sealed class RemoteSemanticClassificationCacheService : BrokeredServiceBase, IRemoteSemanticClassificationCacheService + internal sealed partial class RemoteSemanticClassificationService : BrokeredServiceBase, IRemoteSemanticClassificationService { - internal sealed class Factory : FactoryBase - { - protected override IRemoteSemanticClassificationCacheService CreateService(in ServiceConstructionArguments arguments) - => new RemoteSemanticClassificationCacheService(arguments); - } - - public RemoteSemanticClassificationCacheService(in ServiceConstructionArguments arguments) - : base(arguments) - { - } - /// /// Key we use to look this up in the persistence store for a particular document. /// @@ -54,23 +46,50 @@ public RemoteSemanticClassificationCacheService(in ServiceConstructionArguments /// private readonly LinkedList<(DocumentId id, Checksum checksum, ImmutableArray classifiedSpans)> _cachedData = new(); - public ValueTask CacheSemanticClassificationsAsync( - PinnedSolutionInfo solutionInfo, - DocumentId documentId, - CancellationToken cancellationToken) + /// + /// Queue where we place documents we want to compute and cache full semantic classifications for. Note: the + /// same document may appear multiple times inside of this queue (for different versions of the document). + /// However, we'll only process the last version of any document added. + /// + private readonly AsyncBatchingWorkQueue _workQueue; + private readonly CancellationTokenSource _cancellationTokenSource = new(); + + public RemoteSemanticClassificationService(in ServiceConstructionArguments arguments) + : base(arguments) { - return RunServiceAsync(async cancellationToken => - { - // We only get called to cache classifications once we're fully loaded. At that point there's no need - // for us to keep around any of the data we cached in-memory during the time the solution was loading. - lock (_cachedData) - _cachedData.Clear(); + _workQueue = new AsyncBatchingWorkQueue( + TimeSpan.FromMilliseconds(TaggerConstants.ShortDelay), + CacheSemanticClassificationsAsync, + EqualityComparer.Default, + AsynchronousOperationListenerProvider.NullListener, + _cancellationTokenSource.Token); + } - var solution = await GetSolutionAsync(solutionInfo, cancellationToken).ConfigureAwait(false); - var document = solution.GetRequiredDocument(documentId); + public override void Dispose() + { + _cancellationTokenSource.Cancel(); + base.Dispose(); + } + + public async ValueTask GetCachedSemanticClassificationsAsync( + DocumentKey documentKey, TextSpan textSpan, Checksum checksum, StorageDatabase database, CancellationToken cancellationToken) + { + var classifiedSpans = await TryGetOrReadCachedSemanticClassificationsAsync( + documentKey, checksum, database, cancellationToken).ConfigureAwait(false); + return classifiedSpans.IsDefault + ? null + : SerializableClassifiedSpans.Dehydrate(classifiedSpans.WhereAsArray(c => c.TextSpan.IntersectsWith(textSpan))); + } - await CacheSemanticClassificationsAsync(document, cancellationToken).ConfigureAwait(false); - }, cancellationToken); + private static async ValueTask CacheSemanticClassificationsAsync( + ImmutableArray documents, CancellationToken cancellationToken) + { + // Group all the requests by document (as we may have gotten many requests for the same document). Then, + // only process the last document from each group (we don't need to bother stale versions of a particular + // document). + var groups = documents.GroupBy(d => d.Id); + var tasks = groups.Select(g => Task.Run(() => CacheSemanticClassificationsAsync(g.Last(), cancellationToken), cancellationToken)); + await Task.WhenAll(tasks).ConfigureAwait(false); } private static async Task CacheSemanticClassificationsAsync(Document document, CancellationToken cancellationToken) @@ -157,20 +176,6 @@ private static void WriteTo(ArrayBuilder classifiedSpans, Object } } - public ValueTask GetCachedSemanticClassificationsAsync( - DocumentKey documentKey, TextSpan textSpan, Checksum checksum, StorageDatabase database, CancellationToken cancellationToken) - { - return RunServiceAsync(async cancellationToken => - { - var classifiedSpans = await TryGetOrReadCachedSemanticClassificationsAsync( - documentKey, checksum, database, cancellationToken).ConfigureAwait(false); - if (classifiedSpans.IsDefault) - return null; - - return SerializableClassifiedSpans.Dehydrate(classifiedSpans.WhereAsArray(c => c.TextSpan.IntersectsWith(textSpan))); - }, cancellationToken); - } - private async Task> TryGetOrReadCachedSemanticClassificationsAsync( DocumentKey documentKey, Checksum checksum, diff --git a/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.cs b/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.cs index a6be01ca7c785..cd77d44cbb2df 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.cs @@ -2,18 +2,16 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Classification; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; -using Microsoft.ServiceHub.Framework; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Remote { - internal sealed class RemoteSemanticClassificationService : BrokeredServiceBase, IRemoteSemanticClassificationService + internal sealed partial class RemoteSemanticClassificationService : BrokeredServiceBase, IRemoteSemanticClassificationService { internal sealed class Factory : FactoryBase { @@ -21,16 +19,12 @@ protected override IRemoteSemanticClassificationService CreateService(in Service => new RemoteSemanticClassificationService(arguments); } - public RemoteSemanticClassificationService(in ServiceConstructionArguments arguments) - : base(arguments) - { - } - public ValueTask GetSemanticClassificationsAsync( PinnedSolutionInfo solutionInfo, DocumentId documentId, TextSpan span, ClassificationOptions options, + bool isFullyLoaded, CancellationToken cancellationToken) { return RunServiceAsync(async cancellationToken => @@ -43,6 +37,17 @@ public ValueTask GetSemanticClassificationsAsync( await AbstractClassificationService.AddSemanticClassificationsInCurrentProcessAsync( document, span, options, temp, cancellationToken).ConfigureAwait(false); + if (isFullyLoaded) + { + // Once fully loaded, there's no need for us to keep around any of the data we cached in-memory + // during the time the solution was loading. + lock (_cachedData) + _cachedData.Clear(); + + // Enqueue this document into our work queue to fully classify and cache. + _workQueue.AddWork(document); + } + return SerializableClassifiedSpans.Dehydrate(temp.ToImmutable()); }, cancellationToken); } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/LanguageVersionExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/LanguageVersionExtensions.cs index 65ca1c846c9ce..67faee49d91e6 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/LanguageVersionExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/LanguageVersionExtensions.cs @@ -14,5 +14,10 @@ public static bool IsCSharp10OrAbove(this LanguageVersion languageVersion) public static bool HasConstantInterpolatedStrings(this LanguageVersion languageVersion) => languageVersion.IsCSharp10OrAbove(); + + /// + /// Corresponds to Microsoft.CodeAnalysis.CSharp.LanguageVersionFacts.CSharpNext. + /// + internal const LanguageVersion CSharpNext = LanguageVersion.Preview; } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/TokenBasedFormattingRule.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/TokenBasedFormattingRule.cs index da54acb6fbb91..cf4fe2139fa1f 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/TokenBasedFormattingRule.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/TokenBasedFormattingRule.cs @@ -478,6 +478,13 @@ SyntaxKind.ImplicitObjectCreationExpression or return CreateAdjustSpacesOperation(0, AdjustSpacesOption.ForceSpacesIfOnSingleLine); } + // paramName!! + if (currentToken.IsKind(SyntaxKind.ExclamationExclamationToken) && + currentToken.Parent.IsKind(SyntaxKind.Parameter)) + { + return CreateAdjustSpacesOperation(0, AdjustSpacesOption.ForceSpacesIfOnSingleLine); + } + // pointer case for regular pointers if (currentToken.Kind() == SyntaxKind.AsteriskToken && currentToken.Parent is PointerTypeSyntax) { diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/AbstractSpeculationAnalyzer.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/AbstractSpeculationAnalyzer.cs index 496ce77e66885..22e144fe24d7d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/AbstractSpeculationAnalyzer.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/AbstractSpeculationAnalyzer.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Threading; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; @@ -502,6 +503,9 @@ private bool ReplacementChangesSemanticsForNode(SyntaxNode currentOriginalNode, Debug.Assert(previousOriginalNode == null || previousOriginalNode.Parent == currentOriginalNode); Debug.Assert(previousReplacedNode == null || previousReplacedNode.Parent == currentReplacedNode); + if (!OperationsAreCompatible(currentOriginalNode, currentReplacedNode)) + return true; + if (ExpressionMightReferenceMember(currentOriginalNode)) { // If replacing the node will result in a change in overload resolution, we won't remove it. @@ -557,6 +561,31 @@ private bool ReplacementChangesSemanticsForNode(SyntaxNode currentOriginalNode, return false; } + private bool OperationsAreCompatible(SyntaxNode currentOriginalNode, SyntaxNode currentReplacedNode) + { + var originalOperation = this._semanticModel.GetOperation(currentOriginalNode, CancellationToken); + var currentOperation = this.SpeculativeSemanticModel.GetOperation(currentReplacedNode, CancellationToken); + + if (originalOperation is IInvocationOperation originalInvocation) + { + // Invocations must stay invocations after update. + if (currentOperation is not IInvocationOperation currentInvocation) + return false; + + // An instance call must stay an instance call (and a static call must stay a static call). + if (IsNullOrNone(originalInvocation.Instance) != IsNullOrNone(currentInvocation.Instance)) + return false; + + // Add more invocation tests here. + } + + // Add more operation tests here. + return true; + } + + private static bool IsNullOrNone(IOperation? instance) + => instance is null || instance.Kind == OperationKind.None; + /// /// Determine if removing the cast could cause the semantics of System.Object method call to change. /// E.g. Dim b = CStr(1).GetType() is necessary, but the GetType method symbol info resolves to the same with or without the cast. diff --git a/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/ExpressionSimplifier.vb b/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/ExpressionSimplifier.vb index 56100edb88125..6a205ee2caaa2 100644 --- a/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/ExpressionSimplifier.vb +++ b/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/ExpressionSimplifier.vb @@ -289,16 +289,21 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification.Simplifiers End Function Private Shared Function CanReplaceWithReducedName( - memberAccess As MemberAccessExpressionSyntax, - reducedNode As ExpressionSyntax, - semanticModel As SemanticModel, - symbol As ISymbol, - cancellationToken As CancellationToken - ) As Boolean + memberAccess As MemberAccessExpressionSyntax, + reducedNode As ExpressionSyntax, + semanticModel As SemanticModel, + symbol As ISymbol, + cancellationToken As CancellationToken) As Boolean If Not IsMeOrNamedTypeOrNamespace(memberAccess.Expression, semanticModel) Then Return False End If + ' A static reference off of 'me' can always be replaced with a direct reference to that static symbol + ' without changing semantics. + If memberAccess.Expression.IsKind(SyntaxKind.MeExpression) AndAlso symbol.IsStatic Then + Return True + End If + ' See if we can simplify a member access expression of the form E.M or E.M() to M or M() Dim speculationAnalyzer = New SpeculationAnalyzer(memberAccess, reducedNode, semanticModel, cancellationToken) If Not speculationAnalyzer.SymbolsForOriginalAndReplacedNodesAreCompatible() OrElse