Skip to content
This repository has been archived by the owner on Oct 26, 2023. It is now read-only.

Latest commit

 

History

History
190 lines (154 loc) · 8.75 KB

design.org

File metadata and controls

190 lines (154 loc) · 8.75 KB

You want to know where, when?

check-syntax analyzes one file at a time:

  • syncheck:add-arrow reports two begin [beg end) intervals within the same file.
    • When a binding is lexical, the arrow is between the use and the definition in the same file.
    • When a binding is imported, the arrow is between the use and the require in the same file. Although the required file might contain the definition, it might just re-provide something imported from yet another file. This can continue indefinitely.

      Furthermore, the binding can be renamed at each import or export.

      You could imagine following these small, precise arrows step by step until reaching the ultimate defining file and name, as reported by identifier-binding.

    • Another wrinkle is that there may be two arrows for different portions of one identifier, as a result of prefix-in or sub-range-binders syntax properties.
  • syncheck:add-jump-to-definition (using identifier-binding) reports a “distant” and “vague” location of a definition: A module path (some other file and submodules within it) and the original name of the definition.

    The location within that file isn’t reported. To learn that, you need to e.g. run check-syntax on that file, and process syncheck:add-definition-target.

    You could imagine this “arrow” is a big, direct jump with an imprecise landing.

Multi-file rename

Note that it is challenging to implement a “multi-file rename” command, since the “same” binding can be exported/imported an arbitrary number of times, using various names for different “segments” of the “chain” of export/imports. Ideally such a command should only rename one same-named segment. Less ideally, it could be acceptable for it to rename all segments sharing the same name. Very less ideally, it could rename the entire chain, obliterating or making redundant any renaming exports or imports.

Brain dump of some raw thoughts

When a module imports a binding, it can rename it: The require form can introduce a new name, introduce an alias. Furthermore, the module that proximally exported that binding may have introduced a new name in its provide form. Furthermore, unless that exporting module contains the definition, it may have imported it (possibly renaming) from some other module that exports it (possibly renaming). And so on indefinitely.

Normally things like free-identifier=? and identifier-binding traverse this entire chain of aliases, considering or returning just the two ends of the chain. And normally that’s desirable. However, when the user wants to rename something, either manually or via some automatic command, it’s necessary to consider the points in the chain where a new name is introduced. User renames must be limited to segments of an alias chain that share the same name.

Assume a command given the location of a definition, and a new name; it should rename the definition as well as relevant uses. The idea being the after the rename command finishes, there will not be any compilation error. That command needs to limit itself to replacing things that were NOT renamed by a provide or require. Those provide/require renames “break the chain” – nothing “downstream” of them should be renamed by the command.

Such a command could offer to work when given, not the location of a defintion, by instead the location of a renaming provide or require clause. The location could be treated much like a definition, as the start of a chain of uses. Note that drracket/check-syntax does draw arrows from identifiers to require and provide forms. However, it does not draw an arrow from the “old” and “new” portions of a renaming sub-clause. We should consider adding that?

Another wrinkle: #%require has prefix and prefix-all-except clauses. Ditto #%provide and its prefix-all-defined and prefix-all-defined-except clauses. syncheck:add-arrow gives us two arrows; for the prefix and the suffix. Each can be renamed or not, independently.

Note that the “granularity” here is higher than syncheck:add-jump-to-defnition, which relies on identifier-binding. That gives you the “ends of the chain”. What we need here needs to consider the full chain, or at least the ends of the chain after being “trimmed” to exclude provide/require renames.

ISSUE: drracket/check-syntax syncheck:add-jump-to-definition tells us the defining modpath of a use. We add that to our db, without needing to analyze that defining file immediately. If a command needs the location of the definition within the file, only then we do analyze it – “lazily”, on-demand.

BUT in this case, we only know if the use of a name introduced locally, or, its proximate importing file.

  1. If use is of something defined locally, the definition site is

also the name-introduction site.

  1. If the use is of a name introduced by a renaming import, the

rename-in id stx is the name-intro site.

  1. Otherwise, we need to analyze the proximate importing file. With

that file:

  • If the name is from a renaming export, the rename-out id stx is the name-intro site.
  • Otherwise go to step 0. Note this may recur indefinitely until we’ve found a name-intro site — either some renaming import/export site or the ultimate definition site (which IIUC should always be the same as reported by identifier-binding).

Note about mapping from uses:

Actual definitions: 1. When mapping uses to actual definitions, identifer-binding immediately tells us the ultimate defining modpath; all we lack is the location within that file. So it’s sufficient to record the modpath, and analyze the defining file later on-demand. 2. When doing the reverse – given an actual definition, what are all its uses – we already know the answer; no further analysis is necessary. [Sure, if we haven’t analyzed file-using-foo.rkt, at all, then searching for uses of foo will miss that use. But we immediately know all uses of actual definitions, among any set of analyzed files.]

But the situation with name introductions is trickier: 1. When we encounter a use, all we know is the proximate file supplying the name. We might need to chase down N files before discovering the ultimate name introduction site. Either we do that chase eagerly, which is expensive, OR we have to record the proximate file as an incomplete/tentative answer, and do the chase later. 2. That tentative status makes the reverse – given a name introduction, what are all its uses – much worse. We can’t find all the uses, not even among a set of files that we have analyzed, until we’ve fully resolved the uses from proximate to ultimate.

Idea:

  1. Continue to analyze files “lazily”.
  2. Have a “proximate?” flag to indicate a use isn’t yet fully

resolved to a name-introduction. This is set true intially (unless intro site is in the same file).

  1. When analyzing each file, record its name-introduction sites.

Then query for all uses showing that file as proximate. Update each to point instead to the newly-analyzed file. If that file has the intro site, change use status from proximate to ultimate. Otherwise, leave the new proximate file, for a subsequent file analysis to advance the resolution futher – to yet another proximate file, and eventually resolved to the ultimate site.

As mentioned above, even a plain old “find all uses of an actual definition” command is subject to not knowing about files that were not analyzed at all. A “find all uses of a name-introduction site” command has the further challenge that any use still in a proximate (not ultimate) state might belong to the set of correct answers, but we don’t know that yet. To avoid that, a command could do a simple db query: Are there any just-proximate uses at all? If so, the command can’t run with guaranteed accuracy, yet. (Such a situation will probably correspond to a non-empty queue of files remaining to be analyzed – but I’m not 100% sure about that, yet.) The command must do a full resolution across the entire db.

TL;DR: Although uses of name-introduction sites seems to require an “eager”, “depth-first” analysis of files, we can in fact handle it “lazily”. Uses might remain in a proximate state, but each newly analyzed file may advance some of those one step closer to the ultimate state. In some sense the uses marked proximate are “thunks” or “promises”.

===> Note that any newly analyzed file might have a new use of a name-introduction in a non-proximate file. That is, we need to traverse the chain. So I think we still need some “resolve all” function that does this. Furthermore, since it is only needed by rename commands, we could do none of that work up-front. Wait until some rename command actually needs to run. (Or maybe, wait until we’ve reached an idle quiescent state, and do it. Or, start doing it but be “interruptible”.)