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.
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);
Navigating Scope Hierarchy
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