fez-lisp
Version:
Lisp interpreted & compiled to JavaScript
1,445 lines (1,430 loc) • 91.4 kB
JavaScript
import {
APPLY,
ATOM,
FALSE,
KEYWORDS,
MUTATION_SUFFIX,
MUTATORS_SET,
PLACEHOLDER,
PREDICATE_SUFFIX,
PREDICATES_OUTPUT_SET,
SPECIAL_FORMS_SET,
STATIC_TYPES,
STATIC_TYPES_SET,
TRUE,
TYPE,
VALUE,
WORD
} from './keywords.js'
import { isLeaf } from './parser.js'
import {
SPECIAL_FORM_TYPES,
toTypeNames,
ARG_COUNT,
VARIADIC,
STATS,
ARGUMENTS,
UNKNOWN,
RETURNS,
SCOPE_NAME,
TYPE_PROP,
SIGNATURE,
MAX_RETRY_DEFINITION,
MAX_ARGUMENT_RETRY,
COLLECTION,
ANY,
formatType,
ANONYMOUS_FUNCTION_TYPE_PREFIX,
validateLambda,
NIL,
TRUE_WORD,
FALSE_WORD,
BOOLEAN_SUBTYPE,
formatSubType,
BOOLEAN,
IS_ARGUMENT,
NUMBER,
NUMBER_SUBTYPE,
SubType,
GET_ARRAY_INFERENCE_SET,
GENERIC,
TYPE_NAME,
RETURN_NAME
} from './types.js'
import {
Brr,
getSuffix,
hasApplyLambdaBlock,
hasBlock,
log,
logExp,
stringifyArgs,
wrapInApplyLambda,
wrapInArray,
wrapInBlock
} from './utils.js'
export const identity = (name) => [
[0, 'let'],
[1, name],
[
[0, 'lambda'],
[1, 'x'],
[1, 'x']
]
]
export const typeSetDefaultFunction = (Types, name, env, exp) =>
Types.set(withScope(name, env), () => formatType(name, env))
const returnType = (rest) => {
const body = rest.at(-1)
const rem = hasBlock(body) ? body.at(-1) : body
return isLeaf(rem) ? rem : rem[0]
}
const drillReturnType = (rest, condition) => {
const body = rest.at(-1)
const rem = hasBlock(body) ? body.at(-1) : body
const returns = isLeaf(rem) ? rem : rem[0]
return condition(returns)
? drillReturnType(rem.at(-1), condition)
: [returns, rem]
}
const deepLambdaReturn = (rest, condition) => {
const body = rest.at(-1)
const rem = hasBlock(body) ? body.at(-1) : body
return condition(rem) ? rem : deepLambdaReturn(rem, condition)
}
export const isUnknownNotAnyType = (stats) =>
stats && !isAnyType(stats) && isUnknownType(stats)
export const isUnknownNotAnyReturn = (stats) =>
stats && !isAnyReturn(stats) && isUnknownReturn(stats)
export const castType = (stats, type) => {
return (
(stats[TYPE_PROP][0] = type[RETURNS][0]),
type[RETURNS][1] && (stats[TYPE_PROP][1] = type[RETURNS][1])
)
}
export const castReturn = (stats, type) => {
return (
(stats[RETURNS][0] = type[RETURNS][0]),
type[RETURNS][1] && (stats[RETURNS][1] = type[RETURNS][1])
)
}
export const isGenericReturn = (stats) =>
stats[RETURNS].length === 3 && stats[RETURNS][2][0] !== -1
export const isGenericType = (stats) =>
stats[TYPE_PROP].length === 3 && stats[TYPE_PROP][2][0] !== -1
export const isGenericProp = (stats, prop) =>
stats[prop].length === 3 && stats[prop][2][0] !== -1
export const isTypeAbstraction = (stats) => stats[TYPE_PROP] === APPLY
export const setPropToAtom = (stats, prop) => {
return (
(stats[prop][0] === UNKNOWN || stats[prop][0] === ANY) &&
((stats[prop][0] = ATOM), (stats[prop][1] = NUMBER_SUBTYPE()))
)
}
const setReturnToGeneric = (stats, index) => {
stats[RETURNS] = [UNKNOWN, undefined, [index, 1]]
}
export const setPropToPredicate = (stats, prop) => {
return (stats[prop][1] = BOOLEAN_SUBTYPE())
}
export const setReturnToPredicate = (stats) => {
return (stats[RETURNS][1] = BOOLEAN_SUBTYPE())
}
export const setTypeToPredicate = (stats) => {
return (stats[RETURNS][1] = BOOLEAN_SUBTYPE())
}
export const setPropToAbstraction = (stats, prop) => {
if (stats[prop][0] === UNKNOWN || stats[prop][0] === ANY) {
stats[prop][0] = APPLY
// since we don't know how many they are we just set the args to variadic
if (!stats[ARG_COUNT]) stats[ARG_COUNT] = VARIADIC
}
return APPLY
}
export const setPropToCollection = (stats, prop) => {
return (
(stats[prop][0] === UNKNOWN || stats[prop][0] === ANY) &&
(stats[prop][0] = COLLECTION)
)
}
export const setProp = (stats, prop, value) => {
if (
(stats[prop][0] === UNKNOWN || stats[prop][0] === ANY) &&
value[prop][0] !== UNKNOWN &&
value[prop][0] !== ANY
) {
stats[prop][0] = value[prop][0]
if (value[prop][1]) stats[prop][1] = value[prop][1]
}
}
export const setPropToReturn = (stats, prop, value) => {
if (
(stats[prop][0] === UNKNOWN || stats[prop][0] === ANY) &&
value[RETURNS][0] !== UNKNOWN &&
value[RETURNS][0] !== ANY
) {
stats[prop][0] = value[RETURNS][0]
if (value[RETURNS][1]) stats[prop][1] = value[RETURNS][1]
}
}
export const setPropToReturnRef = (stats, prop, value) => {
if (
stats[prop] &&
(stats[prop][0] === UNKNOWN || stats[prop][0] === ANY) &&
value[RETURNS][0] !== UNKNOWN &&
value[RETURNS][0] !== ANY
)
stats[prop] = value[RETURNS]
}
export const setPropToType = (stats, prop, value) => {
if (
stats[prop] &&
(stats[prop][0] === UNKNOWN || stats[prop][0] === ANY) &&
value[TYPE_PROP][0] !== ANY
) {
stats[prop][0] = value[TYPE_PROP][0]
if (value[TYPE_PROP][1]) stats[prop][1] = value[TYPE_PROP][1]
}
}
export const setPropToTypeRef = (stats, prop, value) => {
if (stats[prop] && (stats[prop][0] === UNKNOWN || stats[prop][0] === ANY))
stats[prop] = value[TYPE_PROP]
}
export const setReturnToAtom = (stats) => {
if (isUnknownReturn(stats) || isAnyReturn(stats)) {
stats[RETURNS][0] = ATOM
stats[RETURNS][1] = NUMBER_SUBTYPE()
}
}
export const setTypeToAtom = (stats) =>
(isUnknownType(stats) || isAnyType(stats)) &&
((stats[TYPE_PROP][0] = ATOM), (stats[TYPE_PROP][1] = NUMBER_SUBTYPE()))
export const setTypeToCollection = (stats) =>
(isUnknownType(stats) || isAnyType(stats)) &&
(stats[TYPE_PROP][0] = COLLECTION)
export const setTypeToAbstraction = (stats) =>
(isUnknownType(stats) || isAnyType(stats)) && (stats[TYPE_PROP][0] = APPLY)
export const setReturnToAbstraction = (stats) =>
isUnknownReturn(stats) && (stats[RETURNS][0] = APPLY)
export const setTypeRef = (stats, value) =>
(isUnknownType(stats) || isAnyType(stats)) &&
(stats[TYPE_PROP] = value[TYPE_PROP])
export const setReturnRef = (stats, value) => {
// to prevent overiding the type of sepcial form or if the type of the function is any
// any could also be deliberate as some functions like array:get and array;first
// are sort of special forms too
// In general anything that has type of any should not be infered
if (SPECIAL_FORMS_SET.has(value[SIGNATURE]) || isAnyReturn(value)) {
return setReturn(stats, value)
}
return isUnknownReturn(stats) && (stats[RETURNS] = value[RETURNS])
}
export const setReturnToTypeRef = (stats, value) => {
return (
(isUnknownReturn(stats) || isAnyReturn(stats)) &&
(stats[RETURNS] = value[TYPE_PROP])
)
}
export const setStats = (a, b) => (a[STATS] = b[STATS])
export const cloneStats = (name, x) => ({
[STATS]: {
...x[STATS],
[SIGNATURE]: name
}
})
export const setStatsRef = (a, b) => (a[STATS] = b[STATS])
export const setTypeToReturnRef = (stats, value) => {
// To prevent getters overwritting the array subtype
// Change main type if unknown
if (isUnknownType(stats) || isAnyType(stats))
if (!isUnknownReturn(value)) stats[TYPE_PROP][0] = value[RETURNS][0]
// TODO: Figure out what we do if generic things get inferred
// change sub type if it doesn't have
if (!hasSubType(stats) || getSubType(stats).has(UNKNOWN))
if (hasSubReturn(value)) stats[TYPE_PROP][1] = value[RETURNS][1]
}
export const setPropRef = (stats, prop, value) => {
return (
(stats[prop][0] === UNKNOWN || stats[prop][0] === ANY) &&
(stats[prop] = value[prop])
)
}
export const setReturn = (stats, value) => {
return (
isUnknownReturn(stats) &&
!isUnknownReturn(value) &&
((stats[RETURNS][0] = value[RETURNS][0]),
value[RETURNS][1] && (stats[RETURNS][1] = value[RETURNS][1]))
)
}
export const setType = (stats, value) =>
(isUnknownType(stats) || isAnyType(stats)) &&
!isUnknownType(value) &&
((stats[TYPE_PROP][0] = value[TYPE_PROP][0]),
value[TYPE_PROP][1] && (stats[TYPE_PROP][1] = value[TYPE_PROP][1]))
export const setSubType = (stats, value) =>
// makes no sense to protect this for now
hasSubType(value) && (stats[TYPE_PROP][1] = value[TYPE_PROP][1])
export const setPropToSubType = (stats, prop, value) =>
// makes no sense to protect this for now
hasSubType(value) && (stats[prop][1] = value[TYPE_PROP][1])
export const setPropToSubReturn = (stats, prop, value) =>
// makes no sense to protect this for now
hasSubReturn(value) && (stats[prop][1] = value[RETURNS][1])
export const setTypeToReturn = (stats, value) =>
(isUnknownType(stats) || isAnyType(stats)) &&
!isUnknownReturn(value) &&
((stats[TYPE_PROP][0] = value[RETURNS][0]),
value[RETURNS][1] && (stats[TYPE_PROP][1] = value[RETURNS][1]))
export const setReturnToType = (stats, value) =>
(isUnknownReturn(stats) || isAnyReturn(stats)) &&
!isUnknownType(value) &&
((stats[RETURNS][0] = value[TYPE_PROP][0]),
value[TYPE_PROP][1] && (stats[RETURNS][1] = value[TYPE_PROP][1]))
export const isAnyReturn = (stats) => stats && stats[RETURNS][0] === ANY
export const isAnyType = (stats) => stats && stats[TYPE_PROP][0] === ANY
export const isUnknownType = (stats) => stats && stats[TYPE_PROP][0] === UNKNOWN
export const isUnknownProp = (stats, prop) =>
stats && stats[prop][0] === UNKNOWN
export const isSubType = (subtype) => subtype instanceof SubType
export const isSubTypeUknown = (subtype) =>
subtype.size === 1 && subtype.has(UNKNOWN)
export const matchSub = (a, b) =>
a.has(UNKNOWN) ||
b.has(UNKNOWN) ||
a.has(ANY) ||
b.has(ANY) ||
a.isMatching(b)
export const isUnknownReturn = (stats) => stats[RETURNS][0] === UNKNOWN
export const getType = (stats) => stats && stats[TYPE_PROP][0]
export const getTypes = (stats) => stats && stats[TYPE_PROP]
export const getReturn = (stats) => stats && stats[RETURNS][0]
export const getReturns = (stats) => stats && stats[RETURNS]
export const getSubType = (stats) => stats && stats[TYPE_PROP][1]
export const hasSubType = (stats) => stats && isSubType(stats[TYPE_PROP][1])
export const getSubReturn = (stats) => stats && stats[RETURNS][1]
export const hasSubReturn = (stats) => stats && isSubType(stats[RETURNS][1])
export const isUnknownSubReturn = (stats) =>
!hasSubReturn(stats) ||
(stats &&
stats[RETURNS] &&
stats[RETURNS][1] &&
isSubTypeUknown(stats[RETURNS][1]))
export const isUnknownSubType = (stats) =>
hasSubType(stats) &&
stats &&
stats[TYPE_PROP] &&
stats[TYPE_PROP][1] &&
isSubTypeUknown(stats[TYPE_PROP][1])
export const isAtomType = (stats) =>
isAnyType(stats) || stats[TYPE_PROP][0] === ATOM
export const isAtomReturn = (stats) =>
isAnyType(stats) || stats[RETURNS][0] === ATOM
export const equalTypes = (a, b) => {
const isAnyAny = isAnyType(a) || isAnyType(b)
if (isAnyAny) return true
const isSameType = a[TYPE_PROP][0] === b[TYPE_PROP][0]
if (!isSameType) return false
return true
}
const isRedefinedInLambda = (env, name, exp) => {
if (exp.slice(1, -1).some((x) => x[VALUE] === name)) return true
else if (
exp
.at(-1)
.some(
(x) =>
!isLeaf(x) &&
x[0][TYPE] === APPLY &&
x[0][VALUE] === KEYWORDS.DEFINE_VARIABLE &&
x[1][VALUE] === name
)
)
return true
else return false
}
const getGenericPropNest = (ref, prop) =>
ref[STATS][prop][0] === COLLECTION
? isSubType(ref[STATS][prop][1])
? ref[STATS][prop][1].nestedLevels()
: 0
: 0
const getGenericProp = (ref, rem, prop) => {
const [index, multiplier] = ref[STATS][prop][2]
const generic = rem.slice(1)[index]
const nestGeneric = getGenericPropNest(ref, prop)
return [
generic,
isLeaf(generic) ? generic : generic[0],
nestGeneric,
multiplier
]
}
const getGenericReturn = (ref, rem) => getGenericProp(ref, rem, RETURNS)
const getGenericType = (ref, rem) => getGenericProp(ref, rem, TYPE_PROP)
const resolveGenericNest = (ref, prop, nestGeneric, multiplier) => {
const hasSubType = isSubType(ref[STATS][prop][1])
const T = hasSubType ? ref[STATS][prop][1].types : [ref[STATS][prop][0]]
if (multiplier === -1) {
if (nestGeneric === 0) {
if (T.at(-1) === NUMBER || T.at(-1) === BOOLEAN) {
if (hasSubType) {
if (ref[STATS][prop][1].types.length === 1) ref[STATS][prop][0] = ATOM
else {
ref[STATS][prop][1] = new SubType(
ref[STATS][prop][1].types.slice(1)
)
ref[STATS][prop][0] = COLLECTION
ref[STATS][prop].length = 2
}
} else ref[STATS][prop][0] = ATOM
}
} else {
if (T.length - nestGeneric - 1) {
if (hasSubType)
for (let i = 0; i < nestGeneric + 1; ++i)
ref[STATS][prop][1].types.shift()
ref[STATS][prop].length = 2
} else {
if (T.at(-1) === NUMBER || T.at(-1) === BOOLEAN) {
ref[STATS][prop][0] = ATOM
ref[STATS][prop][1] = new SubType([T.at(-1)])
ref[STATS][prop].length = 2
} else {
ref[STATS][prop][0] = APPLY
ref[STATS][prop].length = 1
}
}
}
} else if (multiplier === 1) {
const st = new SubType([])
for (let i = 0; i < nestGeneric; ++i) st.add(COLLECTION)
if (ref[STATS][prop][0] === COLLECTION) st.add(COLLECTION)
st.add(...T)
ref[STATS][prop][0] = COLLECTION
ref[STATS][prop][1] = st
}
}
export const equalReturns = (a, b) =>
isAnyReturn(a) || isAnyReturn(b) || a[RETURNS][0] === b[RETURNS][0]
export const equalsTypeWithReturn = (a, b) =>
isAnyType(a) || isAnyReturn(b) || a[TYPE_PROP][0] === b[RETURNS][0]
const IsPredicate = (leaf) =>
(leaf[TYPE] === ATOM && (leaf[VALUE] === TRUE || leaf[VALUE] === FALSE)) ||
(leaf[TYPE] === WORD &&
(leaf[VALUE] === TRUE_WORD ||
leaf[VALUE] === FALSE_WORD ||
leaf[VALUE] === NIL ||
getSuffix(leaf[VALUE]) === PREDICATE_SUFFIX)) ||
(leaf[TYPE] === APPLY &&
(PREDICATES_OUTPUT_SET.has(leaf[VALUE]) ||
getSuffix(leaf[VALUE]) === PREDICATE_SUFFIX))
const equalSubTypes = (a, b) => {
return (
!hasSubType(a) || !hasSubType(b) || matchSub(getSubType(a), getSubType(b))
)
}
const equalSubReturns = (a, b) => {
return (
!hasSubReturn(a) ||
!hasSubReturn(b) ||
matchSub(getSubReturn(a), getSubReturn(b))
)
}
const equalSubTypesWithSubReturn = (a, b) => {
return (
!hasSubType(a) ||
!hasSubReturn(b) ||
matchSub(getSubType(a), getSubReturn(b))
)
}
const isAtomABoolean = (atom) => atom === TRUE || atom === FALSE
const checkPredicateName = (exp, rest) => {
if (getSuffix(rest[0][VALUE]) === PREDICATE_SUFFIX) {
const last = rest.at(-1)
if (last[TYPE] !== APPLY && isLeaf(last) && !IsPredicate(last)) {
if (!IsPredicate(last))
throw new TypeError(
`Assigning predicate (ending in ?) variable (${
rest[0][VALUE]
}) to an (${
STATIC_TYPES.ATOM
}) that is not (or ${TRUE} ${FALSE}) or to another variable which is not a predicate (also ending in ?) or to a variable that is not (or true false nil) (${stringifyArgs(
exp
)}) (check #100)`
)
} else if (last[0][0] === APPLY) {
const application = last[0]
if (application[VALUE] === KEYWORDS.CALL_FUNCTION)
return checkPredicateName(exp, [rest[0], last.at(-1)])
else if (application[VALUE] !== KEYWORDS.IF && !IsPredicate(application))
throw new TypeError(
`Assigning predicate (ending in ?) variable (${
application[VALUE]
}) to another variable which is not a predicate (also ending in ?) or to a variable that is not (or true false nil) (${stringifyArgs(
exp
)}) (check #101)`
)
else if (application[VALUE] === KEYWORDS.IF) {
return (
checkPredicateName(exp, [rest[0], last[2]]) &&
checkPredicateName(exp, [rest[0], last[3]])
)
}
}
return true
}
return false
}
const checkPredicateNameDeep = (name, exp, rest, returns) => {
if (returns[VALUE] === KEYWORDS.CALL_FUNCTION) {
const fn = rest.at(-1).at(-1).at(-1)
return checkPredicateName(exp, [
[WORD, name],
isLeaf(fn)
? fn // when apply is a word (let x? (lambda (apply [] array:empty!)))
: drillReturnType(fn, (r) => r[VALUE] === KEYWORDS.CALL_FUNCTION) // when apply is an anonymous lambda // (let fn? (lambda x (apply x (lambda x (array:empty! [])))))
])
}
return checkPredicateName(exp, [[WORD, name], returns])
}
const fillUnknownArgs = (n) =>
Array.from({ length: n })
.fill(null)
.map(() => ({
[STATS]: {
retried: 0,
[SIGNATURE]: PLACEHOLDER,
[TYPE_PROP]: [UNKNOWN],
[RETURNS]: [UNKNOWN],
[ARGUMENTS]: [],
[ARG_COUNT]: 0
}
}))
const getScopeNames = (scope) => {
const scopeNames = []
let current = scope
while (current) {
if (current[SCOPE_NAME]) {
scopeNames.push(current[SCOPE_NAME])
}
current = Object.getPrototypeOf(current)
}
return scopeNames.reverse()
}
export const withScope = (name, scope) => {
const chain = getScopeNames(scope)
return `${chain.join(' ')} ${name}`
}
const retry = (stats, ctx, stack, cb, method = 'prepend') => {
if (
(isUnknownNotAnyType(stats) || isUnknownNotAnyReturn(stats)) &&
stats.retried < MAX_RETRY_DEFINITION
) {
stats.retried += 1
stagger(stack, method, ctx, cb)
}
}
const once = (stats, ctx, stack, cb, method = 'prepend') => {
if (
(isUnknownNotAnyType(stats) || isUnknownNotAnyReturn(stats)) &&
!stats.tried
) {
stats.tried = true
stagger(stack, method, ctx, cb)
}
}
const doOnce = (stats, ctx, stack, cb, method = 'prepend') => {
if (!stats.tried) {
stats.tried = true
stagger(stack, method, ctx, cb)
}
}
const retryArgs = (stats, ctx, stack, cb) => {
if (stats.counter < MAX_ARGUMENT_RETRY) {
stats.counter++
stagger(stack, 'prepend', ctx, cb)
}
}
const IfApplyBranch = ({
leaf,
branch,
re,
prop,
ref,
env,
stack,
exp,
check
}) => {
switch (leaf[VALUE]) {
case KEYWORDS.IF:
return ifExpression({
re: re.slice(2),
env,
ref,
prop,
stack,
exp,
check
})
case KEYWORDS.CREATE_ARRAY:
setPropToReturnRef(ref[STATS], prop, initArrayType({ rem: re, env }))
break
case KEYWORDS.ANONYMOUS_FUNCTION:
setPropToAbstraction(ref[STATS], prop)
ref[STATS][RETURNS] = [UNKNOWN]
ref[STATS][ARG_COUNT] = re.length - 2
ref[STATS][ARGUMENTS] = fillUnknownArgs(re.length - 2)
break
case KEYWORDS.CALL_FUNCTION:
if (re.at(-1)[TYPE] === WORD) {
const name = re.at(-1)[VALUE]
if (env[name] && re.at(-1)[VALUE] !== ref[STATS][SIGNATURE])
setPropToReturnRef(ref[STATS], prop, env[name][STATS])
} else {
const returns = returnType(re.at(-1))
if (env[returns[VALUE]] && returns[VALUE] !== ref[STATS][SIGNATURE])
IfApplyBranch({
branch: env[returns[VALUE]],
ref,
env,
prop,
leaf: re.at(-1),
re: re.at(-1).slice(2),
stack,
exp
})
}
break
default:
if (
getType(ref[STATS]) === UNKNOWN &&
getReturn(branch[STATS]) === UNKNOWN
) {
retry(ref[STATS], exp, stack, () =>
setPropToReturn(ref[STATS], prop, branch[STATS])
)
return true
}
return setPropToReturn(ref[STATS], prop, branch[STATS])
}
}
const inferIf = (branch, re, env, name) => {
switch (branch[TYPE]) {
case ATOM:
return [[ATOM, NUMBER_SUBTYPE()]]
case WORD:
if (branch[VALUE] === NIL) return [[UNKNOWN]]
if (env[branch[VALUE]]) return [env[branch[VALUE]][STATS][TYPE_PROP]]
break
case APPLY:
if (branch[VALUE] !== name && env[branch[VALUE]])
if (branch[VALUE] === KEYWORDS.CREATE_ARRAY) {
return [initArrayType({ rem: re, env })[RETURNS]]
} else if (branch[VALUE] === KEYWORDS.IF) {
const conc = isLeaf(re[2]) ? re[2] : re[2][0]
const alt = isLeaf(re[3]) ? re[3] : re[3][0]
return inferIf(conc, re[2], env, name).concat(
inferIf(alt, re[3], env, name)
)
} else return [env[branch[VALUE]][STATS][RETURNS]]
break
}
return []
}
const validateIfMatchingBranches = (
concequent,
alternative,
env,
exp,
re,
name
) => {
const A = inferIf(concequent, re[0], env, name).filter(
(x) => x[0] !== UNKNOWN && x[0] !== ANY
)
const B = inferIf(alternative, re[1], env, name).filter(
(x) => x[0] !== UNKNOWN && x[0] !== ANY
)
if (!A.length || !B.length) return
const isSame = (A, B) => {
if (
A[0] !== B[0] ||
(isSubType(A[1]) && isSubType(B[1]) && !A[1].isMatching(B[1]))
) {
throw new TypeError(
`(if) needs to have matching concequent and alternative branches but got (${formatSubType(
A
)}) and (${formatSubType(B)}) (${stringifyArgs(exp)}) (check #1005)`
)
}
}
A.forEach((x) => isSame(A[0], x))
B.forEach((x) => isSame(B[0], x))
const FA = A[0]
const FB = B[0]
if (
FA[0] !== FB[0] ||
(isSubType(FA[1]) && isSubType(FB[1]) && !FA[1].isMatching(FB[1]))
) {
throw new TypeError(
`(if) needs to have matching concequent and alternative branches but got (${formatSubType(
FA
)}) and (${formatSubType(FB)}) (${stringifyArgs(exp)}) (check #1005)`
)
}
}
const ifExpression = ({ re, env, ref, prop, stack, exp, check }) => {
const conc = isLeaf(re[0]) ? re[0] : re[0][0]
const alt = isLeaf(re[1]) ? re[1] : re[1][0]
const concequent = env[conc[VALUE]]
const alternative = env[alt[VALUE]]
if (re[0][TYPE] === ATOM || re[1][TYPE] === ATOM) {
setPropToAtom(ref[STATS], prop)
}
// TODO check that both branches are predicates if one is
else {
// TODO make this more simple - it's so many different things just because types are functions or not
// WHY not consider making return types for everything
if (concequent)
if (conc[TYPE] === WORD) {
setPropToTypeRef(ref[STATS], prop, concequent[STATS])
} else if (
conc[TYPE] === APPLY &&
getType(concequent[STATS]) === APPLY &&
// Making sure the recursive function don't look for their own return type
concequent[STATS][SIGNATURE] !== ref[STATS][SIGNATURE]
)
IfApplyBranch({
leaf: conc,
branch: concequent,
re: re[0],
prop,
env,
ref,
stack,
exp,
check
})
if (alternative)
if (alt[TYPE] === WORD) {
setPropToTypeRef(ref[STATS], prop, alternative[STATS])
} else if (
alt[TYPE] === APPLY &&
getType(alternative[STATS]) === APPLY &&
// Making sure the recursive function don't look for their own return type
alternative[STATS][SIGNATURE] !== ref[STATS][SIGNATURE]
)
IfApplyBranch({
leaf: alt,
branch: alternative,
re: re[1],
prop,
env,
ref,
stack,
exp,
check
})
}
validateIfMatchingBranches(conc, alt, env, exp, re, ref[STATS][SIGNATURE])
}
const resolveCondition = ({ rem, name, env, exp, prop, stack, check }) => {
const ret = rem[0]
const re = rem.slice(2)
const ref = env[name]
checkPredicateName(exp, [[WORD, name], isLeaf(re[0]) ? re[0] : re[0][0]])
checkPredicateName(exp, [[WORD, name], isLeaf(re[1]) ? re[1] : re[1][0]])
switch (ret[VALUE]) {
case KEYWORDS.IF:
ifExpression({ re, env, ref, prop, exp, stack, check })
break
default:
if (env[ret[VALUE]]) setPropRef(ref[STATS], prop, env[ret[VALUE]][STATS])
else
stagger(stack, 'append', exp, () => {
if (env[ret[VALUE]])
setPropRef(ref[STATS], prop, env[ret[VALUE]][STATS])
})
break
}
}
const resolveGetterRec = ([head, tail], env, times = 0) => {
if (GET_ARRAY_INFERENCE_SET.has(head[VALUE])) {
return resolveGetterRec(tail, env, ++times)
} else {
if (Array.isArray(head)) {
tail = head[VALUE]
head = head[TYPE]
}
if (head !== WORD && head !== APPLY) return
const prop = head === WORD ? TYPE_PROP : RETURNS
if (!env[tail] || env[tail][STATS][prop][0] === UNKNOWN) return
const types = isSubType(env[tail][STATS][prop][1])
? env[tail][STATS][prop][1].types
: []
const sub = types.at(-1)
const type =
sub === ATOM || sub === NUMBER || sub === BOOLEAN
? ATOM
: sub === APPLY
? APPLY
: COLLECTION
const len = types.length ? types.length + 1 : times + 1
return [times, len, type, types, tail]
}
}
const resolveGetter = ({ rem, prop, name, env, caller, exp }) => {
const array = isLeaf(rem[1]) ? rem[1] : rem[1][0]
if (!env[array[VALUE]] || !env[name]) return true
switch (array[TYPE]) {
case APPLY:
// TODO: figure out recursively what is the inner type of all nested getters
if (GET_ARRAY_INFERENCE_SET.has(array[VALUE])) {
const rec = resolveGetterRec(rem, env)
if (!rec) return true
const [times, level, type, types] = resolveGetterRec(rem, env)
const isUnknown =
types.at(-1) === UNKNOWN || types.at(-1) === COLLECTION
if (!isUnknown && times >= level)
throw new RangeError(
`(${caller}) is trying to access nested structure at level (${level}) which is deeper than it's (${
times - 1
}) levels at (${stringifyArgs(exp)}) (check #1003)`
)
if (times === level - 1)
setPropToType(env[name][STATS], prop, {
[TYPE_PROP]:
types.length && !isUnknown
? [type, new SubType([types.at(-1)])]
: types.at(-1) === COLLECTION
? [COLLECTION]
: [UNKNOWN]
})
else
setPropToType(env[name][STATS], prop, {
[TYPE_PROP]: types.length
? [COLLECTION, new SubType(types.slice(times))]
: [UNKNOWN]
})
return true
}
if (
getReturn(env[array[VALUE]][STATS]) === UNKNOWN ||
getReturn(env[array[VALUE]][STATS]) === ANY
)
return true
if (getReturn(env[array[VALUE]][STATS]) !== COLLECTION)
throw new TypeError(
`Incorrect type of argument (${0}) for (${caller}). Expected (${formatSubType(
getTypes(env[caller][STATS][ARGUMENTS][0][STATS])
)}) but got (${formatSubType(
getReturns(env[array[VALUE]][STATS])
)}) (${stringifyArgs(exp)}) (check #1001)`
)
if (hasSubReturn(env[array[VALUE]][STATS])) {
const rightSub = getSubReturn(env[array[VALUE]][STATS])
const isAtom = rightSub.has(NUMBER) || rightSub.has(BOOLEAN)
const isCollection = rightSub.has(COLLECTION)
if (isAtom && !isCollection) {
setPropToAtom(env[name][STATS], prop)
setPropToSubReturn(env[name][STATS], prop, env[array[VALUE]][STATS])
} else if (!isAtom && isCollection) {
const [f, ...r] = env[array[VALUE]][STATS][RETURNS][1].types
setPropToReturn(env[name][STATS], prop, {
[RETURNS]: [f, new SubType(r)]
})
} else if (rightSub.has(APPLY))
// TODOD: abstractions go here but what can we do with them
// perhaps show the signature?
setPropToAbstraction(env[name][STATS], prop)
}
break
case WORD:
{
if (
getType(env[array[VALUE]][STATS]) === UNKNOWN ||
getType(env[array[VALUE]][STATS]) === ANY
)
return true
if (getType(env[array[VALUE]][STATS]) !== COLLECTION)
throw new TypeError(
`Incorrect type of argument (${0}) for (${caller}). Expected (${formatSubType(
getTypes(env[caller][STATS][ARGUMENTS][0][STATS])
)}) but got (${formatSubType(
getType(env[array[VALUE]][STATS])
)}) (${stringifyArgs(exp)}) (check #1002)`
)
if (hasSubType(env[array[VALUE]][STATS])) {
const rightSub = getSubType(env[array[VALUE]][STATS])
const isAtom =
rightSub.has(ATOM) || rightSub.has(NUMBER) || rightSub.has(BOOLEAN)
const isCollection = rightSub.has(COLLECTION)
if (isAtom && !isCollection) {
setPropToAtom(env[name][STATS], prop)
setPropToSubType(env[name][STATS], prop, env[array[VALUE]][STATS])
} else if (!isAtom && isCollection) {
const [f, ...r] = env[array[VALUE]][STATS][TYPE_PROP][1].types
setPropToType(env[name][STATS], prop, {
[TYPE_PROP]: [f, new SubType(r)]
})
} else if (rightSub.has(APPLY))
// TODOD: abstractions go here but what can we do with them
// perhaps show the signature?
setPropToAbstraction(env[name][STATS], prop)
}
}
break
}
return true
}
const resolveSetter = (first, rest, env, stack) => {
if (
getSuffix(first[VALUE]) === MUTATION_SUFFIX &&
MUTATORS_SET.has(first[VALUE]) &&
rest[0] &&
isLeaf(rest[0]) &&
rest[0][TYPE] !== ATOM &&
env[rest[0][VALUE]]
) {
const name = rest[0][VALUE]
const current = env[name]
const right = isLeaf(rest.at(-1)) ? rest.at(-1) : rest.at(-1)[0]
const currentSubType = hasSubType(current[STATS])
? getSubType(current[STATS])
: new SubType([UNKNOWN])
switch (right[TYPE]) {
case ATOM:
if (
!currentSubType.has(ANY) &&
!currentSubType.has(UNKNOWN) &&
!currentSubType.has(NUMBER)
)
throw new TypeError(
`Incorrect array type at (${
first[VALUE]
}). ${name} is (${formatSubType(
getTypes(current[STATS])
)}) but insertion is (${formatSubType([
ATOM,
NUMBER_SUBTYPE()
])}) (${stringifyArgs([first, rest])}) (check #199)`
)
current[STATS][TYPE_PROP][1] = NUMBER_SUBTYPE()
break
case WORD:
if (env[right[VALUE]]) {
if (hasSubType(env[right[VALUE]][STATS])) {
if (currentSubType.has(UNKNOWN)) {
if (env[right[VALUE]][STATS][TYPE_PROP][0] === COLLECTION) {
current[STATS][TYPE_PROP][1] = new SubType([
COLLECTION,
...getSubType(env[right[VALUE]][STATS]).types
])
} else
current[STATS][TYPE_PROP][1] = new SubType(
getSubType(env[right[VALUE]][STATS]).types
)
} else if (!equalSubTypes(current[STATS], env[right[VALUE]][STATS]))
throw new TypeError(
`Incorrect array type at (${
first[VALUE]
}). ${name} is (${formatSubType(
getTypes(current[STATS])
)}) but insertion is (${formatSubType(
getTypes(env[right[VALUE]][STATS])
)}) (${stringifyArgs([first, rest])}) (check #198)`
)
// current[STATS][TYPE_PROP][1] = new SubType(
// getSubType(env[right[VALUE]][STATS])
// )
} else
retry(env[right[VALUE]][STATS], [first[VALUE], rest], stack, () =>
resolveSetter(first, rest, env, stack)
)
}
break
case APPLY:
if (env[right[VALUE]]) {
if (right[VALUE] === KEYWORDS.CREATE_ARRAY) {
const inner = initArrayType({
rem: rest.at(-1),
env
})[RETURNS][1]
current[STATS][TYPE_PROP][0] = COLLECTION
if (!inner.has(UNKNOWN)) current[STATS][TYPE_PROP][1] = inner
break
}
if (hasSubReturn(env[right[VALUE]][STATS])) {
if (currentSubType.has(UNKNOWN)) {
if (env[right[VALUE]][STATS][RETURNS][0] === COLLECTION) {
current[STATS][TYPE_PROP][1] = new SubType([
COLLECTION,
...getSubReturn(env[right[VALUE]][STATS]).types
])
} else
current[STATS][TYPE_PROP][1] = new SubType(
getSubReturn(env[right[VALUE]][STATS]).types
)
} else if (
!equalSubTypesWithSubReturn(
current[STATS],
env[right[VALUE]][STATS]
)
)
throw new TypeError(
`Incorrect array type at (${
first[VALUE]
}). ${name} is (${formatSubType(
getTypes(current[STATS])
)}) but insertion is (${formatSubType(
getReturns(env[right[VALUE]][STATS])
)}) (${stringifyArgs([first, rest])}) (check #198)`
)
// current[STATS][TYPE_PROP][1] = new SubType([
// ...getSubReturn(env[right[VALUE]][STATS])
// ])
}
// else
// retry(env[right[VALUE]][STATS], [first[VALUE], rest], stack, () =>
// resolveSetter(first, rest, env, stack)
// )
}
break
}
setTypeToCollection(current[STATS])
// // Retry setting the sub-type if infered it out later
// if (!hasSubType(current[STATS]) || getSubType(current[STATS]).has(UNKNOWN))
// retry(current[STATS], [first[VALUE], rest], stack, () =>
// resolveSetter(first, rest, env, stack)
// )
}
}
const countLevels = (arr, lvl = -1) =>
Array.isArray(arr[0]) ? countLevels(arr[0], ++lvl) : lvl
const initArrayTypeRec = ({ rem, env }) =>
rem.slice(1).map((x) => {
if (isLeaf(x))
if (x[TYPE] === WORD)
if (env[x[VALUE]]) return getTypes(env[x[VALUE]][STATS])
else return [UNKNOWN]
else
return [
x[TYPE],
x[TYPE] === ATOM ? NUMBER_SUBTYPE() : new SubType([UNKNOWN])
]
else if (env[x[0][VALUE]])
if (x.length > 1 && x[0][VALUE] === KEYWORDS.CREATE_ARRAY)
return initArrayTypeRec({ rem: x, env })
else if (
x.length > 1 &&
env[x[0][VALUE]][STATS][RETURNS][0] === COLLECTION
)
return [getReturns(env[x[0][VALUE]][STATS])]
else if (GET_ARRAY_INFERENCE_SET.has(x[0][VALUE])) {
const res = resolveGetterRec(x, env)
if (!res) return [UNKNOWN]
const name = resolveGetterRec(x, env).at(-1)
resolveGetter({
rem: x,
prop: RETURNS,
name,
env,
caller: x[0][VALUE],
exp: rem
})
return getReturns(env[name][STATS])
} else return getReturns(env[x[0][VALUE]][STATS])
else return [UNKNOWN]
})
const initArrayType = ({ rem, env }) => {
const ret = initArrayTypeRec({ rem, env })
const flat = ret.flat(Infinity).filter((x) => !isSubType(x))
const subTypes = ret.flat(Infinity).filter((x) => isSubType(x))
const known = flat.find((x) => x !== ANY && x !== UNKNOWN)
const isCollection = ret.length && Array.isArray(ret[0])
if (
known &&
ret.length &&
!flat.some((x) => known !== x) &&
!subTypes.some((x) => subTypes[0].types[0] !== x.types[0])
) {
if (Array.isArray(ret[0][0])) {
let head = ret[0][0]
ret[0].length = 0
const subT = new SubType([COLLECTION])
ret[0].push(COLLECTION, subT)
while (Array.isArray(head) && !isSubType(head[1])) {
if (head[0] === APPLY) subT.types.push(APPLY)
else subT.add(COLLECTION)
head = head[0]
}
if (Array.isArray(head) && head[1].types.length)
subT.add(head[1].types[0])
}
const [main, sub] = ret[0]
return {
[TYPE_PROP]: [APPLY],
[RETURNS]: [
COLLECTION,
isCollection
? new SubType(isSubType(sub) ? [...sub] : [COLLECTION])
: new SubType(isSubType(sub) ? [...sub] : [main])
]
}
} else
return {
[TYPE_PROP]: [APPLY],
[RETURNS]: [
COLLECTION,
isCollection
? new SubType([
...Array.from({ length: countLevels(ret) }).fill(COLLECTION),
UNKNOWN
])
: new SubType([UNKNOWN])
]
}
}
const resolveReturnType = ({
returns,
rem,
stack,
prop,
exp,
name,
env,
check
}) => {
if (returns[TYPE] === ATOM) setPropToAtom(env[name][STATS], prop)
else if (returns[TYPE] === WORD) {
if (env[returns[VALUE]]) {
if (!isUnknownType(env[returns[VALUE]][STATS]))
setPropToType(env[name][STATS], prop, env[returns[VALUE]][STATS])
else
once(env[name][STATS], exp, stack, () => {
setPropToTypeRef(env[name][STATS], prop, env[returns[VALUE]][STATS])
// if (isUnknownProp(env[name][STATS], prop)) {
// // TODO: DRY
// const index = env[name][STATS][ARGUMENTS]
// ? env[name][STATS][ARGUMENTS].findIndex(
// (x) => x[STATS][SIGNATURE] === returns[VALUE]
// )
// : -1
// if (index >= 0) {
// setReturnToGeneric(env[name][STATS], index)
// return true
// } else if (!env[returns[VALUE]]) return false
// }
})
}
} else {
switch (returns[VALUE]) {
case KEYWORDS.CREATE_ARRAY:
setPropToCollection(env[name][STATS], prop)
setPropToSubReturn(env[name][STATS], prop, initArrayType({ rem, env }))
break
case KEYWORDS.IF:
resolveCondition({ rem, name, env, exp, prop, stack, check })
break
default:
{
if (GET_ARRAY_INFERENCE_SET.has(returns[VALUE])) {
resolveGetter({
rem,
prop,
name,
env,
caller: returns[VALUE],
exp
})
retry(env[name][STATS], exp, stack, () => {
resolveGetter({
rem,
prop,
name,
env,
caller: returns[VALUE],
exp
})
})
}
checkPredicateNameDeep(name, exp, exp.slice(1), returns)
if (!env[returns[VALUE]]) return false
else if (getType(env[returns[VALUE]][STATS]) === APPLY) {
if (returns[TYPE] === WORD) setReturnToAbstraction(env[name][STATS])
// ALWAYS APPLY
// rest.at(-1)[0][TYPE] === APPLY
// Here is upon application to store the result in the variable
if (isGenericReturn(env[returns[VALUE]][STATS])) {
// env[name][STATS][TYPE_PROP] =
const [genericReturn, head, nestGeneric, multiplier] =
getGenericReturn(env[returns[VALUE]], rem)
switch (head[TYPE]) {
case ATOM:
setTypeToAtom(env[name][STATS])
break
case WORD:
if (env[head[VALUE]])
setPropToType(
env[name][STATS],
prop,
env[head[VALUE]][STATS]
)
break
case APPLY:
switch (head[VALUE]) {
case KEYWORDS.ANONYMOUS_FUNCTION:
{
// TODO figure out a better way to do this
// This is initialization of identity or any other
// function that returns it's argument
// Redefine the variable but since it's an error doing that
// Delete it first
delete env[name]
check(
[
[APPLY, KEYWORDS.DEFINE_VARIABLE],
[WORD, name],
genericReturn
],
env,
exp
)
// const n = genericReturn.length
// setTypeToAbstraction(env[name][STATS])
// env[name][STATS][ARG_COUNT] = n - 2
// env[name][STATS][ARGUMENTS] = fillUnknownArgs(
// n - 2
// )
// checkReturnType({
// exp: [genericReturn],
// stack,
// name,
// env,
// check
// })
}
break
case KEYWORDS.CREATE_ARRAY:
{
setTypeToCollection(env[name][STATS])
setPropToSubReturn(
env[name][STATS],
TYPE_PROP,
initArrayType({ rem: genericReturn, env })
)
}
break
default:
break
}
break
default:
if (env[head[VALUE]])
setTypeToReturn(env[name][STATS], env[head[VALUE]][STATS])
break
}
if (env[returns[VALUE]][STATS][RETURNS][0] === COLLECTION) {
resolveGenericNest(env[name], prop, nestGeneric, multiplier)
}
}
if (isUnknownType(env[name][STATS]))
stagger(stack, 'prepend', exp, () => {
setTypeToReturnRef(env[name][STATS], env[returns[VALUE]][STATS])
})
else {
// if (SPECIAL_FORMS_SET.has(returns[VALUE]))
// setReturn(env[name][STATS], env[returns[VALUE]][STATS])
// else
setReturnRef(env[name][STATS], env[returns[VALUE]][STATS])
}
}
}
break
}
}
return true
}
const checkReturnType = ({ exp, stack, name, env, check }) => {
const last = exp.at(-1).at(-1)
const body = hasApplyLambdaBlock(last) ? last.at(-1).at(-1) : last
const rem = hasBlock(body) ? body.at(-1) : body
const returns = isLeaf(rem) ? rem : rem[0]
return resolveReturnType({
returns,
rem,
prop: RETURNS,
exp,
name,
env,
stack,
check
})
}
const stagger = (stack, method, data, fn) => {
stack[method]({ data, fn })
}
export const typeCheck = (
ast,
ctx = SPECIAL_FORM_TYPES,
typeSet = typeSetDefaultFunction
) => {
const Types = new Map()
const stack = new Brr()
const rootScopeIndex = 1
let scopeIndex = 0
// TODO also handle casting
const match = ({ rest, args, i, env, scope, exp }) => {
const first = exp[0]
let actual =
rest[i][0][VALUE] === KEYWORDS.CREATE_ARRAY
? initArrayType({ rem: rest[i], env })
: env[rest[i][0][VALUE]][STATS]
let expected = args[i][STATS]
retryArgs(args[i][STATS], exp, stack, () =>
match({ rest, args, i, env, scope, exp })
)
if (!isUnknownType(expected) && !isUnknownReturn(actual))
if (!equalsTypeWithReturn(expected, actual))
throw new TypeError(
`Incorrect type of argument (${i}) for (${
first[VALUE]
}). Expected (${formatSubType(
getTypes(expected)
)}) but got (${formatSubType(getReturns(actual))}) (${stringifyArgs(
exp
)}) (check #16)`
)
else if (!equalSubTypesWithSubReturn(expected, actual)) {
throw new TypeError(
`Incorrect type of argument (${i}) for (${
first[VALUE]
}). Expected (${formatSubType(
getTypes(expected)
)}) but got (${formatSubType(getReturns(actual))}) (${stringifyArgs(
exp
)}) (check #206)`
)
} else {
switch (getType(expected)) {
// almost exclusively for anonymous lambdas
case APPLY:
{
const argsN = rest[i].length - 2
if (
env[rest[i][0][VALUE]][STATS][SIGNATURE] ===
KEYWORDS.ANONYMOUS_FUNCTION
) {
if (
args[i][STATS][ARG_COUNT] !== VARIADIC &&
argsN !== args[i][STATS][ARG_COUNT]
)
throw new TypeError(
`Incorrect number of arguments for (${
args[i][STATS][SIGNATURE]
}) the (${KEYWORDS.ANONYMOUS_FUNCTION}) argument of (${
first[VALUE]
}) at position (${i}). Expected (= ${
args[i][STATS][ARG_COUNT]
}) but got ${argsN} (${stringifyArgs(exp)}) (check #777)`
)
else {
// ANONYMOUS LAMBDAS TYPE CHECKING
const local = Object.create(env)
const lambdaName = `${ANONYMOUS_FUNCTION_TYPE_PREFIX}${i}::${++scopeIndex}`
check(
[
[APPLY, KEYWORDS.DEFINE_VARIABLE],
[WORD, lambdaName],
rest[i]
],
local,
scope
)
// TODO delete this maybe
// #C2
// It will not be possible to know return type
const match1 = () => {
const actual = local[lambdaName]
const expected = args[i]
if (isGenericReturn(expected[STATS])) return
// if (
// isGenericReturn(args[i][STATS]) &&
// !isUnknownReturn(actual[STATS]) &&
// !isAnyReturn(actual[STATS])
// ) {
// args[i][STATS][RETURNS] = actual[STATS][RETURNS]
// return
// }
if (
!isUnknownReturn(expected[STATS]) &&
!isUnknownReturn(actual[STATS]) &&
!equalReturns(expected[STATS], actual[STATS])
)
throw new TypeError(
`Incorrect return type for (${
expected[STATS][SIGNATURE] ??
ANONYMOUS_FUNCTION_TYPE_PREFIX
}) the (${KEYWORDS.ANONYMOUS_FUNCTION}) argument of (${
first[VALUE]
}) at position (${i}). Expected (${formatSubType(
getReturns(expected[STATS])
)}) but got (${formatSubType(
getReturns(actual[STATS])
)}) (${stringifyArgs(exp)}) (check #779)`
)
else if (!equalSubReturns(expected[STATS], actual[STATS]))
throw new TypeError(
`Incorrect return type for (${
expected[STATS][SIGNATURE]
}) the (${KEYWORDS.ANONYMOUS_FUNCTION}) argument of (${
first[VALUE]
}) at position (${i}). Expected (${formatSubType(
getReturns(expected[STATS])
)}) but got (${formatSubType(
getReturns(actual[STATS])
)}) (${stringifyArgs(exp)}) (check #783)`
)
else
retry(
actual[STATS],
[[WORD, lambdaName], local],
stack,
match1
)
}
match1()
const match2 = () => {
for (let j = 0; j < args[i][STATS][ARGUMENTS].length; ++j) {
const actual = local[lambdaName][STATS][ARGUMENTS][j]
const expected = args[i][STATS][ARGUMENTS][j]
if (isGenericType(expected[STATS])) continue
// TODO: after refactoring the generic nesting and unnesting logic
// apply it here to judge lambda arguments based on the signature
// if (
// isGenericType(expected[STATS]) &&
// !isUnknownType(actual[STATS]) &&
// !isAnyType(actual[STATS])
// ) {
// expected[STATS][TYPE_PROP] = actual[STATS][TYPE_PROP]
// return
// }
if (
!isUnknownType(actual[STATS]) &&
!isUnknownType(expected[STATS]) &&
(!equalTypes(actual[STATS], expected[STATS]) ||
!equalSubTypes(actual[STATS], expected[STATS]))
)
throw new TypeError(
`Incorrect type for (${
KEYWORDS.ANONYMOUS_FUNCTION
}) (${
args[i][STATS][SIGNATURE]
}) argument at position (${j}) named as (${
local[lambdaName][STATS][ARGUMENTS][j][STATS][
SIGNATURE
]
}). Expected (${formatSubType(
getTypes(expected[STATS])