From ce200f087fbfd48f41eafd4bae8324059d6dda7b Mon Sep 17 00:00:00 2001 From: Jeremi Kurdek <59935235+jkurdek@users.noreply.github.com> Date: Mon, 3 Feb 2025 21:39:53 +0100 Subject: [PATCH] [SwiftBindings] Adds runtime support for protocol constraints (#2969) * Add placeholder protocol projections * Add support for conformances in type declarations * Replaced string with ProtocolSpec on ProtocolConformance * Added initial support for non-pat protocol constraint * Reverted some changes introduced in previous PR to ensure defensive handling of constructors * Added PAT support * Removed Conformances from Protocols * Simplified ProtocolConformance record * Fixed bug when passing multiple generic params of the same type resulted in compile error * Refactored ProtocolWitnessTable name generation to use NameProvider for consistency * Fix failing build * Applied review feedback * Used static field for protocolConformanceSymbols * Applied review feedback * Add TODO comment for reverting struct writing in generic method handling --- src/Swift.Bindings/src/DemangledSymbols.cs | 64 ++++++ .../StringEmitter/Handler/MethodHandler.cs | 216 ++++++++++++------ .../StringEmitter/Handler/ModuleHandler.cs | 20 +- .../StringEmitter/Handler/TypeHandler.cs | 174 ++++++++++---- src/Swift.Bindings/src/Marshaler/Conductor.cs | 2 + src/Swift.Bindings/src/Marshaler/IHandler.cs | 16 +- .../src/Marshaler/NameProvider.cs | 24 +- .../src/Model/TypeDecl/ClassDecl.cs | 4 + .../src/Model/TypeDecl/GenericArgumentDecl.cs | 34 +-- .../src/Model/TypeDecl/ProtocolConformance.cs | 15 ++ .../src/Model/TypeDecl/ProtocolDecl.cs | 8 + .../src/Model/TypeDecl/StructDecl.cs | 5 + .../src/Parser/GenericSignatureParser.cs | 34 ++- .../src/Parser/SwiftABIParser.cs | 43 +++- .../FunctionalTests/Generics/GenericTests.cs | 64 ++++++ .../Generics/GenericTests.swift | 129 +++++++++++ .../Protocols/ProtocolsTests.cs | 38 +++ .../Protocols/ProtocolsTests.swift | 8 + .../GenericSignatureParserTests.cs | 23 +- 19 files changed, 721 insertions(+), 200 deletions(-) create mode 100644 src/Swift.Bindings/src/DemangledSymbols.cs create mode 100644 src/Swift.Bindings/src/Model/TypeDecl/ProtocolConformance.cs create mode 100644 src/Swift.Bindings/src/Model/TypeDecl/ProtocolDecl.cs create mode 100644 src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Protocols/ProtocolsTests.cs create mode 100644 src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Protocols/ProtocolsTests.swift diff --git a/src/Swift.Bindings/src/DemangledSymbols.cs b/src/Swift.Bindings/src/DemangledSymbols.cs new file mode 100644 index 000000000000..1b66901ab83a --- /dev/null +++ b/src/Swift.Bindings/src/DemangledSymbols.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using BindingsGeneration; +using Xamarin; + +public readonly record struct DemangledSymbols +{ + public Dictionary<(NamedTypeSpec, NamedTypeSpec), string> ProtocolConformanceDescriptors { get; init; } + + public DemangledSymbols(Dictionary<(NamedTypeSpec, NamedTypeSpec), string> descriptors) + { + ProtocolConformanceDescriptors = descriptors; + } +} + +public sealed class DemangledSymbolsRegister +{ + private static readonly Lazy _instance = new(() => new DemangledSymbolsRegister()); + + private DemangledSymbols _symbols = new(new Dictionary<(NamedTypeSpec, NamedTypeSpec), string>()); + private bool _isLoaded = false; + + public static DemangledSymbolsRegister Instance => _instance.Value; + + private DemangledSymbolsRegister() { } + + public DemangledSymbols GetData(string dylibPath = "") + { + if (!_isLoaded) + { + if (string.IsNullOrEmpty(dylibPath)) + { + throw new ArgumentException("dylibPath cannot be null or empty."); + } + + Load(dylibPath); + } + return _symbols; + } + + private void Load(string dylibPath) + { + try + { + var abis = MachO.GetArchitectures(dylibPath); + var descriptors = DemanglingResults.FromFile(dylibPath, abis[0]).ProtocolConformanceDescriptors; + var dictionary = new Dictionary<(NamedTypeSpec, NamedTypeSpec), string>(); + + foreach (var descriptor in descriptors) + { + dictionary[(descriptor.ImplementingType, descriptor.ProtocolType)] = descriptor.Symbol[1..]; + } + + _symbols = new DemangledSymbols(dictionary); + _isLoaded = true; + } + catch (Exception e) + { + // This happens if the dylib cannot be located on disk, which will be the case for Apple shipped dylibs. (e.g. Foundation, StoreKit, etc.) + Console.Error.WriteLine($"Error loading demangled symbols: {e.Message}"); + } + } +} diff --git a/src/Swift.Bindings/src/Emitter/StringEmitter/Handler/MethodHandler.cs b/src/Swift.Bindings/src/Emitter/StringEmitter/Handler/MethodHandler.cs index 9366d09933d1..ce8ef8e41700 100644 --- a/src/Swift.Bindings/src/Emitter/StringEmitter/Handler/MethodHandler.cs +++ b/src/Swift.Bindings/src/Emitter/StringEmitter/Handler/MethodHandler.cs @@ -5,6 +5,73 @@ namespace BindingsGeneration { + /// + /// Factory class for creating instances of ConstructorHandler. + /// + public class ConstructorHandlerFactory : IFactory + { + /// + /// Determines if the factory handles the specified declaration. + /// + /// The base declaration. + public bool Handles(BaseDecl decl) + { + return decl is MethodDecl methodDecl && methodDecl.IsConstructor; + } + + /// + /// Constructs a new instance of ConstructorHandler. + /// + public IMethodHandler Construct() + { + return new ConstructorHandler(); + } + } + + /// + /// Handler class for constructor declarations. + /// + public class ConstructorHandler : BaseHandler, IMethodHandler + { + public ConstructorHandler() + { + } + + /// + public IEnvironment Marshal(BaseDecl baseDecl, ITypeDatabase typeDatabase) + { + if (baseDecl is not MethodDecl methodDecl) + { + throw new ArgumentException("The provided decl must be a MethodDecl.", nameof(baseDecl)); + } + return new MethodEnvironment(methodDecl, typeDatabase); + } + + /// + public void Emit(CSharpWriter csWriter, SwiftWriter swiftWriter, IEnvironment env, Conductor conductor) + { + var methodEnv = (MethodEnvironment)env; + var signatureHandler = new SignatureHandler(methodEnv); + + if (methodEnv.MethodDecl.IsGeneric) + { + // TODO: This should revert writing the entire struct: https://github.com/dotnet/runtimelab/issues/2890 + Console.WriteLine($"Constructor {methodEnv.MethodDecl.Name} has unsupported generic parameters"); + return; + } + + if (signatureHandler.GetWrapperSignature().ContainsPlaceholder) + { + Console.WriteLine($"Method {methodEnv.MethodDecl.Name} has unsupported signature: ({signatureHandler.GetWrapperSignature().ParametersString()}) -> {signatureHandler.GetWrapperSignature().ReturnType}"); + return; + } + + var wrapperEmitter = new WrapperEmitter(methodEnv, signatureHandler); + wrapperEmitter.EmitConstructor(csWriter); + PInvokeEmitter.EmitPInvoke(csWriter, methodEnv, signatureHandler); + csWriter.WriteLine(); + } + } /// /// Represents a method handler factory. @@ -39,37 +106,22 @@ public MethodHandler() { } - /// - /// Marshals the method declaration. - /// - /// The method declaration. - /// The type database instance. - public IEnvironment Marshal(BaseDecl decl, ITypeDatabase typeDatabase) + /// + public IEnvironment Marshal(BaseDecl baseDecl, ITypeDatabase typeDatabase) { - if (decl is not MethodDecl methodDecl) + if (baseDecl is not MethodDecl methodDecl) { - throw new ArgumentException("The provided decl must be a MethodDecl.", nameof(decl)); + throw new ArgumentException("The provided decl must be a MethodDecl.", nameof(baseDecl)); } return new MethodEnvironment(methodDecl, typeDatabase); } - /// - /// Emits the method declaration. - /// - /// The IndentedTextWriter instance. - /// The environment. - /// The conductor instance. + /// public void Emit(CSharpWriter csWriter, SwiftWriter swiftWriter, IEnvironment env, Conductor conductor) { var methodEnv = (MethodEnvironment)env; var signatureHandler = new SignatureHandler(methodEnv); - if (methodEnv.MethodDecl.GenericParameters.Any(x => x.Constraints.Count > 0)) - { - Console.WriteLine($"Method {methodEnv.MethodDecl.Name} has unsupported generic constraints"); - return; - } - if (signatureHandler.GetWrapperSignature().ContainsPlaceholder) { Console.WriteLine($"Method {methodEnv.MethodDecl.Name} has unsupported signature: ({signatureHandler.GetWrapperSignature().ParametersString()}) -> {signatureHandler.GetWrapperSignature().ReturnType}"); @@ -77,7 +129,7 @@ public void Emit(CSharpWriter csWriter, SwiftWriter swiftWriter, IEnvironment en } var wrapperEmitter = new WrapperEmitter(methodEnv, signatureHandler); - wrapperEmitter.Emit(csWriter, swiftWriter); + wrapperEmitter.EmitMethod(csWriter, swiftWriter); PInvokeEmitter.EmitPInvoke(csWriter, methodEnv, signatureHandler); csWriter.WriteLine(); } @@ -145,8 +197,8 @@ public void HandleReturnType() var argument = _env.MethodDecl.CSSignature.First(); if (argument.IsGeneric) { - var placeholderName = _env.GenericTypeMapping[argument.SwiftTypeSpec.ToString()].PlaceholderName; - SetReturnType(placeholderName); + var csTypeParamName = _env.GenericTypeMapping[argument.SwiftTypeSpec.ToString()].TypeParameter; + SetReturnType(csTypeParamName); } else { @@ -164,8 +216,8 @@ public void HandleArguments() { if (argument.IsGeneric) { - var placeholderName = _env.GenericTypeMapping[argument.SwiftTypeSpec.ToString()].PlaceholderName; - AddParameter(placeholderName, argument.Name); + var csTypeParamName = _env.GenericTypeMapping[argument.SwiftTypeSpec.ToString()].TypeParameter; + AddParameter(csTypeParamName, argument.Name); } else { @@ -264,7 +316,7 @@ public void HandleArguments() { if (argument.IsGeneric) { - var payloadName = _env.GenericTypeMapping[argument.SwiftTypeSpec.ToString()].PayloadName; + var payloadName = NameProvider.GetPayloadName(argument.Name); AddParameter("IntPtr", payloadName); } else if (MarshallingHelpers.ArgumentIsMarshalledAsCSStruct(argument, _env.TypeDatabase)) @@ -286,11 +338,27 @@ public void HandleGenericMetadata() { foreach (var genericParameter in _env.MethodDecl.GenericParameters) { - var metadataName = _env.GenericTypeMapping[genericParameter.TypeName].MetadataName; + var metadataName = NameProvider.GetMetadataName(_env.GenericTypeMapping[genericParameter.TypeName].TypeParameter); AddParameter("TypeMetadata", metadataName); } } + /// + /// Handles the protocol conformances of the generic parameters of the method. + /// + public void HandleProtocolConformance() + { + foreach (var genericParameter in _env.MethodDecl.GenericParameters) + { + var conformances = genericParameter.Constraints.Where(c => c is ProtocolConformance).OrderBy(c => c.ProtocolSpec.NameWithoutModule); + foreach (var conformance in conformances) + { + var pwtName = NameProvider.GetProtocolWitnessTableName(_env.GenericTypeMapping[genericParameter.TypeName].TypeParameter, conformance.ProtocolSpec.NameWithoutModule); + AddParameter("ProtocolWitnessTable", pwtName); + } + } + } + /// /// Handles the SwiftSelf parameter of the method. /// @@ -376,6 +444,7 @@ public Signature GetPInvokeSignature() pInvokeSignature.HandleSwiftAsync(); pInvokeSignature.HandleArguments(); pInvokeSignature.HandleGenericMetadata(); + pInvokeSignature.HandleProtocolConformance(); pInvokeSignature.HandleSwiftSelf(); pInvokeSignature.HandleSwiftError(); _pInvokeSignature = pInvokeSignature.Build(); @@ -453,66 +522,27 @@ internal WrapperEmitter(MethodEnvironment methodEnv, SignatureHandler signatureH _requiresSwiftAsync = _env.MethodDecl.IsAsync; } - /// - /// Emits the wrapper. - /// - /// - internal void Emit(CSharpWriter csWriter, SwiftWriter swiftWriter) - { - if (_env.MethodDecl.IsConstructor) - { - EmitConstructor(csWriter); - } - else - { - EmitMethod(csWriter, swiftWriter); - } - } - /// /// Emits the constructor wrapper. /// /// The IndentedTextWriter instance. - private void EmitConstructor(CSharpWriter csWriter) + internal void EmitConstructor(CSharpWriter csWriter) { EmitSignatureConstructor(csWriter); EmitBodyStart(csWriter); - - EmitDeclarationsForAllocations(csWriter); - - EmitTryBlockStart(csWriter); - EmitSwiftSelf(csWriter); EmitIndirectResultConstructor(csWriter); - EmitGenericArguments(csWriter); EmitPInvokeCall(csWriter); EmitSwiftError(csWriter); EmitReturnConstructor(csWriter); - - EmitTryBlockEnd(csWriter); - - EmitFinally(csWriter); - EmitBodyEnd(csWriter); } - /// - /// Emits the declarations for allocations. - /// - private void EmitDeclarationsForAllocations(CSharpWriter csWriter) - { - foreach (var argument in _env.MethodDecl.CSSignature.Skip(1).Where(a => a.IsGeneric)) - { - var (typeName, metadataName, payloadName) = _env.GenericTypeMapping[argument.SwiftTypeSpec.ToString()]; - csWriter.WriteLine($"IntPtr {payloadName} = IntPtr.Zero;"); - } - } - /// /// Emits the method wrapper. /// /// The IndentedTextWriter instance. - private void EmitMethod(CSharpWriter csWriter, SwiftWriter swiftWriter) + internal void EmitMethod(CSharpWriter csWriter, SwiftWriter swiftWriter) { EmitAsyncWrapper(csWriter); @@ -527,6 +557,7 @@ private void EmitMethod(CSharpWriter csWriter, SwiftWriter swiftWriter) EmitSwiftSelf(csWriter); EmitIndirectResultMethod(csWriter); EmitGenericArguments(csWriter); + EmitProtocolWitnessTables(csWriter); EmitPInvokeCall(csWriter); EmitSwiftError(csWriter); EmitReturnMethod(csWriter); @@ -538,6 +569,18 @@ private void EmitMethod(CSharpWriter csWriter, SwiftWriter swiftWriter) EmitBodyEnd(csWriter); } + /// + /// Emits the declarations for allocations. + /// + private void EmitDeclarationsForAllocations(CSharpWriter csWriter) + { + foreach (var argument in _env.MethodDecl.CSSignature.Skip(1).Where(a => a.IsGeneric)) + { + var payloadName = NameProvider.GetPayloadName(argument.Name); + csWriter.WriteLine($"IntPtr {payloadName} = IntPtr.Zero;"); + } + } + /// /// Emits the SwiftSelf variable. /// @@ -652,11 +695,21 @@ private void EmitIndirectResultMethod(CSharpWriter csWriter) /// private void EmitGenericArguments(CSharpWriter csWriter) { + foreach (var genericParameter in _env.MethodDecl.GenericParameters) + { + var csTypeParamName = _env.GenericTypeMapping[genericParameter.TypeName].TypeParameter; + var metadataName = NameProvider.GetMetadataName(csTypeParamName); + + csWriter.WriteLine($"var {metadataName} = TypeMetadata.GetTypeMetadataOrThrow<{csTypeParamName}>();"); + } + foreach (var argument in _env.MethodDecl.CSSignature.Skip(1).Where(a => a.IsGeneric)) { - var (typeName, metadataName, payloadName) = _env.GenericTypeMapping[argument.SwiftTypeSpec.ToString()]; + var csTypeParamName = _env.GenericTypeMapping[argument.SwiftTypeSpec.ToString()].TypeParameter; + var metadataName = NameProvider.GetMetadataName(csTypeParamName); + var payloadName = NameProvider.GetPayloadName(argument.Name); + var text = $$""" - var {{metadataName}} = TypeMetadata.GetTypeMetadataOrThrow<{{typeName}}>(); {{payloadName}} = (IntPtr)NativeMemory.Alloc({{metadataName}}.Size); SwiftMarshal.MarshalToSwift({{argument.Name}}, {{payloadName}}); """; @@ -665,6 +718,23 @@ private void EmitGenericArguments(CSharpWriter csWriter) csWriter.WriteLine(); } + + private void EmitProtocolWitnessTables(CSharpWriter csWriter) + { + foreach (var genericParameter in _env.MethodDecl.GenericParameters) + { + var csTypeParamName = _env.GenericTypeMapping[genericParameter.TypeName].TypeParameter; + var conformances = genericParameter.Constraints.Where(c => c is ProtocolConformance).OrderBy(c => c.ProtocolSpec.NameWithoutModule); + foreach (var conformance in conformances) + { + var pwtName = NameProvider.GetProtocolWitnessTableName(csTypeParamName, conformance.ProtocolSpec.NameWithoutModule); + var protocolName = NameProvider.GetInterfaceName(conformance.ProtocolSpec.NameWithoutModule); + csWriter.WriteLine($"var {pwtName} = ProtocolWitnessTable.GetOrThrow<{csTypeParamName}, {protocolName}>();"); + } + } + csWriter.WriteLine(); + } + /// /// Emits the PInvoke call. /// @@ -746,7 +816,7 @@ private void EmitSignatureConstructor(CSharpWriter csWriter) { var genericParams = _env.MethodDecl.IsGeneric switch { - true => $"<{string.Join(", ", _env.MethodDecl.GenericParameters.Select(p => _env.GenericTypeMapping[p.TypeName].PlaceholderName))}>", + true => $"<{string.Join(", ", _env.MethodDecl.GenericParameters.Select(p => _env.GenericTypeMapping[p.TypeName].TypeParameter))}>", false => "" }; csWriter.WriteLine($"public {_env.ParentDecl.Name}{genericParams}({_wrapperSignature.ParametersString()})"); @@ -760,7 +830,7 @@ private void EmitSignatureMethod(CSharpWriter csWriter) { var genericParams = _env.MethodDecl.IsGeneric switch { - true => $"<{string.Join(", ", _env.MethodDecl.GenericParameters.Select(p => _env.GenericTypeMapping[p.TypeName].PlaceholderName))}>", + true => $"<{string.Join(", ", _env.MethodDecl.GenericParameters.Select(p => _env.GenericTypeMapping[p.TypeName].TypeParameter))}>", false => "" }; @@ -818,7 +888,7 @@ private void EmitFinally(CSharpWriter csWriter) foreach (var argument in _env.MethodDecl.CSSignature.Skip(1).Where(a => a.IsGeneric)) { - var (_, _, payloadName) = _env.GenericTypeMapping[argument.SwiftTypeSpec.ToString()]; + var payloadName = NameProvider.GetPayloadName(argument.Name); csWriter.WriteLine($"NativeMemory.Free((void*){payloadName});"); } diff --git a/src/Swift.Bindings/src/Emitter/StringEmitter/Handler/ModuleHandler.cs b/src/Swift.Bindings/src/Emitter/StringEmitter/Handler/ModuleHandler.cs index dcdba8c3f48b..675abf7fa0f4 100644 --- a/src/Swift.Bindings/src/Emitter/StringEmitter/Handler/ModuleHandler.cs +++ b/src/Swift.Bindings/src/Emitter/StringEmitter/Handler/ModuleHandler.cs @@ -38,27 +38,17 @@ public ModuleHandler() { } - /// - /// Marshals the module declaration. - /// - /// The module declaration. - /// The type database instance. - public IEnvironment Marshal(BaseDecl decl, ITypeDatabase typeDatabase) + /// + public IEnvironment Marshal(BaseDecl baseDecl, ITypeDatabase typeDatabase) { - if (decl is not ModuleDecl moduleDecl) + if (baseDecl is not ModuleDecl moduleDecl) { - throw new ArgumentException("The provided decl must be a ModuleDecl.", nameof(decl)); + throw new ArgumentException("The provided decl must be a ModuleDecl.", nameof(baseDecl)); } return new ModuleEnvironment(moduleDecl, typeDatabase); } - /// - /// Emits the code for the specified environment. - /// - /// The IndentedTextWriter instance. - /// The environment. - /// The conductor instance. - /// The type database instance. + /// public void Emit(CSharpWriter csWriter, SwiftWriter swiftWriter, IEnvironment env, Conductor conductor) { var moduleEnv = (ModuleEnvironment)env; diff --git a/src/Swift.Bindings/src/Emitter/StringEmitter/Handler/TypeHandler.cs b/src/Swift.Bindings/src/Emitter/StringEmitter/Handler/TypeHandler.cs index dcf836da0274..96424c2abc5f 100644 --- a/src/Swift.Bindings/src/Emitter/StringEmitter/Handler/TypeHandler.cs +++ b/src/Swift.Bindings/src/Emitter/StringEmitter/Handler/TypeHandler.cs @@ -38,27 +38,17 @@ public FrozenStructHandler() { } - /// - /// Marshals the specified struct declaration. - /// - /// The struct declaration. - /// The type database instance. - public IEnvironment Marshal(BaseDecl decl, ITypeDatabase typeDatabase) + /// + public IEnvironment Marshal(BaseDecl baseDecl, ITypeDatabase typeDatabase) { - if (decl is not StructDecl structDecl) + if (baseDecl is not StructDecl structDecl) { - throw new ArgumentException("The provided decl must be a StructDecl.", nameof(decl)); + throw new ArgumentException("The provided decl must be a StructDecl.", nameof(baseDecl)); } return new TypeEnvironment(structDecl, typeDatabase); } - /// - /// Emits the code for the specified environment. - /// - /// The IndentedTextWriter instance. - /// The environment. - /// The conductor instance. - /// The type database instance. + /// public void Emit(CSharpWriter csWriter, SwiftWriter swiftWriter, IEnvironment env, Conductor conductor) { var structEnv = (TypeEnvironment)env; @@ -170,28 +160,18 @@ public NonFrozenStructHandler() { } - /// - /// Marshals the specified struct declaration. - /// - /// The struct declaration. - /// The type database instance. - public IEnvironment Marshal(BaseDecl decl, ITypeDatabase typeDatabase) + /// + public IEnvironment Marshal(BaseDecl baseDecl, ITypeDatabase typeDatabase) { - if (decl is not StructDecl structDecl) + if (baseDecl is not StructDecl structDecl) { - throw new ArgumentException("The provided decl must be a StructDecl.", nameof(decl)); + throw new ArgumentException("The provided decl must be a StructDecl.", nameof(baseDecl)); } return new TypeEnvironment(structDecl, typeDatabase); } - /// - /// Emits the code for the specified environment. - /// - /// The IndentedTextWriter instance. - /// The environment. - /// The conductor instance. - /// The type database instance. + /// public void Emit(CSharpWriter csWriter, SwiftWriter swiftWriter, IEnvironment env, Conductor conductor) { var structEnv = (TypeEnvironment)env; @@ -323,27 +303,17 @@ public ClassHandler() { } - /// - /// Marshals the specified class declaration. - /// - /// The class declaration. - /// The type database instance. - public IEnvironment Marshal(BaseDecl decl, ITypeDatabase typeDatabase) + /// + public IEnvironment Marshal(BaseDecl baseDecl, ITypeDatabase typeDatabase) { - if (decl is not ClassDecl classDecl) + if (baseDecl is not ClassDecl classDecl) { - throw new ArgumentException("The provided decl must be a ClassDecl.", nameof(decl)); + throw new ArgumentException("The provided decl must be a ClassDecl.", nameof(baseDecl)); } return new TypeEnvironment(classDecl, typeDatabase); } - /// - /// Emits the necessary code for the specified environment. - /// - /// The IndentedTextWriter instance. - /// The environment. - /// The conductor instance. - /// The type database instance. + /// public void Emit(CSharpWriter csWriter, SwiftWriter swiftWriter, IEnvironment env, Conductor conductor) { var classEnv = (TypeEnvironment)env; @@ -518,17 +488,127 @@ IntPtr ISwiftObject.MarshalToSwift(IntPtr swiftDest) /// Writes the GetProtocolConformanceDescriptor method for the struct. /// private void WriteGetProtocolConformanceDescriptor() + { + WriteStaticConstructor(); + var libPath = _typeDatabase.GetLibraryPath(_moduleDecl.Name); + var text = $$""" + static ProtocolConformanceDescriptor ISwiftObject.GetProtocolConformanceDescriptor() + where TProtocol : class + { + if (!_protocolConformanceSymbols.TryGetValue(typeof(TProtocol), out var symbolName)) + { + throw new SwiftRuntimeException($"Attempted to retrieve protocol conformance descriptor for type {{_structDecl.Name}} and protocol {typeof(TProtocol).Name}, but no conformance was found."); + } + + return ProtocolConformanceDescriptor.LoadFromSymbol("{{libPath}}", symbolName); + } + """; + + _writer.WriteLines(text); + _writer.WriteLine(); + } + + /// + /// Writes the static constructor for the struct. + /// + private void WriteStaticConstructor() { var text = $$""" - static ProtocolConformanceDescriptor ISwiftObject.GetProtocolConformanceDescriptor() + private static Dictionary _protocolConformanceSymbols; + + static {{_structDecl.Name}}() { - throw new NotImplementedException(); + _protocolConformanceSymbols = new Dictionary + { + {{GenerateGetProtocolConformanceDictionaryEntries()}} + }; } """; _writer.WriteLines(text); _writer.WriteLine(); + } + private string GenerateGetProtocolConformanceDictionaryEntries() + { + var libPath = _typeDatabase.GetLibraryPath(_moduleDecl.Name); + var entries = new List(); + var protocolConformanceDescriptors = DemangledSymbolsRegister.Instance.GetData(libPath).ProtocolConformanceDescriptors; + + foreach (var conformance in _structDecl.Conformances.Where(c => c.ProtocolSpec.Module == _moduleDecl.Name)) // Process only protocol conformances from current module for now + { + var protocol = NameProvider.GetInterfaceName(conformance.ProtocolSpec.NameWithoutModule); + var typeRecord = _typeDatabase.GetTypeRecordOrThrow(_moduleDecl.Name, _structDecl.FullyQualifiedNameWithoutModule); + var protocolConformanceSymbol = protocolConformanceDescriptors.GetValueOrDefault((new NamedTypeSpec(_structDecl.FullyQualifiedName), conformance.ProtocolSpec)); // TODO: Get rid of TypeSpec https://github.com/dotnet/runtimelab/issues/2889 + + entries.Add($"{{typeof({protocol}), \"{protocolConformanceSymbol}\"}}"); + } + + return string.Join(",\n", entries); + } + } + + /// + /// Factory class for creating instances of ProtocolHandler. + /// + public class ProtocolHandlerFactory : IFactory + { + /// + /// Determines if the factory handles the specified declaration. + /// + /// The base declaration. + public bool Handles(BaseDecl decl) + { + return decl is ProtocolDecl; + } + + /// + /// Constructs a new instance of ProtocolHandler. + /// + public ITypeHandler Construct() + { + return new ProtocolHandler(); + } + } + + /// + /// Handler class for protocol declarations. + /// + public class ProtocolHandler : BaseHandler, ITypeHandler + { + public ProtocolHandler() + { + } + + /// + public IEnvironment Marshal(BaseDecl baseDecl, ITypeDatabase typeDatabase) + { + if (baseDecl is not ProtocolDecl protocolDecl) + { + throw new ArgumentException("The provided decl must be a ProtocolDecl.", nameof(baseDecl)); + } + return new TypeEnvironment(protocolDecl, typeDatabase); + } + + /// + public void Emit(CSharpWriter csWriter, SwiftWriter swiftWriter, IEnvironment env, Conductor conductor) + { + var protocolEnv = (TypeEnvironment)env; + var protocolDecl = (ProtocolDecl)protocolEnv.TypeDecl; + + var interfaceName = NameProvider.GetInterfaceName(protocolDecl.Name); + + csWriter.WriteLine($"public interface {interfaceName}"); + csWriter.WriteLine("{"); + csWriter.Indent++; + + // TODO: Implement protocol methods and properties + // base.HandleBaseDecl(writer, protocolDecl.Types, conductor, env.TypeDatabase); + // base.HandleBaseDecl(writer, protocolDecl.Methods, conductor, env.TypeDatabase); + + csWriter.Indent--; + csWriter.WriteLine("}"); + csWriter.WriteLine(); } } } diff --git a/src/Swift.Bindings/src/Marshaler/Conductor.cs b/src/Swift.Bindings/src/Marshaler/Conductor.cs index 4f71390ed9ef..1531e048ee63 100644 --- a/src/Swift.Bindings/src/Marshaler/Conductor.cs +++ b/src/Swift.Bindings/src/Marshaler/Conductor.cs @@ -24,10 +24,12 @@ public class Conductor private readonly List _typeHandlerFactories = [ new NonFrozenStructHandlerFactory(), new FrozenStructHandlerFactory(), + new ProtocolHandlerFactory(), new ClassHandlerFactory(), ]; private readonly List _fieldHandlerFactories = []; private readonly List _methodHandlerFactories = [ + new ConstructorHandlerFactory(), new MethodHandlerFactory(), ]; private readonly List _argumentHandlerFactories = []; diff --git a/src/Swift.Bindings/src/Marshaler/IHandler.cs b/src/Swift.Bindings/src/Marshaler/IHandler.cs index 8a4601a30789..cdf36a963a69 100644 --- a/src/Swift.Bindings/src/Marshaler/IHandler.cs +++ b/src/Swift.Bindings/src/Marshaler/IHandler.cs @@ -15,13 +15,15 @@ public interface IHandler /// Marshals the specified base declaration. /// /// The base declaration. + /// The type database instance. /// The environment corresponding to the base declaration. IEnvironment Marshal(BaseDecl baseDecl, ITypeDatabase typeDatabase); /// /// Emits the necessary code for the specified environment. /// - /// The IndentedTextWriter instance. + /// The csWriter instance. + /// The swiftWriter instance. /// The environment. /// The conductor instance. void Emit(CSharpWriter csWriter, SwiftWriter swiftWriter, IEnvironment env, Conductor conductor); @@ -102,6 +104,18 @@ protected virtual void HandleBaseDecl(CSharpWriter csWriter, SwiftWriter swiftWr Console.WriteLine($"No handler found for method {classDecl.Name}"); } } + else if (baseDecl is ProtocolDecl protocolDecl) + { + if (conductor.TryGetTypeHandler(protocolDecl, out var handler)) + { + var env = handler.Marshal(protocolDecl, typeDatabase); + handler.Emit(csWriter, swiftWriter, env, conductor); + } + else + { + Console.WriteLine($"No handler found for method {protocolDecl.Name}"); + } + } else if (baseDecl is MethodDecl methodDecl) { if (conductor.TryGetMethodHandler(methodDecl, out var handler)) diff --git a/src/Swift.Bindings/src/Marshaler/NameProvider.cs b/src/Swift.Bindings/src/Marshaler/NameProvider.cs index 7c7a8db64a6c..eafd4f775c77 100644 --- a/src/Swift.Bindings/src/Marshaler/NameProvider.cs +++ b/src/Swift.Bindings/src/Marshaler/NameProvider.cs @@ -7,10 +7,8 @@ namespace BindingsGeneration; /// /// Represents a generic parameter name mapping. /// -/// The name of the generic type parameter e.g. T0. -/// The name of the metadata type parameter. -/// The name of the payload type parameter. -public record struct GenericParameterCSName(string PlaceholderName, string MetadataName, string PayloadName); +/// The name of the generic type parameter e.g. T0. +public record struct GenericParameterCSName(string TypeParameter); /// /// Provides methods for generating names. @@ -42,6 +40,16 @@ public static string GetMangledName(MethodDecl methodDecl) return methodDecl.MangledName; } + /// + /// Provides the name of the interface based on a protocol name. + /// + /// The protocol name. + /// The name of the interface. + public static string GetInterfaceName(string protocolName) + { + return $"ISwift{protocolName}"; + } + /// /// Provides the mapping of generic type parameters. /// @@ -51,8 +59,10 @@ public static Dictionary GetGenericTypeMapping(M methodDecl.GenericParameters .Select((param, i) => (param, i)) .ToDictionary(x => x.param.TypeName, x => new GenericParameterCSName( - PlaceholderName: $"T{x.i}", - MetadataName: $"T{x.i}Metadata", - PayloadName: $"T{x.i}Payload" + TypeParameter: $"T{x.i}" )); + + public static string GetMetadataName(string typeName) => $"{typeName}Metadata"; + public static string GetPayloadName(string argumentName) => $"{argumentName}Payload"; + public static string GetProtocolWitnessTableName(string typeName, string protocolName) => $"{typeName}{protocolName}PWT"; } diff --git a/src/Swift.Bindings/src/Model/TypeDecl/ClassDecl.cs b/src/Swift.Bindings/src/Model/TypeDecl/ClassDecl.cs index 5cd9f9f59b72..81b9dd17fb56 100644 --- a/src/Swift.Bindings/src/Model/TypeDecl/ClassDecl.cs +++ b/src/Swift.Bindings/src/Model/TypeDecl/ClassDecl.cs @@ -8,5 +8,9 @@ namespace BindingsGeneration /// public sealed record ClassDecl : TypeDecl { + /// + /// Protocol conformances. + /// + public required List Conformances { get; set; } } } diff --git a/src/Swift.Bindings/src/Model/TypeDecl/GenericArgumentDecl.cs b/src/Swift.Bindings/src/Model/TypeDecl/GenericArgumentDecl.cs index 5d64dadda047..5cc51356460c 100644 --- a/src/Swift.Bindings/src/Model/TypeDecl/GenericArgumentDecl.cs +++ b/src/Swift.Bindings/src/Model/TypeDecl/GenericArgumentDecl.cs @@ -12,37 +12,5 @@ namespace BindingsGeneration; public record GenericArgumentDecl( string TypeName, string SugaredTypeName, - List Constraints + List Constraints ); - -/// -/// Represents a generic argument declaration. -/// -/// The target type of the conformance -/// The protocol name of the conformance -public abstract record Conformance( - string TargetType, - string ProtocolName -); - -/// -/// Represents a protocol conformance. -/// -/// The target type of the conformance -/// The protocol name of the conformance -public record ProtocolConformance( - string TargetType, - string ProtocolName -) : Conformance(TargetType, ProtocolName); - -/// -/// Represents an associated type conformance. -/// -/// The target type of the conformance -/// The protocol name of the conformance -/// The associated type name of the conformance -public record AssociatedTypeConformance( - string TargetType, - string ProtocolName, - string AssociatedTypeName -) : Conformance(TargetType, ProtocolName); diff --git a/src/Swift.Bindings/src/Model/TypeDecl/ProtocolConformance.cs b/src/Swift.Bindings/src/Model/TypeDecl/ProtocolConformance.cs new file mode 100644 index 000000000000..975b56145f21 --- /dev/null +++ b/src/Swift.Bindings/src/Model/TypeDecl/ProtocolConformance.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace BindingsGeneration; + + +/// +/// Represents a protocol conformance. +/// +/// The type spec of the target type of the conformance +/// The protocol spec of the conformance +public record ProtocolConformance( + NamedTypeSpec TargetType, + NamedTypeSpec ProtocolSpec +); diff --git a/src/Swift.Bindings/src/Model/TypeDecl/ProtocolDecl.cs b/src/Swift.Bindings/src/Model/TypeDecl/ProtocolDecl.cs new file mode 100644 index 000000000000..78b5d734472a --- /dev/null +++ b/src/Swift.Bindings/src/Model/TypeDecl/ProtocolDecl.cs @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace BindingsGeneration; + +public sealed record ProtocolDecl : TypeDecl +{ +} diff --git a/src/Swift.Bindings/src/Model/TypeDecl/StructDecl.cs b/src/Swift.Bindings/src/Model/TypeDecl/StructDecl.cs index c09060c5da87..c41a3d905e49 100644 --- a/src/Swift.Bindings/src/Model/TypeDecl/StructDecl.cs +++ b/src/Swift.Bindings/src/Model/TypeDecl/StructDecl.cs @@ -11,5 +11,10 @@ public sealed record StructDecl : TypeDecl public required bool IsBlittable { get; set; } public required bool IsFrozen { get; set; } + + /// + /// Protocol conformances. + /// + public required List Conformances { get; set; } } } diff --git a/src/Swift.Bindings/src/Parser/GenericSignatureParser.cs b/src/Swift.Bindings/src/Parser/GenericSignatureParser.cs index bd17d7083ad7..f608209ccfe8 100644 --- a/src/Swift.Bindings/src/Parser/GenericSignatureParser.cs +++ b/src/Swift.Bindings/src/Parser/GenericSignatureParser.cs @@ -35,7 +35,7 @@ public static List ParseGenericSignature(string? genericSig new GenericArgumentDecl( typeName, paramMap[typeName], - constraints.Where(c => c.TargetType.StartsWith(typeName)).ToList() + constraints.Where(c => c.TargetType.NameWithoutModule.StartsWith(typeName)).ToList() ) ).ToList(); } @@ -59,25 +59,41 @@ private static List ExtractGenericParams(string signature) /// /// The generic signature to extract constraints from. /// A list of constraints. - private static List ExtractConstraints(string signature) + private static List ExtractConstraints(string signature) { var whereIndex = signature.IndexOf("where", StringComparison.OrdinalIgnoreCase); if (whereIndex == -1) - return new List(); + return new List(); - return signature[(whereIndex + "where".Length)..].Split(',').Select(ParseConstraint).ToList(); + var constraintsSection = signature[(whereIndex + "where".Length)..]; + var constraints = constraintsSection.Split(','); + + var parsedConstraints = constraints + .Select(ParseConstraint) + .Where(constraint => constraint is not null) + .Cast(); + + return [.. parsedConstraints]; } /// /// Parses a constraint clause into a Conformance object. /// /// The constraint clause to parse. - /// A Conformance object. - private static Conformance ParseConstraint(string clause) + /// A Conformance object. Null if the constraint refers to the associated type. + private static ProtocolConformance? ParseConstraint(string clause) { var parts = clause.Split(new[] { ":", "==" }, StringSplitOptions.TrimEntries); - return parts[0].Contains('.') - ? new AssociatedTypeConformance(parts[0].Split('.')[0], parts[1], parts[0].Split('.')[1]) - : new ProtocolConformance(parts[0], parts[1]); + if (parts.Length != 2) + { + throw new InvalidOperationException($"Invalid constraint clause: {clause}"); + } + + var target = parts[0]; + var protocol = parts[1]; + + if (!target.Contains(".")) return new ProtocolConformance(new NamedTypeSpec(target), new NamedTypeSpec(protocol)); + + return null; } } diff --git a/src/Swift.Bindings/src/Parser/SwiftABIParser.cs b/src/Swift.Bindings/src/Parser/SwiftABIParser.cs index 838b34b52e83..206c95398545 100644 --- a/src/Swift.Bindings/src/Parser/SwiftABIParser.cs +++ b/src/Swift.Bindings/src/Parser/SwiftABIParser.cs @@ -55,6 +55,7 @@ public record Node public required string? sugared_genericSig { get; set; } public required bool? throwing { get; set; } public required IEnumerable Children { get; set; } = Enumerable.Empty(); + public required IEnumerable Conformances { get; set; } = Enumerable.Empty(); } /// @@ -286,6 +287,10 @@ private List CollectDeclarations(IEnumerable nodes, BaseDecl par decl = CreateClassDecl(node, parentDecl, moduleDecl); break; + case "Protocol": + decl = CreateProtocolDecl(node, parentDecl, moduleDecl); + break; + default: if (_verbose > 1) Console.WriteLine($"Unsupported declaration type '{node.DeclKind} {node.Name}' encountered."); @@ -308,6 +313,16 @@ private List CollectDeclarations(IEnumerable nodes, BaseDecl par return decl; } + private ProtocolConformance HandleConformance(Node node, string typeName) + { + var reduction = demangler.Run(node.MangledName) as TypeSpecReduction ?? throw new InvalidOperationException($"Invalid demangling result for '{node.MangledName}'."); + var protocolTypeSpec = reduction.TypeSpec as NamedTypeSpec ?? throw new InvalidOperationException($"TypeSpec '{reduction.TypeSpec}' is not a NamedTypeSpec"); + + var conformance = new ProtocolConformance(new NamedTypeSpec(typeName), protocolTypeSpec); + + return conformance; + } + /// /// Creates a struct declaration from a node. /// @@ -317,14 +332,16 @@ private List CollectDeclarations(IEnumerable nodes, BaseDecl par /// The struct declaration. private StructDecl CreateStructDecl(Node node, BaseDecl parentDecl, ModuleDecl moduleDecl, bool hasFrozenAttribute) { + var fullyQualifiedName = ExtractFullyQualifiedName(parentDecl.FullyQualifiedName, node.Name); return new StructDecl { Name = ExtractUniqueName(node.Name), - FullyQualifiedName = ExtractFullyQualifiedName(parentDecl.FullyQualifiedName, node.Name), + FullyQualifiedName = fullyQualifiedName, MangledName = node.MangledName, Fields = new List(), Methods = new List(), Types = new List(), + Conformances = [.. node.Conformances.Select(x => HandleConformance(x, fullyQualifiedName))], ParentDecl = parentDecl, ModuleDecl = moduleDecl, IsFrozen = hasFrozenAttribute, @@ -341,7 +358,31 @@ private StructDecl CreateStructDecl(Node node, BaseDecl parentDecl, ModuleDecl m /// The class declaration. private ClassDecl CreateClassDecl(Node node, BaseDecl parentDecl, ModuleDecl moduleDecl) { + var fullyQualifiedName = ExtractFullyQualifiedName(parentDecl.FullyQualifiedName, node.Name); return new ClassDecl + { + Name = ExtractUniqueName(node.Name), + FullyQualifiedName = fullyQualifiedName, + MangledName = node.MangledName, + Fields = new List(), + Methods = new List(), + Types = new List(), + Conformances = [.. node.Conformances.Select(x => HandleConformance(x, fullyQualifiedName))], + ParentDecl = parentDecl, + ModuleDecl = moduleDecl + }; + } + + /// + /// Creates a protocol declaration from a node. + /// + /// The node representing the protocol declaration. + /// The parent declaration. + /// The module declaration. + /// The protocol declaration. + private ProtocolDecl CreateProtocolDecl(Node node, BaseDecl parentDecl, ModuleDecl moduleDecl) + { + return new ProtocolDecl { Name = ExtractUniqueName(node.Name), FullyQualifiedName = ExtractFullyQualifiedName(parentDecl.FullyQualifiedName, node.Name), diff --git a/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Generics/GenericTests.cs b/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Generics/GenericTests.cs index e1912ff2a9bf..2131a340d40d 100644 --- a/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Generics/GenericTests.cs +++ b/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Generics/GenericTests.cs @@ -82,5 +82,69 @@ public void TestFunctionTakesGenericStructAndReturnsOne() var result2 = GenericTests.AcceptsGenericParameterAndReturnsGeneric(b); // No deep comparison for non-frozen structs yet } + + [Fact] + public void TestFunctionTakesMultipleGenericParametersOfSameType() + { + var a = new FrozenStruct(1, 2); + var b = new FrozenStruct(3, 4); + var result = GenericTests.AcceptsTwoValuesOfTheSameGenericType(a, b); + Assert.Equal(a, result); + } + + [Fact] + public void TestFunctionTakesGenericParameterConstrainedToProtocol() + { + var a = new SummableStruct(2, 40); + var result = GenericTests.AcceptsSummable(a); + Assert.Equal(42, result); + } + + [Fact] + public void TestFunctionTakesMultipleGenericParametersOfSameTypeConstrainedToProtocol() + { + var a = new SummableStruct(2, 40); + var b = new SummableStruct(3, 39); + var result = GenericTests.AcceptsMultipleGenericParamsOfTheSameTypeConstrainedByProtocol(a, b); + Assert.Equal(42 + 42, result); + } + + [Fact] + public void TestFunctionTakesGenericParameterConstrainedToMultipleProtocols() + { + var a = new StructWithMultipleProtocols(43, 177); + var result = GenericTests.AcceptsMultipleProtocols(a); + Assert.Equal(43 + 177 + 43 - 177 + 43 * 177, result); + } + + [Fact] + public void TestFunctionTakesMultipleGenericParametersConstrainedToMultipleProtocols() + { + var a = new StructWithMultipleProtocols(43, 177); + var b = new StructWithMultipleProtocols(531, 133); + var result = GenericTests.AcceptsMultipleGenericParamsWithProtocols(a, b); + Assert.Equal((43 + 177) + (43 * 177) + (531 - 133) + (531 / 133), result); + } + + [Fact] + public void TestFunctionTakesMultipleGenericParametersOfDifferentTypesConstrainedByTheSameProtocol() + { + var a = new SummableStruct(2, 40); + var b = new AnotherSummableStruct(3, 39); + var result = GenericTests.AcceptsMultipleGenericParamsOfDifferentTypesConstrainedByTheSameProtocol(a, b); + Assert.Equal(42 + 42, result); + } + + [Fact] + public void TestFunctionWithGenericParamConstrainedToPAT() + { + var a = new IntContainer1(42); + var result = GenericTests.AcceptsIntContainer(a); + Assert.Equal(42 * 2, result); + + var b = new IntContainer2(42); + var result2 = GenericTests.AcceptsIntContainer(b); + Assert.Equal(42 * 4, result2); + } } } diff --git a/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Generics/GenericTests.swift b/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Generics/GenericTests.swift index 28bf8d798962..ab09763c387d 100644 --- a/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Generics/GenericTests.swift +++ b/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Generics/GenericTests.swift @@ -45,3 +45,132 @@ public func AcceptsGenericParameters (a: T, b: U) throws -> Int { public func AcceptsGenericParameterAndReturnsGeneric(a: T) -> T { return a; } + +public func AcceptsTwoValuesOfTheSameGenericType(a: T, b: T) -> T { + return a; +} + +public protocol Summable { + func sum() -> Int +} + +public protocol Subtractable { + func subtract() -> Int +} + +public protocol Multiplicable { + func multiply() -> Int +} + +public protocol Dividable { + func divide() -> Int +} + +@frozen +public struct SummableStruct: Summable { + public var x: Int + public var y: Int + + public init(x: Int, y: Int) { + self.x = x + self.y = y + } + + public func sum() -> Int { + return x + y + } +} + +@frozen +public struct AnotherSummableStruct: Summable { + public var x: Int + public var y: Int + + public init(x: Int, y: Int) { + self.x = x + self.y = y + } + + public func sum() -> Int { + return x + y + } +} + +public func AcceptsSummable(a: T) -> Int { + return a.sum() +} + +public func AcceptsMultipleGenericParamsOfTheSameTypeConstrainedByProtocol(a: T, b: T) -> Int { + return a.sum() + b.sum() +} + +public func AcceptsMultipleGenericParamsOfDifferentTypesConstrainedByTheSameProtocol(a: T, b: U) -> Int { + return a.sum() + b.sum() +} + +public struct StructWithMultipleProtocols: Summable, Subtractable, Multiplicable, Dividable { + public var x: Int + public var y: Int + + public init(x: Int, y: Int) { + self.x = x + self.y = y + } + + public func sum() -> Int { + return x + y + } + + public func subtract() -> Int { + return x - y + } + + public func multiply() -> Int { + return x * y + } + + public func divide() -> Int { + return x / y + } +} + +public func AcceptsMultipleProtocols(a: T) -> Int { + return a.sum() + a.subtract() + a.multiply() +} + +public func AcceptsMultipleGenericParamsWithProtocols(a: T, b: U) -> Int { + return a.sum() + a.multiply() + b.subtract() + b.divide() +} + +public protocol Container { + associatedtype Element + func increase() -> Element +} + +public struct IntContainer1: Container { + public var value: Int + + public init(value: Int) { + self.value = value + } + + public func increase() -> Int { + return value * 2 + } +} + +public struct IntContainer2: Container { + public var value: Int + + public init(value: Int) { + self.value = value + } + + public func increase() -> Int { + return value * 4 + } +} + +public func AcceptsIntContainer(a: T) -> Int where T.Element == Int { + return a.increase() +} diff --git a/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Protocols/ProtocolsTests.cs b/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Protocols/ProtocolsTests.cs new file mode 100644 index 000000000000..f9964b6894ca --- /dev/null +++ b/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Protocols/ProtocolsTests.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Swift.ProtocolsTests; +using Xunit; + +namespace BindingsGeneration.FunctionalTests +{ + public class ProtocolsTests : IClassFixture + { + private readonly TestFixture _fixture; + + public ProtocolsTests(TestFixture fixture) + { + _fixture = fixture; + } + + public class TestFixture + { + static TestFixture() + { + InitializeResources(); + } + + private static void InitializeResources() + { + // Initialize + } + } + + [Fact] + public void ProtocolIsProjected() + { + // This test is to ensure that the protocol is projected, it just needs to compile + Assert.True(typeof(ISwiftPrintable).IsInterface); + } + } +} diff --git a/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Protocols/ProtocolsTests.swift b/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Protocols/ProtocolsTests.swift new file mode 100644 index 000000000000..19e09fd4cecc --- /dev/null +++ b/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Protocols/ProtocolsTests.swift @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import Foundation + +public protocol Printable { + func print() +} diff --git a/src/Swift.Bindings/tests/UnitTests/ParserTests/GenericSignatureParserTests.cs b/src/Swift.Bindings/tests/UnitTests/ParserTests/GenericSignatureParserTests.cs index 6b645552b5f8..d03af70b2820 100644 --- a/src/Swift.Bindings/tests/UnitTests/ParserTests/GenericSignatureParserTests.cs +++ b/src/Swift.Bindings/tests/UnitTests/ParserTests/GenericSignatureParserTests.cs @@ -71,8 +71,8 @@ public void ParseGenericSignature_ParsesSingleParamWithConstraints() Assert.Equal("T", decl.SugaredTypeName); Assert.Single(decl.Constraints); var conformance = Assert.IsType(decl.Constraints[0]); - Assert.Equal("τ_0_0", conformance.TargetType); - Assert.Equal("Swift.Equatable", conformance.ProtocolName); + Assert.Equal("τ_0_0", conformance.TargetType.Name); + Assert.Equal("Swift.Equatable", conformance.ProtocolSpec.Name); } [Fact] @@ -90,16 +90,16 @@ public void ParseGenericSignature_ParsesMultipleParamsWithConstraints() Assert.Equal("T", first.SugaredTypeName); Assert.Single(first.Constraints); var firstConformance = Assert.IsType(first.Constraints[0]); - Assert.Equal("τ_0_0", firstConformance.TargetType); - Assert.Equal("Swift.Equatable", firstConformance.ProtocolName); + Assert.Equal("τ_0_0", firstConformance.TargetType.Name); + Assert.Equal("Swift.Equatable", firstConformance.ProtocolSpec.Name); var second = result[1]; Assert.Equal("τ_0_1", second.TypeName); Assert.Equal("U", second.SugaredTypeName); Assert.Single(second.Constraints); var secondConformance = Assert.IsType(second.Constraints[0]); - Assert.Equal("τ_0_1", secondConformance.TargetType); - Assert.Equal("Swift.Hashable", secondConformance.ProtocolName); + Assert.Equal("τ_0_1", secondConformance.TargetType.Name); + Assert.Equal("Swift.Hashable", secondConformance.ProtocolSpec.Name); } [Fact] @@ -114,15 +114,10 @@ public void ParseGenericSignature_ParsesAssociatedTypeConstraints() var decl = result[0]; Assert.Equal("τ_0_0", decl.TypeName); Assert.Equal("T", decl.SugaredTypeName); - Assert.Equal(2, decl.Constraints.Count); + Assert.Single(decl.Constraints); var proto = Assert.IsType(decl.Constraints[0]); - Assert.Equal("τ_0_0", proto.TargetType); - Assert.Equal("SomeProtocol", proto.ProtocolName); - - var assoc = Assert.IsType(decl.Constraints[1]); - Assert.Equal("τ_0_0", assoc.TargetType); - Assert.Equal("System.Guid", assoc.ProtocolName); - Assert.Equal("ID", assoc.AssociatedTypeName); + Assert.Equal("τ_0_0", proto.TargetType.Name); + Assert.Equal("SomeProtocol", proto.ProtocolSpec.Name); } }