distinct types: nominal newtype over workhorse types — typedef distinct Foo = int#3424
Merged
Conversation
…int)
New Type::tDistinct (AST-only, appended after tFixedArray) + DistinctTypeAnnotation
(TypeAnnotation subclass riding the existing TypeDecl annotation slot; firstType =
the underlying type). ABI is exactly the underlying workhorse type: size/align/pod/
copy/workhorse predicates delegate to firstType, and runtime TypeInfo, interpreter,
JIT, and fusion never observe tDistinct -- erased at makeTypeInfo alongside the
tFixedArray flatten; the JIT peels it in type_to_llvm_type/abi/vec4f marshaling
(LLVM_JIT_CODEGEN_VERSION bumped).
Semantics: no interconversion with the underlying type in either direction, so
overloads on Foo vs int coexist (new 'Q<mod::name>' mangling char, emit + parse).
The only way in is Foo(expr) -- exactly one argument of exactly the underlying
type, a reinterpret relabel with zero runtime cost. The only way out is *a, which
peels one distinct level and yields the underlying as a reference (const flows
from the handle; for distinct-over-pointer it peels the distinct, not the pointer).
== and != are borrowed (language-level, same-distinct-only, lowered to a compare
of the underlyings at infer); all other operators are user-defined. Distinct types
are not table keys by design. var a : Foo and default<Foo> zero-init.
Grammar is gen2-only, and 'distinct' is a CONTEXTUAL keyword: a flex trailing
context ('distinct' followed by optional private/public, NAME, '=') keeps every
other use -- notably daslib/linq's distinct() -- lexing as a plain identifier.
%expect 0 holds.
C++ side: DistinctTypeAnnotation + MAKE_DISTINCT_TYPE_FACTORY + makeDistinctType
let a C++ module register a distinct type and bind externs whose signatures carry
it (dasUnitTest NativeId example). das-declared entities round-trip through module
serialization (version 98 -> 99).
Errors: invalid_distinct_type=30297 (bad underlying, distinct-of-distinct, private
access), mismatching_distinct_type=30413 (construction arity/argument type).
Tests: tests/language/test_distinct.das + test_distinct_interop.das +
_distinct_fixture.das (cross-module + private), tests/typer_errors/
failed_distinct_*.das -- green under interpreter, JIT, and AOT; full test sweep
clean. Reference page: doc/source/reference/language/distinct.rst.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a new nominal distinct type feature to daScript (Gen2) that allows creating zero-cost newtypes over “workhorse” value types (e.g. distinct Id = int), with explicit in/out via Foo(x) and *foo, nominal overload separation, and runtime erasure to the underlying ABI type across interpreter/JIT/AOT/RTTI.
Changes:
- Introduces
Type::tDistinct+DistinctTypeAnnotation, integrates it into type checking/inference, operator handling (==/!=), mangling, and table-key rejection. - Extends the Gen2 lexer/parser to recognize
distinctdeclarations as a contextual keyword and build the corresponding AST annotations/types. - Adds cross-tier support (AST serialization, debug info/typeinfo erasure, LLVM JIT + AOT C++ emission) plus tests and documentation.
Reviewed changes
Copilot reviewed 45 out of 46 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/typer_errors/failed_distinct_table_key.das | New negative test: distinct types rejected as table keys. |
| tests/typer_errors/failed_distinct_private.das | New negative test: private distinct visibility across modules. |
| tests/typer_errors/failed_distinct_mismatch.das | New negative test: no implicit interop with underlying / no borrowed ops. |
| tests/typer_errors/failed_distinct_decl.das | New negative test: distinct-of-distinct and non-workhorse underlying rejected. |
| tests/typer_errors/failed_distinct_ctor.das | New negative test: ctor arity/type and “no fields” behavior. |
| tests/language/test_distinct.das | New language-level coverage for construction/deref/equality/containers/generics/modules. |
| tests/language/test_distinct_interop.das | New interop test for C++-registered distinct types. |
| tests/language/_distinct_fixture.das | Shared fixture module defining public/private distinct types for cross-module tests. |
| tests/aot/CMakeLists.txt | Ensures distinct fixture module is included in AOT language test module set. |
| src/simulate/debug_info.cpp | Adds “distinct” label for debug type name mapping. |
| src/parser/parser_impl.h | Declares ast_distinctDeclaration parser helper. |
| src/parser/parser_impl.cpp | Implements distinct declaration validation + registers DistinctTypeAnnotation. |
| src/parser/lex2.yy.h | Regenerated lexer header line mapping update. |
| src/parser/ds2_parser.ypp | Adds distinct token and grammar rule for distinct declarations. |
| src/parser/ds2_parser.hpp | Regenerated parser token enum includes DAS_DISTINCT. |
| src/parser/ds2_lexer.lpp | Adds contextual lexer rule for distinct ... = to return DAS_DISTINCT. |
| src/parser/ds2_lexer.cpp | Regenerated lexer tables/code for the new distinct rule. |
| src/builtin/module_builtin_rtti.h | Exposes Type.tDistinct in RTTI enum binding. |
| src/builtin/module_builtin_ast_serialize.cpp | Serializes tDistinct TypeDecl + round-trips das-declared distinct annotations in modules. |
| src/ast/ast_typedecl.cpp | TypeDecl behavior updates for tDistinct (describe, hashing/mangling, ABI delegation, etc.). |
| src/ast/ast_simulate.cpp | Interpreter simulation: treat *distinct as compile-time relabel (no null-check/deref). |
| src/ast/ast_program.cpp | Type resolution for distinct annotations + makeConst peels distinct to underlying. |
| src/ast/ast_module.cpp | Adds ModuleLibrary::makeDistinctType for C++ type-factory integration. |
| src/ast/ast_infer_type.cpp | Type inference for *distinct, Foo(expr) construction rewrite, and is_distinct trait. |
| src/ast/ast_infer_type_op.cpp | Lowers ==/!= on same distinct type to underlying compare via deref nodes. |
| src/ast/ast_infer_type_make.cpp | Special-cases default<distinct> folding (currently problematic for non-numeric underlyings). |
| src/ast/ast_infer_type_helper.cpp | Verifies distinct validity + rejects distinct table keys with specific error. |
| src/ast/ast_infer_type_function.cpp | Avoids duplicate/incorrect diagnostics for distinct construction path. |
| src/ast/ast_debug_info_helper.cpp | Erases tDistinct at AST→TypeInfo boundary (like fixed-array flatten). |
| modules/dasUnitTest/unitTest.h | Adds C++ NativeId wrapper + cast/WrapType for distinct interop. |
| modules/dasUnitTest/test_handles.cpp | Registers NativeId as a distinct annotation + binds extern native_id_next. |
| modules/dasLLVM/daslib/llvm_jit.das | JIT visitor: treat *distinct as relabel and adjust vec4f marshaling. |
| modules/dasLLVM/daslib/llvm_jit_run.das | Bumps LLVM_JIT_CODEGEN_VERSION for distinct support. |
| modules/dasLLVM/daslib/llvm_jit_common.das | Erases distinct to underlying in LLVM type mapping (ABI/type). |
| include/daScript/simulate/debug_info.h | Adds tDistinct to Type enum docs (AST-only + erasure notes). |
| include/daScript/ast/compilation_errors.h | Adds new error codes for distinct invalidity/mismatch. |
| include/daScript/ast/ast.h | Adds RTTI hook for distinct annotations + makeDistinctType API on ModuleLibrary. |
| include/daScript/ast/ast_typefactory.h | Adds MAKE_DISTINCT_TYPE_FACTORY for C++ signature typing. |
| include/daScript/ast/ast_typedecl.h | Adds TypeDecl::isDistinct() and makeDistinctType declaration. |
| include/daScript/ast/ast_serializer.h | Bumps AST serializer version for distinct support. |
| include/daScript/ast/ast_handle.h | Defines DistinctTypeAnnotation (nominal distinct entity). |
| doc/source/stdlib/handmade/enumeration-rtti-Type.rst | Updates RTTI Type enum docs to include distinct. |
| doc/source/reference/language/distinct.rst | New language reference page for distinct types (semantics + examples). |
| doc/source/reference/language.rst | Adds distinct page to language reference TOC. |
| daslib/aot_cpp.das | AOT: emits underlying C++ type with distinct comment + handles *distinct as relabel. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
CI's ser/deser sweep hit the serializeAnnotationPointer own-module verify
('expected to see macro module') -- a das-declared DistinctTypeAnnotation is
the first annotation owned by a NON-macro module. Own-module distinct
references now stream as a name and resolve on read against the module's own
registry (the entities round-trip in Module::serialize before anything that
references them). Verified: full-tree --ser/--deser sweep green locally, and
the deserialized distinct tests run 25/25.
Also: explicit tests that two distincts over the same underlying (Id vs
OtherId) are mutually incompatible -- calls and == both reject (30341).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lot round-1 fixes Grammar rework per review discussion: the declaration is now 'typedef [private|public] distinct Foo = int' - a distinct_alias production under DAS_TYPEDEF where 'distinct' is a plain NAME validated in the action. No lexer involvement at all: the trailing-context contextual-keyword rule is gone, comments between tokens lex away normally, and every identifier use of 'distinct' (daslib/linq etc.) is untouched at any position. Privacy sits in the typedef family's slot (typedef private distinct ...) - the kw-first order needs two-token lookahead and would break %expect 0. Nim precedent (type Foo = distinct int). Copilot round 1: - default<Foo> over string/pointer underlyings crashed: the distinct fold called Program::makeConst which covers neither. String/pointer now route like the non-distinct default<> paths (ExprConstString / ExprConstPtr); any other makeConst-null underlying errors 30413 instead of crashing. Probe-confirmed before/after; tests added. - DistinctTypeAnnotation::getOwnSemanticHash null-guards underlyingType, consistent with the class's other methods. tree-sitter grammar: typedef_declaration grows the distinct alternative; corpus case added (42/42); parser.c regenerated (one-time large mechanical diff - standing parser.c was tree-sitter-cli 0.22 output, regen is 0.26.9 per the Node-24 requirement; precedent PR #2877). Validation: full sweep 11315 green interp + AOT, distinct tests green under JIT and ser/deser, Sphinx latex+html clean, lint/format clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment on lines
+1153
to
+1156
| auto underT = expr->type->firstType; | ||
| ExpressionPtr ews; | ||
| if (underT->isString()) { | ||
| ews = new ExprConstString(expr->at); |
This was referenced Jul 10, 2026
pull Bot
pushed a commit
to forksnd/daScript
that referenced
this pull request
Jul 10, 2026
…e zero enumerant The zero-value constant folds in InferTypes::visit(ExprMakeStruct*) (string/ enum/pointer/workhorse) ran even when the make-struct carried field initializers; the reportAstChanged() rerun then discarded preVisit's invalid_structure_type error, so E(a=1) — and V(x=1) via an alias of any workhorse/pointer type — compiled silently to the zero value. Guard the fold chain: with non-empty structs on a non-composite make-type, skip the fold and return, so the preVisit error survives to the final pass. This subsumes the distinct-type guard from GaijinEntertainment#3424 (removed). The zero-value folds stay for the legitimate empty forms (default<T>, T()). Rides along: CLAUDE.md distinct-types entry + .gitignore build32/ leftovers from the GaijinEntertainment#3424 session. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Promotes weak types (
int,float,void?, any workhorse type) to unique nominal types with zero runtime/ABI cost:From the ABI perspective a distinct type IS its underlying type — same size, alignment, pass-by-value. But it never interconverts: functions taking
intwon't takeEntityIdand vice versa, two distincts over the same underlying are mutually incompatible, and overloads on all of them coexist.Semantics
Foo(expr)— exactly one argument of exactly the underlying type; a reinterpret relabel, zero runtime cost.var a : Foo = 5is an error;var a : Foo/default<Foo>zero-init (string/pointer/vector underlyings included).*a— peels exactly one distinct level, yields the underlying as a reference (*a = 5writes; const flows from the handle per the const model). For distinct-over-pointer,*peels the distinct, not the pointer.==/!=are borrowed (language-level, same-distinct-only, lowered to a compare of the underlyings at infer). Everything else is user-defined:def operator + (a, b : Meters) : Meters => Meters(*a + *b).typedef private distinct Foo = intvisibility, like enums.Implementation
distinctis not a keyword at all. The declaration is adistinct_aliasproduction undertypedef:typedef [private|public] distinct Foo = int, withdistincta plain NAME validated in the action (Nim'stype Foo = distinct intshape). No lexer involvement — comments between tokens lex away normally and every identifier use ofdistinct(daslib/linq's function etc.) is untouched. Gen2 only;%expect 0holds. The tree-sitter grammar gets the matchingtypedef_declarationalternative (parser.c regenerated — one-time large mechanical diff, standing parser.c was tree-sitter-cli 0.22 output, regen is 0.26.9 per the Node-24 requirement; precedent tree-sitter: fix type<T>; parse failure in type position #2877).Type::tDistinct, AST-only, appended aftertFixedArray(append-only keeps serialized ordinals/hashes stable). Entity isDistinctTypeAnnotation : TypeAnnotationriding the existingTypeDecl::annotationslot (already cloned/GC-walked/compared/serialized);firstTypeholds the underlying. ABI predicates (size/align/pod/copy/move/local/workhorse/gc-flags/string-data) delegate tofirstType; semantic predicates stay false so nothing leaks.makeTypeInfoboundary (same as the fixed-array flatten): interpreter, RTTI, and fusion never observetDistinct. The JIT walks AST types, so it peels intype_to_llvm_type/type_to_llvm_abi_type/ vec4f marshaling and treats the distinct deref as a pass-through (LLVM_JIT_CODEGEN_VERSIONbumped). AOT emits the underlying C++ type (DAS_COMMENT(distinct Name)annotated), both indescribeCppTypeExanddaslib/aot_cpp.das.Q<module::name>(emit + parse) keepsf(Foo)/f(int)overloads and AOT hashes distinct.aliasSubstitution-style rail: unresolvedExprCallnamed like a distinct type →ExprCastwithreinterpret.Foo()andFoo(a=1)are rejected. The workhorse make-struct fold'sProgram::makeConstnull-deref (pre-existing for any non-const-able type) is fixed; distinctdefault<>routes string/pointer underlyings like the non-distinct paths and errors cleanly on anything makeConst can't produce.TypeDecltDistinct case + das-declared entities round-trip with the module, registered beforealiasTypes/enumerationsdeserialize; own-module annotation references stream by name (serializeAnnotationPointerpreviously assertedmacroContext— das-declared distincts are the first annotations owned by a non-macro module). Version 98 → 99. Exercised by CI's ser/deser sweep (full-tree--ser/--desergreen locally).DistinctTypeAnnotation+MAKE_DISTINCT_TYPE_FACTORY+ModuleLibrary::makeDistinctTypelet a C++ module register a distinct type and bind externs whose signatures carry it. Worked example:NativeIdin dasUnitTest.invalid_distinct_type = 30297,mismatching_distinct_type = 30413.Validation
tests/language/test_distinct.das(26 subtests: construct/deref/write, equality, Foo-vs-Bar nominal walls, string/pointer/float3 underlyings incl.default<>, user operators, overload coexistence, containers + struct/tuple/variant fields, generics +is_distincttrait +typedecl(*x), cross-module + fixture) — green under interpreter, JIT, AOT, and ser/deser.tests/language/test_distinct_interop.das— C++-registered distinct end-to-end.tests/typer_errors/failed_distinct_*.das— declaration, mismatch (incl. cross-distinct), ctor, table-key, private rejections with exactexpectcodes.test_aot; tree-sitter corpus 42/42.doc/source/reference/language/distinct.rst+ rttiTypeenum handmade line; das2rst/Sphinx (html + latex-W) clean; preflight full-pass token + sequence smoke green.🤖 Generated with Claude Code