<commit_before>package check

import (
	"fmt"
	"plaid/parser"
)

// A collection of types native to the execution environment
var (
	BuiltinInt Type = TypeIdent{"Int"}
	BuiltinStr      = TypeIdent{"Str"}
)

// Check takes an existing abstract syntax tree and performs type checks and
// other correctness checks. It returns a list of any errors that were
// discovered inside the AST
func Check(prog parser.Program) *Scope {
	global := makeScope(nil, nil)
	checkProgram(global, prog)
	return global
}

func checkProgram(scope *Scope, prog parser.Program) {
	for _, stmt := range prog.Stmts {
		checkStmt(scope, stmt)
	}
}

func checkStmt(scope *Scope, stmt parser.Stmt) {
	switch stmt := stmt.(type) {
	case parser.DeclarationStmt:
		checkDeclarationStmt(scope, stmt)
		break
	case parser.ReturnStmt:
		checkReturnStmt(scope, stmt)
		break
	}

	if scope.hasBodyQueue() {
		sig, expr := scope.dequeueBody()
		checkFunctionBody(scope, sig, expr)
	}
}

func checkStmtBlock(scope *Scope, block parser.StmtBlock) {
	for _, stmt := range block.Stmts {
		checkStmt(scope, stmt)
	}
}

func checkDeclarationStmt(scope *Scope, stmt parser.DeclarationStmt) {
	name := stmt.Name.Name
	typ := checkExpr(scope, stmt.Expr)
	scope.registerVariable(name, typ)
}

func checkReturnStmt(scope *Scope, stmt parser.ReturnStmt) {
	var ret Type
	if stmt.Expr != nil {
		ret = checkExpr(scope, stmt.Expr)
	}

	expectedReturnValue := scope.pendingReturn != nil
	gotReturnValue := ret != nil

	if scope.hasParent() == false {
		scope.addError(fmt.Errorf("return statements must be inside a function"))
		return
	}

	if gotReturnValue && ret.IsError() {
		return
	}

	if expectedReturnValue && gotReturnValue == false {
		scope.addError(fmt.Errorf("expected a return type of '%s', got nothing", scope.pendingReturn))
		return
	}

	if expectedReturnValue && scope.pendingReturn.Equals(ret) == false {
		scope.addError(fmt.Errorf("expected to return '%s', got '%s'", scope.pendingReturn, ret))
	}

	if expectedReturnValue == false && gotReturnValue {
		scope.addError(fmt.Errorf("expected to return nothing, got '%s'", ret))
	}
}

func checkExpr(scope *Scope, expr parser.Expr) Type {
	switch expr := expr.(type) {
	case parser.FunctionExpr:
		return checkFunctionExpr(scope, expr)
	case parser.DispatchExpr:
		return checkDispatchExpr(scope, expr)
	case parser.BinaryExpr:
		return checkBinaryExpr(scope, expr)
	case parser.IdentExpr:
		return checkIdentExpr(scope, expr)
	case parser.NumberExpr:
		return checkNumberExpr(scope, expr)
	case parser.StringExpr:
		return checkStringExpr(scope, expr)
	default:
		scope.addError(fmt.Errorf("unknown expression type"))
		return TypeError{}
	}
}

func checkFunctionExpr(scope *Scope, expr parser.FunctionExpr) Type {
	ret := convertTypeNote(expr.Ret)
	params := []Type{}
	for _, param := range expr.Params {
		params = append(params, convertTypeNote(param.Note))
	}
	tuple := TypeTuple{params}

	sig := TypeFunction{tuple, ret}
	scope.enqueueBody(ret, expr)
	return sig
}

func checkFunctionBody(scope *Scope, ret Type, expr parser.FunctionExpr) {
	pushed := makeScope(scope, ret)
	checkStmtBlock(pushed, expr.Block)
}

func checkDispatchExpr(scope *Scope, expr parser.DispatchExpr) Type {
	// Resolve arguments to types
	argTypes := []Type{}
	for _, argExpr := range expr.Args {
		argTypes = append(argTypes, checkExpr(scope, argExpr))
	}

	// Resolve callee to type
	calleeType := checkExpr(scope, expr.Callee)
	calleeFunc, ok := calleeType.(TypeFunction)
	if ok == false {
		if calleeType.IsError() == false {
			scope.addError(fmt.Errorf("cannot call function on type '%s'", calleeType))
		}

		return TypeError{}
	}

	// Resolve return type
	retType := calleeFunc.ret<commit_msg>add function parameter variables to the body scope<commit_after>package check

import (
	"fmt"
	"plaid/parser"
)

// A collection of types native to the execution environment
var (
	BuiltinInt Type = TypeIdent{"Int"}
	BuiltinStr      = TypeIdent{"Str"}
)

// Check takes an existing abstract syntax tree and performs type checks and
// other correctness checks. It returns a list of any errors that were
// discovered inside the AST
func Check(prog parser.Program) *Scope {
	global := makeScope(nil, nil)
	checkProgram(global, prog)
	return global
}

func checkProgram(scope *Scope, prog parser.Program) {
	for _, stmt := range prog.Stmts {
		checkStmt(scope, stmt)
	}
}

func checkStmt(scope *Scope, stmt parser.Stmt) {
	switch stmt := stmt.(type) {
	case parser.DeclarationStmt:
		checkDeclarationStmt(scope, stmt)
		break
	case parser.ReturnStmt:
		checkReturnStmt(scope, stmt)
		break
	}

	if scope.hasBodyQueue() {
		sig, expr := scope.dequeueBody()
		checkFunctionBody(scope, sig, expr)
	}
}

func checkStmtBlock(scope *Scope, block parser.StmtBlock) {
	for _, stmt := range block.Stmts {
		checkStmt(scope, stmt)
	}
}

func checkDeclarationStmt(scope *Scope, stmt parser.DeclarationStmt) {
	name := stmt.Name.Name
	typ := checkExpr(scope, stmt.Expr)
	scope.registerVariable(name, typ)
}

func checkReturnStmt(scope *Scope, stmt parser.ReturnStmt) {
	var ret Type
	if stmt.Expr != nil {
		ret = checkExpr(scope, stmt.Expr)
	}

	expectedReturnValue := scope.pendingReturn != nil
	gotReturnValue := ret != nil

	if scope.hasParent() == false {
		scope.addError(fmt.Errorf("return statements must be inside a function"))
		return
	}

	if gotReturnValue && ret.IsError() {
		return
	}

	if expectedReturnValue && gotReturnValue == false {
		scope.addError(fmt.Errorf("expected a return type of '%s', got nothing", scope.pendingReturn))
		return
	}

	if expectedReturnValue && scope.pendingReturn.Equals(ret) == false {
		scope.addError(fmt.Errorf("expected to return '%s', got '%s'", scope.pendingReturn, ret))
	}

	if expectedReturnValue == false && gotReturnValue {
		scope.addError(fmt.Errorf("expected to return nothing, got '%s'", ret))
	}
}

func checkExpr(scope *Scope, expr parser.Expr) Type {
	switch expr := expr.(type) {
	case parser.FunctionExpr:
		return checkFunctionExpr(scope, expr)
	case parser.DispatchExpr:
		return checkDispatchExpr(scope, expr)
	case parser.BinaryExpr:
		return checkBinaryExpr(scope, expr)
	case parser.IdentExpr:
		return checkIdentExpr(scope, expr)
	case parser.NumberExpr:
		return checkNumberExpr(scope, expr)
	case parser.StringExpr:
		return checkStringExpr(scope, expr)
	default:
		scope.addError(fmt.Errorf("unknown expression type"))
		return TypeError{}
	}
}

func checkFunctionExpr(scope *Scope, expr parser.FunctionExpr) Type {
	ret := convertTypeNote(expr.Ret)
	params := []Type{}
	for _, param := range expr.Params {
		params = append(params, convertTypeNote(param.Note))
	}
	tuple := TypeTuple{params}

	sig := TypeFunction{tuple, ret}
	scope.enqueueBody(ret, expr)
	return sig
}

func checkFunctionBody(scope *Scope, ret Type, expr parser.FunctionExpr) {
	pushed := makeScope(scope, ret)

	for _, param := range expr.Params {
		paramName := param.Name.Name
		paramType := convertTypeNote(param.Note)
		pushed.registerVariable(paramName, paramType)
	}

	checkStmtBlock(pushed, expr.Block)
}

func checkDispatchExpr(scope *Scope, expr parser.DispatchExpr) Type {
	// Resolve arguments to types
	argTypes := []Type{}
	for _, argExpr := range expr.Args {
		argTypes = append(argTypes, checkExpr(scope, argExpr))
	}

	// Resolve callee to type
	calleeType := checkExpr(scope, expr.Callee)
	calleeFunc, ok := calleeType.(TypeFunction)
	if ok == false {
		if calleeType.IsError() == false {
			scope.addError(fmt.Errorf("cannot call function on type '%s'", calleeType))
		}

		return TypeError{}
	}

	// Resolve return type
	retType := calleeFunc.ret
{'fraction_non_alphanumeric': 0.11804184761152783, 'fraction_numerical': 0.0, 'mean_word_length': 4.633802816901408, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '601112', 'n_tokens_mistral': 2514, 'n_tokens_neox': 2444, 'n_words': 664}