Skip to content

Commit c93e463

Browse files
Fix typeconversion (#332)
* Add failing test cases for generative interfaces and abstract classes #211 * Attempt to add missing metadata to types and methods * Fix issue in generating virtual method, and update tests to leverage optional invokeCode parameter * Update metadata based on reflected method attributes * Use type-level attributes matching those of other BCL types * Set 'correct' base type for interfaces and abstract classes * Abstract class with virtual member appears to work * Fix type attributes for interface and get abstract classes 'working' * Show that generated abstract class with abstract methods does not work properly. * Restore checks for abstract/interface methods * Better visibility into what is broken * Clean up tests * Fix interface generation * Fix method attributes inside ProvidedMethod to make the fix available at design time * Force abstract attribute on methods that have no defined body * Fix interface implementation generation * Fix mixed dotnet/provided generic type generation Co-authored-by: Ryan Riley <panesofglass@users.noreply.github.com>
1 parent e9d2c93 commit c93e463

7 files changed

+492
-125
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ ProvidedTypes.exe
1919
.vs/
2020
.paket/
2121
test
22-
packages
22+
packages/
23+
paket-files/

src/ProvidedTypes.fs

Lines changed: 224 additions & 122 deletions
Large diffs are not rendered by default.

src/ProvidedTypes.fsi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,10 +291,10 @@ namespace ProviderImplementation.ProvidedTypes
291291
inherit TypeDelegator
292292

293293
/// When making a cross-targeting type provider, use this method instead of the corresponding ProvidedTypeDefinition constructor from ProvidedTypes
294-
new: className: string * baseType: Type option * ?hideObjectMethods: bool * ?nonNullable: bool * ?isErased: bool * ?isSealed: bool * ?isInterface: bool -> ProvidedTypeDefinition
294+
new: className: string * baseType: Type option * ?hideObjectMethods: bool * ?nonNullable: bool * ?isErased: bool * ?isSealed: bool * ?isInterface: bool * ?isAbstract: bool -> ProvidedTypeDefinition
295295

296296
/// When making a cross-targeting type provider, use this method instead of the corresponding ProvidedTypeDefinition constructor from ProvidedTypes
297-
new: assembly: Assembly * namespaceName: string * className: string * baseType: Type option * ?hideObjectMethods: bool * ?nonNullable: bool * ?isErased: bool * ?isSealed: bool * ?isInterface: bool -> ProvidedTypeDefinition
297+
new: assembly: Assembly * namespaceName: string * className: string * baseType: Type option * ?hideObjectMethods: bool * ?nonNullable: bool * ?isErased: bool * ?isSealed: bool * ?isInterface: bool * ?isAbstract: bool -> ProvidedTypeDefinition
298298

299299
/// Add the given type as an implemented interface.
300300
member AddInterfaceImplementation: interfaceType: Type -> unit

tests/FSharp.TypeProviders.SDK.Tests.fsproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
<DefineConstants>$(DefineConstants);INTERNAL_FSHARP_TYPEPROVIDERS_SDK_TESTS</DefineConstants>
77
</PropertyGroup>
88
<ItemGroup>
9+
<None Include="Script1.fsx" />
910
<None Include="xunit.runner.json">
1011
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
1112
</None>
@@ -23,6 +24,8 @@
2324
<Compile Include="AssemblyReaderTests.fs" />
2425
<Compile Include="GeneratedOpTests.fs" />
2526
<Compile Include="GenerativeEnumsProvisionTests.fs" />
27+
<Compile Include="GenerativeInterfacesTests.fs" />
28+
<Compile Include="GenerativeAbstractClassesTests.fs" />
2629
<Compile Include="Program.fs" Condition="'$(TargetFramework)' == 'netstandard2.0' OR '$(TargetFramework)' == 'netcoreapp3.1' " />
2730
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
2831
<PackageReference Include="xunit" Version="2.4.1" />
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#if INTERACTIVE
2+
#load "../src/ProvidedTypes.fsi" "../src/ProvidedTypes.fs"
3+
#load "../src/ProvidedTypesTesting.fs"
4+
5+
#else
6+
7+
module FSharp.TypeProviders.SDK.Tests.GenerativeAbstractClassesTests
8+
#endif
9+
10+
#nowarn "760" // IDisposable needs new
11+
12+
#if !NO_GENERATIVE
13+
14+
open System
15+
open System.Reflection
16+
open Microsoft.FSharp.Core.CompilerServices
17+
open Xunit
18+
open ProviderImplementation.ProvidedTypes
19+
open ProviderImplementation.ProvidedTypesTesting
20+
21+
[<TypeProvider>]
22+
type GenerativeAbstractClassesProvider (config: TypeProviderConfig) as this =
23+
inherit TypeProviderForNamespaces (config)
24+
25+
let ns = "AbstractClasses.Provided"
26+
let tempAssembly = ProvidedAssembly()
27+
let container = ProvidedTypeDefinition(tempAssembly, ns, "Contracts", Some typeof<obj>, isErased = false)
28+
29+
let createAbstractClass name (members: (string * (string * Type) list * Type * bool) list) =
30+
let t = ProvidedTypeDefinition(name, Some typeof<System.MarshalByRefObject>, hideObjectMethods = true, isErased = false, isAbstract = true)
31+
32+
members
33+
|> List.map (fun (name, parameters, retType, isVirtual) ->
34+
let ps =
35+
parameters
36+
|> List.map (fun (name, ty) ->
37+
ProvidedParameter(name, ty))
38+
if isVirtual then
39+
let m = ProvidedMethod(name, ps, retType, invokeCode = fun args ->
40+
<@ raise (NotImplementedException(name + " is not implemented")) @>.Raw
41+
)
42+
m.AddMethodAttrs (MethodAttributes.Virtual ||| MethodAttributes.HasSecurity)
43+
m
44+
else
45+
let m = ProvidedMethod(name, ps, retType)
46+
//m.AddMethodAttrs (MethodAttributes.Virtual ||| MethodAttributes.Abstract)
47+
m
48+
)
49+
|> t.AddMembers
50+
51+
t
52+
53+
do
54+
let members = [ "GetString", [], typeof<string>, false
55+
"Sum", [("x", typeof<int>); ("y", typeof<int>)], typeof<int>, false ]
56+
let contract = createAbstractClass "Contract" members
57+
container.AddMember contract
58+
59+
let members = [ "GetString", [], typeof<string>, true
60+
"Sum", [("x", typeof<int>); ("y", typeof<int>)], typeof<int>, true ]
61+
let virtualContract = createAbstractClass "VirtualContract" members
62+
container.AddMember virtualContract
63+
64+
tempAssembly.AddTypes [container]
65+
this.AddNamespace(container.Namespace, [container])
66+
67+
let testProvidedAssembly test =
68+
if Targets.supportsFSharp40() then
69+
let runtimeAssemblyRefs = Targets.DotNet45FSharp40Refs()
70+
let runtimeAssembly = runtimeAssemblyRefs.[0]
71+
let cfg = Testing.MakeSimulatedTypeProviderConfig (__SOURCE_DIRECTORY__, runtimeAssembly, runtimeAssemblyRefs)
72+
let tp = GenerativeAbstractClassesProvider(cfg) :> TypeProviderForNamespaces
73+
let providedNamespace = tp.Namespaces.[0]
74+
let providedTypes = providedNamespace.GetTypes()
75+
let providedType = providedTypes.[0]
76+
let providedTypeDefinition = providedType :?> ProvidedTypeDefinition
77+
Assert.Equal("Contracts", providedTypeDefinition.Name)
78+
79+
let assemContents = (tp :> ITypeProvider).GetGeneratedAssemblyContents(providedTypeDefinition.Assembly)
80+
let assembly = Assembly.Load assemContents
81+
assembly.ExportedTypes |> Seq.find (fun ty -> ty.Name = "Contracts") |> test
82+
83+
let runningOnMono = try Type.GetType("Mono.Runtime") <> null with _ -> false
84+
85+
[<Fact>]
86+
let ``Abstract classes with abstract members are generated correctly``() =
87+
// // See tracking bug https://github.com/fsprojects/FSharp.TypeProviders.SDK/issues/211
88+
// if not runningOnMono then
89+
testProvidedAssembly <| fun container ->
90+
let contract = container.GetNestedType "Contract"
91+
Assert.NotNull contract
92+
Assert.True(contract.IsAbstract, "Expected Contract to be an abstract type")
93+
94+
let contractGetString = contract.GetMethod("GetString")
95+
Assert.NotNull contractGetString
96+
Assert.True(contractGetString.IsAbstract, "Expected GetString method to be abstract")
97+
Assert.True(contractGetString.IsVirtual, "Expected GetString method to be virtual")
98+
99+
let contractSum = contract.GetMethod("Sum")
100+
Assert.NotNull contractSum
101+
Assert.True(contractSum.IsAbstract, "Expected Sum method to be abstract")
102+
Assert.True(contractSum.IsVirtual, "Expected Sum method to be virtual")
103+
104+
[<Fact>]
105+
let ``Abstract classes with virtual members are generated correctly``() =
106+
// // See tracking bug https://github.com/fsprojects/FSharp.TypeProviders.SDK/issues/211
107+
// if not runningOnMono then
108+
testProvidedAssembly <| fun container ->
109+
let contract = container.GetNestedType "VirtualContract"
110+
Assert.NotNull contract
111+
Assert.True(contract.IsAbstract, "Expected VirtualContract to be an abstract type")
112+
113+
let contractGetString = contract.GetMethod("GetString")
114+
Assert.NotNull contractGetString
115+
Assert.False(contractGetString.IsAbstract, "Expected GetString method to not be abstract")
116+
Assert.True(contractGetString.IsVirtual, "Expected GetString method to be virtual")
117+
118+
let contractSum = contract.GetMethod("Sum")
119+
Assert.NotNull contractSum
120+
Assert.False(contractSum.IsAbstract, "Expected Sum method to not be abstract")
121+
Assert.True(contractSum.IsVirtual, "Expected Sum method to be virtual")
122+
123+
#endif

tests/GenerativeInterfacesTests.fs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#if INTERACTIVE
2+
#load "../src/ProvidedTypes.fsi" "../src/ProvidedTypes.fs"
3+
#load "../src/ProvidedTypesTesting.fs"
4+
5+
#else
6+
7+
module FSharp.TypeProviders.SDK.Tests.GenerativeInterfacesTests
8+
#endif
9+
10+
#nowarn "760" // IDisposable needs new
11+
12+
#if !NO_GENERATIVE
13+
14+
open System
15+
open System.Reflection
16+
open Microsoft.FSharp.Core.CompilerServices
17+
open Xunit
18+
open ProviderImplementation.ProvidedTypes
19+
open ProviderImplementation.ProvidedTypesTesting
20+
21+
22+
[<TypeProvider>]
23+
type GenerativeInterfacesProvider (config: TypeProviderConfig) as this =
24+
inherit TypeProviderForNamespaces (config)
25+
26+
let ns = "Interfaces.Provided"
27+
let tempAssembly = ProvidedAssembly()
28+
let container = ProvidedTypeDefinition(tempAssembly, ns, "Contracts", Some typeof<obj>, isErased = false)
29+
30+
let createInterface name (members: (string * (string * Type) list * Type) list) =
31+
let t = ProvidedTypeDefinition(name, None, isErased = false, isInterface = true)
32+
33+
members
34+
|> List.map (fun (name, parameters, retType) ->
35+
let ps = parameters |> List.map (fun (name, ty) -> ProvidedParameter(name, ty))
36+
let m = ProvidedMethod(name, ps, retType)
37+
//m.SetMethodAttrs (MethodAttributes.PrivateScope ||| MethodAttributes.Public ||| MethodAttributes.Virtual ||| MethodAttributes.HideBySig ||| MethodAttributes.VtableLayoutMask ||| MethodAttributes.Abstract)
38+
m.AddMethodAttrs (MethodAttributes.Virtual ||| MethodAttributes.Abstract)
39+
m)
40+
|> t.AddMembers
41+
42+
t
43+
44+
do
45+
let marker = createInterface "IMarker" []
46+
container.AddMember marker
47+
48+
let members = [ "GetString", [], typeof<string>
49+
"Sum", [("x", typeof<int>); ("y", typeof<int>)], typeof<int> ]
50+
let contract = createInterface "IContract" members
51+
container.AddMember contract
52+
53+
tempAssembly.AddTypes [container]
54+
this.AddNamespace(container.Namespace, [container])
55+
56+
let testProvidedAssembly test =
57+
if Targets.supportsFSharp40() then
58+
let runtimeAssemblyRefs = Targets.DotNet45FSharp40Refs()
59+
let runtimeAssembly = runtimeAssemblyRefs.[0]
60+
let cfg = Testing.MakeSimulatedTypeProviderConfig (__SOURCE_DIRECTORY__, runtimeAssembly, runtimeAssemblyRefs)
61+
let tp = GenerativeInterfacesProvider(cfg) :> TypeProviderForNamespaces
62+
let providedNamespace = tp.Namespaces.[0]
63+
let providedTypes = providedNamespace.GetTypes()
64+
let providedType = providedTypes.[0]
65+
let providedTypeDefinition = providedType :?> ProvidedTypeDefinition
66+
Assert.Equal("Contracts", providedTypeDefinition.Name)
67+
68+
let assemContents = (tp :> ITypeProvider).GetGeneratedAssemblyContents(providedTypeDefinition.Assembly)
69+
let assembly = Assembly.Load assemContents
70+
assembly.ExportedTypes |> Seq.find (fun ty -> ty.Name = "Contracts") |> test
71+
72+
let runningOnMono = try Type.GetType("Mono.Runtime") <> null with _ -> false
73+
74+
[<Fact>]
75+
let ``Marker interfaces are generated correctly``() =
76+
// // See tracking bug https://github.com/fsprojects/FSharp.TypeProviders.SDK/issues/211
77+
// if not runningOnMono then
78+
testProvidedAssembly <| fun container ->
79+
let marker = container.GetNestedType "IMarker"
80+
Assert.NotNull marker
81+
Assert.True(marker.IsInterface, "Expected IMarker to be an interface")
82+
83+
[<Fact>]
84+
let ``Interfaces with methods are generated correctly``() =
85+
// // See tracking bug https://github.com/fsprojects/FSharp.TypeProviders.SDK/issues/211
86+
// if not runningOnMono then
87+
testProvidedAssembly <| fun container ->
88+
let contract = container.GetNestedType "IContract"
89+
Assert.NotNull contract
90+
Assert.True(contract.IsInterface, "Expected IContract to be an interface")
91+
92+
let contractGetString = contract.GetMethod("GetString")
93+
Assert.NotNull contractGetString
94+
Assert.True(contractGetString.IsAbstract, "Expected GetString method to be abstract")
95+
Assert.True(contractGetString.IsVirtual, "Expected GetString method to be virtual")
96+
97+
let contractSum = contract.GetMethod("Sum")
98+
Assert.NotNull contractSum
99+
Assert.True(contractSum.IsAbstract, "Expected Sum method to be abstract")
100+
Assert.True(contractSum.IsVirtual, "Expected Sum method to be virtual")
101+
102+
#endif

tests/Script1.fsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#r "bin/Release/net461/FSharp.TypeProviders.SDK.Tests.dll"
2+
3+
open System
4+
5+
let comparable = typeof<IComparable>
6+
comparable.BaseType
7+
comparable.Attributes
8+
comparable.GetMethod("CompareTo")
9+
10+
typeof<Collections.IEnumerable>.Attributes
11+
12+
let stream = typeof<IO.Stream>
13+
stream.BaseType
14+
stream.Attributes
15+
stream.GetMethod("Read")
16+
stream.GetMethod("BeginRead")
17+
18+
let textReader = typeof<IO.TextReader>
19+
textReader.BaseType
20+
textReader.Attributes
21+
22+
let streamReader = typeof<IO.StreamReader>
23+
streamReader.BaseType
24+
streamReader.Attributes
25+
26+
typeof<obj>.Attributes
27+
28+
29+
open FSharp.TypeProviders.SDK.Tests
30+
31+
let t = typeof<FSharp.TypeProviders.SDK.Tests.StaticProperty.SampleTypeProvider>
32+
let x = t.Assembly.GetTypes()
33+
x
34+
|> Array.map (fun t -> t.Name)
35+
|> Array.sort
36+
|> Array.filter (fun t -> t.StartsWith("IContract"))

0 commit comments

Comments
 (0)