Infer coroutine effects automatically
This commit is contained in:
parent
bac1183593
commit
5b30e49486
15 changed files with 339 additions and 103 deletions
|
|
@ -65,7 +65,6 @@ type FunctionSignature struct {
|
|||
Params []Param
|
||||
ReturnType string
|
||||
ReturnRef TypeRef
|
||||
Suspend bool
|
||||
}
|
||||
|
||||
type FunctionDecl struct {
|
||||
|
|
@ -75,7 +74,6 @@ type FunctionDecl struct {
|
|||
ReturnType string
|
||||
ReturnRef TypeRef
|
||||
Body []Stmt
|
||||
Suspend bool
|
||||
}
|
||||
|
||||
type Param struct {
|
||||
|
|
|
|||
|
|
@ -5,17 +5,17 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
var coroutineBuiltins = map[string]bool{"runBlocking": true, "coroutineScope": true, "launch": true, "async": true, "delay": true, "withTimeout": true, "isActive": true}
|
||||
var coroutineBuiltins = map[string]bool{"runBlocking": true, "coroutineScope": true, "launch": true, "async": true, "delay": true, "withTimeout": true, "isActive": true, "coroutineContext": true}
|
||||
|
||||
func programUsesCoroutines(program *Program) bool {
|
||||
for _, fn := range program.Functions {
|
||||
if fn.Suspend || statementsUseCoroutines(fn.Body) {
|
||||
if statementsUseCoroutines(fn.Body) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, class := range program.Classes {
|
||||
for _, fn := range class.Methods {
|
||||
if fn.Suspend || statementsUseCoroutines(fn.Body) {
|
||||
if statementsUseCoroutines(fn.Body) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -94,6 +94,7 @@ func (g *goGenerator) emitCoroutineSupport() {
|
|||
g.line("func (scope *GotlinCoroutineScope) Scope(block func(*GotlinCoroutineScope)) { child:=gotlinNewCoroutineScope(scope.ctx); defer child.cancel(); defer func(){if value:=recover();value!=nil{child.cancel();child.workers.Wait();panic(value)}}(); block(child); child.wait() }")
|
||||
g.line("func (scope *GotlinCoroutineScope) Delay(ms int) { timer:=time.NewTimer(time.Duration(ms)*time.Millisecond); defer timer.Stop(); select { case <-timer.C: case <-scope.ctx.Done(): panic(scope.ctx.Err()) } }")
|
||||
g.line("func (scope *GotlinCoroutineScope) IsActive() bool { return scope.ctx.Err()==nil }")
|
||||
g.line("func (scope *GotlinCoroutineScope) Context() context.Context { return scope.ctx }")
|
||||
g.line("func (scope *GotlinCoroutineScope) WithTimeout(ms int, block func(*GotlinCoroutineScope)) { ctx,cancel:=context.WithTimeout(scope.ctx,time.Duration(ms)*time.Millisecond); defer cancel(); child:=gotlinNewCoroutineScope(ctx); defer child.cancel(); block(child); child.wait() }")
|
||||
g.line("func gotlinRunBlocking(block func(*GotlinCoroutineScope)) { scope:=gotlinNewCoroutineScope(context.Background()); defer scope.cancel(); defer func(){if value:=recover();value!=nil{scope.cancel();scope.workers.Wait();panic(value)}}(); block(scope); scope.wait() }")
|
||||
g.line("type GotlinDeferred[T any] struct { done chan struct{}; value T; failure any }")
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import (
|
|||
|
||||
func TestGenerateStructuredCoroutines(t *testing.T) {
|
||||
prog, err := Parse(`package demo
|
||||
suspend fun load(): Int { delay(1); return 42 }
|
||||
fun load(): Int { delay(1); return 42 }
|
||||
fun main() {
|
||||
runBlocking {
|
||||
coroutineScope {
|
||||
|
|
@ -31,16 +31,35 @@ fun main() {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSuspendFunctionRequiresScope(t *testing.T) {
|
||||
func TestInferredCoroutineFunctionRequiresScope(t *testing.T) {
|
||||
prog, err := Parse(`package demo
|
||||
suspend fun load(): Int { return 1 }
|
||||
fun load(): Int { delay(1); return 1 }
|
||||
fun main() { println(load()) }`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = GenerateGo(prog)
|
||||
if err == nil || !strings.Contains(err.Error(), "requires a coroutine scope") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
out, err := GenerateGo(prog)
|
||||
if err == nil || !strings.Contains(err.Error(), "contextual function main requires a runBlocking boundary") {
|
||||
t.Fatalf("unexpected error: %v\n%s", err, out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoroutineEffectPropagatesTransitively(t *testing.T) {
|
||||
prog, err := Parse(`package demo
|
||||
fun load(): Int { delay(1); return 42 }
|
||||
fun wrapped(): Int { return load() }
|
||||
fun main() { runBlocking { println(wrapped()) } }`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out, err := GenerateGo(prog)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, expected := range []string{"func load(gotlinScope *GotlinCoroutineScope)", "func wrapped(gotlinScope *GotlinCoroutineScope)", "wrapped(gotlinScope)"} {
|
||||
if !strings.Contains(string(out), expected) {
|
||||
t.Fatalf("missing %q:\n%s", expected, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -52,6 +71,51 @@ func TestLegacyConcurrencySyntaxIsRemoved(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSuspendModifierIsRemoved(t *testing.T) {
|
||||
if _, err := Parse(`package demo suspend fun load() {}`); err == nil {
|
||||
t.Fatal("suspend modifier parsed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnalyzeInfersCoroutineEffects(t *testing.T) {
|
||||
program, err := Parse(`package demo
|
||||
fun direct() { delay(1) }
|
||||
fun transitive() { direct() }
|
||||
fun boundary() { runBlocking { transitive() } }`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
semantic, err := Analyze(program)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !semantic.FunctionEffects["direct"].Has(CoroutineEffect) || !semantic.FunctionEffects["transitive"].Has(CoroutineEffect) {
|
||||
t.Fatalf("effects were not propagated: %#v", semantic.FunctionEffects)
|
||||
}
|
||||
if semantic.FunctionEffects["boundary"].Has(CoroutineEffect) {
|
||||
t.Fatalf("runBlocking did not stop effect propagation: %#v", semantic.FunctionEffects)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoroutineContextUsesAmbientScope(t *testing.T) {
|
||||
program, err := Parse(`package demo
|
||||
import fmt
|
||||
fun useContext() { fmt.sprint(coroutineContext()) }
|
||||
fun main() { runBlocking { useContext() } }`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
output, err := GenerateGo(program)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, expected := range []string{"func useContext(gotlinScope *GotlinCoroutineScope)", "gotlinScope.Context()", "useContext(gotlinScope)"} {
|
||||
if !strings.Contains(string(output), expected) {
|
||||
t.Fatalf("missing %q:\n%s", expected, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunBlockingCancelsAndJoinsChildrenOnPanic(t *testing.T) {
|
||||
prog, err := Parse(`package demo fun main() { runBlocking { launch { delay(1) }; panic("failed") } }`)
|
||||
if err != nil {
|
||||
|
|
|
|||
155
internal/lang/effects.go
Normal file
155
internal/lang/effects.go
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
package lang
|
||||
|
||||
type Effect uint8
|
||||
|
||||
const (
|
||||
NoEffect Effect = 0
|
||||
CoroutineEffect Effect = 1 << 0
|
||||
)
|
||||
|
||||
func (effect Effect) Has(required Effect) bool { return effect&required != 0 }
|
||||
|
||||
type effectNode struct {
|
||||
key string
|
||||
symbol *Symbol
|
||||
direct Effect
|
||||
callees []*Symbol
|
||||
}
|
||||
|
||||
func inferCoroutineEffects(semantic *SemanticProgram) {
|
||||
var nodes []*effectNode
|
||||
for index := range semantic.Syntax.Functions {
|
||||
decl := &semantic.Syntax.Functions[index]
|
||||
symbol, _ := semantic.Global.Lookup(decl.Name)
|
||||
node := &effectNode{key: decl.Name, symbol: symbol}
|
||||
collectFunctionEffects(decl.Body, node)
|
||||
nodes = append(nodes, node)
|
||||
}
|
||||
for classIndex := range semantic.Syntax.Classes {
|
||||
class := &semantic.Syntax.Classes[classIndex]
|
||||
classSymbol := semantic.ClassInfo[class.Name]
|
||||
for methodIndex := range class.Methods {
|
||||
method := &class.Methods[methodIndex]
|
||||
node := &effectNode{key: class.Name + "." + method.Name, symbol: classSymbol.Methods[method.Name]}
|
||||
collectFunctionEffects(method.Body, node)
|
||||
nodes = append(nodes, node)
|
||||
}
|
||||
}
|
||||
changed := true
|
||||
for changed {
|
||||
changed = false
|
||||
for _, node := range nodes {
|
||||
effects := node.direct
|
||||
for _, callee := range node.callees {
|
||||
effects |= callee.Effects
|
||||
}
|
||||
if node.symbol != nil && node.symbol.Effects != effects {
|
||||
node.symbol.Effects = effects
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, node := range nodes {
|
||||
if node.symbol != nil {
|
||||
semantic.FunctionEffects[node.key] = node.symbol.Effects
|
||||
if function, ok := node.symbol.Type.(FunctionType); ok {
|
||||
function.Effects = node.symbol.Effects
|
||||
node.symbol.Type = function
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func collectFunctionEffects(statements []Stmt, node *effectNode) {
|
||||
for _, statement := range statements {
|
||||
switch value := statement.(type) {
|
||||
case VarDecl:
|
||||
collectExpressionEffects(value.Value, node)
|
||||
case MultiVarDecl:
|
||||
collectExpressionEffects(value.Value, node)
|
||||
case AssignStmt:
|
||||
collectExpressionEffects(value.Value, node)
|
||||
case AddAssignStmt:
|
||||
collectExpressionEffects(value.Value, node)
|
||||
case MultiAssignStmt:
|
||||
collectExpressionEffects(value.Value, node)
|
||||
case ReturnStmt:
|
||||
if value.Value != nil {
|
||||
collectExpressionEffects(value.Value, node)
|
||||
}
|
||||
case ThrowStmt:
|
||||
collectExpressionEffects(value.Value, node)
|
||||
case DeferStmt:
|
||||
collectExpressionEffects(value.Value, node)
|
||||
case ExprStmt:
|
||||
collectExpressionEffects(value.Value, node)
|
||||
case IfStmt:
|
||||
collectExpressionEffects(value.Cond, node)
|
||||
collectFunctionEffects(value.Then, node)
|
||||
collectFunctionEffects(value.Else, node)
|
||||
case WhileStmt:
|
||||
collectExpressionEffects(value.Cond, node)
|
||||
collectFunctionEffects(value.Body, node)
|
||||
case ForEachStmt:
|
||||
collectExpressionEffects(value.Source, node)
|
||||
collectFunctionEffects(value.Body, node)
|
||||
case MatchStmt:
|
||||
collectExpressionEffects(value.Value, node)
|
||||
for _, matchCase := range value.Cases {
|
||||
collectFunctionEffects(matchCase.Body, node)
|
||||
}
|
||||
case TryCatchStmt:
|
||||
collectFunctionEffects(value.TryBody, node)
|
||||
collectFunctionEffects(value.CatchBody, node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func collectExpressionEffects(expression Expr, node *effectNode) {
|
||||
switch value := expression.(type) {
|
||||
case CallExpr:
|
||||
if ident, ok := value.Callee.(IdentExpr); ok {
|
||||
if ident.Name == "runBlocking" {
|
||||
return
|
||||
}
|
||||
if coroutineBuiltins[ident.Name] {
|
||||
node.direct |= CoroutineEffect
|
||||
}
|
||||
}
|
||||
if semantic := exprMeta(value); semantic != nil {
|
||||
if call, ok := semantic.Node.(HIRGotlinCall); ok && call.Target != nil {
|
||||
node.callees = append(node.callees, call.Target)
|
||||
}
|
||||
}
|
||||
collectExpressionEffects(value.Callee, node)
|
||||
for _, argument := range value.Args {
|
||||
collectExpressionEffects(argument, node)
|
||||
}
|
||||
for _, argument := range value.NamedArgs {
|
||||
collectExpressionEffects(argument.Value, node)
|
||||
}
|
||||
case UnaryExpr:
|
||||
collectExpressionEffects(value.Value, node)
|
||||
case NonNullExpr:
|
||||
collectExpressionEffects(value.Value, node)
|
||||
case TryExpr:
|
||||
collectExpressionEffects(value.Value, node)
|
||||
case BinaryExpr:
|
||||
collectExpressionEffects(value.Left, node)
|
||||
collectExpressionEffects(value.Right, node)
|
||||
case SelectorExpr:
|
||||
collectExpressionEffects(value.Receiver, node)
|
||||
case SafeSelectorExpr:
|
||||
collectExpressionEffects(value.Receiver, node)
|
||||
case IndexExpr:
|
||||
collectExpressionEffects(value.Receiver, node)
|
||||
collectExpressionEffects(value.Index, node)
|
||||
case MatchExpr:
|
||||
collectExpressionEffects(value.Value, node)
|
||||
for _, matchCase := range value.Cases {
|
||||
collectExpressionEffects(matchCase.Value, node)
|
||||
}
|
||||
case LambdaExpr:
|
||||
collectFunctionEffects(value.Body, node)
|
||||
}
|
||||
}
|
||||
|
|
@ -251,7 +251,11 @@ func (g *goGenerator) function(fn FunctionDecl) error {
|
|||
g.currentClass = nil
|
||||
g.currentFunc = fn
|
||||
previousScope := g.currentCoroutineScope
|
||||
if fn.Suspend {
|
||||
hasCoroutineEffect := g.hasCoroutineEffect(fn)
|
||||
if hasCoroutineEffect && fn.Name == "main" {
|
||||
return fmt.Errorf("contextual function main requires a runBlocking boundary")
|
||||
}
|
||||
if hasCoroutineEffect {
|
||||
g.currentCoroutineScope = "gotlinScope"
|
||||
}
|
||||
defer func() { g.currentCoroutineScope = previousScope }()
|
||||
|
|
@ -284,11 +288,7 @@ func (g *goGenerator) interfaceDecl(decl InterfaceDecl) {
|
|||
g.line("type " + decl.Name + " interface {")
|
||||
g.indentLevel++
|
||||
for _, method := range decl.Methods {
|
||||
prefix := ""
|
||||
if method.Suspend {
|
||||
prefix = "gotlinScope *GotlinCoroutineScope"
|
||||
}
|
||||
g.line(method.Name + g.renderGoParamsWithPrefix(method.Params, prefix) + g.renderGoReturnSuffix(method.ReturnType))
|
||||
g.line(method.Name + g.renderGoParamsWithPrefix(method.Params, "") + g.renderGoReturnSuffix(method.ReturnType))
|
||||
}
|
||||
g.indentLevel--
|
||||
g.line("}")
|
||||
|
|
@ -416,7 +416,7 @@ func (g *goGenerator) method(class ClassDecl, fn FunctionDecl) error {
|
|||
g.currentClass = &class
|
||||
g.currentFunc = fn
|
||||
previousScope := g.currentCoroutineScope
|
||||
if fn.Suspend {
|
||||
if g.hasCoroutineEffect(fn) {
|
||||
g.currentCoroutineScope = "gotlinScope"
|
||||
}
|
||||
defer func() { g.currentCoroutineScope = previousScope }()
|
||||
|
|
@ -954,6 +954,11 @@ func (g *goGenerator) expr(expr Expr, expectedType string) (string, error) {
|
|||
return "", fmt.Errorf("isActive requires a coroutine scope")
|
||||
}
|
||||
return g.currentCoroutineScope + ".IsActive()", nil
|
||||
case "coroutineContext":
|
||||
if g.currentCoroutineScope == "" {
|
||||
return "", fmt.Errorf("coroutineContext requires a coroutine scope")
|
||||
}
|
||||
return g.currentCoroutineScope + ".Context()", nil
|
||||
}
|
||||
}
|
||||
if selector, ok := e.Callee.(SelectorExpr); ok && selector.Name == "await" && len(e.Args) == 0 {
|
||||
|
|
@ -1197,27 +1202,14 @@ func (g *goGenerator) expr(expr Expr, expectedType string) (string, error) {
|
|||
return fmt.Sprintf("New%s%s(%s)", ident.Name, g.renderCallTypeArguments(e.TypeArgs), strings.Join(args, ", ")), nil
|
||||
}
|
||||
}
|
||||
if ident, ok := e.Callee.(IdentExpr); ok {
|
||||
if fn, found := g.semantic.Functions[ident.Name]; found && fn.Suspend {
|
||||
if resolvedCall != nil {
|
||||
if call, ok := resolvedCall.Node.(HIRGotlinCall); ok && call.Target != nil && call.Target.Effects.Has(CoroutineEffect) {
|
||||
if g.currentCoroutineScope == "" {
|
||||
return "", fmt.Errorf("suspend function %s requires a coroutine scope", ident.Name)
|
||||
return "", fmt.Errorf("contextual function %s requires a coroutine scope", call.Target.Name)
|
||||
}
|
||||
args = append([]string{g.currentCoroutineScope}, args...)
|
||||
}
|
||||
}
|
||||
if selector, ok := e.Callee.(SelectorExpr); ok {
|
||||
if class, found := g.classForType(g.exprType(selector.Receiver)); found {
|
||||
for _, method := range class.Methods {
|
||||
if method.Name == selector.Name && method.Suspend {
|
||||
if g.currentCoroutineScope == "" {
|
||||
return "", fmt.Errorf("suspend method %s requires a coroutine scope", selector.Name)
|
||||
}
|
||||
args = append([]string{g.currentCoroutineScope}, args...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
callee, err := g.expr(e.Callee, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
|
@ -2491,12 +2483,20 @@ func (g *goGenerator) cloneScopes() []map[string]bool {
|
|||
|
||||
func (g *goGenerator) renderGoFunctionParams(function FunctionDecl) string {
|
||||
prefix := ""
|
||||
if function.Suspend {
|
||||
if g.hasCoroutineEffect(function) {
|
||||
prefix = "gotlinScope *GotlinCoroutineScope"
|
||||
}
|
||||
return g.renderGoParamsWithPrefix(function.Params, prefix)
|
||||
}
|
||||
|
||||
func (g *goGenerator) hasCoroutineEffect(function FunctionDecl) bool {
|
||||
key := function.Name
|
||||
if g.currentClass != nil {
|
||||
key = g.currentClass.Name + "." + function.Name
|
||||
}
|
||||
return g.semantic.FunctionEffects[key].Has(CoroutineEffect)
|
||||
}
|
||||
|
||||
func renderGoTypeParameters(params []string) string {
|
||||
if len(params) == 0 {
|
||||
return ""
|
||||
|
|
|
|||
|
|
@ -600,6 +600,11 @@ func (resolver *semanticResolver) meaning(expr Expr, scope *Scope) (ExprMeaning,
|
|||
if root, ok := selectorRootAlias(selector); ok && resolver.program.Imports[root] {
|
||||
return GoCallExpr, nil
|
||||
}
|
||||
if class, _ := classInstance(ResolvedType(selector.Receiver)); class != nil {
|
||||
if method, ok := class.Methods[selector.Name]; ok {
|
||||
return MethodCallExpr, method
|
||||
}
|
||||
}
|
||||
receiverType := ResolvedType(selector.Receiver)
|
||||
if !isUnknownType(resolver.program.goMethodType(receiverType, selector.Name)) {
|
||||
return GoCallExpr, nil
|
||||
|
|
@ -755,7 +760,7 @@ var semanticBuiltins = map[string]bool{
|
|||
"panic": true, "recover": true, "string": true, "int": true, "float64": true, "bool": true,
|
||||
"sql": true, "set": true, "now": true, "Result": true, "ByteSlice": true,
|
||||
"runBlocking": true, "coroutineScope": true, "launch": true, "async": true,
|
||||
"delay": true, "withTimeout": true, "isActive": true,
|
||||
"delay": true, "withTimeout": true, "isActive": true, "coroutineContext": true,
|
||||
"continue": true, "break": true,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ func (p *parser) parseProgram() (*Program, error) {
|
|||
default:
|
||||
return nil, fmt.Errorf("unsupported annotation %q", annotation.lexeme)
|
||||
}
|
||||
case p.check(tokenFun) || p.check(tokenSuspend):
|
||||
case p.check(tokenFun):
|
||||
fn, err := p.parseFunction()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -325,7 +325,7 @@ func (p *parser) parseClass() (ClassDecl, error) {
|
|||
var methods []FunctionDecl
|
||||
for !p.check(tokenRBrace) && !p.check(tokenEOF) {
|
||||
p.match(tokenOverride)
|
||||
if !p.check(tokenFun) && !p.check(tokenSuspend) {
|
||||
if !p.check(tokenFun) {
|
||||
tok := p.peek()
|
||||
return ClassDecl{}, fmt.Errorf("expected class member at %d, found %q", tok.pos, tok.lexeme)
|
||||
}
|
||||
|
|
@ -482,12 +482,10 @@ func (p *parser) parseFunction() (FunctionDecl, error) {
|
|||
Params: signature.Params,
|
||||
ReturnType: signature.ReturnType,
|
||||
Body: body,
|
||||
Suspend: signature.Suspend,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *parser) parseFunctionSignature() (FunctionSignature, error) {
|
||||
suspend := p.match(tokenSuspend)
|
||||
if _, err := p.expect(tokenFun, "expected 'fun'"); err != nil {
|
||||
return FunctionSignature{}, err
|
||||
}
|
||||
|
|
@ -524,7 +522,6 @@ func (p *parser) parseFunctionSignature() (FunctionSignature, error) {
|
|||
TypeParams: typeParams,
|
||||
Params: params,
|
||||
ReturnType: returnType,
|
||||
Suspend: suspend,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ type Symbol struct {
|
|||
Type Type
|
||||
Mutable bool
|
||||
Decl any
|
||||
Effects Effect
|
||||
}
|
||||
|
||||
type Scope struct {
|
||||
|
|
@ -56,17 +57,18 @@ type ClassSymbol struct {
|
|||
}
|
||||
|
||||
type SemanticProgram struct {
|
||||
Syntax *Program
|
||||
Global *Scope
|
||||
Classes map[string]ClassDecl
|
||||
ClassInfo map[string]*ClassSymbol
|
||||
Functions map[string]FunctionDecl
|
||||
Enums map[string]EnumDecl
|
||||
Imports map[string]bool
|
||||
GoPackages map[string]*gotypes.Package
|
||||
HIR *HIRProgram
|
||||
Mappings *mappingState
|
||||
Diagnostics []SemanticDiagnostic
|
||||
Syntax *Program
|
||||
Global *Scope
|
||||
Classes map[string]ClassDecl
|
||||
ClassInfo map[string]*ClassSymbol
|
||||
Functions map[string]FunctionDecl
|
||||
Enums map[string]EnumDecl
|
||||
Imports map[string]bool
|
||||
GoPackages map[string]*gotypes.Package
|
||||
HIR *HIRProgram
|
||||
Mappings *mappingState
|
||||
Diagnostics []SemanticDiagnostic
|
||||
FunctionEffects map[string]Effect
|
||||
}
|
||||
|
||||
func Analyze(program *Program) (*SemanticProgram, error) {
|
||||
|
|
@ -91,15 +93,16 @@ func AnalyzeWithContext(program *Program, additional []*Program) (*SemanticProgr
|
|||
|
||||
func analyzeProgram(program *Program, additional []*Program) (*SemanticProgram, error) {
|
||||
semantic := &SemanticProgram{
|
||||
Syntax: program,
|
||||
Global: NewScope(nil),
|
||||
Classes: map[string]ClassDecl{},
|
||||
ClassInfo: map[string]*ClassSymbol{},
|
||||
Functions: map[string]FunctionDecl{},
|
||||
Enums: map[string]EnumDecl{},
|
||||
Imports: map[string]bool{},
|
||||
GoPackages: map[string]*gotypes.Package{},
|
||||
Mappings: &mappingState{functions: map[string]string{}},
|
||||
Syntax: program,
|
||||
Global: NewScope(nil),
|
||||
Classes: map[string]ClassDecl{},
|
||||
ClassInfo: map[string]*ClassSymbol{},
|
||||
Functions: map[string]FunctionDecl{},
|
||||
Enums: map[string]EnumDecl{},
|
||||
Imports: map[string]bool{},
|
||||
GoPackages: map[string]*gotypes.Package{},
|
||||
Mappings: &mappingState{functions: map[string]string{}},
|
||||
FunctionEffects: map[string]Effect{},
|
||||
}
|
||||
programs := append([]*Program{program}, additional...)
|
||||
for _, source := range programs {
|
||||
|
|
@ -198,6 +201,7 @@ func analyzeProgram(program *Program, additional []*Program) (*SemanticProgram,
|
|||
if err := resolver.resolve(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inferCoroutineEffects(semantic)
|
||||
return semantic, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ const (
|
|||
tokenEnum tokenKind = "ENUM"
|
||||
tokenMatch tokenKind = "MATCH"
|
||||
tokenFun tokenKind = "FUN"
|
||||
tokenSuspend tokenKind = "SUSPEND"
|
||||
tokenOverride tokenKind = "OVERRIDE"
|
||||
tokenPrivate tokenKind = "PRIVATE"
|
||||
tokenVal tokenKind = "VAL"
|
||||
|
|
@ -70,7 +69,6 @@ const (
|
|||
|
||||
var keywords = map[string]tokenKind{
|
||||
"fun": tokenFun,
|
||||
"suspend": tokenSuspend,
|
||||
"import": tokenImport,
|
||||
"package": tokenPackage,
|
||||
"class": tokenClass,
|
||||
|
|
|
|||
|
|
@ -125,6 +125,8 @@ func (semantic *SemanticProgram) TypeOf(expr Expr, environment TypeEnvironment)
|
|||
if len(value.TypeArgs) == 1 {
|
||||
return GenericType{Base: NamedType{Name: "Deferred"}, Args: []Type{resolve(value.TypeArgs[0])}}
|
||||
}
|
||||
case "coroutineContext":
|
||||
return NamedType{Name: "context.Context"}
|
||||
}
|
||||
if ident.Name == "keys" && len(value.Args) == 1 {
|
||||
if mapping, ok := semantic.TypeOf(value.Args[0], environment).(GenericType); ok && (mapping.Base.String() == "Map" || mapping.Base.String() == "MutableMap") && len(mapping.Args) == 2 {
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ type FunctionType struct {
|
|||
TypeParams []string
|
||||
Params []Type
|
||||
Result Type
|
||||
Effects Effect
|
||||
}
|
||||
|
||||
func (FunctionType) typeNode() {}
|
||||
|
|
@ -200,7 +201,7 @@ func resolveTypeParameters(typ Type, params map[string]bool) Type {
|
|||
for index, param := range value.Params {
|
||||
resolved[index] = resolveTypeParameters(param, params)
|
||||
}
|
||||
return FunctionType{TypeParams: value.TypeParams, Params: resolved, Result: resolveTypeParameters(value.Result, params)}
|
||||
return FunctionType{TypeParams: value.TypeParams, Params: resolved, Result: resolveTypeParameters(value.Result, params), Effects: value.Effects}
|
||||
case GenericType:
|
||||
args := make([]Type, len(value.Args))
|
||||
for index, arg := range value.Args {
|
||||
|
|
@ -248,7 +249,7 @@ func substituteType(typ Type, bindings map[string]Type) Type {
|
|||
for index, param := range value.Params {
|
||||
params[index] = substituteType(param, bindings)
|
||||
}
|
||||
return FunctionType{TypeParams: value.TypeParams, Params: params, Result: substituteType(value.Result, bindings)}
|
||||
return FunctionType{TypeParams: value.TypeParams, Params: params, Result: substituteType(value.Result, bindings), Effects: value.Effects}
|
||||
default:
|
||||
return typ
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue