|
| 1 | +# Rune Constant Reference Issue Analysis |
| 2 | + |
| 3 | +## Problem Description |
| 4 | + |
| 5 | +When we declare a const in package A: |
| 6 | + |
| 7 | +```go |
| 8 | +const Separator = '/' |
| 9 | +``` |
| 10 | + |
| 11 | +Then import it in package B: |
| 12 | + |
| 13 | +```go |
| 14 | +const separator = a.Separator |
| 15 | +``` |
| 16 | + |
| 17 | +This generates TypeScript code that evaluates the constant at compile time: |
| 18 | + |
| 19 | +```typescript |
| 20 | +let separator: number = 47 |
| 21 | + |
| 22 | +// Later usage: |
| 23 | +console.log("separator used in function:", useInFunction(47)) |
| 24 | +``` |
| 25 | + |
| 26 | +instead of the expected: |
| 27 | + |
| 28 | +```typescript |
| 29 | +let separator: number = subpkg.Separator |
| 30 | + |
| 31 | +// Later usage: |
| 32 | +console.log("separator used in function:", useInFunction(separator)) |
| 33 | +``` |
| 34 | + |
| 35 | +The issue is that the const variable is being evaluated to its literal value instead of referencing the variable name. |
| 36 | + |
| 37 | +## Root Cause Analysis |
| 38 | + |
| 39 | +The issue is in `compiler/compiler.go` in the `WriteIdent` function (lines 746-751): |
| 40 | + |
| 41 | +```go |
| 42 | +// Check if this identifier refers to a constant |
| 43 | +if obj != nil { |
| 44 | + if constObj, isConst := obj.(*types.Const); isConst { |
| 45 | + // Only evaluate constants from the current package being compiled |
| 46 | + // Don't evaluate constants from imported packages (they should use their exported names) |
| 47 | + // Special case: predeclared constants like iota have a nil package, so we should evaluate them |
| 48 | + if constObj.Pkg() == c.pkg.Types || constObj.Pkg() == nil { |
| 49 | + // Write the constant's evaluated value instead of the identifier name |
| 50 | + c.writeConstantValue(constObj) |
| 51 | + return |
| 52 | + } |
| 53 | + } |
| 54 | +} |
| 55 | +``` |
| 56 | + |
| 57 | +The condition `constObj.Pkg() == c.pkg.Types` causes constants from the current package to always be evaluated to their literal values. This means: |
| 58 | + |
| 59 | +1. Constants from imported packages are correctly referenced by name (e.g., `subpkg.Separator`) |
| 60 | +2. Constants defined in the current package are evaluated to literals (e.g., `47` instead of `separator`) |
| 61 | + |
| 62 | +## Desired Behavior |
| 63 | + |
| 64 | +Local constants that are assigned from imported constants should maintain their variable reference rather than being evaluated to literals. This allows for: |
| 65 | + |
| 66 | +1. Better code readability |
| 67 | +2. Proper variable references in function calls |
| 68 | +3. Consistent behavior between imported and locally-declared constants |
| 69 | + |
| 70 | +## Test Case |
| 71 | + |
| 72 | +Created `compliance/tests/rune_const_reference/` with: |
| 73 | + |
| 74 | +- `rune_const_reference.go`: Main test that imports constants and uses them in function calls |
| 75 | +- `subpkg/subpkg.go`: Package with exported rune constants |
| 76 | + |
| 77 | +Current generated TypeScript shows the problem: |
| 78 | + |
| 79 | +```typescript |
| 80 | +// In main: |
| 81 | +console.log("separator used in function:", useInFunction(47)) |
| 82 | +console.log("newline used in function:", useInFunction(10)) |
| 83 | +``` |
| 84 | + |
| 85 | +Expected TypeScript: |
| 86 | + |
| 87 | +```typescript |
| 88 | +// In main: |
| 89 | +console.log("separator used in function:", useInFunction(separator)) |
| 90 | +console.log("newline used in function:", useInFunction(newline)) |
| 91 | +``` |
| 92 | + |
| 93 | +## Proposed Solution |
| 94 | + |
| 95 | +Modify the constant evaluation logic in `WriteIdent` to distinguish between: |
| 96 | + |
| 97 | +1. **Direct constant usage**: Where the original constant identifier is used directly |
| 98 | +2. **Local constant assignment**: Where a local constant is assigned from an imported constant |
| 99 | + |
| 100 | +For case 2, we should reference the local variable name instead of evaluating the value. |
| 101 | + |
| 102 | +The fix would involve checking if the constant is: |
| 103 | +- Defined in the current package AND |
| 104 | +- Assigned from an imported constant |
| 105 | + |
| 106 | +In such cases, use the variable name instead of evaluating the constant value. |
| 107 | + |
| 108 | +## Implementation Plan |
| 109 | + |
| 110 | +1. Update the constant evaluation logic in `WriteIdent` function |
| 111 | +2. Add additional analysis to detect when local constants reference imported constants |
| 112 | +3. Maintain backward compatibility for direct constant usage |
| 113 | +4. Test with the new compliance test case |
0 commit comments