diff --git a/.editorconfig b/.editorconfig index 782cbdfeb..764ac9446 100644 --- a/.editorconfig +++ b/.editorconfig @@ -44,6 +44,9 @@ insert_final_newline = true # C# files [*.cs] +#### File Header Template #### +file_header_template = Licensed to the .NET Foundation under one or more agreements.\nThe .NET Foundation licenses this file to you under the MIT license.\nSee the LICENSE file in the project root for more information. + #### .NET Coding Conventions #### # this. and Me. preferences @@ -342,6 +345,9 @@ dotnet_diagnostic.CA2240.severity = warning dotnet_diagnostic.CA2241.severity = warning dotnet_diagnostic.CA2242.severity = warning +# Require file header OR A source file contains a header that does not match the required text +dotnet_diagnostic.IDE0073.severity = error + # StyleCop Code Analysis # Closing parenthesis should be spaced correctly: "foo()!" diff --git a/.gitignore b/.gitignore index 9c893bb37..45cc92d11 100644 --- a/.gitignore +++ b/.gitignore @@ -15,9 +15,6 @@ [Ll]og/ [Ll]ogs/ -# Generated file from .ttinclude -**/Generated/*.g.cs - # MSBuild Binary and Structured Log *.binlog diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 1f377cc37..ceecdda42 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,76 +1,140 @@ # Contributor Covenant Code of Conduct +## Preamble + +The .NET Foundation was created to foster an open, innovative and inclusive community +around open source .NET. To clarify expected behavior in our communities we have +adopted the [Contributor Covenant][homepage]. This code of conduct has been +adopted by [many other open source communities][adopters], and we feel +it expresses our values well. + +The latest revision is available at . + ## Our Pledge -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, -level of experience, education, socio-economic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. ## Our Standards -Examples of behavior that contributes to creating a positive environment -include: +Examples of behavior that contributes to a positive environment for our +community include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community -Examples of unacceptable behavior by participants include: +Examples of unacceptable behavior include: -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission +* Publishing others' private information, such as a physical or email + address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a - professional setting + professional setting -## Our Responsibilities +## Enforcement Responsibilities -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. ## Scope -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at conduct@dotnetfoundation.org. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. +reported to the community leaders responsible for enforcement at +[conduct@dotnetfoundation.org](mailto:conduct@dotnetfoundation.org). +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. ## Attribution -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. -[homepage]: https://www.contributor-covenant.org +For answers to common questions about this code of conduct, see the FAQ at +. Translations are available +at . -For answers to common questions about this code of conduct, see - +[homepage]: https://contributor-covenant.org +[adopters]: https://contributor-covenant.org/adopters +[Mozilla CoC]: https://github.com/mozilla/diversity diff --git a/CommunityToolkit.Common/Attributes/NotNullIfNotNullAttribute.cs b/CommunityToolkit.Common/Attributes/NotNullIfNotNullAttribute.cs deleted file mode 100644 index a70d1ce2e..000000000 --- a/CommunityToolkit.Common/Attributes/NotNullIfNotNullAttribute.cs +++ /dev/null @@ -1,28 +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. - -#if !NETSTANDARD2_1_OR_GREATER - -namespace System.Diagnostics.CodeAnalysis; - -/// -/// Specifies that the output will be non-null if the named parameter is non-null. -/// -/// Internal copy from the BCL attribute. -[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] -internal sealed class NotNullIfNotNullAttribute : Attribute -{ - /// - /// Initializes a new instance of the class. - /// - /// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null. - public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName; - - /// - /// Gets the associated parameter name. - /// - public string ParameterName { get; } -} - -#endif \ No newline at end of file diff --git a/CommunityToolkit.Common/Attributes/NotNullWhenAttribute.cs b/CommunityToolkit.Common/Attributes/NotNullWhenAttribute.cs deleted file mode 100644 index 39a6b6393..000000000 --- a/CommunityToolkit.Common/Attributes/NotNullWhenAttribute.cs +++ /dev/null @@ -1,28 +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. - -#if !NETSTANDARD2_1_OR_GREATER - -namespace System.Diagnostics.CodeAnalysis; - -/// -/// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. -/// -/// Internal copy from the BCL attribute. -[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] -internal sealed class NotNullWhenAttribute : Attribute -{ - /// - /// Initializes a new instance of the class. - /// - /// The return value condition. If the method returns this value, the associated parameter will not be null. - public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; - - /// - /// Gets a value indicating whether the annotated variable is not . - /// - public bool ReturnValue { get; } -} - -#endif \ No newline at end of file diff --git a/CommunityToolkit.Common/Attributes/SkipLocalsInitAttribute.cs b/CommunityToolkit.Common/Attributes/SkipLocalsInitAttribute.cs deleted file mode 100644 index fcad37015..000000000 --- a/CommunityToolkit.Common/Attributes/SkipLocalsInitAttribute.cs +++ /dev/null @@ -1,27 +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. - -#if !NET6_0_OR_GREATER - -namespace System.Runtime.CompilerServices; - -/// -/// Used to indicate to the compiler that the .locals init flag should not be set in method headers. -/// -/// Internal copy of the .NET 5 attribute. -[AttributeUsage( - AttributeTargets.Module | - AttributeTargets.Class | - AttributeTargets.Struct | - AttributeTargets.Interface | - AttributeTargets.Constructor | - AttributeTargets.Method | - AttributeTargets.Property | - AttributeTargets.Event, - Inherited = false)] -internal sealed class SkipLocalsInitAttribute : Attribute -{ -} - -#endif \ No newline at end of file diff --git a/CommunityToolkit.Common/Properties/AssemblyInfo.cs b/CommunityToolkit.Common/Properties/AssemblyInfo.cs deleted file mode 100644 index 160f1d9da..000000000 --- a/CommunityToolkit.Common/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,8 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Runtime.CompilerServices; - -// See notes in CommunityToolkit.HighPerformance for more info -[module: SkipLocalsInit] \ No newline at end of file diff --git a/CommunityToolkit.Diagnostics/Attributes/CallerArgumentExpressionAttribute.cs b/CommunityToolkit.Diagnostics/Attributes/CallerArgumentExpressionAttribute.cs deleted file mode 100644 index ff9e5a4a2..000000000 --- a/CommunityToolkit.Diagnostics/Attributes/CallerArgumentExpressionAttribute.cs +++ /dev/null @@ -1,31 +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. - -#if !NET5_0_OR_GREATER - -namespace System.Runtime.CompilerServices; - -/// -/// An attribute that allows parameters to receive the expression of other parameters. -/// -/// Internal copy from the BCL attribute. -[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] -internal sealed class CallerArgumentExpressionAttribute : Attribute -{ - /// - /// Initializes a new instance of the class. - /// - /// The condition parameter value. - public CallerArgumentExpressionAttribute(string parameterName) - { - ParameterName = parameterName; - } - - /// - /// Gets the parameter name the expression is retrieved from. - /// - public string ParameterName { get; } -} - -#endif \ No newline at end of file diff --git a/CommunityToolkit.Diagnostics/Attributes/DoesNotReturnAttribute.cs b/CommunityToolkit.Diagnostics/Attributes/DoesNotReturnAttribute.cs deleted file mode 100644 index 06ced088b..000000000 --- a/CommunityToolkit.Diagnostics/Attributes/DoesNotReturnAttribute.cs +++ /dev/null @@ -1,18 +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. - -#if !NETSTANDARD2_1_OR_GREATER - -namespace System.Diagnostics.CodeAnalysis; - -/// -/// Applied to a method that will never return under any circumstance. -/// -/// Internal copy from the BCL attribute. -[AttributeUsage(AttributeTargets.Method, Inherited = false)] -internal sealed class DoesNotReturnAttribute : Attribute -{ -} - -#endif \ No newline at end of file diff --git a/CommunityToolkit.Diagnostics/Attributes/DoesNotReturnIfAttribute.cs b/CommunityToolkit.Diagnostics/Attributes/DoesNotReturnIfAttribute.cs deleted file mode 100644 index e0c1a411d..000000000 --- a/CommunityToolkit.Diagnostics/Attributes/DoesNotReturnIfAttribute.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#if !NETSTANDARD2_1_OR_GREATER - -namespace System.Diagnostics.CodeAnalysis; - -/// -/// Specifies that a given also indicates -/// whether the method will not return (eg. throw an exception). -/// -/// Internal copy from the BCL attribute. -[AttributeUsage(AttributeTargets.Parameter)] -internal sealed class DoesNotReturnIfAttribute : Attribute -{ - /// - /// Initializes a new instance of the class. - /// - /// - /// The condition parameter value. Code after the method will be considered unreachable - /// by diagnostics if the argument to the associated parameter matches this value. - /// - public DoesNotReturnIfAttribute(bool parameterValue) - { - ParameterValue = parameterValue; - } - - /// - /// Gets a value indicating whether the parameter value should be . - /// - public bool ParameterValue { get; } -} - -#endif \ No newline at end of file diff --git a/CommunityToolkit.Diagnostics/Attributes/NotNullAttribute.cs b/CommunityToolkit.Diagnostics/Attributes/NotNullAttribute.cs deleted file mode 100644 index af849ab34..000000000 --- a/CommunityToolkit.Diagnostics/Attributes/NotNullAttribute.cs +++ /dev/null @@ -1,19 +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. - -#if !NETSTANDARD2_1_OR_GREATER - -namespace System.Diagnostics.CodeAnalysis; - -/// -/// Specifies that an output will not be even if the corresponding type allows it. -/// Specifies that an input argument was not when the call returns. -/// -/// Internal copy from the BCL attribute. -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue)] -internal sealed class NotNullAttribute : Attribute -{ -} - -#endif \ No newline at end of file diff --git a/CommunityToolkit.Diagnostics/Attributes/SkipLocalsInitAttribute.cs b/CommunityToolkit.Diagnostics/Attributes/SkipLocalsInitAttribute.cs deleted file mode 100644 index a05d197d8..000000000 --- a/CommunityToolkit.Diagnostics/Attributes/SkipLocalsInitAttribute.cs +++ /dev/null @@ -1,27 +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. - -#if !NET6_0_OR_GREATER - -namespace System.Runtime.CompilerServices; - -/// -/// Used to indicate to the compiler that the .locals init flag should not be set in method headers. -/// -/// Internal copy from the BCL attribute. -[AttributeUsage( - AttributeTargets.Module | - AttributeTargets.Class | - AttributeTargets.Struct | - AttributeTargets.Interface | - AttributeTargets.Constructor | - AttributeTargets.Method | - AttributeTargets.Property | - AttributeTargets.Event, - Inherited = false)] -internal sealed class SkipLocalsInitAttribute : Attribute -{ -} - -#endif \ No newline at end of file diff --git a/CommunityToolkit.Diagnostics/Attributes/StackTraceHiddenAttribute.cs b/CommunityToolkit.Diagnostics/Attributes/StackTraceHiddenAttribute.cs deleted file mode 100644 index 68ef293d4..000000000 --- a/CommunityToolkit.Diagnostics/Attributes/StackTraceHiddenAttribute.cs +++ /dev/null @@ -1,23 +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. - -#if !NET6_0_OR_GREATER - -namespace System.Diagnostics; - -/// -/// Removes annotated methods from the stacktrace in case an exception occurrs while they're on the stack. -/// -/// -/// This is a port of the attribute from the BCL in .NET 6, and it's marked as conditional so that it will -/// be removed for package builds that are released on NuGet. This attribute is only used internally to -/// avoid having to clutter the codebase with many compiler directive switches to check for this API. -/// -[Conditional("DEBUG")] -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Struct, Inherited = false)] -internal sealed class StackTraceHiddenAttribute : Attribute -{ -} - -#endif \ No newline at end of file diff --git a/CommunityToolkit.Diagnostics/Properties/AssemblyInfo.cs b/CommunityToolkit.Diagnostics/Properties/AssemblyInfo.cs deleted file mode 100644 index 160f1d9da..000000000 --- a/CommunityToolkit.Diagnostics/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,8 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Runtime.CompilerServices; - -// See notes in CommunityToolkit.HighPerformance for more info -[module: SkipLocalsInit] \ No newline at end of file diff --git a/CommunityToolkit.HighPerformance/Attributes/NotNullAttribute.cs b/CommunityToolkit.HighPerformance/Attributes/NotNullAttribute.cs deleted file mode 100644 index af849ab34..000000000 --- a/CommunityToolkit.HighPerformance/Attributes/NotNullAttribute.cs +++ /dev/null @@ -1,19 +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. - -#if !NETSTANDARD2_1_OR_GREATER - -namespace System.Diagnostics.CodeAnalysis; - -/// -/// Specifies that an output will not be even if the corresponding type allows it. -/// Specifies that an input argument was not when the call returns. -/// -/// Internal copy from the BCL attribute. -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue)] -internal sealed class NotNullAttribute : Attribute -{ -} - -#endif \ No newline at end of file diff --git a/CommunityToolkit.HighPerformance/Attributes/NotNullWhenAttribute.cs b/CommunityToolkit.HighPerformance/Attributes/NotNullWhenAttribute.cs deleted file mode 100644 index 4d427eb6e..000000000 --- a/CommunityToolkit.HighPerformance/Attributes/NotNullWhenAttribute.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#if !NETSTANDARD2_1_OR_GREATER - -namespace System.Diagnostics.CodeAnalysis; - -/// -/// Specifies that when a method returns , the parameter -/// will not be null even if the corresponding type allows it. -/// -/// Internal copy of the .NET Standard 2.1 attribute. -[AttributeUsage(AttributeTargets.Parameter)] -internal sealed class NotNullWhenAttribute : Attribute -{ - /// - /// Initializes a new instance of the class. - /// - /// - /// The return value condition. If the method returns this value, - /// the associated parameter will not be . - /// - public NotNullWhenAttribute(bool returnValue) - { - ReturnValue = returnValue; - } - - /// - /// Gets a value indicating whether the return value should be . - /// - public bool ReturnValue { get; } -} - -#endif \ No newline at end of file diff --git a/CommunityToolkit.HighPerformance/Attributes/SkipLocalsInitAttribute.cs b/CommunityToolkit.HighPerformance/Attributes/SkipLocalsInitAttribute.cs deleted file mode 100644 index fcad37015..000000000 --- a/CommunityToolkit.HighPerformance/Attributes/SkipLocalsInitAttribute.cs +++ /dev/null @@ -1,27 +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. - -#if !NET6_0_OR_GREATER - -namespace System.Runtime.CompilerServices; - -/// -/// Used to indicate to the compiler that the .locals init flag should not be set in method headers. -/// -/// Internal copy of the .NET 5 attribute. -[AttributeUsage( - AttributeTargets.Module | - AttributeTargets.Class | - AttributeTargets.Struct | - AttributeTargets.Interface | - AttributeTargets.Constructor | - AttributeTargets.Method | - AttributeTargets.Property | - AttributeTargets.Event, - Inherited = false)] -internal sealed class SkipLocalsInitAttribute : Attribute -{ -} - -#endif \ No newline at end of file diff --git a/CommunityToolkit.HighPerformance/Properties/AssemblyInfo.cs b/CommunityToolkit.HighPerformance/Properties/AssemblyInfo.cs deleted file mode 100644 index 333e5efc4..000000000 --- a/CommunityToolkit.HighPerformance/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Runtime.CompilerServices; - -// We can suppress the .init flag for local variables for the entire module. -// This doesn't affect the correctness of methods in this assembly, as none of them -// are relying on the JIT ensuring that all local memory is zeroed out to work. Doing -// this can provide some minor performance benefits, depending on the workload. -[module: SkipLocalsInit] - -// We need to test the RuntimeHelpers polyfills on applicable runtimes -#if !NETSTANDARD2_1_OR_GREATER -[assembly: InternalsVisibleTo("CommunityToolkit.HighPerformance.UnitTests, publickey=002400000480000094000000060200000024000052534131000400000100010041753AF735AE6140C9508567666C51C6AB929806ADB0D210694B30AB142A060237BC741F9682E7D8D4310364B4BBA4EE89CC9D3D5CE7E5583587E8EA44DCA09977996582875E71FB54FA7B170798D853D5D8010B07219633BDB761D01AC924DA44576D6180CDCEAE537973982BB461C541541D58417A3794E34F45E6F2D129E2")] -#endif \ No newline at end of file diff --git a/CommunityToolkit.HighPerformance/Properties/Polyfills/RequiresPreviewFeaturesAttribute.cs b/CommunityToolkit.HighPerformance/Properties/Polyfills/RequiresPreviewFeaturesAttribute.cs deleted file mode 100644 index 103d6f06a..000000000 --- a/CommunityToolkit.HighPerformance/Properties/Polyfills/RequiresPreviewFeaturesAttribute.cs +++ /dev/null @@ -1,55 +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. - -#if !NET6_0_OR_GREATER - -namespace System.Runtime.Versioning; - -/// -/// Indicates that an API is in preview. -/// -[AttributeUsage( - AttributeTargets.Assembly | - AttributeTargets.Module | - AttributeTargets.Class | - AttributeTargets.Interface | - AttributeTargets.Delegate | - AttributeTargets.Struct | - AttributeTargets.Enum | - AttributeTargets.Constructor | - AttributeTargets.Method | - AttributeTargets.Property | - AttributeTargets.Field | - AttributeTargets.Event, - Inherited = false)] -internal sealed class RequiresPreviewFeaturesAttribute : Attribute -{ - /// - /// Initializes a new instance of the class. - /// - public RequiresPreviewFeaturesAttribute() - { - } - - /// - /// Initializes a new instance of the class with the specified message. - /// - /// An optional message associated with this attribute instance. - public RequiresPreviewFeaturesAttribute(string? message) - { - Message = message; - } - - /// - /// Returns the optional message associated with this attribute instance. - /// - public string? Message { get; } - - /// - /// Returns the optional URL associated with this attribute instance. - /// - public string? Url { get; set; } -} - -#endif \ No newline at end of file diff --git a/CommunityToolkit.HighPerformance/ReadOnlyRef{T}.cs b/CommunityToolkit.HighPerformance/ReadOnlyRef{T}.cs deleted file mode 100644 index 0f643c8ef..000000000 --- a/CommunityToolkit.HighPerformance/ReadOnlyRef{T}.cs +++ /dev/null @@ -1,137 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Runtime.CompilerServices; -using System.Runtime.Versioning; -#if NETSTANDARD2_1_OR_GREATER -using System.Runtime.InteropServices; -#else -using CommunityToolkit.HighPerformance.Helpers; -#endif - -namespace CommunityToolkit.HighPerformance; - -/// -/// A that can store a readonly reference to a value of a specified type. -/// -/// The type of value to reference. -[RequiresPreviewFeatures( - "The ReadOnlyRef type has no compiler support to ensure the lifetime of referenced values is respected, and as such using it incorrectly may lead to GC holes.", - Url = "https://github.com/dotnet/runtime/issues/46104")] -public readonly ref struct ReadOnlyRef -{ -#if NETSTANDARD2_1_OR_GREATER - /// - /// The 1-length instance used to track the target value. - /// - internal readonly ReadOnlySpan Span; - - /// - /// Initializes a new instance of the struct. - /// - /// The readonly reference to the target value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlyRef(in T value) - { - ref T r0 = ref Unsafe.AsRef(value); - - this.Span = MemoryMarshal.CreateReadOnlySpan(ref r0, 1); - } - - /// - /// Initializes a new instance of the struct. - /// - /// The pointer to the target value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe ReadOnlyRef(void* pointer) - : this(in Unsafe.AsRef(pointer)) - { - } - - /// - /// Gets the readonly reference represented by the current instance. - /// - public ref readonly T Value - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref MemoryMarshal.GetReference(this.Span); - } - - /// - /// Implicitly converts a instance into a one. - /// - /// The input instance. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator ReadOnlyRef(Ref reference) - { - return new(in reference.Value); - } -#else - /// - /// The owner the current instance belongs to - /// - private readonly object owner; - - /// - /// The target offset within the current instance is pointing to - /// - private readonly IntPtr offset; - - /// - /// Initializes a new instance of the struct. - /// - /// The owner to create a portable reference for. - /// The target offset within for the target reference. - /// The parameter is not validated, and it's responsibility of the caller to ensure it's valid. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ReadOnlyRef(object owner, IntPtr offset) - { - this.owner = owner; - this.offset = offset; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The owner to create a portable reference for. - /// The target reference to point to (it must be within ). - /// The parameter is not validated, and it's responsibility of the caller to ensure it's valid. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlyRef(object owner, in T value) - { - this.owner = owner; - this.offset = ObjectMarshal.DangerousGetObjectDataByteOffset(owner, ref Unsafe.AsRef(value)); - } - - /// - /// Gets the readonly reference represented by the current instance. - /// - public ref readonly T Value - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref ObjectMarshal.DangerousGetObjectDataReferenceAt(this.owner, this.offset); - } - - /// - /// Implicitly converts a instance into a one. - /// - /// The input instance. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator ReadOnlyRef(Ref reference) - { - return new(reference.Owner, reference.Offset); - } -#endif - - /// - /// Implicitly gets the value from a given instance. - /// - /// The input instance. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator T(ReadOnlyRef reference) - { - return reference.Value; - } -} diff --git a/CommunityToolkit.HighPerformance/Ref{T}.cs b/CommunityToolkit.HighPerformance/Ref{T}.cs deleted file mode 100644 index 8deec6c47..000000000 --- a/CommunityToolkit.HighPerformance/Ref{T}.cs +++ /dev/null @@ -1,106 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Runtime.CompilerServices; -using System.Runtime.Versioning; -#if NETSTANDARD2_1_OR_GREATER -using System.Runtime.InteropServices; -#else -using CommunityToolkit.HighPerformance.Helpers; -#endif - -namespace CommunityToolkit.HighPerformance; - -/// -/// A that can store a reference to a value of a specified type. -/// -/// The type of value to reference. -[RequiresPreviewFeatures( - "The Ref type has no compiler support to ensure the lifetime of referenced values is respected, and as such using it incorrectly may lead to GC holes.", - Url = "https://github.com/dotnet/runtime/issues/46104")] -public readonly ref struct Ref -{ -#if NETSTANDARD2_1_OR_GREATER - /// - /// The 1-length instance used to track the target value. - /// - internal readonly Span Span; - - /// - /// Initializes a new instance of the struct. - /// - /// The reference to the target value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Ref(ref T value) - { - this.Span = MemoryMarshal.CreateSpan(ref value, 1); - } - - /// - /// Initializes a new instance of the struct. - /// - /// The pointer to the target value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe Ref(void* pointer) - : this(ref Unsafe.AsRef(pointer)) - { - } - - /// - /// Gets the reference represented by the current instance. - /// - public ref T Value - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref MemoryMarshal.GetReference(this.Span); - } -#else - /// - /// The owner the current instance belongs to - /// - internal readonly object Owner; - - /// - /// The target offset within the current instance is pointing to - /// - /// - /// Using an instead of to avoid the int to - /// native int conversion in the generated asm (an extra movsxd on x64). - /// - internal readonly IntPtr Offset; - - /// - /// Initializes a new instance of the struct. - /// - /// The owner to create a portable reference for. - /// The target reference to point to (it must be within ). - /// The parameter is not validated, and it's responsibility of the caller to ensure it's valid. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Ref(object owner, ref T value) - { - this.Owner = owner; - this.Offset = ObjectMarshal.DangerousGetObjectDataByteOffset(owner, ref value); - } - - /// - /// Gets the reference represented by the current instance. - /// - public ref T Value - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref ObjectMarshal.DangerousGetObjectDataReferenceAt(this.Owner, this.Offset); - } -#endif - - /// - /// Implicitly gets the value from a given instance. - /// - /// The input instance. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator T(Ref reference) - { - return reference.Value; - } -} diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Attributes/NotNullWhenAttribute.cs b/CommunityToolkit.Mvvm.SourceGenerators/Attributes/NotNullWhenAttribute.cs deleted file mode 100644 index acb9c5850..000000000 --- a/CommunityToolkit.Mvvm.SourceGenerators/Attributes/NotNullWhenAttribute.cs +++ /dev/null @@ -1,24 +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. - -namespace System.Diagnostics.CodeAnalysis; - -/// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. -[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] -internal sealed class NotNullWhenAttribute : Attribute -{ - /// - /// Initializes a new instance of the class. - /// - /// The return value condition. If the method returns this value, the associated parameter will not be null. - public NotNullWhenAttribute(bool returnValue) - { - ReturnValue = returnValue; - } - - /// - /// Gets a value indicating whether the annotated parameter will be null depending on the return value. - /// - public bool ReturnValue { get; } -} diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Attributes/NullabilityAttributesGenerator.cs b/CommunityToolkit.Mvvm.SourceGenerators/Attributes/NullabilityAttributesGenerator.cs deleted file mode 100644 index cfb540200..000000000 --- a/CommunityToolkit.Mvvm.SourceGenerators/Attributes/NullabilityAttributesGenerator.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.IO; -using System.Linq; -using System.Reflection; -using CommunityToolkit.Mvvm.SourceGenerators.Extensions; -using Microsoft.CodeAnalysis; - -namespace CommunityToolkit.Mvvm.SourceGenerators; - -/// -/// A source generator for necessary nullability attributes. -/// -[Generator(LanguageNames.CSharp)] -public sealed class NullabilityAttributesGenerator : IIncrementalGenerator -{ - /// - /// The System.Diagnostics.CodeAnalysis.NotNullAttribute metadata name. - /// - private const string NotNullAttributeMetadataName = "System.Diagnostics.CodeAnalysis.NotNullAttribute"; - - /// - /// The System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute metadata name. - /// - private const string NotNullIfNotNullAttributeMetadataName = "System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute"; - - /// - public void Initialize(IncrementalGeneratorInitializationContext context) - { - // Check that the target attributes are not available in the consuming project. To ensure that this - // works fine both in .NET (Core) and .NET Standard implementations, we also need to check that the - // target types are declared as public (we assume that in this case those types are from the BCL). - // This avoids issues on .NET Standard with Roslyn also seeing internal types from referenced assemblies. - - // Check whether [NotNull] is not available - IncrementalValueProvider isNotNullAttributeNotAvailable = - context.CompilationProvider - .Select(static (item, _) => !item.HasAccessibleTypeWithMetadataName(NotNullAttributeMetadataName)); - - // Generate the [NotNull] type - context.RegisterConditionalSourceOutput(isNotNullAttributeNotAvailable, static context => - { - string source = LoadAttributeSourceWithMetadataName(NotNullAttributeMetadataName); - - context.AddSource($"{NotNullAttributeMetadataName}.g.cs", source); - }); - - // Check whether [NotNullIfNotNull] is not available - IncrementalValueProvider isNotNullIfNotNullAttributeNotAvailable = - context.CompilationProvider - .Select(static (item, _) => !item.HasAccessibleTypeWithMetadataName(NotNullIfNotNullAttributeMetadataName)); - - // Generate the [NotNullIfNotNull] type - context.RegisterConditionalSourceOutput(isNotNullIfNotNullAttributeNotAvailable, static context => - { - string source = LoadAttributeSourceWithMetadataName(NotNullIfNotNullAttributeMetadataName); - - context.AddSource($"{NotNullIfNotNullAttributeMetadataName}.g.cs", source); - }); - } - - /// - /// Gets the generated source for a specified attribute. - /// - private static string LoadAttributeSourceWithMetadataName(string typeFullName) - { - string typeName = typeFullName.Split('.').Last(); - string filename = $"CommunityToolkit.Mvvm.SourceGenerators.EmbeddedResources.{typeName}.cs"; - - Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(filename); - StreamReader reader = new(stream); - - return reader.ReadToEnd(); - } -} diff --git a/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.csproj b/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.csproj deleted file mode 100644 index f9e7f71c0..000000000 --- a/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.csproj +++ /dev/null @@ -1,43 +0,0 @@ - - - - netstandard2.0 - false - - - - - - - - - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - - - - - - - - - - \ No newline at end of file diff --git a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/INotifyPropertyChangedGenerator.cs b/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/INotifyPropertyChangedGenerator.cs deleted file mode 100644 index 2831b6a13..000000000 --- a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/INotifyPropertyChangedGenerator.cs +++ /dev/null @@ -1,89 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Immutable; -using System.Linq; -using CommunityToolkit.Mvvm.SourceGenerators.Diagnostics; -using CommunityToolkit.Mvvm.SourceGenerators.Extensions; -using CommunityToolkit.Mvvm.SourceGenerators.Input.Models; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors; - -namespace CommunityToolkit.Mvvm.SourceGenerators; - -/// -/// A source generator for the INotifyPropertyChangedAttribute type. -/// -[Generator(LanguageNames.CSharp)] -public sealed class INotifyPropertyChangedGenerator : TransitiveMembersGenerator -{ - /// - /// Initializes a new instance of the class. - /// - public INotifyPropertyChangedGenerator() - : base("global::CommunityToolkit.Mvvm.ComponentModel.INotifyPropertyChangedAttribute") - { - } - - /// - protected override IncrementalValuesProvider<(INamedTypeSymbol Symbol, INotifyPropertyChangedInfo Info)> GetInfo( - IncrementalGeneratorInitializationContext context, - IncrementalValuesProvider<(INamedTypeSymbol Symbol, AttributeData AttributeData)> source) - { - static INotifyPropertyChangedInfo GetInfo(INamedTypeSymbol typeSymbol, AttributeData attributeData) - { - bool includeAdditionalHelperMethods = attributeData.GetNamedArgument("IncludeAdditionalHelperMethods", true); - - return new(includeAdditionalHelperMethods); - } - - return source.Select(static (item, _) => (item.Symbol, GetInfo(item.Symbol, item.AttributeData))); - } - - /// - protected override bool ValidateTargetType(INamedTypeSymbol typeSymbol, INotifyPropertyChangedInfo info, out ImmutableArray diagnostics) - { - ImmutableArray.Builder builder = ImmutableArray.CreateBuilder(); - - // Check if the type already implements INotifyPropertyChanged - if (typeSymbol.AllInterfaces.Any(i => i.HasFullyQualifiedName("global::System.ComponentModel.INotifyPropertyChanged"))) - { - builder.Add(DuplicateINotifyPropertyChangedInterfaceForINotifyPropertyChangedAttributeError, typeSymbol, typeSymbol); - - diagnostics = builder.ToImmutable(); - - return false; - } - - // Check if the type uses [INotifyPropertyChanged] or [ObservableObject] already (in the type hierarchy too) - if (typeSymbol.HasOrInheritsAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableObjectAttribute") || - typeSymbol.InheritsAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.INotifyPropertyChangedAttribute")) - { - builder.Add(InvalidAttributeCombinationForINotifyPropertyChangedAttributeError, typeSymbol, typeSymbol); - - diagnostics = builder.ToImmutable(); - - return false; - } - - diagnostics = builder.ToImmutable(); - - return true; - } - - /// - protected override ImmutableArray FilterDeclaredMembers(INotifyPropertyChangedInfo info, ImmutableArray memberDeclarations) - { - // If requested, only include the event and the basic methods to raise it, but not the additional helpers - if (!info.IncludeAdditionalHelperMethods) - { - return memberDeclarations.Where(static member => member - is EventFieldDeclarationSyntax - or MethodDeclarationSyntax { Identifier.ValueText: "OnPropertyChanged" }).ToImmutableArray(); - } - - return memberDeclarations; - } -} diff --git a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/AttributeInfo.cs b/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/AttributeInfo.cs deleted file mode 100644 index 569580c9b..000000000 --- a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/AttributeInfo.cs +++ /dev/null @@ -1,118 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using CommunityToolkit.Mvvm.SourceGenerators.Extensions; -using CommunityToolkit.Mvvm.SourceGenerators.Helpers; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; - -namespace CommunityToolkit.Mvvm.SourceGenerators.ComponentModel.Models; - -/// -/// A model representing an attribute declaration. -/// -internal sealed record AttributeInfo( - string TypeName, - ImmutableArray ConstructorArgumentInfo, - ImmutableArray<(string Name, TypedConstantInfo Value)> NamedArgumentInfo) -{ - /// - /// Creates a new instance from a given value. - /// - /// The input value. - /// A instance representing . - public static AttributeInfo From(AttributeData attributeData) - { - string typeName = attributeData.AttributeClass!.GetFullyQualifiedName(); - - // Get the constructor arguments - ImmutableArray constructorArguments = - attributeData.ConstructorArguments - .Select(TypedConstantInfo.From) - .ToImmutableArray(); - - // Get the named arguments - ImmutableArray<(string, TypedConstantInfo)>.Builder namedArguments = ImmutableArray.CreateBuilder<(string, TypedConstantInfo)>(); - - foreach (KeyValuePair arg in attributeData.NamedArguments) - { - namedArguments.Add((arg.Key, TypedConstantInfo.From(arg.Value))); - } - - return new( - typeName, - constructorArguments, - namedArguments.ToImmutable()); - } - - /// - /// Gets an instance representing the current value. - /// - /// The instance representing the current value. - public AttributeSyntax GetSyntax() - { - // Gather the constructor arguments - IEnumerable arguments = - ConstructorArgumentInfo - .Select(static arg => AttributeArgument(arg.GetSyntax())); - - // Gather the named arguments - IEnumerable namedArguments = - NamedArgumentInfo.Select(static arg => - AttributeArgument(arg.Value.GetSyntax()) - .WithNameEquals(NameEquals(IdentifierName(arg.Name)))); - - return Attribute(IdentifierName(TypeName), AttributeArgumentList(SeparatedList(arguments.Concat(namedArguments)))); - - } - - /// - /// An implementation for . - /// - public sealed class Comparer : Comparer - { - /// - protected override void AddToHashCode(ref HashCode hashCode, AttributeInfo obj) - { - hashCode.Add(obj.TypeName); - hashCode.AddRange(obj.ConstructorArgumentInfo, TypedConstantInfo.Comparer.Default); - - foreach ((string key, TypedConstantInfo value) in obj.NamedArgumentInfo) - { - hashCode.Add(key); - hashCode.Add(value, TypedConstantInfo.Comparer.Default); - } - } - - /// - protected override bool AreEqual(AttributeInfo x, AttributeInfo y) - { - if (x.TypeName != y.TypeName || - !x.ConstructorArgumentInfo.SequenceEqual(y.ConstructorArgumentInfo, TypedConstantInfo.Comparer.Default) || - x.NamedArgumentInfo.Length != y.NamedArgumentInfo.Length) - { - return false; - } - - for (int i = 0; i < x.NamedArgumentInfo.Length; i++) - { - (string Name, TypedConstantInfo Value) left = x.NamedArgumentInfo[i]; - (string Name, TypedConstantInfo Value) right = y.NamedArgumentInfo[i]; - - if (left.Name != right.Name || - !TypedConstantInfo.Comparer.Default.Equals(left.Value, right.Value)) - { - return false; - } - } - - return true; - } - } -} diff --git a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/PropertyInfo.cs b/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/PropertyInfo.cs deleted file mode 100644 index 2a63868e2..000000000 --- a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/PropertyInfo.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using CommunityToolkit.Mvvm.SourceGenerators.Extensions; -using CommunityToolkit.Mvvm.SourceGenerators.Helpers; - -namespace CommunityToolkit.Mvvm.SourceGenerators.ComponentModel.Models; - -/// -/// A model representing an generated property -/// -/// The type name for the generated property, including nullability annotations. -/// The field name. -/// The generated property name. -/// The sequence of property changing properties to notify. -/// The sequence of property changed properties to notify. -/// The sequence of commands to notify. -/// Whether or not the generated property also broadcasts changes. -/// Whether or not the generated property also validates its value. -/// The sequence of forwarded attributes for the generated property. -internal sealed record PropertyInfo( - string TypeNameWithNullabilityAnnotations, - string FieldName, - string PropertyName, - ImmutableArray PropertyChangingNames, - ImmutableArray PropertyChangedNames, - ImmutableArray NotifiedCommandNames, - bool NotifyPropertyChangedRecipients, - bool NotifyDataErrorInfo, - ImmutableArray ForwardedAttributes) -{ - /// - /// An implementation for . - /// - public sealed class Comparer : Comparer - { - /// - protected override void AddToHashCode(ref HashCode hashCode, PropertyInfo obj) - { - hashCode.Add(obj.TypeNameWithNullabilityAnnotations); - hashCode.Add(obj.FieldName); - hashCode.Add(obj.PropertyName); - hashCode.AddRange(obj.PropertyChangingNames); - hashCode.AddRange(obj.PropertyChangedNames); - hashCode.AddRange(obj.NotifiedCommandNames); - hashCode.Add(obj.NotifyPropertyChangedRecipients); - hashCode.Add(obj.NotifyDataErrorInfo); - hashCode.AddRange(obj.ForwardedAttributes, AttributeInfo.Comparer.Default); - } - - /// - protected override bool AreEqual(PropertyInfo x, PropertyInfo y) - { - return - x.TypeNameWithNullabilityAnnotations == y.TypeNameWithNullabilityAnnotations && - x.FieldName == y.FieldName && - x.PropertyName == y.PropertyName && - x.PropertyChangingNames.SequenceEqual(y.PropertyChangingNames) && - x.PropertyChangedNames.SequenceEqual(y.PropertyChangedNames) && - x.NotifiedCommandNames.SequenceEqual(y.NotifiedCommandNames) && - x.NotifyPropertyChangedRecipients == y.NotifyPropertyChangedRecipients && - x.NotifyDataErrorInfo == y.NotifyDataErrorInfo && - x.ForwardedAttributes.SequenceEqual(y.ForwardedAttributes, AttributeInfo.Comparer.Default); - } - } -} diff --git a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/TypedConstantInfo.Comparer.cs b/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/TypedConstantInfo.Comparer.cs deleted file mode 100644 index 2d54e0eac..000000000 --- a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/TypedConstantInfo.Comparer.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using CommunityToolkit.Mvvm.SourceGenerators.Helpers; - -namespace CommunityToolkit.Mvvm.SourceGenerators.ComponentModel.Models; - -/// -partial record TypedConstantInfo -{ - /// - /// An implementation for . - /// - public sealed class Comparer : Comparer - { - /// - protected override void AddToHashCode(ref HashCode hashCode, TypedConstantInfo obj) - { - obj.AddToHashCode(ref hashCode); - } - - /// - protected override bool AreEqual(TypedConstantInfo x, TypedConstantInfo y) - { - return x.IsEqualTo(y); - } - } -} diff --git a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/TypedConstantInfo.Factory.cs b/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/TypedConstantInfo.Factory.cs deleted file mode 100644 index 7e138cc95..000000000 --- a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/TypedConstantInfo.Factory.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. - -using System; -using System.Collections.Immutable; -using System.Linq; -using Microsoft.CodeAnalysis; - -namespace CommunityToolkit.Mvvm.SourceGenerators.ComponentModel.Models; - -/// -partial record TypedConstantInfo -{ - /// - /// Creates a new instance from a given value. - /// - /// The input value. - /// A instance representing . - /// Thrown if the input argument is not valid. - public static TypedConstantInfo From(TypedConstant arg) - { - if (arg.IsNull) - { - return new Null(); - } - - if (arg.Kind == TypedConstantKind.Array) - { - string elementTypeName = ((IArrayTypeSymbol)arg.Type!).ElementType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); - ImmutableArray items = arg.Values.Select(From).ToImmutableArray(); - - return new Array(elementTypeName, items); - } - - return (arg.Kind, arg.Value) switch - { - (TypedConstantKind.Primitive, string text) => new Primitive.String(text), - (TypedConstantKind.Primitive, bool flag) => new Primitive.Boolean(flag), - (TypedConstantKind.Primitive, object value) => value switch - { - byte b => new Primitive.Of(b), - char c => new Primitive.Of(c), - double d => new Primitive.Of(d), - float f => new Primitive.Of(f), - int i => new Primitive.Of(i), - long l => new Primitive.Of(l), - sbyte sb => new Primitive.Of(sb), - short sh => new Primitive.Of(sh), - uint ui => new Primitive.Of(ui), - ulong ul => new Primitive.Of(ul), - ushort ush => new Primitive.Of(ush), - _ => throw new ArgumentException("Invalid primitive type") - }, - (TypedConstantKind.Type, ITypeSymbol type) => new Type(type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)), - (TypedConstantKind.Enum, object value) => new Enum(arg.Type!.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), value), - _ => throw new ArgumentException("Invalid typed constant type"), - }; - } -} diff --git a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/ValidationInfo.cs b/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/ValidationInfo.cs deleted file mode 100644 index 734e6b685..000000000 --- a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/ValidationInfo.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using CommunityToolkit.Mvvm.SourceGenerators.Extensions; -using CommunityToolkit.Mvvm.SourceGenerators.Helpers; - -namespace CommunityToolkit.Mvvm.SourceGenerators.Input.Models; - -/// -/// A model with gathered info on all validatable properties in a given type. -/// -/// The filename hint for the current type. -/// The fully qualified type name of the target type. -/// The name of validatable properties. -internal sealed record ValidationInfo( - string FilenameHint, - string TypeName, - ImmutableArray PropertyNames) -{ - /// - /// An implementation for . - /// - public sealed class Comparer : Comparer - { - /// - protected override void AddToHashCode(ref HashCode hashCode, ValidationInfo obj) - { - hashCode.Add(obj.FilenameHint); - hashCode.Add(obj.TypeName); - hashCode.AddRange(obj.PropertyNames); - } - - /// - protected override bool AreEqual(ValidationInfo x, ValidationInfo y) - { - return - x.FilenameHint == y.FilenameHint && - x.TypeName == y.TypeName && - x.PropertyNames.SequenceEqual(y.PropertyNames); - } - } -} diff --git a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableObjectGenerator.cs b/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableObjectGenerator.cs deleted file mode 100644 index 114cb9372..000000000 --- a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableObjectGenerator.cs +++ /dev/null @@ -1,83 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Immutable; -using System.Linq; -using CommunityToolkit.Mvvm.SourceGenerators.Diagnostics; -using CommunityToolkit.Mvvm.SourceGenerators.Extensions; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors; - -namespace CommunityToolkit.Mvvm.SourceGenerators; - -/// -/// A source generator for the ObservableObjectAttribute type. -/// -[Generator(LanguageNames.CSharp)] -public sealed class ObservableObjectGenerator : TransitiveMembersGenerator -{ - /// - /// Initializes a new instance of the class. - /// - public ObservableObjectGenerator() - : base("global::CommunityToolkit.Mvvm.ComponentModel.ObservableObjectAttribute") - { - } - - /// - protected override IncrementalValuesProvider<(INamedTypeSymbol Symbol, object? Info)> GetInfo( - IncrementalGeneratorInitializationContext context, - IncrementalValuesProvider<(INamedTypeSymbol Symbol, AttributeData AttributeData)> source) - { - return source.Select(static (item, _) => (item.Symbol, (object?)null)); - } - - /// - protected override bool ValidateTargetType(INamedTypeSymbol typeSymbol, object? info, out ImmutableArray diagnostics) - { - ImmutableArray.Builder builder = ImmutableArray.CreateBuilder(); - - // Check if the type already implements INotifyPropertyChanged... - if (typeSymbol.AllInterfaces.Any(i => i.HasFullyQualifiedName("global::System.ComponentModel.INotifyPropertyChanged"))) - { - builder.Add(DuplicateINotifyPropertyChangedInterfaceForObservableObjectAttributeError, typeSymbol, typeSymbol); - - diagnostics = builder.ToImmutable(); - - return false; - } - - // ...or INotifyPropertyChanging - if (typeSymbol.AllInterfaces.Any(i => i.HasFullyQualifiedName("global::System.ComponentModel.INotifyPropertyChanging"))) - { - builder.Add(DuplicateINotifyPropertyChangingInterfaceForObservableObjectAttributeError, typeSymbol, typeSymbol); - - diagnostics = builder.ToImmutable(); - - return false; - } - - // Check if the type uses [INotifyPropertyChanged] or [ObservableObject] already (in the type hierarchy too) - if (typeSymbol.InheritsAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableObjectAttribute") || - typeSymbol.HasOrInheritsAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.INotifyPropertyChangedAttribute")) - { - builder.Add(InvalidAttributeCombinationForObservableObjectAttributeError, typeSymbol, typeSymbol); - - diagnostics = builder.ToImmutable(); - - return false; - } - - diagnostics = builder.ToImmutable(); - - return true; - } - - /// - protected override ImmutableArray FilterDeclaredMembers(object? info, ImmutableArray memberDeclarations) - { - return memberDeclarations; - } -} diff --git a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/TransitiveMembersGenerator.cs b/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/TransitiveMembersGenerator.cs deleted file mode 100644 index 82bdfbd87..000000000 --- a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/TransitiveMembersGenerator.cs +++ /dev/null @@ -1,155 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Text; -using CommunityToolkit.Mvvm.SourceGenerators.Extensions; -using CommunityToolkit.Mvvm.SourceGenerators.Models; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors; - -namespace CommunityToolkit.Mvvm.SourceGenerators; - -/// -/// A source generator for a given attribute type. -/// -/// The type of info gathered for each target type to process. -public abstract partial class TransitiveMembersGenerator : IIncrementalGenerator -{ - /// - /// The fully qualified name of the attribute type to look for. - /// - private readonly string attributeType; - - /// - /// An instance to compare intermediate models. - /// - /// - /// This is needed to cache extracted info on attributes used to annotate target types. - /// - private readonly IEqualityComparer comparer; - - /// - /// The preloaded instance with members to generate. - /// - private readonly ClassDeclarationSyntax classDeclaration; - - /// - /// The sequence of member declarations for sealed types. - /// - private ImmutableArray sealedMemberDeclarations; - - /// - /// The resulting sequence of member declarations for non sealed types. - /// - private ImmutableArray nonSealedMemberDeclarations; - - /// - /// Initializes a new instance of the class. - /// - /// The fully qualified name of the attribute type to look for. - /// An instance to compare intermediate models. - private protected TransitiveMembersGenerator(string attributeType, IEqualityComparer? comparer = null) - { - this.attributeType = attributeType; - this.comparer = comparer ?? EqualityComparer.Default; - this.classDeclaration = Execute.LoadClassDeclaration(attributeType); - - Execute.ProcessMemberDeclarations( - GetType(), - this.classDeclaration.Members.ToImmutableArray(), - out this.sealedMemberDeclarations, - out this.nonSealedMemberDeclarations); - } - - /// - public void Initialize(IncrementalGeneratorInitializationContext context) - { - // Get all class declarations - IncrementalValuesProvider typeSymbols = - context.SyntaxProvider - .CreateSyntaxProvider( - static (node, _) => node is ClassDeclarationSyntax { AttributeLists.Count: > 0 }, - static (context, _) => (INamedTypeSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node)!); - - // Filter the types with the target attribute - IncrementalValuesProvider<(INamedTypeSymbol Symbol, AttributeData AttributeData)> typeSymbolsWithAttributeData = - typeSymbols - .Select((item, _) => ( - Symbol: item, - Attribute: item.GetAttributes().FirstOrDefault(a => a.AttributeClass?.HasFullyQualifiedName(this.attributeType) == true))) - .Where(static item => item.Attribute is not null)!; - - // Transform the input data - IncrementalValuesProvider<(INamedTypeSymbol Symbol, TInfo Info)> typeSymbolsWithInfo = GetInfo(context, typeSymbolsWithAttributeData); - - // Filter by language version - context.FilterWithLanguageVersion(ref typeSymbolsWithInfo, LanguageVersion.CSharp8, UnsupportedCSharpLanguageVersionError); - - // Gather all generation info, and any diagnostics - IncrementalValuesProvider> generationInfoWithErrors = - typeSymbolsWithInfo.Select((item, _) => - { - if (ValidateTargetType(item.Symbol, item.Info, out ImmutableArray diagnostics)) - { - return new Result<(HierarchyInfo, bool, TInfo)>( - (HierarchyInfo.From(item.Symbol), item.Symbol.IsSealed, item.Info), - ImmutableArray.Empty); - } - - return new Result<(HierarchyInfo, bool, TInfo)>(default, diagnostics); - }); - - // Emit the diagnostic, if needed - context.ReportDiagnostics(generationInfoWithErrors.Select(static (item, _) => item.Errors)); - - // Get the filtered sequence to enable caching - IncrementalValuesProvider<(HierarchyInfo Hierarchy, bool IsSealed, TInfo Info)> generationInfo = - generationInfoWithErrors - .Where(static item => item.Errors.IsEmpty) - .Select(static (item, _) => item.Value) - .WithComparers(HierarchyInfo.Comparer.Default, EqualityComparer.Default, this.comparer); - - // Generate the required members - context.RegisterSourceOutput(generationInfo, (context, item) => - { - ImmutableArray sourceMemberDeclarations = item.IsSealed ? this.sealedMemberDeclarations : this.nonSealedMemberDeclarations; - ImmutableArray filteredMemberDeclarations = FilterDeclaredMembers(item.Info, sourceMemberDeclarations); - CompilationUnitSyntax compilationUnit = item.Hierarchy.GetCompilationUnit(filteredMemberDeclarations, this.classDeclaration.BaseList); - - context.AddSource($"{item.Hierarchy.FilenameHint}.g.cs", compilationUnit.GetText(Encoding.UTF8)); - }); - } - - /// - /// Gathers info from a source input. - /// - /// The instance in use. - /// The source input. - /// A transformed instance with the gathered data. - protected abstract IncrementalValuesProvider<(INamedTypeSymbol Symbol, TInfo Info)> GetInfo( - IncrementalGeneratorInitializationContext context, - IncrementalValuesProvider<(INamedTypeSymbol Symbol, AttributeData AttributeData)> source); - - /// - /// Validates a target type being processed. - /// - /// The instance for the target type. - /// The instance with the current processing info. - /// The resulting diagnostics from the processing operation. - /// Whether or not the target type is valid and can be processed normally. - protected abstract bool ValidateTargetType(INamedTypeSymbol typeSymbol, TInfo info, out ImmutableArray diagnostics); - - /// - /// Filters the nodes to generate from the input parsed tree. - /// - /// The instance with the current processing info. - /// The input sequence of instances to generate. - /// A sequence of nodes to emit in the generated file. - protected abstract ImmutableArray FilterDeclaredMembers(TInfo info, ImmutableArray memberDeclarations); -} diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticExtensions.cs b/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticExtensions.cs deleted file mode 100644 index 8da472e57..000000000 --- a/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticExtensions.cs +++ /dev/null @@ -1,46 +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. - -// This file is ported and adapted from ComputeSharp (Sergio0694/ComputeSharp), -// more info in ThirdPartyNotices.txt in the root of the project. - -using System.Collections.Immutable; -using System.Linq; -using Microsoft.CodeAnalysis; - -namespace CommunityToolkit.Mvvm.SourceGenerators.Diagnostics; - -/// -/// Extension methods specifically for creating diagnostics. -/// -internal static class DiagnosticExtensions -{ - /// - /// Adds a new diagnostics to the target builder. - /// - /// The collection of produced instances. - /// The input for the diagnostics to create. - /// The source to attach the diagnostics to. - /// The optional arguments for the formatted message to include. - public static void Add( - this ImmutableArray.Builder diagnostics, - DiagnosticDescriptor descriptor, - ISymbol symbol, - params object[] args) - { - diagnostics.Add(descriptor.CreateDiagnostic(symbol, args)); - } - - /// - /// Creates a new instance with the specified parameters. - /// - /// The input for the diagnostics to create. - /// The source to attach the diagnostics to. - /// The optional arguments for the formatted message to include. - /// The resulting instance. - public static Diagnostic CreateDiagnostic(this DiagnosticDescriptor descriptor, ISymbol symbol, params object[] args) - { - return Diagnostic.Create(descriptor, symbol.Locations.FirstOrDefault(), args); - } -} diff --git a/CommunityToolkit.Mvvm.SourceGenerators/EmbeddedResources/NotNullAttribute.cs b/CommunityToolkit.Mvvm.SourceGenerators/EmbeddedResources/NotNullAttribute.cs deleted file mode 100644 index f65251842..000000000 --- a/CommunityToolkit.Mvvm.SourceGenerators/EmbeddedResources/NotNullAttribute.cs +++ /dev/null @@ -1,15 +0,0 @@ -// - -#pragma warning disable - -namespace System.Diagnostics.CodeAnalysis -{ - /// - /// Specifies that an output will not be null even if the corresponding type allows it. - /// Also specifies that an input argument was not null when the call returns. - /// - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] - internal sealed class NotNullAttribute : Attribute - { - } -} \ No newline at end of file diff --git a/CommunityToolkit.Mvvm.SourceGenerators/EmbeddedResources/NotNullIfNotNullAttribute.cs b/CommunityToolkit.Mvvm.SourceGenerators/EmbeddedResources/NotNullIfNotNullAttribute.cs deleted file mode 100644 index 69532ab02..000000000 --- a/CommunityToolkit.Mvvm.SourceGenerators/EmbeddedResources/NotNullIfNotNullAttribute.cs +++ /dev/null @@ -1,27 +0,0 @@ -// - -#pragma warning disable - -namespace System.Diagnostics.CodeAnalysis -{ - /// - /// Specifies that the output will be non-null if the named parameter is non-null. - /// - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] - internal sealed class NotNullIfNotNullAttribute : Attribute - { - /// - /// Initializes a new instance of the class. - /// - /// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null. - public NotNullIfNotNullAttribute(string parameterName) - { - ParameterName = parameterName; - } - - /// - /// Gets the associated parameter name. - /// - public string ParameterName { get; } - } -} \ No newline at end of file diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Extensions/HashCodeExtensions.cs b/CommunityToolkit.Mvvm.SourceGenerators/Extensions/HashCodeExtensions.cs deleted file mode 100644 index 32e0fd670..000000000 --- a/CommunityToolkit.Mvvm.SourceGenerators/Extensions/HashCodeExtensions.cs +++ /dev/null @@ -1,47 +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. - -// This file is ported and adapted from ComputeSharp (Sergio0694/ComputeSharp), -// more info in ThirdPartyNotices.txt in the root of the project. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; - -namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions; - -/// -/// Extension methods for . -/// -internal static class HashCodeExtensions -{ - /// - /// Adds all items from a given instance to an hashcode. - /// - /// The type of items to hash. - /// The target instance. - /// The input items to hash. - public static void AddRange(this ref HashCode hashCode, ImmutableArray items) - { - foreach (T item in items) - { - hashCode.Add(item); - } - } - - /// - /// Adds all items from a given instance to an hashcode. - /// - /// The type of items to hash. - /// The target instance. - /// A comparer to get hashcodes for items. - /// The input items to hash. - public static void AddRange(this ref HashCode hashCode, ImmutableArray items, IEqualityComparer comparer) - { - foreach (T item in items) - { - hashCode.Add(item, comparer); - } - } -} \ No newline at end of file diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Extensions/IEqualityComparerExtensions.cs b/CommunityToolkit.Mvvm.SourceGenerators/Extensions/IEqualityComparerExtensions.cs deleted file mode 100644 index 5bc4d33d0..000000000 --- a/CommunityToolkit.Mvvm.SourceGenerators/Extensions/IEqualityComparerExtensions.cs +++ /dev/null @@ -1,67 +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. - -// This file is ported from ComputeSharp (Sergio0694/ComputeSharp), -// more info in ThirdPartyNotices.txt in the root of the project. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; - -namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions; - -/// -/// Extension methods for . -/// -internal static class IEqualityComparerExtensions -{ - /// - /// Gets an for an sequence. - /// - /// The type of items to compare. - /// The compare for individual items. - public static IEqualityComparer> ForImmutableArray(this IEqualityComparer comparer) - { - return new Comparer(comparer); - } - - /// - /// An implementation for an value. - /// - private sealed class Comparer : IEqualityComparer> - { - /// - /// The comparer. - /// - private readonly IEqualityComparer comparer; - - /// - /// Creates a new instance with the specified parameters. - /// - /// The comparer. - public Comparer(IEqualityComparer comparer) - { - this.comparer = comparer; - } - - /// - public bool Equals(ImmutableArray x, ImmutableArray y) - { - return - x == y || - x.SequenceEqual(y, this.comparer); - } - - /// - public int GetHashCode(ImmutableArray obj) - { - HashCode hashCode = default; - - hashCode.AddRange(obj, this.comparer); - - return hashCode.ToHashCode(); - } - } -} diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Extensions/IncrementalValuesProviderExtensions.cs b/CommunityToolkit.Mvvm.SourceGenerators/Extensions/IncrementalValuesProviderExtensions.cs deleted file mode 100644 index 85eff06e8..000000000 --- a/CommunityToolkit.Mvvm.SourceGenerators/Extensions/IncrementalValuesProviderExtensions.cs +++ /dev/null @@ -1,191 +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. - -// This file is ported and adapted from ComputeSharp (Sergio0694/ComputeSharp), -// more info in ThirdPartyNotices.txt in the root of the project. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using Microsoft.CodeAnalysis; - -namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions; - -/// -/// Extension methods for . -/// -internal static class IncrementalValuesProviderExtensions -{ - /// - /// Groups items in a given sequence by a specified key. - /// - /// The type of left items in each tuple. - /// The type of right items in each tuple. - /// The input instance. - /// A comparer. - /// An with the grouped results. - public static IncrementalValuesProvider<(TLeft Left, ImmutableArray Right)> GroupBy( - this IncrementalValuesProvider<(TLeft Left, TRight Right)> source, - IEqualityComparer comparer) - { - return source.Collect().SelectMany((item, _) => - { - Dictionary.Builder> map = new(comparer); - - foreach ((TLeft hierarchy, TRight info) in item) - { - if (!map.TryGetValue(hierarchy, out ImmutableArray.Builder builder)) - { - builder = ImmutableArray.CreateBuilder(); - - map.Add(hierarchy, builder); - } - - builder.Add(info); - } - - ImmutableArray<(TLeft Hierarchy, ImmutableArray Properties)>.Builder result = - ImmutableArray.CreateBuilder<(TLeft, ImmutableArray)>(); - - foreach (KeyValuePair.Builder> entry in map) - { - result.Add((entry.Key, entry.Value.ToImmutable())); - } - - return result; - }); - } - - /// - /// Creates a new instance with a given pair of comparers. - /// - /// The type of left items in each tuple. - /// The type of right items in each tuple. - /// The input instance. - /// An instance for items. - /// An instance for items. - /// An with the specified comparers applied to each item. - public static IncrementalValuesProvider<(TLeft Left, TRight Right)> WithComparers( - this IncrementalValuesProvider<(TLeft Left, TRight Right)> source, - IEqualityComparer comparerLeft, - IEqualityComparer comparerRight) - { - return source.WithComparer(new Comparer(comparerLeft, comparerRight)); - } - - /// - /// Creates a new instance with a given pair of comparers. - /// - /// The type of first items in each tuple. - /// The type of second items in each tuple. - /// The type of third items in each tuple. - /// The input instance. - /// An instance for items. - /// An instance for items. - /// An instance for items. - /// An with the specified comparers applied to each item. - public static IncrementalValuesProvider<(T1, T2, T3)> WithComparers( - this IncrementalValuesProvider<(T1, T2, T3)> source, - IEqualityComparer comparer1, - IEqualityComparer comparer2, - IEqualityComparer comparer3) - { - return source.WithComparer(new Comparer(comparer1, comparer2, comparer3)); - } - - /// - /// An implementation for a value tuple. - /// - private sealed class Comparer : IEqualityComparer<(TLeft Left, TRight Right)> - { - /// - /// The comparer. - /// - private readonly IEqualityComparer comparerLeft; - - /// - /// The comparer. - /// - private readonly IEqualityComparer comparerRight; - - /// - /// Creates a new instance with the specified parameters. - /// - /// The comparer. - /// The comparer. - public Comparer(IEqualityComparer comparerLeft, IEqualityComparer comparerRight) - { - this.comparerLeft = comparerLeft; - this.comparerRight = comparerRight; - } - - /// - public bool Equals((TLeft Left, TRight Right) x, (TLeft Left, TRight Right) y) - { - return - this.comparerLeft.Equals(x.Left, y.Left) && - this.comparerRight.Equals(x.Right, y.Right); - } - - /// - public int GetHashCode((TLeft Left, TRight Right) obj) - { - return HashCode.Combine( - this.comparerLeft.GetHashCode(obj.Left), - this.comparerRight.GetHashCode(obj.Right)); - } - } - - /// - /// An implementation for a value tuple. - /// - private sealed class Comparer : IEqualityComparer<(T1, T2, T3)> - { - /// - /// The comparer. - /// - private readonly IEqualityComparer comparer1; - - /// - /// The comparer. - /// - private readonly IEqualityComparer comparer2; - - /// - /// The comparer. - /// - private readonly IEqualityComparer comparer3; - - /// - /// Creates a new instance with the specified parameters. - /// - /// The comparer. - /// The comparer. - /// The comparer. - public Comparer(IEqualityComparer comparer1, IEqualityComparer comparer2, IEqualityComparer comparer3) - { - this.comparer1 = comparer1; - this.comparer2 = comparer2; - this.comparer3 = comparer3; - } - - /// - public bool Equals((T1, T2, T3) x, (T1, T2, T3) y) - { - return - this.comparer1.Equals(x.Item1, y.Item1) && - this.comparer2.Equals(x.Item2, y.Item2) && - this.comparer3.Equals(x.Item3, y.Item3); - } - - /// - public int GetHashCode((T1, T2, T3) obj) - { - return HashCode.Combine( - this.comparer1.GetHashCode(obj.Item1), - this.comparer2.GetHashCode(obj.Item2), - this.comparer3.GetHashCode(obj.Item3)); - } - } -} diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Helpers/Comparer{T,TSelf}.cs b/CommunityToolkit.Mvvm.SourceGenerators/Helpers/Comparer{T,TSelf}.cs deleted file mode 100644 index 69f8d72ad..000000000 --- a/CommunityToolkit.Mvvm.SourceGenerators/Helpers/Comparer{T,TSelf}.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; - -namespace CommunityToolkit.Mvvm.SourceGenerators.Helpers; - -/// -/// A base implementation for instances. -/// -/// The type of items to compare. -/// The concrete comparer type. -internal abstract class Comparer : IEqualityComparer - where TSelf : Comparer, new() -{ - /// - /// The singleton instance. - /// - public static TSelf Default { get; } = new(); - - /// - public bool Equals(T? x, T? y) - { - if (x is null && y is null) - { - return true; - } - - if (x is null || y is null) - { - return false; - } - - if (ReferenceEquals(x, y)) - { - return true; - } - - return AreEqual(x, y); - } - - /// - public int GetHashCode(T obj) - { - HashCode hashCode = default; - - AddToHashCode(ref hashCode, obj); - - return hashCode.ToHashCode(); - } - - /// - /// Adds the current instance to an incremental value. - /// - /// The target value. - /// The instance being inspected. - protected abstract void AddToHashCode(ref HashCode hashCode, T obj); - - /// - /// Compares two instances for equality. - /// - /// The first instance to compare. - /// The second instance to compare. - /// Whether or not and are equal. - protected abstract bool AreEqual(T x, T y); -} diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Helpers/HashCode.cs b/CommunityToolkit.Mvvm.SourceGenerators/Helpers/HashCode.cs deleted file mode 100644 index 75d15668c..000000000 --- a/CommunityToolkit.Mvvm.SourceGenerators/Helpers/HashCode.cs +++ /dev/null @@ -1,497 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Generic; -using System.ComponentModel; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Security.Cryptography; - -#pragma warning disable CS0809 - -namespace System; - -/// -/// A polyfill type that mirrors some methods from on .NET 6. -/// -internal struct HashCode -{ - private const uint Prime1 = 2654435761U; - private const uint Prime2 = 2246822519U; - private const uint Prime3 = 3266489917U; - private const uint Prime4 = 668265263U; - private const uint Prime5 = 374761393U; - - private static readonly uint seed = GenerateGlobalSeed(); - - private uint v1, v2, v3, v4; - private uint queue1, queue2, queue3; - private uint length; - - /// - /// Initializes the default seed. - /// - /// A random seed. - private static unsafe uint GenerateGlobalSeed() - { - byte[] bytes = new byte[4]; - - RandomNumberGenerator.Create().GetBytes(bytes); - - return BitConverter.ToUInt32(bytes, 0); - } - - /// - /// Combines a value into a hash code. - /// - /// The type of the value to combine into the hash code. - /// The value to combine into the hash code. - /// The hash code that represents the value. - public static int Combine(T1 value) - { - uint hc1 = (uint)(value?.GetHashCode() ?? 0); - uint hash = MixEmptyState(); - - hash += 4; - hash = QueueRound(hash, hc1); - hash = MixFinal(hash); - - return (int)hash; - } - - /// - /// Combines two values into a hash code. - /// - /// The type of the first value to combine into the hash code. - /// The type of the second value to combine into the hash code. - /// The first value to combine into the hash code. - /// The second value to combine into the hash code. - /// The hash code that represents the values. - public static int Combine(T1 value1, T2 value2) - { - uint hc1 = (uint)(value1?.GetHashCode() ?? 0); - uint hc2 = (uint)(value2?.GetHashCode() ?? 0); - uint hash = MixEmptyState(); - - hash += 8; - hash = QueueRound(hash, hc1); - hash = QueueRound(hash, hc2); - hash = MixFinal(hash); - - return (int)hash; - } - - /// - /// Combines three values into a hash code. - /// - /// The type of the first value to combine into the hash code. - /// The type of the second value to combine into the hash code. - /// The type of the third value to combine into the hash code. - /// The first value to combine into the hash code. - /// The second value to combine into the hash code. - /// The third value to combine into the hash code. - /// The hash code that represents the values. - public static int Combine(T1 value1, T2 value2, T3 value3) - { - uint hc1 = (uint)(value1?.GetHashCode() ?? 0); - uint hc2 = (uint)(value2?.GetHashCode() ?? 0); - uint hc3 = (uint)(value3?.GetHashCode() ?? 0); - uint hash = MixEmptyState(); - - hash += 12; - hash = QueueRound(hash, hc1); - hash = QueueRound(hash, hc2); - hash = QueueRound(hash, hc3); - hash = MixFinal(hash); - - return (int)hash; - } - - /// - /// Combines four values into a hash code. - /// - /// The type of the first value to combine into the hash code. - /// The type of the second value to combine into the hash code. - /// The type of the third value to combine into the hash code. - /// The type of the fourth value to combine into the hash code. - /// The first value to combine into the hash code. - /// The second value to combine into the hash code. - /// The third value to combine into the hash code. - /// The fourth value to combine into the hash code. - /// The hash code that represents the values. - public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4) - { - uint hc1 = (uint)(value1?.GetHashCode() ?? 0); - uint hc2 = (uint)(value2?.GetHashCode() ?? 0); - uint hc3 = (uint)(value3?.GetHashCode() ?? 0); - uint hc4 = (uint)(value4?.GetHashCode() ?? 0); - - Initialize(out uint v1, out uint v2, out uint v3, out uint v4); - - v1 = Round(v1, hc1); - v2 = Round(v2, hc2); - v3 = Round(v3, hc3); - v4 = Round(v4, hc4); - - uint hash = MixState(v1, v2, v3, v4); - - hash += 16; - hash = MixFinal(hash); - - return (int)hash; - } - - /// - /// Combines five values into a hash code. - /// - /// The type of the first value to combine into the hash code. - /// The type of the second value to combine into the hash code. - /// The type of the third value to combine into the hash code. - /// The type of the fourth value to combine into the hash code. - /// The type of the fifth value to combine into the hash code. - /// The first value to combine into the hash code. - /// The second value to combine into the hash code. - /// The third value to combine into the hash code. - /// The fourth value to combine into the hash code. - /// The fifth value to combine into the hash code. - /// The hash code that represents the values. - public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5) - { - uint hc1 = (uint)(value1?.GetHashCode() ?? 0); - uint hc2 = (uint)(value2?.GetHashCode() ?? 0); - uint hc3 = (uint)(value3?.GetHashCode() ?? 0); - uint hc4 = (uint)(value4?.GetHashCode() ?? 0); - uint hc5 = (uint)(value5?.GetHashCode() ?? 0); - - Initialize(out uint v1, out uint v2, out uint v3, out uint v4); - - v1 = Round(v1, hc1); - v2 = Round(v2, hc2); - v3 = Round(v3, hc3); - v4 = Round(v4, hc4); - - uint hash = MixState(v1, v2, v3, v4); - - hash += 20; - hash = QueueRound(hash, hc5); - hash = MixFinal(hash); - - return (int)hash; - } - - /// - /// Combines six values into a hash code. - /// - /// The type of the first value to combine into the hash code. - /// The type of the second value to combine into the hash code. - /// The type of the third value to combine into the hash code. - /// The type of the fourth value to combine into the hash code. - /// The type of the fifth value to combine into the hash code. - /// The type of the sixth value to combine into the hash code. - /// The first value to combine into the hash code. - /// The second value to combine into the hash code. - /// The third value to combine into the hash code. - /// The fourth value to combine into the hash code. - /// The fifth value to combine into the hash code. - /// The sixth value to combine into the hash code. - /// The hash code that represents the values. - public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6) - { - uint hc1 = (uint)(value1?.GetHashCode() ?? 0); - uint hc2 = (uint)(value2?.GetHashCode() ?? 0); - uint hc3 = (uint)(value3?.GetHashCode() ?? 0); - uint hc4 = (uint)(value4?.GetHashCode() ?? 0); - uint hc5 = (uint)(value5?.GetHashCode() ?? 0); - uint hc6 = (uint)(value6?.GetHashCode() ?? 0); - - Initialize(out uint v1, out uint v2, out uint v3, out uint v4); - - v1 = Round(v1, hc1); - v2 = Round(v2, hc2); - v3 = Round(v3, hc3); - v4 = Round(v4, hc4); - - uint hash = MixState(v1, v2, v3, v4); - - hash += 24; - hash = QueueRound(hash, hc5); - hash = QueueRound(hash, hc6); - hash = MixFinal(hash); - - return (int)hash; - } - - /// - /// Combines seven values into a hash code. - /// - /// The type of the first value to combine into the hash code. - /// The type of the second value to combine into the hash code. - /// The type of the third value to combine into the hash code. - /// The type of the fourth value to combine into the hash code. - /// The type of the fifth value to combine into the hash code. - /// The type of the sixth value to combine into the hash code. - /// The type of the seventh value to combine into the hash code. - /// The first value to combine into the hash code. - /// The second value to combine into the hash code. - /// The third value to combine into the hash code. - /// The fourth value to combine into the hash code. - /// The fifth value to combine into the hash code. - /// The sixth value to combine into the hash code. - /// The seventh value to combine into the hash code. - /// The hash code that represents the values. - public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7) - { - uint hc1 = (uint)(value1?.GetHashCode() ?? 0); - uint hc2 = (uint)(value2?.GetHashCode() ?? 0); - uint hc3 = (uint)(value3?.GetHashCode() ?? 0); - uint hc4 = (uint)(value4?.GetHashCode() ?? 0); - uint hc5 = (uint)(value5?.GetHashCode() ?? 0); - uint hc6 = (uint)(value6?.GetHashCode() ?? 0); - uint hc7 = (uint)(value7?.GetHashCode() ?? 0); - - Initialize(out uint v1, out uint v2, out uint v3, out uint v4); - - v1 = Round(v1, hc1); - v2 = Round(v2, hc2); - v3 = Round(v3, hc3); - v4 = Round(v4, hc4); - - uint hash = MixState(v1, v2, v3, v4); - - hash += 28; - hash = QueueRound(hash, hc5); - hash = QueueRound(hash, hc6); - hash = QueueRound(hash, hc7); - hash = MixFinal(hash); - - return (int)hash; - } - - /// - /// Combines eight values into a hash code. - /// - /// The type of the first value to combine into the hash code. - /// The type of the second value to combine into the hash code. - /// The type of the third value to combine into the hash code. - /// The type of the fourth value to combine into the hash code. - /// The type of the fifth value to combine into the hash code. - /// The type of the sixth value to combine into the hash code. - /// The type of the seventh value to combine into the hash code. - /// The type of the eighth value to combine into the hash code. - /// The first value to combine into the hash code. - /// The second value to combine into the hash code. - /// The third value to combine into the hash code. - /// The fourth value to combine into the hash code. - /// The fifth value to combine into the hash code. - /// The sixth value to combine into the hash code. - /// The seventh value to combine into the hash code. - /// The eighth value to combine into the hash code. - /// The hash code that represents the values. - public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8) - { - uint hc1 = (uint)(value1?.GetHashCode() ?? 0); - uint hc2 = (uint)(value2?.GetHashCode() ?? 0); - uint hc3 = (uint)(value3?.GetHashCode() ?? 0); - uint hc4 = (uint)(value4?.GetHashCode() ?? 0); - uint hc5 = (uint)(value5?.GetHashCode() ?? 0); - uint hc6 = (uint)(value6?.GetHashCode() ?? 0); - uint hc7 = (uint)(value7?.GetHashCode() ?? 0); - uint hc8 = (uint)(value8?.GetHashCode() ?? 0); - - Initialize(out uint v1, out uint v2, out uint v3, out uint v4); - - v1 = Round(v1, hc1); - v2 = Round(v2, hc2); - v3 = Round(v3, hc3); - v4 = Round(v4, hc4); - - v1 = Round(v1, hc5); - v2 = Round(v2, hc6); - v3 = Round(v3, hc7); - v4 = Round(v4, hc8); - - uint hash = MixState(v1, v2, v3, v4); - - hash += 32; - hash = MixFinal(hash); - - return (int)hash; - } - - /// - /// Adds a single value to the current hash. - /// - /// The type of the value to add into the hash code. - /// The value to add into the hash code. - public void Add(T value) - { - Add(value?.GetHashCode() ?? 0); - } - - /// - /// Adds a single value to the current hash. - /// - /// The type of the value to add into the hash code. - /// The value to add into the hash code. - /// The instance to use. - public void Add(T value, IEqualityComparer? comparer) - { - Add(value is null ? 0 : (comparer?.GetHashCode(value) ?? value.GetHashCode())); - } - - /// - /// Adds a span of bytes to the hash code. - /// - /// The span. - public void AddBytes(ReadOnlySpan value) - { - ref byte pos = ref MemoryMarshal.GetReference(value); - ref byte end = ref Unsafe.Add(ref pos, value.Length); - - while ((nint)Unsafe.ByteOffset(ref pos, ref end) >= sizeof(int)) - { - Add(Unsafe.ReadUnaligned(ref pos)); - pos = ref Unsafe.Add(ref pos, sizeof(int)); - } - - while (Unsafe.IsAddressLessThan(ref pos, ref end)) - { - Add((int)pos); - pos = ref Unsafe.Add(ref pos, 1); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void Initialize(out uint v1, out uint v2, out uint v3, out uint v4) - { - v1 = seed + Prime1 + Prime2; - v2 = seed + Prime2; - v3 = seed; - v4 = seed - Prime1; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint Round(uint hash, uint input) - { - return RotateLeft(hash + input * Prime2, 13) * Prime1; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint QueueRound(uint hash, uint queuedValue) - { - return RotateLeft(hash + queuedValue * Prime3, 17) * Prime4; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint MixState(uint v1, uint v2, uint v3, uint v4) - { - return RotateLeft(v1, 1) + RotateLeft(v2, 7) + RotateLeft(v3, 12) + RotateLeft(v4, 18); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint MixEmptyState() - { - return seed + Prime5; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint MixFinal(uint hash) - { - hash ^= hash >> 15; - hash *= Prime2; - hash ^= hash >> 13; - hash *= Prime3; - hash ^= hash >> 16; - - return hash; - } - - private void Add(int value) - { - uint val = (uint)value; - uint previousLength = this.length++; - uint position = previousLength % 4; - - if (position == 0) - { - this.queue1 = val; - } - else if (position == 1) - { - this.queue2 = val; - } - else if (position == 2) - { - this.queue3 = val; - } - else - { - if (previousLength == 3) - { - Initialize(out this.v1, out this.v2, out this.v3, out this.v4); - } - - this.v1 = Round(this.v1, this.queue1); - this.v2 = Round(this.v2, this.queue2); - this.v3 = Round(this.v3, this.queue3); - this.v4 = Round(this.v4, val); - } - } - - /// - /// Gets the resulting hashcode from the current instance. - /// - /// The resulting hashcode from the current instance. - public int ToHashCode() - { - uint length = this.length; - uint position = length % 4; - uint hash = length < 4 ? MixEmptyState() : MixState(this.v1, this.v2, this.v3, this.v4); - - hash += length * 4; - - if (position > 0) - { - hash = QueueRound(hash, this.queue1); - - if (position > 1) - { - hash = QueueRound(hash, this.queue2); - - if (position > 2) - { - hash = QueueRound(hash, this.queue3); - } - } - } - - hash = MixFinal(hash); - - return (int)hash; - } - - /// - [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes. Use ToHashCode to retrieve the computed hash code.", error: true)] - [EditorBrowsable(EditorBrowsableState.Never)] - public override int GetHashCode() => throw new NotSupportedException(); - - /// - [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes.", error: true)] - [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object? obj) => throw new NotSupportedException(); - - /// - /// Rotates the specified value left by the specified number of bits. - /// Similar in behavior to the x86 instruction ROL. - /// - /// The value to rotate. - /// The number of bits to rotate by. - /// Any value outside the range [0..31] is treated as congruent mod 32. - /// The rotated value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint RotateLeft(uint value, int offset) - { - return (value << offset) | (value >> (32 - offset)); - } -} \ No newline at end of file diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Input/Models/CommandInfo.cs b/CommunityToolkit.Mvvm.SourceGenerators/Input/Models/CommandInfo.cs deleted file mode 100644 index f9240249f..000000000 --- a/CommunityToolkit.Mvvm.SourceGenerators/Input/Models/CommandInfo.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using CommunityToolkit.Mvvm.SourceGenerators.Extensions; -using CommunityToolkit.Mvvm.SourceGenerators.Helpers; - -namespace CommunityToolkit.Mvvm.SourceGenerators.Input.Models; - -/// -/// A model with gathered info on a given command method. -/// -/// The name of the target method. -/// The resulting field name for the generated command. -/// The resulting property name for the generated command. -/// The command interface type name. -/// The command class type name. -/// The delegate type name for the wrapped method. -/// The type arguments for and , if any. -/// The type arguments for , if any. -/// The member name for the can execute check, if available. -/// The can execute expression type, if available. -/// Whether or not concurrent executions have been enabled. -/// Whether or not exceptions should flow to the task scheduler. -/// Whether or not to also generate a cancel command. -internal sealed record CommandInfo( - string MethodName, - string FieldName, - string PropertyName, - string CommandInterfaceType, - string CommandClassType, - string DelegateType, - ImmutableArray CommandTypeArguments, - ImmutableArray DelegateTypeArguments, - string? CanExecuteMemberName, - CanExecuteExpressionType? CanExecuteExpressionType, - bool AllowConcurrentExecutions, - bool FlowExceptionsToTaskScheduler, - bool IncludeCancelCommand) -{ - /// - /// An implementation for . - /// - public sealed class Comparer : Comparer - { - /// - protected override void AddToHashCode(ref HashCode hashCode, CommandInfo obj) - { - hashCode.Add(obj.MethodName); - hashCode.Add(obj.FieldName); - hashCode.Add(obj.PropertyName); - hashCode.Add(obj.CommandInterfaceType); - hashCode.Add(obj.CommandClassType); - hashCode.Add(obj.DelegateType); - hashCode.AddRange(obj.CommandTypeArguments); - hashCode.AddRange(obj.DelegateTypeArguments); - hashCode.Add(obj.CanExecuteMemberName); - hashCode.Add(obj.CanExecuteExpressionType); - hashCode.Add(obj.AllowConcurrentExecutions); - hashCode.Add(obj.FlowExceptionsToTaskScheduler); - hashCode.Add(obj.IncludeCancelCommand); - } - - /// - protected override bool AreEqual(CommandInfo x, CommandInfo y) - { - return - x.MethodName == y.MethodName && - x.FieldName == y.FieldName && - x.PropertyName == y.PropertyName && - x.CommandInterfaceType == y.CommandInterfaceType && - x.CommandClassType == y.CommandClassType && - x.DelegateType == y.DelegateType && - x.CommandTypeArguments.SequenceEqual(y.CommandTypeArguments) && - x.DelegateTypeArguments.SequenceEqual(y.CommandTypeArguments) && - x.CanExecuteMemberName == y.CanExecuteMemberName && - x.CanExecuteExpressionType == y.CanExecuteExpressionType && - x.AllowConcurrentExecutions == y.AllowConcurrentExecutions && - x.FlowExceptionsToTaskScheduler == y.FlowExceptionsToTaskScheduler && - x.IncludeCancelCommand == y.IncludeCancelCommand; - } - } -} diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Input/RelayCommandGenerator.cs b/CommunityToolkit.Mvvm.SourceGenerators/Input/RelayCommandGenerator.cs deleted file mode 100644 index 766a333b9..000000000 --- a/CommunityToolkit.Mvvm.SourceGenerators/Input/RelayCommandGenerator.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Immutable; -using System.Linq; -using System.Text; -using CommunityToolkit.Mvvm.SourceGenerators.Extensions; -using CommunityToolkit.Mvvm.SourceGenerators.Input.Models; -using CommunityToolkit.Mvvm.SourceGenerators.Models; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors; - -namespace CommunityToolkit.Mvvm.SourceGenerators; - -/// -/// A source generator for generating command properties from annotated methods. -/// -[Generator(LanguageNames.CSharp)] -public sealed partial class RelayCommandGenerator : IIncrementalGenerator -{ - /// - public void Initialize(IncrementalGeneratorInitializationContext context) - { - // Get all method declarations with at least one attribute - IncrementalValuesProvider methodSymbols = - context.SyntaxProvider - .CreateSyntaxProvider( - static (node, _) => node is MethodDeclarationSyntax { Parent: ClassDeclarationSyntax, AttributeLists.Count: > 0 }, - static (context, _) => (IMethodSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node)!); - - // Filter the methods using [RelayCommand] - IncrementalValuesProvider<(IMethodSymbol Symbol, AttributeData Attribute)> methodSymbolsWithAttributeData = - methodSymbols - .Select(static (item, _) => ( - item, - Attribute: item.GetAttributes().FirstOrDefault(a => a.AttributeClass?.HasFullyQualifiedName("global::CommunityToolkit.Mvvm.Input.RelayCommandAttribute") == true))) - .Where(static item => item.Attribute is not null)!; - - // Filter by language version - context.FilterWithLanguageVersion(ref methodSymbolsWithAttributeData, LanguageVersion.CSharp8, UnsupportedCSharpLanguageVersionError); - - // Gather info for all annotated command methods - IncrementalValuesProvider<(HierarchyInfo Hierarchy, Result Info)> commandInfoWithErrors = - methodSymbolsWithAttributeData - .Select(static (item, _) => - { - HierarchyInfo hierarchy = HierarchyInfo.From(item.Symbol.ContainingType); - CommandInfo? commandInfo = Execute.GetInfo(item.Symbol, item.Attribute, out ImmutableArray diagnostics); - - return (hierarchy, new Result(commandInfo, diagnostics)); - }); - - // Output the diagnostics - context.ReportDiagnostics(commandInfoWithErrors.Select(static (item, _) => item.Info.Errors)); - - // Get the filtered sequence to enable caching - IncrementalValuesProvider<(HierarchyInfo Hierarchy, CommandInfo Info)> commandInfo = - commandInfoWithErrors - .Where(static item => item.Info.Value is not null) - .Select(static (item, _) => (item.Hierarchy, item.Info.Value!)) - .WithComparers(HierarchyInfo.Comparer.Default, CommandInfo.Comparer.Default); - - // Generate the commands - context.RegisterSourceOutput(commandInfo, static (context, item) => - { - ImmutableArray memberDeclarations = Execute.GetSyntax(item.Info); - CompilationUnitSyntax compilationUnit = item.Hierarchy.GetCompilationUnit(memberDeclarations); - - context.AddSource($"{item.Hierarchy.FilenameHint}.{item.Info.MethodName}.g.cs", compilationUnit.GetText(Encoding.UTF8)); - }); - } -} diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Messaging/Models/RecipientInfo.cs b/CommunityToolkit.Mvvm.SourceGenerators/Messaging/Models/RecipientInfo.cs deleted file mode 100644 index b9d1f0683..000000000 --- a/CommunityToolkit.Mvvm.SourceGenerators/Messaging/Models/RecipientInfo.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using CommunityToolkit.Mvvm.SourceGenerators.Extensions; -using CommunityToolkit.Mvvm.SourceGenerators.Helpers; - -namespace CommunityToolkit.Mvvm.SourceGenerators.Input.Models; - -/// -/// A model with gathered info on all message types being handled by a recipient. -/// -/// The filename hint for the current type. -/// The fully qualified type name of the target type. -/// The name of messages being received. -internal sealed record RecipientInfo( - string FilenameHint, - string TypeName, - ImmutableArray MessageTypes) -{ - /// - /// An implementation for . - /// - public sealed class Comparer : Comparer - { - /// - protected override void AddToHashCode(ref HashCode hashCode, RecipientInfo obj) - { - hashCode.Add(obj.FilenameHint); - hashCode.Add(obj.TypeName); - hashCode.AddRange(obj.MessageTypes); - } - - /// - protected override bool AreEqual(RecipientInfo x, RecipientInfo y) - { - return - x.FilenameHint == y.FilenameHint && - x.TypeName == y.TypeName && - x.MessageTypes.SequenceEqual(y.MessageTypes); - } - } -} diff --git a/CommunityToolkit.Mvvm.SourceGenerators/System.Runtime.CompilerServices/IsExternalInit.cs b/CommunityToolkit.Mvvm.SourceGenerators/System.Runtime.CompilerServices/IsExternalInit.cs deleted file mode 100644 index 0160c8ef0..000000000 --- a/CommunityToolkit.Mvvm.SourceGenerators/System.Runtime.CompilerServices/IsExternalInit.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.ComponentModel; - -namespace System.Runtime.CompilerServices; - -/// -/// Reserved to be used by the compiler for tracking metadata. -/// This class should not be used by developers in source code. -/// -[EditorBrowsable(EditorBrowsableState.Never)] -internal static class IsExternalInit -{ -} diff --git a/CommunityToolkit.Mvvm/Properties/AssemblyInfo.cs b/CommunityToolkit.Mvvm/Properties/AssemblyInfo.cs deleted file mode 100644 index 873989053..000000000 --- a/CommunityToolkit.Mvvm/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Runtime.CompilerServices; - -// See notes in CommunityToolkit.HighPerformance for more info -[module: SkipLocalsInit] - -[assembly: InternalsVisibleTo("CommunityToolkit.Mvvm.Internals.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010041753AF735AE6140C9508567666C51C6AB929806ADB0D210694B30AB142A060237BC741F9682E7D8D4310364B4BBA4EE89CC9D3D5CE7E5583587E8EA44DCA09977996582875E71FB54FA7B170798D853D5D8010B07219633BDB761D01AC924DA44576D6180CDCEAE537973982BB461C541541D58417A3794E34F45E6F2D129E2")] \ No newline at end of file diff --git a/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/CallerArgumentExpressionAttribute.cs b/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/CallerArgumentExpressionAttribute.cs deleted file mode 100644 index 09d876ccd..000000000 --- a/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/CallerArgumentExpressionAttribute.cs +++ /dev/null @@ -1,31 +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. - -#if !NET6_0_OR_GREATER - -namespace System.Runtime.CompilerServices; - -/// -/// An attribute that allows parameters to receive the expression of other parameters. -/// -/// Internal copy from the BCL attribute. -[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] -internal sealed class CallerArgumentExpressionAttribute : Attribute -{ - /// - /// Initializes a new instance of the class. - /// - /// The condition parameter value. - public CallerArgumentExpressionAttribute(string parameterName) - { - ParameterName = parameterName; - } - - /// - /// Gets the parameter name the expression is retrieved from. - /// - public string ParameterName { get; } -} - -#endif \ No newline at end of file diff --git a/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/DoesNotReturnAttribute.cs b/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/DoesNotReturnAttribute.cs deleted file mode 100644 index 9f6aadb5e..000000000 --- a/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/DoesNotReturnAttribute.cs +++ /dev/null @@ -1,18 +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. - -#if NETSTANDARD2_0 - -namespace System.Diagnostics.CodeAnalysis; - -/// -/// Applied to a method that will never return under any circumstance. -/// -/// Internal copy from the BCL attribute. -[AttributeUsage(AttributeTargets.Method, Inherited = false)] -internal sealed class DoesNotReturnAttribute : Attribute -{ -} - -#endif \ No newline at end of file diff --git a/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/MaybeNullWhenAttribute.cs b/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/MaybeNullWhenAttribute.cs deleted file mode 100644 index f7ce3fb5f..000000000 --- a/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/MaybeNullWhenAttribute.cs +++ /dev/null @@ -1,31 +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. - -#if NETSTANDARD2_0 - -namespace System.Diagnostics.CodeAnalysis; - -/// -/// Specifies that when a method returns , the parameter may be null even if the corresponding type disallows it. -/// -/// Internal copy from the BCL attribute. -[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] -internal sealed class MaybeNullWhenAttribute : Attribute -{ - /// - /// Initializes the attribute with the specified return value condition. - /// - /// The return value condition. If the method returns this value, the associated parameter may be null. - public MaybeNullWhenAttribute(bool returnValue) - { - ReturnValue = returnValue; - } - - /// - /// Gets the return value condition. - /// - public bool ReturnValue { get; } -} - -#endif \ No newline at end of file diff --git a/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/MemberNotNullAttribute.cs b/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/MemberNotNullAttribute.cs deleted file mode 100644 index 457996db4..000000000 --- a/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/MemberNotNullAttribute.cs +++ /dev/null @@ -1,40 +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. - -#if !NET6_0_OR_GREATER - -namespace System.Diagnostics.CodeAnalysis; - -/// -/// Specifies that the method or property will ensure that the listed field and property members have not-null values. -/// -/// Internal copy from the BCL attribute. -[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] -internal sealed class MemberNotNullAttribute : Attribute -{ - /// - /// Initializes the attribute with a field or property member. - /// - /// The field or property member that is promised to be not-null. - public MemberNotNullAttribute(string member) - { - Members = new[] { member }; - } - - /// - /// Initializes the attribute with the list of field and property members. - /// - /// The list of field and property members that are promised to be not-null. - public MemberNotNullAttribute(params string[] members) - { - Members = members; - } - - /// - /// Gets field or property member names. - /// - public string[] Members { get; } -} - -#endif \ No newline at end of file diff --git a/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/NotNullAttribute.cs b/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/NotNullAttribute.cs deleted file mode 100644 index 87cec39b2..000000000 --- a/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/NotNullAttribute.cs +++ /dev/null @@ -1,19 +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. - -#if NETSTANDARD2_0 - -namespace System.Diagnostics.CodeAnalysis; - -/// -/// Specifies that an output will not be null even if the corresponding type allows it. -/// Specifies that an input argument was not null when the call returns. -/// -/// Internal copy from the BCL attribute. -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] -internal sealed class NotNullAttribute : Attribute -{ -} - -#endif \ No newline at end of file diff --git a/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/NotNullIfNotNullAttribute.cs b/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/NotNullIfNotNullAttribute.cs deleted file mode 100644 index 09888a512..000000000 --- a/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/NotNullIfNotNullAttribute.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#if NETSTANDARD2_0 - -namespace System.Diagnostics.CodeAnalysis; - -/// -/// Specifies that the output will be non-null if the named parameter is non-null. -/// -/// Internal copy from the BCL attribute. -[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] -internal sealed class NotNullIfNotNullAttribute : Attribute -{ - /// - /// Initializes a new instance of the class. - /// - /// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null. - public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName; - - /// Gets the associated parameter name. - public string ParameterName { get; } -} - -#endif \ No newline at end of file diff --git a/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/NotNullWhenAttribute.cs b/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/NotNullWhenAttribute.cs deleted file mode 100644 index a8b03f86f..000000000 --- a/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/NotNullWhenAttribute.cs +++ /dev/null @@ -1,28 +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. - -#if NETSTANDARD2_0 - -namespace System.Diagnostics.CodeAnalysis; - -/// -/// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. -/// -/// Internal copy from the BCL attribute. -[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] -internal sealed class NotNullWhenAttribute : Attribute -{ - /// - /// Initializes a new instance of the class. - /// - /// The return value condition. If the method returns this value, the associated parameter will not be null. - public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; - - /// - /// Gets a value indicating whether the annotated variable is not . - /// - public bool ReturnValue { get; } -} - -#endif \ No newline at end of file diff --git a/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/RequiresUnreferencedCodeAttribute.cs b/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/RequiresUnreferencedCodeAttribute.cs deleted file mode 100644 index 3b0974069..000000000 --- a/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/RequiresUnreferencedCodeAttribute.cs +++ /dev/null @@ -1,37 +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. - -#if !NET6_0_OR_GREATER - -namespace System.Diagnostics.CodeAnalysis; - -/// -/// Indicates that the specified method requires dynamic access to code that is not referenced statically. -/// -[AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Class, Inherited = false)] -[Conditional("DEBUG")] -internal sealed class RequiresUnreferencedCodeAttribute : Attribute -{ - /// - /// Initializes a new instance of the class. - /// - /// A message that contains information about the usage of unreferenced code. - public RequiresUnreferencedCodeAttribute(string message) - { - Message = message; - } - - /// - /// Gets a message that contains information about the usage of unreferenced code. - /// - public string Message { get; } - - /// - /// Gets or sets an optional URL that contains more information about the method, - /// why it requires unreferenced code, and what options a consumer has to deal with it. - /// - public string? Url { get; set; } -} - -#endif \ No newline at end of file diff --git a/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/SkipLocalsInitAttribute.cs b/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/SkipLocalsInitAttribute.cs deleted file mode 100644 index a05d197d8..000000000 --- a/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/SkipLocalsInitAttribute.cs +++ /dev/null @@ -1,27 +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. - -#if !NET6_0_OR_GREATER - -namespace System.Runtime.CompilerServices; - -/// -/// Used to indicate to the compiler that the .locals init flag should not be set in method headers. -/// -/// Internal copy from the BCL attribute. -[AttributeUsage( - AttributeTargets.Module | - AttributeTargets.Class | - AttributeTargets.Struct | - AttributeTargets.Interface | - AttributeTargets.Constructor | - AttributeTargets.Method | - AttributeTargets.Property | - AttributeTargets.Event, - Inherited = false)] -internal sealed class SkipLocalsInitAttribute : Attribute -{ -} - -#endif \ No newline at end of file diff --git a/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/UnconditionalSuppressMessageAttribute.cs b/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/UnconditionalSuppressMessageAttribute.cs deleted file mode 100644 index 15ac11371..000000000 --- a/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/UnconditionalSuppressMessageAttribute.cs +++ /dev/null @@ -1,90 +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. - -#if !NET6_0_OR_GREATER - -namespace System.Diagnostics.CodeAnalysis; - -/// -/// Suppresses reporting of a specific rule violation, allowing multiple suppressions on a -/// single code artifact. -/// -/// -/// is different than -/// in that it doesn't have a -/// . So it is always preserved in the compiled assembly. -/// -[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)] -[Conditional("DEBUG")] -internal sealed class UnconditionalSuppressMessageAttribute : Attribute -{ - /// - /// Initializes a new instance of the - /// class, specifying the category of the tool and the identifier for an analysis rule. - /// - /// The category for the attribute. - /// The identifier of the analysis rule the attribute applies to. - public UnconditionalSuppressMessageAttribute(string category, string checkId) - { - Category = category; - CheckId = checkId; - } - - /// - /// Gets the category identifying the classification of the attribute. - /// - /// - /// The property describes the tool or tool analysis category - /// for which a message suppression attribute applies. - /// - public string Category { get; } - - /// - /// Gets the identifier of the analysis tool rule to be suppressed. - /// - /// - /// Concatenated together, the and - /// properties form a unique check identifier. - /// - public string CheckId { get; } - - /// - /// Gets or sets the scope of the code that is relevant for the attribute. - /// - /// - /// The Scope property is an optional argument that specifies the metadata scope for which - /// the attribute is relevant. - /// - public string? Scope { get; set; } - - /// - /// Gets or sets a fully qualified path that represents the target of the attribute. - /// - /// - /// The property is an optional argument identifying the analysis target - /// of the attribute. An example value is "System.IO.Stream.ctor():System.Void". - /// Because it is fully qualified, it can be long, particularly for targets such as parameters. - /// The analysis tool user interface should be capable of automatically formatting the parameter. - /// - public string? Target { get; set; } - - /// - /// Gets or sets an optional argument expanding on exclusion criteria. - /// - /// - /// The property is an optional argument that specifies additional - /// exclusion where the literal metadata target is not sufficiently precise. For example, - /// the cannot be applied within a method, - /// and it may be desirable to suppress a violation against a statement in the method that will - /// give a rule violation, but not against all statements in the method. - /// - public string? MessageId { get; set; } - - /// - /// Gets or sets the justification for suppressing the code analysis message. - /// - public string? Justification { get; set; } -} - -#endif \ No newline at end of file diff --git a/CommunityToolkit.Mvvm/Properties/Polyfills/System.Runtime.CompilerServices/IsExternalInit.cs b/CommunityToolkit.Mvvm/Properties/Polyfills/System.Runtime.CompilerServices/IsExternalInit.cs deleted file mode 100644 index 2b7b72e8a..000000000 --- a/CommunityToolkit.Mvvm/Properties/Polyfills/System.Runtime.CompilerServices/IsExternalInit.cs +++ /dev/null @@ -1,20 +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. - -#if !NET6_0_OR_GREATER - -using System.ComponentModel; - -namespace System.Runtime.CompilerServices; - -/// -/// Reserved to be used by the compiler for tracking metadata. -/// This class should not be used by developers in source code. -/// -[EditorBrowsable(EditorBrowsableState.Never)] -internal static class IsExternalInit -{ -} - -#endif diff --git a/Directory.Build.props b/Directory.Build.props index ff94022ed..37e8c3425 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -25,7 +25,7 @@ - + diff --git a/README.md b/README.md index cc5d7342d..e1d670d78 100644 --- a/README.md +++ b/README.md @@ -2,30 +2,26 @@ .NET Community Toolkit is a collection of helpers and APIs that work for all .NET developers and are agnostic of any specific UI platform. The toolkit is maintained and published by Microsoft, and part of the .NET Foundation. -| Target | Branch | Status | Recommended package version | -| ------ | ------ | ------ | ------ | -| Production | rel/7.1.2 | [![Build Status](https://dev.azure.com/dotnet/CommunityToolkit/_apis/build/status/Toolkit-CI?branchName=rel/7.1.1)](https://dev.azure.com/dotnet/CommunityToolkit/_build/latest?definitionId=10&branchName=rel/7.1.1) | [![NuGet](https://img.shields.io/nuget/v/Microsoft.Toolkit.svg)](https://www.nuget.org/profiles/Microsoft.Toolkit) | -| Previews | main | [![Build Status](https://dev.azure.com/dotnet/CommunityToolkit/_apis/build/status/Toolkit-CI?branchName=main)](https://dev.azure.com/dotnet/CommunityToolkit/_build/latest?definitionId=10) | [![DevOps](https://vsrm.dev.azure.com/dotnet/_apis/public/Release/badge/696bc9fd-f160-4e97-a1bd-7cbbb3b58f66/1/1)](https://dev.azure.com/dotnet/CommunityToolkit/_packaging?_a=feed&feed=CommunityToolkit-MainLatest) | - ## 👀 What does this repo contain? This repository contains several .NET libraries (originally developed as part of the [Windows Community Toolkit](https://github.com/CommunityToolkit/WindowsCommunityToolkit)) that can be used both by application developers (regardless on the specific UI framework in use, they work everywhere!) and library authors. These libraries are also being used internally at Microsoft to power many of our first party apps (such as the new Microsoft Store) and constantly improved by listening to feedbacks from other teams, external partners and other developers from the community. Here's a quick breakdown of the various components you'll find in this repository: -- [`CommunityToolkit.Mvvm` (aka MVVM Toolkit)](https://docs.microsoft.com/windows/communitytoolkit/mvvm/introduction): a fast, modular, platform-agnostic MVVM library, which is the official successor of `MvvmLight`. It's used extensively in the Microsoft Store and other first party apps. -- `CommunityToolkit.Mvvm.SourceGenerators`: the source generators to augment the MVVM Toolkit. -- [`CommunityToolkit.Diagnostics`](https://docs.microsoft.com/windows/communitytoolkit/diagnostics/introduction): a set of helper APIs (specifically, [`Guard`](https://docs.microsoft.com/windows/communitytoolkit/developer-tools/guard) and [`ThrowHelper`](https://docs.microsoft.com/windows/communitytoolkit/developer-tools/throwhelper)) that can be used for cleaner, more efficient and less error-prone argument validation and error checking. -- [`CommunityToolkit.HighPerformance`](https://docs.microsoft.com/windows/communitytoolkit/high-performance/introduction) a collection of helpers for working in high-performance scenarios. It includes APIs such as [pooled buffer helpers](https://docs.microsoft.com/windows/communitytoolkit/high-performance/memoryowner), a fast [string pool](https://docs.microsoft.com/windows/communitytoolkit/high-performance/stringpool) type, a 2D variant of `Memory` and `Span` ([`Memory2D`](https://docs.microsoft.com/windows/communitytoolkit/high-performance/memory2d) and [`Span2D`](https://docs.microsoft.com/windows/communitytoolkit/high-performance/span2d)) also supporting discontiguous regions, helpers for bit shift operations (such as [`BitHelper`](https://docs.microsoft.com/windows/communitytoolkit/high-performance/span2d), also used in [Paint.NET](https://www.getpaint.net/)), and more. -- [`CommunityToolkit.Common`](https://docs.microsoft.com/dotnet/api/?term=communitytoolkit.common): a set of helper APIs shared with other CommunityToolkit libraries. +Package | Latest stable | Latest Preview | Description +---------|---------------|---------------|------------ +[`CommunityToolkit.Common`](https://learn.microsoft.com/dotnet/api/?term=communitytoolkit.common) | [![CommunityToolkit.Common](https://img.shields.io/nuget/v/CommunityToolkit.Common)](https://nuget.org/packages/CommunityToolkit.Common/) | [![CommunityToolkit.Common](https://img.shields.io/nuget/vpre/CommunityToolkit.Common)](https://nuget.org/packages/CommunityToolkit.Common/absoluteLatest) | A set of helper APIs shared with other [`CommunityToolkit`](https://learn.microsoft.com/windows/communitytoolkit) libraries. +[`CommunityToolkit.Diagnostics`](https://learn.microsoft.com/windows/communitytoolkit/diagnostics/introduction) | [![CommunityToolkit.Diagnostics](https://img.shields.io/nuget/v/CommunityToolkit.Diagnostics)](https://nuget.org/packages/CommunityToolkit.Diagnostics/) | [![CommunityToolkit.Diagnostics](https://img.shields.io/nuget/vpre/CommunityToolkit.Diagnostics)](https://nuget.org/packages/CommunityToolkit.Diagnostics/absoluteLatest) | A set of helper APIs (specifically, [`Guard`](https://learn.microsoft.com/windows/communitytoolkit/developer-tools/guard) and [`ThrowHelper`](https://learn.microsoft.com/windows/communitytoolkit/developer-tools/throwhelper)) that can be used for cleaner, more efficient and less error-prone argument validation and error checking. +[`CommunityToolkit.HighPerformance`](https://learn.microsoft.com/windows/communitytoolkit/high-performance/introduction) | [![CommunityToolkit.HighPerformance](https://img.shields.io/nuget/v/CommunityToolkit.HighPerformance)](https://nuget.org/packages/CommunityToolkit.HighPerformance/) | [![CommunityToolkit.HighPerformance](https://img.shields.io/nuget/vpre/CommunityToolkit.HighPerformance)](https://nuget.org/packages/CommunityToolkit.HighPerformance/absoluteLatest) | A collection of helpers for working in high-performance scenarios. It includes APIs such as [pooled buffer helpers](https://learn.microsoft.com/windows/communitytoolkit/high-performance/memoryowner), a fast [string pool](https://learn.microsoft.com/windows/communitytoolkit/high-performance/stringpool) type, a 2D variant of `Memory` and `Span` ([`Memory2D`](https://learn.microsoft.com/windows/communitytoolkit/high-performance/memory2d) and [`Span2D`](https://learn.microsoft.com/windows/communitytoolkit/high-performance/span2d)) also supporting discontiguous regions, helpers for bit shift operations (such as [`BitHelper`](https://learn.microsoft.com/windows/communitytoolkit/high-performance/span2d), also used in [Paint.NET](https://getpaint.net)), and more. +[`CommunityToolkit.Mvvm` (aka MVVM Toolkit)](https://aka.ms/mvvmtoolkit/docs) | [![CommunityToolkit.Mvvm](https://img.shields.io/nuget/v/CommunityToolkit.Mvvm)](https://nuget.org/packages/CommunityToolkit.Mvvm/) | [![CommunityToolkit.Mvvm](https://img.shields.io/nuget/vpre/CommunityToolkit.Mvvm)](https://nuget.org/packages/CommunityToolkit.Mvvm/absoluteLatest) | A fast, modular, platform-agnostic MVVM library, which is the official successor of `MvvmLight`. It's used extensively in the Microsoft Store and other first party apps. [The sample app repository is here](https://aka.ms/mvvmtoolkit/samples). ## 🙌 Getting Started -Please read the [Getting Started with the .NET Community Toolkit](https://docs.microsoft.com/windows/communitytoolkit/getting-started) page for more detailed information. +Please read the [Getting Started with the .NET Community Toolkit](https://learn.microsoft.com/windows/communitytoolkit/getting-started) page for more detailed information. ## 📃 Documentation -All documentation for the toolkit is hosted on [Microsoft Docs](https://docs.microsoft.com/windows/communitytoolkit/). +All documentation for the toolkit is hosted on [Microsoft Docs](https://learn.microsoft.com/dotnet/communitytoolkit/). -All API documentation can be found at the [.NET API Browser](https://docs.microsoft.com/dotnet/api/?view=win-comm-toolkit-dotnet-stable). +All API documentation can be found at the [.NET API Browser](https://learn.microsoft.com/dotnet/api/?view=win-comm-toolkit-dotnet-stable). ## 🚀 Contribution @@ -35,7 +31,7 @@ Check out our [.NET Community Toolkit Wiki](https://aka.ms/wct/wiki) page to lea ## 📦 NuGet Packages -NuGet is a standard package manager for .NET applications which is built into Visual Studio. When you open solution in Visual Studio, choose the *Tools* menu > *NuGet Package Manager* > *Manage NuGet packages for solution…* Enter one of the package names mentioned in [.NET Community Toolkit NuGet Packages](https://docs.microsoft.com/windows/communitytoolkit/nuget-packages) table to search for it online. +NuGet is a standard package manager for .NET applications which is built into Visual Studio. When you open solution in Visual Studio, choose the *Tools* menu > *NuGet Package Manager* > *Manage NuGet packages for solution…* Enter one of the package names mentioned in [.NET Community Toolkit NuGet Packages](https://learn.microsoft.com/windows/communitytoolkit/nuget-packages) table to search for it online. ## 🌍 Roadmap diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 3287eb2df..4738052bf 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -9,71 +9,82 @@ pr: - rel/* pool: - vmImage: windows-2022 + vmImage: windows-latest + +variables: + Build.Configuration: Release + DotNet.Tools: $(Agent.ToolsDirectory)/dotnet + DotNet.Version: 7.0.x jobs: - job: BuildBits + displayName: Build and Test solution timeoutInMinutes: 60 steps: - # Install NuGet - - task: NuGetToolInstaller@0 - displayName: Install NuGet 6.0 + # Cache .NET SDKs and Tools across pipeline runs + - task: Cache@2 + displayName: Cache .NET SDKs inputs: - versionSpec: 6.0.0 + key: 'dotnet | "$(Agent.OS)" | "$(DotNet.Version)"' + restoreKeys: | + dotnet | "$(Agent.OS)" + dotnet + path: $(DotNet.Tools) - # Install NerdBank GitVersioning - - task: DotNetCoreCLI@2 - displayName: Install NBGV tool + # Install the .NET 7 SDK + - task: UseDotNet@2 + displayName: Install the .NET 7 SDK inputs: - command: custom - custom: tool - arguments: update -g nbgv + version: $(DotNet.Version) + performMultiLevelLookup: true # Set Build Version - script: nbgv cloud displayName: Set NBGV version - # Verify headers - - pwsh: build/Update-Headers.ps1 -Verify - displayName: Verify headers + # Restore solution + - script: dotnet restore -p:Configuration=$(Build.Configuration) + displayName: Restore solution # Build solution - - script: dotnet build -c Release + - script: dotnet build --no-restore -c $(Build.Configuration) displayName: Build solution - # Run .NET 6 tests - - script: dotnet test -c Release -f net6.0 -l "trx;LogFileName=VSTestResults_net6.0.trx" - displayName: Run .NET 6 unit tests + # Test solution # - # Run .NET Core 3.1 tests - - script: dotnet test -c Release -f netcoreapp3.1 -l "trx;LogFileName=VSTestResults_netcoreapp3.1.trx" - displayName: Run .NET Core 3.1 unit tests + # Run .NET 7 unit tests + - script: dotnet test --no-build -c $(Build.Configuration) -f net7.0 -l "trx;LogFileName=VSTestResults_net7.0.trx" + displayName: Run .NET 7 unit tests + + # Run .NET 6 unit tests + - script: dotnet test --no-build -c $(Build.Configuration) -f net6.0 -l "trx;LogFileName=VSTestResults_net6.0.trx" + displayName: Run .NET 6 unit tests - # Run .NET Framework 4.7.2 tests - - script: dotnet test -c Release -f net472 -l "trx;LogFileName=VSTestResults_net472.trx" + # Run .NET Framework 4.7.2 unit tests + - script: dotnet test --no-build -c $(Build.Configuration) -f net472 -l "trx;LogFileName=VSTestResults_net472.trx" displayName: Run .NET Framework 4.7.2 unit tests # Publish test results - task: PublishTestResults@2 displayName: Publish test results inputs: - testResultsFormat: 'VSTest' - testResultsFiles: '**/VSTestResults*.trx' + testResultsFormat: VSTest + testResultsFiles: '**/TestResults/VSTestResults*.trx' condition: always() - # Create the NuGet package(s) - - script: dotnet pack -c Release - displayName: Create NuGet package(s) + # Pack solution + - script: dotnet pack --no-build -c $(Build.Configuration) + displayName: Pack solution - # Sign package(s) + # Sign packages - pwsh: build/Sign-Package.ps1 displayName: Authenticode sign packages env: SignClientUser: $(SignClientUser) SignClientSecret: $(SignClientSecret) ArtifactDirectory: bin/nupkg - condition: and(succeeded(), not(eq(variables['Build.Reason'], 'PullRequest')), not(eq(variables['SignClientSecret'], '')), not(eq(variables['SignClientUser'], ''))) + condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'), ne(variables['SignClientUser'], ''), ne(variables['SignClientSecret'], '')) # Publish build artifacts - publish: bin/nupkg diff --git a/build/Community.Toolkit.Common.props b/build/Community.Toolkit.Common.props index 557219218..a43ae645d 100644 --- a/build/Community.Toolkit.Common.props +++ b/build/Community.Toolkit.Common.props @@ -14,15 +14,27 @@ https://raw.githubusercontent.com/CommunityToolkit/dotnet/main/build/nuget.png + + true + + true - 10.0 + 11.0 enable + + + $(NoWarn);CS8500 true $(RepositoryDirectory)toolkit.snk + 002400000480000094000000060200000024000052534131000400000100010041753AF735AE6140C9508567666C51C6AB929806ADB0D210694B30AB142A060237BC741F9682E7D8D4310364B4BBA4EE89CC9D3D5CE7E5583587E8EA44DCA09977996582875E71FB54FA7B170798D853D5D8010B07219633BDB761D01AC924DA44576D6180CDCEAE537973982BB461C541541D58417A3794E34F45E6F2D129E2 diff --git a/build/Update-Headers.ps1 b/build/Update-Headers.ps1 deleted file mode 100644 index d09581d84..000000000 --- a/build/Update-Headers.ps1 +++ /dev/null @@ -1,89 +0,0 @@ -# Script to Update Header comment in C# sources within this repository. - -# This script duplicates the fuctionality provided by the 'UpdateHeaders' target in Cake script present previously. -# Since, Cake build has been removed, this fuctionality has been implimented here in this PowerShell script. - -[CmdletBinding()] -Param( - [Alias("Verify")] - [switch]$DryRun, - [switch]$Clean -) - -function Clear-BuildArtifacts { - if (Get-Command dotnet) { - Write-Host "Starting 'dotnet msbuild -t:Clean' to clean-up build outputs" - dotnet msbuild -noLogo -noAutoRsp -v:m -t:Clean - } - elseif (Get-Command msbuild) { - Write-Host "Starting 'msbuild -t:Clean' to clean-up build outputs" - msbuild -noLogo -noAutoRsp -v:m -t:Clean - } - elseif (Get-Command git) { - Write-Host "Starting 'git clean -Xdf' to clean-up build outputs" - git clean -Xdf - } - else { - Write-Warning "Can't find dotnet/msbuild/git to clean-up build outputs" - } -} - -function Get-SourceFiles ([string]$Path, [string]$Extension) { - $fileType = $Extension.TrimStart('.') - $fileFilter = "*.$fileType" - $fileExcludes = "*.g.$fileType", "*.i.$fileType", "*TemporaryGeneratedFile*.$fileType" - $sourceFiles = Get-ChildItem -Path $Path -File -Recurse -Filter $fileFilter -Exclude $fileExcludes - return $sourceFiles.Where({ !($_.FullName.Contains("\bin\") -or $_.FullName.Contains("\obj\")) }) -} - -# Set Repot Root -$repoRoot = Split-Path -Path $PSScriptRoot -Parent -Push-Location $repoRoot - -# Clean-up Repository build outputs -if ($Clean) { - Clear-BuildArtifacts -} - -# Get C# source files recursively -$sourceFiles = Get-SourceFiles -Path $repoRoot -Extension ".cs" - -Write-Host "Checking $($sourceFiles.Count) C# file header(s)" - -$hasMissing = $false -foreach ($sourceFile in $sourceFiles) { - - $oldContent = Get-Content $sourceFile -Raw | Out-String -NoNewline - - if ($oldContent.Contains("// ") -or $oldContent.Contains("// ")) { - continue - } - - $headerFilePath = Join-Path $PSScriptRoot "header.txt" - $headerContent = Get-Content $headerFilePath -Raw | Out-String - $regexHeader = "^(//.*\r?\n)*\r?\n" - $newContent = $oldContent -replace $regexHeader,$headerContent | Out-String -NoNewline - - if (-not ($newContent -eq $oldContent)) { - $sourceFilePath = [System.IO.Path]::GetRelativePath($repoRoot, $sourceFile.FullName) - - if ($DryRun) { - Write-Warning "Wrong/missing file header in '$sourceFilePath'" - $hasMissing = $true - } - else { - Write-Host "Updating '$sourceFilePath' file header..." - Set-Content -Path $sourceFile -Value $newContent -NoNewline - } - } -} - -if ($DryRun -and $hasMissing) { - Write-Error "Please run 'Update-Headers.ps1' to verify and add missing headers ins source files before commiting your changes." -} -else { - Write-Host "All $($sourceFiles.Count) C# file header(s) are up to date." -} - -# Pop out of Repo Root -Pop-Location diff --git a/build/header.txt b/build/header.txt deleted file mode 100644 index 45274bad5..000000000 --- a/build/header.txt +++ /dev/null @@ -1,3 +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. diff --git a/build/nuget.png b/build/nuget.png index cb8172be0..f1c369cef 100644 Binary files a/build/nuget.png and b/build/nuget.png differ diff --git a/dotnet Community Toolkit.sln b/dotnet Community Toolkit.sln index 32281fbde..8e1b61745 100644 --- a/dotnet Community Toolkit.sln +++ b/dotnet Community Toolkit.sln @@ -4,7 +4,7 @@ VisualStudioVersion = 17.0.31815.197 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{B30036C4-D514-4E5B-A323-587A061772CE}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Common", "CommunityToolkit.Common\CommunityToolkit.Common.csproj", "{6FE128A8-CEFA-4A61-A987-EC92DE6B538E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Common", "src\CommunityToolkit.Common\CommunityToolkit.Common.csproj", "{6FE128A8-CEFA-4A61-A987-EC92DE6B538E}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CFA75BE0-5A44-45DE-8114-426A605B062B}" ProjectSection(SolutionItems) = preProject @@ -19,9 +19,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution version.json = version.json EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm", "CommunityToolkit.Mvvm\CommunityToolkit.Mvvm.csproj", "{D82AE6E1-E612-434E-ACB2-363EE48738D3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm", "src\CommunityToolkit.Mvvm\CommunityToolkit.Mvvm.csproj", "{D82AE6E1-E612-434E-ACB2-363EE48738D3}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.HighPerformance", "CommunityToolkit.HighPerformance\CommunityToolkit.HighPerformance.csproj", "{7E30D48C-4CD8-47BE-B557-10A20391DCC4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.HighPerformance", "src\CommunityToolkit.HighPerformance\CommunityToolkit.HighPerformance.csproj", "{7E30D48C-4CD8-47BE-B557-10A20391DCC4}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.HighPerformance.UnitTests", "tests\CommunityToolkit.HighPerformance.UnitTests\CommunityToolkit.HighPerformance.UnitTests.csproj", "{D9BDBC68-3D0A-47FC-9C88-0BF769101644}" EndProject @@ -34,18 +34,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{88C6FFBE-3 ThirdPartyNotices.txt = ThirdPartyNotices.txt EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Diagnostics", "CommunityToolkit.Diagnostics\CommunityToolkit.Diagnostics.csproj", "{76F89522-CA28-458D-801D-947AB033A758}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Diagnostics", "src\CommunityToolkit.Diagnostics\CommunityToolkit.Diagnostics.csproj", "{76F89522-CA28-458D-801D-947AB033A758}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.SourceGenerators", "CommunityToolkit.Mvvm.SourceGenerators\CommunityToolkit.Mvvm.SourceGenerators.csproj", "{E24D1146-5AD8-498F-A518-4890D8BF4937}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.SourceGenerators.UnitTests", "tests\CommunityToolkit.Mvvm.SourceGenerators.UnitTests\CommunityToolkit.Mvvm.SourceGenerators.UnitTests.csproj", "{338C3BE4-2E71-4F21-AD30-03FDBB47A272}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.ExternalAssembly", "tests\CommunityToolkit.Mvvm.ExternalAssembly\CommunityToolkit.Mvvm.ExternalAssembly.csproj", "{D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.SourceGenerators.Roslyn401", "src\CommunityToolkit.Mvvm.SourceGenerators.Roslyn401\CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.csproj", "{E24D1146-5AD8-498F-A518-4890D8BF4937}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Diagnostics.UnitTests", "tests\CommunityToolkit.Diagnostics.UnitTests\CommunityToolkit.Diagnostics.UnitTests.csproj", "{35E48D4D-6433-4B70-98A9-BA544921EE04}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.UnitTests", "tests\CommunityToolkit.Mvvm.UnitTests\CommunityToolkit.Mvvm.UnitTests.csproj", "{59212E0A-878C-4097-A1CE-58CE3375F8D7}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Common.UnitTests", "tests\CommunityToolkit.Common.UnitTests\CommunityToolkit.Common.UnitTests.csproj", "{17522D0B-CA70-40B6-AFD8-8B8D45E75D92}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{CD16E790-7B7B-411E-9CE7-768E759CC22D}" @@ -65,6 +59,28 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.Disab EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.Internals.UnitTests", "tests\CommunityToolkit.Mvvm.Internals.UnitTests\CommunityToolkit.Mvvm.Internals.UnitTests.csproj", "{743D74BA-12AE-4639-AD77-B9DDA9C03255}" EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "CommunityToolkit.Mvvm.SourceGenerators", "src\CommunityToolkit.Mvvm.SourceGenerators\CommunityToolkit.Mvvm.SourceGenerators.shproj", "{5E7F1212-A54B-40CA-98C5-1FF5CD1A1638}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.SourceGenerators.Roslyn431", "src\CommunityToolkit.Mvvm.SourceGenerators.Roslyn431\CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.csproj", "{DF455C40-B18E-4890-8758-7CCCB5CA7052}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "CommunityToolkit.Mvvm.UnitTests", "tests\CommunityToolkit.Mvvm.UnitTests\CommunityToolkit.Mvvm.UnitTests.shproj", "{B8DCD82E-B53B-4249-AD4E-F9B99ACB9334}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.Roslyn401.UnitTests", "tests\CommunityToolkit.Mvvm.Roslyn401.UnitTests\CommunityToolkit.Mvvm.Roslyn401.UnitTests.csproj", "{AD9C3223-8E37-4FD4-A0D4-A45119551D3A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.Roslyn431.UnitTests", "tests\CommunityToolkit.Mvvm.Roslyn431.UnitTests\CommunityToolkit.Mvvm.Roslyn431.UnitTests.csproj", "{5B44F7F1-DCA2-4776-924E-A266F7BBF753}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "CommunityToolkit.Mvvm.SourceGenerators.UnitTests", "tests\CommunityToolkit.Mvvm.SourceGenerators.UnitTests\CommunityToolkit.Mvvm.SourceGenerators.UnitTests.shproj", "{FB59CE88-7732-4A63-B5BD-AC5681B7DA1A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests", "tests\CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests\CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests.csproj", "{F3799252-7A66-4533-89D8-B3C312052D95}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.UnitTests", "tests\CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.UnitTests\CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.UnitTests.csproj", "{FE3EA695-EA0F-4E5F-9257-E059AAA23B10}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "CommunityToolkit.Mvvm.ExternalAssembly", "tests\CommunityToolkit.Mvvm.ExternalAssembly\CommunityToolkit.Mvvm.ExternalAssembly.shproj", "{E827A9CD-405F-43E4-84C7-68CC7E845CDC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.ExternalAssembly.Roslyn401", "tests\CommunityToolkit.Mvvm.ExternalAssembly.Roslyn401\CommunityToolkit.Mvvm.ExternalAssembly.Roslyn401.csproj", "{ECFE93AA-4B98-4292-B3FA-9430D513B4F9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.ExternalAssembly.Roslyn431", "tests\CommunityToolkit.Mvvm.ExternalAssembly.Roslyn431\CommunityToolkit.Mvvm.ExternalAssembly.Roslyn431.csproj", "{4FCD501C-1BB5-465C-AD19-356DAB6600C6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -199,46 +215,6 @@ Global {E24D1146-5AD8-498F-A518-4890D8BF4937}.Release|x64.Build.0 = Release|Any CPU {E24D1146-5AD8-498F-A518-4890D8BF4937}.Release|x86.ActiveCfg = Release|Any CPU {E24D1146-5AD8-498F-A518-4890D8BF4937}.Release|x86.Build.0 = Release|Any CPU - {338C3BE4-2E71-4F21-AD30-03FDBB47A272}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {338C3BE4-2E71-4F21-AD30-03FDBB47A272}.Debug|Any CPU.Build.0 = Debug|Any CPU - {338C3BE4-2E71-4F21-AD30-03FDBB47A272}.Debug|ARM.ActiveCfg = Debug|Any CPU - {338C3BE4-2E71-4F21-AD30-03FDBB47A272}.Debug|ARM.Build.0 = Debug|Any CPU - {338C3BE4-2E71-4F21-AD30-03FDBB47A272}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {338C3BE4-2E71-4F21-AD30-03FDBB47A272}.Debug|ARM64.Build.0 = Debug|Any CPU - {338C3BE4-2E71-4F21-AD30-03FDBB47A272}.Debug|x64.ActiveCfg = Debug|Any CPU - {338C3BE4-2E71-4F21-AD30-03FDBB47A272}.Debug|x64.Build.0 = Debug|Any CPU - {338C3BE4-2E71-4F21-AD30-03FDBB47A272}.Debug|x86.ActiveCfg = Debug|Any CPU - {338C3BE4-2E71-4F21-AD30-03FDBB47A272}.Debug|x86.Build.0 = Debug|Any CPU - {338C3BE4-2E71-4F21-AD30-03FDBB47A272}.Release|Any CPU.ActiveCfg = Release|Any CPU - {338C3BE4-2E71-4F21-AD30-03FDBB47A272}.Release|Any CPU.Build.0 = Release|Any CPU - {338C3BE4-2E71-4F21-AD30-03FDBB47A272}.Release|ARM.ActiveCfg = Release|Any CPU - {338C3BE4-2E71-4F21-AD30-03FDBB47A272}.Release|ARM.Build.0 = Release|Any CPU - {338C3BE4-2E71-4F21-AD30-03FDBB47A272}.Release|ARM64.ActiveCfg = Release|Any CPU - {338C3BE4-2E71-4F21-AD30-03FDBB47A272}.Release|ARM64.Build.0 = Release|Any CPU - {338C3BE4-2E71-4F21-AD30-03FDBB47A272}.Release|x64.ActiveCfg = Release|Any CPU - {338C3BE4-2E71-4F21-AD30-03FDBB47A272}.Release|x64.Build.0 = Release|Any CPU - {338C3BE4-2E71-4F21-AD30-03FDBB47A272}.Release|x86.ActiveCfg = Release|Any CPU - {338C3BE4-2E71-4F21-AD30-03FDBB47A272}.Release|x86.Build.0 = Release|Any CPU - {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Debug|ARM.ActiveCfg = Debug|Any CPU - {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Debug|ARM.Build.0 = Debug|Any CPU - {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Debug|ARM64.Build.0 = Debug|Any CPU - {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Debug|x64.ActiveCfg = Debug|Any CPU - {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Debug|x64.Build.0 = Debug|Any CPU - {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Debug|x86.ActiveCfg = Debug|Any CPU - {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Debug|x86.Build.0 = Debug|Any CPU - {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Release|Any CPU.Build.0 = Release|Any CPU - {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Release|ARM.ActiveCfg = Release|Any CPU - {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Release|ARM.Build.0 = Release|Any CPU - {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Release|ARM64.ActiveCfg = Release|Any CPU - {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Release|ARM64.Build.0 = Release|Any CPU - {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Release|x64.ActiveCfg = Release|Any CPU - {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Release|x64.Build.0 = Release|Any CPU - {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Release|x86.ActiveCfg = Release|Any CPU - {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Release|x86.Build.0 = Release|Any CPU {35E48D4D-6433-4B70-98A9-BA544921EE04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {35E48D4D-6433-4B70-98A9-BA544921EE04}.Debug|Any CPU.Build.0 = Debug|Any CPU {35E48D4D-6433-4B70-98A9-BA544921EE04}.Debug|ARM.ActiveCfg = Debug|Any CPU @@ -259,26 +235,6 @@ Global {35E48D4D-6433-4B70-98A9-BA544921EE04}.Release|x64.Build.0 = Release|Any CPU {35E48D4D-6433-4B70-98A9-BA544921EE04}.Release|x86.ActiveCfg = Release|Any CPU {35E48D4D-6433-4B70-98A9-BA544921EE04}.Release|x86.Build.0 = Release|Any CPU - {59212E0A-878C-4097-A1CE-58CE3375F8D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {59212E0A-878C-4097-A1CE-58CE3375F8D7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {59212E0A-878C-4097-A1CE-58CE3375F8D7}.Debug|ARM.ActiveCfg = Debug|Any CPU - {59212E0A-878C-4097-A1CE-58CE3375F8D7}.Debug|ARM.Build.0 = Debug|Any CPU - {59212E0A-878C-4097-A1CE-58CE3375F8D7}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {59212E0A-878C-4097-A1CE-58CE3375F8D7}.Debug|ARM64.Build.0 = Debug|Any CPU - {59212E0A-878C-4097-A1CE-58CE3375F8D7}.Debug|x64.ActiveCfg = Debug|Any CPU - {59212E0A-878C-4097-A1CE-58CE3375F8D7}.Debug|x64.Build.0 = Debug|Any CPU - {59212E0A-878C-4097-A1CE-58CE3375F8D7}.Debug|x86.ActiveCfg = Debug|Any CPU - {59212E0A-878C-4097-A1CE-58CE3375F8D7}.Debug|x86.Build.0 = Debug|Any CPU - {59212E0A-878C-4097-A1CE-58CE3375F8D7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {59212E0A-878C-4097-A1CE-58CE3375F8D7}.Release|Any CPU.Build.0 = Release|Any CPU - {59212E0A-878C-4097-A1CE-58CE3375F8D7}.Release|ARM.ActiveCfg = Release|Any CPU - {59212E0A-878C-4097-A1CE-58CE3375F8D7}.Release|ARM.Build.0 = Release|Any CPU - {59212E0A-878C-4097-A1CE-58CE3375F8D7}.Release|ARM64.ActiveCfg = Release|Any CPU - {59212E0A-878C-4097-A1CE-58CE3375F8D7}.Release|ARM64.Build.0 = Release|Any CPU - {59212E0A-878C-4097-A1CE-58CE3375F8D7}.Release|x64.ActiveCfg = Release|Any CPU - {59212E0A-878C-4097-A1CE-58CE3375F8D7}.Release|x64.Build.0 = Release|Any CPU - {59212E0A-878C-4097-A1CE-58CE3375F8D7}.Release|x86.ActiveCfg = Release|Any CPU - {59212E0A-878C-4097-A1CE-58CE3375F8D7}.Release|x86.Build.0 = Release|Any CPU {17522D0B-CA70-40B6-AFD8-8B8D45E75D92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {17522D0B-CA70-40B6-AFD8-8B8D45E75D92}.Debug|Any CPU.Build.0 = Debug|Any CPU {17522D0B-CA70-40B6-AFD8-8B8D45E75D92}.Debug|ARM.ActiveCfg = Debug|Any CPU @@ -339,6 +295,146 @@ Global {743D74BA-12AE-4639-AD77-B9DDA9C03255}.Release|x64.Build.0 = Release|Any CPU {743D74BA-12AE-4639-AD77-B9DDA9C03255}.Release|x86.ActiveCfg = Release|Any CPU {743D74BA-12AE-4639-AD77-B9DDA9C03255}.Release|x86.Build.0 = Release|Any CPU + {DF455C40-B18E-4890-8758-7CCCB5CA7052}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DF455C40-B18E-4890-8758-7CCCB5CA7052}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DF455C40-B18E-4890-8758-7CCCB5CA7052}.Debug|ARM.ActiveCfg = Debug|Any CPU + {DF455C40-B18E-4890-8758-7CCCB5CA7052}.Debug|ARM.Build.0 = Debug|Any CPU + {DF455C40-B18E-4890-8758-7CCCB5CA7052}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {DF455C40-B18E-4890-8758-7CCCB5CA7052}.Debug|ARM64.Build.0 = Debug|Any CPU + {DF455C40-B18E-4890-8758-7CCCB5CA7052}.Debug|x64.ActiveCfg = Debug|Any CPU + {DF455C40-B18E-4890-8758-7CCCB5CA7052}.Debug|x64.Build.0 = Debug|Any CPU + {DF455C40-B18E-4890-8758-7CCCB5CA7052}.Debug|x86.ActiveCfg = Debug|Any CPU + {DF455C40-B18E-4890-8758-7CCCB5CA7052}.Debug|x86.Build.0 = Debug|Any CPU + {DF455C40-B18E-4890-8758-7CCCB5CA7052}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DF455C40-B18E-4890-8758-7CCCB5CA7052}.Release|Any CPU.Build.0 = Release|Any CPU + {DF455C40-B18E-4890-8758-7CCCB5CA7052}.Release|ARM.ActiveCfg = Release|Any CPU + {DF455C40-B18E-4890-8758-7CCCB5CA7052}.Release|ARM.Build.0 = Release|Any CPU + {DF455C40-B18E-4890-8758-7CCCB5CA7052}.Release|ARM64.ActiveCfg = Release|Any CPU + {DF455C40-B18E-4890-8758-7CCCB5CA7052}.Release|ARM64.Build.0 = Release|Any CPU + {DF455C40-B18E-4890-8758-7CCCB5CA7052}.Release|x64.ActiveCfg = Release|Any CPU + {DF455C40-B18E-4890-8758-7CCCB5CA7052}.Release|x64.Build.0 = Release|Any CPU + {DF455C40-B18E-4890-8758-7CCCB5CA7052}.Release|x86.ActiveCfg = Release|Any CPU + {DF455C40-B18E-4890-8758-7CCCB5CA7052}.Release|x86.Build.0 = Release|Any CPU + {AD9C3223-8E37-4FD4-A0D4-A45119551D3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AD9C3223-8E37-4FD4-A0D4-A45119551D3A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AD9C3223-8E37-4FD4-A0D4-A45119551D3A}.Debug|ARM.ActiveCfg = Debug|Any CPU + {AD9C3223-8E37-4FD4-A0D4-A45119551D3A}.Debug|ARM.Build.0 = Debug|Any CPU + {AD9C3223-8E37-4FD4-A0D4-A45119551D3A}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {AD9C3223-8E37-4FD4-A0D4-A45119551D3A}.Debug|ARM64.Build.0 = Debug|Any CPU + {AD9C3223-8E37-4FD4-A0D4-A45119551D3A}.Debug|x64.ActiveCfg = Debug|Any CPU + {AD9C3223-8E37-4FD4-A0D4-A45119551D3A}.Debug|x64.Build.0 = Debug|Any CPU + {AD9C3223-8E37-4FD4-A0D4-A45119551D3A}.Debug|x86.ActiveCfg = Debug|Any CPU + {AD9C3223-8E37-4FD4-A0D4-A45119551D3A}.Debug|x86.Build.0 = Debug|Any CPU + {AD9C3223-8E37-4FD4-A0D4-A45119551D3A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AD9C3223-8E37-4FD4-A0D4-A45119551D3A}.Release|Any CPU.Build.0 = Release|Any CPU + {AD9C3223-8E37-4FD4-A0D4-A45119551D3A}.Release|ARM.ActiveCfg = Release|Any CPU + {AD9C3223-8E37-4FD4-A0D4-A45119551D3A}.Release|ARM.Build.0 = Release|Any CPU + {AD9C3223-8E37-4FD4-A0D4-A45119551D3A}.Release|ARM64.ActiveCfg = Release|Any CPU + {AD9C3223-8E37-4FD4-A0D4-A45119551D3A}.Release|ARM64.Build.0 = Release|Any CPU + {AD9C3223-8E37-4FD4-A0D4-A45119551D3A}.Release|x64.ActiveCfg = Release|Any CPU + {AD9C3223-8E37-4FD4-A0D4-A45119551D3A}.Release|x64.Build.0 = Release|Any CPU + {AD9C3223-8E37-4FD4-A0D4-A45119551D3A}.Release|x86.ActiveCfg = Release|Any CPU + {AD9C3223-8E37-4FD4-A0D4-A45119551D3A}.Release|x86.Build.0 = Release|Any CPU + {5B44F7F1-DCA2-4776-924E-A266F7BBF753}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5B44F7F1-DCA2-4776-924E-A266F7BBF753}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5B44F7F1-DCA2-4776-924E-A266F7BBF753}.Debug|ARM.ActiveCfg = Debug|Any CPU + {5B44F7F1-DCA2-4776-924E-A266F7BBF753}.Debug|ARM.Build.0 = Debug|Any CPU + {5B44F7F1-DCA2-4776-924E-A266F7BBF753}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {5B44F7F1-DCA2-4776-924E-A266F7BBF753}.Debug|ARM64.Build.0 = Debug|Any CPU + {5B44F7F1-DCA2-4776-924E-A266F7BBF753}.Debug|x64.ActiveCfg = Debug|Any CPU + {5B44F7F1-DCA2-4776-924E-A266F7BBF753}.Debug|x64.Build.0 = Debug|Any CPU + {5B44F7F1-DCA2-4776-924E-A266F7BBF753}.Debug|x86.ActiveCfg = Debug|Any CPU + {5B44F7F1-DCA2-4776-924E-A266F7BBF753}.Debug|x86.Build.0 = Debug|Any CPU + {5B44F7F1-DCA2-4776-924E-A266F7BBF753}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5B44F7F1-DCA2-4776-924E-A266F7BBF753}.Release|Any CPU.Build.0 = Release|Any CPU + {5B44F7F1-DCA2-4776-924E-A266F7BBF753}.Release|ARM.ActiveCfg = Release|Any CPU + {5B44F7F1-DCA2-4776-924E-A266F7BBF753}.Release|ARM.Build.0 = Release|Any CPU + {5B44F7F1-DCA2-4776-924E-A266F7BBF753}.Release|ARM64.ActiveCfg = Release|Any CPU + {5B44F7F1-DCA2-4776-924E-A266F7BBF753}.Release|ARM64.Build.0 = Release|Any CPU + {5B44F7F1-DCA2-4776-924E-A266F7BBF753}.Release|x64.ActiveCfg = Release|Any CPU + {5B44F7F1-DCA2-4776-924E-A266F7BBF753}.Release|x64.Build.0 = Release|Any CPU + {5B44F7F1-DCA2-4776-924E-A266F7BBF753}.Release|x86.ActiveCfg = Release|Any CPU + {5B44F7F1-DCA2-4776-924E-A266F7BBF753}.Release|x86.Build.0 = Release|Any CPU + {F3799252-7A66-4533-89D8-B3C312052D95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F3799252-7A66-4533-89D8-B3C312052D95}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F3799252-7A66-4533-89D8-B3C312052D95}.Debug|ARM.ActiveCfg = Debug|Any CPU + {F3799252-7A66-4533-89D8-B3C312052D95}.Debug|ARM.Build.0 = Debug|Any CPU + {F3799252-7A66-4533-89D8-B3C312052D95}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {F3799252-7A66-4533-89D8-B3C312052D95}.Debug|ARM64.Build.0 = Debug|Any CPU + {F3799252-7A66-4533-89D8-B3C312052D95}.Debug|x64.ActiveCfg = Debug|Any CPU + {F3799252-7A66-4533-89D8-B3C312052D95}.Debug|x64.Build.0 = Debug|Any CPU + {F3799252-7A66-4533-89D8-B3C312052D95}.Debug|x86.ActiveCfg = Debug|Any CPU + {F3799252-7A66-4533-89D8-B3C312052D95}.Debug|x86.Build.0 = Debug|Any CPU + {F3799252-7A66-4533-89D8-B3C312052D95}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F3799252-7A66-4533-89D8-B3C312052D95}.Release|Any CPU.Build.0 = Release|Any CPU + {F3799252-7A66-4533-89D8-B3C312052D95}.Release|ARM.ActiveCfg = Release|Any CPU + {F3799252-7A66-4533-89D8-B3C312052D95}.Release|ARM.Build.0 = Release|Any CPU + {F3799252-7A66-4533-89D8-B3C312052D95}.Release|ARM64.ActiveCfg = Release|Any CPU + {F3799252-7A66-4533-89D8-B3C312052D95}.Release|ARM64.Build.0 = Release|Any CPU + {F3799252-7A66-4533-89D8-B3C312052D95}.Release|x64.ActiveCfg = Release|Any CPU + {F3799252-7A66-4533-89D8-B3C312052D95}.Release|x64.Build.0 = Release|Any CPU + {F3799252-7A66-4533-89D8-B3C312052D95}.Release|x86.ActiveCfg = Release|Any CPU + {F3799252-7A66-4533-89D8-B3C312052D95}.Release|x86.Build.0 = Release|Any CPU + {FE3EA695-EA0F-4E5F-9257-E059AAA23B10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FE3EA695-EA0F-4E5F-9257-E059AAA23B10}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FE3EA695-EA0F-4E5F-9257-E059AAA23B10}.Debug|ARM.ActiveCfg = Debug|Any CPU + {FE3EA695-EA0F-4E5F-9257-E059AAA23B10}.Debug|ARM.Build.0 = Debug|Any CPU + {FE3EA695-EA0F-4E5F-9257-E059AAA23B10}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {FE3EA695-EA0F-4E5F-9257-E059AAA23B10}.Debug|ARM64.Build.0 = Debug|Any CPU + {FE3EA695-EA0F-4E5F-9257-E059AAA23B10}.Debug|x64.ActiveCfg = Debug|Any CPU + {FE3EA695-EA0F-4E5F-9257-E059AAA23B10}.Debug|x64.Build.0 = Debug|Any CPU + {FE3EA695-EA0F-4E5F-9257-E059AAA23B10}.Debug|x86.ActiveCfg = Debug|Any CPU + {FE3EA695-EA0F-4E5F-9257-E059AAA23B10}.Debug|x86.Build.0 = Debug|Any CPU + {FE3EA695-EA0F-4E5F-9257-E059AAA23B10}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FE3EA695-EA0F-4E5F-9257-E059AAA23B10}.Release|Any CPU.Build.0 = Release|Any CPU + {FE3EA695-EA0F-4E5F-9257-E059AAA23B10}.Release|ARM.ActiveCfg = Release|Any CPU + {FE3EA695-EA0F-4E5F-9257-E059AAA23B10}.Release|ARM.Build.0 = Release|Any CPU + {FE3EA695-EA0F-4E5F-9257-E059AAA23B10}.Release|ARM64.ActiveCfg = Release|Any CPU + {FE3EA695-EA0F-4E5F-9257-E059AAA23B10}.Release|ARM64.Build.0 = Release|Any CPU + {FE3EA695-EA0F-4E5F-9257-E059AAA23B10}.Release|x64.ActiveCfg = Release|Any CPU + {FE3EA695-EA0F-4E5F-9257-E059AAA23B10}.Release|x64.Build.0 = Release|Any CPU + {FE3EA695-EA0F-4E5F-9257-E059AAA23B10}.Release|x86.ActiveCfg = Release|Any CPU + {FE3EA695-EA0F-4E5F-9257-E059AAA23B10}.Release|x86.Build.0 = Release|Any CPU + {ECFE93AA-4B98-4292-B3FA-9430D513B4F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ECFE93AA-4B98-4292-B3FA-9430D513B4F9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ECFE93AA-4B98-4292-B3FA-9430D513B4F9}.Debug|ARM.ActiveCfg = Debug|Any CPU + {ECFE93AA-4B98-4292-B3FA-9430D513B4F9}.Debug|ARM.Build.0 = Debug|Any CPU + {ECFE93AA-4B98-4292-B3FA-9430D513B4F9}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {ECFE93AA-4B98-4292-B3FA-9430D513B4F9}.Debug|ARM64.Build.0 = Debug|Any CPU + {ECFE93AA-4B98-4292-B3FA-9430D513B4F9}.Debug|x64.ActiveCfg = Debug|Any CPU + {ECFE93AA-4B98-4292-B3FA-9430D513B4F9}.Debug|x64.Build.0 = Debug|Any CPU + {ECFE93AA-4B98-4292-B3FA-9430D513B4F9}.Debug|x86.ActiveCfg = Debug|Any CPU + {ECFE93AA-4B98-4292-B3FA-9430D513B4F9}.Debug|x86.Build.0 = Debug|Any CPU + {ECFE93AA-4B98-4292-B3FA-9430D513B4F9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ECFE93AA-4B98-4292-B3FA-9430D513B4F9}.Release|Any CPU.Build.0 = Release|Any CPU + {ECFE93AA-4B98-4292-B3FA-9430D513B4F9}.Release|ARM.ActiveCfg = Release|Any CPU + {ECFE93AA-4B98-4292-B3FA-9430D513B4F9}.Release|ARM.Build.0 = Release|Any CPU + {ECFE93AA-4B98-4292-B3FA-9430D513B4F9}.Release|ARM64.ActiveCfg = Release|Any CPU + {ECFE93AA-4B98-4292-B3FA-9430D513B4F9}.Release|ARM64.Build.0 = Release|Any CPU + {ECFE93AA-4B98-4292-B3FA-9430D513B4F9}.Release|x64.ActiveCfg = Release|Any CPU + {ECFE93AA-4B98-4292-B3FA-9430D513B4F9}.Release|x64.Build.0 = Release|Any CPU + {ECFE93AA-4B98-4292-B3FA-9430D513B4F9}.Release|x86.ActiveCfg = Release|Any CPU + {ECFE93AA-4B98-4292-B3FA-9430D513B4F9}.Release|x86.Build.0 = Release|Any CPU + {4FCD501C-1BB5-465C-AD19-356DAB6600C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FCD501C-1BB5-465C-AD19-356DAB6600C6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FCD501C-1BB5-465C-AD19-356DAB6600C6}.Debug|ARM.ActiveCfg = Debug|Any CPU + {4FCD501C-1BB5-465C-AD19-356DAB6600C6}.Debug|ARM.Build.0 = Debug|Any CPU + {4FCD501C-1BB5-465C-AD19-356DAB6600C6}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {4FCD501C-1BB5-465C-AD19-356DAB6600C6}.Debug|ARM64.Build.0 = Debug|Any CPU + {4FCD501C-1BB5-465C-AD19-356DAB6600C6}.Debug|x64.ActiveCfg = Debug|Any CPU + {4FCD501C-1BB5-465C-AD19-356DAB6600C6}.Debug|x64.Build.0 = Debug|Any CPU + {4FCD501C-1BB5-465C-AD19-356DAB6600C6}.Debug|x86.ActiveCfg = Debug|Any CPU + {4FCD501C-1BB5-465C-AD19-356DAB6600C6}.Debug|x86.Build.0 = Debug|Any CPU + {4FCD501C-1BB5-465C-AD19-356DAB6600C6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FCD501C-1BB5-465C-AD19-356DAB6600C6}.Release|Any CPU.Build.0 = Release|Any CPU + {4FCD501C-1BB5-465C-AD19-356DAB6600C6}.Release|ARM.ActiveCfg = Release|Any CPU + {4FCD501C-1BB5-465C-AD19-356DAB6600C6}.Release|ARM.Build.0 = Release|Any CPU + {4FCD501C-1BB5-465C-AD19-356DAB6600C6}.Release|ARM64.ActiveCfg = Release|Any CPU + {4FCD501C-1BB5-465C-AD19-356DAB6600C6}.Release|ARM64.Build.0 = Release|Any CPU + {4FCD501C-1BB5-465C-AD19-356DAB6600C6}.Release|x64.ActiveCfg = Release|Any CPU + {4FCD501C-1BB5-465C-AD19-356DAB6600C6}.Release|x64.Build.0 = Release|Any CPU + {4FCD501C-1BB5-465C-AD19-356DAB6600C6}.Release|x86.ActiveCfg = Release|Any CPU + {4FCD501C-1BB5-465C-AD19-356DAB6600C6}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -346,17 +442,37 @@ Global GlobalSection(NestedProjects) = preSolution {D9BDBC68-3D0A-47FC-9C88-0BF769101644} = {B30036C4-D514-4E5B-A323-587A061772CE} {88C6FFBE-322D-4CEA-842B-B2CB281D357D} = {CFA75BE0-5A44-45DE-8114-426A605B062B} - {338C3BE4-2E71-4F21-AD30-03FDBB47A272} = {B30036C4-D514-4E5B-A323-587A061772CE} - {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1} = {B30036C4-D514-4E5B-A323-587A061772CE} {35E48D4D-6433-4B70-98A9-BA544921EE04} = {B30036C4-D514-4E5B-A323-587A061772CE} - {59212E0A-878C-4097-A1CE-58CE3375F8D7} = {B30036C4-D514-4E5B-A323-587A061772CE} {17522D0B-CA70-40B6-AFD8-8B8D45E75D92} = {B30036C4-D514-4E5B-A323-587A061772CE} {CD16E790-7B7B-411E-9CE7-768E759CC22D} = {CFA75BE0-5A44-45DE-8114-426A605B062B} {6640D447-C28D-4DBB-91F4-3ADCE0CA64AD} = {B30036C4-D514-4E5B-A323-587A061772CE} {9E09DA49-4389-4ECE-8B68-EBDB1221DA90} = {6640D447-C28D-4DBB-91F4-3ADCE0CA64AD} {743D74BA-12AE-4639-AD77-B9DDA9C03255} = {B30036C4-D514-4E5B-A323-587A061772CE} + {B8DCD82E-B53B-4249-AD4E-F9B99ACB9334} = {B30036C4-D514-4E5B-A323-587A061772CE} + {AD9C3223-8E37-4FD4-A0D4-A45119551D3A} = {B30036C4-D514-4E5B-A323-587A061772CE} + {5B44F7F1-DCA2-4776-924E-A266F7BBF753} = {B30036C4-D514-4E5B-A323-587A061772CE} + {FB59CE88-7732-4A63-B5BD-AC5681B7DA1A} = {B30036C4-D514-4E5B-A323-587A061772CE} + {F3799252-7A66-4533-89D8-B3C312052D95} = {B30036C4-D514-4E5B-A323-587A061772CE} + {FE3EA695-EA0F-4E5F-9257-E059AAA23B10} = {B30036C4-D514-4E5B-A323-587A061772CE} + {E827A9CD-405F-43E4-84C7-68CC7E845CDC} = {B30036C4-D514-4E5B-A323-587A061772CE} + {ECFE93AA-4B98-4292-B3FA-9430D513B4F9} = {B30036C4-D514-4E5B-A323-587A061772CE} + {4FCD501C-1BB5-465C-AD19-356DAB6600C6} = {B30036C4-D514-4E5B-A323-587A061772CE} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5403B0C4-F244-4F73-A35C-FE664D0F4345} EndGlobalSection + GlobalSection(SharedMSBuildProjectFiles) = preSolution + tests\CommunityToolkit.Mvvm.ExternalAssembly\CommunityToolkit.Mvvm.ExternalAssembly.projitems*{4fcd501c-1bb5-465c-ad19-356dab6600c6}*SharedItemsImports = 5 + tests\CommunityToolkit.Mvvm.UnitTests\CommunityToolkit.Mvvm.UnitTests.projitems*{5b44f7f1-dca2-4776-924e-a266f7bbf753}*SharedItemsImports = 5 + src\CommunityToolkit.Mvvm.SourceGenerators\CommunityToolkit.Mvvm.SourceGenerators.projitems*{5e7f1212-a54b-40ca-98c5-1ff5cd1a1638}*SharedItemsImports = 13 + tests\CommunityToolkit.Mvvm.UnitTests\CommunityToolkit.Mvvm.UnitTests.projitems*{ad9c3223-8e37-4fd4-a0d4-a45119551d3a}*SharedItemsImports = 5 + tests\CommunityToolkit.Mvvm.UnitTests\CommunityToolkit.Mvvm.UnitTests.projitems*{b8dcd82e-b53b-4249-ad4e-f9b99acb9334}*SharedItemsImports = 13 + src\CommunityToolkit.Mvvm.SourceGenerators\CommunityToolkit.Mvvm.SourceGenerators.projitems*{df455c40-b18e-4890-8758-7cccb5ca7052}*SharedItemsImports = 5 + src\CommunityToolkit.Mvvm.SourceGenerators\CommunityToolkit.Mvvm.SourceGenerators.projitems*{e24d1146-5ad8-498f-a518-4890d8bf4937}*SharedItemsImports = 5 + tests\CommunityToolkit.Mvvm.ExternalAssembly\CommunityToolkit.Mvvm.ExternalAssembly.projitems*{e827a9cd-405f-43e4-84c7-68cc7e845cdc}*SharedItemsImports = 13 + tests\CommunityToolkit.Mvvm.ExternalAssembly\CommunityToolkit.Mvvm.ExternalAssembly.projitems*{ecfe93aa-4b98-4292-b3fa-9430d513b4f9}*SharedItemsImports = 5 + tests\CommunityToolkit.Mvvm.SourceGenerators.UnitTests\CommunityToolkit.Mvvm.SourceGenerators.UnitTests.projitems*{f3799252-7a66-4533-89d8-b3c312052d95}*SharedItemsImports = 5 + tests\CommunityToolkit.Mvvm.SourceGenerators.UnitTests\CommunityToolkit.Mvvm.SourceGenerators.UnitTests.projitems*{fb59ce88-7732-4a63-b5bd-ac5681b7da1a}*SharedItemsImports = 13 + tests\CommunityToolkit.Mvvm.SourceGenerators.UnitTests\CommunityToolkit.Mvvm.SourceGenerators.UnitTests.projitems*{fe3ea695-ea0f-4e5f-9257-e059aaa23b10}*SharedItemsImports = 5 + EndGlobalSection EndGlobal diff --git a/CommunityToolkit.Common/CommunityToolkit.Common.csproj b/src/CommunityToolkit.Common/CommunityToolkit.Common.csproj similarity index 67% rename from CommunityToolkit.Common/CommunityToolkit.Common.csproj rename to src/CommunityToolkit.Common/CommunityToolkit.Common.csproj index cb1d362d2..f2d7cb70b 100644 --- a/CommunityToolkit.Common/CommunityToolkit.Common.csproj +++ b/src/CommunityToolkit.Common/CommunityToolkit.Common.csproj @@ -14,12 +14,13 @@ Incremental;Loading;Collection;IncrementalLoadingCollection;String;Array;Extensions;Helpers - - - NETSTANDARD2_1_OR_GREATER - true - true + + + + System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute; + System.Diagnostics.CodeAnalysis.NotNullWhenAttribute; + System.Runtime.CompilerServices.SkipLocalsInitAttribute; + \ No newline at end of file diff --git a/CommunityToolkit.Common/Converters.cs b/src/CommunityToolkit.Common/Converters.cs similarity index 95% rename from CommunityToolkit.Common/Converters.cs rename to src/CommunityToolkit.Common/Converters.cs index 756c4930c..0bd22076e 100644 --- a/CommunityToolkit.Common/Converters.cs +++ b/src/CommunityToolkit.Common/Converters.cs @@ -42,7 +42,7 @@ public static string ToFileSizeString(long size) } else { - return ((size >> 50) / 1024F).ToString("F0") + " EB"; + return ((size >> 50) / 1024F).ToString("F1") + " EB"; } } } diff --git a/CommunityToolkit.Common/Deferred/DeferredCancelEventArgs.cs b/src/CommunityToolkit.Common/Deferred/DeferredCancelEventArgs.cs similarity index 100% rename from CommunityToolkit.Common/Deferred/DeferredCancelEventArgs.cs rename to src/CommunityToolkit.Common/Deferred/DeferredCancelEventArgs.cs diff --git a/CommunityToolkit.Common/Deferred/DeferredEventArgs.cs b/src/CommunityToolkit.Common/Deferred/DeferredEventArgs.cs similarity index 100% rename from CommunityToolkit.Common/Deferred/DeferredEventArgs.cs rename to src/CommunityToolkit.Common/Deferred/DeferredEventArgs.cs diff --git a/CommunityToolkit.Common/Deferred/EventDeferral.cs b/src/CommunityToolkit.Common/Deferred/EventDeferral.cs similarity index 100% rename from CommunityToolkit.Common/Deferred/EventDeferral.cs rename to src/CommunityToolkit.Common/Deferred/EventDeferral.cs diff --git a/CommunityToolkit.Common/Extensions/ArrayExtensions.cs b/src/CommunityToolkit.Common/Extensions/ArrayExtensions.cs similarity index 100% rename from CommunityToolkit.Common/Extensions/ArrayExtensions.cs rename to src/CommunityToolkit.Common/Extensions/ArrayExtensions.cs diff --git a/CommunityToolkit.Common/Extensions/EventHandlerExtensions.cs b/src/CommunityToolkit.Common/Extensions/EventHandlerExtensions.cs similarity index 96% rename from CommunityToolkit.Common/Extensions/EventHandlerExtensions.cs rename to src/CommunityToolkit.Common/Extensions/EventHandlerExtensions.cs index f5d095e54..137d0c32e 100644 --- a/CommunityToolkit.Common/Extensions/EventHandlerExtensions.cs +++ b/src/CommunityToolkit.Common/Extensions/EventHandlerExtensions.cs @@ -54,11 +54,11 @@ public static Task InvokeAsync(this EventHandler? eventHandler, object sen invocationDelegate(sender, eventArgs); #pragma warning disable CS0618 // Type or member is obsolete - EventDeferral? deferral = eventArgs.GetCurrentDeferralAndReset(); + EventDeferral? deferral = eventArgs.GetCurrentDeferralAndReset(); return deferral?.WaitForCompletion(cancellationToken) ?? Task.CompletedTask; #pragma warning restore CS0618 // Type or member is obsolete - }) + }) .ToArray(); return Task.WhenAll(tasks); diff --git a/CommunityToolkit.Common/Extensions/ISettingsStorageHelperExtensions.cs b/src/CommunityToolkit.Common/Extensions/ISettingsStorageHelperExtensions.cs similarity index 100% rename from CommunityToolkit.Common/Extensions/ISettingsStorageHelperExtensions.cs rename to src/CommunityToolkit.Common/Extensions/ISettingsStorageHelperExtensions.cs diff --git a/CommunityToolkit.Common/Extensions/StringExtensions.cs b/src/CommunityToolkit.Common/Extensions/StringExtensions.cs similarity index 99% rename from CommunityToolkit.Common/Extensions/StringExtensions.cs rename to src/CommunityToolkit.Common/Extensions/StringExtensions.cs index c94d84a64..417e15945 100644 --- a/CommunityToolkit.Common/Extensions/StringExtensions.cs +++ b/src/CommunityToolkit.Common/Extensions/StringExtensions.cs @@ -96,7 +96,7 @@ public static bool IsNumeric([NotNullWhen(true)] this string? str) /// /// HTML string. /// Decoded HTML string. - [return: NotNullIfNotNull("htmlText")] + [return: NotNullIfNotNull(nameof(htmlText))] public static string? DecodeHtml(this string? htmlText) { if (htmlText is null) diff --git a/CommunityToolkit.Common/Extensions/TaskExtensions.cs b/src/CommunityToolkit.Common/Extensions/TaskExtensions.cs similarity index 94% rename from CommunityToolkit.Common/Extensions/TaskExtensions.cs rename to src/CommunityToolkit.Common/Extensions/TaskExtensions.cs index 07eac6db4..fd59fdd65 100644 --- a/CommunityToolkit.Common/Extensions/TaskExtensions.cs +++ b/src/CommunityToolkit.Common/Extensions/TaskExtensions.cs @@ -26,12 +26,10 @@ public static class TaskExtensions /// and uses reflection to access the property and boxes the result if it's /// a value type, which adds overhead. It should only be used when using generics is not possible. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static object? GetResultOrDefault( #if NET6_0_OR_GREATER - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] + [RequiresUnreferencedCode("This method uses reflection to try to access the Task.Result property of the input Task instance.")] #endif - this Task task) + public static object? GetResultOrDefault(this Task task) { // Check if the instance is a completed Task if ( diff --git a/CommunityToolkit.Common/Helpers/ObjectStorage/DirectoryItemType.cs b/src/CommunityToolkit.Common/Helpers/ObjectStorage/DirectoryItemType.cs similarity index 100% rename from CommunityToolkit.Common/Helpers/ObjectStorage/DirectoryItemType.cs rename to src/CommunityToolkit.Common/Helpers/ObjectStorage/DirectoryItemType.cs diff --git a/CommunityToolkit.Common/Helpers/ObjectStorage/IFileStorageHelper.cs b/src/CommunityToolkit.Common/Helpers/ObjectStorage/IFileStorageHelper.cs similarity index 100% rename from CommunityToolkit.Common/Helpers/ObjectStorage/IFileStorageHelper.cs rename to src/CommunityToolkit.Common/Helpers/ObjectStorage/IFileStorageHelper.cs diff --git a/CommunityToolkit.Common/Helpers/ObjectStorage/IObjectSerializer.cs b/src/CommunityToolkit.Common/Helpers/ObjectStorage/IObjectSerializer.cs similarity index 100% rename from CommunityToolkit.Common/Helpers/ObjectStorage/IObjectSerializer.cs rename to src/CommunityToolkit.Common/Helpers/ObjectStorage/IObjectSerializer.cs diff --git a/CommunityToolkit.Common/Helpers/ObjectStorage/ISettingsStorageHelper.cs b/src/CommunityToolkit.Common/Helpers/ObjectStorage/ISettingsStorageHelper.cs similarity index 100% rename from CommunityToolkit.Common/Helpers/ObjectStorage/ISettingsStorageHelper.cs rename to src/CommunityToolkit.Common/Helpers/ObjectStorage/ISettingsStorageHelper.cs diff --git a/CommunityToolkit.Common/Helpers/ObjectStorage/SystemSerializer.cs b/src/CommunityToolkit.Common/Helpers/ObjectStorage/SystemSerializer.cs similarity index 100% rename from CommunityToolkit.Common/Helpers/ObjectStorage/SystemSerializer.cs rename to src/CommunityToolkit.Common/Helpers/ObjectStorage/SystemSerializer.cs diff --git a/CommunityToolkit.Common/IncrementalLoadingCollection/IIncrementalSource.cs b/src/CommunityToolkit.Common/IncrementalLoadingCollection/IIncrementalSource.cs similarity index 94% rename from CommunityToolkit.Common/IncrementalLoadingCollection/IIncrementalSource.cs rename to src/CommunityToolkit.Common/IncrementalLoadingCollection/IIncrementalSource.cs index dc6f8af7d..9470e550b 100644 --- a/CommunityToolkit.Common/IncrementalLoadingCollection/IIncrementalSource.cs +++ b/src/CommunityToolkit.Common/IncrementalLoadingCollection/IIncrementalSource.cs @@ -29,5 +29,5 @@ public interface IIncrementalSource /// /// Returns a collection of . /// - Task> GetPagedItemsAsync(int pageIndex, int pageSize, CancellationToken cancellationToken = default(CancellationToken)); + Task> GetPagedItemsAsync(int pageIndex, int pageSize, CancellationToken cancellationToken = default); } diff --git a/CommunityToolkit.Diagnostics/CommunityToolkit.Diagnostics.csproj b/src/CommunityToolkit.Diagnostics/CommunityToolkit.Diagnostics.csproj similarity index 81% rename from CommunityToolkit.Diagnostics/CommunityToolkit.Diagnostics.csproj rename to src/CommunityToolkit.Diagnostics/CommunityToolkit.Diagnostics.csproj index 46cc03aab..6cd510f16 100644 --- a/CommunityToolkit.Diagnostics/CommunityToolkit.Diagnostics.csproj +++ b/src/CommunityToolkit.Diagnostics/CommunityToolkit.Diagnostics.csproj @@ -15,35 +15,34 @@ - - + + + - - NETSTANDARD2_1_OR_GREATER - - - - - - - - NETSTANDARD2_1_OR_GREATER - true - true - - + + + + System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute; + System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute; + System.Diagnostics.CodeAnalysis.NotNullAttribute; + System.Diagnostics.StackTraceHiddenAttribute; + System.Runtime.CompilerServices.CallerArgumentExpressionAttribute; + System.Runtime.CompilerServices.SkipLocalsInitAttribute; + + + TextTemplatingFileGenerator diff --git a/CommunityToolkit.Diagnostics/Extensions/TypeExtensions.cs b/src/CommunityToolkit.Diagnostics/Extensions/TypeExtensions.cs similarity index 100% rename from CommunityToolkit.Diagnostics/Extensions/TypeExtensions.cs rename to src/CommunityToolkit.Diagnostics/Extensions/TypeExtensions.cs diff --git a/CommunityToolkit.Diagnostics/Extensions/ValueTypeExtensions.cs b/src/CommunityToolkit.Diagnostics/Extensions/ValueTypeExtensions.cs similarity index 98% rename from CommunityToolkit.Diagnostics/Extensions/ValueTypeExtensions.cs rename to src/CommunityToolkit.Diagnostics/Extensions/ValueTypeExtensions.cs index 445773c05..d6306cdbe 100644 --- a/CommunityToolkit.Diagnostics/Extensions/ValueTypeExtensions.cs +++ b/src/CommunityToolkit.Diagnostics/Extensions/ValueTypeExtensions.cs @@ -44,7 +44,7 @@ public static class ValueTypeExtensions public static unsafe string ToHexString(this T value) where T : unmanaged { - int sizeOfT = Unsafe.SizeOf(); + int sizeOfT = sizeof(T); int bufferSize = (2 * sizeOfT) + 2; char* p = stackalloc char[bufferSize]; diff --git a/CommunityToolkit.Diagnostics/Generated/Guard.Collection.g.cs b/src/CommunityToolkit.Diagnostics/Generated/Guard.Collection.g.cs similarity index 93% rename from CommunityToolkit.Diagnostics/Generated/Guard.Collection.g.cs rename to src/CommunityToolkit.Diagnostics/Generated/Guard.Collection.g.cs index 4208dabe4..fcf705169 100644 --- a/CommunityToolkit.Diagnostics/Generated/Guard.Collection.g.cs +++ b/src/CommunityToolkit.Diagnostics/Generated/Guard.Collection.g.cs @@ -22,7 +22,7 @@ partial class Guard /// The name of the input parameter being tested. /// Thrown if the size of is != 0. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsEmpty(Span span, [CallerArgumentExpression("span")] string name = "") + public static void IsEmpty(Span span, [CallerArgumentExpression(nameof(span))] string name = "") { if (span.Length == 0) { @@ -40,7 +40,7 @@ public static void IsEmpty(Span span, [CallerArgumentExpression("span")] s /// The name of the input parameter being tested. /// Thrown if the size of is == 0. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotEmpty(Span span, [CallerArgumentExpression("span")] string name = "") + public static void IsNotEmpty(Span span, [CallerArgumentExpression(nameof(span))] string name = "") { if (span.Length != 0) { @@ -59,7 +59,7 @@ public static void IsNotEmpty(Span span, [CallerArgumentExpression("span") /// The name of the input parameter being tested. /// Thrown if the size of is != . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeEqualTo(Span span, int size, [CallerArgumentExpression("span")] string name = "") + public static void HasSizeEqualTo(Span span, int size, [CallerArgumentExpression(nameof(span))] string name = "") { if (span.Length == size) { @@ -78,7 +78,7 @@ public static void HasSizeEqualTo(Span span, int size, [CallerArgumentExpr /// The name of the input parameter being tested. /// Thrown if the size of is == . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeNotEqualTo(Span span, int size, [CallerArgumentExpression("span")] string name = "") + public static void HasSizeNotEqualTo(Span span, int size, [CallerArgumentExpression(nameof(span))] string name = "") { if (span.Length != size) { @@ -97,7 +97,7 @@ public static void HasSizeNotEqualTo(Span span, int size, [CallerArgumentE /// The name of the input parameter being tested. /// Thrown if the size of is <= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeGreaterThan(Span span, int size, [CallerArgumentExpression("span")] string name = "") + public static void HasSizeGreaterThan(Span span, int size, [CallerArgumentExpression(nameof(span))] string name = "") { if (span.Length > size) { @@ -116,7 +116,7 @@ public static void HasSizeGreaterThan(Span span, int size, [CallerArgument /// The name of the input parameter being tested. /// Thrown if the size of is < . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeGreaterThanOrEqualTo(Span span, int size, [CallerArgumentExpression("span")] string name = "") + public static void HasSizeGreaterThanOrEqualTo(Span span, int size, [CallerArgumentExpression(nameof(span))] string name = "") { if (span.Length >= size) { @@ -135,7 +135,7 @@ public static void HasSizeGreaterThanOrEqualTo(Span span, int size, [Calle /// The name of the input parameter being tested. /// Thrown if the size of is >= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThan(Span span, int size, [CallerArgumentExpression("span")] string name = "") + public static void HasSizeLessThan(Span span, int size, [CallerArgumentExpression(nameof(span))] string name = "") { if (span.Length < size) { @@ -154,7 +154,7 @@ public static void HasSizeLessThan(Span span, int size, [CallerArgumentExp /// The name of the input parameter being tested. /// Thrown if the size of is > . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThanOrEqualTo(Span span, int size, [CallerArgumentExpression("span")] string name = "") + public static void HasSizeLessThanOrEqualTo(Span span, int size, [CallerArgumentExpression(nameof(span))] string name = "") { if (span.Length <= size) { @@ -173,7 +173,7 @@ public static void HasSizeLessThanOrEqualTo(Span span, int size, [CallerAr /// The name of the input parameter being tested. /// Thrown if the size of is != the one of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeEqualTo(Span source, Span destination, [CallerArgumentExpression("source")] string name = "") + public static void HasSizeEqualTo(Span source, Span destination, [CallerArgumentExpression(nameof(source))] string name = "") { if (source.Length == destination.Length) { @@ -192,7 +192,7 @@ public static void HasSizeEqualTo(Span source, Span destination, [Calle /// The name of the input parameter being tested. /// Thrown if the size of is > the one of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThanOrEqualTo(Span source, Span destination, [CallerArgumentExpression("source")] string name = "") + public static void HasSizeLessThanOrEqualTo(Span source, Span destination, [CallerArgumentExpression(nameof(source))] string name = "") { if (source.Length <= destination.Length) { @@ -211,7 +211,7 @@ public static void HasSizeLessThanOrEqualTo(Span source, Span destinati /// The name of the input parameter being tested. /// Thrown if is not valid to access . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsInRangeFor(int index, Span span, [CallerArgumentExpression("index")] string name = "") + public static void IsInRangeFor(int index, Span span, [CallerArgumentExpression(nameof(index))] string name = "") { if ((uint)index < (uint)span.Length) { @@ -230,7 +230,7 @@ public static void IsInRangeFor(int index, Span span, [CallerArgumentExpre /// The name of the input parameter being tested. /// Thrown if is valid to access . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotInRangeFor(int index, Span span, [CallerArgumentExpression("index")] string name = "") + public static void IsNotInRangeFor(int index, Span span, [CallerArgumentExpression(nameof(index))] string name = "") { if ((uint)index >= (uint)span.Length) { @@ -248,7 +248,7 @@ public static void IsNotInRangeFor(int index, Span span, [CallerArgumentEx /// The name of the input parameter being tested. /// Thrown if the size of is != 0. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsEmpty(ReadOnlySpan span, [CallerArgumentExpression("span")] string name = "") + public static void IsEmpty(ReadOnlySpan span, [CallerArgumentExpression(nameof(span))] string name = "") { if (span.Length == 0) { @@ -266,7 +266,7 @@ public static void IsEmpty(ReadOnlySpan span, [CallerArgumentExpression("s /// The name of the input parameter being tested. /// Thrown if the size of is == 0. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotEmpty(ReadOnlySpan span, [CallerArgumentExpression("span")] string name = "") + public static void IsNotEmpty(ReadOnlySpan span, [CallerArgumentExpression(nameof(span))] string name = "") { if (span.Length != 0) { @@ -285,7 +285,7 @@ public static void IsNotEmpty(ReadOnlySpan span, [CallerArgumentExpression /// The name of the input parameter being tested. /// Thrown if the size of is != . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeEqualTo(ReadOnlySpan span, int size, [CallerArgumentExpression("span")] string name = "") + public static void HasSizeEqualTo(ReadOnlySpan span, int size, [CallerArgumentExpression(nameof(span))] string name = "") { if (span.Length == size) { @@ -304,7 +304,7 @@ public static void HasSizeEqualTo(ReadOnlySpan span, int size, [CallerArgu /// The name of the input parameter being tested. /// Thrown if the size of is == . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeNotEqualTo(ReadOnlySpan span, int size, [CallerArgumentExpression("span")] string name = "") + public static void HasSizeNotEqualTo(ReadOnlySpan span, int size, [CallerArgumentExpression(nameof(span))] string name = "") { if (span.Length != size) { @@ -323,7 +323,7 @@ public static void HasSizeNotEqualTo(ReadOnlySpan span, int size, [CallerA /// The name of the input parameter being tested. /// Thrown if the size of is <= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeGreaterThan(ReadOnlySpan span, int size, [CallerArgumentExpression("span")] string name = "") + public static void HasSizeGreaterThan(ReadOnlySpan span, int size, [CallerArgumentExpression(nameof(span))] string name = "") { if (span.Length > size) { @@ -342,7 +342,7 @@ public static void HasSizeGreaterThan(ReadOnlySpan span, int size, [Caller /// The name of the input parameter being tested. /// Thrown if the size of is < . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeGreaterThanOrEqualTo(ReadOnlySpan span, int size, [CallerArgumentExpression("span")] string name = "") + public static void HasSizeGreaterThanOrEqualTo(ReadOnlySpan span, int size, [CallerArgumentExpression(nameof(span))] string name = "") { if (span.Length >= size) { @@ -361,7 +361,7 @@ public static void HasSizeGreaterThanOrEqualTo(ReadOnlySpan span, int size /// The name of the input parameter being tested. /// Thrown if the size of is >= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThan(ReadOnlySpan span, int size, [CallerArgumentExpression("span")] string name = "") + public static void HasSizeLessThan(ReadOnlySpan span, int size, [CallerArgumentExpression(nameof(span))] string name = "") { if (span.Length < size) { @@ -380,7 +380,7 @@ public static void HasSizeLessThan(ReadOnlySpan span, int size, [CallerArg /// The name of the input parameter being tested. /// Thrown if the size of is > . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThanOrEqualTo(ReadOnlySpan span, int size, [CallerArgumentExpression("span")] string name = "") + public static void HasSizeLessThanOrEqualTo(ReadOnlySpan span, int size, [CallerArgumentExpression(nameof(span))] string name = "") { if (span.Length <= size) { @@ -399,7 +399,7 @@ public static void HasSizeLessThanOrEqualTo(ReadOnlySpan span, int size, [ /// The name of the input parameter being tested. /// Thrown if the size of is != the one of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeEqualTo(ReadOnlySpan source, Span destination, [CallerArgumentExpression("source")] string name = "") + public static void HasSizeEqualTo(ReadOnlySpan source, Span destination, [CallerArgumentExpression(nameof(source))] string name = "") { if (source.Length == destination.Length) { @@ -418,7 +418,7 @@ public static void HasSizeEqualTo(ReadOnlySpan source, Span destination /// The name of the input parameter being tested. /// Thrown if the size of is > the one of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThanOrEqualTo(ReadOnlySpan source, Span destination, [CallerArgumentExpression("source")] string name = "") + public static void HasSizeLessThanOrEqualTo(ReadOnlySpan source, Span destination, [CallerArgumentExpression(nameof(source))] string name = "") { if (source.Length <= destination.Length) { @@ -437,7 +437,7 @@ public static void HasSizeLessThanOrEqualTo(ReadOnlySpan source, Span d /// The name of the input parameter being tested. /// Thrown if is not valid to access . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsInRangeFor(int index, ReadOnlySpan span, [CallerArgumentExpression("index")] string name = "") + public static void IsInRangeFor(int index, ReadOnlySpan span, [CallerArgumentExpression(nameof(index))] string name = "") { if ((uint)index < (uint)span.Length) { @@ -456,7 +456,7 @@ public static void IsInRangeFor(int index, ReadOnlySpan span, [CallerArgum /// The name of the input parameter being tested. /// Thrown if is valid to access . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotInRangeFor(int index, ReadOnlySpan span, [CallerArgumentExpression("index")] string name = "") + public static void IsNotInRangeFor(int index, ReadOnlySpan span, [CallerArgumentExpression(nameof(index))] string name = "") { if ((uint)index >= (uint)span.Length) { @@ -474,7 +474,7 @@ public static void IsNotInRangeFor(int index, ReadOnlySpan span, [CallerAr /// The name of the input parameter being tested. /// Thrown if the size of is != 0. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsEmpty(Memory memory, [CallerArgumentExpression("memory")] string name = "") + public static void IsEmpty(Memory memory, [CallerArgumentExpression(nameof(memory))] string name = "") { if (memory.Length == 0) { @@ -492,7 +492,7 @@ public static void IsEmpty(Memory memory, [CallerArgumentExpression("memor /// The name of the input parameter being tested. /// Thrown if the size of is == 0. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotEmpty(Memory memory, [CallerArgumentExpression("memory")] string name = "") + public static void IsNotEmpty(Memory memory, [CallerArgumentExpression(nameof(memory))] string name = "") { if (memory.Length != 0) { @@ -511,7 +511,7 @@ public static void IsNotEmpty(Memory memory, [CallerArgumentExpression("me /// The name of the input parameter being tested. /// Thrown if the size of is != . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeEqualTo(Memory memory, int size, [CallerArgumentExpression("memory")] string name = "") + public static void HasSizeEqualTo(Memory memory, int size, [CallerArgumentExpression(nameof(memory))] string name = "") { if (memory.Length == size) { @@ -530,7 +530,7 @@ public static void HasSizeEqualTo(Memory memory, int size, [CallerArgument /// The name of the input parameter being tested. /// Thrown if the size of is == . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeNotEqualTo(Memory memory, int size, [CallerArgumentExpression("memory")] string name = "") + public static void HasSizeNotEqualTo(Memory memory, int size, [CallerArgumentExpression(nameof(memory))] string name = "") { if (memory.Length != size) { @@ -549,7 +549,7 @@ public static void HasSizeNotEqualTo(Memory memory, int size, [CallerArgum /// The name of the input parameter being tested. /// Thrown if the size of is <= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeGreaterThan(Memory memory, int size, [CallerArgumentExpression("memory")] string name = "") + public static void HasSizeGreaterThan(Memory memory, int size, [CallerArgumentExpression(nameof(memory))] string name = "") { if (memory.Length > size) { @@ -568,7 +568,7 @@ public static void HasSizeGreaterThan(Memory memory, int size, [CallerArgu /// The name of the input parameter being tested. /// Thrown if the size of is < . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeGreaterThanOrEqualTo(Memory memory, int size, [CallerArgumentExpression("memory")] string name = "") + public static void HasSizeGreaterThanOrEqualTo(Memory memory, int size, [CallerArgumentExpression(nameof(memory))] string name = "") { if (memory.Length >= size) { @@ -587,7 +587,7 @@ public static void HasSizeGreaterThanOrEqualTo(Memory memory, int size, [C /// The name of the input parameter being tested. /// Thrown if the size of is >= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThan(Memory memory, int size, [CallerArgumentExpression("memory")] string name = "") + public static void HasSizeLessThan(Memory memory, int size, [CallerArgumentExpression(nameof(memory))] string name = "") { if (memory.Length < size) { @@ -606,7 +606,7 @@ public static void HasSizeLessThan(Memory memory, int size, [CallerArgumen /// The name of the input parameter being tested. /// Thrown if the size of is > . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThanOrEqualTo(Memory memory, int size, [CallerArgumentExpression("memory")] string name = "") + public static void HasSizeLessThanOrEqualTo(Memory memory, int size, [CallerArgumentExpression(nameof(memory))] string name = "") { if (memory.Length <= size) { @@ -625,7 +625,7 @@ public static void HasSizeLessThanOrEqualTo(Memory memory, int size, [Call /// The name of the input parameter being tested. /// Thrown if the size of is != the one of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeEqualTo(Memory source, Memory destination, [CallerArgumentExpression("source")] string name = "") + public static void HasSizeEqualTo(Memory source, Memory destination, [CallerArgumentExpression(nameof(source))] string name = "") { if (source.Length == destination.Length) { @@ -644,7 +644,7 @@ public static void HasSizeEqualTo(Memory source, Memory destination, [C /// The name of the input parameter being tested. /// Thrown if the size of is > the one of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThanOrEqualTo(Memory source, Memory destination, [CallerArgumentExpression("source")] string name = "") + public static void HasSizeLessThanOrEqualTo(Memory source, Memory destination, [CallerArgumentExpression(nameof(source))] string name = "") { if (source.Length <= destination.Length) { @@ -663,7 +663,7 @@ public static void HasSizeLessThanOrEqualTo(Memory source, Memory desti /// The name of the input parameter being tested. /// Thrown if is not valid to access . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsInRangeFor(int index, Memory memory, [CallerArgumentExpression("index")] string name = "") + public static void IsInRangeFor(int index, Memory memory, [CallerArgumentExpression(nameof(index))] string name = "") { if ((uint)index < (uint)memory.Length) { @@ -682,7 +682,7 @@ public static void IsInRangeFor(int index, Memory memory, [CallerArgumentE /// The name of the input parameter being tested. /// Thrown if is valid to access . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotInRangeFor(int index, Memory memory, [CallerArgumentExpression("index")] string name = "") + public static void IsNotInRangeFor(int index, Memory memory, [CallerArgumentExpression(nameof(index))] string name = "") { if ((uint)index >= (uint)memory.Length) { @@ -700,7 +700,7 @@ public static void IsNotInRangeFor(int index, Memory memory, [CallerArgume /// The name of the input parameter being tested. /// Thrown if the size of is != 0. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsEmpty(ReadOnlyMemory memory, [CallerArgumentExpression("memory")] string name = "") + public static void IsEmpty(ReadOnlyMemory memory, [CallerArgumentExpression(nameof(memory))] string name = "") { if (memory.Length == 0) { @@ -718,7 +718,7 @@ public static void IsEmpty(ReadOnlyMemory memory, [CallerArgumentExpressio /// The name of the input parameter being tested. /// Thrown if the size of is == 0. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotEmpty(ReadOnlyMemory memory, [CallerArgumentExpression("memory")] string name = "") + public static void IsNotEmpty(ReadOnlyMemory memory, [CallerArgumentExpression(nameof(memory))] string name = "") { if (memory.Length != 0) { @@ -737,7 +737,7 @@ public static void IsNotEmpty(ReadOnlyMemory memory, [CallerArgumentExpres /// The name of the input parameter being tested. /// Thrown if the size of is != . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeEqualTo(ReadOnlyMemory memory, int size, [CallerArgumentExpression("memory")] string name = "") + public static void HasSizeEqualTo(ReadOnlyMemory memory, int size, [CallerArgumentExpression(nameof(memory))] string name = "") { if (memory.Length == size) { @@ -756,7 +756,7 @@ public static void HasSizeEqualTo(ReadOnlyMemory memory, int size, [Caller /// The name of the input parameter being tested. /// Thrown if the size of is == . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeNotEqualTo(ReadOnlyMemory memory, int size, [CallerArgumentExpression("memory")] string name = "") + public static void HasSizeNotEqualTo(ReadOnlyMemory memory, int size, [CallerArgumentExpression(nameof(memory))] string name = "") { if (memory.Length != size) { @@ -775,7 +775,7 @@ public static void HasSizeNotEqualTo(ReadOnlyMemory memory, int size, [Cal /// The name of the input parameter being tested. /// Thrown if the size of is <= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeGreaterThan(ReadOnlyMemory memory, int size, [CallerArgumentExpression("memory")] string name = "") + public static void HasSizeGreaterThan(ReadOnlyMemory memory, int size, [CallerArgumentExpression(nameof(memory))] string name = "") { if (memory.Length > size) { @@ -794,7 +794,7 @@ public static void HasSizeGreaterThan(ReadOnlyMemory memory, int size, [Ca /// The name of the input parameter being tested. /// Thrown if the size of is < . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeGreaterThanOrEqualTo(ReadOnlyMemory memory, int size, [CallerArgumentExpression("memory")] string name = "") + public static void HasSizeGreaterThanOrEqualTo(ReadOnlyMemory memory, int size, [CallerArgumentExpression(nameof(memory))] string name = "") { if (memory.Length >= size) { @@ -813,7 +813,7 @@ public static void HasSizeGreaterThanOrEqualTo(ReadOnlyMemory memory, int /// The name of the input parameter being tested. /// Thrown if the size of is >= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThan(ReadOnlyMemory memory, int size, [CallerArgumentExpression("memory")] string name = "") + public static void HasSizeLessThan(ReadOnlyMemory memory, int size, [CallerArgumentExpression(nameof(memory))] string name = "") { if (memory.Length < size) { @@ -832,7 +832,7 @@ public static void HasSizeLessThan(ReadOnlyMemory memory, int size, [Calle /// The name of the input parameter being tested. /// Thrown if the size of is > . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThanOrEqualTo(ReadOnlyMemory memory, int size, [CallerArgumentExpression("memory")] string name = "") + public static void HasSizeLessThanOrEqualTo(ReadOnlyMemory memory, int size, [CallerArgumentExpression(nameof(memory))] string name = "") { if (memory.Length <= size) { @@ -851,7 +851,7 @@ public static void HasSizeLessThanOrEqualTo(ReadOnlyMemory memory, int siz /// The name of the input parameter being tested. /// Thrown if the size of is != the one of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeEqualTo(ReadOnlyMemory source, Memory destination, [CallerArgumentExpression("source")] string name = "") + public static void HasSizeEqualTo(ReadOnlyMemory source, Memory destination, [CallerArgumentExpression(nameof(source))] string name = "") { if (source.Length == destination.Length) { @@ -870,7 +870,7 @@ public static void HasSizeEqualTo(ReadOnlyMemory source, Memory destina /// The name of the input parameter being tested. /// Thrown if the size of is > the one of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThanOrEqualTo(ReadOnlyMemory source, Memory destination, [CallerArgumentExpression("source")] string name = "") + public static void HasSizeLessThanOrEqualTo(ReadOnlyMemory source, Memory destination, [CallerArgumentExpression(nameof(source))] string name = "") { if (source.Length <= destination.Length) { @@ -889,7 +889,7 @@ public static void HasSizeLessThanOrEqualTo(ReadOnlyMemory source, Memory< /// The name of the input parameter being tested. /// Thrown if is not valid to access . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsInRangeFor(int index, ReadOnlyMemory memory, [CallerArgumentExpression("index")] string name = "") + public static void IsInRangeFor(int index, ReadOnlyMemory memory, [CallerArgumentExpression(nameof(index))] string name = "") { if ((uint)index < (uint)memory.Length) { @@ -908,7 +908,7 @@ public static void IsInRangeFor(int index, ReadOnlyMemory memory, [CallerA /// The name of the input parameter being tested. /// Thrown if is valid to access . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotInRangeFor(int index, ReadOnlyMemory memory, [CallerArgumentExpression("index")] string name = "") + public static void IsNotInRangeFor(int index, ReadOnlyMemory memory, [CallerArgumentExpression(nameof(index))] string name = "") { if ((uint)index >= (uint)memory.Length) { @@ -926,7 +926,7 @@ public static void IsNotInRangeFor(int index, ReadOnlyMemory memory, [Call /// The name of the input parameter being tested. /// Thrown if the size of is != 0. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsEmpty(T[] array, [CallerArgumentExpression("array")] string name = "") + public static void IsEmpty(T[] array, [CallerArgumentExpression(nameof(array))] string name = "") { if (array.Length == 0) { @@ -944,7 +944,7 @@ public static void IsEmpty(T[] array, [CallerArgumentExpression("array")] str /// The name of the input parameter being tested. /// Thrown if the size of is == 0. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotEmpty(T[] array, [CallerArgumentExpression("array")] string name = "") + public static void IsNotEmpty(T[] array, [CallerArgumentExpression(nameof(array))] string name = "") { if (array.Length != 0) { @@ -963,7 +963,7 @@ public static void IsNotEmpty(T[] array, [CallerArgumentExpression("array")] /// The name of the input parameter being tested. /// Thrown if the size of is != . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeEqualTo(T[] array, int size, [CallerArgumentExpression("array")] string name = "") + public static void HasSizeEqualTo(T[] array, int size, [CallerArgumentExpression(nameof(array))] string name = "") { if (array.Length == size) { @@ -982,7 +982,7 @@ public static void HasSizeEqualTo(T[] array, int size, [CallerArgumentExpress /// The name of the input parameter being tested. /// Thrown if the size of is == . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeNotEqualTo(T[] array, int size, [CallerArgumentExpression("array")] string name = "") + public static void HasSizeNotEqualTo(T[] array, int size, [CallerArgumentExpression(nameof(array))] string name = "") { if (array.Length != size) { @@ -1001,7 +1001,7 @@ public static void HasSizeNotEqualTo(T[] array, int size, [CallerArgumentExpr /// The name of the input parameter being tested. /// Thrown if the size of is <= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeGreaterThan(T[] array, int size, [CallerArgumentExpression("array")] string name = "") + public static void HasSizeGreaterThan(T[] array, int size, [CallerArgumentExpression(nameof(array))] string name = "") { if (array.Length > size) { @@ -1020,7 +1020,7 @@ public static void HasSizeGreaterThan(T[] array, int size, [CallerArgumentExp /// The name of the input parameter being tested. /// Thrown if the size of is < . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeGreaterThanOrEqualTo(T[] array, int size, [CallerArgumentExpression("array")] string name = "") + public static void HasSizeGreaterThanOrEqualTo(T[] array, int size, [CallerArgumentExpression(nameof(array))] string name = "") { if (array.Length >= size) { @@ -1039,7 +1039,7 @@ public static void HasSizeGreaterThanOrEqualTo(T[] array, int size, [CallerAr /// The name of the input parameter being tested. /// Thrown if the size of is >= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThan(T[] array, int size, [CallerArgumentExpression("array")] string name = "") + public static void HasSizeLessThan(T[] array, int size, [CallerArgumentExpression(nameof(array))] string name = "") { if (array.Length < size) { @@ -1058,7 +1058,7 @@ public static void HasSizeLessThan(T[] array, int size, [CallerArgumentExpres /// The name of the input parameter being tested. /// Thrown if the size of is > . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThanOrEqualTo(T[] array, int size, [CallerArgumentExpression("array")] string name = "") + public static void HasSizeLessThanOrEqualTo(T[] array, int size, [CallerArgumentExpression(nameof(array))] string name = "") { if (array.Length <= size) { @@ -1077,7 +1077,7 @@ public static void HasSizeLessThanOrEqualTo(T[] array, int size, [CallerArgum /// The name of the input parameter being tested. /// Thrown if the size of is != the one of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeEqualTo(T[] source, T[] destination, [CallerArgumentExpression("source")] string name = "") + public static void HasSizeEqualTo(T[] source, T[] destination, [CallerArgumentExpression(nameof(source))] string name = "") { if (source.Length == destination.Length) { @@ -1096,7 +1096,7 @@ public static void HasSizeEqualTo(T[] source, T[] destination, [CallerArgumen /// The name of the input parameter being tested. /// Thrown if the size of is > the one of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThanOrEqualTo(T[] source, T[] destination, [CallerArgumentExpression("source")] string name = "") + public static void HasSizeLessThanOrEqualTo(T[] source, T[] destination, [CallerArgumentExpression(nameof(source))] string name = "") { if (source.Length <= destination.Length) { @@ -1115,7 +1115,7 @@ public static void HasSizeLessThanOrEqualTo(T[] source, T[] destination, [Cal /// The name of the input parameter being tested. /// Thrown if is not valid to access . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsInRangeFor(int index, T[] array, [CallerArgumentExpression("index")] string name = "") + public static void IsInRangeFor(int index, T[] array, [CallerArgumentExpression(nameof(index))] string name = "") { if ((uint)index < (uint)array.Length) { @@ -1134,7 +1134,7 @@ public static void IsInRangeFor(int index, T[] array, [CallerArgumentExpressi /// The name of the input parameter being tested. /// Thrown if is valid to access . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotInRangeFor(int index, T[] array, [CallerArgumentExpression("index")] string name = "") + public static void IsNotInRangeFor(int index, T[] array, [CallerArgumentExpression(nameof(index))] string name = "") { if ((uint)index >= (uint)array.Length) { @@ -1152,7 +1152,7 @@ public static void IsNotInRangeFor(int index, T[] array, [CallerArgumentExpre /// The name of the input parameter being tested. /// Thrown if the size of is != 0. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsEmpty(List list, [CallerArgumentExpression("list")] string name = "") + public static void IsEmpty(List list, [CallerArgumentExpression(nameof(list))] string name = "") { if (list.Count == 0) { @@ -1170,7 +1170,7 @@ public static void IsEmpty(List list, [CallerArgumentExpression("list")] s /// The name of the input parameter being tested. /// Thrown if the size of is == 0. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotEmpty(List list, [CallerArgumentExpression("list")] string name = "") + public static void IsNotEmpty(List list, [CallerArgumentExpression(nameof(list))] string name = "") { if (list.Count != 0) { @@ -1189,7 +1189,7 @@ public static void IsNotEmpty(List list, [CallerArgumentExpression("list") /// The name of the input parameter being tested. /// Thrown if the size of is != . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeEqualTo(List list, int size, [CallerArgumentExpression("list")] string name = "") + public static void HasSizeEqualTo(List list, int size, [CallerArgumentExpression(nameof(list))] string name = "") { if (list.Count == size) { @@ -1208,7 +1208,7 @@ public static void HasSizeEqualTo(List list, int size, [CallerArgumentExpr /// The name of the input parameter being tested. /// Thrown if the size of is == . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeNotEqualTo(List list, int size, [CallerArgumentExpression("list")] string name = "") + public static void HasSizeNotEqualTo(List list, int size, [CallerArgumentExpression(nameof(list))] string name = "") { if (list.Count != size) { @@ -1227,7 +1227,7 @@ public static void HasSizeNotEqualTo(List list, int size, [CallerArgumentE /// The name of the input parameter being tested. /// Thrown if the size of is <= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeGreaterThan(List list, int size, [CallerArgumentExpression("list")] string name = "") + public static void HasSizeGreaterThan(List list, int size, [CallerArgumentExpression(nameof(list))] string name = "") { if (list.Count > size) { @@ -1246,7 +1246,7 @@ public static void HasSizeGreaterThan(List list, int size, [CallerArgument /// The name of the input parameter being tested. /// Thrown if the size of is < . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeGreaterThanOrEqualTo(List list, int size, [CallerArgumentExpression("list")] string name = "") + public static void HasSizeGreaterThanOrEqualTo(List list, int size, [CallerArgumentExpression(nameof(list))] string name = "") { if (list.Count >= size) { @@ -1265,7 +1265,7 @@ public static void HasSizeGreaterThanOrEqualTo(List list, int size, [Calle /// The name of the input parameter being tested. /// Thrown if the size of is >= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThan(List list, int size, [CallerArgumentExpression("list")] string name = "") + public static void HasSizeLessThan(List list, int size, [CallerArgumentExpression(nameof(list))] string name = "") { if (list.Count < size) { @@ -1284,7 +1284,7 @@ public static void HasSizeLessThan(List list, int size, [CallerArgumentExp /// The name of the input parameter being tested. /// Thrown if the size of is > . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThanOrEqualTo(List list, int size, [CallerArgumentExpression("list")] string name = "") + public static void HasSizeLessThanOrEqualTo(List list, int size, [CallerArgumentExpression(nameof(list))] string name = "") { if (list.Count <= size) { @@ -1303,7 +1303,7 @@ public static void HasSizeLessThanOrEqualTo(List list, int size, [CallerAr /// The name of the input parameter being tested. /// Thrown if the size of is != the one of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeEqualTo(List source, List destination, [CallerArgumentExpression("source")] string name = "") + public static void HasSizeEqualTo(List source, List destination, [CallerArgumentExpression(nameof(source))] string name = "") { if (source.Count == destination.Count) { @@ -1322,7 +1322,7 @@ public static void HasSizeEqualTo(List source, List destination, [Calle /// The name of the input parameter being tested. /// Thrown if the size of is > the one of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThanOrEqualTo(List source, List destination, [CallerArgumentExpression("source")] string name = "") + public static void HasSizeLessThanOrEqualTo(List source, List destination, [CallerArgumentExpression(nameof(source))] string name = "") { if (source.Count <= destination.Count) { @@ -1341,7 +1341,7 @@ public static void HasSizeLessThanOrEqualTo(List source, List destinati /// The name of the input parameter being tested. /// Thrown if is not valid to access . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsInRangeFor(int index, List list, [CallerArgumentExpression("index")] string name = "") + public static void IsInRangeFor(int index, List list, [CallerArgumentExpression(nameof(index))] string name = "") { if ((uint)index < (uint)list.Count) { @@ -1360,7 +1360,7 @@ public static void IsInRangeFor(int index, List list, [CallerArgumentExpre /// The name of the input parameter being tested. /// Thrown if is valid to access . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotInRangeFor(int index, List list, [CallerArgumentExpression("index")] string name = "") + public static void IsNotInRangeFor(int index, List list, [CallerArgumentExpression(nameof(index))] string name = "") { if ((uint)index >= (uint)list.Count) { @@ -1378,7 +1378,7 @@ public static void IsNotInRangeFor(int index, List list, [CallerArgumentEx /// The name of the input parameter being tested. /// Thrown if the size of is != 0. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsEmpty(ICollection collection, [CallerArgumentExpression("collection")] string name = "") + public static void IsEmpty(ICollection collection, [CallerArgumentExpression(nameof(collection))] string name = "") { if (collection.Count == 0) { @@ -1396,7 +1396,7 @@ public static void IsEmpty(ICollection collection, [CallerArgumentExpressi /// The name of the input parameter being tested. /// Thrown if the size of is == 0. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotEmpty(ICollection collection, [CallerArgumentExpression("collection")] string name = "") + public static void IsNotEmpty(ICollection collection, [CallerArgumentExpression(nameof(collection))] string name = "") { if (collection.Count != 0) { @@ -1415,7 +1415,7 @@ public static void IsNotEmpty(ICollection collection, [CallerArgumentExpre /// The name of the input parameter being tested. /// Thrown if the size of is != . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeEqualTo(ICollection collection, int size, [CallerArgumentExpression("collection")] string name = "") + public static void HasSizeEqualTo(ICollection collection, int size, [CallerArgumentExpression(nameof(collection))] string name = "") { if (collection.Count == size) { @@ -1434,7 +1434,7 @@ public static void HasSizeEqualTo(ICollection collection, int size, [Calle /// The name of the input parameter being tested. /// Thrown if the size of is == . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeNotEqualTo(ICollection collection, int size, [CallerArgumentExpression("collection")] string name = "") + public static void HasSizeNotEqualTo(ICollection collection, int size, [CallerArgumentExpression(nameof(collection))] string name = "") { if (collection.Count != size) { @@ -1453,7 +1453,7 @@ public static void HasSizeNotEqualTo(ICollection collection, int size, [Ca /// The name of the input parameter being tested. /// Thrown if the size of is <= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeGreaterThan(ICollection collection, int size, [CallerArgumentExpression("collection")] string name = "") + public static void HasSizeGreaterThan(ICollection collection, int size, [CallerArgumentExpression(nameof(collection))] string name = "") { if (collection.Count > size) { @@ -1472,7 +1472,7 @@ public static void HasSizeGreaterThan(ICollection collection, int size, [C /// The name of the input parameter being tested. /// Thrown if the size of is < . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeGreaterThanOrEqualTo(ICollection collection, int size, [CallerArgumentExpression("collection")] string name = "") + public static void HasSizeGreaterThanOrEqualTo(ICollection collection, int size, [CallerArgumentExpression(nameof(collection))] string name = "") { if (collection.Count >= size) { @@ -1491,7 +1491,7 @@ public static void HasSizeGreaterThanOrEqualTo(ICollection collection, int /// The name of the input parameter being tested. /// Thrown if the size of is >= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThan(ICollection collection, int size, [CallerArgumentExpression("collection")] string name = "") + public static void HasSizeLessThan(ICollection collection, int size, [CallerArgumentExpression(nameof(collection))] string name = "") { if (collection.Count < size) { @@ -1510,7 +1510,7 @@ public static void HasSizeLessThan(ICollection collection, int size, [Call /// The name of the input parameter being tested. /// Thrown if the size of is > . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThanOrEqualTo(ICollection collection, int size, [CallerArgumentExpression("collection")] string name = "") + public static void HasSizeLessThanOrEqualTo(ICollection collection, int size, [CallerArgumentExpression(nameof(collection))] string name = "") { if (collection.Count <= size) { @@ -1529,7 +1529,7 @@ public static void HasSizeLessThanOrEqualTo(ICollection collection, int si /// The name of the input parameter being tested. /// Thrown if the size of is != the one of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeEqualTo(ICollection source, ICollection destination, [CallerArgumentExpression("source")] string name = "") + public static void HasSizeEqualTo(ICollection source, ICollection destination, [CallerArgumentExpression(nameof(source))] string name = "") { if (source.Count == destination.Count) { @@ -1548,7 +1548,7 @@ public static void HasSizeEqualTo(ICollection source, ICollection desti /// The name of the input parameter being tested. /// Thrown if the size of is > the one of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThanOrEqualTo(ICollection source, ICollection destination, [CallerArgumentExpression("source")] string name = "") + public static void HasSizeLessThanOrEqualTo(ICollection source, ICollection destination, [CallerArgumentExpression(nameof(source))] string name = "") { if (source.Count <= destination.Count) { @@ -1567,7 +1567,7 @@ public static void HasSizeLessThanOrEqualTo(ICollection source, ICollectio /// The name of the input parameter being tested. /// Thrown if is not valid to access . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsInRangeFor(int index, ICollection collection, [CallerArgumentExpression("index")] string name = "") + public static void IsInRangeFor(int index, ICollection collection, [CallerArgumentExpression(nameof(index))] string name = "") { if ((uint)index < (uint)collection.Count) { @@ -1586,7 +1586,7 @@ public static void IsInRangeFor(int index, ICollection collection, [Caller /// The name of the input parameter being tested. /// Thrown if is valid to access . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotInRangeFor(int index, ICollection collection, [CallerArgumentExpression("index")] string name = "") + public static void IsNotInRangeFor(int index, ICollection collection, [CallerArgumentExpression(nameof(index))] string name = "") { if ((uint)index >= (uint)collection.Count) { @@ -1604,7 +1604,7 @@ public static void IsNotInRangeFor(int index, ICollection collection, [Cal /// The name of the input parameter being tested. /// Thrown if the size of is != 0. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsEmpty(IReadOnlyCollection collection, [CallerArgumentExpression("collection")] string name = "") + public static void IsEmpty(IReadOnlyCollection collection, [CallerArgumentExpression(nameof(collection))] string name = "") { if (collection.Count == 0) { @@ -1622,7 +1622,7 @@ public static void IsEmpty(IReadOnlyCollection collection, [CallerArgument /// The name of the input parameter being tested. /// Thrown if the size of is == 0. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotEmpty(IReadOnlyCollection collection, [CallerArgumentExpression("collection")] string name = "") + public static void IsNotEmpty(IReadOnlyCollection collection, [CallerArgumentExpression(nameof(collection))] string name = "") { if (collection.Count != 0) { @@ -1641,7 +1641,7 @@ public static void IsNotEmpty(IReadOnlyCollection collection, [CallerArgum /// The name of the input parameter being tested. /// Thrown if the size of is != . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeEqualTo(IReadOnlyCollection collection, int size, [CallerArgumentExpression("collection")] string name = "") + public static void HasSizeEqualTo(IReadOnlyCollection collection, int size, [CallerArgumentExpression(nameof(collection))] string name = "") { if (collection.Count == size) { @@ -1660,7 +1660,7 @@ public static void HasSizeEqualTo(IReadOnlyCollection collection, int size /// The name of the input parameter being tested. /// Thrown if the size of is == . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeNotEqualTo(IReadOnlyCollection collection, int size, [CallerArgumentExpression("collection")] string name = "") + public static void HasSizeNotEqualTo(IReadOnlyCollection collection, int size, [CallerArgumentExpression(nameof(collection))] string name = "") { if (collection.Count != size) { @@ -1679,7 +1679,7 @@ public static void HasSizeNotEqualTo(IReadOnlyCollection collection, int s /// The name of the input parameter being tested. /// Thrown if the size of is <= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeGreaterThan(IReadOnlyCollection collection, int size, [CallerArgumentExpression("collection")] string name = "") + public static void HasSizeGreaterThan(IReadOnlyCollection collection, int size, [CallerArgumentExpression(nameof(collection))] string name = "") { if (collection.Count > size) { @@ -1698,7 +1698,7 @@ public static void HasSizeGreaterThan(IReadOnlyCollection collection, int /// The name of the input parameter being tested. /// Thrown if the size of is < . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeGreaterThanOrEqualTo(IReadOnlyCollection collection, int size, [CallerArgumentExpression("collection")] string name = "") + public static void HasSizeGreaterThanOrEqualTo(IReadOnlyCollection collection, int size, [CallerArgumentExpression(nameof(collection))] string name = "") { if (collection.Count >= size) { @@ -1717,7 +1717,7 @@ public static void HasSizeGreaterThanOrEqualTo(IReadOnlyCollection collect /// The name of the input parameter being tested. /// Thrown if the size of is >= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThan(IReadOnlyCollection collection, int size, [CallerArgumentExpression("collection")] string name = "") + public static void HasSizeLessThan(IReadOnlyCollection collection, int size, [CallerArgumentExpression(nameof(collection))] string name = "") { if (collection.Count < size) { @@ -1736,7 +1736,7 @@ public static void HasSizeLessThan(IReadOnlyCollection collection, int siz /// The name of the input parameter being tested. /// Thrown if the size of is > . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThanOrEqualTo(IReadOnlyCollection collection, int size, [CallerArgumentExpression("collection")] string name = "") + public static void HasSizeLessThanOrEqualTo(IReadOnlyCollection collection, int size, [CallerArgumentExpression(nameof(collection))] string name = "") { if (collection.Count <= size) { @@ -1755,7 +1755,7 @@ public static void HasSizeLessThanOrEqualTo(IReadOnlyCollection collection /// The name of the input parameter being tested. /// Thrown if the size of is != the one of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeEqualTo(IReadOnlyCollection source, ICollection destination, [CallerArgumentExpression("source")] string name = "") + public static void HasSizeEqualTo(IReadOnlyCollection source, ICollection destination, [CallerArgumentExpression(nameof(source))] string name = "") { if (source.Count == destination.Count) { @@ -1774,7 +1774,7 @@ public static void HasSizeEqualTo(IReadOnlyCollection source, ICollection< /// The name of the input parameter being tested. /// Thrown if the size of is > the one of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThanOrEqualTo(IReadOnlyCollection source, ICollection destination, [CallerArgumentExpression("source")] string name = "") + public static void HasSizeLessThanOrEqualTo(IReadOnlyCollection source, ICollection destination, [CallerArgumentExpression(nameof(source))] string name = "") { if (source.Count <= destination.Count) { @@ -1793,7 +1793,7 @@ public static void HasSizeLessThanOrEqualTo(IReadOnlyCollection source, IC /// The name of the input parameter being tested. /// Thrown if is not valid to access . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsInRangeFor(int index, IReadOnlyCollection collection, [CallerArgumentExpression("index")] string name = "") + public static void IsInRangeFor(int index, IReadOnlyCollection collection, [CallerArgumentExpression(nameof(index))] string name = "") { if ((uint)index < (uint)collection.Count) { @@ -1812,7 +1812,7 @@ public static void IsInRangeFor(int index, IReadOnlyCollection collection, /// The name of the input parameter being tested. /// Thrown if is valid to access . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotInRangeFor(int index, IReadOnlyCollection collection, [CallerArgumentExpression("index")] string name = "") + public static void IsNotInRangeFor(int index, IReadOnlyCollection collection, [CallerArgumentExpression(nameof(index))] string name = "") { if ((uint)index >= (uint)collection.Count) { diff --git a/CommunityToolkit.Diagnostics/Generated/Guard.Collection.tt b/src/CommunityToolkit.Diagnostics/Generated/Guard.Collection.tt similarity index 93% rename from CommunityToolkit.Diagnostics/Generated/Guard.Collection.tt rename to src/CommunityToolkit.Diagnostics/Generated/Guard.Collection.tt index 7e2b77762..fd93aa2f2 100644 --- a/CommunityToolkit.Diagnostics/Generated/Guard.Collection.tt +++ b/src/CommunityToolkit.Diagnostics/Generated/Guard.Collection.tt @@ -23,7 +23,7 @@ GenerateTextForItems(EnumerableTypes, item => /// The name of the input parameter being tested. /// Thrown if the size of is != 0. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsEmpty(<#=item.Type#> <#=item.Name#>, [CallerArgumentExpression("<#=item.Name#>")] string name = "") + public static void IsEmpty(<#=item.Type#> <#=item.Name#>, [CallerArgumentExpression(nameof(<#=item.Name#>))] string name = "") { if (<#=item.Name#>.<#=item.Size#> == 0) { @@ -41,7 +41,7 @@ GenerateTextForItems(EnumerableTypes, item => /// The name of the input parameter being tested. /// Thrown if the size of is == 0. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotEmpty(<#=item.Type#> <#=item.Name#>, [CallerArgumentExpression("<#=item.Name#>")] string name = "") + public static void IsNotEmpty(<#=item.Type#> <#=item.Name#>, [CallerArgumentExpression(nameof(<#=item.Name#>))] string name = "") { if (<#=item.Name#>.<#=item.Size#> != 0) { @@ -79,7 +79,7 @@ else /// The name of the input parameter being tested. /// Thrown if the size of is != . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeEqualTo(<#=item.Type#> <#=item.Name#>, int size, [CallerArgumentExpression("<#=item.Name#>")] string name = "") + public static void HasSizeEqualTo(<#=item.Type#> <#=item.Name#>, int size, [CallerArgumentExpression(nameof(<#=item.Name#>))] string name = "") { if (<#=item.Name#>.<#=item.Size#> == size) { @@ -98,7 +98,7 @@ else /// The name of the input parameter being tested. /// Thrown if the size of is == . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeNotEqualTo(<#=item.Type#> <#=item.Name#>, int size, [CallerArgumentExpression("<#=item.Name#>")] string name = "") + public static void HasSizeNotEqualTo(<#=item.Type#> <#=item.Name#>, int size, [CallerArgumentExpression(nameof(<#=item.Name#>))] string name = "") { if (<#=item.Name#>.<#=item.Size#> != size) { @@ -117,7 +117,7 @@ else /// The name of the input parameter being tested. /// Thrown if the size of is <= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeGreaterThan(<#=item.Type#> <#=item.Name#>, int size, [CallerArgumentExpression("<#=item.Name#>")] string name = "") + public static void HasSizeGreaterThan(<#=item.Type#> <#=item.Name#>, int size, [CallerArgumentExpression(nameof(<#=item.Name#>))] string name = "") { if (<#=item.Name#>.<#=item.Size#> > size) { @@ -136,7 +136,7 @@ else /// The name of the input parameter being tested. /// Thrown if the size of is < . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeGreaterThanOrEqualTo(<#=item.Type#> <#=item.Name#>, int size, [CallerArgumentExpression("<#=item.Name#>")] string name = "") + public static void HasSizeGreaterThanOrEqualTo(<#=item.Type#> <#=item.Name#>, int size, [CallerArgumentExpression(nameof(<#=item.Name#>))] string name = "") { if (<#=item.Name#>.<#=item.Size#> >= size) { @@ -155,7 +155,7 @@ else /// The name of the input parameter being tested. /// Thrown if the size of is >= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThan(<#=item.Type#> <#=item.Name#>, int size, [CallerArgumentExpression("<#=item.Name#>")] string name = "") + public static void HasSizeLessThan(<#=item.Type#> <#=item.Name#>, int size, [CallerArgumentExpression(nameof(<#=item.Name#>))] string name = "") { if (<#=item.Name#>.<#=item.Size#> < size) { @@ -174,7 +174,7 @@ else /// The name of the input parameter being tested. /// Thrown if the size of is > . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThanOrEqualTo(<#=item.Type#> <#=item.Name#>, int size, [CallerArgumentExpression("<#=item.Name#>")] string name = "") + public static void HasSizeLessThanOrEqualTo(<#=item.Type#> <#=item.Name#>, int size, [CallerArgumentExpression(nameof(<#=item.Name#>))] string name = "") { if (<#=item.Name#>.<#=item.Size#> <= size) { @@ -193,7 +193,7 @@ else /// The name of the input parameter being tested. /// Thrown if the size of is != the one of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeEqualTo(<#=item.Type#> source, <#=item.DestinationType#> destination, [CallerArgumentExpression("source")] string name = "") + public static void HasSizeEqualTo(<#=item.Type#> source, <#=item.DestinationType#> destination, [CallerArgumentExpression(nameof(source))] string name = "") { if (source.<#=item.Size#> == destination.<#=item.Size#>) { @@ -225,7 +225,7 @@ else /// The name of the input parameter being tested. /// Thrown if the size of is > the one of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThanOrEqualTo(<#=item.Type#> source, <#=item.DestinationType#> destination, [CallerArgumentExpression("source")] string name = "") + public static void HasSizeLessThanOrEqualTo(<#=item.Type#> source, <#=item.DestinationType#> destination, [CallerArgumentExpression(nameof(source))] string name = "") { if (source.<#=item.Size#> <= destination.<#=item.Size#>) { @@ -257,7 +257,7 @@ else /// The name of the input parameter being tested. /// Thrown if is not valid to access . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsInRangeFor(int index, <#=item.Type#> <#=item.Name#>, [CallerArgumentExpression("index")] string name = "") + public static void IsInRangeFor(int index, <#=item.Type#> <#=item.Name#>, [CallerArgumentExpression(nameof(index))] string name = "") { <# // Here we're leveraging the fact that signed integers are represented @@ -283,7 +283,7 @@ else /// The name of the input parameter being tested. /// Thrown if is valid to access . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotInRangeFor(int index, <#=item.Type#> <#=item.Name#>, [CallerArgumentExpression("index")] string name = "") + public static void IsNotInRangeFor(int index, <#=item.Type#> <#=item.Name#>, [CallerArgumentExpression(nameof(index))] string name = "") { if ((uint)index >= (uint)<#=item.Name#>.<#=item.Size#>) { diff --git a/CommunityToolkit.Diagnostics/Generated/Guard.Comparable.Numeric.g.cs b/src/CommunityToolkit.Diagnostics/Generated/Guard.Comparable.Numeric.g.cs similarity index 94% rename from CommunityToolkit.Diagnostics/Generated/Guard.Comparable.Numeric.g.cs rename to src/CommunityToolkit.Diagnostics/Generated/Guard.Comparable.Numeric.g.cs index ecc1cbef1..fc381828c 100644 --- a/CommunityToolkit.Diagnostics/Generated/Guard.Comparable.Numeric.g.cs +++ b/src/CommunityToolkit.Diagnostics/Generated/Guard.Comparable.Numeric.g.cs @@ -21,7 +21,7 @@ partial class Guard /// The name of the input parameter being tested. /// Thrown if is != . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsEqualTo(byte value, byte target, [CallerArgumentExpression("value")] string name = "") + public static void IsEqualTo(byte value, byte target, [CallerArgumentExpression(nameof(value))] string name = "") { if (value == target) { @@ -40,7 +40,7 @@ public static void IsEqualTo(byte value, byte target, [CallerArgumentExpression( /// Thrown if is == . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotEqualTo(byte value, byte target, [CallerArgumentExpression("value")] string name = "") + public static void IsNotEqualTo(byte value, byte target, [CallerArgumentExpression(nameof(value))] string name = "") { if (value != target) { @@ -59,7 +59,7 @@ public static void IsNotEqualTo(byte value, byte target, [CallerArgumentExpressi /// Thrown if is >= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThan(byte value, byte maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThan(byte value, byte maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < maximum) { @@ -78,7 +78,7 @@ public static void IsLessThan(byte value, byte maximum, [CallerArgumentExpressio /// Thrown if is > . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThanOrEqualTo(byte value, byte maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThanOrEqualTo(byte value, byte maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value <= maximum) { @@ -97,7 +97,7 @@ public static void IsLessThanOrEqualTo(byte value, byte maximum, [CallerArgument /// Thrown if is <= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThan(byte value, byte minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThan(byte value, byte minimum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value > minimum) { @@ -116,7 +116,7 @@ public static void IsGreaterThan(byte value, byte minimum, [CallerArgumentExpres /// Thrown if is < . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThanOrEqualTo(byte value, byte minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThanOrEqualTo(byte value, byte minimum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum) { @@ -139,7 +139,7 @@ public static void IsGreaterThanOrEqualTo(byte value, byte minimum, [CallerArgum /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsInRange(byte value, byte minimum, byte maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsInRange(byte value, byte minimum, byte maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum && value < maximum) { @@ -162,7 +162,7 @@ public static void IsInRange(byte value, byte minimum, byte maximum, [CallerArgu /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotInRange(byte value, byte minimum, byte maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotInRange(byte value, byte minimum, byte maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < minimum || value >= maximum) { @@ -185,7 +185,7 @@ public static void IsNotInRange(byte value, byte minimum, byte maximum, [CallerA /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetween(byte value, byte minimum, byte maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetween(byte value, byte minimum, byte maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value > minimum && value < maximum) { @@ -208,7 +208,7 @@ public static void IsBetween(byte value, byte minimum, byte maximum, [CallerArgu /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetween(byte value, byte minimum, byte maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetween(byte value, byte minimum, byte maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value <= minimum || value >= maximum) { @@ -231,7 +231,7 @@ public static void IsNotBetween(byte value, byte minimum, byte maximum, [CallerA /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetweenOrEqualTo(byte value, byte minimum, byte maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetweenOrEqualTo(byte value, byte minimum, byte maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum && value <= maximum) { @@ -254,7 +254,7 @@ public static void IsBetweenOrEqualTo(byte value, byte minimum, byte maximum, [C /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetweenOrEqualTo(byte value, byte minimum, byte maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetweenOrEqualTo(byte value, byte minimum, byte maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < minimum || value > maximum) { @@ -272,7 +272,7 @@ public static void IsNotBetweenOrEqualTo(byte value, byte minimum, byte maximum, /// The name of the input parameter being tested. /// Thrown if is != . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsEqualTo(sbyte value, sbyte target, [CallerArgumentExpression("value")] string name = "") + public static void IsEqualTo(sbyte value, sbyte target, [CallerArgumentExpression(nameof(value))] string name = "") { if (value == target) { @@ -291,7 +291,7 @@ public static void IsEqualTo(sbyte value, sbyte target, [CallerArgumentExpressio /// Thrown if is == . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotEqualTo(sbyte value, sbyte target, [CallerArgumentExpression("value")] string name = "") + public static void IsNotEqualTo(sbyte value, sbyte target, [CallerArgumentExpression(nameof(value))] string name = "") { if (value != target) { @@ -310,7 +310,7 @@ public static void IsNotEqualTo(sbyte value, sbyte target, [CallerArgumentExpres /// Thrown if is >= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThan(sbyte value, sbyte maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThan(sbyte value, sbyte maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < maximum) { @@ -329,7 +329,7 @@ public static void IsLessThan(sbyte value, sbyte maximum, [CallerArgumentExpress /// Thrown if is > . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThanOrEqualTo(sbyte value, sbyte maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThanOrEqualTo(sbyte value, sbyte maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value <= maximum) { @@ -348,7 +348,7 @@ public static void IsLessThanOrEqualTo(sbyte value, sbyte maximum, [CallerArgume /// Thrown if is <= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThan(sbyte value, sbyte minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThan(sbyte value, sbyte minimum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value > minimum) { @@ -367,7 +367,7 @@ public static void IsGreaterThan(sbyte value, sbyte minimum, [CallerArgumentExpr /// Thrown if is < . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThanOrEqualTo(sbyte value, sbyte minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThanOrEqualTo(sbyte value, sbyte minimum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum) { @@ -390,7 +390,7 @@ public static void IsGreaterThanOrEqualTo(sbyte value, sbyte minimum, [CallerArg /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsInRange(sbyte value, sbyte minimum, sbyte maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsInRange(sbyte value, sbyte minimum, sbyte maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum && value < maximum) { @@ -413,7 +413,7 @@ public static void IsInRange(sbyte value, sbyte minimum, sbyte maximum, [CallerA /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotInRange(sbyte value, sbyte minimum, sbyte maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotInRange(sbyte value, sbyte minimum, sbyte maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < minimum || value >= maximum) { @@ -436,7 +436,7 @@ public static void IsNotInRange(sbyte value, sbyte minimum, sbyte maximum, [Call /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetween(sbyte value, sbyte minimum, sbyte maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetween(sbyte value, sbyte minimum, sbyte maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value > minimum && value < maximum) { @@ -459,7 +459,7 @@ public static void IsBetween(sbyte value, sbyte minimum, sbyte maximum, [CallerA /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetween(sbyte value, sbyte minimum, sbyte maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetween(sbyte value, sbyte minimum, sbyte maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value <= minimum || value >= maximum) { @@ -482,7 +482,7 @@ public static void IsNotBetween(sbyte value, sbyte minimum, sbyte maximum, [Call /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetweenOrEqualTo(sbyte value, sbyte minimum, sbyte maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetweenOrEqualTo(sbyte value, sbyte minimum, sbyte maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum && value <= maximum) { @@ -505,7 +505,7 @@ public static void IsBetweenOrEqualTo(sbyte value, sbyte minimum, sbyte maximum, /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetweenOrEqualTo(sbyte value, sbyte minimum, sbyte maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetweenOrEqualTo(sbyte value, sbyte minimum, sbyte maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < minimum || value > maximum) { @@ -523,7 +523,7 @@ public static void IsNotBetweenOrEqualTo(sbyte value, sbyte minimum, sbyte maxim /// The name of the input parameter being tested. /// Thrown if is != . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsEqualTo(short value, short target, [CallerArgumentExpression("value")] string name = "") + public static void IsEqualTo(short value, short target, [CallerArgumentExpression(nameof(value))] string name = "") { if (value == target) { @@ -542,7 +542,7 @@ public static void IsEqualTo(short value, short target, [CallerArgumentExpressio /// Thrown if is == . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotEqualTo(short value, short target, [CallerArgumentExpression("value")] string name = "") + public static void IsNotEqualTo(short value, short target, [CallerArgumentExpression(nameof(value))] string name = "") { if (value != target) { @@ -561,7 +561,7 @@ public static void IsNotEqualTo(short value, short target, [CallerArgumentExpres /// Thrown if is >= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThan(short value, short maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThan(short value, short maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < maximum) { @@ -580,7 +580,7 @@ public static void IsLessThan(short value, short maximum, [CallerArgumentExpress /// Thrown if is > . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThanOrEqualTo(short value, short maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThanOrEqualTo(short value, short maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value <= maximum) { @@ -599,7 +599,7 @@ public static void IsLessThanOrEqualTo(short value, short maximum, [CallerArgume /// Thrown if is <= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThan(short value, short minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThan(short value, short minimum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value > minimum) { @@ -618,7 +618,7 @@ public static void IsGreaterThan(short value, short minimum, [CallerArgumentExpr /// Thrown if is < . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThanOrEqualTo(short value, short minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThanOrEqualTo(short value, short minimum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum) { @@ -641,7 +641,7 @@ public static void IsGreaterThanOrEqualTo(short value, short minimum, [CallerArg /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsInRange(short value, short minimum, short maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsInRange(short value, short minimum, short maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum && value < maximum) { @@ -664,7 +664,7 @@ public static void IsInRange(short value, short minimum, short maximum, [CallerA /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotInRange(short value, short minimum, short maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotInRange(short value, short minimum, short maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < minimum || value >= maximum) { @@ -687,7 +687,7 @@ public static void IsNotInRange(short value, short minimum, short maximum, [Call /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetween(short value, short minimum, short maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetween(short value, short minimum, short maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value > minimum && value < maximum) { @@ -710,7 +710,7 @@ public static void IsBetween(short value, short minimum, short maximum, [CallerA /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetween(short value, short minimum, short maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetween(short value, short minimum, short maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value <= minimum || value >= maximum) { @@ -733,7 +733,7 @@ public static void IsNotBetween(short value, short minimum, short maximum, [Call /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetweenOrEqualTo(short value, short minimum, short maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetweenOrEqualTo(short value, short minimum, short maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum && value <= maximum) { @@ -756,7 +756,7 @@ public static void IsBetweenOrEqualTo(short value, short minimum, short maximum, /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetweenOrEqualTo(short value, short minimum, short maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetweenOrEqualTo(short value, short minimum, short maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < minimum || value > maximum) { @@ -774,7 +774,7 @@ public static void IsNotBetweenOrEqualTo(short value, short minimum, short maxim /// The name of the input parameter being tested. /// Thrown if is != . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsEqualTo(ushort value, ushort target, [CallerArgumentExpression("value")] string name = "") + public static void IsEqualTo(ushort value, ushort target, [CallerArgumentExpression(nameof(value))] string name = "") { if (value == target) { @@ -793,7 +793,7 @@ public static void IsEqualTo(ushort value, ushort target, [CallerArgumentExpress /// Thrown if is == . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotEqualTo(ushort value, ushort target, [CallerArgumentExpression("value")] string name = "") + public static void IsNotEqualTo(ushort value, ushort target, [CallerArgumentExpression(nameof(value))] string name = "") { if (value != target) { @@ -812,7 +812,7 @@ public static void IsNotEqualTo(ushort value, ushort target, [CallerArgumentExpr /// Thrown if is >= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThan(ushort value, ushort maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThan(ushort value, ushort maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < maximum) { @@ -831,7 +831,7 @@ public static void IsLessThan(ushort value, ushort maximum, [CallerArgumentExpre /// Thrown if is > . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThanOrEqualTo(ushort value, ushort maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThanOrEqualTo(ushort value, ushort maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value <= maximum) { @@ -850,7 +850,7 @@ public static void IsLessThanOrEqualTo(ushort value, ushort maximum, [CallerArgu /// Thrown if is <= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThan(ushort value, ushort minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThan(ushort value, ushort minimum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value > minimum) { @@ -869,7 +869,7 @@ public static void IsGreaterThan(ushort value, ushort minimum, [CallerArgumentEx /// Thrown if is < . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThanOrEqualTo(ushort value, ushort minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThanOrEqualTo(ushort value, ushort minimum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum) { @@ -892,7 +892,7 @@ public static void IsGreaterThanOrEqualTo(ushort value, ushort minimum, [CallerA /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsInRange(ushort value, ushort minimum, ushort maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsInRange(ushort value, ushort minimum, ushort maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum && value < maximum) { @@ -915,7 +915,7 @@ public static void IsInRange(ushort value, ushort minimum, ushort maximum, [Call /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotInRange(ushort value, ushort minimum, ushort maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotInRange(ushort value, ushort minimum, ushort maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < minimum || value >= maximum) { @@ -938,7 +938,7 @@ public static void IsNotInRange(ushort value, ushort minimum, ushort maximum, [C /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetween(ushort value, ushort minimum, ushort maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetween(ushort value, ushort minimum, ushort maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value > minimum && value < maximum) { @@ -961,7 +961,7 @@ public static void IsBetween(ushort value, ushort minimum, ushort maximum, [Call /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetween(ushort value, ushort minimum, ushort maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetween(ushort value, ushort minimum, ushort maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value <= minimum || value >= maximum) { @@ -984,7 +984,7 @@ public static void IsNotBetween(ushort value, ushort minimum, ushort maximum, [C /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetweenOrEqualTo(ushort value, ushort minimum, ushort maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetweenOrEqualTo(ushort value, ushort minimum, ushort maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum && value <= maximum) { @@ -1007,7 +1007,7 @@ public static void IsBetweenOrEqualTo(ushort value, ushort minimum, ushort maxim /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetweenOrEqualTo(ushort value, ushort minimum, ushort maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetweenOrEqualTo(ushort value, ushort minimum, ushort maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < minimum || value > maximum) { @@ -1025,7 +1025,7 @@ public static void IsNotBetweenOrEqualTo(ushort value, ushort minimum, ushort ma /// The name of the input parameter being tested. /// Thrown if is != . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsEqualTo(char value, char target, [CallerArgumentExpression("value")] string name = "") + public static void IsEqualTo(char value, char target, [CallerArgumentExpression(nameof(value))] string name = "") { if (value == target) { @@ -1044,7 +1044,7 @@ public static void IsEqualTo(char value, char target, [CallerArgumentExpression( /// Thrown if is == . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotEqualTo(char value, char target, [CallerArgumentExpression("value")] string name = "") + public static void IsNotEqualTo(char value, char target, [CallerArgumentExpression(nameof(value))] string name = "") { if (value != target) { @@ -1063,7 +1063,7 @@ public static void IsNotEqualTo(char value, char target, [CallerArgumentExpressi /// Thrown if is >= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThan(char value, char maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThan(char value, char maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < maximum) { @@ -1082,7 +1082,7 @@ public static void IsLessThan(char value, char maximum, [CallerArgumentExpressio /// Thrown if is > . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThanOrEqualTo(char value, char maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThanOrEqualTo(char value, char maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value <= maximum) { @@ -1101,7 +1101,7 @@ public static void IsLessThanOrEqualTo(char value, char maximum, [CallerArgument /// Thrown if is <= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThan(char value, char minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThan(char value, char minimum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value > minimum) { @@ -1120,7 +1120,7 @@ public static void IsGreaterThan(char value, char minimum, [CallerArgumentExpres /// Thrown if is < . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThanOrEqualTo(char value, char minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThanOrEqualTo(char value, char minimum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum) { @@ -1143,7 +1143,7 @@ public static void IsGreaterThanOrEqualTo(char value, char minimum, [CallerArgum /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsInRange(char value, char minimum, char maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsInRange(char value, char minimum, char maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum && value < maximum) { @@ -1166,7 +1166,7 @@ public static void IsInRange(char value, char minimum, char maximum, [CallerArgu /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotInRange(char value, char minimum, char maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotInRange(char value, char minimum, char maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < minimum || value >= maximum) { @@ -1189,7 +1189,7 @@ public static void IsNotInRange(char value, char minimum, char maximum, [CallerA /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetween(char value, char minimum, char maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetween(char value, char minimum, char maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value > minimum && value < maximum) { @@ -1212,7 +1212,7 @@ public static void IsBetween(char value, char minimum, char maximum, [CallerArgu /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetween(char value, char minimum, char maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetween(char value, char minimum, char maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value <= minimum || value >= maximum) { @@ -1235,7 +1235,7 @@ public static void IsNotBetween(char value, char minimum, char maximum, [CallerA /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetweenOrEqualTo(char value, char minimum, char maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetweenOrEqualTo(char value, char minimum, char maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum && value <= maximum) { @@ -1258,7 +1258,7 @@ public static void IsBetweenOrEqualTo(char value, char minimum, char maximum, [C /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetweenOrEqualTo(char value, char minimum, char maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetweenOrEqualTo(char value, char minimum, char maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < minimum || value > maximum) { @@ -1276,7 +1276,7 @@ public static void IsNotBetweenOrEqualTo(char value, char minimum, char maximum, /// The name of the input parameter being tested. /// Thrown if is != . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsEqualTo(int value, int target, [CallerArgumentExpression("value")] string name = "") + public static void IsEqualTo(int value, int target, [CallerArgumentExpression(nameof(value))] string name = "") { if (value == target) { @@ -1295,7 +1295,7 @@ public static void IsEqualTo(int value, int target, [CallerArgumentExpression("v /// Thrown if is == . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotEqualTo(int value, int target, [CallerArgumentExpression("value")] string name = "") + public static void IsNotEqualTo(int value, int target, [CallerArgumentExpression(nameof(value))] string name = "") { if (value != target) { @@ -1314,7 +1314,7 @@ public static void IsNotEqualTo(int value, int target, [CallerArgumentExpression /// Thrown if is >= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThan(int value, int maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThan(int value, int maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < maximum) { @@ -1333,7 +1333,7 @@ public static void IsLessThan(int value, int maximum, [CallerArgumentExpression( /// Thrown if is > . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThanOrEqualTo(int value, int maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThanOrEqualTo(int value, int maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value <= maximum) { @@ -1352,7 +1352,7 @@ public static void IsLessThanOrEqualTo(int value, int maximum, [CallerArgumentEx /// Thrown if is <= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThan(int value, int minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThan(int value, int minimum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value > minimum) { @@ -1371,7 +1371,7 @@ public static void IsGreaterThan(int value, int minimum, [CallerArgumentExpressi /// Thrown if is < . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThanOrEqualTo(int value, int minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThanOrEqualTo(int value, int minimum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum) { @@ -1394,7 +1394,7 @@ public static void IsGreaterThanOrEqualTo(int value, int minimum, [CallerArgumen /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsInRange(int value, int minimum, int maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsInRange(int value, int minimum, int maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum && value < maximum) { @@ -1417,7 +1417,7 @@ public static void IsInRange(int value, int minimum, int maximum, [CallerArgumen /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotInRange(int value, int minimum, int maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotInRange(int value, int minimum, int maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < minimum || value >= maximum) { @@ -1440,7 +1440,7 @@ public static void IsNotInRange(int value, int minimum, int maximum, [CallerArgu /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetween(int value, int minimum, int maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetween(int value, int minimum, int maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value > minimum && value < maximum) { @@ -1463,7 +1463,7 @@ public static void IsBetween(int value, int minimum, int maximum, [CallerArgumen /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetween(int value, int minimum, int maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetween(int value, int minimum, int maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value <= minimum || value >= maximum) { @@ -1486,7 +1486,7 @@ public static void IsNotBetween(int value, int minimum, int maximum, [CallerArgu /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetweenOrEqualTo(int value, int minimum, int maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetweenOrEqualTo(int value, int minimum, int maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum && value <= maximum) { @@ -1509,7 +1509,7 @@ public static void IsBetweenOrEqualTo(int value, int minimum, int maximum, [Call /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetweenOrEqualTo(int value, int minimum, int maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetweenOrEqualTo(int value, int minimum, int maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < minimum || value > maximum) { @@ -1527,7 +1527,7 @@ public static void IsNotBetweenOrEqualTo(int value, int minimum, int maximum, [C /// The name of the input parameter being tested. /// Thrown if is != . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsEqualTo(uint value, uint target, [CallerArgumentExpression("value")] string name = "") + public static void IsEqualTo(uint value, uint target, [CallerArgumentExpression(nameof(value))] string name = "") { if (value == target) { @@ -1546,7 +1546,7 @@ public static void IsEqualTo(uint value, uint target, [CallerArgumentExpression( /// Thrown if is == . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotEqualTo(uint value, uint target, [CallerArgumentExpression("value")] string name = "") + public static void IsNotEqualTo(uint value, uint target, [CallerArgumentExpression(nameof(value))] string name = "") { if (value != target) { @@ -1565,7 +1565,7 @@ public static void IsNotEqualTo(uint value, uint target, [CallerArgumentExpressi /// Thrown if is >= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThan(uint value, uint maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThan(uint value, uint maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < maximum) { @@ -1584,7 +1584,7 @@ public static void IsLessThan(uint value, uint maximum, [CallerArgumentExpressio /// Thrown if is > . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThanOrEqualTo(uint value, uint maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThanOrEqualTo(uint value, uint maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value <= maximum) { @@ -1603,7 +1603,7 @@ public static void IsLessThanOrEqualTo(uint value, uint maximum, [CallerArgument /// Thrown if is <= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThan(uint value, uint minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThan(uint value, uint minimum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value > minimum) { @@ -1622,7 +1622,7 @@ public static void IsGreaterThan(uint value, uint minimum, [CallerArgumentExpres /// Thrown if is < . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThanOrEqualTo(uint value, uint minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThanOrEqualTo(uint value, uint minimum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum) { @@ -1645,7 +1645,7 @@ public static void IsGreaterThanOrEqualTo(uint value, uint minimum, [CallerArgum /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsInRange(uint value, uint minimum, uint maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsInRange(uint value, uint minimum, uint maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum && value < maximum) { @@ -1668,7 +1668,7 @@ public static void IsInRange(uint value, uint minimum, uint maximum, [CallerArgu /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotInRange(uint value, uint minimum, uint maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotInRange(uint value, uint minimum, uint maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < minimum || value >= maximum) { @@ -1691,7 +1691,7 @@ public static void IsNotInRange(uint value, uint minimum, uint maximum, [CallerA /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetween(uint value, uint minimum, uint maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetween(uint value, uint minimum, uint maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value > minimum && value < maximum) { @@ -1714,7 +1714,7 @@ public static void IsBetween(uint value, uint minimum, uint maximum, [CallerArgu /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetween(uint value, uint minimum, uint maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetween(uint value, uint minimum, uint maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value <= minimum || value >= maximum) { @@ -1737,7 +1737,7 @@ public static void IsNotBetween(uint value, uint minimum, uint maximum, [CallerA /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetweenOrEqualTo(uint value, uint minimum, uint maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetweenOrEqualTo(uint value, uint minimum, uint maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum && value <= maximum) { @@ -1760,7 +1760,7 @@ public static void IsBetweenOrEqualTo(uint value, uint minimum, uint maximum, [C /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetweenOrEqualTo(uint value, uint minimum, uint maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetweenOrEqualTo(uint value, uint minimum, uint maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < minimum || value > maximum) { @@ -1778,7 +1778,7 @@ public static void IsNotBetweenOrEqualTo(uint value, uint minimum, uint maximum, /// The name of the input parameter being tested. /// Thrown if is != . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsEqualTo(float value, float target, [CallerArgumentExpression("value")] string name = "") + public static void IsEqualTo(float value, float target, [CallerArgumentExpression(nameof(value))] string name = "") { if (value == target) { @@ -1797,7 +1797,7 @@ public static void IsEqualTo(float value, float target, [CallerArgumentExpressio /// Thrown if is == . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotEqualTo(float value, float target, [CallerArgumentExpression("value")] string name = "") + public static void IsNotEqualTo(float value, float target, [CallerArgumentExpression(nameof(value))] string name = "") { if (value != target) { @@ -1816,7 +1816,7 @@ public static void IsNotEqualTo(float value, float target, [CallerArgumentExpres /// Thrown if is >= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThan(float value, float maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThan(float value, float maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < maximum) { @@ -1835,7 +1835,7 @@ public static void IsLessThan(float value, float maximum, [CallerArgumentExpress /// Thrown if is > . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThanOrEqualTo(float value, float maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThanOrEqualTo(float value, float maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value <= maximum) { @@ -1854,7 +1854,7 @@ public static void IsLessThanOrEqualTo(float value, float maximum, [CallerArgume /// Thrown if is <= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThan(float value, float minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThan(float value, float minimum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value > minimum) { @@ -1873,7 +1873,7 @@ public static void IsGreaterThan(float value, float minimum, [CallerArgumentExpr /// Thrown if is < . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThanOrEqualTo(float value, float minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThanOrEqualTo(float value, float minimum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum) { @@ -1896,7 +1896,7 @@ public static void IsGreaterThanOrEqualTo(float value, float minimum, [CallerArg /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsInRange(float value, float minimum, float maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsInRange(float value, float minimum, float maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum && value < maximum) { @@ -1919,7 +1919,7 @@ public static void IsInRange(float value, float minimum, float maximum, [CallerA /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotInRange(float value, float minimum, float maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotInRange(float value, float minimum, float maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < minimum || value >= maximum) { @@ -1942,7 +1942,7 @@ public static void IsNotInRange(float value, float minimum, float maximum, [Call /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetween(float value, float minimum, float maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetween(float value, float minimum, float maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value > minimum && value < maximum) { @@ -1965,7 +1965,7 @@ public static void IsBetween(float value, float minimum, float maximum, [CallerA /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetween(float value, float minimum, float maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetween(float value, float minimum, float maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value <= minimum || value >= maximum) { @@ -1988,7 +1988,7 @@ public static void IsNotBetween(float value, float minimum, float maximum, [Call /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetweenOrEqualTo(float value, float minimum, float maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetweenOrEqualTo(float value, float minimum, float maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum && value <= maximum) { @@ -2011,7 +2011,7 @@ public static void IsBetweenOrEqualTo(float value, float minimum, float maximum, /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetweenOrEqualTo(float value, float minimum, float maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetweenOrEqualTo(float value, float minimum, float maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < minimum || value > maximum) { @@ -2029,7 +2029,7 @@ public static void IsNotBetweenOrEqualTo(float value, float minimum, float maxim /// The name of the input parameter being tested. /// Thrown if is != . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsEqualTo(long value, long target, [CallerArgumentExpression("value")] string name = "") + public static void IsEqualTo(long value, long target, [CallerArgumentExpression(nameof(value))] string name = "") { if (value == target) { @@ -2048,7 +2048,7 @@ public static void IsEqualTo(long value, long target, [CallerArgumentExpression( /// Thrown if is == . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotEqualTo(long value, long target, [CallerArgumentExpression("value")] string name = "") + public static void IsNotEqualTo(long value, long target, [CallerArgumentExpression(nameof(value))] string name = "") { if (value != target) { @@ -2067,7 +2067,7 @@ public static void IsNotEqualTo(long value, long target, [CallerArgumentExpressi /// Thrown if is >= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThan(long value, long maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThan(long value, long maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < maximum) { @@ -2086,7 +2086,7 @@ public static void IsLessThan(long value, long maximum, [CallerArgumentExpressio /// Thrown if is > . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThanOrEqualTo(long value, long maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThanOrEqualTo(long value, long maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value <= maximum) { @@ -2105,7 +2105,7 @@ public static void IsLessThanOrEqualTo(long value, long maximum, [CallerArgument /// Thrown if is <= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThan(long value, long minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThan(long value, long minimum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value > minimum) { @@ -2124,7 +2124,7 @@ public static void IsGreaterThan(long value, long minimum, [CallerArgumentExpres /// Thrown if is < . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThanOrEqualTo(long value, long minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThanOrEqualTo(long value, long minimum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum) { @@ -2147,7 +2147,7 @@ public static void IsGreaterThanOrEqualTo(long value, long minimum, [CallerArgum /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsInRange(long value, long minimum, long maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsInRange(long value, long minimum, long maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum && value < maximum) { @@ -2170,7 +2170,7 @@ public static void IsInRange(long value, long minimum, long maximum, [CallerArgu /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotInRange(long value, long minimum, long maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotInRange(long value, long minimum, long maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < minimum || value >= maximum) { @@ -2193,7 +2193,7 @@ public static void IsNotInRange(long value, long minimum, long maximum, [CallerA /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetween(long value, long minimum, long maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetween(long value, long minimum, long maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value > minimum && value < maximum) { @@ -2216,7 +2216,7 @@ public static void IsBetween(long value, long minimum, long maximum, [CallerArgu /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetween(long value, long minimum, long maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetween(long value, long minimum, long maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value <= minimum || value >= maximum) { @@ -2239,7 +2239,7 @@ public static void IsNotBetween(long value, long minimum, long maximum, [CallerA /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetweenOrEqualTo(long value, long minimum, long maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetweenOrEqualTo(long value, long minimum, long maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum && value <= maximum) { @@ -2262,7 +2262,7 @@ public static void IsBetweenOrEqualTo(long value, long minimum, long maximum, [C /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetweenOrEqualTo(long value, long minimum, long maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetweenOrEqualTo(long value, long minimum, long maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < minimum || value > maximum) { @@ -2280,7 +2280,7 @@ public static void IsNotBetweenOrEqualTo(long value, long minimum, long maximum, /// The name of the input parameter being tested. /// Thrown if is != . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsEqualTo(ulong value, ulong target, [CallerArgumentExpression("value")] string name = "") + public static void IsEqualTo(ulong value, ulong target, [CallerArgumentExpression(nameof(value))] string name = "") { if (value == target) { @@ -2299,7 +2299,7 @@ public static void IsEqualTo(ulong value, ulong target, [CallerArgumentExpressio /// Thrown if is == . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotEqualTo(ulong value, ulong target, [CallerArgumentExpression("value")] string name = "") + public static void IsNotEqualTo(ulong value, ulong target, [CallerArgumentExpression(nameof(value))] string name = "") { if (value != target) { @@ -2318,7 +2318,7 @@ public static void IsNotEqualTo(ulong value, ulong target, [CallerArgumentExpres /// Thrown if is >= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThan(ulong value, ulong maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThan(ulong value, ulong maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < maximum) { @@ -2337,7 +2337,7 @@ public static void IsLessThan(ulong value, ulong maximum, [CallerArgumentExpress /// Thrown if is > . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThanOrEqualTo(ulong value, ulong maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThanOrEqualTo(ulong value, ulong maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value <= maximum) { @@ -2356,7 +2356,7 @@ public static void IsLessThanOrEqualTo(ulong value, ulong maximum, [CallerArgume /// Thrown if is <= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThan(ulong value, ulong minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThan(ulong value, ulong minimum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value > minimum) { @@ -2375,7 +2375,7 @@ public static void IsGreaterThan(ulong value, ulong minimum, [CallerArgumentExpr /// Thrown if is < . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThanOrEqualTo(ulong value, ulong minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThanOrEqualTo(ulong value, ulong minimum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum) { @@ -2398,7 +2398,7 @@ public static void IsGreaterThanOrEqualTo(ulong value, ulong minimum, [CallerArg /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsInRange(ulong value, ulong minimum, ulong maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsInRange(ulong value, ulong minimum, ulong maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum && value < maximum) { @@ -2421,7 +2421,7 @@ public static void IsInRange(ulong value, ulong minimum, ulong maximum, [CallerA /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotInRange(ulong value, ulong minimum, ulong maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotInRange(ulong value, ulong minimum, ulong maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < minimum || value >= maximum) { @@ -2444,7 +2444,7 @@ public static void IsNotInRange(ulong value, ulong minimum, ulong maximum, [Call /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetween(ulong value, ulong minimum, ulong maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetween(ulong value, ulong minimum, ulong maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value > minimum && value < maximum) { @@ -2467,7 +2467,7 @@ public static void IsBetween(ulong value, ulong minimum, ulong maximum, [CallerA /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetween(ulong value, ulong minimum, ulong maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetween(ulong value, ulong minimum, ulong maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value <= minimum || value >= maximum) { @@ -2490,7 +2490,7 @@ public static void IsNotBetween(ulong value, ulong minimum, ulong maximum, [Call /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetweenOrEqualTo(ulong value, ulong minimum, ulong maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetweenOrEqualTo(ulong value, ulong minimum, ulong maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum && value <= maximum) { @@ -2513,7 +2513,7 @@ public static void IsBetweenOrEqualTo(ulong value, ulong minimum, ulong maximum, /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetweenOrEqualTo(ulong value, ulong minimum, ulong maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetweenOrEqualTo(ulong value, ulong minimum, ulong maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < minimum || value > maximum) { @@ -2531,7 +2531,7 @@ public static void IsNotBetweenOrEqualTo(ulong value, ulong minimum, ulong maxim /// The name of the input parameter being tested. /// Thrown if is != . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsEqualTo(double value, double target, [CallerArgumentExpression("value")] string name = "") + public static void IsEqualTo(double value, double target, [CallerArgumentExpression(nameof(value))] string name = "") { if (value == target) { @@ -2550,7 +2550,7 @@ public static void IsEqualTo(double value, double target, [CallerArgumentExpress /// Thrown if is == . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotEqualTo(double value, double target, [CallerArgumentExpression("value")] string name = "") + public static void IsNotEqualTo(double value, double target, [CallerArgumentExpression(nameof(value))] string name = "") { if (value != target) { @@ -2569,7 +2569,7 @@ public static void IsNotEqualTo(double value, double target, [CallerArgumentExpr /// Thrown if is >= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThan(double value, double maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThan(double value, double maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < maximum) { @@ -2588,7 +2588,7 @@ public static void IsLessThan(double value, double maximum, [CallerArgumentExpre /// Thrown if is > . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThanOrEqualTo(double value, double maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThanOrEqualTo(double value, double maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value <= maximum) { @@ -2607,7 +2607,7 @@ public static void IsLessThanOrEqualTo(double value, double maximum, [CallerArgu /// Thrown if is <= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThan(double value, double minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThan(double value, double minimum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value > minimum) { @@ -2626,7 +2626,7 @@ public static void IsGreaterThan(double value, double minimum, [CallerArgumentEx /// Thrown if is < . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThanOrEqualTo(double value, double minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThanOrEqualTo(double value, double minimum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum) { @@ -2649,7 +2649,7 @@ public static void IsGreaterThanOrEqualTo(double value, double minimum, [CallerA /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsInRange(double value, double minimum, double maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsInRange(double value, double minimum, double maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum && value < maximum) { @@ -2672,7 +2672,7 @@ public static void IsInRange(double value, double minimum, double maximum, [Call /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotInRange(double value, double minimum, double maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotInRange(double value, double minimum, double maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < minimum || value >= maximum) { @@ -2695,7 +2695,7 @@ public static void IsNotInRange(double value, double minimum, double maximum, [C /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetween(double value, double minimum, double maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetween(double value, double minimum, double maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value > minimum && value < maximum) { @@ -2718,7 +2718,7 @@ public static void IsBetween(double value, double minimum, double maximum, [Call /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetween(double value, double minimum, double maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetween(double value, double minimum, double maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value <= minimum || value >= maximum) { @@ -2741,7 +2741,7 @@ public static void IsNotBetween(double value, double minimum, double maximum, [C /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetweenOrEqualTo(double value, double minimum, double maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetweenOrEqualTo(double value, double minimum, double maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum && value <= maximum) { @@ -2764,7 +2764,7 @@ public static void IsBetweenOrEqualTo(double value, double minimum, double maxim /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetweenOrEqualTo(double value, double minimum, double maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetweenOrEqualTo(double value, double minimum, double maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < minimum || value > maximum) { @@ -2782,7 +2782,7 @@ public static void IsNotBetweenOrEqualTo(double value, double minimum, double ma /// The name of the input parameter being tested. /// Thrown if is != . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsEqualTo(decimal value, decimal target, [CallerArgumentExpression("value")] string name = "") + public static void IsEqualTo(decimal value, decimal target, [CallerArgumentExpression(nameof(value))] string name = "") { if (value == target) { @@ -2801,7 +2801,7 @@ public static void IsEqualTo(decimal value, decimal target, [CallerArgumentExpre /// Thrown if is == . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotEqualTo(decimal value, decimal target, [CallerArgumentExpression("value")] string name = "") + public static void IsNotEqualTo(decimal value, decimal target, [CallerArgumentExpression(nameof(value))] string name = "") { if (value != target) { @@ -2820,7 +2820,7 @@ public static void IsNotEqualTo(decimal value, decimal target, [CallerArgumentEx /// Thrown if is >= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThan(decimal value, decimal maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThan(decimal value, decimal maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < maximum) { @@ -2839,7 +2839,7 @@ public static void IsLessThan(decimal value, decimal maximum, [CallerArgumentExp /// Thrown if is > . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThanOrEqualTo(decimal value, decimal maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThanOrEqualTo(decimal value, decimal maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value <= maximum) { @@ -2858,7 +2858,7 @@ public static void IsLessThanOrEqualTo(decimal value, decimal maximum, [CallerAr /// Thrown if is <= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThan(decimal value, decimal minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThan(decimal value, decimal minimum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value > minimum) { @@ -2877,7 +2877,7 @@ public static void IsGreaterThan(decimal value, decimal minimum, [CallerArgument /// Thrown if is < . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThanOrEqualTo(decimal value, decimal minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThanOrEqualTo(decimal value, decimal minimum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum) { @@ -2900,7 +2900,7 @@ public static void IsGreaterThanOrEqualTo(decimal value, decimal minimum, [Calle /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsInRange(decimal value, decimal minimum, decimal maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsInRange(decimal value, decimal minimum, decimal maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum && value < maximum) { @@ -2923,7 +2923,7 @@ public static void IsInRange(decimal value, decimal minimum, decimal maximum, [C /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotInRange(decimal value, decimal minimum, decimal maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotInRange(decimal value, decimal minimum, decimal maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < minimum || value >= maximum) { @@ -2946,7 +2946,7 @@ public static void IsNotInRange(decimal value, decimal minimum, decimal maximum, /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetween(decimal value, decimal minimum, decimal maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetween(decimal value, decimal minimum, decimal maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value > minimum && value < maximum) { @@ -2969,7 +2969,7 @@ public static void IsBetween(decimal value, decimal minimum, decimal maximum, [C /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetween(decimal value, decimal minimum, decimal maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetween(decimal value, decimal minimum, decimal maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value <= minimum || value >= maximum) { @@ -2992,7 +2992,7 @@ public static void IsNotBetween(decimal value, decimal minimum, decimal maximum, /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetweenOrEqualTo(decimal value, decimal minimum, decimal maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetweenOrEqualTo(decimal value, decimal minimum, decimal maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum && value <= maximum) { @@ -3015,7 +3015,7 @@ public static void IsBetweenOrEqualTo(decimal value, decimal minimum, decimal ma /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetweenOrEqualTo(decimal value, decimal minimum, decimal maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetweenOrEqualTo(decimal value, decimal minimum, decimal maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < minimum || value > maximum) { @@ -3033,7 +3033,7 @@ public static void IsNotBetweenOrEqualTo(decimal value, decimal minimum, decimal /// The name of the input parameter being tested. /// Thrown if is != . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsEqualTo(nint value, nint target, [CallerArgumentExpression("value")] string name = "") + public static void IsEqualTo(nint value, nint target, [CallerArgumentExpression(nameof(value))] string name = "") { if (value == target) { @@ -3052,7 +3052,7 @@ public static void IsEqualTo(nint value, nint target, [CallerArgumentExpression( /// Thrown if is == . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotEqualTo(nint value, nint target, [CallerArgumentExpression("value")] string name = "") + public static void IsNotEqualTo(nint value, nint target, [CallerArgumentExpression(nameof(value))] string name = "") { if (value != target) { @@ -3071,7 +3071,7 @@ public static void IsNotEqualTo(nint value, nint target, [CallerArgumentExpressi /// Thrown if is >= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThan(nint value, nint maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThan(nint value, nint maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < maximum) { @@ -3090,7 +3090,7 @@ public static void IsLessThan(nint value, nint maximum, [CallerArgumentExpressio /// Thrown if is > . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThanOrEqualTo(nint value, nint maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThanOrEqualTo(nint value, nint maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value <= maximum) { @@ -3109,7 +3109,7 @@ public static void IsLessThanOrEqualTo(nint value, nint maximum, [CallerArgument /// Thrown if is <= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThan(nint value, nint minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThan(nint value, nint minimum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value > minimum) { @@ -3128,7 +3128,7 @@ public static void IsGreaterThan(nint value, nint minimum, [CallerArgumentExpres /// Thrown if is < . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThanOrEqualTo(nint value, nint minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThanOrEqualTo(nint value, nint minimum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum) { @@ -3151,7 +3151,7 @@ public static void IsGreaterThanOrEqualTo(nint value, nint minimum, [CallerArgum /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsInRange(nint value, nint minimum, nint maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsInRange(nint value, nint minimum, nint maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum && value < maximum) { @@ -3174,7 +3174,7 @@ public static void IsInRange(nint value, nint minimum, nint maximum, [CallerArgu /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotInRange(nint value, nint minimum, nint maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotInRange(nint value, nint minimum, nint maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < minimum || value >= maximum) { @@ -3197,7 +3197,7 @@ public static void IsNotInRange(nint value, nint minimum, nint maximum, [CallerA /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetween(nint value, nint minimum, nint maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetween(nint value, nint minimum, nint maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value > minimum && value < maximum) { @@ -3220,7 +3220,7 @@ public static void IsBetween(nint value, nint minimum, nint maximum, [CallerArgu /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetween(nint value, nint minimum, nint maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetween(nint value, nint minimum, nint maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value <= minimum || value >= maximum) { @@ -3243,7 +3243,7 @@ public static void IsNotBetween(nint value, nint minimum, nint maximum, [CallerA /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetweenOrEqualTo(nint value, nint minimum, nint maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetweenOrEqualTo(nint value, nint minimum, nint maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum && value <= maximum) { @@ -3266,7 +3266,7 @@ public static void IsBetweenOrEqualTo(nint value, nint minimum, nint maximum, [C /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetweenOrEqualTo(nint value, nint minimum, nint maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetweenOrEqualTo(nint value, nint minimum, nint maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < minimum || value > maximum) { @@ -3284,7 +3284,7 @@ public static void IsNotBetweenOrEqualTo(nint value, nint minimum, nint maximum, /// The name of the input parameter being tested. /// Thrown if is != . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsEqualTo(nuint value, nuint target, [CallerArgumentExpression("value")] string name = "") + public static void IsEqualTo(nuint value, nuint target, [CallerArgumentExpression(nameof(value))] string name = "") { if (value == target) { @@ -3303,7 +3303,7 @@ public static void IsEqualTo(nuint value, nuint target, [CallerArgumentExpressio /// Thrown if is == . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotEqualTo(nuint value, nuint target, [CallerArgumentExpression("value")] string name = "") + public static void IsNotEqualTo(nuint value, nuint target, [CallerArgumentExpression(nameof(value))] string name = "") { if (value != target) { @@ -3322,7 +3322,7 @@ public static void IsNotEqualTo(nuint value, nuint target, [CallerArgumentExpres /// Thrown if is >= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThan(nuint value, nuint maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThan(nuint value, nuint maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < maximum) { @@ -3341,7 +3341,7 @@ public static void IsLessThan(nuint value, nuint maximum, [CallerArgumentExpress /// Thrown if is > . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThanOrEqualTo(nuint value, nuint maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThanOrEqualTo(nuint value, nuint maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value <= maximum) { @@ -3360,7 +3360,7 @@ public static void IsLessThanOrEqualTo(nuint value, nuint maximum, [CallerArgume /// Thrown if is <= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThan(nuint value, nuint minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThan(nuint value, nuint minimum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value > minimum) { @@ -3379,7 +3379,7 @@ public static void IsGreaterThan(nuint value, nuint minimum, [CallerArgumentExpr /// Thrown if is < . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThanOrEqualTo(nuint value, nuint minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThanOrEqualTo(nuint value, nuint minimum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum) { @@ -3402,7 +3402,7 @@ public static void IsGreaterThanOrEqualTo(nuint value, nuint minimum, [CallerArg /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsInRange(nuint value, nuint minimum, nuint maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsInRange(nuint value, nuint minimum, nuint maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum && value < maximum) { @@ -3425,7 +3425,7 @@ public static void IsInRange(nuint value, nuint minimum, nuint maximum, [CallerA /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotInRange(nuint value, nuint minimum, nuint maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotInRange(nuint value, nuint minimum, nuint maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < minimum || value >= maximum) { @@ -3448,7 +3448,7 @@ public static void IsNotInRange(nuint value, nuint minimum, nuint maximum, [Call /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetween(nuint value, nuint minimum, nuint maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetween(nuint value, nuint minimum, nuint maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value > minimum && value < maximum) { @@ -3471,7 +3471,7 @@ public static void IsBetween(nuint value, nuint minimum, nuint maximum, [CallerA /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetween(nuint value, nuint minimum, nuint maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetween(nuint value, nuint minimum, nuint maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value <= minimum || value >= maximum) { @@ -3494,7 +3494,7 @@ public static void IsNotBetween(nuint value, nuint minimum, nuint maximum, [Call /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetweenOrEqualTo(nuint value, nuint minimum, nuint maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetweenOrEqualTo(nuint value, nuint minimum, nuint maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum && value <= maximum) { @@ -3517,7 +3517,7 @@ public static void IsBetweenOrEqualTo(nuint value, nuint minimum, nuint maximum, /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetweenOrEqualTo(nuint value, nuint minimum, nuint maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetweenOrEqualTo(nuint value, nuint minimum, nuint maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < minimum || value > maximum) { diff --git a/CommunityToolkit.Diagnostics/Generated/Guard.Comparable.Numeric.tt b/src/CommunityToolkit.Diagnostics/Generated/Guard.Comparable.Numeric.tt similarity index 93% rename from CommunityToolkit.Diagnostics/Generated/Guard.Comparable.Numeric.tt rename to src/CommunityToolkit.Diagnostics/Generated/Guard.Comparable.Numeric.tt index c22f8837f..c8c0b53da 100644 --- a/CommunityToolkit.Diagnostics/Generated/Guard.Comparable.Numeric.tt +++ b/src/CommunityToolkit.Diagnostics/Generated/Guard.Comparable.Numeric.tt @@ -23,7 +23,7 @@ var (type, prefix) = typeInfo; /// The name of the input parameter being tested. /// Thrown if is != . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsEqualTo(<#=type#> value, <#=type#> target, [CallerArgumentExpression("value")] string name = "") + public static void IsEqualTo(<#=type#> value, <#=type#> target, [CallerArgumentExpression(nameof(value))] string name = "") { if (value == target) { @@ -42,7 +42,7 @@ var (type, prefix) = typeInfo; /// Thrown if is == . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotEqualTo(<#=type#> value, <#=type#> target, [CallerArgumentExpression("value")] string name = "") + public static void IsNotEqualTo(<#=type#> value, <#=type#> target, [CallerArgumentExpression(nameof(value))] string name = "") { if (value != target) { @@ -61,7 +61,7 @@ var (type, prefix) = typeInfo; /// Thrown if is >= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThan(<#=type#> value, <#=type#> maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThan(<#=type#> value, <#=type#> maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < maximum) { @@ -80,7 +80,7 @@ var (type, prefix) = typeInfo; /// Thrown if is > . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThanOrEqualTo(<#=type#> value, <#=type#> maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThanOrEqualTo(<#=type#> value, <#=type#> maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value <= maximum) { @@ -99,7 +99,7 @@ var (type, prefix) = typeInfo; /// Thrown if is <= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThan(<#=type#> value, <#=type#> minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThan(<#=type#> value, <#=type#> minimum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value > minimum) { @@ -118,7 +118,7 @@ var (type, prefix) = typeInfo; /// Thrown if is < . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThanOrEqualTo(<#=type#> value, <#=type#> minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThanOrEqualTo(<#=type#> value, <#=type#> minimum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum) { @@ -141,7 +141,7 @@ var (type, prefix) = typeInfo; /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsInRange(<#=type#> value, <#=type#> minimum, <#=type#> maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsInRange(<#=type#> value, <#=type#> minimum, <#=type#> maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum && value < maximum) { @@ -164,7 +164,7 @@ var (type, prefix) = typeInfo; /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotInRange(<#=type#> value, <#=type#> minimum, <#=type#> maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotInRange(<#=type#> value, <#=type#> minimum, <#=type#> maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < minimum || value >= maximum) { @@ -187,7 +187,7 @@ var (type, prefix) = typeInfo; /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetween(<#=type#> value, <#=type#> minimum, <#=type#> maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetween(<#=type#> value, <#=type#> minimum, <#=type#> maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value > minimum && value < maximum) { @@ -210,7 +210,7 @@ var (type, prefix) = typeInfo; /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetween(<#=type#> value, <#=type#> minimum, <#=type#> maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetween(<#=type#> value, <#=type#> minimum, <#=type#> maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value <= minimum || value >= maximum) { @@ -233,7 +233,7 @@ var (type, prefix) = typeInfo; /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetweenOrEqualTo(<#=type#> value, <#=type#> minimum, <#=type#> maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetweenOrEqualTo(<#=type#> value, <#=type#> minimum, <#=type#> maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value >= minimum && value <= maximum) { @@ -256,7 +256,7 @@ var (type, prefix) = typeInfo; /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetweenOrEqualTo(<#=type#> value, <#=type#> minimum, <#=type#> maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetweenOrEqualTo(<#=type#> value, <#=type#> minimum, <#=type#> maximum, [CallerArgumentExpression(nameof(value))] string name = "") { if (value < minimum || value > maximum) { diff --git a/CommunityToolkit.Diagnostics/Generated/Guard.md b/src/CommunityToolkit.Diagnostics/Generated/Guard.md similarity index 94% rename from CommunityToolkit.Diagnostics/Generated/Guard.md rename to src/CommunityToolkit.Diagnostics/Generated/Guard.md index b787eda14..d2b946064 100644 --- a/CommunityToolkit.Diagnostics/Generated/Guard.md +++ b/src/CommunityToolkit.Diagnostics/Generated/Guard.md @@ -1,6 +1,6 @@ # T4 templates and generated APIs -This folder contains a number of template files (with the `.tt` or `.ttinclude` extensions) and the generated `.g.cs` files generated by those templates. The template files use the T4 format, which is natively supported by Visual Studio (more info is available [here](https://docs.microsoft.com/visualstudio/modeling/code-generation-and-t4-text-templates)). +This folder contains a number of template files (with the `.tt` or `.ttinclude` extensions) and the generated `.g.cs` files generated by those templates. The template files use the T4 format, which is natively supported by Visual Studio (more info is available [here](https://learn.microsoft.com/visualstudio/modeling/code-generation-and-t4-text-templates)). ## Why is this needed? diff --git a/CommunityToolkit.Diagnostics/Generated/ThrowHelper.Collection.g.cs b/src/CommunityToolkit.Diagnostics/Generated/ThrowHelper.Collection.g.cs similarity index 100% rename from CommunityToolkit.Diagnostics/Generated/ThrowHelper.Collection.g.cs rename to src/CommunityToolkit.Diagnostics/Generated/ThrowHelper.Collection.g.cs diff --git a/CommunityToolkit.Diagnostics/Generated/ThrowHelper.Collection.tt b/src/CommunityToolkit.Diagnostics/Generated/ThrowHelper.Collection.tt similarity index 100% rename from CommunityToolkit.Diagnostics/Generated/ThrowHelper.Collection.tt rename to src/CommunityToolkit.Diagnostics/Generated/ThrowHelper.Collection.tt diff --git a/src/CommunityToolkit.Diagnostics/Generated/TypeInfo.g.cs b/src/CommunityToolkit.Diagnostics/Generated/TypeInfo.g.cs new file mode 100644 index 000000000..6b9aad69c --- /dev/null +++ b/src/CommunityToolkit.Diagnostics/Generated/TypeInfo.g.cs @@ -0,0 +1,3 @@ +// ===================== +// Auto generated file +// ===================== diff --git a/CommunityToolkit.Diagnostics/Generated/TypeInfo.ttinclude b/src/CommunityToolkit.Diagnostics/Generated/TypeInfo.ttinclude similarity index 100% rename from CommunityToolkit.Diagnostics/Generated/TypeInfo.ttinclude rename to src/CommunityToolkit.Diagnostics/Generated/TypeInfo.ttinclude diff --git a/CommunityToolkit.Diagnostics/Guard.Boolean.cs b/src/CommunityToolkit.Diagnostics/Guard.Boolean.cs similarity index 97% rename from CommunityToolkit.Diagnostics/Guard.Boolean.cs rename to src/CommunityToolkit.Diagnostics/Guard.Boolean.cs index 6d65c6624..3e80d32bb 100644 --- a/CommunityToolkit.Diagnostics/Guard.Boolean.cs +++ b/src/CommunityToolkit.Diagnostics/Guard.Boolean.cs @@ -3,10 +3,14 @@ // See the LICENSE file in the project root for more information. using System; +#if NET6_0_OR_GREATER using System.ComponentModel; +#endif using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +#if NET6_0_OR_GREATER using System.Text; +#endif namespace CommunityToolkit.Diagnostics; @@ -20,7 +24,7 @@ public static partial class Guard /// The name of the input parameter being tested. /// Thrown if is . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsTrue([DoesNotReturnIf(false)] bool value, [CallerArgumentExpression("value")] string name = "") + public static void IsTrue([DoesNotReturnIf(false)] bool value, [CallerArgumentExpression(nameof(value))] string name = "") { if (value) { @@ -55,7 +59,7 @@ public static void IsTrue([DoesNotReturnIf(false)] bool value, string name, stri /// The name of the input parameter being tested. /// Thrown if is . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsFalse([DoesNotReturnIf(true)] bool value, [CallerArgumentExpression("value")] string name = "") + public static void IsFalse([DoesNotReturnIf(true)] bool value, [CallerArgumentExpression(nameof(value))] string name = "") { if (!value) { @@ -92,7 +96,7 @@ public static void IsFalse([DoesNotReturnIf(true)] bool value, string name, stri /// A message to display if is . /// Thrown if is . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsTrue([DoesNotReturnIf(false)] bool value, string name, [InterpolatedStringHandlerArgument("value")] ref IsTrueInterpolatedStringHandler message) + public static void IsTrue([DoesNotReturnIf(false)] bool value, string name, [InterpolatedStringHandlerArgument(nameof(value))] ref IsTrueInterpolatedStringHandler message) { if (value) { @@ -110,7 +114,7 @@ public static void IsTrue([DoesNotReturnIf(false)] bool value, string name, [Int /// A message to display if is . /// Thrown if is . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsFalse([DoesNotReturnIf(true)] bool value, string name, [InterpolatedStringHandlerArgument("value")] ref IsFalseInterpolatedStringHandler message) + public static void IsFalse([DoesNotReturnIf(true)] bool value, string name, [InterpolatedStringHandlerArgument(nameof(value))] ref IsFalseInterpolatedStringHandler message) { if (!value) { diff --git a/CommunityToolkit.Diagnostics/Guard.Comparable.Generic.cs b/src/CommunityToolkit.Diagnostics/Guard.Comparable.Generic.cs similarity index 95% rename from CommunityToolkit.Diagnostics/Guard.Comparable.Generic.cs rename to src/CommunityToolkit.Diagnostics/Guard.Comparable.Generic.cs index 2c63162c2..52b2f33a7 100644 --- a/CommunityToolkit.Diagnostics/Guard.Comparable.Generic.cs +++ b/src/CommunityToolkit.Diagnostics/Guard.Comparable.Generic.cs @@ -18,7 +18,7 @@ partial class Guard /// The name of the input parameter being tested. /// Thrown if is not . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsDefault(T value, [CallerArgumentExpression("value")] string name = "") + public static void IsDefault(T value, [CallerArgumentExpression(nameof(value))] string name = "") where T : struct, IEquatable { if (value.Equals(default)) @@ -37,7 +37,7 @@ public static void IsDefault(T value, [CallerArgumentExpression("value")] str /// The name of the input parameter being tested. /// Thrown if is . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotDefault(T value, [CallerArgumentExpression("value")] string name = "") + public static void IsNotDefault(T value, [CallerArgumentExpression(nameof(value))] string name = "") where T : struct, IEquatable { if (!value.Equals(default)) @@ -58,7 +58,7 @@ public static void IsNotDefault(T value, [CallerArgumentExpression("value")] /// Thrown if is != . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsEqualTo(T value, T target, [CallerArgumentExpression("value")] string name = "") + public static void IsEqualTo(T value, T target, [CallerArgumentExpression(nameof(value))] string name = "") where T : notnull, IEquatable { if (value.Equals(target)) @@ -79,7 +79,7 @@ public static void IsEqualTo(T value, T target, [CallerArgumentExpression("va /// Thrown if is == . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotEqualTo(T value, T target, [CallerArgumentExpression("value")] string name = "") + public static void IsNotEqualTo(T value, T target, [CallerArgumentExpression(nameof(value))] string name = "") where T : notnull, IEquatable { if (!value.Equals(target)) @@ -99,7 +99,7 @@ public static void IsNotEqualTo(T value, T target, [CallerArgumentExpression( /// The name of the input parameter being tested. /// Thrown if is not a bitwise match for . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void IsBitwiseEqualTo(T value, T target, [CallerArgumentExpression("value")] string name = "") + public static unsafe void IsBitwiseEqualTo(T value, T target, [CallerArgumentExpression(nameof(value))] string name = "") where T : unmanaged { // Include some fast paths if the input type is of size 1, 2, 4, 8, or 16. @@ -214,7 +214,7 @@ private static unsafe bool Bit64Compare(ref ulong left, ref ulong right) /// Thrown if is >= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThan(T value, T maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThan(T value, T maximum, [CallerArgumentExpression(nameof(value))] string name = "") where T : notnull, IComparable { if (value.CompareTo(maximum) < 0) @@ -235,7 +235,7 @@ public static void IsLessThan(T value, T maximum, [CallerArgumentExpression(" /// Thrown if is > . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThanOrEqualTo(T value, T maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThanOrEqualTo(T value, T maximum, [CallerArgumentExpression(nameof(value))] string name = "") where T : notnull, IComparable { if (value.CompareTo(maximum) <= 0) @@ -256,7 +256,7 @@ public static void IsLessThanOrEqualTo(T value, T maximum, [CallerArgumentExp /// Thrown if is <= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThan(T value, T minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThan(T value, T minimum, [CallerArgumentExpression(nameof(value))] string name = "") where T : notnull, IComparable { if (value.CompareTo(minimum) > 0) @@ -277,7 +277,7 @@ public static void IsGreaterThan(T value, T minimum, [CallerArgumentExpressio /// Thrown if is < . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThanOrEqualTo(T value, T minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThanOrEqualTo(T value, T minimum, [CallerArgumentExpression(nameof(value))] string name = "") where T : notnull, IComparable { if (value.CompareTo(minimum) >= 0) @@ -302,7 +302,7 @@ public static void IsGreaterThanOrEqualTo(T value, T minimum, [CallerArgument /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsInRange(T value, T minimum, T maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsInRange(T value, T minimum, T maximum, [CallerArgumentExpression(nameof(value))] string name = "") where T : notnull, IComparable { if (value.CompareTo(minimum) >= 0 && value.CompareTo(maximum) < 0) @@ -327,7 +327,7 @@ public static void IsInRange(T value, T minimum, T maximum, [CallerArgumentEx /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotInRange(T value, T minimum, T maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotInRange(T value, T minimum, T maximum, [CallerArgumentExpression(nameof(value))] string name = "") where T : notnull, IComparable { if (value.CompareTo(minimum) < 0 || value.CompareTo(maximum) >= 0) @@ -352,7 +352,7 @@ public static void IsNotInRange(T value, T minimum, T maximum, [CallerArgumen /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetween(T value, T minimum, T maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetween(T value, T minimum, T maximum, [CallerArgumentExpression(nameof(value))] string name = "") where T : notnull, IComparable { if (value.CompareTo(minimum) > 0 && value.CompareTo(maximum) < 0) @@ -377,7 +377,7 @@ public static void IsBetween(T value, T minimum, T maximum, [CallerArgumentEx /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetween(T value, T minimum, T maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetween(T value, T minimum, T maximum, [CallerArgumentExpression(nameof(value))] string name = "") where T : notnull, IComparable { if (value.CompareTo(minimum) <= 0 || value.CompareTo(maximum) >= 0) @@ -402,7 +402,7 @@ public static void IsNotBetween(T value, T minimum, T maximum, [CallerArgumen /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetweenOrEqualTo(T value, T minimum, T maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetweenOrEqualTo(T value, T minimum, T maximum, [CallerArgumentExpression(nameof(value))] string name = "") where T : notnull, IComparable { if (value.CompareTo(minimum) >= 0 && value.CompareTo(maximum) <= 0) @@ -427,7 +427,7 @@ public static void IsBetweenOrEqualTo(T value, T minimum, T maximum, [CallerA /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetweenOrEqualTo(T value, T minimum, T maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetweenOrEqualTo(T value, T minimum, T maximum, [CallerArgumentExpression(nameof(value))] string name = "") where T : notnull, IComparable { if (value.CompareTo(minimum) < 0 || value.CompareTo(maximum) > 0) diff --git a/CommunityToolkit.Diagnostics/Guard.Comparable.Numeric.cs b/src/CommunityToolkit.Diagnostics/Guard.Comparable.Numeric.cs similarity index 94% rename from CommunityToolkit.Diagnostics/Guard.Comparable.Numeric.cs rename to src/CommunityToolkit.Diagnostics/Guard.Comparable.Numeric.cs index 874f093d2..a69b96f42 100644 --- a/CommunityToolkit.Diagnostics/Guard.Comparable.Numeric.cs +++ b/src/CommunityToolkit.Diagnostics/Guard.Comparable.Numeric.cs @@ -19,7 +19,7 @@ partial class Guard /// The name of the input parameter being tested. /// Thrown if ( - ) > . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsCloseTo(int value, int target, uint delta, [CallerArgumentExpression("value")] string name = "") + public static void IsCloseTo(int value, int target, uint delta, [CallerArgumentExpression(nameof(value))] string name = "") { uint difference; @@ -49,7 +49,7 @@ public static void IsCloseTo(int value, int target, uint delta, [CallerArgumentE /// The name of the input parameter being tested. /// Thrown if ( - ) <= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotCloseTo(int value, int target, uint delta, [CallerArgumentExpression("value")] string name = "") + public static void IsNotCloseTo(int value, int target, uint delta, [CallerArgumentExpression(nameof(value))] string name = "") { uint difference; @@ -79,7 +79,7 @@ public static void IsNotCloseTo(int value, int target, uint delta, [CallerArgume /// The name of the input parameter being tested. /// Thrown if ( - ) > . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsCloseTo(long value, long target, ulong delta, [CallerArgumentExpression("value")] string name = "") + public static void IsCloseTo(long value, long target, ulong delta, [CallerArgumentExpression(nameof(value))] string name = "") { ulong difference; @@ -109,7 +109,7 @@ public static void IsCloseTo(long value, long target, ulong delta, [CallerArgume /// The name of the input parameter being tested. /// Thrown if ( - ) <= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotCloseTo(long value, long target, ulong delta, [CallerArgumentExpression("value")] string name = "") + public static void IsNotCloseTo(long value, long target, ulong delta, [CallerArgumentExpression(nameof(value))] string name = "") { ulong difference; @@ -139,7 +139,7 @@ public static void IsNotCloseTo(long value, long target, ulong delta, [CallerArg /// The name of the input parameter being tested. /// Thrown if ( - ) > . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsCloseTo(float value, float target, float delta, [CallerArgumentExpression("value")] string name = "") + public static void IsCloseTo(float value, float target, float delta, [CallerArgumentExpression(nameof(value))] string name = "") { if (Math.Abs(value - target) <= delta) { @@ -158,7 +158,7 @@ public static void IsCloseTo(float value, float target, float delta, [CallerArgu /// The name of the input parameter being tested. /// Thrown if ( - ) <= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotCloseTo(float value, float target, float delta, [CallerArgumentExpression("value")] string name = "") + public static void IsNotCloseTo(float value, float target, float delta, [CallerArgumentExpression(nameof(value))] string name = "") { if (Math.Abs(value - target) > delta) { @@ -177,7 +177,7 @@ public static void IsNotCloseTo(float value, float target, float delta, [CallerA /// The name of the input parameter being tested. /// Thrown if ( - ) > . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsCloseTo(double value, double target, double delta, [CallerArgumentExpression("value")] string name = "") + public static void IsCloseTo(double value, double target, double delta, [CallerArgumentExpression(nameof(value))] string name = "") { if (Math.Abs(value - target) <= delta) { @@ -196,7 +196,7 @@ public static void IsCloseTo(double value, double target, double delta, [CallerA /// The name of the input parameter being tested. /// Thrown if ( - ) <= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotCloseTo(double value, double target, double delta, [CallerArgumentExpression("value")] string name = "") + public static void IsNotCloseTo(double value, double target, double delta, [CallerArgumentExpression(nameof(value))] string name = "") { if (Math.Abs(value - target) > delta) { @@ -215,7 +215,7 @@ public static void IsNotCloseTo(double value, double target, double delta, [Call /// The name of the input parameter being tested. /// Thrown if ( - ) > . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsCloseTo(nint value, nint target, nuint delta, [CallerArgumentExpression("value")] string name = "") + public static void IsCloseTo(nint value, nint target, nuint delta, [CallerArgumentExpression(nameof(value))] string name = "") { nuint difference; @@ -245,7 +245,7 @@ public static void IsCloseTo(nint value, nint target, nuint delta, [CallerArgume /// The name of the input parameter being tested. /// Thrown if ( - ) <= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotCloseTo(nint value, nint target, nuint delta, [CallerArgumentExpression("value")] string name = "") + public static void IsNotCloseTo(nint value, nint target, nuint delta, [CallerArgumentExpression(nameof(value))] string name = "") { nuint difference; diff --git a/CommunityToolkit.Diagnostics/Guard.IO.cs b/src/CommunityToolkit.Diagnostics/Guard.IO.cs similarity index 94% rename from CommunityToolkit.Diagnostics/Guard.IO.cs rename to src/CommunityToolkit.Diagnostics/Guard.IO.cs index 7d58cdc7e..a4446782e 100644 --- a/CommunityToolkit.Diagnostics/Guard.IO.cs +++ b/src/CommunityToolkit.Diagnostics/Guard.IO.cs @@ -18,7 +18,7 @@ partial class Guard /// The name of the input parameter being tested. /// Thrown if doesn't support reading. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CanRead(Stream stream, [CallerArgumentExpression("stream")] string name = "") + public static void CanRead(Stream stream, [CallerArgumentExpression(nameof(stream))] string name = "") { if (stream.CanRead) { @@ -35,7 +35,7 @@ public static void CanRead(Stream stream, [CallerArgumentExpression("stream")] s /// The name of the input parameter being tested. /// Thrown if doesn't support writing. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CanWrite(Stream stream, [CallerArgumentExpression("stream")] string name = "") + public static void CanWrite(Stream stream, [CallerArgumentExpression(nameof(stream))] string name = "") { if (stream.CanWrite) { @@ -52,7 +52,7 @@ public static void CanWrite(Stream stream, [CallerArgumentExpression("stream")] /// The name of the input parameter being tested. /// Thrown if doesn't support seeking. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CanSeek(Stream stream, [CallerArgumentExpression("stream")] string name = "") + public static void CanSeek(Stream stream, [CallerArgumentExpression(nameof(stream))] string name = "") { if (stream.CanSeek) { @@ -69,7 +69,7 @@ public static void CanSeek(Stream stream, [CallerArgumentExpression("stream")] s /// The name of the input parameter being tested. /// Thrown if is not at the starting position. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsAtStartPosition(Stream stream, [CallerArgumentExpression("stream")] string name = "") + public static void IsAtStartPosition(Stream stream, [CallerArgumentExpression(nameof(stream))] string name = "") { if (stream.Position == 0) { diff --git a/CommunityToolkit.Diagnostics/Guard.String.cs b/src/CommunityToolkit.Diagnostics/Guard.String.cs similarity index 93% rename from CommunityToolkit.Diagnostics/Guard.String.cs rename to src/CommunityToolkit.Diagnostics/Guard.String.cs index ef2d6f964..8b11eb6e3 100644 --- a/CommunityToolkit.Diagnostics/Guard.String.cs +++ b/src/CommunityToolkit.Diagnostics/Guard.String.cs @@ -20,7 +20,7 @@ partial class Guard /// The name of the input parameter being tested. /// Thrown if is neither nor empty. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNullOrEmpty(string? text, [CallerArgumentExpression("text")] string name = "") + public static void IsNullOrEmpty(string? text, [CallerArgumentExpression(nameof(text))] string name = "") { if (string.IsNullOrEmpty(text)) { @@ -38,7 +38,7 @@ public static void IsNullOrEmpty(string? text, [CallerArgumentExpression("text") /// Thrown if is . /// Thrown if is empty. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotNullOrEmpty([NotNull] string? text, [CallerArgumentExpression("text")] string name = "") + public static void IsNotNullOrEmpty([NotNull] string? text, [CallerArgumentExpression(nameof(text))] string name = "") { if (!string.IsNullOrEmpty(text)) { @@ -55,7 +55,7 @@ public static void IsNotNullOrEmpty([NotNull] string? text, [CallerArgumentExpre /// The name of the input parameter being tested. /// Thrown if is neither nor whitespace. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNullOrWhiteSpace(string? text, [CallerArgumentExpression("text")] string name = "") + public static void IsNullOrWhiteSpace(string? text, [CallerArgumentExpression(nameof(text))] string name = "") { if (string.IsNullOrWhiteSpace(text)) { @@ -73,7 +73,7 @@ public static void IsNullOrWhiteSpace(string? text, [CallerArgumentExpression("t /// Thrown if is . /// Thrown if is whitespace. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotNullOrWhiteSpace([NotNull] string? text, [CallerArgumentExpression("text")] string name = "") + public static void IsNotNullOrWhiteSpace([NotNull] string? text, [CallerArgumentExpression(nameof(text))] string name = "") { if (!string.IsNullOrWhiteSpace(text)) { @@ -90,7 +90,7 @@ public static void IsNotNullOrWhiteSpace([NotNull] string? text, [CallerArgument /// The name of the input parameter being tested. /// Thrown if is empty. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsEmpty(string text, [CallerArgumentExpression("text")] string name = "") + public static void IsEmpty(string text, [CallerArgumentExpression(nameof(text))] string name = "") { if (text.Length == 0) { @@ -107,7 +107,7 @@ public static void IsEmpty(string text, [CallerArgumentExpression("text")] strin /// The name of the input parameter being tested. /// Thrown if is empty. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotEmpty(string text, [CallerArgumentExpression("text")] string name = "") + public static void IsNotEmpty(string text, [CallerArgumentExpression(nameof(text))] string name = "") { if (text.Length != 0) { @@ -124,7 +124,7 @@ public static void IsNotEmpty(string text, [CallerArgumentExpression("text")] st /// The name of the input parameter being tested. /// Thrown if is neither nor whitespace. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsWhiteSpace(string text, [CallerArgumentExpression("text")] string name = "") + public static void IsWhiteSpace(string text, [CallerArgumentExpression(nameof(text))] string name = "") { if (string.IsNullOrWhiteSpace(text)) { @@ -141,7 +141,7 @@ public static void IsWhiteSpace(string text, [CallerArgumentExpression("text")] /// The name of the input parameter being tested. /// Thrown if is or whitespace. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotWhiteSpace(string text, [CallerArgumentExpression("text")] string name = "") + public static void IsNotWhiteSpace(string text, [CallerArgumentExpression(nameof(text))] string name = "") { if (!string.IsNullOrWhiteSpace(text)) { @@ -159,7 +159,7 @@ public static void IsNotWhiteSpace(string text, [CallerArgumentExpression("text" /// The name of the input parameter being tested. /// Thrown if the size of is != . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeEqualTo(string text, int size, [CallerArgumentExpression("text")] string name = "") + public static void HasSizeEqualTo(string text, int size, [CallerArgumentExpression(nameof(text))] string name = "") { if (text.Length == size) { @@ -177,7 +177,7 @@ public static void HasSizeEqualTo(string text, int size, [CallerArgumentExpressi /// The name of the input parameter being tested. /// Thrown if the size of is == . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeNotEqualTo(string text, int size, [CallerArgumentExpression("text")] string name = "") + public static void HasSizeNotEqualTo(string text, int size, [CallerArgumentExpression(nameof(text))] string name = "") { if (text.Length != size) { @@ -195,7 +195,7 @@ public static void HasSizeNotEqualTo(string text, int size, [CallerArgumentExpre /// The name of the input parameter being tested. /// Thrown if the size of is <= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeGreaterThan(string text, int size, [CallerArgumentExpression("text")] string name = "") + public static void HasSizeGreaterThan(string text, int size, [CallerArgumentExpression(nameof(text))] string name = "") { if (text.Length > size) { @@ -213,7 +213,7 @@ public static void HasSizeGreaterThan(string text, int size, [CallerArgumentExpr /// The name of the input parameter being tested. /// Thrown if the size of is < . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeGreaterThanOrEqualTo(string text, int size, [CallerArgumentExpression("text")] string name = "") + public static void HasSizeGreaterThanOrEqualTo(string text, int size, [CallerArgumentExpression(nameof(text))] string name = "") { if (text.Length >= size) { @@ -231,7 +231,7 @@ public static void HasSizeGreaterThanOrEqualTo(string text, int size, [CallerArg /// The name of the input parameter being tested. /// Thrown if the size of is >= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThan(string text, int size, [CallerArgumentExpression("text")] string name = "") + public static void HasSizeLessThan(string text, int size, [CallerArgumentExpression(nameof(text))] string name = "") { if (text.Length < size) { @@ -249,7 +249,7 @@ public static void HasSizeLessThan(string text, int size, [CallerArgumentExpress /// The name of the input parameter being tested. /// Thrown if the size of is > . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThanOrEqualTo(string text, int size, [CallerArgumentExpression("text")] string name = "") + public static void HasSizeLessThanOrEqualTo(string text, int size, [CallerArgumentExpression(nameof(text))] string name = "") { if (text.Length <= size) { @@ -268,7 +268,7 @@ public static void HasSizeLessThanOrEqualTo(string text, int size, [CallerArgume /// Thrown if the size of is != the one of . /// The type is immutable, but the name of this API is kept for consistency with the other overloads. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeEqualTo(string text, string destination, [CallerArgumentExpression("text")] string name = "") + public static void HasSizeEqualTo(string text, string destination, [CallerArgumentExpression(nameof(text))] string name = "") { if (text.Length == destination.Length) { @@ -287,7 +287,7 @@ public static void HasSizeEqualTo(string text, string destination, [CallerArgume /// Thrown if the size of is > the one of . /// The type is immutable, but the name of this API is kept for consistency with the other overloads. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThanOrEqualTo(string text, string destination, [CallerArgumentExpression("text")] string name = "") + public static void HasSizeLessThanOrEqualTo(string text, string destination, [CallerArgumentExpression(nameof(text))] string name = "") { if (text.Length <= destination.Length) { @@ -305,7 +305,7 @@ public static void HasSizeLessThanOrEqualTo(string text, string destination, [Ca /// The name of the input parameter being tested. /// Thrown if is not valid to access . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsInRangeFor(int index, string text, [CallerArgumentExpression("index")] string name = "") + public static void IsInRangeFor(int index, string text, [CallerArgumentExpression(nameof(index))] string name = "") { if ((uint)index < (uint)text.Length) { @@ -323,7 +323,7 @@ public static void IsInRangeFor(int index, string text, [CallerArgumentExpressio /// The name of the input parameter being tested. /// Thrown if is valid to access . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotInRangeFor(int index, string text, [CallerArgumentExpression("index")] string name = "") + public static void IsNotInRangeFor(int index, string text, [CallerArgumentExpression(nameof(index))] string name = "") { if ((uint)index >= (uint)text.Length) { diff --git a/CommunityToolkit.Diagnostics/Guard.Tasks.cs b/src/CommunityToolkit.Diagnostics/Guard.Tasks.cs similarity index 93% rename from CommunityToolkit.Diagnostics/Guard.Tasks.cs rename to src/CommunityToolkit.Diagnostics/Guard.Tasks.cs index ea5ffdd66..b3e1970cd 100644 --- a/CommunityToolkit.Diagnostics/Guard.Tasks.cs +++ b/src/CommunityToolkit.Diagnostics/Guard.Tasks.cs @@ -18,7 +18,7 @@ partial class Guard /// The name of the input parameter being tested. /// Thrown if is not in a completed state. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsCompleted(Task task, [CallerArgumentExpression("task")] string name = "") + public static void IsCompleted(Task task, [CallerArgumentExpression(nameof(task))] string name = "") { if (task.IsCompleted) { @@ -35,7 +35,7 @@ public static void IsCompleted(Task task, [CallerArgumentExpression("task")] str /// The name of the input parameter being tested. /// Thrown if is in a completed state. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotCompleted(Task task, [CallerArgumentExpression("task")] string name = "") + public static void IsNotCompleted(Task task, [CallerArgumentExpression(nameof(task))] string name = "") { if (!task.IsCompleted) { @@ -52,7 +52,7 @@ public static void IsNotCompleted(Task task, [CallerArgumentExpression("task")] /// The name of the input parameter being tested. /// Thrown if has not been completed successfully. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsCompletedSuccessfully(Task task, [CallerArgumentExpression("task")] string name = "") + public static void IsCompletedSuccessfully(Task task, [CallerArgumentExpression(nameof(task))] string name = "") { if (task.Status == TaskStatus.RanToCompletion) { @@ -69,7 +69,7 @@ public static void IsCompletedSuccessfully(Task task, [CallerArgumentExpression( /// The name of the input parameter being tested. /// Thrown if has been completed successfully. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotCompletedSuccessfully(Task task, [CallerArgumentExpression("task")] string name = "") + public static void IsNotCompletedSuccessfully(Task task, [CallerArgumentExpression(nameof(task))] string name = "") { if (task.Status != TaskStatus.RanToCompletion) { @@ -86,7 +86,7 @@ public static void IsNotCompletedSuccessfully(Task task, [CallerArgumentExpressi /// The name of the input parameter being tested. /// Thrown if is not faulted. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsFaulted(Task task, [CallerArgumentExpression("task")] string name = "") + public static void IsFaulted(Task task, [CallerArgumentExpression(nameof(task))] string name = "") { if (task.IsFaulted) { @@ -103,7 +103,7 @@ public static void IsFaulted(Task task, [CallerArgumentExpression("task")] strin /// The name of the input parameter being tested. /// Thrown if is faulted. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotFaulted(Task task, [CallerArgumentExpression("task")] string name = "") + public static void IsNotFaulted(Task task, [CallerArgumentExpression(nameof(task))] string name = "") { if (!task.IsFaulted) { @@ -120,7 +120,7 @@ public static void IsNotFaulted(Task task, [CallerArgumentExpression("task")] st /// The name of the input parameter being tested. /// Thrown if is not canceled. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsCanceled(Task task, [CallerArgumentExpression("task")] string name = "") + public static void IsCanceled(Task task, [CallerArgumentExpression(nameof(task))] string name = "") { if (task.IsCanceled) { @@ -137,7 +137,7 @@ public static void IsCanceled(Task task, [CallerArgumentExpression("task")] stri /// The name of the input parameter being tested. /// Thrown if is canceled. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotCanceled(Task task, [CallerArgumentExpression("task")] string name = "") + public static void IsNotCanceled(Task task, [CallerArgumentExpression(nameof(task))] string name = "") { if (!task.IsCanceled) { @@ -155,7 +155,7 @@ public static void IsNotCanceled(Task task, [CallerArgumentExpression("task")] s /// The name of the input parameter being tested. /// Thrown if doesn't match . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasStatusEqualTo(Task task, TaskStatus status, [CallerArgumentExpression("task")] string name = "") + public static void HasStatusEqualTo(Task task, TaskStatus status, [CallerArgumentExpression(nameof(task))] string name = "") { if (task.Status == status) { @@ -173,7 +173,7 @@ public static void HasStatusEqualTo(Task task, TaskStatus status, [CallerArgumen /// The name of the input parameter being tested. /// Thrown if matches . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasStatusNotEqualTo(Task task, TaskStatus status, [CallerArgumentExpression("task")] string name = "") + public static void HasStatusNotEqualTo(Task task, TaskStatus status, [CallerArgumentExpression(nameof(task))] string name = "") { if (task.Status != status) { diff --git a/CommunityToolkit.Diagnostics/Guard.cs b/src/CommunityToolkit.Diagnostics/Guard.cs similarity index 93% rename from CommunityToolkit.Diagnostics/Guard.cs rename to src/CommunityToolkit.Diagnostics/Guard.cs index fc8f48fa4..f0141080b 100644 --- a/CommunityToolkit.Diagnostics/Guard.cs +++ b/src/CommunityToolkit.Diagnostics/Guard.cs @@ -23,7 +23,7 @@ public static partial class Guard /// The name of the input parameter being tested. /// Thrown if is not . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNull(T? value, [CallerArgumentExpression("value")] string name = "") + public static void IsNull(T? value, [CallerArgumentExpression(nameof(value))] string name = "") { if (value is null) { @@ -41,7 +41,7 @@ public static void IsNull(T? value, [CallerArgumentExpression("value")] strin /// The name of the input parameter being tested. /// Thrown if is not . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNull(T? value, [CallerArgumentExpression("value")] string name = "") + public static void IsNull(T? value, [CallerArgumentExpression(nameof(value))] string name = "") where T : struct { if (value is null) @@ -60,7 +60,7 @@ public static void IsNull(T? value, [CallerArgumentExpression("value")] strin /// The name of the input parameter being tested. /// Thrown if is . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotNull([NotNull] T? value, [CallerArgumentExpression("value")] string name = "") + public static void IsNotNull([NotNull] T? value, [CallerArgumentExpression(nameof(value))] string name = "") { if (value is not null) { @@ -78,7 +78,7 @@ public static void IsNotNull([NotNull] T? value, [CallerArgumentExpression("v /// The name of the input parameter being tested. /// Thrown if is . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotNull([NotNull] T? value, [CallerArgumentExpression("value")] string name = "") + public static void IsNotNull([NotNull] T? value, [CallerArgumentExpression(nameof(value))] string name = "") where T : struct { if (value is not null) @@ -97,7 +97,7 @@ public static void IsNotNull([NotNull] T? value, [CallerArgumentExpression("v /// The name of the input parameter being tested. /// Thrown if is not of type . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsOfType(object value, [CallerArgumentExpression("value")] string name = "") + public static void IsOfType(object value, [CallerArgumentExpression(nameof(value))] string name = "") { if (value.GetType() == typeof(T)) { @@ -115,7 +115,7 @@ public static void IsOfType(object value, [CallerArgumentExpression("value")] /// The name of the input parameter being tested. /// Thrown if is of type . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotOfType(object value, [CallerArgumentExpression("value")] string name = "") + public static void IsNotOfType(object value, [CallerArgumentExpression(nameof(value))] string name = "") { if (value.GetType() != typeof(T)) { @@ -133,7 +133,7 @@ public static void IsNotOfType(object value, [CallerArgumentExpression("value /// The name of the input parameter being tested. /// Thrown if the type of is not the same as . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsOfType(object value, Type type, [CallerArgumentExpression("value")] string name = "") + public static void IsOfType(object value, Type type, [CallerArgumentExpression(nameof(value))] string name = "") { if (value.GetType() == type) { @@ -151,7 +151,7 @@ public static void IsOfType(object value, Type type, [CallerArgumentExpression(" /// The name of the input parameter being tested. /// Thrown if the type of is the same as . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotOfType(object value, Type type, [CallerArgumentExpression("value")] string name = "") + public static void IsNotOfType(object value, Type type, [CallerArgumentExpression(nameof(value))] string name = "") { if (value.GetType() != type) { @@ -169,7 +169,7 @@ public static void IsNotOfType(object value, Type type, [CallerArgumentExpressio /// The name of the input parameter being tested. /// Thrown if can't be assigned to type . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsAssignableToType(object value, [CallerArgumentExpression("value")] string name = "") + public static void IsAssignableToType(object value, [CallerArgumentExpression(nameof(value))] string name = "") { if (value is T) { @@ -187,7 +187,7 @@ public static void IsAssignableToType(object value, [CallerArgumentExpression /// The name of the input parameter being tested. /// Thrown if can be assigned to type . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotAssignableToType(object value, [CallerArgumentExpression("value")] string name = "") + public static void IsNotAssignableToType(object value, [CallerArgumentExpression(nameof(value))] string name = "") { if (value is not T) { @@ -205,7 +205,7 @@ public static void IsNotAssignableToType(object value, [CallerArgumentExpress /// The name of the input parameter being tested. /// Thrown if can't be assigned to . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsAssignableToType(object value, Type type, [CallerArgumentExpression("value")] string name = "") + public static void IsAssignableToType(object value, Type type, [CallerArgumentExpression(nameof(value))] string name = "") { if (type.IsInstanceOfType(value)) { @@ -223,7 +223,7 @@ public static void IsAssignableToType(object value, Type type, [CallerArgumentEx /// The name of the input parameter being tested. /// Thrown if can be assigned to . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotAssignableToType(object value, Type type, [CallerArgumentExpression("value")] string name = "") + public static void IsNotAssignableToType(object value, Type type, [CallerArgumentExpression(nameof(value))] string name = "") { if (!type.IsInstanceOfType(value)) { @@ -243,7 +243,7 @@ public static void IsNotAssignableToType(object value, Type type, [CallerArgumen /// Thrown if is not the same instance as . /// The method is generic to prevent using it with value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsReferenceEqualTo(T value, T target, [CallerArgumentExpression("value")] string name = "") + public static void IsReferenceEqualTo(T value, T target, [CallerArgumentExpression(nameof(value))] string name = "") where T : class { if (ReferenceEquals(value, target)) @@ -264,7 +264,7 @@ public static void IsReferenceEqualTo(T value, T target, [CallerArgumentExpre /// Thrown if is the same instance as . /// The method is generic to prevent using it with value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsReferenceNotEqualTo(T value, T target, [CallerArgumentExpression("value")] string name = "") + public static void IsReferenceNotEqualTo(T value, T target, [CallerArgumentExpression(nameof(value))] string name = "") where T : class { if (!ReferenceEquals(value, target)) diff --git a/CommunityToolkit.Diagnostics/Internals/Guard.Collection.Generic.ThrowHelper.cs b/src/CommunityToolkit.Diagnostics/Internals/Guard.Collection.Generic.ThrowHelper.cs similarity index 100% rename from CommunityToolkit.Diagnostics/Internals/Guard.Collection.Generic.ThrowHelper.cs rename to src/CommunityToolkit.Diagnostics/Internals/Guard.Collection.Generic.ThrowHelper.cs diff --git a/CommunityToolkit.Diagnostics/Internals/Guard.Comparable.Generic.ThrowHelper.cs b/src/CommunityToolkit.Diagnostics/Internals/Guard.Comparable.Generic.ThrowHelper.cs similarity index 100% rename from CommunityToolkit.Diagnostics/Internals/Guard.Comparable.Generic.ThrowHelper.cs rename to src/CommunityToolkit.Diagnostics/Internals/Guard.Comparable.Generic.ThrowHelper.cs diff --git a/CommunityToolkit.Diagnostics/Internals/Guard.Comparable.Numeric.ThrowHelper.cs b/src/CommunityToolkit.Diagnostics/Internals/Guard.Comparable.Numeric.ThrowHelper.cs similarity index 100% rename from CommunityToolkit.Diagnostics/Internals/Guard.Comparable.Numeric.ThrowHelper.cs rename to src/CommunityToolkit.Diagnostics/Internals/Guard.Comparable.Numeric.ThrowHelper.cs diff --git a/CommunityToolkit.Diagnostics/Internals/Guard.IO.ThrowHelper.cs b/src/CommunityToolkit.Diagnostics/Internals/Guard.IO.ThrowHelper.cs similarity index 100% rename from CommunityToolkit.Diagnostics/Internals/Guard.IO.ThrowHelper.cs rename to src/CommunityToolkit.Diagnostics/Internals/Guard.IO.ThrowHelper.cs diff --git a/CommunityToolkit.Diagnostics/Internals/Guard.String.ThrowHelper.cs b/src/CommunityToolkit.Diagnostics/Internals/Guard.String.ThrowHelper.cs similarity index 100% rename from CommunityToolkit.Diagnostics/Internals/Guard.String.ThrowHelper.cs rename to src/CommunityToolkit.Diagnostics/Internals/Guard.String.ThrowHelper.cs diff --git a/CommunityToolkit.Diagnostics/Internals/Guard.Tasks.ThrowHelper.cs b/src/CommunityToolkit.Diagnostics/Internals/Guard.Tasks.ThrowHelper.cs similarity index 100% rename from CommunityToolkit.Diagnostics/Internals/Guard.Tasks.ThrowHelper.cs rename to src/CommunityToolkit.Diagnostics/Internals/Guard.Tasks.ThrowHelper.cs diff --git a/CommunityToolkit.Diagnostics/Internals/Guard.ThrowHelper.cs b/src/CommunityToolkit.Diagnostics/Internals/Guard.ThrowHelper.cs similarity index 100% rename from CommunityToolkit.Diagnostics/Internals/Guard.ThrowHelper.cs rename to src/CommunityToolkit.Diagnostics/Internals/Guard.ThrowHelper.cs diff --git a/CommunityToolkit.Diagnostics/ThrowHelper.Generic.cs b/src/CommunityToolkit.Diagnostics/ThrowHelper.Generic.cs similarity index 100% rename from CommunityToolkit.Diagnostics/ThrowHelper.Generic.cs rename to src/CommunityToolkit.Diagnostics/ThrowHelper.Generic.cs diff --git a/CommunityToolkit.Diagnostics/ThrowHelper.cs b/src/CommunityToolkit.Diagnostics/ThrowHelper.cs similarity index 100% rename from CommunityToolkit.Diagnostics/ThrowHelper.cs rename to src/CommunityToolkit.Diagnostics/ThrowHelper.cs diff --git a/CommunityToolkit.HighPerformance/Box{T}.cs b/src/CommunityToolkit.HighPerformance/Box{T}.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Box{T}.cs rename to src/CommunityToolkit.HighPerformance/Box{T}.cs diff --git a/CommunityToolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs b/src/CommunityToolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs similarity index 98% rename from CommunityToolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs rename to src/CommunityToolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs index 5644bcb97..21044fdaf 100644 --- a/CommunityToolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs +++ b/src/CommunityToolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs @@ -99,11 +99,6 @@ public ArrayPoolBufferWriter(ArrayPool pool, int initialCapacity) this.index = 0; } - /// - /// Finalizes an instance of the class. - /// - ~ArrayPoolBufferWriter() => Dispose(); - /// Memory IMemoryOwner.Memory { @@ -311,8 +306,6 @@ public void Dispose() return; } - GC.SuppressFinalize(this); - this.array = null; this.pool.Return(array); diff --git a/CommunityToolkit.HighPerformance/Buffers/Enums/AllocationMode.cs b/src/CommunityToolkit.HighPerformance/Buffers/Enums/AllocationMode.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Buffers/Enums/AllocationMode.cs rename to src/CommunityToolkit.HighPerformance/Buffers/Enums/AllocationMode.cs diff --git a/CommunityToolkit.HighPerformance/Buffers/Interfaces/IBuffer{T}.cs b/src/CommunityToolkit.HighPerformance/Buffers/Interfaces/IBuffer{T}.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Buffers/Interfaces/IBuffer{T}.cs rename to src/CommunityToolkit.HighPerformance/Buffers/Interfaces/IBuffer{T}.cs diff --git a/CommunityToolkit.HighPerformance/Buffers/Internals/ArrayMemoryManager{TFrom,TTo}.cs b/src/CommunityToolkit.HighPerformance/Buffers/Internals/ArrayMemoryManager{TFrom,TTo}.cs similarity index 95% rename from CommunityToolkit.HighPerformance/Buffers/Internals/ArrayMemoryManager{TFrom,TTo}.cs rename to src/CommunityToolkit.HighPerformance/Buffers/Internals/ArrayMemoryManager{TFrom,TTo}.cs index a4e01d19a..dc80abf49 100644 --- a/CommunityToolkit.HighPerformance/Buffers/Internals/ArrayMemoryManager{TFrom,TTo}.cs +++ b/src/CommunityToolkit.HighPerformance/Buffers/Internals/ArrayMemoryManager{TFrom,TTo}.cs @@ -70,13 +70,13 @@ public override Span GetSpan() /// public override unsafe MemoryHandle Pin(int elementIndex = 0) { - if ((uint)elementIndex >= (uint)(this.length * Unsafe.SizeOf() / Unsafe.SizeOf())) + if ((uint)elementIndex >= (uint)(this.length * sizeof(TFrom) / sizeof(TTo))) { ThrowArgumentOutOfRangeExceptionForInvalidIndex(); } - int bytePrefix = this.offset * Unsafe.SizeOf(); - int byteSuffix = elementIndex * Unsafe.SizeOf(); + int bytePrefix = this.offset * sizeof(TFrom); + int byteSuffix = elementIndex * sizeof(TTo); int byteOffset = bytePrefix + byteSuffix; GCHandle handle = GCHandle.Alloc(this.array, GCHandleType.Pinned); diff --git a/CommunityToolkit.HighPerformance/Buffers/Internals/Interfaces/IMemoryManager.cs b/src/CommunityToolkit.HighPerformance/Buffers/Internals/Interfaces/IMemoryManager.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Buffers/Internals/Interfaces/IMemoryManager.cs rename to src/CommunityToolkit.HighPerformance/Buffers/Internals/Interfaces/IMemoryManager.cs diff --git a/CommunityToolkit.HighPerformance/Buffers/Internals/ProxyMemoryManager{TFrom,TTo}.cs b/src/CommunityToolkit.HighPerformance/Buffers/Internals/ProxyMemoryManager{TFrom,TTo}.cs similarity index 91% rename from CommunityToolkit.HighPerformance/Buffers/Internals/ProxyMemoryManager{TFrom,TTo}.cs rename to src/CommunityToolkit.HighPerformance/Buffers/Internals/ProxyMemoryManager{TFrom,TTo}.cs index 879e6a01b..088b18f43 100644 --- a/CommunityToolkit.HighPerformance/Buffers/Internals/ProxyMemoryManager{TFrom,TTo}.cs +++ b/src/CommunityToolkit.HighPerformance/Buffers/Internals/ProxyMemoryManager{TFrom,TTo}.cs @@ -4,7 +4,6 @@ using System; using System.Buffers; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using CommunityToolkit.HighPerformance.Buffers.Internals.Interfaces; using RuntimeHelpers = CommunityToolkit.HighPerformance.Helpers.Internals.RuntimeHelpers; @@ -57,17 +56,17 @@ public override Span GetSpan() } /// - public override MemoryHandle Pin(int elementIndex = 0) + public override unsafe MemoryHandle Pin(int elementIndex = 0) { - if ((uint)elementIndex >= (uint)(this.length * Unsafe.SizeOf() / Unsafe.SizeOf())) + if ((uint)elementIndex >= (uint)(this.length * sizeof(TFrom) / sizeof(TTo))) { ThrowArgumentExceptionForInvalidIndex(); } - int bytePrefix = this.offset * Unsafe.SizeOf(); - int byteSuffix = elementIndex * Unsafe.SizeOf(); + int bytePrefix = this.offset * sizeof(TFrom); + int byteSuffix = elementIndex * sizeof(TTo); int byteOffset = bytePrefix + byteSuffix; - int shiftedOffset = Math.DivRem(byteOffset, Unsafe.SizeOf(), out int remainder); + int shiftedOffset = Math.DivRem(byteOffset, sizeof(TFrom), out int remainder); if (remainder != 0) { diff --git a/CommunityToolkit.HighPerformance/Buffers/Internals/RawObjectMemoryManager{T}.cs b/src/CommunityToolkit.HighPerformance/Buffers/Internals/RawObjectMemoryManager{T}.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Buffers/Internals/RawObjectMemoryManager{T}.cs rename to src/CommunityToolkit.HighPerformance/Buffers/Internals/RawObjectMemoryManager{T}.cs diff --git a/CommunityToolkit.HighPerformance/Buffers/Internals/StringMemoryManager{TTo}.cs b/src/CommunityToolkit.HighPerformance/Buffers/Internals/StringMemoryManager{TTo}.cs similarity index 94% rename from CommunityToolkit.HighPerformance/Buffers/Internals/StringMemoryManager{TTo}.cs rename to src/CommunityToolkit.HighPerformance/Buffers/Internals/StringMemoryManager{TTo}.cs index 1238b3533..430e5af50 100644 --- a/CommunityToolkit.HighPerformance/Buffers/Internals/StringMemoryManager{TTo}.cs +++ b/src/CommunityToolkit.HighPerformance/Buffers/Internals/StringMemoryManager{TTo}.cs @@ -66,13 +66,13 @@ public override Span GetSpan() /// public override unsafe MemoryHandle Pin(int elementIndex = 0) { - if ((uint)elementIndex >= (uint)(this.length * Unsafe.SizeOf() / Unsafe.SizeOf())) + if ((uint)elementIndex >= (uint)(this.length * sizeof(char) / sizeof(TTo))) { ThrowArgumentOutOfRangeExceptionForInvalidIndex(); } - int bytePrefix = this.offset * Unsafe.SizeOf(); - int byteSuffix = elementIndex * Unsafe.SizeOf(); + int bytePrefix = this.offset * sizeof(char); + int byteSuffix = elementIndex * sizeof(TTo); int byteOffset = bytePrefix + byteSuffix; GCHandle handle = GCHandle.Alloc(this.text, GCHandleType.Pinned); diff --git a/CommunityToolkit.HighPerformance/Buffers/MemoryBufferWriter{T}.cs b/src/CommunityToolkit.HighPerformance/Buffers/MemoryBufferWriter{T}.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Buffers/MemoryBufferWriter{T}.cs rename to src/CommunityToolkit.HighPerformance/Buffers/MemoryBufferWriter{T}.cs diff --git a/CommunityToolkit.HighPerformance/Buffers/MemoryOwner{T}.cs b/src/CommunityToolkit.HighPerformance/Buffers/MemoryOwner{T}.cs similarity index 96% rename from CommunityToolkit.HighPerformance/Buffers/MemoryOwner{T}.cs rename to src/CommunityToolkit.HighPerformance/Buffers/MemoryOwner{T}.cs index 579f61397..b172dc2a1 100644 --- a/CommunityToolkit.HighPerformance/Buffers/MemoryOwner{T}.cs +++ b/src/CommunityToolkit.HighPerformance/Buffers/MemoryOwner{T}.cs @@ -6,7 +6,7 @@ using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; -#if NETCOREAPP3_1_OR_GREATER +#if NET6_0_OR_GREATER using System.Runtime.InteropServices; #endif using CommunityToolkit.HighPerformance.Buffers.Views; @@ -77,11 +77,6 @@ private MemoryOwner(int start, int length, ArrayPool pool, T[] array) this.array = array; } - /// - /// Finalizes an instance of the class. - /// - ~MemoryOwner() => Dispose(); - /// /// Gets an empty instance. /// @@ -176,13 +171,13 @@ public Span Span ThrowObjectDisposedException(); } -#if NETCOREAPP3_1_OR_GREATER +#if NET6_0_OR_GREATER ref T r0 = ref array!.DangerousGetReferenceAt(this.start); - // On .NET Core runtimes, we can manually create a span from the starting reference to + // On .NET 6+ runtimes, we can manually create a span from the starting reference to // skip the argument validations, which include an explicit null check, covariance check // for the array and the actual validation for the starting offset and target length. We - // only do this on .NET Core as we can leverage the runtime-specific array layout to get + // only do this on .NET 6+ as we can leverage the runtime-specific array layout to get // a fast access to the initial element, which makes this trick worth it. Otherwise, on // runtimes where we would need to at least access a static field to retrieve the base // byte offset within an SZ array object, we can get better performance by just using the @@ -295,8 +290,6 @@ public void Dispose() return; } - GC.SuppressFinalize(this); - this.array = null; this.pool.Return(array); diff --git a/CommunityToolkit.HighPerformance/Buffers/SpanOwner{T}.cs b/src/CommunityToolkit.HighPerformance/Buffers/SpanOwner{T}.cs similarity index 99% rename from CommunityToolkit.HighPerformance/Buffers/SpanOwner{T}.cs rename to src/CommunityToolkit.HighPerformance/Buffers/SpanOwner{T}.cs index f258f5f1b..152260eab 100644 --- a/CommunityToolkit.HighPerformance/Buffers/SpanOwner{T}.cs +++ b/src/CommunityToolkit.HighPerformance/Buffers/SpanOwner{T}.cs @@ -6,7 +6,7 @@ using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; -#if NETCOREAPP3_1_OR_GREATER +#if NET6_0_OR_GREATER using System.Runtime.InteropServices; #endif using CommunityToolkit.HighPerformance.Buffers.Views; @@ -141,7 +141,7 @@ public Span Span [MethodImpl(MethodImplOptions.AggressiveInlining)] get { -#if NETCOREAPP3_1_OR_GREATER +#if NET6_0_OR_GREATER ref T r0 = ref this.array!.DangerousGetReference(); return MemoryMarshal.CreateSpan(ref r0, this.length); diff --git a/CommunityToolkit.HighPerformance/Buffers/StringPool.cs b/src/CommunityToolkit.HighPerformance/Buffers/StringPool.cs similarity index 99% rename from CommunityToolkit.HighPerformance/Buffers/StringPool.cs rename to src/CommunityToolkit.HighPerformance/Buffers/StringPool.cs index a7aa0c21c..3cb8f12e1 100644 --- a/CommunityToolkit.HighPerformance/Buffers/StringPool.cs +++ b/src/CommunityToolkit.HighPerformance/Buffers/StringPool.cs @@ -493,7 +493,7 @@ public void Reset() private unsafe ref string TryGet(ReadOnlySpan span, int hashcode) { ref MapEntry mapEntriesRef = ref this.mapEntries.DangerousGetReference(); - ref MapEntry entry = ref Unsafe.NullRef(); + ref MapEntry entry = ref *(MapEntry*)null; int length = this.buckets.Length; int bucketIndex = hashcode & (length - 1); @@ -512,7 +512,7 @@ private unsafe ref string TryGet(ReadOnlySpan span, int hashcode) } } - return ref Unsafe.NullRef(); + return ref *(string*)null; } /// diff --git a/CommunityToolkit.HighPerformance/Buffers/Views/MemoryDebugView{T}.cs b/src/CommunityToolkit.HighPerformance/Buffers/Views/MemoryDebugView{T}.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Buffers/Views/MemoryDebugView{T}.cs rename to src/CommunityToolkit.HighPerformance/Buffers/Views/MemoryDebugView{T}.cs diff --git a/CommunityToolkit.HighPerformance/CommunityToolkit.HighPerformance.csproj b/src/CommunityToolkit.HighPerformance/CommunityToolkit.HighPerformance.csproj similarity index 73% rename from CommunityToolkit.HighPerformance/CommunityToolkit.HighPerformance.csproj rename to src/CommunityToolkit.HighPerformance/CommunityToolkit.HighPerformance.csproj index f41d8c86b..1639b330d 100644 --- a/CommunityToolkit.HighPerformance/CommunityToolkit.HighPerformance.csproj +++ b/src/CommunityToolkit.HighPerformance/CommunityToolkit.HighPerformance.csproj @@ -1,7 +1,7 @@ - netstandard2.0;netstandard2.1;netcoreapp3.1;net6.0 + netstandard2.0;netstandard2.1;net6.0;net7.0 @@ -35,37 +35,28 @@ - - - - + - + - - - - - NETSTANDARD2_1_OR_GREATER - true - true - - - - - - NETSTANDARD2_1_OR_GREATER - - - + + + + + + System.Diagnostics.CodeAnalysis.NotNullAttribute; + System.Diagnostics.CodeAnalysis.NotNullWhenAttribute; + System.Runtime.CompilerServices.SkipLocalsInitAttribute; + + + \ No newline at end of file diff --git a/CommunityToolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs b/src/CommunityToolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs similarity index 81% rename from CommunityToolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs rename to src/CommunityToolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs index bd2175eee..dcc9f0880 100644 --- a/CommunityToolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs +++ b/src/CommunityToolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs @@ -21,7 +21,17 @@ namespace CommunityToolkit.HighPerformance.Enumerables; /// The type of items to enumerate. public readonly ref struct ReadOnlyRefEnumerable { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + /// + /// The reference for the instance. + /// + private readonly ref readonly T reference; + + /// + /// The length of the current sequence. + /// + private readonly int length; +#elif NETSTANDARD2_1_OR_GREATER /// /// The instance pointing to the first item in the target memory area. /// @@ -51,6 +61,7 @@ public readonly ref struct ReadOnlyRefEnumerable private readonly int step; #if NETSTANDARD2_1_OR_GREATER +#if !NET7_0_OR_GREATER /// /// Initializes a new instance of the struct. /// @@ -59,9 +70,15 @@ public readonly ref struct ReadOnlyRefEnumerable [MethodImpl(MethodImplOptions.AggressiveInlining)] private ReadOnlyRefEnumerable(ReadOnlySpan span, int step) { +#if NET7_0_OR_GREATER + this.reference = ref MemoryMarshal.GetReference(span); + this.length = span.Length; +#else this.span = span; +#endif this.step = step; } +#endif /// /// Initializes a new instance of the struct. @@ -72,8 +89,14 @@ private ReadOnlyRefEnumerable(ReadOnlySpan span, int step) [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ReadOnlyRefEnumerable(in T reference, int length, int step) { +#if NET7_0_OR_GREATER + this.reference = ref reference; + this.length = length; + this.step = step; +#else this.span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(reference), length); this.step = step; +#endif } /// @@ -124,7 +147,9 @@ internal ReadOnlyRefEnumerable(object? instance, IntPtr offset, int length, int public int Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + get => this.length; +#elif NETSTANDARD2_1_OR_GREATER get => this.span.Length; #else get => this.length; @@ -149,7 +174,9 @@ public ref readonly T this[int index] ThrowHelper.ThrowIndexOutOfRangeException(); } -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + ref T r0 = ref Unsafe.AsRef(in this.reference); +#elif NETSTANDARD2_1_OR_GREATER ref T r0 = ref MemoryMarshal.GetReference(this.span); #else ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); @@ -181,7 +208,9 @@ public ref readonly T this[Index index] [MethodImpl(MethodImplOptions.AggressiveInlining)] public Enumerator GetEnumerator() { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + return new(in this.reference, this.length, this.step); +#elif NETSTANDARD2_1_OR_GREATER return new(this.span, this.step); #else return new(this.instance, this.offset, this.length, this.step); @@ -197,7 +226,26 @@ public Enumerator GetEnumerator() /// public void CopyTo(RefEnumerable destination) { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + if (this.step == 1) + { + destination.CopyFrom(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in this.reference), this.length)); + + return; + } + + if (destination.Step == 1) + { + CopyTo(MemoryMarshal.CreateSpan(ref destination.Reference, destination.Length)); + + return; + } + + ref T sourceRef = ref Unsafe.AsRef(in this.reference); + ref T destinationRef = ref destination.Reference; + int sourceLength = this.length; + int destinationLength = destination.Length; +#elif NETSTANDARD2_1_OR_GREATER if (this.step == 1) { destination.CopyFrom(this.span); @@ -238,7 +286,10 @@ public void CopyTo(RefEnumerable destination) /// Whether or not the operation was successful. public bool TryCopyTo(RefEnumerable destination) { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + int sourceLength = this.length; + int destinationLength = destination.Length; +#elif NETSTANDARD2_1_OR_GREATER int sourceLength = this.span.Length; int destinationLength = destination.Span.Length; #else @@ -265,7 +316,17 @@ public bool TryCopyTo(RefEnumerable destination) /// public void CopyTo(Span destination) { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + if (this.step == 1) + { + MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in this.reference), this.length).CopyTo(destination); + + return; + } + + ref T sourceRef = ref Unsafe.AsRef(in this.reference); + int length = this.length; +#elif NETSTANDARD2_1_OR_GREATER if (this.step == 1) { this.span.CopyTo(destination); @@ -296,7 +357,9 @@ public void CopyTo(Span destination) /// Whether or not the operation was successful. public bool TryCopyTo(Span destination) { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + int length = this.length; +#elif NETSTANDARD2_1_OR_GREATER int length = this.span.Length; #else int length = this.length; @@ -315,7 +378,9 @@ public bool TryCopyTo(Span destination) /// public T[] ToArray() { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + int length = this.length; +#elif NETSTANDARD2_1_OR_GREATER int length = this.span.Length; #else int length = this.length; @@ -341,7 +406,9 @@ public T[] ToArray() [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator ReadOnlyRefEnumerable(RefEnumerable enumerable) { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + return new(in enumerable.Reference, enumerable.Length, enumerable.Step); +#elif NETSTANDARD2_1_OR_GREATER return new(enumerable.Span, enumerable.Step); #else return new(enumerable.Instance, enumerable.Offset, enumerable.Length, enumerable.Step); @@ -353,7 +420,13 @@ public static implicit operator ReadOnlyRefEnumerable(RefEnumerable enumer /// public ref struct Enumerator { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + /// + private readonly ref readonly T reference; + + /// + private readonly int length; +#elif NETSTANDARD2_1_OR_GREATER /// private readonly ReadOnlySpan span; #else @@ -375,7 +448,22 @@ public ref struct Enumerator /// private int position; -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + /// + /// Initializes a new instance of the struct. + /// + /// The reference to the first item of the sequence. + /// The length of the sequence. + /// The distance between items in the sequence to enumerate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Enumerator(in T reference, int length, int step) + { + this.reference = ref reference; + this.length = length; + this.step = step; + this.position = -1; + } +#elif NETSTANDARD2_1_OR_GREATER /// /// Initializes a new instance of the struct. /// @@ -411,7 +499,9 @@ internal Enumerator(object? instance, IntPtr offset, int length, int step) [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + return ++this.position < this.length; +#elif NETSTANDARD2_1_OR_GREATER return ++this.position < this.span.Length; #else return ++this.position < this.length; @@ -424,7 +514,9 @@ public readonly ref readonly T Current [MethodImpl(MethodImplOptions.AggressiveInlining)] get { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + ref T r0 = ref Unsafe.AsRef(in this.reference); +#elif NETSTANDARD2_1_OR_GREATER ref T r0 = ref this.span.DangerousGetReference(); #else ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); diff --git a/CommunityToolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs b/src/CommunityToolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs similarity index 88% rename from CommunityToolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs rename to src/CommunityToolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs index c2000b908..2f6b16a5b 100644 --- a/CommunityToolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs +++ b/src/CommunityToolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs @@ -81,10 +81,22 @@ public readonly Item Current [EditorBrowsable(EditorBrowsableState.Never)] public readonly ref struct Item { +#if NET7_0_OR_GREATER + /// + /// The reference for the instance. + /// + private readonly ref readonly T reference; + + /// + /// The index of the current instance. + /// + private readonly int index; +#else /// /// The source instance. /// private readonly ReadOnlySpan span; +#endif #if NETSTANDARD2_1_OR_GREATER /// @@ -95,7 +107,12 @@ public readonly ref struct Item [MethodImpl(MethodImplOptions.AggressiveInlining)] public Item(ref T value, int index) { +#if NET7_0_OR_GREATER + this.reference = ref value; + this.index = index; +#else this.span = MemoryMarshal.CreateReadOnlySpan(ref value, index); +#endif } #else /// @@ -124,7 +141,9 @@ public ref readonly T Value [MethodImpl(MethodImplOptions.AggressiveInlining)] get { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + return ref this.reference; +#elif NETSTANDARD2_1_OR_GREATER return ref MemoryMarshal.GetReference(this.span); #else ref T r0 = ref MemoryMarshal.GetReference(this.span); @@ -143,7 +162,9 @@ public int Index [MethodImpl(MethodImplOptions.AggressiveInlining)] get { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + return this.index; +#elif NETSTANDARD2_1_OR_GREATER return this.span.Length; #else return this.index; diff --git a/CommunityToolkit.HighPerformance/Enumerables/ReadOnlySpanTokenizer{T}.cs b/src/CommunityToolkit.HighPerformance/Enumerables/ReadOnlySpanTokenizer{T}.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Enumerables/ReadOnlySpanTokenizer{T}.cs rename to src/CommunityToolkit.HighPerformance/Enumerables/ReadOnlySpanTokenizer{T}.cs diff --git a/CommunityToolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/src/CommunityToolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs similarity index 82% rename from CommunityToolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs rename to src/CommunityToolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs index 4713d11a2..a6fab2ab6 100644 --- a/CommunityToolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs +++ b/src/CommunityToolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs @@ -21,7 +21,17 @@ namespace CommunityToolkit.HighPerformance.Enumerables; /// The type of items to enumerate. public readonly ref struct RefEnumerable { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + /// + /// The reference for the instance. + /// + internal readonly ref T Reference; + + /// + /// The length of the current sequence. + /// + private readonly int length; +#elif NETSTANDARD2_1_OR_GREATER /// /// The instance pointing to the first item in the target memory area. /// @@ -55,7 +65,12 @@ public readonly ref struct RefEnumerable [MethodImpl(MethodImplOptions.AggressiveInlining)] internal RefEnumerable(ref T reference, int length, int step) { +#if NET7_0_OR_GREATER + this.Reference = ref reference; + this.length = length; +#else this.Span = MemoryMarshal.CreateSpan(ref reference, length); +#endif this.Step = step; } @@ -108,7 +123,9 @@ internal RefEnumerable(object? instance, IntPtr offset, int length, int step) public int Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + get => this.length; +#elif NETSTANDARD2_1_OR_GREATER get => this.Span.Length; #else get; @@ -133,7 +150,9 @@ public ref T this[int index] ThrowHelper.ThrowIndexOutOfRangeException(); } -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + ref T r0 = ref this.Reference; +#elif NETSTANDARD2_1_OR_GREATER ref T r0 = ref MemoryMarshal.GetReference(this.Span); #else ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset); @@ -165,7 +184,9 @@ public ref T this[Index index] [MethodImpl(MethodImplOptions.AggressiveInlining)] public Enumerator GetEnumerator() { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + return new(ref this.Reference, this.length, this.Step); +#elif NETSTANDARD2_1_OR_GREATER return new(this.Span, this.Step); #else return new(this.Instance, this.Offset, this.Length, this.Step); @@ -177,8 +198,18 @@ public Enumerator GetEnumerator() /// public void Clear() { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER // Fast path for contiguous items + if (this.Step == 1) + { + MemoryMarshal.CreateSpan(ref this.Reference, this.length).Clear(); + + return; + } + + ref T r0 = ref this.Reference; + int length = this.length; +#elif NETSTANDARD2_1_OR_GREATER if (this.Step == 1) { this.Span.Clear(); @@ -205,7 +236,26 @@ public void Clear() /// public void CopyTo(RefEnumerable destination) { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + if (this.Step == 1) + { + destination.CopyFrom(MemoryMarshal.CreateReadOnlySpan(ref this.Reference, this.length)); + + return; + } + + if (destination.Step == 1) + { + CopyTo(MemoryMarshal.CreateSpan(ref destination.Reference, destination.length)); + + return; + } + + ref T sourceRef = ref this.Reference; + ref T destinationRef = ref destination.Reference; + int sourceLength = this.length; + int destinationLength = destination.length; +#elif NETSTANDARD2_1_OR_GREATER if (this.Step == 1) { destination.CopyFrom(this.Span); @@ -246,7 +296,10 @@ public void CopyTo(RefEnumerable destination) /// Whether or not the operation was successful. public bool TryCopyTo(RefEnumerable destination) { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + int sourceLength = this.length; + int destinationLength = destination.length; +#elif NETSTANDARD2_1_OR_GREATER int sourceLength = this.Span.Length; int destinationLength = destination.Span.Length; #else @@ -273,7 +326,17 @@ public bool TryCopyTo(RefEnumerable destination) /// public void CopyTo(Span destination) { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + if (this.Step == 1) + { + MemoryMarshal.CreateReadOnlySpan(ref this.Reference, this.length).CopyTo(destination); + + return; + } + + ref T sourceRef = ref this.Reference; + int length = this.length; +#elif NETSTANDARD2_1_OR_GREATER if (this.Step == 1) { this.Span.CopyTo(destination); @@ -304,7 +367,9 @@ public void CopyTo(Span destination) /// Whether or not the operation was successful. public bool TryCopyTo(Span destination) { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + int length = this.length; +#elif NETSTANDARD2_1_OR_GREATER int length = this.Span.Length; #else int length = this.Length; @@ -329,7 +394,17 @@ public bool TryCopyTo(Span destination) /// internal void CopyFrom(ReadOnlySpan source) { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + if (this.Step == 1) + { + source.CopyTo(MemoryMarshal.CreateSpan(ref this.Reference, this.length)); + + return; + } + + ref T destinationRef = ref this.Reference; + int destinationLength = this.length; +#elif NETSTANDARD2_1_OR_GREATER if (this.Step == 1) { source.CopyTo(this.Span); @@ -361,7 +436,9 @@ internal void CopyFrom(ReadOnlySpan source) /// Whether or not the operation was successful. public bool TryCopyFrom(ReadOnlySpan source) { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + int length = this.length; +#elif NETSTANDARD2_1_OR_GREATER int length = this.Span.Length; #else int length = this.Length; @@ -383,7 +460,17 @@ public bool TryCopyFrom(ReadOnlySpan source) /// The value to assign to each element of the instance. public void Fill(T value) { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + if (this.Step == 1) + { + MemoryMarshal.CreateSpan(ref this.Reference, this.length).Fill(value); + + return; + } + + ref T r0 = ref this.Reference; + int length = this.length; +#elif NETSTANDARD2_1_OR_GREATER if (this.Step == 1) { this.Span.Fill(value); @@ -411,7 +498,9 @@ public void Fill(T value) /// public T[] ToArray() { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + int length = this.length; +#elif NETSTANDARD2_1_OR_GREATER int length = this.Span.Length; #else int length = this.Length; @@ -435,7 +524,13 @@ public T[] ToArray() /// public ref struct Enumerator { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + /// + private readonly ref T reference; + + /// + private readonly int length; +#elif NETSTANDARD2_1_OR_GREATER /// private readonly Span span; #else @@ -457,7 +552,22 @@ public ref struct Enumerator /// private int position; -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + /// + /// Initializes a new instance of the struct. + /// + /// The reference to the first item of the sequence. + /// The length of the sequence. + /// The distance between items in the sequence to enumerate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Enumerator(ref T reference, int length, int step) + { + this.reference = ref reference; + this.length = length; + this.step = step; + this.position = -1; + } +#elif NETSTANDARD2_1_OR_GREATER /// /// Initializes a new instance of the struct. /// @@ -493,7 +603,9 @@ internal Enumerator(object? instance, IntPtr offset, int length, int step) [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + return ++this.position < this.length; +#elif NETSTANDARD2_1_OR_GREATER return ++this.position < this.span.Length; #else return ++this.position < this.length; @@ -506,7 +618,9 @@ public readonly ref T Current [MethodImpl(MethodImplOptions.AggressiveInlining)] get { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + ref T r0 = ref this.reference; +#elif NETSTANDARD2_1_OR_GREATER ref T r0 = ref this.span.DangerousGetReference(); #else ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); diff --git a/CommunityToolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs b/src/CommunityToolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs similarity index 88% rename from CommunityToolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs rename to src/CommunityToolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs index 68b84e983..dcfce51c0 100644 --- a/CommunityToolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs +++ b/src/CommunityToolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs @@ -86,10 +86,22 @@ public readonly Item Current [EditorBrowsable(EditorBrowsableState.Never)] public readonly ref struct Item { +#if NET7_0_OR_GREATER + /// + /// The reference for the instance. + /// + private readonly ref T reference; + + /// + /// The index of the current instance. + /// + private readonly int index; +#else /// /// The source instance. /// private readonly Span span; +#endif #if NETSTANDARD2_1_OR_GREATER /// @@ -100,7 +112,12 @@ public readonly ref struct Item [MethodImpl(MethodImplOptions.AggressiveInlining)] public Item(ref T value, int index) { +#if NET7_0_OR_GREATER + this.reference = ref value; + this.index = index; +#else this.span = MemoryMarshal.CreateSpan(ref value, index); +#endif } #else /// @@ -121,15 +138,17 @@ public Item(Span span, int index) } #endif - /// - /// Gets the reference to the current value. - /// + /// + /// Gets the reference to the current value. + /// public ref T Value { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + return ref this.reference; +#elif NETSTANDARD2_1_OR_GREATER return ref MemoryMarshal.GetReference(this.span); #else ref T r0 = ref MemoryMarshal.GetReference(this.span); @@ -148,7 +167,9 @@ public int Index [MethodImpl(MethodImplOptions.AggressiveInlining)] get { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + return this.index; +#elif NETSTANDARD2_1_OR_GREATER return this.span.Length; #else return this.index; diff --git a/CommunityToolkit.HighPerformance/Enumerables/SpanTokenizer{T}.cs b/src/CommunityToolkit.HighPerformance/Enumerables/SpanTokenizer{T}.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Enumerables/SpanTokenizer{T}.cs rename to src/CommunityToolkit.HighPerformance/Enumerables/SpanTokenizer{T}.cs diff --git a/CommunityToolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs b/src/CommunityToolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs similarity index 85% rename from CommunityToolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs rename to src/CommunityToolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs index 58186ff7e..026994527 100644 --- a/CommunityToolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs +++ b/src/CommunityToolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs @@ -4,7 +4,7 @@ using System; using System.Runtime.CompilerServices; -#if NETCOREAPP3_1_OR_GREATER +#if NET6_0_OR_GREATER using System.Runtime.InteropServices; #endif using CommunityToolkit.HighPerformance.Enumerables; @@ -33,11 +33,6 @@ public static ref T DangerousGetReference(this T[] array) { #if NET6_0_OR_GREATER return ref MemoryMarshal.GetArrayDataReference(array); -#elif NETCOREAPP3_1 - RawArrayData? arrayData = Unsafe.As(array)!; - ref T r0 = ref Unsafe.As(ref arrayData.Data); - - return ref r0; #else IntPtr offset = RuntimeHelpers.GetArrayDataByteOffset(); @@ -60,12 +55,6 @@ public static ref T DangerousGetReferenceAt(this T[] array, int i) ref T r0 = ref MemoryMarshal.GetArrayDataReference(array); ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)i); - return ref ri; -#elif NETCOREAPP3_1 - RawArrayData? arrayData = Unsafe.As(array)!; - ref T r0 = ref Unsafe.As(ref arrayData.Data); - ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)i); - return ref ri; #else IntPtr offset = RuntimeHelpers.GetArrayDataByteOffset(); @@ -76,26 +65,6 @@ public static ref T DangerousGetReferenceAt(this T[] array, int i) #endif } -#if NETCOREAPP3_1 - // Description taken from CoreCLR: see https://source.dot.net/#System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs,285. - // CLR arrays are laid out in memory as follows (multidimensional array bounds are optional): - // [ sync block || pMethodTable || num components || MD array bounds || array data .. ] - // ^ ^ ^ returned reference - // | \-- ref Unsafe.As(array).Data - // \-- array - // The base size of an array includes all the fields before the array data, - // including the sync block and method table. The reference to RawData.Data - // points at the number of components, skipping over these two pointer-sized fields. - [StructLayout(LayoutKind.Sequential)] - private sealed class RawArrayData - { -#pragma warning disable CS0649 // Unassigned fields - public IntPtr Length; - public byte Data; -#pragma warning restore CS0649 - } -#endif - /// /// Counts the number of occurrences of a given value into a target array instance. /// diff --git a/CommunityToolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs b/src/CommunityToolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs similarity index 92% rename from CommunityToolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs rename to src/CommunityToolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs index e86af22ff..dec994ab9 100644 --- a/CommunityToolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs +++ b/src/CommunityToolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs @@ -30,11 +30,6 @@ public static ref T DangerousGetReference(this T[,] array) { #if NET6_0_OR_GREATER return ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(array)); -#elif NETCOREAPP3_1 - RawArray2DData? arrayData = Unsafe.As(array)!; - ref T r0 = ref Unsafe.As(ref arrayData.Data); - - return ref r0; #else IntPtr offset = RuntimeHelpers.GetArray2DDataByteOffset(); @@ -65,13 +60,6 @@ public static ref T DangerousGetReferenceAt(this T[,] array, int i, int j) ref T r0 = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(array)); ref T ri = ref Unsafe.Add(ref r0, index); - return ref ri; -#elif NETCOREAPP3_1 - RawArray2DData? arrayData = Unsafe.As(array)!; - nint offset = ((nint)(uint)i * (nint)(uint)arrayData.Width) + (nint)(uint)j; - ref T r0 = ref Unsafe.As(ref arrayData.Data); - ref T ri = ref Unsafe.Add(ref r0, offset); - return ref ri; #else int width = array.GetLength(1); @@ -84,29 +72,6 @@ public static ref T DangerousGetReferenceAt(this T[,] array, int i, int j) #endif } -#if NETCOREAPP3_1 - // Description adapted from CoreCLR: see https://source.dot.net/#System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs,285. - // CLR 2D arrays are laid out in memory as follows: - // [ sync block || pMethodTable || Length (padded to IntPtr) || HxW || HxW bounds || array data .. ] - // ^ ^ - // | \-- ref Unsafe.As(array).Data - // \-- array - // The length is always padded to IntPtr just like with SZ arrays. - // The total data padding is therefore 20 bytes on x86 (4 + 4 + 4 + 4 + 4), or 24 bytes on x64. - [StructLayout(LayoutKind.Sequential)] - private sealed class RawArray2DData - { -#pragma warning disable CS0649 // Unassigned fields - public IntPtr Length; - public int Height; - public int Width; - public int HeightLowerBound; - public int WidthLowerBound; - public byte Data; -#pragma warning restore CS0649 - } -#endif - /// /// Returns a over a row in a given 2D array instance. /// diff --git a/CommunityToolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs b/src/CommunityToolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs similarity index 92% rename from CommunityToolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs rename to src/CommunityToolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs index d6b72e530..2c02a323e 100644 --- a/CommunityToolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs +++ b/src/CommunityToolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs @@ -29,11 +29,6 @@ public static ref T DangerousGetReference(this T[,,] array) { #if NET6_0_OR_GREATER return ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(array)); -#elif NETCOREAPP3_1 - RawArray3DData? arrayData = Unsafe.As(array)!; - ref T r0 = ref Unsafe.As(ref arrayData.Data); - - return ref r0; #else IntPtr offset = RuntimeHelpers.GetArray3DDataByteOffset(); @@ -68,15 +63,6 @@ public static ref T DangerousGetReferenceAt(this T[,,] array, int i, int j, i ref T r0 = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(array)); ref T ri = ref Unsafe.Add(ref r0, index); - return ref ri; -#elif NETCOREAPP3_1 - RawArray3DData? arrayData = Unsafe.As(array)!; - nint offset = - ((nint)(uint)i * (nint)(uint)arrayData.Height * (nint)(uint)arrayData.Width) + - ((nint)(uint)j * (nint)(uint)arrayData.Width) + (nint)(uint)k; - ref T r0 = ref Unsafe.As(ref arrayData.Data); - ref T ri = ref Unsafe.Add(ref r0, offset); - return ref ri; #else int height = array.GetLength(1); @@ -92,25 +78,6 @@ public static ref T DangerousGetReferenceAt(this T[,,] array, int i, int j, i #endif } -#if NETCOREAPP3_1 - // See description for this in the 2D partial file. - // Using the CHW naming scheme here (like with RGB images). - [StructLayout(LayoutKind.Sequential)] - private sealed class RawArray3DData - { -#pragma warning disable CS0649 // Unassigned fields - public IntPtr Length; - public int Channel; - public int Height; - public int Width; - public int ChannelLowerBound; - public int HeightLowerBound; - public int WidthLowerBound; - public byte Data; -#pragma warning restore CS0649 - } -#endif - #if NETSTANDARD2_1_OR_GREATER /// /// Creates a new over an input 3D array. diff --git a/CommunityToolkit.HighPerformance/Extensions/ArrayPoolBufferWriterExtensions.cs b/src/CommunityToolkit.HighPerformance/Extensions/ArrayPoolBufferWriterExtensions.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Extensions/ArrayPoolBufferWriterExtensions.cs rename to src/CommunityToolkit.HighPerformance/Extensions/ArrayPoolBufferWriterExtensions.cs diff --git a/CommunityToolkit.HighPerformance/Extensions/ArrayPoolExtensions.cs b/src/CommunityToolkit.HighPerformance/Extensions/ArrayPoolExtensions.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Extensions/ArrayPoolExtensions.cs rename to src/CommunityToolkit.HighPerformance/Extensions/ArrayPoolExtensions.cs diff --git a/CommunityToolkit.HighPerformance/Extensions/BoolExtensions.cs b/src/CommunityToolkit.HighPerformance/Extensions/BoolExtensions.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Extensions/BoolExtensions.cs rename to src/CommunityToolkit.HighPerformance/Extensions/BoolExtensions.cs diff --git a/CommunityToolkit.HighPerformance/Extensions/HashCodeExtensions.cs b/src/CommunityToolkit.HighPerformance/Extensions/HashCodeExtensions.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Extensions/HashCodeExtensions.cs rename to src/CommunityToolkit.HighPerformance/Extensions/HashCodeExtensions.cs diff --git a/CommunityToolkit.HighPerformance/Extensions/IBufferWriterExtensions.cs b/src/CommunityToolkit.HighPerformance/Extensions/IBufferWriterExtensions.cs similarity index 89% rename from CommunityToolkit.HighPerformance/Extensions/IBufferWriterExtensions.cs rename to src/CommunityToolkit.HighPerformance/Extensions/IBufferWriterExtensions.cs index ac8d67c82..6d659db42 100644 --- a/CommunityToolkit.HighPerformance/Extensions/IBufferWriterExtensions.cs +++ b/src/CommunityToolkit.HighPerformance/Extensions/IBufferWriterExtensions.cs @@ -44,11 +44,11 @@ public static Stream AsStream(this IBufferWriter writer) /// The type of value to write. /// The target instance to write to. /// The input value to write to . - /// Thrown if reaches the end. - public static void Write(this IBufferWriter writer, T value) + /// Thrown if reaches the end. + public static unsafe void Write(this IBufferWriter writer, T value) where T : unmanaged { - int length = Unsafe.SizeOf(); + int length = sizeof(T); Span span = writer.GetSpan(1); if (span.Length < length) @@ -69,7 +69,7 @@ public static void Write(this IBufferWriter writer, T value) /// The type of value to write. /// The target instance to write to. /// The input value to write to . - /// Thrown if reaches the end. + /// Thrown if reaches the end. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Write(this IBufferWriter writer, T value) { @@ -91,7 +91,7 @@ public static void Write(this IBufferWriter writer, T value) /// The type of value to write. /// The target instance to write to. /// The input to write to . - /// Thrown if reaches the end. + /// Thrown if reaches the end. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Write(this IBufferWriter writer, ReadOnlySpan span) where T : unmanaged @@ -111,7 +111,7 @@ public static void Write(this IBufferWriter writer, ReadOnlySpan spa /// The type of value to write. /// The target instance to write to. /// The input to write to . - /// Thrown if reaches the end. + /// Thrown if reaches the end. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Write(this IBufferWriter writer, ReadOnlySpan span) { diff --git a/CommunityToolkit.HighPerformance/Extensions/IMemoryOwnerExtensions.cs b/src/CommunityToolkit.HighPerformance/Extensions/IMemoryOwnerExtensions.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Extensions/IMemoryOwnerExtensions.cs rename to src/CommunityToolkit.HighPerformance/Extensions/IMemoryOwnerExtensions.cs diff --git a/CommunityToolkit.HighPerformance/Extensions/ListExtensions.cs b/src/CommunityToolkit.HighPerformance/Extensions/ListExtensions.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Extensions/ListExtensions.cs rename to src/CommunityToolkit.HighPerformance/Extensions/ListExtensions.cs diff --git a/CommunityToolkit.HighPerformance/Extensions/MemoryExtensions.cs b/src/CommunityToolkit.HighPerformance/Extensions/MemoryExtensions.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Extensions/MemoryExtensions.cs rename to src/CommunityToolkit.HighPerformance/Extensions/MemoryExtensions.cs diff --git a/CommunityToolkit.HighPerformance/Extensions/NullableExtensions.cs b/src/CommunityToolkit.HighPerformance/Extensions/NullableExtensions.cs similarity index 74% rename from CommunityToolkit.HighPerformance/Extensions/NullableExtensions.cs rename to src/CommunityToolkit.HighPerformance/Extensions/NullableExtensions.cs index 2cd058950..98fe1f38b 100644 --- a/CommunityToolkit.HighPerformance/Extensions/NullableExtensions.cs +++ b/src/CommunityToolkit.HighPerformance/Extensions/NullableExtensions.cs @@ -35,7 +35,11 @@ public static class NullableExtensions public static ref T DangerousGetValueOrDefaultReference(this ref T? value) where T : struct { +#if NET7_0_OR_GREATER + return ref Unsafe.AsRef(in Nullable.GetValueRefOrDefaultRef(in value)); +#else return ref Unsafe.As>(ref value).Value; +#endif } /// @@ -49,14 +53,38 @@ public static ref T DangerousGetValueOrDefaultReference(this ref T? value) public static unsafe ref T DangerousGetValueOrNullReference(ref this T? value) where T : struct { +#if NET7_0_OR_GREATER + ref T resultRef = ref *(T*)null; + + // This pattern ensures that the resulting code ends up having a single return, and a single + // forward branch (the one where the value is null) that is predicted non taken. That is, + // the initial null ref is very cheap as it's just clearing a register, and the rest of the + // code is a single assignment (lea on x86-64) that should always be taken. This results in: + // ============================= + // L0000: xor eax, eax + // L0002: cmp byte ptr[rcx], 0 + // L0005: je short L000b + // L0007: lea rax, [rcx + 4] + // L000b: ret + // ============================= + // This is better than what the code would've been with two separate returns in the method. + if (value.HasValue) + { + resultRef = ref Unsafe.AsRef(in Nullable.GetValueRefOrDefaultRef(in value)); + } + + return ref resultRef; +#else if (value.HasValue) { return ref Unsafe.As>(ref value).Value; } - return ref Unsafe.NullRef(); + return ref *(T*)null; +#endif } +#if !NET7_0_OR_GREATER /// /// Mapping type that reflects the internal layout of the type. /// See https://github.com/dotnet/runtime/blob/master/src/libraries/System.Private.CoreLib/src/System/Nullable.cs. @@ -70,6 +98,7 @@ private struct RawNullableData public T Value; #pragma warning restore CS0649 } +#endif } #endif \ No newline at end of file diff --git a/CommunityToolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs b/src/CommunityToolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs rename to src/CommunityToolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs diff --git a/CommunityToolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs b/src/CommunityToolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs similarity index 99% rename from CommunityToolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs rename to src/CommunityToolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs index d2780d284..0e78fc341 100644 --- a/CommunityToolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs +++ b/src/CommunityToolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs @@ -210,13 +210,13 @@ public static ReadOnlySpan2D AsSpan2D(this ReadOnlySpan span, int offse /// The reference to the target item to get the index for. /// The index of within , or -1. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int IndexOf(this ReadOnlySpan span, in T value) + public static unsafe int IndexOf(this ReadOnlySpan span, in T value) { ref T r0 = ref MemoryMarshal.GetReference(span); ref T r1 = ref Unsafe.AsRef(value); IntPtr byteOffset = Unsafe.ByteOffset(ref r0, ref r1); - nint elementOffset = byteOffset / (nint)(uint)Unsafe.SizeOf(); + nint elementOffset = byteOffset / (nint)(uint)sizeof(T); if ((nuint)elementOffset >= (uint)span.Length) { diff --git a/CommunityToolkit.HighPerformance/Extensions/SpanExtensions.cs b/src/CommunityToolkit.HighPerformance/Extensions/SpanExtensions.cs similarity index 99% rename from CommunityToolkit.HighPerformance/Extensions/SpanExtensions.cs rename to src/CommunityToolkit.HighPerformance/Extensions/SpanExtensions.cs index 434a61037..7fb71727a 100644 --- a/CommunityToolkit.HighPerformance/Extensions/SpanExtensions.cs +++ b/src/CommunityToolkit.HighPerformance/Extensions/SpanExtensions.cs @@ -148,12 +148,12 @@ public static Span Cast(this Span span) /// The reference to the target item to get the index for. /// The index of within , or -1. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int IndexOf(this Span span, ref T value) + public static unsafe int IndexOf(this Span span, ref T value) { ref T r0 = ref MemoryMarshal.GetReference(span); IntPtr byteOffset = Unsafe.ByteOffset(ref r0, ref value); - nint elementOffset = byteOffset / (nint)(uint)Unsafe.SizeOf(); + nint elementOffset = byteOffset / (nint)(uint)sizeof(T); if ((nuint)elementOffset >= (uint)span.Length) { diff --git a/CommunityToolkit.HighPerformance/Extensions/SpinLockExtensions.cs b/src/CommunityToolkit.HighPerformance/Extensions/SpinLockExtensions.cs similarity index 86% rename from CommunityToolkit.HighPerformance/Extensions/SpinLockExtensions.cs rename to src/CommunityToolkit.HighPerformance/Extensions/SpinLockExtensions.cs index 9f30a6940..c9cd360d9 100644 --- a/CommunityToolkit.HighPerformance/Extensions/SpinLockExtensions.cs +++ b/src/CommunityToolkit.HighPerformance/Extensions/SpinLockExtensions.cs @@ -2,11 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.ComponentModel; using System.Runtime.CompilerServices; -#if NETSTANDARD2_1_OR_GREATER -using System.Runtime.Versioning; -#endif using System.Threading; namespace CommunityToolkit.HighPerformance; @@ -33,6 +31,8 @@ public static class SpinLockExtensions /// A wrapper type that will release when its method is called. /// The returned value shouldn't be used directly: use this extension in a block or statement. [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Obsolete("Use SpinLockExtensions.Enter(ref SpinLock) instead.")] + [EditorBrowsable(EditorBrowsableState.Never)] public static unsafe UnsafeLock Enter(SpinLock* spinLock) { return new(spinLock); @@ -80,7 +80,7 @@ public void Dispose() } } -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER /// /// Enters a specified instance and returns a wrapper to use to release the lock. /// This extension should be used though a block or statement: @@ -97,9 +97,6 @@ public void Dispose() /// The target to use /// A wrapper type that will release when its method is called. /// The returned value shouldn't be used directly: use this extension in a block or statement. - [RequiresPreviewFeatures( - "The Lock type has no compiler support to ensure the lifetime of referenced values is respected, and as such using it incorrectly may lead to GC holes.", - Url = "https://github.com/dotnet/runtime/issues/46104")] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Lock Enter(ref this SpinLock spinLock) { @@ -110,15 +107,12 @@ public static Lock Enter(ref this SpinLock spinLock) /// A that is used to enter and hold a through a block or statement. /// [EditorBrowsable(EditorBrowsableState.Never)] - [RequiresPreviewFeatures( - "The Lock type has no compiler support to ensure the lifetime of referenced values is respected, and as such using it incorrectly may lead to GC holes.", - Url = "https://github.com/dotnet/runtime/issues/46104")] public readonly ref struct Lock { /// - /// The instance pointing to the target value to use. + /// The reference to the target value to use. /// - private readonly Ref spinLock; + private readonly ref SpinLock spinLock; /// /// A value indicating whether or not the lock is taken by this instance. @@ -132,7 +126,7 @@ public readonly ref struct Lock [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Lock(ref SpinLock spinLock) { - this.spinLock = new Ref(ref spinLock); + this.spinLock = ref spinLock; this.lockTaken = false; spinLock.Enter(ref this.lockTaken); @@ -146,7 +140,7 @@ public void Dispose() { if (this.lockTaken) { - this.spinLock.Value.Exit(); + this.spinLock.Exit(); } } } diff --git a/CommunityToolkit.HighPerformance/Extensions/StreamExtensions.cs b/src/CommunityToolkit.HighPerformance/Extensions/StreamExtensions.cs similarity index 82% rename from CommunityToolkit.HighPerformance/Extensions/StreamExtensions.cs rename to src/CommunityToolkit.HighPerformance/Extensions/StreamExtensions.cs index e6745bc7d..a4808e15b 100644 --- a/CommunityToolkit.HighPerformance/Extensions/StreamExtensions.cs +++ b/src/CommunityToolkit.HighPerformance/Extensions/StreamExtensions.cs @@ -198,36 +198,60 @@ public static void Write(this Stream stream, ReadOnlySpan buffer) /// The type of value to read. /// The source instance to read from. /// The value read from . - /// Thrown if reaches the end. + /// Thrown if reaches the end. #if NETSTANDARD2_1_OR_GREATER [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif - public static T Read(this Stream stream) + public static unsafe T Read(this Stream stream) where T : unmanaged { -#if NETSTANDARD2_1_OR_GREATER - T result = default; - int length = Unsafe.SizeOf(); +#if NET7_0_OR_GREATER + T result; + + stream.ReadExactly(new Span(&result, sizeof(T))); - unsafe + return result; +#elif NETSTANDARD2_1_OR_GREATER + T result; + int bytesOffset = 0; + + // As per Stream.Read's documentation: + // "The total number of bytes read into the buffer. This can be less than the number of bytes allocated in the + // buffer if that many bytes are not currently available, or zero (0) if the end of the stream has been reached." + // Because of this, we have to loop until all requires bytes have been read, and only throw if the return is 0. + do { - if (stream.Read(new Span(&result, length)) != length) + int bytesRead = stream.Read(new Span((byte*)&result + bytesOffset, sizeof(T) - bytesOffset)); + + // A return value of 0 indicates that the end of the stream has been reached + if (bytesRead == 0) { - ThrowInvalidOperationExceptionForEndOfStream(); + ThrowEndOfStreamException(); } + + bytesOffset += bytesRead; } + while (bytesOffset < sizeof(T)); return result; #else - int length = Unsafe.SizeOf(); - byte[] buffer = ArrayPool.Shared.Rent(length); + int bytesOffset = 0; + byte[] buffer = ArrayPool.Shared.Rent(sizeof(T)); try { - if (stream.Read(buffer, 0, length) != length) + do { - ThrowInvalidOperationExceptionForEndOfStream(); + int bytesRead = stream.Read(buffer, bytesOffset, sizeof(T) - bytesOffset); + + if (bytesRead == 0) + { + ThrowEndOfStreamException(); + } + + bytesOffset += bytesRead; } + while (bytesOffset < sizeof(T)); return Unsafe.ReadUnaligned(ref buffer[0]); } @@ -247,19 +271,19 @@ public static T Read(this Stream stream) #if NETSTANDARD2_1_OR_GREATER [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif - public static void Write(this Stream stream, in T value) + public static unsafe void Write(this Stream stream, in T value) where T : unmanaged { #if NETSTANDARD2_1_OR_GREATER ref T r0 = ref Unsafe.AsRef(value); ref byte r1 = ref Unsafe.As(ref r0); - int length = Unsafe.SizeOf(); + int length = sizeof(T); ReadOnlySpan span = MemoryMarshal.CreateReadOnlySpan(ref r1, length); stream.Write(span); #else - int length = Unsafe.SizeOf(); + int length = sizeof(T); byte[] buffer = ArrayPool.Shared.Rent(length); try @@ -275,11 +299,13 @@ public static void Write(this Stream stream, in T value) #endif } +#if !NET7_0_OR_GREATER /// - /// Throws an when fails. + /// Throws an when fails. /// - private static void ThrowInvalidOperationExceptionForEndOfStream() + private static void ThrowEndOfStreamException() { - throw new InvalidOperationException("The stream didn't contain enough data to read the requested item."); + throw new EndOfStreamException("The stream didn't contain enough data to read the requested item."); } +#endif } diff --git a/CommunityToolkit.HighPerformance/Extensions/StringExtensions.cs b/src/CommunityToolkit.HighPerformance/Extensions/StringExtensions.cs similarity index 99% rename from CommunityToolkit.HighPerformance/Extensions/StringExtensions.cs rename to src/CommunityToolkit.HighPerformance/Extensions/StringExtensions.cs index 6c464e9ca..a3260c5bd 100644 --- a/CommunityToolkit.HighPerformance/Extensions/StringExtensions.cs +++ b/src/CommunityToolkit.HighPerformance/Extensions/StringExtensions.cs @@ -26,7 +26,7 @@ public static class StringExtensions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref char DangerousGetReference(this string text) { -#if NETCOREAPP3_1_OR_GREATER +#if NET6_0_OR_GREATER return ref Unsafe.AsRef(text.GetPinnableReference()); #else return ref MemoryMarshal.GetReference(text.AsSpan()); @@ -43,7 +43,7 @@ public static ref char DangerousGetReference(this string text) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref char DangerousGetReferenceAt(this string text, int i) { -#if NETCOREAPP3_1_OR_GREATER +#if NET6_0_OR_GREATER ref char r0 = ref Unsafe.AsRef(text.GetPinnableReference()); #else ref char r0 = ref MemoryMarshal.GetReference(text.AsSpan()); diff --git a/CommunityToolkit.HighPerformance/Helpers/BitHelper.cs b/src/CommunityToolkit.HighPerformance/Helpers/BitHelper.cs similarity index 99% rename from CommunityToolkit.HighPerformance/Helpers/BitHelper.cs rename to src/CommunityToolkit.HighPerformance/Helpers/BitHelper.cs index b4af97f68..30e6405fb 100644 --- a/CommunityToolkit.HighPerformance/Helpers/BitHelper.cs +++ b/src/CommunityToolkit.HighPerformance/Helpers/BitHelper.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Runtime.CompilerServices; -#if NETCOREAPP3_1_OR_GREATER +#if NET6_0_OR_GREATER using System.Runtime.Intrinsics.X86; #endif @@ -224,7 +224,7 @@ public static unsafe uint SetFlag(uint value, int n, bool flag) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint ExtractRange(uint value, byte start, byte length) { -#if NETCOREAPP3_1_OR_GREATER +#if NET6_0_OR_GREATER if (Bmi1.IsSupported) { return Bmi1.BitFieldExtract(value, start, length); @@ -270,7 +270,7 @@ public static uint SetRange(uint value, byte start, byte length, uint flags) uint loadMask = highBits << start; uint storeMask = (flags & highBits) << start; -#if NETCOREAPP3_1_OR_GREATER +#if NET6_0_OR_GREATER if (Bmi1.IsSupported) { return Bmi1.AndNot(loadMask, value) | storeMask; @@ -386,7 +386,7 @@ public static unsafe ulong SetFlag(ulong value, int n, bool flag) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong ExtractRange(ulong value, byte start, byte length) { -#if NETCOREAPP3_1_OR_GREATER +#if NET6_0_OR_GREATER if (Bmi1.X64.IsSupported) { return Bmi1.X64.BitFieldExtract(value, start, length); @@ -432,7 +432,7 @@ public static ulong SetRange(ulong value, byte start, byte length, ulong flags) ulong loadMask = highBits << start; ulong storeMask = (flags & highBits) << start; -#if NETCOREAPP3_1_OR_GREATER +#if NET6_0_OR_GREATER if (Bmi1.X64.IsSupported) { return Bmi1.X64.AndNot(loadMask, value) | storeMask; diff --git a/CommunityToolkit.HighPerformance/Helpers/HashCode{T}.cs b/src/CommunityToolkit.HighPerformance/Helpers/HashCode{T}.cs similarity index 93% rename from CommunityToolkit.HighPerformance/Helpers/HashCode{T}.cs rename to src/CommunityToolkit.HighPerformance/Helpers/HashCode{T}.cs index 4693c10a9..c32fc31f3 100644 --- a/CommunityToolkit.HighPerformance/Helpers/HashCode{T}.cs +++ b/src/CommunityToolkit.HighPerformance/Helpers/HashCode{T}.cs @@ -24,7 +24,7 @@ namespace CommunityToolkit.HighPerformance.Helpers; /// hashing the same collection multiple times in the same process will always /// result in the same hash code, while the same collection being hashed again from another process /// (or another instance of the same process) is not guaranteed to result in the same final value. -/// For more info, see . +/// For more info, see . /// public struct HashCode where T : notnull @@ -50,7 +50,7 @@ public static int Combine(ReadOnlySpan span) /// The hash code for the input instance /// The returned hash code is not processed through APIs. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static int CombineValues(ReadOnlySpan span) + internal static unsafe int CombineValues(ReadOnlySpan span) { ref T r0 = ref MemoryMarshal.GetReference(span); @@ -70,7 +70,7 @@ internal static int CombineValues(ReadOnlySpan span) // process. In that case it will just compute the byte size as a 32 bit // multiplication with overflow, which is guaranteed never to happen anyway. ref byte rb = ref Unsafe.As(ref r0); - nint length = (nint)((uint)span.Length * (uint)Unsafe.SizeOf()); + nint length = (nint)((uint)span.Length * (uint)sizeof(T)); return SpanHelper.GetDjb2LikeByteHash(ref rb, length); } diff --git a/CommunityToolkit.HighPerformance/Helpers/Internals/BitOperations.cs b/src/CommunityToolkit.HighPerformance/Helpers/Internals/BitOperations.cs similarity index 73% rename from CommunityToolkit.HighPerformance/Helpers/Internals/BitOperations.cs rename to src/CommunityToolkit.HighPerformance/Helpers/Internals/BitOperations.cs index b2ec8f71f..d802529cf 100644 --- a/CommunityToolkit.HighPerformance/Helpers/Internals/BitOperations.cs +++ b/src/CommunityToolkit.HighPerformance/Helpers/Internals/BitOperations.cs @@ -5,10 +5,6 @@ #if !NET6_0_OR_GREATER using System.Runtime.CompilerServices; -#if NETCOREAPP3_1 -using System.Runtime.Intrinsics.X86; -using static System.Numerics.BitOperations; -#endif namespace CommunityToolkit.HighPerformance.Helpers.Internals; @@ -29,22 +25,6 @@ internal static class BitOperations [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe uint RoundUpToPowerOf2(uint value) { -#if NETCOREAPP3_1 - if (Lzcnt.IsSupported) - { - if (sizeof(nint) == 8) - { - return (uint)(0x1_0000_0000ul >> LeadingZeroCount(value - 1)); - } - else - { - int shift = 32 - LeadingZeroCount(value - 1); - - return (1u ^ (uint)(shift >> 5)) << shift; - } - } -#endif - // Based on https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 --value; value |= value >> 1; diff --git a/CommunityToolkit.HighPerformance/Helpers/Internals/RefEnumerableHelper.cs b/src/CommunityToolkit.HighPerformance/Helpers/Internals/RefEnumerableHelper.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Helpers/Internals/RefEnumerableHelper.cs rename to src/CommunityToolkit.HighPerformance/Helpers/Internals/RefEnumerableHelper.cs diff --git a/CommunityToolkit.HighPerformance/Helpers/Internals/RuntimeHelpers.cs b/src/CommunityToolkit.HighPerformance/Helpers/Internals/RuntimeHelpers.cs similarity index 99% rename from CommunityToolkit.HighPerformance/Helpers/Internals/RuntimeHelpers.cs rename to src/CommunityToolkit.HighPerformance/Helpers/Internals/RuntimeHelpers.cs index c08d327c4..be66eb9fe 100644 --- a/CommunityToolkit.HighPerformance/Helpers/Internals/RuntimeHelpers.cs +++ b/src/CommunityToolkit.HighPerformance/Helpers/Internals/RuntimeHelpers.cs @@ -75,7 +75,7 @@ public static nint GetArrayNativeLength(Array array) return (nint)array.LongLength; } -#if !NETCOREAPP3_1_OR_GREATER +#if !NET6_0_OR_GREATER /// /// Gets the byte offset to the first element in a SZ array. /// diff --git a/CommunityToolkit.HighPerformance/Helpers/Internals/SpanHelper.Count.cs b/src/CommunityToolkit.HighPerformance/Helpers/Internals/SpanHelper.Count.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Helpers/Internals/SpanHelper.Count.cs rename to src/CommunityToolkit.HighPerformance/Helpers/Internals/SpanHelper.Count.cs diff --git a/CommunityToolkit.HighPerformance/Helpers/Internals/SpanHelper.Hash.cs b/src/CommunityToolkit.HighPerformance/Helpers/Internals/SpanHelper.Hash.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Helpers/Internals/SpanHelper.Hash.cs rename to src/CommunityToolkit.HighPerformance/Helpers/Internals/SpanHelper.Hash.cs diff --git a/CommunityToolkit.HighPerformance/Helpers/ObjectMarshal.cs b/src/CommunityToolkit.HighPerformance/Helpers/ObjectMarshal.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Helpers/ObjectMarshal.cs rename to src/CommunityToolkit.HighPerformance/Helpers/ObjectMarshal.cs diff --git a/CommunityToolkit.HighPerformance/Helpers/ParallelHelper.For.IAction.cs b/src/CommunityToolkit.HighPerformance/Helpers/ParallelHelper.For.IAction.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Helpers/ParallelHelper.For.IAction.cs rename to src/CommunityToolkit.HighPerformance/Helpers/ParallelHelper.For.IAction.cs diff --git a/CommunityToolkit.HighPerformance/Helpers/ParallelHelper.For.IAction2D.cs b/src/CommunityToolkit.HighPerformance/Helpers/ParallelHelper.For.IAction2D.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Helpers/ParallelHelper.For.IAction2D.cs rename to src/CommunityToolkit.HighPerformance/Helpers/ParallelHelper.For.IAction2D.cs diff --git a/CommunityToolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction.cs b/src/CommunityToolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction.cs rename to src/CommunityToolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction.cs diff --git a/CommunityToolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs b/src/CommunityToolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs rename to src/CommunityToolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs diff --git a/CommunityToolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction.cs b/src/CommunityToolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction.cs rename to src/CommunityToolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction.cs diff --git a/CommunityToolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs b/src/CommunityToolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs rename to src/CommunityToolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs diff --git a/CommunityToolkit.HighPerformance/Helpers/ParallelHelper.ThrowExceptions.cs b/src/CommunityToolkit.HighPerformance/Helpers/ParallelHelper.ThrowExceptions.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Helpers/ParallelHelper.ThrowExceptions.cs rename to src/CommunityToolkit.HighPerformance/Helpers/ParallelHelper.ThrowExceptions.cs diff --git a/CommunityToolkit.HighPerformance/Memory/Internals/OverflowHelper.cs b/src/CommunityToolkit.HighPerformance/Memory/Internals/OverflowHelper.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Memory/Internals/OverflowHelper.cs rename to src/CommunityToolkit.HighPerformance/Memory/Internals/OverflowHelper.cs diff --git a/CommunityToolkit.HighPerformance/Memory/Internals/ThrowHelper.cs b/src/CommunityToolkit.HighPerformance/Memory/Internals/ThrowHelper.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Memory/Internals/ThrowHelper.cs rename to src/CommunityToolkit.HighPerformance/Memory/Internals/ThrowHelper.cs diff --git a/CommunityToolkit.HighPerformance/Memory/Memory2D{T}.cs b/src/CommunityToolkit.HighPerformance/Memory/Memory2D{T}.cs similarity index 99% rename from CommunityToolkit.HighPerformance/Memory/Memory2D{T}.cs rename to src/CommunityToolkit.HighPerformance/Memory/Memory2D{T}.cs index 493f6ac57..6c3cf348b 100644 --- a/CommunityToolkit.HighPerformance/Memory/Memory2D{T}.cs +++ b/src/CommunityToolkit.HighPerformance/Memory/Memory2D{T}.cs @@ -657,7 +657,7 @@ public Span2D Span /// are negative or not within the bounds that are valid for the current instance. /// /// A new instance representing a slice of the current one. - public Memory2D Slice(int row, int column, int height, int width) + public unsafe Memory2D Slice(int row, int column, int height, int width) { if ((uint)row >= Height) { @@ -682,7 +682,7 @@ public Memory2D Slice(int row, int column, int height, int width) int shift = ((this.width + this.pitch) * row) + column; int pitch = this.pitch + (this.width - width); - IntPtr offset = this.offset + (shift * Unsafe.SizeOf()); + IntPtr offset = this.offset + (shift * sizeof(T)); return new(this.instance!, offset, height, width, pitch); } diff --git a/CommunityToolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs b/src/CommunityToolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs similarity index 99% rename from CommunityToolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs rename to src/CommunityToolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs index 62f9e36b7..105b1fcfc 100644 --- a/CommunityToolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs +++ b/src/CommunityToolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs @@ -670,7 +670,7 @@ public ReadOnlySpan2D Span /// are negative or not within the bounds that are valid for the current instance. /// /// A new instance representing a slice of the current one. - public ReadOnlyMemory2D Slice(int row, int column, int height, int width) + public unsafe ReadOnlyMemory2D Slice(int row, int column, int height, int width) { if ((uint)row >= Height) { @@ -695,7 +695,7 @@ public ReadOnlyMemory2D Slice(int row, int column, int height, int width) int shift = ((this.width + this.pitch) * row) + column; int pitch = this.pitch + (this.width - width); - IntPtr offset = this.offset + (shift * Unsafe.SizeOf()); + IntPtr offset = this.offset + (shift * sizeof(T)); return new(this.instance!, offset, height, width, pitch); } diff --git a/CommunityToolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs b/src/CommunityToolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs similarity index 88% rename from CommunityToolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs rename to src/CommunityToolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs index c84b5acd6..6b55632bd 100644 --- a/CommunityToolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs +++ b/src/CommunityToolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.Enumerator.cs @@ -2,13 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if !NET7_0_OR_GREATER using System; +#endif using System.Runtime.CompilerServices; using CommunityToolkit.HighPerformance.Enumerables; using CommunityToolkit.HighPerformance.Memory.Internals; -#if NETSTANDARD2_1_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER && !NET7_0_OR_GREATER using System.Runtime.InteropServices; -#else +#elif NETSTANDARD2_0 using RuntimeHelpers = CommunityToolkit.HighPerformance.Helpers.Internals.RuntimeHelpers; #endif @@ -84,7 +86,17 @@ public ReadOnlyRefEnumerable GetColumn(int column) /// public ref struct Enumerator { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + /// + /// The reference for the instance. + /// + private readonly ref readonly T reference; + + /// + /// The height of the specified 2D region. + /// + private readonly int height; +#elif NETSTANDARD2_1_OR_GREATER /// /// The instance pointing to the first item in the target memory area. /// @@ -133,7 +145,10 @@ public ref struct Enumerator /// The target instance to enumerate. internal Enumerator(ReadOnlySpan2D span) { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + this.reference = ref span.reference; + this.height = span.height; +#elif NETSTANDARD2_1_OR_GREATER this.span = span.span; #else this.instance = span.instance; @@ -167,7 +182,9 @@ public bool MoveNext() // another row available: wrap to a new line and continue. this.x = 0; -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + return ++this.y < this.height; +#elif NETSTANDARD2_1_OR_GREATER return ++this.y < this.span.Length; #else return ++this.y < this.height; @@ -182,7 +199,9 @@ public readonly ref readonly T Current [MethodImpl(MethodImplOptions.AggressiveInlining)] get { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + ref T r0 = ref Unsafe.AsRef(in this.reference); +#elif NETSTANDARD2_1_OR_GREATER ref T r0 = ref MemoryMarshal.GetReference(this.span); #else ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); diff --git a/CommunityToolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs b/src/CommunityToolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs similarity index 93% rename from CommunityToolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs rename to src/CommunityToolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs index 0917fe055..e824c22be 100644 --- a/CommunityToolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs +++ b/src/CommunityToolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs @@ -28,7 +28,17 @@ namespace CommunityToolkit.HighPerformance; [DebuggerDisplay("{ToString(),raw}")] public readonly ref partial struct ReadOnlySpan2D { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + /// + /// The reference for the instance. + /// + private readonly ref readonly T reference; + + /// + /// The height of the specified 2D region. + /// + private readonly int height; +#elif NETSTANDARD2_1_OR_GREATER /// /// The instance pointing to the first item in the target memory area. /// @@ -71,7 +81,12 @@ public readonly ref partial struct ReadOnlySpan2D [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ReadOnlySpan2D(in T value, int height, int width, int pitch) { +#if NET7_0_OR_GREATER + this.reference = ref value; + this.height = height; +#else this.span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(value), height); +#endif this.width = width; this.stride = width + pitch; } @@ -109,7 +124,10 @@ public unsafe ReadOnlySpan2D(void* pointer, int height, int width, int pitch) OverflowHelper.EnsureIsInNativeIntRange(height, width, pitch); -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + this.reference = ref Unsafe.AsRef(pointer); + this.height = height; +#elif NETSTANDARD2_1_OR_GREATER this.span = new ReadOnlySpan(pointer, height); #else this.instance = null; @@ -206,7 +224,10 @@ public ReadOnlySpan2D(T[] array, int offset, int height, int width, int pitch) ThrowHelper.ThrowArgumentException(); } -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + this.reference = ref array.DangerousGetReferenceAt(offset); + this.height = height; +#elif NETSTANDARD2_1_OR_GREATER this.span = MemoryMarshal.CreateReadOnlySpan(ref array.DangerousGetReferenceAt(offset), height); #else this.instance = array; @@ -230,7 +251,10 @@ public ReadOnlySpan2D(T[,]? array) return; } -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + this.reference = ref array.DangerousGetReference(); + this.height = array.GetLength(0); +#elif NETSTANDARD2_1_OR_GREATER this.span = MemoryMarshal.CreateReadOnlySpan(ref array.DangerousGetReference(), array.GetLength(0)); #else this.instance = array; @@ -289,7 +313,10 @@ public ReadOnlySpan2D(T[,]? array, int row, int column, int height, int width) ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); } -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + this.reference = ref array.DangerousGetReferenceAt(row, column); + this.height = height; +#elif NETSTANDARD2_1_OR_GREATER this.span = MemoryMarshal.CreateReadOnlySpan(ref array.DangerousGetReferenceAt(row, column), height); #else this.instance = array; @@ -313,7 +340,10 @@ public ReadOnlySpan2D(T[,,] array, int depth) ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth(); } -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + this.reference = ref array.DangerousGetReferenceAt(depth, 0, 0); + this.height = array.GetLength(1); +#elif NETSTANDARD2_1_OR_GREATER this.span = MemoryMarshal.CreateReadOnlySpan(ref array.DangerousGetReferenceAt(depth, 0, 0), array.GetLength(1)); #else this.instance = array; @@ -363,7 +393,10 @@ public ReadOnlySpan2D(T[,,] array, int depth, int row, int column, int height, i ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); } -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + this.reference = ref array.DangerousGetReferenceAt(depth, row, column); + this.height = height; +#elif NETSTANDARD2_1_OR_GREATER this.span = MemoryMarshal.CreateReadOnlySpan(ref array.DangerousGetReferenceAt(depth, row, column), height); #else this.instance = array; @@ -441,7 +474,12 @@ internal ReadOnlySpan2D(ReadOnlySpan span, int offset, int height, int width, ThrowHelper.ThrowArgumentException(); } +#if NET7_0_OR_GREATER + this.reference = ref span.DangerousGetReferenceAt(offset); + this.height = height; +#else this.span = MemoryMarshal.CreateSpan(ref span.DangerousGetReferenceAt(offset), height); +#endif this.width = width; this.stride = width + pitch; } @@ -509,7 +547,9 @@ public int Height [MethodImpl(MethodImplOptions.AggressiveInlining)] get { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + return this.height; +#elif NETSTANDARD2_1_OR_GREATER return this.span.Length; #else return this.height; @@ -740,13 +780,15 @@ public bool TryCopyTo(Span2D destination) /// A reference to the 0th element, or a reference. [MethodImpl(MethodImplOptions.AggressiveInlining)] [EditorBrowsable(EditorBrowsableState.Never)] - public unsafe ref T GetPinnableReference() + public unsafe ref readonly T GetPinnableReference() { - ref T r0 = ref Unsafe.AsRef(null); + ref readonly T r0 = ref Unsafe.AsRef(null); if (Length != 0) { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + r0 = ref this.reference; +#elif NETSTANDARD2_1_OR_GREATER r0 = ref MemoryMarshal.GetReference(this.span); #else r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); @@ -763,7 +805,9 @@ public unsafe ref T GetPinnableReference() [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref T DangerousGetReference() { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + return ref Unsafe.AsRef(in this.reference); +#elif NETSTANDARD2_1_OR_GREATER return ref MemoryMarshal.GetReference(this.span); #else return ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); @@ -779,7 +823,9 @@ public ref T DangerousGetReference() [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref T DangerousGetReferenceAt(int i, int j) { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + ref T r0 = ref Unsafe.AsRef(in this.reference); +#elif NETSTANDARD2_1_OR_GREATER ref T r0 = ref MemoryMarshal.GetReference(this.span); #else ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); @@ -801,7 +847,7 @@ public ref T DangerousGetReferenceAt(int i, int j) /// are negative or not within the bounds that are valid for the current instance. /// /// A new instance representing a slice of the current one. - public ReadOnlySpan2D Slice(int row, int column, int height, int width) + public unsafe ReadOnlySpan2D Slice(int row, int column, int height, int width) { if ((uint)row >= Height) { @@ -826,12 +872,16 @@ public ReadOnlySpan2D Slice(int row, int column, int height, int width) nint shift = ((nint)(uint)this.stride * (nint)(uint)row) + (nint)(uint)column; int pitch = this.stride - width; -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + ref T r0 = ref Unsafe.Add(ref Unsafe.AsRef(in this.reference), shift); + + return new(in r0, height, width, pitch); +#elif NETSTANDARD2_1_OR_GREATER ref T r0 = ref this.span.DangerousGetReferenceAt(shift); return new(in r0, height, width, pitch); #else - IntPtr offset = this.offset + (shift * (nint)(uint)Unsafe.SizeOf()); + IntPtr offset = this.offset + (shift * (nint)(uint)sizeof(T)); return new(this.instance, offset, height, width, pitch); #endif @@ -868,7 +918,11 @@ public bool TryGetSpan(out ReadOnlySpan span) if (this.stride == this.width && Length <= int.MaxValue) { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + span = MemoryMarshal.CreateSpan(ref Unsafe.AsRef(in this.reference), (int)Length); + + return true; +#elif NETSTANDARD2_1_OR_GREATER span = MemoryMarshal.CreateReadOnlySpan(ref MemoryMarshal.GetReference(this.span), (int)Length); return true; @@ -979,7 +1033,10 @@ public override string ToString() public static bool operator ==(ReadOnlySpan2D left, ReadOnlySpan2D right) { return -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + Unsafe.AreSame(ref Unsafe.AsRef(in left.reference), ref Unsafe.AsRef(in right.reference)) && + left.height == right.height && +#elif NETSTANDARD2_1_OR_GREATER left.span == right.span && #else ReferenceEquals( diff --git a/CommunityToolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs b/src/CommunityToolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs similarity index 89% rename from CommunityToolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs rename to src/CommunityToolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs index 77897568e..eac6b07b0 100644 --- a/CommunityToolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs +++ b/src/CommunityToolkit.HighPerformance/Memory/Span2D{T}.Enumerator.cs @@ -2,13 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if !NET7_0_OR_GREATER using System; +#endif using System.Runtime.CompilerServices; using CommunityToolkit.HighPerformance.Enumerables; using CommunityToolkit.HighPerformance.Memory.Internals; -#if NETSTANDARD2_1_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER && !NET7_0_OR_GREATER using System.Runtime.InteropServices; -#else +#elif NETSTANDARD2_0 using RuntimeHelpers = CommunityToolkit.HighPerformance.Helpers.Internals.RuntimeHelpers; #endif @@ -84,7 +86,17 @@ public RefEnumerable GetColumn(int column) /// public ref struct Enumerator { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + /// + /// The reference for the instance. + /// + private readonly ref T reference; + + /// + /// The height of the specified 2D region. + /// + private readonly int height; +#elif NETSTANDARD2_1_OR_GREATER /// /// The instance pointing to the first item in the target memory area. /// @@ -133,7 +145,10 @@ public ref struct Enumerator /// The target instance to enumerate. internal Enumerator(Span2D span) { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + this.reference = ref span.reference; + this.height = span.height; +#elif NETSTANDARD2_1_OR_GREATER this.span = span.span; #else this.instance = span.Instance; @@ -167,7 +182,9 @@ public bool MoveNext() // another row available: wrap to a new line and continue. this.x = 0; -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + return ++this.y < this.height; +#elif NETSTANDARD2_1_OR_GREATER return ++this.y < this.span.Length; #else return ++this.y < this.height; @@ -182,7 +199,9 @@ public readonly ref T Current [MethodImpl(MethodImplOptions.AggressiveInlining)] get { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + ref T r0 = ref this.reference; +#elif NETSTANDARD2_1_OR_GREATER ref T r0 = ref MemoryMarshal.GetReference(this.span); #else ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); diff --git a/CommunityToolkit.HighPerformance/Memory/Span2D{T}.cs b/src/CommunityToolkit.HighPerformance/Memory/Span2D{T}.cs similarity index 94% rename from CommunityToolkit.HighPerformance/Memory/Span2D{T}.cs rename to src/CommunityToolkit.HighPerformance/Memory/Span2D{T}.cs index 96c6a3d22..2a32c970c 100644 --- a/CommunityToolkit.HighPerformance/Memory/Span2D{T}.cs +++ b/src/CommunityToolkit.HighPerformance/Memory/Span2D{T}.cs @@ -4,6 +4,7 @@ using System; using System.ComponentModel; +using System.Data.Common; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -55,7 +56,17 @@ public readonly ref partial struct Span2D // discontiguous row, so that any arbitrary memory locations // can be used to internally represent a 2D span. This gives // users much more flexibility when creating spans from data. -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + /// + /// The reference for the instance. + /// + private readonly ref T reference; + + /// + /// The height of the specified 2D region. + /// + private readonly int height; +#elif NETSTANDARD2_1_OR_GREATER /// /// The instance pointing to the first item in the target memory area. /// @@ -106,7 +117,12 @@ public readonly ref partial struct Span2D [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Span2D(ref T value, int height, int width, int pitch) { +#if NET7_0_OR_GREATER + this.reference = ref value; + this.height = height; +#else this.span = MemoryMarshal.CreateSpan(ref value, height); +#endif this.width = width; this.Stride = width + pitch; } @@ -144,7 +160,10 @@ public unsafe Span2D(void* pointer, int height, int width, int pitch) OverflowHelper.EnsureIsInNativeIntRange(height, width, pitch); -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + this.reference = ref Unsafe.AsRef(pointer); + this.height = height; +#elif NETSTANDARD2_1_OR_GREATER this.span = new Span(pointer, height); #else this.Instance = null; @@ -245,7 +264,10 @@ public Span2D(T[] array, int offset, int height, int width, int pitch) ThrowHelper.ThrowArgumentException(); } -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + this.reference = ref array.DangerousGetReferenceAt(offset); + this.height = height; +#elif NETSTANDARD2_1_OR_GREATER this.span = MemoryMarshal.CreateSpan(ref array.DangerousGetReferenceAt(offset), height); #else this.Instance = array; @@ -277,7 +299,10 @@ public Span2D(T[,]? array) ThrowHelper.ThrowArrayTypeMismatchException(); } -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + this.reference = ref array.DangerousGetReference(); + this.height = array.GetLength(0); +#elif NETSTANDARD2_1_OR_GREATER this.span = MemoryMarshal.CreateSpan(ref array.DangerousGetReference(), array.GetLength(0)); #else this.Instance = array; @@ -344,7 +369,10 @@ public Span2D(T[,]? array, int row, int column, int height, int width) ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); } -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + this.reference = ref array.DangerousGetReferenceAt(row, column); + this.height = height; +#elif NETSTANDARD2_1_OR_GREATER this.span = MemoryMarshal.CreateSpan(ref array.DangerousGetReferenceAt(row, column), height); #else this.Instance = array; @@ -376,7 +404,10 @@ public Span2D(T[,,] array, int depth) ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth(); } -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + this.reference = ref array.DangerousGetReferenceAt(depth, 0, 0); + this.height = array.GetLength(1); +#elif NETSTANDARD2_1_OR_GREATER this.span = MemoryMarshal.CreateSpan(ref array.DangerousGetReferenceAt(depth, 0, 0), array.GetLength(1)); #else this.Instance = array; @@ -434,7 +465,10 @@ public Span2D(T[,,] array, int depth, int row, int column, int height, int width ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth(); } -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + this.reference = ref array.DangerousGetReferenceAt(depth, row, column); + this.height = height; +#elif NETSTANDARD2_1_OR_GREATER this.span = MemoryMarshal.CreateSpan(ref array.DangerousGetReferenceAt(depth, row, column), height); #else this.Instance = array; @@ -512,7 +546,12 @@ internal Span2D(Span span, int offset, int height, int width, int pitch) ThrowHelper.ThrowArgumentException(); } +#if NET7_0_OR_GREATER + this.reference = ref span.DangerousGetReferenceAt(offset); + this.height = height; +#else this.span = MemoryMarshal.CreateSpan(ref span.DangerousGetReferenceAt(offset), height); +#endif this.width = width; this.Stride = width + pitch; } @@ -580,7 +619,9 @@ public int Height [MethodImpl(MethodImplOptions.AggressiveInlining)] get { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + return this.height; +#elif NETSTANDARD2_1_OR_GREATER return this.span.Length; #else return this.height; @@ -902,7 +943,9 @@ public unsafe ref T GetPinnableReference() if (Length != 0) { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + r0 = ref this.reference; +#elif NETSTANDARD2_1_OR_GREATER r0 = ref MemoryMarshal.GetReference(this.span); #else r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset); @@ -919,7 +962,9 @@ public unsafe ref T GetPinnableReference() [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref T DangerousGetReference() { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + return ref this.reference; +#elif NETSTANDARD2_1_OR_GREATER return ref MemoryMarshal.GetReference(this.span); #else return ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset); @@ -935,7 +980,9 @@ public ref T DangerousGetReference() [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref T DangerousGetReferenceAt(int i, int j) { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + ref T r0 = ref this.reference; +#elif NETSTANDARD2_1_OR_GREATER ref T r0 = ref MemoryMarshal.GetReference(this.span); #else ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset); @@ -957,7 +1004,7 @@ public ref T DangerousGetReferenceAt(int i, int j) /// are negative or not within the bounds that are valid for the current instance. /// /// A new instance representing a slice of the current one. - public Span2D Slice(int row, int column, int height, int width) + public unsafe Span2D Slice(int row, int column, int height, int width) { if ((uint)row >= Height) { @@ -982,12 +1029,16 @@ public Span2D Slice(int row, int column, int height, int width) nint shift = ((nint)(uint)this.Stride * (nint)(uint)row) + (nint)(uint)column; int pitch = this.Stride - width; -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + ref T r0 = ref Unsafe.Add(ref this.reference, shift); + + return new(ref r0, height, width, pitch); +#elif NETSTANDARD2_1_OR_GREATER ref T r0 = ref this.span.DangerousGetReferenceAt(shift); return new(ref r0, height, width, pitch); #else - IntPtr offset = this.Offset + (shift * (nint)(uint)Unsafe.SizeOf()); + IntPtr offset = this.Offset + (shift * (nint)(uint)sizeof(T)); return new(this.Instance, offset, height, width, pitch); #endif @@ -1024,7 +1075,11 @@ public bool TryGetSpan(out Span span) if (this.Stride == this.width && Length <= int.MaxValue) { -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + span = MemoryMarshal.CreateSpan(ref this.reference, (int)Length); + + return true; +#elif NETSTANDARD2_1_OR_GREATER span = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(this.span), (int)Length); return true; @@ -1135,7 +1190,10 @@ public override string ToString() public static bool operator ==(Span2D left, Span2D right) { return -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER + Unsafe.AreSame(ref left.reference, ref right.reference) && + left.height == right.height && +#elif NETSTANDARD2_1_OR_GREATER left.span == right.span && #else ReferenceEquals( diff --git a/CommunityToolkit.HighPerformance/Memory/Views/MemoryDebugView2D{T}.cs b/src/CommunityToolkit.HighPerformance/Memory/Views/MemoryDebugView2D{T}.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Memory/Views/MemoryDebugView2D{T}.cs rename to src/CommunityToolkit.HighPerformance/Memory/Views/MemoryDebugView2D{T}.cs diff --git a/CommunityToolkit.HighPerformance/NullableReadOnlyRef{T}.cs b/src/CommunityToolkit.HighPerformance/NullableReadOnlyRef{T}.cs similarity index 74% rename from CommunityToolkit.HighPerformance/NullableReadOnlyRef{T}.cs rename to src/CommunityToolkit.HighPerformance/NullableReadOnlyRef{T}.cs index 104b96bcb..298dc0f70 100644 --- a/CommunityToolkit.HighPerformance/NullableReadOnlyRef{T}.cs +++ b/src/CommunityToolkit.HighPerformance/NullableReadOnlyRef{T}.cs @@ -2,12 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER using System; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Versioning; namespace CommunityToolkit.HighPerformance; @@ -15,15 +13,12 @@ namespace CommunityToolkit.HighPerformance; /// A that can store an optional readonly reference to a value of a specified type. /// /// The type of value to reference. -[RequiresPreviewFeatures( - "The NullableReadOnlyRef type has no compiler support to ensure the lifetime of referenced values is respected, and as such using it incorrectly may lead to GC holes.", - Url = "https://github.com/dotnet/runtime/issues/46104")] public readonly ref struct NullableReadOnlyRef { /// - /// The 1-length instance used to track the target value. + /// The reference to the target value. /// - private readonly ReadOnlySpan span; + private readonly ref readonly T value; /// /// Initializes a new instance of the struct. @@ -32,19 +27,7 @@ public readonly ref struct NullableReadOnlyRef [MethodImpl(MethodImplOptions.AggressiveInlining)] public NullableReadOnlyRef(in T value) { - ref T r0 = ref Unsafe.AsRef(value); - - this.span = MemoryMarshal.CreateReadOnlySpan(ref r0, 1); - } - - /// - /// Initializes a new instance of the struct. - /// - /// The instance to track the target reference. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private NullableReadOnlyRef(ReadOnlySpan span) - { - this.span = span; + this.value = ref value; } /// @@ -62,13 +45,7 @@ public static NullableReadOnlyRef Null public unsafe bool HasValue { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - // See comment in NullableRef about this - byte length = unchecked((byte)this.span.Length); - - return *(bool*)&length; - } + get => !Unsafe.IsNullRef(ref Unsafe.AsRef(in this.value)); } /// @@ -85,7 +62,7 @@ public ref readonly T Value ThrowInvalidOperationException(); } - return ref MemoryMarshal.GetReference(this.span); + return ref this.value; } } @@ -96,7 +73,7 @@ public ref readonly T Value [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator NullableReadOnlyRef(Ref reference) { - return new(reference.Span); + return new(in reference.Value); } /// @@ -106,7 +83,7 @@ public static implicit operator NullableReadOnlyRef(Ref reference) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator NullableReadOnlyRef(ReadOnlyRef reference) { - return new(reference.Span); + return new(in reference.Value); } /// @@ -116,7 +93,7 @@ public static implicit operator NullableReadOnlyRef(ReadOnlyRef reference) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator NullableReadOnlyRef(NullableRef reference) { - return new(reference.Span); + return new(in reference.Value); } /// diff --git a/CommunityToolkit.HighPerformance/NullableRef{T}.cs b/src/CommunityToolkit.HighPerformance/NullableRef{T}.cs similarity index 65% rename from CommunityToolkit.HighPerformance/NullableRef{T}.cs rename to src/CommunityToolkit.HighPerformance/NullableRef{T}.cs index 5b7163f1a..9e31c0bbf 100644 --- a/CommunityToolkit.HighPerformance/NullableRef{T}.cs +++ b/src/CommunityToolkit.HighPerformance/NullableRef{T}.cs @@ -2,12 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if NETSTANDARD2_1_OR_GREATER +#if NET7_0_OR_GREATER using System; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Versioning; namespace CommunityToolkit.HighPerformance; @@ -15,15 +13,12 @@ namespace CommunityToolkit.HighPerformance; /// A that can store an optional reference to a value of a specified type. /// /// The type of value to reference. -[RequiresPreviewFeatures( - "The NullableRef type has no compiler support to ensure the lifetime of referenced values is respected, and as such using it incorrectly may lead to GC holes.", - Url = "https://github.com/dotnet/runtime/issues/46104")] public readonly ref struct NullableRef { /// - /// The 1-length instance used to track the target value. + /// The reference to the target value. /// - internal readonly Span Span; + private readonly ref T value; /// /// Initializes a new instance of the struct. @@ -32,17 +27,7 @@ public readonly ref struct NullableRef [MethodImpl(MethodImplOptions.AggressiveInlining)] public NullableRef(ref T value) { - this.Span = MemoryMarshal.CreateSpan(ref value, 1); - } - - /// - /// Initializes a new instance of the struct. - /// - /// The instance to track the target reference. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private NullableRef(Span span) - { - this.Span = span; + this.value = ref value; } /// @@ -60,19 +45,7 @@ public static NullableRef Null public unsafe bool HasValue { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - // We know that the span will always have a length of either - // 1 or 0, so instead of using a cmp instruction and setting the - // zero flag to produce our boolean value, we can just cast - // the length to byte without overflow checks (doing a cast will - // also account for the byte endianness of the current system), - // and then reinterpret that value to a bool flag. - // This results in a single movzx instruction on x86-64. - byte length = unchecked((byte)this.Span.Length); - - return *(bool*)&length; - } + get => !Unsafe.IsNullRef(ref this.value); } /// @@ -89,7 +62,7 @@ public ref T Value ThrowInvalidOperationException(); } - return ref MemoryMarshal.GetReference(this.Span); + return ref this.value; } } @@ -100,7 +73,7 @@ public ref T Value [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator NullableRef(Ref reference) { - return new(reference.Span); + return new(ref reference.Value); } /// diff --git a/src/CommunityToolkit.HighPerformance/ReadOnlyRef{T}.cs b/src/CommunityToolkit.HighPerformance/ReadOnlyRef{T}.cs new file mode 100644 index 000000000..31ac75b33 --- /dev/null +++ b/src/CommunityToolkit.HighPerformance/ReadOnlyRef{T}.cs @@ -0,0 +1,72 @@ +// 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. + +#if NET7_0_OR_GREATER + +using System.Runtime.CompilerServices; + +namespace CommunityToolkit.HighPerformance; + +/// +/// A that can store a readonly reference to a value of a specified type. +/// +/// The type of value to reference. +public readonly ref struct ReadOnlyRef +{ + /// + /// The reference to the target value. + /// + private readonly ref readonly T value; + + /// + /// Initializes a new instance of the struct. + /// + /// The readonly reference to the target value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyRef(in T value) + { + this.value = ref value; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The pointer to the target value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe ReadOnlyRef(void* pointer) + : this(in Unsafe.AsRef(pointer)) + { + } + + /// + /// Gets the readonly reference represented by the current instance. + /// + public ref readonly T Value + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref this.value; + } + + /// + /// Implicitly converts a instance into a one. + /// + /// The input instance. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlyRef(Ref reference) + { + return new(in reference.Value); + } + + /// + /// Implicitly gets the value from a given instance. + /// + /// The input instance. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator T(ReadOnlyRef reference) + { + return reference.Value; + } +} + +#endif diff --git a/src/CommunityToolkit.HighPerformance/Ref{T}.cs b/src/CommunityToolkit.HighPerformance/Ref{T}.cs new file mode 100644 index 000000000..59ac62469 --- /dev/null +++ b/src/CommunityToolkit.HighPerformance/Ref{T}.cs @@ -0,0 +1,62 @@ +// 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. + +#if NET7_0_OR_GREATER + +using System.Runtime.CompilerServices; + +namespace CommunityToolkit.HighPerformance; + +/// +/// A that can store a reference to a value of a specified type. +/// +/// The type of value to reference. +public readonly ref struct Ref +{ + /// + /// The reference to the target value. + /// + private readonly ref T value; + + /// + /// Initializes a new instance of the struct. + /// + /// The reference to the target value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Ref(ref T value) + { + this.value = ref value; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The pointer to the target value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe Ref(void* pointer) + : this(ref Unsafe.AsRef(pointer)) + { + } + + /// + /// Gets the reference represented by the current instance. + /// + public ref T Value + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref this.value; + } + + /// + /// Implicitly gets the value from a given instance. + /// + /// The input instance. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator T(Ref reference) + { + return reference.Value; + } +} + +#endif diff --git a/CommunityToolkit.HighPerformance/Streams/IBufferWriterStream{TWriter}.Memory.cs b/src/CommunityToolkit.HighPerformance/Streams/IBufferWriterStream{TWriter}.Memory.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Streams/IBufferWriterStream{TWriter}.Memory.cs rename to src/CommunityToolkit.HighPerformance/Streams/IBufferWriterStream{TWriter}.Memory.cs diff --git a/CommunityToolkit.HighPerformance/Streams/IBufferWriterStream{TWriter}.cs b/src/CommunityToolkit.HighPerformance/Streams/IBufferWriterStream{TWriter}.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Streams/IBufferWriterStream{TWriter}.cs rename to src/CommunityToolkit.HighPerformance/Streams/IBufferWriterStream{TWriter}.cs diff --git a/CommunityToolkit.HighPerformance/Streams/IMemoryOwnerStream{TSource}.cs b/src/CommunityToolkit.HighPerformance/Streams/IMemoryOwnerStream{TSource}.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Streams/IMemoryOwnerStream{TSource}.cs rename to src/CommunityToolkit.HighPerformance/Streams/IMemoryOwnerStream{TSource}.cs diff --git a/CommunityToolkit.HighPerformance/Streams/MemoryStream.ThrowExceptions.cs b/src/CommunityToolkit.HighPerformance/Streams/MemoryStream.ThrowExceptions.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Streams/MemoryStream.ThrowExceptions.cs rename to src/CommunityToolkit.HighPerformance/Streams/MemoryStream.ThrowExceptions.cs diff --git a/CommunityToolkit.HighPerformance/Streams/MemoryStream.Validate.cs b/src/CommunityToolkit.HighPerformance/Streams/MemoryStream.Validate.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Streams/MemoryStream.Validate.cs rename to src/CommunityToolkit.HighPerformance/Streams/MemoryStream.Validate.cs diff --git a/CommunityToolkit.HighPerformance/Streams/MemoryStream.cs b/src/CommunityToolkit.HighPerformance/Streams/MemoryStream.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Streams/MemoryStream.cs rename to src/CommunityToolkit.HighPerformance/Streams/MemoryStream.cs diff --git a/CommunityToolkit.HighPerformance/Streams/MemoryStream{TSource}.Memory.cs b/src/CommunityToolkit.HighPerformance/Streams/MemoryStream{TSource}.Memory.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Streams/MemoryStream{TSource}.Memory.cs rename to src/CommunityToolkit.HighPerformance/Streams/MemoryStream{TSource}.Memory.cs diff --git a/CommunityToolkit.HighPerformance/Streams/MemoryStream{TSource}.cs b/src/CommunityToolkit.HighPerformance/Streams/MemoryStream{TSource}.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Streams/MemoryStream{TSource}.cs rename to src/CommunityToolkit.HighPerformance/Streams/MemoryStream{TSource}.cs diff --git a/CommunityToolkit.HighPerformance/Streams/Sources/ArrayBufferWriterOwner.cs b/src/CommunityToolkit.HighPerformance/Streams/Sources/ArrayBufferWriterOwner.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Streams/Sources/ArrayBufferWriterOwner.cs rename to src/CommunityToolkit.HighPerformance/Streams/Sources/ArrayBufferWriterOwner.cs diff --git a/CommunityToolkit.HighPerformance/Streams/Sources/ArrayOwner.cs b/src/CommunityToolkit.HighPerformance/Streams/Sources/ArrayOwner.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Streams/Sources/ArrayOwner.cs rename to src/CommunityToolkit.HighPerformance/Streams/Sources/ArrayOwner.cs diff --git a/CommunityToolkit.HighPerformance/Streams/Sources/IBufferWriterOwner.cs b/src/CommunityToolkit.HighPerformance/Streams/Sources/IBufferWriterOwner.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Streams/Sources/IBufferWriterOwner.cs rename to src/CommunityToolkit.HighPerformance/Streams/Sources/IBufferWriterOwner.cs diff --git a/CommunityToolkit.HighPerformance/Streams/Sources/Interfaces/ISpanOwner.cs b/src/CommunityToolkit.HighPerformance/Streams/Sources/Interfaces/ISpanOwner.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Streams/Sources/Interfaces/ISpanOwner.cs rename to src/CommunityToolkit.HighPerformance/Streams/Sources/Interfaces/ISpanOwner.cs diff --git a/CommunityToolkit.HighPerformance/Streams/Sources/MemoryManagerOwner.cs b/src/CommunityToolkit.HighPerformance/Streams/Sources/MemoryManagerOwner.cs similarity index 100% rename from CommunityToolkit.HighPerformance/Streams/Sources/MemoryManagerOwner.cs rename to src/CommunityToolkit.HighPerformance/Streams/Sources/MemoryManagerOwner.cs diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.csproj b/src/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.csproj new file mode 100644 index 000000000..d19620f52 --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.csproj @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators.Roslyn431/CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.csproj b/src/CommunityToolkit.Mvvm.SourceGenerators.Roslyn431/CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.csproj new file mode 100644 index 000000000..3cea30b35 --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators.Roslyn431/CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.csproj @@ -0,0 +1,6 @@ + + + + + + diff --git a/CommunityToolkit.Mvvm.SourceGenerators/AnalyzerReleases.Shipped.md b/src/CommunityToolkit.Mvvm.SourceGenerators/AnalyzerReleases.Shipped.md similarity index 87% rename from CommunityToolkit.Mvvm.SourceGenerators/AnalyzerReleases.Shipped.md rename to src/CommunityToolkit.Mvvm.SourceGenerators/AnalyzerReleases.Shipped.md index 00727705c..957d6b750 100644 --- a/CommunityToolkit.Mvvm.SourceGenerators/AnalyzerReleases.Shipped.md +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/AnalyzerReleases.Shipped.md @@ -38,3 +38,14 @@ MVVMTK0028 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator MVVMTK0029 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0029 MVVMTK0030 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0030 MVVMTK0031 | CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator | Error | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0031 + +## Release 8.1 + +### New Rules + +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +MVVMTK0032 | CommunityToolkit.Mvvm.SourceGenerators.INotifyPropertyChangedGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0032 +MVVMTK0033 | CommunityToolkit.Mvvm.SourceGenerators.ObservableObjectGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0033 +MVVMTK0034 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0034 +MVVMTK0035 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0035 diff --git a/CommunityToolkit.Mvvm.SourceGenerators/AnalyzerReleases.Unshipped.md b/src/CommunityToolkit.Mvvm.SourceGenerators/AnalyzerReleases.Unshipped.md similarity index 100% rename from CommunityToolkit.Mvvm.SourceGenerators/AnalyzerReleases.Unshipped.md rename to src/CommunityToolkit.Mvvm.SourceGenerators/AnalyzerReleases.Unshipped.md diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems b/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems new file mode 100644 index 000000000..405e84cfd --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems @@ -0,0 +1,86 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 5e7f1212-a54b-40ca-98c5-1ff5cd1a1638 + + + CommunityToolkit.Mvvm.SourceGenerators + + + + PreserveNewest + INotifyPropertyChanged.cs + + + PreserveNewest + ObservableObject.cs + + + PreserveNewest + ObservableRecipient.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.props b/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.props new file mode 100644 index 000000000..c3130e015 --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.props @@ -0,0 +1,38 @@ + + + + netstandard2.0 + false + + + + + + + $(MSBuildProjectName.Substring(0, $([MSBuild]::Subtract($(MSBuildProjectName.Length), 10)))) + + + $(MSBuildProjectName.Substring($([MSBuild]::Subtract($(MSBuildProjectName.Length), 3)), 1)) + $(MSBuildProjectName.Substring($([MSBuild]::Subtract($(MSBuildProjectName.Length), 2)), 1)) + $(MSBuildProjectName.Substring($([MSBuild]::Subtract($(MSBuildProjectName.Length), 1)), 1)) + $(MvvmToolkitSourceGeneratorRoslynMajorVersion).$(MvvmToolkitSourceGeneratorRoslynMinorVersion).$(MvvmToolkitSourceGeneratorRoslynPatchVersion) + + + $(DefineConstants);ROSLYN_4_3_1_OR_GREATER + + + + + + + \ No newline at end of file diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.shproj b/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.shproj new file mode 100644 index 000000000..a9dfb1cf5 --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.shproj @@ -0,0 +1,13 @@ + + + + 5e7f1212-a54b-40ca-98c5-1ff5cd1a1638 + 14.0 + + + + + + + + diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/INotifyPropertyChangedGenerator.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/INotifyPropertyChangedGenerator.cs new file mode 100644 index 000000000..9a8bccbd0 --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/INotifyPropertyChangedGenerator.cs @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Linq; +using CommunityToolkit.Mvvm.SourceGenerators.ComponentModel.Models; +using CommunityToolkit.Mvvm.SourceGenerators.Extensions; +using CommunityToolkit.Mvvm.SourceGenerators.Helpers; +using CommunityToolkit.Mvvm.SourceGenerators.Models; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors; + +namespace CommunityToolkit.Mvvm.SourceGenerators; + +/// +/// A source generator for the INotifyPropertyChangedAttribute type. +/// +[Generator(LanguageNames.CSharp)] +public sealed class INotifyPropertyChangedGenerator : TransitiveMembersGenerator +{ + /// + /// Initializes a new instance of the class. + /// + public INotifyPropertyChangedGenerator() + : base("CommunityToolkit.Mvvm.ComponentModel.INotifyPropertyChangedAttribute") + { + } + + /// + private protected override INotifyPropertyChangedInfo? ValidateTargetTypeAndGetInfo(INamedTypeSymbol typeSymbol, AttributeData attributeData, Compilation compilation, out ImmutableArray diagnostics) + { + diagnostics = ImmutableArray.Empty; + + INotifyPropertyChangedInfo? info = null; + + // Check if the type already implements INotifyPropertyChanged + if (typeSymbol.AllInterfaces.Any(i => i.HasFullyQualifiedMetadataName("System.ComponentModel.INotifyPropertyChanged"))) + { + diagnostics = ImmutableArray.Create(DiagnosticInfo.Create(DuplicateINotifyPropertyChangedInterfaceForINotifyPropertyChangedAttributeError, typeSymbol, typeSymbol)); + + goto End; + } + + // Check if the type uses [INotifyPropertyChanged] or [ObservableObject] already (in the type hierarchy too) + if (typeSymbol.HasOrInheritsAttributeWithFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableObjectAttribute") || + typeSymbol.InheritsAttributeWithFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.INotifyPropertyChangedAttribute")) + { + diagnostics = ImmutableArray.Create(DiagnosticInfo.Create(InvalidAttributeCombinationForINotifyPropertyChangedAttributeError, typeSymbol, typeSymbol)); + + goto End; + } + + bool includeAdditionalHelperMethods = attributeData.GetNamedArgument("IncludeAdditionalHelperMethods", true); + + info = new INotifyPropertyChangedInfo(includeAdditionalHelperMethods); + + End: + return info; + } + + /// + protected override ImmutableArray FilterDeclaredMembers(INotifyPropertyChangedInfo info, ImmutableArray memberDeclarations) + { + // If requested, only include the event and the basic methods to raise it, but not the additional helpers + if (!info.IncludeAdditionalHelperMethods) + { + using ImmutableArrayBuilder selectedMembers = ImmutableArrayBuilder.Rent(); + + foreach (MemberDeclarationSyntax memberDeclaration in memberDeclarations) + { + if (memberDeclaration is EventFieldDeclarationSyntax or MethodDeclarationSyntax { Identifier.ValueText: "OnPropertyChanged" }) + { + selectedMembers.Add(memberDeclaration); + } + } + + return selectedMembers.ToImmutable(); + } + + return memberDeclarations; + } +} diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/AttributeInfo.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/AttributeInfo.cs new file mode 100644 index 000000000..505988244 --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/AttributeInfo.cs @@ -0,0 +1,116 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using CommunityToolkit.Mvvm.SourceGenerators.Extensions; +using CommunityToolkit.Mvvm.SourceGenerators.Helpers; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; + +namespace CommunityToolkit.Mvvm.SourceGenerators.ComponentModel.Models; + +/// +/// A model representing an attribute declaration. +/// +internal sealed record AttributeInfo( + string TypeName, + EquatableArray ConstructorArgumentInfo, + EquatableArray<(string Name, TypedConstantInfo Value)> NamedArgumentInfo) +{ + /// + /// Creates a new instance from a given value. + /// + /// The input value. + /// A instance representing . + public static AttributeInfo From(AttributeData attributeData) + { + string typeName = attributeData.AttributeClass!.GetFullyQualifiedName(); + + using ImmutableArrayBuilder constructorArguments = ImmutableArrayBuilder.Rent(); + using ImmutableArrayBuilder<(string, TypedConstantInfo)> namedArguments = ImmutableArrayBuilder<(string, TypedConstantInfo)>.Rent(); + + // Get the constructor arguments + foreach (TypedConstant typedConstant in attributeData.ConstructorArguments) + { + constructorArguments.Add(TypedConstantInfo.From(typedConstant)); + } + + // Get the named arguments + foreach (KeyValuePair namedConstant in attributeData.NamedArguments) + { + namedArguments.Add((namedConstant.Key, TypedConstantInfo.From(namedConstant.Value))); + } + + return new( + typeName, + constructorArguments.ToImmutable(), + namedArguments.ToImmutable()); + } + + /// + /// Creates a new instance from a given syntax node. + /// + /// The symbol for the attribute type. + /// The instance for the current run. + /// The sequence of instances to process. + /// The cancellation token for the current operation. + /// A instance representing the input attribute data. + public static AttributeInfo From(INamedTypeSymbol typeSymbol, SemanticModel semanticModel, IEnumerable arguments, CancellationToken token) + { + string typeName = typeSymbol.GetFullyQualifiedName(); + + using ImmutableArrayBuilder constructorArguments = ImmutableArrayBuilder.Rent(); + using ImmutableArrayBuilder<(string, TypedConstantInfo)> namedArguments = ImmutableArrayBuilder<(string, TypedConstantInfo)>.Rent(); + + foreach (AttributeArgumentSyntax argument in arguments) + { + // The attribute expression has to have an available operation to extract information from + if (semanticModel.GetOperation(argument.Expression, token) is not IOperation operation) + { + continue; + } + + TypedConstantInfo argumentInfo = TypedConstantInfo.From(operation, semanticModel, argument.Expression, token); + + // Try to get the identifier name if the current expression is a named argument expression. If it + // isn't, then the expression is a normal attribute constructor argument, so no extra work is needed. + if (argument.NameEquals is { Name.Identifier.ValueText: string argumentName }) + { + namedArguments.Add((argumentName, argumentInfo)); + } + else + { + constructorArguments.Add(argumentInfo); + } + } + + return new( + typeName, + constructorArguments.ToImmutable(), + namedArguments.ToImmutable()); + } + + /// + /// Gets an instance representing the current value. + /// + /// The instance representing the current value. + public AttributeSyntax GetSyntax() + { + // Gather the constructor arguments + IEnumerable arguments = + ConstructorArgumentInfo + .Select(static arg => AttributeArgument(arg.GetSyntax())); + + // Gather the named arguments + IEnumerable namedArguments = + NamedArgumentInfo.Select(static arg => + AttributeArgument(arg.Value.GetSyntax()) + .WithNameEquals(NameEquals(IdentifierName(arg.Name)))); + + return Attribute(IdentifierName(TypeName), AttributeArgumentList(SeparatedList(arguments.Concat(namedArguments)))); + } +} diff --git a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/INotifyPropertyChangedInfo.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/INotifyPropertyChangedInfo.cs similarity index 88% rename from CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/INotifyPropertyChangedInfo.cs rename to src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/INotifyPropertyChangedInfo.cs index 30d1f70d1..dfa2d2997 100644 --- a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/INotifyPropertyChangedInfo.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/INotifyPropertyChangedInfo.cs @@ -2,7 +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. -namespace CommunityToolkit.Mvvm.SourceGenerators.Input.Models; +namespace CommunityToolkit.Mvvm.SourceGenerators.ComponentModel.Models; /// /// A model with gathered info on a given INotifyPropertyChangedAttribute instance. diff --git a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/ObservableRecipientInfo.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/ObservableRecipientInfo.cs similarity index 100% rename from CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/ObservableRecipientInfo.cs rename to src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/ObservableRecipientInfo.cs diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/PropertyInfo.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/PropertyInfo.cs new file mode 100644 index 000000000..c02e123ca --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/PropertyInfo.cs @@ -0,0 +1,30 @@ +// 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 CommunityToolkit.Mvvm.SourceGenerators.Helpers; + +namespace CommunityToolkit.Mvvm.SourceGenerators.ComponentModel.Models; + +/// +/// A model representing an generated property +/// +/// The type name for the generated property, including nullability annotations. +/// The field name. +/// The generated property name. +/// The sequence of property changing properties to notify. +/// The sequence of property changed properties to notify. +/// The sequence of commands to notify. +/// Whether or not the generated property also broadcasts changes. +/// Whether or not the generated property also validates its value. +/// The sequence of forwarded attributes for the generated property. +internal sealed record PropertyInfo( + string TypeNameWithNullabilityAnnotations, + string FieldName, + string PropertyName, + EquatableArray PropertyChangingNames, + EquatableArray PropertyChangedNames, + EquatableArray NotifiedCommandNames, + bool NotifyPropertyChangedRecipients, + bool NotifyDataErrorInfo, + EquatableArray ForwardedAttributes); diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/TypedConstantInfo.Factory.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/TypedConstantInfo.Factory.cs new file mode 100644 index 000000000..825b075ec --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/TypedConstantInfo.Factory.cs @@ -0,0 +1,149 @@ +// 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.Linq; +using System.Threading; +using CommunityToolkit.Mvvm.SourceGenerators.Helpers; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Operations; + +namespace CommunityToolkit.Mvvm.SourceGenerators.ComponentModel.Models; + +/// +partial record TypedConstantInfo +{ + /// + /// Creates a new instance from a given value. + /// + /// The input value. + /// A instance representing . + /// Thrown if the input argument is not valid. + public static TypedConstantInfo From(TypedConstant arg) + { + if (arg.IsNull) + { + return new Null(); + } + + if (arg.Kind == TypedConstantKind.Array) + { + string elementTypeName = ((IArrayTypeSymbol)arg.Type!).ElementType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + ImmutableArray items = arg.Values.Select(From).ToImmutableArray(); + + return new Array(elementTypeName, items); + } + + return (arg.Kind, arg.Value) switch + { + (TypedConstantKind.Primitive, string text) => new Primitive.String(text), + (TypedConstantKind.Primitive, bool flag) => new Primitive.Boolean(flag), + (TypedConstantKind.Primitive, object value) => value switch + { + byte b => new Primitive.Of(b), + char c => new Primitive.Of(c), + double d => new Primitive.Of(d), + float f => new Primitive.Of(f), + int i => new Primitive.Of(i), + long l => new Primitive.Of(l), + sbyte sb => new Primitive.Of(sb), + short sh => new Primitive.Of(sh), + uint ui => new Primitive.Of(ui), + ulong ul => new Primitive.Of(ul), + ushort ush => new Primitive.Of(ush), + _ => throw new ArgumentException("Invalid primitive type") + }, + (TypedConstantKind.Type, ITypeSymbol type) => new Type(type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)), + (TypedConstantKind.Enum, object value) => new Enum(arg.Type!.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), value), + _ => throw new ArgumentException("Invalid typed constant type"), + }; + } + + /// + /// Creates a new instance from a given value. + /// + /// The input value. + /// The that was used to retrieve . + /// The that was retrieved from. + /// The cancellation token for the current operation. + /// A instance representing . + /// Thrown if the input argument is not valid. + public static TypedConstantInfo From( + IOperation operation, + SemanticModel semanticModel, + ExpressionSyntax expression, + CancellationToken token) + { + if (operation.ConstantValue.HasValue) + { + // Enum values are constant but need to be checked explicitly in this case + if (operation.Type?.TypeKind is TypeKind.Enum) + { + return new Enum(operation.Type!.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), operation.ConstantValue.Value!); + } + + // Handle all other constant literals normally + return operation.ConstantValue.Value switch + { + null => new Null(), + string text => new Primitive.String(text), + bool flag => new Primitive.Boolean(flag), + byte b => new Primitive.Of(b), + char c => new Primitive.Of(c), + double d => new Primitive.Of(d), + float f => new Primitive.Of(f), + int i => new Primitive.Of(i), + long l => new Primitive.Of(l), + sbyte sb => new Primitive.Of(sb), + short sh => new Primitive.Of(sh), + uint ui => new Primitive.Of(ui), + ulong ul => new Primitive.Of(ul), + ushort ush => new Primitive.Of(ush), + _ => throw new ArgumentException("Invalid primitive type") + }; + } + + if (operation is ITypeOfOperation typeOfOperation) + { + return new Type(typeOfOperation.TypeOperand.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); + } + + if (operation is IArrayCreationOperation) + { + string? elementTypeName = ((IArrayTypeSymbol?)operation.Type)?.ElementType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + + // If the element type is not available (since the attribute wasn't checked), just default to object + elementTypeName ??= "object"; + + InitializerExpressionSyntax? initializerExpression = + (expression as ImplicitArrayCreationExpressionSyntax)?.Initializer + ?? (expression as ArrayCreationExpressionSyntax)?.Initializer; + + // No initializer found, just return an empty array + if (initializerExpression is null) + { + return new Array(elementTypeName, ImmutableArray.Empty); + } + + using ImmutableArrayBuilder items = ImmutableArrayBuilder.Rent(); + + // Enumerate all array elements and extract serialized info for them + foreach (ExpressionSyntax initializationExpression in initializerExpression.Expressions) + { + if (semanticModel.GetOperation(initializationExpression, token) is not IOperation initializationOperation) + { + throw new ArgumentException("Failed to retrieve an operation for the current array element"); + } + + items.Add(From(initializationOperation, semanticModel, initializationExpression, token)); + } + + return new Array(elementTypeName, items.ToImmutable()); + } + + throw new ArgumentException("Invalid attribute argument value"); + } +} diff --git a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/TypedConstantInfo.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/TypedConstantInfo.cs similarity index 58% rename from CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/TypedConstantInfo.cs rename to src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/TypedConstantInfo.cs index df28d7a9e..e1bd70750 100644 --- a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/TypedConstantInfo.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/TypedConstantInfo.cs @@ -3,8 +3,8 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Immutable; using System.Linq; +using CommunityToolkit.Mvvm.SourceGenerators.Helpers; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -24,26 +24,12 @@ internal abstract partial record TypedConstantInfo /// The instance representing the current constant. public abstract ExpressionSyntax GetSyntax(); - /// - /// Checks whether the current instance is the same as an input one. - /// - /// The instance to compare to. - /// Whether or not the two instances are the same. - /// This method differs from in that it checks for deep equality. - protected abstract bool IsEqualTo(TypedConstantInfo other); - - /// - /// Adds the current instance to an incremental value. - /// - /// The target value. - protected abstract void AddToHashCode(ref HashCode hashCode); - /// /// A type representing an array. /// /// The type name for array elements. /// The sequence of contained elements. - public sealed record Array(string ElementTypeName, ImmutableArray Items) : TypedConstantInfo + public sealed record Array(string ElementTypeName, EquatableArray Items) : TypedConstantInfo { /// public override ExpressionSyntax GetSyntax() @@ -55,38 +41,6 @@ public override ExpressionSyntax GetSyntax() .WithInitializer(InitializerExpression(SyntaxKind.ArrayInitializerExpression) .AddExpressions(Items.Select(static c => c.GetSyntax()).ToArray())); } - - /// - protected override bool IsEqualTo(TypedConstantInfo other) - { - if (other is Array array && - ElementTypeName == array.ElementTypeName && - Items.Length == array.Items.Length) - { - for (int i = 0; i < Items.Length; i++) - { - if (!Items[i].IsEqualTo(array.Items[i])) - { - return false; - } - } - - return true; - } - - return false; - } - - /// - protected override void AddToHashCode(ref HashCode hashCode) - { - hashCode.Add(ElementTypeName); - - foreach (TypedConstantInfo item in Items) - { - item.AddToHashCode(ref hashCode); - } - } } /// @@ -105,20 +59,6 @@ public override ExpressionSyntax GetSyntax() { return LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(Value)); } - - /// - protected override bool IsEqualTo(TypedConstantInfo other) - { - return - other is String @string && - Value == @string.Value; - } - - /// - protected override void AddToHashCode(ref HashCode hashCode) - { - hashCode.Add(Value); - } } /// @@ -132,20 +72,6 @@ public override ExpressionSyntax GetSyntax() { return LiteralExpression(Value ? SyntaxKind.TrueLiteralExpression : SyntaxKind.FalseLiteralExpression); } - - /// - protected override bool IsEqualTo(TypedConstantInfo other) - { - return - other is Boolean @bool && - Value == @bool.Value; - } - - /// - protected override void AddToHashCode(ref HashCode hashCode) - { - hashCode.Add(Value); - } } /// @@ -175,20 +101,6 @@ public override ExpressionSyntax GetSyntax() _ => throw new ArgumentException("Invalid primitive type") }); } - - /// - protected override bool IsEqualTo(TypedConstantInfo other) - { - return - other is Of box && - Value.Equals(box.Value); - } - - /// - protected override void AddToHashCode(ref HashCode hashCode) - { - hashCode.Add(Value); - } } } @@ -203,20 +115,6 @@ public override ExpressionSyntax GetSyntax() { return TypeOfExpression(IdentifierName(TypeName)); } - - /// - protected override bool IsEqualTo(TypedConstantInfo other) - { - return - other is Type type && - TypeName == type.TypeName; - } - - /// - protected override void AddToHashCode(ref HashCode hashCode) - { - hashCode.Add(TypeName); - } } /// @@ -234,22 +132,6 @@ public override ExpressionSyntax GetSyntax() IdentifierName(TypeName), LiteralExpression(SyntaxKind.NumericLiteralExpression, ParseToken(Value.ToString()))); } - - /// - protected override bool IsEqualTo(TypedConstantInfo other) - { - return - other is Enum @enum && - TypeName == @enum.TypeName && - Value.Equals(@enum.Value); - } - - /// - protected override void AddToHashCode(ref HashCode hashCode) - { - hashCode.Add(TypeName); - hashCode.Add(Value); - } } /// @@ -262,17 +144,5 @@ public override ExpressionSyntax GetSyntax() { return LiteralExpression(SyntaxKind.NullLiteralExpression); } - - /// - protected override bool IsEqualTo(TypedConstantInfo other) - { - return other is Null; - } - - /// - protected override void AddToHashCode(ref HashCode hashCode) - { - hashCode.Add((object?)null); - } } } diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/ValidationInfo.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/ValidationInfo.cs new file mode 100644 index 000000000..7d5b7b5d9 --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/ValidationInfo.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using CommunityToolkit.Mvvm.SourceGenerators.Helpers; + +namespace CommunityToolkit.Mvvm.SourceGenerators.ComponentModel.Models; + +/// +/// A model with gathered info on all validatable properties in a given type. +/// +/// The filename hint for the current type. +/// The fully qualified type name of the target type. +/// The name of validatable properties. +internal sealed record ValidationInfo(string FilenameHint, string TypeName, EquatableArray PropertyNames); diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableObjectGenerator.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableObjectGenerator.cs new file mode 100644 index 000000000..a2f4bff22 --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableObjectGenerator.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Linq; +using CommunityToolkit.Mvvm.SourceGenerators.Extensions; +using CommunityToolkit.Mvvm.SourceGenerators.Models; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors; + +namespace CommunityToolkit.Mvvm.SourceGenerators; + +/// +/// A source generator for the ObservableObjectAttribute type. +/// +[Generator(LanguageNames.CSharp)] +public sealed class ObservableObjectGenerator : TransitiveMembersGenerator +{ + /// + /// Initializes a new instance of the class. + /// + public ObservableObjectGenerator() + : base("CommunityToolkit.Mvvm.ComponentModel.ObservableObjectAttribute") + { + } + + /// + private protected override int ValidateTargetTypeAndGetInfo(INamedTypeSymbol typeSymbol, AttributeData attributeData, Compilation compilation, out ImmutableArray diagnostics) + { + diagnostics = ImmutableArray.Empty; + + // Check if the type already implements INotifyPropertyChanged... + if (typeSymbol.AllInterfaces.Any(i => i.HasFullyQualifiedMetadataName("System.ComponentModel.INotifyPropertyChanged"))) + { + diagnostics = ImmutableArray.Create(DiagnosticInfo.Create(DuplicateINotifyPropertyChangedInterfaceForObservableObjectAttributeError, typeSymbol, typeSymbol)); + + goto End; + } + + // ...or INotifyPropertyChanging + if (typeSymbol.AllInterfaces.Any(i => i.HasFullyQualifiedMetadataName("System.ComponentModel.INotifyPropertyChanging"))) + { + diagnostics = ImmutableArray.Create(DiagnosticInfo.Create(DuplicateINotifyPropertyChangingInterfaceForObservableObjectAttributeError, typeSymbol, typeSymbol)); + + goto End; + } + + // Check if the type uses [INotifyPropertyChanged] or [ObservableObject] already (in the type hierarchy too) + if (typeSymbol.InheritsAttributeWithFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableObjectAttribute") || + typeSymbol.HasOrInheritsAttributeWithFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.INotifyPropertyChangedAttribute")) + { + diagnostics = ImmutableArray.Create(DiagnosticInfo.Create(InvalidAttributeCombinationForObservableObjectAttributeError, typeSymbol, typeSymbol)); + + goto End; + } + + End: + return 0; + } + + /// + protected override ImmutableArray FilterDeclaredMembers(int info, ImmutableArray memberDeclarations) + { + return memberDeclarations; + } +} diff --git a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs similarity index 84% rename from CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs rename to src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs index 8d3b6f807..8041977f0 100644 --- a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs @@ -4,11 +4,14 @@ using System.Collections.Immutable; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; +using System.Threading; using CommunityToolkit.Mvvm.SourceGenerators.ComponentModel.Models; -using CommunityToolkit.Mvvm.SourceGenerators.Diagnostics; using CommunityToolkit.Mvvm.SourceGenerators.Extensions; +using CommunityToolkit.Mvvm.SourceGenerators.Helpers; +using CommunityToolkit.Mvvm.SourceGenerators.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -28,12 +31,22 @@ internal static class Execute /// /// Processes a given field. /// + /// The instance to process. /// The input instance to process. + /// The instance for the current run. + /// The cancellation token for the current operation. + /// The resulting value, if successfully retrieved. /// The resulting diagnostics from the processing operation. /// The resulting instance for , if successful. - public static PropertyInfo? TryGetInfo(IFieldSymbol fieldSymbol, out ImmutableArray diagnostics) + public static bool TryGetInfo( + FieldDeclarationSyntax fieldSyntax, + IFieldSymbol fieldSymbol, + SemanticModel semanticModel, + CancellationToken token, + [NotNullWhen(true)] out PropertyInfo? propertyInfo, + out ImmutableArray diagnostics) { - ImmutableArray.Builder builder = ImmutableArray.CreateBuilder(); + using ImmutableArrayBuilder builder = ImmutableArrayBuilder.Rent(); // Validate the target type if (!IsTargetTypeValid(fieldSymbol, out bool shouldInvokeOnPropertyChanging)) @@ -44,9 +57,10 @@ internal static class Execute fieldSymbol.ContainingType, fieldSymbol.Name); + propertyInfo = null; diagnostics = builder.ToImmutable(); - return null; + return false; } // Get the property type and name @@ -63,12 +77,13 @@ internal static class Execute fieldSymbol.ContainingType, fieldSymbol.Name); + propertyInfo = null; diagnostics = builder.ToImmutable(); // If the generated property would collide, skip generating it entirely. This makes sure that // users only get the helpful diagnostic about the collision, and not the normal compiler error // about a definition for "Property" already existing on the target type, which might be confusing. - return null; + return false; } // Check for special cases that are explicitly not allowed @@ -80,15 +95,17 @@ internal static class Execute fieldSymbol.ContainingType, fieldSymbol.Name); + propertyInfo = null; diagnostics = builder.ToImmutable(); - return null; + return false; } - ImmutableArray.Builder propertyChangedNames = ImmutableArray.CreateBuilder(); - ImmutableArray.Builder propertyChangingNames = ImmutableArray.CreateBuilder(); - ImmutableArray.Builder notifiedCommandNames = ImmutableArray.CreateBuilder(); - ImmutableArray.Builder forwardedAttributes = ImmutableArray.CreateBuilder(); + using ImmutableArrayBuilder propertyChangedNames = ImmutableArrayBuilder.Rent(); + using ImmutableArrayBuilder propertyChangingNames = ImmutableArrayBuilder.Rent(); + using ImmutableArrayBuilder notifiedCommandNames = ImmutableArrayBuilder.Rent(); + using ImmutableArrayBuilder forwardedAttributes = ImmutableArrayBuilder.Rent(); + bool notifyRecipients = false; bool notifyDataErrorInfo = false; bool hasOrInheritsClassLevelNotifyPropertyChangedRecipients = false; @@ -122,14 +139,14 @@ internal static class Execute foreach (AttributeData attributeData in fieldSymbol.GetAttributes()) { // Gather dependent property and command names - if (TryGatherDependentPropertyChangedNames(fieldSymbol, attributeData, propertyChangedNames, builder) || - TryGatherDependentCommandNames(fieldSymbol, attributeData, notifiedCommandNames, builder)) + if (TryGatherDependentPropertyChangedNames(fieldSymbol, attributeData, in propertyChangedNames, in builder) || + TryGatherDependentCommandNames(fieldSymbol, attributeData, in notifiedCommandNames, in builder)) { continue; } // Check whether the property should also notify recipients - if (TryGetIsNotifyingRecipients(fieldSymbol, attributeData, builder, hasOrInheritsClassLevelNotifyPropertyChangedRecipients, out isBroadcastTargetValid)) + if (TryGetIsNotifyingRecipients(fieldSymbol, attributeData, in builder, hasOrInheritsClassLevelNotifyPropertyChangedRecipients, out isBroadcastTargetValid)) { notifyRecipients = isBroadcastTargetValid; @@ -137,7 +154,7 @@ internal static class Execute } // Check whether the property should also be validated - if (TryGetNotifyDataErrorInfo(fieldSymbol, attributeData, builder, hasOrInheritsClassLevelNotifyDataErrorInfo, out isValidationTargetValid)) + if (TryGetNotifyDataErrorInfo(fieldSymbol, attributeData, in builder, hasOrInheritsClassLevelNotifyDataErrorInfo, out isValidationTargetValid)) { notifyDataErrorInfo = isValidationTargetValid; @@ -145,7 +162,7 @@ internal static class Execute } // Track the current attribute for forwarding if it is a validation attribute - if (attributeData.AttributeClass?.InheritsFromFullyQualifiedName("global::System.ComponentModel.DataAnnotations.ValidationAttribute") == true) + if (attributeData.AttributeClass?.InheritsFromFullyQualifiedMetadataName("System.ComponentModel.DataAnnotations.ValidationAttribute") == true) { hasAnyValidationAttributes = true; @@ -158,19 +175,66 @@ internal static class Execute // - Scaffold column attributes (System.ComponentModel.DataAnnotations.ScaffoldColumnAttribute) // - Editable attributes (System.ComponentModel.DataAnnotations.EditableAttribute) // - Key attributes (System.ComponentModel.DataAnnotations.KeyAttribute) - if (attributeData.AttributeClass?.HasOrInheritsFromFullyQualifiedName("global::System.ComponentModel.DataAnnotations.UIHintAttribute") == true || - attributeData.AttributeClass?.HasOrInheritsFromFullyQualifiedName("global::System.ComponentModel.DataAnnotations.ScaffoldColumnAttribute") == true || - attributeData.AttributeClass?.HasFullyQualifiedName("global::System.ComponentModel.DataAnnotations.DisplayAttribute") == true || - attributeData.AttributeClass?.HasFullyQualifiedName("global::System.ComponentModel.DataAnnotations.EditableAttribute") == true || - attributeData.AttributeClass?.HasFullyQualifiedName("global::System.ComponentModel.DataAnnotations.KeyAttribute") == true) + if (attributeData.AttributeClass?.HasOrInheritsFromFullyQualifiedMetadataName("System.ComponentModel.DataAnnotations.UIHintAttribute") == true || + attributeData.AttributeClass?.HasOrInheritsFromFullyQualifiedMetadataName("System.ComponentModel.DataAnnotations.ScaffoldColumnAttribute") == true || + attributeData.AttributeClass?.HasFullyQualifiedMetadataName("System.ComponentModel.DataAnnotations.DisplayAttribute") == true || + attributeData.AttributeClass?.HasFullyQualifiedMetadataName("System.ComponentModel.DataAnnotations.EditableAttribute") == true || + attributeData.AttributeClass?.HasFullyQualifiedMetadataName("System.ComponentModel.DataAnnotations.KeyAttribute") == true) { forwardedAttributes.Add(AttributeInfo.From(attributeData)); } } + // Gather explicit forwarded attributes info + foreach (AttributeListSyntax attributeList in fieldSyntax.AttributeLists) + { + // Only look for attribute lists explicitly targeting the (generated) property. Roslyn will normally emit a + // CS0657 warning (invalid target), but that is automatically suppressed by a dedicated diagnostic suppressor + // that recognizes uses of this target specifically to support [ObservableProperty]. + if (attributeList.Target?.Identifier.Kind() is not SyntaxKind.PropertyKeyword) + { + continue; + } + + foreach (AttributeSyntax attribute in attributeList.Attributes) + { + // Roslyn ignores attributes in an attribute list with an invalid target, so we can't get the AttributeData as usual. + // To reconstruct all necessary attribute info to generate the serialized model, we use the following steps: + // - We try to get the attribute symbol from the semantic model, for the current attribute syntax. In case this is not + // available (in theory it shouldn't, but it can be), we try to get it from the candidate symbols list for the node. + // If there are no candidates or more than one, we just issue a diagnostic and stop processing the current attribute. + // The returned symbols might be method symbols (constructor attribute) so in that case we can get the declaring type. + // - We then go over each attribute argument expression and get the operation for it. This will still be available even + // though the rest of the attribute is not validated nor bound at all. From the operation we can still retrieve all + // constant values to build the AttributeInfo model. After all, attributes only support constant values, typeof(T) + // expressions, or arrays of either these two types, or of other arrays with the same rules, recursively. + // - From the syntax, we can also determine the identifier names for named attribute arguments, if any. + // There is no need to validate anything here: the attribute will be forwarded as is, and then Roslyn will validate on the + // generated property. Users will get the same validation they'd have had directly over the field. The only drawback is the + // lack of IntelliSense when constructing attributes over the field, but this is the best we can do from this end anyway. + SymbolInfo attributeSymbolInfo = semanticModel.GetSymbolInfo(attribute, token); + + // Check if the attribute type can be resolved, and emit a diagnostic if that is not the case. This happens if eg. the + // attribute type is spelled incorrectly, or if a user is missing the using directive for the attribute type being used. + if ((attributeSymbolInfo.Symbol ?? attributeSymbolInfo.CandidateSymbols.SingleOrDefault()) is not ISymbol attributeSymbol || + (attributeSymbol as INamedTypeSymbol ?? attributeSymbol.ContainingType) is not INamedTypeSymbol attributeTypeSymbol) + { + builder.Add( + InvalidPropertyTargetedAttributeOnObservablePropertyField, + attribute, + fieldSymbol, + attribute.Name); + + continue; + } + + forwardedAttributes.Add(AttributeInfo.From(attributeTypeSymbol, semanticModel, attribute.ArgumentList?.Arguments ?? Enumerable.Empty(), token)); + } + } + // Log the diagnostic for missing ObservableValidator, if needed if (hasAnyValidationAttributes && - !fieldSymbol.ContainingType.InheritsFromFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableValidator")) + !fieldSymbol.ContainingType.InheritsFromFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableValidator")) { builder.Add( MissingObservableValidatorInheritanceForValidationAttributeError, @@ -190,9 +254,7 @@ internal static class Execute fieldSymbol.Name); } - diagnostics = builder.ToImmutable(); - - return new( + propertyInfo = new PropertyInfo( typeNameWithNullabilityAnnotations, fieldName, propertyName, @@ -202,19 +264,10 @@ internal static class Execute notifyRecipients, notifyDataErrorInfo, forwardedAttributes.ToImmutable()); - } - /// - /// Gets the diagnostics for a field with invalid attribute uses. - /// - /// The input instance to process. - /// The resulting instance for . - public static Diagnostic GetDiagnosticForFieldWithOrphanedDependentAttributes(IFieldSymbol fieldSymbol) - { - return FieldWithOrphanedDependentObservablePropertyAttributesError.CreateDiagnostic( - fieldSymbol, - fieldSymbol.ContainingType, - fieldSymbol.Name); + diagnostics = builder.ToImmutable(); + + return true; } /// @@ -223,18 +276,16 @@ public static Diagnostic GetDiagnosticForFieldWithOrphanedDependentAttributes(IF /// The input instance to process. /// Whether or not property changing events should also be raised. /// Whether or not the containing type for is valid. - private static bool IsTargetTypeValid( - IFieldSymbol fieldSymbol, - out bool shouldInvokeOnPropertyChanging) + private static bool IsTargetTypeValid(IFieldSymbol fieldSymbol, out bool shouldInvokeOnPropertyChanging) { // The [ObservableProperty] attribute can only be used in types that are known to expose the necessary OnPropertyChanged and OnPropertyChanging methods. // That means that the containing type for the field needs to match one of the following conditions: // - It inherits from ObservableObject (in which case it also implements INotifyPropertyChanging). // - It has the [ObservableObject] attribute (on itself or any of its base types). // - It has the [INotifyPropertyChanged] attribute (on itself or any of its base types). - bool isObservableObject = fieldSymbol.ContainingType.InheritsFromFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableObject"); - bool hasObservableObjectAttribute = fieldSymbol.ContainingType.HasOrInheritsAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableObjectAttribute"); - bool hasINotifyPropertyChangedAttribute = fieldSymbol.ContainingType.HasOrInheritsAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.INotifyPropertyChangedAttribute"); + bool isObservableObject = fieldSymbol.ContainingType.InheritsFromFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableObject"); + bool hasObservableObjectAttribute = fieldSymbol.ContainingType.HasOrInheritsAttributeWithFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableObjectAttribute"); + bool hasINotifyPropertyChangedAttribute = fieldSymbol.ContainingType.HasOrInheritsAttributeWithFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.INotifyPropertyChangedAttribute"); shouldInvokeOnPropertyChanging = isObservableObject || hasObservableObjectAttribute; @@ -257,8 +308,8 @@ private static bool IsGeneratedPropertyInvalid(string propertyName, ITypeSymbol { return propertyType.SpecialType == SpecialType.System_Object || - propertyType.HasOrInheritsFromFullyQualifiedName("global::System.ComponentModel.PropertyChangedEventArgs") || - propertyType.HasOrInheritsFromFullyQualifiedName("global::System.ComponentModel.PropertyChangingEventArgs"); + propertyType.HasOrInheritsFromFullyQualifiedMetadataName("System.ComponentModel.PropertyChangedEventArgs") || + propertyType.HasOrInheritsFromFullyQualifiedMetadataName("System.ComponentModel.PropertyChangingEventArgs"); } return false; @@ -275,8 +326,8 @@ private static bool IsGeneratedPropertyInvalid(string propertyName, ITypeSymbol private static bool TryGatherDependentPropertyChangedNames( IFieldSymbol fieldSymbol, AttributeData attributeData, - ImmutableArray.Builder propertyChangedNames, - ImmutableArray.Builder diagnostics) + in ImmutableArrayBuilder propertyChangedNames, + in ImmutableArrayBuilder diagnostics) { // Validates a property name using existing properties bool IsPropertyNameValid(string propertyName) @@ -291,7 +342,7 @@ bool IsPropertyNameValidWithGeneratedMembers(string propertyName) { if (member is IFieldSymbol otherFieldSymbol && !SymbolEqualityComparer.Default.Equals(fieldSymbol, otherFieldSymbol) && - otherFieldSymbol.HasAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute") && + otherFieldSymbol.HasAttributeWithFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute") && propertyName == GetGeneratedPropertyName(otherFieldSymbol)) { return true; @@ -301,7 +352,7 @@ bool IsPropertyNameValidWithGeneratedMembers(string propertyName) return false; } - if (attributeData.AttributeClass?.HasFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.NotifyPropertyChangedForAttribute") == true) + if (attributeData.AttributeClass?.HasFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.NotifyPropertyChangedForAttribute") == true) { foreach (string? dependentPropertyName in attributeData.GetConstructorArguments()) { @@ -340,8 +391,8 @@ bool IsPropertyNameValidWithGeneratedMembers(string propertyName) private static bool TryGatherDependentCommandNames( IFieldSymbol fieldSymbol, AttributeData attributeData, - ImmutableArray.Builder notifiedCommandNames, - ImmutableArray.Builder diagnostics) + in ImmutableArrayBuilder notifiedCommandNames, + in ImmutableArrayBuilder diagnostics) { // Validates a command name using existing properties bool IsCommandNameValid(string commandName, out bool shouldLookForGeneratedMembersToo) @@ -354,8 +405,8 @@ bool IsCommandNameValid(string commandName, out bool shouldLookForGeneratedMembe // target is definitely not valid, and the additional checks below can just be skipped. The property // is valid if it's of type IRelayCommand, or it has IRelayCommand in the set of all interfaces. if (propertySymbol.Type is INamedTypeSymbol typeSymbol && - (typeSymbol.HasFullyQualifiedName("global::CommunityToolkit.Mvvm.Input.IRelayCommand") || - typeSymbol.HasInterfaceWithFullyQualifiedName("global::CommunityToolkit.Mvvm.Input.IRelayCommand"))) + (typeSymbol.HasFullyQualifiedMetadataName("CommunityToolkit.Mvvm.Input.IRelayCommand") || + typeSymbol.HasInterfaceWithFullyQualifiedMetadataName("CommunityToolkit.Mvvm.Input.IRelayCommand"))) { shouldLookForGeneratedMembersToo = true; @@ -380,7 +431,7 @@ bool IsCommandNameValidWithGeneratedMembers(string commandName) foreach (ISymbol member in fieldSymbol.ContainingType.GetAllMembers()) { if (member is IMethodSymbol methodSymbol && - methodSymbol.HasAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.Input.RelayCommandAttribute") && + methodSymbol.HasAttributeWithFullyQualifiedMetadataName("CommunityToolkit.Mvvm.Input.RelayCommandAttribute") && commandName == RelayCommandGenerator.Execute.GetGeneratedFieldAndPropertyNames(methodSymbol).PropertyName) { return true; @@ -390,7 +441,7 @@ bool IsCommandNameValidWithGeneratedMembers(string commandName) return false; } - if (attributeData.AttributeClass?.HasFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.NotifyCanExecuteChangedForAttribute") == true) + if (attributeData.AttributeClass?.HasFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.NotifyCanExecuteChangedForAttribute") == true) { foreach (string? commandName in attributeData.GetConstructorArguments()) { @@ -427,11 +478,11 @@ bool IsCommandNameValidWithGeneratedMembers(string commandName) /// Whether or not the generated property for is in a type annotated with [NotifyPropertyChangedRecipients]. private static bool TryGetIsNotifyingRecipients(IFieldSymbol fieldSymbol, out bool isBroadcastTargetValid) { - if (fieldSymbol.ContainingType?.HasOrInheritsAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.NotifyPropertyChangedRecipientsAttribute") == true) + if (fieldSymbol.ContainingType?.HasOrInheritsAttributeWithFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.NotifyPropertyChangedRecipientsAttribute") == true) { // If the containing type is valid, track it - if (fieldSymbol.ContainingType.InheritsFromFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableRecipient") || - fieldSymbol.ContainingType.HasOrInheritsAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableRecipientAttribute")) + if (fieldSymbol.ContainingType.InheritsFromFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableRecipient") || + fieldSymbol.ContainingType.HasOrInheritsAttributeWithFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableRecipientAttribute")) { isBroadcastTargetValid = true; @@ -462,11 +513,11 @@ private static bool TryGetIsNotifyingRecipients(IFieldSymbol fieldSymbol, out bo private static bool TryGetIsNotifyingRecipients( IFieldSymbol fieldSymbol, AttributeData attributeData, - ImmutableArray.Builder diagnostics, + in ImmutableArrayBuilder diagnostics, bool hasOrInheritsClassLevelNotifyPropertyChangedRecipients, out bool isBroadcastTargetValid) { - if (attributeData.AttributeClass?.HasFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.NotifyPropertyChangedRecipientsAttribute") == true) + if (attributeData.AttributeClass?.HasFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.NotifyPropertyChangedRecipientsAttribute") == true) { // Emit a diagnostic if the attribute is unnecessary if (hasOrInheritsClassLevelNotifyPropertyChangedRecipients) @@ -479,8 +530,8 @@ private static bool TryGetIsNotifyingRecipients( } // If the containing type is valid, track it - if (fieldSymbol.ContainingType.InheritsFromFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableRecipient") || - fieldSymbol.ContainingType.HasOrInheritsAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableRecipientAttribute")) + if (fieldSymbol.ContainingType.InheritsFromFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableRecipient") || + fieldSymbol.ContainingType.HasOrInheritsAttributeWithFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableRecipientAttribute")) { isBroadcastTargetValid = true; @@ -512,8 +563,8 @@ private static bool TryGetIsNotifyingRecipients( public static Diagnostic? GetIsNotifyingRecipientsDiagnosticForType(INamedTypeSymbol typeSymbol) { // If the containing type is valid, track it - if (!typeSymbol.InheritsFromFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableRecipient") && - !typeSymbol.HasOrInheritsAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableRecipientAttribute")) + if (!typeSymbol.InheritsFromFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableRecipient") && + !typeSymbol.HasOrInheritsAttributeWithFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableRecipientAttribute")) { return Diagnostic.Create( InvalidTypeForNotifyPropertyChangedRecipientsError, @@ -532,10 +583,10 @@ private static bool TryGetIsNotifyingRecipients( /// Whether or not the generated property for used [NotifyDataErrorInfo]. private static bool TryGetNotifyDataErrorInfo(IFieldSymbol fieldSymbol, out bool isValidationTargetValid) { - if (fieldSymbol.ContainingType?.HasOrInheritsAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.NotifyDataErrorInfoAttribute") == true) + if (fieldSymbol.ContainingType?.HasOrInheritsAttributeWithFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.NotifyDataErrorInfoAttribute") == true) { // If the containing type is valid, track it - if (fieldSymbol.ContainingType.InheritsFromFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableValidator")) + if (fieldSymbol.ContainingType.InheritsFromFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableValidator")) { isValidationTargetValid = true; @@ -565,11 +616,11 @@ private static bool TryGetNotifyDataErrorInfo(IFieldSymbol fieldSymbol, out bool private static bool TryGetNotifyDataErrorInfo( IFieldSymbol fieldSymbol, AttributeData attributeData, - ImmutableArray.Builder diagnostics, + in ImmutableArrayBuilder diagnostics, bool hasOrInheritsClassLevelNotifyDataErrorInfo, out bool isValidationTargetValid) { - if (attributeData.AttributeClass?.HasFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.NotifyDataErrorInfoAttribute") == true) + if (attributeData.AttributeClass?.HasFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.NotifyDataErrorInfoAttribute") == true) { // Emit a diagnostic if the attribute is unnecessary if (hasOrInheritsClassLevelNotifyDataErrorInfo) @@ -582,7 +633,7 @@ private static bool TryGetNotifyDataErrorInfo( } // If the containing type is valid, track it - if (fieldSymbol.ContainingType.InheritsFromFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableValidator")) + if (fieldSymbol.ContainingType.InheritsFromFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableValidator")) { isValidationTargetValid = true; @@ -614,7 +665,7 @@ private static bool TryGetNotifyDataErrorInfo( public static Diagnostic? GetIsNotifyDataErrorInfoDiagnosticForType(INamedTypeSymbol typeSymbol) { // If the containing type is valid, track it - if (!typeSymbol.InheritsFromFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableValidator")) + if (!typeSymbol.InheritsFromFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableValidator")) { return Diagnostic.Create( InvalidTypeForNotifyDataErrorInfoError, @@ -658,7 +709,7 @@ private static bool TryGetNotifyDataErrorInfo( /// The generated instance for . public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInfo) { - ImmutableArray.Builder setterStatements = ImmutableArray.CreateBuilder(); + using ImmutableArrayBuilder setterStatements = ImmutableArrayBuilder.Rent(); // Get the property type syntax TypeSyntax propertyType = IdentifierName(propertyInfo.TypeNameWithNullabilityAnnotations); @@ -805,7 +856,7 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf .AddArgumentListArguments( Argument(fieldExpression), Argument(IdentifierName("value")))), - Block(setterStatements)); + Block(setterStatements.ToArray())); // Prepare the forwarded attributes, if any ImmutableArray forwardedAttributes = @@ -978,7 +1029,7 @@ private static FieldDeclarationSyntax CreateFieldDeclaration(string typeName, st .AddVariables( VariableDeclarator(Identifier(propertyName)) .WithInitializer(EqualsValueClause( - ImplicitObjectCreationExpression() + ObjectCreationExpression(IdentifierName(typeName)) .AddArgumentListArguments(Argument( LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(propertyName)))))))) .AddModifiers( diff --git a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.cs similarity index 56% rename from CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.cs rename to src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.cs index 413664ef6..6dd813276 100644 --- a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.cs @@ -2,17 +2,16 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Text; using CommunityToolkit.Mvvm.SourceGenerators.ComponentModel.Models; using CommunityToolkit.Mvvm.SourceGenerators.Extensions; +using CommunityToolkit.Mvvm.SourceGenerators.Helpers; using CommunityToolkit.Mvvm.SourceGenerators.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors; namespace CommunityToolkit.Mvvm.SourceGenerators; @@ -25,62 +24,43 @@ public sealed partial class ObservablePropertyGenerator : IIncrementalGenerator /// public void Initialize(IncrementalGeneratorInitializationContext context) { - // Get all field declarations with at least one attribute - IncrementalValuesProvider fieldSymbols = + // Gather info for all annotated fields + IncrementalValuesProvider<(HierarchyInfo Hierarchy, Result Info)> propertyInfoWithErrors = context.SyntaxProvider - .CreateSyntaxProvider( - static (node, _) => node is FieldDeclarationSyntax { Parent: ClassDeclarationSyntax or RecordDeclarationSyntax, AttributeLists.Count: > 0 }, - static (context, _) => ((FieldDeclarationSyntax)context.Node).Declaration.Variables.Select(v => (IFieldSymbol)context.SemanticModel.GetDeclaredSymbol(v)!)) - .SelectMany(static (item, _) => item); - - // Filter the fields using [ObservableProperty] - IncrementalValuesProvider fieldSymbolsWithAttribute = - fieldSymbols - .Where(static item => item.HasAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute")); - - // Get diagnostics for fields using [NotifyPropertyChangedFor], [NotifyCanExecuteChangedFor], [NotifyPropertyChangedRecipients] and [NotifyDataErrorInfo], but not [ObservableProperty] - IncrementalValuesProvider fieldSymbolsWithOrphanedDependentAttributeWithErrors = - fieldSymbols - .Where(static item => - (item.HasAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.NotifyPropertyChangedForAttribute") || - item.HasAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.NotifyCanExecuteChangedForAttribute") || - item.HasAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.NotifyPropertyChangedRecipientsAttribute") || - item.HasAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.NotifyDataErrorInfoAttribute")) && - !item.HasAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute")) - .Select(static (item, _) => Execute.GetDiagnosticForFieldWithOrphanedDependentAttributes(item)); + .ForAttributeWithMetadataName( + "CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute", + static (node, _) => node is VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Parent: FieldDeclarationSyntax { Parent: ClassDeclarationSyntax or RecordDeclarationSyntax, AttributeLists.Count: > 0 } } }, + static (context, token) => + { + if (!context.SemanticModel.Compilation.HasLanguageVersionAtLeastEqualTo(LanguageVersion.CSharp8)) + { + return default; + } - // Output the diagnostics - context.ReportDiagnostics(fieldSymbolsWithOrphanedDependentAttributeWithErrors); + FieldDeclarationSyntax fieldDeclaration = (FieldDeclarationSyntax)context.TargetNode.Parent!.Parent!; + IFieldSymbol fieldSymbol = (IFieldSymbol)context.TargetSymbol; - // Filter by language version - context.FilterWithLanguageVersion(ref fieldSymbolsWithAttribute, LanguageVersion.CSharp8, UnsupportedCSharpLanguageVersionError); + // Get the hierarchy info for the target symbol, and try to gather the property info + HierarchyInfo hierarchy = HierarchyInfo.From(fieldSymbol.ContainingType); - // Gather info for all annotated fields - IncrementalValuesProvider<(HierarchyInfo Hierarchy, Result Info)> propertyInfoWithErrors = - fieldSymbolsWithAttribute - .Select(static (item, _) => - { - HierarchyInfo hierarchy = HierarchyInfo.From(item.ContainingType); - PropertyInfo? propertyInfo = Execute.TryGetInfo(item, out ImmutableArray diagnostics); + _ = Execute.TryGetInfo(fieldDeclaration, fieldSymbol, context.SemanticModel, token, out PropertyInfo? propertyInfo, out ImmutableArray diagnostics); - return (hierarchy, new Result(propertyInfo, diagnostics)); - }); + return (Hierarchy: hierarchy, new Result(propertyInfo, diagnostics)); + }) + .Where(static item => item.Hierarchy is not null); // Output the diagnostics context.ReportDiagnostics(propertyInfoWithErrors.Select(static (item, _) => item.Info.Errors)); // Get the filtered sequence to enable caching - IncrementalValuesProvider<(HierarchyInfo Hierarchy, PropertyInfo Info)> propertyInfo = + IncrementalValuesProvider<(HierarchyInfo Hierarchy, Result Info)> propertyInfo = propertyInfoWithErrors - .Select(static (item, _) => (item.Hierarchy, Info: item.Info.Value)) - .Where(static item => item.Info is not null)! - .WithComparers(HierarchyInfo.Comparer.Default, PropertyInfo.Comparer.Default); + .Where(static item => item.Info.Value is not null)!; // Split and group by containing type - IncrementalValuesProvider<(HierarchyInfo Hierarchy, ImmutableArray Properties)> groupedPropertyInfo = + IncrementalValuesProvider<(HierarchyInfo Hierarchy, EquatableArray Properties)> groupedPropertyInfo = propertyInfo - .GroupBy(HierarchyInfo.Comparer.Default) - .WithComparers(HierarchyInfo.Comparer.Default, PropertyInfo.Comparer.Default.ForImmutableArray()); + .GroupBy(static item => item.Left, static item => item.Right.Value); // Generate the requested properties and methods context.RegisterSourceOutput(groupedPropertyInfo, static (context, item) => @@ -99,12 +79,11 @@ public void Initialize(IncrementalGeneratorInitializationContext context) }); // Gather all property changing names - IncrementalValueProvider> propertyChangingNames = + IncrementalValueProvider> propertyChangingNames = propertyInfo - .SelectMany(static (item, _) => item.Info.PropertyChangingNames) + .SelectMany(static (item, _) => item.Info.Value.PropertyChangingNames) .Collect() - .Select(static (item, _) => item.Distinct().ToImmutableArray()) - .WithComparer(EqualityComparer.Default.ForImmutableArray()); + .Select(static (item, _) => item.Distinct().ToImmutableArray().AsEquatableArray()); // Generate the cached property changing names context.RegisterSourceOutput(propertyChangingNames, static (context, item) => @@ -118,12 +97,11 @@ public void Initialize(IncrementalGeneratorInitializationContext context) }); // Gather all property changed names - IncrementalValueProvider> propertyChangedNames = + IncrementalValueProvider> propertyChangedNames = propertyInfo - .SelectMany(static (item, _) => item.Info.PropertyChangedNames) + .SelectMany(static (item, _) => item.Info.Value.PropertyChangedNames) .Collect() - .Select(static (item, _) => item.Distinct().ToImmutableArray()) - .WithComparer(EqualityComparer.Default.ForImmutableArray()); + .Select(static (item, _) => item.Distinct().ToImmutableArray().AsEquatableArray()); // Generate the cached property changed names context.RegisterSourceOutput(propertyChangedNames, static (context, item) => @@ -146,7 +124,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) // Filter only the type symbols with [NotifyPropertyChangedRecipients] and create diagnostics for them IncrementalValuesProvider notifyRecipientsErrors = classSymbols - .Where(static item => item.HasAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.NotifyPropertyChangedRecipientsAttribute")) + .Where(static item => item.HasAttributeWithFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.NotifyPropertyChangedRecipientsAttribute")) .Select(static (item, _) => Execute.GetIsNotifyingRecipientsDiagnosticForType(item)) .Where(static item => item is not null)!; @@ -156,7 +134,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) // Filter only the type symbols with [NotifyDataErrorInfo] and create diagnostics for them IncrementalValuesProvider notifyDataErrorInfoErrors = classSymbols - .Where(static item => item.HasAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.NotifyDataErrorInfoAttribute")) + .Where(static item => item.HasAttributeWithFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.NotifyDataErrorInfoAttribute")) .Select(static (item, _) => Execute.GetIsNotifyDataErrorInfoDiagnosticForType(item)) .Where(static item => item is not null)!; diff --git a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableRecipientGenerator.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableRecipientGenerator.cs similarity index 63% rename from CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableRecipientGenerator.cs rename to src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableRecipientGenerator.cs index e16a161d9..359fef78c 100644 --- a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableRecipientGenerator.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableRecipientGenerator.cs @@ -5,8 +5,9 @@ using System.Collections.Immutable; using System.Linq; using CommunityToolkit.Mvvm.SourceGenerators.ComponentModel.Models; -using CommunityToolkit.Mvvm.SourceGenerators.Diagnostics; using CommunityToolkit.Mvvm.SourceGenerators.Extensions; +using CommunityToolkit.Mvvm.SourceGenerators.Helpers; +using CommunityToolkit.Mvvm.SourceGenerators.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -25,94 +26,72 @@ public sealed class ObservableRecipientGenerator : TransitiveMembersGenerator class. /// public ObservableRecipientGenerator() - : base("global::CommunityToolkit.Mvvm.ComponentModel.ObservableRecipientAttribute") + : base("CommunityToolkit.Mvvm.ComponentModel.ObservableRecipientAttribute") { } /// - protected override IncrementalValuesProvider<(INamedTypeSymbol Symbol, ObservableRecipientInfo Info)> GetInfo( - IncrementalGeneratorInitializationContext context, - IncrementalValuesProvider<(INamedTypeSymbol Symbol, AttributeData AttributeData)> source) + private protected override ObservableRecipientInfo? ValidateTargetTypeAndGetInfo(INamedTypeSymbol typeSymbol, AttributeData attributeData, Compilation compilation, out ImmutableArray diagnostics) { - static ObservableRecipientInfo GetInfo(INamedTypeSymbol typeSymbol, AttributeData attributeData, bool isRequiresUnreferencedCodeAttributeAvailable) - { - string typeName = typeSymbol.Name; - bool hasExplicitConstructors = !(typeSymbol.InstanceConstructors.Length == 1 && typeSymbol.InstanceConstructors[0] is { Parameters.IsEmpty: true, IsImplicitlyDeclared: true }); - bool isAbstract = typeSymbol.IsAbstract; - bool isObservableValidator = typeSymbol.InheritsFromFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableValidator"); - bool hasOnActivatedMethod = typeSymbol.GetMembers().Any(m => m is IMethodSymbol { Parameters.IsEmpty: true, Name: "OnActivated" }); - bool hasOnDeactivatedMethod = typeSymbol.GetMembers().Any(m => m is IMethodSymbol { Parameters.IsEmpty: true, Name: "OnDeactivated" }); - - return new( - typeName, - hasExplicitConstructors, - isAbstract, - isObservableValidator, - isRequiresUnreferencedCodeAttributeAvailable, - hasOnActivatedMethod, - hasOnDeactivatedMethod); - } - - // Check whether [RequiresUnreferencedCode] is available - IncrementalValueProvider isRequiresUnreferencedCodeAttributeAvailable = - context.CompilationProvider - .Select(static (item, _) => item.GetTypeByMetadataName("System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute") is { DeclaredAccessibility: Accessibility.Public }); + diagnostics = ImmutableArray.Empty; - return - source - .Combine(isRequiresUnreferencedCodeAttributeAvailable) - .Select(static (item, _) => (item.Left.Symbol, GetInfo(item.Left.Symbol, item.Left.AttributeData, item.Right))); - } - - /// - protected override bool ValidateTargetType(INamedTypeSymbol typeSymbol, ObservableRecipientInfo info, out ImmutableArray diagnostics) - { - ImmutableArray.Builder builder = ImmutableArray.CreateBuilder(); + ObservableRecipientInfo? info = null; // Check if the type already inherits from ObservableRecipient - if (typeSymbol.InheritsFromFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableRecipient")) + if (typeSymbol.InheritsFromFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableRecipient")) { - builder.Add(DuplicateObservableRecipientError, typeSymbol, typeSymbol); - - diagnostics = builder.ToImmutable(); + diagnostics = ImmutableArray.Create(DiagnosticInfo.Create(DuplicateObservableRecipientError, typeSymbol, typeSymbol)); - return false; + goto End; } // Check if the type already inherits [ObservableRecipient] - if (typeSymbol.InheritsAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableRecipientAttribute")) + if (typeSymbol.InheritsAttributeWithFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableRecipientAttribute")) { - builder.Add(InvalidAttributeCombinationForObservableRecipientAttributeError, typeSymbol, typeSymbol); - - diagnostics = builder.ToImmutable(); + diagnostics = ImmutableArray.Create(DiagnosticInfo.Create(InvalidAttributeCombinationForObservableRecipientAttributeError, typeSymbol, typeSymbol)); - return false; + goto End; } // In order to use [ObservableRecipient], the target type needs to inherit from ObservableObject, // or be annotated with [ObservableObject] or [INotifyPropertyChanged] (with additional helpers). - if (!typeSymbol.InheritsFromFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableObject") && - !typeSymbol.HasOrInheritsAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableObjectAttribute") && + if (!typeSymbol.InheritsFromFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableObject") && + !typeSymbol.HasOrInheritsAttributeWithFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableObjectAttribute") && !typeSymbol.HasOrInheritsAttribute(static a => - a.AttributeClass?.HasFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.INotifyPropertyChangedAttribute") == true && + a.AttributeClass?.HasFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.INotifyPropertyChangedAttribute") == true && !a.HasNamedArgument("IncludeAdditionalHelperMethods", false))) { - builder.Add(MissingBaseObservableObjectFunctionalityError, typeSymbol, typeSymbol); + diagnostics = ImmutableArray.Create(DiagnosticInfo.Create(MissingBaseObservableObjectFunctionalityError, typeSymbol, typeSymbol)); - diagnostics = builder.ToImmutable(); - - return false; + goto End; } - diagnostics = builder.ToImmutable(); - - return true; + // Gather all necessary info to propagate down the pipeline + string typeName = typeSymbol.Name; + bool hasExplicitConstructors = !(typeSymbol.InstanceConstructors.Length == 1 && typeSymbol.InstanceConstructors[0] is { Parameters.IsEmpty: true, IsImplicitlyDeclared: true }); + bool isAbstract = typeSymbol.IsAbstract; + bool isObservableValidator = typeSymbol.InheritsFromFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableValidator"); + bool isRequiresUnreferencedCodeAttributeAvailable = compilation.HasAccessibleTypeWithMetadataName("System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute"); + bool hasOnActivatedMethod = typeSymbol.GetMembers().Any(m => m is IMethodSymbol { Parameters.IsEmpty: true, Name: "OnActivated" }); + bool hasOnDeactivatedMethod = typeSymbol.GetMembers().Any(m => m is IMethodSymbol { Parameters.IsEmpty: true, Name: "OnDeactivated" }); + + info = new ObservableRecipientInfo( + typeName, + hasExplicitConstructors, + isAbstract, + isObservableValidator, + isRequiresUnreferencedCodeAttributeAvailable, + hasOnActivatedMethod, + hasOnDeactivatedMethod); + + End: + return info; } /// protected override ImmutableArray FilterDeclaredMembers(ObservableRecipientInfo info, ImmutableArray memberDeclarations) { - ImmutableArray.Builder builder = ImmutableArray.CreateBuilder(); + using ImmutableArrayBuilder builder = ImmutableArrayBuilder.Rent(); // If the target type has no constructors, generate constructors as well if (!info.HasExplicitConstructors) diff --git a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableValidatorValidateAllPropertiesGenerator.Execute.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableValidatorValidateAllPropertiesGenerator.Execute.cs similarity index 88% rename from CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableValidatorValidateAllPropertiesGenerator.Execute.cs rename to src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableValidatorValidateAllPropertiesGenerator.Execute.cs index 52d91a39f..fb85b1836 100644 --- a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableValidatorValidateAllPropertiesGenerator.Execute.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableValidatorValidateAllPropertiesGenerator.Execute.cs @@ -5,8 +5,9 @@ using System; using System.Collections.Immutable; using System.Linq; +using CommunityToolkit.Mvvm.SourceGenerators.ComponentModel.Models; using CommunityToolkit.Mvvm.SourceGenerators.Extensions; -using CommunityToolkit.Mvvm.SourceGenerators.Input.Models; +using CommunityToolkit.Mvvm.SourceGenerators.Helpers; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -29,7 +30,7 @@ private static class Execute /// Whether inherits from ObservableValidator. public static bool IsObservableValidator(INamedTypeSymbol typeSymbol) { - return typeSymbol.InheritsFromFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableValidator"); + return typeSymbol.InheritsFromFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableValidator"); } /// @@ -39,7 +40,7 @@ public static bool IsObservableValidator(INamedTypeSymbol typeSymbol) /// The resulting instance for . public static ValidationInfo GetInfo(INamedTypeSymbol typeSymbol) { - ImmutableArray.Builder propertyNames = ImmutableArray.CreateBuilder(); + using ImmutableArrayBuilder propertyNames = ImmutableArrayBuilder.Rent(); foreach (ISymbol memberSymbol in typeSymbol.GetMembers()) { @@ -54,15 +55,15 @@ public static ValidationInfo GetInfo(INamedTypeSymbol typeSymbol) // all generators run in an undefined order and looking at the same original compilation, so the // current one wouldn't be able to see generated properties from other generators directly. if (memberSymbol is IFieldSymbol && - !attributes.Any(static a => a.AttributeClass?.HasFullyQualifiedName( - "global::CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute") == true)) + !attributes.Any(static a => a.AttributeClass?.HasFullyQualifiedMetadataName( + "CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute") == true)) { continue; } // Skip the current member if there are no validation attributes applied to it - if (!attributes.Any(a => a.AttributeClass?.InheritsFromFullyQualifiedName( - "global::System.ComponentModel.DataAnnotations.ValidationAttribute") == true)) + if (!attributes.Any(a => a.AttributeClass?.InheritsFromFullyQualifiedMetadataName( + "System.ComponentModel.DataAnnotations.ValidationAttribute") == true)) { continue; } @@ -79,32 +80,11 @@ public static ValidationInfo GetInfo(INamedTypeSymbol typeSymbol) } return new( - typeSymbol.GetFullMetadataNameForFileName(), + typeSymbol.GetFullyQualifiedMetadataName(), typeSymbol.GetFullyQualifiedName(), propertyNames.ToImmutable()); } - /// - /// Gets the instance from the given info. - /// - /// The type symbol for the target type being inspected. - /// The input array of interface type symbols being handled. - /// A instance for the current type being inspected. - public static RecipientInfo GetInfo(INamedTypeSymbol typeSymbol, ImmutableArray interfaceSymbols) - { - ImmutableArray.Builder names = ImmutableArray.CreateBuilder(interfaceSymbols.Length); - - foreach (INamedTypeSymbol interfaceSymbol in interfaceSymbols) - { - names.Add(interfaceSymbol.TypeArguments[0].GetFullyQualifiedName()); - } - - return new( - typeSymbol.GetFullMetadataNameForFileName(), - typeSymbol.GetFullyQualifiedName(), - names.MoveToImmutable()); - } - /// /// Gets the head instance. /// @@ -112,8 +92,7 @@ public static RecipientInfo GetInfo(INamedTypeSymbol typeSymbol, ImmutableArray< /// The head instance with the type attributes. public static CompilationUnitSyntax GetSyntax(bool isDynamicallyAccessedMembersAttributeAvailable) { - int numberOfAttributes = 5 + (isDynamicallyAccessedMembersAttributeAvailable ? 1 : 0); - ImmutableArray.Builder attributes = ImmutableArray.CreateBuilder(numberOfAttributes); + using ImmutableArrayBuilder attributes = ImmutableArrayBuilder.Rent(); // Prepare the base attributes with are always present: // @@ -171,7 +150,7 @@ public static CompilationUnitSyntax GetSyntax(bool isDynamicallyAccessedMembersA Token(SyntaxKind.InternalKeyword), Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.PartialKeyword)) - .AddAttributeLists(attributes.MoveToImmutable().ToArray()))) + .AddAttributeLists(attributes.ToArray()))) .NormalizeWhitespace(); } @@ -261,7 +240,7 @@ public static CompilationUnitSyntax GetSyntax(ValidationInfo validationInfo) /// The sequence of instances to validate declared properties. private static ImmutableArray EnumerateValidationStatements(ValidationInfo validationInfo) { - ImmutableArray.Builder statements = ImmutableArray.CreateBuilder(validationInfo.PropertyNames.Length); + using ImmutableArrayBuilder statements = ImmutableArrayBuilder.Rent(); // This loop produces a sequence of statements as follows: // @@ -294,7 +273,7 @@ private static ImmutableArray EnumerateValidationStatements(Val IdentifierName(propertyName)))))))); } - return statements.MoveToImmutable(); + return statements.ToImmutable(); } } } diff --git a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableValidatorValidateAllPropertiesGenerator.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableValidatorValidateAllPropertiesGenerator.cs similarity index 60% rename from CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableValidatorValidateAllPropertiesGenerator.cs rename to src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableValidatorValidateAllPropertiesGenerator.cs index 42029748b..66ddec019 100644 --- a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableValidatorValidateAllPropertiesGenerator.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableValidatorValidateAllPropertiesGenerator.cs @@ -4,8 +4,8 @@ using System.Linq; using System.Text; +using CommunityToolkit.Mvvm.SourceGenerators.ComponentModel.Models; using CommunityToolkit.Mvvm.SourceGenerators.Extensions; -using CommunityToolkit.Mvvm.SourceGenerators.Input.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -21,23 +21,43 @@ public sealed partial class ObservableValidatorValidateAllPropertiesGenerator : /// public void Initialize(IncrementalGeneratorInitializationContext context) { - // Get all class declarations. We intentionally skip generating code for abstract types, as that would never be used. - // The methods that are generated by this generator are retrieved through reflection using the type of the invoking - // instance as discriminator, which means a type that is abstract could never be used (since it couldn't be instantiated). - IncrementalValuesProvider typeSymbols = + // Get the types that inherit from ObservableValidator and gather their info + IncrementalValuesProvider validationInfo = context.SyntaxProvider .CreateSyntaxProvider( - static (node, _) => node is ClassDeclarationSyntax, - static (context, _) => (context.Node, Symbol: (INamedTypeSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node)!)) - .Where(static item => item.Symbol is { IsAbstract: false, IsGenericType: false } && item.Node.IsFirstSyntaxDeclarationForSymbol(item.Symbol)) - .Select(static (item, _) => item.Symbol); + static (node, _) => node is ClassDeclarationSyntax classDeclaration && classDeclaration.HasOrPotentiallyHasBaseTypes(), + static (context, token) => + { + if (!context.SemanticModel.Compilation.HasLanguageVersionAtLeastEqualTo(LanguageVersion.CSharp8)) + { + return default; + } - // Get the types that inherit from ObservableValidator and gather their info - IncrementalValuesProvider validationInfo = - typeSymbols - .Where(Execute.IsObservableValidator) - .Select(static (item, _) => Execute.GetInfo(item)) - .WithComparer(ValidationInfo.Comparer.Default); + INamedTypeSymbol typeSymbol = (INamedTypeSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node, token)!; + + // Skip generating code for abstract types, as that would never be used. The methods that are generated by + // this generator are retrieved through reflection using the type of the invoking instance as discriminator, + // which means a type that is abstract could never be used (since it couldn't be instantiated). + if (typeSymbol is not { IsAbstract: false, IsGenericType: false }) + { + return default; + } + + // Just like in IMessengerRegisterAllGenerator, only select the first declaration for this type symbol + if (!context.Node.IsFirstSyntaxDeclarationForSymbol(typeSymbol)) + { + return default; + } + + // Only select types inheriting from ObservableValidator + if (!Execute.IsObservableValidator(typeSymbol)) + { + return default; + } + + return Execute.GetInfo(typeSymbol); + }) + .Where(static item => item is not null)!; // Check whether the header file is needed IncrementalValueProvider isHeaderFileNeeded = diff --git a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/TransitiveMembersGenerator.Execute.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/TransitiveMembersGenerator.Execute.cs similarity index 52% rename from CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/TransitiveMembersGenerator.Execute.cs rename to src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/TransitiveMembersGenerator.Execute.cs index ba20b98e5..b074346dd 100644 --- a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/TransitiveMembersGenerator.Execute.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/TransitiveMembersGenerator.Execute.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Reflection; using CommunityToolkit.Mvvm.SourceGenerators.Extensions; +using CommunityToolkit.Mvvm.SourceGenerators.Helpers; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -23,6 +24,18 @@ partial class TransitiveMembersGenerator /// internal static class Execute { + /// + /// Checks whether or not nullability attributes are currently available. + /// + /// The input instance. + /// Whether or not nullability attributes are currently available. + public static bool IsNullabilitySupported(Compilation compilation) + { + return + compilation.HasAccessibleTypeWithMetadataName("System.Diagnostics.CodeAnalysis.NotNullAttribute") && + compilation.HasAccessibleTypeWithMetadataName("System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute"); + } + /// /// Loads the source instance to get member declarations from. /// @@ -31,7 +44,7 @@ internal static class Execute public static ClassDeclarationSyntax LoadClassDeclaration(string attributeType) { string attributeTypeName = attributeType.Split('.').Last(); - string filename = $"CommunityToolkit.Mvvm.SourceGenerators.EmbeddedResources.{attributeTypeName.Replace("Attribute", string.Empty)}.cs"; + string filename = $"{attributeTypeName.Replace("Attribute", string.Empty)}.cs"; using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(filename); using StreamReader reader = new(stream); @@ -68,8 +81,14 @@ public static void ProcessMemberDeclarations( AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(generatorType.Assembly.GetName().Version.ToString()))))))) .WithLeadingTrivia(member.GetLeadingTrivia()); + // [DebuggerNonUserCode] is not supported on interfaces, fields and event + if (member.Kind() is not (SyntaxKind.InterfaceDeclaration or SyntaxKind.FieldDeclaration or SyntaxKind.EventFieldDeclaration)) + { + member = member.AddAttributeLists(AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.DebuggerNonUserCode"))))); + } + // [ExcludeFromCodeCoverage] is not supported on interfaces and fields - if (member.Kind() is not SyntaxKind.InterfaceDeclaration and not SyntaxKind.FieldDeclaration) + if (member.Kind() is not (SyntaxKind.InterfaceDeclaration or SyntaxKind.FieldDeclaration)) { member = member.AddAttributeLists(AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage"))))); } @@ -95,5 +114,67 @@ public static void ProcessMemberDeclarations( nonSealedMemberDeclarations = annotatedMemberDeclarations; } + + /// + /// Adjusts the nullability annotations for generated members, dropping attributes if needed. + /// + /// The input sequence of member declarations to generate. + /// Whether nullability attributes are supported. + /// The updated collection of member declarations to generate. + public static ImmutableArray AdjustMemberDeclarationNullabilityAnnotations( + ImmutableArray memberDeclarations, + bool isNullabilitySupported) + { + // If nullability attributes are supported, there is nothing else to do + if (isNullabilitySupported) + { + return memberDeclarations; + } + + using ImmutableArrayBuilder builder = ImmutableArrayBuilder.Rent(); + + NullabilityAdjustmentSyntaxRewriter syntaxRewriter = new(); + + // Iterate over all members and adjust the method declarations, if needed + foreach (MemberDeclarationSyntax memberDeclaration in memberDeclarations) + { + if (memberDeclaration is MethodDeclarationSyntax methodDeclaration) + { + builder.Add((MethodDeclarationSyntax)syntaxRewriter.Visit(methodDeclaration)); + } + else + { + builder.Add(memberDeclaration); + } + } + + return builder.ToImmutable(); + } + + /// + /// A custom syntax rewriter that removes nullability attributes from method parameters. + /// + private sealed class NullabilityAdjustmentSyntaxRewriter : CSharpSyntaxRewriter + { + /// + public override SyntaxNode? VisitParameter(ParameterSyntax node) + { + SyntaxNode? updatedNode = base.VisitParameter(node); + + // If the node is a parameter node with a single attribute being either [NotNull] or [NotNullIfNotNull], drop it. + // This expression will match all parameters with the following format: + // + // ([global::.] ) + // + // Where is either "NotNull" or "NotNullIfNotNull". This relies on parameters following this structure + // for nullability annotations, but that is fine in this context given the only source files are the embedded ones. + if (updatedNode is ParameterSyntax { AttributeLists: [{ Attributes: [{ Name: QualifiedNameSyntax { Right.Identifier.Text: "NotNull" or "NotNullIfNotNull"} }] }] } parameterNode) + { + return parameterNode.WithAttributeLists(default); + } + + return updatedNode; + } + } } } diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/TransitiveMembersGenerator.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/TransitiveMembersGenerator.cs new file mode 100644 index 000000000..fc430a04f --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/TransitiveMembersGenerator.cs @@ -0,0 +1,140 @@ +// 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.Linq; +using System.Text; +using CommunityToolkit.Mvvm.SourceGenerators.Extensions; +using CommunityToolkit.Mvvm.SourceGenerators.Models; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace CommunityToolkit.Mvvm.SourceGenerators; + +/// +/// A source generator for a given attribute type. +/// +/// The type of info gathered for each target type to process. +public abstract partial class TransitiveMembersGenerator : IIncrementalGenerator + where TInfo : IEquatable +{ + /// + /// The fully qualified metadata name of the attribute type to look for. + /// + private readonly string fullyQualifiedAttributeMetadataName; + + /// + /// The preloaded instance with members to generate. + /// + private readonly ClassDeclarationSyntax classDeclaration; + + /// + /// The sequence of member declarations for sealed types. + /// + private ImmutableArray sealedMemberDeclarations; + + /// + /// The resulting sequence of member declarations for non sealed types. + /// + private ImmutableArray nonSealedMemberDeclarations; + + /// + /// Initializes a new instance of the class. + /// + /// The fully qualified metadata name of the attribute type to look for. + private protected TransitiveMembersGenerator(string fullyQualifiedAttributeMetadataName) + { + this.fullyQualifiedAttributeMetadataName = fullyQualifiedAttributeMetadataName; + this.classDeclaration = Execute.LoadClassDeclaration(fullyQualifiedAttributeMetadataName); + + Execute.ProcessMemberDeclarations( + GetType(), + this.classDeclaration.Members.ToImmutableArray(), + out this.sealedMemberDeclarations, + out this.nonSealedMemberDeclarations); + } + + /// + public void Initialize(IncrementalGeneratorInitializationContext context) + { + // Gather all generation info, and any diagnostics + IncrementalValuesProvider> generationInfoWithErrors = + context.SyntaxProvider + .ForAttributeWithMetadataName( + this.fullyQualifiedAttributeMetadataName, + static (node, _) => node is ClassDeclarationSyntax classDeclaration && classDeclaration.HasOrPotentiallyHasAttributes(), + (context, token) => + { + if (!context.SemanticModel.Compilation.HasLanguageVersionAtLeastEqualTo(LanguageVersion.CSharp8)) + { + return default; + } + + INamedTypeSymbol typeSymbol = (INamedTypeSymbol)context.TargetSymbol; + + // Gather all generation info, and any diagnostics + TInfo? info = ValidateTargetTypeAndGetInfo(typeSymbol, context.Attributes[0], context.SemanticModel.Compilation, out ImmutableArray diagnostics); + + // If there are any diagnostics, there's no need to compute the hierarchy info at all, just return them + if (diagnostics.Length > 0) + { + return new Result<(HierarchyInfo, MetadataInfo?, TInfo?)>(default, diagnostics); + } + + HierarchyInfo hierarchy = HierarchyInfo.From(typeSymbol); + MetadataInfo metadataInfo = new(typeSymbol.IsSealed, Execute.IsNullabilitySupported(context.SemanticModel.Compilation)); + + return new Result<(HierarchyInfo, MetadataInfo?, TInfo?)>((hierarchy, metadataInfo, info), diagnostics); + }) + .Where(static item => item is not null)!; + + // Emit the diagnostic, if needed + context.ReportDiagnostics(generationInfoWithErrors.Select(static (item, _) => item.Errors)); + + // Get the filtered sequence to enable caching + IncrementalValuesProvider<(HierarchyInfo Hierarchy, MetadataInfo MetadataInfo, TInfo Info)> generationInfo = + generationInfoWithErrors + .Where(static item => item.Errors.IsEmpty) + .Select(static (item, _) => item.Value)!; + + // Generate the required members + context.RegisterSourceOutput(generationInfo, (context, item) => + { + ImmutableArray sourceMemberDeclarations = item.MetadataInfo.IsSealed ? this.sealedMemberDeclarations : this.nonSealedMemberDeclarations; + ImmutableArray filteredMemberDeclarations = FilterDeclaredMembers(item.Info, sourceMemberDeclarations); + ImmutableArray updatedMemberDeclarations = Execute.AdjustMemberDeclarationNullabilityAnnotations(filteredMemberDeclarations, item.MetadataInfo.IsNullabilitySupported); + CompilationUnitSyntax compilationUnit = item.Hierarchy.GetCompilationUnit(updatedMemberDeclarations, this.classDeclaration.BaseList); + + context.AddSource($"{item.Hierarchy.FilenameHint}.g.cs", compilationUnit.GetText(Encoding.UTF8)); + }); + } + + /// + /// Validates the target type being processes, gets the info if possible and produces all necessary diagnostics. + /// + /// The instance currently being processed. + /// The instance for the attribute used over . + /// The compilation that belongs to. + /// The resulting diagnostics, if any. + /// The extracted info for the current type, if possible. + /// If is empty, the returned info will always be ignored and no sources will be produced. + private protected abstract TInfo? ValidateTargetTypeAndGetInfo(INamedTypeSymbol typeSymbol, AttributeData attributeData, Compilation compilation, out ImmutableArray diagnostics); + + /// + /// Filters the nodes to generate from the input parsed tree. + /// + /// The instance with the current processing info. + /// The input sequence of instances to generate. + /// A sequence of nodes to emit in the generated file. + protected abstract ImmutableArray FilterDeclaredMembers(TInfo info, ImmutableArray memberDeclarations); + + /// + /// A small record for metadata info on types to generate. + /// + /// Whether the target type is sealed. + /// Whether nullability attributes are supported. + private sealed record MetadataInfo(bool IsSealed, bool IsNullabilitySupported); +} \ No newline at end of file diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/ClassUsingAttributeInsteadOfInheritanceAnalyzer.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/ClassUsingAttributeInsteadOfInheritanceAnalyzer.cs new file mode 100644 index 000000000..6706d9cca --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/ClassUsingAttributeInsteadOfInheritanceAnalyzer.cs @@ -0,0 +1,76 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors; + +namespace CommunityToolkit.Mvvm.SourceGenerators; + +/// +/// A diagnostic analyzer that generates a warning when a class is using a code generation attribute when it could inherit instead. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class ClassUsingAttributeInsteadOfInheritanceAnalyzer : DiagnosticAnalyzer +{ + /// + /// The mapping of target attributes that will trigger the analyzer. + /// + private static readonly ImmutableDictionary GeneratorAttributeNamesToFullyQualifiedNamesMap = ImmutableDictionary.CreateRange(new[] + { + new KeyValuePair("ObservableObjectAttribute", "CommunityToolkit.Mvvm.ComponentModel.ObservableObjectAttribute"), + new KeyValuePair("INotifyPropertyChangedAttribute", "CommunityToolkit.Mvvm.ComponentModel.INotifyPropertyChangedAttribute"), + }); + + /// + /// The mapping of diagnostics for each target attribute. + /// + private static readonly ImmutableDictionary GeneratorAttributeNamesToDiagnosticsMap = ImmutableDictionary.CreateRange(new[] + { + new KeyValuePair("ObservableObjectAttribute", InheritFromObservableObjectInsteadOfUsingObservableObjectAttributeWarning), + new KeyValuePair("INotifyPropertyChangedAttribute", InheritFromObservableObjectInsteadOfUsingINotifyPropertyChangedAttributeWarning), + }); + + /// + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create( + InheritFromObservableObjectInsteadOfUsingObservableObjectAttributeWarning, + InheritFromObservableObjectInsteadOfUsingINotifyPropertyChangedAttributeWarning); + + /// + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); + context.EnableConcurrentExecution(); + + context.RegisterSymbolAction(static context => + { + // We're looking for class declarations + if (context.Symbol is not INamedTypeSymbol { TypeKind: TypeKind.Class, IsRecord: false, IsStatic: false, IsImplicitlyDeclared: false } classSymbol) + { + return; + } + + foreach (AttributeData attribute in context.Symbol.GetAttributes()) + { + // Same logic as in FieldWithOrphanedDependentObservablePropertyAttributesAnalyzer to find target attributes + if (attribute.AttributeClass is { Name: string attributeName } attributeClass && + GeneratorAttributeNamesToFullyQualifiedNamesMap.TryGetValue(attributeName, out string? fullyQualifiedAttributeName) && + context.Compilation.GetTypeByMetadataName(fullyQualifiedAttributeName) is INamedTypeSymbol attributeSymbol && + SymbolEqualityComparer.Default.Equals(attributeClass, attributeSymbol)) + { + // The type is annotated with either [ObservableObject] or [INotifyPropertyChanged]. + // Next, we need to check whether it isn't already inheriting from another type. + if (classSymbol.BaseType is { SpecialType: SpecialType.System_Object }) + { + // This type is using the attribute when it could just inherit from ObservableObject, which is preferred + context.ReportDiagnostic(Diagnostic.Create(GeneratorAttributeNamesToDiagnosticsMap[attributeClass.Name], context.Symbol.Locations.FirstOrDefault(), context.Symbol)); + } + } + } + }, SymbolKind.NamedType); + } +} diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/FieldReferenceForObservablePropertyFieldAnalyzer.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/FieldReferenceForObservablePropertyFieldAnalyzer.cs new file mode 100644 index 000000000..cda520e6f --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/FieldReferenceForObservablePropertyFieldAnalyzer.cs @@ -0,0 +1,69 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors; + +namespace CommunityToolkit.Mvvm.SourceGenerators; + +/// +/// A diagnostic analyzer that generates a warning when accessing a field instead of a generated observable property. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class FieldReferenceForObservablePropertyFieldAnalyzer : DiagnosticAnalyzer +{ + /// + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(FieldReferenceForObservablePropertyFieldWarning); + + /// + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); + context.EnableConcurrentExecution(); + + context.RegisterOperationAction(static context => + { + // We're only looking for references to fields that could potentially be observable properties + if (context.Operation is not IFieldReferenceOperation + { + Field: IFieldSymbol { IsStatic: false, IsConst: false, IsImplicitlyDeclared: false, ContainingType: INamedTypeSymbol } fieldSymbol, + Instance.Type: ITypeSymbol typeSymbol + }) + { + return; + } + + // Special case field references from within a constructor and don't ever emit warnings for them. The point of this + // analyzer is to prevent mistakes when users assign a field instead of a property and then get confused when the + // property changed event is not raised. But this would never be the case from a constructur anyway, given that + // no handler for that event would possibly be present. Suppressing warnings in this cases though will help to + // avoid scenarios where people get nullability warnings they cannot suppress, in case they were pushed by the + // analyzer in the MVVM Toolkit to not assign a field marked with a non-nullable reference type. Ideally this + // would be solved by habing the generated setter be marked with [MemberNotNullIfNotNull("field", "value")], + // but such an annotation does not currently exist. + if (context.ContainingSymbol is IMethodSymbol { MethodKind: MethodKind.Constructor, ContainingType: INamedTypeSymbol instanceType } && + SymbolEqualityComparer.Default.Equals(instanceType, typeSymbol)) + { + return; + } + + foreach (AttributeData attribute in fieldSymbol.GetAttributes()) + { + // Look for the [ObservableProperty] attribute (there can only ever be one per field) + if (attribute.AttributeClass is { Name: "ObservablePropertyAttribute" } attributeClass && + context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute") is INamedTypeSymbol attributeSymbol && + SymbolEqualityComparer.Default.Equals(attributeClass, attributeSymbol)) + { + // Emit a warning to redirect users to access the generated property instead + context.ReportDiagnostic(Diagnostic.Create(FieldReferenceForObservablePropertyFieldWarning, context.Operation.Syntax.GetLocation(), fieldSymbol)); + + return; + } + } + }, OperationKind.FieldReference); + } +} diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/FieldWithOrphanedDependentObservablePropertyAttributesAnalyzer.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/FieldWithOrphanedDependentObservablePropertyAttributesAnalyzer.cs new file mode 100644 index 000000000..44962f0b9 --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/FieldWithOrphanedDependentObservablePropertyAttributesAnalyzer.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.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using CommunityToolkit.Mvvm.SourceGenerators.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; +using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors; + +namespace CommunityToolkit.Mvvm.SourceGenerators; + +/// +/// A diagnostic analyzer that generates an error whenever a field has an orphaned attribute that depends on [ObservableProperty]. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class FieldWithOrphanedDependentObservablePropertyAttributesAnalyzer : DiagnosticAnalyzer +{ + /// + /// The mapping of target attributes that will trigger the analyzer. + /// + private static readonly ImmutableDictionary GeneratorAttributeNamesToFullyQualifiedNamesMap = ImmutableDictionary.CreateRange(new[] + { + new KeyValuePair("NotifyCanExecuteChangedForAttribute", "CommunityToolkit.Mvvm.ComponentModel.NotifyCanExecuteChangedForAttribute"), + new KeyValuePair("NotifyDataErrorInfoAttribute", "CommunityToolkit.Mvvm.ComponentModel.NotifyDataErrorInfoAttribute"), + new KeyValuePair("NotifyPropertyChangedForAttribute", "CommunityToolkit.Mvvm.ComponentModel.NotifyPropertyChangedForAttribute"), + new KeyValuePair("NotifyPropertyChangedRecipientsAttribute", "CommunityToolkit.Mvvm.ComponentModel.NotifyPropertyChangedRecipientsAttribute") + }); + + /// + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(FieldWithOrphanedDependentObservablePropertyAttributesError); + + /// + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); + context.EnableConcurrentExecution(); + + // Defer the registration so it can be skipped if C# 8.0 or more is not available. + // That is because in that case source generators are not supported at all anyaway. + context.RegisterCompilationStartAction(static context => + { + if (!context.Compilation.HasLanguageVersionAtLeastEqualTo(LanguageVersion.CSharp8)) + { + return; + } + + context.RegisterSymbolAction(static context => + { + ImmutableArray attributes = context.Symbol.GetAttributes(); + + // If the symbol has no attributes, there's nothing left to do + if (attributes.IsEmpty) + { + return; + } + + foreach (AttributeData dependentAttribute in attributes) + { + // Go over each attribute on the target symbol, anche check if any of them matches one of the trigger attributes. + // The logic here is the same as the one in UnsupportedCSharpLanguageVersionAnalyzer, to minimize retrieving symbols. + if (dependentAttribute.AttributeClass is { Name: string attributeName } dependentAttributeClass && + GeneratorAttributeNamesToFullyQualifiedNamesMap.TryGetValue(attributeName, out string? fullyQualifiedDependentAttributeName) && + context.Compilation.GetTypeByMetadataName(fullyQualifiedDependentAttributeName) is INamedTypeSymbol dependentAttributeSymbol && + SymbolEqualityComparer.Default.Equals(dependentAttributeClass, dependentAttributeSymbol)) + { + // If the attribute matches, iterate over the attributes to try to find [ObservableProperty] + foreach (AttributeData attribute in attributes) + { + if (attribute.AttributeClass is { Name: "ObservablePropertyAttribute" } attributeSymbol && + context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute") is INamedTypeSymbol observablePropertySymbol && + SymbolEqualityComparer.Default.Equals(attributeSymbol, observablePropertySymbol)) + { + // If [ObservableProperty] is found, then this field is valid in that it doesn't have orphaned dependent attributes + return; + } + } + + context.ReportDiagnostic(Diagnostic.Create(FieldWithOrphanedDependentObservablePropertyAttributesError, context.Symbol.Locations.FirstOrDefault(), context.Symbol.ContainingType, context.Symbol.Name)); + + // Just like in UnsupportedCSharpLanguageVersionAnalyzer, stop if a diagnostic has been emitted for the current symbol + return; + } + } + }, SymbolKind.Field); + }); + } +} diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/UnsupportedCSharpLanguageVersionAnalyzer.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/UnsupportedCSharpLanguageVersionAnalyzer.cs new file mode 100644 index 000000000..fd5f20b72 --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/UnsupportedCSharpLanguageVersionAnalyzer.cs @@ -0,0 +1,85 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using CommunityToolkit.Mvvm.SourceGenerators.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; +using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors; + +namespace CommunityToolkit.Mvvm.SourceGenerators; + +/// +/// A diagnostic analyzer that generates an error whenever a source-generator attribute is used with not high enough C# version enabled. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class UnsupportedCSharpLanguageVersionAnalyzer : DiagnosticAnalyzer +{ + /// + /// The mapping of target attributes that will trigger the analyzer. + /// + private static readonly ImmutableDictionary GeneratorAttributeNamesToFullyQualifiedNamesMap = ImmutableDictionary.CreateRange(new[] + { + new KeyValuePair("INotifyPropertyChangedAttribute", "CommunityToolkit.Mvvm.ComponentModel.INotifyPropertyChangedAttribute"), + new KeyValuePair("NotifyCanExecuteChangedForAttribute", "CommunityToolkit.Mvvm.ComponentModel.NotifyCanExecuteChangedForAttribute"), + new KeyValuePair("NotifyDataErrorInfoAttribute", "CommunityToolkit.Mvvm.ComponentModel.NotifyDataErrorInfoAttribute"), + new KeyValuePair("NotifyPropertyChangedForAttribute", "CommunityToolkit.Mvvm.ComponentModel.NotifyPropertyChangedForAttribute"), + new KeyValuePair("NotifyPropertyChangedRecipientsAttribute", "CommunityToolkit.Mvvm.ComponentModel.NotifyPropertyChangedRecipientsAttribute"), + new KeyValuePair("ObservableObjectAttribute", "CommunityToolkit.Mvvm.ComponentModel.ObservableObjectAttribute"), + new KeyValuePair("ObservablePropertyAttribute", "CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute"), + new KeyValuePair("ObservableRecipientAttribute", "CommunityToolkit.Mvvm.ComponentModel.ObservableRecipientAttribute"), + new KeyValuePair("RelayCommandAttribute", "CommunityToolkit.Mvvm.Input.RelayCommandAttribute") + }); + + /// + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(UnsupportedCSharpLanguageVersionError); + + /// + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); + context.EnableConcurrentExecution(); + + // Defer the callback registration to when the compilation starts, so we can execute more + // preliminary checks and skip registering any kind of symbol analysis at all if not needed. + context.RegisterCompilationStartAction(static context => + { + // Check that the language version is not high enough, otherwise no diagnostic should ever be produced + if (context.Compilation.HasLanguageVersionAtLeastEqualTo(LanguageVersion.CSharp8)) + { + return; + } + + context.RegisterSymbolAction(static context => + { + // The possible attribute targets are only fields, classes and methods + if (context.Symbol is not (IFieldSymbol or INamedTypeSymbol { TypeKind: TypeKind.Class, IsImplicitlyDeclared: false } or IMethodSymbol)) + { + return; + } + + foreach (AttributeData attribute in context.Symbol.GetAttributes()) + { + // Go over each attribute on the target symbol, and check if the attribute type name is a candidate. + // If it is, double check by actually resolving the symbol from the compilation and comparing against it. + // This minimizes the calls to CompilationGetTypeByMetadataName(string) to only cases where it's almost + // guaranteed we'll actually get a match. If we do have one, then we can emit the diagnostic for the symbol. + if (attribute.AttributeClass is { Name: string attributeName } attributeClass && + GeneratorAttributeNamesToFullyQualifiedNamesMap.TryGetValue(attributeName, out string? fullyQualifiedAttributeName) && + context.Compilation.GetTypeByMetadataName(fullyQualifiedAttributeName) is INamedTypeSymbol attributeSymbol && + SymbolEqualityComparer.Default.Equals(attributeClass, attributeSymbol)) + { + context.ReportDiagnostic(Diagnostic.Create(UnsupportedCSharpLanguageVersionError, context.Symbol.Locations.FirstOrDefault())); + + // If we created a diagnostic for this symbol, we can stop. Even if there's multiple attributes, no need for repeated errors + return; + } + } + }, SymbolKind.Field, SymbolKind.NamedType, SymbolKind.Method); + }); + } +} diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs similarity index 87% rename from CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs rename to src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs index 5af99c5c9..d3c4fd4d1 100644 --- a/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs @@ -4,7 +4,6 @@ using System.ComponentModel; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; #pragma warning disable IDE0090 // Use 'new DiagnosticDescriptor(...)' @@ -134,7 +133,7 @@ internal static class DiagnosticDescriptors id: "MVVMTK0008", title: "Unsupported C# language version", messageFormat: "The source generator features from the MVVM Toolkit require consuming projects to set the C# language version to at least C# 8.0", - category: typeof(CSharpParseOptions).FullName, + category: typeof(UnsupportedCSharpLanguageVersionAnalyzer).FullName, defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true, description: "The source generator features from the MVVM Toolkit require consuming projects to set the C# language version to at least C# 8.0. Make sure to add 8.0 (or above) to your .csproj file.", @@ -507,4 +506,72 @@ internal static class DiagnosticDescriptors isEnabledByDefault: true, description: "Cannot apply the [RelayCommand] attribute specifying a task scheduler exception flow option to methods mapping to non-asynchronous command types.", helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0031"); + + /// + /// Gets a indicating when [INotifyPropertyChanged] is used on a type that could inherit from ObservableObject instead. + /// + /// Format: "The type {0} is using the [INotifyPropertyChanged] attribute while having no base type, and it should instead inherit from ObservableObject". + /// + /// + public static readonly DiagnosticDescriptor InheritFromObservableObjectInsteadOfUsingINotifyPropertyChangedAttributeWarning = new DiagnosticDescriptor( + id: "MVVMTK0032", + title: "Inherit from ObservableObject instead of using [INotifyPropertyChanged]", + messageFormat: "The type {0} is using the [INotifyPropertyChanged] attribute while having no base type, and it should instead inherit from ObservableObject", + category: typeof(INotifyPropertyChangedGenerator).FullName, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: + "Classes with no base types should prefer inheriting from ObservableObject instead of using attributes to generate INotifyPropertyChanged code, as that will " + + "reduce the binary size of the application (the attributes are only meant to support cases where the annotated types are already inheriting from a different type).", + helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0032"); + + /// + /// Gets a indicating when [ObservableObject] is used on a type that could inherit from ObservableObject instead. + /// + /// Format: "The type {0} is using the [ObservableObject] attribute while having no base type, and it should instead inherit from ObservableObject". + /// + /// + public static readonly DiagnosticDescriptor InheritFromObservableObjectInsteadOfUsingObservableObjectAttributeWarning = new DiagnosticDescriptor( + id: "MVVMTK0033", + title: "Inherit from ObservableObject instead of using [ObservableObject]", + messageFormat: "The type {0} is using the [ObservableObject] attribute while having no base type, and it should instead inherit from ObservableObject", + category: typeof(ObservableObjectGenerator).FullName, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: + "Classes with no base types should prefer inheriting from ObservableObject instead of using attributes to generate INotifyPropertyChanged code, as that will " + + "reduce the binary size of the application (the attributes are only meant to support cases where the annotated types are already inheriting from a different type).", + helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0033"); + + /// + /// Gets a indicating when a field with [ObservableProperty] is being directly referenced. + /// + /// Format: "The field {0} is annotated with [ObservableProperty] and should not be directly referenced (use the generated property instead)". + /// + /// + public static readonly DiagnosticDescriptor FieldReferenceForObservablePropertyFieldWarning = new DiagnosticDescriptor( + id: "MVVMTK0034", + title: "Direct field reference to [ObservableProperty] backing field", + messageFormat: "The field {0} is annotated with [ObservableProperty] and should not be directly referenced (use the generated property instead)", + category: typeof(ObservablePropertyGenerator).FullName, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: "Fields with [ObservableProperty] should not be directly referenced, and the generated properties should be used instead.", + helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0034"); + + /// + /// Gets a indicating when a field with [ObservableProperty] is using an invalid attribute targeting the property. + /// + /// Format: "The field {0} annotated with [ObservableProperty] is using attribute "{1}" which was not recognized as a valid type (are you missing a using directive?)". + /// + /// + public static readonly DiagnosticDescriptor InvalidPropertyTargetedAttributeOnObservablePropertyField = new DiagnosticDescriptor( + id: "MVVMTK0035", + title: "Invalid property targeted attribute type", + messageFormat: "The field {0} annotated with [ObservableProperty] is using attribute \"{1}\" which was not recognized as a valid type (are you missing a using directive?)", + category: typeof(ObservablePropertyGenerator).FullName, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "All attributes targeting the generated property for a field annotated with [ObservableProperty] must correctly be resolved to valid types.", + helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0035"); } diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/SuppressionDescriptors.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/SuppressionDescriptors.cs new file mode 100644 index 000000000..ad0000582 --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/SuppressionDescriptors.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis; + +namespace CommunityToolkit.Mvvm.SourceGenerators.Diagnostics; + +/// +/// A container for all instances for suppressed diagnostics by analyzers in this project. +/// +internal static class SuppressionDescriptors +{ + /// + /// Gets a for a field using [ObservableProperty] with on attribute list targeting a property. + /// + public static readonly SuppressionDescriptor PropertyAttributeListForObservablePropertyField = new( + id: "MVVMTKSPR0001", + suppressedDiagnosticId: "CS0657", + justification: "Fields using [ObservableProperty] can use [property:] attribute lists to forward attributes to the generated properties"); +} diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Suppressors/ObservablePropertyAttributeWithPropertyTargetDiagnosticSuppressor.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Suppressors/ObservablePropertyAttributeWithPropertyTargetDiagnosticSuppressor.cs new file mode 100644 index 000000000..5192e7412 --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Suppressors/ObservablePropertyAttributeWithPropertyTargetDiagnosticSuppressor.cs @@ -0,0 +1,63 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.SuppressionDescriptors; + +namespace CommunityToolkit.Mvvm.SourceGenerators; + +/// +/// +/// A diagnostic suppressor to suppress CS0657 warnings for fields with [ObservableProperty] using a [property:] attribute list. +/// +/// +/// That is, this diagnostic suppressor will suppress the following diagnostic: +/// +/// public class MyViewModel : ObservableObject +/// { +/// [ObservableProperty] +/// [property: JsonPropertyName("Name")] +/// private string? name; +/// } +/// +/// +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class ObservablePropertyAttributeWithPropertyTargetDiagnosticSuppressor : DiagnosticSuppressor +{ + /// + public override ImmutableArray SupportedSuppressions => ImmutableArray.Create(PropertyAttributeListForObservablePropertyField); + + /// + public override void ReportSuppressions(SuppressionAnalysisContext context) + { + foreach (Diagnostic diagnostic in context.ReportedDiagnostics) + { + SyntaxNode? syntaxNode = diagnostic.Location.SourceTree?.GetRoot(context.CancellationToken).FindNode(diagnostic.Location.SourceSpan); + + // Check that the target is effectively [property:] over a field declaration with at least one variable, which is the only case we are interested in + if (syntaxNode is AttributeTargetSpecifierSyntax { Parent.Parent: FieldDeclarationSyntax { Declaration.Variables.Count: > 0 } fieldDeclaration } attributeTarget && + attributeTarget.Identifier.IsKind(SyntaxKind.PropertyKeyword)) + { + SemanticModel semanticModel = context.GetSemanticModel(syntaxNode.SyntaxTree); + + // Get the field symbol from the first variable declaration + ISymbol? declaredSymbol = semanticModel.GetDeclaredSymbol(fieldDeclaration.Declaration.Variables[0], context.CancellationToken); + + // Check if the field is using [ObservableProperty], in which case we should suppress the warning + if (declaredSymbol is IFieldSymbol fieldSymbol && + semanticModel.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute") is INamedTypeSymbol observablePropertySymbol && + fieldSymbol.GetAttributes().Select(attribute => attribute.AttributeClass).Contains(observablePropertySymbol, SymbolEqualityComparer.Default)) + { + context.ReportSuppression(Suppression.Create(PropertyAttributeListForObservablePropertyField, diagnostic)); + } + } + } + } +} diff --git a/CommunityToolkit.Mvvm.SourceGenerators/EmbeddedResources/INotifyPropertyChanged.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/EmbeddedResources/INotifyPropertyChanged.cs similarity index 100% rename from CommunityToolkit.Mvvm.SourceGenerators/EmbeddedResources/INotifyPropertyChanged.cs rename to src/CommunityToolkit.Mvvm.SourceGenerators/EmbeddedResources/INotifyPropertyChanged.cs diff --git a/CommunityToolkit.Mvvm.SourceGenerators/EmbeddedResources/ObservableObject.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/EmbeddedResources/ObservableObject.cs similarity index 100% rename from CommunityToolkit.Mvvm.SourceGenerators/EmbeddedResources/ObservableObject.cs rename to src/CommunityToolkit.Mvvm.SourceGenerators/EmbeddedResources/ObservableObject.cs diff --git a/CommunityToolkit.Mvvm.SourceGenerators/EmbeddedResources/ObservableRecipient.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/EmbeddedResources/ObservableRecipient.cs similarity index 100% rename from CommunityToolkit.Mvvm.SourceGenerators/EmbeddedResources/ObservableRecipient.cs rename to src/CommunityToolkit.Mvvm.SourceGenerators/EmbeddedResources/ObservableRecipient.cs diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Extensions/AttributeDataExtensions.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/AttributeDataExtensions.cs similarity index 100% rename from CommunityToolkit.Mvvm.SourceGenerators/Extensions/AttributeDataExtensions.cs rename to src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/AttributeDataExtensions.cs diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Extensions/CompilationExtensions.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/CompilationExtensions.cs similarity index 82% rename from CommunityToolkit.Mvvm.SourceGenerators/Extensions/CompilationExtensions.cs rename to src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/CompilationExtensions.cs index de3cf500f..878b62188 100644 --- a/CommunityToolkit.Mvvm.SourceGenerators/Extensions/CompilationExtensions.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/CompilationExtensions.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions; @@ -11,6 +12,17 @@ namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions; /// internal static class CompilationExtensions { + /// + /// Checks whether a given compilation (assumed to be for C#) is using at least a given language version. + /// + /// The to consider for analysis. + /// The minimum language version to check. + /// Whether is using at least the specified language version. + public static bool HasLanguageVersionAtLeastEqualTo(this Compilation compilation, LanguageVersion languageVersion) + { + return ((CSharpCompilation)compilation).LanguageVersion >= languageVersion; + } + /// /// /// Checks whether or not a type with a specified metadata name is accessible from a given instance. diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/DiagnosticsExtensions.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/DiagnosticsExtensions.cs new file mode 100644 index 000000000..d6edd89f3 --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/DiagnosticsExtensions.cs @@ -0,0 +1,79 @@ +// 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. + +// This file is ported and adapted from ComputeSharp (Sergio0694/ComputeSharp), +// more info in ThirdPartyNotices.txt in the root of the project. + +using CommunityToolkit.Mvvm.SourceGenerators.Helpers; +using CommunityToolkit.Mvvm.SourceGenerators.Models; +using Microsoft.CodeAnalysis; + +namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions; + +/// +/// Extension methods for , specifically for reporting diagnostics. +/// +internal static class DiagnosticsExtensions +{ + /// + /// Adds a new diagnostics to the target builder. + /// + /// The collection of produced instances. + /// The input for the diagnostics to create. + /// The source to attach the diagnostics to. + /// The optional arguments for the formatted message to include. + public static void Add( + this in ImmutableArrayBuilder diagnostics, + DiagnosticDescriptor descriptor, + ISymbol symbol, + params object[] args) + { + diagnostics.Add(DiagnosticInfo.Create(descriptor, symbol, args)); + } + + /// + /// Adds a new diagnostics to the target builder. + /// + /// The collection of produced instances. + /// The input for the diagnostics to create. + /// The source to attach the diagnostics to. + /// The optional arguments for the formatted message to include. + public static void Add( + this in ImmutableArrayBuilder diagnostics, + DiagnosticDescriptor descriptor, + SyntaxNode node, + params object[] args) + { + diagnostics.Add(DiagnosticInfo.Create(descriptor, node, args)); + } + + /// + /// Registers an output node into an to output diagnostics. + /// + /// The input instance. + /// The input sequence of diagnostics. + public static void ReportDiagnostics(this IncrementalGeneratorInitializationContext context, IncrementalValuesProvider diagnostics) + { + context.RegisterSourceOutput(diagnostics, static (context, diagnostic) => + { + context.ReportDiagnostic(diagnostic.ToDiagnostic()); + }); + } + + /// + /// Registers an output node into an to output diagnostics. + /// + /// The input instance. + /// The input sequence of diagnostics. + public static void ReportDiagnostics(this IncrementalGeneratorInitializationContext context, IncrementalValuesProvider> diagnostics) + { + context.RegisterSourceOutput(diagnostics, static (context, diagnostics) => + { + foreach (DiagnosticInfo diagnostic in diagnostics) + { + context.ReportDiagnostic(diagnostic.ToDiagnostic()); + } + }); + } +} diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Extensions/INamedTypeSymbolExtensions.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/INamedTypeSymbolExtensions.cs similarity index 52% rename from CommunityToolkit.Mvvm.SourceGenerators/Extensions/INamedTypeSymbolExtensions.cs rename to src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/INamedTypeSymbolExtensions.cs index c67977d77..8b847bcf0 100644 --- a/CommunityToolkit.Mvvm.SourceGenerators/Extensions/INamedTypeSymbolExtensions.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/INamedTypeSymbolExtensions.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; -using System.Text; using Microsoft.CodeAnalysis; namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions; @@ -13,36 +12,6 @@ namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions; /// internal static class INamedTypeSymbolExtensions { - /// - /// Gets a valid filename for a given instance. - /// - /// The input instance. - /// The full metadata name for that is also a valid filename. - public static string GetFullMetadataNameForFileName(this INamedTypeSymbol symbol) - { - static StringBuilder BuildFrom(ISymbol? symbol, StringBuilder builder) - { - return symbol switch - { - INamespaceSymbol ns when ns.IsGlobalNamespace => builder, - INamespaceSymbol ns when ns.ContainingNamespace is { IsGlobalNamespace: false } - => BuildFrom(ns.ContainingNamespace, builder.Insert(0, $".{ns.MetadataName}")), - ITypeSymbol ts when ts.ContainingType is ISymbol pt - => BuildFrom(pt, builder.Insert(0, $"+{ts.MetadataName}")), - ITypeSymbol ts when ts.ContainingNamespace is ISymbol pn and not INamespaceSymbol { IsGlobalNamespace: true } - => BuildFrom(pn, builder.Insert(0, $".{ts.MetadataName}")), - ISymbol => BuildFrom(symbol.ContainingSymbol, builder.Insert(0, symbol.MetadataName)), - _ => builder - }; - } - - // Build the full metadata name by concatenating the metadata names of all symbols from the input - // one to the outermost namespace, if any. Additionally, the ` and + symbols need to be replaced - // to avoid errors when generating code. This is a known issue with source generators not accepting - // those characters at the moment, see: https://github.com/dotnet/roslyn/issues/58476. - return BuildFrom(symbol, new StringBuilder(256)).ToString().Replace('`', '-').Replace('+', '.'); - } - /// /// Gets all member symbols from a given instance, including inherited ones. /// diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Extensions/ISymbolExtensions.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/ISymbolExtensions.cs similarity index 77% rename from CommunityToolkit.Mvvm.SourceGenerators/Extensions/ISymbolExtensions.cs rename to src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/ISymbolExtensions.cs index 269472de5..a54073fd1 100644 --- a/CommunityToolkit.Mvvm.SourceGenerators/Extensions/ISymbolExtensions.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/ISymbolExtensions.cs @@ -3,6 +3,9 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; +#if !ROSLYN_4_3_1_OR_GREATER +using System.Diagnostics.CodeAnalysis; +#endif using Microsoft.CodeAnalysis; namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions; @@ -44,18 +47,18 @@ public static bool HasFullyQualifiedName(this ISymbol symbol, string name) } /// - /// Checks whether or not a given symbol has an attribute with the specified full name. + /// Checks whether or not a given symbol has an attribute with the specified fully qualified metadata name. /// /// The input instance to check. /// The attribute name to look for. /// Whether or not has an attribute with the specified name. - public static bool HasAttributeWithFullyQualifiedName(this ISymbol symbol, string name) + public static bool HasAttributeWithFullyQualifiedMetadataName(this ISymbol symbol, string name) { ImmutableArray attributes = symbol.GetAttributes(); foreach (AttributeData attribute in attributes) { - if (attribute.AttributeClass?.HasFullyQualifiedName(name) == true) + if (attribute.AttributeClass?.HasFullyQualifiedMetadataName(name) == true) { return true; } @@ -64,6 +67,34 @@ public static bool HasAttributeWithFullyQualifiedName(this ISymbol symbol, strin return false; } +#if !ROSLYN_4_3_1_OR_GREATER + /// + /// Tries to get an attribute with the specified fully qualified metadata name. + /// + /// The input instance to check. + /// The attribute name to look for. + /// The resulting attribute, if it was found. + /// Whether or not has an attribute with the specified name. + public static bool TryGetAttributeWithFullyQualifiedMetadataName(this ISymbol symbol, string name, [NotNullWhen(true)] out AttributeData? attributeData) + { + ImmutableArray attributes = symbol.GetAttributes(); + + foreach (AttributeData attribute in attributes) + { + if (attribute.AttributeClass?.HasFullyQualifiedMetadataName(name) == true) + { + attributeData = attribute; + + return true; + } + } + + attributeData = null; + + return false; + } +#endif + /// /// Calculates the effective accessibility for a given symbol. /// diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Extensions/ITypeSymbolExtensions.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/ITypeSymbolExtensions.cs similarity index 50% rename from CommunityToolkit.Mvvm.SourceGenerators/Extensions/ITypeSymbolExtensions.cs rename to src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/ITypeSymbolExtensions.cs index 50e8c8eaa..52a813986 100644 --- a/CommunityToolkit.Mvvm.SourceGenerators/Extensions/ITypeSymbolExtensions.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/ITypeSymbolExtensions.cs @@ -4,6 +4,7 @@ using System; using System.Linq; +using CommunityToolkit.Mvvm.SourceGenerators.Helpers; using Microsoft.CodeAnalysis; namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions; @@ -19,11 +20,11 @@ internal static class ITypeSymbolExtensions /// The target instance to check. /// The full name of the type to check for inheritance. /// Whether or not is or inherits from . - public static bool HasOrInheritsFromFullyQualifiedName(this ITypeSymbol typeSymbol, string name) + public static bool HasOrInheritsFromFullyQualifiedMetadataName(this ITypeSymbol typeSymbol, string name) { for (ITypeSymbol? currentType = typeSymbol; currentType is not null; currentType = currentType.BaseType) { - if (currentType.HasFullyQualifiedName(name)) + if (currentType.HasFullyQualifiedMetadataName(name)) { return true; } @@ -38,13 +39,13 @@ public static bool HasOrInheritsFromFullyQualifiedName(this ITypeSymbol typeSymb /// The target instance to check. /// The full name of the type to check for inheritance. /// Whether or not inherits from . - public static bool InheritsFromFullyQualifiedName(this ITypeSymbol typeSymbol, string name) + public static bool InheritsFromFullyQualifiedMetadataName(this ITypeSymbol typeSymbol, string name) { INamedTypeSymbol? baseType = typeSymbol.BaseType; while (baseType != null) { - if (baseType.HasFullyQualifiedName(name)) + if (baseType.HasFullyQualifiedMetadataName(name)) { return true; } @@ -61,11 +62,11 @@ public static bool InheritsFromFullyQualifiedName(this ITypeSymbol typeSymbol, s /// The target instance to check. /// The full name of the type to check for interface implementation. /// Whether or not has an interface with the specified name. - public static bool HasInterfaceWithFullyQualifiedName(this ITypeSymbol typeSymbol, string name) + public static bool HasInterfaceWithFullyQualifiedMetadataName(this ITypeSymbol typeSymbol, string name) { foreach (INamedTypeSymbol interfaceType in typeSymbol.AllInterfaces) { - if (interfaceType.HasFullyQualifiedName(name)) + if (interfaceType.HasFullyQualifiedMetadataName(name)) { return true; } @@ -99,11 +100,11 @@ public static bool HasOrInheritsAttribute(this ITypeSymbol typeSymbol, FuncThe target instance to check. /// The name of the attribute to look for. /// Whether or not has an attribute with the specified type name. - public static bool HasOrInheritsAttributeWithFullyQualifiedName(this ITypeSymbol typeSymbol, string name) + public static bool HasOrInheritsAttributeWithFullyQualifiedMetadataName(this ITypeSymbol typeSymbol, string name) { for (ITypeSymbol? currentType = typeSymbol; currentType is not null; currentType = currentType.BaseType) { - if (currentType.HasAttributeWithFullyQualifiedName(name)) + if (currentType.HasAttributeWithFullyQualifiedMetadataName(name)) { return true; } @@ -119,13 +120,91 @@ public static bool HasOrInheritsAttributeWithFullyQualifiedName(this ITypeSymbol /// The target instance to check. /// The name of the attribute to look for. /// Whether or not has an attribute with the specified type name. - public static bool InheritsAttributeWithFullyQualifiedName(this ITypeSymbol typeSymbol, string name) + public static bool InheritsAttributeWithFullyQualifiedMetadataName(this ITypeSymbol typeSymbol, string name) { if (typeSymbol.BaseType is INamedTypeSymbol baseTypeSymbol) { - return HasOrInheritsAttributeWithFullyQualifiedName(baseTypeSymbol, name); + return HasOrInheritsAttributeWithFullyQualifiedMetadataName(baseTypeSymbol, name); } return false; } + + /// + /// Checks whether or not a given type symbol has a specified fully qualified metadata name. + /// + /// The input instance to check. + /// The full name to check. + /// Whether has a full name equals to . + public static bool HasFullyQualifiedMetadataName(this ITypeSymbol symbol, string name) + { + using ImmutableArrayBuilder builder = ImmutableArrayBuilder.Rent(); + + symbol.AppendFullyQualifiedMetadataName(in builder); + + return builder.WrittenSpan.SequenceEqual(name.AsSpan()); + } + + /// + /// Gets the fully qualified metadata name for a given instance. + /// + /// The input instance. + /// The fully qualified metadata name for . + public static string GetFullyQualifiedMetadataName(this ITypeSymbol symbol) + { + using ImmutableArrayBuilder builder = ImmutableArrayBuilder.Rent(); + + symbol.AppendFullyQualifiedMetadataName(in builder); + + return builder.ToString(); + } + + /// + /// Appends the fully qualified metadata name for a given symbol to a target builder. + /// + /// The input instance. + /// The target instance. + private static void AppendFullyQualifiedMetadataName(this ITypeSymbol symbol, in ImmutableArrayBuilder builder) + { + static void BuildFrom(ISymbol? symbol, in ImmutableArrayBuilder builder) + { + switch (symbol) + { + // Namespaces that are nested also append a leading '.' + case INamespaceSymbol { ContainingNamespace.IsGlobalNamespace: false }: + BuildFrom(symbol.ContainingNamespace, in builder); + builder.Add('.'); + builder.AddRange(symbol.MetadataName.AsSpan()); + break; + + // Other namespaces (ie. the one right before global) skip the leading '.' + case INamespaceSymbol { IsGlobalNamespace: false }: + builder.AddRange(symbol.MetadataName.AsSpan()); + break; + + // Types with no namespace just have their metadata name directly written + case ITypeSymbol { ContainingSymbol: INamespaceSymbol { IsGlobalNamespace: true } }: + builder.AddRange(symbol.MetadataName.AsSpan()); + break; + + // Types with a containing non-global namespace also append a leading '.' + case ITypeSymbol { ContainingSymbol: INamespaceSymbol namespaceSymbol }: + BuildFrom(namespaceSymbol, in builder); + builder.Add('.'); + builder.AddRange(symbol.MetadataName.AsSpan()); + break; + + // Nested types append a leading '+' + case ITypeSymbol { ContainingSymbol: ITypeSymbol typeSymbol }: + BuildFrom(typeSymbol, in builder); + builder.Add('+'); + builder.AddRange(symbol.MetadataName.AsSpan()); + break; + default: + break; + } + } + + BuildFrom(symbol, in builder); + } } diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Extensions/IncrementalGeneratorInitializationContextExtensions.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/IncrementalGeneratorInitializationContextExtensions.cs similarity index 63% rename from CommunityToolkit.Mvvm.SourceGenerators/Extensions/IncrementalGeneratorInitializationContextExtensions.cs rename to src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/IncrementalGeneratorInitializationContextExtensions.cs index 994518143..d009810eb 100644 --- a/CommunityToolkit.Mvvm.SourceGenerators/Extensions/IncrementalGeneratorInitializationContextExtensions.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/IncrementalGeneratorInitializationContextExtensions.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Immutable; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions; @@ -14,54 +13,6 @@ namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions; /// internal static class IncrementalGeneratorInitializationContextExtensions { - /// - /// Implements a gate for a language version over items in an input source. - /// - /// The type of items in the input source. - /// The input value being used. - /// The source instance. - /// The minimum language version to gate for. - /// The to emit if the gate detects invalid usage. - /// - /// Items in will be filtered out if the gate fails. If it passes, items will remain untouched. - /// - public static void FilterWithLanguageVersion( - this IncrementalGeneratorInitializationContext context, - ref IncrementalValuesProvider source, - LanguageVersion languageVersion, - DiagnosticDescriptor diagnosticDescriptor) - { - // Check whether the target language version is supported - IncrementalValueProvider isGeneratorSupported = - context.ParseOptionsProvider - .Select((item, _) => item is CSharpParseOptions options && options.LanguageVersion >= languageVersion); - - // Combine each data item with the supported flag - IncrementalValuesProvider<(T Data, bool IsGeneratorSupported)> dataWithSupportedInfo = - source - .Combine(isGeneratorSupported); - - // Get a marker node to show whether an invalid attribute is used - IncrementalValueProvider isUnsupportedAttributeUsed = - dataWithSupportedInfo - .Select(static (item, _) => item.IsGeneratorSupported) - .Where(static item => !item) - .Collect() - .Select(static (item, _) => item.Length > 0); - - // Report them to the output - context.RegisterConditionalSourceOutput(isUnsupportedAttributeUsed, context => - { - context.ReportDiagnostic(Diagnostic.Create(diagnosticDescriptor, null)); - }); - - // Only let data through if the minimum language version is supported - source = - dataWithSupportedInfo - .Where(static item => item.IsGeneratorSupported) - .Select(static (item, _) => item.Data); - } - /// /// Conditionally invokes /// if the value produced by the input is . diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/IncrementalValuesProviderExtensions.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/IncrementalValuesProviderExtensions.cs new file mode 100644 index 000000000..e12e60473 --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/IncrementalValuesProviderExtensions.cs @@ -0,0 +1,73 @@ +// 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. + +// This file is ported and adapted from ComputeSharp (Sergio0694/ComputeSharp), +// more info in ThirdPartyNotices.txt in the root of the project. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using CommunityToolkit.Mvvm.SourceGenerators.Helpers; +using Microsoft.CodeAnalysis; + +namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions; + +/// +/// Extension methods for . +/// +internal static class IncrementalValuesProviderExtensions +{ + /// + /// Groups items in a given sequence by a specified key. + /// + /// The type of left items in each tuple. + /// The type of right items in each tuple. + /// The type of resulting key elements. + /// The type of resulting projected elements. + /// The input instance. + /// The key selection . + /// The element selection . + /// An with the grouped results. + public static IncrementalValuesProvider<(TKey Key, EquatableArray Right)> GroupBy( + this IncrementalValuesProvider<(TLeft Left, TRight Right)> source, + Func<(TLeft Left, TRight Right), TKey> keySelector, + Func<(TLeft Left, TRight Right), TElement> elementSelector) + where TLeft : IEquatable + where TRight : IEquatable + where TKey : IEquatable + where TElement : IEquatable + { + return source.Collect().SelectMany((item, token) => + { + Dictionary.Builder> map = new(); + + foreach ((TLeft, TRight) pair in item) + { + TKey key = keySelector(pair); + TElement element = elementSelector(pair); + + if (!map.TryGetValue(key, out ImmutableArray.Builder builder)) + { + builder = ImmutableArray.CreateBuilder(); + + map.Add(key, builder); + } + + builder.Add(element); + } + + token.ThrowIfCancellationRequested(); + + ImmutableArray<(TKey Key, EquatableArray Elements)>.Builder result = + ImmutableArray.CreateBuilder<(TKey, EquatableArray)>(); + + foreach (KeyValuePair.Builder> entry in map) + { + result.Add((entry.Key, entry.Value.ToImmutable())); + } + + return result; + }); + } +} diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Extensions/MemberDeclarationSyntaxExtensions.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/MemberDeclarationSyntaxExtensions.cs similarity index 100% rename from CommunityToolkit.Mvvm.SourceGenerators/Extensions/MemberDeclarationSyntaxExtensions.cs rename to src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/MemberDeclarationSyntaxExtensions.cs diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Extensions/SyntaxNodeExtensions.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/SyntaxNodeExtensions.cs similarity index 100% rename from CommunityToolkit.Mvvm.SourceGenerators/Extensions/SyntaxNodeExtensions.cs rename to src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/SyntaxNodeExtensions.cs diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/TypeDeclarationSyntaxExtensions.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/TypeDeclarationSyntaxExtensions.cs new file mode 100644 index 000000000..a00130d1b --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/TypeDeclarationSyntaxExtensions.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions; + +/// +/// Extension methods for the type. +/// +internal static class TypeDeclarationSyntaxExtensions +{ + /// + /// Checks whether a given has or could possibly have any base types, using only syntax. + /// + /// The input instance to check. + /// Whether has or could possibly have any base types. + public static bool HasOrPotentiallyHasBaseTypes(this TypeDeclarationSyntax typeDeclaration) + { + // If the base types list is not empty, the type can definitely has implemented interfaces + if (typeDeclaration.BaseList is { Types.Count: > 0 }) + { + return true; + } + + // If the base types list is empty, check if the type is partial. If it is, it means + // that there could be another partial declaration with a non-empty base types list. + foreach (SyntaxToken modifier in typeDeclaration.Modifiers) + { + if (modifier.IsKind(SyntaxKind.PartialKeyword)) + { + return true; + } + } + + return false; + } + + /// + /// Checks whether a given has or could possibly have any attributes, using only syntax. + /// + /// The input instance to check. + /// Whether has or could possibly have any attributes. + public static bool HasOrPotentiallyHasAttributes(this TypeDeclarationSyntax typeDeclaration) + { + // If the type has any attributes lists, then clearly it can have attributes + if (typeDeclaration.AttributeLists.Count > 0) + { + return true; + } + + // If the declaration has no attribute lists, check if the type is partial. If it is, it means + // that there could be another partial declaration with some attribute lists over them. + foreach (SyntaxToken modifier in typeDeclaration.Modifiers) + { + if (modifier.IsKind(SyntaxKind.PartialKeyword)) + { + return true; + } + } + + return false; + } +} diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Helpers/EquatableArray{T}.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Helpers/EquatableArray{T}.cs new file mode 100644 index 000000000..f59be0884 --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Helpers/EquatableArray{T}.cs @@ -0,0 +1,202 @@ +// 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; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace CommunityToolkit.Mvvm.SourceGenerators.Helpers; + +/// +/// Extensions for . +/// +internal static class EquatableArray +{ + /// + /// Creates an instance from a given . + /// + /// The type of items in the input array. + /// The input instance. + /// An instance from a given . + public static EquatableArray AsEquatableArray(this ImmutableArray array) + where T : IEquatable + { + return new(array); + } +} + +/// +/// An imutable, equatable array. This is equivalent to but with value equality support. +/// +/// The type of values in the array. +internal readonly struct EquatableArray : IEquatable>, IEnumerable + where T : IEquatable +{ + /// + /// The underlying array. + /// + private readonly T[]? array; + + /// + /// Creates a new instance. + /// + /// The input to wrap. + public EquatableArray(ImmutableArray array) + { + this.array = Unsafe.As, T[]?>(ref array); + } + + /// + /// Gets a reference to an item at a specified position within the array. + /// + /// The index of the item to retrieve a reference to. + /// A reference to an item at a specified position within the array. + public ref readonly T this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref AsImmutableArray().ItemRef(index); + } + + /// + /// Gets a value indicating whether the current array is empty. + /// + public bool IsEmpty + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => AsImmutableArray().IsEmpty; + } + + /// + public bool Equals(EquatableArray array) + { + return AsSpan().SequenceEqual(array.AsSpan()); + } + + /// + public override bool Equals([NotNullWhen(true)] object? obj) + { + return obj is EquatableArray array && Equals(this, array); + } + + /// + public override int GetHashCode() + { + if (this.array is not T[] array) + { + return 0; + } + + HashCode hashCode = default; + + foreach (T item in array) + { + hashCode.Add(item); + } + + return hashCode.ToHashCode(); + } + + /// + /// Gets an instance from the current . + /// + /// The from the current . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ImmutableArray AsImmutableArray() + { + return Unsafe.As>(ref Unsafe.AsRef(in this.array)); + } + + /// + /// Creates an instance from a given . + /// + /// The input instance. + /// An instance from a given . + public static EquatableArray FromImmutableArray(ImmutableArray array) + { + return new(array); + } + + /// + /// Returns a wrapping the current items. + /// + /// A wrapping the current items. + public ReadOnlySpan AsSpan() + { + return AsImmutableArray().AsSpan(); + } + + /// + /// Copies the contents of this instance. to a mutable array. + /// + /// The newly instantiated array. + public T[] ToArray() + { + return AsImmutableArray().ToArray(); + } + + /// + /// Gets an value to traverse items in the current array. + /// + /// An value to traverse items in the current array. + public ImmutableArray.Enumerator GetEnumerator() + { + return AsImmutableArray().GetEnumerator(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)AsImmutableArray()).GetEnumerator(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)AsImmutableArray()).GetEnumerator(); + } + + /// + /// Implicitly converts an to . + /// + /// An instance from a given . + public static implicit operator EquatableArray(ImmutableArray array) + { + return FromImmutableArray(array); + } + + /// + /// Implicitly converts an to . + /// + /// An instance from a given . + public static implicit operator ImmutableArray(EquatableArray array) + { + return array.AsImmutableArray(); + } + + /// + /// Checks whether two values are the same. + /// + /// The first value. + /// The second value. + /// Whether and are equal. + public static bool operator ==(EquatableArray left, EquatableArray right) + { + return left.Equals(right); + } + + /// + /// Checks whether two values are not the same. + /// + /// The first value. + /// The second value. + /// Whether and are not equal. + public static bool operator !=(EquatableArray left, EquatableArray right) + { + return !left.Equals(right); + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Helpers/HashCode.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Helpers/HashCode.cs new file mode 100644 index 000000000..bd615271c --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Helpers/HashCode.cs @@ -0,0 +1,185 @@ +// 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.ComponentModel; +using System.Runtime.CompilerServices; +using System.Security.Cryptography; + +#pragma warning disable CS0809 + +namespace System; + +/// +/// A polyfill type that mirrors some methods from on .NET 6. +/// +internal struct HashCode +{ + private const uint Prime1 = 2654435761U; + private const uint Prime2 = 2246822519U; + private const uint Prime3 = 3266489917U; + private const uint Prime4 = 668265263U; + private const uint Prime5 = 374761393U; + + private static readonly uint seed = GenerateGlobalSeed(); + + private uint v1, v2, v3, v4; + private uint queue1, queue2, queue3; + private uint length; + + /// + /// Initializes the default seed. + /// + /// A random seed. + private static unsafe uint GenerateGlobalSeed() + { + byte[] bytes = new byte[4]; + + RandomNumberGenerator.Create().GetBytes(bytes); + + return BitConverter.ToUInt32(bytes, 0); + } + + /// + /// Adds a single value to the current hash. + /// + /// The type of the value to add into the hash code. + /// The value to add into the hash code. + public void Add(T value) + { + Add(value?.GetHashCode() ?? 0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Initialize(out uint v1, out uint v2, out uint v3, out uint v4) + { + v1 = seed + Prime1 + Prime2; + v2 = seed + Prime2; + v3 = seed; + v4 = seed - Prime1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint Round(uint hash, uint input) + { + return RotateLeft(hash + input * Prime2, 13) * Prime1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint QueueRound(uint hash, uint queuedValue) + { + return RotateLeft(hash + queuedValue * Prime3, 17) * Prime4; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint MixState(uint v1, uint v2, uint v3, uint v4) + { + return RotateLeft(v1, 1) + RotateLeft(v2, 7) + RotateLeft(v3, 12) + RotateLeft(v4, 18); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint MixEmptyState() + { + return seed + Prime5; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint MixFinal(uint hash) + { + hash ^= hash >> 15; + hash *= Prime2; + hash ^= hash >> 13; + hash *= Prime3; + hash ^= hash >> 16; + + return hash; + } + + private void Add(int value) + { + uint val = (uint)value; + uint previousLength = this.length++; + uint position = previousLength % 4; + + if (position == 0) + { + this.queue1 = val; + } + else if (position == 1) + { + this.queue2 = val; + } + else if (position == 2) + { + this.queue3 = val; + } + else + { + if (previousLength == 3) + { + Initialize(out this.v1, out this.v2, out this.v3, out this.v4); + } + + this.v1 = Round(this.v1, this.queue1); + this.v2 = Round(this.v2, this.queue2); + this.v3 = Round(this.v3, this.queue3); + this.v4 = Round(this.v4, val); + } + } + + /// + /// Gets the resulting hashcode from the current instance. + /// + /// The resulting hashcode from the current instance. + public int ToHashCode() + { + uint length = this.length; + uint position = length % 4; + uint hash = length < 4 ? MixEmptyState() : MixState(this.v1, this.v2, this.v3, this.v4); + + hash += length * 4; + + if (position > 0) + { + hash = QueueRound(hash, this.queue1); + + if (position > 1) + { + hash = QueueRound(hash, this.queue2); + + if (position > 2) + { + hash = QueueRound(hash, this.queue3); + } + } + } + + hash = MixFinal(hash); + + return (int)hash; + } + + /// + [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes. Use ToHashCode to retrieve the computed hash code.", error: true)] + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => throw new NotSupportedException(); + + /// + [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes.", error: true)] + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) => throw new NotSupportedException(); + + /// + /// Rotates the specified value left by the specified number of bits. + /// Similar in behavior to the x86 instruction ROL. + /// + /// The value to rotate. + /// The number of bits to rotate by. + /// Any value outside the range [0..31] is treated as congruent mod 32. + /// The rotated value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint RotateLeft(uint value, int offset) + { + return (value << offset) | (value >> (32 - offset)); + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Helpers/ImmutableArrayBuilder{T}.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Helpers/ImmutableArrayBuilder{T}.cs new file mode 100644 index 000000000..b7982d70e --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Helpers/ImmutableArrayBuilder{T}.cs @@ -0,0 +1,235 @@ +// 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. + +// This file is ported and adapted from ComputeSharp (Sergio0694/ComputeSharp), +// more info in ThirdPartyNotices.txt in the root of the project. + +using System; +using System.Collections.Immutable; +using System.Runtime.CompilerServices; + +namespace CommunityToolkit.Mvvm.SourceGenerators.Helpers; + +/// +/// A helper type to build sequences of values with pooled buffers. +/// +/// The type of items to create sequences for. +internal struct ImmutableArrayBuilder : IDisposable +{ + /// + /// The shared instance to share objects. + /// + private static readonly ObjectPool SharedObjectPool = new(static () => new Writer()); + + /// + /// The rented instance to use. + /// + private Writer? writer; + + /// + /// Creates a value with a pooled underlying data writer. + /// + /// A instance to write data to. + public static ImmutableArrayBuilder Rent() + { + return new(SharedObjectPool.Allocate()); + } + + /// + /// Creates a new object with the specified parameters. + /// + /// The target data writer to use. + private ImmutableArrayBuilder(Writer writer) + { + this.writer = writer; + } + + /// + public int Count + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.writer!.Count; + } + + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + public readonly ReadOnlySpan WrittenSpan + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.writer!.WrittenSpan; + } + + /// + public readonly void Add(T item) + { + this.writer!.Add(item); + } + + /// + /// Adds the specified items to the end of the array. + /// + /// The items to add at the end of the array. + public readonly void AddRange(ReadOnlySpan items) + { + this.writer!.AddRange(items); + } + + /// + public readonly ImmutableArray ToImmutable() + { + T[] array = this.writer!.WrittenSpan.ToArray(); + + return Unsafe.As>(ref array); + } + + /// + public readonly T[] ToArray() + { + return this.writer!.WrittenSpan.ToArray(); + } + + /// + public override readonly string ToString() + { + return this.writer!.WrittenSpan.ToString(); + } + + /// + public void Dispose() + { + Writer? writer = this.writer; + + this.writer = null; + + if (writer is not null) + { + writer.Clear(); + + SharedObjectPool.Free(writer); + } + } + + /// + /// A class handling the actual buffer writing. + /// + private sealed class Writer + { + /// + /// The underlying array. + /// + private T[] array; + + /// + /// The starting offset within . + /// + private int index; + + /// + /// Creates a new instance with the specified parameters. + /// + public Writer() + { + if (typeof(T) == typeof(char)) + { + this.array = new T[1024]; + } + else + { + this.array = new T[8]; + } + + this.index = 0; + } + + /// + public int Count + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.index; + } + + /// + public ReadOnlySpan WrittenSpan + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(this.array, 0, this.index); + } + + /// + public void Add(T value) + { + EnsureCapacity(1); + + this.array[this.index++] = value; + } + + /// + public void AddRange(ReadOnlySpan items) + { + EnsureCapacity(items.Length); + + items.CopyTo(this.array.AsSpan(this.index)); + + this.index += items.Length; + } + + /// + /// Clears the items in the current writer. + /// + public void Clear() + { + if (typeof(T) != typeof(char)) + { + this.array.AsSpan(0, this.index).Clear(); + } + + this.index = 0; + } + + /// + /// Ensures that has enough free space to contain a given number of new items. + /// + /// The minimum number of items to ensure space for in . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void EnsureCapacity(int requestedSize) + { + if (requestedSize > this.array.Length - this.index) + { + ResizeBuffer(requestedSize); + } + } + + /// + /// Resizes to ensure it can fit the specified number of new items. + /// + /// The minimum number of items to ensure space for in . + [MethodImpl(MethodImplOptions.NoInlining)] + private void ResizeBuffer(int sizeHint) + { + int minimumSize = this.index + sizeHint; + int requestedSize = Math.Max(this.array.Length * 2, minimumSize); + + T[] newArray = new T[requestedSize]; + + Array.Copy(this.array, newArray, this.index); + + this.array = newArray; + } + } +} + +/// +/// Private helpers for the type. +/// +file static class ImmutableArrayBuilder +{ + /// + /// Throws an for "index". + /// + public static void ThrowArgumentOutOfRangeExceptionForIndex() + { + throw new ArgumentOutOfRangeException("index"); + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Helpers/ObjectPool{T}.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Helpers/ObjectPool{T}.cs new file mode 100644 index 000000000..73e26d429 --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Helpers/ObjectPool{T}.cs @@ -0,0 +1,163 @@ +// 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. + +// Ported from Roslyn, see: https://github.com/dotnet/roslyn/blob/main/src/Dependencies/PooledObjects/ObjectPool%601.cs. + +using System; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace CommunityToolkit.Mvvm.SourceGenerators.Helpers; + +/// +/// +/// Generic implementation of object pooling pattern with predefined pool size limit. The main purpose +/// is that limited number of frequently used objects can be kept in the pool for further recycling. +/// +/// +/// Notes: +/// +/// +/// It is not the goal to keep all returned objects. Pool is not meant for storage. If there +/// is no space in the pool, extra returned objects will be dropped. +/// +/// +/// It is implied that if object was obtained from a pool, the caller will return it back in +/// a relatively short time. Keeping checked out objects for long durations is ok, but +/// reduces usefulness of pooling. Just new up your own. +/// +/// +/// +/// +/// Not returning objects to the pool in not detrimental to the pool's work, but is a bad practice. +/// Rationale: if there is no intent for reusing the object, do not use pool - just use "new". +/// +/// +/// The type of objects to pool. +internal sealed class ObjectPool + where T : class +{ + /// + /// The factory is stored for the lifetime of the pool. We will call this only when pool needs to + /// expand. compared to "new T()", Func gives more flexibility to implementers and faster than "new T()". + /// + private readonly Func factory; + + /// + /// The array of cached items. + /// + private readonly Element[] items; + + /// + /// Storage for the pool objects. The first item is stored in a dedicated field + /// because we expect to be able to satisfy most requests from it. + /// + private T? firstItem; + + /// + /// Creates a new instance with the specified parameters. + /// + /// The input factory to produce items. + public ObjectPool(Func factory) + : this(factory, Environment.ProcessorCount * 2) + { + } + + /// + /// Creates a new instance with the specified parameters. + /// + /// The input factory to produce items. + /// The pool size to use. + public ObjectPool(Func factory, int size) + { + this.factory = factory; + this.items = new Element[size - 1]; + } + + /// + /// Produces a instance. + /// + /// The returned item to use. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Allocate() + { + T? item = this.firstItem; + + if (item is null || item != Interlocked.CompareExchange(ref this.firstItem, null, item)) + { + item = AllocateSlow(); + } + + return item; + } + + /// + /// Returns a given instance to the pool. + /// + /// The instance to return. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Free(T obj) + { + if (this.firstItem is null) + { + this.firstItem = obj; + } + else + { + FreeSlow(obj); + } + } + + /// + /// Allocates a new item. + /// + /// The returned item to use. + [MethodImpl(MethodImplOptions.NoInlining)] + private T AllocateSlow() + { + foreach (ref Element element in this.items.AsSpan()) + { + T? instance = element.Value; + + if (instance is not null) + { + if (instance == Interlocked.CompareExchange(ref element.Value, null, instance)) + { + return instance; + } + } + } + + return this.factory(); + } + + /// + /// Frees a given item. + /// + /// The item to return to the pool. + [MethodImpl(MethodImplOptions.NoInlining)] + private void FreeSlow(T obj) + { + foreach (ref Element element in this.items.AsSpan()) + { + if (element.Value is null) + { + element.Value = obj; + + break; + } + } + } + + /// + /// A container for a produced item (using a wrapper to avoid covariance checks). + /// + private struct Element + { + /// + /// The value held at the current element. + /// + internal T? Value; + } +} \ No newline at end of file diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Input/Models/CanExecuteExpressionType.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Input/Models/CanExecuteExpressionType.cs similarity index 100% rename from CommunityToolkit.Mvvm.SourceGenerators/Input/Models/CanExecuteExpressionType.cs rename to src/CommunityToolkit.Mvvm.SourceGenerators/Input/Models/CanExecuteExpressionType.cs diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Input/Models/CommandInfo.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Input/Models/CommandInfo.cs new file mode 100644 index 000000000..0721cb966 --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Input/Models/CommandInfo.cs @@ -0,0 +1,38 @@ +// 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 CommunityToolkit.Mvvm.SourceGenerators.Helpers; + +namespace CommunityToolkit.Mvvm.SourceGenerators.Input.Models; + +/// +/// A model with gathered info on a given command method. +/// +/// The name of the target method. +/// The resulting field name for the generated command. +/// The resulting property name for the generated command. +/// The command interface type name. +/// The command class type name. +/// The delegate type name for the wrapped method. +/// The type arguments for and , if any. +/// The type arguments for , if any. +/// The member name for the can execute check, if available. +/// The can execute expression type, if available. +/// Whether or not concurrent executions have been enabled. +/// Whether or not exceptions should flow to the task scheduler. +/// Whether or not to also generate a cancel command. +internal sealed record CommandInfo( + string MethodName, + string FieldName, + string PropertyName, + string CommandInterfaceType, + string CommandClassType, + string DelegateType, + EquatableArray CommandTypeArguments, + EquatableArray DelegateTypeArguments, + string? CanExecuteMemberName, + CanExecuteExpressionType? CanExecuteExpressionType, + bool AllowConcurrentExecutions, + bool FlowExceptionsToTaskScheduler, + bool IncludeCancelCommand); diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Input/RelayCommandGenerator.Execute.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Input/RelayCommandGenerator.Execute.cs similarity index 93% rename from CommunityToolkit.Mvvm.SourceGenerators/Input/RelayCommandGenerator.Execute.cs rename to src/CommunityToolkit.Mvvm.SourceGenerators/Input/RelayCommandGenerator.Execute.cs index 3fc4f5106..89b852953 100644 --- a/CommunityToolkit.Mvvm.SourceGenerators/Input/RelayCommandGenerator.Execute.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Input/RelayCommandGenerator.Execute.cs @@ -7,9 +7,10 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; -using CommunityToolkit.Mvvm.SourceGenerators.Diagnostics; using CommunityToolkit.Mvvm.SourceGenerators.Extensions; +using CommunityToolkit.Mvvm.SourceGenerators.Helpers; using CommunityToolkit.Mvvm.SourceGenerators.Input.Models; +using CommunityToolkit.Mvvm.SourceGenerators.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -27,15 +28,20 @@ partial class RelayCommandGenerator internal static class Execute { /// - /// Processes a given target method. + /// Processes a given annotated methods and produces command info, if possible. /// /// The input instance to process. /// The instance the method was annotated with. + /// The resulting instance, if successfully generated. /// The resulting diagnostics from the processing operation. - /// The resulting instance for , if available. - public static CommandInfo? GetInfo(IMethodSymbol methodSymbol, AttributeData attributeData, out ImmutableArray diagnostics) + /// Whether a instance could be generated successfully. + public static bool TryGetInfo( + IMethodSymbol methodSymbol, + AttributeData attributeData, + [NotNullWhen(true)] out CommandInfo? commandInfo, + out ImmutableArray diagnostics) { - ImmutableArray.Builder builder = ImmutableArray.CreateBuilder(); + using ImmutableArrayBuilder builder = ImmutableArrayBuilder.Rent(); // Validate the method definition is unique if (!IsCommandDefinitionUnique(methodSymbol, builder)) @@ -49,7 +55,7 @@ internal static class Execute // Get the command type symbols if (!TryMapCommandTypesFromMethod( methodSymbol, - builder, + in builder, out string? commandInterfaceType, out string? commandClassType, out string? delegateType, @@ -66,7 +72,7 @@ internal static class Execute methodSymbol, attributeData, commandClassType, - builder, + in builder, out bool allowConcurrentExecutions)) { goto Failure; @@ -77,7 +83,7 @@ internal static class Execute methodSymbol, attributeData, commandClassType, - builder, + in builder, out bool flowExceptionsToTaskScheduler)) { goto Failure; @@ -88,7 +94,7 @@ internal static class Execute methodSymbol, attributeData, commandTypeArguments, - builder, + in builder, out string? canExecuteMemberName, out CanExecuteExpressionType? canExecuteExpressionType)) { @@ -101,15 +107,13 @@ internal static class Execute attributeData, commandClassType, supportsCancellation, - builder, + in builder, out bool generateCancelCommand)) { goto Failure; } - diagnostics = builder.ToImmutable(); - - return new( + commandInfo = new CommandInfo( methodSymbol.Name, fieldName, propertyName, @@ -124,10 +128,15 @@ internal static class Execute flowExceptionsToTaskScheduler, generateCancelCommand); + diagnostics = builder.ToImmutable(); + + return true; + Failure: + commandInfo = null; diagnostics = builder.ToImmutable(); - return null; + return false; } /// @@ -170,7 +179,7 @@ public static ImmutableArray GetSyntax(CommandInfo comm .WithOpenBracketToken(Token(TriviaList(Comment($"/// The backing field for .")), SyntaxKind.OpenBracketToken, TriviaList()))); // Prepares the argument to pass the underlying method to invoke - ImmutableArray.Builder commandCreationArguments = ImmutableArray.CreateBuilder(); + using ImmutableArrayBuilder commandCreationArguments = ImmutableArrayBuilder.Rent(); // The first argument is the execute method, which is always present commandCreationArguments.Add( @@ -354,7 +363,7 @@ public static ImmutableArray GetSyntax(CommandInfo comm /// The input instance to process. /// The current collection of gathered diagnostics. /// Whether or not was unique within its containing type. - private static bool IsCommandDefinitionUnique(IMethodSymbol methodSymbol, ImmutableArray.Builder diagnostics) + private static bool IsCommandDefinitionUnique(IMethodSymbol methodSymbol, in ImmutableArrayBuilder diagnostics) { // If a duplicate is present in any of the base types, always emit a diagnostic for the current method. // That is, there is no need to check the order: we assume the priority is top-down in the type hierarchy. @@ -362,7 +371,7 @@ private static bool IsCommandDefinitionUnique(IMethodSymbol methodSymbol, Immuta foreach (ISymbol symbol in methodSymbol.ContainingType.BaseType?.GetAllMembers(methodSymbol.Name) ?? Enumerable.Empty()) { if (symbol is IMethodSymbol otherSymbol && - otherSymbol.HasAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.Input.RelayCommandAttribute")) + otherSymbol.HasAttributeWithFullyQualifiedMetadataName("CommunityToolkit.Mvvm.Input.RelayCommandAttribute")) { diagnostics.Add( MultipleRelayCommandMethodOverloadsError, @@ -378,7 +387,7 @@ private static bool IsCommandDefinitionUnique(IMethodSymbol methodSymbol, Immuta foreach (ISymbol symbol in methodSymbol.ContainingType.GetMembers(methodSymbol.Name)) { if (symbol is IMethodSymbol otherSymbol && - otherSymbol.HasAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.Input.RelayCommandAttribute")) + otherSymbol.HasAttributeWithFullyQualifiedMetadataName("CommunityToolkit.Mvvm.Input.RelayCommandAttribute")) { // If the first [RelayCommand] overload is the current symbol, return immediately. This makes it so // that if multiple overloads are present, only the ones after the first declared one will have @@ -422,15 +431,24 @@ public static (string FieldName, string PropertyName) GetGeneratedFieldAndProper // Strip the "Async" suffix for methods returning a Task type if (methodSymbol.Name.EndsWith("Async") && - (methodSymbol.ReturnType.HasFullyQualifiedName("global::System.Threading.Tasks.Task") || - methodSymbol.ReturnType.InheritsFromFullyQualifiedName("global::System.Threading.Tasks.Task"))) + methodSymbol.ReturnType.HasOrInheritsFromFullyQualifiedMetadataName("System.Threading.Tasks.Task")) { propertyName = propertyName.Substring(0, propertyName.Length - "Async".Length); } propertyName += "Command"; - string fieldName = $"{char.ToLower(propertyName[0], CultureInfo.InvariantCulture)}{propertyName.Substring(1)}"; + char firstCharacter = propertyName[0]; + char loweredFirstCharacter = char.ToLower(firstCharacter, CultureInfo.InvariantCulture); + + // The field name is generated depending on whether the first character can be lowered: + // - If it can, then the field name is just the property name starting in lowercase. + // - If it can't (eg. starts with '中'), then the '_' prefix is added to the property name. + string fieldName = (firstCharacter == loweredFirstCharacter) switch + { + true => $"_{propertyName}", + false => $"{loweredFirstCharacter}{propertyName.Substring(1)}" + }; return (fieldName, propertyName); } @@ -450,7 +468,7 @@ public static (string FieldName, string PropertyName) GetGeneratedFieldAndProper /// Whether or not was valid and the requested types have been set. private static bool TryMapCommandTypesFromMethod( IMethodSymbol methodSymbol, - ImmutableArray.Builder diagnostics, + in ImmutableArrayBuilder diagnostics, [NotNullWhen(true)] out string? commandInterfaceType, [NotNullWhen(true)] out string? commandClassType, [NotNullWhen(true)] out string? delegateType, @@ -490,8 +508,7 @@ private static bool TryMapCommandTypesFromMethod( } // Map all Task-returning methods - if (methodSymbol.ReturnType.HasFullyQualifiedName("global::System.Threading.Tasks.Task") || - methodSymbol.ReturnType.InheritsFromFullyQualifiedName("global::System.Threading.Tasks.Task")) + if (methodSymbol.ReturnType.HasOrInheritsFromFullyQualifiedMetadataName("System.Threading.Tasks.Task")) { // Map to IAsyncRelayCommand, AsyncRelayCommand, Func if (methodSymbol.Parameters.Length == 0) @@ -511,7 +528,7 @@ private static bool TryMapCommandTypesFromMethod( methodSymbol.Parameters[0] is IParameterSymbol { RefKind: RefKind.None, Type: { IsRefLikeType: false, TypeKind: not TypeKind.Pointer and not TypeKind.FunctionPointer } } singleParameter) { // Map to IAsyncRelayCommand, AsyncRelayCommand, Func - if (singleParameter.Type.HasFullyQualifiedName("global::System.Threading.CancellationToken")) + if (singleParameter.Type.HasFullyQualifiedMetadataName("System.Threading.CancellationToken")) { commandInterfaceType = "global::CommunityToolkit.Mvvm.Input.IAsyncRelayCommand"; commandClassType = "global::CommunityToolkit.Mvvm.Input.AsyncRelayCommand"; @@ -540,7 +557,7 @@ private static bool TryMapCommandTypesFromMethod( if (methodSymbol.Parameters.Length == 2 && methodSymbol.Parameters[0] is IParameterSymbol { RefKind: RefKind.None, Type: { IsRefLikeType: false, TypeKind: not TypeKind.Pointer and not TypeKind.FunctionPointer } } firstParameter && methodSymbol.Parameters[1] is IParameterSymbol { RefKind: RefKind.None, Type: { IsRefLikeType: false, TypeKind: not TypeKind.Pointer and not TypeKind.FunctionPointer } } secondParameter && - secondParameter.Type.HasFullyQualifiedName("global::System.Threading.CancellationToken")) + secondParameter.Type.HasFullyQualifiedMetadataName("System.Threading.CancellationToken")) { commandInterfaceType = "global::CommunityToolkit.Mvvm.Input.IAsyncRelayCommand"; commandClassType = "global::CommunityToolkit.Mvvm.Input.AsyncRelayCommand"; @@ -580,7 +597,7 @@ private static bool TryGetAllowConcurrentExecutionsSwitch( IMethodSymbol methodSymbol, AttributeData attributeData, string commandClassType, - ImmutableArray.Builder diagnostics, + in ImmutableArrayBuilder diagnostics, out bool allowConcurrentExecutions) { // Try to get the custom switch for concurrent executions (the default is false) @@ -618,7 +635,7 @@ private static bool TryGetFlowExceptionsToTaskSchedulerSwitch( IMethodSymbol methodSymbol, AttributeData attributeData, string commandClassType, - ImmutableArray.Builder diagnostics, + in ImmutableArrayBuilder diagnostics, out bool flowExceptionsToTaskScheduler) { // Try to get the custom switch for task scheduler exception flow (the default is false) @@ -657,7 +674,7 @@ private static bool TryGetIncludeCancelCommandSwitch( AttributeData attributeData, string commandClassType, bool supportsCancellation, - ImmutableArray.Builder diagnostics, + in ImmutableArrayBuilder diagnostics, out bool generateCancelCommand) { // Try to get the custom switch for cancel command generation (the default is false) @@ -697,7 +714,7 @@ private static bool TryGetCanExecuteExpressionType( IMethodSymbol methodSymbol, AttributeData attributeData, ImmutableArray commandTypeArguments, - ImmutableArray.Builder diagnostics, + in ImmutableArrayBuilder diagnostics, out string? canExecuteMemberName, out CanExecuteExpressionType? canExecuteExpressionType) { @@ -768,7 +785,7 @@ private static bool TryGetCanExecuteExpressionFromSymbol( if (canExecuteSymbol is IMethodSymbol canExecuteMethodSymbol) { // The return type must always be a bool - if (!canExecuteMethodSymbol.ReturnType.HasFullyQualifiedName("bool")) + if (canExecuteMethodSymbol.ReturnType is not { SpecialType: SpecialType.System_Boolean }) { goto Failure; } @@ -803,7 +820,7 @@ private static bool TryGetCanExecuteExpressionFromSymbol( else if (canExecuteSymbol is IPropertySymbol { GetMethod: not null } canExecutePropertySymbol) { // The property type must always be a bool - if (!canExecutePropertySymbol.Type.HasFullyQualifiedName("bool")) + if (canExecutePropertySymbol.Type is not { SpecialType: SpecialType.System_Boolean }) { goto Failure; } @@ -843,8 +860,7 @@ private static bool TryGetCanExecuteMemberFromGeneratedProperty( foreach (ISymbol memberSymbol in containingType.GetAllMembers()) { // Only look for instance fields of bool type - if (memberSymbol is not IFieldSymbol { IsStatic: false } fieldSymbol || - !fieldSymbol.Type.HasFullyQualifiedName("bool")) + if (memberSymbol is not IFieldSymbol { IsStatic: false, Type.SpecialType: SpecialType.System_Boolean } fieldSymbol) { continue; } @@ -853,8 +869,8 @@ private static bool TryGetCanExecuteMemberFromGeneratedProperty( // Only filter fields with the [ObservableProperty] attribute if (memberSymbol is IFieldSymbol && - !attributes.Any(static a => a.AttributeClass?.HasFullyQualifiedName( - "global::CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute") == true)) + !attributes.Any(static a => a.AttributeClass?.HasFullyQualifiedMetadataName( + "CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute") == true)) { continue; } diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Input/RelayCommandGenerator.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Input/RelayCommandGenerator.cs new file mode 100644 index 000000000..78bdb2294 --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Input/RelayCommandGenerator.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using CommunityToolkit.Mvvm.SourceGenerators.Extensions; +using CommunityToolkit.Mvvm.SourceGenerators.Input.Models; +using CommunityToolkit.Mvvm.SourceGenerators.Models; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace CommunityToolkit.Mvvm.SourceGenerators; + +/// +/// A source generator for generating command properties from annotated methods. +/// +[Generator(LanguageNames.CSharp)] +public sealed partial class RelayCommandGenerator : IIncrementalGenerator +{ + /// + public void Initialize(IncrementalGeneratorInitializationContext context) + { + // Gather info for all annotated command methods (starting from method declarations with at least one attribute) + IncrementalValuesProvider<(HierarchyInfo Hierarchy, Result Info)> commandInfoWithErrors = + context.SyntaxProvider + .ForAttributeWithMetadataName( + "CommunityToolkit.Mvvm.Input.RelayCommandAttribute", + static (node, _) => node is MethodDeclarationSyntax { Parent: ClassDeclarationSyntax, AttributeLists.Count: > 0 }, + static (context, token) => + { + if (!context.SemanticModel.Compilation.HasLanguageVersionAtLeastEqualTo(LanguageVersion.CSharp8)) + { + return default; + } + + IMethodSymbol methodSymbol = (IMethodSymbol)context.TargetSymbol; + + // Get the hierarchy info for the target symbol, and try to gather the command info + HierarchyInfo? hierarchy = HierarchyInfo.From(methodSymbol.ContainingType); + + _ = Execute.TryGetInfo(methodSymbol, context.Attributes[0], out CommandInfo? commandInfo, out ImmutableArray diagnostics); + + return (Hierarchy: hierarchy, new Result(commandInfo, diagnostics)); + }) + .Where(static item => item.Hierarchy is not null)!; + + // Output the diagnostics + context.ReportDiagnostics(commandInfoWithErrors.Select(static (item, _) => item.Info.Errors)); + + // Get the filtered sequence to enable caching + IncrementalValuesProvider<(HierarchyInfo Hierarchy, Result Info)> commandInfo = + commandInfoWithErrors + .Where(static item => item.Info.Value is not null)!; + + // Generate the commands + context.RegisterSourceOutput(commandInfo, static (context, item) => + { + ImmutableArray memberDeclarations = Execute.GetSyntax(item.Info.Value); + CompilationUnitSyntax compilationUnit = item.Hierarchy.GetCompilationUnit(memberDeclarations); + + context.AddSource($"{item.Hierarchy.FilenameHint}.{item.Info.Value.MethodName}.g.cs", compilationUnit.GetText(Encoding.UTF8)); + }); + } +} diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.Execute.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.Execute.cs similarity index 93% rename from CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.Execute.cs rename to src/CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.Execute.cs index a9c13cab8..6a50d4b62 100644 --- a/CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.Execute.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.Execute.cs @@ -5,7 +5,8 @@ using System.Collections.Immutable; using System.Linq; using CommunityToolkit.Mvvm.SourceGenerators.Extensions; -using CommunityToolkit.Mvvm.SourceGenerators.Input.Models; +using CommunityToolkit.Mvvm.SourceGenerators.Helpers; +using CommunityToolkit.Mvvm.SourceGenerators.Messaging.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -28,12 +29,11 @@ private static class Execute /// An array of interface type symbols. public static ImmutableArray GetInterfaces(INamedTypeSymbol typeSymbol) { - ImmutableArray.Builder iRecipientInterfaces = ImmutableArray.CreateBuilder(); + using ImmutableArrayBuilder iRecipientInterfaces = ImmutableArrayBuilder.Rent(); foreach (INamedTypeSymbol interfaceSymbol in typeSymbol.AllInterfaces) { - if (interfaceSymbol.MetadataName is "IRecipient`1" && - interfaceSymbol.OriginalDefinition.HasFullyQualifiedName("global::CommunityToolkit.Mvvm.Messaging.IRecipient")) + if (interfaceSymbol.HasFullyQualifiedMetadataName("CommunityToolkit.Mvvm.Messaging.IRecipient`1")) { iRecipientInterfaces.Add(interfaceSymbol); } @@ -50,7 +50,7 @@ public static ImmutableArray GetInterfaces(INamedTypeSymbol ty /// A instance for the current type being inspected. public static RecipientInfo GetInfo(INamedTypeSymbol typeSymbol, ImmutableArray interfaceSymbols) { - ImmutableArray.Builder names = ImmutableArray.CreateBuilder(interfaceSymbols.Length); + using ImmutableArrayBuilder names = ImmutableArrayBuilder.Rent(); foreach (INamedTypeSymbol interfaceSymbol in interfaceSymbols) { @@ -58,9 +58,9 @@ public static RecipientInfo GetInfo(INamedTypeSymbol typeSymbol, ImmutableArray< } return new( - typeSymbol.GetFullMetadataNameForFileName(), + typeSymbol.GetFullyQualifiedMetadataName(), typeSymbol.GetFullyQualifiedName(), - names.MoveToImmutable()); + names.ToImmutable()); } /// @@ -70,8 +70,7 @@ public static RecipientInfo GetInfo(INamedTypeSymbol typeSymbol, ImmutableArray< /// The head instance with the type attributes. public static CompilationUnitSyntax GetSyntax(bool isDynamicallyAccessedMembersAttributeAvailable) { - int numberOfAttributes = 5 + (isDynamicallyAccessedMembersAttributeAvailable ? 1 : 0); - ImmutableArray.Builder attributes = ImmutableArray.CreateBuilder(numberOfAttributes); + using ImmutableArrayBuilder attributes = ImmutableArrayBuilder.Rent(); // Prepare the base attributes with are always present: // @@ -129,7 +128,7 @@ public static CompilationUnitSyntax GetSyntax(bool isDynamicallyAccessedMembersA Token(SyntaxKind.InternalKeyword), Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.PartialKeyword)) - .AddAttributeLists(attributes.MoveToImmutable().ToArray()))) + .AddAttributeLists(attributes.ToArray()))) .NormalizeWhitespace(); } @@ -284,7 +283,7 @@ public static CompilationUnitSyntax GetSyntax(RecipientInfo recipientInfo) /// The sequence of instances to register message handlers. private static ImmutableArray EnumerateRegistrationStatements(RecipientInfo recipientInfo) { - ImmutableArray.Builder statements = ImmutableArray.CreateBuilder(recipientInfo.MessageTypes.Length); + using ImmutableArrayBuilder statements = ImmutableArrayBuilder.Rent(); // This loop produces a sequence of statements as follows: // @@ -305,7 +304,7 @@ private static ImmutableArray EnumerateRegistrationStatements(R .AddArgumentListArguments(Argument(IdentifierName("recipient"))))); } - return statements.MoveToImmutable(); + return statements.ToImmutable(); } /// @@ -315,7 +314,7 @@ private static ImmutableArray EnumerateRegistrationStatements(R /// The sequence of instances to register message handlers. private static ImmutableArray EnumerateRegistrationStatementsWithTokens(RecipientInfo recipientInfo) { - ImmutableArray.Builder statements = ImmutableArray.CreateBuilder(recipientInfo.MessageTypes.Length); + using ImmutableArrayBuilder statements = ImmutableArrayBuilder.Rent(); // This loop produces a sequence of statements as follows: // @@ -336,7 +335,7 @@ private static ImmutableArray EnumerateRegistrationStatementsWi .AddArgumentListArguments(Argument(IdentifierName("recipient")), Argument(IdentifierName("token"))))); } - return statements.MoveToImmutable(); + return statements.ToImmutable(); } } } diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.cs similarity index 56% rename from CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.cs rename to src/CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.cs index dcb5f6633..03d8319e4 100644 --- a/CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.cs @@ -6,7 +6,7 @@ using System.Linq; using System.Text; using CommunityToolkit.Mvvm.SourceGenerators.Extensions; -using CommunityToolkit.Mvvm.SourceGenerators.Input.Models; +using CommunityToolkit.Mvvm.SourceGenerators.Messaging.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -22,30 +22,46 @@ public sealed partial class IMessengerRegisterAllGenerator : IIncrementalGenerat /// public void Initialize(IncrementalGeneratorInitializationContext context) { - // Get all class declarations. This pipeline step also needs to filter out duplicate recipient - // definitions (it might happen if a recipient has partial declarations). To do this, all pairs - // of class declarations and associated symbols are gathered, and then only the pair where the - // class declaration is the first syntax reference for the associated symbol is kept. - // Just like with the ObservableValidator generator, we also intentionally skip abstract types. - IncrementalValuesProvider typeSymbols = + // Get the recipient info for all target types + IncrementalValuesProvider recipientInfo = context.SyntaxProvider .CreateSyntaxProvider( - static (node, _) => node is ClassDeclarationSyntax, - static (context, _) => (context.Node, Symbol: (INamedTypeSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node)!)) - .Where(static item => item.Symbol is { IsAbstract: false, IsGenericType: false } && item.Node.IsFirstSyntaxDeclarationForSymbol(item.Symbol)) - .Select(static (item, _) => item.Symbol); + static (node, _) => node is ClassDeclarationSyntax classDeclaration && classDeclaration.HasOrPotentiallyHasBaseTypes(), + static (context, token) => + { + if (!context.SemanticModel.Compilation.HasLanguageVersionAtLeastEqualTo(LanguageVersion.CSharp8)) + { + return default; + } - // Get the target IRecipient interfaces and filter out other types - IncrementalValuesProvider<(INamedTypeSymbol Type, ImmutableArray Interfaces)> typeAndInterfaceSymbols = - typeSymbols - .Select(static (item, _) => (item, Interfaces: Execute.GetInterfaces(item))) - .Where(static item => !item.Interfaces.IsEmpty); + INamedTypeSymbol typeSymbol = (INamedTypeSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node, token)!; - // Get the recipient info for all target types - IncrementalValuesProvider recipientInfo = - typeAndInterfaceSymbols - .Select(static (item, _) => Execute.GetInfo(item.Type, item.Interfaces)) - .WithComparer(RecipientInfo.Comparer.Default); + // The type must be a non-abstract, non-generic type (just like with the ObservableValidator generator) + if (typeSymbol is not { IsAbstract: false, IsGenericType: false }) + { + return default; + } + + // This pipeline step also needs to filter out duplicate recipient definitions (it might happen if a + // recipient has partial declarations). To do this, all pairs of class declarations and associated + // symbols are gathered, and then only the pair where the class declaration is the first syntax + // reference for the associated symbol is kept. + if (!context.Node.IsFirstSyntaxDeclarationForSymbol(typeSymbol)) + { + return default; + } + + ImmutableArray interfaceSymbols = Execute.GetInterfaces(typeSymbol); + + // Check that the type implements at least one IRecipient interface + if (interfaceSymbols.IsEmpty) + { + return default; + } + + return Execute.GetInfo(typeSymbol, interfaceSymbols); + }) + .Where(static item => item is not null)!; // Check whether the header file is needed IncrementalValueProvider isHeaderFileNeeded = @@ -60,7 +76,8 @@ public void Initialize(IncrementalGeneratorInitializationContext context) // Gather the conditional flag and attribute availability IncrementalValueProvider<(bool IsHeaderFileNeeded, bool IsDynamicallyAccessedMembersAttributeAvailable)> headerFileInfo = - isHeaderFileNeeded.Combine(isDynamicallyAccessedMembersAttributeAvailable); + isHeaderFileNeeded + .Combine(isDynamicallyAccessedMembersAttributeAvailable); // Generate the header file with the attributes context.RegisterConditionalImplementationSourceOutput(headerFileInfo, static (context, item) => diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Messaging/Models/RecipientInfo.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Messaging/Models/RecipientInfo.cs new file mode 100644 index 000000000..fa8b96e9a --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Messaging/Models/RecipientInfo.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using CommunityToolkit.Mvvm.SourceGenerators.Helpers; + +namespace CommunityToolkit.Mvvm.SourceGenerators.Messaging.Models; + +/// +/// A model with gathered info on all message types being handled by a recipient. +/// +/// The filename hint for the current type. +/// The fully qualified type name of the target type. +/// The name of messages being received. +internal sealed record RecipientInfo(string FilenameHint, string TypeName, EquatableArray MessageTypes); diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Models/DiagnosticInfo.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Models/DiagnosticInfo.cs new file mode 100644 index 000000000..2707b9609 --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Models/DiagnosticInfo.cs @@ -0,0 +1,70 @@ +// 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. + +// This file is ported and adapted from ComputeSharp (Sergio0694/ComputeSharp), +// more info in ThirdPartyNotices.txt in the root of the project. + +using System.Collections.Immutable; +using System.Linq; +using CommunityToolkit.Mvvm.SourceGenerators.Helpers; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; + +namespace CommunityToolkit.Mvvm.SourceGenerators.Models; + +/// +/// A model for a serializeable diagnostic info. +/// +/// The wrapped instance. +/// The tree to use as location for the diagnostic, if available. +/// The span to use as location for the diagnostic. +/// The diagnostic arguments. +internal sealed record DiagnosticInfo( + DiagnosticDescriptor Descriptor, + SyntaxTree? SyntaxTree, + TextSpan TextSpan, + EquatableArray Arguments) +{ + /// + /// Creates a new instance with the state from this model. + /// + /// A new instance with the state from this model. + public Diagnostic ToDiagnostic() + { + if (SyntaxTree is not null) + { + return Diagnostic.Create(Descriptor, Location.Create(SyntaxTree, TextSpan), Arguments.ToArray()); + } + + return Diagnostic.Create(Descriptor, null, Arguments.ToArray()); + } + + /// + /// Creates a new instance with the specified parameters. + /// + /// The input for the diagnostics to create. + /// The source to attach the diagnostics to. + /// The optional arguments for the formatted message to include. + /// A new instance with the specified parameters. + public static DiagnosticInfo Create(DiagnosticDescriptor descriptor, ISymbol symbol, params object[] args) + { + Location location = symbol.Locations.First(); + + return new(descriptor, location.SourceTree, location.SourceSpan, args.Select(static arg => arg.ToString()).ToImmutableArray()); + } + + /// + /// Creates a new instance with the specified parameters. + /// + /// The input for the diagnostics to create. + /// The source to attach the diagnostics to. + /// The optional arguments for the formatted message to include. + /// A new instance with the specified parameters. + public static DiagnosticInfo Create(DiagnosticDescriptor descriptor, SyntaxNode node, params object[] args) + { + Location location = node.GetLocation(); + + return new(descriptor, location.SourceTree, location.SourceSpan, args.Select(static arg => arg.ToString()).ToImmutableArray()); + } +} diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Models/HierarchyInfo.Syntax.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Models/HierarchyInfo.Syntax.cs similarity index 100% rename from CommunityToolkit.Mvvm.SourceGenerators/Models/HierarchyInfo.Syntax.cs rename to src/CommunityToolkit.Mvvm.SourceGenerators/Models/HierarchyInfo.Syntax.cs diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Models/HierarchyInfo.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Models/HierarchyInfo.cs similarity index 64% rename from CommunityToolkit.Mvvm.SourceGenerators/Models/HierarchyInfo.cs rename to src/CommunityToolkit.Mvvm.SourceGenerators/Models/HierarchyInfo.cs index 93924ce79..365c95fbe 100644 --- a/CommunityToolkit.Mvvm.SourceGenerators/Models/HierarchyInfo.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Models/HierarchyInfo.cs @@ -5,10 +5,6 @@ // This file is ported and adapted from ComputeSharp (Sergio0694/ComputeSharp), // more info in ThirdPartyNotices.txt in the root of the project. -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; using CommunityToolkit.Mvvm.SourceGenerators.Extensions; using CommunityToolkit.Mvvm.SourceGenerators.Helpers; using Microsoft.CodeAnalysis; @@ -23,7 +19,7 @@ namespace CommunityToolkit.Mvvm.SourceGenerators.Models; /// The metadata name for the current type. /// Gets the namespace for the current type. /// Gets the sequence of type definitions containing the current type. -internal sealed partial record HierarchyInfo(string FilenameHint, string MetadataName, string Namespace, ImmutableArray Hierarchy) +internal sealed partial record HierarchyInfo(string FilenameHint, string MetadataName, string Namespace, EquatableArray Hierarchy) { /// /// Creates a new instance from a given . @@ -32,7 +28,7 @@ internal sealed partial record HierarchyInfo(string FilenameHint, string Metadat /// A instance describing . public static HierarchyInfo From(INamedTypeSymbol typeSymbol) { - ImmutableArray.Builder hierarchy = ImmutableArray.CreateBuilder(); + using ImmutableArrayBuilder hierarchy = ImmutableArrayBuilder.Rent(); for (INamedTypeSymbol? parent = typeSymbol; parent is not null; @@ -45,34 +41,9 @@ public static HierarchyInfo From(INamedTypeSymbol typeSymbol) } return new( - typeSymbol.GetFullMetadataNameForFileName(), + typeSymbol.GetFullyQualifiedMetadataName(), typeSymbol.MetadataName, typeSymbol.ContainingNamespace.ToDisplayString(new(typeQualificationStyle: NameAndContainingTypesAndNamespaces)), hierarchy.ToImmutable()); } - - /// - /// An implementation for . - /// - public sealed class Comparer : Comparer - { - /// - protected override void AddToHashCode(ref HashCode hashCode, HierarchyInfo obj) - { - hashCode.Add(obj.FilenameHint); - hashCode.Add(obj.MetadataName); - hashCode.Add(obj.Namespace); - hashCode.AddRange(obj.Hierarchy); - } - - /// - protected override bool AreEqual(HierarchyInfo x, HierarchyInfo y) - { - return - x.FilenameHint == y.FilenameHint && - x.MetadataName == y.MetadataName && - x.Namespace == y.Namespace && - x.Hierarchy.SequenceEqual(y.Hierarchy); - } - } } diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Models/Result.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Models/Result.cs similarity index 78% rename from CommunityToolkit.Mvvm.SourceGenerators/Models/Result.cs rename to src/CommunityToolkit.Mvvm.SourceGenerators/Models/Result.cs index 83ca3d1b8..62fbd8a01 100644 --- a/CommunityToolkit.Mvvm.SourceGenerators/Models/Result.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Models/Result.cs @@ -5,8 +5,8 @@ // This file is ported and adapted from ComputeSharp (Sergio0694/ComputeSharp), // more info in ThirdPartyNotices.txt in the root of the project. -using System.Collections.Immutable; -using Microsoft.CodeAnalysis; +using System; +using CommunityToolkit.Mvvm.SourceGenerators.Helpers; namespace CommunityToolkit.Mvvm.SourceGenerators.Models; @@ -16,4 +16,5 @@ namespace CommunityToolkit.Mvvm.SourceGenerators.Models; /// The type of the wrapped value. /// The wrapped value for the current result. /// The associated diagnostic errors, if any. -internal sealed record Result(TValue Value, ImmutableArray Errors); +internal sealed record Result(TValue Value, EquatableArray Errors) + where TValue : IEquatable?; diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Models/TypeInfo.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Models/TypeInfo.cs similarity index 100% rename from CommunityToolkit.Mvvm.SourceGenerators/Models/TypeInfo.cs rename to src/CommunityToolkit.Mvvm.SourceGenerators/Models/TypeInfo.cs diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Polyfills/GeneratorAttributeSyntaxContext.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Polyfills/GeneratorAttributeSyntaxContext.cs new file mode 100644 index 000000000..0f75fdf40 --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Polyfills/GeneratorAttributeSyntaxContext.cs @@ -0,0 +1,61 @@ +// 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. + +#if !ROSLYN_4_3_1_OR_GREATER + +using System.Collections.Immutable; + +namespace Microsoft.CodeAnalysis; + +/// +/// A type containing information for a match from . +/// +internal readonly struct GeneratorAttributeSyntaxContext +{ + /// + /// Creates a new instance with the specified parameters. + /// + /// The syntax node the attribute is attached to. + /// The symbol that the attribute is attached to. + /// Semantic model for the file that is contained within. + /// The collection of matching attributes. + internal GeneratorAttributeSyntaxContext( + SyntaxNode targetNode, + ISymbol targetSymbol, + SemanticModel semanticModel, + ImmutableArray attributes) + { + TargetNode = targetNode; + TargetSymbol = targetSymbol; + SemanticModel = semanticModel; + Attributes = attributes; + } + + /// + /// The syntax node the attribute is attached to. For example, with [CLSCompliant] class C { } this would the class declaration node. + /// + public SyntaxNode TargetNode { get; } + + /// + /// The symbol that the attribute is attached to. For example, with [CLSCompliant] class C { } this would be the for "C". + /// + public ISymbol TargetSymbol { get; } + + /// + /// Semantic model for the file that is contained within. + /// + public SemanticModel SemanticModel { get; } + + /// + /// s for any matching attributes on . Always non-empty. All + /// these attributes will have an whose fully qualified name metadata + /// name matches the name requested in . + /// + /// To get the entire list of attributes, use on . + /// + /// + public ImmutableArray Attributes { get; } +} + +#endif diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Polyfills/SyntaxValueProviderExtensions.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Polyfills/SyntaxValueProviderExtensions.cs new file mode 100644 index 000000000..44a949823 --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Polyfills/SyntaxValueProviderExtensions.cs @@ -0,0 +1,84 @@ +// 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. + +#if !ROSLYN_4_3_1_OR_GREATER + +using System.Threading; +using System; +using System.Collections.Immutable; +using CommunityToolkit.Mvvm.SourceGenerators.Extensions; + +namespace Microsoft.CodeAnalysis; + +/// +/// Extension methods for the type. +/// +internal static class SyntaxValueProviderExtensions +{ + /// + /// Creates an that can provide a transform over all s if that node has an attribute on it that binds to a with the + /// same fully-qualified metadata as the provided . should be the fully-qualified, metadata name of the attribute, including the + /// Attribute suffix. For example "System.CLSCompliantAttribute for . + /// + /// The source instance to use. + /// The fully qualified metadata name of the attribute to look for. + /// A function that determines if the given attribute target () should be transformed. Nodes that do not pass this + /// predicate will not have their attributes looked at at all. + /// A function that performs the transform. This will only be passed nodes that return for and which have a matching whose + /// has the same fully qualified, metadata name as . + public static IncrementalValuesProvider ForAttributeWithMetadataName( + this SyntaxValueProvider syntaxValueProvider, + string fullyQualifiedMetadataName, + Func predicate, + Func transform) + { + return + syntaxValueProvider + .CreateSyntaxProvider( + predicate, + (context, token) => + { + ISymbol? symbol = context.SemanticModel.GetDeclaredSymbol(context.Node, token); + + // If the syntax node doesn't have a declared symbol, just skip this node. This would be + // the case for eg. lambda attributes, but those are not supported by the MVVM Toolkit. + if (symbol is null) + { + return null; + } + + // Skip symbols without the target attribute + if (!symbol.TryGetAttributeWithFullyQualifiedMetadataName(fullyQualifiedMetadataName, out AttributeData? attributeData)) + { + return null; + } + + // Create the GeneratorAttributeSyntaxContext value to pass to the input transform. The attributes array + // will only ever have a single value, but that's fine with the attributes the various generators look for. + GeneratorAttributeSyntaxContext syntaxContext = new( + targetNode: context.Node, + targetSymbol: symbol, + semanticModel: context.SemanticModel, + attributes: ImmutableArray.Create(attributeData)); + + return new Option(transform(syntaxContext, token)); + }) + .Where(static item => item is not null) + .Select(static (item, _) => item!.Value)!; + } + + /// + /// A simple record to wrap a value that might be missing. + /// + /// The type of values to wrap + /// The wrapped value, if it exists. + private sealed record Option(T? Value); +} + +#endif diff --git a/CommunityToolkit.Mvvm/Collections/IReadOnlyObservableGroup.cs b/src/CommunityToolkit.Mvvm/Collections/IReadOnlyObservableGroup.cs similarity index 100% rename from CommunityToolkit.Mvvm/Collections/IReadOnlyObservableGroup.cs rename to src/CommunityToolkit.Mvvm/Collections/IReadOnlyObservableGroup.cs diff --git a/CommunityToolkit.Mvvm/Collections/IReadOnlyObservableGroup{TKey,TElement}.cs b/src/CommunityToolkit.Mvvm/Collections/IReadOnlyObservableGroup{TKey,TElement}.cs similarity index 100% rename from CommunityToolkit.Mvvm/Collections/IReadOnlyObservableGroup{TKey,TElement}.cs rename to src/CommunityToolkit.Mvvm/Collections/IReadOnlyObservableGroup{TKey,TElement}.cs diff --git a/CommunityToolkit.Mvvm/Collections/IReadOnlyObservableGroup{TKey}.cs b/src/CommunityToolkit.Mvvm/Collections/IReadOnlyObservableGroup{TKey}.cs similarity index 100% rename from CommunityToolkit.Mvvm/Collections/IReadOnlyObservableGroup{TKey}.cs rename to src/CommunityToolkit.Mvvm/Collections/IReadOnlyObservableGroup{TKey}.cs diff --git a/CommunityToolkit.Mvvm/Collections/Internals/ObservableGroupHelper.cs b/src/CommunityToolkit.Mvvm/Collections/Internals/ObservableGroupHelper.cs similarity index 100% rename from CommunityToolkit.Mvvm/Collections/Internals/ObservableGroupHelper.cs rename to src/CommunityToolkit.Mvvm/Collections/Internals/ObservableGroupHelper.cs diff --git a/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollectionExtensions.cs b/src/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollectionExtensions.cs similarity index 100% rename from CommunityToolkit.Mvvm/Collections/ObservableGroupedCollectionExtensions.cs rename to src/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollectionExtensions.cs diff --git a/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollection{TKey,TElement}.cs b/src/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollection{TKey,TElement}.cs similarity index 100% rename from CommunityToolkit.Mvvm/Collections/ObservableGroupedCollection{TKey,TElement}.cs rename to src/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollection{TKey,TElement}.cs diff --git a/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TElement}.cs b/src/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TElement}.cs similarity index 100% rename from CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TElement}.cs rename to src/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TElement}.cs diff --git a/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TElement}.cs b/src/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TElement}.cs similarity index 100% rename from CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TElement}.cs rename to src/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TElement}.cs diff --git a/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TElement}.cs b/src/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TElement}.cs similarity index 100% rename from CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TElement}.cs rename to src/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TElement}.cs diff --git a/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj b/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj similarity index 57% rename from CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj rename to src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj index 2bfe1cdf1..43adfb763 100644 --- a/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj +++ b/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj @@ -22,7 +22,7 @@ - + @@ -34,17 +34,34 @@ - - - true - true - - - + - + + + + + + + + + + + System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute; + System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute; + System.Diagnostics.CodeAnalysis.MemberNotNullAttribute; + System.Diagnostics.CodeAnalysis.NotNullAttribute; + System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute; + System.Diagnostics.CodeAnalysis.NotNullWhenAttribute; + System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute; + System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute; + System.Runtime.CompilerServices.CallerArgumentExpressionAttribute; + System.Runtime.CompilerServices.IsExternalInit; + System.Runtime.CompilerServices.SkipLocalsInitAttribute; + + + @@ -53,11 +70,12 @@ - - + + + \ No newline at end of file diff --git a/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.targets b/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.targets similarity index 58% rename from CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.targets rename to src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.targets index 05a78c50d..c2fd56e39 100644 --- a/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.targets +++ b/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.targets @@ -41,6 +41,36 @@ + + + + + + roslyn4.3 + roslyn4.0 + + + + + + + + and events are not raised /// if the current and new value for the target property are the same. /// - protected bool SetProperty([NotNullIfNotNull("newValue")] ref T field, T newValue, [CallerMemberName] string? propertyName = null) + protected bool SetProperty([NotNullIfNotNull(nameof(newValue))] ref T field, T newValue, [CallerMemberName] string? propertyName = null) { // We duplicate the code here instead of calling the overload because we can't // guarantee that the invoked SetProperty will be inlined, and we need the JIT @@ -134,7 +134,7 @@ protected bool SetProperty([NotNullIfNotNull("newValue")] ref T field, T newV /// (optional) The name of the property that changed. /// if the property was changed, otherwise. /// Thrown if is . - protected bool SetProperty([NotNullIfNotNull("newValue")] ref T field, T newValue, IEqualityComparer comparer, [CallerMemberName] string? propertyName = null) + protected bool SetProperty([NotNullIfNotNull(nameof(newValue))] ref T field, T newValue, IEqualityComparer comparer, [CallerMemberName] string? propertyName = null) { ArgumentNullException.ThrowIfNull(comparer); diff --git a/CommunityToolkit.Mvvm/ComponentModel/ObservableRecipient.cs b/src/CommunityToolkit.Mvvm/ComponentModel/ObservableRecipient.cs similarity index 98% rename from CommunityToolkit.Mvvm/ComponentModel/ObservableRecipient.cs rename to src/CommunityToolkit.Mvvm/ComponentModel/ObservableRecipient.cs index e6598825c..63b28441b 100644 --- a/CommunityToolkit.Mvvm/ComponentModel/ObservableRecipient.cs +++ b/src/CommunityToolkit.Mvvm/ComponentModel/ObservableRecipient.cs @@ -152,7 +152,7 @@ protected virtual void Broadcast(T oldValue, T newValue, string? propertyName /// the and events /// are not raised if the current and new value for the target property are the same. /// - protected bool SetProperty([NotNullIfNotNull("newValue")] ref T field, T newValue, bool broadcast, [CallerMemberName] string? propertyName = null) + protected bool SetProperty([NotNullIfNotNull(nameof(newValue))] ref T field, T newValue, bool broadcast, [CallerMemberName] string? propertyName = null) { T oldValue = field; @@ -182,7 +182,7 @@ protected bool SetProperty([NotNullIfNotNull("newValue")] ref T field, T newV /// (optional) The name of the property that changed. /// if the property was changed, otherwise. /// Thrown if is . - protected bool SetProperty([NotNullIfNotNull("newValue")] ref T field, T newValue, IEqualityComparer comparer, bool broadcast, [CallerMemberName] string? propertyName = null) + protected bool SetProperty([NotNullIfNotNull(nameof(newValue))] ref T field, T newValue, IEqualityComparer comparer, bool broadcast, [CallerMemberName] string? propertyName = null) { ArgumentNullException.ThrowIfNull(comparer); diff --git a/CommunityToolkit.Mvvm/ComponentModel/ObservableValidator.cs b/src/CommunityToolkit.Mvvm/ComponentModel/ObservableValidator.cs similarity index 99% rename from CommunityToolkit.Mvvm/ComponentModel/ObservableValidator.cs rename to src/CommunityToolkit.Mvvm/ComponentModel/ObservableValidator.cs index a39af6343..02921bf07 100644 --- a/CommunityToolkit.Mvvm/ComponentModel/ObservableValidator.cs +++ b/src/CommunityToolkit.Mvvm/ComponentModel/ObservableValidator.cs @@ -145,7 +145,7 @@ protected ObservableValidator(ValidationContext validationContext) /// /// Thrown if is . [RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")] - protected bool SetProperty([NotNullIfNotNull("newValue")] ref T field, T newValue, bool validate, [CallerMemberName] string propertyName = null!) + protected bool SetProperty([NotNullIfNotNull(nameof(newValue))] ref T field, T newValue, bool validate, [CallerMemberName] string propertyName = null!) { ArgumentNullException.ThrowIfNull(propertyName); @@ -174,7 +174,7 @@ protected bool SetProperty([NotNullIfNotNull("newValue")] ref T field, T newV /// if the property was changed, otherwise. /// Thrown if or are . [RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")] - protected bool SetProperty([NotNullIfNotNull("newValue")] ref T field, T newValue, IEqualityComparer comparer, bool validate, [CallerMemberName] string propertyName = null!) + protected bool SetProperty([NotNullIfNotNull(nameof(newValue))] ref T field, T newValue, IEqualityComparer comparer, bool validate, [CallerMemberName] string propertyName = null!) { ArgumentNullException.ThrowIfNull(comparer); ArgumentNullException.ThrowIfNull(propertyName); diff --git a/CommunityToolkit.Mvvm/ComponentModel/__Internals/__ObservableValidatorHelper.cs b/src/CommunityToolkit.Mvvm/ComponentModel/__Internals/__ObservableValidatorHelper.cs similarity index 100% rename from CommunityToolkit.Mvvm/ComponentModel/__Internals/__ObservableValidatorHelper.cs rename to src/CommunityToolkit.Mvvm/ComponentModel/__Internals/__ObservableValidatorHelper.cs diff --git a/CommunityToolkit.Mvvm/ComponentModel/__Internals/__TaskExtensions.cs b/src/CommunityToolkit.Mvvm/ComponentModel/__Internals/__TaskExtensions.cs similarity index 100% rename from CommunityToolkit.Mvvm/ComponentModel/__Internals/__TaskExtensions.cs rename to src/CommunityToolkit.Mvvm/ComponentModel/__Internals/__TaskExtensions.cs diff --git a/CommunityToolkit.Mvvm/DependencyInjection/Ioc.cs b/src/CommunityToolkit.Mvvm/DependencyInjection/Ioc.cs similarity index 100% rename from CommunityToolkit.Mvvm/DependencyInjection/Ioc.cs rename to src/CommunityToolkit.Mvvm/DependencyInjection/Ioc.cs diff --git a/CommunityToolkit.Mvvm/Input/AsyncRelayCommand.cs b/src/CommunityToolkit.Mvvm/Input/AsyncRelayCommand.cs similarity index 99% rename from CommunityToolkit.Mvvm/Input/AsyncRelayCommand.cs rename to src/CommunityToolkit.Mvvm/Input/AsyncRelayCommand.cs index 8b38c24e9..ed877ed52 100644 --- a/CommunityToolkit.Mvvm/Input/AsyncRelayCommand.cs +++ b/src/CommunityToolkit.Mvvm/Input/AsyncRelayCommand.cs @@ -10,7 +10,7 @@ using CommunityToolkit.Mvvm.ComponentModel.__Internals; using CommunityToolkit.Mvvm.Input.Internals; -#pragma warning disable CS0618 +#pragma warning disable CS0618, CA1001 namespace CommunityToolkit.Mvvm.Input; diff --git a/CommunityToolkit.Mvvm/Input/AsyncRelayCommandOptions.cs b/src/CommunityToolkit.Mvvm/Input/AsyncRelayCommandOptions.cs similarity index 100% rename from CommunityToolkit.Mvvm/Input/AsyncRelayCommandOptions.cs rename to src/CommunityToolkit.Mvvm/Input/AsyncRelayCommandOptions.cs diff --git a/CommunityToolkit.Mvvm/Input/AsyncRelayCommand{T}.cs b/src/CommunityToolkit.Mvvm/Input/AsyncRelayCommand{T}.cs similarity index 99% rename from CommunityToolkit.Mvvm/Input/AsyncRelayCommand{T}.cs rename to src/CommunityToolkit.Mvvm/Input/AsyncRelayCommand{T}.cs index 8e914dc04..a2b3d7f96 100644 --- a/CommunityToolkit.Mvvm/Input/AsyncRelayCommand{T}.cs +++ b/src/CommunityToolkit.Mvvm/Input/AsyncRelayCommand{T}.cs @@ -10,7 +10,7 @@ using CommunityToolkit.Mvvm.ComponentModel.__Internals; using CommunityToolkit.Mvvm.Input.Internals; -#pragma warning disable CS0618 +#pragma warning disable CS0618, CA1001 namespace CommunityToolkit.Mvvm.Input; diff --git a/CommunityToolkit.Mvvm/Input/Attributes/RelayCommandAttribute.cs b/src/CommunityToolkit.Mvvm/Input/Attributes/RelayCommandAttribute.cs similarity index 100% rename from CommunityToolkit.Mvvm/Input/Attributes/RelayCommandAttribute.cs rename to src/CommunityToolkit.Mvvm/Input/Attributes/RelayCommandAttribute.cs diff --git a/CommunityToolkit.Mvvm/Input/IAsyncRelayCommandExtensions.cs b/src/CommunityToolkit.Mvvm/Input/IAsyncRelayCommandExtensions.cs similarity index 100% rename from CommunityToolkit.Mvvm/Input/IAsyncRelayCommandExtensions.cs rename to src/CommunityToolkit.Mvvm/Input/IAsyncRelayCommandExtensions.cs diff --git a/CommunityToolkit.Mvvm/Input/Interfaces/IAsyncRelayCommand.cs b/src/CommunityToolkit.Mvvm/Input/Interfaces/IAsyncRelayCommand.cs similarity index 100% rename from CommunityToolkit.Mvvm/Input/Interfaces/IAsyncRelayCommand.cs rename to src/CommunityToolkit.Mvvm/Input/Interfaces/IAsyncRelayCommand.cs diff --git a/CommunityToolkit.Mvvm/Input/Interfaces/IAsyncRelayCommand{T}.cs b/src/CommunityToolkit.Mvvm/Input/Interfaces/IAsyncRelayCommand{T}.cs similarity index 100% rename from CommunityToolkit.Mvvm/Input/Interfaces/IAsyncRelayCommand{T}.cs rename to src/CommunityToolkit.Mvvm/Input/Interfaces/IAsyncRelayCommand{T}.cs diff --git a/CommunityToolkit.Mvvm/Input/Interfaces/IRelayCommand.cs b/src/CommunityToolkit.Mvvm/Input/Interfaces/IRelayCommand.cs similarity index 100% rename from CommunityToolkit.Mvvm/Input/Interfaces/IRelayCommand.cs rename to src/CommunityToolkit.Mvvm/Input/Interfaces/IRelayCommand.cs diff --git a/CommunityToolkit.Mvvm/Input/Interfaces/IRelayCommand{T}.cs b/src/CommunityToolkit.Mvvm/Input/Interfaces/IRelayCommand{T}.cs similarity index 100% rename from CommunityToolkit.Mvvm/Input/Interfaces/IRelayCommand{T}.cs rename to src/CommunityToolkit.Mvvm/Input/Interfaces/IRelayCommand{T}.cs diff --git a/CommunityToolkit.Mvvm/Input/Internals/CancelCommand.cs b/src/CommunityToolkit.Mvvm/Input/Internals/CancelCommand.cs similarity index 100% rename from CommunityToolkit.Mvvm/Input/Internals/CancelCommand.cs rename to src/CommunityToolkit.Mvvm/Input/Internals/CancelCommand.cs diff --git a/CommunityToolkit.Mvvm/Input/Internals/DisabledCommand.cs b/src/CommunityToolkit.Mvvm/Input/Internals/DisabledCommand.cs similarity index 100% rename from CommunityToolkit.Mvvm/Input/Internals/DisabledCommand.cs rename to src/CommunityToolkit.Mvvm/Input/Internals/DisabledCommand.cs diff --git a/CommunityToolkit.Mvvm/Input/Internals/ICancellationAwareCommand.cs b/src/CommunityToolkit.Mvvm/Input/Internals/ICancellationAwareCommand.cs similarity index 100% rename from CommunityToolkit.Mvvm/Input/Internals/ICancellationAwareCommand.cs rename to src/CommunityToolkit.Mvvm/Input/Internals/ICancellationAwareCommand.cs diff --git a/CommunityToolkit.Mvvm/Input/RelayCommand.cs b/src/CommunityToolkit.Mvvm/Input/RelayCommand.cs similarity index 100% rename from CommunityToolkit.Mvvm/Input/RelayCommand.cs rename to src/CommunityToolkit.Mvvm/Input/RelayCommand.cs diff --git a/CommunityToolkit.Mvvm/Input/RelayCommand{T}.cs b/src/CommunityToolkit.Mvvm/Input/RelayCommand{T}.cs similarity index 100% rename from CommunityToolkit.Mvvm/Input/RelayCommand{T}.cs rename to src/CommunityToolkit.Mvvm/Input/RelayCommand{T}.cs diff --git a/CommunityToolkit.Mvvm/Messaging/IMessenger.cs b/src/CommunityToolkit.Mvvm/Messaging/IMessenger.cs similarity index 100% rename from CommunityToolkit.Mvvm/Messaging/IMessenger.cs rename to src/CommunityToolkit.Mvvm/Messaging/IMessenger.cs diff --git a/src/CommunityToolkit.Mvvm/Messaging/IMessengerExtensions.Observables.cs b/src/CommunityToolkit.Mvvm/Messaging/IMessengerExtensions.Observables.cs new file mode 100644 index 000000000..996e79be3 --- /dev/null +++ b/src/CommunityToolkit.Mvvm/Messaging/IMessengerExtensions.Observables.cs @@ -0,0 +1,199 @@ +// 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; + +namespace CommunityToolkit.Mvvm.Messaging; + +/// +partial class IMessengerExtensions +{ + /// + /// Creates an instance that can be used to be notified whenever a message of a given type is broadcast by a messenger. + /// + /// The type of message to use to receive notification for through the resulting instance. + /// The instance to use to register the recipient. + /// An instance to receive notifications for messages being broadcast. + /// Thrown if is . + public static IObservable CreateObservable(this IMessenger messenger) + where TMessage : class + { + ArgumentNullException.ThrowIfNull(messenger); + + return new Observable(messenger); + } + + /// + /// Creates an instance that can be used to be notified whenever a message of a given type is broadcast by a messenger. + /// + /// The type of message to use to receive notification for through the resulting instance. + /// The type of token to identify what channel to use to receive messages. + /// The instance to use to register the recipient. + /// A token used to determine the receiving channel to use. + /// An instance to receive notifications for messages being broadcast. + /// Thrown if or are . + public static IObservable CreateObservable(this IMessenger messenger, TToken token) + where TMessage : class + where TToken : IEquatable + { + ArgumentNullException.ThrowIfNull(messenger); + ArgumentNullException.For.ThrowIfNull(token); + + return new Observable(messenger, token); + } + + /// + /// An implementations for a given message type. + /// + /// The type of messages to listen to. + private sealed class Observable : IObservable + where TMessage : class + { + /// + /// The instance to use to register the recipient. + /// + private readonly IMessenger messenger; + + /// + /// Creates a new instance with the given parameters. + /// + /// The instance to use to register the recipient. + public Observable(IMessenger messenger) + { + this.messenger = messenger; + } + + /// + public IDisposable Subscribe(IObserver observer) + { + return new Recipient(this.messenger, observer); + } + + /// + /// An implementation for . + /// + private sealed class Recipient : IRecipient, IDisposable + { + /// + /// The instance to use to register the recipient. + /// + private readonly IMessenger messenger; + + /// + /// The target instance currently in use. + /// + private readonly IObserver observer; + + /// + /// Creates a new instance with the specified parameters. + /// + /// The instance to use to register the recipient. + /// The instance to use to create the recipient for. + public Recipient(IMessenger messenger, IObserver observer) + { + this.messenger = messenger; + this.observer = observer; + + messenger.Register(this); + } + + /// + public void Receive(TMessage message) + { + this.observer.OnNext(message); + } + + /// + public void Dispose() + { + this.messenger.Unregister(this); + } + } + } + + /// + /// An implementations for a given pair of message and token types. + /// + /// The type of messages to listen to. + /// The type of token to identify what channel to use to receive messages. + private sealed class Observable : IObservable + where TMessage : class + where TToken : IEquatable + { + /// + /// The instance to use to register the recipient. + /// + private readonly IMessenger messenger; + + /// + /// The token used to determine the receiving channel to use. + /// + private readonly TToken token; + + /// + /// Creates a new instance with the given parameters. + /// + /// The instance to use to register the recipient. + /// A token used to determine the receiving channel to use. + public Observable(IMessenger messenger, TToken token) + { + this.messenger = messenger; + this.token = token; + } + + /// + public IDisposable Subscribe(IObserver observer) + { + return new Recipient(this.messenger, observer, this.token); + } + + /// + /// An implementation for . + /// + private sealed class Recipient : IRecipient, IDisposable + { + /// + /// The instance to use to register the recipient. + /// + private readonly IMessenger messenger; + + /// + /// The target instance currently in use. + /// + private readonly IObserver observer; + + /// + /// The token used to determine the receiving channel to use. + /// + private readonly TToken token; + + /// + /// Creates a new instance with the specified parameters. + /// + /// The instance to use to register the recipient. + /// The instance to use to create the recipient for. + /// A token used to determine the receiving channel to use. + public Recipient(IMessenger messenger, IObserver observer, TToken token) + { + this.messenger = messenger; + this.observer = observer; + this.token = token; + + messenger.Register(this, token); + } + + /// + public void Receive(TMessage message) + { + this.observer.OnNext(message); + } + + /// + public void Dispose() + { + this.messenger.Unregister(this, this.token); + } + } + } +} diff --git a/CommunityToolkit.Mvvm/Messaging/IMessengerExtensions.cs b/src/CommunityToolkit.Mvvm/Messaging/IMessengerExtensions.cs similarity index 99% rename from CommunityToolkit.Mvvm/Messaging/IMessengerExtensions.cs rename to src/CommunityToolkit.Mvvm/Messaging/IMessengerExtensions.cs index 2163efd86..f6f038166 100644 --- a/CommunityToolkit.Mvvm/Messaging/IMessengerExtensions.cs +++ b/src/CommunityToolkit.Mvvm/Messaging/IMessengerExtensions.cs @@ -15,7 +15,7 @@ namespace CommunityToolkit.Mvvm.Messaging; /// /// Extensions for the type. /// -public static class IMessengerExtensions +public static partial class IMessengerExtensions { /// /// A class that acts as a container to load the instance linked to @@ -256,6 +256,10 @@ public static void Register(this IMessenger messenger, IRecipient(recipient, default); } + else if (messenger is StrongReferenceMessenger strongReferenceMessenger) + { + strongReferenceMessenger.Register(recipient, default); + } else { messenger.Register, TMessage, Unit>(recipient, default, static (r, m) => r.Receive(m)); diff --git a/CommunityToolkit.Mvvm/Messaging/IRecipient{TMessage}.cs b/src/CommunityToolkit.Mvvm/Messaging/IRecipient{TMessage}.cs similarity index 100% rename from CommunityToolkit.Mvvm/Messaging/IRecipient{TMessage}.cs rename to src/CommunityToolkit.Mvvm/Messaging/IRecipient{TMessage}.cs diff --git a/CommunityToolkit.Mvvm/Messaging/Internals/ArrayPoolBufferWriter{T}.cs b/src/CommunityToolkit.Mvvm/Messaging/Internals/ArrayPoolBufferWriter{T}.cs similarity index 100% rename from CommunityToolkit.Mvvm/Messaging/Internals/ArrayPoolBufferWriter{T}.cs rename to src/CommunityToolkit.Mvvm/Messaging/Internals/ArrayPoolBufferWriter{T}.cs diff --git a/CommunityToolkit.Mvvm/Messaging/Internals/MessageHandlerDispatcher.cs b/src/CommunityToolkit.Mvvm/Messaging/Internals/MessageHandlerDispatcher.cs similarity index 100% rename from CommunityToolkit.Mvvm/Messaging/Internals/MessageHandlerDispatcher.cs rename to src/CommunityToolkit.Mvvm/Messaging/Internals/MessageHandlerDispatcher.cs diff --git a/CommunityToolkit.Mvvm/Messaging/Internals/System/Collections.Generic/Dictionary2.cs b/src/CommunityToolkit.Mvvm/Messaging/Internals/System/Collections.Generic/Dictionary2.cs similarity index 98% rename from CommunityToolkit.Mvvm/Messaging/Internals/System/Collections.Generic/Dictionary2.cs rename to src/CommunityToolkit.Mvvm/Messaging/Internals/System/Collections.Generic/Dictionary2.cs index 15fa9ef22..c3ac541a7 100644 --- a/CommunityToolkit.Mvvm/Messaging/Internals/System/Collections.Generic/Dictionary2.cs +++ b/src/CommunityToolkit.Mvvm/Messaging/Internals/System/Collections.Generic/Dictionary2.cs @@ -340,9 +340,9 @@ public TValue GetValue() /// /// Key to look for. /// Reference to the existing value. - private ref TValue FindValue(TKey key) + private unsafe ref TValue FindValue(TKey key) { - ref Entry entry = ref Unsafe.NullRef(); + ref Entry entry = ref *(Entry*)null; uint hashCode = (uint)key.GetHashCode(); int i = GetBucket(hashCode); Entry[] entries = this.entries; @@ -373,7 +373,7 @@ private ref TValue FindValue(TKey key) return ref value; ReturnNotFound: - value = ref Unsafe.NullRef(); + value = ref *(TValue*)null; goto Return; } diff --git a/CommunityToolkit.Mvvm/Messaging/Internals/System/Collections.Generic/HashHelpers.cs b/src/CommunityToolkit.Mvvm/Messaging/Internals/System/Collections.Generic/HashHelpers.cs similarity index 100% rename from CommunityToolkit.Mvvm/Messaging/Internals/System/Collections.Generic/HashHelpers.cs rename to src/CommunityToolkit.Mvvm/Messaging/Internals/System/Collections.Generic/HashHelpers.cs diff --git a/CommunityToolkit.Mvvm/Messaging/Internals/System/Collections.Generic/IDictionary2.cs b/src/CommunityToolkit.Mvvm/Messaging/Internals/System/Collections.Generic/IDictionary2.cs similarity index 100% rename from CommunityToolkit.Mvvm/Messaging/Internals/System/Collections.Generic/IDictionary2.cs rename to src/CommunityToolkit.Mvvm/Messaging/Internals/System/Collections.Generic/IDictionary2.cs diff --git a/CommunityToolkit.Mvvm/Messaging/Internals/System/Collections.Generic/IDictionary2{TKey,TValue}.cs b/src/CommunityToolkit.Mvvm/Messaging/Internals/System/Collections.Generic/IDictionary2{TKey,TValue}.cs similarity index 100% rename from CommunityToolkit.Mvvm/Messaging/Internals/System/Collections.Generic/IDictionary2{TKey,TValue}.cs rename to src/CommunityToolkit.Mvvm/Messaging/Internals/System/Collections.Generic/IDictionary2{TKey,TValue}.cs diff --git a/CommunityToolkit.Mvvm/Messaging/Internals/System/Collections.Generic/IDictionary2{TKey}.cs b/src/CommunityToolkit.Mvvm/Messaging/Internals/System/Collections.Generic/IDictionary2{TKey}.cs similarity index 100% rename from CommunityToolkit.Mvvm/Messaging/Internals/System/Collections.Generic/IDictionary2{TKey}.cs rename to src/CommunityToolkit.Mvvm/Messaging/Internals/System/Collections.Generic/IDictionary2{TKey}.cs diff --git a/CommunityToolkit.Mvvm/Messaging/Internals/System/Gen2GcCallback.cs b/src/CommunityToolkit.Mvvm/Messaging/Internals/System/Gen2GcCallback.cs similarity index 100% rename from CommunityToolkit.Mvvm/Messaging/Internals/System/Gen2GcCallback.cs rename to src/CommunityToolkit.Mvvm/Messaging/Internals/System/Gen2GcCallback.cs diff --git a/CommunityToolkit.Mvvm/Messaging/Internals/System/Runtime.CompilerServices/ConditionalWeakTable2{TKey,TValue}.Proxy.cs b/src/CommunityToolkit.Mvvm/Messaging/Internals/System/Runtime.CompilerServices/ConditionalWeakTable2{TKey,TValue}.Proxy.cs similarity index 100% rename from CommunityToolkit.Mvvm/Messaging/Internals/System/Runtime.CompilerServices/ConditionalWeakTable2{TKey,TValue}.Proxy.cs rename to src/CommunityToolkit.Mvvm/Messaging/Internals/System/Runtime.CompilerServices/ConditionalWeakTable2{TKey,TValue}.Proxy.cs diff --git a/CommunityToolkit.Mvvm/Messaging/Internals/System/Runtime.CompilerServices/ConditionalWeakTable2{TKey,TValue}.ZeroAlloc.cs b/src/CommunityToolkit.Mvvm/Messaging/Internals/System/Runtime.CompilerServices/ConditionalWeakTable2{TKey,TValue}.ZeroAlloc.cs similarity index 98% rename from CommunityToolkit.Mvvm/Messaging/Internals/System/Runtime.CompilerServices/ConditionalWeakTable2{TKey,TValue}.ZeroAlloc.cs rename to src/CommunityToolkit.Mvvm/Messaging/Internals/System/Runtime.CompilerServices/ConditionalWeakTable2{TKey,TValue}.ZeroAlloc.cs index 666a1c5ce..882b6bf50 100644 --- a/CommunityToolkit.Mvvm/Messaging/Internals/System/Runtime.CompilerServices/ConditionalWeakTable2{TKey,TValue}.ZeroAlloc.cs +++ b/src/CommunityToolkit.Mvvm/Messaging/Internals/System/Runtime.CompilerServices/ConditionalWeakTable2{TKey,TValue}.ZeroAlloc.cs @@ -83,6 +83,10 @@ public bool Remove(TKey key) } /// + [UnconditionalSuppressMessage( + "ReflectionAnalysis", + "IL2091", + Justification = "ConditionalWeakTable is only referenced to reuse the callback delegate type, but no value is ever created through reflection.")] public TValue GetValue(TKey key, ConditionalWeakTable.CreateValueCallback createValueCallback) { return TryGetValue(key, out TValue? existingValue) ? @@ -96,6 +100,10 @@ public TValue GetValue(TKey key, ConditionalWeakTable.CreateValueC /// The input key. /// The callback to use to create a new item. /// The new item to store. + [UnconditionalSuppressMessage( + "ReflectionAnalysis", + "IL2091", + Justification = "ConditionalWeakTable is only referenced to reuse the callback delegate type, but no value is ever created through reflection.")] private TValue GetValueLocked(TKey key, ConditionalWeakTable.CreateValueCallback createValueCallback) { // If we got here, the key was not in the table. Invoke the callback diff --git a/CommunityToolkit.Mvvm/Messaging/Internals/System/Runtime.CompilerServices/ConditionalWeakTable2{TKey,TValue}.cs b/src/CommunityToolkit.Mvvm/Messaging/Internals/System/Runtime.CompilerServices/ConditionalWeakTable2{TKey,TValue}.cs similarity index 100% rename from CommunityToolkit.Mvvm/Messaging/Internals/System/Runtime.CompilerServices/ConditionalWeakTable2{TKey,TValue}.cs rename to src/CommunityToolkit.Mvvm/Messaging/Internals/System/Runtime.CompilerServices/ConditionalWeakTable2{TKey,TValue}.cs diff --git a/CommunityToolkit.Mvvm/Messaging/Internals/System/Runtime.CompilerServices/ConditionalWeakTableExtensions.cs b/src/CommunityToolkit.Mvvm/Messaging/Internals/System/Runtime.CompilerServices/ConditionalWeakTableExtensions.cs similarity index 100% rename from CommunityToolkit.Mvvm/Messaging/Internals/System/Runtime.CompilerServices/ConditionalWeakTableExtensions.cs rename to src/CommunityToolkit.Mvvm/Messaging/Internals/System/Runtime.CompilerServices/ConditionalWeakTableExtensions.cs diff --git a/CommunityToolkit.Mvvm/Messaging/Internals/Type2.cs b/src/CommunityToolkit.Mvvm/Messaging/Internals/Type2.cs similarity index 100% rename from CommunityToolkit.Mvvm/Messaging/Internals/Type2.cs rename to src/CommunityToolkit.Mvvm/Messaging/Internals/Type2.cs diff --git a/CommunityToolkit.Mvvm/Messaging/Internals/Unit.cs b/src/CommunityToolkit.Mvvm/Messaging/Internals/Unit.cs similarity index 100% rename from CommunityToolkit.Mvvm/Messaging/Internals/Unit.cs rename to src/CommunityToolkit.Mvvm/Messaging/Internals/Unit.cs diff --git a/CommunityToolkit.Mvvm/Messaging/MessageHandler{TRecipient,TMessage}.cs b/src/CommunityToolkit.Mvvm/Messaging/MessageHandler{TRecipient,TMessage}.cs similarity index 100% rename from CommunityToolkit.Mvvm/Messaging/MessageHandler{TRecipient,TMessage}.cs rename to src/CommunityToolkit.Mvvm/Messaging/MessageHandler{TRecipient,TMessage}.cs diff --git a/CommunityToolkit.Mvvm/Messaging/Messages/AsyncCollectionRequestMessage{T}.cs b/src/CommunityToolkit.Mvvm/Messaging/Messages/AsyncCollectionRequestMessage{T}.cs similarity index 99% rename from CommunityToolkit.Mvvm/Messaging/Messages/AsyncCollectionRequestMessage{T}.cs rename to src/CommunityToolkit.Mvvm/Messaging/Messages/AsyncCollectionRequestMessage{T}.cs index aec986fe8..28e66754b 100644 --- a/CommunityToolkit.Mvvm/Messaging/Messages/AsyncCollectionRequestMessage{T}.cs +++ b/src/CommunityToolkit.Mvvm/Messaging/Messages/AsyncCollectionRequestMessage{T}.cs @@ -8,6 +8,8 @@ using System.Threading; using System.Threading.Tasks; +#pragma warning disable CA1001 + namespace CommunityToolkit.Mvvm.Messaging.Messages; /// diff --git a/CommunityToolkit.Mvvm/Messaging/Messages/AsyncRequestMessage{T}.cs b/src/CommunityToolkit.Mvvm/Messaging/Messages/AsyncRequestMessage{T}.cs similarity index 100% rename from CommunityToolkit.Mvvm/Messaging/Messages/AsyncRequestMessage{T}.cs rename to src/CommunityToolkit.Mvvm/Messaging/Messages/AsyncRequestMessage{T}.cs diff --git a/CommunityToolkit.Mvvm/Messaging/Messages/CollectionRequestMessage{T}.cs b/src/CommunityToolkit.Mvvm/Messaging/Messages/CollectionRequestMessage{T}.cs similarity index 100% rename from CommunityToolkit.Mvvm/Messaging/Messages/CollectionRequestMessage{T}.cs rename to src/CommunityToolkit.Mvvm/Messaging/Messages/CollectionRequestMessage{T}.cs diff --git a/CommunityToolkit.Mvvm/Messaging/Messages/PropertyChangedMessage{T}.cs b/src/CommunityToolkit.Mvvm/Messaging/Messages/PropertyChangedMessage{T}.cs similarity index 100% rename from CommunityToolkit.Mvvm/Messaging/Messages/PropertyChangedMessage{T}.cs rename to src/CommunityToolkit.Mvvm/Messaging/Messages/PropertyChangedMessage{T}.cs diff --git a/CommunityToolkit.Mvvm/Messaging/Messages/RequestMessage{T}.cs b/src/CommunityToolkit.Mvvm/Messaging/Messages/RequestMessage{T}.cs similarity index 100% rename from CommunityToolkit.Mvvm/Messaging/Messages/RequestMessage{T}.cs rename to src/CommunityToolkit.Mvvm/Messaging/Messages/RequestMessage{T}.cs diff --git a/CommunityToolkit.Mvvm/Messaging/Messages/ValueChangedMessage{T}.cs b/src/CommunityToolkit.Mvvm/Messaging/Messages/ValueChangedMessage{T}.cs similarity index 100% rename from CommunityToolkit.Mvvm/Messaging/Messages/ValueChangedMessage{T}.cs rename to src/CommunityToolkit.Mvvm/Messaging/Messages/ValueChangedMessage{T}.cs diff --git a/CommunityToolkit.Mvvm/Messaging/StrongReferenceMessenger.cs b/src/CommunityToolkit.Mvvm/Messaging/StrongReferenceMessenger.cs similarity index 100% rename from CommunityToolkit.Mvvm/Messaging/StrongReferenceMessenger.cs rename to src/CommunityToolkit.Mvvm/Messaging/StrongReferenceMessenger.cs diff --git a/CommunityToolkit.Mvvm/Messaging/WeakReferenceMessenger.cs b/src/CommunityToolkit.Mvvm/Messaging/WeakReferenceMessenger.cs similarity index 100% rename from CommunityToolkit.Mvvm/Messaging/WeakReferenceMessenger.cs rename to src/CommunityToolkit.Mvvm/Messaging/WeakReferenceMessenger.cs diff --git a/CommunityToolkit.Mvvm/Properties/Configuration.cs b/src/CommunityToolkit.Mvvm/Properties/Configuration.cs similarity index 100% rename from CommunityToolkit.Mvvm/Properties/Configuration.cs rename to src/CommunityToolkit.Mvvm/Properties/Configuration.cs diff --git a/CommunityToolkit.Mvvm/Properties/Polyfills/ArgumentNullException.cs b/src/CommunityToolkit.Mvvm/Properties/Polyfills/ArgumentNullException.cs similarity index 94% rename from CommunityToolkit.Mvvm/Properties/Polyfills/ArgumentNullException.cs rename to src/CommunityToolkit.Mvvm/Properties/Polyfills/ArgumentNullException.cs index 9a0408ace..00647683d 100644 --- a/CommunityToolkit.Mvvm/Properties/Polyfills/ArgumentNullException.cs +++ b/src/CommunityToolkit.Mvvm/Properties/Polyfills/ArgumentNullException.cs @@ -18,7 +18,7 @@ internal sealed class ArgumentNullException /// The reference type argument to validate as non-. /// The name of the parameter with which corresponds. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfNull([NotNull] object? argument, [CallerArgumentExpression("argument")] string? paramName = null) + public static void ThrowIfNull([NotNull] object? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null) { if (argument is null) { @@ -42,7 +42,7 @@ public static class For /// The reference type argument to validate as non-. /// The name of the parameter with which corresponds. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfNull([NotNull] T? argument, [CallerArgumentExpression("argument")] string? paramName = null) + public static void ThrowIfNull([NotNull] T? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null) { if (argument is null) { diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 000000000..f3c2112ae --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,11 @@ + + + + + + + all + build; analyzers + + + \ No newline at end of file diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets new file mode 100644 index 000000000..36dde4ce7 --- /dev/null +++ b/src/Directory.Build.targets @@ -0,0 +1,55 @@ + + + + + + + NETSTANDARD2_1_OR_GREATER + + + + + true + true + + + + + $(IntermediateOutputPath)$(MSBuildProjectName).SkipLocalsInit.g.cs + + +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +[module: global::System.Runtime.CompilerServices.SkipLocalsInitAttribute]]]> + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/CommunityToolkit.Common.UnitTests/CommunityToolkit.Common.UnitTests.csproj b/tests/CommunityToolkit.Common.UnitTests/CommunityToolkit.Common.UnitTests.csproj index 1069b19a1..3436db962 100644 --- a/tests/CommunityToolkit.Common.UnitTests/CommunityToolkit.Common.UnitTests.csproj +++ b/tests/CommunityToolkit.Common.UnitTests/CommunityToolkit.Common.UnitTests.csproj @@ -1,17 +1,17 @@ - net472;netcoreapp3.1;net6.0 + net472;net6.0;net7.0 - - - + + + - + \ No newline at end of file diff --git a/tests/CommunityToolkit.Common.UnitTests/Test_Converters.cs b/tests/CommunityToolkit.Common.UnitTests/Test_Converters.cs new file mode 100644 index 000000000..83fd3db8f --- /dev/null +++ b/tests/CommunityToolkit.Common.UnitTests/Test_Converters.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace CommunityToolkit.Common.UnitTests; + +[TestClass] +public class Test_Converters +{ + [TestMethod] + [DataRow(1024L - 1, "1023 bytes")] + [DataRow(1024L, "1.0 KB")] + [DataRow(1024L * 1024, "1.0 MB")] + [DataRow(1024L * 1024 * 1024, "1.0 GB")] + [DataRow(1024L * 1024 * 1024 * 1024, "1.0 TB")] + [DataRow(1024L * 1024 * 1024 * 1024 * 1024, "1.0 PB")] + [DataRow(1024L * 1024 * 1024 * 1024 * 1024 * 1024, "1.0 EB")] + public void Test_ToFileSizeString(long size, string expected) + { + Assert.AreEqual(expected, Converters.ToFileSizeString(size)); + } +} \ No newline at end of file diff --git a/tests/CommunityToolkit.Diagnostics.UnitTests/CommunityToolkit.Diagnostics.UnitTests.csproj b/tests/CommunityToolkit.Diagnostics.UnitTests/CommunityToolkit.Diagnostics.UnitTests.csproj index b59f45f06..c1f33bdb7 100644 --- a/tests/CommunityToolkit.Diagnostics.UnitTests/CommunityToolkit.Diagnostics.UnitTests.csproj +++ b/tests/CommunityToolkit.Diagnostics.UnitTests/CommunityToolkit.Diagnostics.UnitTests.csproj @@ -1,17 +1,17 @@ - net472;netcoreapp3.1;net6.0 + net472;net6.0;net7.0 - - - + + + - + \ No newline at end of file diff --git a/tests/CommunityToolkit.HighPerformance.UnitTests/Buffers/Internals/UnmanagedSpanOwner.cs b/tests/CommunityToolkit.HighPerformance.UnitTests/Buffers/Internals/UnmanagedSpanOwner.cs index 4506f533e..c6154a380 100644 --- a/tests/CommunityToolkit.HighPerformance.UnitTests/Buffers/Internals/UnmanagedSpanOwner.cs +++ b/tests/CommunityToolkit.HighPerformance.UnitTests/Buffers/Internals/UnmanagedSpanOwner.cs @@ -4,7 +4,6 @@ using System; using System.Buffers; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace CommunityToolkit.HighPerformance.UnitTests.Buffers.Internals; @@ -32,7 +31,7 @@ internal sealed unsafe class UnmanagedSpanOwner : MemoryManager /// The size of the buffer to rent. public UnmanagedSpanOwner(int size) { - this.ptr = Marshal.AllocHGlobal(size * Unsafe.SizeOf()); + this.ptr = Marshal.AllocHGlobal(size * sizeof(T)); this.length = size; } diff --git a/tests/CommunityToolkit.HighPerformance.UnitTests/Buffers/Test_StringPool.cs b/tests/CommunityToolkit.HighPerformance.UnitTests/Buffers/Test_StringPool.cs index 915b21efb..08a3715a8 100644 --- a/tests/CommunityToolkit.HighPerformance.UnitTests/Buffers/Test_StringPool.cs +++ b/tests/CommunityToolkit.HighPerformance.UnitTests/Buffers/Test_StringPool.cs @@ -217,7 +217,7 @@ public void Test_StringPool_GetOrAdd_ReadOnlySpan_Misc() Assert.AreEqual(nameof(helloworld), helloworld); Assert.AreEqual(nameof(dotnetCommunityToolkit), dotnetCommunityToolkit); -#if NETCOREAPP +#if NET6_0_OR_GREATER // .NET Framework reuses strings in a way that makes these tests fail. // The actual underlying APIs are still working as expected though. diff --git a/tests/CommunityToolkit.HighPerformance.UnitTests/CommunityToolkit.HighPerformance.UnitTests.csproj b/tests/CommunityToolkit.HighPerformance.UnitTests/CommunityToolkit.HighPerformance.UnitTests.csproj index e09447c38..cd84ec4af 100644 --- a/tests/CommunityToolkit.HighPerformance.UnitTests/CommunityToolkit.HighPerformance.UnitTests.csproj +++ b/tests/CommunityToolkit.HighPerformance.UnitTests/CommunityToolkit.HighPerformance.UnitTests.csproj @@ -1,18 +1,19 @@ - net472;netcoreapp3.1;net6.0 + net472;net6.0;net7.0 true + $(NoWarn);CA2252 - + - - - + + + \ No newline at end of file diff --git a/tests/CommunityToolkit.HighPerformance.UnitTests/Enumerables/Test_ReadOnlyRefEnumerable{T}.cs b/tests/CommunityToolkit.HighPerformance.UnitTests/Enumerables/Test_ReadOnlyRefEnumerable{T}.cs index 7623fb0c0..964914d6a 100644 --- a/tests/CommunityToolkit.HighPerformance.UnitTests/Enumerables/Test_ReadOnlyRefEnumerable{T}.cs +++ b/tests/CommunityToolkit.HighPerformance.UnitTests/Enumerables/Test_ReadOnlyRefEnumerable{T}.cs @@ -2,10 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if NETCOREAPP +#if NET6_0_OR_GREATER using System; -using System.Runtime.CompilerServices; using CommunityToolkit.HighPerformance.Enumerables; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -41,9 +40,9 @@ public void Test_ReadOnlyRefEnumerable_DangerousCreate_Ok(int length, int step, [DataRow(10, -14)] [DataRow(-32, -1)] [ExpectedException(typeof(ArgumentOutOfRangeException))] - public void Test_ReadOnlyRefEnumerable_DangerousCreate_BelowZero(int length, int step) + public unsafe void Test_ReadOnlyRefEnumerable_DangerousCreate_BelowZero(int length, int step) { - _ = ReadOnlyRefEnumerable.DangerousCreate(in Unsafe.NullRef(), length, step); + _ = ReadOnlyRefEnumerable.DangerousCreate(in *(int*)null, length, step); } [TestMethod] @@ -81,7 +80,7 @@ public void Test_ReadOnlyRefEnumerable_Indexer_ThrowsIndexOutOfRange() _ = Assert.ThrowsException(() => ReadOnlyRefEnumerable.DangerousCreate(in array[0], array.Length, 1)[array.Length]); } -#if NETCOREAPP3_1_OR_GREATER +#if NET6_0_OR_GREATER [TestMethod] [DataRow(1, new[] { 1 })] [DataRow(1, new[] { 1, 2, 3, 4 })] diff --git a/tests/CommunityToolkit.HighPerformance.UnitTests/Enumerables/Test_RefEnumerable{T}.cs b/tests/CommunityToolkit.HighPerformance.UnitTests/Enumerables/Test_RefEnumerable{T}.cs index 2763e1cb0..589e7d650 100644 --- a/tests/CommunityToolkit.HighPerformance.UnitTests/Enumerables/Test_RefEnumerable{T}.cs +++ b/tests/CommunityToolkit.HighPerformance.UnitTests/Enumerables/Test_RefEnumerable{T}.cs @@ -2,10 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if NETCOREAPP +#if NET6_0_OR_GREATER using System; -using System.Runtime.CompilerServices; using CommunityToolkit.HighPerformance.Enumerables; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -41,9 +40,9 @@ public void Test_RefEnumerable_DangerousCreate_Ok(int length, int step, int[] va [DataRow(10, -14)] [DataRow(-32, -1)] [ExpectedException(typeof(ArgumentOutOfRangeException))] - public void Test_RefEnumerable_DangerousCreate_BelowZero(int length, int step) + public unsafe void Test_RefEnumerable_DangerousCreate_BelowZero(int length, int step) { - _ = RefEnumerable.DangerousCreate(ref Unsafe.NullRef(), length, step); + _ = RefEnumerable.DangerousCreate(ref *(int*)null, length, step); } [TestMethod] @@ -81,7 +80,7 @@ public void Test_RefEnumerable_Indexer_ThrowsIndexOutOfRange() _ = Assert.ThrowsException(() => RefEnumerable.DangerousCreate(ref array[0], array.Length, 1)[array.Length]); } -#if NETCOREAPP3_1_OR_GREATER +#if NET6_0_OR_GREATER [TestMethod] [DataRow(1, new[] { 1 })] [DataRow(1, new[] { 1, 2, 3, 4 })] diff --git a/tests/CommunityToolkit.HighPerformance.UnitTests/Extensions/Test_ArrayExtensions.2D.cs b/tests/CommunityToolkit.HighPerformance.UnitTests/Extensions/Test_ArrayExtensions.2D.cs index 44fb8aad5..5d41cf9b0 100644 --- a/tests/CommunityToolkit.HighPerformance.UnitTests/Extensions/Test_ArrayExtensions.2D.cs +++ b/tests/CommunityToolkit.HighPerformance.UnitTests/Extensions/Test_ArrayExtensions.2D.cs @@ -419,7 +419,7 @@ public void Test_ArrayExtensions_2D_GetColumn_Empty() _ = Assert.ThrowsException(() => array.GetColumn(0).ToArray()); } -#if NETCOREAPP3_1_OR_GREATER +#if NET6_0_OR_GREATER [TestMethod] public void Test_ArrayExtensions_2D_AsSpan_Empty() { diff --git a/tests/CommunityToolkit.HighPerformance.UnitTests/Extensions/Test_IBufferWriterExtensions.cs b/tests/CommunityToolkit.HighPerformance.UnitTests/Extensions/Test_IBufferWriterExtensions.cs index 63696d903..6ed22de1e 100644 --- a/tests/CommunityToolkit.HighPerformance.UnitTests/Extensions/Test_IBufferWriterExtensions.cs +++ b/tests/CommunityToolkit.HighPerformance.UnitTests/Extensions/Test_IBufferWriterExtensions.cs @@ -3,11 +3,10 @@ // See the LICENSE file in the project root for more information. using System; -#if NETCOREAPP +#if NET6_0_OR_GREATER using System.Buffers; #endif using System.IO; -using System.Runtime.CompilerServices; using CommunityToolkit.HighPerformance.Buffers; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -17,7 +16,7 @@ namespace CommunityToolkit.HighPerformance.UnitTests.Extensions; public class Test_IBufferWriterExtensions { [TestMethod] - public void Test_IBufferWriterExtensions_WriteReadOverBytes() + public unsafe void Test_IBufferWriterExtensions_WriteReadOverBytes() { ArrayPoolBufferWriter writer = new(); @@ -33,7 +32,7 @@ public void Test_IBufferWriterExtensions_WriteReadOverBytes() writer.Write(d); writer.Write(guid); - int count = sizeof(byte) + sizeof(char) + sizeof(float) + sizeof(double) + Unsafe.SizeOf(); + int count = sizeof(byte) + sizeof(char) + sizeof(float) + sizeof(double) + sizeof(Guid); Assert.AreEqual(count, writer.WrittenCount); diff --git a/tests/CommunityToolkit.HighPerformance.UnitTests/Extensions/Test_SpinLockExtensions.cs b/tests/CommunityToolkit.HighPerformance.UnitTests/Extensions/Test_SpinLockExtensions.cs index 8991a7411..6857393fe 100644 --- a/tests/CommunityToolkit.HighPerformance.UnitTests/Extensions/Test_SpinLockExtensions.cs +++ b/tests/CommunityToolkit.HighPerformance.UnitTests/Extensions/Test_SpinLockExtensions.cs @@ -6,6 +6,8 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; +#pragma warning disable CS0618 + namespace CommunityToolkit.HighPerformance.UnitTests.Extensions; [TestClass] @@ -33,7 +35,7 @@ public unsafe void Test_ArrayExtensions_Pointer() Assert.AreEqual(sum, 1000 * 10); } -#if !NETFRAMEWORK +#if NET7_0_OR_GREATER [TestMethod] public void Test_ArrayExtensions_Ref() { diff --git a/tests/CommunityToolkit.HighPerformance.UnitTests/Extensions/Test_StreamExtensions.cs b/tests/CommunityToolkit.HighPerformance.UnitTests/Extensions/Test_StreamExtensions.cs index 73b7c64e3..4977176ef 100644 --- a/tests/CommunityToolkit.HighPerformance.UnitTests/Extensions/Test_StreamExtensions.cs +++ b/tests/CommunityToolkit.HighPerformance.UnitTests/Extensions/Test_StreamExtensions.cs @@ -4,7 +4,6 @@ using System; using System.IO; -using CommunityToolkit.HighPerformance; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace CommunityToolkit.HighPerformance.UnitTests.Extensions; @@ -35,6 +34,85 @@ public void Test_StreamExtensions_ReadWrite() Assert.AreEqual(3.14f, stream.Read()); Assert.AreEqual(unchecked(uint.MaxValue * 324823489204ul), stream.Read()); - _ = Assert.ThrowsException(() => stream.Read()); + _ = Assert.ThrowsException(() => stream.Read()); + } + + // See https://github.com/CommunityToolkit/dotnet/issues/513 + [TestMethod] + public void Test_StreamExtensions_ReadWrite_WithBufferedStream() + { + Stream stream = new BufferedStream(); + + stream.Write(true); + stream.Write(42); + stream.Write(3.14f); + stream.Write(unchecked(uint.MaxValue * 324823489204ul)); + + stream.Position = 0; + + Assert.AreEqual(true, stream.Read()); + Assert.AreEqual(42, stream.Read()); + Assert.AreEqual(3.14f, stream.Read()); + Assert.AreEqual(unchecked(uint.MaxValue * 324823489204ul), stream.Read()); + + _ = Assert.ThrowsException(() => stream.Read()); + } + + private sealed class BufferedStream : MemoryStream + { + private ReadOnlyMemory bufferedBytes; + + public override int Read(byte[] buffer, int offset, int count) + { + if (this.bufferedBytes.IsEmpty) + { + this.bufferedBytes = ReadMoreBytes(); + } + + int bytesToCopy = Math.Min(this.bufferedBytes.Length, count); + + this.bufferedBytes.Span.Slice(0, bytesToCopy).CopyTo(buffer.AsSpan(offset, count)); + this.bufferedBytes = this.bufferedBytes.Slice(bytesToCopy); + + return bytesToCopy; + } + +#if NET6_0_OR_GREATER + public override int Read(Span buffer) + { + if (this.bufferedBytes.IsEmpty) + { + this.bufferedBytes = ReadMoreBytes(); + } + + int bytesToCopy = Math.Min(this.bufferedBytes.Length, buffer.Length); + + this.bufferedBytes.Span.Slice(0, bytesToCopy).CopyTo(buffer); + this.bufferedBytes = this.bufferedBytes.Slice(bytesToCopy); + + return bytesToCopy; + } +#endif + + private byte[] ReadMoreBytes() + { + byte[] array = new byte[3]; + int bytesOffset = 0; + + do + { + int bytesRead = base.Read(array, bytesOffset, 3 - bytesOffset); + + bytesOffset += bytesRead; + + if (bytesRead == 0) + { + return array.AsSpan(0, bytesOffset).ToArray(); + } + } + while (bytesOffset < 3); + + return array; + } } } diff --git a/tests/CommunityToolkit.HighPerformance.UnitTests/Helpers/Internals/Test_RuntimeHelpers.cs b/tests/CommunityToolkit.HighPerformance.UnitTests/Helpers/Internals/Test_RuntimeHelpers.cs index 0d741f7d1..640d3a702 100644 --- a/tests/CommunityToolkit.HighPerformance.UnitTests/Helpers/Internals/Test_RuntimeHelpers.cs +++ b/tests/CommunityToolkit.HighPerformance.UnitTests/Helpers/Internals/Test_RuntimeHelpers.cs @@ -2,7 +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. -#if !NETCOREAPP3_1_OR_GREATER +#if !NET6_0_OR_GREATER using System; using CommunityToolkit.HighPerformance.Helpers.Internals; diff --git a/tests/CommunityToolkit.HighPerformance.UnitTests/Helpers/Test_HashCode{T}.cs b/tests/CommunityToolkit.HighPerformance.UnitTests/Helpers/Test_HashCode{T}.cs index 36029a2af..4191acacb 100644 --- a/tests/CommunityToolkit.HighPerformance.UnitTests/Helpers/Test_HashCode{T}.cs +++ b/tests/CommunityToolkit.HighPerformance.UnitTests/Helpers/Test_HashCode{T}.cs @@ -55,7 +55,7 @@ public void Test_HashCodeOfT_VectorUnsupportedTypes_TestRepeat() TestForType(); } -#if NETCOREAPP3_1_OR_GREATER +#if NET6_0_OR_GREATER [TestMethod] public void Test_HashCodeOfT_ManagedType_TestRepeat() { diff --git a/tests/CommunityToolkit.HighPerformance.UnitTests/Helpers/Test_ParallelHelper.For.cs b/tests/CommunityToolkit.HighPerformance.UnitTests/Helpers/Test_ParallelHelper.For.cs index 7890c83a3..152527604 100644 --- a/tests/CommunityToolkit.HighPerformance.UnitTests/Helpers/Test_ParallelHelper.For.cs +++ b/tests/CommunityToolkit.HighPerformance.UnitTests/Helpers/Test_ParallelHelper.For.cs @@ -38,7 +38,7 @@ public unsafe void Test_ParallelHelper_ForWithIndices() } } -#if NETCOREAPP3_1_OR_GREATER +#if NET6_0_OR_GREATER [TestMethod] [ExpectedException(typeof(ArgumentException))] public void Test_ParallelHelper_ForInvalidRange_FromEnd() diff --git a/tests/CommunityToolkit.HighPerformance.UnitTests/Helpers/Test_ParallelHelper.For2D.cs b/tests/CommunityToolkit.HighPerformance.UnitTests/Helpers/Test_ParallelHelper.For2D.cs index 6226967fe..157b6a4a4 100644 --- a/tests/CommunityToolkit.HighPerformance.UnitTests/Helpers/Test_ParallelHelper.For2D.cs +++ b/tests/CommunityToolkit.HighPerformance.UnitTests/Helpers/Test_ParallelHelper.For2D.cs @@ -50,7 +50,7 @@ public unsafe void Test_ParallelHelper_For2DWithIndices() } } -#if NETCOREAPP3_1_OR_GREATER +#if NET6_0_OR_GREATER [TestMethod] [ExpectedException(typeof(ArgumentException))] public void Test_ParallelHelper_For2DInvalidRange_FromEnd() diff --git a/tests/CommunityToolkit.HighPerformance.UnitTests/Memory/Test_Memory2D{T}.cs b/tests/CommunityToolkit.HighPerformance.UnitTests/Memory/Test_Memory2D{T}.cs index 028a21628..d1e365876 100644 --- a/tests/CommunityToolkit.HighPerformance.UnitTests/Memory/Test_Memory2D{T}.cs +++ b/tests/CommunityToolkit.HighPerformance.UnitTests/Memory/Test_Memory2D{T}.cs @@ -194,7 +194,7 @@ public void Test_Memory2DT_Array3DConstructor_2() _ = Assert.ThrowsException(() => new Memory2D(array, 0, 0, 0, 3, 3)); } -#if NETCOREAPP +#if NET6_0_OR_GREATER [TestMethod] public void Test_Memory2DT_MemoryConstructor() { @@ -352,7 +352,7 @@ public void Test_Memory2DT_TryGetMemory_2() Assert.AreEqual(memory.Span[2], 3); } -#if NETCOREAPP +#if NET6_0_OR_GREATER [TestMethod] public void Test_Memory2DT_TryGetMemory_3() { diff --git a/tests/CommunityToolkit.HighPerformance.UnitTests/Memory/Test_ReadOnlyMemory2D{T}.cs b/tests/CommunityToolkit.HighPerformance.UnitTests/Memory/Test_ReadOnlyMemory2D{T}.cs index fda5cd9bd..55cafefcb 100644 --- a/tests/CommunityToolkit.HighPerformance.UnitTests/Memory/Test_ReadOnlyMemory2D{T}.cs +++ b/tests/CommunityToolkit.HighPerformance.UnitTests/Memory/Test_ReadOnlyMemory2D{T}.cs @@ -174,7 +174,7 @@ public void Test_ReadOnlyMemory2DT_Array3DConstructor_2() _ = Assert.ThrowsException(() => new ReadOnlyMemory2D(array, 0, 0, 0, 3, 3)); } -#if NETCOREAPP +#if NET6_0_OR_GREATER [TestMethod] public void Test_ReadOnlyMemory2DT_ReadOnlyMemoryConstructor() { @@ -316,7 +316,7 @@ public void Test_ReadOnlyMemory2DT_TryGetReadOnlyMemory_2() Assert.AreEqual(memory.Span[2], 3); } -#if NETCOREAPP +#if NET6_0_OR_GREATER [TestMethod] public void Test_ReadOnlyMemory2DT_TryGetReadOnlyMemory_3() { diff --git a/tests/CommunityToolkit.HighPerformance.UnitTests/Memory/Test_ReadOnlySpan2D{T}.cs b/tests/CommunityToolkit.HighPerformance.UnitTests/Memory/Test_ReadOnlySpan2D{T}.cs index 5c4ce1731..4089b74f5 100644 --- a/tests/CommunityToolkit.HighPerformance.UnitTests/Memory/Test_ReadOnlySpan2D{T}.cs +++ b/tests/CommunityToolkit.HighPerformance.UnitTests/Memory/Test_ReadOnlySpan2D{T}.cs @@ -35,7 +35,7 @@ public void Test_ReadOnlySpan2DT_Empty() Assert.AreEqual(empty2.Height, 0); } -#if NETCOREAPP +#if NET6_0_OR_GREATER [TestMethod] public unsafe void Test_ReadOnlySpan2DT_RefConstructor() { @@ -351,7 +351,7 @@ public unsafe void Test_ReadOnlySpan2DT_GetPinnableReference() { Assert.IsTrue(Unsafe.AreSame( ref Unsafe.AsRef(null), - ref ReadOnlySpan2D.Empty.GetPinnableReference())); + ref Unsafe.AsRef(in ReadOnlySpan2D.Empty.GetPinnableReference()))); int[,] array = { @@ -361,7 +361,7 @@ ref Unsafe.AsRef(null), ReadOnlySpan2D span2d = new(array); - ref int r0 = ref span2d.GetPinnableReference(); + ref int r0 = ref Unsafe.AsRef(in span2d.GetPinnableReference()); Assert.IsTrue(Unsafe.AreSame(ref r0, ref array[0, 0])); } @@ -386,7 +386,7 @@ ref Unsafe.AsRef(null), Assert.IsTrue(Unsafe.AreSame(ref r0, ref array[0, 0])); } -#if NETCOREAPP3_1_OR_GREATER +#if NET6_0_OR_GREATER [TestMethod] public unsafe void Test_ReadOnlySpan2DT_Index_Indexer_1() { @@ -535,7 +535,7 @@ public void Test_ReadOnlySpan2DT_Slice_2() Assert.AreEqual(slice3[0, 0], 5); } -#if NETCOREAPP +#if NET6_0_OR_GREATER [TestMethod] public void Test_ReadOnlySpan2DT_GetRowReadOnlySpan() { diff --git a/tests/CommunityToolkit.HighPerformance.UnitTests/Memory/Test_Span2D{T}.cs b/tests/CommunityToolkit.HighPerformance.UnitTests/Memory/Test_Span2D{T}.cs index 05f0946e6..15be6300c 100644 --- a/tests/CommunityToolkit.HighPerformance.UnitTests/Memory/Test_Span2D{T}.cs +++ b/tests/CommunityToolkit.HighPerformance.UnitTests/Memory/Test_Span2D{T}.cs @@ -46,7 +46,7 @@ public void Test_Span2DT_Empty() Assert.AreEqual(empty4.Height, 0); } -#if NETCOREAPP +#if NET6_0_OR_GREATER [TestMethod] public unsafe void Test_Span2DT_RefConstructor() { @@ -546,7 +546,7 @@ ref Unsafe.AsRef(null), Assert.IsTrue(Unsafe.AreSame(ref r0, ref array[0, 0])); } -#if NETCOREAPP3_1_OR_GREATER +#if NET6_0_OR_GREATER [TestMethod] public unsafe void Test_Span2DT_Index_Indexer_1() { @@ -700,7 +700,7 @@ public void Test_Span2DT_Slice_2() Assert.AreEqual(slice3[0, 0], 5); } -#if NETCOREAPP +#if NET6_0_OR_GREATER [TestMethod] public void Test_Span2DT_GetRowSpan() { diff --git a/tests/CommunityToolkit.HighPerformance.UnitTests/Test_NullableReadOnlyRef{T}.cs b/tests/CommunityToolkit.HighPerformance.UnitTests/Test_NullableReadOnlyRef{T}.cs index bcf9ac5dd..7ddffd849 100644 --- a/tests/CommunityToolkit.HighPerformance.UnitTests/Test_NullableReadOnlyRef{T}.cs +++ b/tests/CommunityToolkit.HighPerformance.UnitTests/Test_NullableReadOnlyRef{T}.cs @@ -2,7 +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. -#if NETCOREAPP3_1_OR_GREATER +#if NET7_0_OR_GREATER using System; using System.Runtime.CompilerServices; diff --git a/tests/CommunityToolkit.HighPerformance.UnitTests/Test_NullableRef{T}.cs b/tests/CommunityToolkit.HighPerformance.UnitTests/Test_NullableRef{T}.cs index b10ae8808..7799a67ff 100644 --- a/tests/CommunityToolkit.HighPerformance.UnitTests/Test_NullableRef{T}.cs +++ b/tests/CommunityToolkit.HighPerformance.UnitTests/Test_NullableRef{T}.cs @@ -2,7 +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. -#if NETCOREAPP3_1_OR_GREATER +#if NET7_0_OR_GREATER using System; using System.Runtime.CompilerServices; @@ -76,4 +76,4 @@ public void Test_NullableRefOfT_CreateNullableRefOfT_ExplicitCastOfT_Exception() } } -#endif \ No newline at end of file +#endif diff --git a/tests/CommunityToolkit.HighPerformance.UnitTests/Test_ReadOnlyRef{T}.cs b/tests/CommunityToolkit.HighPerformance.UnitTests/Test_ReadOnlyRef{T}.cs index 863711e4e..f5c8fece8 100644 --- a/tests/CommunityToolkit.HighPerformance.UnitTests/Test_ReadOnlyRef{T}.cs +++ b/tests/CommunityToolkit.HighPerformance.UnitTests/Test_ReadOnlyRef{T}.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if NET7_0_OR_GREATER + using System.Runtime.CompilerServices; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -11,23 +13,6 @@ namespace CommunityToolkit.HighPerformance.UnitTests; public class Test_ReadOnlyRefOfT { [TestMethod] -#if NETFRAMEWORK - public void Test_RefOfT_CreateRefOfT() - { - ReadOnlyFieldOwner model = new(); - ReadOnlyRef reference = new(model, model.Value); - - Assert.IsTrue(Unsafe.AreSame(ref Unsafe.AsRef(model.Value), ref Unsafe.AsRef(reference.Value))); - } - - /// - /// A dummy model that owns an field. - /// - private sealed class ReadOnlyFieldOwner - { - public readonly int Value = 1; - } -#else public void Test_RefOfT_CreateRefOfT() { int value = 1; @@ -35,5 +20,6 @@ public void Test_RefOfT_CreateRefOfT() Assert.IsTrue(Unsafe.AreSame(ref value, ref Unsafe.AsRef(reference.Value))); } -#endif } + +#endif \ No newline at end of file diff --git a/tests/CommunityToolkit.HighPerformance.UnitTests/Test_Ref{T}.cs b/tests/CommunityToolkit.HighPerformance.UnitTests/Test_Ref{T}.cs index d59534e22..ce17f01bb 100644 --- a/tests/CommunityToolkit.HighPerformance.UnitTests/Test_Ref{T}.cs +++ b/tests/CommunityToolkit.HighPerformance.UnitTests/Test_Ref{T}.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if NET7_0_OR_GREATER + using System.Runtime.CompilerServices; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -11,27 +13,6 @@ namespace CommunityToolkit.HighPerformance.UnitTests; public class Test_RefOfT { [TestMethod] -#if NETFRAMEWORK - public void Test_RefOfT_CreateRefOfT() - { - FieldOwner model = new() { Value = 1 }; - Ref reference = new(model, ref model.Value); - - Assert.IsTrue(Unsafe.AreSame(ref model.Value, ref reference.Value)); - - reference.Value++; - - Assert.AreEqual(model.Value, 2); - } - - /// - /// A dummy model that owns an field. - /// - private sealed class FieldOwner - { - public int Value; - } -#else public void Test_RefOfT_CreateRefOfT() { int value = 1; @@ -43,5 +24,6 @@ public void Test_RefOfT_CreateRefOfT() Assert.AreEqual(value, 2); } -#endif } + +#endif \ No newline at end of file diff --git a/tests/CommunityToolkit.Mvvm.DisableINotifyPropertyChanging.UnitTests/CommunityToolkit.Mvvm.DisableINotifyPropertyChanging.UnitTests.csproj b/tests/CommunityToolkit.Mvvm.DisableINotifyPropertyChanging.UnitTests/CommunityToolkit.Mvvm.DisableINotifyPropertyChanging.UnitTests.csproj index b75b2df09..4a949f099 100644 --- a/tests/CommunityToolkit.Mvvm.DisableINotifyPropertyChanging.UnitTests/CommunityToolkit.Mvvm.DisableINotifyPropertyChanging.UnitTests.csproj +++ b/tests/CommunityToolkit.Mvvm.DisableINotifyPropertyChanging.UnitTests/CommunityToolkit.Mvvm.DisableINotifyPropertyChanging.UnitTests.csproj @@ -1,18 +1,18 @@ - net472;netcoreapp3.1;net6.0 + net472;net6.0;net7.0 - - - + + + - - + + \ No newline at end of file diff --git a/tests/CommunityToolkit.Mvvm.ExternalAssembly/CommunityToolkit.Mvvm.ExternalAssembly.csproj b/tests/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn401/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn401.csproj similarity index 51% rename from tests/CommunityToolkit.Mvvm.ExternalAssembly/CommunityToolkit.Mvvm.ExternalAssembly.csproj rename to tests/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn401/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn401.csproj index d0d39011b..ee36d9f08 100644 --- a/tests/CommunityToolkit.Mvvm.ExternalAssembly/CommunityToolkit.Mvvm.ExternalAssembly.csproj +++ b/tests/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn401/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn401.csproj @@ -13,8 +13,10 @@ - - + + + + \ No newline at end of file diff --git a/tests/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn431/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn431.csproj b/tests/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn431/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn431.csproj new file mode 100644 index 000000000..164e42e50 --- /dev/null +++ b/tests/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn431/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn431.csproj @@ -0,0 +1,20 @@ + + + + netstandard2.0 + + + + false + false + $(NoWarn);CS8002;SA0001 + + + + + + + + + + \ No newline at end of file diff --git a/tests/CommunityToolkit.Mvvm.ExternalAssembly/CommunityToolkit.Mvvm.ExternalAssembly.projitems b/tests/CommunityToolkit.Mvvm.ExternalAssembly/CommunityToolkit.Mvvm.ExternalAssembly.projitems new file mode 100644 index 000000000..c4ecf6dc4 --- /dev/null +++ b/tests/CommunityToolkit.Mvvm.ExternalAssembly/CommunityToolkit.Mvvm.ExternalAssembly.projitems @@ -0,0 +1,16 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + e827a9cd-405f-43e4-84c7-68cc7e845cdc + + + CommunityToolkit.Mvvm.ExternalAssembly + + + + + + + \ No newline at end of file diff --git a/tests/CommunityToolkit.Mvvm.ExternalAssembly/CommunityToolkit.Mvvm.ExternalAssembly.shproj b/tests/CommunityToolkit.Mvvm.ExternalAssembly/CommunityToolkit.Mvvm.ExternalAssembly.shproj new file mode 100644 index 000000000..fd605fe42 --- /dev/null +++ b/tests/CommunityToolkit.Mvvm.ExternalAssembly/CommunityToolkit.Mvvm.ExternalAssembly.shproj @@ -0,0 +1,13 @@ + + + + e827a9cd-405f-43e4-84c7-68cc7e845cdc + 14.0 + + + + + + + + diff --git a/tests/CommunityToolkit.Mvvm.ExternalAssembly/ModelWithObservableObjectAttribute.cs b/tests/CommunityToolkit.Mvvm.ExternalAssembly/ModelWithObservableObjectAttribute.cs index 93adb5da1..e42550605 100644 --- a/tests/CommunityToolkit.Mvvm.ExternalAssembly/ModelWithObservableObjectAttribute.cs +++ b/tests/CommunityToolkit.Mvvm.ExternalAssembly/ModelWithObservableObjectAttribute.cs @@ -4,6 +4,8 @@ using CommunityToolkit.Mvvm.ComponentModel; +#pragma warning disable MVVMTK0033 + namespace CommunityToolkit.Mvvm.ExternalAssembly; /// diff --git a/tests/CommunityToolkit.Mvvm.ExternalAssembly/SampleModelWithINPCAndObservableProperties.cs b/tests/CommunityToolkit.Mvvm.ExternalAssembly/SampleModelWithINPCAndObservableProperties.cs index 69a8e5dae..1066a2350 100644 --- a/tests/CommunityToolkit.Mvvm.ExternalAssembly/SampleModelWithINPCAndObservableProperties.cs +++ b/tests/CommunityToolkit.Mvvm.ExternalAssembly/SampleModelWithINPCAndObservableProperties.cs @@ -4,6 +4,8 @@ using CommunityToolkit.Mvvm.ComponentModel; +#pragma warning disable MVVMTK0032 + namespace CommunityToolkit.Mvvm.ExternalAssembly; /// diff --git a/tests/CommunityToolkit.Mvvm.Internals.UnitTests/CommunityToolkit.Mvvm.Internals.UnitTests.csproj b/tests/CommunityToolkit.Mvvm.Internals.UnitTests/CommunityToolkit.Mvvm.Internals.UnitTests.csproj index 1be705075..a1cecde7e 100644 --- a/tests/CommunityToolkit.Mvvm.Internals.UnitTests/CommunityToolkit.Mvvm.Internals.UnitTests.csproj +++ b/tests/CommunityToolkit.Mvvm.Internals.UnitTests/CommunityToolkit.Mvvm.Internals.UnitTests.csproj @@ -1,17 +1,17 @@ - net472;netcoreapp3.1;net6.0 + net472;net6.0;net7.0 - - - + + + - + \ No newline at end of file diff --git a/tests/CommunityToolkit.Mvvm.Internals.UnitTests/Test_Messenger.cs b/tests/CommunityToolkit.Mvvm.Internals.UnitTests/Test_Messenger.cs index 22fd1dd68..70f5408c2 100644 --- a/tests/CommunityToolkit.Mvvm.Internals.UnitTests/Test_Messenger.cs +++ b/tests/CommunityToolkit.Mvvm.Internals.UnitTests/Test_Messenger.cs @@ -14,7 +14,7 @@ namespace CommunityToolkit.Mvvm.Internals.UnitTests; [TestClass] public partial class Test_Messenger { -#if NETCOREAPP // Auto-trimming is disabled on .NET Framework +#if NET6_0_OR_GREATER // Auto-trimming is disabled on .NET Framework [TestMethod] public void Test_WeakReferenceMessenger_AutoCleanup() { diff --git a/tests/CommunityToolkit.Mvvm.Roslyn401.UnitTests/CommunityToolkit.Mvvm.Roslyn401.UnitTests.csproj b/tests/CommunityToolkit.Mvvm.Roslyn401.UnitTests/CommunityToolkit.Mvvm.Roslyn401.UnitTests.csproj new file mode 100644 index 000000000..b4e7b7bba --- /dev/null +++ b/tests/CommunityToolkit.Mvvm.Roslyn401.UnitTests/CommunityToolkit.Mvvm.Roslyn401.UnitTests.csproj @@ -0,0 +1,25 @@ + + + + net472;net6.0;net7.0 + true + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/CommunityToolkit.Mvvm.Roslyn431.UnitTests/CommunityToolkit.Mvvm.Roslyn431.UnitTests.csproj b/tests/CommunityToolkit.Mvvm.Roslyn431.UnitTests/CommunityToolkit.Mvvm.Roslyn431.UnitTests.csproj new file mode 100644 index 000000000..f3dad9cb7 --- /dev/null +++ b/tests/CommunityToolkit.Mvvm.Roslyn431.UnitTests/CommunityToolkit.Mvvm.Roslyn431.UnitTests.csproj @@ -0,0 +1,25 @@ + + + + net472;net6.0;net7.0 + true + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests.csproj b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests.csproj new file mode 100644 index 000000000..8bfbf73a2 --- /dev/null +++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests.csproj @@ -0,0 +1,22 @@ + + + + net472;net6.0;net7.0 + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.UnitTests.csproj b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.UnitTests.csproj new file mode 100644 index 000000000..f8974ac8f --- /dev/null +++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.UnitTests.csproj @@ -0,0 +1,22 @@ + + + + net472;net6.0;net7.0 + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests.csproj b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests.csproj deleted file mode 100644 index c0e0b2a21..000000000 --- a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - net472;netcoreapp3.1;net6.0 - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests.projitems b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests.projitems new file mode 100644 index 000000000..ab60a1c18 --- /dev/null +++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests.projitems @@ -0,0 +1,15 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + fb59ce88-7732-4a63-b5bd-ac5681b7da1a + + + CommunityToolkit.Mvvm.SourceGenerators.UnitTests + + + + + + \ No newline at end of file diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests.shproj b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests.shproj new file mode 100644 index 000000000..5e904f534 --- /dev/null +++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests.shproj @@ -0,0 +1,13 @@ + + + + fb59ce88-7732-4a63-b5bd-ac5681b7da1a + 14.0 + + + + + + + + diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Helpers/CSharpAnalyzerWithLanguageVersionTest{TAnalyzer}.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Helpers/CSharpAnalyzerWithLanguageVersionTest{TAnalyzer}.cs new file mode 100644 index 000000000..a2610aad7 --- /dev/null +++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Helpers/CSharpAnalyzerWithLanguageVersionTest{TAnalyzer}.cs @@ -0,0 +1,65 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.ComponentModel; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis; +#if NET472 +using System.ComponentModel.DataAnnotations; +#endif + +namespace CommunityToolkit.Mvvm.SourceGenerators.UnitTests.Helpers; + +/// +/// A custom that uses a specific C# language version to parse code. +/// +/// The type of the analyzer to test. +internal sealed class CSharpAnalyzerWithLanguageVersionTest : CSharpAnalyzerTest + where TAnalyzer : DiagnosticAnalyzer, new() +{ + /// + /// The C# language version to use to parse code. + /// + private readonly LanguageVersion languageVersion; + + /// + /// Creates a new instance with the specified paramaters. + /// + /// The C# language version to use to parse code. + private CSharpAnalyzerWithLanguageVersionTest(LanguageVersion languageVersion) + { + this.languageVersion = languageVersion; + } + + /// + protected override ParseOptions CreateParseOptions() + { + return new CSharpParseOptions(this.languageVersion, DocumentationMode.Diagnose); + } + + /// + /// The language version to use to run the test. + public static Task VerifyAnalyzerAsync(string source, LanguageVersion languageVersion, params DiagnosticResult[] expected) + { + CSharpAnalyzerWithLanguageVersionTest test = new(languageVersion) { TestCode = source }; + +#if NET6_0_OR_GREATER + test.TestState.ReferenceAssemblies = ReferenceAssemblies.Net.Net60; +#else + test.TestState.ReferenceAssemblies = ReferenceAssemblies.NetFramework.Net472.Default; + test.TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(RequiredAttribute).Assembly.Location)); +#endif + test.TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(ObservableObject).Assembly.Location)); + + test.ExpectedDiagnostics.AddRange(expected); + + return test.RunAsync(CancellationToken.None); + } +} diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs index 263d66837..a2cd11c3e 100644 --- a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs +++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs @@ -11,6 +11,10 @@ using Microsoft.CodeAnalysis.CSharp; using CommunityToolkit.Mvvm.ComponentModel; using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.SourceGenerators.UnitTests.Helpers; +using System.Text.RegularExpressions; +using Microsoft.CodeAnalysis.Diagnostics; namespace CommunityToolkit.Mvvm.SourceGenerators.UnitTests; @@ -20,7 +24,7 @@ public class Test_SourceGeneratorsDiagnostics [TestMethod] public void DuplicateINotifyPropertyChangedInterfaceForINotifyPropertyChangedAttributeError_Explicit() { - string source = @" + string source = """ using System.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel; @@ -31,7 +35,8 @@ public partial class SampleViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler? PropertyChanged; } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0001"); } @@ -39,7 +44,7 @@ public partial class SampleViewModel : INotifyPropertyChanged [TestMethod] public void DuplicateINotifyPropertyChangedInterfaceForINotifyPropertyChangedAttributeError_Inherited() { - string source = @" + string source = """ using System.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel; @@ -58,7 +63,8 @@ namespace MyApp public partial class SampleViewModel : ObservableObject { } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0001"); } @@ -66,7 +72,7 @@ public partial class SampleViewModel : ObservableObject [TestMethod] public void DuplicateINotifyPropertyChangedInterfaceForObservableObjectAttributeError_Explicit() { - string source = @" + string source = """ using System.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel; @@ -77,7 +83,8 @@ public partial class SampleViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler? PropertyChanged; } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0002"); } @@ -85,7 +92,7 @@ public partial class SampleViewModel : INotifyPropertyChanged [TestMethod] public void DuplicateINotifyPropertyChangedInterfaceForObservableObjectAttributeError_Inherited() { - string source = @" + string source = """ using System.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel; @@ -103,7 +110,8 @@ namespace MyApp public partial class SampleViewModel : ObservableObject { } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0002"); } @@ -111,7 +119,7 @@ public partial class SampleViewModel : ObservableObject [TestMethod] public void DuplicateINotifyPropertyChangingInterfaceForObservableObjectAttributeError_Explicit() { - string source = @" + string source = """ using System.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel; @@ -122,7 +130,8 @@ public partial class SampleViewModel : INotifyPropertyChanging { public event PropertyChangingEventHandler? PropertyChanging; } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0003"); } @@ -130,7 +139,7 @@ public partial class SampleViewModel : INotifyPropertyChanging [TestMethod] public void DuplicateINotifyPropertyChangingInterfaceForObservableObjectAttributeError_Inherited() { - string source = @" + string source = """ using System.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel; @@ -145,7 +154,8 @@ public abstract class MyBaseViewModel : INotifyPropertyChanging public partial class SampleViewModel : MyBaseViewModel { } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0003"); } @@ -153,7 +163,7 @@ public partial class SampleViewModel : MyBaseViewModel [TestMethod] public void DuplicateObservableRecipientError() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace CommunityToolkit.Mvvm.ComponentModel @@ -169,7 +179,8 @@ namespace MyApp public partial class SampleViewModel : ObservableRecipient { } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0004"); } @@ -177,7 +188,7 @@ public partial class SampleViewModel : ObservableRecipient [TestMethod] public void MissingBaseObservableObjectFunctionalityError() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp @@ -186,7 +197,8 @@ namespace MyApp public partial class SampleViewModel { } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0005"); } @@ -194,7 +206,7 @@ public partial class SampleViewModel [TestMethod] public void MissingObservableValidatorInheritanceForValidationAttributeError() { - string source = @" + string source = """ using System.ComponentModel.DataAnnotations; using CommunityToolkit.Mvvm.ComponentModel; @@ -207,7 +219,8 @@ public partial class SampleViewModel [Required] private string name; } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0006"); } @@ -215,7 +228,7 @@ public partial class SampleViewModel [TestMethod] public void InvalidRelayCommandMethodSignatureError() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.Input; namespace MyApp @@ -223,76 +236,101 @@ namespace MyApp public partial class SampleViewModel { [RelayCommand] - private string GreetUser() => ""Hello world!""; + private string GreetUser() => "Hello world!"; } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0007"); } [TestMethod] - public void UnsupportedCSharpLanguageVersion_FromINotifyPropertyChangedGenerator() + public async Task UnsupportedCSharpLanguageVersion_FromINotifyPropertyChangedGenerator() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp { [INotifyPropertyChanged] - public partial class SampleViewModel + public partial class {|MVVMTK0008:SampleViewModel|} { } - }"; + } + """; - VerifyGeneratedDiagnostics( - CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7_3)), - "MVVMTK0008"); + await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.CSharp7_3); } [TestMethod] - public void UnsupportedCSharpLanguageVersion_FromObservableObjectGenerator() + public async Task UnsupportedCSharpLanguageVersion_FromObservableObjectGenerator() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp { [ObservableObject] - public partial class SampleViewModel + public partial class {|MVVMTK0008:SampleViewModel|} { } - }"; + } + """; - VerifyGeneratedDiagnostics( - CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7_3)), - "MVVMTK0008"); + await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.CSharp7_3); } [TestMethod] - public void UnsupportedCSharpLanguageVersion_FromObservablePropertyGenerator() + public async Task UnsupportedCSharpLanguageVersion_FromObservablePropertyGenerator() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp { [INotifyPropertyChanged] - public partial class SampleViewModel + public partial class {|MVVMTK0008:SampleViewModel|} { [ObservableProperty] - private string name; + private string {|MVVMTK0008:name|}; + } + } + """; + + await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.CSharp7_3); + } + + [TestMethod] + public async Task UnsupportedCSharpLanguageVersion_FromObservablePropertyGenerator_MultipleAttributes() + { + string source = """ + using CommunityToolkit.Mvvm.ComponentModel; + + namespace MyApp + { + [ObservableObject] + public partial class {|MVVMTK0008:SampleViewModel|} + { + [ObservableProperty] + string {|MVVMTK0008:name|}; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(Bar))] + int {|MVVMTK0008:number|}; + + string Bar { get; set; } } - }"; + } + """; - VerifyGeneratedDiagnostics( - CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7_3)), - "MVVMTK0008"); + await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.CSharp7_3); } [TestMethod] - public void UnsupportedCSharpLanguageVersion_FromObservableValidatorValidateAllPropertiesGenerator() + public async Task UnsupportedCSharpLanguageVersion_FromObservableValidatorValidateAllPropertiesGenerator() { - string source = @" + string source = """ + using System.ComponentModel.DataAnnotations; using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp @@ -302,17 +340,16 @@ public partial class SampleViewModel : ObservableValidator [Required] public string Name { get; set; } } - }"; + } + """; - // This is explicitly allowed in C# < 8.0, as it doesn't use any new features - VerifyGeneratedDiagnostics( - CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7_3))); + await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.CSharp7_3); } [TestMethod] - public void UnsupportedCSharpLanguageVersion_FromRelayCommandGenerator() + public async Task UnsupportedCSharpLanguageVersion_FromRelayCommandGenerator() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.Input; namespace MyApp @@ -320,21 +357,20 @@ namespace MyApp public partial class SampleViewModel { [RelayCommand] - private void GreetUser(object value) + private void {|MVVMTK0008:GreetUser|}(object value) { } } - }"; + } + """; - VerifyGeneratedDiagnostics( - CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7_3)), - "MVVMTK0008"); + await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.CSharp7_3); } [TestMethod] - public void UnsupportedCSharpLanguageVersion_FromIMessengerRegisterAllGenerator() + public async Task UnsupportedCSharpLanguageVersion_FromIMessengerRegisterAllGenerator() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.Messaging; namespace MyApp @@ -349,17 +385,16 @@ public void Receive(MyMessage message) { } } - }"; + } + """; - // This is explicitly allowed in C# < 8.0, as it doesn't use any new features - VerifyGeneratedDiagnostics( - CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7_3))); + await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.CSharp7_3); } [TestMethod] public void InvalidCanExecuteMemberName() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.Input; namespace MyApp @@ -368,12 +403,13 @@ public partial class SampleViewModel { private bool Foo => true; - [RelayCommand(CanExecute = ""Bar"")] + [RelayCommand(CanExecute = "Bar")] private void GreetUser() { } } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0009"); } @@ -381,7 +417,7 @@ private void GreetUser() [TestMethod] public void MultipleCanExecuteMemberNameMatches() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.Input; namespace MyApp @@ -397,7 +433,8 @@ private void GreetUser() { } } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0010"); } @@ -405,7 +442,7 @@ private void GreetUser() [TestMethod] public void InvalidCanExecuteMember_NonReadableProperty() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.Input; namespace MyApp @@ -419,7 +456,8 @@ private void GreetUser() { } } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0011"); } @@ -427,21 +465,22 @@ private void GreetUser() [TestMethod] public void InvalidCanExecuteMember_PropertyWithInvalidType() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.Input; namespace MyApp { public partial class SampleViewModel { - private string Foo => ""Hi!""; + private string Foo => "Hi!"; [RelayCommand(CanExecute = nameof(Foo))] private void GreetUser() { } } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0011"); } @@ -449,21 +488,22 @@ private void GreetUser() [TestMethod] public void InvalidCanExecuteMember_MethodWithInvalidType() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.Input; namespace MyApp { public partial class SampleViewModel { - private string Foo() => ""Hi!""; + private string Foo() => "Hi!"; [RelayCommand(CanExecute = nameof(Foo))] private void GreetUser() { } } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0011"); } @@ -471,7 +511,7 @@ private void GreetUser() [TestMethod] public void InvalidCanExecuteMember_MethodWithIncompatibleInputType_MissingInput() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.Input; namespace MyApp @@ -485,7 +525,8 @@ private void GreetUser() { } } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0011"); } @@ -493,7 +534,7 @@ private void GreetUser() [TestMethod] public void InvalidCanExecuteMember_MethodWithIncompatibleInputType_NonMatchingInputType() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.Input; namespace MyApp @@ -507,7 +548,8 @@ private void GreetUser(string name) { } } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0011"); } @@ -515,7 +557,7 @@ private void GreetUser(string name) [TestMethod] public void InvalidCanExecuteMember_MethodWithIncompatibleInputType_TooManyInputs() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.Input; namespace MyApp @@ -529,7 +571,8 @@ private void GreetUser(string name) { } } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0011"); } @@ -537,7 +580,7 @@ private void GreetUser(string name) [TestMethod] public void InvalidRelayCommandAllowConcurrentExecutionsOption() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.Input; namespace MyApp @@ -549,7 +592,8 @@ private void GreetUser(User user) { } } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0012"); } @@ -557,7 +601,7 @@ private void GreetUser(User user) [TestMethod] public void InvalidRelayCommandIncludeCancelCommandSettings_SynchronousMethod() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.Input; namespace MyApp @@ -569,7 +613,8 @@ private void GreetUser(User user) { } } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0013"); } @@ -577,7 +622,7 @@ private void GreetUser(User user) [TestMethod] public void InvalidRelayCommandIncludeCancelCommandSettings_AsynchronousMethodWithNoCancellationToken() { - string source = @" + string source = """ using System.Threading.Tasks; using CommunityToolkit.Mvvm.Input; @@ -590,7 +635,8 @@ private async Task DoWorkAsync() { } } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0013"); } @@ -598,7 +644,7 @@ private async Task DoWorkAsync() [TestMethod] public void InvalidRelayCommandIncludeCancelCommandSettings_AsynchronousMethodWithParameterAndNoCancellationToken() { - string source = @" + string source = """ using System.Threading.Tasks; using CommunityToolkit.Mvvm.Input; @@ -611,7 +657,8 @@ private async Task GreetUserAsync(User user) { } } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0013"); } @@ -619,7 +666,7 @@ private async Task GreetUserAsync(User user) [TestMethod] public void NameCollisionForGeneratedObservableProperty() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp @@ -629,7 +676,8 @@ public partial class SampleViewModel : ObservableObject [ObservableProperty] private string Name; } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0014"); } @@ -637,7 +685,7 @@ public partial class SampleViewModel : ObservableObject [TestMethod] public void NotifyPropertyChangedForInvalidTargetError_Null() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp @@ -648,7 +696,8 @@ public partial class SampleViewModel : ObservableObject [NotifyPropertyChangedFor(null)] private string name; } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0015"); } @@ -656,7 +705,7 @@ public partial class SampleViewModel : ObservableObject [TestMethod] public void NotifyPropertyChangedForInvalidTargetError_SamePropertyAsGeneratedOneFromSelf() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp @@ -667,7 +716,8 @@ public partial class SampleViewModel : ObservableObject [NotifyPropertyChangedFor(nameof(Name))] private string name; } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0015"); } @@ -675,7 +725,7 @@ public partial class SampleViewModel : ObservableObject [TestMethod] public void NotifyPropertyChangedForInvalidTargetError_Missing() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp @@ -683,10 +733,11 @@ namespace MyApp public partial class SampleViewModel : ObservableObject { [ObservableProperty] - [NotifyPropertyChangedFor(""FooBar"")] + [NotifyPropertyChangedFor("FooBar")] private string name; } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0015"); } @@ -694,7 +745,7 @@ public partial class SampleViewModel : ObservableObject [TestMethod] public void NotifyPropertyChangedForInvalidTargetError_InvalidType() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp @@ -709,7 +760,8 @@ public void Foo() { } } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0015"); } @@ -717,7 +769,7 @@ public void Foo() [TestMethod] public void NotifyCanExecuteChangedForInvalidTargetError_Null() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp @@ -728,7 +780,8 @@ public partial class SampleViewModel : ObservableObject [NotifyCanExecuteChangedFor(null)] private string name; } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0016"); } @@ -736,7 +789,7 @@ public partial class SampleViewModel : ObservableObject [TestMethod] public void NotifyCanExecuteChangedForInvalidTargetError_Missing() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp @@ -744,10 +797,11 @@ namespace MyApp public partial class SampleViewModel : ObservableObject { [ObservableProperty] - [NotifyCanExecuteChangedFor(""FooBar"")] + [NotifyCanExecuteChangedFor("FooBar")] private string name; } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0016"); } @@ -755,7 +809,7 @@ public partial class SampleViewModel : ObservableObject [TestMethod] public void NotifyCanExecuteChangedForInvalidTargetError_InvalidMemberType() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp @@ -770,7 +824,8 @@ public void Foo() { } } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0016"); } @@ -778,7 +833,7 @@ public void Foo() [TestMethod] public void NotifyCanExecuteChangedForInvalidTargetError_InvalidPropertyType() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp @@ -791,7 +846,8 @@ public partial class SampleViewModel : ObservableObject public string Foo { get; } } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0016"); } @@ -799,7 +855,7 @@ public partial class SampleViewModel : ObservableObject [TestMethod] public void NotifyCanExecuteChangedForInvalidTargetError_InvalidCommandType() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; @@ -813,7 +869,8 @@ public partial class SampleViewModel : ObservableObject public ICommand FooCommand { get; } } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0016"); } @@ -821,7 +878,7 @@ public partial class SampleViewModel : ObservableObject [TestMethod] public void InvalidAttributeCombinationForINotifyPropertyChangedAttributeError_InheritingINotifyPropertyChangedAttribute() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp @@ -835,7 +892,8 @@ public partial class A public partial class B : A { } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0017"); } @@ -843,7 +901,7 @@ public partial class B : A [TestMethod] public void InvalidAttributeCombinationForINotifyPropertyChangedAttributeError_InheritingObservableObjectAttribute() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp @@ -857,7 +915,8 @@ public partial class A public partial class B : A { } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0017"); } @@ -865,7 +924,7 @@ public partial class B : A [TestMethod] public void InvalidAttributeCombinationForINotifyPropertyChangedAttributeError_WithAlsoObservableObjectAttribute() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp @@ -875,7 +934,8 @@ namespace MyApp public partial class A { } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0017"); } @@ -883,7 +943,7 @@ public partial class A [TestMethod] public void InvalidAttributeCombinationForObservableObjectAttributeError_InheritingINotifyPropertyChangedAttribute() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp @@ -897,7 +957,8 @@ public partial class A public partial class B : A { } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0018"); } @@ -905,7 +966,7 @@ public partial class B : A [TestMethod] public void InvalidAttributeCombinationForObservableObjectAttributeError_InheritingObservableObjectAttribute() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp @@ -919,7 +980,8 @@ public partial class A public partial class B : A { } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0018"); } @@ -927,7 +989,7 @@ public partial class B : A [TestMethod] public void InvalidAttributeCombinationForObservableObjectAttributeError_WithAlsoINotifyPropertyChangedAttribute() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp @@ -937,7 +999,8 @@ namespace MyApp public partial class A { } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0018"); } @@ -945,7 +1008,7 @@ public partial class A [TestMethod] public void InvalidContainingTypeForObservablePropertyFieldError() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp @@ -957,15 +1020,16 @@ public partial class MyViewModel : INotifyPropertyChanged public event PropertyChangedEventHandler PropertyChanged; } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0019"); } [TestMethod] - public void FieldWithOrphanedDependentObservablePropertyAttributesError_NotifyPropertyChangedFor() + public async Task FieldWithOrphanedDependentObservablePropertyAttributesError_NotifyPropertyChangedFor() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp @@ -973,17 +1037,18 @@ namespace MyApp public partial class MyViewModel { [NotifyPropertyChangedFor("")] - public int number; + public int {|MVVMTK0020:number|}; } - }"; + } + """; - VerifyGeneratedDiagnostics(source, "MVVMTK0020"); + await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.CSharp8); } [TestMethod] - public void FieldWithOrphanedDependentObservablePropertyAttributesError_NotifyCanExecuteChangedFor() + public async Task FieldWithOrphanedDependentObservablePropertyAttributesError_NotifyCanExecuteChangedFor() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp @@ -991,17 +1056,18 @@ namespace MyApp public partial class MyViewModel { [NotifyCanExecuteChangedFor("")] - public int number; + public int {|MVVMTK0020:number|}; } - }"; + } + """; - VerifyGeneratedDiagnostics(source, "MVVMTK0020"); + await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.CSharp8); } [TestMethod] - public void FieldWithOrphanedDependentObservablePropertyAttributesError_NotifyPropertyChangedRecipients() + public async Task FieldWithOrphanedDependentObservablePropertyAttributesError_NotifyPropertyChangedRecipients() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp @@ -1009,17 +1075,18 @@ namespace MyApp public partial class MyViewModel { [NotifyPropertyChangedRecipients] - public int number; + public int {|MVVMTK0020:number|}; } - }"; + } + """; - VerifyGeneratedDiagnostics(source, "MVVMTK0020"); + await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.CSharp8); } [TestMethod] - public void FieldWithOrphanedDependentObservablePropertyAttributesError_MultipleUsesStillGenerateOnlyASingleDiagnostic() + public async Task FieldWithOrphanedDependentObservablePropertyAttributesError_MultipleUsesStillGenerateOnlyASingleDiagnostic() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp @@ -1032,17 +1099,18 @@ public partial class MyViewModel [NotifyCanExecuteChangedFor("")] [NotifyCanExecuteChangedFor("")] [NotifyPropertyChangedRecipients] - public int number; + public int {|MVVMTK0020:number|}; } - }"; + } + """; - VerifyGeneratedDiagnostics(source, "MVVMTK0020"); + await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.CSharp8); } [TestMethod] public void InvalidAttributeCombinationForObservableRecipientAttributeError() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp @@ -1056,7 +1124,8 @@ public partial class A : ObservableObject public partial class B : A { } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0021"); } @@ -1064,7 +1133,7 @@ public partial class B : A [TestMethod] public void InvalidContainingTypeForNotifyPropertyChangedRecipientsFieldError_ObservableObject() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp @@ -1075,7 +1144,8 @@ public partial class MyViewModel : ObservableObject [NotifyPropertyChangedRecipients] public int number; } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0022"); } @@ -1083,7 +1153,7 @@ public partial class MyViewModel : ObservableObject [TestMethod] public void MultipleRelayCommandMethodOverloads_WithOverloads() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.Input; namespace MyApp @@ -1100,7 +1170,8 @@ private void GreetUser(object value) { } } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0023"); } @@ -1108,27 +1179,28 @@ private void GreetUser(object value) [TestMethod] public void MultipleRelayCommandMethodOverloads_WithOverloadInBaseType() { - string source = @" - using CommunityToolkit.Mvvm.Input; + string source = """ + using CommunityToolkit.Mvvm.Input; - namespace MyApp - { - public partial class BaseViewModel + namespace MyApp { - [RelayCommand] - private void GreetUser() + public partial class BaseViewModel { + [RelayCommand] + private void GreetUser() + { + } } - } - public partial class SampleViewModel : BaseViewModel - { - [RelayCommand] - private void GreetUser(object value) + public partial class SampleViewModel : BaseViewModel { + [RelayCommand] + private void GreetUser(object value) + { + } } } - }"; + """; VerifyGeneratedDiagnostics(source, "MVVMTK0023"); } @@ -1136,7 +1208,7 @@ private void GreetUser(object value) [TestMethod] public void InvalidObservablePropertyError_Object() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp @@ -1146,7 +1218,8 @@ public partial class MyViewModel : ObservableObject [ObservableProperty] public object property; } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0024"); } @@ -1154,7 +1227,7 @@ public partial class MyViewModel : ObservableObject [TestMethod] public void InvalidObservablePropertyError_PropertyChangingEventArgs() { - string source = @" + string source = """ using System.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel; @@ -1165,7 +1238,8 @@ public partial class MyViewModel : ObservableObject [ObservableProperty] public PropertyChangingEventArgs property; } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0024"); } @@ -1173,7 +1247,7 @@ public partial class MyViewModel : ObservableObject [TestMethod] public void InvalidObservablePropertyError_PropertyChangedEventArgs() { - string source = @" + string source = """ using System.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel; @@ -1184,7 +1258,8 @@ public partial class MyViewModel : ObservableObject [ObservableProperty] public PropertyChangedEventArgs property; } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0024"); } @@ -1192,7 +1267,7 @@ public partial class MyViewModel : ObservableObject [TestMethod] public void InvalidObservablePropertyError_CustomTypeDerivedFromPropertyChangedEventArgs() { - string source = @" + string source = """ using System.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel; @@ -1211,7 +1286,8 @@ public partial class MyViewModel : ObservableObject [ObservableProperty] public MyPropertyChangedEventArgs property; } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0024"); } @@ -1219,7 +1295,7 @@ public partial class MyViewModel : ObservableObject [TestMethod] public void MissingObservableValidatorInheritanceForNotifyDataErrorInfoError() { - string source = @" + string source = """ using System.ComponentModel.DataAnnotations; using CommunityToolkit.Mvvm.ComponentModel; @@ -1233,7 +1309,8 @@ public partial class SampleViewModel [NotifyDataErrorInfo] private string name; } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0006", "MVVMTK0025"); } @@ -1241,7 +1318,7 @@ public partial class SampleViewModel [TestMethod] public void MissingValidationAttributesForNotifyDataErrorInfoError() { - string source = @" + string source = """ using System.ComponentModel.DataAnnotations; using CommunityToolkit.Mvvm.ComponentModel; @@ -1253,7 +1330,8 @@ public partial class SampleViewModel : ObservableValidator [NotifyDataErrorInfo] private string name; } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0026"); } @@ -1261,7 +1339,7 @@ public partial class SampleViewModel : ObservableValidator [TestMethod] public void InvalidTypeForNotifyPropertyChangedRecipientsError() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp @@ -1272,7 +1350,8 @@ public partial class MyViewModel : ObservableObject [ObservableProperty] public int number; } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0027"); } @@ -1280,7 +1359,7 @@ public partial class MyViewModel : ObservableObject [TestMethod] public void InvalidTypeForNotifyDataErrorInfoError() { - string source = @" + string source = """ using System.ComponentModel.DataAnnotations; using CommunityToolkit.Mvvm.ComponentModel; @@ -1293,7 +1372,8 @@ public partial class SampleViewModel : ObservableObject [Required] private string name; } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0006", "MVVMTK0028"); } @@ -1301,7 +1381,7 @@ public partial class SampleViewModel : ObservableObject [TestMethod] public void UnnecessaryNotifyPropertyChangedRecipientsWarning_SameType() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp @@ -1313,7 +1393,8 @@ public partial class MyViewModel : ObservableRecipient [NotifyPropertyChangedRecipients] public int number; } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0029"); } @@ -1321,7 +1402,7 @@ public partial class MyViewModel : ObservableRecipient [TestMethod] public void UnnecessaryNotifyPropertyChangedRecipientsWarning_BaseType() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp @@ -1337,7 +1418,8 @@ public partial class MyViewModel : MyBaseViewModel [NotifyPropertyChangedRecipients] public int number; } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0029"); } @@ -1345,7 +1427,7 @@ public partial class MyViewModel : MyBaseViewModel [TestMethod] public void UnnecessaryNotifyDataErrorInfoWarning_SameType() { - string source = @" + string source = """ using System.ComponentModel.DataAnnotations; using CommunityToolkit.Mvvm.ComponentModel; @@ -1359,7 +1441,8 @@ public partial class MyViewModel : ObservableValidator [NotifyDataErrorInfo] public int number; } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0030"); } @@ -1367,7 +1450,7 @@ public partial class MyViewModel : ObservableValidator [TestMethod] public void UnnecessaryNotifyDataErrorInfoWarning_BaseType() { - string source = @" + string source = """ using System.ComponentModel.DataAnnotations; using CommunityToolkit.Mvvm.ComponentModel; @@ -1385,7 +1468,8 @@ public partial class MyViewModel : MyBaseViewModel [NotifyDataErrorInfo] public int number; } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0030"); } @@ -1393,7 +1477,7 @@ public partial class MyViewModel : MyBaseViewModel [TestMethod] public void InvalidRelayCommandFlowExceptionsToTaskSchedulerOption() { - string source = @" + string source = """ using CommunityToolkit.Mvvm.Input; namespace MyApp @@ -1405,11 +1489,220 @@ private void GreetUser(User user) { } } - }"; + } + """; VerifyGeneratedDiagnostics(source, "MVVMTK0031"); } + [TestMethod] + public async Task UsingINotifyPropertyChangedAttributeOnClassWithNoBaseType() + { + string source = """ + using CommunityToolkit.Mvvm.ComponentModel; + + namespace MyApp + { + [INotifyPropertyChanged] + public partial class {|MVVMTK0032:SampleViewModel|} + { + } + } + """; + + await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.CSharp8); + } + + [TestMethod] + public async Task UsingObservableObjectAttributeOnClassWithNoBaseType() + { + string source = """ + using CommunityToolkit.Mvvm.ComponentModel; + + namespace MyApp + { + [ObservableObject] + public partial class {|MVVMTK0033:SampleViewModel|} + { + } + } + """; + + await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.CSharp8); + } + + // See https://github.com/CommunityToolkit/dotnet/issues/518 + [TestMethod] + public async Task FieldReferenceToFieldWithObservablePropertyAttribute_Assignment() + { + string source = """ + using CommunityToolkit.Mvvm.ComponentModel; + + partial class MyViewModel : ObservableObject + { + [ObservableProperty] + int number; + + void M1() + { + {|MVVMTK0034:number|} = 1; + } + + void M2() + { + {|MVVMTK0034:this.number|} = 1; + } + } + """; + + await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.CSharp8); + } + + // See https://github.com/CommunityToolkit/dotnet/issues/518 + [TestMethod] + public async Task FieldReferenceToFieldWithObservablePropertyAttribute_Read() + { + string source = """ + using CommunityToolkit.Mvvm.ComponentModel; + + partial class MyViewModel : ObservableObject + { + [ObservableProperty] + int number; + + void M() + { + var temp = {|MVVMTK0034:number|}; + } + } + """; + + await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.CSharp8); + } + + // See https://github.com/CommunityToolkit/dotnet/issues/518 + [TestMethod] + public async Task FieldReferenceToFieldWithObservablePropertyAttribute_Ref() + { + string source = """ + using CommunityToolkit.Mvvm.ComponentModel; + + partial class MyViewModel : ObservableObject + { + [ObservableProperty] + int number; + + void M() + { + ref var x = ref {|MVVMTK0034:number|}; + } + } + """; + + await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.CSharp8); + } + + [TestMethod] + public async Task FieldReferenceToFieldWithObservablePropertyAttribute_DoesNotEmitWarningFromWithinConstructor() + { + string source = """ + using CommunityToolkit.Mvvm.ComponentModel; + + partial class MyViewModel : ObservableObject + { + [ObservableProperty] + int number; + + public MyViewModel() + { + number = 42; + var temp = number; + ref var x = ref number; + } + } + """; + + await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.CSharp8); + } + + // See https://github.com/CommunityToolkit/dotnet/issues/563 + [TestMethod] + public void InvalidPropertyTargetedAttributeOnObservablePropertyField_MissingUsingDirective() + { + string source = """ + using System; + using CommunityToolkit.Mvvm.ComponentModel; + + namespace MyApp + { + public partial class MyViewModel : ObservableObject + { + [ObservableProperty] + [property: MyTest] + public int number; + } + } + + namespace MyAttributes + { + [AttributeUsage(AttributeTargets.Property)] + public class MyTestAttribute : Attribute + { + } + } + """; + + VerifyGeneratedDiagnostics(source, "MVVMTK0035"); + } + + [TestMethod] + public void InvalidPropertyTargetedAttributeOnObservablePropertyField_TypoInAttributeName() + { + string source = """ + using CommunityToolkit.Mvvm.ComponentModel; + + namespace MyApp + { + public partial class MyViewModel : ObservableObject + { + [ObservableProperty] + [property: Fbuifbweif] + public int number; + } + } + """; + + VerifyGeneratedDiagnostics(source, "MVVMTK0035"); + } + + /// + /// Verifies the diagnostic errors for a given analyzer, and that all available source generators can run successfully with the input source (including subsequent compilation). + /// + /// The type of the analyzer to test. + /// The input source to process with diagnostic annotations. + /// The language version to use to parse code and run tests. + private static async Task VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(string markdownSource, LanguageVersion languageVersion) + where TAnalyzer : DiagnosticAnalyzer, new() + { + await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(markdownSource, languageVersion); + + IIncrementalGenerator[] generators = + { + new IMessengerRegisterAllGenerator(), + new ObservableObjectGenerator(), + new INotifyPropertyChangedGenerator(), + new ObservablePropertyGenerator(), + new ObservableRecipientGenerator(), + new ObservableValidatorValidateAllPropertiesGenerator(), + new RelayCommandGenerator() + }; + + // Transform diagnostic annotations back to normal C# (eg. "{|MVVMTK0008:Foo()|}" ---> "Foo()") + string source = Regex.Replace(markdownSource, @"{\|((?:,?\w+)+):(.+)\|}", m => m.Groups[2].Value); + + VerifyGeneratedDiagnostics(CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(languageVersion)), generators, Array.Empty()); + } + /// /// Verifies the output of a source generator. /// @@ -1419,42 +1712,54 @@ private void GreetUser(User user) private static void VerifyGeneratedDiagnostics(string source, params string[] diagnosticsIds) where TGenerator : class, IIncrementalGenerator, new() { - VerifyGeneratedDiagnostics(CSharpSyntaxTree.ParseText(source), diagnosticsIds); + IIncrementalGenerator generator = new TGenerator(); + + VerifyGeneratedDiagnostics(CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp8)), new[] { generator }, diagnosticsIds); } /// - /// Verifies the output of a source generator. + /// Verifies the output of one or more source generators. /// - /// The generator type to use. /// The input source tree to process. - /// The diagnostic ids to expect for the input source code. - private static void VerifyGeneratedDiagnostics(SyntaxTree syntaxTree, params string[] diagnosticsIds) - where TGenerator : class, IIncrementalGenerator, new() + /// The generators to apply to the input syntax tree. + /// The diagnostic ids to expect for the input source code. + private static void VerifyGeneratedDiagnostics(SyntaxTree syntaxTree, IIncrementalGenerator[] generators, string[] generatorDiagnosticsIds) { + // Ensure CommunityToolkit.Mvvm and System.ComponentModel.DataAnnotations are loaded Type observableObjectType = typeof(ObservableObject); Type validationAttributeType = typeof(ValidationAttribute); + // Get all assembly references for the loaded assemblies (easy way to pull in all necessary dependencies) IEnumerable references = from assembly in AppDomain.CurrentDomain.GetAssemblies() where !assembly.IsDynamic let reference = MetadataReference.CreateFromFile(assembly.Location) select reference; + // Create a syntax tree with the input source CSharpCompilation compilation = CSharpCompilation.Create( "original", new SyntaxTree[] { syntaxTree }, references, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); - IIncrementalGenerator generator = new TGenerator(); - - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator).WithUpdatedParseOptions((CSharpParseOptions)syntaxTree.Options); + GeneratorDriver driver = CSharpGeneratorDriver.Create(generators).WithUpdatedParseOptions((CSharpParseOptions)syntaxTree.Options); + // Run all source generators on the input source code _ = driver.RunGeneratorsAndUpdateCompilation(compilation, out Compilation outputCompilation, out ImmutableArray diagnostics); - HashSet resultingIds = diagnostics.Select(diagnostic => diagnostic.Id).ToHashSet(); + string[] resultingIds = diagnostics.Select(diagnostic => diagnostic.Id).ToArray(); + + CollectionAssert.AreEquivalent(generatorDiagnosticsIds, resultingIds, $"resultingIds: {string.Join(", ", resultingIds)}"); + + // If the compilation was supposed to succeed, ensure that no further errors were generated + if (resultingIds.Length == 0) + { + // Compute diagnostics for the final compiled output (just include errors) + List outputCompilationDiagnostics = outputCompilation.GetDiagnostics().Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error).ToList(); - CollectionAssert.AreEquivalent(diagnosticsIds, resultingIds.ToArray()); + Assert.IsTrue(outputCompilationDiagnostics.Count == 0, $"resultingIds: {string.Join(", ", outputCompilationDiagnostics)}"); + } GC.KeepAlive(observableObjectType); GC.KeepAlive(validationAttributeType); diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/CommunityToolkit.Mvvm.UnitTests.csproj b/tests/CommunityToolkit.Mvvm.UnitTests/CommunityToolkit.Mvvm.UnitTests.csproj deleted file mode 100644 index fff3b5625..000000000 --- a/tests/CommunityToolkit.Mvvm.UnitTests/CommunityToolkit.Mvvm.UnitTests.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - net472;netcoreapp3.1;net6.0 - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/CommunityToolkit.Mvvm.UnitTests.projitems b/tests/CommunityToolkit.Mvvm.UnitTests/CommunityToolkit.Mvvm.UnitTests.projitems new file mode 100644 index 000000000..3089c9db3 --- /dev/null +++ b/tests/CommunityToolkit.Mvvm.UnitTests/CommunityToolkit.Mvvm.UnitTests.projitems @@ -0,0 +1,43 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + b8dcd82e-b53b-4249-ad4e-f9b99acb9334 + + + CommunityToolkit.Mvvm.UnitTests + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/CommunityToolkit.Mvvm.UnitTests.shproj b/tests/CommunityToolkit.Mvvm.UnitTests/CommunityToolkit.Mvvm.UnitTests.shproj new file mode 100644 index 000000000..b16b0e5e2 --- /dev/null +++ b/tests/CommunityToolkit.Mvvm.UnitTests/CommunityToolkit.Mvvm.UnitTests.shproj @@ -0,0 +1,13 @@ + + + + b8dcd82e-b53b-4249-ad4e-f9b99acb9334 + 14.0 + + + + + + + + diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Test_INotifyPropertyChangedAttribute.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Test_INotifyPropertyChangedAttribute.cs index e63a71057..095a9fcb0 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Test_INotifyPropertyChangedAttribute.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Test_INotifyPropertyChangedAttribute.cs @@ -8,6 +8,8 @@ using CommunityToolkit.Mvvm.ComponentModel; using Microsoft.VisualStudio.TestTools.UnitTesting; +#pragma warning disable MVVMTK0032 + namespace CommunityToolkit.Mvvm.UnitTests; [TestClass] diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Test_Messenger.Observables.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Test_Messenger.Observables.cs new file mode 100644 index 000000000..e631eddf9 --- /dev/null +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Test_Messenger.Observables.cs @@ -0,0 +1,102 @@ +// 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 CommunityToolkit.Mvvm.Messaging; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace CommunityToolkit.Mvvm.UnitTests; + +partial class Test_Messenger +{ + [TestMethod] + [DataRow(typeof(StrongReferenceMessenger))] + [DataRow(typeof(WeakReferenceMessenger))] + public void Test_Messenger_CreateObservable(Type type) + { + IMessenger messenger = (IMessenger)Activator.CreateInstance(type)!; + + IObservable observable = messenger.CreateObservable(); + + Assert.IsNotNull(observable); + + List messages = new(); + + IDisposable disposable = observable.Subscribe(messages.Add); + + MessageA message1 = new(); + MessageA message2 = new(); + + _ = messenger.Send(message1); + _ = messenger.Send(message2); + + // The expected messages have been observed + CollectionAssert.AreEqual(messages, new[] { message1, message2 }); + + disposable.Dispose(); + + _ = messenger.Send(); + + // No messages are sent after unsubscribing the observable + CollectionAssert.AreEqual(messages, new[] { message1, message2 }); + } + + [TestMethod] + [DataRow(typeof(StrongReferenceMessenger))] + [DataRow(typeof(WeakReferenceMessenger))] + public void Test_Messenger_CreateObservable_WithToken(Type type) + { + IMessenger messenger = (IMessenger)Activator.CreateInstance(type)!; + + IObservable observable = messenger.CreateObservable(42); + + Assert.IsNotNull(observable); + + List messages = new(); + + IDisposable disposable = observable.Subscribe(messages.Add); + + MessageA message1 = new(); + MessageA message2 = new(); + + _ = messenger.Send(message1, 42); + _ = messenger.Send(message2, 42); + + _ = messenger.Send(new MessageA(), 1); + _ = messenger.Send(new MessageA(), 999); + + // The expected messages have been observed (only for matching tokens) + CollectionAssert.AreEqual(messages, new[] { message1, message2 }); + + disposable.Dispose(); + + _ = messenger.Send(new MessageA(), 42); + _ = messenger.Send(new MessageA(), 1); + + // No messages are sent after unsubscribing the observable (regardless of token) + CollectionAssert.AreEqual(messages, new[] { message1, message2 }); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void Test_Messenger_CreateObservable_NullMessenger() + { + _ = IMessengerExtensions.CreateObservable(null!); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void Test_Messenger_CreateObservable_WithToken_NullMessenger() + { + _ = IMessengerExtensions.CreateObservable(null!, "Hello"); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void Test_Messenger_CreateObservable_WithToken_NullToken() + { + _ = IMessengerExtensions.CreateObservable(new WeakReferenceMessenger(), null!); + } +} diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Test_Messenger.Request.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Test_Messenger.Request.cs index 876dc7c62..b6c6f3ac1 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Test_Messenger.Request.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Test_Messenger.Request.cs @@ -12,7 +12,7 @@ namespace CommunityToolkit.Mvvm.UnitTests; -public partial class Test_Messenger +partial class Test_Messenger { [TestMethod] [DataRow(typeof(StrongReferenceMessenger))] diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservableObjectAttribute.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservableObjectAttribute.cs index 750cd93d8..78ed9320d 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservableObjectAttribute.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservableObjectAttribute.cs @@ -6,6 +6,8 @@ using CommunityToolkit.Mvvm.ComponentModel; using Microsoft.VisualStudio.TestTools.UnitTesting; +#pragma warning disable MVVMTK0033 + namespace CommunityToolkit.Mvvm.UnitTests; [TestClass] diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservablePropertyAttribute.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservablePropertyAttribute.cs index a0cbb027e..44adc66aa 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservablePropertyAttribute.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservablePropertyAttribute.cs @@ -8,8 +8,12 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using System.Reflection; +#if NET6_0_OR_GREATER using System.Runtime.CompilerServices; +#endif +using System.Text.Json.Serialization; using System.Threading.Tasks; +using System.Xml.Serialization; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ExternalAssembly; using CommunityToolkit.Mvvm.Input; @@ -17,6 +21,8 @@ using CommunityToolkit.Mvvm.Messaging.Messages; using Microsoft.VisualStudio.TestTools.UnitTesting; +#pragma warning disable MVVMTK0032, MVVMTK0033, MVVMTK0034 + namespace CommunityToolkit.Mvvm.UnitTests; [TestClass] @@ -871,6 +877,112 @@ public void Test_ObservableProperty_WithCommandReferencingGeneratedPropertyFromO Assert.IsTrue(model.HasSaved); } + // See https://github.com/CommunityToolkit/dotnet/issues/413 + [TestMethod] + public void Test_ObservableProperty_WithExplicitAttributeForProperty() + { + PropertyInfo nameProperty = typeof(MyViewModelWithExplicitPropertyAttributes).GetProperty(nameof(MyViewModelWithExplicitPropertyAttributes.Name))!; + + Assert.IsNotNull(nameProperty.GetCustomAttribute()); + Assert.IsNotNull(nameProperty.GetCustomAttribute()); + Assert.AreEqual(nameProperty.GetCustomAttribute()!.Length, 1); + Assert.IsNotNull(nameProperty.GetCustomAttribute()); + Assert.AreEqual(nameProperty.GetCustomAttribute()!.Length, 100); + + PropertyInfo lastNameProperty = typeof(MyViewModelWithExplicitPropertyAttributes).GetProperty(nameof(MyViewModelWithExplicitPropertyAttributes.LastName))!; + + Assert.IsNotNull(lastNameProperty.GetCustomAttribute()); + Assert.AreEqual(lastNameProperty.GetCustomAttribute()!.Name, "lastName"); + Assert.IsNotNull(lastNameProperty.GetCustomAttribute()); + + PropertyInfo justOneSimpleAttributeProperty = typeof(MyViewModelWithExplicitPropertyAttributes).GetProperty(nameof(MyViewModelWithExplicitPropertyAttributes.JustOneSimpleAttribute))!; + + Assert.IsNotNull(justOneSimpleAttributeProperty.GetCustomAttribute()); + + PropertyInfo someComplexValidationAttributeProperty = typeof(MyViewModelWithExplicitPropertyAttributes).GetProperty(nameof(MyViewModelWithExplicitPropertyAttributes.SomeComplexValidationAttribute))!; + + TestValidationAttribute testAttribute = someComplexValidationAttributeProperty.GetCustomAttribute()!; + + Assert.IsNotNull(testAttribute); + Assert.IsNull(testAttribute.O); + Assert.AreEqual(testAttribute.T, typeof(MyViewModelWithExplicitPropertyAttributes)); + Assert.AreEqual(testAttribute.Flag, true); + Assert.AreEqual(testAttribute.D, 6.28); + CollectionAssert.AreEqual(testAttribute.Names, new[] { "Bob", "Ross" }); + + object[]? nestedArray = (object[]?)testAttribute.NestedArray; + + Assert.IsNotNull(nestedArray); + Assert.AreEqual(nestedArray!.Length, 3); + Assert.AreEqual(nestedArray[0], 1); + Assert.AreEqual(nestedArray[1], "Hello"); + Assert.IsTrue(nestedArray[2] is int[]); + CollectionAssert.AreEqual((int[])nestedArray[2], new[] { 2, 3, 4 }); + + Assert.AreEqual(testAttribute.Animal, Animal.Llama); + + PropertyInfo someComplexRandomAttribute = typeof(MyViewModelWithExplicitPropertyAttributes).GetProperty(nameof(MyViewModelWithExplicitPropertyAttributes.SomeComplexRandomAttribute))!; + + Assert.IsNotNull(someComplexRandomAttribute.GetCustomAttribute()); + + PropertyInfoAttribute testAttribute2 = someComplexRandomAttribute.GetCustomAttribute()!; + + Assert.IsNotNull(testAttribute2); + Assert.IsNull(testAttribute2.O); + Assert.AreEqual(testAttribute2.T, typeof(MyViewModelWithExplicitPropertyAttributes)); + Assert.AreEqual(testAttribute2.Flag, true); + Assert.AreEqual(testAttribute2.D, 6.28); + Assert.IsNotNull(testAttribute2.Objects); + Assert.IsTrue(testAttribute2.Objects is object[]); + Assert.AreEqual(((object[])testAttribute2.Objects).Length, 1); + Assert.AreEqual(((object[])testAttribute2.Objects)[0], "Test"); + CollectionAssert.AreEqual(testAttribute2.Names, new[] { "Bob", "Ross" }); + + object[]? nestedArray2 = (object[]?)testAttribute2.NestedArray; + + Assert.IsNotNull(nestedArray2); + Assert.AreEqual(nestedArray2!.Length, 4); + Assert.AreEqual(nestedArray2[0], 1); + Assert.AreEqual(nestedArray2[1], "Hello"); + Assert.AreEqual(nestedArray2[2], 42); + Assert.IsNull(nestedArray2[3]); + + Assert.AreEqual(testAttribute2.Animal, (Animal)67); + } + + // See https://github.com/CommunityToolkit/dotnet/issues/446 + [TestMethod] + public void Test_ObservableProperty_CommandNamesThatCantBeLowered() + { + ModelWithCommandNamesThatCantBeLowered model = new(); + + // Just ensures this builds + _ = model.中文Command; + _ = model.c中文Command; + + FieldInfo? fieldInfo = typeof(ModelWithCommandNamesThatCantBeLowered).GetField($"_{nameof(ModelWithCommandNamesThatCantBeLowered.中文)}Command", BindingFlags.Instance | BindingFlags.NonPublic); + + Assert.AreSame(model.中文Command, fieldInfo?.GetValue(model)); + + fieldInfo = typeof(ModelWithCommandNamesThatCantBeLowered).GetField($"_{nameof(ModelWithCommandNamesThatCantBeLowered.c中文)}Command", BindingFlags.Instance | BindingFlags.NonPublic); + + Assert.AreSame(model.c中文Command, fieldInfo?.GetValue(model)); + } + + // See https://github.com/CommunityToolkit/dotnet/issues/375 + [TestMethod] + public void Test_ObservableProperty_ModelWithObservablePropertyWithUnderscoreAndUppercase() + { + ModelWithObservablePropertyWithUnderscoreAndUppercase model = new(); + + Assert.IsFalse(model.IsReadOnly); + + // Just ensures this builds and the property is generated with the expected name + model.IsReadOnly = true; + + Assert.IsTrue(model.IsReadOnly); + } + public abstract partial class BaseViewModel : ObservableObject { public string? Content { get; set; } @@ -1380,4 +1492,85 @@ public override void Save() HasSaved = true; } } + + public partial class MyViewModelWithExplicitPropertyAttributes : ObservableValidator + { + [ObservableProperty] + [property: Required] + [property: MinLength(1)] + [property: MaxLength(100)] + private string? name; + + [ObservableProperty] + [property: JsonPropertyName("lastName")] + [property: XmlIgnore] + private string? lastName; + + [ObservableProperty] + [property: Test] + private string? justOneSimpleAttribute; + + [ObservableProperty] + [property: TestValidation(null, typeof(MyViewModelWithExplicitPropertyAttributes), true, 6.28, new[] { "Bob", "Ross" }, NestedArray = new object[] { 1, "Hello", new int[] { 2, 3, 4 } }, Animal = Animal.Llama)] + private int someComplexValidationAttribute; + + [ObservableProperty] + [property: Test] + [property: PropertyInfo(null, typeof(MyViewModelWithExplicitPropertyAttributes), true, 6.28, new[] { "Bob", "Ross" }, new object[] { "Test" }, NestedArray = new object[] { 1, "Hello", 42, null }, Animal = (Animal)67)] + private int someComplexRandomAttribute; + } + + [AttributeUsage(AttributeTargets.Property)] + private sealed class TestAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Property)] + private sealed class PropertyInfoAttribute : Attribute + { + public PropertyInfoAttribute(object? o, Type t, bool flag, double d, string[] names, object[] objects) + { + O = o; + T = t; + Flag = flag; + D = d; + Names = names; + Objects = objects; + } + + public object? O { get; } + + public Type T { get; } + + public bool Flag { get; } + + public double D { get; } + + public string[] Names { get; } + + public object? Objects { get; set; } + + public object? NestedArray { get; set; } + + public Animal Animal { get; set; } + } + + private sealed partial class ModelWithCommandNamesThatCantBeLowered + { + [RelayCommand] + public void 中文() + { + } + + [RelayCommand] + public void c中文() + { + } + } + + private partial class ModelWithObservablePropertyWithUnderscoreAndUppercase : ObservableObject + { + [ObservableProperty] + private bool _IsReadOnly; + } } diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservableRecipientAttribute.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservableRecipientAttribute.cs index 602fee322..9e2ee3dd7 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservableRecipientAttribute.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservableRecipientAttribute.cs @@ -12,6 +12,8 @@ using CommunityToolkit.Mvvm.Messaging; using Microsoft.VisualStudio.TestTools.UnitTesting; +#pragma warning disable MVVMTK0033 + namespace CommunityToolkit.Mvvm.UnitTests; [TestClass] diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Test_RelayCommandAttribute.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Test_RelayCommandAttribute.cs index 72fb69001..681f2fe08 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Test_RelayCommandAttribute.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Test_RelayCommandAttribute.cs @@ -8,7 +8,6 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; -using System.Windows.Input; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Test_SourceGenerators.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Test_SourceGenerators.cs index 815c43ee6..6ccde7030 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Test_SourceGenerators.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Test_SourceGenerators.cs @@ -7,6 +7,8 @@ using CommunityToolkit.Mvvm.Input; using Microsoft.VisualStudio.TestTools.UnitTesting; +#pragma warning disable MVVMTK0032 + namespace CommunityToolkit.Mvvm.UnitTests; /// diff --git a/version.json b/version.json index 756026a07..8d6c90680 100644 --- a/version.json +++ b/version.json @@ -1,5 +1,5 @@ { - "version": "8.0.0-build.{height}", + "version": "8.1.0", "publicReleaseRefSpec": [ "^refs/heads/main$", // we release out of main "^refs/heads/dev$", // we release out of dev