Skip to content

Commit 25fafd7

Browse files
authored
Multi-Targetting support for targets used in Source Generators/Analyzers and VSIX (#351)
* Moved poly fills to a shared project. * Using a Roslyn Source generator has problems with dependencies. * Added Code analysis utilities - Targets .NET8 and .NET Standard 2.0 * Doc comment updates/corrections * Removed exception from `DisposableAction.Dispose` - `Dispose` should not throw an exception. * Added support for resource string formatting simlification. * Replaced `nint.Zero` with `IntPtr.Zero` or `0` - Originally, the methods exposed by nint were filtered to exclude `Zero` and then later converted to a pure alias. Sadly, the compiler language version does ***NOT*** control this behavior. It's ` System.Runtime.CompilerServices.RuntimeFeature.NumericIntPtr` * Added multipe define checks based on the target framework to allow projects to multi-target * * Fixed LazyEncoded string to remove need for `System.Runtime.CompilerServices.RuntimeHelpers.GetSubArray<T>` method - Apparently the compiler doesn't take extensions into account and requires existence of the static method directly on the type. So no poly fill is possible. * Added exception to detect if native API returns an invalid (null or empty) * Fixed bug in fluent `ThrowIfOutOfRange` handling * Blocked out `System.Runtime.CompilerServices.RuntimeHelpers.GetSubArray<T>` * Fixed handling of DIExpression * Since the LLVM-C API uses `unwrap<DIExpression>(Expr)` that will `assert` in a debug build that the `Expr` is NOT null. In a release build it sitll does the right thing and treats a null as if it was empty but in a debug build it will assert causing a dialog and potential app crash. - Most cases used a null coelescence operation to provide a non-null expression but `DiBuilderAlias.CreateGlobalVariableExpression` did not do that.
1 parent 80f6173 commit 25fafd7

File tree

65 files changed

+2758
-396
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+2758
-396
lines changed

Directory.Packages.props

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,52 @@
44
Global references are included in ALL projects in this repository
55
-->
66
<ItemGroup>
7-
<GlobalPackageReference Include="Ubiquity.NET.Versioning.Build.Tasks" Version="5.0.7" />
7+
<GlobalPackageReference Include="Ubiquity.NET.Versioning.Build.Tasks" Version="5.0.7">
8+
<PrivateAssets>all</PrivateAssets>
9+
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
10+
</GlobalPackageReference>
811
<GlobalPackageReference Include="IDisposableAnalyzers" Version="4.0.8" Condition="'$(NoCommonAnalyzers)' !=' true'" />
912
<GlobalPackageReference Include="MustUseRetVal" Version="0.0.2" Condition="'$(NoCommonAnalyzers)' !=' true'" />
1013
<!--
1114
NOTE: This analyzer is sadly, perpetually in "pre-release mode". There have been many issues/discussion on the point
1215
and it has all fallen on deaf ears. So policies regarding "NO-Prerelease" components need to be overruled on this one
13-
-->
16+
-->
1417
<GlobalPackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556" Condition="'$(UseStyleCop)' != 'false'" />
1518
</ItemGroup>
16-
1719
<!--
1820
Package versions made consistent across all packages referenced in this repository
1921
-->
2022
<ItemGroup>
2123
<!-- Roslyn Analyzers ***MUST*** target older framework -->
2224
<PackageVersion Include="AnsiCodes" Version="0.2.1" />
25+
<PackageVersion Include="Microsoft.Bcl.HashCode" Version="6.0.0" />
2326
<PackageVersion Include="Microsoft.CodeAnalysis" Version="4.14.0" />
2427
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="4.14.0" />
28+
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.14.0" />
2529
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
2630
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.14.0" />
31+
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing" Version="1.1.2" />
32+
<PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic" Version="4.14.0" />
2733
<PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic.Features" Version="4.14.0" />
34+
<PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing" Version="1.1.2" />
2835
<PackageVersion Include="Basic.Reference.Assemblies.Net80" Version="1.8.3" />
2936
<PackageVersion Include="PolySharp" Version="1.15.0" />
3037
<PackageVersion Include="System.Buffers" Version="4.6.1" />
3138
<PackageVersion Include="System.Collections.Immutable" Version="9.0.10" />
39+
<PackageVersion Include="System.Memory" Version="4.5.5" />
40+
41+
<!-- Currently, only in preview -->
3242
<PackageVersion Include="System.CommandLine" Version="2.0.0-rc.2.25502.107" />
3343

3444
<!-- Security vulnerability overrides -->
35-
<!-- Workaround(2): https://github.com/dotnet/roslyn-sdk/issues/1191 -->
36-
<PackageVersion Include="System.Formats.Asn1" Version="9.0.10" />
45+
<!-- https://github.com/dotnet/roslyn-sdk/issues/1191 -->
46+
<PackageVersion Include="System.Formats.Asn1" Version="10.0.0" />
47+
<!-- https://github.com/advisories/GHSA-7jgj-8wvc-jh57 -->
48+
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
49+
<!-- https://github.com/advisories/GHSA-cmhx-cq75-c4mj -->
50+
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
3751

38-
<!-- Common packages for solution -->
52+
<!-- Common packages for solution -->
3953
<PackageVersion Include="System.Linq.Async" Version="6.0.3" />
4054
<PackageVersion Include="Ubiquity.NET.LibLLVM" Version="20.1.8" />
4155
<PackageVersion Include="Ubiquity.NET.Versioning" Version="6.0.2-beta" />
@@ -46,9 +60,9 @@
4660
<PackageVersion Include="System.CodeDom" Version="9.0.7" />
4761

4862
<!-- Tests all use the same framework versions -->
49-
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
50-
<PackageVersion Include="MSTest.TestAdapter" Version="4.0.1" />
51-
<PackageVersion Include="MSTest.TestFramework" Version="4.0.1" />
63+
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
64+
<PackageVersion Include="MSTest.TestAdapter" Version="4.0.2" />
65+
<PackageVersion Include="MSTest.TestFramework" Version="4.0.2" />
5266
<PackageVersion Include="Tmds.ExecFunction" Version="0.8.0" />
5367
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing" Version="1.1.2" />
5468
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing" Version="1.1.2" />

docfx/ReadMe.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,12 @@ Since this is generated it is listed in the [.gitignore](#gitignore) file.
7878
These folders (named after the `*` portion of the [api-*](#api-*) folder names contains
7979
manually written additional files, articles, samples etc... related to a given library.
8080

81-
## Guid to wrting XML DOC comments
82-
When dealing with doc comments the XML can get in the way of general readability of the
83-
source code. There is an inherent tension beween how a particular editor renders the docs
84-
for a symbol/method (VS calls this "Quick Info") and how it is rendered in the final
85-
documentation by docfx. This guides general use to simplify things as much as possible.
81+
## Guide to wrting XML DOC comments
82+
When dealing with doc comments the XML can sometimes get in the way of general readability
83+
of the source code. There is an inherent tension beween how a particular editor renders the
84+
docs for a symbol/method (VS calls this "Quick Info") and how it is rendered in the final
85+
documentation by a tool like docfx. This guides general use to simplify things as much as
86+
possible.
8687

8788
### Lists
8889
The largest intrusion of the XML into the source is that of lists. The XML doc comments
@@ -115,7 +116,6 @@ versus:
115116
/// 3) Act on the results as proper for the application<br/>
116117
/// a. This might include actions parsed but generally isolating the various stages is an easier to understand/maintain model<br/>
117118
/// b. Usually this is just app specific code that uses the bound results to adapt behavior<br/>
118-
///
119119
```
120120

121121
Which one would ***YOU*** rather encounter in code? Which one is easier to understand when
@@ -126,7 +126,7 @@ should reconsider... :grinning:)
126126
There is little that can be done to alter the rendering of any editor support, at most an
127127
editor might allow specification of a CSS file, but that is the lowest priority of doc
128128
comments. Readability by maintainers of the docs AND the rendering for final docs used by
129-
consumers of of VASTLY higher importance. Still, the editor rendering ***is*** of value to
129+
consumers is of VASTLY higher importance. Still, the editor rendering ***is*** of value to
130130
maintainers so should not be forgotten as it can make a "right mess of things" even if they
131131
render properly in final docs.
132132

@@ -135,8 +135,8 @@ render properly in final docs.
135135
a) Doing so will break the docfx rendering that allows for markdown lists
136136
2) Use `</br>' tags to indicate a line break. This is used by the editor rendering to mark
137137
the end of a line and start a new one. (Stops auto reflow)
138-
3) Accept that the in edotr rendering might "trim" the lines it shows, eliminating any
139-
indentation.
138+
3) Accept that the in editor rendering might "trim" the lines it shows, eliminating any
139+
indentation. [Grrr... Looking at you VS!]
140140
a) Sadly, there is no avoiding this. Addition of any sort of "markup" to control that
141141
will interfere with the readability AND the final docs rendering.
142142
4) Always use a different numbering style for sub lists/items
@@ -147,6 +147,6 @@ render properly in final docs.
147147
API signaure and parameter info. Different editors may allow control of that.
148148
i) In VS [2019|2022] for C# it is controlled by
149149
`Text Editor > C# > Advanced > Editor Help: "Show remarks in Quick Info."`
150-
ii) Turning this off can greatly reduce the noise AND reduce the problems of
151-
different rende
150+
1) Turning this off can greatly reduce the noise AND reduce the problems of
151+
different rendering as lists are generally not used in the other elements.
152152

src/Interop/Ubiquity.NET.Llvm.Interop/Library.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ public static ILibLlvm InitializeLLVM( )
4949
}
5050

5151
// Verify the version of LibLLVM.
52+
string verString = LibLLVMGetVersion()?.ToString() ?? string.Empty;
53+
if(string.IsNullOrWhiteSpace(verString))
54+
{
55+
throw new InvalidOperationException("Internal error: LLVM reported an empty string for version!");
56+
}
57+
5258
var libVersion = SemVer.Parse(LibLLVMGetVersion()?.ToString() ?? string.Empty, SemVerFormatProvider.CaseInsensitive);
5359
if( libVersion is CSemVerCI semVerCI)
5460
{

src/Samples/CodeGenWithDebugInfo/Program.cs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,35 @@ public static void Main( string[] args )
113113
, constArray
114114
);
115115

116-
var bar = module.AddGlobal( fooType, false, 0, barValue, "bar" );
116+
var bar = module.AddGlobal( fooType, false, 0, barValue, "bar"u8 );
117117
bar.Alignment = module.Layout.AbiAlignmentOf( fooType );
118-
bar.AddDebugInfo( diBuilder.CreateGlobalVariableExpression( compilationUnit, "bar", string.Empty, diFile, 8, fooType.DebugInfoType, false, null ) );
119-
120-
var baz = module.AddGlobal( fooType, false, Linkage.Common, Constant.NullValueFor( fooType ), "baz" );
118+
bar.AddDebugInfo(
119+
diBuilder.CreateGlobalVariableExpression(
120+
compilationUnit,
121+
"bar"u8,
122+
linkageName: string.Empty,
123+
diFile,
124+
lineNo: 8,
125+
fooType.DebugInfoType,
126+
isLocalToUnit: false,
127+
value: null
128+
)
129+
);
130+
131+
var baz = module.AddGlobal( fooType, false, Linkage.Common, Constant.NullValueFor( fooType ), "baz"u8 );
121132
baz.Alignment = module.Layout.AbiAlignmentOf( fooType );
122-
baz.AddDebugInfo( diBuilder.CreateGlobalVariableExpression( compilationUnit, "baz", string.Empty, diFile, 9, fooType.DebugInfoType, false, null ) );
133+
baz.AddDebugInfo(
134+
diBuilder.CreateGlobalVariableExpression(
135+
compilationUnit,
136+
"baz"u8,
137+
linkageName: string.Empty,
138+
diFile,
139+
lineNo: 9,
140+
fooType.DebugInfoType,
141+
isLocalToUnit: false,
142+
value: null
143+
)
144+
);
123145

124146
// add module flags and compiler identifiers...
125147
// this can technically occur at any point, though placing it here makes
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
2+
// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
3+
4+
// Mostly from https://github.com/Sergio0694/PolySharp/blob/main/src/PolySharp.SourceGenerators/Extensions/AnalyzerConfigOptionsProviderExtensions.cs
5+
// Reformatted and made to conform to repo guides
6+
7+
namespace Ubiquity.NET.CodeAnalysis.Utils
8+
{
9+
/// <summary>Extension methods for the <see cref="AnalyzerConfigOptionsProvider"/> type.</summary>
10+
public static class AnalyzerConfigOptionsProviderExtensions
11+
{
12+
/// <summary>Checks whether the input property has a valid <see cref="bool"/> value.</summary>
13+
/// <param name="options">The input <see cref="AnalyzerConfigOptionsProvider"/> instance.</param>
14+
/// <param name="propertyName">The Build property name.</param>
15+
/// <param name="propertyValue">The resulting property value, if invalid.</param>
16+
/// <returns>Whether the target property is a valid <see cref="bool"/> value.</returns>
17+
public static bool IsValidBoolBuildProperty(
18+
this AnalyzerConfigOptionsProvider options,
19+
string propertyName,
20+
[NotNullWhen( false )] out string? propertyValue
21+
)
22+
{
23+
return !options.GlobalOptions.TryGetValue( $"{BuildProperty}.{propertyName}", out propertyValue )
24+
|| string.IsNullOrEmpty( propertyValue )
25+
|| string.Equals( propertyValue, bool.TrueString, StringComparison.OrdinalIgnoreCase )
26+
|| string.Equals( propertyValue, bool.FalseString, StringComparison.OrdinalIgnoreCase );
27+
}
28+
29+
/// <summary>Gets the value of a <see cref="bool"/> build property.</summary>
30+
/// <param name="options">The input <see cref="AnalyzerConfigOptionsProvider"/> instance.</param>
31+
/// <param name="propertyName">The build property name.</param>
32+
/// <returns>The value of the specified build property.</returns>
33+
/// <remarks>
34+
/// The return value is equivalent to a (case insensitive) <c>'$(PropertyName)' == 'true'</c> check.
35+
/// That is, any other value, including empty/not present, is considered <see langword="true"/>.
36+
/// </remarks>
37+
public static bool GetBoolBuildProperty( this AnalyzerConfigOptionsProvider options, string propertyName )
38+
{
39+
return options.GlobalOptions.TryGetValue( $"{BuildProperty}.{propertyName}", out string? propertyValue )
40+
&& string.Equals( propertyValue, bool.TrueString, StringComparison.OrdinalIgnoreCase );
41+
}
42+
43+
/// <summary>Gets the value of a Build property representing a semicolon-separated list of strings.</summary>
44+
/// <param name="options">The input <see cref="AnalyzerConfigOptionsProvider"/> instance.</param>
45+
/// <param name="propertyName">The build property name.</param>
46+
/// <returns>The value of the specified build property.</returns>
47+
public static ImmutableArray<string> GetStringArrayBuildProperty( this AnalyzerConfigOptionsProvider options, string propertyName )
48+
{
49+
return options.GlobalOptions.TryGetValue( $"{BuildProperty}.{propertyName}", out string? propertyValue )
50+
? [ .. propertyValue.Split( ',', ';' ) ]
51+
: [];
52+
}
53+
54+
// MSBuild properties that are visible to the compiler are available with the "build_property." prefix
55+
// See: https://andrewlock.net/creating-a-source-generator-part-13-providing-and-accessing-msbuild-settings-in-source-generators/
56+
private const string BuildProperty = "build_property";
57+
}
58+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
2+
// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
3+
4+
namespace Ubiquity.NET.CodeAnalysis.Utils
5+
{
6+
/// <summary>Utility type to provide extensions for <see cref="BaseTypeDeclarationSyntax"/></summary>
7+
public static class BaseTypeDeclarationSyntaxExtensions
8+
{
9+
// The following 2 extension methods are based on:
10+
// https://andrewlock.net/creating-a-source-generator-part-5-finding-a-type-declarations-namespace-and-type-hierarchy/
11+
12+
/// <summary>Gets the declared namespace for a <see cref="BaseTypeDeclarationSyntax"/></summary>
13+
/// <param name="syntax">Syntax to get the namespace for</param>
14+
/// <returns>Namespace of <paramref name="syntax"/></returns>
15+
public static string GetDeclaredNamespace(this BaseTypeDeclarationSyntax syntax)
16+
{
17+
// If we don't have a namespace at all we'll return an empty string
18+
// This accounts for the "default namespace" case
19+
string nameSpace = string.Empty;
20+
21+
// Get the containing syntax node for the type declaration
22+
// (could be a nested type, for example)
23+
SyntaxNode? potentialNamespaceParent = syntax.Parent;
24+
25+
// Keep moving "out" of nested classes etc until we get to a namespace
26+
// or until we run out of parents
27+
while (potentialNamespaceParent != null
28+
&& potentialNamespaceParent is not NamespaceDeclarationSyntax
29+
&& potentialNamespaceParent is not FileScopedNamespaceDeclarationSyntax)
30+
{
31+
potentialNamespaceParent = potentialNamespaceParent.Parent;
32+
}
33+
34+
// Build up the final namespace by looping until we no longer have a namespace declaration
35+
if (potentialNamespaceParent is BaseNamespaceDeclarationSyntax namespaceParent)
36+
{
37+
// We have a namespace. Use that as the type
38+
nameSpace = namespaceParent.Name.ToString();
39+
40+
// Keep moving "out" of the namespace declarations until there
41+
// are no more nested namespace declarations.
42+
while (true)
43+
{
44+
if (namespaceParent.Parent is not NamespaceDeclarationSyntax parent)
45+
{
46+
break;
47+
}
48+
49+
// Add the outer namespace as a prefix to the final namespace
50+
nameSpace = $"{namespaceParent.Name}.{nameSpace}";
51+
namespaceParent = parent;
52+
}
53+
}
54+
55+
// return the final namespace
56+
return nameSpace;
57+
}
58+
59+
/// <summary>Gets the nested class name for a <see cref="BaseTypeDeclarationSyntax"/></summary>
60+
/// <param name="syntax">Syntax to get the name for</param>
61+
/// <param name="includeSelf">Flag to indicate if the type itself is included in the name [Default: <see langword="false"/></param>
62+
/// <returns><see cref="NestedClassName"/> of the syntax or <see langword="null"/></returns>
63+
public static NestedClassName? GetNestedClassName( this BaseTypeDeclarationSyntax syntax, bool includeSelf = false)
64+
{
65+
// Try and get the parent syntax. If it isn't a type like class/struct, this will be null
66+
TypeDeclarationSyntax? parentSyntax = includeSelf ? syntax as TypeDeclarationSyntax : syntax.Parent as TypeDeclarationSyntax;
67+
NestedClassName? parentClassInfo = null;
68+
69+
// We can only be nested in class/struct/record
70+
71+
// Keep looping while we're in a supported nested type
72+
while (parentSyntax is not null)
73+
{
74+
// NOTE: due to bug https://github.com/dotnet/roslyn/issues/78042 this
75+
// is not using a local static function to evaluate this in the condition
76+
// of the while loop [Workaround: go back to "old" extension syntax...]
77+
var rawKind = parentSyntax.Kind();
78+
bool isAllowedKind
79+
= rawKind == SyntaxKind.ClassDeclaration
80+
|| rawKind == SyntaxKind.StructDeclaration
81+
|| rawKind == SyntaxKind.RecordDeclaration;
82+
83+
if (!isAllowedKind)
84+
{
85+
break;
86+
}
87+
88+
// Record the parent type keyword (class/struct etc), name, and constraints
89+
parentClassInfo = new NestedClassName(
90+
keyword: parentSyntax.Keyword.ValueText,
91+
name: parentSyntax.Identifier.ToString() + parentSyntax.TypeParameterList,
92+
constraints: parentSyntax.ConstraintClauses.ToString(),
93+
children: parentClassInfo is null ? [] : [parentClassInfo]); // set the child link (null initially)
94+
95+
// Move to the next outer type
96+
parentSyntax = parentSyntax.Parent as TypeDeclarationSyntax;
97+
}
98+
99+
// return a link to the outermost parent type
100+
return parentClassInfo;
101+
}
102+
}
103+
}

0 commit comments

Comments
 (0)