Fixing the 'using' Directive Bug and Adding HTTP Status Code Constants in EZ Language
Overview
I contributed to EZ, which is a modern programming language built with Go.
I worked on two related issues: adding HTTP status code constants to the @http module (Issue #901) and fixing a bug where the using directive did not bring stdlib constants into scope (Issue #909).
Background: What is the using Directive?
In EZ, you can import modules and use them with a namespace prefix:
import @http
do main() {
println("${http.OK}") // Works: 200
}
The using directive allows you to access module exports without the prefix:
import @std
using std
do main() {
println("Hello") // No need for std.println
}
The Problem
Issue #901: Need for HTTP Status Code Constants
Users had to use raw integers for HTTP status code comparisons:
if response.status == 200 {
// ...
}
This is not readable. We wanted to support named constants like:
if response.status == http.OK {
// ...
}
Issue #909: using Directive Only Works for Functions
After implementing the HTTP constants, we discovered a bigger problem. The using directive did not bring constants into scope:
import @http
using http
do main() {
println("${OK}") // Error: undefined variable 'OK'
println("${http.OK}") // Works: 200
}
This bug affected all stdlib modules with constants:
@http- OK, CREATED, NOT_FOUND, etc.@os- MAC_OS, LINUX, WINDOWS@math- PI, E, PHI, SQRT2@time- SUNDAY, MONDAY, JANUARY, HOUR, DAY@io- READ_ONLY, WRITE_ONLY, SEEK_START@db- ALPHA, NUMERIC, KEY_LEN@uuid- NIL
My Solution
I solved both issues with a two-part approach.
Part 1: Adding HTTP Status Code Constants (#907)
First, I added HTTP status code constants to pkg/stdlib/http.go:
const (
OK int64 = 200
CREATED int64 = 201
ACCEPTED int64 = 202
NO_CONTENT int64 = 204
BAD_REQUEST int64 = 400
UNAUTHORIZED int64 = 401
FORBIDDEN int64 = 403
NOT_FOUND int64 = 404
INTERNAL_SERVER_ERROR int64 = 500
// ... and more
)
Then I exposed these as builtins in the HttpBuiltins map:
"http.OK": {
Fn: func(args ...object.Object) object.Object {
return &object.Integer{Value: big.NewInt(OK)}
},
},
Part 2: Fixing the using Directive Bug (#979)
The core problem was in the type checker. It only checked for functions when validating identifiers accessed via using, not constants.
Step 1: Add IsConstant flag to all stdlib constants
I added an IsConstant: true flag to distinguish constants from functions:
"http.OK": {
Fn: func(args ...object.Object) object.Object {
return &object.Integer{Value: big.NewInt(OK)}
},
IsConstant: true, // Added this flag
},
I applied this to all constants across 7 stdlib modules: db.go, http.go, io.go, math.go, os.go, time.go, and uuid.go.
Step 2: Add isStdlibConstant() function to type checker
I added a new function in pkg/typechecker/typechecker.go to check if an identifier is a stdlib constant:
func (tc *TypeChecker) isStdlibConstant(moduleName, constName string) bool {
stdConsts := map[string]map[string]bool{
"http": {
"OK": true, "CREATED": true, "BAD_REQUEST": true,
"NOT_FOUND": true, "INTERNAL_SERVER_ERROR": true,
// ... all HTTP constants
},
"os": {
"MAC_OS": true, "LINUX": true, "WINDOWS": true,
"CURRENT_OS": true,
},
"math": {
"PI": true, "E": true, "PHI": true, "SQRT2": true,
},
// ... other modules
}
if modConsts, ok := stdConsts[moduleName]; ok {
return modConsts[constName]
}
return false
}
Step 3: Update isKnownIdentifier() to check constants
I modified the isKnownIdentifier() function to also check stdlib constants when using is declared:
// Check if it's a stdlib constant accessible via 'using'
for moduleName := range tc.fileUsingModules {
if tc.isStdlibConstant(moduleName, name) {
return true
}
}
if tc.currentScope != nil {
for moduleName := range tc.currentScope.usingModules {
if tc.isStdlibConstant(moduleName, name) {
return true
}
}
}
Step 4: Add unit tests for each module
I wrote unit tests to verify the fix works for all stdlib constants:
func TestHttpModuleConstantWithoutModuleNamespace(t *testing.T) {
input := `
import @http
using http
do main() {
const status_200 int = OK
const status_404 int = NOT_FOUND
const status_500 int = INTERNAL_SERVER_ERROR
}
`
tc := typecheck(t, input)
assertNoErrors(t, tc)
}
Result
The changes were merged in PR #907 and PR #979.
What was achieved:
- New Feature: Users can now use readable HTTP status code constants (
http.OK,http.NOT_FOUND, etc.) - Bug Fix: The
usingdirective now correctly brings all stdlib constants into scope - Consistency: Constants now work the same way as functions with
using - Test Coverage: Added unit tests for all stdlib module constants
Now users can write clean code like:
import @http
using http
do main() {
temp resp, err = http.get("https://api.example.com")
if resp.status == OK {
println("Success!")
} else if resp.status == NOT_FOUND {
println("Resource not found")
}
}
This was a great experience contributing to a programming language project and understanding how type checkers validate identifiers!