Skip to content

Commit

Permalink
Refactoring to improve Type Test
Browse files Browse the repository at this point in the history
Type Test wasn't working with compressed compilation nor when
constructors weren't previously defined, thus the refactoring. Several
tests broke though, need to fix' em.
  • Loading branch information
alfonsogarciacaro committed Feb 22, 2015
1 parent 17e28d6 commit f843186
Show file tree
Hide file tree
Showing 11 changed files with 259 additions and 246 deletions.
64 changes: 43 additions & 21 deletions src/main/FunScript/CommonOperators.fs
@@ -1,7 +1,10 @@
module internal FunScript.CommonOperators

open AST
open ReflectedDefinitions

open System.Reflection
open Microsoft.FSharp.Reflection
open Microsoft.FSharp.Quotations

[<Inline; JS>]
Expand Down Expand Up @@ -49,6 +52,10 @@ let private defaultValue =
| Patterns.DefaultValue _ -> [ returnStategy.Return Null ]
| _ -> []

let private localized (name:string) =
let sections = name.Split '-'
JavaScriptNameMapper.sanitizeAux sections.[sections.Length - 1]

// TODO: Refactor!!!
let private coerce =
CompilerComponent.create <| fun (|Split|) compiler returnStrategy ->
Expand Down Expand Up @@ -81,11 +88,11 @@ let private coerce =
let members =
targetMethods
|> Seq.map (fun realMi ->
let replacementMi = Objects.replaceIfAvailable compiler realMi Quote.CallType.MethodCall
Objects.localized replacementMi.Name, replacementMi)
let replacementMi = ReflectedDefinitions.replaceIfAvailable compiler realMi Quote.CallType.MethodCall
localized replacementMi.Name, replacementMi)
|> Seq.groupBy fst
|> Seq.map (fun (name, mis) ->
name, mis |> Seq.tryPick (snd >> Objects.methodCallPattern))
name, mis |> Seq.tryPick (snd >> methodCallPattern))
|> Seq.toArray
let hasAllMembers =
members |> Array.forall (snd >> Option.isSome)
Expand Down Expand Up @@ -123,10 +130,19 @@ let private coerce =
// else []
| _ -> []

open Reflection
let private getPrimaryConstructorName compiler (t: System.Type): string =
let cons = t.GetConstructors(BindingFlags.Public ||| BindingFlags.NonPublic ||| BindingFlags.Instance).[0]
JavaScriptNameMapper.mapMethod cons + (Reflection.getSpecializationString compiler <| Reflection.getGenericMethodArgs cons)
let private getPrimaryConstructorVar compiler (t: System.Type) =
let cons = t.GetConstructors(BindingFlags.Public |||
BindingFlags.NonPublic |||
BindingFlags.Instance).[0]
// Unions must be dealed with in the calling method
if FSharpType.IsTuple t then
t.GenericTypeArguments
|> List.ofArray
|> Reflection.getTupleConstructorVar compiler
elif FSharpType.IsRecord t then
Reflection.getRecordConstructorVar compiler t
else
getObjectConstructorVar compiler cons

// TODO: Add tests
let private typeTest =
Expand All @@ -143,32 +159,38 @@ let private typeTest =
if t = typeof<obj> then
[ returnStrategy.Return <| Boolean true ]

// Type information about function signature will be lost
elif FSharpType.IsFunction t then
returnTypeTest "typeof" (String "function")

// Type information about collection generic will be lost
// Collections not based on JS arrays won't match this
elif typeof<System.Collections.IEnumerable>.IsAssignableFrom t then
returnTypeTest "instanceof" (String "Array")

// Primitives
elif jsNumberTypes.Contains t.FullName || t.IsEnum then
elif Reflection.jsNumberTypes.Contains t.FullName || t.IsEnum then
returnTypeTest "typeof" (String "number")
elif jsStringTypes.Contains t.FullName then
elif Reflection.jsStringTypes.Contains t.FullName then
returnTypeTest "typeof" (String "string")
elif t = typeof<bool> then
returnTypeTest "typeof" (String "boolean")
elif t = typeof<System.DateTime> then
returnTypeTest "instanceof" (String "Date")

// TODO: Union types: Check against all the union case constructors
elif FSharpType.IsUnion t then
[ returnStrategy.Return <| Boolean false ]

// Interfaces
// Interfaces // TODO: Implement
elif t.IsInterface then
// TODO: Implement
// TODO: Check if interface comes from JS library
[]

// Objects
// Objects // TODO: Inheritance (recursively check for type of "base" JS property)
else
// TODO: Check if the constructor has already been defined in JS
// TODO: Check if there's reflected definition of constructor
// TODO: Support inheritance (recursively check for type of "base" JS property)

let cons = getPrimaryConstructorName compiler t
returnTypeTest "instanceof" (EmitExpr (fun _ -> cons))

// TODO: Do sth with Array and ResizeArray?
getPrimaryConstructorVar compiler t
|> JSExpr.Reference
|> returnTypeTest "instanceof"

| _ -> [ returnStrategy.Return <| Boolean false ]
| _ -> [ returnStrategy.Return <| Boolean false ]
Expand Down
4 changes: 2 additions & 2 deletions src/main/FunScript/Core/GenericConstants.fs
Expand Up @@ -3,8 +3,8 @@ module FunScript.Core.GenericConstants

open FunScript

[<JSEmit("return 0;")>]
[<JSEmitInline("0")>]
let Zero<'a> :'a = failwith "never"

[<JSEmit("return 1;")>]
[<JSEmitInline("1")>]
let One<'a> : 'a = failwith "never"
5 changes: 2 additions & 3 deletions src/main/FunScript/Core/LanguagePrimitives.fs
Expand Up @@ -4,17 +4,16 @@ module FunScript.Core.LanguagePrimitives
open FunScript
open System.Collections.Generic

[<JSEmit("return {0};")>]
[<JSEmitInline("{0}")>]
let UnboxGeneric (x:obj) :'a = failwith "never"

[<JSEmit("return {0};")>]
[<JSEmitInline("{0}")>]
let UnboxFast (x:obj) :'a = failwith "never"

type GenericComparer<'a when 'a: comparison>() =
interface IComparer<'a> with
member __.Compare(x, y) = compare x y


type KeyValuePair<'Key, 'Value>(key, value) =
member __.Key: 'Key = key
member __.Value: 'Value = value
Expand Down
2 changes: 2 additions & 0 deletions src/main/FunScript/LambdaApplication.fs
Expand Up @@ -6,6 +6,8 @@ open Microsoft.FSharp.Quotations
let private application =
CompilerComponent.create <| fun (|Split|) compiler returnStrategy ->
function

// TODO: Implement these two optimizations directly in the pipe operator?
| Patterns.Application(Patterns.Lambda(var, (Patterns.Call _ as call)), lambdaArg) ->
compiler.Compile returnStrategy <| call.Substitute(fun v ->
if v.Name = var.Name then Some lambdaArg else None)
Expand Down
101 changes: 0 additions & 101 deletions src/main/FunScript/Objects.fs
Expand Up @@ -40,107 +40,6 @@ let private propertySetter =
| _ -> []


let localized (name:string) =
let sections = name.Split '-'
JavaScriptNameMapper.sanitizeAux sections.[sections.Length - 1]

let private getAllMethods (t:System.Type) =
t.GetMethods(
BindingFlags.Public |||
BindingFlags.NonPublic |||
BindingFlags.FlattenHierarchy |||
BindingFlags.Instance)

let replaceIfAvailable (compiler:InternalCompiler.ICompiler) (mb : MethodBase) callType =
match compiler.ReplacementFor mb callType with
| None -> mb //GetGenericMethod()...
| Some mi -> upcast mi

let deconstructTuple (tupleVar : Var) =
if tupleVar.Type = typeof<unit> then
[tupleVar], Expr.Value(())
else
let elementTypes = FSharpType.GetTupleElements tupleVar.Type
let elementVars =
elementTypes |> Array.mapi (fun i elementType ->
Var(sprintf "%s_%i" tupleVar.Name i, elementType, tupleVar.IsMutable))
|> Array.toList
let elementExprs = elementVars |> List.map Expr.Var
let tupleConstructionExpr =
match elementExprs with
| [] -> Expr.Value(())
| _ -> Expr.NewTuple elementExprs
elementVars, tupleConstructionExpr

let extractVars (mb : MethodBase) (argCounts : CompilationArgumentCountsAttribute) = function
| DerivedPatterns.Lambdas(vars, bodyExpr) ->
let instanceVar, argVars =
if mb.IsStatic || mb.IsConstructor then None, vars
elif vars.Head.Length <> 1 then failwith "Unexpected argument format"
else Some vars.Head.[0], vars.Tail
let actualArgCounts =
let hasCounts = argCounts <> Unchecked.defaultof<_>
if hasCounts then argCounts.Counts |> Seq.toList |> Some
else None
let expectedArgCount =
let baseParamCount = max 1 (mb.GetParameters().Length)
match actualArgCounts with
| None -> baseParamCount
| Some counts -> max baseParamCount (counts |> Seq.sum)
let groupCounts =
match actualArgCounts with
| None -> argVars |> List.map List.length
| Some counts -> counts
let bodyExpr, freeArgVars =
List.zip groupCounts argVars
|> List.fold (fun (totalCount, groups) (groupCount, varGroup) ->
let subTotal = groupCount + totalCount
subTotal, (subTotal, groupCount, varGroup) :: groups) (0, [])
|> snd
|> List.fold (fun (restExpr, freeVars) (subTotal, groupCount, varGroup) ->
if subTotal > expectedArgCount &&
subTotal - groupCount >= expectedArgCount then
if varGroup.Length = 1 then
Expr.Lambda(varGroup.[0], restExpr), freeVars
elif varGroup.Length = groupCount then
failwith "todo"
else failwith "Unexpected argument format"
elif subTotal > expectedArgCount then
failwith "Unexpected argument format"
else
if varGroup.Length = groupCount then
restExpr, varGroup @ freeVars
elif varGroup.Length = 1 then
let tupleVar = varGroup.[0]
let elementVars, tupleConstructionExpr =
deconstructTuple tupleVar
Expr.Let(tupleVar, tupleConstructionExpr, restExpr), elementVars @ freeVars
else
failwith "Unexpected argument format") (bodyExpr, [])
let freeVars =
match instanceVar with
| None -> freeArgVars
| Some ivar -> ivar :: freeArgVars
freeVars, bodyExpr
| expr -> [], expr

let methodCallPattern (mb:MethodBase) =
let argCounts = mb.GetCustomAttribute<CompilationArgumentCountsAttribute>()
match Expr.tryGetReflectedDefinition mb with
| Some fullExpr -> Some(fun () -> extractVars mb argCounts fullExpr)
| None -> None

let (|CallPattern|_|) = methodCallPattern

let getFields (t:Type) =
t.GetProperties(BindingFlags.NonPublic ||| BindingFlags.Public ||| BindingFlags.Instance)
|> Seq.map (fun p -> p, p.GetCustomAttribute<CompilationMappingAttribute>())
|> Seq.filter (fun (p, attr) -> not <| obj.ReferenceEquals(null, attr))
|> Seq.filter (fun (p, attr) -> SourceConstructFlags.Field = attr.SourceConstructFlags)
|> Seq.sortBy (fun (p, attr) -> attr.SequenceNumber)
|> Seq.map (fun (p, attr) -> JavaScriptNameMapper.sanitizeAux p.Name, p.PropertyType)
|> Seq.toList

let components = [
propertyGetter
propertySetter
Expand Down
10 changes: 6 additions & 4 deletions src/main/FunScript/Options.fs
Expand Up @@ -5,9 +5,11 @@ open Microsoft.FSharp.Quotations
[<FunScript.JS>]
module Replacements =

let createNullable x = x
[<JSEmitInline("{0}")>]
let fakeNullable x = x

[<JSEmitInline("({0} !== null)")>]
let hasValue x = not(obj.ReferenceEquals(x, null))
let getValue x = x

let createEmptyNullable =
CompilerComponent.create <| fun split compiler returnStrategy ->
Expand All @@ -24,9 +26,9 @@ let components =
ExpressionReplacer.createUnsafe <@ fun (maybe:_ option) -> maybe.Value @> <@ FunScript.Core.Option.GetValue @>

Replacements.createEmptyNullable
ExpressionReplacer.createUnsafe <@ fun x -> System.Nullable(x) @> <@ Replacements.createNullable @>
ExpressionReplacer.createUnsafe <@ fun x -> System.Nullable(x) @> <@ Replacements.fakeNullable @>
ExpressionReplacer.createUnsafe <@ fun (x:_ System.Nullable) -> x.HasValue @> <@ Replacements.hasValue @>
ExpressionReplacer.createUnsafe <@ fun (x:_ System.Nullable) -> x.Value @> <@ Replacements.getValue @>
ExpressionReplacer.createUnsafe <@ fun (x:_ System.Nullable) -> x.Value @> <@ Replacements.fakeNullable @>
]
ExpressionReplacer.createModuleMapping
"FSharp.Core" "Microsoft.FSharp.Core.OptionModule"
Expand Down
35 changes: 11 additions & 24 deletions src/main/FunScript/RecordTypes.fs
Expand Up @@ -5,14 +5,9 @@ open Quote
open Microsoft.FSharp.Quotations
open System.Reflection

let private getRecordVars recType =
Objects.getFields recType
|> Seq.map fst
|> Seq.map (fun name -> Var(name, typeof<obj>))
|> Seq.toList

let private createConstructor recType compiler =
let vars = getRecordVars recType
let vars = Reflection.getRecordVars recType
let this = Var("__this", typeof<obj>)
vars, Block [
yield CopyThisToVar(this)
Expand All @@ -22,28 +17,20 @@ let private createConstructor recType compiler =
let private creation =
CompilerComponent.create <| fun (|Split|) compiler returnStategy ->
function
| Patterns.NewRecord(recType, exprs) when recType.Name = typeof<Ref<obj>>.Name ->
| Patterns.NewRecord(recType, exprs) ->
let decls, refs =
exprs
|> List.map (fun (Split(valDecl, valRef)) -> valDecl, valRef)
|> List.unzip
let propNames = getRecordVars recType |> List.map (fun v -> v.Name)
let fields = List.zip propNames refs
[ yield! decls |> Seq.concat
yield returnStategy.Return <| Object fields
]
| PatternsExt.NewRecord(recType, exprs) ->
let decls, refs =
exprs
|> List.map (fun (Split(valDecl, valRef)) -> valDecl, valRef)
|> List.unzip
let name = Reflection.getRecordConstructorName compiler recType
let cons =
compiler.DefineGlobal name (fun var ->
[Assign(Reference var, Lambda <| createConstructor recType compiler)])
[ yield! decls |> Seq.concat
yield returnStategy.Return <| New(cons, refs)
]
if recType.Name = typeof<Ref<obj>>.Name then
let propNames = Reflection.getRecordVars recType |> List.map (fun v -> v.Name)
let fields = List.zip propNames refs
[ yield! decls |> Seq.concat
yield returnStategy.Return <| Object fields ]
else
let cons = Reflection.getRecordConstructorVar compiler recType
[ yield! decls |> Seq.concat
yield returnStategy.Return <| New(cons, refs) ]
| _ -> []

let components = [ creation ]

0 comments on commit f843186

Please sign in to comment.