Go Code Inlining Engine
This challenge asks you to build a Go program that can analyze and potentially inline simple functions within other Go functions. Inlining is a compiler optimization technique that replaces a function call with the actual code of the called function, which can improve performance by reducing function call overhead.
Problem Description
Your task is to create an "inlining engine" for a simplified subset of Go code. This engine will take a string representing Go source code, identify specific functions that are candidates for inlining, and then perform the inlining operation.
Key Requirements:
- Parse Go Code: The engine must be able to parse Go source code into an Abstract Syntax Tree (AST). The
go/parserandgo/astpackages are highly recommended for this. - Identify Inlinable Functions: Define criteria for which functions are considered "inlinable." For this challenge, a function is inlinable if:
- It is a top-level function (not a method or a function literal).
- It has a single return statement.
- It has no side effects (e.g., no printing to console, no file I/O, no complex control flow like
deferorpanic). For simplicity, we'll consider functions with only assignment statements, arithmetic operations, and simple return values as having no side effects. - It is called only once within the scope of other functions that could be modified by the inlining engine.
- Perform Inlining: For each identified inlinable function and its single call site:
- Replace the function call with the body of the inlined function.
- Ensure variable scopes are handled correctly (e.g., remapping parameters to local variables and adjusting variable names if conflicts arise).
- Remove the original function definition if it's no longer called anywhere else (though for this challenge, we can simplify and just remove the call).
- Output Modified Code: The engine should output the modified Go source code as a string.
Expected Behavior:
The engine should process the input Go code, find eligible functions, and substitute their bodies into their call sites. Functions that do not meet the inlining criteria, or functions that are called multiple times, should remain as they are.
Edge Cases to Consider:
- Functions with no parameters.
- Functions with multiple parameters.
- Functions returning different types.
- Variable name collisions between the caller and the inlined function.
- The case where an inlinable function is called within the same function definition.
- Input code with syntax errors (though for this challenge, assume valid Go syntax for the functions being processed).
Examples
Example 1:
package main
import "fmt"
func add(a, b int) int {
return a + b
}
func main() {
x := 5
y := 10
result := add(x, y)
fmt.Println(result)
}
Output:
package main
import "fmt"
func main() {
x := 5
y := 10
result := x + y // Inlined 'add' function
fmt.Println(result)
}
Explanation: The add function is simple, has one return, and is called only once in main. Its body a + b replaces the add(x, y) call, with a being x and b being y.
Example 2:
package main
import "fmt"
func multiply(a, b int) int {
return a * b
}
func calculate(val int) int {
temp := multiply(val, 2)
return temp + 5
}
func main() {
val := 3
finalResult := calculate(val)
fmt.Println(finalResult)
}
Output:
package main
import "fmt"
func calculate(val int) int {
temp := val * 2 // Inlined 'multiply' function
return temp + 5
}
func main() {
val := 3
finalResult := calculate(val)
fmt.Println(finalResult)
}
Explanation: The multiply function is inlinable. It's called within calculate. The multiply(val, 2) call is replaced by val * 2. The original multiply function definition is removed from the output for clarity, although the core task is to modify the call site.
Example 3:
package main
import "fmt"
func complexOperation(a int) int {
if a > 10 {
return a * 2
} else {
return a + 5
}
}
func anotherFunc(b int) int {
return b * 3
}
func main() {
val1 := 15
res1 := complexOperation(val1)
fmt.Println(res1)
val2 := 7
res2 := complexOperation(val2) // Second call to complexOperation
fmt.Println(res2)
val3 := 4
res3 := anotherFunc(val3)
fmt.Println(res3)
}
Output:
package main
import "fmt"
func complexOperation(a int) int {
if a > 10 {
return a * 2
} else {
return a + 5
}
}
func anotherFunc(b int) int {
return b * 3
}
func main() {
val1 := 15
res1 := complexOperation(val1)
fmt.Println(res1)
val2 := 7
res2 := complexOperation(val2) // Second call to complexOperation
fmt.Println(res2)
val3 := 4
res3 := anotherFunc(val3)
fmt.Println(res3)
}
Explanation: complexOperation is not inlinable because it has conditional logic (multiple return paths). anotherFunc is inlinable, but it's only called once. The problem description prioritizes inlining functions that are called only once within the functions that the engine will modify. In this simplified scenario, neither function is inlined because complexOperation doesn't meet the criteria, and while anotherFunc does, the example focuses on showcasing why complexOperation isn't chosen. If anotherFunc were called only once in main, it would be a candidate.
Constraints
- The input Go source code will be a single string representing a valid Go package.
- Focus on inlining top-level functions with simple bodies (assignments, arithmetic, single return). Avoid complex control flow (
if,for,switch,select),defer,panic,gostatements, and external function calls within the inlinable function's body. - The solution should be written entirely in Go.
- For this challenge, assume no external dependencies beyond the standard Go library (
go/parser,go/ast,go/token,go/printer, etc.). - The performance of the parsing and AST manipulation is not a primary concern, but the resulting code generation should be reasonably efficient.
Notes
- The
go/astpackage provides a powerful way to represent and manipulate Go code structure. - You'll likely need to traverse the AST to find function declarations and function calls.
- Handling variable name conflicts is crucial. Consider prefixing or renaming variables from the inlined function's scope to avoid clashes with variables in the calling scope.
- For simplification, you might want to implement the "single call site" check by first identifying all calls to a particular function and then proceeding only if there's exactly one.
- The
go/printerpackage can be used to convert the modified AST back into Go source code.