├── .gitignore ├── LICENSE ├── Main.hs ├── README.md ├── Setup.hs ├── docs ├── dicussion.md ├── infernu.png ├── infernu.svg ├── inst-graph.png ├── make.sh ├── memoize.js ├── semantics.hs └── type-system.md ├── doctest.sh ├── infernu.cabal ├── lambdabot-plugin ├── LICENSE ├── Lambdabot │ └── Plugin │ │ └── Infernu.hs ├── Setup.hs └── lambdabot-plugin-inferno.cabal ├── others ├── ConstId.cs ├── GetPut.java ├── flow │ ├── .flowconfig │ ├── debounce.js │ ├── guards.js │ ├── id.js │ ├── memoize │ │ └── memoize.js │ ├── problem-id.js │ ├── problem.js │ ├── row.js │ └── simple-bug.js ├── lint │ └── string_constr.js ├── notabug.caml └── typescript │ ├── bugs.ts │ └── memoize.ts ├── src └── Infernu │ ├── Builtins.hs │ ├── Builtins │ ├── Array.hs │ ├── DOM.hs │ ├── Date.hs │ ├── Math.hs │ ├── Names.hs │ ├── Number.hs │ ├── Object.hs │ ├── Operators.hs │ ├── Regex.hs │ ├── String.hs │ ├── StringMap.hs │ ├── TypeClasses.hs │ └── Util.hs │ ├── Decycle.hs │ ├── Expr.hs │ ├── Fix.hs │ ├── Infer.hs │ ├── InferState.hs │ ├── Lib.hs │ ├── Log.hs │ ├── Options.hs │ ├── Parse.hs │ ├── Prelude.hs │ ├── Pretty.hs │ ├── Source.hs │ ├── Types.hs │ ├── Unify.hs │ └── Util.hs ├── stack.yaml ├── test ├── .gitignore ├── Demo.hs ├── Test.hs ├── fail.txt ├── ghcjs │ └── all.js ├── invalid │ ├── .flowconfig │ ├── array-assign-in-func.js │ ├── array-assign.js │ ├── array-idx.js │ ├── array-param.js │ ├── array.js │ ├── blo.js │ ├── blo2.js │ ├── builtins │ │ ├── Math.js │ │ ├── binary-not.js │ │ ├── equality.js │ │ ├── isFinite.js │ │ ├── logical-not.js │ │ ├── minus.js │ │ ├── minusminus.js │ │ ├── or.js │ │ ├── parse.js │ │ ├── parseInt.js │ │ ├── plus-func.js │ │ ├── plus.js │ │ ├── plusplus.js │ │ ├── regex-exec.js │ │ ├── string-error.js │ │ ├── string-replace.js │ │ ├── string_constr.js │ │ ├── typeof.js │ │ └── uri.js │ ├── call2.js │ ├── cond.js │ ├── debounce-simplified.js │ ├── debounce.js │ ├── dowhile.js │ ├── fix-usage.js │ ├── for-1.js │ ├── for-2.js │ ├── for-in.js │ ├── for.js │ ├── func-as-row.js │ ├── func-no-currying.js │ ├── generalize.js │ ├── getput.js │ ├── if.js │ ├── infer-from-call.js │ ├── infinite.js │ ├── infinite2.js │ ├── inheritance.js │ ├── method-type.js │ ├── mutability │ │ ├── bug.js │ │ ├── bug2.js │ │ ├── bug3.js │ │ ├── bug5.js │ │ ├── bug6.js │ │ ├── bug7.js │ │ └── mutable-id.js │ ├── naked_array.js │ ├── new-override.js │ ├── new-return.js │ ├── new.js │ ├── not-a-method.js │ ├── num.js │ ├── object-chaining.js │ ├── occurs-erisco.js │ ├── occurs.js │ ├── occurs2.js │ ├── open-row-erisco.js │ ├── open-row.js │ ├── polyfunc.js │ ├── polyfunc2.js │ ├── polyfunc3.js │ ├── promise-method-workaround-simplified.js │ ├── promise-method-workaround.js │ ├── promise-simple-api.js │ ├── prop-polymorphic-assign.js │ ├── prop-polymorphic-assign2-b.js │ ├── prop-polymorphic-assign2-this.js │ ├── prop-polymorphic-assign2.js │ ├── prop-polymorphic-assign3-b.js │ ├── prop-polymorphic-assign3.js │ ├── prop-polymorphic-closure.js │ ├── prop-polymorphic-closure2.js │ ├── prop-wrong.js │ ├── prop_assign.js │ ├── rank-2-method-b.js │ ├── rank-2-method-simple.js │ ├── rank-2-method.js │ ├── recursive-type.js │ ├── return-multi.js │ ├── row-func.js │ ├── row-return.js │ ├── row1.js │ ├── stringmap-preds.js │ ├── switch-1.js │ ├── switch-2.js │ ├── this-func.js │ ├── this.js │ ├── undefined.js │ ├── unfortunately │ │ ├── inheritance.js │ │ ├── mauke.js │ │ ├── new-recursive-prototype.js │ │ └── polymorphic-row-fields │ │ │ ├── prop-polymorphic-assign.js │ │ │ ├── prop-polymorphic-assign2-this.js │ │ │ └── prop-polymorphic-assign3.js │ ├── value_restriction_apply.js │ ├── var-recursive-nonfunc.js │ ├── vartest.js │ ├── while.js │ └── y.js ├── makebig.hs ├── num.js ├── purescript │ ├── Assert.js │ ├── Control.Monad.Eff.js │ ├── Control.Monad.ST.js │ ├── Data.Function.js │ ├── Debug.Trace.js │ ├── Prelude.Unsafe.js │ └── Prelude.js ├── regexlike-exec-2.js ├── runall.sh ├── runtest.sh ├── test.sh └── valid │ ├── .flowconfig │ ├── arguably │ └── if-undefined.js │ ├── array-2d-get.js │ ├── array-idx.js │ ├── array-length.js │ ├── array-with-magic-props.js │ ├── array.js │ ├── blo.js │ ├── break.js │ ├── builtins │ ├── Boolean.js │ ├── Date.js │ ├── JSON.js │ ├── Math.js │ ├── Number.js │ ├── String.js │ ├── assign-ops-index.js │ ├── assign-ops-property.js │ ├── assign-ops.js │ ├── binary-not.js │ ├── isFinite.js │ ├── isNaN.js │ ├── logical-not.js │ ├── map.js │ ├── map2.js │ ├── minus.js │ ├── minusminus.js │ ├── or.js │ ├── parse.js │ ├── plus-func.js │ ├── plus.js │ ├── plusplus.js │ ├── regex-exec-2.js │ ├── regex-exec.js │ ├── regex-test.js │ ├── regex.js │ ├── string-ops.js │ ├── string-replace.js │ ├── typeof.js │ └── uri.js │ ├── call-this.js │ ├── call.js │ ├── complex │ ├── kinda-underscore.js │ └── sortedUniq.js │ ├── cond.js │ ├── continue.js │ ├── debounce-indexable.js │ ├── debounce.js │ ├── double-func.js │ ├── fix.js │ ├── for-in.js │ ├── for.js │ ├── func-stmt-expr-erisco.js │ ├── func.js │ ├── get-length.js │ ├── if.js │ ├── index-and-plus.js │ ├── indexable.js │ ├── list.js │ ├── map.js │ ├── memoize.js │ ├── method-as-func.js │ ├── method.js │ ├── mutability │ ├── array-of-polyfuncs-eta-expanded.js │ ├── array-of-polyfuncs.js │ ├── indirect-mutable.js │ ├── indirect-mutable2.js │ ├── mutable-assign-app.js │ ├── mutable-indexassign-app.js │ ├── mutable-propassign-app.js │ ├── ok7.js │ ├── ok7b.js │ ├── ok7c.js │ └── var.js │ ├── new-func.js │ ├── new-recursive.js │ ├── new-return.js │ ├── new-simple.js │ ├── new.js │ ├── nothis-as-obj-param.js │ ├── nothis-as-param.js │ ├── nothis.js │ ├── nothis2.js │ ├── object-chaining-long.js │ ├── object-chaining-triple.js │ ├── object-chaining.js │ ├── objlit-func.js │ ├── objlit.js │ ├── objlit2.js │ ├── once.js │ ├── passing-obj-with-poly-method.js │ ├── point.js │ ├── poll.js │ ├── poly-array-assign.js │ ├── poly-array-assign2.js │ ├── poly-method-rank-3-nothis.js │ ├── poly-method-rank-3.js │ ├── poly-method-recursive-this.js │ ├── poly-method-simple.js │ ├── poly-row-field-using-let.js │ ├── poly-row-field.js │ ├── poly-row-type-func.js │ ├── poly-this-new.js │ ├── poly-this.js │ ├── poly-this2.js │ ├── polyfunc.js │ ├── polyfunc2.js │ ├── promise-checking-subsumption.js │ ├── promise-method-workaround.js │ ├── promise-simple-api.js │ ├── promise-simplified.js │ ├── promise-simplified2.js │ ├── promise.js │ ├── prop-nested-get.js │ ├── prop_access.js │ ├── prop_assign.js │ ├── record.js │ ├── recurse.js │ ├── recurse2.js │ ├── recursive-type.js │ ├── recursive_value.js │ ├── regex.js │ ├── return-multi.js │ ├── return.js │ ├── row-return.js │ ├── switch.js │ ├── test.js │ ├── this.js │ ├── trivial.js │ ├── try.js │ ├── var-empty.js │ └── while.js └── webidl ├── Document.webidl ├── Event.webidl ├── EventTarget.webidl └── Node.webidl /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | cabal-dev 3 | *.o 4 | *.hi 5 | *.chi 6 | *.chs.h 7 | .virtualenv 8 | .hsenv 9 | .cabal-sandbox/ 10 | cabal.sandbox.config 11 | *~ 12 | \#* 13 | .#* 14 | 15 | *.dyn* 16 | TAGS 17 | *.orig 18 | 19 | .stack-work/ 20 | -------------------------------------------------------------------------------- /Main.hs: -------------------------------------------------------------------------------- 1 | module Main (main) where 2 | 3 | import Control.Monad (forM, when) 4 | import qualified Options.Applicative as OptParse 5 | import System.Exit (exitFailure) 6 | import qualified Text.Parsec.Pos as Pos 7 | 8 | import Text.PrettyPrint.ANSI.Leijen (Pretty (..)) 9 | import Infernu.Options (Options (..), opts) 10 | import Infernu.Types (QualType) 11 | import Infernu.Source (Source(..)) 12 | import Infernu.Util 13 | import Infernu.Prelude 14 | 15 | process :: [(Source, QualType)] -> [(Pos.SourceName, [String])] -> String 16 | process ts sourceCodes = concatMap (\(f, ds) -> annotatedSource (filteredTypes f ts) ds) sourceCodes 17 | where filteredTypes f' = filter (\(Source (_, p), _) -> (Pos.sourceName p == f')) 18 | 19 | main :: IO () 20 | main = do 21 | options <- OptParse.execParser opts 22 | let files = optFileNames options 23 | res <- checkFiles options files 24 | case res of 25 | Right ts -> when (not $ optQuiet options) $ do sourceCodes <- forM files $ \f -> do d <- readFile f 26 | return (f, lines d) 27 | putStrLn $ process ts sourceCodes 28 | Left e -> putStrLn (show $ pretty e) >> exitFailure 29 | 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![Infernu](docs/infernu.png) 2 | 3 | Static type inference for JavaScript. 4 | 5 | *Inside every big ugly language there is a small beautiful language trying to come out.* 6 | 7 | ## MOTHBALLED 8 | 9 | I have little time for this project, and don't use JS at work anymore so I'm less motivated to continue. But I still believe it's the right way to go with type safety in JS. 10 | 11 | ## Features 12 | 13 | * It's just JavaScript: runs as-is in your browser. No transpilation or compilation required for running. 14 | * Full type inference: no type annotations necessary. 15 | * Safety: a strict type system with no workarounds sets a high bar for code correctness. *So you can sleep at night!* 16 | * **Work in progress**: it can set your computer on fire! 17 | 18 | ### Type System 19 | 20 | * Parametric polymorphism (aka "generics"), based on Hindley-Milner type inference. 21 | * Row-type polymorphism, otherwise known as "static duck typing". 22 | * Simple type classes (which allow for example correct support of JS `+` and `[]` operators). 23 | * Recursive types for true representation of object-oriented methods. 24 | * Correct handling of JS's `this` dynamic scoping rules. 25 | 26 | For more information see [Infernu's Type System](docs/type-system.md). 27 | 28 | Also see the [intro blog post](https://noamlewis.wordpress.com/2015/01/20/introducing-sjs-a-type-inferer-and-checker-for-javascript/) for a short discussion comparing infernu to **other type checkers**. 29 | 30 | 31 | ## Installation 32 | 33 | ### Quick and Dirty 34 | 35 | First, install [haskell stack](https://docs.haskellstack.org/en/stable/install_and_upgrade/). 36 | 37 | Then use stack to install infernu: 38 | 39 | git clone git@github.com:sinelaw/infernu.git 40 | cd infernu/ 41 | stack setup 42 | stack install 43 | 44 | Usage: see `infernu --help` 45 | 46 | Quick example usage: 47 | 48 | echo 'function getLength(x) { return x.length; }' > getLength.js 49 | 50 | infernu getLength.js 51 | 52 | Output: 53 | 54 | ```javascript 55 | // getLength : a.({length: b, ..c} -> b) 56 | function getLength(x) { return x.length; } 57 | ``` 58 | 59 | 60 | ### A bit more detailed instructions 61 | 62 | 1. Install [Haskell stack](https://docs.haskellstack.org/en/stable/install_and_upgrade/). 63 | 2. Clone this repository. 64 | 65 | Then run: 66 | 67 | cd infernu/ 68 | stack setup 69 | stack install 70 | 71 | The `infernu` executable will be installed to your `~/.local/bin`. You may want to add it to your `PATH`. 72 | 73 | ### Running tests 74 | 75 | cd infernu 76 | stack setup 77 | stack build 78 | cd test 79 | ./test.sh 80 | 81 | Currently there are still a few failing tests due to unclosed issues in the type system. 82 | 83 | The file `test/fail.txt` records the test failures and is kept in git. This makes it easier to track progress on outstanding bugs. 84 | 85 | ## Examples 86 | 87 | ### Basic 88 | 89 | JavaScript: 90 | 91 | var num = 2; 92 | var arrNums = [num, num]; 93 | 94 | Infernu infers: 95 | 96 | // num : Number 97 | // arrNums : [Number] 98 | 99 | That is, an array of numbers. 100 | 101 | Objects: 102 | 103 | var obj = { something: 'hi', value: num }; 104 | 105 | Inferred type: 106 | 107 | // obj : {something: String, 108 | value: Number} 109 | 110 | That is, an object with two properties: 'something', of type string, and 'value' of type number. 111 | 112 | ### Functions and `this` 113 | 114 | In JS, `this` is one truly awful part. `this` is a dynamically scoped variable that takes on values depending on how the current function was invoked. Infernu knows about this (pun intended) and infers types for functions indicating what `this` must be. 115 | 116 | For example: 117 | 118 | function useThisData() { 119 | return this.data + 3; 120 | } 121 | 122 | Infernu infers: 123 | 124 | // useThisData : {data: Number, ..a}.(() -> Number) 125 | 126 | In words: a function which expects `this` to be an object with at least one property, "data" of type `Number`. It takes no arguments (hence the empty `()`). It returns a `Number`. 127 | 128 | If we call a function that needs `this` incorrectly, Infernu will be angry: 129 | 130 | Error: Could not unify: 131 | {data: Number, ..a} 132 | With: 133 | Undefined 134 | 135 | Because we called `useThisData` without a preceding object property access (e.g. `obj.useThisData`), it will get `undefined` for `this`. Infernu is telling us that our expected type for `this` is not unifiable with the type `undefined`. 136 | 137 | ### Polymorphism 138 | 139 | Given the following function: 140 | 141 | function makeData(x) { 142 | return {data: x}; 143 | } 144 | 145 | Infernu infers the following type: 146 | 147 | a.(b -> {data: b}) 148 | 149 | In words: A function that takes anything for its `this`, and an argument of any type `b`. It returns an object containing a single field, `data` of the same type `b` as the argument. 150 | 151 | ### Row-type polymorphism (static duck typing) 152 | 153 | Given the following function: 154 | 155 | function getData(obj) { 156 | return obj.data; 157 | } 158 | 159 | Infernu infers: 160 | 161 | h.({data: i, ..j} -> i) 162 | 163 | In words: a function taking any type `h` for `this`, and a parameter that contains **at least one property**, named "data" that has some type `i` (could be any type). The function returns the same type `i` as the data property. 164 | 165 | 166 | ### Type Classes 167 | 168 | See [here](docs/type-system.md#type-classes) for more about Infernu's type classes. 169 | 170 | The basic example is for the `+` operator: 171 | 172 | function add(x,y) { return x + y; } 173 | 174 | The type for `add` is inferred to be: 175 | 176 | // add : Plus b => a.((b, b) -> b) 177 | 178 | Meaning: given any type `a` that is an instance of the `Plus` type class, the function takes two `a`s and returns an `a`. 179 | 180 | The two instances of `Plus` currently defined are the types `Number` and `String`. 181 | 182 | 183 | 184 | ------------ 185 | 186 | ## Author 187 | 188 | Noam Lewis - [@noam_lewis](https://twitter.com/noam_lewis) 189 | 190 | ## Road map 191 | 192 | The following needs to be done to make infernu reasonably usable: 193 | 194 | - Finish support for basic builtin JS apis. 195 | - Add support for browser / DOM / web apis. 196 | - Add ability to declare types for, or wrap external libraries. 197 | - Add support for some kind of module system. 198 | - Better error messages. 199 | 200 | ## Pending discussion 201 | 202 | Things that could be done, but may not be so important: 203 | 204 | - [ ] Allow empty var decls (use first assignment as starting point for types) - how to prevent uninitialized variable issues? General approach should be to add a translation pass that moves var decls down to the first assignment, but care must be taken to avoid escaping usage-before-assignment to an otherwise shadowed name. 205 | - [ ] When concluding that two recursive types are equivalent, use that information to simplify the resulting types (perhaps using the simpler of the two everywhere) - nice to have, because currently recursive types are displayed opaquely anyway. 206 | 207 | More important but also more complicated or impossible to implement: 208 | 209 | - [ ] Find a reasonable solution for optional parameters - perhaps using an implicit "Maybe"-like type or implicit type unions, and require guards? 210 | - [ ] An (implicitly inferred) maybe type, for better safety of things like array access at index. Unfortunately because the maybe wrap/unwrap will have to implicit, this isn't easy to solve. See branch `maybe`. 211 | - [ ] Sum types with guards as pattern matchers. Required because some functions, like array index access, can return 'undefined' (e.g. if index is out of range) - breaks parametricity and complicates the inference greatly. 212 | - [ ] Allow defining constructor-object properties using the notation `obj.prototype.something = ...` - requires non-local context to deteremine the type of a constructor function. 213 | - [ ] support `arguments` (a tuple?) and function `bind` 214 | - [ ] Should we treat functions as objects with properties? the only properties they have are: length (very weird! we might as well leave it out), and call/bind/apply (which need special handling) 215 | 216 | ### Future 217 | 218 | - [ ] Type annotations 219 | - [ ] Add support for CommonJS modules 220 | 221 | p.s. *(Formerly known as Inferno / Safe JS / SJS)* 222 | 223 | 225 | 227 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /docs/dicussion.md: -------------------------------------------------------------------------------- 1 | **Note**: Most of this is outdated and/or unmaintained. 2 | 3 | # Discussions 4 | 5 | ## AST translation to core language: yes or no? 6 | 7 | There are two general approaches to implement type inference on a new syntax: 8 | 9 | 1. Implement type inference directly on the given AST 10 | 2. Map the AST to a simpler core language, and use well-understood algorithms to infer types on the core language. Finally, map the inferred types back to the original AST. 11 | 12 | Currently the (incomplete, buggy) code takes the first approach - it infers types directly on (a subset of) the JS syntax. However to make the code simpler and more robust, it may make more sense to take the translation approach. 13 | 14 | One advantage of the direct approach (no translation) is that it allows us to directly deal with language constructs such as while, for, if, return, and the quirks that come with them. 15 | 16 | ### Mapping to a core language 17 | 18 | One possible target core language is Damas-Hindley-Milner (simply typed lambda calculus with parameteric polymorphism and let-bindings) plus row type polymorphism, reference cells, and something similar to SML-style value restriction. 19 | 20 | The mapping will have to deal with statement sequences (function and if/while/for blocks), various statement types, etc. An example mapping is: 21 | 22 | - Statment sequences such as `st1; st2;` to `(\_ -> st2) st1` 23 | - `function f(a,b) { statements... }` to `\a -> \b -> ...` 24 | - `var x = 2; x = x + 1` to `let x = ref 2 in x := !x + 1` 25 | - and so on. 26 | 27 | 28 | ## Polymorphism and the value restriction 29 | 30 | Javascript variables are storage cells. If we compare JS to Standard ML, JS variables seem to be equivalent to `ref` values. SML has the concept of value restriction: but it applies to let-bound names, and `ref` variables in SML are *always* monotypes. If we followed suit when inferring JS variable types, all our variables would be monotypes and we would lose all polymorphism. 31 | 32 | A safe behavior for a JS variable can be either a monotype (`'a ref` in ML) or, when storing polymorphic values (such as an empty list or polymorphic functions), something more like an ocaml record with a mutable cell with a quantified type variable (forall). 33 | 34 | A mutable monotype variable has the same type as ocaml's `ref`, which is: 35 | 36 | ```ocaml 37 | type 'a ref = { mutable content : 'a } ;; 38 | ``` 39 | 40 | Here's (in ocaml) a mutable variable that can only contain the polymorphic empty list. The inner record field has a `forall` quantifier on the type of the list item. 41 | 42 | ```ocaml 43 | type t1 = { mutable v1 : 'a. 'a list } ;; (* notice the quantifier 'a. *) 44 | 45 | let x = { v1 = [] } ;; 46 | x.v1 <- [] ;; (* not too many things we can put in there... *) 47 | ``` 48 | 49 | More interesting is the type of a mutable variable that contains polymorphic functions of type `'a -> 'a list` (or `a -> [a]` if you prefer): 50 | 51 | ```ocaml 52 | type t2 = { mutable v2 : 'a. 'a -> 'a list } ;; 53 | 54 | let y = { v2 = fun x -> [ x; x; ] };; 55 | y.v2 3;; (* evaluates to [ 3; 3 ]*) 56 | y.v2 'a';; (* [ 'a'; 'a' ] *) 57 | 58 | y.v2 <- fun x -> [ x; x; x; ] ;; 59 | y.v2 3;; (* [ 3; 3; 3 ] *) 60 | etc.. 61 | ``` 62 | 63 | ### Possibly solutions to polymorphism of mutable variables 64 | 65 | 1. Infer types, and allow full polymorphism of mutable variables, allowing even nonsense like `x = 'a'; x = 3;`, which makes this solution useless. 66 | 2. Infer types, and disable polymorphism (treating JS vars like ML `ref`s). Variables are still allowed to be of types where **`a` is free** (such as `a -> a`), but not allowed to be of **closed types** (such as `forall a. a -> a`). 67 | 3. Don't infer types - require programmer to annotate code with type ascriptions. Interpret all type variables as universally quantified, e.g. `a` is interpreted as `forall a. a` (and no value can ever be assigned) or more usefully, `a -> [a]` will be interpreted as `forall a. a -> [a]` (and many list-constructing functions will inhabit this type). This approach is similar to ocaml's **mutable record fields**. 68 | 4. Infer types, but allow polymorphism only in certain cases. **TODO: Which cases?** 69 | 70 | 71 | ### Examples 72 | 73 | Consider the following examples: 74 | 75 | ```javascript 76 | var x = 1; 77 | x = 'a'; // should not type check 78 | ``` 79 | 80 | Here we have assigned two types to the same variable (recall that variables are reference cells), which should not be allowed. 81 | 82 | ```javascript 83 | var x = []; 84 | x = [1]; 85 | x = ['a']; // should not type check 86 | ``` 87 | 88 | Here again we used two types. Unlike the previous example, we initially assign a value with unknown type (`[a]`, but we don't know what `a` is). 89 | 90 | ```javascript 91 | var x; 92 | x = 1; 93 | x = 'a'; // should not type check 94 | ``` 95 | 96 | In the above example we have no information at all about `x`'s type at declaration time. There is no analogy for this in SML-like languages. 97 | 98 | ```javascript 99 | var f = function (x) { return x; } 100 | var n = f(1); 101 | var s = f('a'); // ok 102 | ``` 103 | 104 | Here we have assigned a polymorphic function (of type `a -> a`) to the variable `f`. Later we invoke this function twice, using different types each time, which should be allowed. This is an example of desired polymorphism. 105 | 106 | ```javascript 107 | var f = function() { return function(x) { return x; } } 108 | var g = f(); // g should also be polymorphic 109 | var n = g(1); 110 | var s = g('a'); // this is ok, but value restriction would not allow this to type check 111 | ``` 112 | 113 | Here again we make use of polymorphism. However, because we're assigning `g` from an expression that isn't a syntactic value (`f()` is a function invocation), languages such as SML will restrict `g` to a monotype and unify `g` to type `number -> number` after the call to `g(1)`. When designing our type system we must consider applying this limitation, to avoid other problems that the value restriction was designed to avoid. 114 | 115 | ```javascript 116 | var x = 1; 117 | var f = function(y) { var res = x; x = y; return res; } // should get type: number -> number 118 | ``` 119 | 120 | The above function, in pure (dynamically typed) JS will return the value that was passed "last time" the function was called, regardless of its type. With unmodified HM, the inferred type is `number -> number` because `x` has been assigned a number `1`, and everything is fine. What should happen when `x` is not assigned (only declared)? 121 | 122 | ```javascript 123 | var x; 124 | var f = function(y) { var res = x; x = y; return res; } // type ??? 125 | ``` 126 | 127 | A variable's type can't be determined at declaration time (`var x;`). Only when the variable is assigned `x = expr` we can infer its type. The declaration serves simply to bind the variable's name to the current scope and to possibly shadow variables declared in outer scopes (a variable's scope in JS is always the nearest function, if any, or otherwise the global scope). 128 | 129 | To solve this problem we must "wait" until the first assignment to the newly declared variable occurs. 130 | 131 | ### Desired polymorphism 132 | 133 | - function polymorphism (function calls instantiate type schemes) 134 | - row type polymorphism 135 | 136 | 137 | Implementation options: 138 | 139 | 1. In unification allow also negative rules such as: t != function, and we can use them in assignment expressions (in `f = ...` => f must not be a function) 140 | 2. In call expressions, tell the lvalue subexpr that it can instantiate a type scheme. Otherwise, var exprs do not really instantiate type schemes (they don't allow any bound tvars) 141 | 3. Switch from type schemes to "forall" as a basic type (higher order types): 142 | 143 | example (of option 3): 144 | 145 | makeFunc :: () -> (forall t. t -> t) 146 | obj.a = makeFunc() // will get: obj.a :: forall t. t -> t 147 | 148 | 149 | Examples: 150 | 151 | var x, y, f, g, h; 152 | x = { a: 1 }; // x has type '{ "a": number, ... }' 153 | x = 2; // type error 154 | y = { a: 2, b: 'bla' }; // y has type '{ "a": number, "b": string, ... }' 155 | x = y; // OK! x is more general (a la 'subtyping' but not really) 156 | y = x; // type error - x's type has no "b" field 157 | 158 | // f has type *scheme*: \t -> function(t) : [t] 159 | f = function(a) { return [a]; }; 160 | f = 2; // type error 161 | g = f(2); // g has type [number] 162 | h = f('a'); // h has type [string] -- crucial point! function calls cause instantiation of type scheme 163 | 164 | 165 | # Normalizing recursive types on outermost scope 166 | 167 | t = { method: t, x: String } -> Int 168 | { method: { method: t, x: String } -> Int, x: String } -> Int 169 | 170 | => o = { method: t, x: String } 171 | 172 | t = o -> Int 173 | 174 | => o = { method: o -> Int, x: String } 175 | 176 | { method: o -> Int, x: String } -> Int 177 | o -> Int 178 | 179 | 180 | # Higher-rank polymorphism for object methods 181 | 182 | See: 183 | 184 | - http://blog.huoc.org/higher-rank-polymorphism.html 185 | - "Semi-Explicit First-Class Polymorphism for ML" (1999), by Jacques Guarrigue and Didier Rémy. (1999) http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.46.4858 186 | 187 | -------------------------------------------------------------------------------- /docs/infernu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinelaw/infernu/e8b7aff6ea628c9099fb82aa011eb5c12aca36ba/docs/infernu.png -------------------------------------------------------------------------------- /docs/infernu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 27 | 28 | 46 | 48 | 49 | 51 | image/svg+xml 52 | 54 | 55 | 56 | 57 | 58 | 62 | 64 | 69 | 73 | infernu 90 | 91 | infernu 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /docs/inst-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinelaw/infernu/e8b7aff6ea628c9099fb82aa011eb5c12aca36ba/docs/inst-graph.png -------------------------------------------------------------------------------- /docs/make.sh: -------------------------------------------------------------------------------- 1 | pandoc --from=markdown_github+lhs --to=markdown_github semantics.md.lhs -o out/semantics.md 2 | -------------------------------------------------------------------------------- /docs/memoize.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | // 'f' is a function that takes a recursion function, and the current value. It should call the recursion function instead of itself when it needs to recurse. 3 | function memoize(toKeyString, f) { 4 | var mem = {}; 5 | 6 | return function memed(x) { 7 | var key = toKeyString(x); 8 | mem[key] = mem[key] || f(memed, x); 9 | return mem[key]; 10 | }; 11 | } 12 | 13 | var facTest = memoize(String, function fac(memed, x) { if (x < 2) { return 1; } return x * memed(x - 1); }); 14 | 15 | // The number of n-dimensional vectors whose scalar's sums to k 16 | // Hat-tip to https://stackoverflow.com/questions/3242597/what-is-memoization-good-for-and-is-it-really-all-that-helpful 17 | var nkTest = memoize( 18 | function (x) { 19 | return String(x.k) + ', ' + String(x.n); }, 20 | function f(m, x) { 21 | if (x.k === 0) { return 1; } 22 | if (x.n === 0) { return 0; } 23 | return m({ n: x.n-1, k: x.k }) + m({ n: x.n, k: x.k-1 }) + m({ n: x.n-1, k: x.k-1 }); 24 | }); 25 | 26 | 27 | nkTest({ n: 30, k: 30 }); 28 | -------------------------------------------------------------------------------- /docs/semantics.hs: -------------------------------------------------------------------------------- 1 | -- | Semantics of JS according to Infernu 2 | -- These definitions don't necessarily correspond to ECMAScript. 3 | 4 | module Semantics where 5 | 6 | import Data.Map (Map) 7 | import qualified Data.Map as Map 8 | 9 | -- | The term language 10 | 11 | newtype Id = Id String 12 | deriving (Show, Eq, Ord) 13 | 14 | data Value = VString String 15 | | VBool Bool 16 | | VNum Double 17 | | VUndef 18 | | VRecord (Map String Location) 19 | | VFun (RealWorld -> Value -> Value -> (RealWorld, Value)) 20 | 21 | instance Show Value where 22 | show (VString s) = "VString " ++ show s 23 | show (VBool b) = "VBool " ++ show b 24 | show (VNum b) = "VNum " ++ show b 25 | show VUndef = "undefined" 26 | show (VRecord props) = "VRecord " ++ show props 27 | show (VFun _) = "VFun" -- "VFun (" ++ show props ++ ", )" 28 | 29 | data Operator = Plus | Minus | GreaterThan 30 | deriving (Show) 31 | 32 | data Expr = Var String 33 | | Lit Value 34 | | App Expr Expr 35 | | Abs String Statement 36 | | Op Operator Expr Expr 37 | deriving (Show) 38 | 39 | (^+^) :: Expr -> Expr -> Expr 40 | (^+^) x y = Op Plus x y 41 | (^-^) x y = Op Minus x y 42 | (^>^) x y = Op GreaterThan x y 43 | (^$^) f x = App f x 44 | 45 | 46 | data Statement = Empty 47 | | Seq Statement Statement 48 | | Return Expr 49 | | JustExpr Expr 50 | | VarDecl String 51 | | Assign String Expr 52 | | While Expr Statement 53 | | If Expr Statement Statement 54 | -- Let String Expr Statement 55 | deriving (Show) 56 | 57 | (^=) x v = Assign x v 58 | 59 | -- | RealWorld 60 | 61 | newtype Location = Location Int deriving (Ord, Eq, Show) 62 | data RealWorld = RealWorld (Map Location Value) Location 63 | deriving (Show) 64 | 65 | initialWorld :: RealWorld 66 | initialWorld = RealWorld Map.empty (Location 0) 67 | 68 | alloc :: RealWorld -> (RealWorld, Location) 69 | alloc (RealWorld m (Location l)) = ( RealWorld m (Location (l + 1)) 70 | , Location l) 71 | 72 | store :: Location -> Value -> RealWorld -> RealWorld 73 | store l v (RealWorld m maxLoc) = RealWorld (Map.insert l v m) maxLoc 74 | 75 | load :: RealWorld -> Location -> Value 76 | load (RealWorld m _) l = case Map.lookup l m of 77 | Just v -> v 78 | Nothing -> error $ "Unallocated location: " ++ show l 79 | 80 | -- | Environment 81 | 82 | data Env = Env { bindings :: [(Id, Location)] 83 | , returns :: [Value] } 84 | deriving (Show) 85 | 86 | emptyEnv :: Env 87 | emptyEnv = Env [] [] 88 | 89 | push :: Id -> Location -> Env -> Env 90 | push x l (Env bs rs) = Env ((x, l) : bs) rs 91 | 92 | pushReturn :: Value -> Env -> Env 93 | pushReturn r (Env bs rs) = Env bs (r:rs) 94 | 95 | popReturn :: Env -> (Env, Value) 96 | popReturn (Env bs []) = error "No more returns" 97 | popReturn (Env bs (r:rs)) = (Env bs rs, r) 98 | 99 | lookup' :: Id -> Env -> Location 100 | lookup' x (Env bs _) = case lookup x bs of 101 | Just p -> p 102 | Nothing -> error $ "Undeclared name: " ++ show x ++ " not in env: " ++ show bs 103 | 104 | get :: Id -> Env -> RealWorld -> Value 105 | get x env rw = load rw $ lookup' x env 106 | 107 | location :: Id -> Env -> Location 108 | location = lookup' 109 | 110 | -- | Expressions 111 | 112 | emean :: Expr -> Env -> RealWorld -> (RealWorld, Value) 113 | 114 | emean (Lit v) = \_env rw -> (rw, v) 115 | 116 | emean (Var x) = \env rw -> (rw, get (Id x) env rw) 117 | 118 | emean (Op op x y) = \env rw -> 119 | case emean x env rw of 120 | (rw', VNum vx) -> case emean y env rw' of 121 | (rw'', VNum vy) -> (rw'', vx `f` vy) 122 | where f = case op of 123 | Plus -> \a b -> VNum (a + b) 124 | Minus -> \a b -> VNum (a - b) 125 | GreaterThan -> \a b -> VBool (a > b) 126 | 127 | -- | Function expressions 128 | -- 129 | -- The meaning of Abs is a function that takes values and returns a value. 130 | -- 131 | -- Because the function takes values, not locations, and because there 132 | -- is no "pointer" in the language, calls are by value. The record 133 | -- value is special because it's a map from names to locations, so you 134 | -- can mutably modify the data. 135 | -- 136 | -- Return values are passed using a special "return" name pushed onto 137 | -- the environment. The result of a function is the value bound in the 138 | -- environment to that value when the function completes. 139 | 140 | emean (Abs args body) = 141 | \env rw -> 142 | ( rw 143 | , VFun 144 | $ \rw1 -> 145 | \this args' -> 146 | let (rw2, argLoc) = alloc rw1 147 | (rw3, thisLoc) = alloc rw2 148 | rw4 = store argLoc args' rw3 149 | rw5 = store thisLoc this rw4 150 | bodyEnv = 151 | push (Id args) argLoc 152 | . push (Id "this") thisLoc 153 | $ env 154 | 155 | in case (smean body) halt bodyEnv rw5 of 156 | (env'', rw6) -> (rw6, snd $ popReturn env'') 157 | ) 158 | 159 | -- | Function Call 160 | 161 | emean (App f args) = 162 | \env rw -> 163 | case emean f env rw of 164 | (rw', VFun f') -> case (emean args env rw') of (rw'', v) -> f' rw'' VUndef v 165 | _ -> error "Expected a function" 166 | 167 | 168 | 169 | -- | Statements 170 | 171 | halt :: Env -> RealWorld -> (Env, RealWorld) 172 | halt = (,) 173 | 174 | smean :: Statement 175 | -> (Env -> RealWorld -> (Env, RealWorld)) 176 | -> Env -> RealWorld -> (Env, RealWorld) 177 | 178 | smean Empty = id 179 | 180 | -- | Return statement 181 | 182 | smean (Return expr) = 183 | \k env rw -> case (emean expr) env rw of 184 | (rw', val) -> halt (pushReturn val env) rw' 185 | 186 | -- | Expression statements 187 | 188 | smean (JustExpr expr) = \k env rw -> k env . fst . (emean expr) env $ rw 189 | 190 | -- | Statement sequence ; 191 | 192 | smean (Seq stmtA stmtB) = \k env rw -> 193 | smean stmtA (smean stmtB k) env rw 194 | 195 | -- | (Mutable) variable declaration 196 | 197 | smean (VarDecl x) = \k env rw -> 198 | case alloc rw of 199 | (rw', loc) -> k (push (Id x) loc env) rw' 200 | 201 | -- | Assignment 202 | 203 | smean (Assign x expr) = \k env rw -> 204 | case ((emean expr) env rw) of 205 | (rw', val) -> k env (store (location (Id x) env) val rw') 206 | 207 | -- | While loop 208 | 209 | smean (While expr body) = while 210 | where 211 | while = 212 | \k env rw -> 213 | case (emean expr) env rw of 214 | (rw', VBool False) -> k env rw' 215 | (rw', VBool True) -> smean body (while k) env rw' 216 | _ -> error "Expected boolean" 217 | 218 | --Note: Recursive let using in the meaning here. It should be the same as using `fix`. 219 | 220 | -- | If statement 221 | 222 | smean (If expr b1 b2) = 223 | \k env rw -> 224 | case (emean expr) env rw of 225 | (rw', VBool True) -> (smean b1) k env rw' 226 | (rw', VBool False) -> (smean b2) k env rw' 227 | _ -> error "Expected boolean" 228 | 229 | 230 | 231 | -- | Non-JS fragments 232 | 233 | --These syntax constructs are added: 234 | 235 | -- | Let expression (immutable variable) 236 | 237 | --Non-recursive ('x' not free in 'expr'): 238 | 239 | -- smean (Let x expr stmt) = \k env rw -> 240 | -- case (emean expr) env rw of 241 | -- (rw', v) -> (smean stmt) k (push (Id x) v env) rw' 242 | 243 | --Recursive: 244 | 245 | -- s[[ let x = expr in stmt ]] = s[[ let x = fix(\x -> expr) in stmt ]] 246 | 247 | --Where `x` is free in `expr`. 248 | 249 | --Note that this definition restricts to non-polymorphic recursion. 250 | 251 | 252 | 253 | -- | Fix 254 | 255 | --TODO 256 | 257 | -- e[[ fix ]] = \env -> \rw -> 258 | block = foldr Seq Empty 259 | 260 | -- | test 261 | -- >>> snd $ emean (testReturn True) emptyEnv initialWorld 262 | -- VNum 1.0 263 | testReturn :: Bool -> Expr 264 | testReturn f = (App (Abs "y" 265 | (Seq 266 | (Return (Lit $ VNum 1) ) 267 | (If (Var "y") 268 | (Return (Lit $ VNum 2)) 269 | (Return (Lit $ VNum 3)) 270 | )) 271 | ) 272 | (Lit $ VBool f)) 273 | 274 | -- | test 275 | -- >>> snd $ emean (testWhile 3) emptyEnv initialWorld 276 | -- VNum 6.0 277 | testWhile :: Double -> Expr 278 | testWhile x = App (Abs "x" 279 | $ block [ sumUpToXIntoY 280 | , Return (Var "y") 281 | ] 282 | ) (Lit $ VNum x) 283 | where 284 | sumUpToXIntoY = 285 | block 286 | [ VarDecl "y" 287 | , "y" ^= Lit (VNum 0) 288 | , While 289 | (Var "x" ^>^ Lit (VNum 0)) 290 | $ block 291 | [ "y" ^= (Var "y" ^+^ Var "x") 292 | , "x" ^= (Var "x" ^-^ Lit (VNum 1)) 293 | ] 294 | ] 295 | 296 | -- | test 297 | -- >>> snd $ emean (test True) emptyEnv initialWorld 298 | -- VNum 2.0 299 | test :: Bool -> Expr 300 | test f = (App (Abs "y" 301 | (If (Var "y") 302 | (Return (Lit $ VNum 2)) 303 | (Return (Lit $ VNum 3)) 304 | ) 305 | ) 306 | (Lit $ VBool f)) 307 | 308 | -------------------------------------------------------------------------------- /doctest.sh: -------------------------------------------------------------------------------- 1 | find src -iname "*.hs" | xargs doctest 2 | -------------------------------------------------------------------------------- /infernu.cabal: -------------------------------------------------------------------------------- 1 | name: infernu 2 | version: 0.0.0.1 3 | synopsis: Type inference and checker for JavaScript (experimental) 4 | description: This version is highly experimental and may set your computer on fire (also, a lot of JS is not supported yet, so it may not be very useful.) 5 | . 6 | Infernu is a type checker for JavaScript. Since JavaScript is dynamically and weakly typed, it makes no sense to talk about "type errors" in arbitrary JavaScript code. 7 | . 8 | Consequently Infernu makes assumptions about the code and expects it to follow certain rules that 9 | are not required by plain JavaScript (for example, implicit coercions such as `3 + 'a'` are not 10 | allowed.) 11 | . 12 | Infernu's type system is designed for writing dynamic-looking code in a safe statically type-checked 13 | environment. Type annotations are not required (though they would be nice to support, for various 14 | reasons). Instead, Infernu *infers* the types of expressions by examining the code. If the inferred 15 | types contradict each other, Infernu reports the contradiction as an error. 16 | . 17 | Infernu places restrictions on JS programs that are otherwise valid. In other words, Infernu is a 18 | **subset of JavaScript**. Infernu tries to strike a balance between type system complexity and 19 | dynamic-style coding flexibility. 20 | . 21 | See the .md files included in the package for more information. 22 | 23 | license: GPL-2 24 | homepage: https://github.com/sinelaw/infernu 25 | bug-reports: https://github.com/sinelaw/infernu/issues 26 | license-file: LICENSE 27 | author: Noam Lewis 28 | maintainer: jones.noamle@gmail.com 29 | copyright: Noam Lewis, 2014-2015 30 | -- category: 31 | build-type: Simple 32 | extra-source-files: README.md 33 | cabal-version: >=1.10 34 | stability: experimental 35 | 36 | source-repository head 37 | type: git 38 | location: git@github.com:sinelaw/infernu.git 39 | 40 | 41 | flag quickcheck 42 | default: False 43 | manual: True 44 | 45 | flag trace 46 | default: False 47 | manual: True 48 | 49 | flag debug 50 | default: False 51 | manual: True 52 | 53 | library 54 | hs-source-dirs: src 55 | exposed-modules: Infernu.Builtins.Array 56 | , Infernu.Builtins.Date 57 | -- WIP: Dom 58 | --, Infernu.Builtins.DOM 59 | , Infernu.Builtins.Math 60 | , Infernu.Builtins.Names 61 | , Infernu.Builtins.Number 62 | , Infernu.Builtins.Object 63 | , Infernu.Builtins.Operators 64 | , Infernu.Builtins.Regex 65 | , Infernu.Builtins.String 66 | , Infernu.Builtins.StringMap 67 | , Infernu.Builtins.TypeClasses 68 | , Infernu.Builtins 69 | , Infernu.Decycle 70 | , Infernu.Expr 71 | , Infernu.Fix 72 | , Infernu.Infer 73 | , Infernu.InferState 74 | , Infernu.Lib 75 | , Infernu.Log 76 | , Infernu.Options 77 | , Infernu.Parse 78 | , Infernu.Prelude 79 | , Infernu.Pretty 80 | , Infernu.Source 81 | , Infernu.Types 82 | , Infernu.Unify 83 | , Infernu.Util 84 | other-modules: Infernu.Builtins.Util 85 | -- TODO: use only mtl (not transformers) 86 | build-depends: base >= 4.6 && < 5, base-compat, base-orphans, mtl, containers, transformers, either, language-ecmascript, digits, parsec, optparse-applicative, ansi-wl-pprint, unordered-containers, hashable 87 | default-language: Haskell2010 88 | default-extensions: NoImplicitPrelude 89 | ghc-options: -Wall -O2 -rtsopts -threaded 90 | ghc-prof-options: -threaded -auto-all -caf-all -O2 -rtsopts 91 | if flag(debug) 92 | ghc-options: -g 93 | if flag(trace) 94 | cpp-options: -DTRACE 95 | if flag(quickcheck) 96 | cpp-options: -DQUICKCHECK 97 | build-depends: QuickCheck, derive 98 | default-extensions: TemplateHaskell, DeriveGeneric, FlexibleInstances 99 | 100 | executable infernu 101 | main-is: Main.hs 102 | build-depends: base, parsec, infernu, optparse-applicative, ansi-wl-pprint 103 | default-language: Haskell2010 104 | default-extensions: NoImplicitPrelude 105 | ghc-options: -Wall -O2 -rtsopts -threaded 106 | ghc-prof-options: -threaded -auto-all -caf-all -O2 -rtsopts 107 | if flag(debug) 108 | ghc-options: -g 109 | if flag(trace) 110 | cpp-options: -DTRACE 111 | if flag(quickcheck) 112 | cpp-options: -DQUICKCHECK 113 | build-depends: QuickCheck, derive 114 | default-extensions: TemplateHaskell, DeriveGeneric 115 | 116 | executable infernu-demo 117 | main-is: Demo.hs 118 | hs-source-dirs: test 119 | build-depends: base, infernu, ansi-wl-pprint 120 | default-language: Haskell2010 121 | default-extensions: NoImplicitPrelude 122 | ghc-options: -Wall -O2 -main-is Demo 123 | if flag(trace) 124 | cpp-options: -DTRACE 125 | if flag(quickcheck) 126 | cpp-options: -DQUICKCHECK 127 | build-depends: QuickCheck, derive 128 | default-extensions: TemplateHaskell, DeriveGeneric 129 | 130 | executable test 131 | if flag(quickcheck) 132 | cpp-options: -DQUICKCHECK 133 | build-depends: QuickCheck, derive 134 | default-extensions: TemplateHaskell, DeriveGeneric 135 | else 136 | buildable: False 137 | main-is: Test.hs 138 | hs-source-dirs: test 139 | build-depends: base, infernu 140 | default-language: Haskell2010 141 | default-extensions: NoImplicitPrelude 142 | ghc-options: -Wall -O2 -main-is Test 143 | if flag(trace) 144 | cpp-options: -DTRACE 145 | -------------------------------------------------------------------------------- /lambdabot-plugin/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Noam Lewis 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /lambdabot-plugin/Lambdabot/Plugin/Infernu.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE MultiParamTypeClasses #-} 2 | {-# LANGUAGE TemplateHaskell #-} 3 | {-# LANGUAGE TypeSynonymInstances #-} 4 | -- | Infernu 5 | module Lambdabot.Plugin.Infernu (infernuPlugin) where 6 | 7 | import Data.Maybe (catMaybes) 8 | import qualified Data.ByteString.Char8 as P 9 | import Data.Functor ((<$>)) 10 | import Data.List (intersperse) 11 | import qualified Data.Map as Map 12 | import Lambdabot.Plugin 13 | import qualified Language.ECMAScript3.Parser as ES3Parser 14 | import qualified Language.ECMAScript3.Syntax as ES3 15 | 16 | import Infernu.Infer (getAnnotations, minifyVars, 17 | pretty, runTypeInference) 18 | import Infernu.Parse (translate) 19 | import Infernu.Types (GenInfo(..), Source(..)) 20 | 21 | import Text.PrettyPrint.ANSI.Leijen (plain, displayS, renderPretty, Doc) 22 | 23 | infernuPlugin :: Module () 24 | infernuPlugin = newModule 25 | { moduleCmds = return 26 | [ (command "js") 27 | { aliases = [] 28 | , help = say "js " 29 | , process = say . sayType 30 | } 31 | ] 32 | } 33 | 34 | showWidth :: Int -> Doc -> String 35 | showWidth w x = displayS (renderPretty 0.4 w x) "" 36 | 37 | showDoc = showWidth 120 38 | 39 | --sayType :: Monad m => String -> Cmd m () 40 | sayType :: String -> String 41 | sayType rest = case runTypeInference . fmap Source . translate . ES3.unJavaScript <$> ES3Parser.parseFromString rest of 42 | Left e -> show e 43 | Right res -> case getAnnotations <$> res of 44 | Left e' -> showDoc $ pretty e' 45 | Right [] -> show "There is nothing there." 46 | Right xs -> concat . intersperse "\n" $ filterGen xs 47 | where filterGen = catMaybes . map (\(Source (GenInfo g n, _), t) -> 48 | case (g,n) of 49 | (False, Just n) -> Just (showDoc (plain $ pretty n) ++ " : " ++ (showDoc $ plain $ pretty $ minifyVars t)) 50 | _ -> Nothing 51 | ) 52 | 53 | -------------------------------------------------------------------------------- /lambdabot-plugin/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /lambdabot-plugin/lambdabot-plugin-inferno.cabal: -------------------------------------------------------------------------------- 1 | -- Initial lambdabot-plugin-infernu.cabal generated by cabal init. For further 2 | -- documentation, see http://haskell.org/cabal/users-guide/ 3 | 4 | name: lambdabot-plugin-infernu 5 | version: 0.1.0.0 6 | synopsis: Lambdabot plugin for infernu 7 | -- description: 8 | license: MIT 9 | license-file: LICENSE 10 | author: Noam Lewis 11 | maintainer: jones.noamle@gmail.com 12 | -- copyright: 13 | -- category: 14 | build-type: Simple 15 | -- extra-source-files: 16 | cabal-version: >=1.10 17 | 18 | library 19 | exposed-modules: Lambdabot.Plugin.Infernu 20 | -- other-modules: 21 | -- other-extensions: 22 | other-extensions: TemplateHaskell, MultiParamTypeClasses, TypeSynonymInstances 23 | build-depends: base >=4.7 && < 5, lambdabot-core, containers, bytestring, language-ecmascript, ansi-wl-pprint, infernu >= 0.0.0.1 24 | -- build-depends: base >=4.7 && <4.8, language-ecmascript, infernu == 0.1.0.0 25 | -- hs-source-dirs: 26 | default-language: Haskell2010 27 | -------------------------------------------------------------------------------- /others/ConstId.cs: -------------------------------------------------------------------------------- 1 | class Test 2 | { 3 | public Func ConstId(U a) 4 | { 5 | return x => x; 6 | } 7 | } -------------------------------------------------------------------------------- /others/GetPut.java: -------------------------------------------------------------------------------- 1 | public class GetPut 2 | { 3 | private T _value; 4 | 5 | public T Get() 6 | { 7 | return _value; 8 | } 9 | 10 | public T Put(T x) 11 | { 12 | T old = _value; 13 | _value = x; 14 | return old; 15 | } 16 | 17 | } 18 | 19 | -------------------------------------------------------------------------------- /others/flow/.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | [include] 4 | 5 | [libs] 6 | 7 | [options] 8 | -------------------------------------------------------------------------------- /others/flow/debounce.js: -------------------------------------------------------------------------------- 1 | function f1(x) { return x; } 2 | function debounce(f) { 3 | var current = []; 4 | var has = false; 5 | return function(arg) { 6 | var old = has ? current[0] : arg; 7 | has = true; 8 | current = [arg]; 9 | return f(old); 10 | }; 11 | } 12 | var fonce = debounce(function (x) { return x[0]; }); 13 | var x = fonce([3]); 14 | var fonce2 = debounce(function (x) { return x[0]; }); 15 | var y = fonce2(['s']); 16 | //x.length; 17 | -------------------------------------------------------------------------------- /others/flow/guards.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | function foo(b) { if (b) { return 21; } else { return ''; } } 3 | function bar(b) { // : number 4 | var x = foo(b); 5 | var y = foo(b); 6 | if (typeof x == 'number' && typeof y == 'number') { return x + y; } 7 | return 0; 8 | } 9 | 10 | function poly(x : X) : X { 11 | if (typeof x == 'number') { return 0; } 12 | return x; 13 | } -------------------------------------------------------------------------------- /others/flow/id.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | var id = function (x) { return x; }; 3 | var x = id(3); 4 | var y = id('s'); 5 | var z = x.length; 6 | -------------------------------------------------------------------------------- /others/flow/memoize/memoize.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | function memoize(f) { 4 | var mem = {}; 5 | 6 | return function(x) { 7 | var key = String(x); 8 | mem[key] = mem[key] || function(){return f(x);}; 9 | return mem[key](); 10 | }; 11 | } 12 | 13 | var test = memoize(function(x) { return x + 2; }); 14 | 15 | test('a'); 16 | -------------------------------------------------------------------------------- /others/flow/problem-id.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | var id = function (x) { return x; }; 3 | var x = id(3); 4 | var y = id('s'); 5 | 6 | -------------------------------------------------------------------------------- /others/flow/problem.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | function debounce(f) { 3 | return function(arg) { 4 | return f(arg); 5 | }; 6 | } 7 | var id = function (x) { return x; }; 8 | var id1 = debounce(id); 9 | var x = id1(3); 10 | var id2 = debounce(id); 11 | var y = id2('s'); 12 | //x.length; 13 | -------------------------------------------------------------------------------- /others/flow/row.js: -------------------------------------------------------------------------------- 1 | /* flow */ 2 | function f(x) { 3 | return x.length; 4 | } 5 | 6 | //var x = f({ length: 2}); 7 | //var y = x.foo; 8 | 9 | var a = [0,1,2,'b']; 10 | 11 | var x = 1+2; 12 | var y = a[x]; 13 | 14 | var z = y.length; 15 | 16 | -------------------------------------------------------------------------------- /others/flow/simple-bug.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | var y = 2; 3 | y = 'hi'; 4 | 5 | var r = y + 2; 6 | 7 | var l = r.length; 8 | 9 | 10 | // flow allows all of the above without errors or warnings. 11 | // r will be 'hi2' and l will be equal to 3. 12 | 13 | -------------------------------------------------------------------------------- /others/lint/string_constr.js: -------------------------------------------------------------------------------- 1 | var Foo = String; 2 | var x = new Foo('b'); 3 | -------------------------------------------------------------------------------- /others/notabug.caml: -------------------------------------------------------------------------------- 1 | let x = ref None ;; 2 | let setX v = x := Some v ;; 3 | let y = setX (fun b -> b) ;; 4 | let z = setX (fun _ -> "a") ;; 5 | let z' = match !x with 6 | Some f -> f 2;; 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /others/typescript/bugs.ts: -------------------------------------------------------------------------------- 1 | b = 3; // var hoisting 2 | var a = 'a'; 3 | var b = +a; // implicit conversion to string 4 | var c = !a; // implicit conversion to bool 5 | delete a; // delete works on anything, not just object properties 6 | var d = void a; // why do we need void? 7 | var e = this.foo; // 'this'' has same horrible behavior as in javascript (and is always in context) 8 | -------------------------------------------------------------------------------- /others/typescript/memoize.ts: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | function memoize(f) { 4 | var mem = {}; 5 | 6 | return function(x) { 7 | var key = String(x); 8 | mem[key] = mem[key] || function(){return f(x);}; 9 | return mem[key](); 10 | }; 11 | } 12 | 13 | var test = memoize(function(x) { return x + 2; }); 14 | 15 | test('a'); 16 | -------------------------------------------------------------------------------- /src/Infernu/Builtins.hs: -------------------------------------------------------------------------------- 1 | module Infernu.Builtins 2 | ( builtins 3 | , arrayRowType 4 | , dateRowType 5 | , regexRowType 6 | , stringRowType 7 | , numberRowType 8 | , stringMapRowType 9 | ) 10 | where 11 | 12 | import Infernu.Builtins.Array (arrayRowType) 13 | import Infernu.Builtins.Date (dateRowType) 14 | import qualified Infernu.Builtins.Operators as Operators 15 | import Infernu.Types (TypeScheme) 16 | import Infernu.Expr (EVarName) 17 | import Infernu.Builtins.Regex (regexRowType) 18 | import Infernu.Builtins.String (stringRowType) 19 | import Infernu.Builtins.Number (numberRowType) 20 | import Infernu.Builtins.StringMap (stringMapRowType) 21 | 22 | import Data.Map (Map) 23 | 24 | builtins :: Map EVarName TypeScheme 25 | builtins = Operators.builtins 26 | 27 | -------------------------------------------------------------------------------- /src/Infernu/Builtins/Array.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TupleSections #-} 2 | module Infernu.Builtins.Array 3 | (arrayRowType) 4 | where 5 | 6 | import Control.Monad (foldM) 7 | 8 | import Infernu.Builtins.Util 9 | import Infernu.InferState (Infer) 10 | import Infernu.Prelude 11 | import Infernu.Types 12 | import Infernu.Expr (EPropName(..)) 13 | 14 | arrayProps :: Type -> [(String, TypeScheme)] 15 | arrayProps elemType = let aType = array elemType in 16 | [ ("length", ty number) 17 | , ("concat", ty $ func aType aType aType) 18 | -- TODO support thisArg (requires type variables) 19 | , ("every", ts [] $ func aType (funcN [tvar 1, elemType] boolean) boolean) -- missing thisArg 20 | , ("filter", ts [] $ func aType (funcN [tvar 1, elemType] boolean) aType) -- missing thisArg 21 | , ("forEach", ts [2] $ func aType (funcN [tvar 1, elemType] (tvar 2)) undef) -- missing thisArg 22 | -- TODO support optional argument for fromIndex (last parameter) 23 | , ("indexOf", ty $ funcN [aType, elemType, number] number) 24 | , ("join", ty $ func aType string string) 25 | , ("lastIndexOf", ty $ func aType number number) 26 | , ("map", ts [2] $ func aType (funcN [tvar 1, elemType] (tvar 2)) (array $ tvar 2)) 27 | , ("pop", ty $ funcN [aType] elemType) 28 | , ("push", ty $ funcN [aType, elemType] number) 29 | , ("reduce", ts [1] $ funcN [aType, funcN [tvar 0, tvar 1, elemType] (tvar 1), (tvar 1)] (tvar 1)) 30 | , ("reverse", ty $ funcN [aType] aType) 31 | , ("shift", ty $ funcN [aType] elemType) 32 | , ("slice", ty $ funcN [aType, number, number] aType) 33 | , ("some", ts [] $ func aType (funcN [tvar 0, elemType] boolean) aType) -- missing thisArg 34 | , ("sort", ts [] $ func aType (funcN [tvar 0, elemType, elemType] number) aType) 35 | , ("splice", ty $ funcN [aType, number, number] aType) 36 | , ("unshift", ty $ funcN [aType] elemType) 37 | ] 38 | 39 | -- TODO: when inserting builtin types, do fresh renaming of scheme qvars 40 | arrayRowType :: Type -> Infer (TRowList Type) 41 | arrayRowType elemType = TRowProp (TPropGetName EPropSetIndex) (ty $ funcN [aType, number, elemType] undef) 42 | . TRowProp (TPropGetName EPropGetIndex) (ty $ func aType number elemType) 43 | <$> foldM addProp (TRowEnd Nothing) (arrayProps elemType) 44 | where aType = array elemType 45 | -------------------------------------------------------------------------------- /src/Infernu/Builtins/DOM.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TupleSections #-} 2 | module Infernu.Builtins.DOM 3 | where 4 | 5 | import Infernu.Builtins.Util 6 | import Infernu.Prelude 7 | import Infernu.Types 8 | 9 | data PropType t = R t | RW t 10 | -- [Constructor(DOMString type, optional EventInit eventInitDict), 11 | -- Exposed=(Window,Worker,System)] 12 | -- interface Event { 13 | eventProps eventTarget = 14 | -- [Pure] 15 | -- readonly attribute DOMString type; 16 | [ ("type", R $ ty string) 17 | -- [Pure] 18 | -- readonly attribute EventTarget? target; 19 | , ("target", R $ ty eventTarget) -- TODO: "nullable" 20 | -- [Pure] 21 | -- readonly attribute EventTarget? currentTarget; 22 | , ("currentTarget", R $ ty eventTarget) 23 | 24 | -- const unsigned short NONE = 0; 25 | , ("NONE", R $ ty number) 26 | -- const unsigned short CAPTURING_PHASE = 1; 27 | , ("CAPTURING_PHASE", R $ ty number) 28 | -- const unsigned short AT_TARGET = 2; 29 | , ("AT_TARGET", R $ ty number) 30 | -- const unsigned short BUBBLING_PHASE = 3; 31 | , ("BUBBLING_PHASE", R $ ty number) 32 | -- [Pure] 33 | -- readonly attribute unsigned short eventPhase; 34 | , ("eventPhase", R $ ty number) 35 | 36 | -- void stopPropagation(); 37 | , ("stopPropagation", RW $ ts [0] $ funcN [tvar 0]) 38 | -- void stopImmediatePropagation(); 39 | , ("stopImmediatePropagation", RW $ ts [0] $ funcN [tvar 0]) 40 | 41 | -- [Pure] 42 | -- readonly attribute boolean bubbles; 43 | -- [Pure] 44 | -- readonly attribute boolean cancelable; 45 | -- void preventDefault(); 46 | -- [Pure] 47 | -- readonly attribute boolean defaultPrevented; 48 | 49 | -- [Unforgeable, Pure] 50 | -- readonly attribute boolean isTrusted; 51 | -- [Pure] 52 | -- readonly attribute DOMHighResTimeStamp timeStamp; 53 | 54 | -- [Throws] 55 | -- void initEvent(DOMString type, boolean bubbles, boolean cancelable); 56 | ] 57 | -- }; 58 | 59 | --eventRowType = foldM addProp (TRowEnd Nothing) eventProps 60 | 61 | --interface EventTarget { 62 | eventTargetProps eventListener event = 63 | -- /* Passing null for wantsUntrusted means "default behavior", which 64 | -- differs in content and chrome. In content that default boolean 65 | -- value is true, while in chrome the default boolean value is 66 | -- false. */ 67 | -- [Throws] 68 | -- void addEventListener(DOMString type, 69 | -- EventListener? listener, 70 | -- optional boolean capture = false, 71 | -- optional boolean? wantsUntrusted = null); 72 | [ ("addEventListener", ts [0] $ funcN [tvar 0, string, eventListener]) -- TODO optional: , boolean, boolean 73 | -- [Throws] 74 | -- void removeEventListener(DOMString type, 75 | -- EventListener? listener, 76 | -- optional boolean capture = false); 77 | , ("removeEventListener", ts [0] $ funcN [tvar 0, string, eventListener]) -- boolean 78 | -- [Throws] 79 | -- boolean dispatchEvent(Event event); 80 | , ("dispatchEvent", ts [0] $ funcN [tvar 0, event]) 81 | ] 82 | -- }; 83 | 84 | elementGlobalAttributes :: [(String, TypeScheme)] 85 | elementGlobalAttributes = 86 | -- TODO add more 87 | [ ("accesskey", ty string) 88 | , ("class", ty string) 89 | , ("id", ty string) 90 | , ("style", ty string) 91 | , ("tabindex", ty number) 92 | , ("title", ty string) 93 | ] 94 | 95 | -------------------------------------------------------------------------------- /src/Infernu/Builtins/Date.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TupleSections #-} 2 | module Infernu.Builtins.Date 3 | (dateRowType) 4 | where 5 | 6 | import Control.Monad (foldM) 7 | 8 | import Infernu.Builtins.Util 9 | import Infernu.InferState (Infer) 10 | import Infernu.Prelude 11 | import Infernu.Types 12 | 13 | dateFunc :: Type -> Type 14 | dateFunc = funcN [date] 15 | 16 | getNumFromDate :: TScheme Type 17 | getNumFromDate = ty $ dateFunc number 18 | 19 | getNumFuncs :: [String] 20 | getNumFuncs = [ "getDate" 21 | , "getDay" 22 | , "getFullYear" 23 | , "getHours" 24 | , "getMilliseconds" 25 | , "getMinutes" 26 | , "getMonth" 27 | , "getSeconds" 28 | , "getTime" 29 | , "getTimezoneOffset" 30 | , "getUTCDate" 31 | , "getUTCDay" 32 | , "getUTCFullYear" 33 | , "getUTCHours" 34 | , "getUTCMilliseconds" 35 | , "getUTCMinutes" 36 | , "getUTCMonth" 37 | , "getUTCSeconds" 38 | , "valueOf" 39 | ] 40 | 41 | setNumOnDate :: TScheme Type 42 | setNumOnDate = ty $ funcN [date, number] number 43 | 44 | -- Note: many of these functions accept multiple optional parameters, but there are other ways to 45 | -- achieve the same result. Leaving these optional parameters out. 46 | setNumFuncs :: [String] 47 | setNumFuncs = 48 | [ "setDate" 49 | , "setFullYear" 50 | , "setHours" 51 | , "setMilliseconds" 52 | , "setMinutes" 53 | , "setMonth" 54 | , "setSeconds" 55 | , "setTime" 56 | , "setUTCDate" 57 | , "setUTCFullYear" 58 | , "setUTCHours" 59 | , "setUTCMilliseconds" 60 | , "setUTCMinutes" 61 | , "setUTCMonth" 62 | , "setUTCSeconds" 63 | -- , "setYear" 64 | ] 65 | 66 | getStringFuncType :: TScheme (Fix FType) 67 | getStringFuncType = ty $ funcN [date] string 68 | 69 | getStringFuncs :: [String] 70 | getStringFuncs = 71 | [ "toDateString" 72 | -- , "toGMTString" 73 | , "toISOString" 74 | , "toJSON" 75 | -- , "toLocaleFormat" 76 | -- , "toSource" 77 | , "toString" 78 | , "toTimeString" 79 | , "toUTCString" 80 | ] 81 | -- , "toLocaleDateString" 82 | -- , "toLocaleString" 83 | -- , "toLocaleTimeString" 84 | 85 | dateProps :: [(String, TypeScheme)] 86 | dateProps = 87 | map (,getNumFromDate) getNumFuncs 88 | ++ map (,setNumOnDate) setNumFuncs 89 | ++ map (,getStringFuncType) getStringFuncs 90 | 91 | dateRowType :: Infer (TRowList Type) 92 | dateRowType = namedProps 93 | where namedProps = foldM addProp (TRowEnd Nothing) dateProps 94 | -------------------------------------------------------------------------------- /src/Infernu/Builtins/Math.hs: -------------------------------------------------------------------------------- 1 | module Infernu.Builtins.Math 2 | (math) 3 | where 4 | 5 | import Infernu.Builtins.Util 6 | import Infernu.Prelude 7 | import Infernu.Types 8 | import Infernu.Expr (EPropName(..)) 9 | 10 | numProp :: String -> TRowList Type -> TRowList Type 11 | numProp name = TRowProp (TPropGetName $ EPropName name) $ ts [] number 12 | 13 | numFuncProp :: String -> TRowList (Fix FType) -> TRowList (Fix FType) 14 | numFuncProp name = TRowProp (TPropGetName $ EPropName name) $ ts [0] $ Fix $ TFunc [tvar 0, number] number 15 | 16 | numFuncProp2 :: String -> TRowList (Fix FType) -> TRowList (Fix FType) 17 | numFuncProp2 name = TRowProp (TPropGetName $ EPropName name) $ ts [0] $ Fix $ TFunc [tvar 0, number, number] number 18 | 19 | math :: TScheme (Fix FType) 20 | math = ts [] $ Fix $ TRow (Just "Math") 21 | $ numProp "E" 22 | $ numProp "LN10" 23 | $ numProp "LN2" 24 | $ numProp "LOG10E" 25 | $ numProp "LOG2E" 26 | $ numProp "PI" 27 | $ numProp "SQRT1_2" 28 | $ numProp "SQRT2" 29 | $ numFuncProp "abs" 30 | $ numFuncProp "acos" 31 | -- $ numFuncProp "acosh" 32 | $ numFuncProp "asin" 33 | -- $ numFuncProp "asinh" 34 | $ numFuncProp "atan" 35 | $ numFuncProp2 "atan2" 36 | -- $ numFuncProp "atanh" 37 | -- $ numFuncProp "cbrt" 38 | $ numFuncProp "ceil" 39 | -- $ numFuncProp "clz32" 40 | $ numFuncProp "cos" 41 | -- $ numFuncProp "cosh" 42 | $ numFuncProp "exp" 43 | -- $ numFuncProp "expm1" 44 | $ numFuncProp "floor" 45 | -- $ numFuncProp "fround" 46 | -- $ numFuncProp "hypot" 47 | -- $ numFuncProp "imul" 48 | $ numFuncProp "log" 49 | -- $ numFuncProp "log10" 50 | -- $ numFuncProp "log1p" 51 | -- $ numFuncProp "log2" 52 | -- Requires support for variable number of arguments 53 | $ numFuncProp2 "max" 54 | $ numFuncProp2 "min" 55 | $ numFuncProp2 "pow" 56 | $ TRowProp (TPropGetName $ EPropName "random") (ts [] $ Fix $ TFunc [tvar 0] number) 57 | $ numFuncProp "round" 58 | $ numFuncProp "sign" 59 | $ numFuncProp "sin" 60 | -- $ numFuncProp "sinh" 61 | $ numFuncProp "sqrt" 62 | $ numFuncProp "tan" 63 | -- $ numFuncProp "tanh" 64 | -- $ numFuncProp "trunc" 65 | $ TRowEnd Nothing 66 | -------------------------------------------------------------------------------- /src/Infernu/Builtins/Names.hs: -------------------------------------------------------------------------------- 1 | module Infernu.Builtins.Names where 2 | 3 | import Infernu.Prelude 4 | 5 | refOp :: String 6 | refOp = "`ref" 7 | 8 | derefOp :: String 9 | derefOp = "`deref" 10 | 11 | refAssignOp :: String 12 | refAssignOp = "`:=" 13 | 14 | -------------------------------------------------------------------------------- /src/Infernu/Builtins/Number.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TupleSections #-} 2 | module Infernu.Builtins.Number 3 | (numberRowType) 4 | where 5 | 6 | import Control.Monad (foldM) 7 | 8 | import Infernu.Builtins.Util 9 | import Infernu.InferState (Infer) 10 | import Infernu.Prelude 11 | import Infernu.Types 12 | import Infernu.Expr (EPropName(..)) 13 | 14 | 15 | numberProps :: [(String, TypeScheme)] 16 | numberProps = 17 | [ 18 | ("toFixed", ty $ func number number string) 19 | ] 20 | 21 | -- TODO: when inserting builtin types, do fresh renaming of scheme qvars 22 | -- TODO: this code is actually pure, refactor to pure function and 'return' wrapper. 23 | numberRowType :: Infer (TRowList Type) 24 | numberRowType = foldM addProp (TRowEnd Nothing) $ numberProps 25 | -------------------------------------------------------------------------------- /src/Infernu/Builtins/Object.hs: -------------------------------------------------------------------------------- 1 | module Infernu.Builtins.Object 2 | (object) 3 | where 4 | 5 | import Infernu.Builtins.Util 6 | import Infernu.Prelude 7 | import Infernu.Types 8 | 9 | keyObj :: Int -> Type -> TQual Type 10 | keyObj tv = withTypeClass "StringKeys" (tvar tv) 11 | 12 | object :: TScheme (Fix FType) 13 | object = ts [] 14 | $ Fix $ TRow (Just "Object") 15 | -- assign - ES6 16 | $ prop "create" (ts [0, 1] $ funcN [tvar 0, openRow 1] (openRow 1)) 17 | -- can't be supported in a sane way: 18 | -- "defineProperties" 19 | -- "defineProperty" 20 | -- "getOwnPropertyDescriptor" 21 | $ prop "freeze" (tsq [0, 1] $ keyObj 1 $ funcN [tvar 0, tvar 1] (tvar 1)) 22 | $ prop "getOwnPropertyNames" (tsq [0, 1] $ keyObj 1 $ funcN [tvar 0, tvar 1] (array string)) 23 | -- "getPrototypeOf" -- should we just return the same type? we don't support prototypes 24 | $ prop "isExtensible" (tsq [0, 1] $ keyObj 1 $ funcN [tvar 0, tvar 1] boolean) 25 | $ prop "isFrozen" (tsq [0, 1] $ keyObj 1 $ funcN [tvar 0, tvar 1] boolean) 26 | $ prop "isSealed" (tsq [0, 1] $ keyObj 1 $ funcN [tvar 0, tvar 1] boolean) 27 | $ prop "keys" (tsq [0, 1] $ keyObj 1 $ funcN [tvar 0, tvar 1] (array string)) 28 | $ prop "preventExtensions" (tsq [0, 1] $ keyObj 1 $ funcN [tvar 0, tvar 1] (tvar 1)) 29 | $ prop "seal" (tsq [0, 1] $ keyObj 1 $ funcN [tvar 0, tvar 1] (tvar 1)) 30 | $ TRowEnd Nothing 31 | -------------------------------------------------------------------------------- /src/Infernu/Builtins/Operators.hs: -------------------------------------------------------------------------------- 1 | module Infernu.Builtins.Operators 2 | (builtins) 3 | where 4 | 5 | import Infernu.Prelude 6 | import Infernu.Types 7 | 8 | import Data.Map.Strict (Map) 9 | import qualified Data.Map.Strict as Map 10 | 11 | import Infernu.Builtins.Math (math) 12 | import Infernu.Builtins.Object (object) 13 | import Infernu.Builtins.Util 14 | import qualified Infernu.Builtins.Names as Names 15 | import Infernu.Expr (EPropName(..), EVarName) 16 | 17 | unaryFunc :: Type -> Type -> TypeScheme 18 | unaryFunc t1 t2 = ts [0] $ Fix $ TFunc [tvar 0, t1] t2 19 | 20 | binaryFunc :: Type -> Type -> Type -> Type -> Fix FType 21 | binaryFunc tThis t1 t2 t3 = Fix $ TFunc [tThis, t1, t2] t3 22 | 23 | binarySimpleFunc :: Type -> Type -> Type 24 | binarySimpleFunc tThis t = Fix $ TFunc [tThis, t, t] t 25 | 26 | binaryFuncS :: Type -> Type -> Type -> TypeScheme 27 | binaryFuncS t1 t2 t3 = ts [0] $ binaryFunc (tvar 0) t1 t2 t3 28 | 29 | numRelation :: TypeScheme 30 | numRelation = binaryFuncS number number boolean 31 | 32 | numOp :: TypeScheme 33 | numOp = binaryFuncS number number number 34 | 35 | builtins :: Map EVarName TypeScheme 36 | builtins = Map.fromList [ 37 | 38 | -- TODO: refOp and derefOp should be data constructors of the built-in Ref type (need to add 39 | -- support for data constructors) 40 | (Names.refOp, ts [1] $ Fix $ TFunc [tvar 1] (Fix $ TCons TRef [tvar 1])), 41 | (Names.refAssignOp, ts [1] $ Fix $ TFunc [Fix $ TCons TRef [tvar 1], tvar 1] (tvar 1)), 42 | (Names.derefOp, ts [1] $ Fix $ TFunc [Fix $ TCons TRef [tvar 1]] (tvar 1)), 43 | 44 | ("!", unaryFunc boolean boolean), 45 | ("~", unaryFunc number number), 46 | ("typeof", ts [0, 1] $ Fix $ TFunc [tvar 1, tvar 0] string), 47 | ("instanceof", ts [0, 1, 2] $ Fix $ TFunc [tvar 2, tvar 0, tvar 1] boolean), 48 | ("+", TScheme [Flex 0, Flex 1] TQual { qualPred = [TPredIsIn (ClassName "Plus") (tvar 1)] 49 | , qualType = binarySimpleFunc (tvar 0) (tvar 1) }), 50 | ("-", numOp), 51 | ("*", numOp), 52 | ("/", numOp), 53 | ("%", numOp), 54 | ("<<", numOp), 55 | (">>", numOp), 56 | (">>>", numOp), 57 | ("&", numOp), 58 | ("^", numOp), 59 | ("|", numOp), 60 | ("<", numRelation), 61 | ("<=", numRelation), 62 | (">", numRelation), 63 | (">=", numRelation), 64 | ("===", ts [0, 1, 2] $ Fix $ TFunc [tvar 2, tvar 0, tvar 1] boolean), 65 | ("!==", ts [0, 1, 2] $ Fix $ TFunc [tvar 2, tvar 0, tvar 1] boolean), 66 | ("&&", ts [0, 1] $ Fix $ TFunc [tvar 0, tvar 1, tvar 1] (tvar 1)), 67 | ("||", ts [0, 1] $ Fix $ TFunc [tvar 0, tvar 1, tvar 1] (tvar 1)), 68 | -- avoid coercions on == and != 69 | ("==", ts [0, 1] $ Fix $ TFunc [tvar 1, tvar 0, tvar 0] boolean), 70 | ("!=", ts [0, 1] $ Fix $ TFunc [tvar 1, tvar 0, tvar 0] boolean), 71 | ("RegExp", ts [] $ Fix $ TFunc [undef, string, string] regex), 72 | ("String", ts [1] $ Fix $ TFunc [undef, tvar 1] string), 73 | ("Number", ts [1] $ Fix $ TFunc [undef, tvar 1] number), 74 | ("Boolean", ts [1] $ Fix $ TFunc [undef, tvar 1] boolean), 75 | ("NaN", ts [] number), 76 | ("Infinity", ts [] number), 77 | ("undefined", ts [0] undef), 78 | ("isFinite", ts [0] $ Fix $ TFunc [tvar 0, number] boolean), 79 | ("isNaN", ts [0] $ Fix $ TFunc [tvar 0, number] boolean), 80 | ("parseFloat", ts [0] $ Fix $ TFunc [tvar 0, string] number), 81 | ("parseInt", ts [0] $ Fix $ TFunc [tvar 0, string, number] number), 82 | ("decodeURI", ts [0] $ Fix $ TFunc [tvar 0, string] string), 83 | ("decodeURIComponent", ts [0] $ Fix $ TFunc [tvar 0, string] string), 84 | ("encodeURI", ts [0] $ Fix $ TFunc [tvar 0, string] string), 85 | ("encodeURIComponent", ts [0] $ Fix $ TFunc [tvar 0, string] string), 86 | ("Date", ts [] $ Fix $ TRow (Just "Date[Constructor]") 87 | $ TRowProp (TPropGetName EPropFun) (ts [] $ Fix $ TFunc [Fix $ TBody TDate] string) 88 | $ prop "now" (ts [0] $ Fix $ TFunc [tvar 0] number) 89 | $ prop "parse" (ts [0] $ Fix $ TFunc [tvar 0, string] (Fix $ TBody TDate)) 90 | $ prop "UTC" (ts [0] $ Fix $ TFunc [tvar 0, number, number, number, number, number, number, number] (Fix $ TBody TDate)) 91 | $ TRowEnd Nothing), 92 | ("Math", math), 93 | ("Object", object), 94 | ("JSON", ts [] $ Fix $ TRow (Just "JSON") 95 | $ prop "stringify" (ts [0, 1] $ Fix $ TFunc [tvar 0, tvar 1] string) 96 | -- TODO: should really be "maybe (tvar 1)" 97 | $ prop "parse" (ts [0, 1] $ Fix $ TFunc [tvar 0, string] (tvar 1)) 98 | $ TRowEnd Nothing) 99 | ] 100 | -------------------------------------------------------------------------------- /src/Infernu/Builtins/Regex.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TupleSections #-} 2 | module Infernu.Builtins.Regex 3 | (regexRowType) 4 | where 5 | 6 | import Control.Monad (foldM) 7 | 8 | import Infernu.Builtins.Util 9 | import Infernu.InferState (Infer) 10 | import Infernu.Prelude 11 | import Infernu.Types 12 | import Infernu.Expr (EPropName(..)) 13 | 14 | regexMatch :: Type 15 | regexMatch = Fix . TRow (Just "RegexMatch") 16 | -- TODO: instead of quantifying 'this', it should be a recursive type (regexMatch itself) 17 | . TRowProp (TPropGetName EPropGetIndex) (ts [] $ func (tvar 0) number string) 18 | . TRowProp (TPropGetName $ EPropName "index") (ty number) 19 | . TRowProp (TPropGetName $ EPropName "input") (ty string) 20 | $ TRowEnd Nothing 21 | 22 | regexProps :: [(String, TypeScheme)] 23 | regexProps = 24 | [ ("source", ty string) 25 | , ("exec", ty $ func regex string regexMatch) 26 | , ("lastIndex", ty number) 27 | , ("global", ty boolean) 28 | , ("ignoreCase", ty boolean) 29 | , ("multiline", ty boolean) 30 | , ("source", ty string) 31 | , ("test", ty $ func regex string boolean) 32 | ] 33 | 34 | -- TODO: when inserting builtin types, do fresh renaming of scheme qvars 35 | -- TODO: this code is actually pure, refactor to pure function and 'return' wrapper. 36 | regexRowType :: Infer (TRowList Type) 37 | regexRowType = foldM addProp (TRowEnd Nothing) $ regexProps 38 | -------------------------------------------------------------------------------- /src/Infernu/Builtins/String.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TupleSections #-} 2 | module Infernu.Builtins.String 3 | (stringRowType) 4 | where 5 | 6 | import Control.Monad (foldM) 7 | 8 | import Infernu.Builtins.Util 9 | import Infernu.InferState (Infer) 10 | import Infernu.Prelude 11 | import Infernu.Types 12 | import Infernu.Expr (EPropName(..)) 13 | 14 | 15 | stringProps :: [(String, TypeScheme)] 16 | stringProps = 17 | [ ("length", ty number) 18 | , ("charAt", ty $ func string number string) 19 | , ("charCodeAt", ty $ func string number number) 20 | , ("concat", ty $ func string string string) -- TODO: concat really accepty a variable number of arguments 21 | , ("indexOf", ty $ func string string number) -- TODO: optional parameter 22 | , ("lastIndexOf", ty $ func string string number) -- TODO: optional parameter 23 | , ("localeCompare", ty $ func string string number) -- TODO: optional parameters 24 | 25 | -- To support 'match' we need to allow different result types, different for global and non-global 26 | -- regexes. One possibility is to define two regex types RegexSingle and RegexGlobal and use 27 | -- associated types: 28 | -- class Regex r where 29 | -- type R r = r 30 | -- type M RegexLocal = -- match result type for RegexLocal 31 | -- type M RegexGlobal = [String] 32 | -- 33 | -- , ("match", ty $ func string regex 34 | 35 | , ("replace", TScheme [Flex 0] $ withTypeClass "Pattern" (tvar 0) $ funcN [string, tvar 0, string] string) 36 | , ("split", ty $ func string string (array string) ) -- TODO review 37 | ] 38 | 39 | -- TODO: when inserting builtin types, do fresh renaming of scheme qvars 40 | -- TODO: this code is actually pure, refactor to pure function and 'return' wrapper. 41 | stringRowType :: Infer (TRowList Type) 42 | stringRowType = TRowProp (TPropGetName EPropGetIndex) (ty $ func string number string) <$> namedProps 43 | where namedProps = foldM addProp (TRowEnd Nothing) $ stringProps 44 | -------------------------------------------------------------------------------- /src/Infernu/Builtins/StringMap.hs: -------------------------------------------------------------------------------- 1 | module Infernu.Builtins.StringMap where 2 | 3 | import Infernu.Builtins.Util 4 | import Infernu.Prelude 5 | import Infernu.Types 6 | import Infernu.Expr (EPropName(..)) 7 | 8 | stringMapRowType :: Monad m => Type -> m (TRowList Type) 9 | stringMapRowType elemType = return 10 | . TRowProp (TPropGetName EPropSetIndex) (ty $ funcN [aType, string, elemType] undef) 11 | . TRowProp (TPropGetName EPropGetIndex) (ty $ func aType string elemType) 12 | $ TRowEnd Nothing 13 | where aType = Fix $ TCons TStringMap [elemType] 14 | -------------------------------------------------------------------------------- /src/Infernu/Builtins/TypeClasses.hs: -------------------------------------------------------------------------------- 1 | module Infernu.Builtins.TypeClasses 2 | (typeClasses) 3 | where 4 | 5 | import Infernu.Prelude 6 | import Infernu.Types 7 | 8 | typeClasses :: [(ClassName, Class (Fix FType))] 9 | typeClasses = 10 | [ 11 | (ClassName "Pattern", Class { classInstances = 12 | [ schemeEmpty $ Fix $ TBody TRegex 13 | , schemeEmpty $ Fix $ TBody TString 14 | ]}) 15 | , (ClassName "Plus", Class { classInstances = 16 | [ schemeEmpty $ Fix $ TBody TNumber 17 | , schemeEmpty $ Fix $ TBody TString 18 | ]}) 19 | , (ClassName "StringKeys", Class { classInstances = 20 | [ TScheme [Flex 0] $ qualEmpty $ Fix $ TRow Nothing $ TRowEnd $ Just $ RowTVar (Flex 0) 21 | , TScheme [Flex 0] $ qualEmpty $ Fix $ TCons TArray [Fix . TBody . TVar $ Flex 0] 22 | , TScheme [Flex 0] $ qualEmpty $ Fix $ TCons TStringMap [Fix . TBody . TVar $ Flex 0] 23 | ]}) 24 | ] 25 | -------------------------------------------------------------------------------- /src/Infernu/Builtins/Util.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TupleSections #-} 2 | module Infernu.Builtins.Util 3 | where 4 | 5 | import Control.Monad (forM) 6 | 7 | import Infernu.InferState (fresh, Infer) 8 | import Infernu.Lib (safeLookup) 9 | import Infernu.Prelude 10 | import Infernu.Expr (EPropName(..)) 11 | import Infernu.Types 12 | 13 | func :: Type -> Type -> Type -> Type 14 | func this x y = Fix $ TFunc [this, x] y 15 | 16 | funcN :: [Fix FType] -> Fix FType -> Fix FType 17 | funcN xs tres = Fix $ TFunc xs tres 18 | 19 | string :: Type 20 | string = Fix $ TBody TString 21 | 22 | date :: Type 23 | date = Fix $ TBody TDate 24 | 25 | regex :: Type 26 | regex = Fix $ TBody TRegex 27 | 28 | nullT :: Type 29 | nullT = Fix $ TBody TNull 30 | 31 | number :: Type 32 | number = Fix $ TBody TNumber 33 | 34 | array :: Type -> Type 35 | array t = Fix $ TCons TArray [t] 36 | 37 | stringMap :: Type -> Type 38 | stringMap t = Fix $ TCons TStringMap [t] 39 | 40 | boolean :: Fix FType 41 | boolean = Fix $ TBody TBoolean 42 | 43 | undef :: Type 44 | undef = Fix $ TBody TUndefined 45 | 46 | ts :: [Int] -> t -> TScheme t 47 | ts vs t = TScheme (map Flex vs) $ qualEmpty t 48 | 49 | tsq :: [Int] -> TQual t -> TScheme t 50 | tsq vs = TScheme (map Flex vs) 51 | 52 | ty :: t -> TScheme t 53 | ty t = TScheme [] $ qualEmpty t 54 | 55 | tvar :: Int -> Type 56 | tvar = Fix . TBody . TVar . Flex 57 | 58 | withTypeClass :: String -> a -> a -> TQual a 59 | withTypeClass n t t' = TQual { qualPred = [TPredIsIn { predClass = ClassName n, predType = t }], qualType = t' } 60 | 61 | openRow :: Int -> Type 62 | openRow tv = Fix $ TRow Nothing $ TRowEnd $ Just $ RowTVar (Flex tv) 63 | 64 | prop :: String -> TScheme t -> TRowList t -> TRowList t 65 | prop name = TRowProp (TPropGetName $ EPropName name) 66 | 67 | addProp :: VarNames t => TRowList t -> (String, TScheme t) -> Infer (TRowList t) 68 | addProp rowlist (name, propTS) = 69 | do allocNames <- forM (schemeVars propTS) $ \tvName -> (tvName,) . Flex <$> fresh 70 | let ts' = mapVarNames (safeLookup allocNames) propTS 71 | return $ TRowProp (TPropGetName $ EPropName name) ts' rowlist 72 | -------------------------------------------------------------------------------- /src/Infernu/Decycle.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE ScopedTypeVariables #-} 2 | 3 | -- | copied from https://raw.githubusercontent.com/Peaker/lamdu/wip_integration/bottlelib/Data/Function/Decycle.hs 4 | 5 | module Infernu.Decycle(decycleOn, decycle, decycle2, decycle3) where 6 | 7 | import Infernu.Prelude 8 | import qualified Data.Set as Set 9 | 10 | -- | A fix for functions that terminates recursive cycles 11 | decycleOn :: forall a b res . Ord b => (a -> b) -> (Maybe (a -> res) -> a -> res) -> a -> res 12 | decycleOn toOrd f = go Set.empty 13 | where 14 | go :: Set.Set b -> a -> res 15 | go visited x = f (mRecurse visited (toOrd x)) x 16 | 17 | mRecurse :: Set.Set b -> b -> Maybe (a -> res) 18 | mRecurse visited o = if Set.member o visited 19 | then Nothing 20 | else Just $ go (Set.insert o visited) 21 | 22 | decycle :: Ord a => (Maybe (a -> res) -> a -> res) -> a -> res 23 | decycle = decycleOn id 24 | 25 | decycle2 :: (Ord b, Ord a) => (Maybe (a -> b -> c) -> a -> b -> c) -> a -> b -> c 26 | decycle2 f arg1 arg2 = decycle (\recurse (arg1', arg2') -> f (curry <$> recurse) arg1' arg2') (arg1, arg2) 27 | 28 | decycle3 :: (Ord b, Ord a, Ord c) => (Maybe (a -> b -> c -> res) -> a -> b -> c -> res) -> a -> b -> c -> res 29 | decycle3 f arg1 arg2 arg3 = decycle f3 (arg1, arg2, arg3) 30 | where f3 Nothing (x,y,z) = f Nothing x y z 31 | f3 (Just rec3) (x,y,z) = f (Just (\a b c -> rec3 (a,b,c))) x y z 32 | -------------------------------------------------------------------------------- /src/Infernu/Expr.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveFoldable #-} 2 | {-# LANGUAGE DeriveFunctor #-} 3 | {-# LANGUAGE DeriveGeneric #-} 4 | -- | 5 | 6 | module Infernu.Expr 7 | ( Exp(..) 8 | , mapTopAnnotation 9 | , LitVal(..) 10 | , EVarName(..), EPropName(..) 11 | ) where 12 | 13 | import Data.Hashable (Hashable(..)) 14 | import GHC.Generics (Generic) 15 | 16 | import Infernu.Prelude 17 | 18 | 19 | type EVarName = String 20 | data EPropName = EPropName String 21 | | EPropGetIndex 22 | | EPropSetIndex 23 | | EPropFun 24 | deriving (Show, Eq, Ord, Generic) 25 | 26 | instance Hashable EPropName where 27 | 28 | data LitVal = LitNumber !Double 29 | | LitBoolean !Bool 30 | | LitString !String 31 | | LitRegex !String !Bool !Bool 32 | | LitUndefined 33 | | LitNull 34 | | LitEmptyThis 35 | deriving (Show, Eq, Ord) 36 | 37 | data Exp a = EVar !a !EVarName 38 | | EApp !a !(Exp a) ![Exp a] 39 | | EAbs !a ![EVarName] !(Exp a) 40 | | ELet !a !EVarName !(Exp a) !(Exp a) 41 | | ECase !a !(Exp a) ![(LitVal, Exp a)] 42 | | EProp !a !(Exp a) !EPropName 43 | | EPropAssign !a !(Exp a) !EPropName !(Exp a) !(Exp a) 44 | -- TODO consider better options for causing rows to become closed outside the 'new' call 45 | | ENew !a !(Exp a) ![Exp a] 46 | -- Various literal expressions 47 | | ELit !a !LitVal 48 | | EArray !a ![Exp a] 49 | | ETuple !a ![Exp a] 50 | | ERow !a !Bool ![(EPropName, Exp a)] 51 | | EStringMap !a ![(String, Exp a)] 52 | deriving (Show, Eq, Ord, Functor, Foldable) 53 | 54 | ---------------------------------------------------------------------- 55 | 56 | -- TODO: Horrible, terrible boilerplate. get rid of it. 57 | mapTopAnnotation :: (a -> a) -> Exp a -> Exp a 58 | mapTopAnnotation f expr = 59 | case expr of 60 | (EVar a b) -> EVar (f a) b 61 | (EApp a x y) -> EApp (f a) x y 62 | (EAbs a x y) -> EAbs (f a) x y 63 | (ELet a x y z) -> ELet (f a) x y z 64 | (ELit a x) -> ELit (f a) x 65 | (EPropAssign a x y z v) -> EPropAssign (f a) x y z v 66 | (EArray a x) -> EArray (f a) x 67 | (ETuple a x) -> ETuple (f a) x 68 | (ERow a x y) -> ERow (f a) x y 69 | (EStringMap a x) -> EStringMap (f a) x 70 | (ECase a x ys) -> ECase (f a) x ys 71 | (EProp a x y) -> EProp (f a) x y 72 | (ENew a x y) -> ENew (f a) x y 73 | 74 | ---------------------------------------------------------------------- 75 | -------------------------------------------------------------------------------- /src/Infernu/Fix.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE UndecidableInstances #-} 2 | {-# LANGUAGE FlexibleContexts #-} 3 | {-# LANGUAGE StandaloneDeriving #-} 4 | module Infernu.Fix 5 | ( Fix(..) 6 | , fmapReplace 7 | , replaceFix 8 | , fixToList 9 | ) 10 | where 11 | 12 | import Infernu.Prelude 13 | 14 | newtype Fix f = Fix { unFix :: f (Fix f) } 15 | 16 | instance Show (f (Fix f)) => Show (Fix f) where 17 | show (Fix x) = "Fix (" ++ (show x) ++ ")" 18 | deriving instance Eq (f (Fix f)) => Eq (Fix f) 19 | deriving instance Ord (f (Fix f)) => Ord (Fix f) 20 | 21 | fmapReplace :: (Functor f, Eq (f a)) => (f a -> f b -> a -> b) -> f a -> f b -> f a -> f b 22 | fmapReplace recurse tsource tdest t = 23 | if t == tsource 24 | then tdest 25 | else fmap (recurse tsource tdest) t 26 | 27 | replaceFix :: (Functor f, Eq (f (Fix f))) => f (Fix f) -> f (Fix f) -> Fix f -> Fix f 28 | replaceFix tsource tdest (Fix t') = Fix $ fmapReplace replaceFix tsource tdest t' 29 | 30 | 31 | -- | Flattens a fix-type to a list of all tree nodes 32 | -- 33 | -- >>> fixToList $ (Fix $ TCons TArray [Fix $ TCons TArray [Fix $ TBody TNumber]]) 34 | -- [Fix (TCons TArray [Fix (TCons TArray [Fix (TBody TNumber)])]),Fix (TCons TArray [Fix (TBody TNumber)]),Fix (TBody TNumber)] 35 | -- >>> fixToList $ (Fix $ TRow $ TRowProp "x" (TScheme [] $ Fix $ TBody TNumber) (TRowEnd Nothing)) 36 | -- [Fix (TRow (TRowProp "x" (TScheme {schemeVars = [], schemeType = Fix (TBody TNumber)}) (TRowEnd Nothing))),Fix (TBody TNumber)] 37 | fixToList :: Foldable t => Fix t -> [Fix t] 38 | fixToList (Fix t) = (Fix t) : (foldr (\t' b -> fixToList t' ++ b) [] t) 39 | -------------------------------------------------------------------------------- /src/Infernu/Lib.hs: -------------------------------------------------------------------------------- 1 | module Infernu.Lib where 2 | 3 | import Data.Maybe (fromMaybe) 4 | import Data.Map.Strict (Map) 5 | import qualified Data.Map.Strict as Map 6 | import Data.Set (Set) 7 | import qualified Data.Set as Set 8 | 9 | import Infernu.Prelude 10 | 11 | 12 | matchZip :: [a] -> [b] -> Maybe [(a,b)] 13 | matchZip [] [] = Just [] 14 | matchZip (_:_) [] = Nothing 15 | matchZip [] (_:_) = Nothing 16 | matchZip (x:xs) (y:ys) = fmap ((x,y):) $ matchZip xs ys 17 | 18 | safeLookup :: Eq a => [(a,a)] -> a -> a 19 | safeLookup assoc n = fromMaybe n $ lookup n assoc 20 | 21 | -- | Creates an inverse map. Multiple keys mapping to the same values are collected into Sets. 22 | -- 23 | -- >>> flipMap $ Map.fromList [(1,2),(2,2)] 24 | -- fromList [(2,fromList [1,2])] 25 | flipMap :: (Ord k, Ord v) => Map k v -> Map v (Set k) 26 | flipMap m = Map.foldrWithKey (\k v m' -> Map.alter (Just . addKeyToSet' k) v m') Map.empty m 27 | where addKeyToSet' k Nothing = Set.singleton k 28 | addKeyToSet' k (Just s) = Set.insert k s 29 | 30 | splatMap :: Ord k => Map (Set k) a -> Map k a 31 | splatMap m = Map.foldrWithKey (\ks v m' -> foldr (\k m'' -> Map.insert k v m'') m' (Set.toList ks)) Map.empty m 32 | 33 | -------------------------------------------------------------------------------- /src/Infernu/Log.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {-# LANGUAGE TupleSections #-} 3 | {-# LANGUAGE BangPatterns #-} 4 | 5 | module Infernu.Log 6 | (trace, tracePretty, traceLog, traceLogVal) 7 | where 8 | 9 | import Infernu.Prelude 10 | import Text.PrettyPrint.ANSI.Leijen (Pretty(..), (<+>), Doc) 11 | 12 | #if TRACE 13 | import Debug.Trace (trace) 14 | #else 15 | trace :: a -> b -> b 16 | trace _ y = y 17 | #endif 18 | 19 | tracePretty :: Pretty a => Doc -> a -> a 20 | tracePretty prefix x = trace (show $ prefix <+> pretty x) x 21 | 22 | traceLogVal :: Applicative f => String -> a -> f a 23 | traceLogVal !s !r = pure $! trace s r `seq` r 24 | 25 | traceLog :: Applicative f => Doc -> f () 26 | traceLog !s = pure $! trace (show s) () `seq` () 27 | -------------------------------------------------------------------------------- /src/Infernu/Options.hs: -------------------------------------------------------------------------------- 1 | module Infernu.Options 2 | (Options(..), defaultOptions, opts) 3 | where 4 | 5 | import Infernu.Prelude 6 | 7 | import Options.Applicative 8 | 9 | data Options = Options 10 | { optQuiet :: Bool 11 | , optShowCore :: Bool 12 | , optShowParsed :: Bool 13 | , optFileNames :: [String] 14 | } 15 | 16 | defaultOptions :: Options 17 | defaultOptions = Options { optQuiet = False, optShowCore = False, optShowParsed = False, optFileNames = [] } 18 | 19 | opts :: ParserInfo Options 20 | opts = info (helper <*> parseOpts) 21 | ( fullDesc 22 | <> progDesc "Infer types in the given JavaScript FILES and check for type errors. Unless -q is given, the source annotated with type signatures is outputted." 23 | <> header "infernu - static type checker for JavaScript using full type inference" ) 24 | 25 | parseOpts :: Parser Options 26 | parseOpts = Options 27 | <$> switch (long "quiet" 28 | <> short 'q' 29 | <> help "Report only errors; don't output the annotated source with inferred types") 30 | <*> switch (long "dump-translation" 31 | <> help "Dump internal translation (used for debugging infernu)" ) 32 | <*> switch (long "dump-parsed" 33 | <> help "Dump parsed JS syntax tree (used for debugging infernu)" ) 34 | <*> some (argument str (metavar "FILES...")) 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/Infernu/Prelude.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | 3 | -- | The sole purpose of this module is to fix pre/post ghc 7.10 compatibility issues 4 | module Infernu.Prelude 5 | ( module Prelude.Compat 6 | , bool 7 | ) 8 | where 9 | 10 | import Prelude.Compat 11 | import Data.Orphans () 12 | 13 | #if MIN_VERSION_base(4,7,0) 14 | import Data.Bool (bool) 15 | #else 16 | 17 | bool :: a -> a -> Bool -> a 18 | bool f _ False = f 19 | bool _ t True = t 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /src/Infernu/Pretty.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleInstances #-} 2 | {-# OPTIONS_GHC -fno-warn-orphans #-} 3 | module Infernu.Pretty where 4 | 5 | 6 | 7 | import Infernu.Prelude 8 | import Infernu.Expr 9 | import Infernu.Source 10 | import Infernu.Types 11 | import qualified Infernu.Builtins.Names as Names 12 | 13 | import Data.Char (chr, ord) 14 | import qualified Data.Digits as Digits 15 | import qualified Data.List as List 16 | import Data.Map.Strict (Map) 17 | import qualified Data.Map.Strict as Map 18 | 19 | import qualified Data.Set as Set 20 | import qualified Text.Parsec.Pos as Pos 21 | 22 | import Text.PrettyPrint.ANSI.Leijen hiding ((<$>)) 23 | 24 | -- tab :: Int -> String 25 | -- tab t = replicate (t*4) ' ' 26 | 27 | -- class Pretty a where 28 | -- prettyTab :: Int -> a -> String 29 | 30 | -- instance Pretty a => Pretty (Maybe a) where 31 | -- pretty x = maybe "Nothing" pretty x 32 | 33 | -- instance (Pretty a, Pretty b) => Pretty (a,b) where 34 | -- pretty (a,b) = "(" ++ pretty a ++ ", " ++ pretty b ++ ")" 35 | 36 | -- instance (Pretty a, Pretty b, Pretty c) => Pretty (a,b,c) where 37 | -- pretty (a,b,c) = "(" ++ pretty a ++ ", " ++ pretty b ++ ", " ++ pretty c ++ ")" 38 | 39 | -- prettyList :: Pretty a => [a] -> String 40 | -- prettyList [] = "[]" 41 | -- prettyList xs = "[" ++ intercalate "," (map pretty xs) ++ "]" 42 | 43 | -- instance Pretty [String] where 44 | -- pretty = prettyList 45 | 46 | -- instance Pretty [Type] where 47 | -- pretty = prettyList 48 | 49 | -- instance (Pretty a, Pretty b) => Pretty [(a,b)] where 50 | -- pretty xs = "[" ++ intercalate "," (map pretty xs) ++ "]" 51 | 52 | -- pretty :: Pretty a => a -> String 53 | -- pretty = prettyTab 0 54 | 55 | 56 | 57 | instance Pretty Pos.SourcePos where 58 | pretty p = string (Pos.sourceName p) <> string ":" <> (string . show $ Pos.sourceLine p) <> string ":" <> (string . show $ Pos.sourceColumn p) 59 | 60 | ifStr :: Bool -> String -> Doc 61 | ifStr b s = if b then string s else empty 62 | 63 | instance Pretty Source where 64 | pretty (Source (genInfo, pos)) = 65 | ifStr (isGen genInfo) "*" 66 | <> pretty pos 67 | <> maybe empty (\x -> string ":" <> string x <> string ":") (declName genInfo) 68 | 69 | 70 | instance Pretty LitVal where 71 | pretty (LitNumber x) = pretty x 72 | pretty (LitBoolean x) = pretty x 73 | pretty (LitString x) = pretty x 74 | pretty (LitRegex x g i) = enclose (string "/") (string "/") (string x) <> (ifStr g "g") <> (ifStr i "i") 75 | pretty LitUndefined = string "undefined" 76 | pretty LitNull = string "null" 77 | pretty LitEmptyThis = string "(undefined 'this')" 78 | 79 | nakedSingleOrTuple :: Pretty a => [a] -> Doc 80 | nakedSingleOrTuple [] = string "()" 81 | nakedSingleOrTuple [x] = pretty x 82 | nakedSingleOrTuple xs = tupled $ map pretty xs 83 | 84 | instance Pretty EPropName where 85 | pretty (EPropName x) = string x 86 | pretty EPropGetIndex = string "=[]" 87 | pretty EPropSetIndex = string "[]=" 88 | pretty EPropFun = string "call()" 89 | 90 | instance Pretty (Exp a) where 91 | pretty (EVar _ n) = string n 92 | pretty (EApp _ (EVar _ n) [arg1,arg2]) | n == Names.refAssignOp = parens $ pretty arg1 <+> string ":=" <+> pretty arg2 93 | pretty (EApp _ e1 args) = parens $ pretty e1 <+> nakedSingleOrTuple args 94 | pretty (EAbs _ args e) = parens $ string "\\" <> nakedSingleOrTuple args <+> string "->" <+> pretty e 95 | pretty (ELet _ n e1 e2) = string "let" <+> align (vsep $ letLine n e1 e2) 96 | where letLine n' e' eBody = curLine n' e' : rest eBody 97 | curLine n' e' = pretty n' <+> string "=" <+> pretty e' 98 | rest eBody = case eBody of 99 | ELet _ nb e1b e2b -> letLine nb e1b e2b 100 | _ -> [string "in" <+> align (pretty eBody)] 101 | 102 | pretty (ELit _ l) = pretty l 103 | pretty (EPropAssign _ obj n e1 e2) = align $ vsep [ pretty obj <> dot <> pretty n <+> string ":=" <+> pretty e1 <> string ";" 104 | , pretty e2] 105 | pretty (EArray _ es) = encloseSep lbracket rbracket comma $ map pretty es 106 | pretty (ETuple _ es) = pretty es 107 | pretty (ERow _ _ props) = encloseSep lbrace rbrace comma 108 | $ map (\(n,v) -> fill 6 (pretty n) <> string ":" <> space <> pretty v) props 109 | -- <> ifStr isOpen " | ? " 110 | pretty (ECase _ ep es) = hang 4 $ 111 | vsep [ string "case" <+> pretty ep <+> string "of" 112 | , hang 4 $ align (vsep (map formatBranch' es)) 113 | ] 114 | where formatBranch' (pat, branch) = fill 6 (pretty pat) <+> string "->" <+> align (pretty branch) 115 | pretty (EProp _ e n) = pretty e <> dot <> pretty n 116 | pretty (ENew _ e args) = string "new" <> space <> pretty e <> space <> nakedSingleOrTuple (map pretty args) 117 | pretty (EStringMap _ exprs) = encloseSep langle rangle comma $ map (\(n,v) -> pretty n <+> string "=>" <+> pretty v) exprs 118 | 119 | 120 | ----------------------------------------------------------------------------- 121 | 122 | toChr :: Int -> Char 123 | toChr n = chr (ord 'a' + (n - 1)) 124 | 125 | ptv :: Int -> String 126 | ptv x = foldr ((++) . (:[]) . toChr) [] (Digits.digits 26 (x + 1)) 127 | 128 | colorFuncs :: [Doc -> Doc] 129 | colorFuncs = [ red 130 | , green 131 | , yellow 132 | , blue 133 | , magenta 134 | , cyan 135 | , white 136 | ] 137 | 138 | colorBy :: Int -> Doc -> Doc 139 | colorBy n = colorFuncs!!(n `mod` length colorFuncs) 140 | 141 | -- | 142 | -- >>> pretty (0 :: TVarName) 143 | -- "a" 144 | -- >>> pretty (26 :: TVarName) 145 | -- "aa" 146 | instance Pretty TVarName where 147 | pretty tvn = bold $ case tvn of 148 | Flex n -> colorBy n $ string $ ptv n 149 | Skolem n -> colorBy n $ string $ '!' : ptv n 150 | 151 | -- instance Pretty Bool where 152 | -- prettyTab _ x = show x 153 | 154 | instance Pretty TypeId where 155 | pretty (TypeId n) = dullred $ text $ 'R' : ptv n 156 | 157 | instance Pretty TBody where 158 | pretty (TVar n) = pretty n 159 | pretty x = bold $ text $ case show x of 160 | 'T':xs -> xs 161 | xs -> xs 162 | 163 | instance Pretty TConsName where 164 | pretty = dullgreen . text . show 165 | 166 | instance Pretty RowTVar where 167 | pretty t = dullblue $ pretty (getRowTVar t) 168 | 169 | instance Show t => Pretty (FlatRowEnd t) where 170 | pretty = text . show 171 | 172 | instance Pretty Type where 173 | pretty = prettyType . unFix 174 | 175 | instance Pretty (FType Type) where 176 | pretty = prettyType 177 | 178 | prettyType :: FType Type -> Doc 179 | prettyType (TBody t) = pretty t 180 | prettyType (TFunc ts tres) = wrapThis this $ parens $ args <+> string "->" <+> pretty tres 181 | where nonThisArgs = map pretty . drop 1 $ ts 182 | (this, args) = case ts of 183 | [] -> (Nothing, nakedSingleOrTuple nonThisArgs) 184 | (this_:_) -> (Just this_, nakedSingleOrTuple nonThisArgs) 185 | wrapThis Nothing s = s 186 | wrapThis (Just (Fix (TBody TUndefined))) s = s 187 | wrapThis (Just (Fix (TBody TEmptyThis))) s = s 188 | -- if "this" is a recursive type, only show the recursive type name (no params) - this is "lossy" 189 | wrapThis (Just (Fix (TCons (TName name) _))) s = pretty name <> dot <> s 190 | wrapThis (Just (Fix (TBody (TVar n)))) s | not (n `Set.member` (freeTypeVars $ drop 1 ts)) = s 191 | wrapThis (Just t) s = pretty t <> dot <> s 192 | -- prettyTab _ (TCons TFunc ts) = error $ "Malformed TFunc: " ++ intercalate ", " (map pretty ts) 193 | prettyType (TCons TArray [t]) = brackets $ pretty t 194 | prettyType (TCons TTuple ts) = pretty ts 195 | prettyType (TCons (TName name) ts) = angles $ pretty name <> colon <+> hsep (map pretty ts) 196 | prettyType (TCons TStringMap [t]) = text "StringMap " <+> pretty t 197 | prettyType (TCons TRef [t]) = text "Mut" <+> pretty t 198 | prettyType (TCons tcn ts) = error $ "Malformed TCons: " ++ show (pretty tcn <+> pretty ts) 199 | prettyType (TRow label rl) = 200 | hsep [ case label of 201 | Just l' -> string l' <> string "=" 202 | Nothing -> empty 203 | , encloseSep (string "{ ") space (string ", ") body' 204 | <> case r of 205 | FlatRowEndTVar r' -> maybe empty ((text "|" <+>) . pretty) r' 206 | FlatRowEndRec tid ts -> comma <+> indent 4 (pretty (Fix $ TCons (TName tid) ts)) -- TODO 207 | <> rbrace 208 | ] 209 | where (props, r) = flattenRow rl 210 | isGet (TPropGetName _) = True 211 | isGet _ = False 212 | isSet (TPropSetName _) = True 213 | isSet _ = False 214 | 215 | propKeysByName = map (\ps -> let name = tpropName . fst $ head ps 216 | keys = map fst ps 217 | in (name, snd $ head ps, (any isGet keys, any isSet keys))) 218 | $ List.groupBy (\(x,xv) (y,yv) -> tpropName x == tpropName y && xv == yv) $ Map.toList props 219 | printProp' (n,v,getSet) = pn <> string ":" <+> align (pretty v) 220 | where pn = case getSet of 221 | (True, True) -> pretty n 222 | (True, False) -> string "get" <+> pretty n 223 | (False, True) -> string "set" <+> pretty n 224 | _ -> error "Expected at least one of get or set" 225 | body' = map printProp' propKeysByName 226 | 227 | instance Pretty TProp where 228 | pretty (TPropSetName n) = text "set" <+> pretty n 229 | pretty (TPropGetName n) = text "get" <+> pretty n 230 | 231 | instance Pretty ClassName where 232 | pretty (ClassName c) = text c 233 | 234 | instance (Pretty t) => Pretty (TPred t) where 235 | pretty (TPredIsIn cn t) = pretty cn <+> pretty t 236 | 237 | instance (VarNames t, Pretty t) => Pretty (TQual t) where 238 | pretty (TQual [] t) = pretty t 239 | pretty (TQual preds t) = pretty preds <+> pretty "=>" <+> align (pretty t) 240 | 241 | instance (Ord t, VarNames t, Pretty t) => Pretty (TScheme t) where 242 | pretty (TScheme vars t) = forall <> align (pretty t) 243 | where forall = if null vars then empty else text "forall" <+> hsep (map pretty vars) <> pretty "." <> space 244 | 245 | -- instance (Pretty a, Pretty b) => Pretty (Either a b) where 246 | -- prettyTab n (Left x) = "Error: " ++ prettyTab n x 247 | -- prettyTab n (Right x) = prettyTab n x 248 | 249 | instance (Pretty k, Pretty v) => Pretty (Map k v) where 250 | pretty s = string "Map" <+> encloseSep lbrace rbrace comma (map (\(k,v) -> pretty k <+> pretty "=>" <+> pretty v) $ Map.toList s) 251 | 252 | instance (Pretty k) => Pretty (Set.Set k) where 253 | pretty s = string "Set" <+> encloseSep lbrace rbrace comma (map pretty $ Set.toList s) 254 | 255 | -- instance Pretty GenInfo where 256 | -- prettyTab _ g = show g 257 | 258 | instance Pretty TypeError where 259 | pretty (TypeError s m) = pretty s <> text ": Error:" <+> line <+> indent 4 (pretty m) 260 | 261 | instance Pretty NameSource where 262 | pretty = string . show 263 | 264 | instance Pretty VarId where 265 | pretty = string . show 266 | 267 | instance (Ord t, VarNames t, Pretty t) => Pretty (Class t) where 268 | pretty c = braces $ string "instances = " <+> pretty (classInstances c) 269 | 270 | instance Pretty InferState where 271 | pretty (InferState ns sub vs tn cs pu) = 272 | text "InferState" 273 | <+> (align . encloseSep lbrace rbrace comma 274 | $ [ fill 10 (string "nameSource: ") <+> pretty ns 275 | , fill 10 (string "subst: ") <+> pretty sub 276 | , fill 10 (string "varSchemes: ") <+> pretty vs 277 | , fill 10 (string "namedTypes: ") <+> pretty tn 278 | , fill 10 (string "pendingUni: ") <+> pretty pu 279 | , fill 10 (string "classes: ") <+> pretty cs 280 | ]) 281 | -------------------------------------------------------------------------------- /src/Infernu/Source.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | 3 | module Infernu.Source 4 | ( GenInfo(..) 5 | , Source(..) 6 | , emptySource 7 | , TypeError(..) 8 | ) where 9 | 10 | import qualified Text.Parsec.Pos as Pos 11 | import Text.PrettyPrint.ANSI.Leijen (Doc) 12 | 13 | import Infernu.Prelude 14 | 15 | data GenInfo = GenInfo { isGen :: Bool, declName :: Maybe String } 16 | deriving (Show, Eq, Ord) 17 | 18 | newtype Source = Source (GenInfo, Pos.SourcePos) 19 | deriving (Show, Eq, Ord) 20 | 21 | emptySource :: Source 22 | emptySource = Source (GenInfo True Nothing, Pos.initialPos "") 23 | 24 | data TypeError = TypeError { source :: Source, message :: Doc } 25 | deriving (Show) 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Infernu/Util.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | module Infernu.Util (checkFiles, annotatedSource, checkSource) where 3 | 4 | import Control.Monad (forM, when) 5 | import Data.Maybe (catMaybes) 6 | import Data.List (intercalate) 7 | import qualified Data.Set as Set 8 | import qualified Language.ECMAScript3.Parser as ES3Parser 9 | import qualified Language.ECMAScript3.PrettyPrint as ES3Pretty 10 | import qualified Language.ECMAScript3.Syntax as ES3 11 | import qualified Text.Parsec.Pos as Pos 12 | import Text.PrettyPrint.ANSI.Leijen (Pretty (..), (<+>), string, renderPretty, Doc, displayS) 13 | 14 | import Infernu.Prelude 15 | import Infernu.Options (Options(..)) 16 | import Infernu.Parse (translate) 17 | -- TODO move pretty stuff to Pretty module 18 | import Infernu.Infer (getAnnotations, minifyVars, runTypeInference) 19 | import Infernu.Types (QualType) 20 | import Infernu.Source (GenInfo(..), Source(..), TypeError(..)) 21 | 22 | zipByPos :: [(Pos.SourcePos, String)] -> [(Int, String)] -> [String] 23 | zipByPos [] xs = map snd xs 24 | zipByPos _ [] = [] 25 | zipByPos ps'@((pos, s):ps) xs'@((i,x):xs) = if Pos.sourceLine pos == i 26 | then formattedAnnotation : zipByPos ps xs' 27 | else x : zipByPos ps' xs 28 | where indentToColumn n = replicate (n-1) ' ' 29 | isMultiline = length sLines > 1 30 | sLines = lines s 31 | formattedAnnotation = if isMultiline 32 | then ("/*" 33 | ++ indentToColumn (Pos.sourceColumn pos - 2) 34 | ++ head sLines 35 | ++ "\n" 36 | ++ (intercalate "\n" . map (\l -> indentToColumn (Pos.sourceColumn pos) ++ l) $ tail sLines) ++ " */") 37 | else "//" ++ indentToColumn (Pos.sourceColumn pos - 2) ++ s 38 | 39 | 40 | indexList :: [a] -> [(Int, a)] 41 | indexList = zip [1..] 42 | 43 | 44 | checkSource :: String -> Either TypeError [(Source, QualType)] 45 | checkSource src = case ES3Parser.parseFromString src of 46 | Left parseError -> Left $ TypeError { source = Source (GenInfo True Nothing, Pos.initialPos ""), message = string (show parseError) } 47 | Right expr -> -- case ES3.isValid expr of 48 | -- False -> Left $ TypeError { source = Source (GenInfo True, Pos.initialPos ""), message = "Invalid syntax" } 49 | -- True -> 50 | fmap getAnnotations $ fmap minifyVars $ runTypeInference $ fmap Source $ translate $ ES3.unJavaScript expr 51 | 52 | checkFiles :: Options -> [String] -> IO (Either TypeError [(Source, QualType)]) 53 | checkFiles options fileNames = do 54 | expr <- concatMap ES3.unJavaScript <$> forM fileNames ES3Parser.parseFromFile 55 | when (optShowParsed options) $ putStrLn $ show $ ES3Pretty.prettyPrint expr 56 | let expr' = fmap Source $ translate $ expr 57 | when (optShowCore options) $ putStrLn $ show $ pretty expr' 58 | let expr'' = fmap minifyVars $ runTypeInference expr' 59 | res = fmap getAnnotations expr'' 60 | return res 61 | 62 | showWidth :: Int -> Doc -> String 63 | showWidth w x = displayS (renderPretty 0.4 w x) "" 64 | 65 | showDoc :: Doc -> String 66 | showDoc = showWidth 120 67 | 68 | annotatedSource :: [(Source, QualType)] -> [String] -> String 69 | annotatedSource xs sourceCode = unlines $ zipByPos (prettyRes $ unGenInfo $ filterGen xs) indexedSource 70 | where indexedSource = indexList sourceCode 71 | unGenInfo :: [(Source, QualType)] -> [(String, Pos.SourcePos, QualType)] 72 | unGenInfo = catMaybes . map (\(Source (g, s), q) -> fmap (\n -> (n, s, q)) $ declName g) 73 | filterGen :: [(Source, QualType)] -> [(Source, QualType)] 74 | filterGen = filter (\(Source (g, _), _) -> not . isGen $ g) 75 | prettyRes = Set.toList . Set.fromList . fmap (\(n, s, q) -> (s, showDoc $ pretty n <+> string ":" <+> pretty q)) 76 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | flags: {} 2 | packages: 3 | - '.' 4 | extra-deps: 5 | resolver: lts-6.27 6 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | .psci_modules/ 2 | -------------------------------------------------------------------------------- /test/Demo.hs: -------------------------------------------------------------------------------- 1 | module Demo where 2 | 3 | import Data.List (intercalate) 4 | 5 | import Text.PrettyPrint.ANSI.Leijen (Pretty (..)) 6 | import Infernu.Util (checkFiles) 7 | import Infernu.Options (defaultOptions) 8 | import Infernu.Prelude 9 | 10 | import System.Environment (getArgs) 11 | 12 | isRight :: Either a b -> Bool 13 | isRight (Right _) = True 14 | isRight _ = False 15 | 16 | main :: IO () 17 | main = do 18 | args <- getArgs 19 | let [shouldPassS, fileName] = args 20 | res <- fmap last <$> checkFiles defaultOptions [fileName] 21 | let shouldPass = if shouldPassS == "y" then id else not 22 | typeChecked = isRight res 23 | message = case res of 24 | Left e -> show $ pretty e 25 | Right _ -> "" 26 | toOk = bool "FAIL" "OK" . shouldPass 27 | --print $ fmap (pretty . snd) res 28 | putStrLn $ "// " ++ toOk typeChecked ++ " " ++ (intercalate " | " $ lines message) 29 | -------------------------------------------------------------------------------- /test/Test.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TemplateHaskell, TypeSynonymInstances, FlexibleInstances #-} 2 | module Test where 3 | 4 | import Infernu.Types 5 | 6 | main :: IO () 7 | main = do 8 | res <- runAllTests 9 | print res 10 | return () 11 | 12 | -------------------------------------------------------------------------------- /test/fail.txt: -------------------------------------------------------------------------------- 1 | // FAIL valid/arguably/if-undefined.js:8:3: Error: | Failed unif y valid/arguably/if-undefined.js 2 | // FAIL valid/nothis-as-obj-param.js:10:7: Error: | Failed unify y valid/nothis-as-obj-param.js 3 | // FAIL valid/nothis-as-param.js:8:7: Error: | Failed unifying: y valid/nothis-as-param.js 4 | // FAIL valid/objlit-func.js:9:1: Error: | Failed unifying: | y valid/objlit-func.js 5 | // FAIL valid/poly-method-recursive-this.js:18:17: Error: | Fail y valid/poly-method-recursive-this.js 6 | -------------------------------------------------------------------------------- /test/invalid/.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | [include] 4 | -------------------------------------------------------------------------------- /test/invalid/array-assign-in-func.js: -------------------------------------------------------------------------------- 1 | function f(arr, x) { 2 | arr[0] = x; 3 | } 4 | f; 5 | var a = [1]; 6 | f(a, 's'); 7 | -------------------------------------------------------------------------------- /test/invalid/array-assign.js: -------------------------------------------------------------------------------- 1 | var arr = []; 2 | arr[0] = 1; 3 | arr[1] = 'a'; 4 | -------------------------------------------------------------------------------- /test/invalid/array-idx.js: -------------------------------------------------------------------------------- 1 | var x = [0,1,2]; 2 | var y = ['a','b']; 3 | 4 | var z = [x[0], y[0]]; 5 | -------------------------------------------------------------------------------- /test/invalid/array-param.js: -------------------------------------------------------------------------------- 1 | function f(x) { 2 | x.stuff = 2; 3 | return x; 4 | } 5 | var arr = []; 6 | f(arr[0]); 7 | arr[0].muff = 3; 8 | arr[0] = { muff: 123 }; // should require also .stuff = to work. 9 | return arr; 10 | -------------------------------------------------------------------------------- /test/invalid/array.js: -------------------------------------------------------------------------------- 1 | var x = [1, 2, 'a']; 2 | -------------------------------------------------------------------------------- /test/invalid/blo.js: -------------------------------------------------------------------------------- 1 | function f(x) { 2 | var len = x.length; 3 | return { obj: x, len: len }; 4 | } 5 | var y = f({ moshe: 3, length: 'a' }); 6 | var z = f({ length: 2 }); 7 | 8 | var g = y.len; 9 | g = z.obj.length; 10 | g; 11 | -------------------------------------------------------------------------------- /test/invalid/blo2.js: -------------------------------------------------------------------------------- 1 | var x = 2; 2 | var y = { obj: { bla: x }, blo: x}; 3 | y.obj.bla = 2; 4 | y.blo = 'a'; 5 | y; 6 | -------------------------------------------------------------------------------- /test/invalid/builtins/Math.js: -------------------------------------------------------------------------------- 1 | Math.E = 0; 2 | -------------------------------------------------------------------------------- /test/invalid/builtins/binary-not.js: -------------------------------------------------------------------------------- 1 | var x = ~false; 2 | -------------------------------------------------------------------------------- /test/invalid/builtins/equality.js: -------------------------------------------------------------------------------- 1 | var x = 2 == 'a'; 2 | -------------------------------------------------------------------------------- /test/invalid/builtins/isFinite.js: -------------------------------------------------------------------------------- 1 | var x = isFinite('a'); 2 | -------------------------------------------------------------------------------- /test/invalid/builtins/logical-not.js: -------------------------------------------------------------------------------- 1 | var x = !2; 2 | -------------------------------------------------------------------------------- /test/invalid/builtins/minus.js: -------------------------------------------------------------------------------- 1 | var x = true - 4; 2 | -------------------------------------------------------------------------------- /test/invalid/builtins/minusminus.js: -------------------------------------------------------------------------------- 1 | var x = false; 2 | var y = x--; 3 | -------------------------------------------------------------------------------- /test/invalid/builtins/or.js: -------------------------------------------------------------------------------- 1 | var y = '' || 2; 2 | -------------------------------------------------------------------------------- /test/invalid/builtins/parse.js: -------------------------------------------------------------------------------- 1 | var x = parseFloat(3.0); 2 | 3 | 4 | -------------------------------------------------------------------------------- /test/invalid/builtins/parseInt.js: -------------------------------------------------------------------------------- 1 | var y = parseInt('3'); // base always required 2 | -------------------------------------------------------------------------------- /test/invalid/builtins/plus-func.js: -------------------------------------------------------------------------------- 1 | function f(x,y) { return x + y; } 2 | 3 | f; 4 | 5 | 6 | f(false,false); 7 | 8 | -------------------------------------------------------------------------------- /test/invalid/builtins/plus.js: -------------------------------------------------------------------------------- 1 | var x = 2 + false; 2 | -------------------------------------------------------------------------------- /test/invalid/builtins/plusplus.js: -------------------------------------------------------------------------------- 1 | var x = false; 2 | var y = x++; 3 | -------------------------------------------------------------------------------- /test/invalid/builtins/regex-exec.js: -------------------------------------------------------------------------------- 1 | var r = /123/g; 2 | 3 | var m = r.exec('12'); 4 | 5 | 6 | var x = 0; 7 | x = m.index; 8 | 9 | var s = ''; 10 | s = m[0]; 11 | s = m.input; 12 | 13 | m[0] = 'a'; 14 | 15 | -------------------------------------------------------------------------------- /test/invalid/builtins/string-error.js: -------------------------------------------------------------------------------- 1 | function f(o) { return o.moo; } 2 | f('hi'); 3 | -------------------------------------------------------------------------------- /test/invalid/builtins/string-replace.js: -------------------------------------------------------------------------------- 1 | function f(p, r) { return 'hi'.replace(p, r); } 2 | var g = function(p, r) { return 'hi'.replace(p, r); }; 3 | var s = 'abc'.replace('junk', 'punk'); 4 | var t = 'abd'.replace(/bd/, 'pink'); 5 | f(2,'a'); 6 | 7 | -------------------------------------------------------------------------------- /test/invalid/builtins/string_constr.js: -------------------------------------------------------------------------------- 1 | var Foo = String; 2 | var x = new Foo('b'); 3 | -------------------------------------------------------------------------------- /test/invalid/builtins/typeof.js: -------------------------------------------------------------------------------- 1 | var x = typeof 3; 2 | x = 2; 3 | 4 | -------------------------------------------------------------------------------- /test/invalid/builtins/uri.js: -------------------------------------------------------------------------------- 1 | decodeURI('bla') == 1; 2 | decodeURIComponent(2) == 'bla'; 3 | encodeURI(); 4 | encodeURIComponent('bla') == 3; 5 | -------------------------------------------------------------------------------- /test/invalid/call2.js: -------------------------------------------------------------------------------- 1 | function f(x) { x = 0; return x(2); } 2 | -------------------------------------------------------------------------------- /test/invalid/cond.js: -------------------------------------------------------------------------------- 1 | var x = false ? false : 0; 2 | x; 3 | -------------------------------------------------------------------------------- /test/invalid/debounce-simplified.js: -------------------------------------------------------------------------------- 1 | function debounce(f) { 2 | var scheduledArgs = []; 3 | return function(arg) { 4 | scheduledArgs = [arg]; 5 | return { 6 | invoke: function() { 7 | f(scheduledArgs[0]); 8 | } 9 | }; 10 | }; 11 | } 12 | var fonce = debounce(function (x) { return x[0]; }); 13 | fonce(false); 14 | //return debounce; 15 | -------------------------------------------------------------------------------- /test/invalid/debounce.js: -------------------------------------------------------------------------------- 1 | var $timeout = { 2 | set: function(f, num) { num = 0; return 0; }, 3 | cancel: function(t) { t=0; } 4 | }; 5 | 6 | function debounce(f, millis) { 7 | var timer = 0; 8 | var timerSet = false; 9 | var scheduledArgs = []; 10 | function resetTimeout() { 11 | if (timerSet) { 12 | $timeout.cancel(timer); 13 | } 14 | timerSet = true; 15 | timer = $timeout.set(function() { 16 | // Note: f may end up running more than once per args set. 17 | f(scheduledArgs[0]);//.apply(null, scheduledArgs); 18 | }, millis); 19 | return; 20 | } 21 | return function(arg) { 22 | scheduledArgs = [arg]; //Array.prototype.slice.call(arguments, 0); 23 | return { 24 | invoke: function() { 25 | resetTimeout(); 26 | } 27 | }; 28 | }; 29 | } 30 | var fonce = debounce(function (x) { return x[0]; }, 0); 31 | fonce(false); 32 | //return debounce; 33 | -------------------------------------------------------------------------------- /test/invalid/dowhile.js: -------------------------------------------------------------------------------- 1 | do { 1; } while (2) 2 | -------------------------------------------------------------------------------- /test/invalid/fix-usage.js: -------------------------------------------------------------------------------- 1 | function fix(f) { return f(fix(f)); } 2 | 3 | var g = fix(function (x) { var y = 0; y = x; return 'a'; }); 4 | g; 5 | -------------------------------------------------------------------------------- /test/invalid/for-1.js: -------------------------------------------------------------------------------- 1 | var z = false; 2 | var i = 0; 3 | for (i = 2; i; i = 3) { 4 | z = i; 5 | } 6 | -------------------------------------------------------------------------------- /test/invalid/for-2.js: -------------------------------------------------------------------------------- 1 | var z = 0; 2 | for (i = 2; i; i = 3) { 3 | z = i; 4 | } 5 | -------------------------------------------------------------------------------- /test/invalid/for-in.js: -------------------------------------------------------------------------------- 1 | var arr = ['a','b']; 2 | 3 | var i = 0; 4 | for (i in arr) { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /test/invalid/for.js: -------------------------------------------------------------------------------- 1 | var i = 0; 2 | for (i = 2; i; i = 3) { 3 | 3; 4 | } 5 | -------------------------------------------------------------------------------- /test/invalid/func-as-row.js: -------------------------------------------------------------------------------- 1 | function f(g) { return g.length; } 2 | f(function(x) { return x;}); 3 | 4 | -------------------------------------------------------------------------------- /test/invalid/func-no-currying.js: -------------------------------------------------------------------------------- 1 | var f = function (x) { 2 | return function (y) { 3 | return [x,y]; 4 | }; 5 | }; 6 | function g(x, y) { 7 | return [x, y]; 8 | }; 9 | var z = [f(0), g(0)]; 10 | z; 11 | -------------------------------------------------------------------------------- /test/invalid/generalize.js: -------------------------------------------------------------------------------- 1 | var x = 0; 2 | 3 | var f = function(a) { a = x; return a; }; 4 | 5 | 6 | var obj = { method: function(a) { return a; } }; 7 | 8 | obj.method = function(a) { a = x; return a; }; 9 | -------------------------------------------------------------------------------- /test/invalid/getput.js: -------------------------------------------------------------------------------- 1 | function getput(_) { 2 | var x = []; 3 | return function(y) { var old = x; x = y; return old; }; 4 | }; 5 | 6 | var gp = getput(false); 7 | gp([1]); 8 | gp([false]); 9 | gp; 10 | -------------------------------------------------------------------------------- /test/invalid/if.js: -------------------------------------------------------------------------------- 1 | if (2) { 1; } else { false; } 2 | -------------------------------------------------------------------------------- /test/invalid/infer-from-call.js: -------------------------------------------------------------------------------- 1 | function f(o) { o.momo(2); } 2 | f({momo: 'baboon'}); // should fail, momo is not a function 3 | -------------------------------------------------------------------------------- /test/invalid/infinite.js: -------------------------------------------------------------------------------- 1 | function f(x) { return x(x); } 2 | -------------------------------------------------------------------------------- /test/invalid/infinite2.js: -------------------------------------------------------------------------------- 1 | var f = function(x) { return function(z) { return f(x); }; }; 2 | f 3 | -------------------------------------------------------------------------------- /test/invalid/inheritance.js: -------------------------------------------------------------------------------- 1 | function Base() { 2 | this.x = 0; 3 | } 4 | 5 | function Sub() { 6 | this.y = 'b'; 7 | } 8 | 9 | var a = new Sub(); 10 | a.x = 2; 11 | a.y = 'c'; 12 | -------------------------------------------------------------------------------- /test/invalid/method-type.js: -------------------------------------------------------------------------------- 1 | function test(o) { return o.method(2); } 2 | test; 3 | var y = test({method: function(x) { return x; }}); 4 | y='a'; 5 | -------------------------------------------------------------------------------- /test/invalid/mutability/bug.js: -------------------------------------------------------------------------------- 1 | var x = function(a) { return a; }; 2 | function setX(v) { x = v; return false; } 3 | setX(function (a) { return 'a'; }); 4 | setX; 5 | x = function(a) { return 2; }; 6 | -------------------------------------------------------------------------------- /test/invalid/mutability/bug2.js: -------------------------------------------------------------------------------- 1 | var x = function (a) { return a; }; 2 | x = function (a) { return false; }; 3 | x; 4 | x('a'); 5 | -------------------------------------------------------------------------------- /test/invalid/mutability/bug3.js: -------------------------------------------------------------------------------- 1 | var x = function (a) { return a; }; 2 | var y = x('bla'); 3 | x = function (b) { return false; }; // should cause an error, because x was already used as a string -> string 4 | x; 5 | 6 | -------------------------------------------------------------------------------- /test/invalid/mutability/bug5.js: -------------------------------------------------------------------------------- 1 | var x = function (a) { return a; }; // forall a. a -> a | c -> c | (String -> String) 2 | var getX = function (v1) { return x; }; // forall b a. b -> (a -> a) | forall b. b -> (c -> c) | forall b. b -> (String -> String) 3 | var setX = function (v2) { x = v2; return true; }; // x is mutable => | (c -> c) -> Bool | (String -> String) -> Bool 4 | setX(function(a2) { return 'a'; }); // | c => String 5 | getX(false)(false); 6 | 7 | -------------------------------------------------------------------------------- /test/invalid/mutability/bug6.js: -------------------------------------------------------------------------------- 1 | var x = []; 2 | var getX = function (v1) { return x; }; // forall b. X = forall b X(a). X(a) 3 | var setX = function (v2) { x = v2; return true; }; // X -> Bool 4 | setX([1]); // X ~ forall c. c -> String ==> forall a. a -> a ~ forall c. c -> String ==> a=c, a=String ==> X = String -> String 5 | getX(false); 6 | setX([false]); 7 | 8 | -------------------------------------------------------------------------------- /test/invalid/mutability/bug7.js: -------------------------------------------------------------------------------- 1 | function makeX(_) { 2 | var x = []; 3 | var setX = function (v2) { x = [v2]; return true; }; 4 | return setX; 5 | } 6 | var mx1 = makeX(0); 7 | mx1(0); 8 | mx1(false); 9 | var mx2 = makeX(false); 10 | mx2(false); 11 | mx1; 12 | -------------------------------------------------------------------------------- /test/invalid/mutability/mutable-id.js: -------------------------------------------------------------------------------- 1 | var x = function(z) { return z; }; 2 | var z = x(true); 3 | var y = x('a'); 4 | x = function(z1) { return z1; }; 5 | x; 6 | -------------------------------------------------------------------------------- /test/invalid/naked_array.js: -------------------------------------------------------------------------------- 1 | 2 | function test() { 3 | var result = []; 4 | result[0] = 2; 5 | return result[0]; 6 | } 7 | 8 | var s = 'a'; 9 | s = test(); 10 | -------------------------------------------------------------------------------- /test/invalid/new-override.js: -------------------------------------------------------------------------------- 1 | function Constr(x) { this.bla = x; } 2 | var constr = new Constr(2); 3 | constr.bla = 'a'; 4 | -------------------------------------------------------------------------------- /test/invalid/new-return.js: -------------------------------------------------------------------------------- 1 | function Constr(x) { this.bla = x; return 2; } 2 | var constr = new Constr(2); 3 | constr.another = 2; 4 | 5 | -------------------------------------------------------------------------------- /test/invalid/new.js: -------------------------------------------------------------------------------- 1 | function Constr(x) { this.bla = x; } 2 | var constr = new Constr(2); 3 | constr.another = 2; 4 | 5 | -------------------------------------------------------------------------------- /test/invalid/not-a-method.js: -------------------------------------------------------------------------------- 1 | function func() { } 2 | 3 | var obj = {}; 4 | 5 | obj.func(); // should fail. 6 | -------------------------------------------------------------------------------- /test/invalid/num.js: -------------------------------------------------------------------------------- 1 | function f(x) { var y = 0; y = x[0]; return false; } 2 | var j = ['b']; 3 | f(j); 4 | j; 5 | -------------------------------------------------------------------------------- /test/invalid/object-chaining.js: -------------------------------------------------------------------------------- 1 | function Obj(y) { 2 | this.x = y; 3 | this.doIt = function(x) { this.x = x; return this; }; 4 | } 5 | var o = new Obj(2); 6 | var o2 = o.doIt(2); 7 | var o3 = o2.doIt('a'); 8 | -------------------------------------------------------------------------------- /test/invalid/occurs-erisco.js: -------------------------------------------------------------------------------- 1 | var f = function (x) { 2 | x.y = function (z) { return f({y:z}); }; 3 | return x; 4 | }; 5 | 6 | f({ y : function (x){ 7 | var z = 2; 8 | z = x; 9 | return x;} 10 | }).y(0); 11 | -------------------------------------------------------------------------------- /test/invalid/occurs.js: -------------------------------------------------------------------------------- 1 | //[a] -> [a] 2 | var f = function (x) { 3 | // [a, z -> [z]] 4 | var bla = [x[0], function (z) { 5 | return f([z]); }]; 6 | return x; 7 | }; 8 | f; 9 | 10 | f([false]); 11 | 12 | -------------------------------------------------------------------------------- /test/invalid/occurs2.js: -------------------------------------------------------------------------------- 1 | var f = function (x) { 2 | return function (z) { return f(z); }; 3 | }; 4 | -------------------------------------------------------------------------------- /test/invalid/open-row-erisco.js: -------------------------------------------------------------------------------- 1 | function open(o) { 2 | o.x = 0; 3 | return o; 4 | } 5 | var y = open({x:0}); 6 | y.z = 3; 7 | y; 8 | -------------------------------------------------------------------------------- /test/invalid/open-row.js: -------------------------------------------------------------------------------- 1 | var o = {bla: 1}; 2 | o.blo = 2; 3 | o; 4 | -------------------------------------------------------------------------------- /test/invalid/polyfunc.js: -------------------------------------------------------------------------------- 1 | var f = function(x) { return function(y) { return y[x]; }; }; 2 | var num = f([3])([2]); 3 | f; 4 | 5 | -------------------------------------------------------------------------------- /test/invalid/polyfunc2.js: -------------------------------------------------------------------------------- 1 | var f = function(x, y) { return function(z) { x = z; return y; }; }; 2 | f(1, 'a')(['b']); 3 | f; 4 | 5 | -------------------------------------------------------------------------------- /test/invalid/polyfunc3.js: -------------------------------------------------------------------------------- 1 | function debounce(f) { 2 | var timer = 0; 3 | var timerSet = false; 4 | var scheduledArgs = []; 5 | function resetTimeout() { 6 | if (timerSet) { 7 | } 8 | timerSet = true; 9 | function g() { 10 | // Note: f may end up running more than once per args set. 11 | f(scheduledArgs[0]);//.apply(null, scheduledArgs); 12 | } 13 | return g; 14 | } 15 | return function(arg) { 16 | scheduledArgs = [arg]; //Array.prototype.slice.call(arguments, 0); 17 | return { 18 | invoke: function() { 19 | resetTimeout(); 20 | } 21 | }; 22 | }; 23 | } 24 | var fonce = debounce(function (x) { return x[0]; }, 0); 25 | fonce('a'); 26 | //return debounce; 27 | -------------------------------------------------------------------------------- /test/invalid/promise-method-workaround-simplified.js: -------------------------------------------------------------------------------- 1 | // Based on https://gist.github.com/unscriptable/814052 - (c) copyright unscriptable.com / John Hann, License MIT 2 | 3 | function Promise () { 4 | var _thens = []; 5 | var that = this; 6 | 7 | this.then = function (onResolve, onReject) { 8 | _thens.push({ resolve: onResolve, reject: onReject }); 9 | }; 10 | 11 | var resolve = function (val) { 12 | var errorFunc = function(val) { throw new Error('Already completed.'); }; 13 | var t3 = that; 14 | that.then = function(resolve, reject) { resolve(val); }; 15 | that.resolve = errorFunc; 16 | var r = _thens[0].resolve; 17 | r(val); 18 | _thens = []; 19 | }; 20 | 21 | var t1 = resolve; 22 | this.resolve = resolve; 23 | var t2 = this; 24 | }; 25 | 26 | var promise = new Promise(); 27 | 28 | var str = 'a'; 29 | 30 | promise.then(function(x) {str =x; }, function (ex) { }); 31 | 32 | promise.resolve(3); // error; 3 is not a string 33 | 34 | -------------------------------------------------------------------------------- /test/invalid/promise-method-workaround.js: -------------------------------------------------------------------------------- 1 | // Based on https://gist.github.com/unscriptable/814052 - (c) copyright unscriptable.com / John Hann, License MIT 2 | 3 | function Promise () { 4 | var _thens = []; 5 | var that = this; 6 | /* This is the "front end" API. */ 7 | 8 | // then(onResolve, onReject): Code waiting for this promise uses the 9 | // then() method to be notified when the promise is complete. There 10 | // are two completion callbacks: onReject and onResolve. A more 11 | // robust promise implementation will also have an onProgress handler. 12 | this.then = function (onResolve, onReject) { 13 | // capture calls to then() 14 | _thens.push({ resolve: onResolve, reject: onReject }); 15 | }; 16 | 17 | /* This is the "back end" API. */ 18 | 19 | // resolve(resolvedValue): The resolve() method is called when a promise 20 | // is resolved (duh). The resolved value (if any) is passed by the resolver 21 | // to this method. All waiting onResolve callbacks are called 22 | // and any future ones are, too, each being passed the resolved value. 23 | this.resolve = function (val) { 24 | var errorFunc = function(val) { throw new Error('Already completed.'); }; 25 | var i = 0; 26 | that.then = function(resolve, reject) { resolve(val); }; 27 | that.resolve = errorFunc; 28 | that.reject = errorFunc; 29 | for (i = 0; i < _thens.length; i++) { 30 | var r = _thens[i].resolve; 31 | r(val); 32 | } 33 | _thens = []; 34 | }; 35 | 36 | // reject(exception): The reject() method is called when a promise cannot 37 | // be resolved. Typically, you'd pass an exception as the single parameter, 38 | // but any other argument, including none at all, is acceptable. 39 | // All waiting and all future onReject callbacks are called when reject() 40 | // is called and are passed the exception parameter. 41 | this.reject = function (ex) { 42 | var errorFunc = function(val) { throw new Error('Already completed.'); }; 43 | var i = 0; 44 | that.then = function(resolve, reject) { reject(ex); }; 45 | that.resolve = errorFunc; 46 | that.reject = errorFunc; 47 | for (i = 0; i < _thens.length; i++) { 48 | var r = _thens[i].reject; 49 | r(ex); 50 | } 51 | _thens = []; 52 | }; 53 | 54 | }; 55 | 56 | var promise = new Promise(); 57 | 58 | var str = 'a'; 59 | 60 | promise.then(function(x) {str =x; }, function (ex) { }); 61 | 62 | promise.resolve(3); // error; 3 is not a string 63 | 64 | -------------------------------------------------------------------------------- /test/invalid/promise-simple-api.js: -------------------------------------------------------------------------------- 1 | function Promise() { 2 | var thens = []; 3 | var that = this; 4 | that.then = function(cb) { 5 | var p = new Promise(); 6 | thens.push(function(val) { p.resolve(cb(val)); }); 7 | return p; 8 | }; 9 | that.resolve = function(val) { 10 | var i = 0; 11 | for (i = 0; i < thens.length; i++) { 12 | thens[i](val); 13 | } 14 | that.then = function(cb) { 15 | var p = new Promise(); 16 | p.resolve(cb(val)); 17 | return p; 18 | }; 19 | }; 20 | }; 21 | 22 | 23 | var p = new Promise(); 24 | p.kaki = 2; 25 | -------------------------------------------------------------------------------- /test/invalid/prop-polymorphic-assign.js: -------------------------------------------------------------------------------- 1 | var o2 = { method: function(x) { return x; }, jank: [] }; 2 | 3 | o2.jank[0] = 3; 4 | o2.jank[1] = 'a'; 5 | -------------------------------------------------------------------------------- /test/invalid/prop-polymorphic-assign2-b.js: -------------------------------------------------------------------------------- 1 | var o2 = { method: function(x) { return x; } }; 2 | 3 | var x = o2.method('a'); 4 | 5 | // requires o2.method to become more specific (Number -> Number) 6 | // should not work. 7 | o2.method = function (x) { x = 2; return x; }; 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/invalid/prop-polymorphic-assign2-this.js: -------------------------------------------------------------------------------- 1 | var o2 = { method: function(x) { return x; } }; 2 | 3 | // Should fail because it's less general than forall a. a -> a 4 | o2.method = function (x) { x = this; return x; }; 5 | 6 | -------------------------------------------------------------------------------- /test/invalid/prop-polymorphic-assign2.js: -------------------------------------------------------------------------------- 1 | var o2 = { method: function(x) { return x; } }; 2 | 3 | // causes o2.method to become more specific (Number -> Number) 4 | // and so should fail 5 | o2.method = function (x) { x = 2; return x; }; 6 | 7 | -------------------------------------------------------------------------------- /test/invalid/prop-polymorphic-assign3-b.js: -------------------------------------------------------------------------------- 1 | var obj = { prop : function(x) { return x; } }; var barr = [function(x) { return x; }]; barr[0](2); obj.prop = barr[0]; 2 | -------------------------------------------------------------------------------- /test/invalid/prop-polymorphic-assign3.js: -------------------------------------------------------------------------------- 1 | var obj = { prop : function(x) { return x; } }; var barr = [function(x) { return x; }]; obj.prop = barr[0]; barr[0](2); 2 | -------------------------------------------------------------------------------- /test/invalid/prop-polymorphic-closure.js: -------------------------------------------------------------------------------- 1 | var o2 = { y: [], setY: function(x) { this.y = [x]; }, getY: function() { return this.y; } }; 2 | 3 | o2.setY(2); 4 | 5 | var z = 'string'; 6 | 7 | z = o2.getY(); 8 | -------------------------------------------------------------------------------- /test/invalid/prop-polymorphic-closure2.js: -------------------------------------------------------------------------------- 1 | var o2 = { 2 | last: function() { 3 | var y = []; 4 | return function(x2) { 5 | var z = y; 6 | y = [x2]; 7 | return z; 8 | }; 9 | } 10 | }; 11 | 12 | o2; 13 | 14 | 15 | var f = o2.last(); 16 | 17 | f('a'); 18 | 19 | f(3); 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/invalid/prop-wrong.js: -------------------------------------------------------------------------------- 1 | function setX(obj, val) { 2 | obj.x = val; 3 | } 4 | 5 | setX({ notX: 2 }, 3); 6 | -------------------------------------------------------------------------------- /test/invalid/prop_assign.js: -------------------------------------------------------------------------------- 1 | function setX(obj, val) { 2 | obj.x = val; 3 | } 4 | 5 | var z = { x: 3 }; 6 | setX(z, 'a'); 7 | z; 8 | -------------------------------------------------------------------------------- /test/invalid/rank-2-method-b.js: -------------------------------------------------------------------------------- 1 | var obj = { bla: function(x) {return x; } }; 2 | obj = { bla: function(x) {return 2; } }; 3 | var x = 'a'; 4 | x = obj.bla('b'); // returns 2, incompatible with 'a' 5 | 6 | -------------------------------------------------------------------------------- /test/invalid/rank-2-method-simple.js: -------------------------------------------------------------------------------- 1 | var x = { ap: function(f) { f(); } }; 2 | 3 | var y = []; 4 | x.ap(function() {y.push('a');}); 5 | x.ap(function() {y.push(3);}); 6 | -------------------------------------------------------------------------------- /test/invalid/rank-2-method.js: -------------------------------------------------------------------------------- 1 | // node doesn't like _ 2 | var _u = { 3 | each: function(list, iteratee) { 4 | var i = 0; 5 | for (i = 0; i < list.length; i++) { 6 | iteratee(list[i], i, list); 7 | } 8 | return list; 9 | } 10 | }; 11 | 12 | 13 | 14 | var p = []; 15 | 16 | function test(f) { 17 | _u.each([1,2,3], f); 18 | } 19 | 20 | 21 | _u.each([1,2,3], function(e,i,l) { p.push(e + 'aba'); }); 22 | 23 | p; 24 | -------------------------------------------------------------------------------- /test/invalid/recursive-type.js: -------------------------------------------------------------------------------- 1 | var getObj = function (x) { 2 | return { 3 | x: x, 4 | method: function(y) { this.x = y; return this; } 5 | }; 6 | }; 7 | var x = getObj(2).method(3); 8 | var y = getObj('a').method('a'); 9 | var z = x == y; // should fail. checker should enforce weak equality to only be applied when both sides have the same type. 10 | 11 | -------------------------------------------------------------------------------- /test/invalid/return-multi.js: -------------------------------------------------------------------------------- 1 | function f() { 2 | var g = 'bla'; 3 | if (false) { 4 | return 2; 5 | } 6 | return 3; 7 | g = 'momo'; 8 | return g; 9 | } 10 | 11 | var x = f(); 12 | var y = 2; 13 | y = x; 14 | -------------------------------------------------------------------------------- /test/invalid/row-func.js: -------------------------------------------------------------------------------- 1 | function f(obj) { 2 | var x = obj.stuff; 3 | return 2; 4 | }; 5 | f({}); 6 | -------------------------------------------------------------------------------- /test/invalid/row-return.js: -------------------------------------------------------------------------------- 1 | function f(parent) { 2 | return parent.child; 3 | } 4 | var x = f({ child: { grandChild: false } }); 5 | x.smurf; 6 | -------------------------------------------------------------------------------- /test/invalid/row1.js: -------------------------------------------------------------------------------- 1 | function f(x) { 2 | return x.c; 3 | } 4 | //f({a:2, c:3}); 5 | //f({b:true, c:3}); 6 | f({a:3}); 7 | // var x = { a: 2, b: 'a' }; 8 | // x = { a:3, b:'b',c:0 }; 9 | // x.c; 10 | -------------------------------------------------------------------------------- /test/invalid/stringmap-preds.js: -------------------------------------------------------------------------------- 1 | var m = { 'hi': function(x,y) { return x + y; }, 2 | 'baoo': function(x,y) { return x; } 3 | }; 4 | 5 | m['hi'](false, true); 6 | -------------------------------------------------------------------------------- /test/invalid/switch-1.js: -------------------------------------------------------------------------------- 1 | switch (3) { 2 | case 'a': 'a'; 3 | case 'b': 'b'; 4 | defualt: null; 5 | } 6 | 7 | -------------------------------------------------------------------------------- /test/invalid/switch-2.js: -------------------------------------------------------------------------------- 1 | switch (3) { 2 | case 2: 'a'; 3 | case 'b': 'b'; 4 | defualt: null; 5 | } 6 | 7 | -------------------------------------------------------------------------------- /test/invalid/this-func.js: -------------------------------------------------------------------------------- 1 | function f(x) { 2 | this.doStuff(x); 3 | return x; 4 | } 5 | var bla={ f:f, doStuff: 789}; 6 | bla.f(2); // should fail, doStuff isn't a function. 7 | -------------------------------------------------------------------------------- /test/invalid/this.js: -------------------------------------------------------------------------------- 1 | var obj = { name: '3', getName: function() { return this.name; } }; 2 | obj; 3 | var x = obj.getName(); 4 | x; 5 | var f = obj.getName; 6 | f(); 7 | -------------------------------------------------------------------------------- /test/invalid/undefined.js: -------------------------------------------------------------------------------- 1 | function getUndefined() {} 2 | var x = 3; 3 | x = getUndefined(); 4 | -------------------------------------------------------------------------------- /test/invalid/unfortunately/inheritance.js: -------------------------------------------------------------------------------- 1 | function Base() { 2 | this.x = 0; 3 | } 4 | 5 | function Sub() { 6 | Base.call(this); 7 | 8 | this.y = 'b'; 9 | } 10 | 11 | var a = new Sub(); 12 | a.x = 2; 13 | a.y = 'c'; 14 | -------------------------------------------------------------------------------- /test/invalid/unfortunately/mauke.js: -------------------------------------------------------------------------------- 1 | var a = "x"; 2 | function foo(x) { 3 | var e; 4 | if (false) { e = "equal"; } 5 | else { e = "not equal"; } 6 | return x; 7 | } 8 | foo("x"); 9 | a = 42; 10 | foo(0); 11 | -------------------------------------------------------------------------------- /test/invalid/unfortunately/new-recursive-prototype.js: -------------------------------------------------------------------------------- 1 | function Foo() { 2 | 3 | } 4 | 5 | Foo.prototype.bar = function() { return new Foo(); }; 6 | 7 | 8 | var x = new Foo(); 9 | 10 | var y = x.bar(); 11 | -------------------------------------------------------------------------------- /test/invalid/unfortunately/polymorphic-row-fields/prop-polymorphic-assign.js: -------------------------------------------------------------------------------- 1 | var o2 = { method: function(x) { return x; } }; 2 | 3 | // Should succeed because it's more general than forall a. (this: a, a) -> a 4 | o2.method = function (x) { return x; }; 5 | 6 | -------------------------------------------------------------------------------- /test/invalid/unfortunately/polymorphic-row-fields/prop-polymorphic-assign2-this.js: -------------------------------------------------------------------------------- 1 | var o2 = { method: function(x) { x = this; return x; } }; 2 | 3 | // Should succeed because it's more general than forall a. (this: a, a) -> a 4 | o2.method = function (x) { return x; }; 5 | 6 | -------------------------------------------------------------------------------- /test/invalid/unfortunately/polymorphic-row-fields/prop-polymorphic-assign3.js: -------------------------------------------------------------------------------- 1 | var o2 = { method: function(x) { x = 2; return x; } }; 2 | 3 | function f(x) { return x; }; 4 | 5 | // Should succeed because it's more general than Number -> Number 6 | o2.method = f; 7 | 8 | -------------------------------------------------------------------------------- /test/invalid/value_restriction_apply.js: -------------------------------------------------------------------------------- 1 | function getId(_) { return function (x) { return x; }; } 2 | var id1 = getId(false); 3 | id1(2); 4 | id1('a'); 5 | id1; 6 | -------------------------------------------------------------------------------- /test/invalid/var-recursive-nonfunc.js: -------------------------------------------------------------------------------- 1 | var x = [[0], [x]]; 2 | x; // should fail, because a. it's an infinite type, and b. in javascript the value of [x] above will be [undefined] 3 | -------------------------------------------------------------------------------- /test/invalid/vartest.js: -------------------------------------------------------------------------------- 1 | var x = false; 2 | x = 'a'; 3 | 4 | -------------------------------------------------------------------------------- /test/invalid/while.js: -------------------------------------------------------------------------------- 1 | while (2) { 1; } 2 | -------------------------------------------------------------------------------- /test/invalid/y.js: -------------------------------------------------------------------------------- 1 | function h(f) { 2 | var g = function(x) { 3 | f(x(x)); 4 | }; 5 | return g(g); 6 | } 7 | -------------------------------------------------------------------------------- /test/makebig.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | main = do 4 | putStrLn "var a0=0;" 5 | mapM_ (\x -> putStrLn $ "var a"++show x++"=1+a"++show (x-1)++";") [1..2000] 6 | 7 | -------------------------------------------------------------------------------- /test/num.js: -------------------------------------------------------------------------------- 1 | function f(x) { var y = 0; y = x[0]; return 'a'; } 2 | var j = []; 3 | f(j); 4 | //j=[]; 5 | return j; 6 | -------------------------------------------------------------------------------- /test/purescript/Assert.js: -------------------------------------------------------------------------------- 1 | /* global exports */ 2 | "use strict"; 3 | 4 | function Error(x) { this.name = ''; this.message = x; } 5 | var console = { log: function(s) { } }; 6 | 7 | // module Assert 8 | function module(exports) { 9 | 10 | exports.error = function(msg) { 11 | throw msg; 12 | }; 13 | 14 | exports.assertPartial = function(f) { 15 | return function() { 16 | try { 17 | return f(); 18 | } catch (e) { 19 | if (e instanceof Error) return; 20 | throw new Error('Pattern match failure is not Error'); 21 | } 22 | }; 23 | }; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /test/purescript/Control.Monad.Eff.js: -------------------------------------------------------------------------------- 1 | /* global exports */ 2 | "use strict"; 3 | 4 | // module Control.Monad.Eff 5 | function module(exports) { 6 | 7 | exports.returnE = function (a) { 8 | return function () { 9 | return a; 10 | }; 11 | }; 12 | 13 | exports.bindE = function (a) { 14 | return function (f) { 15 | return function () { 16 | return f(a())(); 17 | }; 18 | }; 19 | }; 20 | 21 | exports.runPure = function (f) { 22 | return f(); 23 | }; 24 | 25 | exports.untilE = function (f) { 26 | return function () { 27 | while (!f()); 28 | return {}; 29 | }; 30 | }; 31 | 32 | exports.whileE = function (f) { 33 | return function (a) { 34 | return function () { 35 | while (f()) { 36 | a(); 37 | } 38 | return {}; 39 | }; 40 | }; 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /test/purescript/Control.Monad.ST.js: -------------------------------------------------------------------------------- 1 | /* global exports */ 2 | "use strict"; 3 | 4 | // module Control.Monad.ST 5 | function module(exports) { 6 | 7 | exports.newSTRef = function (val) { 8 | return function () { 9 | return { value: val }; 10 | }; 11 | }; 12 | 13 | exports.readSTRef = function (ref) { 14 | return function () { 15 | return ref.value; 16 | }; 17 | }; 18 | 19 | exports.modifySTRef = function (ref) { 20 | return function (f) { 21 | return function () { 22 | /* jshint boss: true */ 23 | return ref.value = f(ref.value); 24 | }; 25 | }; 26 | }; 27 | 28 | exports.writeSTRef = function (ref) { 29 | return function (a) { 30 | return function () { 31 | /* jshint boss: true */ 32 | return ref.value = a; 33 | }; 34 | }; 35 | }; 36 | 37 | exports.runST = function (f) { 38 | return f; 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /test/purescript/Data.Function.js: -------------------------------------------------------------------------------- 1 | /* global exports */ 2 | "use strict"; 3 | 4 | // module Data.Function 5 | function module(exports) { 6 | 7 | exports.mkFn0 = function (fn) { 8 | return function () { 9 | return fn({}); 10 | }; 11 | }; 12 | 13 | exports.mkFn1 = function (fn) { 14 | return function (a) { 15 | return fn(a); 16 | }; 17 | }; 18 | 19 | exports.mkFn2 = function (fn) { 20 | /* jshint maxparams: 2 */ 21 | return function (a, b) { 22 | return fn(a)(b); 23 | }; 24 | }; 25 | 26 | exports.mkFn3 = function (fn) { 27 | /* jshint maxparams: 3 */ 28 | return function (a, b, c) { 29 | return fn(a)(b)(c); 30 | }; 31 | }; 32 | 33 | exports.mkFn4 = function (fn) { 34 | /* jshint maxparams: 4 */ 35 | return function (a, b, c, d) { 36 | return fn(a)(b)(c)(d); 37 | }; 38 | }; 39 | 40 | exports.mkFn5 = function (fn) { 41 | /* jshint maxparams: 5 */ 42 | return function (a, b, c, d, e) { 43 | return fn(a)(b)(c)(d)(e); 44 | }; 45 | }; 46 | 47 | exports.mkFn6 = function (fn) { 48 | /* jshint maxparams: 6 */ 49 | return function (a, b, c, d, e, f) { 50 | return fn(a)(b)(c)(d)(e)(f); 51 | }; 52 | }; 53 | 54 | exports.mkFn7 = function (fn) { 55 | /* jshint maxparams: 7 */ 56 | return function (a, b, c, d, e, f, g) { 57 | return fn(a)(b)(c)(d)(e)(f)(g); 58 | }; 59 | }; 60 | 61 | exports.mkFn8 = function (fn) { 62 | /* jshint maxparams: 8 */ 63 | return function (a, b, c, d, e, f, g, h) { 64 | return fn(a)(b)(c)(d)(e)(f)(g)(h); 65 | }; 66 | }; 67 | 68 | exports.mkFn9 = function (fn) { 69 | /* jshint maxparams: 9 */ 70 | return function (a, b, c, d, e, f, g, h, i) { 71 | return fn(a)(b)(c)(d)(e)(f)(g)(h)(i); 72 | }; 73 | }; 74 | 75 | exports.mkFn10 = function (fn) { 76 | /* jshint maxparams: 10 */ 77 | return function (a, b, c, d, e, f, g, h, i, j) { 78 | return fn(a)(b)(c)(d)(e)(f)(g)(h)(i)(j); 79 | }; 80 | }; 81 | 82 | exports.runFn0 = function (fn) { 83 | return fn(); 84 | }; 85 | 86 | exports.runFn1 = function (fn) { 87 | return function (a) { 88 | return fn(a); 89 | }; 90 | }; 91 | 92 | exports.runFn2 = function (fn) { 93 | return function (a) { 94 | return function (b) { 95 | return fn(a, b); 96 | }; 97 | }; 98 | }; 99 | 100 | exports.runFn3 = function (fn) { 101 | return function (a) { 102 | return function (b) { 103 | return function (c) { 104 | return fn(a, b, c); 105 | }; 106 | }; 107 | }; 108 | }; 109 | 110 | exports.runFn4 = function (fn) { 111 | return function (a) { 112 | return function (b) { 113 | return function (c) { 114 | return function (d) { 115 | return fn(a, b, c, d); 116 | }; 117 | }; 118 | }; 119 | }; 120 | }; 121 | 122 | exports.runFn5 = function (fn) { 123 | return function (a) { 124 | return function (b) { 125 | return function (c) { 126 | return function (d) { 127 | return function (e) { 128 | return fn(a, b, c, d, e); 129 | }; 130 | }; 131 | }; 132 | }; 133 | }; 134 | }; 135 | 136 | exports.runFn6 = function (fn) { 137 | return function (a) { 138 | return function (b) { 139 | return function (c) { 140 | return function (d) { 141 | return function (e) { 142 | return function (f) { 143 | return fn(a, b, c, d, e, f); 144 | }; 145 | }; 146 | }; 147 | }; 148 | }; 149 | }; 150 | }; 151 | 152 | exports.runFn7 = function (fn) { 153 | return function (a) { 154 | return function (b) { 155 | return function (c) { 156 | return function (d) { 157 | return function (e) { 158 | return function (f) { 159 | return function (g) { 160 | return fn(a, b, c, d, e, f, g); 161 | }; 162 | }; 163 | }; 164 | }; 165 | }; 166 | }; 167 | }; 168 | }; 169 | 170 | exports.runFn8 = function (fn) { 171 | return function (a) { 172 | return function (b) { 173 | return function (c) { 174 | return function (d) { 175 | return function (e) { 176 | return function (f) { 177 | return function (g) { 178 | return function (h) { 179 | return fn(a, b, c, d, e, f, g, h); 180 | }; 181 | }; 182 | }; 183 | }; 184 | }; 185 | }; 186 | }; 187 | }; 188 | }; 189 | 190 | exports.runFn9 = function (fn) { 191 | return function (a) { 192 | return function (b) { 193 | return function (c) { 194 | return function (d) { 195 | return function (e) { 196 | return function (f) { 197 | return function (g) { 198 | return function (h) { 199 | return function (i) { 200 | return fn(a, b, c, d, e, f, g, h, i); 201 | }; 202 | }; 203 | }; 204 | }; 205 | }; 206 | }; 207 | }; 208 | }; 209 | }; 210 | }; 211 | 212 | exports.runFn10 = function (fn) { 213 | return function (a) { 214 | return function (b) { 215 | return function (c) { 216 | return function (d) { 217 | return function (e) { 218 | return function (f) { 219 | return function (g) { 220 | return function (h) { 221 | return function (i) { 222 | return function (j) { 223 | return fn(a, b, c, d, e, f, g, h, i, j); 224 | }; 225 | }; 226 | }; 227 | }; 228 | }; 229 | }; 230 | }; 231 | }; 232 | }; 233 | }; 234 | }; 235 | } 236 | -------------------------------------------------------------------------------- /test/purescript/Debug.Trace.js: -------------------------------------------------------------------------------- 1 | /* global exports */ 2 | "use strict"; 3 | 4 | // module Debug.Trace 5 | function module(exports) { 6 | 7 | exports.trace = function(s) { 8 | return function() { 9 | console.log(s); 10 | return {}; 11 | }; 12 | }; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /test/purescript/Prelude.Unsafe.js: -------------------------------------------------------------------------------- 1 | /* global exports */ 2 | "use strict"; 3 | 4 | // module Prelude.Unsafe 5 | function module(exports) { 6 | 7 | exports.unsafeIndex = function(xs) { 8 | return function(n) { 9 | return xs[n]; 10 | }; 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /test/purescript/Prelude.js: -------------------------------------------------------------------------------- 1 | /* global exports */ 2 | "use strict"; 3 | var JSON={stringify: function(x) { return ''; } }; 4 | function prelude(exports) { 5 | // module Prelude 6 | 7 | exports.cons = function(e) { 8 | return function(l) { 9 | return [e].concat(l); 10 | }; 11 | }; 12 | 13 | exports.concat = function(l1) { 14 | return function(l2) { 15 | return l1.concat(l2); 16 | }; 17 | }; 18 | 19 | exports.length = function(a) { 20 | return a.length; 21 | }; 22 | 23 | exports.concatString = function(s1) { 24 | return function(s2) { 25 | return s1 + s2; 26 | }; 27 | }; 28 | 29 | exports.numAdd = function (n1) { 30 | return function (n2) { 31 | return n1 + n2; 32 | }; 33 | }; 34 | 35 | exports.numMul = function (n1) { 36 | return function (n2) { 37 | return n1 * n2; 38 | }; 39 | }; 40 | 41 | exports.numDiv = function (n1) { 42 | return function (n2) { 43 | return n1 / n2; 44 | }; 45 | }; 46 | 47 | exports.numSub = function (n1) { 48 | return function (n2) { 49 | return n1 - n2; 50 | }; 51 | }; 52 | 53 | exports.jsMod = function(x) { 54 | return function (y) { 55 | return x % y; 56 | }; 57 | }; 58 | 59 | exports.refEq = function(r1) { 60 | return function(r2) { 61 | return r1 === r2; 62 | }; 63 | }; 64 | 65 | exports.refIneq = function(r1) { 66 | return function(r2) { 67 | return r1 !== r2; 68 | }; 69 | }; 70 | 71 | exports.eqArrayImpl = function(f) { 72 | return function(xs) { 73 | return function(ys) { 74 | if (xs.length !== ys.length) return false; 75 | for (var i = 0; i < xs.length; i++) { 76 | if (!f(xs[i])(ys[i])) return false; 77 | } 78 | return true; 79 | }; 80 | }; 81 | }; 82 | 83 | exports.ordArrayImpl = function(f) { 84 | return function (xs) { 85 | return function (ys) { 86 | var i = 0; 87 | while (i < xs.length && i < ys.length) { 88 | var x = xs[i]; 89 | var y = ys[i]; 90 | var o = f(x)(y); 91 | if (o !== 0) { 92 | return o; 93 | } 94 | } 95 | if (xs.length == ys.length) { 96 | return 0; 97 | } else if (xs.length > ys.length) { 98 | return 1; 99 | } else { 100 | return -1; 101 | } 102 | }; 103 | }; 104 | }; 105 | 106 | exports.unsafeCompareImpl = function(lt) { 107 | return function(eq) { 108 | return function(gt) { 109 | return function(x) { 110 | return function(y) { 111 | return x < y ? lt : x > y ? gt : eq; 112 | }; 113 | }; 114 | }; 115 | }; 116 | }; 117 | 118 | exports.boolOr = function (b1) { 119 | return function (b2) { 120 | return b1 || b2; 121 | }; 122 | }; 123 | 124 | exports.boolAnd = function (b1) { 125 | return function (b2) { 126 | return b1 && b2; 127 | }; 128 | }; 129 | 130 | exports.boolNot = function (b) { 131 | return !b; 132 | }; 133 | 134 | exports.showNumberImpl = function (n) { 135 | /* jshint bitwise: false */ 136 | return String(n) + (n === (n | 0) ? ".0" : ""); 137 | }; 138 | 139 | exports.showStringImpl = function (s) { 140 | return JSON.stringify(s); 141 | }; 142 | 143 | exports.showArrayImpl = function (f) { 144 | return function (xs) { 145 | var ss = []; 146 | for (var i = 0, l = xs.length; i < l; i++) { 147 | ss[i] = f(xs[i]); 148 | } 149 | return "[" + ss.join(",") + "]"; 150 | }; 151 | }; 152 | return exports; 153 | } 154 | -------------------------------------------------------------------------------- /test/regexlike-exec-2.js: -------------------------------------------------------------------------------- 1 | function getGroup(regex, str, idx) { 2 | return regex.exec(str)[idx]; 3 | } 4 | 5 | var myRegex = { exec: function(s) { return [123]; } }; 6 | var result = getGroup(myRegex, 'hi', 2); 7 | 8 | -------------------------------------------------------------------------------- /test/runall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | set -u 4 | find valid -type f -iname "*.js" -print0 | xargs -0 -n1 ./runtest.sh y | sort 5 | find invalid -type f -iname "*.js" -print0 | xargs -0 -n1 ./runtest.sh n | sort 6 | >&2 echo "" 7 | 8 | -------------------------------------------------------------------------------- /test/runtest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | set -u 4 | SCRIPT_DIR=$(dirname $(readlink -f $0)) 5 | cd $SCRIPT_DIR 6 | ARGS="$@" 7 | #echo $ARGS 8 | >&2 printf "." 9 | ((timeout 3 stack exec infernu-demo $ARGS) || echo "ERROR") 2>&1 | tail -1 | cut -b-70 | xargs -0 -d'\n' -iresult printf "%-5s %s\n" result "$ARGS" 10 | -------------------------------------------------------------------------------- /test/test.sh: -------------------------------------------------------------------------------- 1 | ./runall.sh |grep -v OK | tee fail.txt 2 | -------------------------------------------------------------------------------- /test/valid/.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | [include] 4 | -------------------------------------------------------------------------------- /test/valid/arguably/if-undefined.js: -------------------------------------------------------------------------------- 1 | function f(x) { 2 | if (x != undefined) { 3 | // do somethin' 4 | // should be 'dead code' and could be eliminated 5 | } 6 | } 7 | 8 | f(3); // bug! 9 | -------------------------------------------------------------------------------- /test/valid/array-2d-get.js: -------------------------------------------------------------------------------- 1 | function f(x) { return x[0][0]; }; 2 | f([[0]]); 3 | -------------------------------------------------------------------------------- /test/valid/array-idx.js: -------------------------------------------------------------------------------- 1 | var x = [0,1,2]; 2 | var y = [3,4]; 3 | 4 | var z = [x[0], y[0]]; 5 | -------------------------------------------------------------------------------- /test/valid/array-length.js: -------------------------------------------------------------------------------- 1 | var a = [1,2,3]; 2 | a.length; 3 | -------------------------------------------------------------------------------- /test/valid/array-with-magic-props.js: -------------------------------------------------------------------------------- 1 | function f1(x) { return x.o; } 2 | function f2(x) { return x[0]; } 3 | 4 | var g = []; 5 | f1(g[0]); 6 | f2(g[0]); 7 | -------------------------------------------------------------------------------- /test/valid/array.js: -------------------------------------------------------------------------------- 1 | var x = [1,2,3]; 2 | var y = ['a','b','c']; 3 | var empty = []; 4 | var i = 0; 5 | empty = [1]; 6 | empty[0]; 7 | x[1]; 8 | y[i]; 9 | null; 10 | -------------------------------------------------------------------------------- /test/valid/blo.js: -------------------------------------------------------------------------------- 1 | function f(x) { 2 | var len = x.length; 3 | return { obj: x, len: len }; 4 | } 5 | var y = f({ moshe: 3, length: 4 }); 6 | var z = f({ length: 'a' }); 7 | f; 8 | 9 | -------------------------------------------------------------------------------- /test/valid/break.js: -------------------------------------------------------------------------------- 1 | while (false) { 1; break; } 2 | -------------------------------------------------------------------------------- /test/valid/builtins/Boolean.js: -------------------------------------------------------------------------------- 1 | var x = Boolean(123); 2 | x = false; 3 | x = Boolean({}); 4 | x = Boolean('a'); 5 | x = Boolean(NaN); 6 | x = Boolean([]); 7 | x = Boolean(null); 8 | x = Boolean(false); 9 | -------------------------------------------------------------------------------- /test/valid/builtins/Date.js: -------------------------------------------------------------------------------- 1 | var x = new Date(); 2 | var y = Date.now(); 3 | var z = Date.parse('2015-01-01'); 4 | var u = Date.UTC(2015,3,1,11,10,9,932); 5 | 6 | 7 | var n = 1; 8 | 9 | n = x.getDate(); 10 | n = x.getDay(); 11 | n = x.getFullYear(); 12 | n = x.getHours(); 13 | n = x.getMilliseconds(); 14 | n = x.getMinutes(); 15 | n = x.getMonth(); 16 | n = x.getSeconds(); 17 | n = x.getTime(); 18 | n = x.getTimezoneOffset(); 19 | n = x.getUTCDate(); 20 | n = x.getUTCDay(); 21 | n = x.getUTCFullYear(); 22 | n = x.getUTCHours(); 23 | n = x.getUTCMilliseconds(); 24 | n = x.getUTCMinutes(); 25 | n = x.getUTCMonth(); 26 | n = x.getUTCSeconds(); 27 | n = x.valueOf(); 28 | 29 | x.setDate(n); 30 | x.setFullYear(n); 31 | x.setHours(n); 32 | x.setMilliseconds(n); 33 | x.setMinutes(n); 34 | x.setMonth(n); 35 | x.setSeconds(n); 36 | x.setTime(n); 37 | x.setUTCDate(n); 38 | x.setUTCFullYear(n); 39 | x.setUTCHours(n); 40 | x.setUTCMilliseconds(n); 41 | x.setUTCMinutes(n); 42 | x.setUTCMonth(n); 43 | x.setUTCSeconds(n); 44 | //x.setYear(n); 45 | 46 | var s = ''; 47 | s = x.toDateString(); 48 | s = x.toISOString(); 49 | s = x.toJSON(); 50 | s = x.toString(); 51 | s = x.toTimeString(); 52 | s = x.toUTCString(); 53 | 54 | -------------------------------------------------------------------------------- /test/valid/builtins/JSON.js: -------------------------------------------------------------------------------- 1 | var s = ''; 2 | s = JSON.stringify(3); 3 | var n = 0; 4 | n = JSON.parse(s); 5 | -------------------------------------------------------------------------------- /test/valid/builtins/Math.js: -------------------------------------------------------------------------------- 1 | var n = 0; 2 | n = Math.E; 3 | n = Math.LN10; 4 | n = Math.LN2; 5 | n = Math.LOG10E; 6 | n = Math.LOG2E; 7 | n = Math.PI; 8 | n = Math.SQRT1_2; 9 | n = Math.SQRT2; 10 | 11 | n = Math.abs(1); 12 | n = Math.acos(1); 13 | n = Math.asin(1); 14 | n = Math.atan(1); 15 | n = Math.atan2(1,2); 16 | n = Math.ceil(1); 17 | n = Math.cos(1); 18 | n = Math.exp(1); 19 | n = Math.floor(1); 20 | n = Math.log(1); 21 | n = Math.pow(2,3); 22 | n = Math.random(); 23 | n = Math.round(1); 24 | n = Math.sign(1); 25 | n = Math.sin(1); 26 | n = Math.sqrt(1); 27 | n = Math.tan(1); 28 | 29 | 30 | -------------------------------------------------------------------------------- /test/valid/builtins/Number.js: -------------------------------------------------------------------------------- 1 | var x = Number(123); 2 | x = 2; 3 | x = Number({}); 4 | x = Number('a'); 5 | x = Number(NaN); 6 | x = Number([]); 7 | x = Number(null); 8 | x = Number(false); 9 | -------------------------------------------------------------------------------- /test/valid/builtins/String.js: -------------------------------------------------------------------------------- 1 | var x = String(123); 2 | x = 'b'; 3 | x = String({}); 4 | x = String('a'); 5 | x = String(NaN); 6 | x = String([]); 7 | x = String(null); 8 | x = String(false); 9 | -------------------------------------------------------------------------------- /test/valid/builtins/assign-ops-index.js: -------------------------------------------------------------------------------- 1 | var arr = [432]; 2 | arr[0] = 1; 3 | arr[0] += 2; 4 | arr[0] -= 3; 5 | arr[0] *= 4; 6 | arr[0] /= 5; 7 | arr[0] %= 8; 8 | arr[0] <<= 6; 9 | arr[0] >>= 7; 10 | arr[0] >>>= 8; 11 | arr[0] &= 9; 12 | arr[0] ^= 10; 13 | arr[0] |= 11; 14 | -------------------------------------------------------------------------------- /test/valid/builtins/assign-ops-property.js: -------------------------------------------------------------------------------- 1 | var obj = { x: 0 }; 2 | obj.x = 1; 3 | obj.x += 2; 4 | obj.x -= 3; 5 | obj.x *= 4; 6 | obj.x /= 5; 7 | obj.x %= 8; 8 | obj.x <<= 6; 9 | obj.x >>= 7; 10 | obj.x >>>= 8; 11 | obj.x &= 9; 12 | obj.x ^= 10; 13 | obj.x |= 11; 14 | -------------------------------------------------------------------------------- /test/valid/builtins/assign-ops.js: -------------------------------------------------------------------------------- 1 | var x = 0; 2 | x = 1; 3 | x += 2; 4 | x -= 3; 5 | x *= 4; 6 | x /= 5; 7 | x %= 8; 8 | x <<= 6; 9 | x >>= 7; 10 | x >>>= 8; 11 | x &= 9; 12 | x ^= 10; 13 | x |= 11; 14 | -------------------------------------------------------------------------------- /test/valid/builtins/binary-not.js: -------------------------------------------------------------------------------- 1 | var x = ~2; 2 | -------------------------------------------------------------------------------- /test/valid/builtins/isFinite.js: -------------------------------------------------------------------------------- 1 | var x = isFinite(3); 2 | 3 | var y = isFinite(-3.8) == false; 4 | -------------------------------------------------------------------------------- /test/valid/builtins/isNaN.js: -------------------------------------------------------------------------------- 1 | var x = isNaN(3); 2 | 3 | var y = isNaN(NaN) == false; 4 | -------------------------------------------------------------------------------- /test/valid/builtins/logical-not.js: -------------------------------------------------------------------------------- 1 | var x = !false; 2 | -------------------------------------------------------------------------------- /test/valid/builtins/map.js: -------------------------------------------------------------------------------- 1 | var a = [1,2,3]; 2 | var n = a.map(function (x) { return x; }); 3 | var b = a.map(function (x) { return 'bla'; }); 4 | a; 5 | 6 | n; 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/valid/builtins/map2.js: -------------------------------------------------------------------------------- 1 | var a = [1,2,3]; 2 | var b = ['a','b','c']; 3 | 4 | var a1 = a.map(function(x) { return String(x); }); 5 | var b1 = b.map(function(x) { return parseInt(x, 10); }); 6 | -------------------------------------------------------------------------------- /test/valid/builtins/minus.js: -------------------------------------------------------------------------------- 1 | var x = 2 - 3; 2 | -------------------------------------------------------------------------------- /test/valid/builtins/minusminus.js: -------------------------------------------------------------------------------- 1 | var x = 0; 2 | var y = x--; 3 | -------------------------------------------------------------------------------- /test/valid/builtins/or.js: -------------------------------------------------------------------------------- 1 | var x = '' || 'bla'; 2 | var y = 0 || 2; 3 | -------------------------------------------------------------------------------- /test/valid/builtins/parse.js: -------------------------------------------------------------------------------- 1 | var x = parseFloat('3.0') == 3.0; 2 | 3 | var y = parseInt('3', 10) == 3; 4 | -------------------------------------------------------------------------------- /test/valid/builtins/plus-func.js: -------------------------------------------------------------------------------- 1 | // let _/_ = undefined 2 | // in let f = (\(this, x, y) -> let return = [] 3 | // in return[0.0] := (+ (null, x, y)); // + :: forall b. Plus b => b -> b -> b instantiated here -> Plus c => c -> c -> c 4 | // return[0.0]) 5 | // in let _/_ = f 6 | // in _/_ 7 | function f(x,y) { return x + y; } 8 | 9 | 10 | f; 11 | 12 | 13 | f(1,2); 14 | 15 | 16 | f('b','c'); 17 | -------------------------------------------------------------------------------- /test/valid/builtins/plus.js: -------------------------------------------------------------------------------- 1 | var x = 2 + 3; 2 | var s = 'a' + 'b'; 3 | 4 | -------------------------------------------------------------------------------- /test/valid/builtins/plusplus.js: -------------------------------------------------------------------------------- 1 | var x = 0; 2 | var y = x++; 3 | -------------------------------------------------------------------------------- /test/valid/builtins/regex-exec-2.js: -------------------------------------------------------------------------------- 1 | function getGroup(regex, str, idx) { 2 | return regex.exec(str)[idx]; 3 | } 4 | 5 | var result = getGroup(/bla/, 'hi', 2) 6 | -------------------------------------------------------------------------------- /test/valid/builtins/regex-exec.js: -------------------------------------------------------------------------------- 1 | var r = /123/g; 2 | 3 | var m = r.exec('12'); 4 | 5 | 6 | var x = 0; 7 | x = m.index; 8 | 9 | var s = ''; 10 | s = m[0]; 11 | s = m.input; 12 | 13 | -------------------------------------------------------------------------------- /test/valid/builtins/regex-test.js: -------------------------------------------------------------------------------- 1 | var x = /hi/; 2 | 3 | function matchesHi() { 4 | if (x.test('hi')) { 5 | return true; 6 | } 7 | return false; 8 | } 9 | 10 | var m = matchesHi(); 11 | 12 | -------------------------------------------------------------------------------- /test/valid/builtins/regex.js: -------------------------------------------------------------------------------- 1 | var r = /<%-([\s\S]+?)%>/g; 2 | var s = r.source; 3 | -------------------------------------------------------------------------------- /test/valid/builtins/string-ops.js: -------------------------------------------------------------------------------- 1 | var x = 'bla'; 2 | var y = x.length + 2; 3 | 'b' + x.charAt(0); 4 | x.concat('hello') + 'abc'; 5 | x.indexOf('hello') + 2; 6 | -------------------------------------------------------------------------------- /test/valid/builtins/string-replace.js: -------------------------------------------------------------------------------- 1 | function f(p, r) { return 'hi'.replace(p, r); } 2 | var s = 'abc'.replace('junk', 'punk'); 3 | var t = 'abd'.replace(/bd/, 'pink'); 4 | f('2','a'); 5 | 6 | -------------------------------------------------------------------------------- /test/valid/builtins/typeof.js: -------------------------------------------------------------------------------- 1 | var x = typeof 3; 2 | -------------------------------------------------------------------------------- /test/valid/builtins/uri.js: -------------------------------------------------------------------------------- 1 | decodeURI('bla') == 'bla'; 2 | decodeURIComponent('bla') == 'bla'; 3 | encodeURI('bla') == 'bla'; 4 | encodeURIComponent('bla') == 'bla'; 5 | -------------------------------------------------------------------------------- /test/valid/call-this.js: -------------------------------------------------------------------------------- 1 | function f() { return this.x; } 2 | var res = f.call({x:3}); 3 | res = 2; 4 | var res1 = f.call({x:'a'}); 5 | res1 = 'b'; 6 | -------------------------------------------------------------------------------- /test/valid/call.js: -------------------------------------------------------------------------------- 1 | function f(x) { x = 0; return x; } 2 | f(2); 3 | -------------------------------------------------------------------------------- /test/valid/complex/kinda-underscore.js: -------------------------------------------------------------------------------- 1 | var each = function(list, iteratee) { 2 | var i = 0; 3 | for (i = 0; i < list.length; i++) { 4 | iteratee(list[i], i, list); 5 | } 6 | return list; 7 | }; 8 | 9 | var p = []; 10 | each([1,2,3], function(e,i,l) { p.push(e + 55); }); 11 | // would give an obscure error: 12 | //each([1,2,3], function(e,i,l) { p.push(e + 'ah'); }); 13 | 14 | //p; 15 | -------------------------------------------------------------------------------- /test/valid/complex/sortedUniq.js: -------------------------------------------------------------------------------- 1 | // from lodash 2 | function sortedUniq(array, iteratee) { 3 | var seen, 4 | index = -1, 5 | length = array.length, 6 | resIndex = -1, 7 | result = []; 8 | 9 | while (++index < length) { 10 | var value = array[index], 11 | computed = iteratee(value, index, array); 12 | 13 | if (index === 0 || seen !== computed) { 14 | seen = computed; 15 | result[++resIndex] = value; 16 | } 17 | } 18 | return result; 19 | } 20 | -------------------------------------------------------------------------------- /test/valid/cond.js: -------------------------------------------------------------------------------- 1 | var x = false ? 1 : 0; 2 | x; 3 | -------------------------------------------------------------------------------- /test/valid/continue.js: -------------------------------------------------------------------------------- 1 | while (false) { 1; continue; } 2 | -------------------------------------------------------------------------------- /test/valid/debounce-indexable.js: -------------------------------------------------------------------------------- 1 | var $timeout = { 2 | set: function(f, num) { num = 0; return 0; }, 3 | cancel: function(t) { t=0; } 4 | }; 5 | 6 | function debounce(f, millis) { 7 | var timer = 0; 8 | var timerSet = false; 9 | var scheduledArgs = []; 10 | function resetTimeout() { 11 | if (timerSet) { 12 | $timeout.cancel(timer); 13 | } 14 | timerSet = true; 15 | timer = $timeout.set(function() { 16 | // Note: f may end up running more than once per args set. 17 | f(scheduledArgs[0]);//.apply(null, scheduledArgs); 18 | }, millis); 19 | return; 20 | } 21 | return function(arg) { 22 | scheduledArgs = [arg]; //Array.prototype.slice.call(arguments, 0); 23 | return { 24 | invoke: function() { 25 | resetTimeout(); 26 | } 27 | }; 28 | }; 29 | } 30 | var fonce = debounce(function (x) { return x[0]; }, 0); 31 | fonce('a'); 32 | //return debounce; 33 | -------------------------------------------------------------------------------- /test/valid/debounce.js: -------------------------------------------------------------------------------- 1 | var $timeout = { 2 | set: function(f, num) { num = 0; return 0; }, 3 | cancel: function(t) { t=0; } 4 | }; 5 | 6 | function debounce(f, initial, millis) { 7 | var timer = 0; 8 | var res = initial; 9 | var timerSet = false; 10 | var scheduledArgs = []; 11 | function resetTimeout() { 12 | if (timerSet) { 13 | $timeout.cancel(timer); 14 | } 15 | timerSet = true; 16 | timer = $timeout.set(function() { 17 | // Note: f may end up running more than once per args set. 18 | res = f(scheduledArgs[0]);//.apply(null, scheduledArgs); 19 | }, millis); 20 | return res; 21 | } 22 | return function(arg) { 23 | scheduledArgs = [arg]; //Array.prototype.slice.call(arguments, 0); 24 | return { 25 | invoke: function() { 26 | return resetTimeout(); 27 | } 28 | }; 29 | }; 30 | } 31 | var fonce = debounce(function (x) { /*x = 0;*/ return 'a'; }, 'c', 3); 32 | var y = fonce(2).invoke(); 33 | y; 34 | //return debounce; 35 | -------------------------------------------------------------------------------- /test/valid/double-func.js: -------------------------------------------------------------------------------- 1 | function f(x) { return x()(); }; 2 | f(function(){return function(){return 0; }; }); 3 | -------------------------------------------------------------------------------- /test/valid/fix.js: -------------------------------------------------------------------------------- 1 | function Y(g) { return g(Y(g)); } 2 | Y; 3 | -------------------------------------------------------------------------------- /test/valid/for-in.js: -------------------------------------------------------------------------------- 1 | var arr = ['a','b']; 2 | 3 | var i = ''; 4 | for (i in arr) { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /test/valid/for.js: -------------------------------------------------------------------------------- 1 | var z = 0; 2 | var i = 0; 3 | for (i = 2; false; i = 3) { 4 | z = i; 5 | } 6 | -------------------------------------------------------------------------------- /test/valid/func-stmt-expr-erisco.js: -------------------------------------------------------------------------------- 1 | var f = function () { return 0; }; 2 | function g() { return 0; }; 3 | var z = [f, g]; 4 | z; 5 | -------------------------------------------------------------------------------- /test/valid/func.js: -------------------------------------------------------------------------------- 1 | function f() { var x = 1; return x; } 2 | f; 3 | -------------------------------------------------------------------------------- /test/valid/get-length.js: -------------------------------------------------------------------------------- 1 | function getLength(x) { 2 | return x.length; 3 | } 4 | 5 | var n = getLength('hi'); 6 | 7 | n; 8 | -------------------------------------------------------------------------------- /test/valid/if.js: -------------------------------------------------------------------------------- 1 | if (false) { 1; } else { false; } 2 | 3 | function f(x,y) { 4 | if (x) { y = 1; } else { return 4; } 5 | return 2; 6 | } 7 | 8 | -------------------------------------------------------------------------------- /test/valid/index-and-plus.js: -------------------------------------------------------------------------------- 1 | function f(x, i) { 2 | return x[i] + x[i]; 3 | } 4 | 5 | f([1,2,3], 0); 6 | 7 | f({'mo': 3 }, 'mo'); 8 | -------------------------------------------------------------------------------- /test/valid/indexable.js: -------------------------------------------------------------------------------- 1 | function zzz() { return 0; } 2 | 3 | function test(y) { 4 | function callback() { 5 | return y; 6 | } 7 | var x= { 8 | invoke: function() { 9 | return callback(); 10 | } 11 | }; 12 | 13 | } 14 | 15 | 16 | test('hi'); 17 | -------------------------------------------------------------------------------- /test/valid/list.js: -------------------------------------------------------------------------------- 1 | var x = true; 2 | x = (1,'a',false); 3 | 4 | -------------------------------------------------------------------------------- /test/valid/map.js: -------------------------------------------------------------------------------- 1 | function map(arr, f) { 2 | var i = 0; 3 | var res = []; 4 | for (i = 0; i < arr.length; i = i + 1) { 5 | res.push(f(arr[i])); 6 | } 7 | return res; 8 | } 9 | 10 | 11 | var arnum = map([1,2,3], function (x) { return x + 1; }); 12 | 13 | //var arstr = map(['a','b','c'], function (x) { return x; }); 14 | 15 | //arnum; 16 | //arstr; 17 | -------------------------------------------------------------------------------- /test/valid/memoize.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | // 'f' is a function that takes a recursion function, and the current value. It should call the recursion function instead of itself when it needs to recurse. 3 | function memoize(toKeyString, f) { 4 | var mem = {}; 5 | 6 | return function memed(x) { 7 | var key = toKeyString(x); 8 | mem[key] = mem[key] || f(memed, x); 9 | return mem[key]; 10 | }; 11 | } 12 | 13 | function recFac(memed, x) { 14 | if (x < 2) { 15 | return 1; 16 | } 17 | return x * memed(x - 1); 18 | }; 19 | 20 | var facTest = memoize(String, recFac); 21 | 22 | // The number of n-dimensional vectors whose scalar's sums to k 23 | // Hat-tip to https://stackoverflow.com/questions/3242597/what-is-memoization-good-for-and-is-it-really-all-that-helpful 24 | var nkTest = memoize( 25 | function (x) { 26 | return String(x.k) + ', ' + String(x.n); }, 27 | function f(m, x) { 28 | if (x.k === 0) { return 1; } 29 | if (x.n === 0) { return 0; } 30 | return m({ n: x.n-1, k: x.k }) + m({ n: x.n, k: x.k-1 }) + m({ n: x.n-1, k: x.k-1 }); 31 | }); 32 | 33 | 34 | nkTest({ n: 30, k: 30 }); 35 | 36 | -------------------------------------------------------------------------------- /test/valid/method-as-func.js: -------------------------------------------------------------------------------- 1 | var o = { method: function() { return 3; } }; 2 | var mo = function(){return o;}; 3 | o.method(); 4 | var f = o.method; 5 | f(); 6 | -------------------------------------------------------------------------------- /test/valid/method.js: -------------------------------------------------------------------------------- 1 | function f(obj, x) { return obj.method(x); } 2 | f; 3 | 4 | -------------------------------------------------------------------------------- /test/valid/mutability/array-of-polyfuncs-eta-expanded.js: -------------------------------------------------------------------------------- 1 | var arr = [function(x) { return x; }]; 2 | // because 'arr' is not generalized, the next line causes arr to be the monomorphic type [a.(Number -> Number)] where 'a' is free (undetermined so far, but not quantified) 3 | arr.push(function(x) { return 0; }); 4 | // here the call uses 'undefined' for 'this', so the 'a' tvar is unified with Undefined, leaving arr with type [Undefined.(Number -> Number)] 5 | arr[0](1); 6 | 7 | // will give us: obj : {f: Undefined.(Number -> Number)} 8 | var obj = { f: function(x) { return arr[0](x); } }; 9 | 10 | // passes obj as 'this' to 'f', so requires the compiler to allow passing any type when Undefined is expected as an argument 11 | obj.f(1); 12 | -------------------------------------------------------------------------------- /test/valid/mutability/array-of-polyfuncs.js: -------------------------------------------------------------------------------- 1 | var arr = [function(x) { return x; }]; 2 | // because 'arr' is not generalized, the next line causes arr to be the monomorphic type [a.(Number -> Number)] where 'a' is free (undetermined so far, but not quantified) 3 | arr.push(function(x) { return 0; }); 4 | // here the call uses 'undefined' for 'this', so the 'a' tvar is unified with Undefined, leaving arr with type [Undefined.(Number -> Number)] 5 | arr[0](1); 6 | 7 | // will give us: obj : {f: Undefined.(Number -> Number)} 8 | var obj = { f: arr[0] }; 9 | 10 | // passes obj as 'this' to 'f', so requires the compiler to allow passing any type when Undefined is expected as an argument 11 | obj.f(1); 12 | -------------------------------------------------------------------------------- /test/valid/mutability/indirect-mutable.js: -------------------------------------------------------------------------------- 1 | var x = function(q){return q;}; 2 | var y = x; 3 | y = function(q){return false;}; 4 | x(2); 5 | x; 6 | y; 7 | -------------------------------------------------------------------------------- /test/valid/mutability/indirect-mutable2.js: -------------------------------------------------------------------------------- 1 | function x(q){return q;}; 2 | var y = x; 3 | y = function(q){return false;}; 4 | x(2); 5 | x; 6 | y; 7 | -------------------------------------------------------------------------------- /test/valid/mutability/mutable-assign-app.js: -------------------------------------------------------------------------------- 1 | function f(x) { return x; } 2 | 3 | var x = 0; 4 | 5 | x = f(3); 6 | 7 | f('a'); 8 | -------------------------------------------------------------------------------- /test/valid/mutability/mutable-indexassign-app.js: -------------------------------------------------------------------------------- 1 | function f(x) { return x; } 2 | 3 | var x = []; 4 | 5 | x[0] = f(3); 6 | 7 | f('a'); 8 | -------------------------------------------------------------------------------- /test/valid/mutability/mutable-propassign-app.js: -------------------------------------------------------------------------------- 1 | function f(x) { return x; } 2 | 3 | var x = { v: 0 }; 4 | 5 | x.v = f(3); 6 | 7 | f('a'); 8 | -------------------------------------------------------------------------------- /test/valid/mutability/ok7.js: -------------------------------------------------------------------------------- 1 | function makeX() { 2 | var x = []; 3 | var setX = function (v2) { x = [v2]; return true; }; 4 | return { setX: setX, getX: function() { return x; } }; 5 | } 6 | var mx1 = makeX(); 7 | mx1.setX(false); 8 | var x1 = mx1.getX(); 9 | var mx2 = makeX(); 10 | mx2.setX(3); 11 | var x2 = mx2.getX(); 12 | -------------------------------------------------------------------------------- /test/valid/mutability/ok7b.js: -------------------------------------------------------------------------------- 1 | function makeX(_) { 2 | var x = []; 3 | var setX = function (v2) { x = [v2]; return true; }; 4 | return setX; 5 | } 6 | var mx1 = makeX(false); 7 | mx1(false); 8 | var mx2 = makeX(false); 9 | mx2(3); 10 | -------------------------------------------------------------------------------- /test/valid/mutability/ok7c.js: -------------------------------------------------------------------------------- 1 | function makeX(_) { 2 | var x = []; 3 | var setX = function (v2, v3) { x = [v2]; return v3; }; 4 | return setX; 5 | } 6 | var mx1 = makeX(false); 7 | mx1(false, 1); 8 | var mx2 = makeX(false); 9 | mx2(1, 'bla'); 10 | mx1; 11 | mx2; 12 | -------------------------------------------------------------------------------- /test/valid/mutability/var.js: -------------------------------------------------------------------------------- 1 | var x = 0; 2 | x = 1; 3 | -------------------------------------------------------------------------------- /test/valid/new-func.js: -------------------------------------------------------------------------------- 1 | function mkn(f, a) { return new f(a); } 2 | 3 | mkn; 4 | 5 | function Foo(x) { this.bla = x; } 6 | 7 | mkn(Foo, 2); 8 | 9 | -------------------------------------------------------------------------------- /test/valid/new-recursive.js: -------------------------------------------------------------------------------- 1 | function Foo() { 2 | this.bar = function() { return new Foo(); }; 3 | } 4 | 5 | var f = new Foo(); 6 | 7 | -------------------------------------------------------------------------------- /test/valid/new-return.js: -------------------------------------------------------------------------------- 1 | function Constr(x) { this.bla = x; return 2; } 2 | var constr = new Constr(2); 3 | constr.bla = 3; 4 | 5 | -------------------------------------------------------------------------------- /test/valid/new-simple.js: -------------------------------------------------------------------------------- 1 | function Foo() {} 2 | var z = new Foo(); 3 | -------------------------------------------------------------------------------- /test/valid/new.js: -------------------------------------------------------------------------------- 1 | function Constr(x) { this.bla = x; } 2 | var constr = new Constr(2); 3 | var x = constr.bla; 4 | x = 2; 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/valid/nothis-as-obj-param.js: -------------------------------------------------------------------------------- 1 | 2 | function id(x) { 3 | return x; 4 | } 5 | function test(obj) { 6 | obj.f(2); 7 | 8 | var g = obj.f; 9 | 10 | g(3); 11 | } 12 | -------------------------------------------------------------------------------- /test/valid/nothis-as-param.js: -------------------------------------------------------------------------------- 1 | function id(x) { return x; } 2 | 3 | function test(f) { 4 | var obj = { f: f }; 5 | obj.f(2); 6 | var g = obj.f; 7 | // error, because 'f' was already inferred to take a 'this' parameter with the same type as 'obj' 8 | g(3); 9 | } 10 | 11 | test(id); 12 | -------------------------------------------------------------------------------- /test/valid/nothis.js: -------------------------------------------------------------------------------- 1 | function f(x) { 2 | return x; 3 | } 4 | 5 | var obj = { f: f }; 6 | 7 | obj.f(2); 8 | 9 | obj; 10 | 11 | var g = obj.f; 12 | 13 | g(3); 14 | -------------------------------------------------------------------------------- /test/valid/nothis2.js: -------------------------------------------------------------------------------- 1 | function f(x) { 2 | return x; 3 | } 4 | 5 | var obj = { f: f }; 6 | 7 | var g = obj.f; 8 | 9 | g(3); 10 | 11 | obj.f(2); 12 | -------------------------------------------------------------------------------- /test/valid/object-chaining-long.js: -------------------------------------------------------------------------------- 1 | ({ method: function() { return this; } }).method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method().method(); 2 | -------------------------------------------------------------------------------- /test/valid/object-chaining-triple.js: -------------------------------------------------------------------------------- 1 | ({ method: function() { return this; } }).method().method().method(); 2 | -------------------------------------------------------------------------------- /test/valid/object-chaining.js: -------------------------------------------------------------------------------- 1 | function Obj(y) { 2 | this.x = y; 3 | this.doIt = function(x) { this.x = x; return this; }; 4 | } 5 | var o = new Obj(2); 6 | o.doIt(2).doIt(3); 7 | var o2 = new Obj('b'); 8 | o2.doIt('fa').doIt('mo'); 9 | -------------------------------------------------------------------------------- /test/valid/objlit-func.js: -------------------------------------------------------------------------------- 1 | var f = function(obj, x) { 2 | return obj.doStuff(x); 3 | }; 4 | 5 | var testObj = {bla: 3, doStuff: function(r) { return false;}}; 6 | 7 | f(testObj, 'b'); 8 | 9 | f = function(obj, x) { 10 | return obj.doStuff('a'); // ignores x, uses 'a' instead. 11 | }; 12 | 13 | -------------------------------------------------------------------------------- /test/valid/objlit.js: -------------------------------------------------------------------------------- 1 | var x = { a: 2, b: 'a' }; 2 | x; 3 | -------------------------------------------------------------------------------- /test/valid/objlit2.js: -------------------------------------------------------------------------------- 1 | function f(x) { 2 | return x.c; 3 | } 4 | f({a:2, c:3}); 5 | f({b:true, c:3}); 6 | f; 7 | // var x = { a: 2, b: 'a' }; 8 | // x = { a:3, b:'b',c:0 }; 9 | // x.c; 10 | -------------------------------------------------------------------------------- /test/valid/once.js: -------------------------------------------------------------------------------- 1 | function once(f) { 2 | var ran = true; 3 | var result = []; 4 | return function(x) { 5 | if (ran) { 6 | ran = false; 7 | result = [f(x)]; 8 | } 9 | return result[0]; 10 | }; 11 | } 12 | 13 | var h = once(function(x) { return 'shimon'; }); 14 | h; 15 | -------------------------------------------------------------------------------- /test/valid/passing-obj-with-poly-method.js: -------------------------------------------------------------------------------- 1 | function bar(objArg) { return objArg.foo(5) == 2; } 2 | var obj = { foo : function(x) { return x; } }; 3 | bar(obj); 4 | -------------------------------------------------------------------------------- /test/valid/point.js: -------------------------------------------------------------------------------- 1 | // # let rotate angle p = (p#new_x (cos angle *. p#get_x -. sin angle *. p#get_y))#new_y (sin angle *. p#get_x +. cos angle *. p#get_y);; 2 | function cos(x) { return x; } 3 | function sin(x) { return x; } 4 | function rotate(angle, p) { 5 | p.new_x(cos(angle) * p.get_x() - sin(angle) * p.get_y()); 6 | p.new_y(sin(angle) * p.get_x() + cos(angle) * p.get_y()); 7 | } 8 | 9 | function Point(x0, y0) { 10 | var x = x0, y = y0; 11 | this.new_x = function(x1) { x = x1; }; 12 | this.new_y = function(y1) { y = y1; }; 13 | this.get_x = function() { return x; }; 14 | this.get_y = function() { return y; }; 15 | } 16 | 17 | var p = new Point(1,2); 18 | 19 | rotate(20, p); 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/valid/poll.js: -------------------------------------------------------------------------------- 1 | function Date() { 2 | 3 | } 4 | var window = { 5 | setTimeout: function(f, t) { 6 | while (t > 0) { 7 | t -= 1; 8 | } 9 | f(); 10 | } 11 | }; 12 | function Error(s) { this.message = s; } 13 | 14 | // Copied from http://davidwalsh.name/essential-javascript-functions 15 | function poll(fn, callback, errback, timeout, interval) { 16 | var endTime = Number(new Date()) + (timeout || 2000); 17 | interval = interval || 100; 18 | 19 | (function p() { 20 | // If the condition is met, we're done! 21 | if(fn()) { 22 | callback(); 23 | } 24 | // If the condition isn't met but the timeout hasn't elapsed, go again 25 | else if (Number(new Date()) < endTime) { 26 | window.setTimeout(p, interval); 27 | } 28 | // Didn't match and too much time, reject! 29 | else { 30 | errback(new Error('timed out')); 31 | } 32 | })(); 33 | } 34 | -------------------------------------------------------------------------------- /test/valid/poly-array-assign.js: -------------------------------------------------------------------------------- 1 | 2 | function setFirst(arr, val) { arr[0] = val; } 3 | 4 | setFirst([1,2,3], 2); 5 | 6 | setFirst(['a','b'], 'd'); 7 | -------------------------------------------------------------------------------- /test/valid/poly-array-assign2.js: -------------------------------------------------------------------------------- 1 | 2 | function setFirst(arr, val) { arr[0] = val; } 3 | 4 | function DoStuff() { 5 | var elems = []; 6 | this.set = function(y) { setFirst(elems, y); }; 7 | } 8 | 9 | var x = new DoStuff(); 10 | x.set(2); 11 | var y = new DoStuff(); 12 | y.set('a'); 13 | -------------------------------------------------------------------------------- /test/valid/poly-method-rank-3-nothis.js: -------------------------------------------------------------------------------- 1 | // supposedly equivalent to this problem: 2 | // -- forall a b. ((() -> (a -> b)), a) -> b 3 | // -- forall . ((() -> (forall d. (d -> d)), Bool) -> Bool 4 | 5 | function forceUndef(x) { x = undefined; } 6 | 7 | var obj = { getIdHolder: function() { forceUndef(this); return { id: function(x) { forceUndef(this); return x; } }; } }; 8 | 9 | function doId(o,x) { 10 | forceUndef(this); 11 | var f = o.getIdHolder; 12 | var o2 = f(); 13 | var g = o2.id; 14 | return g(x); 15 | } 16 | 17 | doId(obj, 2); 18 | -------------------------------------------------------------------------------- /test/valid/poly-method-rank-3.js: -------------------------------------------------------------------------------- 1 | 2 | var obj = { getIdHolder: function() { return { id: function(x) { return x; } }; } }; 3 | 4 | function doId(o,x) { return o.getIdHolder().id(x); } 5 | 6 | doId(obj, 2); 7 | -------------------------------------------------------------------------------- /test/valid/poly-method-recursive-this.js: -------------------------------------------------------------------------------- 1 | function foo(x, y) { return { o: x, v: x.bla(y) }; } 2 | 3 | var obj = { bla: function(z) { return z == this.val; }, 4 | val: false 5 | }; 6 | 7 | 8 | var detachedBla = obj.bla; 9 | var otherObj = { val: 3, bloop: detachedBla }; 10 | otherObj.bloop(2); 11 | 12 | // the returned obj2 is the same object as obj, but has a different type! 13 | var obj2 = foo(obj, true).o; 14 | 15 | var detachedBla2 = obj2.bla; 16 | var otherObj2 = { val: 3, bloop: detachedBla2 }; 17 | // This fails becase otherObj2 is monomorphic. Disable the next line and compare the resulting type of otherObj2 with that of otherObj. 18 | otherObj2.bloop(2); 19 | -------------------------------------------------------------------------------- /test/valid/poly-method-simple.js: -------------------------------------------------------------------------------- 1 | 2 | var obj = { id: function(x) { return x; } }; 3 | 4 | function doId(o,x) { return o.id(x); } 5 | 6 | doId(obj, 2); 7 | -------------------------------------------------------------------------------- /test/valid/poly-row-field-using-let.js: -------------------------------------------------------------------------------- 1 | var obj = { magnitude: 2 | function(p) { 3 | var a = p.x; 4 | var b = p.y;//(p.x*p.x) + (p.y*p.y); 5 | return [a,b]; 6 | } 7 | }; 8 | 9 | var m1 = obj.magnitude; 10 | m1({ x: 1, y: 2 }); 11 | m1({ x: false, y: true }); 12 | 13 | -------------------------------------------------------------------------------- /test/valid/poly-row-field.js: -------------------------------------------------------------------------------- 1 | var obj = { magnitude: 2 | function(p) { 3 | var a = p.x; 4 | var b = p.y;//(p.x*p.x) + (p.y*p.y); 5 | return [a,b]; 6 | } 7 | }; 8 | 9 | obj.magnitude({ x: 1, y: 2 }); 10 | obj.magnitude({ x: false, y: true }); 11 | 12 | -------------------------------------------------------------------------------- /test/valid/poly-row-type-func.js: -------------------------------------------------------------------------------- 1 | function f(o) { return o.length; } 2 | 3 | f({ length: 2 }); 4 | f({ length: false }); 5 | -------------------------------------------------------------------------------- /test/valid/poly-this-new.js: -------------------------------------------------------------------------------- 1 | 2 | function MkObj() { 3 | this.m = function(x) { return x; }; 4 | } 5 | var obj1 = new MkObj(); 6 | var obj2 = new MkObj(); 7 | 8 | obj1.m(2); 9 | 10 | var g = obj2.m; 11 | g(3); 12 | -------------------------------------------------------------------------------- /test/valid/poly-this.js: -------------------------------------------------------------------------------- 1 | 2 | function makeObj() { 3 | return { m: function(x) { return x; } }; // 'this' isn't used. 4 | } 5 | 6 | 7 | var f = makeObj().m; 8 | makeObj().m(2); 9 | 10 | f(3); 11 | -------------------------------------------------------------------------------- /test/valid/poly-this2.js: -------------------------------------------------------------------------------- 1 | 2 | function f(x) { return x; } 3 | var obj = { m: f }; // 'this' isn't used. 4 | 5 | var g = obj.m; 6 | obj.m(2); 7 | 8 | g(3); 9 | -------------------------------------------------------------------------------- /test/valid/polyfunc.js: -------------------------------------------------------------------------------- 1 | var f = function(x) { return x; }; 2 | var num = f(3); 3 | var str = f('a'); 4 | f; 5 | -------------------------------------------------------------------------------- /test/valid/polyfunc2.js: -------------------------------------------------------------------------------- 1 | var f = function(x, y) { return function(z) { x = z; return y; }; }; 2 | f([2], 'a')([3]); 3 | f; 4 | 5 | -------------------------------------------------------------------------------- /test/valid/promise-checking-subsumption.js: -------------------------------------------------------------------------------- 1 | function Promise () { 2 | var that = this; 3 | this.then = function (rej) { }; 4 | this.reject = function (ex) { 5 | that.then = function(rej) { rej(ex); }; 6 | }; 7 | }; 8 | 9 | -------------------------------------------------------------------------------- /test/valid/promise-method-workaround.js: -------------------------------------------------------------------------------- 1 | // Based on https://gist.github.com/unscriptable/814052 - (c) copyright unscriptable.com / John Hann, License MIT 2 | 3 | function Promise () { 4 | var _thens = []; 5 | var that = this; 6 | /* This is the "front end" API. */ 7 | 8 | // then(onResolve, onReject): Code waiting for this promise uses the 9 | // then() method to be notified when the promise is complete. There 10 | // are two completion callbacks: onReject and onResolve. A more 11 | // robust promise implementation will also have an onProgress handler. 12 | this.then = function (onResolve, onReject) { 13 | // capture calls to then() 14 | _thens.push({ resolve: onResolve, reject: onReject }); 15 | }; 16 | 17 | /* This is the "back end" API. */ 18 | 19 | // resolve(resolvedValue): The resolve() method is called when a promise 20 | // is resolved (duh). The resolved value (if any) is passed by the resolver 21 | // to this method. All waiting onResolve callbacks are called 22 | // and any future ones are, too, each being passed the resolved value. 23 | this.resolve = function (val) { 24 | var errorFunc = function(val) { throw new Error('Already completed.'); }; 25 | var i = 0; 26 | that.then = function(resolve, reject) { resolve(val); }; 27 | that.resolve = errorFunc; 28 | that.reject = errorFunc; 29 | for (i = 0; i < _thens.length; i++) { 30 | var r = _thens[i].resolve; 31 | r(val); 32 | } 33 | _thens = []; 34 | }; 35 | 36 | // reject(exception): The reject() method is called when a promise cannot 37 | // be resolved. Typically, you'd pass an exception as the single parameter, 38 | // but any other argument, including none at all, is acceptable. 39 | // All waiting and all future onReject callbacks are called when reject() 40 | // is called and are passed the exception parameter. 41 | this.reject = function (ex) { 42 | var errorFunc = function(val) { throw new Error('Already completed.'); }; 43 | var i = 0; 44 | that.then = function(resolve, reject) { reject(ex); }; 45 | that.resolve = errorFunc; 46 | that.reject = errorFunc; 47 | for (i = 0; i < _thens.length; i++) { 48 | var r = _thens[i].reject; 49 | r(ex); 50 | } 51 | _thens = []; 52 | }; 53 | 54 | }; 55 | 56 | var promise = new Promise(); 57 | 58 | var str = 'a'; 59 | 60 | promise.then(function(x) {str =x; }, function (ex) { }); 61 | 62 | promise.resolve('a'); 63 | // //promise.resolve(2); 64 | -------------------------------------------------------------------------------- /test/valid/promise-simple-api.js: -------------------------------------------------------------------------------- 1 | function Promise() { 2 | var thens = []; 3 | var that = this; 4 | that.then = function(cb) { 5 | var p = new Promise(); 6 | thens.push(function(val) { p.resolve(cb(val)); }); 7 | return p; 8 | }; 9 | that.resolve = function(val) { 10 | var i = 0; 11 | for (i = 0; i < thens.length; i++) { 12 | thens[i](val); 13 | } 14 | that.then = function(cb) { 15 | var p = new Promise(); 16 | p.resolve(cb(val)); 17 | return p; 18 | }; 19 | }; 20 | }; 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/valid/promise-simplified.js: -------------------------------------------------------------------------------- 1 | function Promise(x) { 2 | var _thens = []; 3 | _thens.push(x); 4 | }; 5 | 6 | var p = Promise(123); 7 | var pb = Promise('a'); 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/valid/promise-simplified2.js: -------------------------------------------------------------------------------- 1 | function Promise() { 2 | var _thens = []; 3 | _thens = []; 4 | this.push = function(x) { 5 | _thens.push(x); 6 | }; 7 | }; 8 | 9 | var p = new Promise(); 10 | var p1 = p.push(1); 11 | 12 | var pb = new Promise(); 13 | var pb1 = pb.push('2'); 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/valid/promise.js: -------------------------------------------------------------------------------- 1 | // Based on https://gist.github.com/unscriptable/814052 - (c) copyright unscriptable.com / John Hann, License MIT 2 | 3 | function Promise () { 4 | var _thens = []; 5 | var that = this; 6 | /* This is the "front end" API. */ 7 | 8 | // then(onResolve, onReject): Code waiting for this promise uses the 9 | // then() method to be notified when the promise is complete. There 10 | // are two completion callbacks: onReject and onResolve. A more 11 | // robust promise implementation will also have an onProgress handler. 12 | this.then = function (onResolve, onReject) { 13 | // capture calls to then() 14 | _thens.push({ resolve: onResolve, reject: onReject }); 15 | }; 16 | 17 | /* This is the "back end" API. */ 18 | 19 | // resolve(resolvedValue): The resolve() method is called when a promise 20 | // is resolved (duh). The resolved value (if any) is passed by the resolver 21 | // to this method. All waiting onResolve callbacks are called 22 | // and any future ones are, too, each being passed the resolved value. 23 | this.resolve = function (val) { 24 | var errorFunc = function(val) { throw new Error('Already completed.'); }; 25 | var i = 0; 26 | that.then = function(resolve, reject) { resolve(val); }; 27 | that.resolve = errorFunc; 28 | that.reject = errorFunc; 29 | for (i = 0; i < _thens.length; i++) { 30 | _thens[i].resolve(val); 31 | } 32 | _thens = []; 33 | }; 34 | 35 | // reject(exception): The reject() method is called when a promise cannot 36 | // be resolved. Typically, you'd pass an exception as the single parameter, 37 | // but any other argument, including none at all, is acceptable. 38 | // All waiting and all future onReject callbacks are called when reject() 39 | // is called and are passed the exception parameter. 40 | this.reject = function (ex) { 41 | var errorFunc = function(val) { throw new Error('Already completed.'); }; 42 | var i = 0; 43 | that.then = function(resolve, reject) { reject(ex); }; 44 | that.resolve = errorFunc; 45 | that.reject = errorFunc; 46 | for (i = 0; i < _thens.length; i++) { 47 | _thens[i].reject(ex); 48 | } 49 | _thens = []; 50 | }; 51 | 52 | }; 53 | 54 | var p = new Promise(); 55 | var num = 32; 56 | var p1 = p.then(function(x) { num = x; }, function (ex) { }); 57 | 58 | var pb = new Promise(); 59 | var str = 'b'; 60 | var pb1 = pb.then(function(x) { str = x; }, function (ex) { }); 61 | 62 | 63 | -------------------------------------------------------------------------------- /test/valid/prop-nested-get.js: -------------------------------------------------------------------------------- 1 | function f(x) { return x.foo.foo; }; 2 | var x = f({ foo: { foo: 0 } }); 3 | -------------------------------------------------------------------------------- /test/valid/prop_access.js: -------------------------------------------------------------------------------- 1 | function magnitude(p) { 2 | var a = p.x; 3 | var b = p.y;//(p.x*p.x) + (p.y*p.y); 4 | return [a,b]; 5 | } 6 | 7 | magnitude({ x: 1, y: 2 }); 8 | magnitude({ x: false, y: true }); 9 | -------------------------------------------------------------------------------- /test/valid/prop_assign.js: -------------------------------------------------------------------------------- 1 | function setX(obj, val) { 2 | obj.x = val; 3 | } 4 | 5 | var z = { x: 3 }; 6 | setX(z, 2); 7 | z; 8 | -------------------------------------------------------------------------------- /test/valid/record.js: -------------------------------------------------------------------------------- 1 | var o = { x: 1, y: 2}; 2 | function f(t) { return t.x; } 3 | f(o); 4 | -------------------------------------------------------------------------------- /test/valid/recurse.js: -------------------------------------------------------------------------------- 1 | function f(x) { return f(x); } 2 | f; 3 | -------------------------------------------------------------------------------- /test/valid/recurse2.js: -------------------------------------------------------------------------------- 1 | function f(x) { return f(x); } 2 | f; 3 | -------------------------------------------------------------------------------- /test/valid/recursive-type.js: -------------------------------------------------------------------------------- 1 | var o = { method: function() { return this; } }; 2 | var x = o.method(); 3 | var y = x.method(); 4 | var z = x === y; 5 | z; 6 | -------------------------------------------------------------------------------- /test/valid/recursive_value.js: -------------------------------------------------------------------------------- 1 | function getUndefined() {} 2 | var x = x; // in JS, x is now 'undefined' 3 | x = getUndefined(); 4 | -------------------------------------------------------------------------------- /test/valid/regex.js: -------------------------------------------------------------------------------- 1 | var x = /[0-9].*\w/g; 2 | x; 3 | -------------------------------------------------------------------------------- /test/valid/return-multi.js: -------------------------------------------------------------------------------- 1 | function f() { 2 | var g = 'bla'; 3 | if (false) { 4 | return 2; 5 | } 6 | return 3; 7 | g = 'momo'; 8 | } 9 | 10 | var x = f(); 11 | var y = 2; 12 | y = x; 13 | -------------------------------------------------------------------------------- /test/valid/return.js: -------------------------------------------------------------------------------- 1 | function f(x) { return; } 2 | var y = f(2); 3 | 4 | -------------------------------------------------------------------------------- /test/valid/row-return.js: -------------------------------------------------------------------------------- 1 | function f(parent) { 2 | return parent.child; 3 | } 4 | var x = f({ child: { grandChild: false } }); 5 | x.grandChild; 6 | -------------------------------------------------------------------------------- /test/valid/switch.js: -------------------------------------------------------------------------------- 1 | switch (3) { 2 | case 0: 'a'; 3 | case 1: 'b'; 4 | defualt: null; 5 | } 6 | 7 | -------------------------------------------------------------------------------- /test/valid/test.js: -------------------------------------------------------------------------------- 1 | function bla(options) { 2 | options.x = options.y; 3 | options.x = 3; 4 | return options.y; 5 | } 6 | bla; 7 | -------------------------------------------------------------------------------- /test/valid/this.js: -------------------------------------------------------------------------------- 1 | var obj = { name: '3', getName: function() { return this.name; } }; 2 | obj; 3 | var x = obj.getName(); 4 | x; 5 | -------------------------------------------------------------------------------- /test/valid/trivial.js: -------------------------------------------------------------------------------- 1 | var x = 3; 2 | x; 3 | -------------------------------------------------------------------------------- /test/valid/try.js: -------------------------------------------------------------------------------- 1 | try { 2 | } 3 | catch (e) { 4 | e = 2; 5 | } 6 | finally { 7 | } 8 | 9 | var x = 2; 10 | try { 11 | x = 3; 12 | throw Error("hi"); 13 | } 14 | catch (bla) { 15 | x = bla; 16 | } 17 | finally { 18 | x = 3; 19 | } 20 | -------------------------------------------------------------------------------- /test/valid/var-empty.js: -------------------------------------------------------------------------------- 1 | var x; 2 | x; 3 | -------------------------------------------------------------------------------- /test/valid/while.js: -------------------------------------------------------------------------------- 1 | while (true) { 1; } 2 | do { 1; } while (true) 3 | -------------------------------------------------------------------------------- /webidl/Event.webidl: -------------------------------------------------------------------------------- 1 | /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, 4 | * You can obtain one at http://mozilla.org/MPL/2.0/. 5 | * 6 | * The origin of this IDL file is 7 | * http://www.w3.org/TR/2012/WD-dom-20120105/ 8 | * 9 | * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C 10 | * liability, trademark and document use rules apply. 11 | */ 12 | 13 | [Constructor(DOMString type, optional EventInit eventInitDict), 14 | Exposed=(Window,Worker,System)] 15 | interface Event { 16 | [Pure] 17 | readonly attribute DOMString type; 18 | [Pure] 19 | readonly attribute EventTarget? target; 20 | [Pure] 21 | readonly attribute EventTarget? currentTarget; 22 | 23 | const unsigned short NONE = 0; 24 | const unsigned short CAPTURING_PHASE = 1; 25 | const unsigned short AT_TARGET = 2; 26 | const unsigned short BUBBLING_PHASE = 3; 27 | [Pure] 28 | readonly attribute unsigned short eventPhase; 29 | 30 | void stopPropagation(); 31 | void stopImmediatePropagation(); 32 | 33 | [Pure] 34 | readonly attribute boolean bubbles; 35 | [Pure] 36 | readonly attribute boolean cancelable; 37 | void preventDefault(); 38 | [Pure] 39 | readonly attribute boolean defaultPrevented; 40 | 41 | [Unforgeable, Pure] 42 | readonly attribute boolean isTrusted; 43 | [Pure] 44 | readonly attribute DOMHighResTimeStamp timeStamp; 45 | 46 | [Throws] 47 | void initEvent(DOMString type, boolean bubbles, boolean cancelable); 48 | }; 49 | 50 | // Mozilla specific legacy stuff. 51 | partial interface Event { 52 | const long ALT_MASK = 0x00000001; 53 | const long CONTROL_MASK = 0x00000002; 54 | const long SHIFT_MASK = 0x00000004; 55 | const long META_MASK = 0x00000008; 56 | 57 | readonly attribute EventTarget? originalTarget; 58 | readonly attribute EventTarget? explicitOriginalTarget; 59 | [ChromeOnly] readonly attribute EventTarget? composedTarget; 60 | [ChromeOnly] readonly attribute boolean multipleActionsPrevented; 61 | [ChromeOnly] readonly attribute boolean isSynthesized; 62 | 63 | boolean getPreventDefault(); 64 | }; 65 | 66 | dictionary EventInit { 67 | boolean bubbles = false; 68 | boolean cancelable = false; 69 | }; 70 | -------------------------------------------------------------------------------- /webidl/EventTarget.webidl: -------------------------------------------------------------------------------- 1 | /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, 4 | * You can obtain one at http://mozilla.org/MPL/2.0/. 5 | * 6 | * The origin of this IDL file is 7 | * http://www.w3.org/TR/2012/WD-dom-20120105/ 8 | * 9 | * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C 10 | * liability, trademark and document use rules apply. 11 | */ 12 | 13 | [Exposed=(Window,Worker,WorkerDebugger,System)] 14 | interface EventTarget { 15 | /* Passing null for wantsUntrusted means "default behavior", which 16 | differs in content and chrome. In content that default boolean 17 | value is true, while in chrome the default boolean value is 18 | false. */ 19 | [Throws] 20 | void addEventListener(DOMString type, 21 | EventListener? listener, 22 | optional boolean capture = false, 23 | optional boolean? wantsUntrusted = null); 24 | [Throws] 25 | void removeEventListener(DOMString type, 26 | EventListener? listener, 27 | optional boolean capture = false); 28 | [Throws] 29 | boolean dispatchEvent(Event event); 30 | }; 31 | 32 | // Mozilla extensions for use by JS-implemented event targets to 33 | // implement on* properties. 34 | partial interface EventTarget { 35 | // The use of [TreatNonCallableAsNull] here is a bit of a hack: it just makes 36 | // the codegen check whether the type involved is either 37 | // [TreatNonCallableAsNull] or [TreatNonObjectAsNull] and if it is handle it 38 | // accordingly. In particular, it will NOT actually treat a non-null 39 | // non-callable object as null here. 40 | [ChromeOnly, Throws] 41 | void setEventHandler(DOMString type, 42 | [TreatNonCallableAsNull] EventHandler handler); 43 | 44 | [ChromeOnly] 45 | EventHandler getEventHandler(DOMString type); 46 | }; 47 | 48 | // Mozilla extension to make firing events on event targets from 49 | // chrome easier. This returns the window which can be used to create 50 | // events to fire at this EventTarget, or null if there isn't one. 51 | partial interface EventTarget { 52 | [ChromeOnly, Exposed=Window, BinaryName="ownerGlobalForBindings"] 53 | readonly attribute WindowProxy? ownerGlobal; 54 | }; 55 | -------------------------------------------------------------------------------- /webidl/Node.webidl: -------------------------------------------------------------------------------- 1 | /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, 4 | * You can obtain one at http://mozilla.org/MPL/2.0/. 5 | * 6 | * The origin of this IDL file is 7 | * http://www.w3.org/TR/2012/WD-dom-20120105/ 8 | * 9 | * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C 10 | * liability, trademark and document use rules apply. 11 | */ 12 | 13 | interface Principal; 14 | interface URI; 15 | 16 | interface Node : EventTarget { 17 | const unsigned short ELEMENT_NODE = 1; 18 | const unsigned short ATTRIBUTE_NODE = 2; // historical 19 | const unsigned short TEXT_NODE = 3; 20 | const unsigned short CDATA_SECTION_NODE = 4; // historical 21 | const unsigned short ENTITY_REFERENCE_NODE = 5; // historical 22 | const unsigned short ENTITY_NODE = 6; // historical 23 | const unsigned short PROCESSING_INSTRUCTION_NODE = 7; 24 | const unsigned short COMMENT_NODE = 8; 25 | const unsigned short DOCUMENT_NODE = 9; 26 | const unsigned short DOCUMENT_TYPE_NODE = 10; 27 | const unsigned short DOCUMENT_FRAGMENT_NODE = 11; 28 | const unsigned short NOTATION_NODE = 12; // historical 29 | [Constant] 30 | readonly attribute unsigned short nodeType; 31 | [Pure] 32 | readonly attribute DOMString nodeName; 33 | 34 | [Pure] 35 | readonly attribute DOMString? baseURI; 36 | 37 | [Pure] 38 | readonly attribute Document? ownerDocument; 39 | [Pure] 40 | readonly attribute Node? parentNode; 41 | [Pure] 42 | readonly attribute Element? parentElement; 43 | [Pure] 44 | boolean hasChildNodes(); 45 | [SameObject] 46 | readonly attribute NodeList childNodes; 47 | [Pure] 48 | readonly attribute Node? firstChild; 49 | [Pure] 50 | readonly attribute Node? lastChild; 51 | [Pure] 52 | readonly attribute Node? previousSibling; 53 | [Pure] 54 | readonly attribute Node? nextSibling; 55 | 56 | [SetterThrows, Pure] 57 | attribute DOMString? nodeValue; 58 | [Throws, Pure] 59 | attribute DOMString? textContent; 60 | [Throws] 61 | Node insertBefore(Node node, Node? child); 62 | [Throws] 63 | Node appendChild(Node node); 64 | [Throws] 65 | Node replaceChild(Node node, Node child); 66 | [Throws] 67 | Node removeChild(Node child); 68 | void normalize(); 69 | 70 | [Throws] 71 | Node cloneNode(optional boolean deep = false); 72 | [Pure] 73 | boolean isEqualNode(Node? node); 74 | 75 | const unsigned short DOCUMENT_POSITION_DISCONNECTED = 0x01; 76 | const unsigned short DOCUMENT_POSITION_PRECEDING = 0x02; 77 | const unsigned short DOCUMENT_POSITION_FOLLOWING = 0x04; 78 | const unsigned short DOCUMENT_POSITION_CONTAINS = 0x08; 79 | const unsigned short DOCUMENT_POSITION_CONTAINED_BY = 0x10; 80 | const unsigned short DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 0x20; // historical 81 | [Pure] 82 | unsigned short compareDocumentPosition(Node other); 83 | [Pure] 84 | boolean contains(Node? other); 85 | 86 | [Pure] 87 | DOMString? lookupPrefix(DOMString? namespace); 88 | [Pure] 89 | DOMString? lookupNamespaceURI(DOMString? prefix); 90 | [Pure] 91 | boolean isDefaultNamespace(DOMString? namespace); 92 | 93 | // Mozilla-specific stuff 94 | // These have been moved to Element in the spec. 95 | // If we move namespaceURI, prefix and localName to Element they should return 96 | // a non-nullable type. 97 | [Constant] 98 | readonly attribute DOMString? namespaceURI; 99 | [Constant] 100 | readonly attribute DOMString? prefix; 101 | [Constant] 102 | readonly attribute DOMString? localName; 103 | 104 | [Throws, Func="IsChromeOrXBL"] 105 | any setUserData(DOMString key, any data); 106 | [Throws, Func="IsChromeOrXBL"] 107 | any getUserData(DOMString key); 108 | [ChromeOnly] 109 | readonly attribute Principal nodePrincipal; 110 | [ChromeOnly] 111 | readonly attribute URI? baseURIObject; 112 | [ChromeOnly] 113 | sequence getBoundMutationObservers(); 114 | }; 115 | --------------------------------------------------------------------------------