Skip to content

Commit

Permalink
sema: improve return statement analysis
Browse files Browse the repository at this point in the history
  • Loading branch information
mertcandav committed Oct 22, 2024
1 parent 3c45bf5 commit 280ef65
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 79 deletions.
3 changes: 2 additions & 1 deletion std/jule/build/log.jule
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,8 @@ enum LogMsg: str {
InvalidTypeForComptimeIter: `type @ is not supports comptime iterations`,
InvalidComptimeIter: `comptime iterations can only be range iteration`,
InvalidComptimeTypeMatchExpr: `comptime type-match expressions can take only type declarations`,
WrongRetForward: "function return forwaring is wrong\n want (@)\n have (@)",
NotEnoughVariablesForRet: "not enough variables for return\n want (@)\n have (@)",
TooManyVariablesForRet: "too many variables for return\n want (@)\n have (@)",
ExportedUsedAsAnonymous: `define @ is exported for backend so you cannot use as anonymous function`,
InvalidImportPath: `invalid import path: @`,
AutoAliasFail: `import path is not suitable for auto-aliasing: @`,
Expand Down
9 changes: 8 additions & 1 deletion std/jule/sema/fn.jule
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,14 @@ impl Fn {

// Reports whether function has return variable(s).
fn AnyVar(self): bool {
ret self.Result != nil && len(self.Result.Idents) > 0
if self.Result != nil {
for _, ident in self.Result.Idents {
if !token::IsAnonIdent(ident.Kind) {
ret true
}
}
}
ret false
}

// Force to new instance.
Expand Down
6 changes: 3 additions & 3 deletions std/jule/sema/sema.jule
Original file line number Diff line number Diff line change
Expand Up @@ -1406,7 +1406,7 @@ impl sema {
ret
}

fn checkFnDeclResultDup(mut self, &f: &Fn): (ok: bool) {
fn checkFnDeclResultDup(mut self, mut &f: &Fn): (ok: bool) {
ok = true
if f.IsVoid() {
ret
Expand All @@ -1431,12 +1431,12 @@ impl sema {
}

// Lookup in return identifiers.
itself_lookup:
itselfLookup:
for j, jv in f.Result.Idents {
match {
| j >= i:
// Skip current and following identifiers.
break itself_lookup
break itselfLookup
| jv.Kind == v.Kind:
goto exist
}
Expand Down
137 changes: 63 additions & 74 deletions std/jule/sema/type2.jule
Original file line number Diff line number Diff line change
Expand Up @@ -1263,8 +1263,9 @@ impl rangeChecker {
struct retTypeChecker {
sc: &scopeChecker
f: &FnIns
types: []&Type // Return types.
exprs: []&ast::Expr // Return expressions.
types: []&Type // Return types.
exprs: []&Data // Return expressions.
exprDecls: []&ast::Expr
errorToken: &token::Token
model: ExprModel
mutable: bool // Whether the return expression should be mutable.
Expand All @@ -1277,91 +1278,80 @@ impl retTypeChecker {
}
}

fn prepareExprs(mut self, mut &e: &ast::Expr) {
fn prepareExprs(mut self, mut &e: &ast::Expr): (ok: bool) {
if e == nil {
ret
ret true
}
match type e.Kind {
| &ast::TupleExpr:
self.exprs = (&ast::TupleExpr)(e.Kind).Expr
self.exprDecls = (&ast::TupleExpr)(e.Kind).Expr
|:
self.exprs = append(self.exprs, e)
self.exprDecls = [e]
}
}

fn checkExprs(mut self) {
if len(self.exprs) == 0 {
ret
}
mut datas := make([]&Data, 0, len(self.exprs))
ok = true
mut eval := self.sc.s.eval(self.sc)
eval.target.mutable = self.mutable
for (i, mut expr) in self.exprs {
if i >= len(self.types) {
break
for (i, mut expr) in self.exprDecls {
if i < len(self.types) {
eval.prefix = self.types[i]
}

mut t := self.types[i]

eval.prefix = t
mut d := eval.evalExpr(expr)
if d == nil {
continue
ok = ok && d != nil
if ok {
self.exprs = append(self.exprs, d)
}
}
ret
}

datas = append(datas, d)

fn checkExprs(mut self) {
if len(self.exprs) == 0 {
ret
}
for (i, mut d) in self.exprs {
mut expr := self.exprDecls[i]
if self.mutable && !d.Mutable && d.Type.Mutable() {
self.sc.s.pushErr(self.errorToken, build::LogMsg.RetWithMutTypedNonMut)
self.sc.s.pushErr(expr.Token, build::LogMsg.RetWithMutTypedNonMut)
ret
}

mut ac := assignTypeChecker{
s: self.sc.s,
dest: t,
dest: self.types[i],
d: d,
errorToken: self.errorToken,
errorToken: expr.Token,
refers: self.sc.getHardRoot().owner.Refers,
}
ac.check()
}

// Set Model.
if len(datas) > 1 { // Tuple
self.model = &TupleExprModel{
Datas: datas,
}
} else if len(datas) == 1 {
self.model = datas[0].Model
if len(self.exprs) > 1 { // Tuple
self.model = &TupleExprModel{Datas: self.exprs}
} else if len(self.exprs) == 1 {
self.model = self.exprs[0].Model
}
}

// Assumes len(self.exprs) == 0 and kind is [&ast::FnCallExpr].
fn tryFuncMultiRetForward(mut self): bool {
mut eval := self.sc.s.eval(self.sc)
mut d := eval.evalExpr(self.exprs[0])
if d == nil {
// Return true to skip following "false" handling errors.
// Evaluation error is enough for developer for now.
ret true
}
mut tup := d.Type.Tup()
if tup == nil {
ret false
}
// Assumes the len(self.exprs) == 1 and self.exprs[0].Type.Tup() != nil.
fn tryFuncMultiRetForward(mut self) {
mut tup := self.exprs[0].Type.Tup()
if len(tup.Types) != len(self.types) {
goto err
}
self.model = d.Model
self.model = self.exprs[0].Model
for i in self.types {
mut t1 := self.types[i]
mut t2 := tup.Types[i]
if !self.sc.s._checkTypeCompatibility(t1, t2, self.errorToken) {
goto err
}
}
ret true
ret
err:
self.notFitVariables()
}

fn notFitVariables(mut self) {
mut wanted := strings::Builder.New(1 << 5)
for i, t in self.types {
wanted.WriteStr(t.Str())!
Expand All @@ -1370,43 +1360,42 @@ impl retTypeChecker {
}
}
mut given := strings::Builder.New(1 << 5)
for i, t in tup.Types {
given.WriteStr(t.Str())!
if len(self.types)-i > 1 {
for i, d in self.exprs {
given.WriteStr(d.Type.Str())!
if len(self.exprs)-i > 1 {
given.WriteStr(", ")!
}
}
self.sc.s.pushErr(self.errorToken, build::LogMsg.WrongRetForward, wanted.Str(), given.Str())
ret false
if len(self.exprs) < len(self.types) {
self.sc.s.pushErr(self.errorToken, build::LogMsg.NotEnoughVariablesForRet, wanted.Str(), given.Str())
} else {
self.sc.s.pushErr(self.errorToken, build::LogMsg.TooManyVariablesForRet, wanted.Str(), given.Str())
}
}

fn check(mut self, mut &e: &ast::Expr): bool {
self.prepareTypes()
self.prepareExprs(e)
mut ok := self.prepareExprs(e)
if !ok {
ret false
}

// Check whether we have enough variables for return.
match {
| len(self.exprs) == 0 && len(self.types) > 0:
if !self.f.Decl.AnyVar() {
self.sc.s.pushErr(self.errorToken, build::LogMsg.RequireRetExpr)
ret false
}
| len(self.exprs) == 0 && len(self.types) > 0 && self.f.Decl.AnyVar():
ret true
| len(self.exprs) > 0 && self.f != nil && self.f.Decl.IsVoid():
self.sc.s.pushErr(self.errorToken, build::LogMsg.VoidFnRetExpr)
ret false
| len(self.exprs) == 1 && len(self.types) > 1:
match type self.exprs[0].Kind {
| &ast::FnCallExpr:
if self.tryFuncMultiRetForward() {
ret true
}
| len(self.exprs) == 1 && len(self.types) > 1 && self.exprs[0].Type.Tup() != nil:
match type self.exprs[0].Model {
| &FnCallExprModel:
self.tryFuncMultiRetForward()
ret true
}
self.sc.s.pushErr(self.errorToken, build::LogMsg.MissingMultiRet)
fall
| len(self.exprs) != len(self.types):
self.notFitVariables()
ret false
| len(self.exprs) < len(self.types):
self.sc.s.pushErr(self.errorToken, build::LogMsg.MissingMultiRet)
| len(self.exprs) > len(self.types):
self.sc.s.pushErr(self.errorToken, build::LogMsg.OverflowRet)
}

self.checkExprs()
ret true
}
Expand Down

0 comments on commit 280ef65

Please sign in to comment.