Giter Site home page Giter Site logo

emacs-elsa / elsa Goto Github PK

View Code? Open in Web Editor NEW
630.0 20.0 26.0 2.07 MB

Emacs Lisp Static Analyzer and gradual type system.

License: GNU General Public License v3.0

Emacs Lisp 99.44% Shell 0.27% Batchfile 0.29%
static-analysis emacs emacs-lisp elsa gradual-typing

elsa's People

Contributors

alphapapa avatar d12frosted avatar damiencassou avatar darwinawardwinner avatar fmdkdd avatar fuco1 avatar gopiandcode avatar jcs090218 avatar jwilk avatar vermiculus avatar xuchunyang avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

elsa's Issues

Add type for eieio classes

What we need at minimum:

  • Parse the properties and get their types from native eieio type annotations
  • Add annotation syntax for slots with more complex types
  • Resolve the type hierarchy (what is parent of what and what can be substituted for what).
  • Teach oref and oset to work with this information.
  • Document how types and annotations interact.

Add function to get the function signature

For built-ins subr-arity.

See also

(defun help-function-arglist (def &optional preserve-names)
  "Return a formal argument list for the function DEF.
IF PRESERVE-NAMES is non-nil, return a formal arglist that uses
the same names as used in the original source code, when possible."
  ;; Handle symbols aliased to other symbols.
  (if (and (symbolp def) (fboundp def)) (setq def (indirect-function def)))
  ;; If definition is a macro, find the function inside it.
  (if (eq (car-safe def) 'macro) (setq def (cdr def)))
  (cond
   ((and (byte-code-function-p def) (listp (aref def 0))) (aref def 0))
   ((eq (car-safe def) 'lambda) (nth 1 def))
   ((eq (car-safe def) 'closure) (nth 2 def))
   ((or (and (byte-code-function-p def) (integerp (aref def 0)))
        (subrp def))
    (or (when preserve-names
          (let* ((doc (condition-case nil (documentation def) (error nil)))
                 (docargs (if doc (car (help-split-fundoc doc nil))))
                 (arglist (if docargs
                              (cdar (read-from-string (downcase docargs)))))
                 (valid t))
            ;; Check validity.
            (dolist (arg arglist)
              (unless (and (symbolp arg)
                           (let ((name (symbol-name arg)))
                             (if (eq (aref name 0) ?&)
                                 (memq arg '(&rest &optional))
                               (not (string-match "\\." name)))))
                (setq valid nil)))
            (when valid arglist)))
        (let* ((args-desc (if (not (subrp def))
                              (aref def 0)
                            (let ((a (subr-arity def)))
                              (logior (car a)
                                      (if (numberp (cdr a))
                                          (lsh (cdr a) 8)
                                        (lsh 1 7))))))
               (max (lsh args-desc -8))
               (min (logand args-desc 127))
               (rest (logand args-desc 128))
               (arglist ()))
          (dotimes (i min)
            (push (intern (concat "arg" (number-to-string (1+ i)))) arglist))
          (when (> max min)
            (push '&optional arglist)
            (dotimes (i (- max min))
              (push (intern (concat "arg" (number-to-string (+ 1 i min))))
                    arglist)))
          (unless (zerop rest) (push '&rest arglist) (push 'rest arglist))
          (nreverse arglist))))
   ((and (autoloadp def) (not (eq (nth 4 def) 'keymap)))
    "[Arg list not available until function definition is loaded.]")
   (t t)))

(a . ((b))) is read as (a (b)) and the `elsa--read-cons` breaks

We need to track the existence of the dot read syntax independent of the resulting read form.

This breaks reading forms such as

(defvar sp-pairs
  '((t
     .
     ((:open "\\\\(" :close "\\\\)" :actions (insert wrap autoskip navigate))
      (:open "\\{"   :close "\\}"   :actions (insert wrap autoskip navigate))
      (:open "\\("   :close "\\)"   :actions (insert wrap autoskip navigate))
      (:open "\\\""  :close "\\\""  :actions (insert wrap autoskip navigate))
      (:open "\""    :close "\""
       :actions (insert wrap autoskip navigate escape)
       :unless (sp-in-string-quotes-p)
       :post-handlers (sp-escape-wrapped-region sp-escape-quotes-after-insert))
      (:open "'"     :close "'"
       :actions (insert wrap autoskip navigate escape)
       :unless (sp-in-string-quotes-p sp-point-after-word-p)
       :post-handlers (sp-escape-wrapped-region sp-escape-quotes-after-insert))
      (:open "("     :close ")"     :actions (insert wrap autoskip navigate))
      (:open "["     :close "]"     :actions (insert wrap autoskip navigate))
      (:open "{"     :close "}"     :actions (insert wrap autoskip navigate))
      (:open "`"     :close "`"     :actions (insert wrap autoskip navigate)))))
  "List of pair definitions.

Maximum length of opening or closing pair is
`sp-max-pair-length' characters.")

Detect impossible conditionals

With #50 implemented, we can detect nonsense code such as (and (stringp x) (numberp x)) because after both of the narrowings the domain of x will be empty => condition will never be true.

Store line number and columns in the reader for use in error reporting

Currently we save the start and end positions of the forms but those are not good for error reporting since we would have to open the file buffers again and calculate the lines and offsets which can be done in the reader itself (probably from the mail function for each of the forms uniformly).

Add support for composite variadic types (such as `(a | b)...`)

Some functions like = take a variadic number of "Number | Marker" which we can not express yet.

We can add a constructor syntax such as Variadic (Number | Marker) which would be quite easy. A nicer syntax such as (Number | Marker)... can be also added.

Add check for unused variables.

This should probably be tracked on the scope because as we nest bindings variables get pushed and popped and so the information would get mixed.

We can simply walk through the scope and on the first use set a flag. When we are popping a variable, check if it was never used in the scope and flag it as a warning.

Beware, the popping might happen as implementation detail for example in conditionals narrowing... we should only disregard the information when popping a "defining" entry like a binding from let or defun. Otherwise the information needs to be propagated upwards.

To make this simple we should probably add the defining form to the variable class so that we can refer back to it later.

  • Add form to the variable class
  • Extend scope with usage tracking information
  • Add a check for usage.

Add a plist type

  • Regular plist (:foo a :bar b)
  • Add support for reading the &keys syntax from cl-defun
  • As a "key" argument to functions, for example (propertize "string" 'a b 'c d), a and c are keys and b is a value... so the type is something like (function (string &rest [symbol value]) string). We need to add a good syntax for repeating a set of values. Take inspiration in macro instrumentation.

Fix printers for types

A type Cons Int (Int | Symbol) is printed as Cons Int Int | Symbol which is a different type. Similarly for functions with nested functions etc.

We need to decide on wrapping with parens based on the type of type (composite or atom). This is I think relevant to sum, diff and function types only.

Add support for `cl-defmethod`

Blocked by #36.

  • parse the cl-defmethod as if it were a defun
  • handle cl-defgeneric
  • add type information for arguments which have the native type annotation
  • handle cl-defmethod being declared multiple times by intersecting all the type signatures together

We need to add support for analysing cl-defmethod as if it were a defun with addition of handling the "cl-style" argument list. For start just extend it to the forms like (this type) so that we can have type information better than Mixed on actually typed arguments.

At some point we would need to add (elsa ::) annotations anyway for the rest of the arguments. The type in the function should always take precedence over the comment annotation because that is what is actually going to be used.

Properly handle types of quoted things

  • 'foo should have type symbol
  • '(foo) should have type list
  • self-quoting atoms should also be promoted (strings, :keywords, numbers...) since e.g. (stringp '"foo") is t

Annotate all the built-in C functions

See https://github.com/Fuco1/Elsa/blob/master/dev/builtin.txt

  • w32proc.c
  • data.c
  • bytecode.c
  • w32fns.c
  • coding.c
  • callproc.c
  • kqueue.c
  • print.c
  • xmenu.c
  • inotify.c
  • indent.c
  • minibuf.c
  • decompress.c
  • undo.c
  • casefiddle.c
  • eval.c
  • keyboard.c
  • xwidget.c
  • w32menu.c
  • fringe.c
  • buffer.c
  • marker.c
  • xdisp.c
  • composite.c
  • floatfns.c
  • callint.c
  • w32notify.c
  • alloc.c
  • cmds.c
  • atimer.c
  • xfaces.c
  • dispnew.c
  • chartab.c
  • w16select.c
  • cygw32.c
  • insdel.c
  • ccl.c
  • xml.c
  • process.c
  • emacs.c
  • xfns.c
  • dbusbind.c
  • profiler.c
  • lread.c
  • fns.c
  • font.c
  • window.c
  • w32font.c
  • term.c
  • image.c
  • filelock.c
  • charset.c
  • dosfns.c
  • doc.c
  • w32select.c
  • sound.c
  • xsettings.c
  • search.c
  • casetab.c
  • category.c
  • fontset.c
  • textprop.c
  • dired.c
  • editfns.c
  • keymap.c
  • gfilenotify.c
  • terminal.c
  • nsfns.m
  • syntax.c
  • fileio.c
  • frame.c
  • xsmfns.c
  • macros.c
  • gnutls.c
  • emacs-module.c
  • w32console.c
  • nsselect.m
  • menu.c
  • msdos.c
  • nsmenu.m
  • character.c
  • xselect.c

Add generic type unification mechanism

There is not really much to implement except the unification mechanism and then some simple substitution magic on elsa-get-type which would take the types from the surroundings and fill the still generic variables with whatever appropriate (or mixed).

Do not analyse arguments of macros by default

Since we don't know what is supposed to be evaluated and what not, do not evaluate anything by default.

Later we should learn to read the declare debug form and analyse only those parts which are evaluated, such as form or body.

Add a check that private functions are referenced

It might happen that a private function is not referenced from anywhere inside the package. In which case it's dead code and we should remove it.

This will require construction of something like a call graph, because a function might be referenced from another which is never called, then both should be marked as unreachable.

Also think about mutually recursive functions which are unreachable from outside.

So basically directed components and then use those as equivalence classes and try reaching them from the entry points (= public functions).

Add some support for "dumping" type definitions.

elsa-make-type can be pretty slow at times, we should support dumping the typed files into a byte-compiled native format which can be read much faster.

I suspect that simply byte-compiling the file should work as the macros should be able to expand during compile-time.

Implement type narrowing in conditionals

If I have a variable a which has type Mixed, in the following form

(if (stringp a) (progn ...) (progn ...))

it should have type String in the then body and Mixed \ String in the else body.

  • if
  • cond
  • when
  • unless
  • or
  • and

We should also have some unit tests... this is pretty core logic.

  • tests

Annotate all the defuns and variables from elisp files loaded on startup

These are not ordered by priority but load-order. Pick something useful first.

subr and simple are good initial candidates.

  • international/mule-util.elc
  • leim/leim-list.el
  • tooltip.elc
  • cus-start.elc
  • emacs-lisp/eldoc.elc
  • electric.elc
  • uniquify.elc
  • vc/ediff-hook.elc
  • vc/vc-hooks.elc
  • emacs-lisp/float-sup.elc
  • mwheel.elc
  • term/x-win.elc
  • term/common-win.elc
  • x-dnd.elc
  • dynamic-setting.elc
  • tool-bar.elc
  • dnd.elc
  • international/fontset.elc
  • image.elc
  • emacs-lisp/regexp-opt.elc
  • fringe.elc
  • buff-menu.elc
  • emacs-lisp/tabulated-list.elc
  • replace.elc
  • newcomment.elc
  • textmodes/fill.elc
  • textmodes/text-mode.elc
  • progmodes/elisp-mode.elc
  • emacs-lisp/lisp-mode.elc
  • progmodes/prog-mode.elc
  • textmodes/paragraphs.elc
  • register.elc
  • textmodes/page.elc
  • emacs-lisp/lisp.elc
  • menu-bar.elc
  • rfn-eshadow.elc
  • isearch.elc
  • emacs-lisp/timer.elc
  • select.elc
  • scroll-bar.elc
  • mouse.elc
  • jit-lock.elc
  • font-lock.elc
  • emacs-lisp/syntax.elc
  • facemenu.elc
  • font-core.elc
  • term/tty-colors.elc
  • startup.elc
  • frame.elc
  • emacs-lisp/cl-generic.elc
  • indent.elc
  • language/cham.elc
  • language/burmese.elc
  • language/khmer.elc
  • language/georgian.elc
  • language/utf-8-lang.elc
  • language/misc-lang.elc
  • language/vietnamese.elc
  • language/tibetan.elc
  • language/thai.elc
  • language/tai-viet.elc
  • language/lao.elc
  • language/korean.elc
  • language/japanese.elc
  • international/eucjp-ms.elc
  • international/cp51932.elc
  • language/hebrew.elc
  • language/greek.elc
  • language/romanian.elc
  • language/slovak.elc
  • language/czech.elc
  • language/european.elc
  • language/ethiopic.elc
  • language/english.elc
  • language/sinhala.elc
  • language/indian.elc
  • language/cyrillic.elc
  • language/chinese.elc
  • composite.elc
  • international/characters.elc
  • international/uni-category.el
  • international/charscript.elc
  • international/uni-brackets.el
  • international/uni-mirrored.el
  • international/uni-bidi.el
  • international/charprop.el
  • case-table.elc
  • international/mule-cmds.elc
  • epa-hook.elc
  • jka-cmpr-hook.elc
  • help.elc
  • simple.elc
  • abbrev.elc
  • minibuffer.elc
  • emacs-lisp/cl-preloaded.elc
  • emacs-lisp/nadvice.elc
  • loaddefs.el
  • button.elc
  • faces.elc
  • cus-face.elc
  • emacs-lisp/macroexp.elc
  • files.elc
  • window.elc
  • bindings.elc
  • format.elc
  • env.elc
  • international/mule-conf.elc
  • international/mule.elc
  • emacs-lisp/map-ynp.elc
  • custom.elc
  • widget.elc
  • version.elc
  • subr.elc
  • emacs-lisp/backquote.elc
  • emacs-lisp/byte-run.elc

Replace the tests for elsa-make-type comparing to vectors with comparing to print representation

Because different versions of emacs use different representation of objects (vectors before 26 and records after 26) half the tests fails.

We can kill two birds with one stone by testing that whatever type the macro produces serializes back to the form that created it, such that reading it back would produce the same type.

However, we need to take into account normalization of nested parens, such that (Int) and Int are equivalent even though the form is different (both will print as Int)

This was discovered in #20.

Add better api for collection of messages

We should thread a state variable through the analysis and it should have a method to add a message.

This will also save us all the filtering for -non-nil and -flatten and so on.

Unable to read (function)

'(repeat :value ("k" . ignore)
   (choice :value ("k" . ignore)
     (list :tag "Descriptive Headline" (string :tag "Headline"))
     (cons :tag "Letter and Command"
       (string :tag "Command letter")
       (choice (function) (sexp)))))

(from org.el)

Add a rule for comparing strings with eq

(let ((a "a"))
  (eq a a)) ;; => t

(let ((a "a")
      (b "a"))
  (eq a b)) ;; => nil

I can not imagine when the first is much useful... Probably this is an error. It should be a warning but configurable.

Reading of backticks, commas and splice operators

This is tricky because simply using read can not distinguish different forms of the input

,asd

and

(\, asd)

read to the same expression but we need to handle them differently in the reader (though they should bot be read into the same structure).

Make a generic interface to get a return type of a type

Functions in elisp can be without arguments and so we can have (current-buffer-name) :: string but it still should be a "function" type?

Every type should support elsa-type-return which returns the last type in the list, so for atomic types just that and for real function types the last type in the sequence.

Implement the "type algebra"

  • elsa-type-sum
  • elsa-type-intersect
  • elsa-type-diff

We can first make this as naive as possible and then add some normalization on top to reduce types into minimal forms.

Then remove unnecessary functions

  • elsa-sum-type-remove
  • elsa-diff-type-remove-positive

It should be possible to do all the operations generically on all the types (that's the point of having the class interface) so that these specific functions make no selse.

We should also move the methods that work on the classes back to elsa-types.el and only have defuns in helpers (or maybe remove that file alltogether).

This possibly also means (think about it!) that we don't need the surely and surely-not properties on elsa-variable as those are simply expressible as sum and diff types.

Add constant types

For example

(put 'subr-arity 'elsa-type (elsa-make-type Mixed -> Cons Int (Int | Symbol)))

Returns either Int or a symbol 'many as the cdr. Saying just Symbol is overly vague.

Once implemented, go over the typed definitions and adjust those where it makes sense

  • subr-arity

Implement extensions for `-let` and friends.

This is quite important as these are quite common and break a lot of code by introducing "unbound variables".

For start just adding mixed variables is OK, later we might want to actually analyse the binding forms also (where possible... destructuring is super tricky because the structure might not be known).

Figure out a way of tracking "callability" of a form

;; (elsa :: (Int -> Bool) -> Bool)
(defun foo (fun) t)

;; (elsa :: Number -> Bool)
(defun bar (n) t)

(let ((a (lambda (x) t))) (foo a))
(let ((a 'null)) (foo a))   ;; does not work currently, a is Mixed (and would be a Symbol)

(foo 'bar)
(foo 'null)

Maybe we need to keep track of a second type for places where the symbol is used as a callable? Once a is bound to 'null we lose the information about the symbol name so the function definition should probably be bound to something to keep track.

I think this is only relevant property for symbols as other atoms are not callable.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.