Skip to content

feat(typecheck): String relational ops (<, >, <=, >=) (closes #458) - #464

Merged
hyperpolymath merged 1 commit into
mainfrom
feat/typecheck-string-relational-ops-458
May 30, 2026
Merged

feat(typecheck): String relational ops (<, >, <=, >=) (closes #458)#464
hyperpolymath merged 1 commit into
mainfrom
feat/typecheck-string-relational-ops-458

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

Summary

Closes #458 — `String < String` (and `>` / `<=` / `>=`) now type-check, lowering to JS's native lexicographic string comparison. Pre-fix: `TypeMismatch (String, Int)`.

Implementation

Single addition to the existing comparison dispatch in `Typecheck.synth_expr` for `ExprBinary`:

```ocaml
match repr lhs_ty with
| TCon "Float" -> ...
| TCon "String" ->
let* () = check ctx rhs ty_string in
Ok ty_bool
| _ -> ... (* legacy Int monomorphism *)
```

Pattern mirrors the existing Float dispatch a few lines up. No codegen changes needed — JavaScript's `<` / `>` / `<=` / `>=` on strings is lex compare natively, and the JS-family backends already emit those operators verbatim.

Test plan

New regression fixture `tests/codegen-deno/string_lex_cmp.affine` + harness with 22 assertions:

  • All four ops via functional form (`lt(a, b)`, etc.) — covers each operator's positive/negative direction

  • All four ops via literal form (`first_lt()`, etc.)

  • Equal-string corner cases — `x <= x` true, `x >= x` true, `x < x` false

  • Empty strings — `"" < "a"`, `"" <= ""`

  • Prefix relations — `"abc" < "abcd"`

  • Local `./tools/run_codegen_deno_tests.sh`: 14/14 harnesses green

  • Local `dune test`: 352/352 green

  • Smoke compile: `return a < b;` emits as `return (a < b);` (JS native)

Out of scope

Refs

🤖 Generated with Claude Code

Adds a `TCon "String"` case to the comparison branch in
`Typecheck.synth_expr`'s `ExprBinary` handler, alongside the existing
Int and Float dispatches. Lex-compare semantics — JS's native string
comparison gives byte-wise lexicographic order out of the box, so the
JS-family backends (codegen_deno, js_codegen) need no change: the
existing `<` / `>` / `<=` / `>=` op emission already does the right
thing once typecheck stops rejecting String operands.

Pre-fix, `let _ = "a" < "b"` raised `TypeMismatch (String, Int)`,
forcing downstream ports (e.g. standards#284's `check-ts-allowlist`
port) to inline byte-wise `str_lt` helpers using
`char_to_int(string_get(...))` at every use site.

Regression fixture `tests/codegen-deno/string_lex_cmp.affine` +
harness exercise 22 assertions covering all four ops (functional
form + literal form), equal-string corner cases (<, <=, >= behaviour
all three directions), empty strings, and prefix-relations.

Non-ASCII string comparison is naturally exercised once #463 (the
companion Unicode-escape codegen fix for #460) lands — until then,
this fixture stays ASCII-only so it doesn't inherit the octal-escape
ESM SyntaxError. The relational typecheck is orthogonal to the
literal encoding; both ship together once both PRs are in.

Verified: `tools/run_codegen_deno_tests.sh` (14/14 harnesses green);
`dune test` (352/352 green).

Closes #458
Refs hyperpolymath/standards#284 (the seam-analyst PR that surfaced
this — `str_lt` workaround)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@hyperpolymath
hyperpolymath enabled auto-merge (squash) May 30, 2026 14:11
@github-actions

Copy link
Copy Markdown

🔍 Hypatia Security Scan

Findings: 83 issues detected

Severity Count
🔴 Critical 4
🟠 High 11
🟡 Medium 68

⚠️ Action Required: Critical security issues found!

View findings
[
  {
    "reason": "Action perpolymath/standards/.github/workflows/governance-reusable.yml@main\n needs attention",
    "type": "unpinned_action",
    "file": "governance.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Action ons/checkout@v6\n    needs attention",
    "type": "unpinned_action",
    "file": "publish-jsr.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Action land/setup-deno@v2\n    needs attention",
    "type": "unpinned_action",
    "file": "publish-jsr.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in affine-vscode-publish.yml",
    "type": "unknown",
    "file": "affine-vscode-publish.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in casket-pages.yml",
    "type": "unknown",
    "file": "casket-pages.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in casket-pages.yml",
    "type": "unknown",
    "file": "casket-pages.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in ci.yml",
    "type": "unknown",
    "file": "ci.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in ci.yml",
    "type": "unknown",
    "file": "ci.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in ci.yml",
    "type": "unknown",
    "file": "ci.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in ci.yml",
    "type": "unknown",
    "file": "ci.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  }
]

Powered by Hypatia Neurosymbolic CI/CD Intelligence

@hyperpolymath
hyperpolymath merged commit a050c65 into main May 30, 2026
26 of 27 checks passed
@hyperpolymath
hyperpolymath deleted the feat/typecheck-string-relational-ops-458 branch May 30, 2026 14:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant