Skip to main content

Overview

The IScope interface represents a scope in Lua code. Scopes define the visibility and lifetime of variables, and form a hierarchical structure from global scope down through files, functions, and blocks.

Scope Kinds

Loretta recognizes four kinds of scopes through the ScopeKind enum:
  • Global - The top-level scope containing all files
  • File - A file’s scope (represented by IFileScope)
  • Function - A function’s scope (represented by IFunctionScope)
  • Block - A block scope (do…end, if, while, etc.)

Properties

Kind

Gets the kind of this scope.
ScopeKind Kind { get; }
Example:
if (scope.Kind == ScopeKind.Function)
{
    var funcScope = (IFunctionScope)scope;
    // Access function-specific properties
}

Node

Gets the syntax node that originated this scope. Returns null for the global scope.
SyntaxNode? Node { get; }
Example:
if (scope.Node is FunctionDeclarationStatementSyntax funcDecl)
{
    Console.WriteLine($"Function name: {funcDecl.Name}");
}

ContainingScope

Gets the parent scope that contains this scope, or null for the global scope.
IScope? ContainingScope { get; }
Example:
// Navigate up the scope hierarchy
for (var current = scope; current != null; current = current.ContainingScope)
{
    Console.WriteLine($"{current.Kind} scope");
}

Parent

Deprecated. Use ContainingScope instead.
[Obsolete("Use ContainingScope instead.")]
IScope? Parent { get; }

DeclaredVariables

Gets the variables declared directly within this scope. Since variables can be shadowed or redeclared, there may be multiple variables with the same name.
IEnumerable<IVariable> DeclaredVariables { get; }
Example:
foreach (var variable in scope.DeclaredVariables)
{
    Console.WriteLine($"{variable.Name}: {variable.Kind}");
}

ReferencedVariables

Gets the variables that are directly referenced by this scope (including both declared and captured variables).
IEnumerable<IVariable> ReferencedVariables { get; }
Example:
var referencedCount = scope.ReferencedVariables.Count();
Console.WriteLine($"This scope references {referencedCount} variables");

ContainedScopes

Gets the scopes directly contained within this scope.
IEnumerable<IScope> ContainedScopes { get; }
Example:
// Recursively print all scopes
void PrintScopeTree(IScope scope, int indent = 0)
{
    var prefix = new string(' ', indent * 2);
    Console.WriteLine($"{prefix}{scope.Kind}");
    
    foreach (var child in scope.ContainedScopes)
    {
        PrintScopeTree(child, indent + 1);
    }
}

PrintScopeTree(script.RootScope);

GotoLabels

Gets the goto labels contained within this scope.
IEnumerable<IGotoLabel> GotoLabels { get; }
Example:
foreach (var label in scope.GotoLabels)
{
    Console.WriteLine($"Label: {label.Name}");
    Console.WriteLine($"  Jumps: {label.JumpSyntaxes.Count()}");
}

Methods

FindVariable

Attempts to find a variable with the given name, searching up the scope hierarchy.
IVariable? FindVariable(string name, ScopeKind kind = ScopeKind.Global)
Parameters:
  • name - The name of the variable to search for
  • kind - The kind of scope up to which to search (defaults to ScopeKind.Global)
Returns:
  • The variable if found, or null otherwise
Exceptions:
  • ArgumentNullException - Thrown when name is null
  • ArgumentException - Thrown when name is not a valid identifier
Search Behavior: The kind parameter controls how far up the scope hierarchy to search:
  • ScopeKind.Block - Searches only block scopes
  • ScopeKind.Function - Searches function and block scopes
  • ScopeKind.File - Searches file, function, and block scopes
  • ScopeKind.Global - Searches all scopes (global, file, function, and block)
Example:
// Search for a variable in the current scope and parent scopes
var variable = scope.FindVariable("myVar");

if (variable != null)
{
    Console.WriteLine($"Found {variable.Name} in {variable.ContainingScope.Kind} scope");
}
else
{
    Console.WriteLine("Variable not found");
}

// Search only up to the function scope
var localVar = scope.FindVariable("param", ScopeKind.Function);
Here’s a complete example showing how to navigate and analyze the scope hierarchy:
using Loretta.CodeAnalysis;
using Loretta.CodeAnalysis.Lua;
using Loretta.CodeAnalysis.Lua.Syntax;
using System.Collections.Immutable;

var code = @"
local globalVar = 1

function outer(param1)
    local outerLocal = 2
    
    function inner(param2)
        local innerLocal = 3
        print(globalVar, param1, outerLocal, innerLocal)
    end
    
    do
        local blockLocal = 4
    end
end
";

var tree = LuaSyntaxTree.ParseText(code);
var script = new Script(ImmutableArray.Create(tree));

// Navigate to the innermost function
var innerFunc = tree.GetRoot()
    .DescendantNodes()
    .OfType<AnonymousFunctionExpressionSyntax>()
    .Last();

var innerScope = script.GetScope(innerFunc);

if (innerScope != null)
{
    Console.WriteLine("Scope hierarchy from inner to outer:");
    var current = innerScope;
    var level = 0;
    
    while (current != null)
    {
        Console.WriteLine($"{new string(' ', level * 2)}{current.Kind} scope");
        Console.WriteLine($"{new string(' ', level * 2)}  Declared variables: {string.Join(", ", current.DeclaredVariables.Select(v => v.Name))}");
        Console.WriteLine($"{new string(' ', level * 2)}  Referenced variables: {string.Join(", ", current.ReferencedVariables.Select(v => v.Name))}");
        
        current = current.ContainingScope;
        level++;
    }
    
    // Find a variable from the inner scope
    Console.WriteLine("\nVariable lookup from inner scope:");
    
    var vars = new[] { "innerLocal", "param2", "param1", "outerLocal", "globalVar" };
    foreach (var varName in vars)
    {
        var variable = innerScope.FindVariable(varName);
        if (variable != null)
        {
            Console.WriteLine($"  {varName}: found in {variable.ContainingScope.Kind} scope");
        }
    }
}

Specialized Scope Interfaces

IFileScope

Represents a file’s scope with special implicit variables.
public interface IFileScope : IScope
{
    IVariable ArgVariable { get; }      // The implicit 'arg' parameter
    IVariable VarArgParameter { get; }  // The implicit '...' vararg
}
Example:
var root = tree.GetRoot();
var fileScope = script.GetScope(root) as IFileScope;

if (fileScope != null)
{
    Console.WriteLine($"File has implicit 'arg': {fileScope.ArgVariable.Name}");
    Console.WriteLine($"File has implicit vararg: {fileScope.VarArgParameter.Name}");
}

IFunctionScope

Represents a function’s scope with parameters and captured variables.
public interface IFunctionScope : IScope
{
    IEnumerable<IVariable> Parameters { get; }        // Function parameters
    IEnumerable<IVariable> CapturedVariables { get; } // Variables captured as upvalues
}
Example:
var funcDecl = tree.GetRoot()
    .DescendantNodes()
    .OfType<FunctionDeclarationStatementSyntax>()
    .First();

var funcScope = script.GetScope(funcDecl) as IFunctionScope;

if (funcScope != null)
{
    Console.WriteLine("Function parameters:");
    foreach (var param in funcScope.Parameters)
    {
        Console.WriteLine($"  - {param.Name}");
    }
    
    Console.WriteLine("\nCaptured variables (upvalues):");
    foreach (var captured in funcScope.CapturedVariables)
    {
        Console.WriteLine($"  - {captured.Name} (from {captured.ContainingScope.Kind} scope)");
    }
}

Detecting Closure Captures

Here’s an example that detects when variables are captured by closures:
var code = @"
function makeCounter()
    local count = 0
    
    return function()
        count = count + 1
        return count
    end
end
";

var tree = LuaSyntaxTree.ParseText(code);
var script = new Script(ImmutableArray.Create(tree));

// Find all function scopes
var functionScopes = tree.GetRoot()
    .DescendantNodes()
    .Where(n => n is FunctionDeclarationStatementSyntax || n is AnonymousFunctionExpressionSyntax)
    .Select(n => script.GetScope(n))
    .OfType<IFunctionScope>();

foreach (var funcScope in functionScopes)
{
    if (funcScope.CapturedVariables.Any())
    {
        Console.WriteLine($"\nFunction at line {funcScope.Node?.GetLocation().GetLineSpan().StartLinePosition.Line} captures:");
        
        foreach (var captured in funcScope.CapturedVariables)
        {
            Console.WriteLine($"  - {captured.Name} (declared in {captured.ContainingScope.Kind} scope)");
            Console.WriteLine($"    Captured by {captured.CapturingScopes.Count()} functions");
        }
    }
}

See Also

Build docs developers (and LLMs) love