Merge pull request 'assign-return-break-to-anything' (#56) from assign-return-break-to-anything into main

Reviewed-on: #56
This commit is contained in:
Sasha Koshka 2024-03-06 22:42:18 +00:00
commit f17dba23ce
11 changed files with 200 additions and 41 deletions

View File

@ -42,7 +42,7 @@ func primitiveType (name string) *entity.TypeNamed {
func init () {
builtinTypes["Index"] = &entity.TypeWord { Acc: entity.AccessPublic }
builtinTypes["Byte"] = &entity.TypeInt { Acc: entity.AccessPublic, Signed: false, Width: 8 }
builtinTypes["Bool"] = &entity.TypeInt { Acc: entity.AccessPublic, Signed: false, Width: 8 }
builtinTypes["Bool"] = &entity.TypeInt { Acc: entity.AccessPublic, Signed: false, Width: 1 }
builtinTypes["Rune"] = &entity.TypeInt { Acc: entity.AccessPublic, Signed: false, Width: 32 }
builtinTypes["String"] = &entity.TypeSlice {
Acc: entity.AccessPublic,

View File

@ -23,6 +23,16 @@ U: (| Int F64)
`)
}
func TestMatchReturnValueUsed (test *testing.T) {
testString (test,
`
U: (| Int F64)
[isInt u:U]:Bool = match u in
| u:Int [return true]
| u:F64 false
`)
}
func TestMatchUnionUnderComplete (test *testing.T) {
testString (test,
`
@ -79,3 +89,23 @@ U: (| Int F64 UInt)
| u:Int u
`)
}
func TestIfElseReturnValueUsed (test *testing.T) {
testString (test,
`
[is5 x:Int]:Bool = if [= x 5]
then true
else [return false]
`)
}
func TestIfElseBreakValueUsed (test *testing.T) {
testString (test,
`
[f x:Int]: Int = loop {
y:Int = if [= x 5]
then [break 0]
else x
}
`)
}

View File

@ -816,12 +816,6 @@ func (this *Tree) analyzeBreak (
entity.Expression,
error,
) {
if into != nil {
return nil, errors.Errorf (
brk.Position, "expected %v",
entity.FormatType(into))
}
loop, ok := this.topLoop()
if !ok {
return nil, errors.Errorf (
@ -853,12 +847,6 @@ func (this *Tree) analyzeReturn (
entity.Expression,
error,
) {
if into != nil {
return nil, errors.Errorf (
ret.Position, "expected %v",
entity.FormatType(into))
}
ret.Declaration, _ = this.topDeclaration()
var ty entity.Type
switch ret.Declaration.(type) {

View File

@ -81,3 +81,13 @@ testUnit (test,
"", "F64\n",
0,
)}
func TestReturnAssign (test *testing.T) {
dependencies := []string {
compileDependency(test, "io"),
}
testUnit (test,
"/test-data/data/returnassign", dependencies,
"", "false\n",
0,
)}

View File

@ -0,0 +1,2 @@
'678748d4-ce9e-45db-bccb-07eecf067770'
+ 'io'

View File

@ -0,0 +1,10 @@
[main]:I32 'main' = {
if [is5 7]
then io::[println 'true']
else io::[println 'false']
0
}
[is5 x:Int]:Bool = if [= x 5]
then true
else [return false]

View File

@ -67,7 +67,8 @@ chunks of memory, and to index arrays and such.
Byte is defined as the smallest addressable integer. It is unsigned. It is
usually equivalent to U8.
### Bool
Bool is a boolean type. It is equivalent to Byte.
Bool is a boolean type. It is equivalent to U1. For now, since arbitrary width
integers are not supported, it is the only way to get a U1 type.
### Rune
Rune is defined as a U32. It represents a single UTF-32 code point.
### String
@ -266,13 +267,15 @@ assignment rules of all of its break statements. Loops may be nested, and break
statements only apply to the closest containing loop. The value of the loop's
expression is never used.
### Break
Break allows breaking out of loops. It has no value and may not be assigned to
anything.
Break allows breaking out of loops. It may be assigned to anything, but the
assignment will have no effect as execution of the code after and surrounding it
will cease.
### Return
Return allows terminating functions before they have reached their end. It
accepts values that may be assigned to the function's return type. If a function
does not return anything, the return statement does not accept a value. In all
cases, return statements have no value and may not be assigned to anything.
does not return anything, the return statement does not accept a value. It may
be assigned to anything, but the assignment will have no effect as execution of
the function or method will cease.
### Assignment
Assignment allows assigning the result of one expression to one or more location
expressions. The assignment expression itself has no value and may not be

View File

@ -200,7 +200,7 @@ U: (| Int F64)
func TestMatchReturn (test *testing.T) {
testString (test,
`%"AAAAAAAAAAAAAAAAAAAAAA==::Bool" = type i8
`%"AAAAAAAAAAAAAAAAAAAAAA==::Bool" = type i1
%"0zNZN147MN2wzMAQ6NS2dQ==::U" = type { i64, i64 }
define %"AAAAAAAAAAAAAAAAAAAAAA==::Bool" @"0zNZN147MN2wzMAQ6NS2dQ==::isInt"(%"0zNZN147MN2wzMAQ6NS2dQ==::U" %u) {
0:
@ -230,6 +230,45 @@ U: (| Int F64)
`)
}
func TestMatchReturnValueUsed (test *testing.T) {
testString (test,
`%"AAAAAAAAAAAAAAAAAAAAAA==::Bool" = type i1
%"0zNZN147MN2wzMAQ6NS2dQ==::U" = type { i64, i64 }
define %"AAAAAAAAAAAAAAAAAAAAAA==::Bool" @"0zNZN147MN2wzMAQ6NS2dQ==::isInt"(%"0zNZN147MN2wzMAQ6NS2dQ==::U" %u) {
0:
%1 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::U"
store %"0zNZN147MN2wzMAQ6NS2dQ==::U" %u, ptr %1
%2 = alloca i64
%3 = alloca double
%4 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::U", ptr %1, i32 0, i32 0
%5 = load i64, ptr %4
switch i64 %5, label %6 [
i64 7620767046192759206, label %8
i64 9186060094042213285, label %11
]
6:
%7 = phi i1 [ false, %11 ], [ zeroinitializer, %0 ]
ret i1 %7
8:
%9 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::U", ptr %1, i32 0, i32 1
%10 = load i64, ptr %9
store i64 %10, ptr %2
ret i1 true
11:
%12 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::U", ptr %1, i32 0, i32 1
%13 = load double, ptr %12
store double %13, ptr %3
br label %6
}
`,
`
U: (| Int F64)
[isInt u:U]:Bool = match u in
| u:Int [return true]
| u:F64 false
`)
}
func TestMatchUnionUnderComplete (test *testing.T) {
testString (test,
`%"0zNZN147MN2wzMAQ6NS2dQ==::U" = type { i64, i64 }
@ -298,3 +337,63 @@ U: (| Int F64 UInt)
| u:F64 [print 'F64']
`)
}
func TestIfElseReturnValueUsed (test *testing.T) {
testString (test,
`%"AAAAAAAAAAAAAAAAAAAAAA==::Bool" = type i1
define %"AAAAAAAAAAAAAAAAAAAAAA==::Bool" @"0zNZN147MN2wzMAQ6NS2dQ==::is5"(i64 %x) {
0:
%1 = alloca i64
store i64 %x, ptr %1
%2 = load i64, ptr %1
%3 = icmp eq i64 %2, 5
br i1 %3, label %4, label %7
4:
br label %5
5:
%6 = phi i1 [ true, %4 ]
ret i1 %6
7:
ret i1 false
}
`,
`
[is5 x:Int]:Bool = if [= x 5]
then true
else [return false]
`)
}
func TestIfElseBreakValueUsed (test *testing.T) {
testString (test,
`define i64 @"0zNZN147MN2wzMAQ6NS2dQ==::f"(i64 %x) {
0:
%1 = alloca i64
store i64 %x, ptr %1
%2 = alloca i64
br label %3
3:
%4 = load i64, ptr %1
%5 = icmp eq i64 %4, 5
br i1 %5, label %6, label %9
6:
br label %11
7:
%8 = phi i64 [ %10, %9 ]
store i64 %8, ptr %2
br label %3
9:
%10 = load i64, ptr %1
br label %7
11:
ret i64 0
}
`,
`
[f x:Int]: Int = loop {
y:Int = if [= x 5]
then [break 0]
else x
}
`)
}

View File

@ -49,6 +49,7 @@ func (this *generator) generateIfElse (ifelse *entity.IfElse, mode resultMode) (
var trueBlock, falseBlock *llvm.Block
var tru, fals llvm.Value
var loc bool
var irIncomings []*llvm.Incoming
trueBlock = this.blockManager.newBlock()
exitBlock := this.blockManager.newBlock()
@ -56,7 +57,15 @@ func (this *generator) generateIfElse (ifelse *entity.IfElse, mode resultMode) (
tru, loc, err = this.generateExpression(ifelse.True, mode)
if err != nil { return nil, false, err }
if !this.blockManager.Terminated() { this.blockManager.NewBr(exitBlock) }
if !this.blockManager.Terminated() {
if tru != nil {
irIncomings = append(irIncomings, &llvm.Incoming {
X: tru,
Predecessor: this.blockManager.Block,
})
}
this.blockManager.NewBr(exitBlock)
}
if ifelse.False == nil {
// there is no false case
@ -66,9 +75,22 @@ func (this *generator) generateIfElse (ifelse *entity.IfElse, mode resultMode) (
} else {
// there is a false case
falseBlock = this.blockManager.newBlock()
fals, _, err = this.generateExpression(ifelse.False, mode)
thisMode := mode
if mode != resultModeAny {
if loc { thisMode = resultModeLoc
} else { thisMode = resultModeVal }
}
fals, _, err = this.generateExpression(ifelse.False, thisMode)
if err != nil { return nil, false, err }
if !this.blockManager.Terminated() { this.blockManager.NewBr(exitBlock) }
if !this.blockManager.Terminated() {
if fals != nil {
irIncomings = append(irIncomings, &llvm.Incoming {
X: fals,
Predecessor: this.blockManager.Block,
})
}
this.blockManager.NewBr(exitBlock)
}
if mode == resultModeAny {
// discard results of statements
@ -78,18 +100,9 @@ func (this *generator) generateIfElse (ifelse *entity.IfElse, mode resultMode) (
} else {
// obtain results of statements
// set up phi to capture results
trueIncoming := &llvm.Incoming {
X: tru,
Predecessor: trueBlock,
}
falseIncoming := &llvm.Incoming {
X: fals,
Predecessor: falseBlock,
}
previous.NewCondBr(condition, trueBlock, falseBlock)
this.blockManager.Block = exitBlock
return this.blockManager.NewPhi(trueIncoming, falseIncoming), loc, nil
return this.blockManager.NewPhi(irIncomings...), loc, nil
}
}
}
@ -105,8 +118,8 @@ func (this *generator) generateMatch (match *entity.Match, mode resultMode) (llv
exitBlock := this.blockManager.newBlock()
irHashType := llvm.I64
irCases := make([]*llvm.Case, len(match.Cases))
irIncomings := make([]*llvm.Incoming, len(match.Cases))
irCases := make([]*llvm.Case, len(match.Cases))
irIncomings := []*llvm.Incoming { }
for index, cas := range match.Cases {
// set up ir case
caseBlock := this.blockManager.newBlock()
@ -124,7 +137,7 @@ func (this *generator) generateMatch (match *entity.Match, mode resultMode) (llv
this.blockManager.addDeclaration(cas.Declaration, data)
// generate case expression
thisMode := mode // FIXME this logic isnt in if/else, why??
thisMode := mode
if index != 0 && mode != resultModeAny {
if loc { thisMode = resultModeLoc
} else { thisMode = resultModeVal }
@ -132,11 +145,15 @@ func (this *generator) generateMatch (match *entity.Match, mode resultMode) (llv
caseExpression, thisLoc, err := this.generateExpression(cas.Expression, thisMode)
if err != nil { return nil, false, err }
if index == 0 { loc = thisLoc }
irIncomings[index] = &llvm.Incoming {
X: caseExpression,
Predecessor: caseBlock,
if !this.blockManager.Terminated() {
if caseExpression != nil {
irIncomings = append(irIncomings, &llvm.Incoming {
X: caseExpression,
Predecessor: this.blockManager.Block,
})
}
this.blockManager.NewBr(exitBlock)
}
if !this.blockManager.Terminated() { this.blockManager.NewBr(exitBlock) }
}
// create switch branch

View File

@ -93,7 +93,7 @@ testString (test,
func TestCompare (test *testing.T) {
testString (test,
`%"AAAAAAAAAAAAAAAAAAAAAA==::Bool" = type i8
`%"AAAAAAAAAAAAAAAAAAAAAA==::Bool" = type i1
%"0zNZN147MN2wzMAQ6NS2dQ==::A" = type i64
define void @"0zNZN147MN2wzMAQ6NS2dQ==::main"() {
0:

View File

@ -9,7 +9,7 @@ testString (test,
%"0zNZN147MN2wzMAQ6NS2dQ==::Pegasus" = type { ptr, ptr, ptr, ptr, ptr }
%"0zNZN147MN2wzMAQ6NS2dQ==::Point" = type { i64, i64 }
%"0zNZN147MN2wzMAQ6NS2dQ==::Rectangle" = type { %"0zNZN147MN2wzMAQ6NS2dQ==::Point", %"0zNZN147MN2wzMAQ6NS2dQ==::Point" }
%"AAAAAAAAAAAAAAAAAAAAAA==::Bool" = type i8
%"AAAAAAAAAAAAAAAAAAAAAA==::Bool" = type i1
%"AAAAAAAAAAAAAAAAAAAAAA==::Byte" = type i8
%"AAAAAAAAAAAAAAAAAAAAAA==::Rune" = type i32
%"0zNZN147MN2wzMAQ6NS2dQ==::AllInts" = type { %"AAAAAAAAAAAAAAAAAAAAAA==::Bool", %"AAAAAAAAAAAAAAAAAAAAAA==::Byte", %"AAAAAAAAAAAAAAAAAAAAAA==::Index", %"AAAAAAAAAAAAAAAAAAAAAA==::Rune", i64, i64, i8, i16, i32, i64, i8, i16, i32, i64 }