Skip to content

Commit f3401dd

Browse files
authored
Merge pull request #251 from julia-vscode/envs
add infrastructure for file-specific environments
2 parents 2ce4ea5 + 6d51b56 commit f3401dd

File tree

12 files changed

+251
-245
lines changed

12 files changed

+251
-245
lines changed

src/StaticLint.jl

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,18 @@ hasscope(m::Meta) = m.scope isa Scope
3838
scopeof(m::Meta) = m.scope
3939
bindingof(m::Meta) = m.binding
4040

41+
42+
"""
43+
ExternalEnv
44+
45+
Holds a representation of an environment cached by SymbolServer.
46+
"""
47+
mutable struct ExternalEnv
48+
symbols::SymbolServer.EnvStore
49+
extended_methods::Dict{SymbolServer.VarRef,Vector{SymbolServer.VarRef}}
50+
project_deps::Vector{Symbol}
51+
end
52+
4153
abstract type State end
4254
mutable struct Toplevel{T} <: State
4355
file::T
@@ -47,6 +59,7 @@ mutable struct Toplevel{T} <: State
4759
modified_exprs::Union{Nothing,Vector{EXPR}}
4860
delayed::Vector{EXPR}
4961
resolveonly::Vector{EXPR}
62+
env::ExternalEnv
5063
server
5164
end
5265

@@ -59,7 +72,7 @@ function (state::Toplevel)(x::EXPR)
5972
s0 = scopes(x, state)
6073
resolve_ref(x, state)
6174
followinclude(x, state)
62-
75+
6376
old_in_modified_expr = state.in_modified_expr
6477
if state.modified_exprs !== nothing && x in state.modified_exprs
6578
state.in_modified_expr = true
@@ -73,14 +86,15 @@ function (state::Toplevel)(x::EXPR)
7386
else
7487
traverse(x, state)
7588
end
76-
89+
7790
state.in_modified_expr = old_in_modified_expr
7891
state.scope != s0 && (state.scope = s0)
7992
return state.scope
8093
end
8194

8295
mutable struct Delayed <: State
8396
scope::Scope
97+
env::ExternalEnv
8498
server
8599
end
86100

@@ -95,8 +109,8 @@ function (state::Delayed)(x::EXPR)
95109
traverse(x, state)
96110
if state.scope != s0
97111
for b in values(state.scope.names)
98-
infer_type_by_use(b, state.server)
99-
check_unused_binding(b, state.scope)
112+
infer_type_by_use(b, state.env)
113+
check_unused_binding(b, state.scope)
100114
end
101115
state.scope = s0
102116
end
@@ -105,6 +119,7 @@ end
105119

106120
mutable struct ResolveOnly <: State
107121
scope::Scope
122+
env::ExternalEnv
108123
server
109124
end
110125

@@ -132,26 +147,27 @@ Performs a semantic pass across a project from the entry point `file`. A first p
132147
"""
133148
function semantic_pass(file, modified_expr=nothing)
134149
server = file.server
135-
setscope!(getcst(file), Scope(nothing, getcst(file), Dict(), Dict{Symbol,Any}(:Base => getsymbolserver(server)[:Base], :Core => getsymbolserver(server)[:Core]), nothing))
136-
state = Toplevel(file, [getpath(file)], scopeof(getcst(file)), modified_expr === nothing, modified_expr, EXPR[], EXPR[], server)
150+
env = getenv(file, server)
151+
setscope!(getcst(file), Scope(nothing, getcst(file), Dict(), Dict{Symbol,Any}(:Base => env.symbols[:Base], :Core => env.symbols[:Core]), nothing))
152+
state = Toplevel(file, [getpath(file)], scopeof(getcst(file)), modified_expr === nothing, modified_expr, EXPR[], EXPR[], env, server)
137153
state(getcst(file))
138154
for x in state.delayed
139155
if hasscope(x)
140-
traverse(x, Delayed(scopeof(x), server))
141-
for b in values(scopeof(x).names)
142-
infer_type_by_use(b, state.server)
156+
traverse(x, Delayed(scopeof(x), env, server))
157+
for (k, b) in scopeof(x).names
158+
infer_type_by_use(b, env)
143159
check_unused_binding(b, scopeof(x))
144160
end
145161
else
146-
traverse(x, Delayed(retrieve_delayed_scope(x), server))
162+
traverse(x, Delayed(retrieve_delayed_scope(x), env, server))
147163
end
148164
end
149165
if state.resolveonly !== nothing
150166
for x in state.resolveonly
151167
if hasscope(x)
152-
traverse(x, ResolveOnly(scopeof(x), server))
168+
traverse(x, ResolveOnly(scopeof(x), env, server))
153169
else
154-
traverse(x, ResolveOnly(retrieve_delayed_scope(x), server))
170+
traverse(x, ResolveOnly(retrieve_delayed_scope(x), env, server))
155171
end
156172
end
157173
end
@@ -263,7 +279,7 @@ the path of the file to be included. Has limited support for `joinpath` calls.
263279
function get_path(x::EXPR, state)
264280
if CSTParser.iscall(x) && length(x.args) == 2
265281
parg = x.args[2]
266-
282+
267283
if CSTParser.isstringliteral(parg)
268284
if occursin("\0", valof(parg))
269285
seterror!(parg, IncludePathContainsNULL)

src/bindings.jl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ function add_binding(x, state, scope=state.scope)
300300
lhs_ref = refof_maybe_getfield(parentof(parentof(b.name)).args[1])
301301
if lhs_ref isa SymbolServer.ModuleStore && haskey(lhs_ref.vals, Symbol(name))
302302
# Overloading
303-
if haskey(tls.names, name) && eventually_overloads(tls.names[name], lhs_ref.vals[Symbol(name)], state.server)
303+
if haskey(tls.names, name) && eventually_overloads(tls.names[name], lhs_ref.vals[Symbol(name)], state)
304304
# Though we're explicitly naming a function for overloading, it has already been imported to the toplevel scope.
305305
if !hasref(b.name)
306306
setref!(b.name, tls.names[name]) # Add ref to previous overload
@@ -311,7 +311,7 @@ function add_binding(x, state, scope=state.scope)
311311
# Name is already available
312312
tls.names[name] = b
313313
if !hasref(b.name) # Is this an appropriate indicator that we've not marked the overload?
314-
push!(b.refs, maybe_lookup(lhs_ref[Symbol(name)], state.server))
314+
push!(b.refs, maybe_lookup(lhs_ref[Symbol(name)], state))
315315
setref!(b.name, b) # we actually set the rhs of the qualified name to point to this binding
316316
end
317317
else
@@ -373,13 +373,13 @@ name_is_getfield(x) = parentof(x) isa EXPR && parentof(parentof(x)) isa EXPR &&
373373

374374

375375
"""
376-
eventually_overloads(b, x, server)
376+
eventually_overloads(b, x, state)
377377
378378
379379
"""
380-
eventually_overloads(b::Binding, ss::SymbolServer.SymStore, server) = b.val == ss || (b.refs !== nothing && length(b.refs) > 0 && first(b.refs) == ss)
381-
eventually_overloads(b::Binding, ss::SymbolServer.VarRef, server) = eventually_overloads(b, maybe_lookup(ss, server), server)
382-
eventually_overloads(b, ss, server) = false
380+
eventually_overloads(b::Binding, ss::SymbolServer.SymStore, state) = b.val == ss || (b.refs !== nothing && length(b.refs) > 0 && first(b.refs) == ss)
381+
eventually_overloads(b::Binding, ss::SymbolServer.VarRef, state) = eventually_overloads(b, maybe_lookup(ss, state), state)
382+
eventually_overloads(b, ss, state) = false
383383

384384
isglobal(name, scope) = false
385385
isglobal(name::String, scope) = scope !== nothing && scopehasbinding(scope, "#globals") && name in scope.names["#globals"].refs

src/imports.jl

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@ function resolve_import_block(x::EXPR, state::State, root, usinged, markfinal=tr
1717
arg = x.args[i]
1818
if isoperator(arg) && valof(arg) == "."
1919
# Leading dots. Can only be leading elements.
20-
if root == getsymbolserver(state.server)
20+
if root == getsymbols(state)
2121
root = state.scope
2222
elseif root isa Scope && parentof(root) !== nothing
2323
root = parentof(root)
2424
else
2525
return
2626
end
2727
elseif isidentifier(arg) || (i == n && (CSTParser.ismacroname(arg) || isoperator(arg)))
28-
root = maybe_lookup(hasref(arg) ? refof(arg) : _get_field(root, arg, state), state.server)
28+
root = maybe_lookup(hasref(arg) ? refof(arg) : _get_field(root, arg, state), state)
2929
setref!(arg, root)
3030
if i == n
3131
markfinal && _mark_import_arg(arg, root, state, usinged)
@@ -37,7 +37,7 @@ function resolve_import_block(x::EXPR, state::State, root, usinged, markfinal=tr
3737
end
3838
end
3939

40-
function resolve_import(x::EXPR, state::State, root=getsymbolserver(state.server))
40+
function resolve_import(x::EXPR, state::State, root=getsymbols(state))
4141
if headof(x) === :using || headof(x) === :import
4242
usinged = headof(x) === :using
4343
if length(x.args) > 0 && isoperator(headof(x.args[1])) && valof(headof(x.args[1])) == ":"
@@ -50,9 +50,6 @@ function resolve_import(x::EXPR, state::State, root=getsymbolserver(state.server
5050
resolve_import_block(x.args[i], state, root, usinged)
5151
end
5252
end
53-
for i = 1:length(x.args)
54-
resolve_import_block(x.args[i], state, root, usinged)
55-
end
5653
end
5754
end
5855

@@ -62,7 +59,7 @@ function _mark_import_arg(arg, par, state, usinged)
6259
push!(par.refs, arg)
6360
end
6461
if par isa SymbolServer.VarRef
65-
par = SymbolServer._lookup(par, getsymbolserver(state.server), true)
62+
par = SymbolServer._lookup(par, getsymbols(state), true)
6663
!(par isa SymbolServer.SymStore) && return
6764
end
6865
if bindingof(arg) === nothing
@@ -116,20 +113,22 @@ function _get_field(par, arg, state)
116113
if (arg_scope = retrieve_scope(arg)) !== nothing && (tlm = get_named_toplevel_module(arg_scope, arg_str_rep)) !== nothing && hasbinding(tlm)
117114
return bindingof(tlm)
118115
elseif haskey(par, Symbol(arg_str_rep))
119-
return par[Symbol(arg_str_rep)]
116+
if isempty(state.env.project_deps) || Symbol(arg_str_rep) in state.env.project_deps
117+
return par[Symbol(arg_str_rep)]
118+
end
120119
end
121120
elseif par isa SymbolServer.ModuleStore # imported module
122121
if Symbol(arg_str_rep) === par.name.name
123122
return par
124123
elseif haskey(par, Symbol(arg_str_rep))
125124
par = par[Symbol(arg_str_rep)]
126125
if par isa SymbolServer.VarRef # reference to dependency
127-
return SymbolServer._lookup(par, getsymbolserver(state.server), true)
126+
return SymbolServer._lookup(par, getsymbols(state), true)
128127
end
129128
return par
130129
end
131130
for used_module_name in par.used_modules
132-
used_module = maybe_lookup(par[used_module_name], state.server)
131+
used_module = maybe_lookup(par[used_module_name], state)
133132
if used_module !== nothing && isexportedby(Symbol(arg_str_rep), used_module)
134133
return used_module[Symbol(arg_str_rep)]
135134
end
@@ -140,7 +139,7 @@ function _get_field(par, arg, state)
140139
elseif par.modules !== nothing
141140
for used_module in values(par.modules)
142141
if used_module isa SymbolServer.ModuleStore && isexportedby(Symbol(arg_str_rep), used_module)
143-
return maybe_lookup(used_module[Symbol(arg_str_rep)], state.server)
142+
return maybe_lookup(used_module[Symbol(arg_str_rep)], state)
144143
elseif used_module isa Scope && scope_exports(used_module, arg_str_rep, state)
145144
return used_module.names[arg_str_rep]
146145
end

src/interface.jl

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@ it is paired with a collected list of errors/hints.
1515
"""
1616
function lint_string(s::String, server = setup_server(); gethints = false)
1717
empty!(server.files)
18-
f = StaticLint.File("", s, CSTParser.parse(s, true), nothing, server)
19-
StaticLint.setroot(f, f)
20-
StaticLint.setfile(server, "", f)
21-
StaticLint.semantic_pass(f)
22-
StaticLint.check_all(f.cst, StaticLint.LintOptions(), server)
18+
f = File("", s, CSTParser.parse(s, true), nothing, server)
19+
env = getenv(f, server)
20+
setroot(f, f)
21+
setfile(server, "", f)
22+
semantic_pass(f)
23+
check_all(f.cst, LintOptions(), env)
2324
if gethints
24-
return f.cst, [(x, string(haserror(x) ? LintCodeDescriptions[x.meta.error] : "Missing reference", " at offset ", offset)) for (offset, x) in collect_hints(f.cst, server)]
25+
return f.cst, [(x, string(haserror(x) ? LintCodeDescriptions[x.meta.error] : "Missing reference", " at offset ", offset)) for (offset, x) in collect_hints(f.cst, env)]
2526
else
2627
return f.cst
2728
end
@@ -30,23 +31,23 @@ end
3031
"""
3132
lint_file(rootpath, server)
3233
33-
Read a file from disc, parse and run a semantic pass over it. The file should be the
34+
Read a file from disc, parse and run a semantic pass over it. The file should be the
3435
root of a project, e.g. for this package that file is `src/StaticLint.jl`. Other files
3536
in the project will be loaded automatically (calls to `include` with complicated arguments
36-
are not handled, see `followinclude` for details). A `FileServer` will be returned
37+
are not handled, see `followinclude` for details). A `FileServer` will be returned
3738
containing the `File`s of the package.
3839
"""
3940
function lint_file(rootpath, server = setup_server(); gethints = false)
4041
empty!(server.files)
41-
root = StaticLint.loadfile(server, rootpath)
42-
StaticLint.semantic_pass(root)
42+
root = loadfile(server, rootpath)
43+
semantic_pass(root)
4344
for f in values(server.files)
44-
StaticLint.check_all(f.cst, StaticLint.LintOptions(), server)
45+
check_all(f.cst, LintOptions(), getenv(f, server))
4546
end
4647
if gethints
4748
hints = []
4849
for (p,f) in server.files
49-
append!(hints, [(x, string(haserror(x) ? LintCodeDescriptions[x.meta.error] : "Missing reference", " at offset ", offset, " of ", p)) for (offset, x) in collect_hints(f.cst, server)])
50+
append!(hints, [(x, string(haserror(x) ? LintCodeDescriptions[x.meta.error] : "Missing reference", " at offset ", offset, " of ", p)) for (offset, x) in collect_hints(f.cst, getenv(f, server))])
5051
end
5152
return root, hints
5253
else

0 commit comments

Comments
 (0)