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

Abierto
#1,366 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
5/5
Tiempo estimado
Más de una semana
Aptitud para principiantes
35/100
Tipo de issue
Nueva funcionalidad
Claridad
Bastante claro
Estado de actividad
Activo
Stack tecnológico
rust

Línea de trabajo

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.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

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.
Lenguaje dominante
Rust
Estrellas
530
Forks
111
Merge medio
2 d 6 h
PR fusionados (30 d)
32

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de EmmyLuaLs/emmylua-analyzer-rust

Todos los issues de EmmyLuaLs/emmylua-analyzer-rust

Issues similares

Más issues de Rust

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.