diff --git a/.globalconfig b/.globalconfig new file mode 100644 index 0000000000000..865b6307a9a72 --- /dev/null +++ b/.globalconfig @@ -0,0 +1,2 @@ +# RS0006: Roslyn experimental language API +dotnet_diagnostic.RSEXPERIMENTAL006.severity = none diff --git a/docs/contributing/Compiler Test Plan.md b/docs/contributing/Compiler Test Plan.md index f058d85dbcfde..6cd648a432389 100644 --- a/docs/contributing/Compiler Test Plan.md +++ b/docs/contributing/Compiler Test Plan.md @@ -11,6 +11,23 @@ This document provides guidance for thinking about language interactions and tes - BCL (including mono) and other customer impact - Determinism - Loading from metadata (source vs. loaded from metadata) +- VB/F# interop +- C++/CLI interop (particularly for metadata format changes, e.g. DIMs, static abstracts in interfaces, or generic attributes) +- Performance and stress testing +- Can build VS +- Check that `Obsolete` is honored for members used in binding/lowering +- LangVersion +- IL verification (file issue on `runtime` repo as needed and track [here](https://github.com/dotnet/roslyn/issues/22872)) +- Does the feature use cryptographic hashes in any way? (examples: metadata names of file-local types, extension types, assembly strong naming, PDB document table, etc.) + - Consider using non-cryptographic hash such as `XxHash128` instead. + - If you must use a cryptographic hash in the feature implementation, then use `SourceHashAlgorithms.Default`, and not any specific hash. + - A cryptographic hash must never be included in a public API name. Taking a change to the default crypto algorithm would then change public API surface, which would be enormously breaking. + - **DO NOT** allow using the value of a crypto hash in a field, method or type name + - **DO** allow using the value of a crypto hash in attribute or field values + - Any time the compiler reads in metadata containing crypto hashes, even if it's an attribute value, ensure the crypto hash algorithm name is included in the metadata (e.g. prefixing it to the hash value), so that it can be changed over time and the compiler can continue to read both metadata using both the old and new algorithms. + +## Public APIs + - Public compiler APIs (including semantic model and other APIs listed below): - GetDeclaredSymbol - GetEnclosingSymbol @@ -32,21 +49,10 @@ This document provides guidance for thinking about language interactions and tes - GetOperation (`IOperation`) - GetCFG (`ControlFlowGraph`), including a scenario with some nested conditional - DocumentationCommentId APIs -- VB/F# interop -- C++/CLI interop (particularly for metadata format changes, e.g. DIMs, static abstracts in interfaces, or generic attributes) -- Performance and stress testing -- Can build VS -- Check that `Obsolete` is honored for members used in binding/lowering -- LangVersion -- IL verification (file issue on `runtime` repo as needed and track [here](https://github.com/dotnet/roslyn/issues/22872)) - -- Does the feature use cryptographic hashes in any way? (examples: metadata names of file-local types, extension types, assembly strong naming, PDB document table, etc.) - - Consider using non-cryptographic hash such as `XxHash128` instead. - - If you must use a cryptographic hash in the feature implementation, then use `SourceHashAlgorithms.Default`, and not any specific hash. - - A cryptographic hash must never be included in a public API name. Taking a change to the default crypto algorithm would then change public API surface, which would be enormously breaking. - - **DO NOT** allow using the value of a crypto hash in a field, method or type name - - **DO** allow using the value of a crypto hash in attribute or field values - - Any time the compiler reads in metadata containing crypto hashes, even if it's an attribute value, ensure the crypto hash algorithm name is included in the metadata (e.g. prefixing it to the hash value), so that it can be changed over time and the compiler can continue to read both metadata using both the old and new algorithms. +- All newly added APIs are experimental + - Tracking issue for marking APIs as non-experimental + - APIs are marked with `[Experimental(RoslynExperiments.PreviewLanguageFeatureApi, UrlFormat = "link to tracking issue")]` + - APIs have gone through API review ## Type and members diff --git a/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Main.Generated.cs b/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Main.Generated.cs index e8febb0d116fa..f14d0e34ce0e0 100644 --- a/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Main.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Main.Generated.cs @@ -229,6 +229,7 @@ public partial class CSharpSyntaxVisitor public virtual TResult? VisitSpreadElement(SpreadElementSyntax node) => this.DefaultVisit(node); /// Called when the visitor visits a WithElementSyntax node. + [Experimental(global::Microsoft.CodeAnalysis.RoslynExperiments.PreviewLanguageFeatureApi, UrlFormat = @"https://github.com/dotnet/roslyn/issues/82210")] public virtual TResult? VisitWithElement(WithElementSyntax node) => this.DefaultVisit(node); /// Called when the visitor visits a QueryExpressionSyntax node. @@ -976,6 +977,7 @@ public partial class CSharpSyntaxVisitor public virtual void VisitSpreadElement(SpreadElementSyntax node) => this.DefaultVisit(node); /// Called when the visitor visits a WithElementSyntax node. + [Experimental(global::Microsoft.CodeAnalysis.RoslynExperiments.PreviewLanguageFeatureApi, UrlFormat = @"https://github.com/dotnet/roslyn/issues/82210")] public virtual void VisitWithElement(WithElementSyntax node) => this.DefaultVisit(node); /// Called when the visitor visits a QueryExpressionSyntax node. @@ -1722,6 +1724,7 @@ public partial class CSharpSyntaxRewriter : CSharpSyntaxVisitor public override SyntaxNode? VisitSpreadElement(SpreadElementSyntax node) => node.Update(VisitToken(node.OperatorToken), (ExpressionSyntax?)Visit(node.Expression) ?? throw new ArgumentNullException("expression")); + [Experimental(global::Microsoft.CodeAnalysis.RoslynExperiments.PreviewLanguageFeatureApi, UrlFormat = @"https://github.com/dotnet/roslyn/issues/82210")] public override SyntaxNode? VisitWithElement(WithElementSyntax node) => node.Update(VisitToken(node.WithKeyword), (ArgumentListSyntax?)Visit(node.ArgumentList) ?? throw new ArgumentNullException("argumentList")); @@ -3446,6 +3449,7 @@ public static SpreadElementSyntax SpreadElement(ExpressionSyntax expression) => SyntaxFactory.SpreadElement(SyntaxFactory.Token(SyntaxKind.DotDotToken), expression); /// Creates a new WithElementSyntax instance. + [Experimental(global::Microsoft.CodeAnalysis.RoslynExperiments.PreviewLanguageFeatureApi, UrlFormat = @"https://github.com/dotnet/roslyn/issues/82210")] public static WithElementSyntax WithElement(SyntaxToken withKeyword, ArgumentListSyntax argumentList) { if (withKeyword.Kind() != SyntaxKind.WithKeyword) throw new ArgumentException(nameof(withKeyword)); @@ -3454,6 +3458,7 @@ public static WithElementSyntax WithElement(SyntaxToken withKeyword, ArgumentLis } /// Creates a new WithElementSyntax instance. + [Experimental(global::Microsoft.CodeAnalysis.RoslynExperiments.PreviewLanguageFeatureApi, UrlFormat = @"https://github.com/dotnet/roslyn/issues/82210")] public static WithElementSyntax WithElement(ArgumentListSyntax? argumentList = default) => SyntaxFactory.WithElement(SyntaxFactory.Token(SyntaxKind.WithKeyword), argumentList ?? SyntaxFactory.ArgumentList()); diff --git a/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Syntax.Generated.cs b/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Syntax.Generated.cs index 15426d48b5bd3..d63f67c258ade 100644 --- a/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Syntax.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Syntax.Generated.cs @@ -4279,6 +4279,7 @@ public SpreadElementSyntax Update(SyntaxToken operatorToken, ExpressionSyntax ex /// /// /// +[Experimental(global::Microsoft.CodeAnalysis.RoslynExperiments.PreviewLanguageFeatureApi, UrlFormat = @"https://github.com/dotnet/roslyn/issues/82210")] public sealed partial class WithElementSyntax : CollectionElementSyntax { private ArgumentListSyntax? argumentList; diff --git a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt index 253ca47f335d1..809b435bda6fd 100644 --- a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt @@ -1,19 +1,19 @@ Microsoft.CodeAnalysis.CSharp.ForEachStatementInfo.DisposeAwaitableInfo.get -> Microsoft.CodeAnalysis.CSharp.AwaitExpressionInfo Microsoft.CodeAnalysis.CSharp.ForEachStatementInfo.MoveNextAwaitableInfo.get -> Microsoft.CodeAnalysis.CSharp.AwaitExpressionInfo -Microsoft.CodeAnalysis.CSharp.SyntaxKind.WithElement = 9081 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind +[RSEXPERIMENTAL006]Microsoft.CodeAnalysis.CSharp.SyntaxKind.WithElement = 9081 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetAwaitExpressionInfo(this Microsoft.CodeAnalysis.SemanticModel? semanticModel, Microsoft.CodeAnalysis.CSharp.Syntax.LocalDeclarationStatementSyntax! awaitUsingDeclaration) -> Microsoft.CodeAnalysis.CSharp.AwaitExpressionInfo static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetAwaitExpressionInfo(this Microsoft.CodeAnalysis.SemanticModel? semanticModel, Microsoft.CodeAnalysis.CSharp.Syntax.UsingStatementSyntax! awaitUsingStatement) -> Microsoft.CodeAnalysis.CSharp.AwaitExpressionInfo -Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax -Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax.AddArgumentListArguments(params Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentSyntax![]! items) -> Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax! -Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax.ArgumentList.get -> Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentListSyntax! -Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken withKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentListSyntax! argumentList) -> Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax! -Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax.WithArgumentList(Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentListSyntax! argumentList) -> Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax! -Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax.WithKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken -Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax.WithWithKeyword(Microsoft.CodeAnalysis.SyntaxToken withKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax! -override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitWithElement(Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax! node) -> Microsoft.CodeAnalysis.SyntaxNode? -override Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor! visitor) -> void -override Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor! visitor) -> TResult? -static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.WithElement(Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentListSyntax? argumentList = null) -> Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax! -static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.WithElement(Microsoft.CodeAnalysis.SyntaxToken withKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentListSyntax! argumentList) -> Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax! -virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitWithElement(Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax! node) -> void -virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitWithElement(Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax! node) -> TResult? +[RSEXPERIMENTAL006]Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax +[RSEXPERIMENTAL006]Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax.AddArgumentListArguments(params Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentSyntax![]! items) -> Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax! +[RSEXPERIMENTAL006]Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax.ArgumentList.get -> Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentListSyntax! +[RSEXPERIMENTAL006]Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken withKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentListSyntax! argumentList) -> Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax! +[RSEXPERIMENTAL006]Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax.WithArgumentList(Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentListSyntax! argumentList) -> Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax! +[RSEXPERIMENTAL006]Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax.WithKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken +[RSEXPERIMENTAL006]Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax.WithWithKeyword(Microsoft.CodeAnalysis.SyntaxToken withKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax! +[RSEXPERIMENTAL006]override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitWithElement(Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax! node) -> Microsoft.CodeAnalysis.SyntaxNode? +[RSEXPERIMENTAL006]override Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor! visitor) -> void +[RSEXPERIMENTAL006]override Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor! visitor) -> TResult? +[RSEXPERIMENTAL006]static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.WithElement(Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentListSyntax? argumentList = null) -> Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax! +[RSEXPERIMENTAL006]static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.WithElement(Microsoft.CodeAnalysis.SyntaxToken withKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentListSyntax! argumentList) -> Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax! +[RSEXPERIMENTAL006]virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitWithElement(Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax! node) -> void +[RSEXPERIMENTAL006]virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitWithElement(Microsoft.CodeAnalysis.CSharp.Syntax.WithElementSyntax! node) -> TResult? diff --git a/src/Compilers/CSharp/Portable/Syntax/Syntax.xml b/src/Compilers/CSharp/Portable/Syntax/Syntax.xml index aada4dc192051..1049f058945c1 100644 --- a/src/Compilers/CSharp/Portable/Syntax/Syntax.xml +++ b/src/Compilers/CSharp/Portable/Syntax/Syntax.xml @@ -1839,7 +1839,7 @@ - + diff --git a/src/Compilers/CSharp/Portable/Syntax/Syntax.xsd b/src/Compilers/CSharp/Portable/Syntax/Syntax.xsd index ef8ce1b6cf169..bbfac4fae23a1 100644 --- a/src/Compilers/CSharp/Portable/Syntax/Syntax.xsd +++ b/src/Compilers/CSharp/Portable/Syntax/Syntax.xsd @@ -8,6 +8,7 @@ + @@ -34,6 +35,7 @@ + @@ -60,6 +62,7 @@ + @@ -83,6 +86,7 @@ + @@ -95,6 +99,7 @@ + @@ -131,6 +136,7 @@ + @@ -170,6 +176,7 @@ + @@ -182,6 +189,7 @@ + @@ -206,6 +214,7 @@ + @@ -220,6 +229,7 @@ + diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs index ebfab2785ee04..191392dc1278e 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs @@ -2,9 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Diagnostics.CodeAnalysis; + namespace Microsoft.CodeAnalysis.CSharp { #pragma warning disable CA1200 // Avoid using cref tags with a prefix - The prefix is required since this file is referenced in projects that can't access syntax nodes + // When adding new experimental kinds, you will need to manually specify RSEXPERIMENTAL006, as not all projects that reference this file have RoslynExperiments available. // DO NOT CHANGE NUMBERS ASSIGNED TO EXISTING KINDS OR YOU WILL BREAK BINARY COMPATIBILITY public enum SyntaxKind : ushort { @@ -933,6 +936,7 @@ public enum SyntaxKind : ushort IgnoredDirectiveTrivia = 9080, + [Experimental("RSEXPERIMENTAL006", UrlFormat = "https://github.com/dotnet/roslyn/issues/82210")] WithElement = 9081, } } diff --git a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs index 02c3491acd282..c9f18f5e87683 100644 --- a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs +++ b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs @@ -1722,6 +1722,8 @@ public void LanguageVersionAdded_Canary() // - [ ] replace all references to C# "Next" (such as `TestOptions.RegularNext` or `LanguageVersionFacts.CSharpNext`) with the new version and fix failing tests // - [ ] update _MaxAvailableLangVersion cap (a relevant test should break when new version is introduced) // - [ ] update the "UpgradeProject" codefixer + // - [ ] Remove the `ExperimentalUrl` section from any entries for language features being shipped in Syntax.xml and OperationInterfaces.xml, and rerun the generator + // - [ ] Search the codebase for references tied to issues linked from Syntax.xml and OperationInterfaces.xml, and remove suppressions or attributes added for those issues. // - [ ] test VS insertion and deal with breaking changes. (note: the runtime repo uses "preview" so breaks are resolved sooner) // // Other repos also need updates: diff --git a/src/Compilers/Core/Portable/Generated/FlowAnalysis.Generated.cs b/src/Compilers/Core/Portable/Generated/FlowAnalysis.Generated.cs index 58caf7901843c..7ec409e282783 100644 --- a/src/Compilers/Core/Portable/Generated/FlowAnalysis.Generated.cs +++ b/src/Compilers/Core/Portable/Generated/FlowAnalysis.Generated.cs @@ -5,6 +5,7 @@ #nullable enable using System; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.PooledObjects; diff --git a/src/Compilers/Core/Portable/Generated/OperationKind.Generated.cs b/src/Compilers/Core/Portable/Generated/OperationKind.Generated.cs index c920786f9d2dd..08e8f14e0119c 100644 --- a/src/Compilers/Core/Portable/Generated/OperationKind.Generated.cs +++ b/src/Compilers/Core/Portable/Generated/OperationKind.Generated.cs @@ -5,6 +5,7 @@ #nullable enable using System; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis.FlowAnalysis; using Microsoft.CodeAnalysis.Operations; namespace Microsoft.CodeAnalysis @@ -286,6 +287,7 @@ public enum OperationKind /// Indicates an . Spread = 0x80, /// Indicates an . + [Experimental(global::Microsoft.CodeAnalysis.RoslynExperiments.PreviewLanguageFeatureApi, UrlFormat = @"https://github.com/dotnet/roslyn/issues/82210")] CollectionExpressionElementsPlaceholder = 0x81, } } diff --git a/src/Compilers/Core/Portable/Generated/Operations.Generated.cs b/src/Compilers/Core/Portable/Generated/Operations.Generated.cs index b5cd48551be0e..6f8cc2f38ae71 100644 --- a/src/Compilers/Core/Portable/Generated/Operations.Generated.cs +++ b/src/Compilers/Core/Portable/Generated/Operations.Generated.cs @@ -3968,6 +3968,7 @@ public interface ICollectionExpressionOperation : IOperation /// . The actual elements passed /// to the creation method are contained in . /// + [Experimental(global::Microsoft.CodeAnalysis.RoslynExperiments.PreviewLanguageFeatureApi, UrlFormat = @"https://github.com/dotnet/roslyn/issues/82210")] ImmutableArray ConstructArguments { get; } /// /// Collection expression elements. @@ -4028,6 +4029,7 @@ public interface ISpreadOperation : IOperation /// This interface is reserved for implementation by its associated APIs. We reserve the right to /// change it in the future. /// + [Experimental(global::Microsoft.CodeAnalysis.RoslynExperiments.PreviewLanguageFeatureApi, UrlFormat = @"https://github.com/dotnet/roslyn/issues/82210")] public interface ICollectionExpressionElementsPlaceholderOperation : IOperation { } @@ -11620,6 +11622,7 @@ internal virtual void VisitNoneOperation(IOperation operation) { /* no-op */ } public virtual void VisitInlineArrayAccess(IInlineArrayAccessOperation operation) => DefaultVisit(operation); public virtual void VisitCollectionExpression(ICollectionExpressionOperation operation) => DefaultVisit(operation); public virtual void VisitSpread(ISpreadOperation operation) => DefaultVisit(operation); + [Experimental(global::Microsoft.CodeAnalysis.RoslynExperiments.PreviewLanguageFeatureApi, UrlFormat = @"https://github.com/dotnet/roslyn/issues/82210")] public virtual void VisitCollectionExpressionElementsPlaceholder(ICollectionExpressionElementsPlaceholderOperation operation) => DefaultVisit(operation); } public abstract partial class OperationVisitor @@ -11760,6 +11763,7 @@ public abstract partial class OperationVisitor public virtual TResult? VisitInlineArrayAccess(IInlineArrayAccessOperation operation, TArgument argument) => DefaultVisit(operation, argument); public virtual TResult? VisitCollectionExpression(ICollectionExpressionOperation operation, TArgument argument) => DefaultVisit(operation, argument); public virtual TResult? VisitSpread(ISpreadOperation operation, TArgument argument) => DefaultVisit(operation, argument); + [Experimental(global::Microsoft.CodeAnalysis.RoslynExperiments.PreviewLanguageFeatureApi, UrlFormat = @"https://github.com/dotnet/roslyn/issues/82210")] public virtual TResult? VisitCollectionExpressionElementsPlaceholder(ICollectionExpressionElementsPlaceholderOperation operation, TArgument argument) => DefaultVisit(operation, argument); } #endregion diff --git a/src/Compilers/Core/Portable/InternalUtilities/RoslynExperiments.cs b/src/Compilers/Core/Portable/InternalUtilities/RoslynExperiments.cs index 319732e193bbd..44db6c6ad693d 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/RoslynExperiments.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/RoslynExperiments.cs @@ -15,6 +15,9 @@ internal static class RoslynExperiments internal const string GeneratorHostOutputs = "RSEXPERIMENTAL004"; internal const string GeneratorHostOutputs_Url = "https://github.com/dotnet/roslyn/issues/74753"; + // The UrlFormat property is customized per-api to point at the test plan for the feature, not a single general issue. + internal const string PreviewLanguageFeatureApi = "RSEXPERIMENTAL006"; + // Previously taken: RSEXPERIMENTAL003 - https://github.com/dotnet/roslyn/issues/73002 (SyntaxTokenParser) // Previously taken: RSEXPERIMENTAL005 - https://github.com/dotnet/roslyn/issues/77697 } diff --git a/src/Compilers/Core/Portable/Operations/OperationInterfaces.xml b/src/Compilers/Core/Portable/Operations/OperationInterfaces.xml index 325f35130bd45..e6439f9758e18 100644 --- a/src/Compilers/Core/Portable/Operations/OperationInterfaces.xml +++ b/src/Compilers/Core/Portable/Operations/OperationInterfaces.xml @@ -3697,7 +3697,7 @@ - + Arguments passed to to , if present. Arguments are in evaluation order. This can @@ -3761,7 +3761,7 @@ - + Represents the elements of a collection expression as they are passed to some construction method diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index 01c5dbd2a5f3f..8fa6e512a7b65 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -2,8 +2,8 @@ Microsoft.CodeAnalysis.Emit.EmitDifferenceOptions.MethodImplEntriesSupported.get Microsoft.CodeAnalysis.Emit.EmitDifferenceOptions.MethodImplEntriesSupported.init -> void Microsoft.CodeAnalysis.IMethodSymbol.ReduceExtensionMember(Microsoft.CodeAnalysis.ITypeSymbol! receiverType) -> Microsoft.CodeAnalysis.IMethodSymbol? Microsoft.CodeAnalysis.IPropertySymbol.ReduceExtensionMember(Microsoft.CodeAnalysis.ITypeSymbol! receiverType) -> Microsoft.CodeAnalysis.IPropertySymbol? -Microsoft.CodeAnalysis.OperationKind.CollectionExpressionElementsPlaceholder = 129 -> Microsoft.CodeAnalysis.OperationKind -Microsoft.CodeAnalysis.Operations.ICollectionExpressionElementsPlaceholderOperation +[RSEXPERIMENTAL006]Microsoft.CodeAnalysis.OperationKind.CollectionExpressionElementsPlaceholder = 129 -> Microsoft.CodeAnalysis.OperationKind Microsoft.CodeAnalysis.Operations.ICollectionExpressionOperation.ConstructArguments.get -> System.Collections.Immutable.ImmutableArray -virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitCollectionExpressionElementsPlaceholder(Microsoft.CodeAnalysis.Operations.ICollectionExpressionElementsPlaceholderOperation! operation) -> void -virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitCollectionExpressionElementsPlaceholder(Microsoft.CodeAnalysis.Operations.ICollectionExpressionElementsPlaceholderOperation! operation, TArgument argument) -> TResult? +[RSEXPERIMENTAL006]Microsoft.CodeAnalysis.Operations.ICollectionExpressionElementsPlaceholderOperation +[RSEXPERIMENTAL006]virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitCollectionExpressionElementsPlaceholder(Microsoft.CodeAnalysis.Operations.ICollectionExpressionElementsPlaceholderOperation! operation) -> void +[RSEXPERIMENTAL006]virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitCollectionExpressionElementsPlaceholder(Microsoft.CodeAnalysis.Operations.ICollectionExpressionElementsPlaceholderOperation! operation, TArgument argument) -> TResult? diff --git a/src/RoslynAnalyzers/PublicApiAnalyzers/Core/Analyzers/DeclarePublicApiAnalyzer.Impl.cs b/src/RoslynAnalyzers/PublicApiAnalyzers/Core/Analyzers/DeclarePublicApiAnalyzer.Impl.cs index a491bae5f2327..8c8619c8ba380 100644 --- a/src/RoslynAnalyzers/PublicApiAnalyzers/Core/Analyzers/DeclarePublicApiAnalyzer.Impl.cs +++ b/src/RoslynAnalyzers/PublicApiAnalyzers/Core/Analyzers/DeclarePublicApiAnalyzer.Impl.cs @@ -603,6 +603,7 @@ private ApiName GetApiName(ISymbol symbol) { for (var current = symbol; current is not null; current = current.ContainingSymbol) { +start: foreach (var attribute in current.GetAttributes()) { if (attribute.AttributeClass is { Name: "ExperimentalAttribute", ContainingSymbol: INamespaceSymbol { Name: nameof(System.Diagnostics.CodeAnalysis), ContainingNamespace: { Name: nameof(System.Diagnostics), ContainingNamespace: { Name: nameof(System), ContainingNamespace.IsGlobalNamespace: true } } } }) @@ -611,9 +612,14 @@ private ApiName GetApiName(ISymbol symbol) return "???"; return diagnosticId; - } } + + if (current is IMethodSymbol { AssociatedSymbol: { } associatedSymbol }) + { + current = associatedSymbol; + goto start; + } } return null; diff --git a/src/RoslynAnalyzers/PublicApiAnalyzers/UnitTests/DeclarePublicAPIAnalyzerTestsBase.cs b/src/RoslynAnalyzers/PublicApiAnalyzers/UnitTests/DeclarePublicAPIAnalyzerTestsBase.cs index 3e1174ef8e70c..02833097f4d57 100644 --- a/src/RoslynAnalyzers/PublicApiAnalyzers/UnitTests/DeclarePublicAPIAnalyzerTestsBase.cs +++ b/src/RoslynAnalyzers/PublicApiAnalyzers/UnitTests/DeclarePublicAPIAnalyzerTestsBase.cs @@ -2783,6 +2783,112 @@ public Task TestExperimentalApiAsync() [ID1]C.C() -> void """); + [Fact] + [WorkItem(82131, "https://github.com/dotnet/roslyn/pull/82131")] + public Task TestExperimentalApi_PropertyAsync() + => VerifyNet80CSharpAdditionalFileFixAsync($$""" + using System.Diagnostics.CodeAnalysis; + + {{EnabledModifierCSharp}} class {|{{AddNewApiId}}:{|{{AddNewApiId}}:C|}|} + { + [Experimental("ID1")] + {{EnabledModifierCSharp}} int Property { {|{{AddNewApiId}}:get|}; {|{{AddNewApiId}}:set|}; } + } + """, @"", @"", """ + C + C.C() -> void + [ID1]C.Property.get -> int + [ID1]C.Property.set -> void + """); + + [Fact] + [WorkItem(82131, "https://github.com/dotnet/roslyn/pull/82131")] + public Task TestExperimentalApi_PropertyGetterOnlyAsync() + => VerifyNet80CSharpAdditionalFileFixAsync($$""" + using System.Diagnostics.CodeAnalysis; + + {{EnabledModifierCSharp}} class {|{{AddNewApiId}}:{|{{AddNewApiId}}:C|}|} + { + [Experimental("ID1")] + {{EnabledModifierCSharp}} int Property { {|{{AddNewApiId}}:get|}; } + } + """, @"", @"", """ + C + C.C() -> void + [ID1]C.Property.get -> int + """); + + [Fact] + [WorkItem(82131, "https://github.com/dotnet/roslyn/pull/82131")] + public Task TestExperimentalApi_PropertySetterOnlyAsync() + => VerifyNet80CSharpAdditionalFileFixAsync($$""" + using System.Diagnostics.CodeAnalysis; + + {{EnabledModifierCSharp}} class {|{{AddNewApiId}}:{|{{AddNewApiId}}:C|}|} + { + [Experimental("ID1")] + {{EnabledModifierCSharp}} int Property { {|{{AddNewApiId}}:set|} { } } + } + """, @"", @"", """ + C + C.C() -> void + [ID1]C.Property.set -> void + """); + + [Fact] + [WorkItem(82131, "https://github.com/dotnet/roslyn/pull/82131")] + public Task TestExperimentalApi_IndexerAsync() + => VerifyNet80CSharpAdditionalFileFixAsync($$""" + using System.Diagnostics.CodeAnalysis; + + {{EnabledModifierCSharp}} class {|{{AddNewApiId}}:{|{{AddNewApiId}}:C|}|} + { + [Experimental("ID1")] + {{EnabledModifierCSharp}} int this[int index] { {|{{AddNewApiId}}:get|} => 0; {|{{AddNewApiId}}:set|} { } } + } + """, @"", @"", """ + C + C.C() -> void + [ID1]C.this[int index].get -> int + [ID1]C.this[int index].set -> void + """); + + [Fact] + [WorkItem(82131, "https://github.com/dotnet/roslyn/pull/82131")] + public Task TestExperimentalApi_EventAsync() + => VerifyNet80CSharpAdditionalFileFixAsync($$""" + using System; + using System.Diagnostics.CodeAnalysis; + + {{EnabledModifierCSharp}} class {|{{AddNewApiId}}:{|{{AddNewApiId}}:C|}|} + { + [Experimental("ID1")] + {{EnabledModifierCSharp}} event EventHandler {|{{AddNewApiId}}:MyEvent|}; + } + """, @"", @"", """ + C + C.C() -> void + [ID1]C.MyEvent -> System.EventHandler + """); + + [Fact] + [WorkItem(82131, "https://github.com/dotnet/roslyn/pull/82131")] + public Task TestExperimentalApi_PropertyInExperimentalClassAsync() + => VerifyNet80CSharpAdditionalFileFixAsync($$""" + using System.Diagnostics.CodeAnalysis; + + [Experimental("ID1")] + {{EnabledModifierCSharp}} class {|{{AddNewApiId}}:{|{{AddNewApiId}}:C|}|} + { + {{EnabledModifierCSharp}} int Property { {|{{AddNewApiId}}:get|}; {|{{AddNewApiId}}:set|}; } + } + """, @"", @"", """ + [ID1]C + [ID1]C.C() -> void + [ID1]C.Property.get -> int + [ID1]C.Property.set -> void + """); + [Theory] [InlineData("")] [InlineData("null")] diff --git a/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/Model/Field.cs b/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/Model/Field.cs index dcdb02097f0cb..4d5ac9e67ee83 100644 --- a/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/Model/Field.cs +++ b/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/Model/Field.cs @@ -44,6 +44,9 @@ public class Field : TreeTypeChild [XmlAttribute] public string Type; + [XmlAttribute] + public string ExperimentalUrl; + [XmlAttribute] public string Optional; diff --git a/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/Model/TreeType.cs b/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/Model/TreeType.cs index fce0006a516cc..a0910b3b0ab62 100644 --- a/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/Model/TreeType.cs +++ b/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/Model/TreeType.cs @@ -17,6 +17,9 @@ public class TreeType [XmlAttribute] public string Base; + [XmlAttribute] + public string ExperimentalUrl; + [XmlAttribute] public string SkipConvenienceFactories; diff --git a/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/SourceWriter.cs b/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/SourceWriter.cs index 495b00d5d6af7..4555515221a18 100644 --- a/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/SourceWriter.cs +++ b/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/SourceWriter.cs @@ -26,6 +26,17 @@ private SourceWriter(TextWriter writer, Tree tree, CancellationToken cancellatio public static void WriteSyntax(TextWriter writer, Tree tree, CancellationToken cancellationToken = default) => new SourceWriter(writer, tree, cancellationToken).WriteSyntax(); + private static string QuoteString(string value) + => "@\"" + value.Replace("\"", "\"\"") + "\""; + + private void WriteExperimentalIfNeeded(string experimentalUrl) + { + if (!string.IsNullOrEmpty(experimentalUrl)) + { + WriteLine($"[Experimental(global::Microsoft.CodeAnalysis.RoslynExperiments.PreviewLanguageFeatureApi, UrlFormat = {QuoteString(experimentalUrl)})]"); + } + } + private void WriteFileHeader() { WriteLine("// "); @@ -702,6 +713,7 @@ private void WriteRedType(TreeType node) if (node is AbstractNode) { var nd = (AbstractNode)node; + WriteExperimentalIfNeeded(node.ExperimentalUrl); WriteLine($"public abstract partial class {node.Name} : {node.Base}"); OpenBlock(); WriteLine($"internal {node.Name}(InternalSyntax.CSharpSyntaxNode green, SyntaxNode? parent, int position)"); @@ -720,6 +732,7 @@ private void WriteRedType(TreeType node) var fieldType = GetRedFieldType(field); WriteLine(); WriteComment(field.PropertyComment, ""); + WriteExperimentalIfNeeded(field.ExperimentalUrl); WriteLine($"{"public"} abstract {(IsNew(field) ? "new " : "")}{fieldType} {field.Name} {{ get; }}"); WriteLine($"public {node.Name} With{field.Name}({fieldType} {CamelCase(field.Name)}) => With{field.Name}Core({CamelCase(field.Name)});"); WriteLine($"internal abstract {node.Name} With{field.Name}Core({fieldType} {CamelCase(field.Name)});"); @@ -756,6 +769,7 @@ private void WriteRedType(TreeType node) { WriteLine(); WriteComment(field.PropertyComment, ""); + WriteExperimentalIfNeeded(field.ExperimentalUrl); WriteLine($"{"public"} abstract {(IsNew(field) ? "new " : "")}{field.Type} {field.Name} {{ get; }}"); } @@ -818,6 +832,7 @@ private void WriteRedType(TreeType node) WriteComment($""); WriteComment($""); + WriteExperimentalIfNeeded(node.ExperimentalUrl); WriteLine($"public sealed partial class {node.Name} : {node.Base}"); OpenBlock(); @@ -869,6 +884,7 @@ private void WriteRedType(TreeType node) if (field.Type == "SyntaxToken") { WriteComment(field.PropertyComment, ""); + WriteExperimentalIfNeeded(field.ExperimentalUrl); Write($"public {OverrideOrNewModifier(field)}{GetRedPropertyType(field)} {field.Name}"); if (IsOptional(field)) { @@ -889,6 +905,7 @@ private void WriteRedType(TreeType node) else if (field.Type == "SyntaxList") { WriteComment(field.PropertyComment, ""); + WriteExperimentalIfNeeded(field.ExperimentalUrl); WriteLine($"public {OverrideOrNewModifier(field)}SyntaxTokenList {field.Name}"); OpenBlock(); WriteLine("get"); @@ -901,6 +918,7 @@ private void WriteRedType(TreeType node) else { WriteComment(field.PropertyComment, ""); + WriteExperimentalIfNeeded(field.ExperimentalUrl); Write($"public {OverrideOrNewModifier(field)}{GetRedPropertyType(field)} {field.Name}"); if (IsNodeList(field.Type)) @@ -942,6 +960,7 @@ private void WriteRedType(TreeType node) foreach (var field in valueFields) { WriteComment(field.PropertyComment, ""); + WriteExperimentalIfNeeded(field.ExperimentalUrl); WriteLine($"{"public"} {OverrideOrNewModifier(field)}{field.Type} {field.Name} => ((InternalSyntax.{node.Name})this.Green).{field.Name};"); WriteLine(); } @@ -1088,6 +1107,7 @@ private void WriteRedVisitor(bool genericResult) WriteLine(); nWritten++; WriteComment($"Called when the visitor visits a {node.Name} node."); + WriteExperimentalIfNeeded(node.ExperimentalUrl); WriteLine($"public virtual {(genericResult ? "TResult?" : "void")} Visit{StripPost(node.Name, "Syntax")}({node.Name} node) => this.DefaultVisit(node);"); } CloseBlock(); @@ -1161,6 +1181,7 @@ private void WriteRedWithMethods(Node node) } } + WriteExperimentalIfNeeded(field.ExperimentalUrl); Write( $"public{(isNew ? " new " : " ")}{node.Name} With{StripPost(field.Name, "Opt")}({type} {CamelCase(field.Name)})" + " => Update("); @@ -1262,6 +1283,7 @@ private void WriteRedListHelperMethods(Node node, Field field) } } + WriteExperimentalIfNeeded(field.ExperimentalUrl); WriteLine($"public{(isNew ? " new " : " ")}{node.Name} Add{field.Name}(params {argType}[] items) => With{StripPost(field.Name, "Opt")}(this.{field.Name}.AddRange(items));"); } @@ -1281,6 +1303,7 @@ private void WriteRedNestedListHelperMethods(Node node, Field field, Node refere } // AddBaseListTypes + WriteExperimentalIfNeeded(field.ExperimentalUrl); Write($"public{(isNew ? " new " : " ")}{node.Name} Add{StripPost(field.Name, "Opt")}{referencedNodeField.Name}(params {argType}[] items)"); if (IsOptional(field)) @@ -1313,6 +1336,7 @@ private void WriteRedRewriter() if (nWritten > 0) WriteLine(); nWritten++; + WriteExperimentalIfNeeded(node.ExperimentalUrl); WriteLine($"public override SyntaxNode? Visit{StripPost(node.Name, "Syntax")}({node.Name} node)"); if (node.Fields.Count == 0) @@ -1439,6 +1463,7 @@ private void WriteRedFactory(Node nd) WriteComment($"Creates a new {nd.Name} instance."); + WriteExperimentalIfNeeded(nd.ExperimentalUrl); Write($"public static {nd.Name} {StripPost(nd.Name, "Syntax")}("); WriteRedFactoryParameters(nd); @@ -1624,6 +1649,7 @@ private void WriteRedFactoryWithNoAutoCreatableTokens(Node nd) this.WriteLine(); WriteComment($"Creates a new {nd.Name} instance."); + WriteExperimentalIfNeeded(nd.ExperimentalUrl); Write($"public static {nd.Name} {StripPost(nd.Name, "Syntax")}("); Write(CommaJoin( nd.Kinds.Count > 1 ? "SyntaxKind kind" : "", @@ -1713,6 +1739,7 @@ private void WriteRedMinimalFactory(Node nd, bool withStringNames = false) } WriteComment($"Creates a new {nd.Name} instance."); + WriteExperimentalIfNeeded(nd.ExperimentalUrl); Write($"public static {nd.Name} {StripPost(nd.Name, "Syntax")}("); Write(CommaJoin( nd.Kinds.Count > 1 ? "SyntaxKind kind" : "", diff --git a/src/Tools/Source/CompilerGeneratorTools/Source/IOperationGenerator/IOperationClassWriter.Verifier.cs b/src/Tools/Source/CompilerGeneratorTools/Source/IOperationGenerator/IOperationClassWriter.Verifier.cs index 45c1562960f9d..56d9cf9489b09 100644 --- a/src/Tools/Source/CompilerGeneratorTools/Source/IOperationGenerator/IOperationClassWriter.Verifier.cs +++ b/src/Tools/Source/CompilerGeneratorTools/Source/IOperationGenerator/IOperationClassWriter.Verifier.cs @@ -33,6 +33,12 @@ private bool ModelHasErrors(Tree tree) error = true; } + if (abstractNode.IsInternal && !string.IsNullOrEmpty(abstractNode.ExperimentalUrl)) + { + Console.WriteLine($"{abstractNode.Name} is marked as internal and experimental. Internal nodes cannot be experimental."); + error = true; + } + if (!abstractNode.IsInternal && abstractNode.Obsolete is null) { if (abstractNode.Comments?.Elements?[0].Name != "summary") @@ -43,6 +49,12 @@ private bool ModelHasErrors(Tree tree) foreach (var prop in abstractNode.Properties) { + if (prop.IsInternal && !string.IsNullOrEmpty(prop.ExperimentalUrl)) + { + Console.WriteLine($"{abstractNode.Name}.{prop.Name} is marked as internal and experimental. Internal properties cannot be experimental."); + error = true; + } + if (prop.Comments?.Elements?[0].Name != "summary" && !prop.IsInternal && !prop.IsOverride) { Console.WriteLine($"{abstractNode.Name}.{prop.Name} does not have correctly formatted comments, please ensure that there is a block for the property."); diff --git a/src/Tools/Source/CompilerGeneratorTools/Source/IOperationGenerator/IOperationClassWriter.cs b/src/Tools/Source/CompilerGeneratorTools/Source/IOperationGenerator/IOperationClassWriter.cs index 5f516ad7ed94c..22bfb75f08b01 100644 --- a/src/Tools/Source/CompilerGeneratorTools/Source/IOperationGenerator/IOperationClassWriter.cs +++ b/src/Tools/Source/CompilerGeneratorTools/Source/IOperationGenerator/IOperationClassWriter.cs @@ -116,6 +116,7 @@ private bool WriteFiles() } WriteUsing("System.Collections.Immutable"); + WriteUsing("System.Diagnostics.CodeAnalysis"); if (@namespace != "Operations") { @@ -123,7 +124,6 @@ private bool WriteFiles() } else { - WriteUsing("System.Diagnostics.CodeAnalysis"); WriteUsing("Microsoft.CodeAnalysis.FlowAnalysis"); } @@ -163,6 +163,7 @@ private bool WriteFiles() writeHeader(); WriteUsing("System"); WriteUsing("System.ComponentModel"); + WriteUsing("System.Diagnostics.CodeAnalysis"); WriteUsing("Microsoft.CodeAnalysis.FlowAnalysis"); WriteUsing("Microsoft.CodeAnalysis.Operations"); @@ -205,6 +206,7 @@ private void WriteInterface(AbstractNode node) { WriteComments(node.Comments, getNodeKinds(node), writeReservedRemark: true); + WriteExperimentalAttributeIfNeeded(node); WriteObsoleteIfNecessary(node.Obsolete); WriteLine($"{(node.IsInternal ? "internal" : "public")} interface {node.Name} : {node.Base}"); Brace(); @@ -295,6 +297,7 @@ private void WriteInterfaceProperty(Property prop) if (prop.IsInternal || prop.IsOverride) return; WriteComments(prop.Comments, operationKinds: Enumerable.Empty(), writeReservedRemark: false); + WriteExperimentalAttributeIfNeeded(prop); var modifiers = prop.IsNew ? "new " : ""; WriteLine($"{modifiers}{prop.Type} {prop.Name} {{ get; }}"); } @@ -350,7 +353,8 @@ private void WriteOperationKind() entry.ExtraDescription, entry.EditorBrowsable ?? true, node.Obsolete?.Message, - node.Obsolete?.ErrorText); + node.Obsolete?.ErrorText, + experimentalUrl: node.ExperimentalUrl); } } else @@ -362,14 +366,15 @@ private void WriteOperationKind() currentEntry.OperationKind?.ExtraDescription, editorBrowsable: true, currentEntry.Obsolete?.Message, - currentEntry.Obsolete?.ErrorText); + currentEntry.Obsolete?.ErrorText, + experimentalUrl: currentEntry.ExperimentalUrl); Debug.Assert(elementsToKindEnumerator.MoveNext() || i == numKinds); } } Unbrace(); - void writeEnumElement(string kind, int value, string operationName, string? extraText, bool editorBrowsable, string? obsoleteMessage, string? obsoleteError) + void writeEnumElement(string kind, int value, string operationName, string? extraText, bool editorBrowsable, string? obsoleteMessage, string? obsoleteError, string? experimentalUrl) { WriteLine($"/// Indicates an .{(extraText is object ? $" {extraText}" : "")}"); @@ -378,6 +383,11 @@ void writeEnumElement(string kind, int value, string operationName, string? extr WriteLine("[EditorBrowsable(EditorBrowsableState.Never)]"); } + if (!string.IsNullOrEmpty(experimentalUrl)) + { + WriteExperimentalAttribute(experimentalUrl); + } + if (obsoleteMessage is object) { WriteLine($"[Obsolete({obsoleteMessage}, error: {obsoleteError})]"); @@ -1029,6 +1039,7 @@ public virtual void DefaultVisit(IOperation operation) { /* no-op */ } if (type.SkipInVisitor) continue; + WriteExperimentalAttributeIfNeeded(type); WriteObsoleteIfNecessary(type.Obsolete); var accessibility = type.IsInternal ? "internal" : "public"; var baseName = GetSubName(type.Name); @@ -1049,6 +1060,7 @@ public virtual void DefaultVisit(IOperation operation) { /* no-op */ } if (type.SkipInVisitor) continue; + WriteExperimentalAttributeIfNeeded(type); WriteObsoleteIfNecessary(type.Obsolete); var accessibility = type.IsInternal ? "internal" : "public"; WriteLine($"{accessibility} virtual TResult? {GetVisitorName(type)}({type.Name} operation, TArgument argument) => DefaultVisit(operation, argument);"); @@ -1066,6 +1078,27 @@ private void WriteObsoleteIfNecessary(ObsoleteTag? tag) } } + private void WriteExperimentalAttribute(string experimentalUrl) + { + WriteLine($"[Experimental(global::Microsoft.CodeAnalysis.RoslynExperiments.PreviewLanguageFeatureApi, UrlFormat = @\"{experimentalUrl.Replace("\"", "\"\"")}\")]"); + } + + private void WriteExperimentalAttributeIfNeeded(TreeType node) + { + if (!string.IsNullOrEmpty(node.ExperimentalUrl)) + { + WriteExperimentalAttribute(node.ExperimentalUrl); + } + } + + private void WriteExperimentalAttributeIfNeeded(Property prop) + { + if (!prop.IsOverride && !string.IsNullOrEmpty(prop.ExperimentalUrl)) + { + WriteExperimentalAttribute(prop.ExperimentalUrl); + } + } + private string GetVisitorName(Node type) { return type.VisitorName ?? $"Visit{GetSubName(type.Name)}"; diff --git a/src/Tools/Source/CompilerGeneratorTools/Source/IOperationGenerator/Model.cs b/src/Tools/Source/CompilerGeneratorTools/Source/IOperationGenerator/Model.cs index 0ea483c917c89..426f09dafa327 100644 --- a/src/Tools/Source/CompilerGeneratorTools/Source/IOperationGenerator/Model.cs +++ b/src/Tools/Source/CompilerGeneratorTools/Source/IOperationGenerator/Model.cs @@ -34,6 +34,9 @@ public class TreeType [XmlAttribute] public string Base; + [XmlAttribute] + public string? ExperimentalUrl; + [XmlAttribute] public string? Namespace; @@ -109,6 +112,9 @@ public class Property [XmlAttribute] public string Type; + [XmlAttribute] + public string? ExperimentalUrl; + [XmlAttribute(AttributeName = "New")] public string NewText; public bool IsNew => NewText == "true";