Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions changelog/unreleased/1589.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
- removes 273 of the 357 inline `/** @type {T} */ (v)` casts under `fjs/`,
which AGENTS.md asks to avoid: 182 were redundant outright, 23 became
`@satisfies` or an annotated declaration, and 68 became the runtime check
they stood in for — `assertNotNullish`, a discriminant `assert`, or a
checked accessor in the MCP and CAS proofs, where a response is `unknown`
and its shape is the very thing the proof exists to establish
- `effects/node/virtual`, `effects/node` and `ci` proofs narrow `_Entity` to
`Dir` with `instanceof Array` rather than a cast; `Array.isArray` narrows to
`any[]`, which `readonly Vec[]` is not assignable to, so its negative branch
never removed a `readonly` array from the union
- `fjs run` asserts a module's `main` is callable before invoking it, failing
with the file name instead of `main is not a function` from inside the
effect runner
9 changes: 8 additions & 1 deletion fjs/asn.1/module.f.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
vec,
vec8,
} from '../types/bit_vec/module.f.mjs'
import { assert } from '../asserts/module.f.mjs'
import { identity } from '../types/function/module.f.mjs'
import { max } from '../types/function/compare/module.f.mjs'
import { encode as b128encode, decode as b128decode } from '../basen/base128/module.f.mjs'
Expand Down Expand Up @@ -65,7 +66,13 @@ const parsedTagEncode = ([classPc, number]) => {
/** @type {(v: Vec) => readonly[_ParsedTag, Vec]} */
const parsedTagDecode = v => {
const [firstByte, rest] = pop8(v)
const classPc = /** @type {_ClassPc} */(firstByte & classPcMask)
const classPc = firstByte & classPcMask
// `classPcMask` is the top three bits, so the result is one of eight values;
// the assert is what narrows `bigint` to `_ClassPc`.
assert(classPc === 0b000_00000n || classPc === 0b001_00000n
|| classPc === 0b010_00000n || classPc === 0b011_00000n
|| classPc === 0b100_00000n || classPc === 0b101_00000n
|| classPc === 0b110_00000n || classPc === 0b111_00000n, classPc)
const firstByteNumber = firstByte & tagNumberMask
const [number, rest1] = firstByteNumber < tagNumberMask
? [firstByteNumber, rest]
Expand Down
7 changes: 4 additions & 3 deletions fjs/bnf/ll1/module.f.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
*/

import { strictEqual } from '../../types/function/operator/module.f.mjs'
import { assertNotNullish } from '../../asserts/module.f.mjs'
import { toArray } from '../../types/list/module.f.mjs'
import { rangeMap } from '../../types/range_map/module.f.mjs'
import { contains, set } from '../../types/string_set/module.f.mjs'
Expand Down Expand Up @@ -95,7 +96,7 @@ export const dispatchMap = ruleSet => {
result = result.map(x => [addRuleToDispatch(x[0], item), x[1]])
} else {
dm = dispatchRule(dm, item, newCurrent)
const dr = /** @type {_DispatchRule} */ (dm[item])
const dr = assertNotNullish(dm[item])
if (emptyTag === true) {
result = result.map(x => [addRuleToDispatch(x[0], item), x[1]])
result = toArray(dispatchOp.merge(result)(dr.rangeMap))
Expand All @@ -116,7 +117,7 @@ export const dispatchMap = ruleSet => {
let emptyTag = undefined
for (const [tag, item] of entries) {
dm = dispatchRule(dm, item, newCurrent)
const dr = /** @type {_DispatchRule} */ (dm[item])
const dr = assertNotNullish(dm[item])
if (nullMap[item] !== undefined) {
emptyTag = tag
} else {
Expand Down Expand Up @@ -255,7 +256,7 @@ export const parserRuleSet = ruleSet => {
const map = dispatchMap(ruleSet)

/** @type {(name: string) => _DispatchRule} */
const dispatched = name => /** @type {_DispatchRule} */ (map[name])
const dispatched = name => assertNotNullish(map[name])

// The matcher as an explicit-stack machine: each iteration either starts the
// current task (dispatching one rule and pushing a frame for the rules chain
Expand Down
11 changes: 5 additions & 6 deletions fjs/cas/evo/module.f.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,9 @@
* @import { Effect, Operation } from '../../effects/types.ts'
* @import { Key, MemOp } from '../../effects/memory/types.ts'
* @import { Cas } from '../types.ts'
* @import { Ok, Result } from '../../types/result/types.ts'
* @import { Result } from '../../types/result/types.ts'
* @import { Vec } from '../../types/bit_vec/types.ts'
* @import { IoResult } from '../../effects/node/types.ts'
* @import { List } from '../../effects/list/types.ts'
* @import { LockField, LockMap, Revision } from '../../media/revision/types.ts'
* @import { Hash, Subject, RevisionData, SubjectState, Cache, Evo } from './types.ts'
*/
Expand All @@ -67,7 +66,8 @@ import { isNotFound } from '../../effects/node/module.f.mjs'
import { decodeText, encodeText, dialect, checkReferences, isHash } from '../../media/revision/module.f.mjs'

/** A cache with no known subjects yet — the starting point for {@link buildCache}. */
export const emptyCache = /** @type {Cache} */ ({ bySubject: {} })
/** @type {Cache} */
export const emptyCache = { bySubject: {} }

/** @type {SubjectState} */
const emptySubjectState = { hashes: [], parents: [], archived: [] }
Expand Down Expand Up @@ -291,8 +291,7 @@ const resolveParents = cas => parents => {
return mapStep(
resolveParent(cas)(parentRef),
(/** @type {Result<Revision, string>} */ parentResult) =>
/** @type {Result<readonly Revision[], string>} */
(parentResult[0] === 'error' ? parentResult : ok([...acc[1], parentResult[1]])))
parentResult[0] === 'error' ? parentResult : ok([...acc[1], parentResult[1]]))
})
}

Expand Down Expand Up @@ -461,7 +460,7 @@ export const addRevision = cas => cacheKey => input =>
return pure(error('revision too large to encode'))
}
return step(
cas.write(nonEmpty(ok(bytes), /** @type {List<never, Ok<Vec>>} */ (elEmpty()))),
cas.write(nonEmpty(ok(bytes), elEmpty())),
(/** @type {IoResult<Vec>} */ writeResult) => {
if (writeResult[0] === 'error') {
return /** @type {Effect<MemOp, Result<Hash, string>>} */ (pure(error('failed to write revision to CAS')))
Expand Down
21 changes: 10 additions & 11 deletions fjs/cas/evo/proof.f.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
* @import { Cas } from '../types.ts'
* @import { Vec } from '../../types/bit_vec/types.ts'
* @import { Ok } from '../../types/result/types.ts'
* @import { IoResult } from '../../effects/node/types.ts'
* @import { List } from '../../effects/list/types.ts'
* @import { RevisionData } from './types.ts'
*/
Expand Down Expand Up @@ -43,7 +42,7 @@ const writeFailingCas = {
// large for `collectRead` to buffer looks like to a caller.
/** @type {Cas<never>} */
const readFailingCas = {
read: () => nonEmpty(/** @type {IoResult<Vec>} */ (error('boom')), elEmpty()),
read: () => nonEmpty(error('boom'), elEmpty()),
write: () => pure(error('write not supported')),
list: () => pure([]),
}
Expand All @@ -57,8 +56,8 @@ const fixedCas = entries => ({
read: hash => {
const found = entries.find(([h]) => vecToCBase32(h) === vecToCBase32(hash))
return found === undefined
? nonEmpty(/** @type {IoResult<Vec>} */ (error('not found')), elEmpty())
: nonEmpty(/** @type {IoResult<Vec>} */ (ok(found[1])), elEmpty())
? nonEmpty(error('not found'), elEmpty())
: nonEmpty(ok(found[1]), elEmpty())
},
write: () => pure(error('write not supported')),
list: () => pure(entries.map(([h]) => h)),
Expand All @@ -73,7 +72,7 @@ export const proof = {
buildCacheSkipsNonRevisionBlob: () => {
const c = fileCas(sha256)(home)
const content = vec8(0x41n) // 'A' — valid UTF-8, not revision JSON
const [state1] = virtual(emptyState)(c.write(nonEmpty(ok(content), /** @type {List<never, Ok<Vec>>} */ (elEmpty()))))
const [state1] = virtual(emptyState)(c.write(nonEmpty(ok(content), /** @satisfies {List<never, Ok<Vec>>} */ (elEmpty()))))
const [, cache] = virtual(state1)(buildCache(c))
assertEq(Object.keys(cache.bySubject).length, 0)
},
Expand All @@ -85,15 +84,15 @@ export const proof = {
decodeRevisionBlobNonUtf8IsNull: () => {
const c = fileCas(sha256)(home)
const oddVec = vec(5n)(0b10101n) // not a whole number of bytes
const [state1, w] = virtual(emptyState)(c.write(nonEmpty(ok(oddVec), /** @type {List<never, Ok<Vec>>} */ (elEmpty()))))
const [state1, w] = virtual(emptyState)(c.write(nonEmpty(ok(oddVec), /** @satisfies {List<never, Ok<Vec>>} */ (elEmpty()))))
assert(w[0] === 'ok', ['expected write ok', w])
const [, revision] = virtual(state1)(decodeRevisionBlob(c)(w[1]))
assertEq(revision, null)
},
decodeRevisionBlobInvalidJsonIsNull: () => {
const c = fileCas(sha256)(home)
const content = vec8(0x7bn) // '{' alone: valid UTF-8, not parseable JSON
const [state1, w] = virtual(emptyState)(c.write(nonEmpty(ok(content), /** @type {List<never, Ok<Vec>>} */ (elEmpty()))))
const [state1, w] = virtual(emptyState)(c.write(nonEmpty(ok(content), /** @satisfies {List<never, Ok<Vec>>} */ (elEmpty()))))
assert(w[0] === 'ok', ['expected write ok', w])
const [, revision] = virtual(state1)(decodeRevisionBlob(c)(w[1]))
assertEq(revision, null)
Expand All @@ -104,7 +103,7 @@ export const proof = {
const text = `{"dialect":"${revisionDialect}","subject":"${subjectHash}","parents":[],"snapshot":"${subjectHash}","generation":0}`
const bytes = tryUtf8(text)
assert(bytes !== null, 'expected the sample revision text to encode as UTF-8')
const [state1, w] = virtual(emptyState)(c.write(nonEmpty(ok(bytes), /** @type {List<never, Ok<Vec>>} */ (elEmpty()))))
const [state1, w] = virtual(emptyState)(c.write(nonEmpty(ok(bytes), /** @satisfies {List<never, Ok<Vec>>} */ (elEmpty()))))
assert(w[0] === 'ok', ['expected write ok', w])
const [, revision] = virtual(state1)(decodeRevisionBlob(c)(w[1]))
assert(revision !== null, 'expected a decoded revision')
Expand All @@ -120,7 +119,7 @@ export const proof = {
const text = `{"dialect":"${revisionDialect}","subject":"${subjectHash}","parents":[],"snapshot":"${subjectHash}","generation":0}`
const bytes = tryUtf8(text)
assert(bytes !== null, 'expected the sample revision text to encode as UTF-8')
const [state1, w] = virtual(emptyState)(fileCas(sha256)(home).write(nonEmpty(ok(bytes), /** @type {List<never, Ok<Vec>>} */ (elEmpty()))))
const [state1, w] = virtual(emptyState)(fileCas(sha256)(home).write(nonEmpty(ok(bytes), /** @satisfies {List<never, Ok<Vec>>} */ (elEmpty()))))
assert(w[0] === 'ok', ['expected write ok', w])
const [, cache] = virtual(state1)(buildCache(c))
assertEq(cache.bySubject[subjectHash]?.hashes.length, 1)
Expand Down Expand Up @@ -582,7 +581,7 @@ export const proof = {
const [state0, cacheKey] = virtual(emptyState)(initEvo(c))
const e = evo(c)(cacheKey)
const content = vec8(0x41n) // 'A' — valid UTF-8, not revision JSON
const [state1, w] = virtual(state0)(c.write(nonEmpty(ok(content), /** @type {List<never, Ok<Vec>>} */ (elEmpty()))))
const [state1, w] = virtual(state0)(c.write(nonEmpty(ok(content), /** @satisfies {List<never, Ok<Vec>>} */ (elEmpty()))))
assert(w[0] === 'ok', ['expected write ok', w])
const [, result] = virtual(state1)(e.revision(vecToCBase32(w[1])))
assertEq(result[0], 'error')
Expand All @@ -606,7 +605,7 @@ export const proof = {
const text = `{"dialect":"${revisionDialect}","subject":"doc","parents":["${parentAlias}"],"snapshot":"${snapshotAlias}","generation":1}`
const bytes = tryUtf8(text)
assert(bytes !== null, 'expected the sample revision text to encode as UTF-8')
const [state1, w] = virtual(state0)(c.write(nonEmpty(ok(bytes), /** @type {List<never, Ok<Vec>>} */ (elEmpty()))))
const [state1, w] = virtual(state0)(c.write(nonEmpty(ok(bytes), /** @satisfies {List<never, Ok<Vec>>} */ (elEmpty()))))
assert(w[0] === 'ok', ['expected write ok', w])
const [, result] = virtual(state1)(e.revision(vecToCBase32(w[1])))
assert(result[0] === 'ok', ['expected revision ok', result])
Expand Down
6 changes: 3 additions & 3 deletions fjs/cas/module.f.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ export const fileCas = sha2 => path => {
const loop = offset =>
step(
readBytes(p, offset, chunkBytes),
/** @type {(result: IoResult<Vec>) => List<FileCasOperation, IoResult<Vec>>} */ (result) => {
(result) => {
const [t, v] = result
// A missing shard or read error is an explicit error item, never EOF.
if (t === 'error') {
Expand All @@ -271,7 +271,7 @@ export const fileCas = sha2 => path => {
// genuine storage error and is surfaced, not masked as "no hashes".
step(access(storePrefix), a => {
if (a[0] === 'error') {
if (isNotFound(a[1])) { return pure(/** @type {readonly Vec[]} */ ([])) }
if (isNotFound(a[1])) { return pure([]) }
throw a[1]
}
return mapStep(
Expand Down Expand Up @@ -306,7 +306,7 @@ const streamFile = filePath => {
const loop = offset =>
step(
readBytes(filePath, offset, chunkBytes),
/** @type {(result: IoResult<Vec>) => List<ReadBytes, IoResult<Vec>>} */ (result) => {
(result) => {
if (result[0] === 'error') {
return nonEmpty(result, elEmpty())
}
Expand Down
Loading
Loading