Feature Request: Add `@global_scope` and `@using_scope` annotations for organizing global variable visibility

Aperta
#1,366 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
5/5
Tempo stimato
Più di una settimana
Idoneità per principianti
35/100
Tipo di issue
Funzionalità
Chiarezza
Abbastanza chiara
Stato di attività
Attiva
Stack tecnologico
rust

Direzione di ricerca

Start by reading the analyzer's annotation parsing, workspace indexing, global name resolution, diagnostics, and completion entry points; the issue does not name specific files or tests. Trace how existing @namespace support is represented and consumed, then map the requested declaration, import, conflict, and _G behavior onto those paths. Done means the new annotations affect static visibility and type resolution, report duplicate, unknown, and ambiguous scopes, and leave Lua runtime behavior unchanged.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Summary

Propose two new annotations for EmmyLua that introduce a static-analysis-level organization layer for global variables, parallel to how @namespace organizes types:

  • ---@global_scope <name> — declares that subsequent global variable assignments in the current file belong to the named scope.
  • ---@using_scope <name> — declares that the current file may resolve global symbols provided by the named scope.

These annotations do not affect Lua runtime behavior, do not modify _G, and are purely editor/analyzer hints for symbol visibility, completion filtering, and type resolution.

Motivation

Large Lua projects (game mods, embedded scripting environments, framework-driven UIs) frequently rely on implicit global conventions:

  • A bootstrap file loads modules into _G (e.g., mp = require 'mp').
  • A framework exposes a DSL as globals (import, bind, Widget, Button, ...).
  • Downstream files assume these globals exist without any explicit declaration.

Today, EmmyLua has no way to express which globals a given file is expected to see. The consequences:

  1. Undefined-global diagnostics are either noisy (every DSL symbol flagged) or disabled entirely (losing all protection).
  2. Completion lists are polluted by every global declared anywhere in the workspace.
  3. There is no way to trace which file/meta provided a given global.
  4. Conflicting global definitions across unrelated frameworks cannot be disambiguated.

@namespace solves this for types but not for values. This proposal fills that gap.

Proposed Syntax

Declaration side
---@global_scope mp_global

mp = require 'mp'

---@global_scope ui_framework

import = function(modname) end
mod = function(modname) end
bind = function(slot, widget) end
Widget = {}
ElevatedButton = {}
Consumption side
---@using_scope mp_global

mp.get_script_name() -- resolved via mp_global

---@using_scope ui_framework

import('mp')
import('player')
bind('app.controls.right', Builder{
    builder: function (context)
        return ElevatedButton{
            color = Theme.of(context).data.surface_color,
            on_click = function () logger.info('Button click!') end,
            child = Text('ElevatedButton')
        }
    end
})
Naming rationale
  • @using_scope is introduced as a new annotation rather than reusing @using, to avoid ambiguity with @namespace usage. Mixing a type-introduction directive and a value-scope-introduction directive under the same keyword would force the parser to disambiguate by context, which is unnecessary complexity.
  • An optional @using alias for @using_scope may be considered only if it can be resolved unambiguously (e.g., permitted exclusively at file header level and only when no type namespace is in scope). This is a nice-to-have, not a requirement.

Semantics

1. Symbol attribution — full-file, global assignments only
  • @global_scope <name> applies to the entire file it appears in, regardless of position within the file.
  • Only global variable assignments (assignments without a preceding local keyword) that appear in the file are attributed to the scope.
  • Local variables are unaffected.
  • A file may declare at most one @global_scope. Multiple declarations should be reported as an error.
2. Injection — static only, never runtime
  • @using_scope <name> tells the analyzer: when resolving an unresolved global reference in this file, include the symbols attributed to <name> in the candidate set.
  • It does not add anything to _G, does not change runtime behavior, and does not emit any code.
  • It is equivalent in spirit to "open this scope for name resolution in this file."
3. Relationship with _G
  • @global_scope is a symbol-organization concept; _G is a runtime-access concept. They are orthogonal.
  • However, for explicit _G.foo accesses, the analyzer should still consult scope information to resolve foo's type when foo is provided by a @using_scoped scope. This does not "interfere with _G"; it merely gives the analyzer more accurate source information for the same field.
  • The rule can be summarized as: _G's runtime semantics are owned by _G; type/visibility information for its fields is owned by scopes.
4. Conflict resolution

Conflicts should follow the same intuition as ordinary global-variable shadowing, with one important addition:

  • File-local definition shadows imported scope symbol. If a file has both @using_scope A and its own foo = ..., the file-local foo wins; the imported foo is shadowed.
  • Multiple imported scopes with the same symbol produce a diagnostic. If a file has @using_scope A and @using_scope B and both define foo, the analyzer should emit an ambiguity diagnostic (e.g., global 'foo' is provided by multiple scopes: A, B; qualify it or remove one @using_scope) rather than silently picking one. Silently picking a winner makes the scope abstraction leaky and is almost always a user mistake.

Relationship with @namespace

The two annotation families are intentionally symmetric:

Concept Type-level Value-level
Declare a grouping @namespace @global_scope
Open a grouping in a file (implicit via class/alias resolution) @using_scope

@global_scope does not replace type information. Symbols inside a scope can (and should) still carry ---@type, ---@class, ---@field, etc. @global_scope only records ownership/visibility, not type.

Alternatives Considered

  1. @meta + direct global declarations — Provides types but no visibility scoping. All meta-declared globals are visible workspace-wide.
  2. @namespace — Designed for types (classes, aliases), not for value-level global visibility.
  3. ---@class _G + ---@field — Merges everything into one _G view; no isolation between scopes.
  4. Disabling undefined-global diagnostics via .emmyrc.json — Hides the symptom, provides no organization.
  5. Reusing @using — Rejected due to ambiguity with type-namespace semantics (see Naming rationale).

Implementation Notes / Open Questions

  • Scope resolution must be able to locate a scope declared in another file (typically a @meta file). The proposal does not mandate a file-naming convention; any .lua file containing @global_scope <name> should register that scope in the workspace index.
  • Diagnostics to add:
    • Duplicate @global_scope in the same file.
    • @using_scope referencing an unknown scope.
    • Ambiguous symbol from multiple @using_scoped scopes.
  • Completion filtering should prioritize symbols from @using_scoped scopes and file-local globals; other workspace globals may still be offered but de-prioritized (configurable).
  • Should @global_scope declarations themselves be permitted only in meta files in a future strict mode? Left as an open question.
  • Interaction with ---@meta: a meta file that declares @global_scope should be treated exactly like a regular file for attribution purposes.

Impact

  • No runtime impact. Pure analyzer feature.
  • No breaking changes. Files without the new annotations behave exactly as today.
  • Significant quality-of-life improvement for projects that rely on framework-injected global DSLs and module-loading bootstraps.
Lingua principale
Rust
Stelle
527
Fork
110
Merge medio
2g 6h
PR unite (30g)
32

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di EmmyLuaLs/emmylua-analyzer-rust

Tutte le issue di EmmyLuaLs/emmylua-analyzer-rust

Issue simili

Altre issue su Rust

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.