unused_variable_warnings #3

Merged
mattiashz merged 4 commits from unused_variable_warnings into main 2026-04-13 13:39:37 +02:00
3 changed files with 76 additions and 3 deletions
Showing only changes of commit 0bfbfcdde5 - Show all commits

View file

@ -222,6 +222,12 @@ func (c *Compiler) Compile(lines []preproc.Line) ([]string, error) {
// Analyze for overlapping absolute addresses in function call chains // Analyze for overlapping absolute addresses in function call chains
c.checkAbsoluteOverlaps() c.checkAbsoluteOverlaps()
// Check for unused variables and print warnings
warnings := c.ctx.SymbolTable.CheckUnused()
for _, warning := range warnings {
_, _ = fmt.Fprintf(os.Stderr, "%s\n", warning)
}
// Assemble final output with headers and footers // Assemble final output with headers and footers
return c.assembleOutput(codeOutput), nil return c.assembleOutput(codeOutput), nil
} }

View file

@ -130,8 +130,8 @@ func (fh *FunctionHandler) HandleFuncDecl(line preproc.Line) ([]string, error) {
} }
} }
// Look up variable in symbol table // Look up variable in symbol table (validation only, not usage)
sym := fh.symTable.Lookup(varName, []string{funcName}) sym := fh.symTable.LookupWithoutUsage(varName, []string{funcName})
if sym == nil { if sym == nil {
fh.currentFuncs = fh.currentFuncs[:len(fh.currentFuncs)-1] fh.currentFuncs = fh.currentFuncs[:len(fh.currentFuncs)-1]
return nil, fmt.Errorf("%s:%d: FUNC %s: parameter %q not declared", line.Filename, line.LineNo, funcName, varName) return nil, fmt.Errorf("%s:%d: FUNC %s: parameter %q not declared", line.Filename, line.LineNo, funcName, varName)

View file

@ -26,6 +26,12 @@ const (
FlagLabelRef FlagLabelRef
) )
// Usage tracking for variables
const (
UsageNone uint8 = iota
UsageUsed
)
// Symbol represents a variable or constant declaration // Symbol represents a variable or constant declaration
type Symbol struct { type Symbol struct {
Name string Name string
@ -34,6 +40,7 @@ type Symbol struct {
Value uint16 // init value or const value Value uint16 // init value or const value
AbsAddr uint16 // if FlagAbsolute set AbsAddr uint16 // if FlagAbsolute set
LabelRef string // if FlagLabelRef set LabelRef string // if FlagLabelRef set
Usage uint8 // tracks variable usage (see Usage constants)
} }
// Helper methods for Symbol // Helper methods for Symbol
@ -52,6 +59,11 @@ func (s *Symbol) IsAbsolute() bool { return s.Has(FlagAbsolute) }
func (s *Symbol) IsZeroPage() bool { return s.Has(FlagZeroPage) } func (s *Symbol) IsZeroPage() bool { return s.Has(FlagZeroPage) }
func (s *Symbol) IsZeroPagePointer() bool { return s.HasAll(FlagAbsolute | FlagZeroPage | FlagWord) } func (s *Symbol) IsZeroPagePointer() bool { return s.HasAll(FlagAbsolute | FlagZeroPage | FlagWord) }
// MarkUsed marks the symbol as used (should not be called for constants or absolutes)
func (s *Symbol) MarkUsed() {
s.Usage = UsageUsed
}
// FullName returns the fully qualified name (scope.name or just name) // FullName returns the fully qualified name (scope.name or just name)
func (s *Symbol) FullName() string { func (s *Symbol) FullName() string {
if s.Scope == "" { if s.Scope == "" {
@ -208,12 +220,17 @@ func (st *SymbolTable) add(sym *Symbol) error {
// Lookup finds a symbol by name, resolving scope // Lookup finds a symbol by name, resolving scope
// Searches local scope first (if currentScopes provided), then global // Searches local scope first (if currentScopes provided), then global
// Marks non-constant, non-absolute variables as used
func (st *SymbolTable) Lookup(name string, currentScopes []string) *Symbol { func (st *SymbolTable) Lookup(name string, currentScopes []string) *Symbol {
// Try local scopes first (innermost to outermost) // Try local scopes first (innermost to outermost)
for i := len(currentScopes) - 1; i >= 0; i-- { for i := len(currentScopes) - 1; i >= 0; i-- {
scope := currentScopes[i] scope := currentScopes[i]
if scopeMap, ok := st.byScope[scope]; ok { if scopeMap, ok := st.byScope[scope]; ok {
if sym, ok := scopeMap[name]; ok { if sym, ok := scopeMap[name]; ok {
// Mark as used if it's a regular variable (not constant, not absolute)
if !sym.IsConst() && !sym.IsAbsolute() {
sym.MarkUsed()
}
return sym return sym
} }
} }
@ -222,6 +239,10 @@ func (st *SymbolTable) Lookup(name string, currentScopes []string) *Symbol {
// Try global scope // Try global scope
if scopeMap, ok := st.byScope[""]; ok { if scopeMap, ok := st.byScope[""]; ok {
if sym, ok := scopeMap[name]; ok { if sym, ok := scopeMap[name]; ok {
// Mark as used if it's a regular variable (not constant, not absolute)
if !sym.IsConst() && !sym.IsAbsolute() {
sym.MarkUsed()
}
return sym return sym
} }
} }
@ -253,10 +274,33 @@ func (st *SymbolTable) ExpandName(name string, currentScopes []string) string {
return name return name
} }
// LookupWithoutUsage finds a symbol by name without marking it as used
// Used for validation-only lookups (e.g., checking if variable exists)
func (st *SymbolTable) LookupWithoutUsage(name string, currentScopes []string) *Symbol {
// Try local scopes first (innermost to outermost)
for i := len(currentScopes) - 1; i >= 0; i-- {
scope := currentScopes[i]
if scopeMap, ok := st.byScope[scope]; ok {
if sym, ok := scopeMap[name]; ok {
return sym
}
}
}
// Try global scope
if scopeMap, ok := st.byScope[""]; ok {
if sym, ok := scopeMap[name]; ok {
return sym
}
}
return nil
}
// LookupConstant looks up a constant by name and returns its value if found // LookupConstant looks up a constant by name and returns its value if found
// Returns (value, true) if symbol exists and is a constant, (0, false) otherwise // Returns (value, true) if symbol exists and is a constant, (0, false) otherwise
func (st *SymbolTable) LookupConstant(name string, currentScopes []string) (int64, bool) { func (st *SymbolTable) LookupConstant(name string, currentScopes []string) (int64, bool) {
sym := st.Lookup(name, currentScopes) sym := st.LookupWithoutUsage(name, currentScopes)
if sym != nil && sym.IsConst() { if sym != nil && sym.IsConst() {
return int64(sym.Value), true return int64(sym.Value), true
} }
@ -271,6 +315,29 @@ func (st *SymbolTable) ConstantLookupFunc(currentScopes []string) func(string) (
} }
} }
// CheckUnused returns warnings for unused variables
// Returns slice of warning messages for regular variables (not constants, not absolutes) that were never used
func (st *SymbolTable) CheckUnused() []string {
var warnings []string
for _, sym := range st.symbols {
// Skip constants and absolute variables
if sym.IsConst() || sym.IsAbsolute() {
continue
}
// Check if variable was never used
if sym.Usage == UsageNone {
// Format warning message
var scopeInfo string
if sym.Scope != "" {
scopeInfo = fmt.Sprintf(" in function '%s'", sym.Scope)
}
warning := fmt.Sprintf("warning: variable '%s'%s declared but never used", sym.Name, scopeInfo)
warnings = append(warnings, warning)
}
}
return warnings
}
// String representation for debugging // String representation for debugging
func (s *Symbol) String() string { func (s *Symbol) String() string {
var parts []string var parts []string