From c2cd1e50fe0d3e06ccabd09239f96e392eab3f0b Mon Sep 17 00:00:00 2001 From: XeroOl Date: Sat, 13 Aug 2022 14:20:40 -0500 Subject: [PATCH] Improved goto-definition sifting destructures --- src/fennel-ls/compiler.fnl | 34 ++++++++++++++++++----------- src/fennel-ls/handlers.fnl | 2 +- src/fennel-ls/language.fnl | 41 ++++++++++++++++++++++++++--------- test/goto-definition-test.fnl | 18 ++++++++++----- test/test-project/example.fnl | 16 ++++++++++++++ 5 files changed, 81 insertions(+), 30 deletions(-) diff --git a/src/fennel-ls/compiler.fnl b/src/fennel-ls/compiler.fnl index e6cdb3c..de02db7 100644 --- a/src/fennel-ls/compiler.fnl +++ b/src/fennel-ls/compiler.fnl @@ -32,12 +32,13 @@ (λ compile [file] "Compile the file, and record all the useful information from the compiler into the file object" - (local references []) - (local definitions (doto {} (setmetatable has-tables-mt))) + (local references {}) + (local definitions-by-scope (doto {} (setmetatable has-tables-mt))) + (local definitions {}) (λ find-definition [name ?scope] (when ?scope - (or (. definitions ?scope name) + (or (. definitions-by-scope ?scope name) (find-definition name ?scope.parent)))) (λ reference [ast scope] @@ -53,15 +54,21 @@ ;; recursively explore the binding (which, in the general case, is a destructuring assignment) ;; right now I'm not keeping track of *how* the symbol was destructured: just finding all the symbols for now. ;; also, there's no logic for (values) - (λ recurse [binding] + (λ recurse [binding keys] (if (fennel.sym? binding) - (tset (. definitions scope) - (tostring binding) - {: binding :definition ?definition}) + (let [definition + {: binding + : ?definition + :?keys (fcollect [i 1 (length keys)] + (. keys i))}] + (tset (. definitions-by-scope scope) (tostring binding) definition) + (tset definitions binding definition)) (= :table (type binding)) (each [k v (iter binding)] - (recurse v)))) - (recurse binding)) + (table.insert keys k) + (recurse v keys) + (table.remove keys)))) + (recurse binding [])) (λ define-function-name [ast scope] ;; add a function definition to the definitions @@ -70,10 +77,10 @@ (and (fennel.sym? name) (not (multisym? name)) ;; not dealing with multisym for now (fennel.sequence? args))) - (tset (. definitions scope.parent) + (tset (. definitions-by-scope scope) ;; !!! parent or child? (tostring name) {:binding name - :definition ast}))) + :?definition ast}))) (λ define-function-args [ast scope] ;; add the definitions of function arguments to the definitions @@ -82,7 +89,7 @@ (where [_fn args] (fennel.sequence? args)) args (where [_fn _name args] (fennel.sequence? args)) args)) (each [_ argument (ipairs args)] - (define nil argument scope))) ;; we say function arguments are set to nil + (define nil argument scope))) ;; we say function arguments are set to nil ;; !!! parent or child? (λ define-function [ast scope] ;; handle the definitions of a function @@ -124,7 +131,8 @@ ;; write things back to the file object (set file.references references) - ;; (set file.definitions definitions) ;; not needed yet + (set file.definitions definitions) + ;; (set file.definitions-by-scope definitions-by-scope) ;; not needed yet (set file.ast ast)) ;; (set file.compiled? true)) {: compile} diff --git a/src/fennel-ls/handlers.fnl b/src/fennel-ls/handlers.fnl index 6d1761d..89febcc 100644 --- a/src/fennel-ls/handlers.fnl +++ b/src/fennel-ls/handlers.fnl @@ -63,7 +63,7 @@ Every time the client sends a message, it gets handled by a function in the corr (local byte (pos->byte file.text position.line position.character)) (match (language.find-symbol file.ast byte) symbol - (match (language.search-symbol self file symbol []) + (match (language.search-main self file symbol []) (definition result-file) ;; curse you, magical match rules (message.range-and-uri definition result-file)))) diff --git a/src/fennel-ls/language.fnl b/src/fennel-ls/language.fnl index e851cb5..9723b35 100644 --- a/src/fennel-ls/language.fnl +++ b/src/fennel-ls/language.fnl @@ -15,21 +15,23 @@ nil) (set search-assignment - (λ search-assignment [self file binding ?definition stack] + (λ search-assignment [self file {: binding : ?definition : ?keys} stack] (if (= 0 (length stack)) (values binding file) ;; BASE CASE!! - - ;; TODO sift down the binding - (search self file ?definition stack)))) + (do + (if ?keys + (fcollect [i (length ?keys) 1 -1 &into stack] + (. ?keys i))) + (search self file ?definition stack))))) (set search-symbol (λ search-symbol [self file symbol stack] (let [split (utils.multi-sym-split symbol)] - (for [i (length split) 2 -1] - (table.insert stack (. split i)))) + (fcollect [i (length split) 2 -1 &into stack] + (. split i))) ;; TODO test coverage for this line (match (. file.references symbol) - to (search-assignment self file to.binding to.definition stack) - nil nil))) ;; BASE CASE: Give up + to (search-assignment self file to stack)))) + (set search (λ search [self file item stack] @@ -37,7 +39,9 @@ (fennelutils.table? item) (if (. item (. stack (length stack))) (search self file (. item (table.remove stack)) stack) - nil) ;; BASE CASE: Give up + (= 0 (length stack)) + (values item file) ;; BASE CASE !! + nil) ;; BASE CASE Give up (sym? item) (search-symbol self file item stack) ;; TODO @@ -49,6 +53,22 @@ (search self newfile newitem stack)) _ (error (.. "I don't know what to do with " (fennel.view item))))))) +(λ search-main [self file symbol] + ;; TODO partial byting, go to different defitition sites depending on which section of the symbol the trigger happens on + + ;; The stack is the multi-sym parts still to search + ;; for example, if I'm searching for "foo.bar.baz", my "item" or "symbol" is foo, + ;; and the stack has ["baz" "bar"], with "bar" at the "top"/"end" of the stack as the next key to search. + (local stack + (let [split (utils.multi-sym-split symbol)] + (fcollect [i (length split) 2 -1] + (. split i)))) + (match (values (. file.references symbol) (. file.definitions symbol)) + (ref _) + (search-assignment self file ref stack) + (_ def) + (search self file def.?definition stack))) + (λ past? [?ast byte] ;; check if a byte is past an ast object (and (= (type ?ast) :table) @@ -101,4 +121,5 @@ (find-symbol v byte true))))) {: find-symbol - : search-symbol} + : search-symbol + : search-main} diff --git a/test/goto-definition-test.fnl b/test/goto-definition-test.fnl index 739251a..3451036 100644 --- a/test/goto-definition-test.fnl +++ b/test/goto-definition-test.fnl @@ -49,19 +49,25 @@ (it "can go to a function inside a table" (check "example.fnl" 28 6 "example.fnl" 4 4 4 7)) + ;; (it "can go to a field inside of a table") + (it "can go to a function in another file when accessed by multisym" - (check "example.fnl" 7 7 "foo.fnl" 2 4 2 13))) + (check "example.fnl" 7 7 "foo.fnl" 2 4 2 13)) - ;; (it "goes further if you go to definition on a binding") + (it "goes further if you go to definition on a binding" + (check "example.fnl" 31 12 "example.fnl" 23 4 23 5)) + + + ;; (it "can go to a destructured function argument") + + ;; it can go up and down destructuring + (it "can trace a variable that was introduced with destructuring assignment" + (check "example.fnl" 38 15 "example.fnl" 33 7 33 13))) - ;; (it "handles (local _ (require XXX))") - ;; (check "example.fnl" 0 10 "foo.fnl" 0 0 0 0) ;; (it "works directly on a require/include (require XXX))" ;; (check "example.fnl" 1 5 "bar.fnl" 0 0 0 0)) - ;; (it "can go to a field inside of a table") - ;; (it "can go to a destructured function argument") ;; (it "can go to a reference that occurs in a macro") ;; (it "doesn't have ghost definitions from the same byte ranges as the macro files it's using") ;; (it "can go to a function in another file imported via destructuring assignment") diff --git a/test/test-project/example.fnl b/test/test-project/example.fnl index a51a526..c1edd5c 100644 --- a/test/test-project/example.fnl +++ b/test/test-project/example.fnl @@ -28,3 +28,19 @@ (obj.bar 2 3) (obj:a) + +(local redefinition b) + +(local findme 10) +(local deep {:a {:b {:field findme}}}) +(local {:a {:b shallow}} deep) +(local mixed [{:key [5 {:foo shallow}]}]) +(local [{:key [_ {:foo funny}]}] mixed) +(print funny.field) + +(local findme 10) ;; via field access instead of destructure +(local deep {:a {:b {:field findme}}}) +(local shallow deep.a.b) +(local mixed [{:key [5 {:foo shallow}]}]) +(local funny (. mixed 1 :key 2 foo)) +(print funny.field)