Post

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 using directive 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!

This post is licensed under CC BY 4.0 by the author.