Skip to content

Commit 23b0a15

Browse files
committed
refactor: improve control flow analysis and error handling in functions
1 parent 3d99c47 commit 23b0a15

File tree

6 files changed

+117
-12
lines changed

6 files changed

+117
-12
lines changed

app/cmd/if_test.fer

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ fn isEven(number: i32) -> bool {
5555
} else {
5656
return false;
5757
}
58+
59+
// unreachable code
60+
let unreachableVar := 100;
5861
}
5962

6063
fn classify(score: i32) -> i32 {
@@ -80,12 +83,15 @@ fn complexLogic(x: i32, y: i32, z: i32) -> i32 {
8083
} else {
8184
return 2; // x and y positive, z non-positive
8285
}
86+
return 0;
8387
} else {
8488
return 3; // x positive, y non-positive
8589
}
8690
} else {
8791
return 4; // x non-positive
8892
}
93+
94+
return 0;
8995
}
9096

9197
// Function that uses other functions

app/cmd/proposal.fer

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
2+
// fetch returns a string or an error
3+
fn fetchData() -> str, error {
4+
5+
// dummy variable "dataAvailable" to simulate data availability
6+
if !dataAvailable {
7+
return Error("network down", 500)!; // "!" suffix indicates an error message
8+
}
9+
10+
return "payload"; // successful return
11+
}
12+
13+
fn main() {
14+
15+
// | value | { ... } is a closure that handles the error case for possible error return from functions
16+
let data, _ := fetchData()
17+
18+
19+
20+
21+
22+
23+
print("Got: " + data);
24+
25+
// Optional types use Type? syntax. Value can be of the type or None.
26+
// None is a constant representing absence of value.
27+
let maybeUser: str? = None;
28+
let name: str = maybeUser ?? "Guest"; // "??" operator provides a default value if the variable is None
29+
print("Hello, " + name);
30+
}
31+
32+

app/cmd/start.fer

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,21 @@ geometry::area_circle(3.0);
3232

3333
fn doSomething() {
3434
// This function does something
35+
add(1, 5, voidfn);
36+
//myfunc();
3537
}
3638

37-
fn add(a: i32, b: i32) -> i32 {
39+
fn add(a: i32, b: i32, callback: fn()) -> i32 {
40+
41+
callback();
3842
return a + b;
3943
}
4044

4145
let value := 10;
4246

47+
// let myfunc := fn() {
48+
49+
// };
4350

4451
let myname := "Ferret";
4552
let byt := myname[0];

app/cmd/test.fer

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,51 @@ const later := fn() -> i32 {
2525

2626
// comment
2727
//let x := a + ;
28+
29+
30+
// type Point struct {
31+
// .x: i32,
32+
// .y: i32
33+
// };
34+
35+
// let p := Point { .x = 12, .y = 38 };
36+
37+
// print(p.x)
38+
39+
// -------------------
40+
41+
// type Point struct {
42+
// x: i32,
43+
// y: i32
44+
// };
45+
46+
// let p := @Point { x: 12, y: 29 };
47+
48+
49+
// // Optional types
50+
51+
// let res : str?; // string or None
52+
// res = "hello"; // valid
53+
// res = None; // valid
54+
55+
// let data: str = res ?? "No value"; // if res is None, assign "No value" to data
56+
57+
// // if res != None {
58+
// // print(res);
59+
// // } else {
60+
// // print("no value");
61+
// // }
62+
63+
// // Error return from functions
64+
// fn mayFail() -> str ! {
65+
// if dataAvalable {
66+
// return "Data";
67+
// } else {
68+
// return "Error occurred"!;
69+
// }
70+
// }
71+
72+
// let result: str = mayFail() | error | {
73+
// print("Handling error: " + error);
74+
// return;
75+
// }

app/fer.ret

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ name = "demo-app"
44
version = "0.0.2"
55

66
[build]
7-
entry = "cmd/start.fer"
7+
entry = "cmd/if_test.fer"
88
output = "bin"
99

1010
[cache]

compiler/internal/semantic/typecheck/control_flow.go

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,8 @@ func analyzeControlFlow(r *analyzer.AnalyzerNode, block *ast.Block, cm *modules.
161161
hasConditionals := false
162162

163163
// Process all nodes in the block
164-
earlyReturn, hasConditionals := processBlockNodes(r, block, cm, expectedReturnType, &result, &conditionalResults, hasConditionals)
165-
if earlyReturn {
164+
foundReturn, hasConditionals := processBlockNodes(r, block, cm, expectedReturnType, &result, &conditionalResults, hasConditionals)
165+
if foundReturn {
166166
return result
167167
}
168168

@@ -179,15 +179,20 @@ func createEmptyControlFlowResult() ControlFlowResult {
179179
}
180180
}
181181

182-
// processBlockNodes processes all nodes in a block and returns early if return found
182+
// processBlockNodes processes all nodes in a block and detects unreachable code
183183
func processBlockNodes(r *analyzer.AnalyzerNode, block *ast.Block, cm *modules.Module, expectedReturnType stype.Type,
184184
result *ControlFlowResult, conditionalResults *[]ControlFlowResult, hasConditionals bool) (bool, bool) {
185185

186186
reachable := true
187+
foundReturn := false
188+
var unreachableStart *source.Location
187189

188190
for _, node := range block.Nodes {
189191
if !reachable {
190-
reportUnreachableCode(r, node)
192+
// Mark the start of unreachable section on first unreachable node
193+
if unreachableStart == nil {
194+
unreachableStart = node.Loc()
195+
}
191196
continue
192197
}
193198

@@ -198,28 +203,35 @@ func processBlockNodes(r *analyzer.AnalyzerNode, block *ast.Block, cm *modules.M
198203
if !hasConditionals {
199204
result.HasFallbackReturn = true
200205
}
201-
return true, hasConditionals // Early return found
206+
reachable = false // Mark subsequent code as unreachable
207+
foundReturn = true
202208
case *ast.IfStmt:
203209
hasConditionals = true
204210
ifResult := analyzeIfStatement(r, n, cm, expectedReturnType)
205211
*conditionalResults = append(*conditionalResults, ifResult)
206212
if ifResult.AllPathsReturn {
207213
result.AllPathsReturn = true
208-
return true, hasConditionals // All paths in if statement return
214+
reachable = false // Mark subsequent code as unreachable
215+
foundReturn = true
209216
}
210217
default:
211218
checkNode(r, node, cm)
212219
}
213220
}
214221

215-
return false, hasConditionals
222+
// Report unreachable section once if any was found
223+
if unreachableStart != nil {
224+
reportUnreachableSection(r, unreachableStart)
225+
}
226+
227+
return foundReturn, hasConditionals
216228
}
217229

218-
// reportUnreachableCode reports unreachable code after return
219-
func reportUnreachableCode(r *analyzer.AnalyzerNode, node ast.Node) {
230+
// reportUnreachableSection reports a section of unreachable code after return
231+
func reportUnreachableSection(r *analyzer.AnalyzerNode, startLoc *source.Location) {
220232
r.Ctx.Reports.AddSemanticError(
221233
r.Program.FullPath,
222-
node.Loc(),
234+
startLoc,
223235
"unreachable code after return statement",
224236
report.TYPECHECK_PHASE,
225237
)

0 commit comments

Comments
 (0)