ffi 3.2 A2: implement #selector("explicit:string") override

Make-green half of the cadence step started in A1. Wires the
`#selector` directive end-to-end:

- Lexer token `hash_selector` at src/token.zig + lookup row in
  src/lexer.zig.
- AST field `selector_override: ?[]const u8 = null` on
  `ForeignMethodDecl` (src/ast.zig).
- Parser block in src/parser.zig that mirrors
  `#jni_method_descriptor` — both occupy the same slot after the
  optional `-> ReturnType` and before the body/terminator. Not
  mutually exclusive at parse time.
- LSP semantic-token list (src/lsp/server.zig) updated.
- Lowering: `deriveObjcSelector` returns
  `{ sel, keyword_count, is_override }`. When `is_override` is true,
  the selector string is the user's literal and `keyword_count` is
  the colon count in that literal. Both `lowerObjcMethodCall` and
  `lowerObjcStaticCall` use the result.

Diagnostic policy when override colon-count ≠ call arity:

- Default mangling path: stays an error (`.err`). The user can fix
  the sx-side name to produce the right keyword count.
- Override path: downgrades to a warning (`.warn`). Rationale:
  Obj-C's `objc_msgSend` doesn't validate colon-vs-arg the way JNI's
  `GetMethodID` validates the descriptor — the runtime dispatches
  regardless and the wrong-arity case becomes silent calling-
  convention corruption. The compiler is the last line of defense
  for this typo class, but the warning preserves the override's
  escape-hatch character (deliberate mismatches still proceed).

Snapshot for `examples/ffi-objc-dsl-06-selector-override.sx` flips
from the pre-3.2 parser-error to working output:

  static override non-null: true

The mismatch diagnostic text in
`examples/ffi-objc-dsl-04-mismatch.sx`'s snapshot is updated to
drop the "once that lands (3.2)" phrasing now that 3.2 is here.

165/165 example tests.
This commit is contained in:
agra
2026-05-25 17:00:23 +03:00
parent a908ecf28f
commit 572ab12142
10 changed files with 98 additions and 37 deletions

View File

@@ -1239,6 +1239,22 @@ pub const Parser = struct {
try self.expect(.r_paren);
}
// Optional `#selector("explicit:string")` — explicit Obj-C selector override
// (Phase 3.2). Same slot as the JNI descriptor; they're not mutually
// exclusive at parse time though they belong to different runtimes.
var sel_override: ?[]const u8 = null;
if (self.current.tag == .hash_selector) {
self.advance(); // skip `#selector`
try self.expect(.l_paren);
if (self.current.tag != .string_literal) {
return self.fail("expected string literal selector after '#selector('");
}
const raw_sel = self.tokenSlice(self.current);
sel_override = raw_sel[1 .. raw_sel.len - 1];
self.advance();
try self.expect(.r_paren);
}
// Method body is optional: `;` → declaration (foreign or inherited
// method we just want to call); `{ ... }` → sx-side implementation
// for sx-defined classes.
@@ -1256,6 +1272,7 @@ pub const Parser = struct {
.return_type = return_type,
.is_static = is_static,
.jni_descriptor_override = desc_override,
.selector_override = sel_override,
.body = body_node,
} });
}