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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

lexer/typecheck: String < String not built-in for lex comparison

1 participant