diff --git a/docs/Language Feature Status.md b/docs/Language Feature Status.md index 768a966e61f4e..1685a00f7e9d7 100644 --- a/docs/Language Feature Status.md +++ b/docs/Language Feature Status.md @@ -10,11 +10,7 @@ efforts behind them. | Feature | Branch | State | Developer | Reviewer | LDM Champ | | ------- | ------ | ----- | --------- | -------- | --------- | -| [Static Abstract Members In Interfaces](https://github.com/dotnet/csharplang/issues/4436) | [StaticAbstractMembersInInterfaces](https://github.com/dotnet/roslyn/tree/features/StaticAbstractMembersInInterfaces) | [In Progress](https://github.com/dotnet/roslyn/issues/52221) | [AlekseyTs](https://github.com/AlekseyTs) | [333fred](https://github.com/333fred), [RikkiGibson](https://github.com/RikkiGibson) | [MadsTorgersen](https://github.com/MadsTorgersen) | -| [File-scoped namespace](https://github.com/dotnet/csharplang/issues/137) | [FileScopedNamespaces](https://github.com/dotnet/roslyn/tree/features/FileScopedNamespaces) | [In Progress](https://github.com/dotnet/roslyn/issues/49000) | [RikkiGibson](https://github.com/RikkiGibson) | [jcouv](https://github.com/jcouv), [chsienki](https://github.com/chsienki) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | -| [Interpolated string improvements](https://github.com/dotnet/csharplang/issues/4487) | [interpolated-string](https://github.com/dotnet/roslyn/tree/features/interpolated-string) | [In Progress](https://github.com/dotnet/roslyn/issues/51499) | [333fred](https://github.com/333fred) | [AlekseyTs](https://github.com/AlekseyTs), [chsienki](https://github.com/chsienki) | [jaredpar](https://github.com/jaredpar) | | [Parameterless struct constructors](https://github.com/dotnet/csharplang/issues/99) | [struct-ctors](https://github.com/dotnet/roslyn/tree/features/struct-ctors) | [In Progress](https://github.com/dotnet/roslyn/issues/51698) | [cston](https://github.com/cston) | [jcouv](https://github.com/jcouv), [333fred](https://github.com/333fred) | [jcouv](https://github.com/jouv) | -| [Lambda improvements](https://github.com/dotnet/csharplang/blob/main/proposals/lambda-improvements.md) | [lambdas](https://github.com/dotnet/roslyn/tree/features/lambdas) | [In Progress](https://github.com/dotnet/roslyn/issues/52192) | [cston](https://github.com/cston) | [333fred](https://github.com/333fred), [jcouv](https://github.com/jcouv) | [jaredpar](https://github.com/jaredpar) | | [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) | [fayrose](https://github.com/fayrose) | [agocke](https://github.com/agocke) | [jaredpar](https://github.com/jaredpar) | @@ -38,6 +34,10 @@ efforts behind them. | [Mix declarations and variables in deconstruction](https://github.com/dotnet/csharplang/issues/125) | main | [Merged into 16.10](https://github.com/dotnet/roslyn/issues/47746) | [YairHalberstadt ](https://github.com/YairHalberstadt) | [jcouv](https://github.com/jcouv) | [MadsTorgersen](https://github.com/MadsTorgersen) | | [Async method builder override](https://github.com/dotnet/csharplang/issues/1407) | main | [Merged into 17.0p2](https://github.com/dotnet/roslyn/issues/51999) | [jcouv](https://github.com/jcouv) | [cston](https://github.com/cston), [RikkiGibson](https://github.com/RikkiGibson) | [stephentoub](https://github.com/stephentoub) | | [Enhanced `#line` directive](https://github.com/dotnet/csharplang/issues/4747) | main | [Merged into 17.0p2](https://github.com/dotnet/roslyn/issues/54509) | [cston](https://github.com/cston) | [jcouv](https://github.com/jcouv), [RikkiGibson](https://github.com/RikkiGibson) | [MadsTorgersen](https://github.com/MadsTorgersen) | +| [Lambda improvements](https://github.com/dotnet/csharplang/blob/main/proposals/lambda-improvements.md) | main | [Merged into 17.0p2](https://github.com/dotnet/roslyn/issues/52192) | [cston](https://github.com/cston) | [333fred](https://github.com/333fred), [jcouv](https://github.com/jcouv) | [jaredpar](https://github.com/jaredpar) | +| [Static Abstract Members In Interfaces C# 10 Preview](https://github.com/dotnet/csharplang/issues/4436) | main | [Merged into 17.0p2](https://github.com/dotnet/roslyn/issues/52221) | [AlekseyTs](https://github.com/AlekseyTs) | [333fred](https://github.com/333fred), [RikkiGibson](https://github.com/RikkiGibson) | [MadsTorgersen](https://github.com/MadsTorgersen) | +| [Interpolated string improvements](https://github.com/dotnet/csharplang/issues/4487) | main | [Merged into 17.0p3](https://github.com/dotnet/roslyn/issues/51499) | [333fred](https://github.com/333fred) | [AlekseyTs](https://github.com/AlekseyTs), [chsienki](https://github.com/chsienki) | [jaredpar](https://github.com/jaredpar) | +| [File-scoped namespace](https://github.com/dotnet/csharplang/issues/137) | main | [Merged into 17.0p3](https://github.com/dotnet/roslyn/issues/49000) | [RikkiGibson](https://github.com/RikkiGibson) | [jcouv](https://github.com/jcouv), [chsienki](https://github.com/chsienki) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | # VB 16.9 diff --git a/docs/features/source-generators.cookbook.md b/docs/features/source-generators.cookbook.md index 499d585aaaa7a..5ef562d10b60b 100644 --- a/docs/features/source-generators.cookbook.md +++ b/docs/features/source-generators.cookbook.md @@ -693,10 +693,10 @@ await new VerifyCS.Test TestState = { Sources = { code }, - }, - GeneratedSources = - { - (typeof(YourGenerator), "GeneratedFileName", SourceText.From(generated, Encoding.UTF8, SourceHashAlgorithm.Sha256)), + GeneratedSources = + { + (typeof(YourGenerator), "GeneratedFileName", SourceText.From(generated, Encoding.UTF8, SourceHashAlgorithm.Sha256)), + }, }, }.RunAsync(); ``` diff --git a/docs/wiki/NuGet-packages.md b/docs/wiki/NuGet-packages.md index d7e1a31b51f58..242a13d5919b8 100644 --- a/docs/wiki/NuGet-packages.md +++ b/docs/wiki/NuGet-packages.md @@ -33,6 +33,8 @@ Below are the versions of the language available in the NuGet packages. Remember - Version `3.4` includes C# 8.0 (Visual Studio 2019 version 16.4, .NET Core 3.1) - ... - Version `3.8` includes C# 9.0 (Visual Studio 2019 version 16.8, .NET 5) +- ... +- Version `4.0` includes C# 10.0 (Visual Studio 2022 version 17.0, .NET 6) See the [history of C# language features](https://github.com/dotnet/csharplang/blob/main/Language-Version-History.md) for more details. diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index e3d71b5799150..9e372390d4dfe 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -6,9 +6,9 @@ 7e80445ee82adbf9a8e6ae601ac5e239d982afaa - + https://github.com/dotnet/source-build - bf8af9a5202d5016b4bcf3424fac6bac55ef7c8e + 80d41001c0b5a72651f5459b9b1fc3e1e9b1231d @@ -18,9 +18,9 @@ 78da7776965b428ff31da8f1ff2cb073506212b7 - + https://github.com/dotnet/roslyn - ca27d128f3533dc41a46b010b8e878916328f2e4 + ea623578b108856d3416af28af61060ed3d695e8 https://github.com/dotnet/arcade diff --git a/eng/Versions.props b/eng/Versions.props index 1a88275962197..f3bf6b0bc33e4 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -23,7 +23,7 @@ - 4.0.0-2.21359.14 + 4.0.0-3.21369.1 diff --git a/eng/config/globalconfigs/Common.globalconfig b/eng/config/globalconfigs/Common.globalconfig index 51441f5c46ad5..9be4ffd974176 100644 --- a/eng/config/globalconfigs/Common.globalconfig +++ b/eng/config/globalconfigs/Common.globalconfig @@ -5,6 +5,9 @@ dotnet_diagnostic.CA1068.severity = warning dotnet_diagnostic.CA1200.severity = warning dotnet_diagnostic.CA1821.severity = warning +# CA2012: Use ValueTasks correctly +dotnet_diagnostic.CA2012.severity = warning + dotnet_diagnostic.IDE0055.severity = warning dotnet_diagnostic.RS1001.severity = none diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs index a4555b2a11018..560b5bf221e26 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs @@ -334,8 +334,16 @@ private BoundExpression ConvertConditionalExpression( : GenerateConversionForAssignment(destination, source.Alternative, diagnostics); var constantValue = FoldConditionalOperator(condition, trueExpr, falseExpr); hasErrors |= constantValue?.IsBad == true; - if (targetTyped && !destination.IsErrorType()) - MessageID.IDS_FeatureTargetTypedConditional.CheckFeatureAvailability(diagnostics, source.Syntax); + if (targetTyped && !destination.IsErrorType() && !Compilation.IsFeatureEnabled(MessageID.IDS_FeatureTargetTypedConditional)) + { + diagnostics.Add( + ErrorCode.ERR_NoImplicitConvTargetTypedConditional, + source.Syntax.Location, + Compilation.LanguageVersion.ToDisplayString(), + source.Consequence.Display, + source.Alternative.Display, + new CSharpRequiredLanguageVersion(MessageID.IDS_FeatureTargetTypedConditional.RequiredVersion())); + } return new BoundConditionalOperator(source.Syntax, isRef: false, condition, trueExpr, falseExpr, constantValue, source.Type, wasTargetTyped: targetTyped, destination, hasErrors) .WithSuppression(source.IsSuppressed); diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index de133b845394d..ec8f847e7ce6d 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -6442,6 +6442,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ target-typed conditional expression + + Conditional expression is not valid in language version {0} because a common type was not found between '{1}' and '{2}'. To use a target-typed conversion, upgrade to language version {3} or greater. + '{0}' does not override expected method from 'object'. diff --git a/src/Compilers/CSharp/Portable/CommandLine/CSharpCommandLineParser.cs b/src/Compilers/CSharp/Portable/CommandLine/CSharpCommandLineParser.cs index 5e565391886b7..bddf333acf395 100644 --- a/src/Compilers/CSharp/Portable/CommandLine/CSharpCommandLineParser.cs +++ b/src/Compilers/CSharp/Portable/CommandLine/CSharpCommandLineParser.cs @@ -24,6 +24,7 @@ public class CSharpCommandLineParser : CommandLineParser public static CSharpCommandLineParser Script { get; } = new CSharpCommandLineParser(isScriptCommandLineParser: true); private static readonly char[] s_quoteOrEquals = new[] { '"', '=' }; + private static readonly char[] s_warningSeparators = new char[] { ',', ';', ' ' }; internal CSharpCommandLineParser(bool isScriptCommandLineParser = false) : base(CSharp.MessageProvider.Instance, isScriptCommandLineParser) @@ -51,7 +52,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar Debug.Assert(baseDirectory == null || PathUtilities.IsAbsolute(baseDirectory)); List diagnostics = new List(); - List flattenedArgs = new List(); + var flattenedArgs = ArrayBuilder.GetInstance(); List? scriptArgs = IsScriptCommandLineParser ? new List() : null; List? responsePaths = IsScriptCommandLineParser ? new List() : null; FlattenArgs(args, diagnostics, flattenedArgs, scriptArgs, baseDirectory, responsePaths); @@ -142,14 +143,13 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar { foreach (string arg in flattenedArgs) { - string? name, value; - if (TryParseOption(arg, out name, out value) && (name == "ruleset")) + if (IsOption("ruleset", arg, out ReadOnlyMemory name, out ReadOnlyMemory? value)) { var unquoted = RemoveQuotesAndSlashes(value); if (RoslynString.IsNullOrEmpty(unquoted)) { - AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsString, "", name); + AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsString, "", name.ToString()); } else { @@ -164,13 +164,18 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar { Debug.Assert(optionsEnded || !arg.StartsWith("@", StringComparison.Ordinal)); - string? name, value; - if (optionsEnded || !TryParseOption(arg, out name, out value)) + ArrayBuilder filePathBuilder; + ReadOnlyMemory nameMemory; + ReadOnlyMemory? valueMemory; + if (optionsEnded || !TryParseOption(arg, out nameMemory, out valueMemory)) { - foreach (var path in ParseFileArgument(arg, baseDirectory, diagnostics)) + filePathBuilder = ArrayBuilder.GetInstance(); + ParseFileArgument(arg.AsMemory(), baseDirectory, filePathBuilder, diagnostics); + foreach (var path in filePathBuilder) { sourceFiles.Add(ToCommandLineSourceFile(path)); } + filePathBuilder.Free(); if (sourceFiles.Count > 0) { @@ -180,6 +185,68 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; } + string? value; + string? valueMemoryString() => valueMemory is { } m ? m.Span.ToString() : null; + + // The main 'switch' for argument handling forces an allocation of the option name field. For the most + // common options we special case the handling below to avoid this allocation as it can contribute significantly + // to parsing allocations. + // + // When we allow for switching on Span this can be undone as the name 'switch' will be allocation free + // https://github.com/dotnet/roslyn/pull/44388 + if (IsOptionName("r", "reference", nameMemory)) + { + ParseAssemblyReferences(arg, valueMemory, diagnostics, embedInteropTypes: false, metadataReferences); + continue; + } + else if (IsOptionName("langversion", nameMemory)) + { + value = RemoveQuotesAndSlashes(valueMemory); + if (RoslynString.IsNullOrEmpty(value)) + { + AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsString, MessageID.IDS_Text.Localize(), "/langversion:"); + } + else if (value.StartsWith("0", StringComparison.Ordinal)) + { + // This error was added in 7.1 to stop parsing versions as ints (behaviour in previous Roslyn compilers), and explicitly + // treat them as identifiers (behaviour in native compiler). This error helps users identify that breaking change. + AddDiagnostic(diagnostics, ErrorCode.ERR_LanguageVersionCannotHaveLeadingZeroes, value); + } + else if (value == "?") + { + displayLangVersions = true; + } + else if (!LanguageVersionFacts.TryParse(value, out languageVersion)) + { + AddDiagnostic(diagnostics, ErrorCode.ERR_BadCompatMode, value); + } + continue; + } + else if (!IsScriptCommandLineParser && IsOptionName("a", "analyzer", nameMemory)) + { + ParseAnalyzers(arg, valueMemory, analyzers, diagnostics); + continue; + } + else if (!IsScriptCommandLineParser && IsOptionName("nowarn", nameMemory)) + { + if (valueMemory is null) + { + AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsNumber, nameMemory.ToString()); + continue; + } + + if (valueMemory.Value.Length == 0) + { + AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsNumber, nameMemory.ToString()); + } + else + { + AddWarnings(noWarns, ReportDiagnostic.Suppress, valueMemory.Value); + } + continue; + } + + string name = nameMemory.Span.ToString().ToLowerInvariant(); switch (name) { case "?": @@ -191,34 +258,8 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar displayVersion = true; continue; - case "langversion": - value = RemoveQuotesAndSlashes(value); - if (RoslynString.IsNullOrEmpty(value)) - { - AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsString, MessageID.IDS_Text.Localize(), "/langversion:"); - } - else if (value.StartsWith("0", StringComparison.Ordinal)) - { - // This error was added in 7.1 to stop parsing versions as ints (behaviour in previous Roslyn compilers), and explicitly - // treat them as identifiers (behaviour in native compiler). This error helps users identify that breaking change. - AddDiagnostic(diagnostics, ErrorCode.ERR_LanguageVersionCannotHaveLeadingZeroes, value); - } - else if (value == "?") - { - displayLangVersions = true; - } - else if (!LanguageVersionFacts.TryParse(value, out languageVersion)) - { - AddDiagnostic(diagnostics, ErrorCode.ERR_BadCompatMode, value); - } - continue; - - case "r": - case "reference": - metadataReferences.AddRange(ParseAssemblyReferences(arg, value, diagnostics, embedInteropTypes: false)); - continue; - case "features": + value = valueMemoryString(); if (value == null) { features.Clear(); @@ -232,7 +273,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar case "lib": case "libpath": case "libpaths": - ParseAndResolveReferencePaths(name, value, baseDirectory, libPaths, MessageID.IDS_LIB_OPTION, diagnostics); + ParseAndResolveReferencePaths(name, valueMemory, baseDirectory, libPaths, MessageID.IDS_LIB_OPTION, diagnostics); continue; #if DEBUG @@ -244,6 +285,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar if (IsScriptCommandLineParser) { + value = valueMemoryString(); switch (name) { case "-": // csi -- script.csx @@ -279,7 +321,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar case "loadpath": case "loadpaths": - ParseAndResolveReferencePaths(name, value, baseDirectory, sourcePaths, MessageID.IDS_REFERENCEPATH_OPTION, diagnostics); + ParseAndResolveReferencePaths(name, valueMemory, baseDirectory, sourcePaths, MessageID.IDS_REFERENCEPATH_OPTION, diagnostics); continue; case "u": @@ -295,26 +337,21 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar { switch (name) { - case "a": - case "analyzer": - analyzers.AddRange(ParseAnalyzers(arg, value, diagnostics)); - continue; - case "d": case "define": - if (RoslynString.IsNullOrEmpty(value)) + if (valueMemory is not { Length: > 0 }) { AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsString, "", arg); continue; } IEnumerable defineDiagnostics; - defines.AddRange(ParseConditionalCompilationSymbols(RemoveQuotesAndSlashes(value), out defineDiagnostics)); + ParseConditionalCompilationSymbols(RemoveQuotesAndSlashesEx(valueMemory.Value), defines, out defineDiagnostics); diagnostics.AddRange(defineDiagnostics); continue; case "codepage": - value = RemoveQuotesAndSlashes(value); + value = RemoveQuotesAndSlashes(valueMemory); if (value == null) { AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsString, "", name); @@ -332,13 +369,14 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; case "checksumalgorithm": - if (RoslynString.IsNullOrEmpty(value)) + value = valueMemoryString(); + if (string.IsNullOrEmpty(value)) { AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsString, "", name); continue; } - var newChecksumAlgorithm = TryParseHashAlgorithmName(value); + var newChecksumAlgorithm = TryParseHashAlgorithmName(value!); if (newChecksumAlgorithm == SourceHashAlgorithm.None) { AddDiagnostic(diagnostics, ErrorCode.FTL_BadChecksumAlgorithm, value); @@ -350,7 +388,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar case "checked": case "checked+": - if (value != null) + if (valueMemory is not null) { break; } @@ -359,7 +397,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; case "checked-": - if (value != null) + if (valueMemory is not null) break; checkOverflow = false; @@ -367,7 +405,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar case "nullable": - value = RemoveQuotesAndSlashes(value); + value = RemoveQuotesAndSlashes(valueMemory); if (value != null) { if (value.IsEmpty()) @@ -408,7 +446,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar case "nullable+": - if (value != null) + if (valueMemory is not null) { break; } @@ -417,14 +455,14 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; case "nullable-": - if (value != null) + if (valueMemory is not null) break; nullableContextOptions = NullableContextOptions.Disable; continue; case "instrument": - value = RemoveQuotesAndSlashes(value); + value = RemoveQuotesAndSlashes(valueMemory); if (RoslynString.IsNullOrEmpty(value)) { AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsString, "", name); @@ -448,7 +486,8 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar case "sqmsessionguid": // The use of SQM is deprecated in the compiler but we still support the parsing of the option for - // back compat reasons. + // back compat reason + value = valueMemoryString(); if (value == null) { AddDiagnostic(diagnostics, ErrorCode.ERR_MissingGuidForOption, "", name); @@ -464,7 +503,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; case "preferreduilang": - value = RemoveQuotesAndSlashes(value); + value = RemoveQuotesAndSlashes(valueMemory); if (RoslynString.IsNullOrEmpty(value)) { @@ -498,6 +537,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; case "out": + value = valueMemoryString(); if (RoslynString.IsNullOrWhiteSpace(value)) { AddDiagnostic(diagnostics, ErrorCode.ERR_NoFileSpec, arg); @@ -510,7 +550,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; case "refout": - value = RemoveQuotesAndSlashes(value); + value = RemoveQuotesAndSlashes(valueMemory); if (RoslynString.IsNullOrEmpty(value)) { AddDiagnostic(diagnostics, ErrorCode.ERR_NoFileSpec, arg); @@ -523,7 +563,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; case "refonly": - if (value != null) + if (valueMemory is not null) break; refOnly = true; @@ -531,6 +571,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar case "t": case "target": + value = valueMemoryString(); if (value == null) { break; // force 'unrecognized option' @@ -548,6 +589,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; case "moduleassemblyname": + value = valueMemoryString(); value = value != null ? value.Unquote() : null; if (RoslynString.IsNullOrEmpty(value)) @@ -567,7 +609,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; case "modulename": - var unquotedModuleName = RemoveQuotesAndSlashes(value); + var unquotedModuleName = RemoveQuotesAndSlashes(valueMemory); if (string.IsNullOrEmpty(unquotedModuleName)) { AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsString, MessageID.IDS_Text.Localize(), "modulename"); @@ -581,7 +623,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; case "platform": - value = RemoveQuotesAndSlashes(value); + value = RemoveQuotesAndSlashes(valueMemory); if (RoslynString.IsNullOrEmpty(value)) { AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsString, "", arg); @@ -593,7 +635,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; case "recurse": - value = RemoveQuotesAndSlashes(value); + value = RemoveQuotesAndSlashes(valueMemory); if (value == null) { @@ -615,7 +657,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; case "generatedfilesout": - value = RemoveQuotesAndSlashes(value); + value = RemoveQuotesAndSlashes(valueMemory); if (string.IsNullOrWhiteSpace(value)) { AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsString, MessageID.IDS_Text.Localize(), arg); @@ -628,12 +670,13 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar case "doc": parseDocumentationComments = true; + value = valueMemoryString(); if (RoslynString.IsNullOrEmpty(value)) { AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsString, MessageID.IDS_Text.Localize(), arg); continue; } - string? unquoted = RemoveQuotesAndSlashes(value); + string? unquoted = RemoveQuotesAndSlashes(valueMemory); if (RoslynString.IsNullOrEmpty(unquoted)) { // CONSIDER: This diagnostic exactly matches dev11, but it would be simpler (and more consistent with /out) @@ -647,11 +690,12 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; case "addmodule": + value = valueMemoryString(); if (value == null) { AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsString, MessageID.IDS_Text.Localize(), "/addmodule:"); } - else if (string.IsNullOrEmpty(value)) + else if (value.Length == 0) { AddDiagnostic(diagnostics, ErrorCode.ERR_NoFileSpec, arg); } @@ -667,19 +711,19 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar case "l": case "link": - metadataReferences.AddRange(ParseAssemblyReferences(arg, value, diagnostics, embedInteropTypes: true)); + ParseAssemblyReferences(arg, valueMemory, diagnostics, embedInteropTypes: true, metadataReferences); continue; case "win32res": - win32ResourceFile = GetWin32Setting(arg, value, diagnostics); + win32ResourceFile = GetWin32Setting(arg, valueMemoryString(), diagnostics); continue; case "win32icon": - win32IconFile = GetWin32Setting(arg, value, diagnostics); + win32IconFile = GetWin32Setting(arg, valueMemoryString(), diagnostics); continue; case "win32manifest": - win32ManifestFile = GetWin32Setting(arg, value, diagnostics); + win32ManifestFile = GetWin32Setting(arg, valueMemoryString(), diagnostics); noWin32Manifest = false; continue; @@ -690,12 +734,12 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar case "res": case "resource": - if (value == null) + if (valueMemory is null) { break; // Dev11 reports unrecognized option } - var embeddedResource = ParseResourceDescription(arg, value, baseDirectory, diagnostics, embedded: true); + var embeddedResource = ParseResourceDescription(arg, valueMemory.Value, baseDirectory, diagnostics, embedded: true); if (embeddedResource != null) { managedResources.Add(embeddedResource); @@ -706,12 +750,12 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar case "linkres": case "linkresource": - if (value == null) + if (valueMemory is null) { break; // Dev11 reports unrecognized option } - var linkedResource = ParseResourceDescription(arg, value, baseDirectory, diagnostics, embedded: false); + var linkedResource = ParseResourceDescription(arg, valueMemory.Value, baseDirectory, diagnostics, embedded: false); if (linkedResource != null) { managedResources.Add(linkedResource); @@ -721,7 +765,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; case "sourcelink": - value = RemoveQuotesAndSlashes(value); + value = RemoveQuotesAndSlashes(valueMemory); if (RoslynString.IsNullOrEmpty(value)) { AddDiagnostic(diagnostics, ErrorCode.ERR_NoFileSpec, arg); @@ -736,7 +780,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar emitPdb = true; // unused, parsed for backward compat only - value = RemoveQuotesAndSlashes(value); + value = RemoveQuotesAndSlashes(valueMemory); if (value != null) { if (value.IsEmpty()) @@ -765,7 +809,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar case "debug+": //guard against "debug+:xx" - if (value != null) + if (valueMemory is not null) break; emitPdb = true; @@ -773,7 +817,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; case "debug-": - if (value != null) + if (valueMemory is not null) break; emitPdb = false; @@ -784,7 +828,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar case "optimize": case "o+": case "optimize+": - if (value != null) + if (valueMemory is not null) break; optimize = true; @@ -792,7 +836,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar case "o-": case "optimize-": - if (value != null) + if (valueMemory is not null) break; optimize = false; @@ -800,14 +844,14 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar case "deterministic": case "deterministic+": - if (value != null) + if (valueMemory is not null) break; deterministic = true; continue; case "deterministic-": - if (value != null) + if (valueMemory is not null) break; deterministic = false; continue; @@ -816,7 +860,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar case "parallel": case "p+": case "parallel+": - if (value != null) + if (valueMemory is not null) break; concurrentBuild = true; @@ -824,7 +868,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar case "p-": case "parallel-": - if (value != null) + if (valueMemory is not null) break; concurrentBuild = false; @@ -832,7 +876,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar case "warnaserror": case "warnaserror+": - if (value == null) + if (valueMemory is null) { generalDiagnosticOption = ReportDiagnostic.Error; @@ -850,18 +894,18 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; } - if (string.IsNullOrEmpty(value)) + if (valueMemory.Value.Length == 0) { AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsNumber, name); } else { - AddWarnings(warnAsErrors, ReportDiagnostic.Error, ParseWarnings(value)); + AddWarnings(warnAsErrors, ReportDiagnostic.Error, valueMemory.Value); } continue; case "warnaserror-": - if (value == null) + if (valueMemory is null) { generalDiagnosticOption = ReportDiagnostic.Default; @@ -871,13 +915,15 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; } - if (string.IsNullOrEmpty(value)) + if (valueMemory is not { Length: > 0 }) { AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsNumber, name); } else { - foreach (var id in ParseWarnings(value)) + var builder = ArrayBuilder.GetInstance(); + ParseWarnings(valueMemory.Value, builder); + foreach (var id in builder) { ReportDiagnostic ruleSetValue; if (diagnosticOptions.TryGetValue(id, out ruleSetValue)) @@ -889,12 +935,13 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar warnAsErrors[id] = ReportDiagnostic.Default; } } + builder.Free(); } continue; case "w": case "warn": - value = RemoveQuotesAndSlashes(value); + value = RemoveQuotesAndSlashes(valueMemory); if (value == null) { AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsNumber, name); @@ -917,33 +964,16 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar } continue; - case "nowarn": - if (value == null) - { - AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsNumber, name); - continue; - } - - if (string.IsNullOrEmpty(value)) - { - AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsNumber, name); - } - else - { - AddWarnings(noWarns, ReportDiagnostic.Suppress, ParseWarnings(value)); - } - continue; - case "unsafe": case "unsafe+": - if (value != null) + if (valueMemory is not null) break; allowUnsafe = true; continue; case "unsafe-": - if (value != null) + if (valueMemory is not null) break; allowUnsafe = false; @@ -951,7 +981,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar case "delaysign": case "delaysign+": - if (value != null) + if (valueMemory is not null) { break; } @@ -960,7 +990,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; case "delaysign-": - if (value != null) + if (valueMemory is not null) { break; } @@ -970,7 +1000,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar case "publicsign": case "publicsign+": - if (value != null) + if (valueMemory is not null) { break; } @@ -979,7 +1009,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; case "publicsign-": - if (value != null) + if (valueMemory is not null) { break; } @@ -988,7 +1018,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; case "keyfile": - value = RemoveQuotesAndSlashes(value); + value = RemoveQuotesAndSlashes(valueMemory); if (string.IsNullOrEmpty(value)) { AddDiagnostic(diagnostics, ErrorCode.ERR_NoFileSpec, "keyfile"); @@ -1009,6 +1039,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; case "keycontainer": + value = valueMemoryString(); if (string.IsNullOrEmpty(value)) { AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsString, MessageID.IDS_Text.Localize(), "keycontainer"); @@ -1030,14 +1061,14 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar case "highentropyva": case "highentropyva+": - if (value != null) + if (valueMemory is not null) break; highEntropyVA = true; continue; case "highentropyva-": - if (value != null) + if (valueMemory is not null) break; highEntropyVA = false; @@ -1048,7 +1079,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; case "baseaddress": - value = RemoveQuotesAndSlashes(value); + value = RemoveQuotesAndSlashes(valueMemory); ulong newBaseAddress; if (string.IsNullOrEmpty(value) || !TryParseUInt64(value, out newBaseAddress)) @@ -1070,6 +1101,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; case "subsystemversion": + value = valueMemoryString(); if (RoslynString.IsNullOrEmpty(value)) { AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsString, MessageID.IDS_Text.Localize(), "subsystemversion"); @@ -1090,7 +1122,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; case "touchedfiles": - unquoted = RemoveQuotesAndSlashes(value); + unquoted = RemoveQuotesAndSlashes(valueMemory); if (string.IsNullOrEmpty(unquoted)) { AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsString, MessageID.IDS_Text.Localize(), "touchedfiles"); @@ -1108,7 +1140,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; case "utf8output": - if (value != null) + if (valueMemory is not null) break; utf8output = true; @@ -1118,7 +1150,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar case "main": // Remove any quotes for consistent behavior as MSBuild can return quoted or // unquoted main. - unquoted = RemoveQuotesAndSlashes(value); + unquoted = RemoveQuotesAndSlashes(valueMemory); if (string.IsNullOrEmpty(unquoted)) { AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsString, "", name); @@ -1129,7 +1161,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; case "fullpaths": - if (value != null) + if (valueMemory is not null) break; printFullPaths = true; @@ -1138,7 +1170,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar case "pathmap": // "/pathmap:K1=V1,K2=V2..." { - unquoted = RemoveQuotesAndSlashes(value); + unquoted = RemoveQuotesAndSlashes(valueMemory); if (unquoted == null) { @@ -1150,7 +1182,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; case "filealign": - value = RemoveQuotesAndSlashes(value); + value = RemoveQuotesAndSlashes(valueMemory); ushort newAlignment; if (RoslynString.IsNullOrEmpty(value)) @@ -1172,7 +1204,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; case "pdb": - value = RemoveQuotesAndSlashes(value); + value = RemoveQuotesAndSlashes(valueMemory); if (RoslynString.IsNullOrEmpty(value)) { AddDiagnostic(diagnostics, ErrorCode.ERR_NoFileSpec, arg); @@ -1193,14 +1225,14 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar case "skipanalyzers": case "skipanalyzers+": - if (value != null) + if (valueMemory is not null) break; skipAnalyzers = true; continue; case "skipanalyzers-": - if (value != null) + if (valueMemory is not null) break; skipAnalyzers = false; @@ -1208,14 +1240,14 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar case "nostdlib": case "nostdlib+": - if (value != null) + if (valueMemory is not null) break; noStdLib = true; continue; case "nostdlib-": - if (value != null) + if (valueMemory is not null) break; noStdLib = false; @@ -1225,23 +1257,23 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; case "errorlog": - unquoted = RemoveQuotesAndSlashes(value); - if (RoslynString.IsNullOrEmpty(unquoted)) + valueMemory = RemoveQuotesAndSlashesEx(valueMemory); + if (valueMemory is not { Length: > 0 }) { AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsString, ErrorLogOptionFormat, RemoveQuotesAndSlashes(arg)); } else { - errorLogOptions = ParseErrorLogOptions(unquoted, diagnostics, baseDirectory, out bool diagnosticAlreadyReported); + errorLogOptions = ParseErrorLogOptions(valueMemory.Value, diagnostics, baseDirectory, out bool diagnosticAlreadyReported); if (errorLogOptions == null && !diagnosticAlreadyReported) { - AddDiagnostic(diagnostics, ErrorCode.ERR_BadSwitchValue, unquoted, "/errorlog:", ErrorLogOptionFormat); + AddDiagnostic(diagnostics, ErrorCode.ERR_BadSwitchValue, valueMemory.Value.ToString(), "/errorlog:", ErrorLogOptionFormat); } } continue; case "appconfig": - unquoted = RemoveQuotesAndSlashes(value); + unquoted = RemoveQuotesAndSlashes(valueMemory); if (RoslynString.IsNullOrEmpty(unquoted)) { AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsString, ":", RemoveQuotesAndSlashes(arg)); @@ -1253,7 +1285,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; case "runtimemetadataversion": - unquoted = RemoveQuotesAndSlashes(value); + unquoted = RemoveQuotesAndSlashes(valueMemory); if (string.IsNullOrEmpty(unquoted)) { AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsString, "", name); @@ -1268,39 +1300,48 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar continue; case "additionalfile": - if (RoslynString.IsNullOrEmpty(value)) + if (valueMemory is not { Length: > 0 }) { AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsString, "", name); continue; } - foreach (var path in ParseSeparatedFileArgument(value, baseDirectory, diagnostics)) + filePathBuilder = ArrayBuilder.GetInstance(); + ParseSeparatedFileArgument(valueMemory.Value, baseDirectory, filePathBuilder, diagnostics); + foreach (var path in filePathBuilder) { additionalFiles.Add(ToCommandLineSourceFile(path)); } + filePathBuilder.Free(); continue; - case "analyzerconfig": - if (RoslynString.IsNullOrEmpty(value)) + if (valueMemory is not { Length: > 0 }) { AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsString, "", name); continue; } - analyzerConfigPaths.AddRange(ParseSeparatedFileArgument(value, baseDirectory, diagnostics)); + filePathBuilder = ArrayBuilder.GetInstance(); + ParseSeparatedFileArgument(valueMemory.Value, baseDirectory, filePathBuilder, diagnostics); + analyzerConfigPaths.AddRange(filePathBuilder); + filePathBuilder.Free(); continue; case "embed": + value = valueMemoryString(); if (RoslynString.IsNullOrEmpty(value)) { embedAllSourceFiles = true; continue; } - foreach (var path in ParseSeparatedFileArgument(value, baseDirectory, diagnostics)) + filePathBuilder = ArrayBuilder.GetInstance(); + ParseSeparatedFileArgument(value.AsMemory(), baseDirectory, filePathBuilder, diagnostics); + foreach (var path in filePathBuilder) { embeddedFiles.Add(ToCommandLineSourceFile(path)); } + filePathBuilder.Free(); continue; case "-": @@ -1363,7 +1404,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar // add additional reference paths if specified if (!string.IsNullOrEmpty(additionalReferenceDirectories)) { - ParseAndResolveReferencePaths(null, additionalReferenceDirectories, baseDirectory, libPaths, MessageID.IDS_LIB_ENV, diagnostics); + ParseAndResolveReferencePaths(null, additionalReferenceDirectories.AsMemory(), baseDirectory, libPaths, MessageID.IDS_LIB_ENV, diagnostics); } ImmutableArray referencePaths = BuildSearchPaths(sdkDirectory, libPaths, responsePaths); @@ -1412,6 +1453,8 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar string? compilationName; GetCompilationAndModuleNames(diagnostics, outputKind, sourceFiles, sourceFilesSpecified, moduleAssemblyName, ref outputFileName, ref moduleName, out compilationName); + flattenedArgs.Free(); + var parseOptions = new CSharpParseOptions ( languageVersion: languageVersion, @@ -1538,16 +1581,16 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar }; } - private static void ParseAndResolveReferencePaths(string? switchName, string? switchValue, string? baseDirectory, List builder, MessageID origin, List diagnostics) + private static void ParseAndResolveReferencePaths(string? switchName, ReadOnlyMemory? switchValue, string? baseDirectory, List builder, MessageID origin, List diagnostics) { - if (string.IsNullOrEmpty(switchValue)) + if (switchValue is not { Length: > 0 }) { RoslynDebug.Assert(!RoslynString.IsNullOrEmpty(switchName)); AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsString, MessageID.IDS_PathList.Localize(), switchName); return; } - foreach (string path in ParseSeparatedPaths(switchValue)) + foreach (string path in ParseSeparatedPaths(switchValue.Value.ToString())) { string? resolvedPath = FileUtilities.ResolveRelativePath(path, baseDirectory); if (resolvedPath == null) @@ -1688,35 +1731,55 @@ private ImmutableArray BuildSearchPaths(string? sdkDirectoryOpt, List ParseConditionalCompilationSymbols(string value, out IEnumerable diagnostics) + { + var builder = ArrayBuilder.GetInstance(); + ParseConditionalCompilationSymbols(value.AsMemory(), builder, out diagnostics); + return builder.ToArrayAndFree(); + } + + internal static void ParseConditionalCompilationSymbols(ReadOnlyMemory valueMemory, ArrayBuilder defines, out IEnumerable diagnostics) { DiagnosticBag outputDiagnostics = DiagnosticBag.GetInstance(); - value = value.TrimEnd(null); - // Allow a trailing semicolon or comma in the options - if (!value.IsEmpty() && - (value.Last() == ';' || value.Last() == ',')) + if (valueMemory.IsWhiteSpace()) { - value = value.Substring(0, value.Length - 1); + outputDiagnostics.Add(Diagnostic.Create(CSharp.MessageProvider.Instance, (int)ErrorCode.WRN_DefineIdentifierRequired, valueMemory.ToString())); + diagnostics = outputDiagnostics.ToReadOnlyAndFree(); + return; } - string[] values = value.Split(new char[] { ';', ',' } /*, StringSplitOptions.RemoveEmptyEntries*/); - var defines = new ArrayBuilder(values.Length); + var valueSpan = valueMemory.Span; + var nextIndex = 0; + var index = 0; + while (index < valueSpan.Length) + { + if (valueSpan[index] is ';' or ',') + { + add(); + nextIndex = index + 1; + } + index++; + } - foreach (string id in values) + if (nextIndex < valueSpan.Length) { - string trimmedId = id.Trim(); - if (SyntaxFacts.IsValidIdentifier(trimmedId)) + add(); + } + + void add() + { + var id = valueMemory.Slice(nextIndex, index - nextIndex).Trim().ToString(); + if (SyntaxFacts.IsValidIdentifier(id)) { - defines.Add(trimmedId); + defines.Add(id); } else { - outputDiagnostics.Add(Diagnostic.Create(CSharp.MessageProvider.Instance, (int)ErrorCode.WRN_DefineIdentifierRequired, trimmedId)); + outputDiagnostics.Add(Diagnostic.Create(CSharp.MessageProvider.Instance, (int)ErrorCode.WRN_DefineIdentifierRequired, id)); } } diagnostics = outputDiagnostics.ToReadOnlyAndFree(); - return defines.AsEnumerable(); } private static Platform ParsePlatform(string value, IList diagnostics) @@ -1785,38 +1848,46 @@ private static IEnumerable ParseUsings(string arg, string? value, IList< } } - private static IEnumerable ParseAnalyzers(string arg, string? value, List diagnostics) + private static void ParseAnalyzers(string arg, ReadOnlyMemory? valueMemory, List analyzerReferences, List diagnostics) { - if (value == null) + if (valueMemory is not { } value) { AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsString, MessageID.IDS_Text.Localize(), arg); - yield break; + return; } else if (value.Length == 0) { AddDiagnostic(diagnostics, ErrorCode.ERR_NoFileSpec, arg); - yield break; + return; } - List paths = ParseSeparatedPaths(value).Where((path) => !string.IsNullOrWhiteSpace(path)).ToList(); - - foreach (string path in paths) + var builder = ArrayBuilder>.GetInstance(); + ParseSeparatedPathsEx(value, builder); + foreach (var path in builder) { - yield return new CommandLineAnalyzerReference(path); + if (path.Length == 0) + { + continue; + } + + analyzerReferences.Add(new CommandLineAnalyzerReference(path.ToString())); } + builder.Free(); } - private static IEnumerable ParseAssemblyReferences(string arg, string? value, IList diagnostics, bool embedInteropTypes) + private static void ParseAssemblyReferences(string arg, ReadOnlyMemory? valueMemory, IList diagnostics, bool embedInteropTypes, List commandLineReferences) { - if (value == null) + if (valueMemory is null) { AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsString, MessageID.IDS_Text.Localize(), arg); - yield break; + return; } - else if (value.Length == 0) + + var value = valueMemory.Value; + if (value.Length == 0) { AddDiagnostic(diagnostics, ErrorCode.ERR_NoFileSpec, arg); - yield break; + return; } // /r:"reference" @@ -1829,18 +1900,19 @@ private static IEnumerable ParseAssemblyReferences(string // /r:alias=reference;reference ... error 2034 // /r:nonidf=reference ... error 1679 - int eqlOrQuote = value.IndexOfAny(s_quoteOrEquals); + var valueSpan = value.Span; + int eqlOrQuote = valueSpan.IndexOfAny(s_quoteOrEquals); string? alias; - if (eqlOrQuote >= 0 && value[eqlOrQuote] == '=') + if (eqlOrQuote >= 0 && valueSpan[eqlOrQuote] == '=') { - alias = value.Substring(0, eqlOrQuote); - value = value.Substring(eqlOrQuote + 1); + alias = value.Slice(0, eqlOrQuote).ToString(); + value = value.Slice(eqlOrQuote + 1); if (!SyntaxFacts.IsValidIdentifier(alias)) { AddDiagnostic(diagnostics, ErrorCode.ERR_BadExternIdentifier, alias); - yield break; + return; } } else @@ -1848,31 +1920,42 @@ private static IEnumerable ParseAssemblyReferences(string alias = null; } - List paths = ParseSeparatedPaths(value).Where((path) => !string.IsNullOrWhiteSpace(path)).ToList(); - if (alias != null) + var builder = ArrayBuilder>.GetInstance(); + ParseSeparatedPathsEx(value, builder); + var pathCount = 0; + foreach (var path in builder) { - if (paths.Count > 1) + if (path.IsWhiteSpace()) { - AddDiagnostic(diagnostics, ErrorCode.ERR_OneAliasPerReference, value); - yield break; + continue; } - if (paths.Count == 0) - { - AddDiagnostic(diagnostics, ErrorCode.ERR_AliasMissingFile, alias); - yield break; - } - } + pathCount++; - foreach (string path in paths) - { // NOTE(tomat): Dev10 used to report CS1541: ERR_CantIncludeDirectory if the path was a directory. // Since we now support /referencePaths option we would need to search them to see if the resolved path is a directory. var aliases = (alias != null) ? ImmutableArray.Create(alias) : ImmutableArray.Empty; var properties = new MetadataReferenceProperties(MetadataImageKind.Assembly, aliases, embedInteropTypes); - yield return new CommandLineReference(path, properties); + commandLineReferences.Add(new CommandLineReference(path.ToString(), properties)); + } + builder.Free(); + + if (alias != null) + { + if (pathCount > 1) + { + commandLineReferences.RemoveRange(commandLineReferences.Count - pathCount, pathCount); + AddDiagnostic(diagnostics, ErrorCode.ERR_OneAliasPerReference, value.ToString()); + return; + } + + if (pathCount == 0) + { + AddDiagnostic(diagnostics, ErrorCode.ERR_AliasMissingFile, alias); + return; + } } } @@ -1920,6 +2003,14 @@ private static IEnumerable ParseInstrumentationKinds(string string resourceDescriptor, string? baseDirectory, IList diagnostics, + bool embedded) => + ParseResourceDescription(arg, resourceDescriptor.AsMemory(), baseDirectory, diagnostics, embedded); + + internal static ResourceDescription? ParseResourceDescription( + string arg, + ReadOnlyMemory resourceDescriptor, + string? baseDirectory, + IList diagnostics, bool embedded) { string? filePath; @@ -1981,27 +2072,33 @@ private static IEnumerable ParseInstrumentationKinds(string return new ResourceDescription(resourceName, fileName, dataProvider, isPublic, embedded, checkArgs: false); } - private static IEnumerable ParseWarnings(string value) + private static void ParseWarnings(ReadOnlyMemory value, ArrayBuilder ids) { value = value.Unquote(); - string[] values = value.Split(new char[] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries); - foreach (string id in values) + var parts = ArrayBuilder>.GetInstance(); + + var nullableSpan = "nullable".AsSpan(); + ParseSeparatedStrings(value, s_warningSeparators, removeEmptyEntries: true, parts); + foreach (ReadOnlyMemory part in parts) { - if (string.Equals(id, "nullable", StringComparison.OrdinalIgnoreCase)) + if (part.Span.Equals(nullableSpan, StringComparison.OrdinalIgnoreCase)) { foreach (var errorCode in ErrorFacts.NullableWarnings) { - yield return errorCode; + ids.Add(errorCode); } - yield return CSharp.MessageProvider.Instance.GetIdForErrorCode((int)ErrorCode.WRN_MissingNonNullTypesContextForAnnotation); - yield return CSharp.MessageProvider.Instance.GetIdForErrorCode((int)ErrorCode.WRN_MissingNonNullTypesContextForAnnotationInGeneratedCode); + ids.Add(CSharp.MessageProvider.Instance.GetIdForErrorCode((int)ErrorCode.WRN_MissingNonNullTypesContextForAnnotation)); + ids.Add(CSharp.MessageProvider.Instance.GetIdForErrorCode((int)ErrorCode.WRN_MissingNonNullTypesContextForAnnotationInGeneratedCode)); + continue; } - else if (ushort.TryParse(id, NumberStyles.Integer, CultureInfo.InvariantCulture, out ushort number) && + + var id = part.ToString(); + if (ushort.TryParse(id, NumberStyles.Integer, CultureInfo.InvariantCulture, out ushort number) && ErrorFacts.IsWarning((ErrorCode)number)) { // The id refers to a compiler warning. - yield return CSharp.MessageProvider.Instance.GetIdForErrorCode(number); + ids.Add(CSharp.MessageProvider.Instance.GetIdForErrorCode(number)); } else { @@ -2009,14 +2106,18 @@ private static IEnumerable ParseWarnings(string value) // whenever an unrecognized warning code was supplied in /nowarn or // /warnaserror. We no longer generate a warning in such cases. // Instead we assume that the unrecognized id refers to a custom diagnostic. - yield return id; + ids.Add(id); } } + + parts.Free(); } - private static void AddWarnings(Dictionary d, ReportDiagnostic kind, IEnumerable items) + private static void AddWarnings(Dictionary d, ReportDiagnostic kind, ReadOnlyMemory warningArgument) { - foreach (var id in items) + var idsBuilder = ArrayBuilder.GetInstance(); + ParseWarnings(warningArgument, idsBuilder); + foreach (var id in idsBuilder) { ReportDiagnostic existing; if (d.TryGetValue(id, out existing)) @@ -2030,6 +2131,8 @@ private static void AddWarnings(Dictionary d, ReportDi d.Add(id, kind); } } + + idsBuilder.Free(); } private static void UnimplementedSwitch(IList diagnostics, string switchName) diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 38d21fdfa5b58..edd35b8bcf802 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -1980,7 +1980,8 @@ internal enum ErrorCode ERR_MultipleFileScopedNamespace = 8954, ERR_FileScopedAndNormalNamespace = 8955, ERR_FileScopedNamespaceNotBeforeAllMembers = 8956, - ERR_AttrTypeArgCannotBeTypeVar = 8957, + ERR_NoImplicitConvTargetTypedConditional = 8957, + ERR_AttrTypeArgCannotBeTypeVar = 8958, #endregion diff --git a/src/Compilers/CSharp/Portable/Errors/MessageID.cs b/src/Compilers/CSharp/Portable/Errors/MessageID.cs index b3273752e7006..000b5ebaddde1 100644 --- a/src/Compilers/CSharp/Portable/Errors/MessageID.cs +++ b/src/Compilers/CSharp/Portable/Errors/MessageID.cs @@ -229,7 +229,7 @@ internal enum MessageID IDS_FeatureStaticAbstractMembersInInterfaces = MessageBase + 12803, IDS_FeatureLambdaReturnType = MessageBase + 12804, IDS_AsyncMethodBuilderOverride = MessageBase + 12805, - IDS_FeatureImplicitImplementationOfNonPublicMemebers = MessageBase + 12806, + IDS_FeatureImplicitImplementationOfNonPublicMembers = MessageBase + 12806, IDS_FeatureLineSpanDirective = MessageBase + 12807, IDS_FeatureImprovedInterpolatedStrings = MessageBase + 12808, IDS_FeatureFileScopedNamespace = MessageBase + 12809, @@ -359,7 +359,7 @@ internal static LanguageVersion RequiredVersion(this MessageID feature) case MessageID.IDS_FeatureLambdaReturnType: // semantic check case MessageID.IDS_AsyncMethodBuilderOverride: // semantic check case MessageID.IDS_FeatureConstantInterpolatedStrings: // semantic check - case MessageID.IDS_FeatureImplicitImplementationOfNonPublicMemebers: // semantic check + case MessageID.IDS_FeatureImplicitImplementationOfNonPublicMembers: // semantic check case MessageID.IDS_FeatureLineSpanDirective: case MessageID.IDS_FeatureFileScopedNamespace: // syntax check case MessageID.IDS_FeatureGenericAttributes: // semantic check diff --git a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs index f90f3fe486e9c..95c1c0d50f5f7 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs @@ -1039,6 +1039,17 @@ public BoundLiteral StringLiteral(String stringValue) return StringLiteral(ConstantValue.Create(stringValue)); } + public BoundLiteral CharLiteral(ConstantValue charConst) + { + Debug.Assert(charConst.IsChar || charConst.IsDefaultValue); + return new BoundLiteral(Syntax, charConst, SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Char)) { WasCompilerGenerated = true }; + } + + public BoundLiteral CharLiteral(Char charValue) + { + return CharLiteral(ConstantValue.Create(charValue)); + } + public BoundArrayLength ArrayLength(BoundExpression array) { Debug.Assert(array.Type is { TypeKind: TypeKind.Array }); diff --git a/src/Compilers/CSharp/Portable/Parser/Lexer.cs b/src/Compilers/CSharp/Portable/Parser/Lexer.cs index 42af25f897c20..353f8ffda388b 100644 --- a/src/Compilers/CSharp/Portable/Parser/Lexer.cs +++ b/src/Compilers/CSharp/Portable/Parser/Lexer.cs @@ -440,7 +440,7 @@ private void ScanSyntaxToken(ref TokenInfo info) { case '\"': case '\'': - this.ScanStringLiteral(ref info); + this.ScanStringLiteral(ref info, inDirective: false); break; case '/': @@ -2907,7 +2907,7 @@ private bool ScanDirectiveToken(ref TokenInfo info) break; case '\"': - this.ScanStringLiteral(ref info, false); + this.ScanStringLiteral(ref info, inDirective: true); break; case '\\': diff --git a/src/Compilers/CSharp/Portable/Parser/Lexer_StringLiteral.cs b/src/Compilers/CSharp/Portable/Parser/Lexer_StringLiteral.cs index da04733e21f80..e7a04e322aaae 100644 --- a/src/Compilers/CSharp/Portable/Parser/Lexer_StringLiteral.cs +++ b/src/Compilers/CSharp/Portable/Parser/Lexer_StringLiteral.cs @@ -17,86 +17,82 @@ namespace Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax { internal partial class Lexer { - private void ScanStringLiteral(ref TokenInfo info, bool allowEscapes = true) + private void ScanStringLiteral(ref TokenInfo info, bool inDirective) { var quoteCharacter = TextWindow.PeekChar(); - if (quoteCharacter == '\'' || quoteCharacter == '"') + Debug.Assert(quoteCharacter == '\'' || quoteCharacter == '"'); + + TextWindow.AdvanceChar(); + _builder.Length = 0; + + while (true) { - TextWindow.AdvanceChar(); - _builder.Length = 0; - while (true) + char ch = TextWindow.PeekChar(); + + // Normal string & char constants can have escapes. Strings in directives cannot. + if (ch == '\\' && !inDirective) { - char ch = TextWindow.PeekChar(); - if (ch == '\\' && allowEscapes) - { - // normal string & char constants can have escapes - char c2; - ch = this.ScanEscapeSequence(out c2); - _builder.Append(ch); - if (c2 != SlidingTextWindow.InvalidCharacter) - { - _builder.Append(c2); - } - } - else if (ch == quoteCharacter) - { - TextWindow.AdvanceChar(); - break; - } - else if (SyntaxFacts.IsNewLine(ch) || - (ch == SlidingTextWindow.InvalidCharacter && TextWindow.IsReallyAtEnd())) - { - //String and character literals can contain any Unicode character. They are not limited - //to valid UTF-16 characters. So if we get the SlidingTextWindow's sentinel value, - //double check that it was not real user-code contents. This will be rare. - Debug.Assert(TextWindow.Width > 0); - this.AddError(ErrorCode.ERR_NewlineInConst); - break; - } - else + char c2; + ch = this.ScanEscapeSequence(out c2); + _builder.Append(ch); + if (c2 != SlidingTextWindow.InvalidCharacter) { - TextWindow.AdvanceChar(); - _builder.Append(ch); + _builder.Append(c2); } } + else if (ch == quoteCharacter) + { + TextWindow.AdvanceChar(); + break; + } + else if (SyntaxFacts.IsNewLine(ch) || + (ch == SlidingTextWindow.InvalidCharacter && TextWindow.IsReallyAtEnd())) + { + //String and character literals can contain any Unicode character. They are not limited + //to valid UTF-16 characters. So if we get the SlidingTextWindow's sentinel value, + //double check that it was not real user-code contents. This will be rare. + Debug.Assert(TextWindow.Width > 0); + this.AddError(ErrorCode.ERR_NewlineInConst); + break; + } + else + { + TextWindow.AdvanceChar(); + _builder.Append(ch); + } + } - info.Text = TextWindow.GetText(true); - if (quoteCharacter == '\'') + info.Text = TextWindow.GetText(true); + if (quoteCharacter == '\'') + { + info.Kind = SyntaxKind.CharacterLiteralToken; + if (_builder.Length != 1) { - info.Kind = SyntaxKind.CharacterLiteralToken; - if (_builder.Length != 1) - { - this.AddError((_builder.Length != 0) ? ErrorCode.ERR_TooManyCharsInConst : ErrorCode.ERR_EmptyCharConst); - } + this.AddError((_builder.Length != 0) ? ErrorCode.ERR_TooManyCharsInConst : ErrorCode.ERR_EmptyCharConst); + } - if (_builder.Length > 0) - { - info.StringValue = TextWindow.Intern(_builder); - info.CharValue = info.StringValue[0]; - } - else - { - info.StringValue = string.Empty; - info.CharValue = SlidingTextWindow.InvalidCharacter; - } + if (_builder.Length > 0) + { + info.StringValue = TextWindow.Intern(_builder); + info.CharValue = info.StringValue[0]; } else { - info.Kind = SyntaxKind.StringLiteralToken; - if (_builder.Length > 0) - { - info.StringValue = TextWindow.Intern(_builder); - } - else - { - info.StringValue = string.Empty; - } + info.StringValue = string.Empty; + info.CharValue = SlidingTextWindow.InvalidCharacter; } } else { - info.Kind = SyntaxKind.None; - info.Text = null; + info.Kind = SyntaxKind.StringLiteralToken; + if (_builder.Length > 0) + { + info.StringValue = TextWindow.Intern(_builder); + } + else + { + info.StringValue = string.Empty; + } } } @@ -294,60 +290,61 @@ internal static SyntaxToken RescanInterpolatedString(InterpolatedStringExpressio private class InterpolatedStringScanner { - public readonly Lexer lexer; - public bool isVerbatim; - public bool allowNewlines; + private readonly Lexer _lexer; + private bool _isVerbatim; + private bool _allowNewlines; public SyntaxDiagnosticInfo error; + public InterpolatedStringScanner( Lexer lexer, bool isVerbatim) { - this.lexer = lexer; - this.isVerbatim = isVerbatim; - this.allowNewlines = isVerbatim; + this._lexer = lexer; + this._isVerbatim = isVerbatim; + this._allowNewlines = isVerbatim; } private bool IsAtEnd() { - return IsAtEnd(isVerbatim && allowNewlines); + return IsAtEnd(_isVerbatim && _allowNewlines); } private bool IsAtEnd(bool allowNewline) { - char ch = lexer.TextWindow.PeekChar(); + char ch = _lexer.TextWindow.PeekChar(); return !allowNewline && SyntaxFacts.IsNewLine(ch) || - (ch == SlidingTextWindow.InvalidCharacter && lexer.TextWindow.IsReallyAtEnd()); + (ch == SlidingTextWindow.InvalidCharacter && _lexer.TextWindow.IsReallyAtEnd()); } internal void ScanInterpolatedStringLiteralTop(ArrayBuilder interpolations, ref TokenInfo info, out bool closeQuoteMissing) { - if (isVerbatim) + if (_isVerbatim) { Debug.Assert( - (lexer.TextWindow.PeekChar() == '@' && lexer.TextWindow.PeekChar(1) == '$') || - (lexer.TextWindow.PeekChar() == '$' && lexer.TextWindow.PeekChar(1) == '@')); + (_lexer.TextWindow.PeekChar() == '@' && _lexer.TextWindow.PeekChar(1) == '$') || + (_lexer.TextWindow.PeekChar() == '$' && _lexer.TextWindow.PeekChar(1) == '@')); // @$ or $@ - lexer.TextWindow.AdvanceChar(); - lexer.TextWindow.AdvanceChar(); + _lexer.TextWindow.AdvanceChar(); + _lexer.TextWindow.AdvanceChar(); } else { - Debug.Assert(lexer.TextWindow.PeekChar() == '$'); - lexer.TextWindow.AdvanceChar(); // $ + Debug.Assert(_lexer.TextWindow.PeekChar() == '$'); + _lexer.TextWindow.AdvanceChar(); // $ } - Debug.Assert(lexer.TextWindow.PeekChar() == '"'); - lexer.TextWindow.AdvanceChar(); // " + Debug.Assert(_lexer.TextWindow.PeekChar() == '"'); + _lexer.TextWindow.AdvanceChar(); // " ScanInterpolatedStringLiteralContents(interpolations); - if (lexer.TextWindow.PeekChar() != '"') + if (_lexer.TextWindow.PeekChar() != '"') { Debug.Assert(IsAtEnd()); if (error == null) { - int position = IsAtEnd(true) ? lexer.TextWindow.Position - 1 : lexer.TextWindow.Position; - error = lexer.MakeError(position, 1, isVerbatim ? ErrorCode.ERR_UnterminatedStringLit : ErrorCode.ERR_NewlineInConst); + int position = IsAtEnd(true) ? _lexer.TextWindow.Position - 1 : _lexer.TextWindow.Position; + error = _lexer.MakeError(position, 1, _isVerbatim ? ErrorCode.ERR_UnterminatedStringLit : ErrorCode.ERR_NewlineInConst); } closeQuoteMissing = true; @@ -355,7 +352,7 @@ internal void ScanInterpolatedStringLiteralTop(ArrayBuilder inter else { // found the closing quote - lexer.TextWindow.AdvanceChar(); // " + _lexer.TextWindow.AdvanceChar(); // " closeQuoteMissing = false; } @@ -372,7 +369,7 @@ private void ScanInterpolatedStringLiteralContents(ArrayBuilder i return; } - switch (lexer.TextWindow.PeekChar()) + switch (_lexer.TextWindow.PeekChar()) { case '"' when RecoveringFromRunawayLexing(): // When recovering from mismatched delimiters, we consume the next @@ -381,51 +378,51 @@ private void ScanInterpolatedStringLiteralContents(ArrayBuilder i // See, for example, https://github.com/dotnet/roslyn/issues/44789 return; case '"': - if (isVerbatim && lexer.TextWindow.PeekChar(1) == '"') + if (_isVerbatim && _lexer.TextWindow.PeekChar(1) == '"') { - lexer.TextWindow.AdvanceChar(); // " - lexer.TextWindow.AdvanceChar(); // " + _lexer.TextWindow.AdvanceChar(); // " + _lexer.TextWindow.AdvanceChar(); // " continue; } // found the end of the string return; case '}': - var pos = lexer.TextWindow.Position; - lexer.TextWindow.AdvanceChar(); // } + var pos = _lexer.TextWindow.Position; + _lexer.TextWindow.AdvanceChar(); // } // ensure any } characters are doubled up - if (lexer.TextWindow.PeekChar() == '}') + if (_lexer.TextWindow.PeekChar() == '}') { - lexer.TextWindow.AdvanceChar(); // } + _lexer.TextWindow.AdvanceChar(); // } } else if (error == null) { - error = lexer.MakeError(pos, 1, ErrorCode.ERR_UnescapedCurly, "}"); + error = _lexer.MakeError(pos, 1, ErrorCode.ERR_UnescapedCurly, "}"); } continue; case '{': - if (lexer.TextWindow.PeekChar(1) == '{') + if (_lexer.TextWindow.PeekChar(1) == '{') { - lexer.TextWindow.AdvanceChar(); - lexer.TextWindow.AdvanceChar(); + _lexer.TextWindow.AdvanceChar(); + _lexer.TextWindow.AdvanceChar(); } else { - int openBracePosition = lexer.TextWindow.Position; - lexer.TextWindow.AdvanceChar(); + int openBracePosition = _lexer.TextWindow.Position; + _lexer.TextWindow.AdvanceChar(); int colonPosition = 0; ScanInterpolatedStringLiteralHoleBalancedText('}', true, ref colonPosition); - int closeBracePosition = lexer.TextWindow.Position; + int closeBracePosition = _lexer.TextWindow.Position; bool closeBraceMissing = false; - if (lexer.TextWindow.PeekChar() == '}') + if (_lexer.TextWindow.PeekChar() == '}') { - lexer.TextWindow.AdvanceChar(); + _lexer.TextWindow.AdvanceChar(); } else { closeBraceMissing = true; if (error == null) { - error = lexer.MakeError(openBracePosition - 1, 2, ErrorCode.ERR_UnclosedExpressionHole); + error = _lexer.MakeError(openBracePosition - 1, 2, ErrorCode.ERR_UnclosedExpressionHole); } } @@ -433,23 +430,23 @@ private void ScanInterpolatedStringLiteralContents(ArrayBuilder i } continue; case '\\': - if (isVerbatim) + if (_isVerbatim) { goto default; } - var escapeStart = lexer.TextWindow.Position; + var escapeStart = _lexer.TextWindow.Position; char c2; - char ch = lexer.ScanEscapeSequence(out c2); + char ch = _lexer.ScanEscapeSequence(out c2); if ((ch == '{' || ch == '}') && error == null) { - error = lexer.MakeError(escapeStart, lexer.TextWindow.Position - escapeStart, ErrorCode.ERR_EscapedCurly, ch); + error = _lexer.MakeError(escapeStart, _lexer.TextWindow.Position - escapeStart, ErrorCode.ERR_EscapedCurly, ch); } continue; default: // found some other character in the string portion - lexer.TextWindow.AdvanceChar(); + _lexer.TextWindow.AdvanceChar(); continue; } } @@ -457,28 +454,28 @@ private void ScanInterpolatedStringLiteralContents(ArrayBuilder i private void ScanFormatSpecifier() { - Debug.Assert(lexer.TextWindow.PeekChar() == ':'); - lexer.TextWindow.AdvanceChar(); + Debug.Assert(_lexer.TextWindow.PeekChar() == ':'); + _lexer.TextWindow.AdvanceChar(); while (true) { - char ch = lexer.TextWindow.PeekChar(); - if (ch == '\\' && !isVerbatim) + char ch = _lexer.TextWindow.PeekChar(); + if (ch == '\\' && !_isVerbatim) { // normal string & char constants can have escapes - var pos = lexer.TextWindow.Position; + var pos = _lexer.TextWindow.Position; char c2; - ch = lexer.ScanEscapeSequence(out c2); + ch = _lexer.ScanEscapeSequence(out c2); if ((ch == '{' || ch == '}') && error == null) { - error = lexer.MakeError(pos, 1, ErrorCode.ERR_EscapedCurly, ch); + error = _lexer.MakeError(pos, 1, ErrorCode.ERR_EscapedCurly, ch); } } else if (ch == '"') { - if (isVerbatim && lexer.TextWindow.PeekChar(1) == '"') + if (_isVerbatim && _lexer.TextWindow.PeekChar(1) == '"') { - lexer.TextWindow.AdvanceChar(); - lexer.TextWindow.AdvanceChar(); + _lexer.TextWindow.AdvanceChar(); + _lexer.TextWindow.AdvanceChar(); } else { @@ -487,24 +484,24 @@ private void ScanFormatSpecifier() } else if (ch == '{') { - var pos = lexer.TextWindow.Position; - lexer.TextWindow.AdvanceChar(); + var pos = _lexer.TextWindow.Position; + _lexer.TextWindow.AdvanceChar(); // ensure any { characters are doubled up - if (lexer.TextWindow.PeekChar() == '{') + if (_lexer.TextWindow.PeekChar() == '{') { - lexer.TextWindow.AdvanceChar(); // { + _lexer.TextWindow.AdvanceChar(); // { } else if (error == null) { - error = lexer.MakeError(pos, 1, ErrorCode.ERR_UnescapedCurly, "{"); + error = _lexer.MakeError(pos, 1, ErrorCode.ERR_UnescapedCurly, "{"); } } else if (ch == '}') { - if (lexer.TextWindow.PeekChar(1) == '}') + if (_lexer.TextWindow.PeekChar(1) == '}') { - lexer.TextWindow.AdvanceChar(); - lexer.TextWindow.AdvanceChar(); + _lexer.TextWindow.AdvanceChar(); + _lexer.TextWindow.AdvanceChar(); } else { @@ -517,7 +514,7 @@ private void ScanFormatSpecifier() } else { - lexer.TextWindow.AdvanceChar(); + _lexer.TextWindow.AdvanceChar(); } } } @@ -535,37 +532,37 @@ private void ScanInterpolatedStringLiteralHoleBalancedText(char endingChar, bool return; } - char ch = lexer.TextWindow.PeekChar(); + char ch = _lexer.TextWindow.PeekChar(); switch (ch) { case '#': // preprocessor directives not allowed. if (error == null) { - error = lexer.MakeError(lexer.TextWindow.Position, 1, ErrorCode.ERR_SyntaxError, endingChar.ToString()); + error = _lexer.MakeError(_lexer.TextWindow.Position, 1, ErrorCode.ERR_SyntaxError, endingChar.ToString()); } - lexer.TextWindow.AdvanceChar(); + _lexer.TextWindow.AdvanceChar(); continue; case '$': - if (lexer.TextWindow.PeekChar(1) == '"' || lexer.TextWindow.PeekChar(1) == '@' && lexer.TextWindow.PeekChar(2) == '"') + if (_lexer.TextWindow.PeekChar(1) == '"' || _lexer.TextWindow.PeekChar(1) == '@' && _lexer.TextWindow.PeekChar(2) == '"') { - bool isVerbatimSubstring = lexer.TextWindow.PeekChar(1) == '@'; + bool isVerbatimSubstring = _lexer.TextWindow.PeekChar(1) == '@'; var interpolations = (ArrayBuilder)null; var info = default(TokenInfo); - bool wasVerbatim = this.isVerbatim; - bool wasAllowNewlines = this.allowNewlines; + bool wasVerbatim = this._isVerbatim; + bool wasAllowNewlines = this._allowNewlines; try { - this.isVerbatim = isVerbatimSubstring; - this.allowNewlines &= isVerbatim; + this._isVerbatim = isVerbatimSubstring; + this._allowNewlines &= _isVerbatim; bool closeQuoteMissing; ScanInterpolatedStringLiteralTop(interpolations, ref info, out closeQuoteMissing); } finally { - this.isVerbatim = wasVerbatim; - this.allowNewlines = wasAllowNewlines; + this._isVerbatim = wasVerbatim; + this._allowNewlines = wasAllowNewlines; } continue; } @@ -576,7 +573,7 @@ private void ScanInterpolatedStringLiteralHoleBalancedText(char endingChar, bool if (isHole) { Debug.Assert(colonPosition == 0); - colonPosition = lexer.TextWindow.Position; + colonPosition = _lexer.TextWindow.Position; ScanFormatSpecifier(); return; } @@ -592,7 +589,7 @@ private void ScanInterpolatedStringLiteralHoleBalancedText(char endingChar, bool if (error == null) { - error = lexer.MakeError(lexer.TextWindow.Position, 1, ErrorCode.ERR_SyntaxError, endingChar.ToString()); + error = _lexer.MakeError(_lexer.TextWindow.Position, 1, ErrorCode.ERR_SyntaxError, endingChar.ToString()); } goto default; @@ -608,46 +605,46 @@ private void ScanInterpolatedStringLiteralHoleBalancedText(char endingChar, bool ScanInterpolatedStringLiteralNestedString(); continue; case '@': - if (lexer.TextWindow.PeekChar(1) == '"' && !RecoveringFromRunawayLexing()) + if (_lexer.TextWindow.PeekChar(1) == '"' && !RecoveringFromRunawayLexing()) { // check for verbatim string inside an expression hole. ScanInterpolatedStringLiteralNestedVerbatimString(); continue; } - else if (lexer.TextWindow.PeekChar(1) == '$' && lexer.TextWindow.PeekChar(2) == '"') + else if (_lexer.TextWindow.PeekChar(1) == '$' && _lexer.TextWindow.PeekChar(2) == '"') { - lexer.CheckFeatureAvailability(MessageID.IDS_FeatureAltInterpolatedVerbatimStrings); + _lexer.CheckFeatureAvailability(MessageID.IDS_FeatureAltInterpolatedVerbatimStrings); var interpolations = (ArrayBuilder)null; var info = default(TokenInfo); - bool wasVerbatim = this.isVerbatim; - bool wasAllowNewlines = this.allowNewlines; + bool wasVerbatim = this._isVerbatim; + bool wasAllowNewlines = this._allowNewlines; try { - this.isVerbatim = true; - this.allowNewlines = true; + this._isVerbatim = true; + this._allowNewlines = true; bool closeQuoteMissing; ScanInterpolatedStringLiteralTop(interpolations, ref info, out closeQuoteMissing); } finally { - this.isVerbatim = wasVerbatim; - this.allowNewlines = wasAllowNewlines; + this._isVerbatim = wasVerbatim; + this._allowNewlines = wasAllowNewlines; } continue; } goto default; case '/': - switch (lexer.TextWindow.PeekChar(1)) + switch (_lexer.TextWindow.PeekChar(1)) { case '/': - if (isVerbatim && allowNewlines) + if (_isVerbatim && _allowNewlines) { - lexer.TextWindow.AdvanceChar(); // skip / - lexer.TextWindow.AdvanceChar(); // skip / + _lexer.TextWindow.AdvanceChar(); // skip / + _lexer.TextWindow.AdvanceChar(); // skip / while (!IsAtEnd(false)) { - lexer.TextWindow.AdvanceChar(); // skip // comment character + _lexer.TextWindow.AdvanceChar(); // skip // comment character } } else @@ -655,11 +652,11 @@ private void ScanInterpolatedStringLiteralHoleBalancedText(char endingChar, bool // error: single-line comment not allowed in an interpolated string if (error == null) { - error = lexer.MakeError(lexer.TextWindow.Position, 2, ErrorCode.ERR_SingleLineCommentInExpressionHole); + error = _lexer.MakeError(_lexer.TextWindow.Position, 2, ErrorCode.ERR_SingleLineCommentInExpressionHole); } - lexer.TextWindow.AdvanceChar(); - lexer.TextWindow.AdvanceChar(); + _lexer.TextWindow.AdvanceChar(); + _lexer.TextWindow.AdvanceChar(); } continue; case '*': @@ -667,7 +664,7 @@ private void ScanInterpolatedStringLiteralHoleBalancedText(char endingChar, bool ScanInterpolatedStringLiteralNestedComment(); continue; default: - lexer.TextWindow.AdvanceChar(); + _lexer.TextWindow.AdvanceChar(); continue; } case '{': @@ -684,7 +681,7 @@ private void ScanInterpolatedStringLiteralHoleBalancedText(char endingChar, bool continue; default: // part of code in the expression hole - lexer.TextWindow.AdvanceChar(); + _lexer.TextWindow.AdvanceChar(); continue; } } @@ -698,10 +695,10 @@ private void ScanInterpolatedStringLiteralHoleBalancedText(char endingChar, bool private void ScanInterpolatedStringLiteralNestedComment() { - Debug.Assert(lexer.TextWindow.PeekChar() == '/'); - lexer.TextWindow.AdvanceChar(); - Debug.Assert(lexer.TextWindow.PeekChar() == '*'); - lexer.TextWindow.AdvanceChar(); + Debug.Assert(_lexer.TextWindow.PeekChar() == '/'); + _lexer.TextWindow.AdvanceChar(); + Debug.Assert(_lexer.TextWindow.PeekChar() == '*'); + _lexer.TextWindow.AdvanceChar(); while (true) { if (IsAtEnd()) @@ -709,11 +706,11 @@ private void ScanInterpolatedStringLiteralNestedComment() return; // let the caller complain about the unterminated quote } - var ch = lexer.TextWindow.PeekChar(); - lexer.TextWindow.AdvanceChar(); - if (ch == '*' && lexer.TextWindow.PeekChar() == '/') + var ch = _lexer.TextWindow.PeekChar(); + _lexer.TextWindow.AdvanceChar(); + if (ch == '*' && _lexer.TextWindow.PeekChar() == '/') { - lexer.TextWindow.AdvanceChar(); // skip */ + _lexer.TextWindow.AdvanceChar(); // skip */ return; } } @@ -722,24 +719,24 @@ private void ScanInterpolatedStringLiteralNestedComment() private void ScanInterpolatedStringLiteralNestedString() { var discarded = default(TokenInfo); - lexer.ScanStringLiteral(ref discarded, true); + _lexer.ScanStringLiteral(ref discarded, inDirective: false); } private void ScanInterpolatedStringLiteralNestedVerbatimString() { var discarded = default(TokenInfo); - lexer.ScanVerbatimStringLiteral(ref discarded, allowNewlines: allowNewlines); + _lexer.ScanVerbatimStringLiteral(ref discarded, _allowNewlines); } private void ScanInterpolatedStringLiteralHoleBracketed(char start, char end) { - Debug.Assert(start == lexer.TextWindow.PeekChar()); - lexer.TextWindow.AdvanceChar(); + Debug.Assert(start == _lexer.TextWindow.PeekChar()); + _lexer.TextWindow.AdvanceChar(); int colon = 0; ScanInterpolatedStringLiteralHoleBalancedText(end, false, ref colon); - if (lexer.TextWindow.PeekChar() == end) + if (_lexer.TextWindow.PeekChar() == end) { - lexer.TextWindow.AdvanceChar(); + _lexer.TextWindow.AdvanceChar(); } else { diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPrintMembers.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPrintMembers.cs index 3f05883416741..5f5e6acfb6b1d 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPrintMembers.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPrintMembers.cs @@ -162,14 +162,20 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, for (var i = 0; i < printableMembers.Length; i++) { - // builder.Append(); - // builder.Append(" = "); - // builder.Append((object)); OR builder.Append(.ToString()); for value types - // builder.Append(", "); // except for last member + // builder.Append(", = "); // if previous members exist + // builder.Append(" = "); // if it is the first member + + // The only printable members are fields and properties, + // which cannot be generic so as to have variant names var member = printableMembers[i]; - block.Add(makeAppendString(F, builder, member.Name)); - block.Add(makeAppendString(F, builder, " = ")); + var memberHeader = $"{member.Name} = "; + if (i > 0) + { + memberHeader = ", " + memberHeader; + } + + block.Add(makeAppendString(F, builder, memberHeader)); var value = member.Kind switch { @@ -178,6 +184,8 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, _ => throw ExceptionUtilities.UnexpectedValue(member.Kind) }; + // builder.Append((object)); OR builder.Append(.ToString()); for value types + Debug.Assert(value.Type is not null); if (value.Type.IsValueType) { @@ -193,11 +201,6 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, F.WellKnownMethod(WellKnownMember.System_Text_StringBuilder__AppendObject), F.Convert(F.SpecialType(SpecialType.System_Object), value)))); } - - if (i < printableMembers.Length - 1) - { - block.Add(makeAppendString(F, builder, ", ")); - } } block.Add(F.Return(F.Literal(true))); diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordToString.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordToString.cs index 17f70250dc4fe..28ff426445f4a 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordToString.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordToString.cs @@ -66,11 +66,11 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, // builder.Append(" { "); block.Add(makeAppendString(F, builderLocal, " { ")); - // if (this.PrintMembers(builder)) builder.Append(" "); - block.Add(F.If(F.Call(F.This(), _printMethod, builderLocal), makeAppendString(F, builderLocal, " "))); + // if (this.PrintMembers(builder)) builder.Append(' '); + block.Add(F.If(F.Call(F.This(), _printMethod, builderLocal), makeAppendChar(F, builderLocal, ' '))); - // builder.Append("}"); - block.Add(makeAppendString(F, builderLocal, "}")); + // builder.Append('}'); + block.Add(makeAppendChar(F, builderLocal, '}')); // return builder.ToString(); block.Add(F.Return(F.Call(builderLocal, F.SpecialMethod(SpecialMember.System_Object__ToString)))); @@ -87,6 +87,11 @@ static BoundStatement makeAppendString(SyntheticBoundNodeFactory F, BoundLocal b { return F.ExpressionStatement(F.Call(receiver: builder, F.WellKnownMethod(WellKnownMember.System_Text_StringBuilder__AppendString), F.StringLiteral(value))); } + + static BoundStatement makeAppendChar(SyntheticBoundNodeFactory F, BoundLocal builder, char value) + { + return F.ExpressionStatement(F.Call(receiver: builder, F.WellKnownMethod(WellKnownMember.System_Text_StringBuilder__AppendChar), F.CharLiteral(value))); + } } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/TypeSymbol.cs index 755781e851806..3f8dae9399887 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeSymbol.cs @@ -567,7 +567,7 @@ public virtual bool IsAnonymousType /// Verify if the given type is a tuple of a given cardinality, or can be used to back a tuple type /// with the given cardinality. /// - public bool IsTupleTypeOfCardinality(int targetCardinality) + internal bool IsTupleTypeOfCardinality(int targetCardinality) { if (IsTupleType) { @@ -1007,7 +1007,7 @@ private static Symbol ComputeImplementationForInterfaceMember(Symbol interfaceMe if (interfaceMember.Kind == SymbolKind.Method && (object)implementingBaseOpt == null) // Otherwise any approprite errors are going to be reported for the base. { - LanguageVersion requiredVersion = MessageID.IDS_FeatureImplicitImplementationOfNonPublicMemebers.RequiredVersion(); + LanguageVersion requiredVersion = MessageID.IDS_FeatureImplicitImplementationOfNonPublicMembers.RequiredVersion(); LanguageVersion? availableVersion = implementingType.DeclaringCompilation?.LanguageVersion; if (requiredVersion > availableVersion) { diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index 6dce0699bab19..dc7b51a2fadaa 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -827,6 +827,11 @@ V základním typu {0} se nenašel žádný přístupný kopírovací konstruktor. + + Conditional expression is not valid in language version {0} because a common type was not found between '{1}' and '{2}'. To use a target-typed conversion, upgrade to language version {3} or greater. + Conditional expression is not valid in language version {0} because a common type was not found between '{1}' and '{2}'. To use a target-typed conversion, upgrade to language version {3} or greater. + + Output directory could not be determined Nepovedlo se určit výstupní adresář. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 394976f076936..58003a5db8cd5 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -827,6 +827,11 @@ Im Basistyp "{0}" wurde kein zugänglicher Kopierkonstruktor gefunden. + + Conditional expression is not valid in language version {0} because a common type was not found between '{1}' and '{2}'. To use a target-typed conversion, upgrade to language version {3} or greater. + Conditional expression is not valid in language version {0} because a common type was not found between '{1}' and '{2}'. To use a target-typed conversion, upgrade to language version {3} or greater. + + Output directory could not be determined Das Ausgabeverzeichnis konnte nicht bestimmt werden. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index b23615d2e4d2c..ede3e02613fe0 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -827,6 +827,11 @@ No se encontró ningún constructor de copia accesible en el tipo de base "{0}". + + Conditional expression is not valid in language version {0} because a common type was not found between '{1}' and '{2}'. To use a target-typed conversion, upgrade to language version {3} or greater. + Conditional expression is not valid in language version {0} because a common type was not found between '{1}' and '{2}'. To use a target-typed conversion, upgrade to language version {3} or greater. + + Output directory could not be determined No se pudo determinar el directorio de salida. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index e448ee074be50..a1af83dc73d33 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -827,6 +827,11 @@ Aucun constructeur de copie accessible n'a été trouvé dans le type de base '{0}'. + + Conditional expression is not valid in language version {0} because a common type was not found between '{1}' and '{2}'. To use a target-typed conversion, upgrade to language version {3} or greater. + Conditional expression is not valid in language version {0} because a common type was not found between '{1}' and '{2}'. To use a target-typed conversion, upgrade to language version {3} or greater. + + Output directory could not be determined Impossible de déterminer le répertoire de sortie diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 158b6c38f6640..c36fc6623d863 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -827,6 +827,11 @@ Non è stato trovato alcun costruttore di copia accessibile nel tipo di base '{0}'. + + Conditional expression is not valid in language version {0} because a common type was not found between '{1}' and '{2}'. To use a target-typed conversion, upgrade to language version {3} or greater. + Conditional expression is not valid in language version {0} because a common type was not found between '{1}' and '{2}'. To use a target-typed conversion, upgrade to language version {3} or greater. + + Output directory could not be determined Non è stato possibile individuare la directory di output diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index f58205c7d66a7..9281c593f523b 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -827,6 +827,11 @@ 基本型 '{0}' にアクセス可能なコピー コンストラクターが見つかりませんでした。 + + Conditional expression is not valid in language version {0} because a common type was not found between '{1}' and '{2}'. To use a target-typed conversion, upgrade to language version {3} or greater. + Conditional expression is not valid in language version {0} because a common type was not found between '{1}' and '{2}'. To use a target-typed conversion, upgrade to language version {3} or greater. + + Output directory could not be determined 出力ディレクトリを特定できませんでした diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index cac56c7b486ec..2601999542983 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -827,6 +827,11 @@ 기본 형식 '{0}'에 액세스 가능한 복사 생성자가 없습니다. + + Conditional expression is not valid in language version {0} because a common type was not found between '{1}' and '{2}'. To use a target-typed conversion, upgrade to language version {3} or greater. + Conditional expression is not valid in language version {0} because a common type was not found between '{1}' and '{2}'. To use a target-typed conversion, upgrade to language version {3} or greater. + + Output directory could not be determined 출력 디렉터리를 확인할 수 없습니다. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index c88af13137e65..834f1cbcd38de 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -827,6 +827,11 @@ W typie podstawowym „{0}” nie znaleziono dostępnego konstruktora kopiującego. + + Conditional expression is not valid in language version {0} because a common type was not found between '{1}' and '{2}'. To use a target-typed conversion, upgrade to language version {3} or greater. + Conditional expression is not valid in language version {0} because a common type was not found between '{1}' and '{2}'. To use a target-typed conversion, upgrade to language version {3} or greater. + + Output directory could not be determined Nie można było określić katalogu wyjściowego diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index c02b4151d8222..2b064a7ce0964 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -827,6 +827,11 @@ Não foi encontrado nenhum construtor de cópia acessível no tipo base '{0}'. + + Conditional expression is not valid in language version {0} because a common type was not found between '{1}' and '{2}'. To use a target-typed conversion, upgrade to language version {3} or greater. + Conditional expression is not valid in language version {0} because a common type was not found between '{1}' and '{2}'. To use a target-typed conversion, upgrade to language version {3} or greater. + + Output directory could not be determined Não foi possível determinar o diretório de saída diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index c2e68d13e07a3..e30bb79e6190d 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -827,6 +827,11 @@ Доступный конструктор копий не найден в базовом типе "{0}". + + Conditional expression is not valid in language version {0} because a common type was not found between '{1}' and '{2}'. To use a target-typed conversion, upgrade to language version {3} or greater. + Conditional expression is not valid in language version {0} because a common type was not found between '{1}' and '{2}'. To use a target-typed conversion, upgrade to language version {3} or greater. + + Output directory could not be determined Не удалось определить выходной каталог diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index 0a0cd4d39dd7d..20b1ae05dbcfe 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -827,6 +827,11 @@ '{0}' temel türünde erişilebilir kopya oluşturucu bulunamadı. + + Conditional expression is not valid in language version {0} because a common type was not found between '{1}' and '{2}'. To use a target-typed conversion, upgrade to language version {3} or greater. + Conditional expression is not valid in language version {0} because a common type was not found between '{1}' and '{2}'. To use a target-typed conversion, upgrade to language version {3} or greater. + + Output directory could not be determined Çıkış dizini belirlenemedi diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index e385fddc16c18..bfaaedc130264 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -827,6 +827,11 @@ 在基类型“{0}”中找不到可访问的复制构造函数。 + + Conditional expression is not valid in language version {0} because a common type was not found between '{1}' and '{2}'. To use a target-typed conversion, upgrade to language version {3} or greater. + Conditional expression is not valid in language version {0} because a common type was not found between '{1}' and '{2}'. To use a target-typed conversion, upgrade to language version {3} or greater. + + Output directory could not be determined 无法确定输出目录 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 9d6053fdcb056..ac1b5d2e5501b 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -827,6 +827,11 @@ 在基底類型 '{0}' 中找不到可存取的複製建構函式。 + + Conditional expression is not valid in language version {0} because a common type was not found between '{1}' and '{2}'. To use a target-typed conversion, upgrade to language version {3} or greater. + Conditional expression is not valid in language version {0} because a common type was not found between '{1}' and '{2}'. To use a target-typed conversion, upgrade to language version {3} or greater. + + Output directory could not be determined 無法判斷輸出目錄 diff --git a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs index bc35ca5830830..05fa00db21656 100644 --- a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs +++ b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs @@ -25,6 +25,7 @@ using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Test.Resources.Proprietary; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; @@ -936,7 +937,7 @@ public void ParseResources() Assert.Null(desc); diags.Clear(); - desc = CSharpCommandLineParser.ParseResourceDescription("", null, WorkingDirectory, diags, embedded: false); + desc = CSharpCommandLineParser.ParseResourceDescription("", (string)null, WorkingDirectory, diags, embedded: false); diags.Verify(Diagnostic(ErrorCode.ERR_NoFileSpec).WithArguments("")); Assert.Null(desc); diags.Clear(); @@ -6209,6 +6210,63 @@ static int Main() CleanupAllGeneratedFiles(rsp); } + [Fact] + public void ResponseFileOrdering() + { + var rspFilePath1 = Temp.CreateFile().WriteAllText(@" +/b +/c +").Path; + + assertOrder( + new[] { "/a", "/b", "/c", "/d" }, + new[] { "/a", @$"@""{rspFilePath1}""", "/d" }); + + var rspFilePath2 = Temp.CreateFile().WriteAllText(@" +/c +/d +").Path; + + rspFilePath1 = Temp.CreateFile().WriteAllText(@$" +/b +@""{rspFilePath2}"" +").Path; + + assertOrder( + new[] { "/a", "/b", "/c", "/d", "/e" }, + new[] { "/a", @$"@""{rspFilePath1}""", "/e" }); + + rspFilePath1 = Temp.CreateFile().WriteAllText(@$" +/b +").Path; + + rspFilePath2 = Temp.CreateFile().WriteAllText(@" +# this will be ignored +/c +/d +").Path; + + assertOrder( + new[] { "/a", "/b", "/c", "/d", "/e" }, + new[] { "/a", @$"@""{rspFilePath1}""", $@"@""{rspFilePath2}""", "/e" }); + + void assertOrder(string[] expected, string[] args) + { + var flattenedArgs = ArrayBuilder.GetInstance(); + var diagnostics = new List(); + CSharpCommandLineParser.Default.FlattenArgs( + args, + diagnostics, + flattenedArgs, + scriptArgsOpt: null, + baseDirectory: Path.DirectorySeparatorChar == '\\' ? @"c:\" : "/"); + + Assert.Empty(diagnostics); + Assert.Equal(expected, flattenedArgs); + flattenedArgs.Free(); + } + } + [WorkItem(545832, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545832")] [Fact] public void ResponseFilesWithEmptyAliasReference2() diff --git a/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs index 78acdc77f3244..3aeed8be822f1 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs @@ -902,23 +902,23 @@ private static void CompareAssemblies(string sourceTemplate, string change1, str string name = GetUniqueName(); string source1 = sourceTemplate.Replace("CHANGE", change1); CSharpCompilation comp1 = CreateCompilation(Parse(source1), options: TestOptions.DebugDll.WithDeterministic(true), assemblyName: name); - ImmutableArray image1 = comp1.EmitToArray(EmitOptions.Default.WithEmitMetadataOnly(true).WithIncludePrivateMembers(includePrivateMembers)); + var image1 = comp1.EmitToStream(EmitOptions.Default.WithEmitMetadataOnly(true).WithIncludePrivateMembers(includePrivateMembers)); var source2 = sourceTemplate.Replace("CHANGE", change2); Compilation comp2 = CreateCompilation(Parse(source2), options: TestOptions.DebugDll.WithDeterministic(true), assemblyName: name); - ImmutableArray image2 = comp2.EmitToArray(EmitOptions.Default.WithEmitMetadataOnly(true).WithIncludePrivateMembers(includePrivateMembers)); + var image2 = comp2.EmitToStream(EmitOptions.Default.WithEmitMetadataOnly(true).WithIncludePrivateMembers(includePrivateMembers)); if (expectMatch) { - AssertEx.Equal(image1, image2, message: $"Expecting match for includePrivateMembers={includePrivateMembers} case, but differences were found."); + AssertEx.Equal(image1.GetBuffer(), image2.GetBuffer(), message: $"Expecting match for includePrivateMembers={includePrivateMembers} case, but differences were found."); } else { - AssertEx.NotEqual(image1, image2, message: $"Expecting difference for includePrivateMembers={includePrivateMembers} case, but they matched."); + AssertEx.NotEqual(image1.GetBuffer(), image2.GetBuffer(), message: $"Expecting difference for includePrivateMembers={includePrivateMembers} case, but they matched."); } - var mvid1 = BuildTasks.MvidReader.ReadAssemblyMvidOrEmpty(new MemoryStream(image1.DangerousGetUnderlyingArray())); - var mvid2 = BuildTasks.MvidReader.ReadAssemblyMvidOrEmpty(new MemoryStream(image2.DangerousGetUnderlyingArray())); + var mvid1 = BuildTasks.MvidReader.ReadAssemblyMvidOrEmpty(image1); + var mvid2 = BuildTasks.MvidReader.ReadAssemblyMvidOrEmpty(image2); if (!includePrivateMembers) { diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IThrowOperation.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IThrowOperation.cs index 06b66d0f38179..5b91f5e895754 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IThrowOperation.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IThrowOperation.cs @@ -2171,9 +2171,9 @@ void F(bool x, bool y, System.Exception ex1, System.Exception ex2) var compilation = CreateCompilation(source, parseOptions: TestOptions.Regular8); compilation.VerifyDiagnostics( - // (6,13): error CS8400: Feature 'target-typed conditional expression' is not available in C# 8.0. Please use language version 9.0 or greater. + // (6,13): error CS8957: Conditional expression is not valid in language version 8.0 because a common type was not found between '' and ''. To use a target-typed conversion, upgrade to language version 9.0 or greater. // x = y ? throw ex1 : throw ex2; - Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion8, "y ? throw ex1 : throw ex2").WithArguments("target-typed conditional expression", "9.0").WithLocation(6, 13) + Diagnostic(ErrorCode.ERR_NoImplicitConvTargetTypedConditional, "y ? throw ex1 : throw ex2").WithArguments("8.0", "", "", "9.0").WithLocation(6, 13) ); string expectedOperationTree = @" diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs index 75948efc9befc..54b8e0e62cd7d 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs @@ -2054,6 +2054,7 @@ public class Attribute { } public class String { } public struct Void { } public struct Boolean { } + public struct Char { } public struct Int32 { } public interface IEquatable { } } @@ -2070,7 +2071,8 @@ namespace System.Text public class StringBuilder { public StringBuilder Append(string s) => null; - public StringBuilder Append(object s) => null; + public StringBuilder Append(char c) => null; + public StringBuilder Append(object o) => null; } } "; @@ -2146,6 +2148,7 @@ public class Attribute { } public class String { } public struct Void { } public struct Boolean { } + public struct Char { } public struct Int32 { } public interface IEquatable { } } @@ -2162,7 +2165,8 @@ namespace System.Text public class StringBuilder { public StringBuilder Append(string s) => null; - public StringBuilder Append(object s) => null; + public StringBuilder Append(char c) => null; + public StringBuilder Append(object o) => null; } } "; @@ -4069,6 +4073,7 @@ public class Attribute { } public class String { } public struct Void { } public struct Boolean { } + public struct Char { } public struct Int32 { } public interface IEquatable { } } @@ -4085,7 +4090,8 @@ namespace System.Text public class StringBuilder { public StringBuilder Append(string s) => null; - public StringBuilder Append(object s) => null; + public StringBuilder Append(char c) => null; + public StringBuilder Append(object o) => null; } } "; @@ -4320,6 +4326,7 @@ public class Attribute { } public class String { } public struct Void { } public struct Boolean { } + public struct Char { } public struct Int32 { } public interface IEquatable { } } @@ -4336,7 +4343,8 @@ namespace System.Text public class StringBuilder { public StringBuilder Append(string s) => null; - public StringBuilder Append(object s) => null; + public StringBuilder Append(char c) => null; + public StringBuilder Append(object o) => null; } } "; @@ -4707,7 +4715,7 @@ .maxstack 1 "); v.VerifyIL("C1." + WellKnownMemberNames.ObjectToString, @" { - // Code size 70 (0x46) + // Code size 64 (0x40) .maxstack 2 .locals init (System.Text.StringBuilder V_0) IL_0000: newobj ""System.Text.StringBuilder..ctor()"" @@ -4723,18 +4731,18 @@ .locals init (System.Text.StringBuilder V_0) IL_001e: ldarg.0 IL_001f: ldloc.0 IL_0020: call ""bool C1.PrintMembers(System.Text.StringBuilder)"" - IL_0025: brfalse.s IL_0033 + IL_0025: brfalse.s IL_0030 IL_0027: ldloc.0 - IL_0028: ldstr "" "" - IL_002d: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_0032: pop - IL_0033: ldloc.0 - IL_0034: ldstr ""}"" - IL_0039: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_003e: pop - IL_003f: ldloc.0 - IL_0040: callvirt ""string object.ToString()"" - IL_0045: ret + IL_0028: ldc.i4.s 32 + IL_002a: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(char)"" + IL_002f: pop + IL_0030: ldloc.0 + IL_0031: ldc.i4.s 125 + IL_0033: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(char)"" + IL_0038: pop + IL_0039: ldloc.0 + IL_003a: callvirt ""string object.ToString()"" + IL_003f: ret } "); } @@ -4903,25 +4911,21 @@ record struct C1 v.VerifyIL("C1." + WellKnownMemberNames.PrintMembersMethodName, @" { - // Code size 50 (0x32) + // Code size 38 (0x26) .maxstack 2 IL_0000: ldarg.1 - IL_0001: ldstr ""field"" + IL_0001: ldstr ""field = "" IL_0006: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" IL_000b: pop IL_000c: ldarg.1 - IL_000d: ldstr "" = "" - IL_0012: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_0017: pop - IL_0018: ldarg.1 - IL_0019: ldarg.0 - IL_001a: ldflda ""int C1.field"" - IL_001f: constrained. ""int"" - IL_0025: callvirt ""string object.ToString()"" - IL_002a: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_002f: pop - IL_0030: ldc.i4.1 - IL_0031: ret + IL_000d: ldarg.0 + IL_000e: ldflda ""int C1.field"" + IL_0013: constrained. ""int"" + IL_0019: callvirt ""string object.ToString()"" + IL_001e: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_0023: pop + IL_0024: ldc.i4.1 + IL_0025: ret } "); } @@ -4945,25 +4949,21 @@ record struct C1 where T : struct v.VerifyIL("C1." + WellKnownMemberNames.PrintMembersMethodName, @" { - // Code size 50 (0x32) + // Code size 38 (0x26) .maxstack 2 IL_0000: ldarg.1 - IL_0001: ldstr ""field"" + IL_0001: ldstr ""field = "" IL_0006: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" IL_000b: pop IL_000c: ldarg.1 - IL_000d: ldstr "" = "" - IL_0012: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_0017: pop - IL_0018: ldarg.1 - IL_0019: ldarg.0 - IL_001a: ldflda ""T C1.field"" - IL_001f: constrained. ""T"" - IL_0025: callvirt ""string object.ToString()"" - IL_002a: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_002f: pop - IL_0030: ldc.i4.1 - IL_0031: ret + IL_000d: ldarg.0 + IL_000e: ldflda ""T C1.field"" + IL_0013: constrained. ""T"" + IL_0019: callvirt ""string object.ToString()"" + IL_001e: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_0023: pop + IL_0024: ldc.i4.1 + IL_0025: ret } "); } @@ -4987,23 +4987,19 @@ record struct C1 v.VerifyIL("C1." + WellKnownMemberNames.PrintMembersMethodName, @" { - // Code size 39 (0x27) + // Code size 27 (0x1b) .maxstack 2 IL_0000: ldarg.1 - IL_0001: ldstr ""field"" + IL_0001: ldstr ""field = "" IL_0006: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" IL_000b: pop IL_000c: ldarg.1 - IL_000d: ldstr "" = "" - IL_0012: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_0017: pop - IL_0018: ldarg.1 - IL_0019: ldarg.0 - IL_001a: ldfld ""string C1.field"" - IL_001f: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(object)"" - IL_0024: pop - IL_0025: ldc.i4.1 - IL_0026: ret + IL_000d: ldarg.0 + IL_000e: ldfld ""string C1.field"" + IL_0013: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(object)"" + IL_0018: pop + IL_0019: ldc.i4.1 + IL_001a: ret } "); } @@ -5035,62 +5031,42 @@ record struct C1(int I) v.VerifyIL("C1." + WellKnownMemberNames.PrintMembersMethodName, @" { - // Code size 151 (0x97) + // Code size 91 (0x5b) .maxstack 2 .locals init (int V_0) IL_0000: ldarg.1 - IL_0001: ldstr ""I"" + IL_0001: ldstr ""I = "" IL_0006: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" IL_000b: pop IL_000c: ldarg.1 - IL_000d: ldstr "" = "" - IL_0012: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_0017: pop - IL_0018: ldarg.1 - IL_0019: ldarg.0 - IL_001a: call ""readonly int C1.I.get"" - IL_001f: stloc.0 - IL_0020: ldloca.s V_0 - IL_0022: constrained. ""int"" - IL_0028: callvirt ""string object.ToString()"" + IL_000d: ldarg.0 + IL_000e: call ""readonly int C1.I.get"" + IL_0013: stloc.0 + IL_0014: ldloca.s V_0 + IL_0016: constrained. ""int"" + IL_001c: callvirt ""string object.ToString()"" + IL_0021: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_0026: pop + IL_0027: ldarg.1 + IL_0028: ldstr "", field1 = "" IL_002d: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" IL_0032: pop IL_0033: ldarg.1 - IL_0034: ldstr "", "" - IL_0039: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_003e: pop - IL_003f: ldarg.1 - IL_0040: ldstr ""field1"" - IL_0045: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_004a: pop - IL_004b: ldarg.1 - IL_004c: ldstr "" = "" - IL_0051: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_0056: pop - IL_0057: ldarg.1 - IL_0058: ldarg.0 - IL_0059: ldfld ""string C1.field1"" - IL_005e: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(object)"" - IL_0063: pop - IL_0064: ldarg.1 - IL_0065: ldstr "", "" - IL_006a: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_006f: pop - IL_0070: ldarg.1 - IL_0071: ldstr ""field2"" - IL_0076: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_007b: pop - IL_007c: ldarg.1 - IL_007d: ldstr "" = "" - IL_0082: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_0087: pop - IL_0088: ldarg.1 - IL_0089: ldarg.0 - IL_008a: ldfld ""string C1.field2"" - IL_008f: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(object)"" - IL_0094: pop - IL_0095: ldc.i4.1 - IL_0096: ret + IL_0034: ldarg.0 + IL_0035: ldfld ""string C1.field1"" + IL_003a: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(object)"" + IL_003f: pop + IL_0040: ldarg.1 + IL_0041: ldstr "", field2 = "" + IL_0046: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_004b: pop + IL_004c: ldarg.1 + IL_004d: ldarg.0 + IL_004e: ldfld ""string C1.field2"" + IL_0053: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(object)"" + IL_0058: pop + IL_0059: ldc.i4.1 + IL_005a: ret } "); } @@ -5111,33 +5087,29 @@ public void ToString_TopLevelRecord_Readonly() v.VerifyIL("C1." + WellKnownMemberNames.PrintMembersMethodName, @" { - // Code size 53 (0x35) + // Code size 41 (0x29) .maxstack 2 .locals init (int V_0) IL_0000: ldarg.1 - IL_0001: ldstr ""I"" + IL_0001: ldstr ""I = "" IL_0006: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" IL_000b: pop IL_000c: ldarg.1 - IL_000d: ldstr "" = "" - IL_0012: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_0017: pop - IL_0018: ldarg.1 - IL_0019: ldarg.0 - IL_001a: call ""int C1.I.get"" - IL_001f: stloc.0 - IL_0020: ldloca.s V_0 - IL_0022: constrained. ""int"" - IL_0028: callvirt ""string object.ToString()"" - IL_002d: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_0032: pop - IL_0033: ldc.i4.1 - IL_0034: ret + IL_000d: ldarg.0 + IL_000e: call ""int C1.I.get"" + IL_0013: stloc.0 + IL_0014: ldloca.s V_0 + IL_0016: constrained. ""int"" + IL_001c: callvirt ""string object.ToString()"" + IL_0021: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_0026: pop + IL_0027: ldc.i4.1 + IL_0028: ret } "); v.VerifyIL("C1." + WellKnownMemberNames.ObjectToString, @" { - // Code size 70 (0x46) + // Code size 64 (0x40) .maxstack 2 .locals init (System.Text.StringBuilder V_0) IL_0000: newobj ""System.Text.StringBuilder..ctor()"" @@ -5153,18 +5125,18 @@ .locals init (System.Text.StringBuilder V_0) IL_001e: ldarg.0 IL_001f: ldloc.0 IL_0020: call ""bool C1.PrintMembers(System.Text.StringBuilder)"" - IL_0025: brfalse.s IL_0033 + IL_0025: brfalse.s IL_0030 IL_0027: ldloc.0 - IL_0028: ldstr "" "" - IL_002d: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_0032: pop - IL_0033: ldloc.0 - IL_0034: ldstr ""}"" - IL_0039: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_003e: pop - IL_003f: ldloc.0 - IL_0040: callvirt ""string object.ToString()"" - IL_0045: ret + IL_0028: ldc.i4.s 32 + IL_002a: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(char)"" + IL_002f: pop + IL_0030: ldloc.0 + IL_0031: ldc.i4.s 125 + IL_0033: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(char)"" + IL_0038: pop + IL_0039: ldloc.0 + IL_003a: callvirt ""string object.ToString()"" + IL_003f: ret } "); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs index 61a4ebe97e08e..b07b691f06556 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs @@ -5000,7 +5000,7 @@ .maxstack 1 "); v.VerifyIL("C1." + WellKnownMemberNames.ObjectToString, @" { - // Code size 70 (0x46) + // Code size 64 (0x40) .maxstack 2 .locals init (System.Text.StringBuilder V_0) IL_0000: newobj ""System.Text.StringBuilder..ctor()"" @@ -5016,18 +5016,18 @@ .locals init (System.Text.StringBuilder V_0) IL_001e: ldarg.0 IL_001f: ldloc.0 IL_0020: callvirt ""bool C1.PrintMembers(System.Text.StringBuilder)"" - IL_0025: brfalse.s IL_0033 + IL_0025: brfalse.s IL_0030 IL_0027: ldloc.0 - IL_0028: ldstr "" "" - IL_002d: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_0032: pop - IL_0033: ldloc.0 - IL_0034: ldstr ""}"" - IL_0039: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_003e: pop - IL_003f: ldloc.0 - IL_0040: callvirt ""string object.ToString()"" - IL_0045: ret + IL_0028: ldc.i4.s 32 + IL_002a: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(char)"" + IL_002f: pop + IL_0030: ldloc.0 + IL_0031: ldc.i4.s 125 + IL_0033: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(char)"" + IL_0038: pop + IL_0039: ldloc.0 + IL_003a: callvirt ""string object.ToString()"" + IL_003f: ret } "); } @@ -5075,7 +5075,7 @@ .maxstack 1 "); v.VerifyIL("C1." + WellKnownMemberNames.ObjectToString, @" { - // Code size 70 (0x46) + // Code size 64 (0x40) .maxstack 2 .locals init (System.Text.StringBuilder V_0) IL_0000: newobj ""System.Text.StringBuilder..ctor()"" @@ -5091,18 +5091,18 @@ .locals init (System.Text.StringBuilder V_0) IL_001e: ldarg.0 IL_001f: ldloc.0 IL_0020: callvirt ""bool C1.PrintMembers(System.Text.StringBuilder)"" - IL_0025: brfalse.s IL_0033 + IL_0025: brfalse.s IL_0030 IL_0027: ldloc.0 - IL_0028: ldstr "" "" - IL_002d: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_0032: pop - IL_0033: ldloc.0 - IL_0034: ldstr ""}"" - IL_0039: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_003e: pop - IL_003f: ldloc.0 - IL_0040: callvirt ""string object.ToString()"" - IL_0045: ret + IL_0028: ldc.i4.s 32 + IL_002a: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(char)"" + IL_002f: pop + IL_0030: ldloc.0 + IL_0031: ldc.i4.s 125 + IL_0033: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(char)"" + IL_0038: pop + IL_0039: ldloc.0 + IL_003a: callvirt ""string object.ToString()"" + IL_003f: ret } "); } @@ -5154,7 +5154,7 @@ .maxstack 2 "); v.VerifyIL("C1." + WellKnownMemberNames.ObjectToString, @" { - // Code size 70 (0x46) + // Code size 64 (0x40) .maxstack 2 .locals init (System.Text.StringBuilder V_0) IL_0000: newobj ""System.Text.StringBuilder..ctor()"" @@ -5170,18 +5170,18 @@ .locals init (System.Text.StringBuilder V_0) IL_001e: ldarg.0 IL_001f: ldloc.0 IL_0020: callvirt ""bool Base.PrintMembers(System.Text.StringBuilder)"" - IL_0025: brfalse.s IL_0033 + IL_0025: brfalse.s IL_0030 IL_0027: ldloc.0 - IL_0028: ldstr "" "" - IL_002d: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_0032: pop - IL_0033: ldloc.0 - IL_0034: ldstr ""}"" - IL_0039: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_003e: pop - IL_003f: ldloc.0 - IL_0040: callvirt ""string object.ToString()"" - IL_0045: ret + IL_0028: ldc.i4.s 32 + IL_002a: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(char)"" + IL_002f: pop + IL_0030: ldloc.0 + IL_0031: ldc.i4.s 125 + IL_0033: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(char)"" + IL_0038: pop + IL_0039: ldloc.0 + IL_003a: callvirt ""string object.ToString()"" + IL_003f: ret } "); } @@ -5259,6 +5259,26 @@ record C1(int P); ); } + [Fact] + public void ToString_TopLevelRecord_OneProperty_MissingStringBuilderAppendStringAndChar() + { + var src = @" +record C1(int P); +"; + + var comp = CreateCompilation(src); + comp.MakeMemberMissing(WellKnownMember.System_Text_StringBuilder__AppendString); + comp.MakeMemberMissing(WellKnownMember.System_Text_StringBuilder__AppendChar); + comp.VerifyEmitDiagnostics( + // (2,1): error CS0656: Missing compiler required member 'System.Text.StringBuilder.Append' + // record C1(int P); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "record C1(int P);").WithArguments("System.Text.StringBuilder", "Append").WithLocation(2, 1), + // (2,1): error CS0656: Missing compiler required member 'System.Text.StringBuilder.Append' + // record C1(int P); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "record C1(int P);").WithArguments("System.Text.StringBuilder", "Append").WithLocation(2, 1) + ); + } + [Fact] public void ToString_TopLevelRecord_Empty_Sealed() { @@ -5539,25 +5559,21 @@ record C1 v.VerifyIL("C1." + WellKnownMemberNames.PrintMembersMethodName, @" { - // Code size 50 (0x32) + // Code size 38 (0x26) .maxstack 2 IL_0000: ldarg.1 - IL_0001: ldstr ""field"" + IL_0001: ldstr ""field = "" IL_0006: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" IL_000b: pop IL_000c: ldarg.1 - IL_000d: ldstr "" = "" - IL_0012: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_0017: pop - IL_0018: ldarg.1 - IL_0019: ldarg.0 - IL_001a: ldflda ""int C1.field"" - IL_001f: constrained. ""int"" - IL_0025: callvirt ""string object.ToString()"" - IL_002a: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_002f: pop - IL_0030: ldc.i4.1 - IL_0031: ret + IL_000d: ldarg.0 + IL_000e: ldflda ""int C1.field"" + IL_0013: constrained. ""int"" + IL_0019: callvirt ""string object.ToString()"" + IL_001e: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_0023: pop + IL_0024: ldc.i4.1 + IL_0025: ret } "); } @@ -5581,25 +5597,21 @@ record C1 where T : struct v.VerifyIL("C1." + WellKnownMemberNames.PrintMembersMethodName, @" { - // Code size 50 (0x32) + // Code size 38 (0x26) .maxstack 2 IL_0000: ldarg.1 - IL_0001: ldstr ""field"" + IL_0001: ldstr ""field = "" IL_0006: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" IL_000b: pop IL_000c: ldarg.1 - IL_000d: ldstr "" = "" - IL_0012: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_0017: pop - IL_0018: ldarg.1 - IL_0019: ldarg.0 - IL_001a: ldflda ""T C1.field"" - IL_001f: constrained. ""T"" - IL_0025: callvirt ""string object.ToString()"" - IL_002a: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_002f: pop - IL_0030: ldc.i4.1 - IL_0031: ret + IL_000d: ldarg.0 + IL_000e: ldflda ""T C1.field"" + IL_0013: constrained. ""T"" + IL_0019: callvirt ""string object.ToString()"" + IL_001e: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_0023: pop + IL_0024: ldc.i4.1 + IL_0025: ret } "); } @@ -5623,23 +5635,19 @@ record C1 v.VerifyIL("C1." + WellKnownMemberNames.PrintMembersMethodName, @" { - // Code size 39 (0x27) + // Code size 27 (0x1b) .maxstack 2 IL_0000: ldarg.1 - IL_0001: ldstr ""field"" + IL_0001: ldstr ""field = "" IL_0006: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" IL_000b: pop IL_000c: ldarg.1 - IL_000d: ldstr "" = "" - IL_0012: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_0017: pop - IL_0018: ldarg.1 - IL_0019: ldarg.0 - IL_001a: ldfld ""string C1.field"" - IL_001f: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(object)"" - IL_0024: pop - IL_0025: ldc.i4.1 - IL_0026: ret + IL_000d: ldarg.0 + IL_000e: ldfld ""string C1.field"" + IL_0013: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(object)"" + IL_0018: pop + IL_0019: ldc.i4.1 + IL_001a: ret } "); } @@ -5667,24 +5675,20 @@ record C1 v.VerifyIL("C1." + WellKnownMemberNames.PrintMembersMethodName, @" { - // Code size 44 (0x2c) + // Code size 32 (0x20) .maxstack 2 IL_0000: ldarg.1 - IL_0001: ldstr ""field"" + IL_0001: ldstr ""field = "" IL_0006: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" IL_000b: pop IL_000c: ldarg.1 - IL_000d: ldstr "" = "" - IL_0012: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_0017: pop - IL_0018: ldarg.1 - IL_0019: ldarg.0 - IL_001a: ldfld ""T C1.field"" - IL_001f: box ""T"" - IL_0024: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(object)"" - IL_0029: pop - IL_002a: ldc.i4.1 - IL_002b: ret + IL_000d: ldarg.0 + IL_000e: ldfld ""T C1.field"" + IL_0013: box ""T"" + IL_0018: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(object)"" + IL_001d: pop + IL_001e: ldc.i4.1 + IL_001f: ret } "); } @@ -5752,40 +5756,28 @@ record C1 v.VerifyIL("C1." + WellKnownMemberNames.PrintMembersMethodName, @" { - // Code size 88 (0x58) + // Code size 52 (0x34) .maxstack 2 IL_0000: ldarg.1 - IL_0001: ldstr ""field1"" + IL_0001: ldstr ""field1 = "" IL_0006: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" IL_000b: pop IL_000c: ldarg.1 - IL_000d: ldstr "" = "" - IL_0012: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_0017: pop - IL_0018: ldarg.1 - IL_0019: ldarg.0 - IL_001a: ldfld ""string C1.field1"" - IL_001f: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(object)"" + IL_000d: ldarg.0 + IL_000e: ldfld ""string C1.field1"" + IL_0013: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(object)"" + IL_0018: pop + IL_0019: ldarg.1 + IL_001a: ldstr "", field2 = "" + IL_001f: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" IL_0024: pop IL_0025: ldarg.1 - IL_0026: ldstr "", "" - IL_002b: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_0030: pop - IL_0031: ldarg.1 - IL_0032: ldstr ""field2"" - IL_0037: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_003c: pop - IL_003d: ldarg.1 - IL_003e: ldstr "" = "" - IL_0043: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_0048: pop - IL_0049: ldarg.1 - IL_004a: ldarg.0 - IL_004b: ldfld ""string C1.field2"" - IL_0050: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(object)"" - IL_0055: pop - IL_0056: ldc.i4.1 - IL_0057: ret + IL_0026: ldarg.0 + IL_0027: ldfld ""string C1.field2"" + IL_002c: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(object)"" + IL_0031: pop + IL_0032: ldc.i4.1 + IL_0033: ret } "); } @@ -5817,28 +5809,24 @@ record C1(int Property) v.VerifyIL("C1." + WellKnownMemberNames.PrintMembersMethodName, @" { - // Code size 53 (0x35) + // Code size 41 (0x29) .maxstack 2 .locals init (int V_0) IL_0000: ldarg.1 - IL_0001: ldstr ""Property"" + IL_0001: ldstr ""Property = "" IL_0006: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" IL_000b: pop IL_000c: ldarg.1 - IL_000d: ldstr "" = "" - IL_0012: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_0017: pop - IL_0018: ldarg.1 - IL_0019: ldarg.0 - IL_001a: call ""int C1.Property.get"" - IL_001f: stloc.0 - IL_0020: ldloca.s V_0 - IL_0022: constrained. ""int"" - IL_0028: callvirt ""string object.ToString()"" - IL_002d: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_0032: pop - IL_0033: ldc.i4.1 - IL_0034: ret + IL_000d: ldarg.0 + IL_000e: call ""int C1.Property.get"" + IL_0013: stloc.0 + IL_0014: ldloca.s V_0 + IL_0016: constrained. ""int"" + IL_001c: callvirt ""string object.ToString()"" + IL_0021: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_0026: pop + IL_0027: ldc.i4.1 + IL_0028: ret } "); } @@ -5883,9 +5871,9 @@ record C1(int A1, int B1) : Base(A1) comp.VerifyEmitDiagnostics(); var v = CompileAndVerify(comp, expectedOutput: "C1 { A1 = 42, A2 = 100, B1 = 43, B2 = 101 }", verify: Verification.Skipped /* init-only */); - v.VerifyIL("C1.PrintMembers", @" + v.VerifyIL("C1." + WellKnownMemberNames.PrintMembersMethodName, @" { - // Code size 134 (0x86) + // Code size 98 (0x62) .maxstack 2 .locals init (int V_0) IL_0000: ldarg.0 @@ -5897,43 +5885,31 @@ .locals init (int V_0) IL_000f: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" IL_0014: pop IL_0015: ldarg.1 - IL_0016: ldstr ""B1"" + IL_0016: ldstr ""B1 = "" IL_001b: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" IL_0020: pop IL_0021: ldarg.1 - IL_0022: ldstr "" = "" - IL_0027: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_002c: pop - IL_002d: ldarg.1 - IL_002e: ldarg.0 - IL_002f: call ""int C1.B1.get"" - IL_0034: stloc.0 - IL_0035: ldloca.s V_0 - IL_0037: constrained. ""int"" - IL_003d: callvirt ""string object.ToString()"" + IL_0022: ldarg.0 + IL_0023: call ""int C1.B1.get"" + IL_0028: stloc.0 + IL_0029: ldloca.s V_0 + IL_002b: constrained. ""int"" + IL_0031: callvirt ""string object.ToString()"" + IL_0036: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" + IL_003b: pop + IL_003c: ldarg.1 + IL_003d: ldstr "", B2 = "" IL_0042: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" IL_0047: pop IL_0048: ldarg.1 - IL_0049: ldstr "", "" - IL_004e: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_0053: pop - IL_0054: ldarg.1 - IL_0055: ldstr ""B2"" + IL_0049: ldarg.0 + IL_004a: ldflda ""int C1.B2"" + IL_004f: constrained. ""int"" + IL_0055: callvirt ""string object.ToString()"" IL_005a: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" IL_005f: pop - IL_0060: ldarg.1 - IL_0061: ldstr "" = "" - IL_0066: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_006b: pop - IL_006c: ldarg.1 - IL_006d: ldarg.0 - IL_006e: ldflda ""int C1.B2"" - IL_0073: constrained. ""int"" - IL_0079: callvirt ""string object.ToString()"" - IL_007e: callvirt ""System.Text.StringBuilder System.Text.StringBuilder.Append(string)"" - IL_0083: pop - IL_0084: ldc.i4.1 - IL_0085: ret + IL_0060: ldc.i4.1 + IL_0061: ret } "); } @@ -16380,6 +16356,7 @@ public class String { } public abstract class ValueType { } public struct Void { } public struct Boolean { } + public struct Char { } public struct Int32 { } public interface IEquatable { @@ -16391,7 +16368,8 @@ namespace System.Text public class StringBuilder { public StringBuilder Append(string s) => null; - public StringBuilder Append(object s) => null; + public StringBuilder Append(char c) => null; + public StringBuilder Append(object o) => null; } } "; @@ -17323,6 +17301,7 @@ public class String { } public abstract class ValueType { } public struct Void { } public struct Boolean { } + public struct Char { } public struct Int32 { } public interface IEquatable { @@ -17334,7 +17313,8 @@ namespace System.Text public class StringBuilder { public StringBuilder Append(string s) => null; - public StringBuilder Append(object s) => null; + public StringBuilder Append(char c) => null; + public StringBuilder Append(object o) => null; } } @@ -17421,6 +17401,7 @@ public class String { } public abstract class ValueType { } public struct Void { } public struct Boolean { } + public struct Char { } public struct Int32 { } public interface IEquatable { @@ -17432,7 +17413,8 @@ namespace System.Text public class StringBuilder { public StringBuilder Append(string s) => null; - public StringBuilder Append(object s) => null; + public StringBuilder Append(char c) => null; + public StringBuilder Append(object o) => null; } } "; @@ -17515,6 +17497,7 @@ public class String { } public abstract class ValueType { } public struct Void { } public struct Boolean { } + public struct Char { } public struct Int32 { } public interface IEquatable { @@ -17526,7 +17509,8 @@ namespace System.Text public class StringBuilder { public StringBuilder Append(string s) => null; - public StringBuilder Append(object s) => null; + public StringBuilder Append(char c) => null; + public StringBuilder Append(object o) => null; } } "; diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs index 42cd3670adbfe..8d4f8ac776b1e 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs @@ -6531,9 +6531,9 @@ public static void Main() // (21,16): error CS0172: Type of conditional expression cannot be determined because 'Square.Circle' and 'Square' implicitly convert to one another // var o1 = (1 == 1) ? aa : ii; // CS0172 Diagnostic(ErrorCode.ERR_AmbigQM, "(1 == 1) ? aa : ii").WithArguments("Square.Circle", "Square").WithLocation(21, 16), - // (22,19): error CS8400: Feature 'target-typed conditional expression' is not available in C# 8.0. Please use language version 9.0 or greater. + // (22,19): error CS8957: Conditional expression is not valid in language version 8.0 because a common type was not found between 'Square.Circle' and 'Square'. To use a target-typed conversion, upgrade to language version 9.0 or greater. // object o2 = (1 == 1) ? aa : ii; // CS8652 - Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion8, "(1 == 1) ? aa : ii").WithArguments("target-typed conditional expression", "9.0").WithLocation(22, 19) + Diagnostic(ErrorCode.ERR_NoImplicitConvTargetTypedConditional, "(1 == 1) ? aa : ii").WithArguments("8.0", "Square.Circle", "Square", "9.0").WithLocation(22, 19) ); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/TargetTypedConditionalOperatorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/TargetTypedConditionalOperatorTests.cs index 4966e95dab2a7..8144e3d6c4487 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/TargetTypedConditionalOperatorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/TargetTypedConditionalOperatorTests.cs @@ -640,9 +640,10 @@ static B F(bool b, A a) // (15,16): error CS0029: Cannot implicitly convert type 'A' to 'B' // return (b ? a : 0) + a; Diagnostic(ErrorCode.ERR_NoImplicitConv, "(b ? a : 0) + a").WithArguments("A", "B").WithLocation(15, 16), - // (15,17): error CS8370: Feature 'target-typed conditional expression' is not available in C# 7.3. Please use language version 9.0 or greater. + // (15,17): error CS8957: Conditional expression is not valid in language version 7.3 because a common type was not found between 'A' and 'int'. To use a target-typed conversion, upgrade to language version 9.0 or greater. // return (b ? a : 0) + a; - Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_3, "b ? a : 0").WithArguments("target-typed conditional expression", "9.0").WithLocation(15, 17)); + Diagnostic(ErrorCode.ERR_NoImplicitConvTargetTypedConditional, "b ? a : 0").WithArguments("7.3", "A", "int", "9.0").WithLocation(15, 17)); + CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(MessageID.IDS_FeatureTargetTypedConditional.RequiredVersion())).VerifyDiagnostics( // (15,16): error CS0029: Cannot implicitly convert type 'A' to 'B' // return (b ? a : 0) + a; @@ -690,5 +691,27 @@ static void F(bool b, int x) Assert.Null(typeInfo.Type); Assert.Equal("System.Int32?", typeInfo.ConvertedType.ToTestDisplayString()); } + + [Fact] + public void DiagnosticClarity_LangVersion8() + { + var source = @" +class C +{ + void M(bool b) + { + int? i = b ? 1 : null; + } +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular8); + comp.VerifyDiagnostics( + // (6,18): 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. + // int? i = b ? 1 : null; + Diagnostic(ErrorCode.ERR_NoImplicitConvTargetTypedConditional, "b ? 1 : null").WithArguments("8.0", "int", "", "9.0").WithLocation(6, 18)); + + comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/SourceGeneration/StateTableTests.cs b/src/Compilers/CSharp/Test/Semantic/SourceGeneration/StateTableTests.cs index 2a86998678c58..df401904ed1d9 100644 --- a/src/Compilers/CSharp/Test/Semantic/SourceGeneration/StateTableTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/SourceGeneration/StateTableTests.cs @@ -177,6 +177,31 @@ public void Node_Builder_Handles_Modification_When_Both_Tables_Have_Empty_Entrie AssertTableEntries(newTable, expected); } + [Fact] + public void Node_Table_Doesnt_Modify_Single_Item_Multiple_Times_When_Same() + { + var builder = NodeStateTable.Empty.ToBuilder(); + builder.AddEntries(ImmutableArray.Create(1), EntryState.Added); + builder.AddEntries(ImmutableArray.Create(2), EntryState.Added); + builder.AddEntries(ImmutableArray.Create(3), EntryState.Added); + builder.AddEntries(ImmutableArray.Create(4), EntryState.Added); + var previousTable = builder.ToImmutableAndFree(); + + var expected = ImmutableArray.Create((1, EntryState.Added), (2, EntryState.Added), (3, EntryState.Added), (4, EntryState.Added)); + AssertTableEntries(previousTable, expected); + + builder = previousTable.ToBuilder(); + Assert.True(builder.TryModifyEntry(1, EqualityComparer.Default)); // ((1, EntryState.Cached)) + Assert.True(builder.TryModifyEntry(2, EqualityComparer.Default)); // ((2, EntryState.Cached)) + Assert.True(builder.TryModifyEntry(5, EqualityComparer.Default)); // ((5, EntryState.Modified)) + Assert.True(builder.TryModifyEntry(4, EqualityComparer.Default)); // ((4, EntryState.Cached)) + + var newTable = builder.ToImmutableAndFree(); + + expected = ImmutableArray.Create((1, EntryState.Cached), (2, EntryState.Cached), (5, EntryState.Modified), (4, EntryState.Cached)); + AssertTableEntries(newTable, expected); + } + [Fact] public void Driver_Table_Calls_Into_Node_With_Self() { diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/ImmutableArrayExtensionsTests.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/ImmutableArrayExtensionsTests.cs index 0672c3fbc5229..be6724db373c3 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/ImmutableArrayExtensionsTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/ImmutableArrayExtensionsTests.cs @@ -380,17 +380,6 @@ public void SelectAsArrayWithPredicate() Assert.Empty(array.SelectAsArray(item => item < 0, item => throw null)); } - [Fact] - public void DangerousCreateFromUnderlyingArray() - { - var array = new[] { 1, 2, 3, 4 }; - var copy = array; - var immutable = ImmutableArrayExtensions.DangerousCreateFromUnderlyingArray(ref copy); - Assert.Null(copy); - AssertEx.Equal(array, immutable); - Assert.Same(array, ImmutableArrayExtensions.DangerousGetUnderlyingArray(immutable)); - } - [Fact] public void ZipAsArray() { diff --git a/src/Compilers/Core/CodeAnalysisTest/CommonCommandLineParserTests.cs b/src/Compilers/Core/CodeAnalysisTest/CommonCommandLineParserTests.cs index f80799b530052..ab436d334ce56 100644 --- a/src/Compilers/Core/CodeAnalysisTest/CommonCommandLineParserTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/CommonCommandLineParserTests.cs @@ -7,7 +7,9 @@ using System; using System.Globalization; using System.Linq; +using System.Runtime.InteropServices; using System.Threading; +using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Test.Utilities; using Xunit; @@ -1106,20 +1108,23 @@ public void GetEffectiveIncludes_TwoLevels() Assert.Equal(expected: include2.Path, actual: includePaths[2]); } + private string[] ParseSeparatedStrings(string arg, char[] separators, bool removeEmptyEntries = true) + { + var builder = ArrayBuilder>.GetInstance(); + CommandLineParser.ParseSeparatedStrings(arg.AsMemory(), separators, removeEmptyEntries, builder); + return builder.Select(x => x.ToString()).ToArray(); + } + [Fact] public void ParseSeparatedStrings_ExcludeSeparatorChar() { Assert.Equal( - CommandLineParser.ParseSeparatedStrings(@"a,b", new[] { ',' }, StringSplitOptions.RemoveEmptyEntries), + ParseSeparatedStrings(@"a,b", new[] { ',' }), new[] { "a", "b" }); Assert.Equal( - CommandLineParser.ParseSeparatedStrings(@"a,,b", new[] { ',' }, StringSplitOptions.RemoveEmptyEntries), + ParseSeparatedStrings(@"a,,b", new[] { ',' }), new[] { "a", "b" }); - - Assert.Equal( - CommandLineParser.ParseSeparatedStrings(@"a,,b", new[] { ',' }, StringSplitOptions.None), - new[] { "a", "", "b" }); } /// @@ -1130,15 +1135,15 @@ public void ParseSeparatedStrings_ExcludeSeparatorChar() public void ParseSeparatedStrings_IncludeQuotes() { Assert.Equal( - CommandLineParser.ParseSeparatedStrings(@"""a"",b", new[] { ',' }, StringSplitOptions.RemoveEmptyEntries), + ParseSeparatedStrings(@"""a"",b", new[] { ',' }), new[] { @"""a""", "b" }); Assert.Equal( - CommandLineParser.ParseSeparatedStrings(@"""a,b""", new[] { ',' }, StringSplitOptions.RemoveEmptyEntries), + ParseSeparatedStrings(@"""a,b""", new[] { ',' }), new[] { @"""a,b""" }); Assert.Equal( - CommandLineParser.ParseSeparatedStrings(@"""a"",""b", new[] { ',' }, StringSplitOptions.RemoveEmptyEntries), + ParseSeparatedStrings(@"""a"",""b", new[] { ',' }), new[] { @"""a""", @"""b" }); } @@ -1228,7 +1233,7 @@ public void SplitCommandLineIntoArguments_Quotes() /// as \"\\test.cs\". /// [Fact] - public void RemoveQuotes() + public void RemoveQuotesAndSlashes() { Assert.Equal(@"\\test.cs", CommandLineParser.RemoveQuotesAndSlashes(@"\\test.cs")); Assert.Equal(@"\\test.cs", CommandLineParser.RemoveQuotesAndSlashes(@"""\\test.cs""")); @@ -1240,6 +1245,27 @@ public void RemoveQuotes() Assert.Equal(@"a"" mid ""b.cs", CommandLineParser.RemoveQuotesAndSlashes(@"a\"" mid \""b.cs")); Assert.Equal(@"a mid b.cs", CommandLineParser.RemoveQuotesAndSlashes(@"a"" mid ""b.cs")); Assert.Equal(@"a.cs", CommandLineParser.RemoveQuotesAndSlashes(@"""a.cs""")); + Assert.Equal(@"C:""My Folder\MyBinary.xml", CommandLineParser.RemoveQuotesAndSlashes(@"C:\""My Folder""\MyBinary.xml")); + } + + /// + /// Verify that for the standard cases we do not allocate new memory but instead + /// return a to the existing string + /// + [Fact] + public void RemoveQuotesAndSlashes_NoAllocation() + { + assertSame(@"c:\test.cs"); + assertSame(@"""c:\test.cs"""); + assertSame(@"""c:\\\\\\\\\test.cs"""); + assertSame(@"""c:\\\\\\\\\test.cs"""); + + void assertSame(string arg) + { + var memory = CommandLineParser.RemoveQuotesAndSlashesEx(arg.AsMemory()); + Assert.True(MemoryMarshal.TryGetString(memory, out var memoryString, out _, out _)); + Assert.Same(arg, memoryString); + } } } } diff --git a/src/Compilers/Core/CodeAnalysisTest/Emit/EmitBaselineTests.cs b/src/Compilers/Core/CodeAnalysisTest/Emit/EmitBaselineTests.cs index 139140eda3899..d9201a9a07e41 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Emit/EmitBaselineTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Emit/EmitBaselineTests.cs @@ -16,7 +16,7 @@ namespace Microsoft.CodeAnalysis.UnitTests.Emit public class EmitBaselineTests { [Fact] - public void CreateInitialBaseline_Errors() + public unsafe void CreateInitialBaseline_Errors() { var debugInfoProvider = new Func(_ => default); var localSigProvider = new Func(_ => default); @@ -24,17 +24,19 @@ public void CreateInitialBaseline_Errors() var peReader = peModule.Module.PEReaderOpt; var mdBytes = peReader.GetMetadata().GetContent(); - var mdBytesHandle = GCHandle.Alloc(mdBytes.DangerousGetUnderlyingArray(), GCHandleType.Pinned); - var mdModule = ModuleMetadata.CreateFromMetadata(mdBytesHandle.AddrOfPinnedObject(), mdBytes.Length); + fixed (byte* mdBytesPointer = mdBytes.AsSpan()) + { + var mdModule = ModuleMetadata.CreateFromMetadata((IntPtr)mdBytesPointer, mdBytes.Length); - Assert.Throws(() => EmitBaseline.CreateInitialBaseline(null, debugInfoProvider)); - Assert.Throws(() => EmitBaseline.CreateInitialBaseline(peModule, null)); - Assert.Throws(() => EmitBaseline.CreateInitialBaseline(mdModule, debugInfoProvider)); + Assert.Throws(() => EmitBaseline.CreateInitialBaseline(null, debugInfoProvider)); + Assert.Throws(() => EmitBaseline.CreateInitialBaseline(peModule, null)); + Assert.Throws(() => EmitBaseline.CreateInitialBaseline(mdModule, debugInfoProvider)); - Assert.Throws(() => EmitBaseline.CreateInitialBaseline(null, debugInfoProvider, localSigProvider, true)); - Assert.Throws(() => EmitBaseline.CreateInitialBaseline(peModule, null, localSigProvider, true)); - Assert.Throws(() => EmitBaseline.CreateInitialBaseline(mdModule, debugInfoProvider, null, true)); - Assert.NotNull(EmitBaseline.CreateInitialBaseline(mdModule, debugInfoProvider, localSigProvider, true)); + Assert.Throws(() => EmitBaseline.CreateInitialBaseline(null, debugInfoProvider, localSigProvider, true)); + Assert.Throws(() => EmitBaseline.CreateInitialBaseline(peModule, null, localSigProvider, true)); + Assert.Throws(() => EmitBaseline.CreateInitialBaseline(mdModule, debugInfoProvider, null, true)); + Assert.NotNull(EmitBaseline.CreateInitialBaseline(mdModule, debugInfoProvider, localSigProvider, true)); + } } } } diff --git a/src/Compilers/Core/MSBuildTask/Microsoft.Build.Tasks.CodeAnalysis.csproj b/src/Compilers/Core/MSBuildTask/Microsoft.Build.Tasks.CodeAnalysis.csproj index 8e18c9cd89601..46f844e7edc5c 100644 --- a/src/Compilers/Core/MSBuildTask/Microsoft.Build.Tasks.CodeAnalysis.csproj +++ b/src/Compilers/Core/MSBuildTask/Microsoft.Build.Tasks.CodeAnalysis.csproj @@ -54,6 +54,8 @@ + + diff --git a/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs b/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs index b2f8948f09f10..66d29b193e4f2 100644 --- a/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs +++ b/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs @@ -737,27 +737,6 @@ public static int Count(this ImmutableArray items, Func predicate return count; } - [StructLayout(LayoutKind.Sequential)] - private struct ImmutableArrayProxy - { - internal T[] MutableArray; - } - - // TODO(https://github.com/dotnet/corefx/issues/34126): Remove when System.Collections.Immutable - // provides a Span API - internal static T[] DangerousGetUnderlyingArray(this ImmutableArray array) - => Unsafe.As, ImmutableArrayProxy>(ref array).MutableArray; - - internal static ReadOnlySpan AsSpan(this ImmutableArray array) - => array.DangerousGetUnderlyingArray(); - - internal static ImmutableArray DangerousCreateFromUnderlyingArray([MaybeNull] ref T[] array) - { - var proxy = new ImmutableArrayProxy { MutableArray = array }; - array = null!; - return Unsafe.As, ImmutableArray>(ref proxy); - } - internal static Dictionary> ToDictionary(this ImmutableArray items, Func keySelector, IEqualityComparer? comparer = null) where K : notnull { diff --git a/src/Compilers/Core/Portable/CommandLine/CommandLineParser.cs b/src/Compilers/Core/Portable/CommandLine/CommandLineParser.cs index 30660b895886a..9c6219b762728 100644 --- a/src/Compilers/Core/Portable/CommandLine/CommandLineParser.cs +++ b/src/Compilers/Core/Portable/CommandLine/CommandLineParser.cs @@ -80,16 +80,81 @@ public CommandLineArguments Parse(IEnumerable args, string baseDirectory return CommonParse(args, baseDirectory, sdkDirectory, additionalReferenceDirectories); } - private static bool IsOption(string arg) + internal static bool IsOptionName(string optionName, ReadOnlyMemory value) => + IsOptionName(optionName, value.Span); + + internal static bool IsOptionName(string shortOptionName, string longOptionName, ReadOnlyMemory value) => + IsOptionName(shortOptionName, value) || IsOptionName(longOptionName, value); + + /// + /// Determines if a is equal to the provided option name + /// + /// + /// Prefer this over the Equals methods on . The + /// implementation allocates a . + /// The 99% case here is that we are dealing with an ASCII string that matches the input hence + /// it's worth special casing that here and falling back to the more complicated comparison + /// when dealing with non-ASCII input + /// + internal static bool IsOptionName(string optionName, ReadOnlySpan value) { - return !string.IsNullOrEmpty(arg) && (arg[0] == '/' || arg[0] == '-'); + Debug.Assert(isAllAscii(optionName.AsSpan())); + if (isAllAscii(value)) + { + if (optionName.Length != value.Length) + return false; + + for (int i = 0; i < optionName.Length; i++) + { + if (optionName[i] != char.ToLowerInvariant(value[i])) + { + return false; + } + } + return true; + } + + return optionName.AsSpan().Equals(value, StringComparison.InvariantCultureIgnoreCase); + + static bool isAllAscii(ReadOnlySpan span) + { + foreach (char ch in span) + { + if (ch > 127) + return false; + } + return true; + } } + internal static bool IsOption(string arg) => IsOption(arg.AsSpan()); + + internal static bool IsOption(ReadOnlySpan arg) => + arg.Length > 0 && (arg[0] == '/' || arg[0] == '-'); + + internal static bool IsOption(string optionName, string arg, out ReadOnlyMemory name, out ReadOnlyMemory? value) => + TryParseOption(arg, out name, out value) && + IsOptionName(optionName, name); + internal static bool TryParseOption(string arg, [NotNullWhen(true)] out string? name, out string? value) + { + if (TryParseOption(arg, out ReadOnlyMemory nameMemory, out ReadOnlyMemory? valueMemory)) + { + name = nameMemory.ToString().ToLowerInvariant(); + value = valueMemory?.ToString(); + return true; + } + + name = null; + value = null; + return false; + } + + internal static bool TryParseOption(string arg, out ReadOnlyMemory name, out ReadOnlyMemory? value) { if (!IsOption(arg)) { - name = null; + name = default; value = null; return false; } @@ -97,7 +162,7 @@ internal static bool TryParseOption(string arg, [NotNullWhen(true)] out string? // handle stdin operator if (arg == "-") { - name = arg; + name = arg.AsMemory(); value = null; return true; } @@ -115,76 +180,83 @@ internal static bool TryParseOption(string arg, [NotNullWhen(true)] out string? { // "/goo/ // "// - name = null; + name = default; value = null; return false; } } + var argMemory = arg.AsMemory(); if (colon >= 0) { - name = arg.Substring(1, colon - 1); - value = arg.Substring(colon + 1); + name = argMemory.Slice(1, colon - 1); + value = argMemory.Slice(colon + 1); } else { - name = arg.Substring(1); + name = argMemory.Slice(1); value = null; } - name = name.ToLowerInvariant(); return true; } internal ErrorLogOptions? ParseErrorLogOptions( - string arg, + ReadOnlyMemory arg, IList diagnostics, string? baseDirectory, out bool diagnosticAlreadyReported) { diagnosticAlreadyReported = false; - IEnumerator partsEnumerator = ParseSeparatedStrings(arg, s_pathSeparators, StringSplitOptions.RemoveEmptyEntries).GetEnumerator(); - if (!partsEnumerator.MoveNext() || string.IsNullOrEmpty(partsEnumerator.Current)) + var parts = ArrayBuilder>.GetInstance(); + try { - return null; - } + ParseSeparatedStrings(arg, s_pathSeparators, removeEmptyEntries: true, parts); + if (parts.Count == 0 || parts[0].Length == 0) + { + return null; + } - string? path = ParseGenericPathToFile(partsEnumerator.Current, diagnostics, baseDirectory); - if (path is null) - { - // ParseGenericPathToFile already reported the failure, so the caller should not - // report its own failure. - diagnosticAlreadyReported = true; - return null; - } + string? path = ParseGenericPathToFile(parts[0].ToString(), diagnostics, baseDirectory); + if (path is null) + { + // ParseGenericPathToFile already reported the failure, so the caller should not + // report its own failure. + diagnosticAlreadyReported = true; + return null; + } - const char ParameterNameValueSeparator = '='; - SarifVersion sarifVersion = SarifVersion.Default; + const char ParameterNameValueSeparator = '='; + SarifVersion sarifVersion = SarifVersion.Default; + if (parts.Count > 1 && parts[1].Length > 0) + { + string part = parts[1].ToString(); - if (partsEnumerator.MoveNext() && !string.IsNullOrEmpty(partsEnumerator.Current)) - { - string part = partsEnumerator.Current; + string versionParameterDesignator = "version" + ParameterNameValueSeparator; + int versionParameterDesignatorLength = versionParameterDesignator.Length; - string versionParameterDesignator = "version" + ParameterNameValueSeparator; - int versionParameterDesignatorLength = versionParameterDesignator.Length; + if (!( + part.Length > versionParameterDesignatorLength && + part.Substring(0, versionParameterDesignatorLength).Equals(versionParameterDesignator, StringComparison.OrdinalIgnoreCase) && + SarifVersionFacts.TryParse(part.Substring(versionParameterDesignatorLength), out sarifVersion) + )) + { + return null; + } + } - if (!( - part.Length > versionParameterDesignatorLength && - part.Substring(0, versionParameterDesignatorLength).Equals(versionParameterDesignator, StringComparison.OrdinalIgnoreCase) && - SarifVersionFacts.TryParse(part.Substring(versionParameterDesignatorLength), out sarifVersion) - )) + if (parts.Count > 2) { return null; } - } - if (partsEnumerator.MoveNext()) + return new ErrorLogOptions(path, sarifVersion); + } + finally { - return null; + parts.Free(); } - - return new ErrorLogOptions(path, sarifVersion); } internal static void ParseAndNormalizeFile( @@ -411,7 +483,7 @@ internal void ParseOutputFile( internal void FlattenArgs( IEnumerable rawArguments, IList diagnostics, - List processedArgs, + ArrayBuilder processedArgs, List? scriptArgsOpt, string? baseDirectory, List? responsePaths = null) @@ -420,13 +492,17 @@ internal void FlattenArgs( bool sourceFileSeen = false; bool optionsEnded = false; - var args = new Stack(rawArguments.Reverse()); - while (args.Count > 0) + var args = ArrayBuilder.GetInstance(); + args.AddRange(rawArguments); + args.ReverseContents(); + var argsIndex = args.Count - 1; + while (argsIndex >= 0) { // EDMAURER trim off whitespace. Otherwise behavioral differences arise // when the strings which represent args are constructed by cmd or users. // cmd won't produce args with whitespace at the end. - string arg = args.Pop().TrimEnd(); + string arg = args[argsIndex].TrimEnd(); + argsIndex--; if (parsingScriptArgs) { @@ -467,18 +543,7 @@ internal void FlattenArgs( string? resolvedPath = FileUtilities.ResolveRelativePath(path, baseDirectory); if (resolvedPath != null) { - foreach (string newArg in ParseResponseFile(resolvedPath, diagnostics).Reverse()) - { - // Ignores /noconfig option specified in a response file - if (!string.Equals(newArg, "/noconfig", StringComparison.OrdinalIgnoreCase) && !string.Equals(newArg, "-noconfig", StringComparison.OrdinalIgnoreCase)) - { - args.Push(newArg); - } - else - { - diagnostics.Add(Diagnostic.Create(_messageProvider, _messageProvider.WRN_NoConfigNotOnCommandLine)); - } - } + parseResponseFile(resolvedPath); if (responsePaths != null) { @@ -504,6 +569,108 @@ internal void FlattenArgs( sourceFileSeen |= optionsEnded || !IsOption(arg); } } + args.Free(); + + void parseResponseFile(string fullPath) + { + var stringBuilder = PooledStringBuilder.GetInstance(); + var splitList = new List(); + + try + { + Debug.Assert(PathUtilities.IsAbsolute(fullPath)); + using TextReader reader = CreateTextFileReader(fullPath); + Span lineBuffer = stackalloc char[256]; + var lineBufferLength = 0; + while (true) + { + var ch = reader.Read(); + if (ch == -1) + { + if (lineBufferLength > 0) + { + stringBuilder.Builder.Length = 0; + CommandLineUtilities.SplitCommandLineIntoArguments( + lineBuffer.Slice(0, lineBufferLength), + removeHashComments: true, + stringBuilder.Builder, + splitList, + out _); + } + break; + } + + if (ch is '\r' or '\n') + { + if (ch is '\r' && reader.Peek() == '\n') + { + reader.Read(); + } + + stringBuilder.Builder.Length = 0; + CommandLineUtilities.SplitCommandLineIntoArguments( + lineBuffer.Slice(0, lineBufferLength), + removeHashComments: true, + stringBuilder.Builder, + splitList, + out _); + lineBufferLength = 0; + } + else + { + if (lineBufferLength >= lineBuffer.Length) + { + var temp = new char[lineBuffer.Length * 2]; + lineBuffer.CopyTo(temp.AsSpan()); + lineBuffer = temp; + } + + lineBuffer[lineBufferLength] = (char)ch; + lineBufferLength++; + } + } + } + catch (Exception) + { + diagnostics.Add(Diagnostic.Create(_messageProvider, _messageProvider.ERR_OpenResponseFile, fullPath)); + return; + } + + for (var i = splitList.Count - 1; i >= 0; i--) + { + var newArg = splitList[i]; + // Ignores /noconfig option specified in a response file + if (!string.Equals(newArg, "/noconfig", StringComparison.OrdinalIgnoreCase) && !string.Equals(newArg, "-noconfig", StringComparison.OrdinalIgnoreCase)) + { + argsIndex++; + if (argsIndex < args.Count) + { + args[argsIndex] = newArg; + } + else + { + args.Add(newArg); + } + } + else + { + diagnostics.Add(Diagnostic.Create(_messageProvider, _messageProvider.WRN_NoConfigNotOnCommandLine)); + } + } + + stringBuilder.Free(); + } + } + + internal static IEnumerable ParseResponseLines(IEnumerable lines) + { + var arguments = new List(); + foreach (string line in lines) + { + arguments.AddRange(CommandLineUtilities.SplitCommandLineIntoArguments(line, removeHashComments: true)); + } + + return arguments; } /// @@ -636,51 +803,10 @@ static bool isClientArgsOption(string arg, string optionName, out bool hasValue, internal static string MismatchedVersionErrorText => CodeAnalysisResources.MismatchedVersion; - /// - /// Parse a response file into a set of arguments. Errors opening the response file are output into "errors". - /// - internal IEnumerable ParseResponseFile(string fullPath, IList errors) - { - List lines = new List(); - try - { - Debug.Assert(PathUtilities.IsAbsolute(fullPath)); - using TextReader reader = CreateTextFileReader(fullPath); - string? str; - while ((str = reader.ReadLine()) != null) - { - lines.Add(str); - } - } - catch (Exception) - { - errors.Add(Diagnostic.Create(_messageProvider, _messageProvider.ERR_OpenResponseFile, fullPath)); - return SpecializedCollections.EmptyEnumerable(); - } - - return ParseResponseLines(lines); - } - - /// - /// Take a string of lines from a response file, remove comments, - /// and split into a set of command line arguments. - /// - internal static IEnumerable ParseResponseLines(IEnumerable lines) - { - List arguments = new List(); - - foreach (string line in lines) - { - arguments.AddRange(SplitCommandLineIntoArguments(line, removeHashComments: true)); - } - - return arguments; - } - private static readonly char[] s_resourceSeparators = { ',' }; internal static void ParseResourceDescription( - string resourceDescriptor, + ReadOnlyMemory resourceDescriptor, string? baseDirectory, bool skipLeadingSeparators, //VB does this out string? filePath, @@ -696,15 +822,16 @@ internal static void ParseResourceDescription( accessibility = null; // resource descriptor is: "[,[,public|private]]" - string[] parts = ParseSeparatedStrings(resourceDescriptor, s_resourceSeparators).ToArray(); + var parts = ArrayBuilder>.GetInstance(); + ParseSeparatedStrings(resourceDescriptor, s_resourceSeparators, removeEmptyEntries: false, parts); int offset = 0; - int length = parts.Length; + int length = parts.Count; if (skipLeadingSeparators) { - for (; offset < length && string.IsNullOrEmpty(parts[offset]); offset++) + for (; offset < length && parts[offset].Length == 0; offset++) { } @@ -727,6 +854,7 @@ internal static void ParseResourceDescription( accessibility = RemoveQuotesAndSlashes(parts[offset + 2]); } + parts.Free(); if (RoslynString.IsNullOrWhiteSpace(filePath)) { return; @@ -761,15 +889,34 @@ public static IEnumerable SplitCommandLineIntoArguments(string commandLi /// quirks in error cases. /// [return: NotNullIfNotNull(parameterName: "arg")] - internal static string? RemoveQuotesAndSlashes(string? arg) + internal static string? RemoveQuotesAndSlashes(string? arg) => + arg is not null + ? RemoveQuotesAndSlashes(arg.AsMemory()) + : null; + + internal static string RemoveQuotesAndSlashes(ReadOnlyMemory argMemory) => + RemoveQuotesAndSlashesEx(argMemory).ToString(); + + internal static string? RemoveQuotesAndSlashes(ReadOnlyMemory? argMemory) => + argMemory is { } m + ? RemoveQuotesAndSlashesEx(m).ToString() + : null; + + internal static ReadOnlyMemory? RemoveQuotesAndSlashesEx(ReadOnlyMemory? argMemory) => + argMemory is { } m + ? RemoveQuotesAndSlashesEx(m) + : null; + + internal static ReadOnlyMemory RemoveQuotesAndSlashesEx(ReadOnlyMemory argMemory) { - if (arg == null) + if (removeFastPath(argMemory) is { } m) { - return arg; + return m; } var pool = PooledStringBuilder.GetInstance(); var builder = pool.Builder; + var arg = argMemory.Span; var i = 0; while (i < arg.Length) { @@ -777,7 +924,7 @@ public static IEnumerable SplitCommandLineIntoArguments(string commandLi switch (cur) { case '\\': - ProcessSlashes(builder, arg, ref i); + processSlashes(builder, arg, ref i); break; case '"': // Intentionally dropping quotes that don't have explicit escaping. @@ -790,108 +937,146 @@ public static IEnumerable SplitCommandLineIntoArguments(string commandLi } } - return pool.ToStringAndFree(); - } - - /// - /// Mimic behavior of the native function by the same name. - /// - internal static void ProcessSlashes(StringBuilder builder, string arg, ref int i) - { - RoslynDebug.Assert(arg != null); - Debug.Assert(i < arg.Length); + return pool.ToStringAndFree().AsMemory(); - var slashCount = 0; - while (i < arg.Length && arg[i] == '\\') + // Mimic behavior of the native function by the same name. + static void processSlashes(StringBuilder builder, ReadOnlySpan arg, ref int i) { - slashCount++; - i++; - } + RoslynDebug.Assert(arg != null); + Debug.Assert(i < arg.Length); - if (i < arg.Length && arg[i] == '"') - { - // Before a quote slashes are interpretted as escape sequences for other slashes so - // output one for every two. - while (slashCount >= 2) + var slashCount = 0; + while (i < arg.Length && arg[i] == '\\') { - builder.Append('\\'); - slashCount -= 2; + slashCount++; + i++; } - Debug.Assert(slashCount >= 0); - - // If there is an odd number of slashes then the quote is escaped and hence a part - // of the output. Otherwise it is a normal quote and can be ignored. - if (slashCount == 1) + if (i < arg.Length && arg[i] == '"') { - // The quote is escaped so eat it. - builder.Append('"'); - } + // Before a quote slashes are interpretted as escape sequences for other slashes so + // output one for every two. + while (slashCount >= 2) + { + builder.Append('\\'); + slashCount -= 2; + } - i++; - } - else - { - // Slashes that aren't followed by quotes are simply slashes. - while (slashCount > 0) + Debug.Assert(slashCount >= 0); + + // If there is an odd number of slashes then the quote is escaped and hence a part + // of the output. Otherwise it is a normal quote and can be ignored. + if (slashCount == 1) + { + // The quote is escaped so eat it. + builder.Append('"'); + } + + i++; + } + else { - builder.Append('\\'); - slashCount--; + // Slashes that aren't followed by quotes are simply slashes. + while (slashCount > 0) + { + builder.Append('\\'); + slashCount--; + } } } - } - /// - /// Split a string, based on whether "splitHere" returned true on each character. - /// - private static IEnumerable Split(string? str, Func splitHere) - { - if (str == null) + // The 99% case when using MSBuild is that at worst a path has quotes at the start and + // end of the string but no where else. When that happens there is no need to allocate + // a new string here and instead we can just do a simple Slice on the existing + // ReadOnlyMemory object. + // + // This removes one of the largest allocation paths during command line parsing + static ReadOnlyMemory? removeFastPath(ReadOnlyMemory arg) { - yield break; - } + int start = 0; + int end = arg.Length; + var span = arg.Span; - int nextPiece = 0; + while (end > 0 && span[end - 1] == '"') + { + end--; + } - for (int c = 0; c < str.Length; c++) - { - if (splitHere(str[c])) + while (start < end && span[start] == '"') { - yield return str.Substring(nextPiece, c - nextPiece); - nextPiece = c + 1; + start++; } - } - yield return str.Substring(nextPiece); + for (int i = start; i < end; i++) + { + if (span[i] == '"') + { + return null; + } + } + + return arg.Slice(start, end - start); + } } private static readonly char[] s_pathSeparators = { ';', ',' }; private static readonly char[] s_wildcards = new[] { '*', '?' }; - internal static IEnumerable ParseSeparatedPaths(string? str) + internal static IEnumerable ParseSeparatedPaths(string arg) + { + var builder = ArrayBuilder>.GetInstance(); + ParseSeparatedPathsEx(arg.AsMemory(), builder); + return builder.ToArrayAndFree().Select(static x => x.ToString()); + } + + internal static void ParseSeparatedPathsEx(ReadOnlyMemory? str, ArrayBuilder> builder) { - return ParseSeparatedStrings(str, s_pathSeparators, StringSplitOptions.RemoveEmptyEntries).Select(RemoveQuotesAndSlashes)!; + ParseSeparatedStrings(str, s_pathSeparators, removeEmptyEntries: true, builder); + for (var i = 0; i < builder.Count; i++) + { + builder[i] = RemoveQuotesAndSlashesEx(builder[i]); + } } /// /// Split a string by a set of separators, taking quotes into account. /// - internal static IEnumerable ParseSeparatedStrings(string? str, char[] separators, StringSplitOptions options = StringSplitOptions.None) + internal static void ParseSeparatedStrings(ReadOnlyMemory? strMemory, char[] separators, bool removeEmptyEntries, ArrayBuilder> builder) { - bool inQuotes = false; + if (strMemory is null) + { + return; + } - var result = Split(str, - (c => + int nextPiece = 0; + var inQuotes = false; + var memory = strMemory.Value; + var span = memory.Span; + for (int i = 0; i < span.Length; i++) + { + var c = span[i]; + if (c == '\"') { - if (c == '\"') + inQuotes = !inQuotes; + } + + if (!inQuotes && separators.IndexOf(c) >= 0) + { + var current = memory.Slice(nextPiece, i - nextPiece); + if (!removeEmptyEntries || current.Length > 0) { - inQuotes = !inQuotes; + builder.Add(current); } - return !inQuotes && separators.Contains(c); - })); + nextPiece = i + 1; + } + } - return (options == StringSplitOptions.RemoveEmptyEntries) ? result.Where(s => s.Length > 0) : result; + var last = memory.Slice(nextPiece); + if (!removeEmptyEntries || last.Length > 0) + { + builder.Add(last); + } } internal IEnumerable ResolveRelativePaths(IEnumerable paths, string baseDirectory, IList errors) @@ -912,12 +1097,11 @@ internal IEnumerable ResolveRelativePaths(IEnumerable paths, str private protected CommandLineSourceFile ToCommandLineSourceFile(string resolvedPath, bool isInputRedirected = false) { - string extension = PathUtilities.GetExtension(resolvedPath); - bool isScriptFile; if (IsScriptCommandLineParser) { - isScriptFile = !string.Equals(extension, RegularFileExtension, StringComparison.OrdinalIgnoreCase); + ReadOnlyMemory extension = PathUtilities.GetExtension(resolvedPath.AsMemory()); + isScriptFile = !extension.Span.Equals(RegularFileExtension.AsSpan(), StringComparison.OrdinalIgnoreCase); } else { @@ -929,9 +1113,9 @@ private protected CommandLineSourceFile ToCommandLineSourceFile(string resolvedP return new CommandLineSourceFile(resolvedPath, isScriptFile, isInputRedirected); } - internal IEnumerable ParseFileArgument(string arg, string? baseDirectory, IList errors) + internal void ParseFileArgument(ReadOnlyMemory arg, string? baseDirectory, ArrayBuilder filePathBuilder, IList errors) { - Debug.Assert(IsScriptCommandLineParser || !arg.StartsWith("-", StringComparison.Ordinal) && !arg.StartsWith("@", StringComparison.Ordinal)); + Debug.Assert(IsScriptCommandLineParser || !arg.StartsWith('-') && !arg.StartsWith('@')); // We remove all doubles quotes from a file name. So that, for example: // "Path With Spaces"\goo.cs @@ -939,13 +1123,12 @@ internal IEnumerable ParseFileArgument(string arg, string? baseDirectory // Path With Spaces\goo.cs string path = RemoveQuotesAndSlashes(arg); - int wildcard = path.IndexOfAny(s_wildcards); if (wildcard != -1) { foreach (var file in ExpandFileNamePattern(path, baseDirectory, SearchOption.TopDirectoryOnly, errors)) { - yield return file; + filePathBuilder.Add(file); } } else @@ -957,20 +1140,36 @@ internal IEnumerable ParseFileArgument(string arg, string? baseDirectory } else { - yield return resolvedPath; + filePathBuilder.Add(resolvedPath); } } } - private protected IEnumerable ParseSeparatedFileArgument(string value, string? baseDirectory, IList errors) + private protected void ParseSeparatedFileArgument(ReadOnlyMemory value, string? baseDirectory, ArrayBuilder filePathBuilder, IList errors) { - foreach (string path in ParseSeparatedPaths(value).Where((path) => !string.IsNullOrWhiteSpace(path))) + var pathBuilder = ArrayBuilder>.GetInstance(); + ParseSeparatedPathsEx(value, pathBuilder); + foreach (ReadOnlyMemory path in pathBuilder) { - foreach (var file in ParseFileArgument(path, baseDirectory, errors)) + if (path.IsWhiteSpace()) { - yield return file; + continue; } + + ParseFileArgument(path, baseDirectory, filePathBuilder, errors); + } + pathBuilder.Free(); + } + + private protected IEnumerable ParseSeparatedFileArgument(string value, string? baseDirectory, IList errors) + { + var builder = ArrayBuilder.GetInstance(); + ParseSeparatedFileArgument(value.AsMemory(), baseDirectory, builder, errors); + foreach (var filePath in builder) + { + yield return filePath; } + builder.Free(); } internal IEnumerable ParseRecurseArgument(string arg, string? baseDirectory, IList errors) diff --git a/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs b/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs index c936acfede87e..11c8a1220ab40 100644 --- a/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs +++ b/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs @@ -1626,7 +1626,7 @@ private static void EmitDeterminismKey(CommandLineArguments args, string[] rawAr private static string CreateDeterminismKey(CommandLineArguments args, string[] rawArgs, string baseDirectory, CommandLineParser parser) { List diagnostics = new List(); - List flattenedArgs = new List(); + var flattenedArgs = ArrayBuilder.GetInstance(); parser.FlattenArgs(rawArgs, diagnostics, flattenedArgs, null, baseDirectory); var builder = new StringBuilder(); @@ -1662,6 +1662,7 @@ private static string CreateDeterminismKey(CommandLineArguments args, string[] r builder.AppendLine($"\t{sourceFileName} - {hashValue}"); } + flattenedArgs.Free(); return builder.ToString(); } } diff --git a/src/Compilers/Core/Portable/FileSystem/PathUtilities.cs b/src/Compilers/Core/Portable/FileSystem/PathUtilities.cs index b3b7cedccfe78..9c652bc46138a 100644 --- a/src/Compilers/Core/Portable/FileSystem/PathUtilities.cs +++ b/src/Compilers/Core/Portable/FileSystem/PathUtilities.cs @@ -93,6 +93,11 @@ public static string GetExtension(string path) return FileNameUtilities.GetExtension(path); } + public static ReadOnlyMemory GetExtension(ReadOnlyMemory path) + { + return FileNameUtilities.GetExtension(path); + } + public static string ChangeExtension(string path, string? extension) { return FileNameUtilities.ChangeExtension(path, extension); diff --git a/src/Compilers/Core/Portable/InternalUtilities/CommandLineUtilities.cs b/src/Compilers/Core/Portable/InternalUtilities/CommandLineUtilities.cs index 2141417da0ace..91d5edf3acaab 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/CommandLineUtilities.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/CommandLineUtilities.cs @@ -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. +using System; using System.Collections.Generic; using System.Text; @@ -45,17 +46,23 @@ internal static class CommandLineUtilities /// and the double quotation mark is "escaped" by the remaining backslash, /// causing a literal double quotation mark (") to be placed in argv. /// - public static IEnumerable SplitCommandLineIntoArguments(string commandLine, bool removeHashComments) + public static List SplitCommandLineIntoArguments(string commandLine, bool removeHashComments) { return SplitCommandLineIntoArguments(commandLine, removeHashComments, out _); } - public static IEnumerable SplitCommandLineIntoArguments(string commandLine, bool removeHashComments, out char? illegalChar) + public static List SplitCommandLineIntoArguments(string commandLine, bool removeHashComments, out char? illegalChar) { - var builder = new StringBuilder(commandLine.Length); var list = new List(); + SplitCommandLineIntoArguments(commandLine.AsSpan(), removeHashComments, new StringBuilder(), list, out illegalChar); + return list; + } + + public static void SplitCommandLineIntoArguments(ReadOnlySpan commandLine, bool removeHashComments, StringBuilder builder, List list, out char? illegalChar) + { var i = 0; + builder.Length = 0; illegalChar = null; while (i < commandLine.Length) { @@ -146,8 +153,6 @@ public static IEnumerable SplitCommandLineIntoArguments(string commandLi list.Add(builder.ToString()); } } - - return list; } } } diff --git a/src/Compilers/Core/Portable/InternalUtilities/FileNameUtilities.cs b/src/Compilers/Core/Portable/InternalUtilities/FileNameUtilities.cs index 2ff4ad4ec0dbe..04b5078ee3dff 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/FileNameUtilities.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/FileNameUtilities.cs @@ -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. +using System; using System.Diagnostics.CodeAnalysis; namespace Roslyn.Utilities @@ -39,13 +40,13 @@ internal static bool IsFileName([NotNullWhen(returnValue: true)] string? path) /// Returns 0 for path ".goo". /// Returns -1 for path "goo.". /// - private static int IndexOfExtension(string? path) - { - if (path == null) - { - return -1; - } + private static int IndexOfExtension(string? path) => + path is null + ? -1 + : IndexOfExtension(path.AsSpan()); + private static int IndexOfExtension(ReadOnlySpan path) + { int length = path.Length; int i = length; @@ -90,6 +91,12 @@ private static int IndexOfExtension(string? path) return (index >= 0) ? path.Substring(index) : string.Empty; } + internal static ReadOnlyMemory GetExtension(ReadOnlyMemory path) + { + int index = IndexOfExtension(path.Span); + return (index >= 0) ? path.Slice(index) : default; + } + /// /// Removes extension from path. /// diff --git a/src/Compilers/Core/Portable/MemoryExtensions.cs b/src/Compilers/Core/Portable/MemoryExtensions.cs new file mode 100644 index 0000000000000..6950afbf81243 --- /dev/null +++ b/src/Compilers/Core/Portable/MemoryExtensions.cs @@ -0,0 +1,91 @@ +// 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.Generic; +using System.Text; + +namespace Microsoft.CodeAnalysis +{ + internal static class MemoryExtensions + { + public static int IndexOfAny(this ReadOnlySpan span, char[] characters) + { + for (int i = 0; i < span.Length; i++) + { + var c = span[i]; + foreach (var target in characters) + { + if (c == target) + { + return i; + } + } + } + + return -1; + } + +#if !NETCOREAPP + internal static ReadOnlyMemory TrimStart(this ReadOnlyMemory memory) + { + var span = memory.Span; + var index = 0; + while (index < span.Length && char.IsWhiteSpace(span[index])) + { + index++; + } + + return memory.Slice(index, span.Length); + } + + internal static ReadOnlyMemory TrimEnd(this ReadOnlyMemory memory) + { + var span = memory.Span; + var length = span.Length; + while (length - 1 >= 0 && char.IsWhiteSpace(span[length - 1])) + { + length--; + } + + return memory.Slice(0, length); + } + + internal static ReadOnlyMemory Trim(this ReadOnlyMemory memory) => memory.TrimStart().TrimEnd(); +#endif + + internal static bool IsNullOrEmpty(this ReadOnlyMemory? memory) => + memory is not { Length: > 0 }; + + internal static bool IsNullOrWhiteSpace(this ReadOnlyMemory? memory) => + memory is not { } m || IsWhiteSpace(m); + + internal static bool IsWhiteSpace(this ReadOnlyMemory memory) + { + var span = memory.Span; + foreach (var c in span) + { + if (!char.IsWhiteSpace(c)) + { + return false; + } + } + + return true; + } + + internal static bool StartsWith(this ReadOnlyMemory memory, char c) => memory.Length > 0 && memory.Span[0] == c; + + internal static ReadOnlyMemory Unquote(this ReadOnlyMemory memory) + { + var span = memory.Span; + if (span.Length > 1 && span[0] == '"' && span[span.Length - 1] == '"') + { + return memory.Slice(1, memory.Length - 2); + } + + return memory; + } + } +} diff --git a/src/Compilers/Core/Portable/SourceGeneration/Nodes/NodeStateTable.cs b/src/Compilers/Core/Portable/SourceGeneration/Nodes/NodeStateTable.cs index 5b7d5356795a0..af76e8e9be004 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/Nodes/NodeStateTable.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/Nodes/NodeStateTable.cs @@ -186,7 +186,7 @@ public bool TryModifyEntry(T value, IEqualityComparer comparer) } Debug.Assert(_previous._states[_states.Count].Count == 1); - _states.Add(new TableEntry(value, comparer.Equals(_previous._states[0].GetItem(0), value) ? EntryState.Cached : EntryState.Modified)); + _states.Add(new TableEntry(value, comparer.Equals(_previous._states[_states.Count].GetItem(0), value) ? EntryState.Cached : EntryState.Modified)); return true; } diff --git a/src/Compilers/Core/Portable/WellKnownMember.cs b/src/Compilers/Core/Portable/WellKnownMember.cs index eb0becb8652d7..33ad33dfd544d 100644 --- a/src/Compilers/Core/Portable/WellKnownMember.cs +++ b/src/Compilers/Core/Portable/WellKnownMember.cs @@ -508,6 +508,7 @@ internal enum WellKnownMember System_Runtime_CompilerServices_NativeIntegerAttribute__ctorTransformFlags, System_Text_StringBuilder__AppendString, + System_Text_StringBuilder__AppendChar, System_Text_StringBuilder__AppendObject, System_Text_StringBuilder__ctor, diff --git a/src/Compilers/Core/Portable/WellKnownMembers.cs b/src/Compilers/Core/Portable/WellKnownMembers.cs index e0e7cab92196a..53fb57f4ee1ae 100644 --- a/src/Compilers/Core/Portable/WellKnownMembers.cs +++ b/src/Compilers/Core/Portable/WellKnownMembers.cs @@ -3489,6 +3489,14 @@ static WellKnownMembers() 1, // Method Signature (byte)SignatureTypeCode.TypeHandle, (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Text_StringBuilder - WellKnownType.ExtSentinel), // Return Type (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, + + // System_Text_StringBuilder__AppendChar + (byte)MemberFlags.Method, // Flags + (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Text_StringBuilder - WellKnownType.ExtSentinel), // DeclaringTypeId + 0, // Arity + 1, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Text_StringBuilder - WellKnownType.ExtSentinel), // Return Type + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, // System_Text_StringBuilder__AppendObject (byte)MemberFlags.Method, // Flags @@ -3949,6 +3957,7 @@ static WellKnownMembers() ".ctor", // System_Runtime_CompilerServices_NativeIntegerAttribute__ctor ".ctor", // System_Runtime_CompilerServices_NativeIntegerAttribute__ctorTransformFlags "Append", // System_Text_StringBuilder__AppendString + "Append", // System_Text_StringBuilder__AppendChar "Append", // System_Text_StringBuilder__AppendObject ".ctor", // System_Text_StringBuilder__ctor "ToStringAndClear", // System_Runtime_CompilerServices_DefaultInterpolatedStringHandler__ToStringAndClear diff --git a/src/Compilers/Test/Core/FX/PinnedBlob.cs b/src/Compilers/Test/Core/FX/PinnedBlob.cs index 8bcc16b8eaabe..1b9abfe0746c6 100644 --- a/src/Compilers/Test/Core/FX/PinnedBlob.cs +++ b/src/Compilers/Test/Core/FX/PinnedBlob.cs @@ -5,34 +5,38 @@ #nullable disable using System; +using System.Buffers; using System.Collections.Immutable; -using System.Runtime.InteropServices; -using Microsoft.CodeAnalysis; -using Roslyn.Utilities; namespace Roslyn.Test.Utilities { internal class PinnedBlob : IDisposable { - private GCHandle _bytes; // non-readonly as Free() mutates to prevent double-free. - public readonly IntPtr Pointer; - public readonly int Size; + // non-readonly as Dispose() mutates + private MemoryHandle _handle; + public IntPtr Pointer; + public int Size; public PinnedBlob(ImmutableArray blob) - : this(blob.DangerousGetUnderlyingArray()) - { - } + : this(blob.AsMemory()) + { } + + public PinnedBlob(byte[] blob) + : this(blob.AsMemory()) + { } - public unsafe PinnedBlob(byte[] blob) + public unsafe PinnedBlob(ReadOnlyMemory blob) { - _bytes = GCHandle.Alloc(blob, GCHandleType.Pinned); - Pointer = _bytes.AddrOfPinnedObject(); - Size = blob.Length; + _handle = blob.Pin(); + this.Size = blob.Length; + this.Pointer = (IntPtr)_handle.Pointer; } - public void Dispose() + public virtual void Dispose() { - _bytes.Free(); + _handle.Dispose(); + Pointer = IntPtr.Zero; + Size = 0; } } } diff --git a/src/Compilers/Test/Core/FX/PinnedMetadata.cs b/src/Compilers/Test/Core/FX/PinnedMetadata.cs index fb0b2a5dc9458..b949012c2d2e9 100644 --- a/src/Compilers/Test/Core/FX/PinnedMetadata.cs +++ b/src/Compilers/Test/Core/FX/PinnedMetadata.cs @@ -7,30 +7,23 @@ using System; using System.Collections.Immutable; using System.Reflection.Metadata; -using System.Runtime.InteropServices; -using Microsoft.CodeAnalysis; -using Roslyn.Utilities; namespace Roslyn.Test.Utilities { - internal class PinnedMetadata : IDisposable + internal sealed class PinnedMetadata : PinnedBlob, IDisposable { - private GCHandle _bytes; // non-readonly as Free() mutates to prevent double-free. - public readonly MetadataReader Reader; - public readonly IntPtr Pointer; - public readonly int Size; + public MetadataReader Reader; public unsafe PinnedMetadata(ImmutableArray metadata) + : base(metadata) { - _bytes = GCHandle.Alloc(metadata.DangerousGetUnderlyingArray(), GCHandleType.Pinned); - this.Pointer = _bytes.AddrOfPinnedObject(); - this.Size = metadata.Length; - this.Reader = new MetadataReader((byte*)this.Pointer, this.Size, MetadataReaderOptions.None, null); + this.Reader = new MetadataReader((byte*)Pointer, this.Size, MetadataReaderOptions.None, null); } - public void Dispose() + public override void Dispose() { - _bytes.Free(); + base.Dispose(); + Reader = null; } } } diff --git a/src/Compilers/VisualBasic/Portable/CommandLine/VisualBasicCommandLineParser.vb b/src/Compilers/VisualBasic/Portable/CommandLine/VisualBasicCommandLineParser.vb index 6be41e8f1dc1a..0cd36c7faab8a 100644 --- a/src/Compilers/VisualBasic/Portable/CommandLine/VisualBasicCommandLineParser.vb +++ b/src/Compilers/VisualBasic/Portable/CommandLine/VisualBasicCommandLineParser.vb @@ -80,7 +80,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Const GenerateFileNameForDocComment As String = "USE-OUTPUT-NAME" Dim diagnostics As List(Of Diagnostic) = New List(Of Diagnostic)() - Dim flattenedArgs As List(Of String) = New List(Of String)() + Dim flattenedArgs = ArrayBuilder(Of String).GetInstance() Dim scriptArgs As List(Of String) = If(IsScriptCommandLineParser, New List(Of String)(), Nothing) ' normalized paths to directories containing response files: @@ -193,9 +193,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Dim name As String = Nothing Dim value As String = Nothing If Not TryParseOption(arg, name, value) Then - For Each path In ParseFileArgument(arg, baseDirectory, diagnostics) + Dim builder = ArrayBuilder(Of String).GetInstance() + ParseFileArgument(arg.AsMemory(), baseDirectory, builder, diagnostics) + For Each path In builder sourceFiles.Add(ToCommandLineSourceFile(path)) Next + builder.Free() hasSourceFiles = True Continue For End If @@ -574,7 +577,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic AddDiagnostic(diagnostics, ERRID.ERR_ArgumentRequired, "errorlog", ErrorLogOptionFormat) Else Dim diagnosticAlreadyReported As Boolean - errorLogOptions = ParseErrorLogOptions(unquoted, diagnostics, baseDirectory, diagnosticAlreadyReported) + errorLogOptions = ParseErrorLogOptions(unquoted.AsMemory(), diagnostics, baseDirectory, diagnosticAlreadyReported) If errorLogOptions Is Nothing And Not diagnosticAlreadyReported Then AddDiagnostic(diagnostics, ERRID.ERR_BadSwitchValue, unquoted, "errorlog", ErrorLogOptionFormat) Continue For @@ -1377,6 +1380,8 @@ lVbRuntimePlus: AddDiagnostic(diagnostics, ERRID.ERR_NoSourcesOut) End If + flattenedArgs.Free() + Dim parseOptions = New VisualBasicParseOptions( languageVersion:=languageVersion, documentationMode:=If(parseDocumentationComments, DocumentationMode.Diagnose, DocumentationMode.None), @@ -1682,7 +1687,7 @@ lVbRuntimePlus: Dim accessibility As String = Nothing ParseResourceDescription( - resourceDescriptor, + resourceDescriptor.AsMemory(), baseDirectory, True, filePath, diff --git a/src/EditorFeatures/CSharpTest/AddUsing/AddUsingNuGetTests.cs b/src/EditorFeatures/CSharpTest/AddUsing/AddUsingNuGetTests.cs index 730d22d238c58..131075a32d653 100644 --- a/src/EditorFeatures/CSharpTest/AddUsing/AddUsingNuGetTests.cs +++ b/src/EditorFeatures/CSharpTest/AddUsing/AddUsingNuGetTests.cs @@ -65,10 +65,10 @@ public async Task TestSearchPackageSingleName() var packageServiceMock = new Mock(MockBehavior.Strict); packageServiceMock.Setup(s => s.FindReferenceAssembliesWithTypeAsync("NuGetType", 0, It.IsAny())) - .Returns(ValueTaskFactory.FromResult(ImmutableArray.Empty)); + .Returns(() => ValueTaskFactory.FromResult(ImmutableArray.Empty)); packageServiceMock.Setup(s => s.FindPackagesWithTypeAsync( NugetOrgSource, "NuGetType", 0, It.IsAny())) - .Returns(CreateSearchResult("NuGetPackage", "NuGetType", CreateNameParts("NuGetNamespace"))); + .Returns(() => CreateSearchResult("NuGetPackage", "NuGetType", CreateNameParts("NuGetNamespace"))); await TestInRegularAndScriptAsync( @"class C @@ -96,10 +96,10 @@ public async Task TestSearchPackageMultipleNames() var packageServiceMock = new Mock(MockBehavior.Strict); packageServiceMock.Setup(s => s.FindReferenceAssembliesWithTypeAsync("NuGetType", 0, It.IsAny())) - .Returns(ValueTaskFactory.FromResult(ImmutableArray.Empty)); + .Returns(() => ValueTaskFactory.FromResult(ImmutableArray.Empty)); packageServiceMock.Setup(s => s.FindPackagesWithTypeAsync( NugetOrgSource, "NuGetType", 0, It.IsAny())) - .Returns(CreateSearchResult("NuGetPackage", "NuGetType", CreateNameParts("NS1", "NS2"))); + .Returns(() => CreateSearchResult("NuGetPackage", "NuGetType", CreateNameParts("NS1", "NS2"))); await TestInRegularAndScriptAsync( @"class C @@ -125,10 +125,10 @@ public async Task TestMissingIfPackageAlreadyInstalled() var packageServiceMock = new Mock(MockBehavior.Strict); packageServiceMock.Setup(s => s.FindReferenceAssembliesWithTypeAsync("NuGetType", 0, It.IsAny())) - .Returns(ValueTaskFactory.FromResult(ImmutableArray.Empty)); + .Returns(() => ValueTaskFactory.FromResult(ImmutableArray.Empty)); packageServiceMock.Setup(s => s.FindPackagesWithTypeAsync( NugetOrgSource, "NuGetType", 0, It.IsAny())) - .Returns(CreateSearchResult("NuGetPackage", "NuGetType", CreateNameParts("NS1", "NS2"))); + .Returns(() => CreateSearchResult("NuGetPackage", "NuGetType", CreateNameParts("NS1", "NS2"))); await TestMissingInRegularAndScriptAsync( @"class C @@ -151,10 +151,10 @@ public async Task TestOptionsOffered() var packageServiceMock = new Mock(MockBehavior.Strict); packageServiceMock.Setup(s => s.FindReferenceAssembliesWithTypeAsync("NuGetType", 0, It.IsAny())) - .Returns(ValueTaskFactory.FromResult(ImmutableArray.Empty)); + .Returns(() => ValueTaskFactory.FromResult(ImmutableArray.Empty)); packageServiceMock.Setup(s => s.FindPackagesWithTypeAsync( NugetOrgSource, "NuGetType", 0, It.IsAny())) - .Returns(CreateSearchResult("NuGetPackage", "NuGetType", CreateNameParts("NS1", "NS2"))); + .Returns(() => CreateSearchResult("NuGetPackage", "NuGetType", CreateNameParts("NS1", "NS2"))); var data = new FixProviderData(installerServiceMock.Object, packageServiceMock.Object); await TestSmartTagTextAsync( @@ -195,10 +195,10 @@ public async Task TestInstallGetsCalledNoVersion() var packageServiceMock = new Mock(MockBehavior.Strict); packageServiceMock.Setup(s => s.FindReferenceAssembliesWithTypeAsync("NuGetType", 0, It.IsAny())) - .Returns(ValueTaskFactory.FromResult(ImmutableArray.Empty)); + .Returns(() => ValueTaskFactory.FromResult(ImmutableArray.Empty)); packageServiceMock.Setup(s => s.FindPackagesWithTypeAsync( NugetOrgSource, "NuGetType", 0, It.IsAny())) - .Returns(CreateSearchResult("NuGetPackage", "NuGetType", CreateNameParts("NuGetNamespace"))); + .Returns(() => CreateSearchResult("NuGetPackage", "NuGetType", CreateNameParts("NuGetNamespace"))); await TestInRegularAndScriptAsync( @"class C @@ -229,9 +229,9 @@ public async Task TestInstallGetsCalledWithVersion() var packageServiceMock = new Mock(MockBehavior.Strict); packageServiceMock.Setup(s => s.FindReferenceAssembliesWithTypeAsync("NuGetType", 0, It.IsAny())) - .Returns(ValueTaskFactory.FromResult(ImmutableArray.Empty)); + .Returns(() => ValueTaskFactory.FromResult(ImmutableArray.Empty)); packageServiceMock.Setup(s => s.FindPackagesWithTypeAsync(NugetOrgSource, "NuGetType", 0, It.IsAny())) - .Returns(CreateSearchResult("NuGetPackage", "NuGetType", CreateNameParts("NuGetNamespace"))); + .Returns(() => CreateSearchResult("NuGetPackage", "NuGetType", CreateNameParts("NuGetNamespace"))); await TestInRegularAndScriptAsync( @"class C @@ -263,9 +263,9 @@ public async Task TestFailedInstallRollsBackFile() var packageServiceMock = new Mock(MockBehavior.Strict); packageServiceMock.Setup(s => s.FindReferenceAssembliesWithTypeAsync("NuGetType", 0, It.IsAny())) - .Returns(ValueTaskFactory.FromResult(ImmutableArray.Empty)); + .Returns(() => ValueTaskFactory.FromResult(ImmutableArray.Empty)); packageServiceMock.Setup(s => s.FindPackagesWithTypeAsync(NugetOrgSource, "NuGetType", 0, It.IsAny())) - .Returns(CreateSearchResult("NuGetPackage", "NuGetType", CreateNameParts("NuGetNamespace"))); + .Returns(() => CreateSearchResult("NuGetPackage", "NuGetType", CreateNameParts("NuGetNamespace"))); await TestInRegularAndScriptAsync( @"class C diff --git a/src/EditorFeatures/CSharpTest/Classification/SyntacticClassifierTests_Preprocessor.cs b/src/EditorFeatures/CSharpTest/Classification/SyntacticClassifierTests_Preprocessor.cs index 55af8074c593b..838d464781d79 100644 --- a/src/EditorFeatures/CSharpTest/Classification/SyntacticClassifierTests_Preprocessor.cs +++ b/src/EditorFeatures/CSharpTest/Classification/SyntacticClassifierTests_Preprocessor.cs @@ -904,6 +904,56 @@ await TestAsync(code, Comment("//Goo")); } + [Theory] + [CombinatorialData] + public async Task PP_LineSpanWithCharacterOffset(TestHost testHost) + { + var code = @"#line (1, 2) - (3, 4) 5 ""file.txt"""; + + await TestAsync(code, + testHost, + PPKeyword("#"), + PPKeyword("line"), + Punctuation.OpenParen, + Number("1"), + Punctuation.Comma, + Number("2"), + Punctuation.CloseParen, + Operators.Minus, + Punctuation.OpenParen, + Number("3"), + Punctuation.Comma, + Number("4"), + Punctuation.CloseParen, + Number("5"), + String("\"file.txt\"")); + } + + [Theory] + [CombinatorialData] + public async Task PP_LineSpanWithComment(TestHost testHost) + { + var code = @"#line (1, 2) - (3, 4) """" //comment"; + + await TestAsync(code, + testHost, + PPKeyword("#"), + PPKeyword("line"), + Punctuation.OpenParen, + Number("1"), + Punctuation.Comma, + Number("2"), + Punctuation.CloseParen, + Operators.Minus, + Punctuation.OpenParen, + Number("3"), + Punctuation.Comma, + Number("4"), + Punctuation.CloseParen, + String("\"\""), + Comment("//comment")); + } + [Theory] [CombinatorialData] public async Task PP_NullableEnable(TestHost testHost) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractCSharpCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractCSharpCompletionProviderTests.cs index 26f5f83d83b26..98ee749aa3df6 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractCSharpCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractCSharpCompletionProviderTests.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Completion; using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion; using Microsoft.CodeAnalysis.Editor.UnitTests.Completion; @@ -27,6 +28,15 @@ public abstract class AbstractCSharpCompletionProviderTests : { protected const string NonBreakingSpaceString = "\x00A0"; + protected static string GetMarkup(string source, LanguageVersion languageVersion) + => $@" + + +{source} + + +"; + protected override TestWorkspace CreateWorkspace(string fileContents) => TestWorkspace.CreateCSharp(fileContents, exportProvider: ExportProvider); diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs new file mode 100644 index 0000000000000..18a76261af6fc --- /dev/null +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs @@ -0,0 +1,283 @@ +// 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.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Completion.Providers; +using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Recommendations +{ + /// + /// The adds async modifier if the return type is Task or ValueTask. + /// The tests here are only checking whether the completion item is provided or not. + /// Tests for checking adding async modifier are in: + /// src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb + /// + [Trait(Traits.Feature, Traits.Features.Completion)] + public class AwaitCompletionProviderTests : AbstractCSharpCompletionProviderTests + { + internal override Type GetCompletionProviderType() => typeof(AwaitCompletionProvider); + + private async Task VerifyAbsenceAsync(string code) + { + await VerifyItemIsAbsentAsync(code, "await"); + } + + private async Task VerifyAbsenceAsync(string code, LanguageVersion languageVersion) + { + await VerifyItemIsAbsentAsync(GetMarkup(code, languageVersion), "await"); + } + + private async Task VerifyKeywordAsync(string code, LanguageVersion languageVersion, string? inlineDescription = null) + { + await VerifyItemExistsAsync(GetMarkup(code, languageVersion), "await", glyph: (int)Glyph.Keyword, inlineDescription: inlineDescription); + } + + [Fact] + public async Task TestNotInTypeContext() + { + await VerifyAbsenceAsync(@" +class Program +{ + $$ +}"); + } + + [Fact] + public async Task TestStatementInMethod() + { + await VerifyKeywordAsync(@" +class C +{ + void F() + { + $$ } +}", LanguageVersion.CSharp9, CSharpFeaturesResources.Make_container_async); + } + + [Fact] + public async Task TestStatementInMethod_Async() + { + await VerifyKeywordAsync(@" +class C +{ + async Task F() + { + $$ } +}", LanguageVersion.CSharp9); + } + + [Fact] + public async Task TestStatementInMethod_TopLevel() + { + await VerifyKeywordAsync("$$", LanguageVersion.CSharp9); + } + + [Fact] + public async Task TestExpressionInAsyncMethod() + { + await VerifyKeywordAsync(@" +class C +{ + async Task F() + { + var z = $$ } +} +", LanguageVersion.CSharp9); + } + + [Fact] + public async Task TestExpressionInNonAsyncMethodWithTaskReturn() + { + await VerifyKeywordAsync(@" +class C +{ + Task F() + { + var z = $$ } +} +", LanguageVersion.CSharp9, CSharpFeaturesResources.Make_container_async); + } + + [Fact] + public async Task TestExpressionInAsyncMethod_TopLevel() + { + await VerifyKeywordAsync("var z = $$", LanguageVersion.CSharp9); + } + + [Fact] + public async Task TestUsingStatement() + { + await VerifyAbsenceAsync(@" +class C +{ + async Task F() + { + using $$ } +}", LanguageVersion.CSharp9); + } + + [Fact] + public async Task TestUsingStatement_TopLevel() + { + await VerifyAbsenceAsync("using $$", LanguageVersion.CSharp9); + } + + [Fact] + public async Task TestUsingDirective() + => await VerifyAbsenceAsync("using $$"); + + [Fact] + public async Task TestGlobalUsingDirective() + => await VerifyAbsenceAsync("global using $$"); + + [Fact] + public async Task TestForeachStatement() + { + await VerifyAbsenceAsync(@" +class C +{ + async Task F() + { + foreach $$ } +}", LanguageVersion.CSharp9); + } + + [Fact] + public async Task TestForeachStatement_TopLevel() + { + await VerifyAbsenceAsync("foreach $$", LanguageVersion.CSharp9); + } + + [Fact] + public async Task TestNotInQuery() + { + await VerifyAbsenceAsync(@" +class C +{ + async Task F() + { + var z = from a in ""char"" + select $$ } + } +", LanguageVersion.CSharp9); + } + + [Fact] + public async Task TestNotInQuery_TopLevel() + { + await VerifyAbsenceAsync( +@"var z = from a in ""char"" + select $$", LanguageVersion.CSharp9); + } + + [WorkItem(907052, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/907052")] + [Fact] + public async Task TestInFinally() + { + await VerifyKeywordAsync(@" +class C +{ + async Task F() + { + try { } +finally { $$ } } +}", LanguageVersion.CSharp9); + } + + [WorkItem(907052, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/907052")] + [Fact] + public async Task TestInFinally_TopLevel() + { + await VerifyKeywordAsync( +@"try { } +finally { $$ }", LanguageVersion.CSharp9); + } + + [WorkItem(907052, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/907052")] + [Fact] + public async Task TestInCatch() + { + await VerifyKeywordAsync(@" +class C +{ + async Task F() + { + try { } +catch { $$ } } +}", LanguageVersion.CSharp9); + } + + [WorkItem(907052, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/907052")] + [Fact] + public async Task TestInCatch_TopLevel() + { + await VerifyKeywordAsync( +@"try { } +catch { $$ }", LanguageVersion.CSharp9); + } + + [Fact] + public async Task TestNotInLock() + { + await VerifyAbsenceAsync(@" +class C +{ + async Task F() + { + lock(this) { $$ } } +}", LanguageVersion.CSharp9); + } + + [Fact] + public async Task TestNotInLock_TopLevel() + { + await VerifyAbsenceAsync("lock (this) { $$ }", LanguageVersion.CSharp9); + } + + [Fact] + public async Task TestInAsyncLambdaInCatch() + { + await VerifyKeywordAsync(@" +class C +{ + async Task F() + { + try { } +catch { var z = async () => $$ } } +}", LanguageVersion.CSharp9); + } + + [Fact] + public async Task TestInAsyncLambdaInCatch_TopLevel() + { + await VerifyKeywordAsync( +@"try { } +catch { var z = async () => $$ }", LanguageVersion.CSharp9); + } + + [Fact] + public async Task TestAwaitInLock() + { + await VerifyKeywordAsync(@" +class C +{ + async Task F() + { + lock($$ } +}", LanguageVersion.CSharp9); + } + + [Fact] + public async Task TestAwaitInLock_TopLevel() + { + await VerifyKeywordAsync("lock($$", LanguageVersion.CSharp9); + } + } +} diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CompletionProviderOrderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CompletionProviderOrderTests.cs index efb842b49c7e6..db03d1223b24c 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CompletionProviderOrderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CompletionProviderOrderTests.cs @@ -38,6 +38,7 @@ public void TestCompletionProviderOrder() typeof(AttributeNamedParameterCompletionProvider), typeof(NamedParameterCompletionProvider), typeof(KeywordCompletionProvider), + typeof(AwaitCompletionProvider), typeof(SpeculativeTCompletionProvider), typeof(SymbolCompletionProvider), typeof(UnnamedSymbolCompletionProvider), diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/DeclarationNameCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/DeclarationNameCompletionProviderTests.cs index 61bb8ef0a1039..b06f1882473d1 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/DeclarationNameCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/DeclarationNameCompletionProviderTests.cs @@ -2715,14 +2715,5 @@ private static SerializableNamingRule CreateRule(SymbolSpecification specificati EnforcementLevel = ReportDiagnostic.Error }; } - - private static string GetMarkup(string source, LanguageVersion languageVersion) - => $@" - - -{source} - - -"; } } diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/UpgradeProject/UpgradeProjectTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/UpgradeProject/UpgradeProjectTests.cs index ef4139fe6426c..cb3706a1cbe96 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/UpgradeProject/UpgradeProjectTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/UpgradeProject/UpgradeProjectTests.cs @@ -1054,7 +1054,7 @@ await TestLanguageVersionUpgradedAsync(@" } [Fact] - public async Task UpgradeProjectForImplicitImplementationOfNonPublicMemebers_CS8704() + public async Task UpgradeProjectForImplicitImplementationOfNonPublicMembers_CS8704() { await TestLanguageVersionUpgradedAsync( @" @@ -1071,5 +1071,20 @@ public void M01() {} expected: LanguageVersion.CSharp10, new CSharpParseOptions(LanguageVersion.CSharp9)); } + + [Fact] + public async Task UpgradeProjectForTargetTypedConditional() + { + await TestLanguageVersionUpgradedAsync(@" +class C +{ + void M(bool b) + { + int? i = [|b ? 1 : null|]; + } +}", + expected: LanguageVersion.CSharp9, + new CSharpParseOptions(LanguageVersion.CSharp8)); + } } } diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.Methods.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.Methods.cs index 6ce641947edec..422c7a34e5536 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.Methods.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.Methods.cs @@ -62,26 +62,26 @@ static void Main(string[] args) public void Method_Body_Delete1() { var src1 = "class C { int M() { return 1; } }"; - var src2 = "class C { int M(); }"; + var src2 = "class C { extern int M(); }"; var edits = GetTopEdits(src1, src2); var active = GetActiveStatements(src1, src2); edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.MethodBodyDelete, "int M()", FeaturesResources.method)); + Diagnostic(RudeEditKind.ModifiersUpdate, "extern int M()", FeaturesResources.method)); } [Fact] public void Method_ExpressionBody_Delete1() { var src1 = "class C { int M() => 1; }"; - var src2 = "class C { int M(); }"; + var src2 = "class C { extern int M(); }"; var edits = GetTopEdits(src1, src2); var active = GetActiveStatements(src1, src2); edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.MethodBodyDelete, "int M()", FeaturesResources.method)); + Diagnostic(RudeEditKind.ModifiersUpdate, "extern int M()", FeaturesResources.method)); } [Fact] @@ -232,7 +232,7 @@ static void Swap(T lhs, T rhs) where T : System.IComparable var active = GetActiveStatements(src1, src2); edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.GenericMethodUpdate, "static void Swap(T lhs, T rhs)", FeaturesResources.method)); + Diagnostic(RudeEditKind.GenericMethodUpdate, "static void Swap(T lhs, T rhs)")); } // Async @@ -711,7 +711,7 @@ public T this[int i] var active = GetActiveStatements(src1, src2); edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.GenericTypeUpdate, "set", CSharpFeaturesResources.indexer_setter)); + Diagnostic(RudeEditKind.GenericTypeUpdate, "set")); } [WorkItem(750244, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/750244")] @@ -765,7 +765,7 @@ public T this[int i] // Rude edits of active statements (AS:1) are not reported if the top-level edits are rude. edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.GenericTypeUpdate, "set", CSharpFeaturesResources.indexer_setter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "set"), Diagnostic(RudeEditKind.ActiveStatementUpdate, "stringCollection[1] = \"hello\";")); } @@ -816,7 +816,7 @@ public T this[int i] var active = GetActiveStatements(src1, src2); edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.GenericTypeUpdate, "get", CSharpFeaturesResources.indexer_getter)); + Diagnostic(RudeEditKind.GenericTypeUpdate, "get")); } [WorkItem(750244, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/750244")] @@ -868,7 +868,7 @@ public T this[int i] // Rude edits of active statements (AS:1) are not reported if the top-level edits are rude. edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.GenericTypeUpdate, "get", CSharpFeaturesResources.indexer_getter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "get"), Diagnostic(RudeEditKind.ActiveStatementUpdate, "Console.WriteLine(stringCollection[1]);")); } @@ -919,7 +919,7 @@ public T this[int i] var active = GetActiveStatements(src1, src2); edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.GenericTypeUpdate, "set", CSharpFeaturesResources.indexer_setter)); + Diagnostic(RudeEditKind.GenericTypeUpdate, "set")); } [Fact] @@ -1018,7 +1018,7 @@ public T this[int i] var active = GetActiveStatements(src1, src2); edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.GenericTypeUpdate, "get", CSharpFeaturesResources.indexer_getter)); + Diagnostic(RudeEditKind.GenericTypeUpdate, "get")); } [Fact] diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs index eeb0066db42c8..d0dd3d4d2180e 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs @@ -1496,8 +1496,7 @@ class C var edits = GetTopEdits(src1, src2); var active = GetActiveStatements(src1, src2); - edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.MethodBodyAdd, "get", CSharpFeaturesResources.property_getter)); + edits.VerifyRudeDiagnostics(active); } [Fact] diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs index 739938b3d8fd4..52c058b281df5 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs @@ -52,30 +52,30 @@ internal static void VerifyRudeDiagnostics( internal static void VerifyLineEdits( this EditScript editScript, - IEnumerable expectedLineEdits, - IEnumerable expectedNodeUpdates, - params RudeEditDiagnosticDescription[] expectedDiagnostics) + SourceLineUpdate[] lineEdits, + SemanticEditDescription[]? semanticEdits = null, + RudeEditDiagnosticDescription[]? diagnostics = null) { - Assert.NotEmpty(expectedLineEdits); + Assert.NotEmpty(lineEdits); VerifyLineEdits( editScript, - new[] { new SequencePointUpdates(editScript.Match.OldRoot.SyntaxTree.FilePath, expectedLineEdits.ToImmutableArray()) }, - expectedNodeUpdates, - expectedDiagnostics); + new[] { new SequencePointUpdates(editScript.Match.OldRoot.SyntaxTree.FilePath, lineEdits.ToImmutableArray()) }, + semanticEdits, + diagnostics); } internal static void VerifyLineEdits( this EditScript editScript, - IEnumerable expectedLineEdits, - IEnumerable expectedNodeUpdates, - params RudeEditDiagnosticDescription[] expectedDiagnostics) + SequencePointUpdates[] lineEdits, + SemanticEditDescription[]? semanticEdits = null, + RudeEditDiagnosticDescription[]? diagnostics = null) { new CSharpEditAndContinueTestHelpers().VerifyLineEdits( editScript, - expectedLineEdits, - expectedNodeUpdates, - expectedDiagnostics); + lineEdits, + semanticEdits, + diagnostics); } internal static void VerifySemanticDiagnostics( diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs index 794c9d3f61a9e..fd25bce6cc13c 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs @@ -64,18 +64,18 @@ internal static DocumentAnalysisResultsDescription DocumentResults( ActiveStatementsDescription? activeStatements = null, SemanticEditDescription[]? semanticEdits = null, RudeEditDiagnosticDescription[]? diagnostics = null) - => new(activeStatements, semanticEdits, diagnostics); + => new(activeStatements, semanticEdits, lineEdits: null, diagnostics); - private static SyntaxTree ParseSource(string markedSource) + private static SyntaxTree ParseSource(string markedSource, int documentIndex = 0) => SyntaxFactory.ParseSyntaxTree( ActiveStatementsDescription.ClearTags(markedSource), CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Preview), - path: "test.cs"); + path: documentIndex.ToString()); - internal static EditScript GetTopEdits(string src1, string src2) + internal static EditScript GetTopEdits(string src1, string src2, int documentIndex = 0) { - var tree1 = ParseSource(src1); - var tree2 = ParseSource(src2); + var tree1 = ParseSource(src1, documentIndex); + var tree2 = ParseSource(src2, documentIndex); tree1.GetDiagnostics().Verify(); tree2.GetDiagnostics().Verify(); diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/LineEditTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/LineEditTests.cs index 1f9085b4ae8d2..2ff87944d57d0 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/LineEditTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/LineEditTests.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Immutable; +using System.Linq; using Microsoft.CodeAnalysis.CSharp.UnitTests; using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; @@ -59,8 +60,7 @@ static void Goo() new SourceLineUpdate(4, 9), AbstractEditAndContinueAnalyzer.CreateZeroDeltaSourceLineUpdate(7), new SourceLineUpdate(9, 4) - }, - Array.Empty()); + }); } [Fact] @@ -112,8 +112,7 @@ static int Bar() AbstractEditAndContinueAnalyzer.CreateZeroDeltaSourceLineUpdate(8), new SourceLineUpdate(10, 4), AbstractEditAndContinueAnalyzer.CreateZeroDeltaSourceLineUpdate(13), - }, - Array.Empty()); + }); } [Fact] @@ -141,7 +140,7 @@ static void Bar() var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( Array.Empty(), - Array.Empty()); + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.Bar")) }); } [Fact] @@ -172,7 +171,7 @@ void F() var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( Array.Empty(), - new[] { "void F()" }); + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F")) }); } [Fact] @@ -199,8 +198,7 @@ static void Bar() }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(4, 6) }, - Array.Empty()); + new[] { new SourceLineUpdate(4, 6) }); } [Fact] @@ -226,8 +224,7 @@ static void Bar() }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(4, 5) }, - Array.Empty()); + new[] { new SourceLineUpdate(4, 5) }); } [Fact] @@ -254,8 +251,7 @@ class C { new SourceLineUpdate(3, 4), AbstractEditAndContinueAnalyzer.CreateZeroDeltaSourceLineUpdate(4) - }, - Array.Empty()); + }); } [Fact] @@ -281,7 +277,7 @@ static void Bar() var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( Array.Empty(), - new string[] { "static void Bar()" }); + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.Bar")) }); } [Fact] @@ -307,8 +303,7 @@ static void Bar() }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(6, 5) }, - Array.Empty()); + new[] { new SourceLineUpdate(6, 5) }); } [Fact] @@ -335,8 +330,7 @@ static void Bar() }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(5, 4) }, - Array.Empty()); + new[] { new SourceLineUpdate(5, 4) }); } [Fact] @@ -366,7 +360,7 @@ static void Bar() var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( Array.Empty(), - new string[] { "static void Bar()" }); + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.Bar")) }); var active = GetActiveStatements(src1, src2); var syntaxMap = GetSyntaxMap(src1, src2); @@ -388,7 +382,7 @@ class C { /*--*/static void Bar() { } }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( Array.Empty(), - new string[] { "static void Bar() { }" }); + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.Bar")) }); } [Fact] @@ -415,8 +409,7 @@ static void Bar() var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(6, 5) }, - Array.Empty()); + new[] { new SourceLineUpdate(6, 5) }); } [Fact] @@ -443,8 +436,7 @@ static void Bar() var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( Array.Empty(), - new string[] { "static void Bar()" }, - Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, "\r\n /*edit*/", FeaturesResources.method)); + diagnostics: new[] { Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, "\r\n /*edit*/", FeaturesResources.method) }); } [Fact] @@ -471,8 +463,7 @@ static void Bar() var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( Array.Empty(), - new string[] { "static void Bar()" }, - Diagnostic(RudeEditKind.GenericMethodTriviaUpdate, "\r\n ", FeaturesResources.method)); + diagnostics: new[] { Diagnostic(RudeEditKind.GenericMethodTriviaUpdate, "\r\n ", FeaturesResources.method) }); } [Fact] @@ -501,7 +492,7 @@ static async Task Bar() var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( Array.Empty(), - new string[] { "static async Task Bar()" }); + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.Bar"), preserveLocalVariables: true) }); } #endregion @@ -541,8 +532,7 @@ public C(int a) new SourceLineUpdate(4, 8), AbstractEditAndContinueAnalyzer.CreateZeroDeltaSourceLineUpdate(6), new SourceLineUpdate(8, 4) - }, - Array.Empty()); + }); } [Fact] @@ -568,8 +558,7 @@ public C(int a) }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(4, 5) }, - Array.Empty()); + new[] { new SourceLineUpdate(4, 5) }); } [Fact] @@ -593,8 +582,7 @@ public C(int a) => }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(5, 6) }, - Array.Empty()); + new[] { new SourceLineUpdate(5, 6) }); } [Fact] @@ -618,8 +606,7 @@ public C(int a) }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(5, 6) }, - Array.Empty()); + new[] { new SourceLineUpdate(5, 6) }); } [Fact] @@ -643,8 +630,7 @@ public C(int a) => }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(5, 6) }, - Array.Empty()); + new[] { new SourceLineUpdate(5, 6) }); } [Fact] @@ -671,8 +657,7 @@ public C(int a) }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(6, 8) }, - Array.Empty()); + new[] { new SourceLineUpdate(6, 8) }); } [Fact] @@ -696,8 +681,7 @@ public C(int a) }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(5, 6) }, - Array.Empty()); + new[] { new SourceLineUpdate(5, 6) }); } [Fact] @@ -722,8 +706,7 @@ public C(int a) }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new SourceLineUpdate[] { new(5, 6) }, - Array.Empty()); + new SourceLineUpdate[] { new(5, 6) }); } [Fact] @@ -748,7 +731,7 @@ public C(int a) var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( Array.Empty(), - new string[] { "public C(int a)" }); + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) }); } [Fact] @@ -774,8 +757,7 @@ public C(int a) }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new SourceLineUpdate[] { new(5, 6) }, - Array.Empty()); + new SourceLineUpdate[] { new(5, 6) }); } [Fact] @@ -801,7 +783,7 @@ public C(int a) var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( Array.Empty(), - new string[] { "public C(int a)" }); + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) }); } [Fact] @@ -827,8 +809,7 @@ public C(int a) var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( Array.Empty(), - new string[] { "public C(int a)" }, - Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, " ", FeaturesResources.constructor)); + diagnostics: new[] { Diagnostic(RudeEditKind.GenericTypeUpdate, "public C(int a)") }); } #endregion @@ -856,8 +837,7 @@ class C }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(5, 4) }, - Array.Empty()); + new[] { new SourceLineUpdate(5, 4) }); } [Fact] @@ -877,8 +857,7 @@ class C }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(3, 4) }, - Array.Empty()); + new[] { new SourceLineUpdate(3, 4) }); } [Fact] @@ -898,8 +877,7 @@ class C }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(3, 4) }, - Array.Empty()); + new[] { new SourceLineUpdate(3, 4) }); } #endregion @@ -923,8 +901,7 @@ class C }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - Array.Empty(), - Array.Empty()); + Array.Empty()); } [Fact] @@ -944,8 +921,7 @@ class C }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - Array.Empty(), - Array.Empty()); + Array.Empty()); } [Fact] @@ -966,8 +942,8 @@ class C }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(3, 4), new SourceLineUpdate(4, 3) }, - Array.Empty()); + Array.Empty(), + diagnostics: new[] { Diagnostic(RudeEditKind.Move, "static int Bar = 2", FeaturesResources.field) }); } [Fact] @@ -989,8 +965,7 @@ class C }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(3, 6) }, - Array.Empty()); + new[] { new SourceLineUpdate(3, 6) }); } [Fact] @@ -1011,7 +986,10 @@ class C var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( Array.Empty(), - new[] { "Bar = 2" }); + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + }); } [Fact] @@ -1031,8 +1009,7 @@ class C }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new SourceLineUpdate[] { new SourceLineUpdate(3, 4) }, - Array.Empty()); + new SourceLineUpdate[] { new SourceLineUpdate(3, 4) }); } [Fact] @@ -1053,7 +1030,10 @@ class C var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( Array.Empty(), - new string[] { "Goo = " }); + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").StaticConstructors.Single(), preserveLocalVariables: true) + }); } [Fact] @@ -1074,7 +1054,10 @@ static int Goo var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( Array.Empty(), - new string[] { "Goo " }); + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").StaticConstructors.Single(), preserveLocalVariables: true) + }); } [Fact] @@ -1095,7 +1078,10 @@ static int var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( Array.Empty(), - new string[] { "Goo = 1" }); + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").StaticConstructors.Single(), preserveLocalVariables: true) + }); } [Fact] @@ -1116,7 +1102,10 @@ class C var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( Array.Empty(), - new string[] { "Goo = 1" }); + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").StaticConstructors.Single(), preserveLocalVariables: true) + }); } [Fact] @@ -1137,7 +1126,10 @@ class C var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( Array.Empty(), - new string[] { "Goo = 1" }); + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").StaticConstructors.Single(), preserveLocalVariables: true) + }); } [Fact] @@ -1157,7 +1149,10 @@ class C var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( Array.Empty(), - new string[] { "Goo = 1 + 1" }); + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").StaticConstructors.Single(), preserveLocalVariables: true) + }); } [Fact] @@ -1177,8 +1172,10 @@ class C var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( Array.Empty(), - new string[] { "Goo = 1 + 1" }, - Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, " ", FeaturesResources.field)); + diagnostics: new[] + { + Diagnostic(RudeEditKind.GenericTypeUpdate, "class C") + }); } #endregion @@ -1203,7 +1200,7 @@ class C var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( Array.Empty(), - new string[] { "get { return " }); + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.P").GetMethod) }); } [Fact] @@ -1222,9 +1219,7 @@ int P { get { return 1; } } }"; var edits = GetTopEdits(src1, src2); - edits.VerifyLineEdits( - new[] { new SourceLineUpdate(3, 4) }, - Array.Empty()); + edits.VerifyLineEdits(new[] { new SourceLineUpdate(3, 4) }); } [Fact] @@ -1244,8 +1239,7 @@ class C }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(3, 4) }, - Array.Empty()); + new[] { new SourceLineUpdate(3, 4) }); } [Fact] @@ -1265,8 +1259,7 @@ class C }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(3, 4) }, - Array.Empty()); + new[] { new SourceLineUpdate(3, 4) }); } [Fact] @@ -1286,8 +1279,7 @@ class C }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(3, 4) }, - Array.Empty()); + new[] { new SourceLineUpdate(3, 4) }); } [Fact] @@ -1307,8 +1299,7 @@ class C }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(3, 4) }, - Array.Empty()); + new[] { new SourceLineUpdate(3, 4) }); } [Fact] @@ -1328,8 +1319,7 @@ int P { }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(3, 4) }, - Array.Empty()); + new[] { new SourceLineUpdate(3, 4) }); } [Fact] @@ -1349,8 +1339,7 @@ class C }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(3, 4) }, - Array.Empty()); + new[] { new SourceLineUpdate(3, 4) }); } [Fact] @@ -1370,7 +1359,7 @@ class C var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( Array.Empty(), - new string[] { "int P { get; } = 1;" }); + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) }); } #endregion @@ -1395,7 +1384,7 @@ class C var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( Array.Empty(), - new string[] { "get { return " }); + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.this[]").GetMethod) }); } [Fact] @@ -1414,9 +1403,7 @@ int this[int a] { get { return 1; } } }"; var edits = GetTopEdits(src1, src2); - edits.VerifyLineEdits( - new[] { new SourceLineUpdate(3, 4) }, - Array.Empty()); + edits.VerifyLineEdits(new[] { new SourceLineUpdate(3, 4) }); } [Fact] @@ -1435,9 +1422,7 @@ class C int this[int a] { get { return 1; } set { } } }"; var edits = GetTopEdits(src1, src2); - edits.VerifyLineEdits( - new[] { new SourceLineUpdate(3, 4) }, - Array.Empty()); + edits.VerifyLineEdits(new[] { new SourceLineUpdate(3, 4) }); } [Fact] @@ -1457,8 +1442,7 @@ class C }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(3, 4) }, - Array.Empty()); + new[] { new SourceLineUpdate(3, 4) }); } [Fact] @@ -1478,8 +1462,7 @@ class C }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(3, 4) }, - Array.Empty()); + new[] { new SourceLineUpdate(3, 4) }); } [Fact] @@ -1499,8 +1482,7 @@ class C }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(3, 4) }, - Array.Empty()); + new[] { new SourceLineUpdate(3, 4) }); } #endregion @@ -1524,8 +1506,7 @@ event Action E { add { } remove { } } }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(3, 4) }, - Array.Empty()); + new[] { new SourceLineUpdate(3, 4) }); } [Fact] @@ -1546,7 +1527,7 @@ event Action E { add { } remove { } } var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( new[] { new SourceLineUpdate(4, 3) }, - new string[] { "add { }" }); + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.E").AddMethod) }); } [Fact] @@ -1567,7 +1548,7 @@ event Action E { add { } remove { } } var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( Array.Empty(), - new string[] { "remove { }" }); + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.E").RemoveMethod) }); } [Fact] @@ -1587,8 +1568,7 @@ event Action E { add { } remove { } } }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(4, 3) }, - Array.Empty()); + new[] { new SourceLineUpdate(4, 3) }); } [Fact] @@ -1610,7 +1590,7 @@ event Action E { add { } remove var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( Array.Empty(), - new[] { "remove " }); + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.E").RemoveMethod) }); } [Fact, WorkItem(53263, "https://github.com/dotnet/roslyn/issues/53263")] @@ -1632,7 +1612,7 @@ class C var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( new[] { new SourceLineUpdate(3, 4) }, - new[] { "remove => " }); + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.E").RemoveMethod) }); } [Fact] @@ -1653,8 +1633,7 @@ class C }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( - new[] { new SourceLineUpdate(4, 3), new SourceLineUpdate(5, 3) }, - Array.Empty()); + new[] { new SourceLineUpdate(4, 3), new SourceLineUpdate(5, 3) }); } #endregion @@ -1697,8 +1676,7 @@ class C new SourceLineUpdate(3, 9), AbstractEditAndContinueAnalyzer.CreateZeroDeltaSourceLineUpdate(5), new SourceLineUpdate(9, 3) - }, - Array.Empty()); + }); } #endregion @@ -1737,7 +1715,7 @@ static void F() // Line deltas can't be applied on the whole breakpoint span hence recompilation. edits.VerifyLineEdits( Array.Empty(), - new[] { "static void F()" }); + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F")) }); } /// @@ -1760,10 +1738,10 @@ class D public D() {} #line 5 ""a"" - public F3() {} + void F3() {} #line 6 ""a"" - public F4() {} + void F4() {} }"; var src2 = @" #line 11 ""a"" @@ -1779,8 +1757,8 @@ class D public D() {} #line 5 ""a"" - public F3() {} - public F4() {} + void F3() {} + void F4() {} } "; var edits = GetTopEdits(src1, src2); @@ -1793,10 +1771,10 @@ public F4() {} AbstractEditAndContinueAnalyzer.CreateZeroDeltaSourceLineUpdate(6), // lines between F2 and D ctor new(9, 19))) // D ctor }, - new[] + semanticEdits: new[] { - "public F3() {}", // overlaps with "void F1() { }" - "public F4() {}" // overlaps with "void F2() { }" + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("D.F3")), // overlaps with "void F1() { }" + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("D.F4")), // overlaps with "void F2() { }" }); } @@ -1858,8 +1836,7 @@ static void F() { new("a", ImmutableArray.Create(new SourceLineUpdate(0, 1))), new("b", ImmutableArray.Create(new SourceLineUpdate(0, 1))), - }, - Array.Empty()); + }); } [Fact] @@ -1901,12 +1878,10 @@ static void F() { new("a", ImmutableArray.Create(new SourceLineUpdate(6, 4))), }, - expectedNodeUpdates: new[] { "static void F()" }); - - edits.VerifySemantics(ActiveStatementsDescription.Empty, new[] - { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F")) - }); + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F")) + }); } [Fact] @@ -1923,8 +1898,10 @@ class C { static void Bar() { } }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( Array.Empty(), - new string[] { "static void Bar() { }" }, - Diagnostic(RudeEditKind.GenericMethodTriviaUpdate, "{", FeaturesResources.method)); + diagnostics: new[] + { + Diagnostic(RudeEditKind.GenericMethodTriviaUpdate, "{", FeaturesResources.method) + }); } #endregion diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs index 27e7ed214b1a2..610c6ff3870b8 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs @@ -1825,9 +1825,15 @@ interface I { void F() {} } DocumentResults( diagnostics: new[] { - Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, "void F()", FeaturesResources.method), - Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, "void F()", FeaturesResources.method), - Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, "void F()", FeaturesResources.method), + Diagnostic(RudeEditKind.GenericTypeUpdate, "class C"), + Diagnostic(RudeEditKind.GenericTypeUpdate, "struct S"), + Diagnostic(RudeEditKind.GenericTypeUpdate, "interface I"), + Diagnostic(RudeEditKind.GenericTypeUpdate, "void F()"), + Diagnostic(RudeEditKind.GenericTypeUpdate, "void F()"), + Diagnostic(RudeEditKind.GenericTypeUpdate, "void F()"), + Diagnostic(RudeEditKind.GenericTypeUpdate, "T"), + Diagnostic(RudeEditKind.GenericTypeUpdate, "T"), + Diagnostic(RudeEditKind.GenericTypeUpdate, "T"), }) }); } @@ -3358,7 +3364,6 @@ public void EnumInitializerUpdate2() "Update [Blue = 2]@22 -> [Blue = 2 << 1]@27"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.InitializerUpdate, "Red = 1 << 0", FeaturesResources.enum_value), Diagnostic(RudeEditKind.InitializerUpdate, "Blue = 2 << 1", FeaturesResources.enum_value)); } @@ -3855,7 +3860,8 @@ public void Delegates_TypeParameter_Rename() "Update [T]@22 -> [S]@22"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Renamed, "S", FeaturesResources.type_parameter)); + Diagnostic(RudeEditKind.Renamed, "S", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "S")); } [Fact] @@ -3870,7 +3876,8 @@ public void Delegates_TypeParameter_Variance1() "Update [T]@22 -> [in T]@22"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.VarianceUpdate, "T", FeaturesResources.type_parameter)); + Diagnostic(RudeEditKind.VarianceUpdate, "T", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "T")); } [Fact] @@ -3885,7 +3892,8 @@ public void Delegates_TypeParameter_Variance2() "Update [out T]@22 -> [T]@22"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.VarianceUpdate, "T", FeaturesResources.type_parameter)); + Diagnostic(RudeEditKind.VarianceUpdate, "T", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "T")); } [Fact] @@ -3900,7 +3908,8 @@ public void Delegates_TypeParameter_Variance3() "Update [out T]@22 -> [in T]@22"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.VarianceUpdate, "T", FeaturesResources.type_parameter)); + Diagnostic(RudeEditKind.VarianceUpdate, "T", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "T")); } [Fact] @@ -3916,13 +3925,9 @@ public void Delegates_TypeParameter_AddAttribute() edits.VerifyEdits( "Update [T]@70 -> [[A]T]@70"); - edits.VerifySemantics( - ActiveStatementsDescription.Empty, - new[] - { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("D")), - }, - capabilities: EditAndContinueTestHelpers.Net6RuntimeCapabilities); + edits.VerifyRudeDiagnostics( + EditAndContinueTestHelpers.Net6RuntimeCapabilities, + Diagnostic(RudeEditKind.GenericTypeUpdate, "T")); } [Fact] @@ -4614,7 +4619,9 @@ public void NestedPartialTypeInPartialType_InsertDeleteAndChange_TypeParameterAt DocumentResults( diagnostics: new[] { - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "T", FeaturesResources.type_parameter) + Diagnostic(RudeEditKind.GenericTypeUpdate, "partial class C<[A]T>"), + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "T", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "T") }), DocumentResults(), @@ -4641,7 +4648,9 @@ public void NestedPartialTypeInPartialType_InsertDeleteAndChange_Constraint() DocumentResults( diagnostics: new[] { - Diagnostic(RudeEditKind.ChangingConstraints, "where T : new()", FeaturesResources.type_parameter) + Diagnostic(RudeEditKind.GenericTypeUpdate, "partial class C"), + Diagnostic(RudeEditKind.ChangingConstraints, "where T : new()", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "where T : new()") }), DocumentResults(), @@ -5077,8 +5086,8 @@ public void PartialMember_DeleteInsert_GenericMethod() DocumentResults(), DocumentResults(diagnostics: new[] { - // TODO: better message - Diagnostic(RudeEditKind.GenericMethodTriviaUpdate, "void F()", FeaturesResources.method) + Diagnostic(RudeEditKind.GenericMethodUpdate, "void F()"), + Diagnostic(RudeEditKind.GenericMethodUpdate, "T") }) }); } @@ -5098,8 +5107,7 @@ public void PartialMember_DeleteInsert_GenericType() DocumentResults(), DocumentResults(diagnostics: new[] { - // TODO: better message - Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, "void F()", FeaturesResources.method) + Diagnostic(RudeEditKind.GenericTypeUpdate, "void F()") }) }); } @@ -7446,19 +7454,14 @@ public void PartialMethod_Swap_ImplementationAndDefinitionParts() var srcA2 = "partial class C { partial void F() { } }"; var srcB2 = "partial class C { partial void F(); }"; - // current: - GetTopEdits(srcA1, srcA2).VerifyRudeDiagnostics(Diagnostic(RudeEditKind.MethodBodyAdd, "partial void F()", FeaturesResources.method)); - GetTopEdits(srcB1, srcB2).VerifyRudeDiagnostics(Diagnostic(RudeEditKind.MethodBodyDelete, "partial void F()", FeaturesResources.method)); - - // correct: TODO - //EditAndContinueValidation.VerifySemantics( - // new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, - // new[] - // { - // DocumentResults(), - // DocumentResults( - // semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F")) }), - // }); + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F").PartialImplementationPart) }), + DocumentResults(), + }); } [Fact] @@ -7575,7 +7578,19 @@ public void Operator_Conversion_ExternModifiers_Add() var edits = GetTopEdits(src1, src2); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.MethodBodyDelete, "extern public static implicit operator bool (C c)", CSharpFeaturesResources.conversion_operator)); + Diagnostic(RudeEditKind.ModifiersUpdate, "extern public static implicit operator bool (C c)", CSharpFeaturesResources.conversion_operator)); + } + + [Fact] + public void Operator_Conversion_ExternModifiers_Remove() + { + var src1 = "class C { extern public static implicit operator bool (C c); }"; + var src2 = "class C { public static implicit operator bool (C c) => default; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, "public static implicit operator bool (C c)", CSharpFeaturesResources.conversion_operator)); } [Fact] @@ -7864,8 +7879,9 @@ public void Constructor_ExternModifier_Add() "Insert [public extern C();]@10", "Insert [()]@25"); - // The compiler generates an empty constructor. - edits.VerifySemanticDiagnostics(); + // This can be allowed as the compiler generates an empty constructor, but it's not worth the complexity. + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, "public extern C()", FeaturesResources.constructor)); } [Fact] @@ -7908,7 +7924,7 @@ public C(int a) { } "Update [public C(int a) : base(a) { }]@21 -> [public C(int a) { }]@21"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.GenericTypeUpdate, "public C(int a)", FeaturesResources.constructor)); + Diagnostic(RudeEditKind.GenericTypeUpdate, "public C(int a)")); } [Fact] @@ -7951,7 +7967,7 @@ public C(int a) : base(a + 1) { } "Update [public C(int a) : base(a) { }]@21 -> [public C(int a) : base(a + 1) { }]@21"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.GenericTypeUpdate, "public C(int a)", FeaturesResources.constructor)); + Diagnostic(RudeEditKind.GenericTypeUpdate, "public C(int a)")); } [WorkItem(743552, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/743552")] @@ -9352,8 +9368,31 @@ public void PropertyInitializer_Update2() "Update [int a { get; } = 0;]@10 -> [int a { get { return 1; } }]@10", "Update [get;]@18 -> [get { return 1; }]@18"); - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.MethodBodyAdd, "get", CSharpFeaturesResources.property_getter)); + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.a").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(), preserveLocalVariables: true)); + } + + [Fact] + public void PropertyInitializer_InsertDelete() + { + var srcA1 = "partial class C { }"; + var srcB1 = "partial class C { int a { get; } = 0; }"; + var srcA2 = "partial class C { int a { get { return 1; } } }"; + var srcB2 = "partial class C { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.a").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(), partialType: "C", preserveLocalVariables: true) + }), + DocumentResults() + }); } [Fact] @@ -9384,8 +9423,13 @@ public void PropertyInitializer_Update3() "Update [int a { get { return 1; } }]@10 -> [int a { get; } = 0;]@10", "Update [get { return 1; }]@18 -> [get;]@18"); - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.MethodBodyDelete, "get", CSharpFeaturesResources.property_getter)); + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.a").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + }); } [Fact] @@ -9701,7 +9745,8 @@ public void FieldInitializerUpdate_GenericType() "Update [a = 1]@17 -> [a = 2]@17"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.GenericTypeInitializerUpdate, "a = 2", FeaturesResources.field)); + Diagnostic(RudeEditKind.GenericTypeUpdate, "a = 2"), + Diagnostic(RudeEditKind.GenericTypeUpdate, "class C")); } [Fact] @@ -9713,7 +9758,8 @@ public void PropertyInitializerUpdate_GenericType() var edits = GetTopEdits(src1, src2); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.GenericTypeInitializerUpdate, "int a", FeaturesResources.auto_property)); + Diagnostic(RudeEditKind.GenericTypeUpdate, "int a"), + Diagnostic(RudeEditKind.GenericTypeUpdate, "class C")); } [Fact] @@ -10905,15 +10951,18 @@ public void Field_Attribute_Add_InsertDelete() [Fact] public void Field_FixedSize_Update() { - var src1 = "struct S { public unsafe fixed byte bs[1]; }"; - var src2 = "struct S { public unsafe fixed byte bs[2]; }"; + var src1 = "struct S { public unsafe fixed byte a[1], b[2]; }"; + var src2 = "struct S { public unsafe fixed byte a[2], b[3]; }"; var edits = GetTopEdits(src1, src2); - edits.VerifyEdits("Update [bs[1]]@36 -> [bs[2]]@36"); + edits.VerifyEdits( + "Update [a[1]]@36 -> [a[2]]@36", + "Update [b[2]]@42 -> [b[3]]@42"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.FixedSizeFieldUpdate, "bs[2]", FeaturesResources.field)); + Diagnostic(RudeEditKind.FixedSizeFieldUpdate, "a[2]", FeaturesResources.field), + Diagnostic(RudeEditKind.FixedSizeFieldUpdate, "b[3]", FeaturesResources.field)); } [WorkItem(1120407, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1120407")] @@ -10928,7 +10977,7 @@ public void Field_Const_Update() edits.VerifyEdits("Update [x = 0]@20 -> [x = 1]@20"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Update, "x = 1", FeaturesResources.const_field)); + Diagnostic(RudeEditKind.InitializerUpdate, "x = 1", FeaturesResources.const_field)); } [Fact] @@ -14398,8 +14447,9 @@ public void MethodTypeParameter_Attribute_Insert1() "Update [T]@72 -> [[A]T]@72"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.GenericMethodTriviaUpdate, "", FeaturesResources.method), - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "T", FeaturesResources.type_parameter)); + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "T", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericMethodUpdate, "T"), + Diagnostic(RudeEditKind.GenericMethodTriviaUpdate, "", FeaturesResources.method)); } [Fact] @@ -14417,8 +14467,9 @@ public void MethodTypeParameter_Attribute_Insert2() "Update [[A]T]@120 -> [[A, B]T]@120"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.GenericMethodTriviaUpdate, "", FeaturesResources.method), - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "T", FeaturesResources.type_parameter)); + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "T", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericMethodUpdate, "T"), + Diagnostic(RudeEditKind.GenericMethodTriviaUpdate, "", FeaturesResources.method)); } [Fact] @@ -14435,8 +14486,9 @@ public void MethodTypeParameter_Attribute_Delete() "Update [[A]T]@72 -> [T]@72"); edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "T", FeaturesResources.type_parameter), Diagnostic(RudeEditKind.GenericMethodTriviaUpdate, "", FeaturesResources.method), - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "T", FeaturesResources.type_parameter)); + Diagnostic(RudeEditKind.GenericMethodUpdate, "T")); } [Fact] @@ -14454,7 +14506,8 @@ public void MethodTypeParameter_Attribute_Update_NotSupportedByRuntime() "Update [[System.Obsolete(\"1\"), B]T]@120 -> [[System.Obsolete(\"2\"), A]T]@120"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "T", FeaturesResources.type_parameter)); + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "T", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericMethodUpdate, "T")); } [Fact] @@ -14470,10 +14523,9 @@ public void MethodTypeParameter_Attribute_Update() edits.VerifyEdits( "Update [[A(0)]T]@67 -> [[A(1)]T]@67"); - edits.VerifySemantics( - ActiveStatementsDescription.Empty, - new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F")) }, - capabilities: EditAndContinueTestHelpers.Net6RuntimeCapabilities); + edits.VerifyRudeDiagnostics( + EditAndContinueTestHelpers.Net6RuntimeCapabilities, + Diagnostic(RudeEditKind.GenericMethodUpdate, "T")); } [Fact] @@ -14491,8 +14543,9 @@ public void MethodTypeParameter_Attribute_Update_WithBodyUpdate() "Update [[A(0)]T]@67 -> [[A(1)]T]@67"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.GenericMethodUpdate, "void F<[A(1)]T>(T a)", FeaturesResources.method), - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "T", FeaturesResources.type_parameter)); + Diagnostic(RudeEditKind.GenericMethodUpdate, "void F<[A(1)]T>(T a)"), + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "T", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericMethodUpdate, "T")); } #endregion @@ -14571,7 +14624,8 @@ public void TypeTypeParameterUpdate() "Update [A]@8 -> [B]@8"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Renamed, "B", FeaturesResources.type_parameter)); + Diagnostic(RudeEditKind.Renamed, "B", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "B")); } [Fact] @@ -14603,7 +14657,8 @@ public void TypeTypeParameterReorderAndUpdate() edits.VerifyRudeDiagnostics( Diagnostic(RudeEditKind.Move, "B", FeaturesResources.type_parameter), - Diagnostic(RudeEditKind.Renamed, "C", FeaturesResources.type_parameter)); + Diagnostic(RudeEditKind.Renamed, "C", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "C")); } [Fact] @@ -14620,7 +14675,8 @@ public void TypeTypeParameterAttributeInsert1() "Update [T]@56 -> [[A]T]@56"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "T", FeaturesResources.type_parameter)); + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "T", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "T")); } [Fact] @@ -14638,7 +14694,8 @@ public void TypeTypeParameterAttributeInsert2() "Update [[A]T]@104 -> [[A, B]T]@104"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "T", FeaturesResources.type_parameter)); + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "T", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "T")); } [Fact] @@ -14654,10 +14711,9 @@ public void TypeTypeParameterAttributeInsert_SupportedByRuntime() edits.VerifyEdits( "Update [T]@56 -> [[A]T]@56"); - edits.VerifySemantics( - ActiveStatementsDescription.Empty, - new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C")) }, - capabilities: EditAndContinueTestHelpers.Net6RuntimeCapabilities); + edits.VerifyRudeDiagnostics( + EditAndContinueTestHelpers.Net6RuntimeCapabilities, + Diagnostic(RudeEditKind.GenericTypeUpdate, "T")); } [Fact] @@ -14674,7 +14730,8 @@ public void TypeTypeParameterAttributeDelete() "Update [[A]T]@56 -> [T]@56"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "T", FeaturesResources.type_parameter)); + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "T", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "T")); } [Fact] @@ -14692,7 +14749,8 @@ public void TypeTypeParameterAttributeUpdate() "Update [[System.Obsolete(\"1\"), B]T]@104 -> [[System.Obsolete(\"2\"), A]T]@104"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "T", FeaturesResources.type_parameter)); + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "T", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "T")); } #endregion @@ -14718,7 +14776,8 @@ public void TypeConstraint_Insert(string newConstraint) "Insert [where T : " + newConstraint + "]@13"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingConstraints, "where T : " + newConstraint, FeaturesResources.type_parameter)); + Diagnostic(RudeEditKind.ChangingConstraints, "where T : " + newConstraint, FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "where T : " + newConstraint)); } [Theory] @@ -14740,7 +14799,8 @@ public void TypeConstraint_Delete(string oldConstraint) "Delete [where T : " + oldConstraint + "]@13"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingConstraints, "T", FeaturesResources.type_parameter)); + Diagnostic(RudeEditKind.ChangingConstraints, "T", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "T")); } [Theory] @@ -14754,8 +14814,8 @@ public void TypeConstraint_Update_RuntimeTypeUnchanged(string oldType, string ne var edits = GetTopEdits(src1, src2); - edits.VerifySemantics( - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C"))); + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.GenericTypeUpdate, "where T : System.Collections.Generic.List<" + newType + ">")); } [Theory] @@ -14770,7 +14830,8 @@ public void TypeConstraint_Update_RuntimeTypeChanged(string oldType, string newT var edits = GetTopEdits(src1, src2); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingConstraints, "where T : System.Collections.Generic.List<" + newType + ">", FeaturesResources.type_parameter)); + Diagnostic(RudeEditKind.ChangingConstraints, "where T : System.Collections.Generic.List<" + newType + ">", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "where T : System.Collections.Generic.List<" + newType + ">")); } [Fact] @@ -14797,7 +14858,8 @@ public void TypeConstraint_MultipleClauses_Insert() "Insert [where S : unmanaged]@13"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingConstraints, "where S : unmanaged", FeaturesResources.type_parameter)); + Diagnostic(RudeEditKind.ChangingConstraints, "where S : unmanaged", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "where S : unmanaged")); } [Fact] @@ -14812,7 +14874,8 @@ public void TypeConstraint_MultipleClauses_Delete() "Delete [where S : new()]@13"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingConstraints, "S", FeaturesResources.type_parameter)); + Diagnostic(RudeEditKind.ChangingConstraints, "S", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "S")); } [Fact] @@ -14846,7 +14909,9 @@ public void TypeConstraint_MultipleClauses_UpdateAndReorder() edits.VerifyRudeDiagnostics( Diagnostic(RudeEditKind.Move, "T", FeaturesResources.type_parameter), Diagnostic(RudeEditKind.ChangingConstraints, "where T : class, I", FeaturesResources.type_parameter), - Diagnostic(RudeEditKind.ChangingConstraints, "where S : class, new()", FeaturesResources.type_parameter)); + Diagnostic(RudeEditKind.GenericTypeUpdate, "where T : class, I"), + Diagnostic(RudeEditKind.ChangingConstraints, "where S : class, new()", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "where S : class, new()")); } #endregion diff --git a/src/EditorFeatures/CSharpTest/MakeTypeAbstract/MakeTypeAbstractTests.cs b/src/EditorFeatures/CSharpTest/MakeTypeAbstract/MakeTypeAbstractTests.cs index f96a5079a7d62..59ce7da631124 100644 --- a/src/EditorFeatures/CSharpTest/MakeTypeAbstract/MakeTypeAbstractTests.cs +++ b/src/EditorFeatures/CSharpTest/MakeTypeAbstract/MakeTypeAbstractTests.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics; using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; using Xunit; using Xunit.Abstractions; @@ -143,7 +144,8 @@ public abstract class Goo }"); } - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/41654"), Trait(Traits.Feature, Traits.Features.CodeActionsMakeTypeAbstract)] + [WorkItem(54218, "https://github.com/dotnet/roslyn/issues/54218")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeTypeAbstract)] public async Task TestPartialClass() { await TestInRegularAndScript1Async( @@ -157,7 +159,7 @@ public partial class Goo { }", @" -public partial abstract class Goo +public abstract partial class Goo { public abstract void M(); } diff --git a/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs b/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs index b591d3a86d4d9..13b2c49012e62 100644 --- a/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs +++ b/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs @@ -71,7 +71,7 @@ public async Task NotFullyLoadedOnlyMakesOneSearchProjectCallIfValueReturned() // Simulate a host that says the solution isn't fully loaded. var hostMock = new Mock(MockBehavior.Strict); - hostMock.Setup(h => h.IsFullyLoadedAsync(It.IsAny())).Returns(IsFullyLoadedAsync(projectSystem: false, remoteHost: false)); + hostMock.Setup(h => h.IsFullyLoadedAsync(It.IsAny())).Returns(() => IsFullyLoadedAsync(projectSystem: false, remoteHost: false)); hostMock.Setup(h => h.GetNavigateToSearchService(It.IsAny())).Returns(searchService.Object); var callbackMock = new Mock(MockBehavior.Strict); @@ -113,7 +113,7 @@ public async Task NotFullyLoadedMakesTwoSearchProjectCallIfValueNotReturned(bool // Simulate a host that says the solution isn't fully loaded. var hostMock = new Mock(MockBehavior.Strict); - hostMock.Setup(h => h.IsFullyLoadedAsync(It.IsAny())).Returns(IsFullyLoadedAsync(projectSystemFullyLoaded, remoteHost: false)); + hostMock.Setup(h => h.IsFullyLoadedAsync(It.IsAny())).Returns(() => IsFullyLoadedAsync(projectSystemFullyLoaded, remoteHost: false)); hostMock.Setup(h => h.GetNavigateToSearchService(It.IsAny())).Returns(searchService.Object); var callbackMock = new Mock(MockBehavior.Strict); @@ -155,7 +155,7 @@ public async Task NotFullyLoadedStillReportsAsFullyCompletedIfSecondCallReturnsN // Simulate a host that says the solution isn't fully loaded. var hostMock = new Mock(MockBehavior.Strict); - hostMock.Setup(h => h.IsFullyLoadedAsync(It.IsAny())).Returns(IsFullyLoadedAsync(projectIsFullyLoaded, remoteHost: false)); + hostMock.Setup(h => h.IsFullyLoadedAsync(It.IsAny())).Returns(() => IsFullyLoadedAsync(projectIsFullyLoaded, remoteHost: false)); hostMock.Setup(h => h.GetNavigateToSearchService(It.IsAny())).Returns(searchService.Object); var callbackMock = new Mock(MockBehavior.Strict); @@ -193,7 +193,7 @@ public async Task FullyLoadedMakesSingleSearchProjectCallIfValueNotReturned() // Simulate a host that says the solution is fully loaded. var hostMock = new Mock(MockBehavior.Strict); - hostMock.Setup(h => h.IsFullyLoadedAsync(It.IsAny())).Returns(IsFullyLoadedAsync(projectSystem: true, remoteHost: true)); + hostMock.Setup(h => h.IsFullyLoadedAsync(It.IsAny())).Returns(() => IsFullyLoadedAsync(projectSystem: true, remoteHost: true)); hostMock.Setup(h => h.GetNavigateToSearchService(It.IsAny())).Returns(searchService.Object); var callbackMock = new Mock(MockBehavior.Strict); diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/AwaitKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/AwaitKeywordRecommenderTests.cs deleted file mode 100644 index 5a32ffd5205b4..0000000000000 --- a/src/EditorFeatures/CSharpTest2/Recommendations/AwaitKeywordRecommenderTests.cs +++ /dev/null @@ -1,120 +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. - -#nullable disable - -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Test.Utilities; -using Roslyn.Test.Utilities; -using Xunit; - -namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Recommendations -{ - public class AwaitKeywordRecommenderTests : KeywordRecommenderTests - { - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestNotInTypeContext() - { - await VerifyAbsenceAsync(@" -class Program -{ - $$ -}"); - } - - [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - [CombinatorialData] - public async Task TestStatementInMethod(bool isAsync, bool topLevelStatement) - { - await VerifyKeywordAsync(AddInsideMethod( -@"$$", isAsync: isAsync, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); - } - - [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - [CombinatorialData] - public async Task TestExpressionInAsyncMethod(bool topLevelStatement) - { - await VerifyKeywordAsync(AddInsideMethod( -@"var z = $$", isAsync: true, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); - } - - [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - [CombinatorialData] - public async Task TestUsingStatement(bool topLevelStatement) - { - await VerifyAbsenceAsync(AddInsideMethod( -@"using $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); - } - - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestUsingDirective() - => await VerifyAbsenceAsync("using $$"); - - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestGlobalUsingDirective() - => await VerifyAbsenceAsync("global using $$"); - - [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - [CombinatorialData] - public async Task TestForeachStatement(bool topLevelStatement) - { - await VerifyAbsenceAsync(AddInsideMethod( -@"foreach $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); - } - - [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - [CombinatorialData] - public async Task TestNotInQuery(bool topLevelStatement) - { - await VerifyAbsenceAsync(AddInsideMethod( -@"var z = from a in ""char"" - select $$", isAsync: true, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); - } - - [WorkItem(907052, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/907052")] - [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - [CombinatorialData] - public async Task TestInFinally(bool topLevelStatement) - { - await VerifyKeywordAsync(AddInsideMethod( -@"try { } -finally { $$ }", isAsync: true, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); - } - - [WorkItem(907052, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/907052")] - [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - [CombinatorialData] - public async Task TestInCatch(bool topLevelStatement) - { - await VerifyKeywordAsync(AddInsideMethod( -@"try { } -catch { $$ }", isAsync: true, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); - } - - [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - [CombinatorialData] - public async Task TestNotInLock(bool topLevelStatement) - { - await VerifyAbsenceAsync(AddInsideMethod( -@"lock(this) { $$ }", isAsync: true, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); - } - - [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - [CombinatorialData] - public async Task TestInAsyncLambdaInCatch(bool topLevelStatement) - { - await VerifyKeywordAsync(AddInsideMethod( -@"try { } -catch { var z = async () => $$ }", isAsync: true, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); - } - - [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - [CombinatorialData] - public async Task TestAwaitInLock(bool topLevelStatement) - { - await VerifyKeywordAsync(AddInsideMethod( -@"lock($$", isAsync: true, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); - } - } -} diff --git a/src/EditorFeatures/Core/Implementation/Classification/ClassificationTypeDefinitions.cs b/src/EditorFeatures/Core/Implementation/Classification/ClassificationTypeDefinitions.cs index f428f8836f352..cbf993aad2582 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/ClassificationTypeDefinitions.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/ClassificationTypeDefinitions.cs @@ -123,7 +123,7 @@ internal sealed class ClassificationTypeDefinitions [BaseDefinition(PredefinedClassificationTypeNames.Identifier)] internal readonly ClassificationTypeDefinition UserMembersConstantsTypeDefinition; #endregion - #region User Memebers - Locals + #region User Members - Locals [Export] [Name(ClassificationTypeNames.LocalName)] [BaseDefinition(PredefinedClassificationTypeNames.Identifier)] diff --git a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs index c431c78d5336b..805f6ee36e202 100644 --- a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs @@ -148,7 +148,8 @@ private async Task StartDebuggingSessionAsync( var sessionId = await service.StartDebuggingSessionAsync( solution, _debuggerService, - captureMatchingDocuments: false, + captureMatchingDocuments: ImmutableArray.Empty, + captureAllMatchingDocuments: false, reportDiagnostics: true, CancellationToken.None); @@ -362,8 +363,9 @@ public override Task LoadTextAndVersionAsync(Workspace workspace } } - [Fact] - public async Task StartDebuggingSession_CapturingDocuments() + [Theory] + [CombinatorialData] + public async Task StartDebuggingSession_CapturingDocuments(bool captureAllDocuments) { var encodingA = Encoding.BigEndianUnicode; var encodingB = Encoding.Unicode; @@ -454,7 +456,11 @@ public async Task StartDebuggingSession_CapturingDocuments() loader: new FailingTextLoader(), filePath: sourceFileD.Path)); - var sessionId = await service.StartDebuggingSessionAsync(solution, _debuggerService, captureMatchingDocuments: true, reportDiagnostics: true, CancellationToken.None); + var captureMatchingDocuments = captureAllDocuments ? + ImmutableArray.Empty : + (from project in solution.Projects from documentId in project.DocumentIds select documentId).ToImmutableArray(); + + var sessionId = await service.StartDebuggingSessionAsync(solution, _debuggerService, captureMatchingDocuments, captureAllDocuments, reportDiagnostics: true, CancellationToken.None); var debuggingSession = service.GetTestAccessor().GetDebuggingSession(sessionId); var matchingDocuments = debuggingSession.LastCommittedSolution.Test_GetDocumentStates(); @@ -690,7 +696,7 @@ public async Task BreakMode_DesignTimeOnlyDocument_Wpf(bool delayLoad) LoadLibraryToDebuggee(moduleId); } - var sessionId = await service.StartDebuggingSessionAsync(solution, _debuggerService, captureMatchingDocuments: false, reportDiagnostics: true, CancellationToken.None); + var sessionId = await service.StartDebuggingSessionAsync(solution, _debuggerService, captureMatchingDocuments: ImmutableArray.Empty, captureAllMatchingDocuments: false, reportDiagnostics: true, CancellationToken.None); var debuggingSession = service.GetTestAccessor().GetDebuggingSession(sessionId); EnterBreakState(debuggingSession); @@ -3573,7 +3579,8 @@ public async Task MultiSession() var sessionId = await encService.StartDebuggingSessionAsync( solution, _debuggerService, - captureMatchingDocuments: false, + captureMatchingDocuments: ImmutableArray.Empty, + captureAllMatchingDocuments: true, reportDiagnostics: true, CancellationToken.None); diff --git a/src/EditorFeatures/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs b/src/EditorFeatures/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs index 6b8bd86ee3f28..21b920503718d 100644 --- a/src/EditorFeatures/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs @@ -135,9 +135,13 @@ void VerifyReanalyzeInvocation(ImmutableArray documentIds) IManagedEditAndContinueDebuggerService? remoteDebuggeeModuleMetadataProvider = null; - var debuggingSession = mockEncService.StartDebuggingSessionImpl = (solution, debuggerService, captureMatchingDocuments, reportDiagnostics) => + var debuggingSession = mockEncService.StartDebuggingSessionImpl = (solution, debuggerService, captureMatchingDocuments, captureAllMatchingDocuments, reportDiagnostics) => { Assert.Equal("proj", solution.Projects.Single().Name); + AssertEx.Equal(new[] { document1.Id }, captureMatchingDocuments); + Assert.False(captureAllMatchingDocuments); + Assert.True(reportDiagnostics); + remoteDebuggeeModuleMetadataProvider = debuggerService; return new DebuggingSessionId(1); }; @@ -149,7 +153,8 @@ void VerifyReanalyzeInvocation(ImmutableArray documentIds) IsEditAndContinueAvailable = _ => new ManagedEditAndContinueAvailability(ManagedEditAndContinueAvailabilityStatus.NotAllowedForModule, "can't do enc"), GetActiveStatementsImpl = () => ImmutableArray.Create(as1) }, - captureMatchingDocuments: false, + captureMatchingDocuments: ImmutableArray.Create(document1.Id), + captureAllMatchingDocuments: false, reportDiagnostics: true, CancellationToken.None).ConfigureAwait(false); @@ -179,7 +184,7 @@ void VerifyReanalyzeInvocation(ImmutableArray documentIds) { Assert.Equal("proj", solution.Projects.Single().Name); Assert.Equal("test.cs", sourceFilePath); - AssertEx.Equal(activeSpans1, activeStatementSpanProvider(document1.Id, "test.cs", CancellationToken.None).Result); + AssertEx.Equal(activeSpans1, activeStatementSpanProvider(document1.Id, "test.cs", CancellationToken.None).AsTask().Result); return true; }; @@ -193,7 +198,7 @@ void VerifyReanalyzeInvocation(ImmutableArray documentIds) { var project = solution.Projects.Single(); Assert.Equal("proj", project.Name); - AssertEx.Equal(activeSpans1, activeStatementSpanProvider(document1.Id, "test.cs", CancellationToken.None).Result); + AssertEx.Equal(activeSpans1, activeStatementSpanProvider(document1.Id, "test.cs", CancellationToken.None).AsTask().Result); var deltas = ImmutableArray.Create(new ManagedModuleUpdate( module: moduleId1, @@ -283,7 +288,7 @@ void VerifyReanalyzeInvocation(ImmutableArray documentIds) { Assert.Equal("proj", solution.Projects.Single().Name); Assert.Equal(instructionId1, instructionId); - AssertEx.Equal(activeSpans1, activeStatementSpanProvider(document1.Id, "test.cs", CancellationToken.None).Result); + AssertEx.Equal(activeSpans1, activeStatementSpanProvider(document1.Id, "test.cs", CancellationToken.None).AsTask().Result); return new LinePositionSpan(new LinePosition(1, 2), new LinePosition(1, 5)); }; @@ -321,7 +326,7 @@ void VerifyReanalyzeInvocation(ImmutableArray documentIds) mockEncService.GetAdjustedActiveStatementSpansImpl = (document, activeStatementSpanProvider) => { Assert.Equal("test.cs", document.Name); - AssertEx.Equal(activeSpans1, activeStatementSpanProvider(document.Id, "test.cs", CancellationToken.None).Result); + AssertEx.Equal(activeSpans1, activeStatementSpanProvider(document.Id, "test.cs", CancellationToken.None).AsTask().Result); return ImmutableArray.Create(activeStatementSpan1); }; diff --git a/src/EditorFeatures/Test/EditAndContinue/RudeEditDiagnosticTests.cs b/src/EditorFeatures/Test/EditAndContinue/RudeEditDiagnosticTests.cs index 6cb4b1af0b2ae..3ab0c795fd1a9 100644 --- a/src/EditorFeatures/Test/EditAndContinue/RudeEditDiagnosticTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/RudeEditDiagnosticTests.cs @@ -37,8 +37,6 @@ public void ToDiagnostic() RudeEditKind.DeclareLibraryUpdate, RudeEditKind.DeclareAliasUpdate, RudeEditKind.InsertDllImport, - RudeEditKind.MethodBodyAdd, - RudeEditKind.MethodBodyDelete, RudeEditKind.GenericMethodUpdate, RudeEditKind.GenericTypeUpdate, RudeEditKind.ExperimentalFeaturesEnabled, diff --git a/src/EditorFeatures/Test2/GoToDefinition/CSharpGoToDefinitionTests.vb b/src/EditorFeatures/Test2/GoToDefinition/CSharpGoToDefinitionTests.vb index f5e8c6909494c..88d4a4bc661e5 100644 --- a/src/EditorFeatures/Test2/GoToDefinition/CSharpGoToDefinitionTests.vb +++ b/src/EditorFeatures/Test2/GoToDefinition/CSharpGoToDefinitionTests.vb @@ -3316,5 +3316,22 @@ class C Test(workspace) End Sub + + Public Sub TopLevelStatements_EmptySpace() + Dim workspace = + + + +using System; + +Console.WriteLine(1); + +$$ + + + + + Test(workspace, expectedResult:=False) + End Sub End Class End Namespace diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb new file mode 100644 index 0000000000000..98fa65e7ca75e --- /dev/null +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb @@ -0,0 +1,388 @@ +' 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.CodeAnalysis.CSharp + +Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense + + + Public Class CSharpCompletionCommandHandlerTests_Await + + Public Async Function AwaitCompletionAddsAsync_MethodDeclaration() As Task + Using state = TestStateFactory.CreateCSharpTestState( + + ) + state.SendTypeChars("aw") + Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=CSharpFeaturesResources.Make_container_async) + + state.SendTab() + Assert.Equal(" +using System.Threading.Tasks; + +public class C +{ + public static async Task Main() + { + await + } +} +", state.GetDocumentText()) + End Using + End Function + + + Public Async Function AwaitCompletionAddsAsync_LocalFunctionDeclaration() As Task + Using state = TestStateFactory.CreateCSharpTestState( + + ) + state.SendTypeChars("aw") + Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=CSharpFeaturesResources.Make_container_async) + + state.SendTab() + Assert.Equal(" +using System.Threading.Tasks; + +public class C +{ + public static Task Main() + { + async Task LocalFunc() + { + await + } + } +} +", state.GetDocumentText()) + End Using + End Function + + + Public Async Function AwaitCompletionAddsAsync_AnonymousMethodExpression_Void() As Task + Using state = TestStateFactory.CreateCSharpTestState( + a = static delegate(int i) { $$ }; + } +} +]]> + ) + state.SendTypeChars("aw") + Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=CSharpFeaturesResources.Make_container_async) + + state.SendTab() + Assert.Equal(" +using System; + +public class C +{ + public void F() + { + Action a = static async delegate(int i) { await }; + } +} +", state.GetDocumentText()) + End Using + End Function + + + Public Async Function AwaitCompletionAddsAsync_AnonymousMethodExpression_Task() As Task + Using state = TestStateFactory.CreateCSharpTestState( + a = static delegate(int i) { $$ }; + } +} +]]> + ) + state.SendTypeChars("aw") + Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=CSharpFeaturesResources.Make_container_async) + + state.SendTab() + Assert.Equal(" +using System; +using System.Threading.Tasks; + +public class C +{ + public void F() + { + Func a = static async delegate(int i) { await }; + } +} +", state.GetDocumentText()) + End Using + End Function + + + Public Async Function AwaitCompletionAddsAsync_SimpleLambdaExpression_Void() As Task + Using state = TestStateFactory.CreateCSharpTestState( + b = static a => { $$ }; + } +} +]]> + ) + state.SendTypeChars("aw") + Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=CSharpFeaturesResources.Make_container_async) + + state.SendTab() + Assert.Equal(" +using System; + +public class C +{ + public void F() + { + Action b = static async a => { await }; + } +} +", state.GetDocumentText()) + End Using + End Function + + + Public Async Function AwaitCompletionAddsAsync_SimpleLambdaExpression_Task() As Task + Using state = TestStateFactory.CreateCSharpTestState( + b = static a => { $$ }; + } +} +]]> + ) + state.SendTypeChars("aw") + Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=CSharpFeaturesResources.Make_container_async) + + state.SendTab() + Assert.Equal(" +using System; +using System.Threading.Tasks; + +public class C +{ + public void F() + { + Func b = static async a => { await }; + } +} +", state.GetDocumentText()) + End Using + End Function + + + Public Async Function AwaitCompletionAddsAsync_ParenthesizedLambdaExpression_Void() As Task + Using state = TestStateFactory.CreateCSharpTestState( + c = static (a) => { $$ }; + } +} +]]> + ) + state.SendTypeChars("aw") + Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=CSharpFeaturesResources.Make_container_async) + + state.SendTab() + Assert.Equal(" +using System; + +public class C +{ + public void F() + { + Action c = static async (a) => { await }; + } +} +", state.GetDocumentText()) + End Using + End Function + + + Public Async Function AwaitCompletionAddsAsync_ParenthesizedLambdaExpression_Task() As Task + Using state = TestStateFactory.CreateCSharpTestState( + c = static (a) => { $$ }; + } +} +]]> + ) + state.SendTypeChars("aw") + Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=CSharpFeaturesResources.Make_container_async) + + state.SendTab() + Assert.Equal(" +using System; +using System.Threading.Tasks; + +public class C +{ + public void F() + { + Func c = static async (a) => { await }; + } +} +", state.GetDocumentText()) + End Using + End Function + + + Public Async Function AwaitCompletionAddsAsync_ParenthesizedLambdaExpression_ExplicitType() As Task + Using state = TestStateFactory.CreateCSharpTestState( + c = static Task (a) => { $$ }; + } +} +]]> + ) + state.SendTypeChars("aw") + Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=CSharpFeaturesResources.Make_container_async) + + state.SendTab() + Assert.Equal(" +using System; +using System.Threading.Tasks; + +public class C +{ + public void F() + { + Func c = static async Task (a) => { await }; + } +} +", state.GetDocumentText()) + End Using + End Function + + + Public Async Function AwaitCompletionDoesNotAddAsync_NotTask() As Task + Using state = TestStateFactory.CreateCSharpTestState( + + ) + state.SendTypeChars("aw") + Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=CSharpFeaturesResources.Make_container_async) + + state.SendTab() + Assert.Equal(" +using System.Threading.Tasks; + +public class C +{ + public static async void Main() + { + await + } +} +", state.GetDocumentText()) + End Using + End Function + + + Public Async Function AwaitCompletionAddsAsync_Trivia() As Task + Using state = TestStateFactory.CreateCSharpTestState( + + ) + state.SendTypeChars("aw") + Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=CSharpFeaturesResources.Make_container_async) + + state.SendTab() + Assert.Equal(" +using System.Threading.Tasks; + +public class C +{ + public /*after public*/ static /*after static*/ async Task /*after task*/ Main() + { + await + } +} +", state.GetDocumentText()) + End Using + End Function + End Class +End Namespace diff --git a/src/EditorFeatures/TestUtilities/EditAndContinue/DocumentAnalysisResultsDescription.cs b/src/EditorFeatures/TestUtilities/EditAndContinue/DocumentAnalysisResultsDescription.cs index bb569665adeb3..549ee0fd6b8bc 100644 --- a/src/EditorFeatures/TestUtilities/EditAndContinue/DocumentAnalysisResultsDescription.cs +++ b/src/EditorFeatures/TestUtilities/EditAndContinue/DocumentAnalysisResultsDescription.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; +using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; namespace Microsoft.CodeAnalysis.EditAndContinue.UnitTests { @@ -15,14 +16,17 @@ internal readonly struct DocumentAnalysisResultsDescription /// public readonly ImmutableArray SemanticEdits; + public readonly ImmutableArray LineEdits; + public readonly ImmutableArray Diagnostics; public DocumentAnalysisResultsDescription( ActiveStatementsDescription? activeStatements = null, SemanticEditDescription[]? semanticEdits = null, + SequencePointUpdates[]? lineEdits = null, RudeEditDiagnosticDescription[]? diagnostics = null) { - // The test must validate semantic edits, diagnostics or both. + // The test must validate semantic edits, lineEdits, diagnostics or all of the above. // If neither is specified then assume the expectation is that // the documents has no edits and no diagnostics. if (semanticEdits is null && diagnostics is null) @@ -36,6 +40,7 @@ public DocumentAnalysisResultsDescription( Diagnostics = diagnostics.AsImmutableOrEmpty(); } + LineEdits = lineEdits.AsImmutableOrNull(); ActiveStatements = activeStatements ?? ActiveStatementsDescription.Empty; } } diff --git a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs index c016d6cc6552d..8c4f13303c736 100644 --- a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs +++ b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs @@ -81,48 +81,15 @@ private void VerifyDocumentActiveStatementsAndExceptionRegions( internal void VerifyLineEdits( EditScript editScript, - IEnumerable expectedLineEdits, - IEnumerable expectedNodeUpdates, - RudeEditDiagnosticDescription[] expectedDiagnostics) + SequencePointUpdates[] expectedLineEdits, + SemanticEditDescription[]? expectedSemanticEdits, + RudeEditDiagnosticDescription[]? expectedDiagnostics) { - var newText = SourceText.From(editScript.Match.NewRoot.SyntaxTree.ToString()); - - var diagnostics = new ArrayBuilder(); - var editMap = BuildEditMap(editScript); - - var triviaEdits = new ArrayBuilder<(SyntaxNode OldNode, SyntaxNode NewNode)>(); - var actualLineEdits = new ArrayBuilder(); - - Analyzer.GetTestAccessor().AnalyzeTrivia( - editScript.Match, - editMap, - triviaEdits, - actualLineEdits, - diagnostics, - default); - - VerifyDiagnostics(expectedDiagnostics, diagnostics, newText); - - // check files are matching: - AssertEx.Equal( - expectedLineEdits.Select(e => e.FileName), - actualLineEdits.Select(e => e.FileName), - itemSeparator: ",\r\n"); - - // check lines are matching: - _ = expectedLineEdits.Zip(actualLineEdits, (expected, actual) => - { - AssertEx.Equal( - expected.LineUpdates, - actual.LineUpdates, - itemSeparator: ",\r\n", - itemInspector: s => $"new({s.OldLine}, {s.NewLine})"); - - return true; - }).ToArray(); - - var actualNodeUpdates = triviaEdits.Select(e => e.NewNode.ToString().ToLines().First()); - AssertEx.Equal(expectedNodeUpdates, actualNodeUpdates, itemSeparator: ",\r\n"); + VerifySemantics( + new[] { editScript }, + TargetFramework.NetStandard20, + new[] { new DocumentAnalysisResultsDescription(semanticEdits: expectedSemanticEdits, lineEdits: expectedLineEdits, diagnostics: expectedDiagnostics) }, + capabilities: Net5RuntimeCapabilities); } internal void VerifySemantics(EditScript[] editScripts, TargetFramework targetFramework, DocumentAnalysisResultsDescription[] expectedResults, EditAndContinueCapabilities? capabilities = null) @@ -205,6 +172,34 @@ internal void VerifySemantics(EditScript[] editScripts, TargetFramew result.ActiveStatements, result.ExceptionRegions); } + + if (!result.RudeEditErrors.IsEmpty) + { + Assert.True(result.LineEdits.IsDefault); + Assert.True(expectedResult.LineEdits.IsDefaultOrEmpty); + } + else if (!expectedResult.LineEdits.IsDefault) + { + // check files of line edits: + AssertEx.Equal( + expectedResult.LineEdits.Select(e => e.FileName), + result.LineEdits.Select(e => e.FileName), + itemSeparator: ",\r\n", + message: "File names of line edits differ in " + assertMessagePrefix); + + // check lines of line edits: + _ = expectedResult.LineEdits.Zip(result.LineEdits, (expected, actual) => + { + AssertEx.Equal( + expected.LineUpdates, + actual.LineUpdates, + itemSeparator: ",\r\n", + itemInspector: s => $"new({s.OldLine}, {s.NewLine})", + message: "Line deltas differ in " + assertMessagePrefix); + + return true; + }).ToArray(); + } } // check if we can merge edits without throwing: diff --git a/src/EditorFeatures/TestUtilities/EditAndContinue/MockEditAndContinueWorkspaceService.cs b/src/EditorFeatures/TestUtilities/EditAndContinue/MockEditAndContinueWorkspaceService.cs index 0016badd68855..be25d9f98bb7e 100644 --- a/src/EditorFeatures/TestUtilities/EditAndContinue/MockEditAndContinueWorkspaceService.cs +++ b/src/EditorFeatures/TestUtilities/EditAndContinue/MockEditAndContinueWorkspaceService.cs @@ -19,7 +19,7 @@ internal class MockEditAndContinueWorkspaceService : IEditAndContinueWorkspaceSe public Func? GetCurrentActiveStatementPositionImpl; public Func>? GetAdjustedActiveStatementSpansImpl; - public Func? StartDebuggingSessionImpl; + public Func, bool, bool, DebuggingSessionId>? StartDebuggingSessionImpl; public ActionOut>? EndDebuggingSessionImpl; public Func? HasChangesImpl; @@ -76,7 +76,7 @@ public ValueTask HasChangesAsync(DebuggingSessionId sessionId, Solution so public void OnSourceFileUpdated(Document document) => OnSourceFileUpdatedImpl?.Invoke(document); - public ValueTask StartDebuggingSessionAsync(Solution solution, IManagedEditAndContinueDebuggerService debuggerService, bool captureMatchingDocuments, bool reportDiagnostics, CancellationToken cancellationToken) - => new((StartDebuggingSessionImpl ?? throw new NotImplementedException()).Invoke(solution, debuggerService, captureMatchingDocuments, reportDiagnostics)); + public ValueTask StartDebuggingSessionAsync(Solution solution, IManagedEditAndContinueDebuggerService debuggerService, ImmutableArray captureMatchingDocuments, bool captureAllMatchingDocuments, bool reportDiagnostics, CancellationToken cancellationToken) + => new((StartDebuggingSessionImpl ?? throw new NotImplementedException()).Invoke(solution, debuggerService, captureMatchingDocuments, captureAllMatchingDocuments, reportDiagnostics)); } } diff --git a/src/EditorFeatures/TestUtilities/Workspaces/TestHostDocument.cs b/src/EditorFeatures/TestUtilities/Workspaces/TestHostDocument.cs index 4612eb1c0fe90..74caef655974a 100644 --- a/src/EditorFeatures/TestUtilities/Workspaces/TestHostDocument.cs +++ b/src/EditorFeatures/TestUtilities/Workspaces/TestHostDocument.cs @@ -60,7 +60,7 @@ public DocumentId Id { var workspace = _languageServiceProvider!.WorkspaceServices.Workspace; var project = workspace.CurrentSolution.GetRequiredProject(_project!.Id); - var sourceGeneratedDocuments = project.GetSourceGeneratedDocumentsAsync(CancellationToken.None).Result; + var sourceGeneratedDocuments = project.GetSourceGeneratedDocumentsAsync(CancellationToken.None).AsTask().Result; _id = sourceGeneratedDocuments.Single(d => d.FilePath == this.FilePath).Id; } diff --git a/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs b/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs index c41c6e033352b..edc6f436460f2 100644 --- a/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs +++ b/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs @@ -537,7 +537,7 @@ private static string GetRootNamespace(TestWorkspace workspace, CompilationOptio if (GetLanguage(workspace, projectElement) == LanguageNames.VisualBasic) { - // For VB tests, root namespace value must be defined in compilation options element, + // For VB tests, root namespace value must be defined in compilation options element, // it can't use the property in project element to avoid confusion. Assert.Null(rootNamespaceAttribute); @@ -876,7 +876,7 @@ private static IReadOnlyList GetFolders(XElement documentElement) } /// - /// Takes completely valid code, compiles it, and emits it to a MetadataReference without using + /// Takes completely valid code, compiles it, and emits it to a MetadataReference without using /// the file system /// private static MetadataReference CreateMetadataReferenceFromSource(TestWorkspace workspace, XElement referencedSource) @@ -955,8 +955,7 @@ private static IList CreateReferenceList(TestWorkspace worksp // objects that are no longer in use. There are no public APIs available to directly dispose of these // images, so we are relying on GC running finalizers to avoid OutOfMemoryException during tests. var content = File.ReadAllBytes(reference.Value); - var peImage = ImmutableArrayExtensions.DangerousCreateFromUnderlyingArray(ref content); - references.Add(MetadataReference.CreateFromImage(peImage, filePath: reference.Value)); + references.Add(MetadataReference.CreateFromImage(content, filePath: reference.Value)); } foreach (var metadataReferenceFromSource in element.Elements(MetadataReferenceFromSourceElementName)) diff --git a/src/EditorFeatures/VisualBasicTest/Diagnostics/AddImport/AddImportTests_NuGet.vb b/src/EditorFeatures/VisualBasicTest/Diagnostics/AddImport/AddImportTests_NuGet.vb index e644a5eeeab94..8d6d0f351eeb8 100644 --- a/src/EditorFeatures/VisualBasicTest/Diagnostics/AddImport/AddImportTests_NuGet.vb +++ b/src/EditorFeatures/VisualBasicTest/Diagnostics/AddImport/AddImportTests_NuGet.vb @@ -57,9 +57,9 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.CodeActions.AddImp Dim packageServiceMock = New Mock(Of ISymbolSearchService)(MockBehavior.Strict) packageServiceMock.Setup(Function(s) s.FindReferenceAssembliesWithTypeAsync("NuGetType", 0, It.IsAny(Of CancellationToken))). - Returns(ValueTaskFactory.FromResult(ImmutableArray(Of ReferenceAssemblyWithTypeResult).Empty)) + Returns(Function() ValueTaskFactory.FromResult(ImmutableArray(Of ReferenceAssemblyWithTypeResult).Empty)) packageServiceMock.Setup(Function(s) s.FindPackagesWithTypeAsync(NugetOrgSource, "NuGetType", 0, It.IsAny(Of CancellationToken)())). - Returns(CreateSearchResult("NuGetPackage", "NuGetType", CreateNameParts("NuGetNamespace"))) + Returns(Function() CreateSearchResult("NuGetPackage", "NuGetType", CreateNameParts("NuGetNamespace"))) Await TestInRegularAndScriptAsync( " @@ -86,9 +86,9 @@ End Class", fixProviderData:=New ProviderData(installerServiceMock.Object, packa Dim packageServiceMock = New Mock(Of ISymbolSearchService)(MockBehavior.Strict) packageServiceMock.Setup(Function(s) s.FindReferenceAssembliesWithTypeAsync("NuGetType", 0, It.IsAny(Of CancellationToken))). - Returns(ValueTaskFactory.FromResult(ImmutableArray(Of ReferenceAssemblyWithTypeResult).Empty)) + Returns(Function() ValueTaskFactory.FromResult(ImmutableArray(Of ReferenceAssemblyWithTypeResult).Empty)) packageServiceMock.Setup(Function(s) s.FindPackagesWithTypeAsync(NugetOrgSource, "NuGetType", 0, It.IsAny(Of CancellationToken)())). - Returns(CreateSearchResult("NuGetPackage", "NuGetType", CreateNameParts("NS1", "NS2"))) + Returns(Function() CreateSearchResult("NuGetPackage", "NuGetType", CreateNameParts("NS1", "NS2"))) Await TestInRegularAndScriptAsync( " @@ -115,9 +115,9 @@ End Class", fixProviderData:=New ProviderData(installerServiceMock.Object, packa Dim packageServiceMock = New Mock(Of ISymbolSearchService)(MockBehavior.Strict) packageServiceMock.Setup(Function(s) s.FindReferenceAssembliesWithTypeAsync("NuGetType", 0, It.IsAny(Of CancellationToken))). - Returns(ValueTaskFactory.FromResult(ImmutableArray(Of ReferenceAssemblyWithTypeResult).Empty)) + Returns(Function() ValueTaskFactory.FromResult(ImmutableArray(Of ReferenceAssemblyWithTypeResult).Empty)) packageServiceMock.Setup(Function(s) s.FindPackagesWithTypeAsync(NugetOrgSource, "NuGetType", 0, It.IsAny(Of CancellationToken)())). - Returns(CreateSearchResult("NuGetPackage", "NuGetType", CreateNameParts("NS1", "NS2"))) + Returns(Function() CreateSearchResult("NuGetPackage", "NuGetType", CreateNameParts("NS1", "NS2"))) Await TestInRegularAndScriptAsync( " @@ -140,9 +140,9 @@ End Class", fixProviderData:=New ProviderData(installerServiceMock.Object, packa Dim packageServiceMock = New Mock(Of ISymbolSearchService)(MockBehavior.Strict) packageServiceMock.Setup(Function(s) s.FindReferenceAssembliesWithTypeAsync("NuGetType", 0, It.IsAny(Of CancellationToken))). - Returns(ValueTaskFactory.FromResult(ImmutableArray(Of ReferenceAssemblyWithTypeResult).Empty)) + Returns(Function() ValueTaskFactory.FromResult(ImmutableArray(Of ReferenceAssemblyWithTypeResult).Empty)) packageServiceMock.Setup(Function(s) s.FindPackagesWithTypeAsync(NugetOrgSource, "NuGetType", 0, It.IsAny(Of CancellationToken)())). - Returns(CreateSearchResult("NuGetPackage", "NuGetType", CreateNameParts("NS1", "NS2"))) + Returns(Function() CreateSearchResult("NuGetPackage", "NuGetType", CreateNameParts("NS1", "NS2"))) Await TestMissingInRegularAndScriptAsync( " @@ -165,9 +165,9 @@ New TestParameters(fixProviderData:=New ProviderData(installerServiceMock.Object Dim packageServiceMock = New Mock(Of ISymbolSearchService)(MockBehavior.Strict) packageServiceMock.Setup(Function(s) s.FindReferenceAssembliesWithTypeAsync("NuGetType", 0, It.IsAny(Of CancellationToken))). - Returns(ValueTaskFactory.FromResult(ImmutableArray(Of ReferenceAssemblyWithTypeResult).Empty)) + Returns(Function() ValueTaskFactory.FromResult(ImmutableArray(Of ReferenceAssemblyWithTypeResult).Empty)) packageServiceMock.Setup(Function(s) s.FindPackagesWithTypeAsync(NugetOrgSource, "NuGetType", 0, It.IsAny(Of CancellationToken)())). - Returns(CreateSearchResult("NuGetPackage", "NuGetType", CreateNameParts("NS1", "NS2"))) + Returns(Function() CreateSearchResult("NuGetPackage", "NuGetType", CreateNameParts("NS1", "NS2"))) Dim data = New ProviderData(installerServiceMock.Object, packageServiceMock.Object) Await TestSmartTagTextAsync( @@ -207,9 +207,9 @@ parameters:=New TestParameters(index:=2, fixProviderData:=data)) Dim packageServiceMock = New Mock(Of ISymbolSearchService)(MockBehavior.Strict) packageServiceMock.Setup(Function(s) s.FindReferenceAssembliesWithTypeAsync("NuGetType", 0, It.IsAny(Of CancellationToken))). - Returns(ValueTaskFactory.FromResult(ImmutableArray(Of ReferenceAssemblyWithTypeResult).Empty)) + Returns(Function() ValueTaskFactory.FromResult(ImmutableArray(Of ReferenceAssemblyWithTypeResult).Empty)) packageServiceMock.Setup(Function(s) s.FindPackagesWithTypeAsync(NugetOrgSource, "NuGetType", 0, It.IsAny(Of CancellationToken)())). - Returns(CreateSearchResult("NuGetPackage", "NuGetType", CreateNameParts("NuGetNamespace"))) + Returns(Function() CreateSearchResult("NuGetPackage", "NuGetType", CreateNameParts("NuGetNamespace"))) Await TestInRegularAndScriptAsync( " @@ -239,9 +239,9 @@ End Class", fixProviderData:=New ProviderData(installerServiceMock.Object, packa Dim packageServiceMock = New Mock(Of ISymbolSearchService)(MockBehavior.Strict) packageServiceMock.Setup(Function(s) s.FindReferenceAssembliesWithTypeAsync("NuGetType", 0, It.IsAny(Of CancellationToken))). - Returns(ValueTaskFactory.FromResult(ImmutableArray(Of ReferenceAssemblyWithTypeResult).Empty)) + Returns(Function() ValueTaskFactory.FromResult(ImmutableArray(Of ReferenceAssemblyWithTypeResult).Empty)) packageServiceMock.Setup(Function(s) s.FindPackagesWithTypeAsync(NugetOrgSource, "NuGetType", 0, It.IsAny(Of CancellationToken)())). - Returns(CreateSearchResult("NuGetPackage", "NuGetType", CreateNameParts("NuGetNamespace"))) + Returns(Function() CreateSearchResult("NuGetPackage", "NuGetType", CreateNameParts("NuGetNamespace"))) Await TestInRegularAndScriptAsync( " diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditAndContinueValidation.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditAndContinueValidation.vb index 2e153ed184fe0..c540b86ec0dd5 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditAndContinueValidation.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditAndContinueValidation.vb @@ -28,25 +28,25 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests Friend Sub VerifyLineEdits(editScript As EditScript(Of SyntaxNode), - expectedLineEdits As IEnumerable(Of SourceLineUpdate), - expectedNodeUpdates As IEnumerable(Of String), - ParamArray expectedDiagnostics As RudeEditDiagnosticDescription()) - Assert.NotEmpty(expectedLineEdits) + lineEdits As SourceLineUpdate(), + Optional semanticEdits As SemanticEditDescription() = Nothing, + Optional diagnostics As RudeEditDiagnosticDescription() = Nothing) + Assert.NotEmpty(lineEdits) VerifyLineEdits( editScript, - {New SequencePointUpdates(editScript.Match.OldRoot.SyntaxTree.FilePath, expectedLineEdits.ToImmutableArray())}, - expectedNodeUpdates, - expectedDiagnostics) + {New SequencePointUpdates(editScript.Match.OldRoot.SyntaxTree.FilePath, lineEdits.ToImmutableArray())}, + semanticEdits, + diagnostics) End Sub Friend Sub VerifyLineEdits(editScript As EditScript(Of SyntaxNode), - expectedLineEdits As IEnumerable(Of SequencePointUpdates), - expectedNodeUpdates As IEnumerable(Of String), - ParamArray expectedDiagnostics As RudeEditDiagnosticDescription()) + lineEdits As SequencePointUpdates(), + Optional semanticEdits As SemanticEditDescription() = Nothing, + Optional diagnostics As RudeEditDiagnosticDescription() = Nothing) Dim validator = New VisualBasicEditAndContinueTestHelpers() - validator.VerifyLineEdits(editScript, expectedLineEdits, expectedNodeUpdates, expectedDiagnostics) + validator.VerifyLineEdits(editScript, lineEdits, semanticEdits, diagnostics) End Sub @@ -76,7 +76,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests Optional capabilities As EditAndContinueCapabilities? = Nothing) VerifySemantics( {editScript}, - {New DocumentAnalysisResultsDescription(activeStatements, semanticEdits, diagnostics)}, + {New DocumentAnalysisResultsDescription(activeStatements, semanticEdits, lineEdits:=Nothing, diagnostics)}, targetFrameworks, capabilities) End Sub diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb index 5a6a8a423c7ee..062db44057304 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb @@ -82,19 +82,19 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests Optional activeStatements As ActiveStatementsDescription = Nothing, Optional semanticEdits As SemanticEditDescription() = Nothing, Optional diagnostics As RudeEditDiagnosticDescription() = Nothing) As DocumentAnalysisResultsDescription - Return New DocumentAnalysisResultsDescription(activeStatements, semanticEdits, diagnostics) + Return New DocumentAnalysisResultsDescription(activeStatements, semanticEdits, lineEdits:=Nothing, diagnostics) End Function - Private Shared Function ParseSource(markedSource As String) As SyntaxTree + Private Shared Function ParseSource(markedSource As String, Optional documentIndex As Integer = 0) As SyntaxTree Return SyntaxFactory.ParseSyntaxTree( ActiveStatementsDescription.ClearTags(markedSource), VisualBasicParseOptions.Default.WithLanguageVersion(LanguageVersion.Latest), - path:="test.vb") + path:=documentIndex.ToString()) End Function - Friend Shared Function GetTopEdits(src1 As String, src2 As String) As EditScript(Of SyntaxNode) - Dim tree1 = ParseSource(src1) - Dim tree2 = ParseSource(src2) + Friend Shared Function GetTopEdits(src1 As String, src2 As String, Optional documentIndex As Integer = 0) As EditScript(Of SyntaxNode) + Dim tree1 = ParseSource(src1, documentIndex) + Dim tree2 = ParseSource(src2, documentIndex) tree1.GetDiagnostics().Verify() tree2.GetDiagnostics().Verify() diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/LineEditTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/LineEditTests.vb index a3b70d11715e1..4f9a5ed622202 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/LineEditTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/LineEditTests.vb @@ -38,7 +38,9 @@ End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), Array.Empty(Of String)) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("C.Bar"))}) End Sub @@ -190,7 +192,9 @@ End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Shared Sub _"}) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("C.Bar"))}) End Sub @@ -212,7 +216,9 @@ End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Shared Sub Bar()"}) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("C.Bar"))}) End Sub @@ -286,13 +292,16 @@ End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Shared Sub Bar()"}) - Dim active = GetActiveStatements(src1, src2) Dim syntaxMap = GetSyntaxMap(src1, src2) - edits.VerifySemantics(active, - {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("C.Bar"), syntaxMap(0))}) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("C.Bar"))}) + + edits.VerifySemantics( + active, + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("C.Bar"), syntaxMap:=syntaxMap(0))}) End Sub @@ -310,7 +319,9 @@ End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Shared Sub Bar() : End Sub"}) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("C.Bar"))}) End Sub @@ -333,8 +344,8 @@ End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({New SourceLineUpdate(4, 3)}, - Array.Empty(Of String)) + edits.VerifyLineEdits( + {New SourceLineUpdate(4, 3)}) End Sub @@ -355,9 +366,9 @@ Class C(Of T) End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), - {"Shared Sub Bar()"}, - Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, vbCrLf & " ", FeaturesResources.method)) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + diagnostics:={Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, vbCrLf & " ", FeaturesResources.method)}) End Sub @@ -379,9 +390,9 @@ End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), - {"Shared Sub Bar(Of T)()"}, - Diagnostic(RudeEditKind.GenericMethodTriviaUpdate, vbCrLf & " ", FeaturesResources.method)) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + diagnostics:={Diagnostic(RudeEditKind.GenericMethodTriviaUpdate, vbCrLf & " ", FeaturesResources.method)}) End Sub @@ -404,8 +415,9 @@ End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), - {"Shared Async Function Bar() As Task(Of Integer)"}) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("C.Bar"), preserveLocalVariables:=True)}) End Sub #End Region @@ -430,7 +442,9 @@ End Class" End Class" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Shared Sub _"}) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -451,7 +465,9 @@ End Class" End Class" Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Shared Sub _"}) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single())}) End Sub #End Region @@ -473,7 +489,9 @@ Class C End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({New SourceLineUpdate(2, 3), New SourceLineUpdate(3, 2)}, {}) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + diagnostics:={Diagnostic(RudeEditKind.Move, "Shared Bar As Integer = 2", FeaturesResources.field)}) End Sub @@ -491,7 +509,9 @@ Class C End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({New SourceLineUpdate(2, 3), New SourceLineUpdate(3, 2)}, {}) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + diagnostics:={Diagnostic(RudeEditKind.Move, "Shared c As New C()", FeaturesResources.field)}) End Sub @@ -511,8 +531,9 @@ End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({New SourceLineUpdate(2, 3), - New SourceLineUpdate(3, 2)}, {}) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + diagnostics:={Diagnostic(RudeEditKind.Move, "Shared c, d As New C()", FeaturesResources.field)}) End Sub @@ -606,7 +627,9 @@ End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits({New SourceLineUpdate(2, 3)}, {"Goo"}) + edits.VerifyLineEdits( + {New SourceLineUpdate(2, 3)}, + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -662,7 +685,9 @@ End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Goo = _"}) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -681,7 +706,9 @@ End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Goo _ "}) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -700,7 +727,9 @@ End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Goo _"}) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -718,7 +747,9 @@ End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Goo = 1"}) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -736,7 +767,9 @@ End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Goo As Integer = 1 + 1"}) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -755,7 +788,9 @@ End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Goo As _"}) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -774,7 +809,9 @@ End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Goo _"}) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -795,7 +832,9 @@ End Class Dim edits = GetTopEdits(src1, src2) ' to make it simpler, we recompile the constructor (by reporting a field as a node update) - edits.VerifyLineEdits({New SourceLineUpdate(2, 3)}, {"Goo"}) + edits.VerifyLineEdits( + {New SourceLineUpdate(2, 3)}, + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -815,7 +854,9 @@ End Class Dim edits = GetTopEdits(src1, src2) ' we treat "Goo + New D()" as a whole for simplicity - edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Goo", "Bar"}) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -833,7 +874,9 @@ End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Goo", "Bar"}) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -852,7 +895,9 @@ End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Goo", "Bar"}) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -869,7 +914,9 @@ Class C End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Goo(1)"}) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -887,9 +934,9 @@ End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), - {"Goo As Integer = 1 + 1"}, - Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, " ", FeaturesResources.field)) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + diagnostics:={Diagnostic(RudeEditKind.GenericTypeUpdate, "Class C(Of T)")}) End Sub #End Region @@ -1043,7 +1090,9 @@ End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Property Goo _"}) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -1062,7 +1111,9 @@ End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Property Goo As _"}) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -1081,7 +1132,9 @@ End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Property Goo As Integer _"}) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -1100,7 +1153,9 @@ End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Property Goo As Integer = _"}) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -1119,7 +1174,9 @@ End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Property Goo As _"}) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -1138,7 +1195,9 @@ End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifyLineEdits(Array.Empty(Of SequencePointUpdates), {"Property Goo$ = _"}) + edits.VerifyLineEdits( + Array.Empty(Of SequencePointUpdates), + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub #End Region @@ -1195,9 +1254,10 @@ End Class AbstractEditAndContinueAnalyzer.CreateZeroDeltaSourceLineUpdate(5),' lines between F2 And D ctor New SourceLineUpdate(7, 17)))' D ctor }, + semanticEdits:= { - "Sub F3() : End Sub", ' overlaps with "Sub F1" - "Sub F4() : End Sub" ' overlaps with "Sub F2" + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("D.F3")), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("D.F4")) }) End Sub @@ -1257,8 +1317,7 @@ End Class" { New SequencePointUpdates("a", ImmutableArray.Create(New SourceLineUpdate(0, 1))), New SequencePointUpdates("b", ImmutableArray.Create(New SourceLineUpdate(0, 1))) - }, - Array.Empty(Of String)) + }) End Sub @@ -1298,7 +1357,7 @@ End Class" { New SequencePointUpdates("a", ImmutableArray.Create(New SourceLineUpdate(6, 4))) }, - {"Sub F()"}) + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("C.F"))}) edits.VerifySemantics(ActiveStatementsDescription.Empty, { @@ -1328,8 +1387,7 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifyLineEdits( Array.Empty(Of SequencePointUpdates)(), - {"Sub Bar(Of T)()"}, - Diagnostic(RudeEditKind.GenericMethodTriviaUpdate, "Sub Bar(Of T)()", FeaturesResources.method)) + diagnostics:={Diagnostic(RudeEditKind.GenericMethodTriviaUpdate, "Sub Bar(Of T)()", FeaturesResources.method)}) End Sub #End Region diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb index d0750b0576f5e..76ea68af92414 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb @@ -1061,7 +1061,8 @@ End Class" Diagnostic(RudeEditKind.InsertIntoGenericType, "F4 As New Object", FeaturesResources.field), Diagnostic(RudeEditKind.InsertIntoGenericType, "F5(1, 2)", FeaturesResources.field), Diagnostic(RudeEditKind.InsertIntoGenericType, "F6?", FeaturesResources.field), - Diagnostic(RudeEditKind.InsertIntoGenericType, "WE As Object", VBFeaturesResources.WithEvents_field)) + Diagnostic(RudeEditKind.InsertIntoGenericType, "WE As Object", VBFeaturesResources.WithEvents_field), + Diagnostic(RudeEditKind.GenericTypeUpdate, "Class C(Of T)")) End Sub @@ -1253,9 +1254,15 @@ End Interface DocumentResults( diagnostics:= { - Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, "Sub F()", FeaturesResources.method), - Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, "Sub F()", FeaturesResources.method), - Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, "Sub F()", FeaturesResources.method) + Diagnostic(RudeEditKind.GenericTypeUpdate, "Class C(Of T)"), + Diagnostic(RudeEditKind.GenericTypeUpdate, "Structure S(Of T)"), + Diagnostic(RudeEditKind.GenericTypeUpdate, "Interface I(Of T)"), + Diagnostic(RudeEditKind.GenericTypeUpdate, "Sub F()"), + Diagnostic(RudeEditKind.GenericTypeUpdate, "Sub F()"), + Diagnostic(RudeEditKind.GenericTypeUpdate, "Sub F()"), + Diagnostic(RudeEditKind.GenericTypeUpdate, "T"), + Diagnostic(RudeEditKind.GenericTypeUpdate, "T"), + Diagnostic(RudeEditKind.GenericTypeUpdate, "T") }) }) End Sub @@ -1574,21 +1581,20 @@ End Class "Update [Blue = 2]@23 -> [Blue = 2 << 1]@28") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.InitializerUpdate, "Red = 1 << 0", FeaturesResources.enum_value), Diagnostic(RudeEditKind.InitializerUpdate, "Blue = 2 << 1", FeaturesResources.enum_value)) End Sub Public Sub Enum_MemberInitializer_Update3() - Dim src1 = "Enum Color : Red = int.MinValue : End Enum" - Dim src2 = "Enum Color : Red = int.MaxValue : End Enum" + Dim src1 = "Enum Color : Red = Integer.MinValue : End Enum" + Dim src2 = "Enum Color : Red = Integer.MaxValue : End Enum" Dim edits = GetTopEdits(src1, src2) edits.VerifyEdits( - "Update [Red = int.MinValue]@13 -> [Red = int.MaxValue]@13") + "Update [Red = Integer.MinValue]@13 -> [Red = Integer.MaxValue]@13") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.InitializerUpdate, "Red = int.MaxValue", FeaturesResources.enum_value)) + Diagnostic(RudeEditKind.InitializerUpdate, "Red = Integer.MaxValue", FeaturesResources.enum_value)) End Sub @@ -1886,7 +1892,8 @@ End Class "Update [T]@30 -> [S]@30") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Renamed, "S", FeaturesResources.type_parameter)) + Diagnostic(RudeEditKind.Renamed, "S", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "S")) End Sub @@ -1899,7 +1906,8 @@ End Class "Update [T]@30 -> [In T]@30") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.VarianceUpdate, "T", FeaturesResources.type_parameter)) + Diagnostic(RudeEditKind.VarianceUpdate, "T", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "T")) End Sub @@ -1912,7 +1920,8 @@ End Class "Update [Out T]@30 -> [T]@30") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.VarianceUpdate, "T", FeaturesResources.type_parameter)) + Diagnostic(RudeEditKind.VarianceUpdate, "T", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "T")) End Sub @@ -1925,7 +1934,8 @@ End Class "Update [Out T]@30 -> [In T]@30") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.VarianceUpdate, "T", FeaturesResources.type_parameter)) + Diagnostic(RudeEditKind.VarianceUpdate, "T", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "T")) End Sub @@ -2497,7 +2507,12 @@ End Structure { DocumentResults(), DocumentResults( - diagnostics:={Diagnostic(RudeEditKind.ChangingConstraints, "T", FeaturesResources.type_parameter)}), + diagnostics:= + { + Diagnostic(RudeEditKind.GenericTypeUpdate, "Partial Class C(Of T As New)"), + Diagnostic(RudeEditKind.ChangingConstraints, "T", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "T") + }), DocumentResults() }) End Sub @@ -2804,14 +2819,14 @@ End Class Dim srcA2 = "Partial Class C : End Class" Dim srcB2 = "Partial Class C" + vbCrLf + "Sub F(Of T)() : End Sub : End Class" - ' TODO better message EditAndContinueValidation.VerifySemantics( {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, { DocumentResults(), DocumentResults(diagnostics:= { - Diagnostic(RudeEditKind.GenericMethodTriviaUpdate, "Sub F(Of T)()", FeaturesResources.method) + Diagnostic(RudeEditKind.GenericMethodUpdate, "Sub F(Of T)()"), + Diagnostic(RudeEditKind.GenericMethodUpdate, "T") }) }) End Sub @@ -2830,7 +2845,8 @@ End Class DocumentResults(), DocumentResults(diagnostics:= { - Diagnostic(RudeEditKind.GenericMethodTriviaUpdate, "Sub F(Of T)()", FeaturesResources.method) + Diagnostic(RudeEditKind.GenericMethodUpdate, "Sub F(Of T)()"), + Diagnostic(RudeEditKind.GenericMethodUpdate, "T") }) }) End Sub @@ -3893,12 +3909,48 @@ End Class Public Sub MethodUpdate_ImplementsDelete() - Dim src1 = "Class C : Implements I, J : " & vbLf & "Sub Goo Implements I.Goo : End Sub : " & vbLf & "Sub JGoo Implements J.Goo : End Sub : End Class" - Dim src2 = "Class C : Implements I, J : " & vbLf & "Sub Goo : End Sub : " & vbLf & "Sub JGoo Implements J.Goo : End Sub : End Class" + Dim src1 = " +Class C + Implements I, J + + Sub Goo Implements I.Goo + End Sub + + Sub JGoo Implements J.Goo + End Sub +End Class + +Interface I + Sub Goo +End Interface + +Interface J + Sub Goo +End Interface +" + Dim src2 = " +Class C + Implements I, J + + Sub Goo + End Sub + + Sub JGoo Implements J.Goo + End Sub +End Class + +Interface I + Sub Goo +End Interface + +Interface J + Sub Goo +End Interface +" Dim edits = GetTopEdits(src1, src2) edits.VerifyEdits( - "Update [Sub Goo Implements I.Goo]@29 -> [Sub Goo]@29") + "Update [Sub Goo Implements I.Goo]@39 -> [Sub Goo]@39") edits.VerifyRudeDiagnostics( Diagnostic(RudeEditKind.ImplementsClauseUpdate, "Sub Goo", FeaturesResources.method)) @@ -3906,12 +3958,49 @@ End Class Public Sub MethodUpdate_ImplementsInsert() - Dim src1 = "Class C : Implements I, J : " & vbLf & "Sub Goo : End Sub : " & vbLf & "Sub JGoo Implements J.Goo : End Sub : End Class" - Dim src2 = "Class C : Implements I, J : " & vbLf & "Sub Goo Implements I.Goo : End Sub : " & vbLf & "Sub JGoo Implements J.Goo : End Sub : End Class" + Dim src1 = " +Class C + Implements I, J + + Sub Goo + End Sub + + Sub JGoo Implements J.Goo + End Sub +End Class + +Interface I + Sub Goo +End Interface + +Interface J + Sub Goo +End Interface +" + Dim src2 = " +Class C + Implements I, J + + Sub Goo Implements I.Goo + End Sub + + Sub JGoo Implements J.Goo + End Sub +End Class + +Interface I + Sub Goo +End Interface + +Interface J + Sub Goo +End Interface +" + Dim edits = GetTopEdits(src1, src2) edits.VerifyEdits( - "Update [Sub Goo]@29 -> [Sub Goo Implements I.Goo]@29") + "Update [Sub Goo]@39 -> [Sub Goo Implements I.Goo]@39") edits.VerifyRudeDiagnostics( Diagnostic(RudeEditKind.ImplementsClauseUpdate, "Sub Goo", FeaturesResources.method)) @@ -3919,16 +4008,53 @@ End Class Public Sub MethodUpdate_ImplementsUpdate() - Dim src1 = "Class C : Implements I, J : " & vbLf & "Sub IGoo Implements I.Goo : End Sub : " & vbLf & "Sub JGoo Implements J.Goo : End Sub : End Class" - Dim src2 = "Class C : Implements I, J : " & vbLf & "Sub IGoo Implements J.Goo : End Sub : " & vbLf & "Sub JGoo Implements I.Goo : End Sub : End Class" + Dim src1 = " +Class C + Implements I, J + + Sub Goo Implements I.Goo + End Sub + + Sub JGoo Implements J.Goo + End Sub +End Class + +Interface I + Sub Goo +End Interface + +Interface J + Sub Goo +End Interface +" + Dim src2 = " +Class C + Implements I, J + + Sub Goo Implements J.Goo + End Sub + + Sub JGoo Implements I.Goo + End Sub +End Class + +Interface I + Sub Goo +End Interface + +Interface J + Sub Goo +End Interface +" + Dim edits = GetTopEdits(src1, src2) edits.VerifyEdits( - "Update [Sub IGoo Implements I.Goo]@29 -> [Sub IGoo Implements J.Goo]@29", - "Update [Sub JGoo Implements J.Goo]@68 -> [Sub JGoo Implements I.Goo]@68") + "Update [Sub Goo Implements I.Goo]@39 -> [Sub Goo Implements J.Goo]@39", + "Update [Sub JGoo Implements J.Goo]@84 -> [Sub JGoo Implements I.Goo]@84") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ImplementsClauseUpdate, "Sub IGoo", FeaturesResources.method), + Diagnostic(RudeEditKind.ImplementsClauseUpdate, "Sub Goo", FeaturesResources.method), Diagnostic(RudeEditKind.ImplementsClauseUpdate, "Sub JGoo", FeaturesResources.method)) End Sub @@ -4538,7 +4664,7 @@ End Class "[Public Sub New(a As Integer) : End Sub]@14") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.GenericTypeUpdate, "Public Sub New(a As Integer)", FeaturesResources.constructor)) + Diagnostic(RudeEditKind.GenericTypeUpdate, "Public Sub New(a As Integer)")) End Sub @@ -7582,7 +7708,8 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.GenericTypeInitializerUpdate, "a As Integer = 2", FeaturesResources.field)) + Diagnostic(RudeEditKind.GenericTypeUpdate, "a As Integer = 2"), + Diagnostic(RudeEditKind.GenericTypeUpdate, "Class C(Of T)")) End Sub @@ -7592,7 +7719,8 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.GenericTypeInitializerUpdate, "Property a", FeaturesResources.auto_property)) + Diagnostic(RudeEditKind.GenericTypeUpdate, "Property a"), + Diagnostic(RudeEditKind.GenericTypeUpdate, "Class C(Of T)")) End Sub @@ -8023,7 +8151,7 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Update, "x = 1", FeaturesResources.const_field)) + Diagnostic(RudeEditKind.InitializerUpdate, "x = 1", FeaturesResources.const_field)) End Sub @@ -9576,7 +9704,6 @@ End Class "Insert [A]@27") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.GenericMethodTriviaUpdate, " ", FeaturesResources.method), Diagnostic(RudeEditKind.Insert, "A", FeaturesResources.type_parameter)) End Sub @@ -9606,7 +9733,8 @@ End Class "Delete [A]@27") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "Public Sub M()", DeletedSymbolDisplay(FeaturesResources.type_parameter, "A"))) + Diagnostic(RudeEditKind.Delete, "Public Sub M()", DeletedSymbolDisplay(FeaturesResources.type_parameter, "A")), + Diagnostic(RudeEditKind.GenericMethodTriviaUpdate, " : ", FeaturesResources.method)) End Sub @@ -9634,7 +9762,8 @@ End Class "Update [A]@27 -> [B]@27") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Renamed, "B", FeaturesResources.type_parameter)) + Diagnostic(RudeEditKind.Renamed, "B", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericMethodUpdate, "B")) End Sub @@ -9662,7 +9791,8 @@ End Class edits.VerifyRudeDiagnostics( Diagnostic(RudeEditKind.Move, "B", FeaturesResources.type_parameter), - Diagnostic(RudeEditKind.Renamed, "C", FeaturesResources.type_parameter)) + Diagnostic(RudeEditKind.Renamed, "C", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericMethodUpdate, "C")) End Sub #End Region @@ -9734,7 +9864,8 @@ End Class "Update [A]@11 -> [B]@11") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Renamed, "B", FeaturesResources.type_parameter)) + Diagnostic(RudeEditKind.Renamed, "B", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "B")) End Sub @@ -9762,7 +9893,8 @@ End Class edits.VerifyRudeDiagnostics( Diagnostic(RudeEditKind.Move, "B", FeaturesResources.type_parameter), - Diagnostic(RudeEditKind.Renamed, "C", FeaturesResources.type_parameter)) + Diagnostic(RudeEditKind.Renamed, "C", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "C")) End Sub #End Region @@ -9778,7 +9910,8 @@ End Class "Update [T]@11 -> [T As Class]@11") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingConstraints, "T", FeaturesResources.type_parameter)) + Diagnostic(RudeEditKind.ChangingConstraints, "T", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "T")) End Sub @@ -9791,7 +9924,8 @@ End Class "Update [S]@11 -> [S As New]@11") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingConstraints, "S", FeaturesResources.type_parameter)) + Diagnostic(RudeEditKind.ChangingConstraints, "S", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "S")) End Sub @@ -9804,7 +9938,8 @@ End Class "Update [T As Class]@14 -> [T]@14") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingConstraints, "T", FeaturesResources.type_parameter)) + Diagnostic(RudeEditKind.ChangingConstraints, "T", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "T")) End Sub @@ -9818,7 +9953,8 @@ End Class "Update [S As New]@11 -> [S]@11") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingConstraints, "S", FeaturesResources.type_parameter)) + Diagnostic(RudeEditKind.ChangingConstraints, "S", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "S")) End Sub @@ -9833,7 +9969,9 @@ End Class edits.VerifyRudeDiagnostics( Diagnostic(RudeEditKind.ChangingConstraints, "S", FeaturesResources.type_parameter), - Diagnostic(RudeEditKind.ChangingConstraints, "T", FeaturesResources.type_parameter)) + Diagnostic(RudeEditKind.GenericTypeUpdate, "S"), + Diagnostic(RudeEditKind.ChangingConstraints, "T", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "T")) End Sub @@ -9848,7 +9986,9 @@ End Class edits.VerifyRudeDiagnostics( Diagnostic(RudeEditKind.ChangingConstraints, "S", FeaturesResources.type_parameter), - Diagnostic(RudeEditKind.ChangingConstraints, "T", FeaturesResources.type_parameter)) + Diagnostic(RudeEditKind.GenericTypeUpdate, "S"), + Diagnostic(RudeEditKind.ChangingConstraints, "T", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "T")) End Sub @@ -9862,7 +10002,10 @@ End Class "Update [T As Class]@21 -> [T As {Class}]@23", "Update [U As I]@33 -> [U As {I}]@37") - edits.VerifyRudeDiagnostics() + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.GenericTypeUpdate, "S"), + Diagnostic(RudeEditKind.GenericTypeUpdate, "T"), + Diagnostic(RudeEditKind.GenericTypeUpdate, "U")) End Sub @@ -9875,7 +10018,8 @@ End Class "Update [S As {I, J}]@11 -> [S As {J, I}]@11") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingConstraints, "S", FeaturesResources.type_parameter)) + Diagnostic(RudeEditKind.ChangingConstraints, "S", FeaturesResources.type_parameter), + Diagnostic(RudeEditKind.GenericTypeUpdate, "S")) End Sub #End Region diff --git a/src/EditorFeatures/VisualBasicTest/MakeTypeAbstract/MakeTypeAbstractTests.vb b/src/EditorFeatures/VisualBasicTest/MakeTypeAbstract/MakeTypeAbstractTests.vb index 1a43a627be113..78bd99e1fc8d3 100644 --- a/src/EditorFeatures/VisualBasicTest/MakeTypeAbstract/MakeTypeAbstractTests.vb +++ b/src/EditorFeatures/VisualBasicTest/MakeTypeAbstract/MakeTypeAbstractTests.vb @@ -135,5 +135,24 @@ Namespace NS End Class End Namespace") End Function + + + + Public Async Function TestMethod_PartialClass() As Task + Await TestInRegularAndScriptAsync(" +Partial Public Class [|Foo|] + Public MustOverride Sub M() +End Class + +Partial Public Class Foo +End Class", +" +Partial Public MustInherit Class Foo + Public MustOverride Sub M() +End Class + +Partial Public Class Foo +End Class") + End Function End Class End Namespace diff --git a/src/Features/CSharp/Portable/CSharpFeaturesResources.resx b/src/Features/CSharp/Portable/CSharpFeaturesResources.resx index 209f102173c6c..38cffbfe26c88 100644 --- a/src/Features/CSharp/Portable/CSharpFeaturesResources.resx +++ b/src/Features/CSharp/Portable/CSharpFeaturesResources.resx @@ -618,6 +618,10 @@ Change to cast + + Make container 'async' + {Locked="async"} "async" is a C# keyword and should not be localized. + record diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs new file mode 100644 index 0000000000000..8d09d671a80a0 --- /dev/null +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs @@ -0,0 +1,115 @@ +// 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.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.Completion.Providers; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers +{ + /// + /// A completion provider for offering keyword. + /// This is implemented separately, not as a keyword recommender as it contains extra logic for making container method async. + /// + /// + /// The container is made async if and only if the containing method is returning a Task-like type. + /// + [ExportCompletionProvider(nameof(AwaitCompletionProvider), LanguageNames.CSharp)] + [ExtensionOrder(After = nameof(KeywordCompletionProvider))] + [Shared] + internal sealed class AwaitCompletionProvider : LSPCompletionProvider + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public AwaitCompletionProvider() + { + } + + public override ImmutableHashSet TriggerCharacters => CompletionUtilities.CommonTriggerCharactersWithArgumentList; + + public override async Task ProvideCompletionsAsync(CompletionContext context) + { + var document = context.Document; + var position = context.Position; + var cancellationToken = context.CancellationToken; + var semanticModel = await document.ReuseExistingSpeculativeModelAsync(position, cancellationToken).ConfigureAwait(false); + var workspace = document.Project.Solution.Workspace; + var syntaxContext = CSharpSyntaxContext.CreateContext(workspace, semanticModel, position, cancellationToken); + if (!syntaxContext.IsAwaitKeywordContext(position)) + { + return; + } + + var method = syntaxContext.TargetToken.GetAncestor(node => node.IsAsyncSupportingFunctionSyntax()); + var shouldMakeContainerAsync = method is not null && !method.GetModifiers().Any(SyntaxKind.AsyncKeyword); + var completionItem = CommonCompletionItem.Create( + displayText: SyntaxFacts.GetText(SyntaxKind.AwaitKeyword), + displayTextSuffix: "", + rules: CompletionItemRules.Default, + Glyph.Keyword, + description: RecommendedKeyword.CreateDisplayParts(SyntaxFacts.GetText(SyntaxKind.AwaitKeyword), string.Empty), + inlineDescription: shouldMakeContainerAsync ? CSharpFeaturesResources.Make_container_async : null, + isComplexTextEdit: shouldMakeContainerAsync); + context.AddItem(completionItem); + } + + public override async Task GetChangeAsync(Document document, CompletionItem item, char? commitKey = null, CancellationToken cancellationToken = default) + { + // IsComplexTextEdit is true when we want to add async to the container. + if (!item.IsComplexTextEdit) + { + return await base.GetChangeAsync(document, item, commitKey, cancellationToken).ConfigureAwait(false); + } + + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var declaration = root.FindToken(item.Span.Start).GetAncestor(node => node.IsAsyncSupportingFunctionSyntax()); + if (declaration is null) + { + // We already check that in ProvideCompletionsAsync above. + Debug.Assert(false, "Expected non-null value for declaration."); + return await base.GetChangeAsync(document, item, commitKey, cancellationToken).ConfigureAwait(false); + } + + using var _ = ArrayBuilder.GetInstance(out var builder); + builder.Add(new TextChange(new TextSpan(GetSpanStart(declaration), 0), "async ")); + builder.Add(new TextChange(item.Span, item.DisplayText)); + + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + var newText = text.WithChanges(builder); + return CompletionChange.Create(CodeAnalysis.Completion.Utilities.Collapse(newText, builder.ToImmutableArray())); + } + + /// + /// Gets the span start where async keyword should go. + /// + private static int GetSpanStart(SyntaxNode declaration) + { + return declaration switch + { + MethodDeclarationSyntax method => method.ReturnType.SpanStart, + LocalFunctionStatementSyntax local => local.ReturnType.SpanStart, + AnonymousMethodExpressionSyntax anonymous => anonymous.DelegateKeyword.SpanStart, + // If we have an explicit lambda return type, async should go just before it. Otherwise, it should go before parameter list. + // static [|async|] (a) => .... + // static [|async|] ExplicitReturnType (a) => .... + ParenthesizedLambdaExpressionSyntax parenthesizedLambda => (parenthesizedLambda.ReturnType as SyntaxNode ?? parenthesizedLambda.ParameterList).SpanStart, + SimpleLambdaExpressionSyntax simpleLambda => simpleLambda.Parameter.SpanStart, + _ => throw ExceptionUtilities.UnexpectedValue(declaration.Kind()) + }; + } + } +} diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/KeywordCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/KeywordCompletionProvider.cs index 20cec3f82ed49..e55496ace8686 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/KeywordCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/KeywordCompletionProvider.cs @@ -34,7 +34,6 @@ public KeywordCompletionProvider() new AsKeywordRecommender(), new AssemblyKeywordRecommender(), new AsyncKeywordRecommender(), - new AwaitKeywordRecommender(), new BaseKeywordRecommender(), new BoolKeywordRecommender(), new BreakKeywordRecommender(), diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/SpeculativeTCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/SpeculativeTCompletionProvider.cs index 192ca16d3576b..522dea0daf69f 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/SpeculativeTCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/SpeculativeTCompletionProvider.cs @@ -23,7 +23,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers { [ExportCompletionProvider(nameof(SpeculativeTCompletionProvider), LanguageNames.CSharp)] - [ExtensionOrder(After = nameof(KeywordCompletionProvider))] + [ExtensionOrder(After = nameof(AwaitCompletionProvider))] [Shared] internal class SpeculativeTCompletionProvider : LSPCompletionProvider { diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/AwaitKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/AwaitKeywordRecommender.cs deleted file mode 100644 index 361530d72fa76..0000000000000 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/AwaitKeywordRecommender.cs +++ /dev/null @@ -1,60 +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. - -#nullable disable - -using System.Threading; -using Microsoft.CodeAnalysis.CSharp.Extensions; -using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Shared.Extensions; - -namespace Microsoft.CodeAnalysis.CSharp.Completion.KeywordRecommenders -{ - internal class AwaitKeywordRecommender : AbstractSyntacticSingleKeywordRecommender - { - public AwaitKeywordRecommender() - : base(SyntaxKind.AwaitKeyword) - { - } - - protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) - { - if (context.IsGlobalStatementContext) - { - return true; - } - - if (context.IsAnyExpressionContext || context.IsStatementContext) - { - foreach (var node in context.LeftToken.GetAncestors()) - { - if (node.IsAnyLambdaOrAnonymousMethod()) - { - return true; - } - - if (node.IsKind(SyntaxKind.QueryExpression)) - { - return false; - } - - if (node.IsKind(SyntaxKind.LockStatement, out LockStatementSyntax lockStatement)) - { - if (lockStatement.Statement != null && - !lockStatement.Statement.IsMissing && - lockStatement.Statement.Span.Contains(position)) - { - return false; - } - } - } - - return true; - } - - return false; - } - } -} diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index 362af78ee0b3a..79fedf833dda5 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -186,6 +186,27 @@ protected override ImmutableArray GetCapturedVariables(SemanticModel mo return model.AnalyzeDataFlow(memberBody).Captured; } + protected override bool AreFixedSizeBufferSizesEqual(IFieldSymbol oldField, IFieldSymbol newField, CancellationToken cancellationToken) + { + // TODO: replace with symbolic API once available (https://github.com/dotnet/roslyn/issues/54799) + + Debug.Assert(oldField.IsFixedSizeBuffer); + Debug.Assert(oldField.DeclaringSyntaxReferences.Length == 1); + Debug.Assert(newField.IsFixedSizeBuffer); + Debug.Assert(newField.DeclaringSyntaxReferences.Length == 1); + + var oldSyntax = (VariableDeclaratorSyntax)oldField.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken); + var newSyntax = (VariableDeclaratorSyntax)newField.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken); + + Debug.Assert(oldSyntax.ArgumentList != null); + Debug.Assert(newSyntax.ArgumentList != null); + + return AreEquivalent(oldSyntax.ArgumentList, newSyntax.ArgumentList); + } + + protected override bool AreHandledEventsEqual(IMethodSymbol oldMethod, IMethodSymbol newMethod) + => true; + internal override bool HasParameterClosureScope(ISymbol member) { // in instance constructor parameters are lifted to a closure different from method body @@ -1372,6 +1393,7 @@ join newVariable in newVariables on oldVariable.Identifier.Text equals newVariab var symbol = model.GetDeclaredSymbol(node, cancellationToken); + // TODO: this is incorrect (https://github.com/dotnet/roslyn/issues/54800) // Ignore partial method definition parts. // Partial method that does not have implementation part is not emitted to metadata. // Partial method without a definition part is a compilation error. @@ -1880,6 +1902,7 @@ internal override string GetDisplayName(IMethodSymbol symbol) { // top-level + case SyntaxKind.CompilationUnit: case SyntaxKind.GlobalStatement: return CSharpFeaturesResources.global_statement; @@ -2238,8 +2261,6 @@ public void ClassifyEdit() } } - #region Move and Reorder - private void ClassifyMove(SyntaxNode newNode) { if (newNode.IsKind(SyntaxKind.LocalFunctionStatement)) @@ -2261,38 +2282,6 @@ private void ClassifyReorder(SyntaxNode newNode) switch (newNode.Kind()) { - case SyntaxKind.GlobalStatement: - return; - - case SyntaxKind.ExternAliasDirective: - case SyntaxKind.UsingDirective: - case SyntaxKind.NamespaceDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.StructDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.RecordDeclaration: - case SyntaxKind.RecordStructDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.DelegateDeclaration: - case SyntaxKind.VariableDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.ConversionOperatorDeclaration: - case SyntaxKind.OperatorDeclaration: - case SyntaxKind.ConstructorDeclaration: - case SyntaxKind.DestructorDeclaration: - case SyntaxKind.IndexerDeclaration: - case SyntaxKind.EventDeclaration: - case SyntaxKind.GetAccessorDeclaration: - case SyntaxKind.SetAccessorDeclaration: - case SyntaxKind.InitAccessorDeclaration: - case SyntaxKind.AddAccessorDeclaration: - case SyntaxKind.RemoveAccessorDeclaration: - case SyntaxKind.TypeParameterConstraintClause: - case SyntaxKind.AttributeList: - case SyntaxKind.Attribute: - // We'll ignore these edits. A general policy is to ignore edits that are only discoverable via reflection. - return; - case SyntaxKind.PropertyDeclaration: case SyntaxKind.FieldDeclaration: case SyntaxKind.EventFieldDeclaration: @@ -2311,38 +2300,19 @@ private void ClassifyReorder(SyntaxNode newNode) case SyntaxKind.Parameter: ReportError(RudeEditKind.Move); return; - - default: - throw ExceptionUtilities.UnexpectedValue(newNode.Kind()); } } - #endregion - - #region Insert - private void ClassifyInsert(SyntaxNode node) { switch (node.Kind()) { - case SyntaxKind.GlobalStatement: - // An insert of a global statement is actually an update to the synthesized main so we need to check some extra things - ClassifyUpdate((GlobalStatementSyntax)node); - return; - case SyntaxKind.ExternAliasDirective: case SyntaxKind.NamespaceDeclaration: + case SyntaxKind.FileScopedNamespaceDeclaration: ReportError(RudeEditKind.Insert); return; - case SyntaxKind.ArrowExpressionClause: - if (node.Parent.IsKind(SyntaxKind.PropertyDeclaration, SyntaxKind.IndexerDeclaration)) - { - return; - } - - break; - case SyntaxKind.Attribute: case SyntaxKind.AttributeList: // To allow inserting of attributes we need to check if the inserted attribute @@ -2359,33 +2329,18 @@ private void ClassifyInsert(SyntaxNode node) } } - #endregion - - #region Delete - private void ClassifyDelete(SyntaxNode oldNode) { switch (oldNode.Kind()) { - case SyntaxKind.GlobalStatement: - return; - case SyntaxKind.ExternAliasDirective: case SyntaxKind.NamespaceDeclaration: + case SyntaxKind.FileScopedNamespaceDeclaration: // To allow removal of declarations we would need to update method bodies that // were previously binding to them but now are binding to another symbol that was previously hidden. ReportError(RudeEditKind.Delete); return; - case SyntaxKind.ArrowExpressionClause: - // We do not report error here since it will be reported in semantic analysis. - if (oldNode.Parent.IsKind(SyntaxKind.PropertyDeclaration, SyntaxKind.IndexerDeclaration)) - { - return; - } - - break; - case SyntaxKind.AttributeList: case SyntaxKind.Attribute: // To allow removal of attributes we need to check if the removed attribute @@ -2402,18 +2357,10 @@ private void ClassifyDelete(SyntaxNode oldNode) } } - #endregion - - #region Update - private void ClassifyUpdate(SyntaxNode oldNode, SyntaxNode newNode) { switch (newNode.Kind()) { - case SyntaxKind.GlobalStatement: - ClassifyUpdate((GlobalStatementSyntax)newNode); - return; - case SyntaxKind.ExternAliasDirective: ReportError(RudeEditKind.Update); return; @@ -2423,58 +2370,6 @@ private void ClassifyUpdate(SyntaxNode oldNode, SyntaxNode newNode) ClassifyUpdate((BaseNamespaceDeclarationSyntax)oldNode, (BaseNamespaceDeclarationSyntax)newNode); return; - case SyntaxKind.VariableDeclaration: - if (!oldNode.IsParentKind(SyntaxKind.FieldDeclaration, SyntaxKind.EventFieldDeclaration)) - { - ClassifyUpdate((VariableDeclarationSyntax)oldNode, (VariableDeclarationSyntax)newNode); - } - - return; - - case SyntaxKind.VariableDeclarator: - ClassifyUpdate((VariableDeclaratorSyntax)oldNode, (VariableDeclaratorSyntax)newNode); - return; - - case SyntaxKind.MethodDeclaration: - ClassifyUpdate((MethodDeclarationSyntax)oldNode, (MethodDeclarationSyntax)newNode); - return; - - case SyntaxKind.ConversionOperatorDeclaration: - ClassifyUpdate((ConversionOperatorDeclarationSyntax)oldNode, (ConversionOperatorDeclarationSyntax)newNode); - return; - - case SyntaxKind.OperatorDeclaration: - ClassifyUpdate((OperatorDeclarationSyntax)oldNode, (OperatorDeclarationSyntax)newNode); - return; - - case SyntaxKind.ConstructorDeclaration: - ClassifyUpdate((ConstructorDeclarationSyntax)oldNode, (ConstructorDeclarationSyntax)newNode); - return; - - case SyntaxKind.DestructorDeclaration: - ClassifyUpdate((DestructorDeclarationSyntax)oldNode, (DestructorDeclarationSyntax)newNode); - return; - - case SyntaxKind.PropertyDeclaration: - ClassifyUpdate((PropertyDeclarationSyntax)oldNode, (PropertyDeclarationSyntax)newNode); - return; - - case SyntaxKind.IndexerDeclaration: - ClassifyUpdate((IndexerDeclarationSyntax)oldNode, (IndexerDeclarationSyntax)newNode); - return; - - case SyntaxKind.EnumMemberDeclaration: - ClassifyUpdate((EnumMemberDeclarationSyntax)oldNode, (EnumMemberDeclarationSyntax)newNode); - return; - - case SyntaxKind.GetAccessorDeclaration: - case SyntaxKind.SetAccessorDeclaration: - case SyntaxKind.InitAccessorDeclaration: - case SyntaxKind.AddAccessorDeclaration: - case SyntaxKind.RemoveAccessorDeclaration: - ClassifyUpdate((AccessorDeclarationSyntax)oldNode, (AccessorDeclarationSyntax)newNode); - return; - case SyntaxKind.Attribute: // To allow update of attributes we need to check if the updated attribute // is a pseudo-custom attribute that CLR allows us to change, or if it is a compiler well-know attribute @@ -2490,230 +2385,12 @@ private void ClassifyUpdate(SyntaxNode oldNode, SyntaxNode newNode) } } - private void ClassifyUpdate(GlobalStatementSyntax node) - { - ClassifyDeclarationBodyRudeUpdates(node.Statement); - } - private void ClassifyUpdate(BaseNamespaceDeclarationSyntax oldNode, BaseNamespaceDeclarationSyntax newNode) { Debug.Assert(!SyntaxFactory.AreEquivalent(oldNode.Name, newNode.Name)); ReportError(RudeEditKind.Renamed); } - private void ClassifyUpdate(VariableDeclarationSyntax oldNode, VariableDeclarationSyntax newNode) - { - if (!SyntaxFactory.AreEquivalent(oldNode.Type, newNode.Type)) - { - ReportError(RudeEditKind.TypeUpdate); - return; - } - } - - private void ClassifyUpdate(VariableDeclaratorSyntax oldNode, VariableDeclaratorSyntax newNode) - { - // If the argument lists are mismatched the field must have mismatched "fixed" modifier, - // which is reported by the field declaration. - if (oldNode.ArgumentList is null == newNode.ArgumentList is null) - { - if (!SyntaxFactory.AreEquivalent(oldNode.ArgumentList, newNode.ArgumentList)) - { - ReportError(RudeEditKind.FixedSizeFieldUpdate); - return; - } - } - - var typeDeclaration = (TypeDeclarationSyntax?)oldNode.Parent!.Parent!.Parent!; - if (typeDeclaration.Arity > 0) - { - ReportError(RudeEditKind.GenericTypeInitializerUpdate); - return; - } - - // Check if a constant field is updated: - var fieldDeclaration = (BaseFieldDeclarationSyntax)oldNode.Parent.Parent; - if (fieldDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword)) - { - ReportError(RudeEditKind.Update); - return; - } - - ClassifyDeclarationBodyRudeUpdates(newNode); - } - - private void ClassifyUpdate(MethodDeclarationSyntax oldNode, MethodDeclarationSyntax newNode) - { - ClassifyMethodBodyRudeUpdate( - (SyntaxNode?)oldNode.Body ?? oldNode.ExpressionBody?.Expression, - (SyntaxNode?)newNode.Body ?? newNode.ExpressionBody?.Expression, - containingMethod: newNode, - containingType: (TypeDeclarationSyntax?)newNode.Parent); - } - - private void ClassifyUpdate(ConversionOperatorDeclarationSyntax oldNode, ConversionOperatorDeclarationSyntax newNode) - { - ClassifyMethodBodyRudeUpdate( - (SyntaxNode?)oldNode.Body ?? oldNode.ExpressionBody?.Expression, - (SyntaxNode?)newNode.Body ?? newNode.ExpressionBody?.Expression, - containingMethod: null, - containingType: (TypeDeclarationSyntax?)newNode.Parent); - } - - private void ClassifyUpdate(OperatorDeclarationSyntax oldNode, OperatorDeclarationSyntax newNode) - { - ClassifyMethodBodyRudeUpdate( - (SyntaxNode?)oldNode.Body ?? oldNode.ExpressionBody?.Expression, - (SyntaxNode?)newNode.Body ?? newNode.ExpressionBody?.Expression, - containingMethod: null, - containingType: (TypeDeclarationSyntax?)newNode.Parent); - } - - private void ClassifyUpdate(AccessorDeclarationSyntax oldNode, AccessorDeclarationSyntax newNode) - { - if (oldNode.Kind() != newNode.Kind()) - { - return; - } - - RoslynDebug.Assert(newNode.Parent is AccessorListSyntax); - RoslynDebug.Assert(newNode.Parent.Parent is BasePropertyDeclarationSyntax); - - ClassifyMethodBodyRudeUpdate( - (SyntaxNode?)oldNode.Body ?? oldNode.ExpressionBody?.Expression, - (SyntaxNode?)newNode.Body ?? newNode.ExpressionBody?.Expression, - containingMethod: null, - containingType: (TypeDeclarationSyntax?)newNode.Parent.Parent.Parent); - } - - private void ClassifyUpdate(EnumMemberDeclarationSyntax oldNode, EnumMemberDeclarationSyntax newNode) - { - if (!SyntaxFactory.AreEquivalent(oldNode.EqualsValue, newNode.EqualsValue)) - { - ReportError(RudeEditKind.InitializerUpdate); - return; - } - - // Attributes are processed during semantic analysis - } - - private void ClassifyUpdate(ConstructorDeclarationSyntax oldNode, ConstructorDeclarationSyntax newNode) - { - ClassifyMethodBodyRudeUpdate( - (SyntaxNode?)oldNode.Body ?? oldNode.ExpressionBody?.Expression, - (SyntaxNode?)newNode.Body ?? newNode.ExpressionBody?.Expression, - containingMethod: null, - containingType: (TypeDeclarationSyntax?)newNode.Parent); - } - - private void ClassifyUpdate(DestructorDeclarationSyntax oldNode, DestructorDeclarationSyntax newNode) - { - ClassifyMethodBodyRudeUpdate( - (SyntaxNode?)oldNode.Body ?? oldNode.ExpressionBody?.Expression, - (SyntaxNode?)newNode.Body ?? newNode.ExpressionBody?.Expression, - containingMethod: null, - containingType: (TypeDeclarationSyntax?)newNode.Parent); - } - - private void ClassifyUpdate(PropertyDeclarationSyntax oldNode, PropertyDeclarationSyntax newNode) - { - var containingType = (TypeDeclarationSyntax)newNode.Parent!; - - // TODO: We currently don't support switching from auto-props to properties with accessors and vice versa. - // If we do we should also allow it for expression bodies. - - if (!SyntaxFactory.AreEquivalent(oldNode.ExpressionBody, newNode.ExpressionBody)) - { - var oldBody = SyntaxUtilities.TryGetEffectiveGetterBody(oldNode.ExpressionBody, oldNode.AccessorList); - var newBody = SyntaxUtilities.TryGetEffectiveGetterBody(newNode.ExpressionBody, newNode.AccessorList); - - ClassifyMethodBodyRudeUpdate( - oldBody, - newBody, - containingMethod: null, - containingType: containingType); - - return; - } - - if (!SyntaxFactory.AreEquivalent(oldNode.Initializer, newNode.Initializer)) - { - if (containingType.Arity > 0) - { - ReportError(RudeEditKind.GenericTypeInitializerUpdate); - return; - } - - if (newNode.Initializer != null) - { - ClassifyDeclarationBodyRudeUpdates(newNode.Initializer); - } - } - } - - private void ClassifyUpdate(IndexerDeclarationSyntax oldNode, IndexerDeclarationSyntax newNode) - { - if (SyntaxFactory.AreEquivalent(oldNode.ExpressionBody, newNode.ExpressionBody)) - { - var oldBody = SyntaxUtilities.TryGetEffectiveGetterBody(oldNode.ExpressionBody, oldNode.AccessorList); - var newBody = SyntaxUtilities.TryGetEffectiveGetterBody(newNode.ExpressionBody, newNode.AccessorList); - - ClassifyMethodBodyRudeUpdate( - oldBody, - newBody, - containingMethod: null, - containingType: (TypeDeclarationSyntax?)newNode.Parent); - } - } - - private void ClassifyMethodBodyRudeUpdate( - SyntaxNode? oldBody, - SyntaxNode? newBody, - MethodDeclarationSyntax? containingMethod, - TypeDeclarationSyntax? containingType) - { - Debug.Assert(oldBody is BlockSyntax || oldBody is ExpressionSyntax || oldBody == null); - Debug.Assert(newBody is BlockSyntax || newBody is ExpressionSyntax || newBody == null); - - if ((oldBody == null) != (newBody == null)) - { - if (oldBody == null) - { - ReportError(RudeEditKind.MethodBodyAdd); - return; - } - else - { - ReportError(RudeEditKind.MethodBodyDelete); - return; - } - } - - ClassifyMemberBodyRudeUpdate(containingMethod, containingType, isTriviaUpdate: false); - - if (newBody != null) - { - ClassifyDeclarationBodyRudeUpdates(newBody); - } - } - - public void ClassifyMemberBodyRudeUpdate( - MethodDeclarationSyntax? containingMethod, - TypeDeclarationSyntax? containingType, - bool isTriviaUpdate) - { - if (SyntaxUtilities.Any(containingMethod?.TypeParameterList)) - { - ReportError(isTriviaUpdate ? RudeEditKind.GenericMethodTriviaUpdate : RudeEditKind.GenericMethodUpdate); - return; - } - - if (SyntaxUtilities.Any(containingType?.TypeParameterList)) - { - ReportError(isTriviaUpdate ? RudeEditKind.GenericTypeTriviaUpdate : RudeEditKind.GenericTypeUpdate); - return; - } - } - public void ClassifyDeclarationBodyRudeUpdates(SyntaxNode newDeclarationOrBody) { foreach (var node in newDeclarationOrBody.DescendantNodesAndSelf(LambdaUtilities.IsNotLambda)) @@ -2727,8 +2404,6 @@ public void ClassifyDeclarationBodyRudeUpdates(SyntaxNode newDeclarationOrBody) } } } - - #endregion } internal override void ReportTopLevelSyntacticRudeEdits( @@ -2746,15 +2421,9 @@ internal override void ReportTopLevelSyntacticRudeEdits( classifier.ClassifyEdit(); } - internal override void ReportMemberUpdateRudeEdits(ArrayBuilder diagnostics, SyntaxNode newMember, TextSpan? span) + internal override void ReportMemberBodyUpdateRudeEdits(ArrayBuilder diagnostics, SyntaxNode newMember, TextSpan? span) { var classifier = new EditClassifier(this, diagnostics, oldNode: null, newMember, EditKind.Update, span: span); - - classifier.ClassifyMemberBodyRudeUpdate( - newMember as MethodDeclarationSyntax, - newMember.FirstAncestorOrSelf(), - isTriviaUpdate: true); - classifier.ClassifyDeclarationBodyRudeUpdates(newMember); } diff --git a/src/Features/CSharp/Portable/UpgradeProject/CSharpUpgradeProjectCodeFixProvider.cs b/src/Features/CSharp/Portable/UpgradeProject/CSharpUpgradeProjectCodeFixProvider.cs index 30c60d39781ed..bdc0f9bcb06dd 100644 --- a/src/Features/CSharp/Portable/UpgradeProject/CSharpUpgradeProjectCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/UpgradeProject/CSharpUpgradeProjectCodeFixProvider.cs @@ -49,6 +49,7 @@ public CSharpUpgradeProjectCodeFixProvider() "CS8904", // error CS8904: Invalid variance: The type parameter 'T1' must be contravariantly valid on 'I2.M1(T1)' unless language version 'preview' or greater is used. 'T1' is covariant. "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. }); public override string UpgradeThisProjectResource => CSharpFeaturesResources.Upgrade_this_project_to_csharp_language_version_0; diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf index 59a69d7bd9cda..08ba0eb2114a7 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf @@ -137,6 +137,11 @@ Zjistily se konflikty. + + Make container 'async' + Make container 'async' + {Locked="async"} "async" is a C# keyword and should not be localized. + Make private fields readonly when possible Nastavit privátní pole jako jenom pro čtení, kde je to možné diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf index 523b0f0c44e0b..de9c02bc3832f 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf @@ -137,6 +137,11 @@ Konflikt(e) erkannt. + + Make container 'async' + Make container 'async' + {Locked="async"} "async" is a C# keyword and should not be localized. + Make private fields readonly when possible Private Felder nach Möglichkeit als schreibgeschützt festlegen diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf index b77658a3239e3..5c7ef32e33dc3 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf @@ -137,6 +137,11 @@ Conflicto(s) detectado(s). + + Make container 'async' + Make container 'async' + {Locked="async"} "async" is a C# keyword and should not be localized. + Make private fields readonly when possible Cuando sea posible, hacer que los cambios privados sean solo de lectura diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf index 1626a52ba750a..538d55038f6f8 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf @@ -137,6 +137,11 @@ Conflit(s) détecté(s). + + Make container 'async' + Make container 'async' + {Locked="async"} "async" is a C# keyword and should not be localized. + Make private fields readonly when possible Configurer les champs privés en lecture seule quand cela est possible diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf index 44372203cd116..8f30b216c1f96 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf @@ -137,6 +137,11 @@ Sono stati rilevati conflitti. + + Make container 'async' + Make container 'async' + {Locked="async"} "async" is a C# keyword and should not be localized. + Make private fields readonly when possible Imposta i campi privati come di sola lettura quando possibile diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf index 505a4e62dc426..e208f3a042481 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf @@ -137,6 +137,11 @@ 競合が検出されました。 + + Make container 'async' + Make container 'async' + {Locked="async"} "async" is a C# keyword and should not be localized. + Make private fields readonly when possible 可能な場合、private フィールドを読み取り専用にする diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf index 6d5c13c1a38c9..723cfd6052082 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf @@ -137,6 +137,11 @@ 충돌이 감지되었습니다. + + Make container 'async' + Make container 'async' + {Locked="async"} "async" is a C# keyword and should not be localized. + Make private fields readonly when possible 가능한 경우 프라이빗 필드를 읽기 전용으로 만들기 diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf index 520636608d62b..ef5c616525221 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf @@ -137,6 +137,11 @@ Wykryto konflikty. + + Make container 'async' + Make container 'async' + {Locked="async"} "async" is a C# keyword and should not be localized. + Make private fields readonly when possible Ustawiaj pola prywatne jako tylko do odczytu, gdy to możliwe diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf index 2c9f8821a4934..5036c840aeb58 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf @@ -137,6 +137,11 @@ Conflito(s) detectado(s). + + Make container 'async' + Make container 'async' + {Locked="async"} "async" is a C# keyword and should not be localized. + Make private fields readonly when possible Tornar os campos privados somente leitura quando possível diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf index 0f9d6b5a02d2c..36ccd194ffd1d 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf @@ -137,6 +137,11 @@ Обнаружены конфликты. + + Make container 'async' + Make container 'async' + {Locked="async"} "async" is a C# keyword and should not be localized. + Make private fields readonly when possible При возможности делать частные поля доступными только для чтения diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf index aabe1f81302aa..ac6d1057e20c2 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf @@ -137,6 +137,11 @@ Çakışmalar algılandı. + + Make container 'async' + Make container 'async' + {Locked="async"} "async" is a C# keyword and should not be localized. + Make private fields readonly when possible Özel alanları mümkün olduğunda salt okunur yap diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf index 56f4c5cc4873b..fbb88a49aabe3 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf @@ -137,6 +137,11 @@ 检测到冲突。 + + Make container 'async' + Make container 'async' + {Locked="async"} "async" is a C# keyword and should not be localized. + Make private fields readonly when possible 尽可能将私有字段设置为“只读” diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf index 0878046a9c3c0..02922e9c7ae33 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf @@ -137,6 +137,11 @@ 偵測到衝突。 + + Make container 'async' + Make container 'async' + {Locked="async"} "async" is a C# keyword and should not be localized. + Make private fields readonly when possible 若可能的話,請將私用欄位設定為唯讀 diff --git a/src/Features/Core/Portable/Common/TaggedText.cs b/src/Features/Core/Portable/Common/TaggedText.cs index be1f5013980a3..91170be6d4445 100644 --- a/src/Features/Core/Portable/Common/TaggedText.cs +++ b/src/Features/Core/Portable/Common/TaggedText.cs @@ -87,38 +87,28 @@ public override string ToString() internal static class TaggedTextExtensions { - public static ImmutableArray ToTaggedText(this IEnumerable displayParts, Func getNavigationHint = null) - => displayParts.ToTaggedText(TaggedTextStyle.None, getNavigationHint); + public static ImmutableArray ToTaggedText(this IEnumerable displayParts, Func getNavigationHint = null, bool includeNavigationHints = true) + => displayParts.ToTaggedText(TaggedTextStyle.None, getNavigationHint, includeNavigationHints); public static ImmutableArray ToTaggedText( - this IEnumerable displayParts, TaggedTextStyle style, Func getNavigationHint = null) + this IEnumerable displayParts, TaggedTextStyle style, Func getNavigationHint = null, bool includeNavigationHints = true) { if (displayParts == null) return ImmutableArray.Empty; - getNavigationHint ??= GetNavigationHint; + getNavigationHint ??= static symbol => symbol?.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); return displayParts.SelectAsArray(d => new TaggedText( SymbolDisplayPartKindTags.GetTag(d.Kind), d.ToString(), style, - GetNavigationTarget(d.Symbol), - getNavigationHint(d.Symbol))); - - static string GetNavigationTarget(ISymbol symbol) - { - if (symbol is null) - { - return null; - } - - return SymbolKey.CreateString(symbol); - } + includeNavigationHints ? GetNavigationTarget(d.Symbol) : null, + includeNavigationHints ? getNavigationHint(d.Symbol) : null)); } - private static string GetNavigationHint(ISymbol symbol) - => symbol?.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); + private static string GetNavigationTarget(ISymbol symbol) + => symbol is null ? null : SymbolKey.CreateString(symbol); public static string JoinText(this ImmutableArray values) { diff --git a/src/Features/Core/Portable/DocumentationComments/IDocumentationCommentFormattingService.cs b/src/Features/Core/Portable/DocumentationComments/IDocumentationCommentFormattingService.cs index 12063736dc035..e0fa6d0a7bdb7 100644 --- a/src/Features/Core/Portable/DocumentationComments/IDocumentationCommentFormattingService.cs +++ b/src/Features/Core/Portable/DocumentationComments/IDocumentationCommentFormattingService.cs @@ -2,9 +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.Collections.Generic; using System.Collections.Immutable; using System.Threading; using Microsoft.CodeAnalysis.Host; @@ -13,7 +10,7 @@ namespace Microsoft.CodeAnalysis.DocumentationComments { internal interface IDocumentationCommentFormattingService : ILanguageService { - string Format(string rawXmlText, Compilation compilation = null); - ImmutableArray Format(string rawXmlText, ISymbol symbol, SemanticModel semanticModel, int position, SymbolDisplayFormat format, CancellationToken cancellationToken); + string? Format(string? rawXmlText, Compilation? compilation = null); + ImmutableArray Format(string? rawXmlText, ISymbol symbol, SemanticModel semanticModel, int position, SymbolDisplayFormat format, CancellationToken cancellationToken); } } diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 8fe1d00a96ea2..176a52ebd9c13 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -287,6 +287,9 @@ protected virtual bool StateMachineSuspensionPointKindEquals(SyntaxNode suspensi /// protected abstract IEnumerable GetVariableUseSites(IEnumerable roots, ISymbol localOrParameter, SemanticModel model, CancellationToken cancellationToken); + protected abstract bool AreFixedSizeBufferSizesEqual(IFieldSymbol oldField, IFieldSymbol newField, CancellationToken cancellationToken); + protected abstract bool AreHandledEventsEqual(IMethodSymbol oldMethod, IMethodSymbol newMethod); + // diagnostic spans: protected abstract TextSpan? TryGetDiagnosticSpan(SyntaxNode node, EditKind editKind); @@ -400,7 +403,7 @@ protected virtual string GetSuspensionPointDisplayName(SyntaxNode node, EditKind internal abstract void ReportTopLevelSyntacticRudeEdits(ArrayBuilder diagnostics, Match match, Edit edit, Dictionary editMap); internal abstract void ReportEnclosingExceptionHandlingRudeEdits(ArrayBuilder diagnostics, IEnumerable> exceptionHandlingEdits, SyntaxNode oldStatement, TextSpan newStatementSpan); internal abstract void ReportOtherRudeEditsAroundActiveStatement(ArrayBuilder diagnostics, Match match, SyntaxNode oldStatement, SyntaxNode newStatement, bool isNonLeaf); - internal abstract void ReportMemberUpdateRudeEdits(ArrayBuilder diagnostics, SyntaxNode newMember, TextSpan? span); + internal abstract void ReportMemberBodyUpdateRudeEdits(ArrayBuilder diagnostics, SyntaxNode newMember, TextSpan? span); internal abstract void ReportInsertedMemberSymbolRudeEdits(ArrayBuilder diagnostics, ISymbol newSymbol, SyntaxNode newNode, bool insertingIntoExistingContainingType); internal abstract void ReportStateMachineSuspensionPointRudeEdits(ArrayBuilder diagnostics, SyntaxNode oldNode, SyntaxNode newNode); @@ -604,30 +607,17 @@ public async Task AnalyzeDocumentAsync( cancellationToken.ThrowIfCancellationRequested(); - using var _3 = ArrayBuilder<(SyntaxNode OldNode, SyntaxNode NewNode)>.GetInstance(out var triviaEdits); + using var _3 = ArrayBuilder<(SyntaxNode OldNode, SyntaxNode NewNode, TextSpan DiagnosticSpan)>.GetInstance(out var triviaEdits); using var _4 = ArrayBuilder.GetInstance(out var lineEdits); - // Do not analyze trivia in presence of syntactic rude edits. - // The implementation depends on edit map capturing all updates and inserts, - // which might not be the case when rude edits are reported. - if (diagnostics.Count == 0) - { - AnalyzeTrivia( - topMatch, - editMap, - triviaEdits, - lineEdits, - diagnostics, - cancellationToken); - - if (diagnostics.Count > 0 && !hasRudeEdits) - { - DocumentAnalysisResults.Log.Write("{0} trivia rude edits, first: {1}@{2}", diagnostics.Count, newDocument.FilePath, diagnostics.First().Span.Start); - hasRudeEdits = true; - } + AnalyzeTrivia( + topMatch, + editMap, + triviaEdits, + lineEdits, + cancellationToken); - cancellationToken.ThrowIfCancellationRequested(); - } + cancellationToken.ThrowIfCancellationRequested(); var oldActiveStatements = (oldTree == null) ? ImmutableArray.Empty : oldActiveStatementMap.GetOldActiveStatements(this, oldTree, oldText, oldRoot, cancellationToken); @@ -705,11 +695,6 @@ private void ReportTopLevelSyntacticRudeEdits(ArrayBuilder d /// internal virtual void ReportDeclarationInsertDeleteRudeEdits(ArrayBuilder diagnostics, SyntaxNode oldNode, SyntaxNode newNode, ISymbol oldSymbol, ISymbol newSymbol, CancellationToken cancellationToken) { - if (oldSymbol is ITypeParameterSymbol or IParameterSymbol) - { - return; - } - // When a method is moved to a different declaration and its parameters are changed at the same time // the new method symbol key will not resolve to the old one since the parameters are different. // As a result we will report separate delete and insert rude edits. @@ -723,25 +708,7 @@ internal virtual void ReportDeclarationInsertDeleteRudeEdits(ArrayBuilder BuildEditMap(EditScript editScript) @@ -974,6 +941,8 @@ private void AnalyzeChangedMemberBody( try { + ReportMemberBodyUpdateRudeEdits(diagnostics, newDeclaration, GetDiagnosticSpan(newDeclaration, EditKind.Update)); + _testFaultInjector?.Invoke(newBody); // Populated with active lambdas and matched lambdas. @@ -2036,13 +2005,10 @@ private static int IndexOfEquivalent(SyntaxNode newNode, List topMatch, IReadOnlyDictionary editMap, - [Out] ArrayBuilder<(SyntaxNode OldNode, SyntaxNode NewNode)> triviaEdits, + [Out] ArrayBuilder<(SyntaxNode OldNode, SyntaxNode NewNode, TextSpan DiagnosticSpan)> triviaEdits, [Out] ArrayBuilder lineEdits, - [Out] ArrayBuilder diagnostics, CancellationToken cancellationToken) { - Debug.Assert(diagnostics.Count == 0); - var oldTree = topMatch.OldRoot.SyntaxTree; var newTree = topMatch.NewRoot.SyntaxTree; @@ -2211,8 +2177,6 @@ void AddCurrentSegment() // All tokens of a member body have been processed now. if (requiresUpdate) { - triviaEdits.Add((oldNode, newNode)); - // report the rude edit for the span of tokens that forced recompilation: if (rudeEditSpan.IsEmpty) { @@ -2221,7 +2185,7 @@ void AddCurrentSegment() newTokensEnum.Current.SpanStart); } - ReportMemberUpdateRudeEdits(diagnostics, newNode, rudeEditSpan); + triviaEdits.Add((oldNode, newNode, rudeEditSpan)); // remove all segments added for the current member body: segments.Count = firstSegmentIndex; @@ -2274,8 +2238,7 @@ void AddCurrentSegment() { // The segment overlaps the previous one that has a different line delta. We need to recompile the method. // The debugger filters out line deltas that correspond to recompiled methods so we don't need to. - triviaEdits.Add((segment.oldNode, segment.newNode)); - ReportMemberUpdateRudeEdits(diagnostics, segment.newNode, span: null); + triviaEdits.Add((segment.oldNode, segment.newNode, segment.newNode.Span)); continue; } @@ -2358,6 +2321,9 @@ public int GetHashCode(IAssemblySymbol? obj) private static readonly SymbolEquivalenceComparer s_exactSymbolEqualityComparer = new( AssemblyEqualityComparer.Instance, distinguishRefFromOut: true, tupleNamesMustMatch: true, ignoreNullableAnnotations: false); + protected static bool SymbolsEquivalent(ISymbol oldSymbol, ISymbol newSymbol) + => s_exactSymbolEqualityComparer.Equals(oldSymbol, newSymbol); + protected static bool SignaturesEquivalent(ImmutableArray oldParameters, ITypeSymbol oldReturnType, ImmutableArray newParameters, ITypeSymbol newReturnType) => ParametersEquivalent(oldParameters, newParameters, exact: false) && s_runtimeSymbolEqualityComparer.Equals(oldReturnType, newReturnType); // TODO: should check ref, ref readonly, custom mods @@ -2369,18 +2335,21 @@ protected static bool CustomModifiersEquivalent(CustomModifier oldModifier, Cust => oldModifier.IsOptional == newModifier.IsOptional && TypesEquivalent(oldModifier.Modifier, newModifier.Modifier, exact); + protected static bool CustomModifiersEquivalent(ImmutableArray oldModifiers, ImmutableArray newModifiers, bool exact) + => oldModifiers.SequenceEqual(newModifiers, exact, (x, y, exact) => CustomModifiersEquivalent(x, y, exact)); + protected static bool ReturnTypesEquivalent(IMethodSymbol oldMethod, IMethodSymbol newMethod, bool exact) => oldMethod.ReturnsByRef == newMethod.ReturnsByRef && oldMethod.ReturnsByRefReadonly == newMethod.ReturnsByRefReadonly && - oldMethod.ReturnTypeCustomModifiers.SequenceEqual(newMethod.ReturnTypeCustomModifiers, exact, (x, y, exact) => CustomModifiersEquivalent(x, y, exact)) && - oldMethod.RefCustomModifiers.SequenceEqual(newMethod.RefCustomModifiers, exact, (x, y, exact) => CustomModifiersEquivalent(x, y, exact)) && + CustomModifiersEquivalent(oldMethod.ReturnTypeCustomModifiers, newMethod.ReturnTypeCustomModifiers, exact) && + CustomModifiersEquivalent(oldMethod.RefCustomModifiers, newMethod.RefCustomModifiers, exact) && TypesEquivalent(oldMethod.ReturnType, newMethod.ReturnType, exact); protected static bool ReturnTypesEquivalent(IPropertySymbol oldProperty, IPropertySymbol newProperty, bool exact) => oldProperty.ReturnsByRef == newProperty.ReturnsByRef && oldProperty.ReturnsByRefReadonly == newProperty.ReturnsByRefReadonly && - oldProperty.TypeCustomModifiers.SequenceEqual(newProperty.TypeCustomModifiers, exact, (x, y, exact) => CustomModifiersEquivalent(x, y, exact)) && - oldProperty.RefCustomModifiers.SequenceEqual(newProperty.RefCustomModifiers, exact, (x, y, exact) => CustomModifiersEquivalent(x, y, exact)) && + CustomModifiersEquivalent(oldProperty.TypeCustomModifiers, newProperty.TypeCustomModifiers, exact) && + CustomModifiersEquivalent(oldProperty.RefCustomModifiers, newProperty.RefCustomModifiers, exact) && TypesEquivalent(oldProperty.Type, newProperty.Type, exact); protected static bool ReturnTypesEquivalent(IEventSymbol oldEvent, IEventSymbol newEvent, bool exact) @@ -2390,11 +2359,14 @@ protected static bool ReturnTypesEquivalent(IEventSymbol oldEvent, IEventSymbol protected static bool TypesEquivalent(ITypeSymbol? oldType, ITypeSymbol? newType, bool exact) => (exact ? s_exactSymbolEqualityComparer : (IEqualityComparer)s_runtimeSymbolEqualityComparer.SignatureTypeEquivalenceComparer).Equals(oldType, newType); + protected static bool TypesEquivalent(ImmutableArray oldTypes, ImmutableArray newTypes, bool exact) where T : ITypeSymbol + => oldTypes.SequenceEqual(newTypes, exact, (x, y, exact) => TypesEquivalent(x, y, exact)); + protected static bool ParameterTypesEquivalent(IParameterSymbol oldParameter, IParameterSymbol newParameter, bool exact) => (exact ? s_exactSymbolEqualityComparer : s_runtimeSymbolEqualityComparer).ParameterEquivalenceComparer.Equals(oldParameter, newParameter); protected static bool TypeParameterConstraintsEquivalent(ITypeParameterSymbol oldParameter, ITypeParameterSymbol newParameter, bool exact) - => oldParameter.ConstraintTypes.SequenceEqual(newParameter.ConstraintTypes, exact, (x, y, exact) => TypesEquivalent(x, y, exact)) && + => TypesEquivalent(oldParameter.ConstraintTypes, newParameter.ConstraintTypes, exact) && oldParameter.HasReferenceTypeConstraint == newParameter.HasReferenceTypeConstraint && oldParameter.HasValueTypeConstraint == newParameter.HasValueTypeConstraint && oldParameter.HasConstructorConstraint == newParameter.HasConstructorConstraint && @@ -2407,7 +2379,7 @@ protected static bool TypeParametersEquivalent(ImmutableArray TypesEquivalent(oldType.BaseType, newType.BaseType, exact) && - oldType.AllInterfaces.SequenceEqual(newType.AllInterfaces, exact, (x, y, exact) => TypesEquivalent(x, y, exact)); + TypesEquivalent(oldType.AllInterfaces, newType.AllInterfaces, exact); protected static bool MemberSignaturesEquivalent( ISymbol? oldMember, @@ -2470,7 +2442,7 @@ private async Task> AnalyzeSemanticsAsync( IReadOnlyDictionary editMap, ImmutableArray oldActiveStatements, ImmutableArray newActiveStatementSpans, - IReadOnlyList<(SyntaxNode OldNode, SyntaxNode NewNode)> triviaEdits, + IReadOnlyList<(SyntaxNode OldNode, SyntaxNode NewNode, TextSpan DiagnosticSpan)> triviaEdits, Project oldProject, Document? oldDocument, Document newDocument, @@ -2553,19 +2525,14 @@ private async Task> AnalyzeSemanticsAsync( // Treat the edit as Insert/Delete. This may happen e.g. when all C# global statements are removed, the first one is added or they are moved to another file. if (syntacticEditKind == EditKind.Update) { - if (oldSymbol == null) + if (oldSymbol == null || oldDeclaration != null && oldDeclaration.SyntaxTree != oldModel?.SyntaxTree) { syntacticEditKind = EditKind.Insert; } - else if (newSymbol == null) + else if (newSymbol == null || newDeclaration != null && newDeclaration.SyntaxTree != newModel.SyntaxTree) { syntacticEditKind = EditKind.Delete; } - else if (oldDeclaration != null && oldModel != null && oldDeclaration.SyntaxTree != oldModel.SyntaxTree || - newDeclaration != null && newDeclaration.SyntaxTree != newModel.SyntaxTree) - { - syntacticEditKind = edit.Kind; - } } switch (syntacticEditKind) @@ -3035,24 +3002,7 @@ private async Task> AnalyzeSemanticsAsync( Contract.ThrowIfNull(oldSymbol); AnalyzeSymbolUpdate(oldSymbol, newSymbol, edit.NewNode, newCompilation, capabilities, diagnostics, semanticEdits, syntaxMap, cancellationToken); - - // The only update to the type itself that's supported is an addition or removal of the partial modifier, - // which does not have impact on the emitted type metadata. - if (newSymbol is INamedTypeSymbol) - { - continue; - } - - // The field/property/event itself is being updated. Currently we do not allow any modifiers to be updated. - // Attribute updates will have been handled already. - if (newSymbol is IFieldSymbol or IPropertySymbol or IEventSymbol) - { - continue; - } - - // The only updates allowed for a parameter or type parameter is an attribute change, but we only need the edit - // for the containing symbol which will be handled elsewhere. - if (newSymbol is IParameterSymbol or ITypeParameterSymbol) + if (newSymbol is INamedTypeSymbol or IFieldSymbol or IPropertySymbol or IEventSymbol or IParameterSymbol or ITypeParameterSymbol) { continue; } @@ -3064,7 +3014,7 @@ private async Task> AnalyzeSemanticsAsync( } } - foreach (var (oldEditNode, newEditNode) in triviaEdits) + foreach (var (oldEditNode, newEditNode, diagnosticSpan) in triviaEdits) { Contract.ThrowIfNull(oldModel); Contract.ThrowIfNull(newModel); @@ -3120,6 +3070,16 @@ private async Task> AnalyzeSemanticsAsync( continue; } + ReportMemberBodyUpdateRudeEdits(diagnostics, newDeclaration, diagnosticSpan); + + // updating generic methods and types + if (InGenericContext(oldSymbol, out var oldIsGenericMethod)) + { + var rudeEdit = oldIsGenericMethod ? RudeEditKind.GenericMethodTriviaUpdate : RudeEditKind.GenericTypeTriviaUpdate; + diagnostics.Add(new RudeEditDiagnostic(rudeEdit, diagnosticSpan, newEditNode, new[] { GetDisplayName(newEditNode) })); + continue; + } + // Edits in data member initializers and constructors are deferred, edits of other members (even on partial types) // do not need merging accross partial type declarations. var symbolKey = SymbolKey.Create(newSymbol, cancellationToken); @@ -3247,7 +3207,8 @@ private void ReportUpdatedSymbolDeclarationRudeEdits( if (oldSymbol.IsStatic != newSymbol.IsStatic || oldSymbol.IsVirtual != newSymbol.IsVirtual || oldSymbol.IsAbstract != newSymbol.IsAbstract || - oldSymbol.IsOverride != newSymbol.IsOverride) + oldSymbol.IsOverride != newSymbol.IsOverride || + oldSymbol.IsExtern != newSymbol.IsExtern) { // Do not report for accessors as the error will be reported on their associated symbol. if (oldSymbol is not IMethodSymbol { AssociatedSymbol: not null }) @@ -3265,6 +3226,21 @@ private void ReportUpdatedSymbolDeclarationRudeEdits( rudeEdit = RudeEditKind.ModifiersUpdate; } + // Report rude edit for updating const fields and values of enums. + // The latter is only reported whne the enum underlying type does not change to avoid cascading rude edits. + if (oldField.IsConst && newField.IsConst && !Equals(oldField.ConstantValue, newField.ConstantValue) && + TypesEquivalent(oldField.ContainingType.EnumUnderlyingType, newField.ContainingType.EnumUnderlyingType, exact: false)) + { + rudeEdit = RudeEditKind.InitializerUpdate; + } + + if (oldField.IsFixedSizeBuffer && + newField.IsFixedSizeBuffer && + !AreFixedSizeBufferSizesEqual(oldField, newField, cancellationToken)) + { + rudeEdit = RudeEditKind.FixedSizeFieldUpdate; + } + AnalyzeType(oldField.Type, newField.Type, ref rudeEdit, ref hasGeneratedAttributeChange); } else if (oldSymbol is IMethodSymbol oldMethod && newSymbol is IMethodSymbol newMethod) @@ -3312,6 +3288,18 @@ private void ReportUpdatedSymbolDeclarationRudeEdits( } } + // VB implements clause + if (!oldMethod.ExplicitInterfaceImplementations.SequenceEqual(newMethod.ExplicitInterfaceImplementations, (x, y) => SymbolsEquivalent(x, y))) + { + rudeEdit = RudeEditKind.ImplementsClauseUpdate; + } + + // VB handles clause + if (!AreHandledEventsEqual(oldMethod, newMethod)) + { + rudeEdit = RudeEditKind.HandlesClauseUpdate; + } + // Check return type - do not report for accessors, their containing symbol will report the rude edits and attribute updates. if (rudeEdit == RudeEditKind.None && oldMethod.AssociatedSymbol == null && newMethod.AssociatedSymbol == null) { @@ -3550,6 +3538,13 @@ private void AnalyzeSymbolUpdate( { AddCustomAttributeSemanticEdits(semanticEdits, newSymbol, syntaxMap, hasAttributeChange, hasReturnTypeAttributeChange, cancellationToken); } + + // updating generic methods and types + if (InGenericContext(oldSymbol, out var oldIsGenericMethod) || InGenericContext(newSymbol, out _)) + { + var rudeEdit = oldIsGenericMethod ? RudeEditKind.GenericMethodUpdate : RudeEditKind.GenericTypeUpdate; + ReportUpdateRudeEdit(diagnostics, rudeEdit, newSymbol, newNode, cancellationToken); + } } private static void AddCustomAttributeSemanticEdits( @@ -3874,7 +3869,12 @@ private void ReportUpdateRudeEdit(ArrayBuilder diagnostics, { var node = newNode ?? GetRudeEditDiagnosticNode(newSymbol, cancellationToken); var span = (rudeEdit == RudeEditKind.ChangeImplicitMainReturnType) ? GetGlobalStatementDiagnosticSpan(node) : GetDiagnosticSpan(node, EditKind.Update); - var arguments = (rudeEdit is RudeEditKind.TypeKindUpdate or RudeEditKind.ChangeImplicitMainReturnType) ? Array.Empty() : new[] { GetDisplayName(newSymbol) }; + + var arguments = (rudeEdit is + RudeEditKind.TypeKindUpdate or + RudeEditKind.ChangeImplicitMainReturnType or + RudeEditKind.GenericMethodUpdate or + RudeEditKind.GenericTypeUpdate) ? Array.Empty() : new[] { GetDisplayName(newSymbol) }; diagnostics.Add(new RudeEditDiagnostic(rudeEdit, span, node, arguments)); } @@ -4236,7 +4236,7 @@ static bool IsNotInDocument(SyntaxReference reference, SyntaxTree syntaxTree) (accumulate, node) => (node.SpanStart < accumulate.min) ? (node.SpanStart, node.Span) : accumulate).span; Contract.ThrowIfTrue(firstSpan.IsEmpty); - ReportMemberUpdateRudeEdits(diagnostics, newDeclaration, firstSpan); + ReportMemberBodyUpdateRudeEdits(diagnostics, newDeclaration, firstSpan); } // When explicitly implementing the copy constructor of a record the parameter name must match for symbol matching to work @@ -4251,9 +4251,8 @@ static bool IsNotInDocument(SyntaxReference reference, SyntaxTree syntaxTree) diagnostics.Add(new RudeEditDiagnostic( RudeEditKind.ExplicitRecordMethodParameterNamesMustMatch, GetDiagnosticSpan(newDeclaration, EditKind.Update), - arguments: new[] { - oldCtor.ToDisplayString(SymbolDisplayFormats.NameFormat) - })); + arguments: new[] { oldCtor.ToDisplayString(SymbolDisplayFormats.NameFormat) })); + continue; } } @@ -5226,7 +5225,7 @@ private void ReportStateMachineRudeEdits( #endregion - #region Helpers + #region Helpers private static SyntaxNode? TryGetNode(SyntaxNode root, int position) => root.FullSpan.Contains(position) ? root.FindToken(position).Parent : null; @@ -5287,6 +5286,33 @@ private static bool IsGlobalMain(ISymbol symbol) => symbol is IMethodSymbol { Name: WellKnownMemberNames.TopLevelStatementsEntryPointMethodName, ContainingType.Name: WellKnownMemberNames.TopLevelStatementsEntryPointTypeName }; #pragma warning restore format + private static bool InGenericContext(ISymbol symbol, out bool isGenericMethod) + { + var current = symbol; + + while (true) + { + if (current is IMethodSymbol { Arity: > 0 }) + { + isGenericMethod = true; + return true; + } + + if (current is INamedTypeSymbol { Arity: > 0 }) + { + isGenericMethod = false; + return true; + } + + current = current.ContainingSymbol; + if (current == null) + { + isGenericMethod = false; + return false; + } + } + } + #endregion #region Testing @@ -5323,17 +5349,6 @@ internal Match ComputeBodyMatch( { return _abstractEditAndContinueAnalyzer.ComputeBodyMatch(oldBody, newBody, activeNodes, diagnostics, out oldHasStateMachineSuspensionPoint, out newHasStateMachineSuspensionPoint); } - - internal void AnalyzeTrivia( - Match topMatch, - IReadOnlyDictionary editMap, - [Out] ArrayBuilder<(SyntaxNode OldNode, SyntaxNode NewNode)> triviaEdits, - [Out] ArrayBuilder lineEdits, - [Out] ArrayBuilder diagnostics, - CancellationToken cancellationToken) - { - _abstractEditAndContinueAnalyzer.AnalyzeTrivia(topMatch, editMap, triviaEdits, lineEdits, diagnostics, cancellationToken); - } } #endregion diff --git a/src/Features/Core/Portable/EditAndContinue/CommittedSolution.cs b/src/Features/Core/Portable/EditAndContinue/CommittedSolution.cs index 089e2da7d7b03..506450de104ea 100644 --- a/src/Features/Core/Portable/EditAndContinue/CommittedSolution.cs +++ b/src/Features/Core/Portable/EditAndContinue/CommittedSolution.cs @@ -309,15 +309,16 @@ public Task OnSourceFileUpdatedAsync(Document document, CancellationToken cancel } } - internal static async Task>> GetMatchingDocumentsAsync(Solution solution, Func compilationOutputsProvider, CancellationToken cancellationToken) + internal static async Task>> GetMatchingDocumentsAsync( + IEnumerable<(Project, IEnumerable)> documentsByProject, + Func compilationOutputsProvider, + CancellationToken cancellationToken) { - var projectTasks = solution.Projects.Select(async project => + var projectTasks = documentsByProject.Select(async projectDocumentStates => { - using var debugInfoReaderProvider = GetMethodDebugInfoReader(compilationOutputsProvider(project), project.Name); - if (debugInfoReaderProvider == null) - { - return Array.Empty(); - } + cancellationToken.ThrowIfCancellationRequested(); + + var (project, documentStates) = projectDocumentStates; // Skip projects that do not support Roslyn EnC (e.g. F#, etc). // Source files of these do not even need to be captured in the solution snapshot. @@ -326,9 +327,15 @@ public Task OnSourceFileUpdatedAsync(Document document, CancellationToken cancel return Array.Empty(); } + using var debugInfoReaderProvider = GetMethodDebugInfoReader(compilationOutputsProvider(project), project.Name); + if (debugInfoReaderProvider == null) + { + return Array.Empty(); + } + var debugInfoReader = debugInfoReaderProvider.CreateEditAndContinueMethodDebugInfoReader(); - var documentTasks = project.State.DocumentStates.States.Values.Select(async documentState => + var documentTasks = documentStates.Select(async documentState => { cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs index bbc3e647a839d..edf44e821eef1 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs @@ -96,8 +96,6 @@ void AddGeneralDiagnostic(EditAndContinueErrorCode code, string resourceName, Di AddRudeEdit(RudeEditKind.InsertGenericMethod, nameof(FeaturesResources.Adding_a_generic_0_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.Move, nameof(FeaturesResources.Moving_0_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.Delete, nameof(FeaturesResources.Deleting_0_will_prevent_the_debug_session_from_continuing)); - AddRudeEdit(RudeEditKind.MethodBodyAdd, nameof(FeaturesResources.Adding_a_method_body_will_prevent_the_debug_session_from_continuing)); - AddRudeEdit(RudeEditKind.MethodBodyDelete, nameof(FeaturesResources.Deleting_a_method_body_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.GenericMethodUpdate, nameof(FeaturesResources.Modifying_a_generic_method_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.GenericMethodTriviaUpdate, nameof(FeaturesResources.Modifying_whitespace_or_comments_in_a_generic_0_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.GenericTypeUpdate, nameof(FeaturesResources.Modifying_a_method_inside_the_context_of_a_generic_type_will_prevent_the_debug_session_from_continuing)); diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueWorkspaceService.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueWorkspaceService.cs index a2970db04abf6..3430af562aaf1 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueWorkspaceService.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueWorkspaceService.cs @@ -101,14 +101,32 @@ public void OnSourceFileUpdated(Document document) } } - public async ValueTask StartDebuggingSessionAsync(Solution solution, IManagedEditAndContinueDebuggerService debuggerService, bool captureMatchingDocuments, bool reportDiagnostics, CancellationToken cancellationToken) + public async ValueTask StartDebuggingSessionAsync( + Solution solution, + IManagedEditAndContinueDebuggerService debuggerService, + ImmutableArray captureMatchingDocuments, + bool captureAllMatchingDocuments, + bool reportDiagnostics, + CancellationToken cancellationToken) { - var initialDocumentStates = - captureMatchingDocuments ? await CommittedSolution.GetMatchingDocumentsAsync(solution, _compilationOutputsProvider, cancellationToken).ConfigureAwait(false) : - SpecializedCollections.EmptyEnumerable>(); + Contract.ThrowIfTrue(captureAllMatchingDocuments && !captureMatchingDocuments.IsEmpty); - var runtimeCapabilities = await debuggerService.GetCapabilitiesAsync(cancellationToken).ConfigureAwait(false); + IEnumerable> initialDocumentStates; + if (captureAllMatchingDocuments || !captureMatchingDocuments.IsEmpty) + { + var documentsByProject = captureAllMatchingDocuments ? + solution.Projects.Select(project => (project, project.State.DocumentStates.States.Values)) : + GetDocumentStatesGroupedByProject(solution, captureMatchingDocuments); + + initialDocumentStates = await CommittedSolution.GetMatchingDocumentsAsync(documentsByProject, _compilationOutputsProvider, cancellationToken).ConfigureAwait(false); + } + else + { + initialDocumentStates = SpecializedCollections.EmptyEnumerable>(); + } + + var runtimeCapabilities = await debuggerService.GetCapabilitiesAsync(cancellationToken).ConfigureAwait(false); var capabilities = ParseCapabilities(runtimeCapabilities); // For now, runtimes aren't returning capabilities, we just fall back to a known set. @@ -128,6 +146,13 @@ public async ValueTask StartDebuggingSessionAsync(Solution s return sessionId; } + private static IEnumerable<(Project, IEnumerable)> GetDocumentStatesGroupedByProject(Solution solution, ImmutableArray documentIds) + => from documentId in documentIds + where solution.ContainsDocument(documentId) + group documentId by documentId.ProjectId into projectDocumentIds + let project = solution.GetRequiredProject(projectDocumentIds.Key) + select (project, from documentId in projectDocumentIds select project.State.DocumentStates.GetState(documentId)); + // internal for testing internal static EditAndContinueCapabilities ParseCapabilities(ImmutableArray capabilities) { diff --git a/src/Features/Core/Portable/EditAndContinue/IEditAndContinueWorkspaceService.cs b/src/Features/Core/Portable/EditAndContinue/IEditAndContinueWorkspaceService.cs index 3e0545c4eed1c..c827e14e9d30b 100644 --- a/src/Features/Core/Portable/EditAndContinue/IEditAndContinueWorkspaceService.cs +++ b/src/Features/Core/Portable/EditAndContinue/IEditAndContinueWorkspaceService.cs @@ -22,7 +22,7 @@ internal interface IEditAndContinueWorkspaceService : IWorkspaceService void OnSourceFileUpdated(Document document); - ValueTask StartDebuggingSessionAsync(Solution solution, IManagedEditAndContinueDebuggerService debuggerService, bool captureMatchingDocuments, bool reportDiagnostics, CancellationToken cancellationToken); + ValueTask StartDebuggingSessionAsync(Solution solution, IManagedEditAndContinueDebuggerService debuggerService, ImmutableArray captureMatchingDocuments, bool captureAllMatchingDocuments, bool reportDiagnostics, CancellationToken cancellationToken); void BreakStateEntered(DebuggingSessionId sessionId, out ImmutableArray documentsToReanalyze); void EndDebuggingSession(DebuggingSessionId sessionId, out ImmutableArray documentsToReanalyze); diff --git a/src/Features/Core/Portable/EditAndContinue/Remote/IRemoteEditAndContinueService.cs b/src/Features/Core/Portable/EditAndContinue/Remote/IRemoteEditAndContinueService.cs index 2e7d0edfc1109..ed5d7e5d6fa2b 100644 --- a/src/Features/Core/Portable/EditAndContinue/Remote/IRemoteEditAndContinueService.cs +++ b/src/Features/Core/Portable/EditAndContinue/Remote/IRemoteEditAndContinueService.cs @@ -35,7 +35,7 @@ internal interface ICallback ValueTask> CommitSolutionUpdateAsync(DebuggingSessionId sessionId, CancellationToken cancellationToken); ValueTask DiscardSolutionUpdateAsync(DebuggingSessionId sessionId, CancellationToken cancellationToken); - ValueTask StartDebuggingSessionAsync(PinnedSolutionInfo solutionInfo, RemoteServiceCallbackId callbackId, bool captureMatchingDocuments, bool reportDiagnostics, CancellationToken cancellationToken); + ValueTask StartDebuggingSessionAsync(PinnedSolutionInfo solutionInfo, RemoteServiceCallbackId callbackId, ImmutableArray captureMatchingDocuments, bool captureAllMatchingDocuments, bool reportDiagnostics, CancellationToken cancellationToken); /// /// Returns ids of documents for which diagnostics need to be refreshed in-proc. diff --git a/src/Features/Core/Portable/EditAndContinue/Remote/RemoteEditAndContinueServiceProxy.cs b/src/Features/Core/Portable/EditAndContinue/Remote/RemoteEditAndContinueServiceProxy.cs index eb724e5479ca7..291b355e3bfc0 100644 --- a/src/Features/Core/Portable/EditAndContinue/Remote/RemoteEditAndContinueServiceProxy.cs +++ b/src/Features/Core/Portable/EditAndContinue/Remote/RemoteEditAndContinueServiceProxy.cs @@ -120,14 +120,15 @@ private IEditAndContinueWorkspaceService GetLocalService() public async ValueTask StartDebuggingSessionAsync( Solution solution, IManagedEditAndContinueDebuggerService debuggerService, - bool captureMatchingDocuments, + ImmutableArray captureMatchingDocuments, + bool captureAllMatchingDocuments, bool reportDiagnostics, CancellationToken cancellationToken) { var client = await RemoteHostClient.TryGetClientAsync(Workspace, cancellationToken).ConfigureAwait(false); if (client == null) { - var sessionId = await GetLocalService().StartDebuggingSessionAsync(solution, debuggerService, captureMatchingDocuments, reportDiagnostics, cancellationToken).ConfigureAwait(false); + var sessionId = await GetLocalService().StartDebuggingSessionAsync(solution, debuggerService, captureMatchingDocuments, captureAllMatchingDocuments, reportDiagnostics, cancellationToken).ConfigureAwait(false); return new RemoteDebuggingSessionProxy(Workspace, LocalConnection.Instance, sessionId); } @@ -137,7 +138,7 @@ private IEditAndContinueWorkspaceService GetLocalService() var sessionIdOpt = await connection.TryInvokeAsync( solution, - async (service, solutionInfo, callbackId, cancellationToken) => await service.StartDebuggingSessionAsync(solutionInfo, callbackId, captureMatchingDocuments, reportDiagnostics, cancellationToken).ConfigureAwait(false), + async (service, solutionInfo, callbackId, cancellationToken) => await service.StartDebuggingSessionAsync(solutionInfo, callbackId, captureMatchingDocuments, captureAllMatchingDocuments, reportDiagnostics, cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false); if (sessionIdOpt.HasValue) diff --git a/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs b/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs index 6a641e23d4379..a976f5eb8e78a 100644 --- a/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs +++ b/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs @@ -47,8 +47,8 @@ internal enum RudeEditKind : ushort InsertIntoClassWithLayout = 31, Move = 32, Delete = 33, - MethodBodyAdd = 34, - MethodBodyDelete = 35, + // MethodBodyAdd = 34, + // MethodBodyDelete = 35, GenericMethodUpdate = 36, GenericMethodTriviaUpdate = 37, GenericTypeUpdate = 38, diff --git a/src/Tools/ExternalAccess/Razor/Extensions.cs b/src/Features/Core/Portable/ExternalAccess/Razor/Api/Extensions.cs similarity index 92% rename from src/Tools/ExternalAccess/Razor/Extensions.cs rename to src/Features/Core/Portable/ExternalAccess/Razor/Api/Extensions.cs index bb0a4c895776f..fa680b55e3df6 100644 --- a/src/Tools/ExternalAccess/Razor/Extensions.cs +++ b/src/Features/Core/Portable/ExternalAccess/Razor/Api/Extensions.cs @@ -4,7 +4,7 @@ using Microsoft.CodeAnalysis.Host; -namespace Microsoft.CodeAnalysis.ExternalAccess.Razor +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Api { internal static class Extensions { diff --git a/src/Tools/ExternalAccess/Razor/IRazorDocumentOptions.cs b/src/Features/Core/Portable/ExternalAccess/Razor/Api/IRazorDocumentOptions.cs similarity index 86% rename from src/Tools/ExternalAccess/Razor/IRazorDocumentOptions.cs rename to src/Features/Core/Portable/ExternalAccess/Razor/Api/IRazorDocumentOptions.cs index f3948be3e0560..3f90dff9e06af 100644 --- a/src/Tools/ExternalAccess/Razor/IRazorDocumentOptions.cs +++ b/src/Features/Core/Portable/ExternalAccess/Razor/Api/IRazorDocumentOptions.cs @@ -4,7 +4,7 @@ using Microsoft.CodeAnalysis.Options; -namespace Microsoft.CodeAnalysis.ExternalAccess.Razor +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Api { internal interface IRazorDocumentOptions { diff --git a/src/Tools/ExternalAccess/Razor/IRazorDocumentOptionsService.cs b/src/Features/Core/Portable/ExternalAccess/Razor/Api/IRazorDocumentOptionsService.cs similarity index 83% rename from src/Tools/ExternalAccess/Razor/IRazorDocumentOptionsService.cs rename to src/Features/Core/Portable/ExternalAccess/Razor/Api/IRazorDocumentOptionsService.cs index c79030cb79173..5f3bde4a6816d 100644 --- a/src/Tools/ExternalAccess/Razor/IRazorDocumentOptionsService.cs +++ b/src/Features/Core/Portable/ExternalAccess/Razor/Api/IRazorDocumentOptionsService.cs @@ -4,9 +4,8 @@ using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Host; -namespace Microsoft.CodeAnalysis.ExternalAccess.Razor +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Api { internal interface IRazorDocumentOptionsService { diff --git a/src/Tools/ExternalAccess/Razor/RazorDocumentOptionsProviderFactory.cs b/src/Features/Core/Portable/ExternalAccess/Razor/Api/RazorDocumentOptionsProviderFactory.cs similarity index 78% rename from src/Tools/ExternalAccess/Razor/RazorDocumentOptionsProviderFactory.cs rename to src/Features/Core/Portable/ExternalAccess/Razor/Api/RazorDocumentOptionsProviderFactory.cs index b8d9394e72616..87a01af6e36db 100644 --- a/src/Tools/ExternalAccess/Razor/RazorDocumentOptionsProviderFactory.cs +++ b/src/Features/Core/Portable/ExternalAccess/Razor/Api/RazorDocumentOptionsProviderFactory.cs @@ -8,12 +8,12 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Shared.Extensions; -namespace Microsoft.CodeAnalysis.ExternalAccess.Razor +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Api { [Shared] [Export(typeof(IDocumentOptionsProviderFactory))] + [ExtensionOrder(Before = PredefinedDocumentOptionsProviderNames.EditorConfig)] internal sealed class RazorDocumentOptionsProviderFactory : IDocumentOptionsProviderFactory { private readonly Lazy _innerRazorDocumentOptionsService; @@ -32,28 +32,28 @@ public RazorDocumentOptionsProviderFactory( } public IDocumentOptionsProvider? TryCreate(Workspace workspace) - { - var optionsService = _innerRazorDocumentOptionsService.Value; - return new RazorDocumentOptionsProvider(optionsService); - } + => new RazorDocumentOptionsProvider(_innerRazorDocumentOptionsService); private sealed class RazorDocumentOptionsProvider : IDocumentOptionsProvider { - public readonly IRazorDocumentOptionsService RazorDocumentOptionsService; + public readonly Lazy RazorDocumentOptionsService; - public RazorDocumentOptionsProvider(IRazorDocumentOptionsService razorDocumentOptionsService) + public RazorDocumentOptionsProvider(Lazy razorDocumentOptionsService) { RazorDocumentOptionsService = razorDocumentOptionsService; } - public async Task GetOptionsForDocumentAsync(Document document, CancellationToken cancellationToken) + public async Task GetOptionsForDocumentAsync( + Document document, + CancellationToken cancellationToken) { if (!document.IsRazorDocument()) { return null; } - var options = await RazorDocumentOptionsService.GetOptionsForDocumentAsync(document, cancellationToken).ConfigureAwait(false); + var options = await RazorDocumentOptionsService.Value.GetOptionsForDocumentAsync( + document, cancellationToken).ConfigureAwait(false); return new RazorDocumentOptions(options); } } diff --git a/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/UnitTestingHotReloadService.cs b/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/UnitTestingHotReloadService.cs index fdc128d2512ab..f318af069729d 100644 --- a/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/UnitTestingHotReloadService.cs +++ b/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/UnitTestingHotReloadService.cs @@ -72,7 +72,14 @@ public UnitTestingHotReloadService(HostWorkspaceServices services) /// Cancellation token. public async Task StartSessionAsync(Solution solution, ImmutableArray capabilities, CancellationToken cancellationToken) { - var newSessionId = await _encService.StartDebuggingSessionAsync(solution, new DebuggerService(capabilities), captureMatchingDocuments: true, reportDiagnostics: false, cancellationToken).ConfigureAwait(false); + var newSessionId = await _encService.StartDebuggingSessionAsync( + solution, + new DebuggerService(capabilities), + captureMatchingDocuments: ImmutableArray.Empty, + captureAllMatchingDocuments: true, + reportDiagnostics: false, + cancellationToken).ConfigureAwait(false); + Contract.ThrowIfFalse(_sessionId == default, "Session already started"); _sessionId = newSessionId; } diff --git a/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs b/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs index 1badd4a005f6f..94f5eb230b5e0 100644 --- a/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs +++ b/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs @@ -69,7 +69,14 @@ public WatchHotReloadService(HostWorkspaceServices services) /// Cancellation token. public async Task StartSessionAsync(Solution solution, CancellationToken cancellationToken) { - var newSessionId = await _encService.StartDebuggingSessionAsync(solution, DebuggerService.Instance, captureMatchingDocuments: true, reportDiagnostics: false, cancellationToken).ConfigureAwait(false); + var newSessionId = await _encService.StartDebuggingSessionAsync( + solution, + DebuggerService.Instance, + captureMatchingDocuments: ImmutableArray.Empty, + captureAllMatchingDocuments: true, + reportDiagnostics: false, + cancellationToken).ConfigureAwait(false); + Contract.ThrowIfFalse(_sessionId == default, "Session already started"); _sessionId = newSessionId; } diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx index 97bd53da30702..73ee20ce8e880 100644 --- a/src/Features/Core/Portable/FeaturesResources.resx +++ b/src/Features/Core/Portable/FeaturesResources.resx @@ -507,12 +507,6 @@ Deleting '{0}' around an active statement will prevent the debug session from continuing. - - Adding a method body will prevent the debug session from continuing. - - - Deleting a method body will prevent the debug session from continuing. - An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. diff --git a/src/Features/Core/Portable/GoToDefinition/AbstractGoToDefinitionSymbolService.cs b/src/Features/Core/Portable/GoToDefinition/AbstractGoToDefinitionSymbolService.cs index 1dc5d8f921ea6..419f7149476fc 100644 --- a/src/Features/Core/Portable/GoToDefinition/AbstractGoToDefinitionSymbolService.cs +++ b/src/Features/Core/Portable/GoToDefinition/AbstractGoToDefinitionSymbolService.cs @@ -40,6 +40,12 @@ internal abstract class AbstractGoToDefinitionSymbolService : IGoToDefinitionSym var syntaxTree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); var syntaxFacts = document.GetRequiredLanguageService(); var token = await syntaxTree.GetTouchingTokenAsync(position, syntaxFacts.IsBindableToken, cancellationToken, findInsideTrivia: true).ConfigureAwait(false); + + if (token == default) + { + return null; + } + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); return GetTargetPositionIfControlFlow(semanticModel, token); diff --git a/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/AbstractSymbolDisplayService.AbstractSymbolDescriptionBuilder.cs b/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/AbstractSymbolDisplayService.AbstractSymbolDescriptionBuilder.cs index fe8226cce6929..f643c1fa58865 100644 --- a/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/AbstractSymbolDisplayService.AbstractSymbolDescriptionBuilder.cs +++ b/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/AbstractSymbolDisplayService.AbstractSymbolDescriptionBuilder.cs @@ -13,6 +13,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.DocumentationComments; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.QuickInfo; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; @@ -167,13 +168,36 @@ private void AddDocumentationContent(ISymbol symbol) { var formatter = Workspace.Services.GetLanguageServices(_semanticModel.Language).GetRequiredService(); + if (symbol is IParameterSymbol or ITypeParameterSymbol) + { + // Can just defer to the standard helper here. We only want to get the summary portion for just the + // param/type-param and we have no need for remarks/returns/value. + _documentationMap.Add( + SymbolDescriptionGroups.Documentation, + symbol.GetDocumentationParts(_semanticModel, _position, formatter, CancellationToken)); + return; + } + + if (symbol is IAliasSymbol alias) + symbol = alias.Target; + + var original = symbol.OriginalDefinition; + var format = ISymbolExtensions2.CrefFormat; + var compilation = _semanticModel.Compilation; + + // Grab the doc comment once as computing it for each portion we're concatenating can be expensive for + // lsif (which does this for every symbol in an entire solution). + var documentationComment = original is IMethodSymbol method + ? ISymbolExtensions2.GetMethodDocumentation(method, compilation, CancellationToken) + : original.GetDocumentationComment(compilation, expandIncludes: true, expandInheritdoc: true, cancellationToken: CancellationToken); + _documentationMap.Add( SymbolDescriptionGroups.Documentation, - symbol.GetDocumentationParts(_semanticModel, _position, formatter, CancellationToken)); + formatter.Format(documentationComment.SummaryText, symbol, _semanticModel, _position, format, CancellationToken)); _documentationMap.Add( SymbolDescriptionGroups.RemarksDocumentation, - symbol.GetRemarksDocumentationParts(_semanticModel, _position, formatter, CancellationToken)); + formatter.Format(documentationComment.RemarksText, symbol, _semanticModel, _position, format, CancellationToken)); AddReturnsDocumentationParts(symbol, formatter); AddValueDocumentationParts(symbol, formatter); @@ -182,10 +206,10 @@ private void AddDocumentationContent(ISymbol symbol) void AddReturnsDocumentationParts(ISymbol symbol, IDocumentationCommentFormattingService formatter) { - var parts = symbol.GetReturnsDocumentationParts(_semanticModel, _position, formatter, CancellationToken); + var parts = formatter.Format(documentationComment.ReturnsText, symbol, _semanticModel, _position, format, CancellationToken); if (!parts.IsDefaultOrEmpty) { - var _ = ArrayBuilder.GetInstance(out var builder); + using var _ = ArrayBuilder.GetInstance(out var builder); builder.Add(new TaggedText(TextTags.Text, FeaturesResources.Returns_colon)); builder.AddRange(LineBreak().ToTaggedText()); @@ -193,23 +217,23 @@ void AddReturnsDocumentationParts(ISymbol symbol, IDocumentationCommentFormattin builder.AddRange(parts); builder.Add(new TaggedText(TextTags.ContainerEnd, string.Empty)); - _documentationMap.Add(SymbolDescriptionGroups.ReturnsDocumentation, builder.ToImmutableArray()); + _documentationMap.Add(SymbolDescriptionGroups.ReturnsDocumentation, builder.ToImmutable()); } } void AddValueDocumentationParts(ISymbol symbol, IDocumentationCommentFormattingService formatter) { - var parts = symbol.GetValueDocumentationParts(_semanticModel, _position, formatter, CancellationToken); + var parts = formatter.Format(documentationComment.ValueText, symbol, _semanticModel, _position, format, CancellationToken); if (!parts.IsDefaultOrEmpty) { - var _ = ArrayBuilder.GetInstance(out var builder); + using var _ = ArrayBuilder.GetInstance(out var builder); builder.Add(new TaggedText(TextTags.Text, FeaturesResources.Value_colon)); builder.AddRange(LineBreak().ToTaggedText()); builder.Add(new TaggedText(TextTags.ContainerStart, " ")); builder.AddRange(parts); builder.Add(new TaggedText(TextTags.ContainerEnd, string.Empty)); - _documentationMap.Add(SymbolDescriptionGroups.ValueDocumentation, builder.ToImmutableArray()); + _documentationMap.Add(SymbolDescriptionGroups.ValueDocumentation, builder.ToImmutable()); } } } @@ -434,11 +458,13 @@ private static int GetPrecedingNewLineCount(SymbolDescriptionGroups group) private IDictionary> BuildDescriptionSections() { + var includeNavigationHints = this.Workspace.Options.GetOption(QuickInfoOptions.IncludeNavigationHintsInQuickInfo); + // Merge the two maps into one final result. var result = new Dictionary>(_documentationMap); foreach (var (group, parts) in _groupMap) { - var taggedText = parts.ToTaggedText(_getNavigationHint); + var taggedText = parts.ToTaggedText(_getNavigationHint, includeNavigationHints); if (group == SymbolDescriptionGroups.MainDescription) { // Mark the main description as a code block. diff --git a/src/Features/Core/Portable/MakeClassAbstract/AbstractMakeTypeAbstractCodeFixProvider.cs b/src/Features/Core/Portable/MakeClassAbstract/AbstractMakeTypeAbstractCodeFixProvider.cs index bbffd968fd915..357c93d403cf8 100644 --- a/src/Features/Core/Portable/MakeClassAbstract/AbstractMakeTypeAbstractCodeFixProvider.cs +++ b/src/Features/Core/Portable/MakeClassAbstract/AbstractMakeTypeAbstractCodeFixProvider.cs @@ -41,7 +41,7 @@ protected sealed override Task FixAllAsync(Document document, ImmutableArray generator.WithModifiers(currentTypeDeclaration, DeclarationModifiers.Abstract)); + (currentTypeDeclaration, generator) => generator.WithModifiers(currentTypeDeclaration, generator.GetModifiers(currentTypeDeclaration).WithIsAbstract(true))); } } diff --git a/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj b/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj index 24dadfa1fdbb7..b9a2fbe8317c5 100644 --- a/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj +++ b/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj @@ -105,6 +105,8 @@ + + diff --git a/src/Features/Core/Portable/QuickInfo/QuickInfoOptions.cs b/src/Features/Core/Portable/QuickInfo/QuickInfoOptions.cs index 04a5c11ffe80b..0c942bb5d5eec 100644 --- a/src/Features/Core/Portable/QuickInfo/QuickInfoOptions.cs +++ b/src/Features/Core/Portable/QuickInfo/QuickInfoOptions.cs @@ -10,5 +10,8 @@ internal static class QuickInfoOptions { public static readonly PerLanguageOption2 ShowRemarksInQuickInfo = new(nameof(QuickInfoOptions), nameof(ShowRemarksInQuickInfo), defaultValue: true, storageLocations: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.ShowRemarks")); + + public static readonly Option2 IncludeNavigationHintsInQuickInfo = new(nameof(QuickInfoOptions), nameof(IncludeNavigationHintsInQuickInfo), defaultValue: true, + storageLocations: new RoamingProfileStorageLocation("TextEditor.Specific.IncludeNavigationHintsInQuickInfo")); } } diff --git a/src/Features/Core/Portable/Shared/Extensions/ISymbolExtensions_2.cs b/src/Features/Core/Portable/Shared/Extensions/ISymbolExtensions_2.cs index 3ac0667b42987..0a5c49dc63f88 100644 --- a/src/Features/Core/Portable/Shared/Extensions/ISymbolExtensions_2.cs +++ b/src/Features/Core/Portable/Shared/Extensions/ISymbolExtensions_2.cs @@ -181,50 +181,19 @@ public static ImmutableArray GetDocumentationParts(this ISymbol symb => formatter.Format(GetDocumentation(symbol.OriginalDefinition, semanticModel.Compilation, cancellationToken), symbol, semanticModel, position, CrefFormat, cancellationToken); - public static ImmutableArray GetRemarksDocumentationParts(this ISymbol symbol, SemanticModel semanticModel, int position, IDocumentationCommentFormattingService formatter, CancellationToken cancellationToken) - => formatter.Format(GetRemarksDocumentation(symbol.OriginalDefinition, semanticModel.Compilation, cancellationToken), - symbol, semanticModel, position, CrefFormat, cancellationToken); - - public static ImmutableArray GetReturnsDocumentationParts(this ISymbol symbol, SemanticModel semanticModel, int position, IDocumentationCommentFormattingService formatter, CancellationToken cancellationToken) - => formatter.Format(GetReturnsDocumentation(symbol.OriginalDefinition, semanticModel.Compilation, cancellationToken), - symbol, semanticModel, position, CrefFormat, cancellationToken); - - public static ImmutableArray GetValueDocumentationParts(this ISymbol symbol, SemanticModel semanticModel, int position, IDocumentationCommentFormattingService formatter, CancellationToken cancellationToken) - => formatter.Format(GetValueDocumentation(symbol.OriginalDefinition, semanticModel.Compilation, cancellationToken), - symbol, semanticModel, position, CrefFormat, cancellationToken); - private static string? GetDocumentation(ISymbol symbol, Compilation compilation, CancellationToken cancellationToken) - => symbol switch + { + return symbol switch { - IParameterSymbol parameter => GetParameterDocumentation(parameter, compilation, cancellationToken), - ITypeParameterSymbol typeParam => typeParam.ContainingSymbol.GetDocumentationComment(compilation, expandIncludes: true, expandInheritdoc: true, cancellationToken: cancellationToken).GetTypeParameterText(symbol.Name), + IParameterSymbol parameter => GetParameterDocumentation(parameter, compilation, cancellationToken)?.GetParameterText(parameter.Name), + ITypeParameterSymbol typeParam => typeParam.ContainingSymbol.GetDocumentationComment(compilation, expandIncludes: true, expandInheritdoc: true, cancellationToken: cancellationToken)?.GetTypeParameterText(typeParam.Name), IMethodSymbol method => GetMethodDocumentation(method, compilation, cancellationToken).SummaryText, IAliasSymbol alias => alias.Target.GetDocumentationComment(compilation, expandIncludes: true, expandInheritdoc: true, cancellationToken: cancellationToken).SummaryText, _ => symbol.GetDocumentationComment(compilation, expandIncludes: true, expandInheritdoc: true, cancellationToken: cancellationToken).SummaryText, }; + } - private static string? GetRemarksDocumentation(ISymbol symbol, Compilation compilation, CancellationToken cancellationToken) - => symbol switch - { - IMethodSymbol method => GetMethodDocumentation(method, compilation, cancellationToken).RemarksText, - _ => symbol.GetDocumentationComment(compilation, expandIncludes: true, expandInheritdoc: true, cancellationToken: cancellationToken).RemarksText, - }; - - private static string? GetReturnsDocumentation(ISymbol symbol, Compilation compilation, CancellationToken cancellationToken) - => symbol switch - { - IMethodSymbol method => GetMethodDocumentation(method, compilation, cancellationToken).ReturnsText, - _ => symbol.GetDocumentationComment(compilation, expandIncludes: true, expandInheritdoc: true, cancellationToken: cancellationToken).ReturnsText, - }; - - private static string? GetValueDocumentation(ISymbol symbol, Compilation compilation, CancellationToken cancellationToken) - => symbol switch - { - IMethodSymbol method => GetMethodDocumentation(method, compilation, cancellationToken).ValueText, - _ => symbol.GetDocumentationComment(compilation, expandIncludes: true, expandInheritdoc: true, cancellationToken: cancellationToken).ValueText, - }; - - private static string? GetParameterDocumentation(IParameterSymbol parameter, Compilation compilation, CancellationToken cancellationToken) + public static DocumentationComment? GetParameterDocumentation(IParameterSymbol parameter, Compilation compilation, CancellationToken cancellationToken) { var containingSymbol = parameter.ContainingSymbol; if (containingSymbol.ContainingSymbol.IsDelegateType() && containingSymbol is IMethodSymbol methodSymbol) @@ -249,7 +218,7 @@ public static ImmutableArray GetValueDocumentationParts(this ISymbol } // Get the comments from the original definition of the containing symbol. - return containingSymbol.OriginalDefinition.GetDocumentationComment(compilation, expandIncludes: true, expandInheritdoc: true, cancellationToken: cancellationToken).GetParameterText(parameter.Name); + return containingSymbol.OriginalDefinition.GetDocumentationComment(compilation, expandIncludes: true, expandInheritdoc: true, cancellationToken: cancellationToken); } public static Func> GetDocumentationPartsFactory( @@ -269,7 +238,7 @@ public static Func> GetDocumentationP SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers | SymbolDisplayMiscellaneousOptions.UseSpecialTypes); - private static DocumentationComment GetMethodDocumentation(IMethodSymbol method, Compilation compilation, CancellationToken cancellationToken) + public static DocumentationComment GetMethodDocumentation(IMethodSymbol method, Compilation compilation, CancellationToken cancellationToken) { switch (method.MethodKind) { diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf index 7d0d239fdc2d9..cc356be6ece44 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf @@ -3304,16 +3304,6 @@ Pokud se specifikátor formátu g použije bez dalších specifikátorů vlastn Odstranění prvku {0} v okolí aktivního příkazu zabrání v pokračování relace ladění. - - Adding a method body will prevent the debug session from continuing. - Přidání těla metody zabrání v pokračování relace ladění. - - - - Deleting a method body will prevent the debug session from continuing. - Odstranění těla metody zabrání v pokračování relace ladění. - - Updating async or iterator modifier around an active statement will prevent the debug session from continuing. Aktualizace modifikátoru async nebo iterator kolem aktivního příkazu bude bránit relaci ladění v tom, aby pokračovala. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf index 339902abb3b37..be3144797f9e3 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf @@ -3304,16 +3304,6 @@ Bei Verwendung des Formatbezeichners "g" ohne weitere benutzerdefinierte Formatb Durch Löschen von "{0}" um eine aktive Anweisung herum wird verhindert, dass die Debuggingsitzung fortgesetzt wird. - - Adding a method body will prevent the debug session from continuing. - Durch Hinzufügen eines Methodenkörpers wird verhindert, dass die Debuggingsitzung fortgesetzt wird. - - - - Deleting a method body will prevent the debug session from continuing. - Durch Löschen eines Methodenkörpers wird verhindert, dass eine Debuggingsitzung fortgesetzt wird. - - Updating async or iterator modifier around an active statement will prevent the debug session from continuing. Eine asynchrone Aktualisierung oder ein iteratormodifizierer um eine aktive Anweisung verhindert, dass die Debugsitzung fortgesetzt wird. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf index bdc894f0c2e1a..eaa1749cb69f4 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf @@ -3304,16 +3304,6 @@ Si el especificador de formato "g" se usa sin otros especificadores de formato p Eliminar '{0}' en una instrucción activa impedirá que continúe la sesión de depuración. - - Adding a method body will prevent the debug session from continuing. - Agregar un cuerpo de método impedirá que continúe la sesión de depuración. - - - - Deleting a method body will prevent the debug session from continuing. - Eliminar un cuerpo de método impedirá que continúe la sesión de depuración. - - Updating async or iterator modifier around an active statement will prevent the debug session from continuing. La actualización del modificador async o iterator en una instrucción activa impedirá que la sesión de depuración continúe. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf index f92ccdedc18bf..3129470c0141e 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf @@ -3304,16 +3304,6 @@ Si le spécificateur de format "g" est utilisé sans autres spécificateurs de f La suppression de '{0}' autour d'une instruction active empêche la session de débogage de se poursuivre. - - Adding a method body will prevent the debug session from continuing. - L'ajout d'un corps de méthode empêche la session de débogage de se poursuivre. - - - - Deleting a method body will prevent the debug session from continuing. - La suppression d'un corps de méthode empêche la session de débogage de se poursuivre. - - Updating async or iterator modifier around an active statement will prevent the debug session from continuing. La mise à jour d'un modificateur async ou iterator autour d'une instruction active empêche la session de débogage de se poursuivre. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf index 9d13a938e5066..819457a5f11b1 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf @@ -3304,16 +3304,6 @@ Se l'identificatore di formato "g" viene usato senza altri identificatori di for Se si elimina '{0}' in prossimità di un'istruzione attiva, la sessione di debug non potrà continuare. - - Adding a method body will prevent the debug session from continuing. - Se si aggiunge un corpo del metodo, la sessione di debug non potrà continuare. - - - - Deleting a method body will prevent the debug session from continuing. - Se si elimina un corpo del metodo, la sessione di debug non potrà continuare. - - Updating async or iterator modifier around an active statement will prevent the debug session from continuing. Se si aggiorna un modificatore iterator o async in prossimità di un'istruzione attiva, la sessione di debug non potrà continuare. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf index 66d41c2fd01fe..16381ccc5f2c9 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf @@ -3304,16 +3304,6 @@ If the "g" format specifier is used without other custom format specifiers, it's アクティブ ステートメントの前後の '{0}' を削除すると、デバッグ セッションは続行されません。 - - Adding a method body will prevent the debug session from continuing. - メソッド本体を追加すると、デバッグ セッションは続行されません。 - - - - Deleting a method body will prevent the debug session from continuing. - メソッド本体を削除すると、デバッグ セッションは続行されません。 - - Updating async or iterator modifier around an active statement will prevent the debug session from continuing. アクティブ ステートメントの前後の async 修飾子または iterator 修飾子を更新すると、デバッグ セッションが継続しなくなります。 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf index 4a5730dd76c44..1a5b762cd534d 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf @@ -3304,16 +3304,6 @@ If the "g" format specifier is used without other custom format specifiers, it's 활성 문 주위에서 '{0}'을(를) 삭제하면 디버그 세션을 계속할 수 없습니다. - - Adding a method body will prevent the debug session from continuing. - 메서드 본문을 추가하면 디버그 세션을 계속할 수 없습니다. - - - - Deleting a method body will prevent the debug session from continuing. - 메서드 본문을 삭제하면 디버그 세션을 계속할 수 없습니다. - - Updating async or iterator modifier around an active statement will prevent the debug session from continuing. 활성 문 주위의 async 또는 iterator 한정자를 업데이트하면 디버그 세션이 계속되지 않습니다. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf index 66f37d7e2207f..9a1aebfd64925 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf @@ -3304,16 +3304,6 @@ Jeśli specyfikator formatu „g” jest używany bez innych niestandardowych sp Usunięcie elementu „{0}” w ramach aktywnej instrukcji uniemożliwi kontynuowanie sesji debugowania. - - Adding a method body will prevent the debug session from continuing. - Dodanie treści metody uniemożliwi kontynuowanie sesji debugowania. - - - - Deleting a method body will prevent the debug session from continuing. - Usunięcie treści metody uniemożliwi kontynuowanie sesji debugowania. - - Updating async or iterator modifier around an active statement will prevent the debug session from continuing. Aktualizowanie modyfikatora asynchronicznego lub powiązanego z iteratorem w pobliżu aktywnej instrukcji uniemożliwi kontynuowanie sesji debugowania. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf index abb54b4699b88..055c7b78a3e82 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf @@ -3304,16 +3304,6 @@ Se o especificador de formato "g" for usado sem outros especificadores de format Excluir "{0}" em uma instrução ativa impedirá que a sessão de depuração continue. - - Adding a method body will prevent the debug session from continuing. - Adicionar um corpo do método impedirá que a sessão de depuração continue. - - - - Deleting a method body will prevent the debug session from continuing. - Excluir um corpo de método impedirá que a sessão de depuração continue. - - Updating async or iterator modifier around an active statement will prevent the debug session from continuing. A atualização do modificador "async" ou "iterator" em torno de uma instrução ativa impedirá a sessão de depuração de continuar. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf index 11e679e57d815..cdb668ea6c403 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf @@ -3304,16 +3304,6 @@ If the "g" format specifier is used without other custom format specifiers, it's Удаление "{0}" рядом с активным оператором сделает продолжение сеанса отладки невозможным. - - Adding a method body will prevent the debug session from continuing. - Добавление тела метода сделает продолжение сеанса отладки невозможным. - - - - Deleting a method body will prevent the debug session from continuing. - Удаление тела метода сделает продолжение сеанса отладки невозможным. - - Updating async or iterator modifier around an active statement will prevent the debug session from continuing. Обновление модификатора async или iterator рядом с активным оператором сделает продолжение сеанса отладки невозможным. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf index f640b1efd5fb7..dfc553f36d4db 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf @@ -3304,16 +3304,6 @@ If the "g" format specifier is used without other custom format specifiers, it's Etkin bir deyimin etrafından '{0}' öğesini silme, hata ayıklama oturumunun devam etmesini engelleyecek. - - Adding a method body will prevent the debug session from continuing. - Bir yöntem gövdesi ekleme, hata ayıklama oturumunun devam etmesini engelleyecek. - - - - Deleting a method body will prevent the debug session from continuing. - Bir yöntem gövdesi silme, hata ayıklama oturumunun devam etmesini engelleyecek. - - Updating async or iterator modifier around an active statement will prevent the debug session from continuing. Etkin bir deyimin etrafındaki async ya da iterator değiştiricisini güncelleştirmek hata ayıklama oturumunun devam etmesini önler. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf index eeb8e3f0073fe..8d5a4962c373a 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf @@ -3304,16 +3304,6 @@ If the "g" format specifier is used without other custom format specifiers, it's 删除活动语句周围的“{0}”将阻止调试会话继续。 - - Adding a method body will prevent the debug session from continuing. - 添加方法主体将阻止调试会话继续。 - - - - Deleting a method body will prevent the debug session from continuing. - 删除方法主体将阻止调试会话继续。 - - Updating async or iterator modifier around an active statement will prevent the debug session from continuing. 更新当前语句的 async 或 iterator 修饰符可以阻止调试会话继续。 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf index 7bfbd66b92237..2e434493caf66 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf @@ -3304,16 +3304,6 @@ If the "g" format specifier is used without other custom format specifiers, it's 刪除現用陳述式前後的 '{0}',會造成偵錯工作階段無法繼續。 - - Adding a method body will prevent the debug session from continuing. - 加入方法主體,會造成偵錯工作階段無法繼續。 - - - - Deleting a method body will prevent the debug session from continuing. - 刪除方法主體,會造成偵錯工作階段無法繼續。 - - Updating async or iterator modifier around an active statement will prevent the debug session from continuing. 更新作用中陳述式前後的 async 或 iterator 修飾元,會造成偵錯工作階段無法繼續。 diff --git a/src/Features/Lsif/Generator/Generator.cs b/src/Features/Lsif/Generator/Generator.cs index 0e094db7bf6ca..2289715f537dc 100644 --- a/src/Features/Lsif/Generator/Generator.cs +++ b/src/Features/Lsif/Generator/Generator.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.Writing; using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.QuickInfo; using Roslyn.Utilities; using Methods = Microsoft.VisualStudio.LanguageServer.Protocol.Methods; @@ -64,13 +65,19 @@ public void GenerateForCompilation(Compilation compilation, string projectPath, var topLevelSymbolsWriter = new BatchingLsifJsonWriter(_lsifJsonWriter); var topLevelSymbolsResultSetTracker = new SymbolHoldingResultSetTracker(topLevelSymbolsWriter, compilation, _idFactory); + // Disable navigation hints in quick info as computing them both takes too long, and they're never + // even emitted in the final lsif hover information. + var workspace = languageServices.WorkspaceServices.Workspace; + workspace.SetOptions(workspace.Options.WithChangedOption( + QuickInfoOptions.IncludeNavigationHintsInQuickInfo, false)); + Parallel.ForEach(compilation.SyntaxTrees, syntaxTree => { var semanticModel = compilation.GetSemanticModel(syntaxTree); // We generate the document contents into an in-memory copy, and then write that out at once at the end. This // allows us to collect everything and avoid a lot of fine-grained contention on the write to the single - // LSIF file. Becasue of the rule that vertices must be written before they're used by an edge, we'll flush any top- + // LSIF file. Because of the rule that vertices must be written before they're used by an edge, we'll flush any top- // level symbol result sets made first, since the document contents will point to that. Parallel calls to CopyAndEmpty // are allowed and might flush other unrelated stuff at the same time, but there's no harm -- the "causality" ordering // is preserved. @@ -121,7 +128,7 @@ public void GenerateForCompilation(Compilation compilation, string projectPath, var text = semanticModel.SyntaxTree.GetText(); // We always use UTF-8 encoding when writing out file contents, as that's expected by LSIF implementations. - // TODO: when we move to .NET Core, is there a way to reduce allocatios here? + // TODO: when we move to .NET Core, is there a way to reduce allocations here? contentBase64Encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(text.ToString())); } diff --git a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb index 48c816486ac67..bd5083287b434 100644 --- a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb +++ b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb @@ -2289,52 +2289,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue ClassifyUpdate(DirectCast(oldNode, NamespaceStatementSyntax), DirectCast(newNode, NamespaceStatementSyntax)) Return - Case SyntaxKind.VariableDeclarator - ClassifyUpdate(DirectCast(oldNode, VariableDeclaratorSyntax), DirectCast(newNode, VariableDeclaratorSyntax)) - Return - - Case SyntaxKind.ModifiedIdentifier - ClassifyUpdate(DirectCast(newNode, ModifiedIdentifierSyntax)) - Return - - Case SyntaxKind.SubBlock, - SyntaxKind.FunctionBlock - ClassifyUpdate(DirectCast(oldNode, MethodBlockSyntax), DirectCast(newNode, MethodBlockSyntax)) - Return - - Case SyntaxKind.SubStatement, - SyntaxKind.FunctionStatement - ClassifyUpdate(DirectCast(oldNode, MethodStatementSyntax), DirectCast(newNode, MethodStatementSyntax)) - Return - - Case SyntaxKind.OperatorBlock - ClassifyUpdate(DirectCast(oldNode, OperatorBlockSyntax), DirectCast(newNode, OperatorBlockSyntax)) - Return - - Case SyntaxKind.ConstructorBlock - ClassifyUpdate(DirectCast(oldNode, ConstructorBlockSyntax), DirectCast(newNode, ConstructorBlockSyntax)) - Return - - Case SyntaxKind.PropertyStatement - ClassifyUpdate(DirectCast(oldNode, PropertyStatementSyntax), DirectCast(newNode, PropertyStatementSyntax)) - Return - - Case SyntaxKind.EventStatement - ClassifyUpdate(DirectCast(oldNode, EventStatementSyntax), DirectCast(newNode, EventStatementSyntax)) - Return - - Case SyntaxKind.GetAccessorBlock, - SyntaxKind.SetAccessorBlock, - SyntaxKind.AddHandlerAccessorBlock, - SyntaxKind.RemoveHandlerAccessorBlock, - SyntaxKind.RaiseEventAccessorBlock - ClassifyUpdate(DirectCast(oldNode, AccessorBlockSyntax), DirectCast(newNode, AccessorBlockSyntax)) - Return - - Case SyntaxKind.EnumMemberDeclaration - ClassifyUpdate(DirectCast(oldNode, EnumMemberDeclarationSyntax), DirectCast(newNode, EnumMemberDeclarationSyntax)) - Return - Case SyntaxKind.AttributesStatement ReportError(RudeEditKind.Update) Return @@ -2354,161 +2308,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue ReportError(RudeEditKind.Renamed) End Sub - Private Sub ClassifyUpdate(newNode As ModifiedIdentifierSyntax) - ' Otherwise only the size of the array changed, which is a legal initializer update - ' unless it contains lambdas, queries etc. - ClassifyDeclarationBodyRudeUpdates(newNode) - End Sub - - Private Sub ClassifyUpdate(oldNode As VariableDeclaratorSyntax, newNode As VariableDeclaratorSyntax) - Dim typeDeclaration = DirectCast(oldNode.Parent.Parent, TypeBlockSyntax) - If typeDeclaration.BlockStatement.Arity > 0 Then - ReportError(RudeEditKind.GenericTypeInitializerUpdate) - Return - End If - - If ClassifyTypeAndInitializerUpdates(oldNode.Initializer, - oldNode.AsClause, - newNode.Initializer, - newNode.AsClause) Then - ' Check if a constant field is updated: - Dim fieldDeclaration = DirectCast(oldNode.Parent, FieldDeclarationSyntax) - If fieldDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword) Then - ReportError(RudeEditKind.Update) - Return - End If - End If - End Sub - - Private Sub ClassifyUpdate(oldNode As PropertyStatementSyntax, newNode As PropertyStatementSyntax) - If Not SyntaxFactory.AreEquivalent(oldNode.ImplementsClause, newNode.ImplementsClause) Then - ReportError(RudeEditKind.ImplementsClauseUpdate) - Return - End If - - If ClassifyTypeAndInitializerUpdates(oldNode.Initializer, oldNode.AsClause, newNode.Initializer, newNode.AsClause) Then - ' change in an initializer of an auto-property - Dim typeDeclaration = DirectCast(oldNode.Parent, TypeBlockSyntax) - If typeDeclaration.BlockStatement.Arity > 0 Then - ReportError(RudeEditKind.GenericTypeInitializerUpdate) - Return - End If - End If - End Sub - - ' Returns true if the initializer has changed. - Private Function ClassifyTypeAndInitializerUpdates(oldEqualsValue As EqualsValueSyntax, - oldClause As AsClauseSyntax, - newEqualsValue As EqualsValueSyntax, - newClause As AsClauseSyntax) As Boolean - - Dim oldInitializer = GetInitializerExpression(oldEqualsValue, oldClause) - Dim newInitializer = GetInitializerExpression(newEqualsValue, newClause) - - If newInitializer IsNot Nothing AndAlso Not SyntaxFactory.AreEquivalent(oldInitializer, newInitializer) Then - ClassifyDeclarationBodyRudeUpdates(newInitializer) - Return True - End If - - Return False - End Function - - Private Sub ClassifyUpdate(oldNode As EventStatementSyntax, newNode As EventStatementSyntax) - ' A custom event can't be matched with a field event and vice versa: - Debug.Assert(SyntaxFactory.AreEquivalent(oldNode.CustomKeyword, newNode.CustomKeyword)) - - If Not SyntaxFactory.AreEquivalent(oldNode.ImplementsClause, newNode.ImplementsClause) Then - ReportError(RudeEditKind.ImplementsClauseUpdate) - Return - End If - End Sub - - Private Sub ClassifyUpdate(oldNode As MethodBlockSyntax, newNode As MethodBlockSyntax) - ClassifyMethodBodyRudeUpdate(oldNode, - newNode, - containingMethod:=newNode, - containingType:=DirectCast(newNode.Parent, TypeBlockSyntax)) - End Sub - - Private Sub ClassifyUpdate(oldNode As MethodStatementSyntax, newNode As MethodStatementSyntax) - ' TODO (tomat): We can support this - If Not SyntaxFactory.AreEquivalent(oldNode.HandlesClause, newNode.HandlesClause) Then - ReportError(RudeEditKind.HandlesClauseUpdate) - Return - End If - - If Not SyntaxFactory.AreEquivalent(oldNode.ImplementsClause, newNode.ImplementsClause) Then - ReportError(RudeEditKind.ImplementsClauseUpdate) - Return - End If - End Sub - - Private Sub ClassifyUpdate(oldNode As OperatorBlockSyntax, newNode As OperatorBlockSyntax) - ClassifyMethodBodyRudeUpdate(oldNode, - newNode, - containingMethod:=Nothing, - containingType:=DirectCast(newNode.Parent, TypeBlockSyntax)) - End Sub - - Private Sub ClassifyUpdate(oldNode As AccessorBlockSyntax, newNode As AccessorBlockSyntax) - Debug.Assert(newNode.Parent.IsKind(SyntaxKind.EventBlock) OrElse - newNode.Parent.IsKind(SyntaxKind.PropertyBlock)) - - ClassifyMethodBodyRudeUpdate(oldNode, - newNode, - containingMethod:=Nothing, - containingType:=DirectCast(newNode.Parent.Parent, TypeBlockSyntax)) - End Sub - - Private Sub ClassifyUpdate(oldNode As EnumMemberDeclarationSyntax, newNode As EnumMemberDeclarationSyntax) - If Not SyntaxFactory.AreEquivalent(oldNode.Initializer, newNode.Initializer) Then - ReportError(RudeEditKind.InitializerUpdate) - Return - End If - End Sub - - Private Sub ClassifyUpdate(oldNode As ConstructorBlockSyntax, newNode As ConstructorBlockSyntax) - ClassifyMethodBodyRudeUpdate(oldNode, - newNode, - containingMethod:=Nothing, - containingType:=DirectCast(newNode.Parent, TypeBlockSyntax)) - End Sub - - Private Sub ClassifyMethodBodyRudeUpdate(oldBody As MethodBlockBaseSyntax, - newBody As MethodBlockBaseSyntax, - containingMethod As MethodBlockSyntax, - containingType As TypeBlockSyntax) - - If (oldBody.EndBlockStatement Is Nothing) <> (newBody.EndBlockStatement Is Nothing) Then - If oldBody.EndBlockStatement Is Nothing Then - ReportError(RudeEditKind.MethodBodyAdd) - Return - Else - ReportError(RudeEditKind.MethodBodyDelete) - Return - End If - End If - - ' The method only gets called if there are no other changes to the method declaration. - ' Since we got the update edit something has to be different in the body. - Debug.Assert(newBody.EndBlockStatement IsNot Nothing) - - ClassifyMemberBodyRudeUpdate(containingMethod, containingType, isTriviaUpdate:=False) - ClassifyDeclarationBodyRudeUpdates(newBody) - End Sub - - Public Sub ClassifyMemberBodyRudeUpdate(containingMethodOpt As MethodBlockSyntax, containingTypeOpt As TypeBlockSyntax, isTriviaUpdate As Boolean) - If containingMethodOpt?.SubOrFunctionStatement.TypeParameterList IsNot Nothing Then - ReportError(If(isTriviaUpdate, RudeEditKind.GenericMethodTriviaUpdate, RudeEditKind.GenericMethodUpdate)) - Return - End If - - If containingTypeOpt?.BlockStatement.Arity > 0 Then - ReportError(If(isTriviaUpdate, RudeEditKind.GenericTypeTriviaUpdate, RudeEditKind.GenericTypeUpdate)) - Return - End If - End Sub - Public Sub ClassifyDeclarationBodyRudeUpdates(newDeclarationOrBody As SyntaxNode) For Each node In newDeclarationOrBody.DescendantNodesAndSelf() Select Case node.Kind @@ -2565,20 +2364,29 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue classifier.ClassifyEdit() End Sub - Friend Overrides Sub ReportMemberUpdateRudeEdits(diagnostics As ArrayBuilder(Of RudeEditDiagnostic), newMember As SyntaxNode, span As TextSpan?) + Friend Overrides Sub ReportMemberBodyUpdateRudeEdits(diagnostics As ArrayBuilder(Of RudeEditDiagnostic), newMember As SyntaxNode, span As TextSpan?) Dim classifier = New EditClassifier(Me, diagnostics, Nothing, newMember, EditKind.Update, span:=span) - - classifier.ClassifyMemberBodyRudeUpdate( - TryCast(newMember, MethodBlockSyntax), - newMember.FirstAncestorOrSelf(Of TypeBlockSyntax)(), - isTriviaUpdate:=True) - classifier.ClassifyDeclarationBodyRudeUpdates(newMember) End Sub #End Region #Region "Semantic Rude Edits" + Protected Overrides Function AreFixedSizeBufferSizesEqual(oldField As IFieldSymbol, newField As IFieldSymbol, cancellationToken As CancellationToken) As Boolean + Throw ExceptionUtilities.Unreachable + End Function + + Protected Overrides Function AreHandledEventsEqual(oldMethod As IMethodSymbol, newMethod As IMethodSymbol) As Boolean + Return oldMethod.HandledEvents.SequenceEqual( + newMethod.HandledEvents, + Function(x, y) + Return x.HandlesKind = y.HandlesKind AndAlso + SymbolsEquivalent(x.EventContainer, y.EventContainer) AndAlso + SymbolsEquivalent(x.EventSymbol, y.EventSymbol) AndAlso + SymbolsEquivalent(x.WithEventsSourceProperty, y.WithEventsSourceProperty) + End Function) + End Function + Friend Overrides Sub ReportInsertedMemberSymbolRudeEdits(diagnostics As ArrayBuilder(Of RudeEditDiagnostic), newSymbol As ISymbol, newNode As SyntaxNode, insertingIntoExistingContainingType As Boolean) Dim kind = GetInsertedMemberSymbolRudeEditKind(newSymbol, insertingIntoExistingContainingType) diff --git a/src/Tools/BuildBoss/CompilerNuGetCheckerUtil.cs b/src/Tools/BuildBoss/CompilerNuGetCheckerUtil.cs index da55e7e6fc89c..a0beac8371f15 100644 --- a/src/Tools/BuildBoss/CompilerNuGetCheckerUtil.cs +++ b/src/Tools/BuildBoss/CompilerNuGetCheckerUtil.cs @@ -275,7 +275,7 @@ private bool GetPackageAssets(TextWriter textWriter, List packageA /// private bool GetPackageAssetsCore(TextWriter textWriter, bool isDesktop, List packageAssets, params string[] directoryPaths) { - var relativeNameMap = new Dictionary(PathComparer); + var relativeNameMap = new Dictionary(PathComparer); var allGood = true; IEnumerable enumerateAssets(string directory, SearchOption searchOption = SearchOption.TopDirectoryOnly) @@ -320,16 +320,18 @@ IEnumerable enumerateFiles() var assetRelativeName = getRelativeName(assetFilePath); var hash = md5.ComputeHash(stream); var hashString = BitConverter.ToString(hash); - if (relativeNameMap.TryGetValue(assetRelativeName, out PackageAsset existingAsset)) + if (relativeNameMap.TryGetValue(assetRelativeName, out var tuple)) { // Make sure that all copies of the DLL have the same contents. The DLLs are being merged into // a single directory in the resulting NuGet. If the contents are different then our merge is // invalid. - if (existingAsset.Checksum != hashString) + if (tuple.PackageAsset.Checksum != hashString) { textWriter.WriteLine($"Asset {assetRelativeName} exists at two different versions"); - textWriter.WriteLine($"\tHash 1: {hashString}"); - textWriter.WriteLine($"\tHash 2: {existingAsset.Checksum}"); + textWriter.WriteLine($"\tFile Path 1: {tuple.OriginalFilePath}"); + textWriter.WriteLine($"\tHash 1: {tuple.PackageAsset.Checksum}"); + textWriter.WriteLine($"\tFile Path 2: {assetFilePath}"); + textWriter.WriteLine($"\tHash 2: {hashString}"); allGood = false; } } @@ -337,7 +339,7 @@ IEnumerable enumerateFiles() { var packageAsset = new PackageAsset(assetRelativeName, hashString, isDesktop); packageAssets.Add(packageAsset); - relativeNameMap[assetRelativeName] = packageAsset; + relativeNameMap[assetRelativeName] = (packageAsset, assetFilePath); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedEditAndContinueLanguageService.cs b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedEditAndContinueLanguageService.cs index 10f0351999e98..e758e088ba372 100644 --- a/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedEditAndContinueLanguageService.cs +++ b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedEditAndContinueLanguageService.cs @@ -79,7 +79,15 @@ public async Task StartDebuggingAsync(DebugSessionFlags flags, CancellationToken try { var solution = GetCurrentCompileTimeSolution(); - _debuggingSession = await _proxy.StartDebuggingSessionAsync(solution, _debuggerService, captureMatchingDocuments: false, reportDiagnostics: true, cancellationToken).ConfigureAwait(false); + var openedDocumentIds = _proxy.Workspace.GetOpenDocumentIds().ToImmutableArray(); + + _debuggingSession = await _proxy.StartDebuggingSessionAsync( + solution, + _debuggerService, + captureMatchingDocuments: openedDocumentIds, + captureAllMatchingDocuments: false, + reportDiagnostics: true, + cancellationToken).ConfigureAwait(false); } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) { diff --git a/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedHotReloadLanguageService.cs b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedHotReloadLanguageService.cs index e9a5c2fa869f1..266be61bf358b 100644 --- a/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedHotReloadLanguageService.cs +++ b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedHotReloadLanguageService.cs @@ -92,7 +92,15 @@ public async ValueTask StartSessionAsync(CancellationToken cancellationToken) try { var solution = GetCurrentCompileTimeSolution(); - _debuggingSession = await _proxy.StartDebuggingSessionAsync(solution, _debuggerService, captureMatchingDocuments: false, reportDiagnostics: true, cancellationToken).ConfigureAwait(false); + var openedDocumentIds = _proxy.Workspace.GetOpenDocumentIds().ToImmutableArray(); + + _debuggingSession = await _proxy.StartDebuggingSessionAsync( + solution, + _debuggerService, + captureMatchingDocuments: openedDocumentIds, + captureAllMatchingDocuments: false, + reportDiagnostics: true, + cancellationToken).ConfigureAwait(false); } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) { diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioDocumentNavigationService.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioDocumentNavigationService.cs index b9b585607588d..0c42ba3fd8cb8 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioDocumentNavigationService.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioDocumentNavigationService.cs @@ -261,7 +261,7 @@ private bool TryNavigateToLocation( var generatedDocument = project .GetSourceGeneratedDocumentAsync(documentId, cancellationToken) - .GetAwaiter().GetResult(); + .AsTask().GetAwaiter().GetResult(); if (generatedDocument != null) { diff --git a/src/VisualStudio/Core/Def/PackageRegistration.pkgdef b/src/VisualStudio/Core/Def/PackageRegistration.pkgdef index 423f550810185..04e201bbaf387 100644 --- a/src/VisualStudio/Core/Def/PackageRegistration.pkgdef +++ b/src/VisualStudio/Core/Def/PackageRegistration.pkgdef @@ -16,13 +16,13 @@ [$RootKey$\FeatureFlags\Roslyn\LSP\Editor] "Description"="Enables the LSP-powered C#/VB editing experience outside of CodeSpaces." "Value"=dword:00000000 -"Title"="Enable experimental C#/VB editor (requires restart)" +"Title"="C#/VB LSP editor (requires restart)" "PreviewPaneChannels"="IntPreview,int.main" [$RootKey$\FeatureFlags\Roslyn\LSP\Completion] "Description"="Enables the LSP-powered C#/VB completion experience to operate with prototype behavior. Note this currently will not affect local C#/VB scenarios except for C# in Razor." -"Value"=dword:00000000 -"Title"="Enable experimental C#/VB LSP completion experience" +"Value"=dword:00000001 +"Title"="C#/VB LSP completion experience" "PreviewPaneChannels"="IntPreview,int.main" // The option page configuration is duplicated in RoslynPackage diff --git a/src/VisualStudio/Core/Def/ValueTracking/TreeItemViewModel.cs b/src/VisualStudio/Core/Def/ValueTracking/TreeItemViewModel.cs index 4b4267b6988fe..4ec7232d1ad59 100644 --- a/src/VisualStudio/Core/Def/ValueTracking/TreeItemViewModel.cs +++ b/src/VisualStudio/Core/Def/ValueTracking/TreeItemViewModel.cs @@ -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. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Collections.ObjectModel; @@ -89,6 +90,16 @@ public TreeItemViewModel( NotifyPropertyChanged(nameof(ShowGlyph)); } }; + + TreeViewModel.PropertyChanged += (s, e) => + { + if (e.PropertyName == nameof(TreeViewModel.HighlightBrush)) + { + // If the highlight changes we need to recalculate the inlines so the + // highlighting is correct + NotifyPropertyChanged(nameof(Inlines)); + } + }; } public virtual void NavigateTo() @@ -121,7 +132,7 @@ private ImmutableArray CalculateInlines() }); var spanStartPosition = TextSpan.Start - ClassifiedSpans[0].TextSpan.Start; - var spanEndPosition = TextSpan.End - ClassifiedSpans[0].TextSpan.End; + var highlightSpan = new TextSpan(spanStartPosition, TextSpan.Length); return classifiedTexts.ToInlines( TreeViewModel.ClassificationFormatMap, @@ -130,7 +141,11 @@ private ImmutableArray CalculateInlines() { if (TreeViewModel.HighlightBrush is not null) { - if (position >= spanStartPosition && position <= spanEndPosition) + // Check the span start first because we always want to highlight a run that + // is at the start, even if the TextSpan length is 0. If it's not the start, + // highlighting should still happen if the run position is contained within + // the span. + if (position == highlightSpan.Start || highlightSpan.Contains(position)) { run.SetValue( TextElement.BackgroundProperty, diff --git a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingRoot.xaml b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingRoot.xaml new file mode 100644 index 0000000000000..6bb2533a84b65 --- /dev/null +++ b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingRoot.xaml @@ -0,0 +1,15 @@ + + + + + diff --git a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingRoot.xaml.cs b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingRoot.xaml.cs new file mode 100644 index 0000000000000..adb84633967ed --- /dev/null +++ b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingRoot.xaml.cs @@ -0,0 +1,49 @@ +// 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.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace Microsoft.VisualStudio.LanguageServices.ValueTracking +{ + /// + /// Interaction logic for ValueTrackingRoot.xaml + /// + internal partial class ValueTrackingRoot : UserControl + { + public string EmptyText => ServicesVSResources.Select_an_appropriate_symbol_to_start_value_tracking; + + public ValueTrackingRoot() + { + InitializeComponent(); + } + + public void SetChild(FrameworkElement? child) + { + RootGrid.Children.Clear(); + + if (child is null) + { + EmptyTextMessage.Visibility = Visibility.Visible; + } + else + { + EmptyTextMessage.Visibility = Visibility.Collapsed; + RootGrid.Children.Add(child); + } + } + } +} diff --git a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingToolWindow.cs b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingToolWindow.cs index c47b9d092cc54..d6353b0414292 100644 --- a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingToolWindow.cs +++ b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingToolWindow.cs @@ -8,13 +8,16 @@ using System.Runtime.InteropServices; using Microsoft.VisualStudio.Shell; using Roslyn.Utilities; +using System.Windows; +using System.Windows.Data; +using System.Windows.Markup; namespace Microsoft.VisualStudio.LanguageServices.ValueTracking { [Guid(Guids.ValueTrackingToolWindowIdString)] internal class ValueTrackingToolWindow : ToolWindowPane { - private readonly Grid _rootGrid = new(); + private readonly ValueTrackingRoot _root = new(); public static ValueTrackingToolWindow? Instance { get; set; } @@ -30,8 +33,7 @@ public ValueTrackingTreeViewModel? ViewModel } _viewModel = value; - _rootGrid.Children.Clear(); - _rootGrid.Children.Add(new ValueTrackingTree(_viewModel)); + _root.SetChild(new ValueTrackingTree(_viewModel)); } } @@ -44,20 +46,14 @@ public ValueTrackingTreeViewModel? ViewModel public ValueTrackingToolWindow() : base(null) { Caption = ServicesVSResources.Value_Tracking; - - _rootGrid.Children.Add(new TextBlock() - { - Text = ServicesVSResources.Select_an_appropriate_symbol_to_start_value_tracking - }); - - Content = _rootGrid; + Content = _root; } public ValueTrackingToolWindow(ValueTrackingTreeViewModel viewModel) : base(null) { Caption = ServicesVSResources.Value_Tracking; - Content = _rootGrid; + Content = _root; ViewModel = viewModel; } diff --git a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingTree.xaml b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingTree.xaml index 88087b3356b33..b8709da06ace2 100644 --- a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingTree.xaml +++ b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingTree.xaml @@ -39,7 +39,7 @@ Background="{DynamicResource {x:Static vs:ProgressBarColors.BackgroundBrushKey}}" /> - +