Skip to content

Commit

Permalink
fix: report all unused variables, not just the first one found
Browse files Browse the repository at this point in the history
  • Loading branch information
tucats committed Jan 4, 2025
1 parent 1b0ed6f commit c851f97
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 16 deletions.
2 changes: 1 addition & 1 deletion compiler/assignment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ func TestCompiler_compileAssignment(t *testing.T) {
if err := c.compileAssignment(); (err == nil) != (tt.wantErr == nil) {
t.Errorf("Compiler.compileAssignment() %s, error = %v, wantErr %v", tt.name, err, tt.wantErr)
} else {
if err != nil && err.Error() != tt.wantErr.Error() {
if err != nil && !errors.SameBaseError(err, tt.wantErr) {
t.Errorf("Compiler.compileAssignment() %s, error = %v, wantErr %v", tt.name, err, tt.wantErr)
}
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func TestCompiler_compileBlock(t *testing.T) {
if err := c.compileBlock(); (err == nil) != (tt.wantErr == nil) {
t.Errorf("Compiler.compileBlock() %s, error = %v, wantErr %v", tt.name, err, tt.wantErr)
} else {
if err != nil && err.Error() != tt.wantErr.Error() {
if err != nil && !errors.SameBaseError(err, tt.wantErr) {
t.Errorf("Compiler.compileBlock() %s, error = %v, wantErr %v", tt.name, err, tt.wantErr)
}
}
Expand Down
4 changes: 2 additions & 2 deletions compiler/constant_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,10 @@ func TestCompiler_compileConst(t *testing.T) {
if err := c.compileConst(); (err == nil) != (tt.wantErr == nil) {
t.Errorf("Compiler.compileConst() %s, error = %v, wantErr %v", tt.name, err, tt.wantErr)
} else {
if err != nil && err.Error() != tt.wantErr.Error() {
if err != nil && !errors.SameBaseError(err, tt.wantErr) {
t.Errorf("Compiler.compileConst() %s, error = %v, wantErr %v", tt.name, err, tt.wantErr)
}
}
})
})
}
}
53 changes: 43 additions & 10 deletions compiler/symbols.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package compiler

import (
"github.com/tucats/ego/app-cli/ui"
"github.com/tucats/ego/errors"
)

Expand All @@ -12,6 +13,9 @@ type scope struct {

type scopeStack []scope

// Flag used to turn on logging for symbol tracking, used during development debugging.
var symbolUsageDebugging = false

func newScope(name string, line int) scope {
return scope{
module: name,
Expand All @@ -32,33 +36,52 @@ func (c *Compiler) PushScope() {
}

func (c *Compiler) PopScope() error {
var err *errors.Error

if !c.flags.unusedVars {
return nil
}

if len(c.scopes) == 0 {
pos := len(c.scopes) - 1
if pos < 0 {
return nil
}

scope := c.scopes[len(c.scopes)-1]
for _, used := range scope.usage {
if used != nil {
return used
scope := c.scopes[pos]
for name, usageError := range scope.usage {
if usageError != nil {
if symbolUsageDebugging {
ui.Log(ui.InternalLogger, "Usage error %s, %v", name, usageError)
}

err = errors.Chain(err, usageError)
}
}

c.scopes = c.scopes[:len(c.scopes)-1]
c.scopes = c.scopes[:pos]

if err == nil {
return nil
}

return nil
return err
}

func (c *Compiler) CreateVariable(name string) *Compiler {
if len(c.scopes) == 0 {
c.PushScope()
}

if _, found := c.scopes[len(c.scopes)-1].usage[name]; !found {
c.scopes[len(c.scopes)-1].usage[name] = c.error(errors.ErrUnusedVariable, name)
pos := len(c.scopes) - 1
if _, found := c.scopes[pos].usage[name]; !found {
err := c.error(errors.ErrUnusedVariable).Context(name)
c.scopes[pos].usage[name] = err

if symbolUsageDebugging {
ui.Log(ui.InternalLogger, "Create variable %s, %v", name, err)
}
} else if symbolUsageDebugging {
ui.Log(ui.InternalLogger, "Write variable %s", name)
}

return c
Expand All @@ -67,10 +90,20 @@ func (c *Compiler) CreateVariable(name string) *Compiler {
func (c *Compiler) UseVariable(name string) *Compiler {
// Scan the scopes stack in reverse order and search for an entry for the
// given variable. If found, mark it as used.
for i := len(c.scopes) - 1; i >= 0; i-- {
if len(c.scopes) == 0 {
return c
}

pos := len(c.scopes) - 1

for i := pos; i >= 0; i-- {
if _, found := c.scopes[i].usage[name]; found {
c.scopes[i].usage[name] = nil

if symbolUsageDebugging {
ui.Log(ui.InternalLogger, "Use variable %s", name)
}

break
}
}
Expand Down
79 changes: 78 additions & 1 deletion errors/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type Error struct {
err error
location *location
context string
next *Error
}

// New creates a new Error object, and fills in the native
Expand All @@ -38,15 +39,81 @@ func New(err error) *Error {
return nil
}

// If it's one of our errors, make a clone of it
if e, ok := err.(*Error); ok {
return e
return e.Clone()
}

return &Error{
err: err,
}
}

func (e *Error) Clone() *Error {
if e == nil {
return nil
}

err := &Error{
err: e.err,
context: e.context,
next: e.next,
}

if e.location != nil {
err.location = &location{
name: e.location.name,
line: e.location.line,
column: e.location.column,
}
}

return err
}

// Chain new error to existing error, and return start of chain.
func Chain(existingError, newError *Error) *Error {
if existingError == nil {
return newError
}

if newError == nil {
return existingError
}

newError.next = existingError

return newError
}

// SameBaseError checks to see if the two errors are the same base error,
// irrespective of any additional attributes of the error, if the error
// is an Ego error. This allows comparisons for error types without concern
// for line numbers, module names, etc.
func SameBaseError(e error, other error) bool {
var t1, t2 string

if e == nil && other == nil {
return true
}

if e == nil || other == nil {
return false
}

t1 = e.Error()
if err, ok := e.(*Error); ok {
t1 = err.err.Error()
}

t2 = other.Error()
if err, ok := other.(*Error); ok {
t2 = err.err.Error()
}

return t1 == t2
}

// In specifies the location name. This can be the name of
// a source code module, or a function name.
func (e *Error) In(name string) *Error {
Expand Down Expand Up @@ -241,6 +308,16 @@ func (e *Error) Error() string {
return ""
}

// IF this is part of a chain, format the linked message first. If the
// message is getting kind of long, add a break to it.
if e.next != nil {
errorString := i18n.L("error") + ": "

b.WriteString(e.next.Error())
b.WriteString(",\n")
b.WriteString(strings.Repeat(" ", len(errorString)))
}

// If we have a location, report that as module or module/line number
if e.location != nil {
if predicate {
Expand Down
2 changes: 1 addition & 1 deletion tools/buildver.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.5-1177
1.5-1178

0 comments on commit c851f97

Please sign in to comment.