diff --git a/documentation/for-contributors/generators/symbol-layer/symbols/README.md b/documentation/for-contributors/generators/symbol-layer/symbols/README.md index 9b82fe3d4e..6cd34caa64 100644 --- a/documentation/for-contributors/generators/symbol-layer/symbols/README.md +++ b/documentation/for-contributors/generators/symbol-layer/symbols/README.md @@ -19,18 +19,19 @@ Parent Symbols (Unlisted, abstract): | TypeSymbol | | | MethodSymbol | -| Name | Symbol Layer File | Symbol Layer Tests | Emitter Tests | -| -------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | -| ClassSymbol | [here](../../../../../src/generators/Silk.NET.SilkTouch.Symbols/ClassSymbol.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/ClassSymbolTests.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Emitter.Tests/ClassSymbolTests.cs) | -| ExternalTypeReference | [here](../../../../../src/generators/Silk.NET.SilkTouch.Symbols/ExternalTypeReference.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/ExternalTypeReferenceTests.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Emitter.Tests/ExternalTypeReferenceTests.cs) | -| FieldSymbol | [here](../../../../../src/generators/Silk.NET.SilkTouch.Symbols/FieldSymbol.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/FieldTests.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Emitter.Tests/EmitterFieldTests.cs) | -| IdentifierSymbol | [here](../../../../../src/generators/Silk.NET.SilkTouch.Symbols/IdentifierSymbol.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/IdentifierTests.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Emitter.Tests/IdentifierSymbolTests.cs) | -| InternalTypeReference | [here](../../../../../src/generators/Silk.NET.SilkTouch.Symbols/InternalTypeReference.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/InternalTypeReferenceTests.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Emitter.Tests/InternalTypeReferenceTests.cs) | -| NamespaceSymbol | [here](../../../../../src/generators/Silk.NET.SilkTouch.Symbols/NamespaceSymbol.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/NamespaceTests.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Emitter.Tests/EmitterNamespaceTests.cs) | -| PointerTypeReference | [here](../../../../../src/generators/Silk.NET.SilkTouch.Symbols/PointerTypeReference.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/PointerTypeReferenceTests.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Emitter.Tests/PointerTypeReferenceTests.cs) | -| StaticExternalMethodSymbol | [here](../../../../../src/generators/Silk.NET.SilkTouch.Symbols/StaticExternalMethodSymbol.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/StaticExternalMethodSymbolTests.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Emitter.Tests/StaticExternalMethodSymbolTests.cs) | -| StructSymbol | [here](../../../../../src/generators/Silk.NET.SilkTouch.Symbols/StructSymbol.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/StructTests.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Emitter.Tests/EmitterStructTests.cs) | -| UnresolvedTypeReference | [here](src/generators/Silk.NET.SilkTouch.Symbols/UnresolvedTypeReference.cs) | [here](tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/UnresolvedTypeReferenceTests.cs) | - | +| Name | Symbol Layer File | Symbol Layer Tests | Emitter Tests | +| ---------------------------- | ------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | +| ClassSymbol | [here](../../../../../src/generators/Silk.NET.SilkTouch.Symbols/ClassSymbol.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/ClassSymbolTests.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Emitter.Tests/ClassSymbolTests.cs) | +| ExternalTypeReference | [here](../../../../../src/generators/Silk.NET.SilkTouch.Symbols/ExternalTypeReference.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/ExternalTypeReferenceTests.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Emitter.Tests/ExternalTypeReferenceTests.cs) | +| FunctionPointerTypeReference | [here](../../../../../src/generators/Silk.NET.SilkTouch.Symbols/FunctionPointerTypeReference.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/FunctionPointerTypeReferenceTests.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Emitter.Tests/FunctionPointerTypeReferenceTests.cs) | +| FieldSymbol | [here](../../../../../src/generators/Silk.NET.SilkTouch.Symbols/FieldSymbol.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/FieldTests.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Emitter.Tests/EmitterFieldTests.cs) | +| IdentifierSymbol | [here](../../../../../src/generators/Silk.NET.SilkTouch.Symbols/IdentifierSymbol.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/IdentifierTests.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Emitter.Tests/IdentifierSymbolTests.cs) | +| InternalTypeReference | [here](../../../../../src/generators/Silk.NET.SilkTouch.Symbols/InternalTypeReference.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/InternalTypeReferenceTests.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Emitter.Tests/InternalTypeReferenceTests.cs) | +| NamespaceSymbol | [here](../../../../../src/generators/Silk.NET.SilkTouch.Symbols/NamespaceSymbol.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/NamespaceTests.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Emitter.Tests/EmitterNamespaceTests.cs) | +| PointerTypeReference | [here](../../../../../src/generators/Silk.NET.SilkTouch.Symbols/PointerTypeReference.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/PointerTypeReferenceTests.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Emitter.Tests/PointerTypeReferenceTests.cs) | +| StaticExternalMethodSymbol | [here](../../../../../src/generators/Silk.NET.SilkTouch.Symbols/StaticExternalMethodSymbol.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/StaticExternalMethodSymbolTests.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Emitter.Tests/StaticExternalMethodSymbolTests.cs) | +| StructSymbol | [here](../../../../../src/generators/Silk.NET.SilkTouch.Symbols/StructSymbol.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/StructTests.cs) | [here](../../../../../tests/Silk.NET.SilkTouch.Emitter.Tests/EmitterStructTests.cs) | +| UnresolvedTypeReference | [here](src/generators/Silk.NET.SilkTouch.Symbols/UnresolvedTypeReference.cs) | [here](tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/UnresolvedTypeReferenceTests.cs) | - | ## How to create a symbol diff --git a/src/generators/Silk.NET.SilkTouch.DotnetTool/Program.cs b/src/generators/Silk.NET.SilkTouch.DotnetTool/Program.cs index 22750c22b7..e9341e2e55 100644 --- a/src/generators/Silk.NET.SilkTouch.DotnetTool/Program.cs +++ b/src/generators/Silk.NET.SilkTouch.DotnetTool/Program.cs @@ -342,6 +342,11 @@ private static IEnumerable ProcessSymbols visitors.Add(ActivatorUtilities.CreateInstance(serviceProvider, typeStore)); } + if ((usedSymbolVisitors & AvailableSymbolVisitors.FunctionPointerTypeResolver) != 0) + { + visitors.Add(ActivatorUtilities.CreateInstance(serviceProvider, typeStore)); + } + if ((usedSymbolVisitors & AvailableSymbolVisitors.InternalTypeResolver) != 0) { var typeScopeSymbolVisitor = ActivatorUtilities.CreateInstance @@ -518,7 +523,8 @@ private enum AvailableSymbolVisitors PointerTypeResolver = 1 << 1, InternalTypeResolver = 1 << 2, PrimitiveTypeResolver = 1 << 3, - AllTypeResolvers = PointerTypeResolver | InternalTypeResolver | PrimitiveTypeResolver, + FunctionPointerTypeResolver = 1 << 4, + AllTypeResolvers = PointerTypeResolver | InternalTypeResolver | PrimitiveTypeResolver | FunctionPointerTypeResolver, } } } diff --git a/src/generators/Silk.NET.SilkTouch.Emitter/CSharpEmitter.cs b/src/generators/Silk.NET.SilkTouch.Emitter/CSharpEmitter.cs index ac20adadb7..d710696797 100644 --- a/src/generators/Silk.NET.SilkTouch.Emitter/CSharpEmitter.cs +++ b/src/generators/Silk.NET.SilkTouch.Emitter/CSharpEmitter.cs @@ -236,6 +236,45 @@ protected override PointerTypeReference VisitPointerTypeReference(PointerTypeRef return pointerTypeReference; } + protected override FunctionPointerTypeReference VisitFunctionPointerTypeReference + (FunctionPointerTypeReference functionPointerTypeReference) + { + AssertClearState(); + + var paramList = functionPointerTypeReference.ParameterTypes + .Append(functionPointerTypeReference.ReturnType) + .Select + ( + (x, i) => + { + VisitTypeReference(x); + if (_syntax is not TypeSyntax typeSyntax) + throw new InvalidOperationException("TypeReference did not return TypeSyntax"); + ClearState(); + + if (i == 0 || i > functionPointerTypeReference.ParameterTypes.Length) + return typeSyntax; + else + return typeSyntax.WithLeadingTrivia(Space); // not ideal, but the easiest way to do this + } + ) + .Select(FunctionPointerParameter) + .ToImmutableArray(); + + _syntax = FunctionPointerType + ( + Token(SyntaxKind.DelegateKeyword), + Token(SyntaxKind.AsteriskToken), + FunctionPointerCallingConvention(Token(SyntaxKind.UnmanagedKeyword)).WithLeadingTrivia(Space), + FunctionPointerParameterList + ( + SeparatedList(paramList) + ) + ); + + return functionPointerTypeReference; + } + protected override ExternalTypeReference VisitExternalTypeReference(ExternalTypeReference typeReference) { AssertClearState(); diff --git a/src/generators/Silk.NET.SilkTouch.Symbols/FunctionPointerTypeReference.cs b/src/generators/Silk.NET.SilkTouch.Symbols/FunctionPointerTypeReference.cs new file mode 100644 index 0000000000..ca9a9a38cb --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.Symbols/FunctionPointerTypeReference.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Immutable; + +namespace Silk.NET.SilkTouch.Symbols; + +/// +/// A representing a function pointer. +/// +public sealed record FunctionPointerTypeReference(TypeReference ReturnType, ImmutableArray ParameterTypes) : TypeReference +{ +} diff --git a/src/generators/Silk.NET.SilkTouch.Symbols/SymbolVisitor.cs b/src/generators/Silk.NET.SilkTouch.Symbols/SymbolVisitor.cs index c79f2593d7..fab812e421 100644 --- a/src/generators/Silk.NET.SilkTouch.Symbols/SymbolVisitor.cs +++ b/src/generators/Silk.NET.SilkTouch.Symbols/SymbolVisitor.cs @@ -120,6 +120,7 @@ protected virtual TypeReference VisitTypeReference(TypeReference typeReference) if (typeReference is InternalTypeReference itr) return VisitInternalTypeReference(itr); if (typeReference is UnresolvedTypeReference utr) UnresolvedTypeReference.ThrowInvalidSymbol(); if (typeReference is PointerTypeReference ptr) return VisitPointerTypeReference(ptr); + if (typeReference is FunctionPointerTypeReference fptr) return VisitFunctionPointerTypeReference(fptr); return ThrowUnknownSymbol(typeReference); } @@ -135,7 +136,24 @@ protected virtual PointerTypeReference VisitPointerTypeReference(PointerTypeRefe { return new PointerTypeReference(VisitTypeReference(pointerTypeReference.Underlying)); } - + /// + /// Visit a . Will call the appropriate methods to visit the different parts of the symbol. + /// + /// The function pointer type reference to visit + /// The rewritten symbol + /// + /// The order in which the parts are visited is kept as an implementation detail. Do not rely on this order. + /// + protected virtual FunctionPointerTypeReference VisitFunctionPointerTypeReference + (FunctionPointerTypeReference functionPointerTypeReference) + { + return new FunctionPointerTypeReference + ( + VisitTypeReference(functionPointerTypeReference.ReturnType), + functionPointerTypeReference.ParameterTypes.Select(VisitTypeReference).ToImmutableArray() + ); + } + /// /// Visit a . Will call the appropriate methods to visit the different parts of the symbol. /// diff --git a/src/generators/Silk.NET.SilkTouch.TypeResolution/FunctionPointerTypeResolver.cs b/src/generators/Silk.NET.SilkTouch.TypeResolution/FunctionPointerTypeResolver.cs new file mode 100644 index 0000000000..2784a0fbdf --- /dev/null +++ b/src/generators/Silk.NET.SilkTouch.TypeResolution/FunctionPointerTypeResolver.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Immutable; +using System.Text.RegularExpressions; +using Silk.NET.SilkTouch.Symbols; + +namespace Silk.NET.SilkTouch.TypeResolution; + +/// +/// A that resolves strings of standard C# form +/// +public class FunctionPointerTypeResolver : SimpleTypeResolverBase +{ + private static readonly Regex _regex = new + (/*lang=regex*/ + @"delegate\*\sunmanaged(\[((?'modifier'.(?=,\s?)?)+)*\])?\<((?'parameter'(.(?=,\s?)+))(,\s?))*(?'return_type'(.)+)\>", + RegexOptions.CultureInvariant + ); + + /// + public FunctionPointerTypeResolver(TypeStore typeStore) : base(typeStore) + { + } + + /// + protected override bool TryResolve(UnresolvedTypeReference utr, out TypeReference? resolved) + { + if (utr.Text.StartsWith("delegate*")) + { + var match = _regex.Match(utr.Text); + if (match.Success) + { + var parameters = match.Groups["parameter"] + .Captures.OfType() + .Select(x => new UnresolvedTypeReference(x.Value)) + .Cast() + .ToImmutableArray(); + var returnType = new UnresolvedTypeReference(match.Groups["return_type"].Value); + + resolved = new FunctionPointerTypeReference(returnType, parameters); + return true; + } + } + resolved = null; + return false; + } +} diff --git a/tests/Silk.NET.SilkTouch.Emitter.Tests/FunctionPointerTypeReferenceTests.cs b/tests/Silk.NET.SilkTouch.Emitter.Tests/FunctionPointerTypeReferenceTests.cs new file mode 100644 index 0000000000..832514c30a --- /dev/null +++ b/tests/Silk.NET.SilkTouch.Emitter.Tests/FunctionPointerTypeReferenceTests.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Immutable; +using Silk.NET.SilkTouch.Symbols; +using Xunit; + +namespace Silk.NET.SilkTouch.Emitter.Tests; + +public class FunctionPointerTypeReferenceTests : EmitterTest +{ + [Fact, Trait("Category", "Symbols"), + Trait("Target Language", "C#")] + public void StringTestNoParams() + { + var symbol = new FunctionPointerTypeReference + (new ExternalTypeReference(null, new IdentifierSymbol("Ret")), ImmutableArray.Empty); + + var transformed = Transform(symbol); + + Assert.Equal("delegate* unmanaged", transformed.ToFullString()); + } + + [Fact, Trait("Category", "Symbols"), + Trait("Target Language", "C#")] + public void StringTestWithParams() + { + var symbol = new FunctionPointerTypeReference + (new ExternalTypeReference(null, new IdentifierSymbol("Ret")), new TypeReference[] + { + new ExternalTypeReference(null, new IdentifierSymbol("Param1")), + new ExternalTypeReference(null, new IdentifierSymbol("Param2")), + }.ToImmutableArray()); + + var transformed = Transform(symbol); + + Assert.Equal("delegate* unmanaged", transformed.ToFullString()); + } +} diff --git a/tests/Silk.NET.SilkTouch.IntegrationTests/TestHelper.cs b/tests/Silk.NET.SilkTouch.IntegrationTests/TestHelper.cs index 974a332c63..2b41031905 100644 --- a/tests/Silk.NET.SilkTouch.IntegrationTests/TestHelper.cs +++ b/tests/Silk.NET.SilkTouch.IntegrationTests/TestHelper.cs @@ -55,6 +55,7 @@ public static string GetCSharpOutputFromCpp(string cpp) var processors = new SymbolVisitor[] { ActivatorUtilities.CreateInstance(serviceProvider, typeStore), + ActivatorUtilities.CreateInstance(serviceProvider, typeStore), ActivatorUtilities.CreateInstance(serviceProvider, typeStore), typeScopeSymbolVisitor, diff --git a/tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/FunctionPointerTypeReferenceTests.cs b/tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/FunctionPointerTypeReferenceTests.cs new file mode 100644 index 0000000000..a6eca9d576 --- /dev/null +++ b/tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/FunctionPointerTypeReferenceTests.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Immutable; +using Moq; +using Moq.Protected; +using Xunit; + +namespace Silk.NET.SilkTouch.Symbols.Tests.SymbolVisitorTests; + +public class FunctionPointerTypeReferenceTests +{ + [Fact, Trait("Category", "Symbols")] + public void VisitedAsSelf() + { + var symbol = new FunctionPointerTypeReference(new InternalTypeReference(TypeId.CreateNew()), ImmutableArray.Empty); + var visitor = new Mock { CallBase = true }; + visitor.Object.Visit(symbol); + + visitor.Protected() + .Verify + ("VisitFunctionPointerTypeReference", Times.Once(), ItExpr.IsAny()); + } + + [Fact, Trait("Category", "Symbols")] + public void VisitedAsRef() + { + var symbol = new FunctionPointerTypeReference(new InternalTypeReference(TypeId.CreateNew()), ImmutableArray.Empty); + var visitor = new Mock { CallBase = true }; + visitor.Object.Visit(symbol); + + visitor.Protected() + .Verify + ("VisitTypeReference", Times.Once(), ItExpr.Is(x => ReferenceEquals(x, symbol))); + } +} diff --git a/tests/Silk.NET.SilkTouch.TypeResolution.Tests/FunctionPointerTypeResolverTests.cs b/tests/Silk.NET.SilkTouch.TypeResolution.Tests/FunctionPointerTypeResolverTests.cs new file mode 100644 index 0000000000..344bbd0706 --- /dev/null +++ b/tests/Silk.NET.SilkTouch.TypeResolution.Tests/FunctionPointerTypeResolverTests.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Linq; +using Silk.NET.SilkTouch.Symbols; +using Xunit; + +namespace Silk.NET.SilkTouch.TypeResolution.Tests; + +public class FunctionPointerTypeResolverTests +{ + [Theory, Trait("Category", "Type Resolution"), + InlineData("delegate* unmanaged", "A", new string[0]), + InlineData("delegate* unmanaged", "C", new[] { "A", "B"}), + InlineData("delegate* unmanaged[Cdecl]", "A", new string[0]), + InlineData("delegate* unmanaged[Cdecl]", "C", new[] { "A", "B"}), + InlineData("delegate* unmanaged", "C", new[] { "A", "B"}), + InlineData("delegate* unmanaged[Cdecl]", "C", new[] { "A", "B"}), + ] + public void ShouldMatch(string text, string returnString, string[] parameters) + { + var result = new FunctionPointerTypeResolver(new TypeStore()).Visit(new UnresolvedTypeReference(text)); + + var fptr = Assert.IsType(result); + Assert.Equal(returnString, Assert.IsType(fptr.ReturnType).Text); + Assert.Collection + ( + fptr.ParameterTypes, + parameters.Select + ( + x => new Action(r => Assert.Equal(x, Assert.IsType(r).Text)) + ) + .ToArray() + ); + } + + + [Theory, Trait("Category", "Type Resolution"), + InlineData(""), + InlineData("a"), + InlineData("*"), + InlineData("a.b.c"), + InlineData("longType"), + InlineData("int"), + InlineData("using"), + InlineData("delegate*") + ] + public void ShouldNotMatch(string text) + { + var result = new FunctionPointerTypeResolver(new TypeStore()).Visit(new UnresolvedTypeReference(text)); + + Assert.IsNotType(result); + } +}