diff --git a/docs/features/source-generators.cookbook.md b/docs/features/source-generators.cookbook.md index cae15298818a1..d419405b1dddf 100644 --- a/docs/features/source-generators.cookbook.md +++ b/docs/features/source-generators.cookbook.md @@ -115,26 +115,26 @@ namespace GeneratedNamespace ```csharp [Generator] public class FileTransformGenerator : ISourceGenerator - { - public void Initialize(InitializationContext context) {} +{ + public void Initialize(InitializationContext context) {} - public void Execute(SourceGeneratorContext context) + public void Execute(SourceGeneratorContext context) + { + // find anything that matches our files + var myFiles = context.AnalyzerOptions.AdditionalFiles.Where(at => at.Path.EndsWith(".xml")); + foreach (var file in myFiles) { - // find anything that matches our files - var myFiles = context.AnalyzerOptions.AdditionalFiles.Where(at => at.Path.EndsWith(".xml")); - foreach (var file in myFiles) - { - var content = file.GetText(context.CancellationToken); + var content = file.GetText(context.CancellationToken); - // do some transforms based on the file context - string output = MyXmlToCSharpCompiler.Compile(content); + // do some transforms based on the file context + string output = MyXmlToCSharpCompiler.Compile(content); - var sourceText = SourceText.From(output, Encoding.UTF8); + var sourceText = SourceText.From(output, Encoding.UTF8); - context.AddSource($"{file.Name}generated.cs", sourceText); - } + context.AddSource($"{file.Name}generated.cs", sourceText); } } +} ``` ### Augment user code @@ -628,7 +628,7 @@ partial class MyRecord } ``` -This attribute could also be used for #participate-in-the-ide-experience, +This attribute could also be used for [Participate in the IDE experience](#participate-in-the-ide-experience), when the full scope of that feature is fully designed. In that scenario, instead of the generator finding every type marked with the given attribute, the compiler would notify the generator of every type marked with the given @@ -670,8 +670,8 @@ public string Serialize() ``` Obviously this is heavily simplified -- this example only handles the `string` and `int` -types properly and has no error recovery, but it should serve to demonstrate the kind -of code a source generator could add to a compilation. +types properly, adds a trailing comma to the json output and has no error recovery, but +it should serve to demonstrate the kind of code a source generator could add to a compilation. Our next task is design a generator to generate the above code, since the above code is itself customized in the `// Body` section according to the diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbol.cs index 1ad6aa39447a9..64b98f7bbf1ee 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbol.cs @@ -158,6 +158,14 @@ internal override bool IsExpressionBodied } } + protected override bool AllowRefOrOut + { + get + { + return true; + } + } + protected override bool IsWithinExpressionOrBlockBody(int position, out int offset) { ConstructorDeclarationSyntax ctorSyntax = GetSyntax(); diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbolBase.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbolBase.cs index 783c891e0ba76..e95316aa32930 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbolBase.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbolBase.cs @@ -48,7 +48,7 @@ protected sealed override void MethodChecks(DiagnosticBag diagnostics) SyntaxToken arglistToken; _lazyParameters = ParameterHelpers.MakeParameters( signatureBinder, this, parameterList, out arglistToken, - allowRefOrOut: true, + allowRefOrOut: AllowRefOrOut, allowThis: false, addRefReadOnlyModifier: false, diagnostics: diagnostics); @@ -72,6 +72,8 @@ protected sealed override void MethodChecks(DiagnosticBag diagnostics) #nullable enable protected abstract ParameterListSyntax GetParameterList(); + + protected abstract bool AllowRefOrOut { get; } #nullable restore internal sealed override void AfterAddingTypeMembersChecks(ConversionsBase conversions, DiagnosticBag diagnostics) diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordConstructor.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordConstructor.cs index 3d083b323c07c..d5d562a861469 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordConstructor.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordConstructor.cs @@ -4,11 +4,8 @@ #nullable enable -using System.Collections.Immutable; using System.Diagnostics; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.PooledObjects; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Symbols { @@ -36,13 +33,9 @@ internal RecordDeclarationSyntax GetSyntax() return GetSyntax().PrimaryConstructorBaseType; } - internal override bool IsExpressionBodied - { - get - { - return false; - } - } + protected override bool AllowRefOrOut => false; + + internal override bool IsExpressionBodied => false; protected override bool IsWithinExpressionOrBlockBody(int position, out int offset) { diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs index 7ad2efa10b00c..3efb75b1fd3b5 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs @@ -145,7 +145,7 @@ public static IEnumerable GetPreprocessorKeywordKinds() public static bool IsPunctuation(SyntaxKind kind) { - return kind >= SyntaxKind.TildeToken && kind <= SyntaxKind.PercentEqualsToken; + return kind >= SyntaxKind.TildeToken && kind <= SyntaxKind.QuestionQuestionEqualsToken; } public static bool IsLanguagePunctuation(SyntaxKind kind) diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs index 287385c0e4b96..75fc4d91b19f5 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs @@ -15864,6 +15864,237 @@ record R(int P1, int* P2, delegate* P3);"; Assert.True(p.HasPointerType); } + [Fact, WorkItem(45008, "https://github.com/dotnet/roslyn/issues/45008")] + public void PositionalMemberModifiers_RefOrOut() + { + var src = @" +record R(ref int P1, out int P2); +"; + + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (2,9): error CS0177: The out parameter 'P2' must be assigned to before control leaves the current method + // record R(ref int P1, out int P2); + Diagnostic(ErrorCode.ERR_ParamUnassigned, "(ref int P1, out int P2)").WithArguments("P2").WithLocation(2, 9), + // (2,10): error CS0631: ref and out are not valid in this context + // record R(ref int P1, out int P2); + Diagnostic(ErrorCode.ERR_IllegalRefParam, "ref").WithLocation(2, 10), + // (2,22): error CS0631: ref and out are not valid in this context + // record R(ref int P1, out int P2); + Diagnostic(ErrorCode.ERR_IllegalRefParam, "out").WithLocation(2, 22) + ); + } + + [Fact, WorkItem(45008, "https://github.com/dotnet/roslyn/issues/45008")] + public void PositionalMemberModifiers_RefOrOut_WithBase() + { + var src = @" +record Base(int I); +record R(ref int P1, out int P2) : Base(P2 = 1); +"; + + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (3,10): error CS0631: ref and out are not valid in this context + // record R(ref int P1, out int P2) : Base(P2 = 1); + Diagnostic(ErrorCode.ERR_IllegalRefParam, "ref").WithLocation(3, 10), + // (3,22): error CS0631: ref and out are not valid in this context + // record R(ref int P1, out int P2) : Base(P2 = 1); + Diagnostic(ErrorCode.ERR_IllegalRefParam, "out").WithLocation(3, 22) + ); + } + + [Fact, WorkItem(45008, "https://github.com/dotnet/roslyn/issues/45008")] + public void PositionalMemberModifiers_In() + { + var src = @" +record R(in int P1); + +public class C +{ + public static void Main() + { + var r = new R(42); + int i = 43; + var r2 = new R(in i); + System.Console.Write((r.P1, r2.P1)); + } +} +"; + + var comp = CreateCompilation(new[] { src, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + comp.VerifyDiagnostics(); + CompileAndVerify(comp, expectedOutput: "(42, 43)", verify: Verification.Skipped /* init-only */); + + var actualMembers = comp.GetMember("R").Constructors.ToTestDisplayStrings(); + var expectedMembers = new[] + { + "R..ctor(in System.Int32 P1)", + "R..ctor(R )" + }; + AssertEx.Equal(expectedMembers, actualMembers); + } + + [Fact, WorkItem(45008, "https://github.com/dotnet/roslyn/issues/45008")] + public void PositionalMemberModifiers_This() + { + var src = @" +record R(this int i); +"; + + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (2,10): error CS0027: Keyword 'this' is not available in the current context + // record R(this int i); + Diagnostic(ErrorCode.ERR_ThisInBadContext, "this").WithLocation(2, 10) + ); + } + + [Fact, WorkItem(45008, "https://github.com/dotnet/roslyn/issues/45008")] + public void PositionalMemberModifiers_Params() + { + var src = @" +record R(params int[] Array); + +public class C +{ + public static void Main() + { + var r = new R(42, 43); + var r2 = new R(new[] { 44, 45 }); + System.Console.Write((r.Array[0], r.Array[1], r2.Array[0], r2.Array[1])); + } +} +"; + + var comp = CreateCompilation(new[] { src, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + comp.VerifyDiagnostics(); + CompileAndVerify(comp, expectedOutput: "(42, 43, 44, 45)", verify: Verification.Skipped /* init-only */); + + var actualMembers = comp.GetMember("R").Constructors.ToTestDisplayStrings(); + var expectedMembers = new[] + { + "R..ctor(params System.Int32[] Array)", + "R..ctor(R )" + }; + AssertEx.Equal(expectedMembers, actualMembers); + } + + [Fact, WorkItem(45008, "https://github.com/dotnet/roslyn/issues/45008")] + public void PositionalMemberDefaultValue() + { + var src = @" +record R(int P = 42) +{ + public static void Main() + { + var r = new R(); + System.Console.Write(r.P); + } +} +"; + + var comp = CreateCompilation(new[] { src, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + comp.VerifyDiagnostics(); + CompileAndVerify(comp, expectedOutput: "42", verify: Verification.Skipped /* init-only */); + } + + [Fact, WorkItem(45008, "https://github.com/dotnet/roslyn/issues/45008")] + public void PositionalMemberDefaultValue_AndPropertyWithInitializer() + { + var src = @" +record R(int P = 1) +{ + public int P { get; init; } = 42; + + public static void Main() + { + var r = new R(); + System.Console.Write(r.P); + } +} +"; + var comp = CreateCompilation(new[] { src, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "42", verify: Verification.Skipped /* init-only */); + + verifier.VerifyIL("R..ctor(int)", @" +{ + // Code size 16 (0x10) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.s 42 + IL_0003: stfld ""int R.

k__BackingField"" + IL_0008: ldarg.0 + IL_0009: call ""object..ctor()"" + IL_000e: nop + IL_000f: ret +}"); + } + + [Fact, WorkItem(45008, "https://github.com/dotnet/roslyn/issues/45008")] + public void PositionalMemberDefaultValue_AndPropertyWithoutInitializer() + { + var src = @" +record R(int P = 42) +{ + public int P { get; init; } + + public static void Main() + { + var r = new R(); + System.Console.Write(r.P); + } +} +"; + var comp = CreateCompilation(new[] { src, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "0", verify: Verification.Skipped /* init-only */); + + verifier.VerifyIL("R..ctor(int)", @" +{ + // Code size 8 (0x8) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: call ""object..ctor()"" + IL_0006: nop + IL_0007: ret +}"); + } + + [Fact, WorkItem(45008, "https://github.com/dotnet/roslyn/issues/45008")] + public void PositionalMemberDefaultValue_AndPropertyWithInitializer_CopyingParameter() + { + var src = @" +record R(int P = 42) +{ + public int P { get; init; } = P; + + public static void Main() + { + var r = new R(); + System.Console.Write(r.P); + } +} +"; + var comp = CreateCompilation(new[] { src, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "42", verify: Verification.Skipped /* init-only */); + + verifier.VerifyIL("R..ctor(int)", @" +{ + // Code size 15 (0xf) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld ""int R.

k__BackingField"" + IL_0007: ldarg.0 + IL_0008: call ""object..ctor()"" + IL_000d: nop + IL_000e: ret +}"); + } + [Fact] public void AttributesOnPrimaryConstructorParameters_01() { diff --git a/src/EditorFeatures/CSharpTest/Classification/SyntacticClassifierTests.cs b/src/EditorFeatures/CSharpTest/Classification/SyntacticClassifierTests.cs index 58cb338edbf14..86af78aafd94b 100644 --- a/src/EditorFeatures/CSharpTest/Classification/SyntacticClassifierTests.cs +++ b/src/EditorFeatures/CSharpTest/Classification/SyntacticClassifierTests.cs @@ -3141,6 +3141,7 @@ public void method() i ^= i; i <<= i; i >>= i; + i ??= i; object s = x => x + 1; Point point; unsafe @@ -3374,6 +3375,10 @@ public void method() Operators.GreaterThanGreaterThanEquals, Identifier("i"), Punctuation.Semicolon, + Identifier("i"), + Operators.QuestionQuestionEquals, + Identifier("i"), + Punctuation.Semicolon, Keyword("object"), Local("s"), Operators.Equals, diff --git a/src/EditorFeatures/TestUtilities/Classification/FormattedClassifications.Operators.cs b/src/EditorFeatures/TestUtilities/Classification/FormattedClassifications.Operators.cs index 02326c27e303f..fce3dcac45e21 100644 --- a/src/EditorFeatures/TestUtilities/Classification/FormattedClassifications.Operators.cs +++ b/src/EditorFeatures/TestUtilities/Classification/FormattedClassifications.Operators.cs @@ -53,6 +53,7 @@ private static FormattedClassification New(string text) public static FormattedClassification PlusEquals { get; } = New("+="); public static FormattedClassification PlusPlus { get; } = New("++"); public static FormattedClassification QuestionMark { get; } = New("?"); + public static FormattedClassification QuestionQuestionEquals { get; } = New("??="); public static FormattedClassification Slash { get; } = New("/"); public static FormattedClassification SlashEquals { get; } = New("/="); public static FormattedClassification Tilde { get; } = New("~"); diff --git a/src/VisualStudio/LiveShare/Impl/PackageRegistration.pkgdef b/src/VisualStudio/LiveShare/Impl/PackageRegistration.pkgdef index d038caab86690..d7eeabf5098f1 100644 --- a/src/VisualStudio/LiveShare/Impl/PackageRegistration.pkgdef +++ b/src/VisualStudio/LiveShare/Impl/PackageRegistration.pkgdef @@ -19,3 +19,10 @@ @="{bb7e83f4-eaf6-456c-b140-f8c027a7ed8a}" "Name"="CSharpLspLanguageService" +;; Set the C#_LSP NotALanguage attribute for this package. +;; This will suppress the duplicate C#_LSP entry from appearing in Tools > Options > Text Editor +;; This can be removed once we remove the CSharpLspPackage +[$RootKey$\Languages\Language Services\C#_LSP] +"Package"="{BB7E83F4-EAF6-456C-B140-F8C027A7ED8A}" +"NotALanguage"=dword:00000001 + diff --git a/src/Workspaces/CSharp/Portable/Classification/ClassificationHelpers.cs b/src/Workspaces/CSharp/Portable/Classification/ClassificationHelpers.cs index 8d01b99c85f38..d1efe154e901c 100644 --- a/src/Workspaces/CSharp/Portable/Classification/ClassificationHelpers.cs +++ b/src/Workspaces/CSharp/Portable/Classification/ClassificationHelpers.cs @@ -440,6 +440,7 @@ private static bool IsOperator(this SyntaxKind kind) case SyntaxKind.MinusEqualsToken: case SyntaxKind.CaretEqualsToken: case SyntaxKind.PercentEqualsToken: + case SyntaxKind.QuestionQuestionEqualsToken: return true; default: