Introduce typed semantic analysis pipeline
This commit is contained in:
parent
117d188194
commit
f4cd4f4458
30 changed files with 2079 additions and 2381 deletions
|
|
@ -2,31 +2,17 @@ package lang
|
|||
|
||||
import "fmt"
|
||||
|
||||
func validateMutability(program *Program) error {
|
||||
checker := mutabilityChecker{}
|
||||
for _, fn := range program.Functions {
|
||||
if err := checker.checkFunction(fn, nil, nil); err != nil {
|
||||
func validateMutability(semantic *SemanticProgram) error {
|
||||
checker := mutabilityChecker{semantic: semantic}
|
||||
for index := range semantic.Syntax.Functions {
|
||||
if err := checker.checkFunction(&semantic.Syntax.Functions[index], nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, class := range program.Classes {
|
||||
fields := make(map[string]bool, len(class.Fields))
|
||||
for _, field := range class.Fields {
|
||||
fields[field.Name] = field.Mutable
|
||||
}
|
||||
for _, method := range class.Methods {
|
||||
if err := checker.checkFunction(method, fields, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, worker := range program.Workers {
|
||||
fields := make(map[string]bool, len(worker.Fields))
|
||||
for _, field := range worker.Fields {
|
||||
fields[field.Name] = field.Mutable
|
||||
}
|
||||
for _, method := range worker.Methods {
|
||||
if err := checker.checkFunction(method, nil, fields); err != nil {
|
||||
for index := range semantic.Syntax.Classes {
|
||||
class := semantic.ClassInfo[semantic.Syntax.Classes[index].Name]
|
||||
for methodIndex := range semantic.Syntax.Classes[index].Methods {
|
||||
if err := checker.checkFunction(&semantic.Syntax.Classes[index].Methods[methodIndex], class); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -35,138 +21,123 @@ func validateMutability(program *Program) error {
|
|||
}
|
||||
|
||||
type mutabilityChecker struct {
|
||||
scopes []map[string]bool
|
||||
classFields map[string]bool
|
||||
workerFields map[string]bool
|
||||
semantic *SemanticProgram
|
||||
scope *Scope
|
||||
class *ClassSymbol
|
||||
}
|
||||
|
||||
func (c *mutabilityChecker) checkFunction(fn FunctionDecl, classFields map[string]bool, workerFields map[string]bool) error {
|
||||
c.scopes = nil
|
||||
c.classFields = classFields
|
||||
c.workerFields = workerFields
|
||||
c.pushScope()
|
||||
defer c.popScope()
|
||||
for _, param := range fn.Params {
|
||||
c.define(param.Name, false)
|
||||
func (checker *mutabilityChecker) checkFunction(function *FunctionDecl, class *ClassSymbol) error {
|
||||
checker.class = class
|
||||
checker.scope = NewScope(checker.semantic.Global)
|
||||
if class != nil {
|
||||
_ = checker.scope.Define(&Symbol{Name: "this", Kind: VariableSymbol, Type: ClassType{Class: class}})
|
||||
}
|
||||
return c.checkStmts(fn.Body)
|
||||
for _, param := range function.Params {
|
||||
typ, _ := checker.semantic.ResolveType(param.Type)
|
||||
_ = checker.scope.Define(&Symbol{Name: param.Name, Kind: VariableSymbol, Type: typ})
|
||||
}
|
||||
return checker.checkStmts(function.Body)
|
||||
}
|
||||
|
||||
func (c *mutabilityChecker) checkStmts(stmts []Stmt) error {
|
||||
for _, stmt := range stmts {
|
||||
switch s := stmt.(type) {
|
||||
func (checker *mutabilityChecker) checkStmts(statements []Stmt) error {
|
||||
for _, statement := range statements {
|
||||
switch value := statement.(type) {
|
||||
case VarDecl:
|
||||
if err := c.checkExpr(s.Value); err != nil {
|
||||
if err := checker.checkExpr(value.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
c.define(s.Name, s.Mutable)
|
||||
typ, _ := checker.semantic.ResolveType(value.Type)
|
||||
_ = checker.scope.Define(&Symbol{Name: value.Name, Kind: VariableSymbol, Type: typ, Mutable: value.Mutable})
|
||||
case MultiVarDecl:
|
||||
if err := c.checkExpr(s.Value); err != nil {
|
||||
if err := checker.checkExpr(value.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, name := range s.Names {
|
||||
c.define(name, s.Mutable)
|
||||
for _, name := range value.Names {
|
||||
_ = checker.scope.Define(&Symbol{Name: name, Kind: VariableSymbol, Type: UnknownType{}, Mutable: value.Mutable})
|
||||
}
|
||||
case AssignStmt:
|
||||
if err := c.requireMutable(s.Name, s.Pos); err != nil {
|
||||
if err := checker.requireMutable(value.Name, value.Pos); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.checkExpr(s.Value); err != nil {
|
||||
if err := checker.checkExpr(value.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
case AddAssignStmt:
|
||||
if err := c.requireMutable(s.Name, s.Pos); err != nil {
|
||||
if err := checker.requireMutable(value.Name, value.Pos); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.checkExpr(s.Value); err != nil {
|
||||
if err := checker.checkExpr(value.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
case MultiAssignStmt:
|
||||
for i, name := range s.Names {
|
||||
pos := 0
|
||||
if i < len(s.Positions) {
|
||||
pos = s.Positions[i]
|
||||
for index, name := range value.Names {
|
||||
position := 0
|
||||
if index < len(value.Positions) {
|
||||
position = value.Positions[index]
|
||||
}
|
||||
if err := c.requireMutable(name, pos); err != nil {
|
||||
if err := checker.requireMutable(name, position); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := c.checkExpr(s.Value); err != nil {
|
||||
if err := checker.checkExpr(value.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
case ReturnStmt:
|
||||
if s.Value != nil {
|
||||
if err := c.checkExpr(s.Value); err != nil {
|
||||
if value.Value != nil {
|
||||
if err := checker.checkExpr(value.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case ThrowStmt:
|
||||
if err := c.checkExpr(s.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
case GoStmt:
|
||||
if err := c.checkExpr(s.Value); err != nil {
|
||||
if err := checker.checkExpr(value.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
case DeferStmt:
|
||||
if err := c.checkExpr(s.Value); err != nil {
|
||||
if err := checker.checkExpr(value.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
case ExprStmt:
|
||||
if err := c.checkExpr(s.Value); err != nil {
|
||||
if err := checker.checkExpr(value.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
case IfStmt:
|
||||
if err := c.checkExpr(s.Cond); err != nil {
|
||||
if err := checker.checkExpr(value.Cond); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.checkBlock(s.Then, nil); err != nil {
|
||||
if err := checker.checkBlock(value.Then, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.checkBlock(s.Else, nil); err != nil {
|
||||
if err := checker.checkBlock(value.Else, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
case WhileStmt:
|
||||
if err := c.checkExpr(s.Cond); err != nil {
|
||||
if err := checker.checkExpr(value.Cond); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.checkBlock(s.Body, nil); err != nil {
|
||||
if err := checker.checkBlock(value.Body, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
case ForEachStmt:
|
||||
if err := c.checkExpr(s.Source); err != nil {
|
||||
if err := checker.checkExpr(value.Source); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.checkBlock(s.Body, map[string]bool{s.Name: false}); err != nil {
|
||||
if err := checker.checkBlock(value.Body, []string{value.Name}); err != nil {
|
||||
return err
|
||||
}
|
||||
case SelectStmt:
|
||||
for _, sc := range s.Cases {
|
||||
if err := c.checkExpr(sc.Source); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.checkBlock(sc.Body, map[string]bool{"it": false}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case MatchStmt:
|
||||
if err := c.checkExpr(s.Value); err != nil {
|
||||
if err := checker.checkExpr(value.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, matchCase := range s.Cases {
|
||||
bindings := map[string]bool{}
|
||||
for _, binding := range matchCase.Bindings {
|
||||
bindings[binding] = false
|
||||
}
|
||||
if err := c.checkBlock(matchCase.Body, bindings); err != nil {
|
||||
for _, matchCase := range value.Cases {
|
||||
if err := checker.checkBlock(matchCase.Body, matchCase.Bindings); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case TryCatchStmt:
|
||||
if err := c.checkBlock(s.TryBody, nil); err != nil {
|
||||
if err := checker.checkBlock(value.TryBody, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.checkBlock(s.CatchBody, map[string]bool{s.CatchName: false}); err != nil {
|
||||
if err := checker.checkBlock(value.CatchBody, []string{value.CatchName}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -174,124 +145,104 @@ func (c *mutabilityChecker) checkStmts(stmts []Stmt) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *mutabilityChecker) checkBlock(stmts []Stmt, bindings map[string]bool) error {
|
||||
c.pushScope()
|
||||
defer c.popScope()
|
||||
for name, mutable := range bindings {
|
||||
c.define(name, mutable)
|
||||
func (checker *mutabilityChecker) checkBlock(statements []Stmt, bindings []string) error {
|
||||
previous := checker.scope
|
||||
checker.scope = NewScope(previous)
|
||||
defer func() { checker.scope = previous }()
|
||||
for _, name := range bindings {
|
||||
_ = checker.scope.Define(&Symbol{Name: name, Kind: VariableSymbol, Type: UnknownType{}})
|
||||
}
|
||||
return c.checkStmts(stmts)
|
||||
return checker.checkStmts(statements)
|
||||
}
|
||||
|
||||
func (c *mutabilityChecker) checkExpr(expr Expr) error {
|
||||
switch e := expr.(type) {
|
||||
func (checker *mutabilityChecker) checkExpr(expr Expr) error {
|
||||
switch value := expr.(type) {
|
||||
case UnaryExpr:
|
||||
return c.checkExpr(e.Value)
|
||||
return checker.checkExpr(value.Value)
|
||||
case BinaryExpr:
|
||||
if err := c.checkExpr(e.Left); err != nil {
|
||||
if err := checker.checkExpr(value.Left); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.checkExpr(e.Right)
|
||||
return checker.checkExpr(value.Right)
|
||||
case CallExpr:
|
||||
if err := c.checkExpr(e.Callee); err != nil {
|
||||
if err := checker.checkExpr(value.Callee); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, arg := range e.Args {
|
||||
if err := c.checkExpr(arg); err != nil {
|
||||
for _, argument := range value.Args {
|
||||
if err := checker.checkExpr(argument); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, arg := range e.NamedArgs {
|
||||
if err := c.checkExpr(arg.Value); err != nil {
|
||||
for _, argument := range value.NamedArgs {
|
||||
if err := checker.checkExpr(argument.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case SelectorExpr:
|
||||
return c.checkExpr(e.Receiver)
|
||||
return checker.checkExpr(value.Receiver)
|
||||
case SafeSelectorExpr:
|
||||
return c.checkExpr(e.Receiver)
|
||||
return checker.checkExpr(value.Receiver)
|
||||
case NonNullExpr:
|
||||
return c.checkExpr(e.Value)
|
||||
return checker.checkExpr(value.Value)
|
||||
case TryExpr:
|
||||
return c.checkExpr(e.Value)
|
||||
return checker.checkExpr(value.Value)
|
||||
case IndexExpr:
|
||||
if err := c.checkExpr(e.Receiver); err != nil {
|
||||
if err := checker.checkExpr(value.Receiver); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.checkExpr(e.Index)
|
||||
return checker.checkExpr(value.Index)
|
||||
case EnumVariantExpr:
|
||||
for _, value := range e.Values {
|
||||
if err := c.checkExpr(value); err != nil {
|
||||
for _, item := range value.Values {
|
||||
if err := checker.checkExpr(item); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case MatchExpr:
|
||||
if err := c.checkExpr(e.Value); err != nil {
|
||||
if err := checker.checkExpr(value.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, matchCase := range e.Cases {
|
||||
if err := c.checkExpr(matchCase.Value); err != nil {
|
||||
for _, matchCase := range value.Cases {
|
||||
if err := checker.checkExpr(matchCase.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case LambdaExpr:
|
||||
bindings := map[string]bool{}
|
||||
if e.ImplicitIt {
|
||||
bindings["it"] = false
|
||||
previous := checker.scope
|
||||
checker.scope = NewScope(previous)
|
||||
defer func() { checker.scope = previous }()
|
||||
if value.ImplicitIt {
|
||||
_ = checker.scope.Define(&Symbol{Name: "it", Kind: VariableSymbol, Type: UnknownType{}})
|
||||
}
|
||||
for _, param := range e.Params {
|
||||
bindings[param.Name] = false
|
||||
for _, param := range value.Params {
|
||||
typ, _ := checker.semantic.ResolveType(param.Type)
|
||||
_ = checker.scope.Define(&Symbol{Name: param.Name, Kind: VariableSymbol, Type: typ})
|
||||
}
|
||||
return c.checkBlock(e.Body, bindings)
|
||||
return checker.checkStmts(value.Body)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *mutabilityChecker) pushScope() {
|
||||
c.scopes = append(c.scopes, map[string]bool{})
|
||||
}
|
||||
|
||||
func (c *mutabilityChecker) popScope() {
|
||||
if len(c.scopes) == 0 {
|
||||
return
|
||||
func (checker *mutabilityChecker) requireMutable(name string, position int) error {
|
||||
if symbol, ok := checker.scope.Lookup(name); ok && symbol.Kind == VariableSymbol {
|
||||
if symbol.Mutable {
|
||||
return nil
|
||||
}
|
||||
return immutableAssignmentError(name, position)
|
||||
}
|
||||
c.scopes = c.scopes[:len(c.scopes)-1]
|
||||
}
|
||||
|
||||
func (c *mutabilityChecker) define(name string, mutable bool) {
|
||||
if len(c.scopes) == 0 {
|
||||
c.pushScope()
|
||||
}
|
||||
c.scopes[len(c.scopes)-1][name] = mutable
|
||||
}
|
||||
|
||||
func (c *mutabilityChecker) requireMutable(name string, pos int) error {
|
||||
for i := len(c.scopes) - 1; i >= 0; i-- {
|
||||
if mutable, ok := c.scopes[i][name]; ok {
|
||||
if mutable {
|
||||
if checker.class != nil {
|
||||
if field, ok := checker.class.Fields[name]; ok {
|
||||
if field.Mutable {
|
||||
return nil
|
||||
}
|
||||
return immutableAssignmentError(name, pos)
|
||||
return immutableAssignmentError(name, position)
|
||||
}
|
||||
}
|
||||
if mutable, ok := c.classFields[name]; ok {
|
||||
if mutable {
|
||||
return nil
|
||||
}
|
||||
return immutableAssignmentError(name, pos)
|
||||
}
|
||||
if mutable, ok := c.workerFields[name]; ok {
|
||||
if mutable {
|
||||
return nil
|
||||
}
|
||||
return immutableAssignmentError(name, pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func immutableAssignmentError(name string, pos int) error {
|
||||
if pos > 0 {
|
||||
return fmt.Errorf("cannot reassign immutable name %s at %d", name, pos)
|
||||
func immutableAssignmentError(name string, position int) error {
|
||||
if position > 0 {
|
||||
return fmt.Errorf("cannot reassign immutable name %s at %d", name, position)
|
||||
}
|
||||
return fmt.Errorf("cannot reassign immutable name %s", name)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue