diff --git a/WoofWare.PawPrint.Test/WoofWare.PawPrint.Test.fsproj b/WoofWare.PawPrint.Test/WoofWare.PawPrint.Test.fsproj index a77ab1e..1955467 100644 --- a/WoofWare.PawPrint.Test/WoofWare.PawPrint.Test.fsproj +++ b/WoofWare.PawPrint.Test/WoofWare.PawPrint.Test.fsproj @@ -26,6 +26,7 @@ + diff --git a/WoofWare.PawPrint.Test/sources/ResizeArray.cs b/WoofWare.PawPrint.Test/sources/ResizeArray.cs new file mode 100644 index 0000000..c677256 --- /dev/null +++ b/WoofWare.PawPrint.Test/sources/ResizeArray.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.Linq; + +namespace HelloWorldApp +{ + class Program + { + static int Main(string[] args) + { + var l = new List(); + l.Add(3); + l.Add(100); + var m = l.Select(x => x.ToString()).ToList(); + // 2 + 103 + (1 + 3) = 109 + return m.Count + l.Sum() + m.Select(x => x.Length).Sum(); + } + } +} diff --git a/WoofWare.PawPrint/Assembly.fs b/WoofWare.PawPrint/Assembly.fs index a9032d4..4ea6ed1 100644 --- a/WoofWare.PawPrint/Assembly.fs +++ b/WoofWare.PawPrint/Assembly.fs @@ -428,6 +428,7 @@ module Assembly = (assemblies : ImmutableDictionary) (referencedInAssembly : DumpedAssembly) (target : TypeRef) + (genericArgs : ImmutableArray option) : TypeResolutionResult = match target.ResolutionScope with @@ -457,16 +458,20 @@ module Assembly = match targetType with | [ t ] -> - // If resolved from TypeDef (above), it won't have generic parameters, I hope? let t = - t |> TypeInfo.mapGeneric (fun _ -> failwith "no generic parameters") + t + |> TypeInfo.mapGeneric (fun param -> + match genericArgs with + | None -> failwith "got a generic TypeRef but no generic args in context" + | Some genericArgs -> genericArgs.[param.SequenceNumber] + ) TypeResolutionResult.Resolved (assy, t) | _ :: _ :: _ -> failwith $"Multiple matching type definitions! {nsPath} {target.Name}" | [] -> match assy.ExportedType (Some target.Namespace) target.Name with | None -> failwith $"Failed to find type {nsPath} {target.Name} in {assy.Name.FullName}!" - | Some ty -> resolveTypeFromExport assy assemblies ty + | Some ty -> resolveTypeFromExport assy assemblies ty genericArgs | k -> failwith $"Unexpected: {k}" and internal resolveTypeFromName @@ -474,6 +479,7 @@ module Assembly = (assemblies : ImmutableDictionary) (ns : string option) (name : string) + (genericArgs : ImmutableArray option) : TypeResolutionResult = match ns with @@ -482,26 +488,30 @@ module Assembly = match assy.TypeDef ns name with | Some typeDef -> - // If resolved from TypeDef, it won't have generic parameters, I hope? let typeDef = typeDef - |> TypeInfo.mapGeneric (fun _ -> failwith "no generic parameters") + |> TypeInfo.mapGeneric (fun param -> + match genericArgs with + | None -> failwith $"tried to resolve generic type {ns}.{name} but no generics in scope" + | Some genericArgs -> genericArgs.[param.SequenceNumber] + ) TypeResolutionResult.Resolved (assy, typeDef) | None -> match assy.TypeRef ns name with - | Some typeRef -> resolveTypeRef assemblies assy typeRef + | Some typeRef -> resolveTypeRef assemblies assy typeRef genericArgs | None -> match assy.ExportedType (Some ns) name with - | Some export -> resolveTypeFromExport assy assemblies export + | Some export -> resolveTypeFromExport assy assemblies export genericArgs | None -> failwith $"TODO: type resolution unimplemented for {ns} {name}" and resolveTypeFromExport (fromAssembly : DumpedAssembly) (assemblies : ImmutableDictionary) (ty : WoofWare.PawPrint.ExportedType) + (genericArgs : ImmutableArray option) : TypeResolutionResult = match ty.Data with @@ -511,4 +521,4 @@ module Assembly = match assemblies.TryGetValue assy.Name.FullName with | false, _ -> TypeResolutionResult.FirstLoadAssy assy - | true, toAssy -> resolveTypeFromName toAssy assemblies ty.Namespace ty.Name + | true, toAssy -> resolveTypeFromName toAssy assemblies ty.Namespace ty.Name genericArgs diff --git a/WoofWare.PawPrint/BasicCliType.fs b/WoofWare.PawPrint/BasicCliType.fs index 02046f3..d428e44 100644 --- a/WoofWare.PawPrint/BasicCliType.fs +++ b/WoofWare.PawPrint/BasicCliType.fs @@ -100,7 +100,8 @@ module CliType = let rec zeroOf (assemblies : ImmutableDictionary) (assy : DumpedAssembly) - (generics : TypeDefn ImmutableArray) + (typeGenerics : TypeDefn ImmutableArray option) + (methodGenerics : TypeDefn ImmutableArray option) (ty : TypeDefn) : CliTypeResolutionResult = @@ -135,7 +136,7 @@ module CliType = match signatureTypeKind with | SignatureTypeKind.Unknown -> failwith "todo" | SignatureTypeKind.ValueType -> - match Assembly.resolveTypeRef assemblies assy typeRef with + match Assembly.resolveTypeRef assemblies assy typeRef typeGenerics with | TypeResolutionResult.Resolved (_, ty) -> failwith $"TODO: {ty}" | TypeResolutionResult.FirstLoadAssy assy -> CliTypeResolutionResult.FirstLoad assy | SignatureTypeKind.Class -> CliType.ObjectRef None |> CliTypeResolutionResult.Resolved @@ -148,17 +149,20 @@ module CliType = let fields = typeDef.Fields - |> List.map (fun fi -> zeroOf assemblies assy generics fi.Signature) + |> List.map (fun fi -> zeroOf assemblies assy typeGenerics methodGenerics fi.Signature) CliType.ObjectRef None |> CliTypeResolutionResult.Resolved | SignatureTypeKind.Class -> CliType.ObjectRef None |> CliTypeResolutionResult.Resolved | _ -> raise (ArgumentOutOfRangeException ()) - | TypeDefn.GenericInstantiation (generic, args) -> - // TODO: this is rather concerning and probably incorrect - zeroOf assemblies assy args generic + | TypeDefn.GenericInstantiation (generic, args) -> zeroOf assemblies assy (Some args) methodGenerics generic | TypeDefn.FunctionPointer typeMethodSignature -> failwith "todo" | TypeDefn.GenericTypeParameter index -> // TODO: can generics depend on other generics? presumably, so we pass the array down again - zeroOf assemblies assy generics generics.[index] - | TypeDefn.GenericMethodParameter index -> zeroOf assemblies assy generics generics.[index] + match typeGenerics with + | None -> failwith "asked for a type parameter of generic type, but no generics in scope" + | Some generics -> zeroOf assemblies assy (Some generics) methodGenerics generics.[index] + | TypeDefn.GenericMethodParameter index -> + match methodGenerics with + | None -> failwith "asked for a method parameter of generic type, but no generics in scope" + | Some generics -> zeroOf assemblies assy typeGenerics (Some generics) generics.[index] | TypeDefn.Void -> failwith "should never construct an element of type Void" diff --git a/WoofWare.PawPrint/IlMachineState.fs b/WoofWare.PawPrint/IlMachineState.fs index c303575..2838edd 100644 --- a/WoofWare.PawPrint/IlMachineState.fs +++ b/WoofWare.PawPrint/IlMachineState.fs @@ -205,11 +205,12 @@ module IlMachineState = (loggerFactory : ILoggerFactory) (ns : string option) (name : string) + (genericArgs : ImmutableArray option) (assy : DumpedAssembly) (state : IlMachineState) : IlMachineState * DumpedAssembly * WoofWare.PawPrint.TypeInfo = - match Assembly.resolveTypeFromName assy state._LoadedAssemblies ns name with + match Assembly.resolveTypeFromName assy state._LoadedAssemblies ns name genericArgs with | TypeResolutionResult.Resolved (assy, typeDef) -> state, assy, typeDef | TypeResolutionResult.FirstLoadAssy loadFirst -> let state, _, _ = @@ -219,16 +220,17 @@ module IlMachineState = (fst loadFirst.Handle) state - resolveTypeFromName loggerFactory ns name assy state + resolveTypeFromName loggerFactory ns name genericArgs assy state and resolveTypeFromExport (loggerFactory : ILoggerFactory) (fromAssembly : DumpedAssembly) (ty : WoofWare.PawPrint.ExportedType) + (genericArgs : ImmutableArray option) (state : IlMachineState) : IlMachineState * DumpedAssembly * WoofWare.PawPrint.TypeInfo = - match Assembly.resolveTypeFromExport fromAssembly state._LoadedAssemblies ty with + match Assembly.resolveTypeFromExport fromAssembly state._LoadedAssemblies ty genericArgs with | TypeResolutionResult.Resolved (assy, typeDef) -> state, assy, typeDef | TypeResolutionResult.FirstLoadAssy loadFirst -> let state, targetAssy, _ = @@ -238,59 +240,64 @@ module IlMachineState = (fst loadFirst.Handle) state - resolveTypeFromName loggerFactory ty.Namespace ty.Name targetAssy state + resolveTypeFromName loggerFactory ty.Namespace ty.Name genericArgs targetAssy state and resolveTypeFromRef (loggerFactory : ILoggerFactory) (referencedInAssembly : DumpedAssembly) (target : TypeRef) + (genericArgs : ImmutableArray option) (state : IlMachineState) : IlMachineState * DumpedAssembly * WoofWare.PawPrint.TypeInfo = - match Assembly.resolveTypeRef state._LoadedAssemblies referencedInAssembly target with + match Assembly.resolveTypeRef state._LoadedAssemblies referencedInAssembly target genericArgs with | TypeResolutionResult.Resolved (assy, typeDef) -> state, assy, typeDef | TypeResolutionResult.FirstLoadAssy loadFirst -> let state, _, _ = loadAssembly loggerFactory - (state._LoadedAssemblies.[snd(loadFirst.Handle).FullName]) + state._LoadedAssemblies.[snd(loadFirst.Handle).FullName] (fst loadFirst.Handle) state - resolveTypeFromRef loggerFactory referencedInAssembly target state + resolveTypeFromRef loggerFactory referencedInAssembly target genericArgs state and resolveType (loggerFactory : ILoggerFactory) (ty : TypeReferenceHandle) + (genericArgs : ImmutableArray option) (assy : DumpedAssembly) (state : IlMachineState) : IlMachineState * DumpedAssembly * WoofWare.PawPrint.TypeInfo = let target = assy.TypeRefs.[ty] - resolveTypeFromRef loggerFactory assy target state + resolveTypeFromRef loggerFactory assy target genericArgs state let rec resolveTypeFromDefn (loggerFactory : ILoggerFactory) (ty : TypeDefn) + (genericArgs : ImmutableArray option) (assy : DumpedAssembly) (state : IlMachineState) - : IlMachineState * - DumpedAssembly * - WoofWare.PawPrint.TypeInfo * - TypeDefn ImmutableArray option + : IlMachineState * DumpedAssembly * WoofWare.PawPrint.TypeInfo = match ty with | TypeDefn.GenericInstantiation (generic, args) -> - let state, _, generic, subArgs = - resolveTypeFromDefn loggerFactory generic assy state + resolveTypeFromDefn loggerFactory generic (Some args) assy state + | TypeDefn.FromDefinition (defn, _typeKind) -> + let defn = + assy.TypeDefs.[defn.Get] + |> TypeInfo.mapGeneric (fun param -> + match genericArgs with + | None -> failwith "somehow got a generic TypeDefn.FromDefinition without any generic args" + | Some genericArgs -> genericArgs.[param.SequenceNumber] + ) - match subArgs with - | Some _ -> failwith "unexpectedly had multiple generic instantiations for the same type" - | None -> - - state, assy, generic, Some args - | TypeDefn.FromDefinition (defn, _typeKind) -> state, assy, assy.TypeDefs.[defn.Get], None + state, assy, defn + | TypeDefn.FromReference (ref, _typeKind) -> + let state, assy, ty = resolveTypeFromRef loggerFactory assy ref genericArgs state + state, assy, ty | s -> failwith $"TODO: resolveTypeFromDefn unimplemented for {s}" let resolveTypeFromSpec @@ -300,35 +307,25 @@ module IlMachineState = (state : IlMachineState) : IlMachineState * DumpedAssembly * WoofWare.PawPrint.TypeInfo = - let state, assy, generic, args = - resolveTypeFromDefn loggerFactory assy.TypeSpecs.[ty].Signature assy state - - match args with - | None -> - let generic = - generic - |> TypeInfo.mapGeneric (fun _ -> failwith "no generic parameters") - - state, assy, generic - | Some args -> - let generic = TypeInfo.withGenerics args generic - state, assy, generic + // Any necessary generics will be baked into the TypeDefn e.g. as a `GenericInstantiation`. + resolveTypeFromDefn loggerFactory assy.TypeSpecs.[ty].Signature None assy state let rec cliTypeZeroOf (loggerFactory : ILoggerFactory) (assy : DumpedAssembly) (ty : TypeDefn) - (generics : TypeDefn ImmutableArray) + (typeGenerics : TypeDefn ImmutableArray option) + (methodGenerics : TypeDefn ImmutableArray option) (state : IlMachineState) : IlMachineState * CliType = - match CliType.zeroOf state._LoadedAssemblies assy generics ty with + match CliType.zeroOf state._LoadedAssemblies assy typeGenerics methodGenerics ty with | CliTypeResolutionResult.Resolved result -> state, result | CliTypeResolutionResult.FirstLoad ref -> let state, _, _ = loadAssembly loggerFactory state._LoadedAssemblies.[snd(ref.Handle).FullName] (fst ref.Handle) state - cliTypeZeroOf loggerFactory assy ty generics state + cliTypeZeroOf loggerFactory assy ty typeGenerics methodGenerics state let callMethod (loggerFactory : ILoggerFactory) @@ -342,16 +339,16 @@ module IlMachineState = (state : IlMachineState) : IlMachineState = + let typeGenerics = + match methodToCall.DeclaringType.Generics with + | [] -> None + | x -> Some (ImmutableArray.CreateRange x) + let state, argZeroObjects = ((state, []), methodToCall.Signature.ParameterTypes) ||> List.fold (fun (state, zeros) ty -> let state, zero = - cliTypeZeroOf - loggerFactory - (state.ActiveAssembly thread) - ty - (methodGenerics |> Option.defaultValue ImmutableArray.Empty) - state + cliTypeZeroOf loggerFactory (state.ActiveAssembly thread) ty typeGenerics methodGenerics state state, zero :: zeros ) @@ -553,7 +550,13 @@ module IlMachineState = | NothingToDo state -> Ok state | TypeRef typeReferenceHandle -> let state, assy, targetType = - resolveType loggerFactory typeReferenceHandle (state.ActiveAssembly currentThread) state + // TypeRef won't have any generics; it would be a TypeSpec if it did + resolveType + loggerFactory + typeReferenceHandle + None + (state.ActiveAssembly currentThread) + state logger.LogDebug ( "Resolved base type of {TypeDefNamespace}.{TypeDefName} to a typeref in assembly {ResolvedAssemblyName}, {BaseTypeNamespace}.{BaseTypeName}", @@ -564,7 +567,6 @@ module IlMachineState = targetType.Name ) - // TypeRef won't have any generics; it would be a TypeSpec if it did let ty = ConcreteType.make assy.Name targetType.TypeDefHandle [] match loadClass loggerFactory ty currentThread state with @@ -900,7 +902,7 @@ module IlMachineState = let state, assy, targetType = match mem.Parent with - | MetadataToken.TypeReference parent -> resolveType loggerFactory parent assy state + | MetadataToken.TypeReference parent -> resolveType loggerFactory parent None assy state | MetadataToken.TypeSpecification parent -> resolveTypeFromSpec loggerFactory parent assy state | parent -> failwith $"Unexpected: {parent}" @@ -992,12 +994,7 @@ module IlMachineState = | retType -> // TODO: generics let state, zero = - cliTypeZeroOf - loggerFactory - (state.ActiveAssembly currentThread) - retType - ImmutableArray.Empty - state + cliTypeZeroOf loggerFactory (state.ActiveAssembly currentThread) retType None None state let toPush = EvalStackValue.toCliTypeCoerced zero retVal diff --git a/WoofWare.PawPrint/MethodState.fs b/WoofWare.PawPrint/MethodState.fs index 63081d4..f9bc9e4 100644 --- a/WoofWare.PawPrint/MethodState.fs +++ b/WoofWare.PawPrint/MethodState.fs @@ -164,12 +164,16 @@ and MethodState = let requiredAssemblies = ResizeArray () + let typeGenerics = + match method.DeclaringType.Generics with + | [] -> None + | x -> ImmutableArray.CreateRange x |> Some + let localVars = - // TODO: generics? let result = ImmutableArray.CreateBuilder () for var in localVariableSig do - match CliType.zeroOf loadedAssemblies containingAssembly ImmutableArray.Empty var with + match CliType.zeroOf loadedAssemblies containingAssembly typeGenerics methodGenerics var with | CliTypeResolutionResult.Resolved t -> result.Add t | CliTypeResolutionResult.FirstLoad (assy : WoofWare.PawPrint.AssemblyReference) -> requiredAssemblies.Add assy diff --git a/WoofWare.PawPrint/UnaryMetadataIlOp.fs b/WoofWare.PawPrint/UnaryMetadataIlOp.fs index 7ab26fa..997001e 100644 --- a/WoofWare.PawPrint/UnaryMetadataIlOp.fs +++ b/WoofWare.PawPrint/UnaryMetadataIlOp.fs @@ -165,17 +165,17 @@ module internal UnaryMetadataIlOp = ctorType.Name ) + let typeGenerics = + match ctor.DeclaringType.Generics with + | [] -> None + | l -> Some (ImmutableArray.CreateRange l) + let state, fieldZeros = ((state, []), ctorType.Fields) ||> List.fold (fun (state, zeros) field -> // TODO: generics let state, zero = - IlMachineState.cliTypeZeroOf - loggerFactory - ctorAssembly - field.Signature - ImmutableArray.Empty - state + IlMachineState.cliTypeZeroOf loggerFactory ctorAssembly field.Signature typeGenerics None state state, (field.Name, zero) :: zeros ) @@ -307,12 +307,18 @@ module internal UnaryMetadataIlOp = let valueToStore, state = IlMachineState.popEvalStack thread state + let typeGenerics = + match declaringType.Generics with + | [] -> None + | l -> Some (ImmutableArray.CreateRange l) + let state, zero = IlMachineState.cliTypeZeroOf loggerFactory (state.ActiveAssembly thread) field.Signature - (ImmutableArray.CreateRange declaringType.Generics) + typeGenerics + None // field can't have its own generics state let valueToStore = EvalStackValue.toCliTypeCoerced zero valueToStore @@ -403,12 +409,18 @@ module internal UnaryMetadataIlOp = let popped, state = IlMachineState.popEvalStack thread state + let typeGenerics = + match field.DeclaringType.Generics with + | [] -> None + | l -> Some (ImmutableArray.CreateRange l) + let state, zero = IlMachineState.cliTypeZeroOf loggerFactory activeAssy field.Signature - (ImmutableArray.CreateRange field.DeclaringType.Generics) + typeGenerics + None // field can't have its own generics state let toStore = EvalStackValue.toCliTypeCoerced zero popped @@ -453,6 +465,11 @@ module internal UnaryMetadataIlOp = let currentObj, state = IlMachineState.popEvalStack thread state + let typeGenerics = + match field.DeclaringType.Generics with + | [] -> None + | l -> Some (ImmutableArray.CreateRange l) + if field.Attributes.HasFlag FieldAttributes.Static then let state, staticField = match state.GetStatic field.DeclaringType field.Name with @@ -463,7 +480,8 @@ module internal UnaryMetadataIlOp = loggerFactory (state.LoadedAssembly(field.DeclaringType.Assembly).Value) field.Signature - (ImmutableArray.CreateRange field.DeclaringType.Generics) + typeGenerics + None // field can't have its own generics state let state = state.SetStatic field.DeclaringType field.Name zero @@ -533,6 +551,11 @@ module internal UnaryMetadataIlOp = | FirstLoadThis state -> state, WhatWeDid.SuspendedForClassInit | NothingToDo state -> + let typeGenerics = + match field.DeclaringType.Generics with + | [] -> None + | l -> Some (ImmutableArray.CreateRange l) + let fieldValue, state = match state.GetStatic field.DeclaringType field.Name with | None -> @@ -541,7 +564,8 @@ module internal UnaryMetadataIlOp = loggerFactory activeAssy field.Signature - (field.DeclaringType.Generics |> ImmutableArray.CreateRange) + typeGenerics + None // field can't have its own generics state newVal, state.SetStatic field.DeclaringType field.Name newVal @@ -600,13 +624,19 @@ module internal UnaryMetadataIlOp = |> IlMachineState.advanceProgramCounter thread |> Tuple.withRight WhatWeDid.Executed | None -> + let typeGenerics = + match field.DeclaringType.Generics with + | [] -> None + | l -> Some (ImmutableArray.CreateRange l) + // Field is not yet initialised let state, zero = IlMachineState.cliTypeZeroOf loggerFactory activeAssy field.Signature - (field.DeclaringType.Generics |> ImmutableArray.CreateRange) + typeGenerics + None // field can't have its own generics state state.SetStatic field.DeclaringType field.Name zero