Skip to content

Commit 73871ce

Browse files
author
Batiste Bieler
committed
Update doc
1 parent 1d5d74c commit 73871ce

4 files changed

Lines changed: 197 additions & 20 deletions

File tree

CHANGELOG.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,46 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.3.0] - 2026-03-05
9+
10+
This release continues to strengthen the type system and developer experience with immutability
11+
patterns, improved type narrowing, and better error diagnostics.
12+
13+
### Added
14+
15+
**Type system**
16+
- `readonly` modifier for object properties and arrays: prevent accidental mutations and catch
17+
errors at compile time. Syntax: `readonly name: string`, `readonly string[]` (#84).
18+
- `as const` assertions: freeze inferred types to their literal equivalents. Useful for creating
19+
immutable configuration objects and type-safe enums (#85).
20+
- Type narrowing on object sub-elements: `if obj?.property` now correctly narrows the type of
21+
nested properties within the conditional block.
22+
- "Object is possibly undefined" warnings: improved diagnostics when accessing properties on
23+
potentially nullish objects (#86).
24+
25+
**Editor & diagnostics**
26+
- Auto-completion improvements: better handling of optional chaining when suggesting property
27+
and method names.
28+
- Improved error messages: clearer explanations of type mismatches and compilation failures.
29+
- Enhanced arity checking: better error reporting when function calls have incorrect argument
30+
counts with suggestions for fixes (#87).
31+
32+
**Formatting & code quality**
33+
- Whitespace enforcement: stricter parsing limits to maximum 2 consecutive spaces to encourage
34+
consistent code style and prevent accidental formatting issues.
35+
36+
### Changed
37+
38+
- Type narrowing logic refined to handle nested property access patterns more accurately.
39+
- Error message formatting improved for readability.
40+
41+
### Fixed
42+
43+
- Auto-completion with optional chaining operator (`?.`) now suggests correct members.
44+
- Edge cases in type narrowing on sub-elements.
45+
46+
---
47+
848
## [1.2.0] - 2026-02-25
949

1050
This release focuses heavily on making the language more solid: the inference engine

docs/MODERN_FEATURES.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,96 @@ The compiler registers dynamic import paths in the module's dependency list, so
311311
- Conditional features (load a debug panel only in development)
312312
- Plugin systems
313313
314+
## Immutability
315+
316+
Blop provides two powerful tools to enforce immutability and prevent accidental mutations: `readonly` and `as const`.
317+
318+
### The readonly Modifier
319+
320+
The `readonly` modifier marks individual properties or arrays as immutable. Any attempt to modify a readonly field results in a compile-time error.
321+
322+
```typescript
323+
type User = {
324+
readonly id: number,
325+
readonly name: string,
326+
email: string // mutable
327+
}
328+
329+
user: User = { id: 1, name: 'Alice', email: 'alice@example.com' }
330+
331+
// ❌ Error: cannot mutate readonly property
332+
user.id = 2
333+
user.name = 'Bob'
334+
335+
// ✅ OK: email is not readonly
336+
user.email = 'alice.new@example.com'
337+
```
338+
339+
Readonly also works with arrays:
340+
341+
```typescript
342+
forbiddenIds: readonly number[] = [1, 2, 3]
343+
344+
// ❌ Error: cannot mutate readonly array
345+
forbiddenIds.push(4)
346+
forbiddenIds[0] = 10
347+
348+
// ✅ OK: reading values
349+
count = forbiddenIds.length
350+
```
351+
352+
### The as const Assertion
353+
354+
`as const` is a powerful assertion that freezes an entire object or array, converting all types to their literal equivalents and marking everything as readonly:
355+
356+
```typescript
357+
// Without as const: { status: string }
358+
settings1 = { status: 'active' }
359+
360+
// With as const: { readonly status: 'active' }
361+
settings2 = { status: 'active' } as const
362+
```
363+
364+
This is particularly useful for creating type-safe enums and configuration constants:
365+
366+
```typescript
367+
// Type-safe enum pattern
368+
HttpStatus = {
369+
OK: 200,
370+
NotFound: 404,
371+
ServerError: 500
372+
} as const
373+
// Type: { readonly OK: 200, readonly NotFound: 404, readonly ServerError: 500 }
374+
375+
// Now the type system knows exact values, enabling better type checking
376+
code: HttpStatus.OK = HttpStatus.OK // ✅ OK
377+
code2: HttpStatus.NotFound = HttpStatus.OK // ❌ Error: 200 is not assignable to 404
378+
```
379+
380+
Array literals with `as const`:
381+
382+
```typescript
383+
AllowedRoles = ['admin', 'user', 'guest'] as const
384+
// Type: readonly ['admin', 'user', 'guest']
385+
386+
role1: AllowedRoles = 'admin' // ✅ OK
387+
role2: AllowedRoles = 'owner' // ❌ Error: 'owner' is not in the literal type
388+
```
389+
390+
### Key Differences
391+
392+
| Feature | `readonly` | `as const` |
393+
|---------|-----------|-----------|
394+
| Scope | Individual properties | Entire object/array |
395+
| Type change | Keeps general type | Converts to literal types |
396+
| Use case | Data structures with some mutable fields | Immutable constants and enums |
397+
| Syntax | `readonly name: string` | `obj as const` |
398+
399+
### Use Cases
400+
401+
- **readonly**: Data transfer objects with mostly immutable fields, API configurations, frozen lookups
402+
- **as const**: Feature flags, HTTP status codes, permission levels, immutable configuration tables
403+
314404
## Testing
315405

316406
Tests for modern features are in [src/tests/modern-features.test.blop](../src/tests/modern-features.test.blop).

docs/SYNTAX_REFERENCE.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ A practical reference for writing Blop code. Each section explains the syntax, s
1313
- [Objects and Arrays](#objects-and-arrays)
1414
- [Classes](#classes)
1515
- [Type Aliases](#type-aliases)
16+
- [Type Modifiers](#type-modifiers)
1617
- [try / catch / throw](#try--catch--throw)
1718
- [Compound Assignment](#compound-assignment)
1819
- [Virtual DOM](#virtual-dom)
@@ -291,6 +292,51 @@ def greet(u: User): string {
291292
}
292293
```
293294

295+
### Type Modifiers
296+
297+
#### readonly
298+
299+
The `readonly` modifier prevents modification of object properties and arrays. Attempting to modify a readonly field results in a compile-time error:
300+
301+
```typescript
302+
// Readonly properties in objects
303+
type Config = {
304+
readonly apiUrl: string,
305+
readonly timeout: number,
306+
retries: number
307+
}
308+
309+
// Readonly arrays
310+
type StringList = readonly string[]
311+
312+
// Using readonly in variables
313+
config: Config = { readonly apiUrl: 'https://api.example.com', timeout: 5000, retries: 3 }
314+
config.apiUrl = 'https://new-api.example.com' // ❌ Compile error: cannot mutate readonly property
315+
config.retries = 4 // ✅ OK: retries is mutable
316+
```
317+
318+
#### as const
319+
320+
The `as const` assertion freezes an object or array to use literal types instead of general types. This is useful for creating immutable configuration objects and type-safe enums:
321+
322+
```typescript
323+
// Without as const: inferred as { name: string, level: number }
324+
settings = { name: 'default', level: 1 }
325+
326+
// With as const: inferred as { readonly name: 'default', readonly level: 1 }
327+
immutableSettings = { name: 'default', level: 1 } as const
328+
329+
// as const with arrays: preserves specific value types
330+
directions = ['north', 'south', 'east', 'west'] as const
331+
// Type: readonly ['north', 'south', 'east', 'west']
332+
333+
// Type-safe enums
334+
Status = { Active: 'active', Inactive: 'inactive' } as const
335+
// Type: { readonly Active: 'active', readonly Inactive: 'inactive' }
336+
```
337+
338+
When you use `as const`, all nested types become readonly and use literal types, making them safer for constants and immutable data structures.
339+
294340
---
295341

296342
## try / catch / throw

docs/TYPESCRIPT_GAP_ANALYSIS.md

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ This document tracks which TypeScript type-system features are present in Blop a
2828
| Dead code detection (unreachable code after `return` / `throw`) | `statements.js` — warns on code after an unconditional early exit |
2929
| **Tuple types (`[string, number]`)** | `TupleType` — see design notes below |
3030
| **`as` type assertion** | Grammar reuses the `as` keyword; inference stamps asserted type; backend erases the annotation |
31+
| **`as const` / const assertions** | `{ x: 1 } as const` — freeze inferred types to literal equivalents; recursively convert to `LiteralType` |
32+
| **`readonly` modifier** | `readonly name: string`, `readonly T[]``readonly` flag on `ObjectType` properties and `ArrayType`; errors on assignment |
3133
| **User-defined type predicates (`x is T`)** | `PredicateType` — see design notes below |
3234
| **`keyof` operator** | `KeyofType` — see design notes below |
3335
| **Class member type annotations** | `classMembers.test.js` — member type annotations, method signatures, and `this` inference all work |
@@ -50,45 +52,44 @@ These are self-contained, add immediate practical value, and do not require majo
5052

5153
| # | Feature | TS example | Blocked by | Notes |
5254
|---|---|---|---|---|
53-
| 1 | **`as const` / const assertions** | `{ x: 1 } as const` || Freeze all inferred types to their literal equivalents. Reuse `as` grammar; add a `constAssertion` flag. Recursively convert `number``LiteralType(n)`, `string``LiteralType(s)`, turn object values into readonly literals. |
54-
| 2 | **`readonly` modifier** | `readonly name: string`, `readonly T[]` || Add a `readonly` flag in `ObjectType` properties and `ArrayType`. Emit a type error on assignment. Grammar: add `readonly` as an optional prefix on property annotations and array type syntax. No new `Type` subclass needed. |
55-
| 3 | **`satisfies` operator** | `expr satisfies T` | `as` infrastructure | Like `as` but with a compatibility check: error if the expression type is not assignable to `T`. Reuse `parseTypeExpression` + `isCompatibleWith`; keep the original inferred type (not the asserted one) for downstream inference. A strict complement to `as`. |
56-
| 4 | **Exhaustiveness checking** | `switch (x) { … }` + `never` narrowing | Type narrowing | After all branches of an `if/else` or `switch` have been handled, the remaining type should reduce to `never`. Emit a warning if a `never`-typed value is used (e.g. returned). Extend the checking phase's narrowing logic in `statements.js`. |
55+
| 1 | **`satisfies` operator** | `expr satisfies T` | `as` infrastructure | Like `as` but with a compatibility check: error if the expression type is not assignable to `T`. Reuse `parseTypeExpression` + `isCompatibleWith`; keep the original inferred type (not the asserted one) for downstream inference. A strict complement to `as`. |
56+
| 2 | **Exhaustiveness checking** | `switch (x) { … }` + `never` narrowing | Type narrowing | After all branches of an `if/else` or `switch` have been handled, the remaining type should reduce to `never`. Emit a warning if a `never`-typed value is used (e.g. returned). Extend the checking phase's narrowing logic in `statements.js`. |
5757

5858
### Tier 2 — Medium complexity (unlock utility types)
5959

6060
These require new `Type` subclasses but have well-understood semantics.
6161

6262
| # | Feature | TS example | Blocked by | Notes |
6363
|---|---|---|---|---|
64-
| 6 | **Mapped types** | `{ [K in keyof T]: T[K] }` | `keyof`| New `MappedType` class with `keyParam`, `sourceType`, `valueExpr`. Resolution: expand `keyof sourceType`, bind `K` to each key, evaluate `valueExpr`. Unlocks `Partial<T>`, `Required<T>`, `Pick<T,K>`, `Omit<T,K>` as standard library aliases. |
65-
| 7 | **Conditional types** | `T extends U ? X : Y` | New `ConditionalType` | New `ConditionalType` class with `checkType`, `extendsType`, `trueType`, `falseType`. Resolution: check if `checkType` is assignable to `extendsType`; pick branch. Enables `ReturnType<F>`, `Parameters<F>`, `NonNullable<T>`. |
66-
| 8 | **`infer` keyword** | `T extends Array<infer E>` | Conditional types | Binding mechanism inside the `extendsType` of a conditional. The `infer` variable is in scope only in the true branch. |
67-
| 9 | **`Partial<T>`, `Required<T>`, `Pick<T,K>`, `Omit<T,K>`** || Mapped types | Once mapped types land, these become simple type alias definitions in `stdlib.js`. |
68-
| 10 | **`ReturnType<F>`, `Parameters<F>`** || Conditional + `infer` | `ReturnType<F>` = `F extends (...args: any[]) => infer R ? R : never`. |
69-
| 11 | **Function overloads** | Multiple call signatures || `FunctionType[]` per symbol. At call sites: find the first compatible overload. Grammar: a block of signature declarations before the implementation body. |
70-
| 12 | **Template literal types** | `` `prefix-${string}` `` | New `TemplateLiteralType` | `TemplateLiteralType` with `parts: Array<string \| Type>`. Evaluates against string literal types; widens to `string` otherwise. |
64+
| 3 | **Mapped types** | `{ [K in keyof T]: T[K] }` | `keyof`| New `MappedType` class with `keyParam`, `sourceType`, `valueExpr`. Resolution: expand `keyof sourceType`, bind `K` to each key, evaluate `valueExpr`. Unlocks `Partial<T>`, `Required<T>`, `Pick<T,K>`, `Omit<T,K>` as standard library aliases. |
65+
| 4 | **Conditional types** | `T extends U ? X : Y` | New `ConditionalType` | New `ConditionalType` class with `checkType`, `extendsType`, `trueType`, `falseType`. Resolution: check if `checkType` is assignable to `extendsType`; pick branch. Enables `ReturnType<F>`, `Parameters<F>`, `NonNullable<T>`. |
66+
| 5 | **`infer` keyword** | `T extends Array<infer E>` | Conditional types | Binding mechanism inside the `extendsType` of a conditional. The `infer` variable is in scope only in the true branch. |
67+
| 6 | **`Partial<T>`, `Required<T>`, `Pick<T,K>`, `Omit<T,K>`** || Mapped types | Once mapped types land, these become simple type alias definitions in `stdlib.js`. |
68+
| 7 | **`ReturnType<F>`, `Parameters<F>`** || Conditional + `infer` | `ReturnType<F>` = `F extends (...args: any[]) => infer R ? R : never`. |
69+
| 8 | **Function overloads** | Multiple call signatures || `FunctionType[]` per symbol. At call sites: find the first compatible overload. Grammar: a block of signature declarations before the implementation body. |
70+
| 9 | **Template literal types** | `` `prefix-${string}` `` | New `TemplateLiteralType` | `TemplateLiteralType` with `parts: Array<string \| Type>`. Evaluates against string literal types; widens to `string` otherwise. |
7171

7272
### Tier 3 — Advanced / lower immediate impact
7373

74+
| # | Feature | TS example | Notes |
7475
| # | Feature | TS example | Notes |
7576
|---|---|---|---|
76-
| 13 | **`typeof x` in annotation position** | `type T = typeof someVar` | Type query — resolves to the inferred type of a variable. Requires look-up in the scope snapshot at declaration site. |
77-
| 14 | **`implements` interface checking** | `class Foo implements IFoo` | Class member annotations are already typed; `implements` would cross-check the class shape against a declared object type at the class-definition node. Straightforward extension of the existing excess-properties check. |
78-
| 15 | **Generic constraints** | `K extends keyof T` | Grammar for `extends` on type parameters; enforce in `substituteTypeParams`. `keyof` exists but cannot yet appear as a constraint. |
79-
| 16 | **Assertion functions** | `asserts cond` | Grammar + narrowing — analog to `PredicateType`; requires an `AssertionType` class and narrowing in the post-call scope. |
80-
| 17 | **Exhaustiveness in discriminated unions** | `switch (tag) { case 'a': … }` | Extension of Tier 1 #5; matches on literal types to peel the union case-by-case. |
81-
| 18 | **Declaration / interface merging** | Same-name type aliases | Symbol table change: collect all declarations of a name and merge their shapes. |
82-
| 19 | **Variance annotations** | `in`/`out` on generics | New flag on `GenericType`; enforced during `isCompatibleWith` on generic arguments. |
77+
| 10 | **`typeof x` in annotation position** | `type T = typeof someVar` | Type query — resolves to the inferred type of a variable. Requires look-up in the scope snapshot at declaration site. |
78+
| 11 | **`implements` interface checking** | `class Foo implements IFoo` | Class member annotations are already typed; `implements` would cross-check the class shape against a declared object type at the class-definition node. Straightforward extension of the existing excess-properties check. |
79+
| 12 | **Generic constraints** | `K extends keyof T` | Grammar for `extends` on type parameters; enforce in `substituteTypeParams`. `keyof` exists but cannot yet appear as a constraint. |
80+
| 13 | **Assertion functions** | `asserts cond` | Grammar + narrowing — analog to `PredicateType`; requires an `AssertionType` class and narrowing in the post-call scope. |
81+
| 14 | **Exhaustiveness in discriminated unions** | `switch (tag) { case 'a': … }` | Extension of Tier 1 #2; matches on literal types to peel the union case-by-case. |
82+
| 15 | **Declaration / interface merging** | Same-name type aliases | Symbol table change: collect all declarations of a name and merge their shapes. |
83+
| 16 | **Variance annotations** | `in`/`out` on generics | New flag on `GenericType`; enforced during `isCompatibleWith` on generic arguments. |
8384

8485
---
8586

8687
## What Would Move the Needle Most
8788

8889
If the goal is maximum practical value for Blop programs written today, the suggested order of attack is:
8990

90-
1. **`as const`** — unlocks literal-type patterns everywhere; touches only the `as` infrastructure already in place.
91-
2. **`readonly`** — adds an immutability safety net; property flag, no new type class.
91+
1. **`as const`** — unlocks literal-type patterns everywhere; touches only the `as` infrastructure already in place. **DONE as of v1.3.0**.
92+
2. **`readonly`** — adds an immutability safety net; property flag, no new type class. **DONE as of v1.3.0**.
9293
3. **`satisfies`** — adds the strict complement to `as`; reuses all type-check infrastructure.
9394
4. **Exhaustiveness checking** — big DX win for discriminated-union patterns; extends existing dead-code logic.
9495
5. **Mapped types** — medium effort, high reward: immediately unlocks `Partial`, `Pick`, `Omit`, `Required`.

0 commit comments

Comments
 (0)