diff --git a/src/basis/InternalExpression.ts b/src/basis/InternalExpression.ts index 4606a0d09..b39983658 100644 --- a/src/basis/InternalExpression.ts +++ b/src/basis/InternalExpression.ts @@ -86,7 +86,7 @@ export default class InternalExpression extends SimpleExpression { return this; } - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, diff --git a/src/basis/Iteration.ts b/src/basis/Iteration.ts index 27ede10cf..bfc87103f 100644 --- a/src/basis/Iteration.ts +++ b/src/basis/Iteration.ts @@ -188,7 +188,7 @@ export class Iteration extends Expression { return this; } - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, diff --git a/src/nodes/BinaryEvaluate.ts b/src/nodes/BinaryEvaluate.ts index 32cb0897c..58036e739 100644 --- a/src/nodes/BinaryEvaluate.ts +++ b/src/nodes/BinaryEvaluate.ts @@ -334,7 +334,7 @@ export default class BinaryEvaluate extends Expression { /** * Type checks narrow the set to the specified type, if contained in the set and if the check is on the same bind. * */ - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, @@ -343,13 +343,13 @@ export default class BinaryEvaluate extends Expression { // If conjunction, then we compute the intersection of the left and right's possible types. // Note that we pass the left's possible types because we don't evaluate the right if the left isn't true. if (this.getOperator() === AND_SYMBOL) { - const left = this.left.evaluateTypeSet( + const left = this.left.evaluateTypeGuards( bind, original, current, context ); - const right = this.right.evaluateTypeSet( + const right = this.right.evaluateTypeGuards( bind, original, left, @@ -360,13 +360,13 @@ export default class BinaryEvaluate extends Expression { // If disjunction of type checks, then we return the union. // Note that we pass the left's possible types because we don't evaluate the right if the left is true. else if (this.getOperator() === OR_SYMBOL) { - const left = this.left.evaluateTypeSet( + const left = this.left.evaluateTypeGuards( bind, original, current, context ); - const right = this.right.evaluateTypeSet( + const right = this.right.evaluateTypeGuards( bind, original, left, @@ -433,8 +433,8 @@ export default class BinaryEvaluate extends Expression { } // Otherwise, just pass the types down and return the original types. - this.left.evaluateTypeSet(bind, original, current, context); - this.right.evaluateTypeSet(bind, original, current, context); + this.left.evaluateTypeGuards(bind, original, current, context); + this.right.evaluateTypeGuards(bind, original, current, context); return current; } diff --git a/src/nodes/Bind.ts b/src/nodes/Bind.ts index 9a1dab82b..77a19f44e 100644 --- a/src/nodes/Bind.ts +++ b/src/nodes/Bind.ts @@ -281,7 +281,7 @@ export default class Bind extends Expression { return true; } - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, @@ -289,7 +289,7 @@ export default class Bind extends Expression { ): TypeSet { return this.value === undefined ? current - : this.value.evaluateTypeSet(bind, original, current, context); + : this.value.evaluateTypeGuards(bind, original, current, context); } hasName(name: string) { diff --git a/src/nodes/Block.ts b/src/nodes/Block.ts index a8a4ca689..ae834acde 100644 --- a/src/nodes/Block.ts +++ b/src/nodes/Block.ts @@ -307,7 +307,7 @@ export default class Block extends Expression { * Blocks don't do any type checks, but we do have them delegate type checks to their final expression. * since we use them for parentheticals in boolean logic. * */ - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, @@ -316,7 +316,7 @@ export default class Block extends Expression { if (this.statements.length === 0) return current; const last = this.statements[this.statements.length - 1]; return last instanceof Expression - ? last.evaluateTypeSet(bind, original, current, context) + ? last.evaluateTypeGuards(bind, original, current, context) : current; } diff --git a/src/nodes/BooleanLiteral.ts b/src/nodes/BooleanLiteral.ts index 4e4ad4cd9..978396ddb 100644 --- a/src/nodes/BooleanLiteral.ts +++ b/src/nodes/BooleanLiteral.ts @@ -72,7 +72,7 @@ export default class BooleanLiteral extends Literal { return this.value.text.toString() === TRUE_SYMBOL; } - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, diff --git a/src/nodes/Borrow.ts b/src/nodes/Borrow.ts index 8881fc50c..3befd98ec 100644 --- a/src/nodes/Borrow.ts +++ b/src/nodes/Borrow.ts @@ -246,7 +246,7 @@ export default class Borrow extends SimpleExpression { : definition.getType(context); } - evaluateTypeSet(_: Bind, __: TypeSet, current: TypeSet): TypeSet { + evaluateTypeGuards(_: Bind, __: TypeSet, current: TypeSet): TypeSet { return current; } diff --git a/src/nodes/Changed.ts b/src/nodes/Changed.ts index 547543fe3..55bffa93a 100644 --- a/src/nodes/Changed.ts +++ b/src/nodes/Changed.ts @@ -125,14 +125,14 @@ export default class Changed extends SimpleExpression { return new BoolValue(this, evaluator.didStreamCauseReaction(stream)); } - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, context: Context ) { if (this.stream instanceof Expression) - this.stream.evaluateTypeSet(bind, original, current, context); + this.stream.evaluateTypeGuards(bind, original, current, context); return current; } diff --git a/src/nodes/Conditional.ts b/src/nodes/Conditional.ts index 4612b1c7c..946fe5a3b 100644 --- a/src/nodes/Conditional.ts +++ b/src/nodes/Conditional.ts @@ -174,14 +174,14 @@ export default class Conditional extends Expression { /** * Type checks narrow the set to the specified type, if contained in the set and if the check is on the same bind. * */ - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, context: Context ) { // Evaluate the condition with the current types. - const revisedTypes = this.condition.evaluateTypeSet( + const revisedTypes = this.condition.evaluateTypeGuards( bind, original, current, @@ -194,11 +194,11 @@ export default class Conditional extends Expression { // Evaluate the yes branch with the revised types. if (this.yes instanceof Expression) - this.yes.evaluateTypeSet(bind, original, revisedTypes, context); + this.yes.evaluateTypeGuards(bind, original, revisedTypes, context); // Evaluate the no branch with the complement of the revised types, unless they weren't guarded, in which case we pass through the current types. if (this.no instanceof Expression) { - this.no.evaluateTypeSet( + this.no.evaluateTypeGuards( bind, original, guarded ? current.difference(revisedTypes, context) : current, diff --git a/src/nodes/ConversionDefinition.ts b/src/nodes/ConversionDefinition.ts index e193b3900..51b1750ac 100644 --- a/src/nodes/ConversionDefinition.ts +++ b/src/nodes/ConversionDefinition.ts @@ -172,14 +172,19 @@ export default class ConversionDefinition extends DefinitionExpression { return value; } - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, context: Context ) { if (this.expression instanceof Expression) - this.expression.evaluateTypeSet(bind, original, current, context); + this.expression.evaluateTypeGuards( + bind, + original, + current, + context + ); return current; } diff --git a/src/nodes/Convert.ts b/src/nodes/Convert.ts index 085071e32..9e4663a22 100644 --- a/src/nodes/Convert.ts +++ b/src/nodes/Convert.ts @@ -245,14 +245,19 @@ export default class Convert extends Expression { return evaluator.popValue(this); } - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, context: Context ) { if (this.expression instanceof Expression) - this.expression.evaluateTypeSet(bind, original, current, context); + this.expression.evaluateTypeGuards( + bind, + original, + current, + context + ); return current; } diff --git a/src/nodes/Delete.ts b/src/nodes/Delete.ts index 12064192f..fe0301dad 100644 --- a/src/nodes/Delete.ts +++ b/src/nodes/Delete.ts @@ -218,16 +218,16 @@ export default class Delete extends Expression { return new TableValue(this, table.type, list); } - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, context: Context ) { if (this.table instanceof Expression) - this.table.evaluateTypeSet(bind, original, current, context); + this.table.evaluateTypeGuards(bind, original, current, context); if (this.query instanceof Expression) - this.query.evaluateTypeSet(bind, original, current, context); + this.query.evaluateTypeGuards(bind, original, current, context); return current; } diff --git a/src/nodes/DocumentedExpression.ts b/src/nodes/DocumentedExpression.ts index 0e89fe16f..846ffe652 100644 --- a/src/nodes/DocumentedExpression.ts +++ b/src/nodes/DocumentedExpression.ts @@ -65,7 +65,7 @@ export default class DocumentedExpression extends SimpleExpression { ) as this; } - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, diff --git a/src/nodes/Evaluate.ts b/src/nodes/Evaluate.ts index d1ced4d75..c117a291f 100644 --- a/src/nodes/Evaluate.ts +++ b/src/nodes/Evaluate.ts @@ -898,17 +898,17 @@ export default class Evaluate extends Expression { return bindings; } - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, context: Context ) { if (this.fun instanceof Expression) - this.fun.evaluateTypeSet(bind, original, current, context); + this.fun.evaluateTypeGuards(bind, original, current, context); this.inputs.forEach((input) => { if (input instanceof Expression) - input.evaluateTypeSet(bind, original, current, context); + input.evaluateTypeGuards(bind, original, current, context); }); return current; } diff --git a/src/nodes/Expression.ts b/src/nodes/Expression.ts index bac50644d..cf9de8fc0 100644 --- a/src/nodes/Expression.ts +++ b/src/nodes/Expression.ts @@ -73,7 +73,7 @@ export default abstract class Expression extends Node { * Used to determine what types are possible for a given after evaluating this expression, implementing type guards. * Most expressions do not manipulate possible types at all; primarily is just logical operators and type checks. * */ - abstract evaluateTypeSet( + abstract evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, diff --git a/src/nodes/ExpressionPlaceholder.ts b/src/nodes/ExpressionPlaceholder.ts index 5afbb04d7..84972e4dc 100644 --- a/src/nodes/ExpressionPlaceholder.ts +++ b/src/nodes/ExpressionPlaceholder.ts @@ -166,7 +166,7 @@ export default class ExpressionPlaceholder extends SimpleExpression { return new UnimplementedException(evaluator, this); } - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, diff --git a/src/nodes/FormattedLiteral.ts b/src/nodes/FormattedLiteral.ts index 011adc2ab..caff68504 100644 --- a/src/nodes/FormattedLiteral.ts +++ b/src/nodes/FormattedLiteral.ts @@ -137,7 +137,7 @@ export default class FormattedLiteral extends Literal { return FormattedType.make(); } - evaluateTypeSet(_: Bind, __: TypeSet, current: TypeSet): TypeSet { + evaluateTypeGuards(_: Bind, __: TypeSet, current: TypeSet): TypeSet { return current; } diff --git a/src/nodes/FunctionDefinition.ts b/src/nodes/FunctionDefinition.ts index f6a73d3a2..d55828542 100644 --- a/src/nodes/FunctionDefinition.ts +++ b/src/nodes/FunctionDefinition.ts @@ -421,14 +421,19 @@ export default class FunctionDefinition extends DefinitionExpression { ); } - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, context: Context ) { if (this.expression !== undefined) - this.expression.evaluateTypeSet(bind, original, current, context); + this.expression.evaluateTypeGuards( + bind, + original, + current, + context + ); return current; } diff --git a/src/nodes/Initial.ts b/src/nodes/Initial.ts index d76fd75a5..5cfd7227b 100644 --- a/src/nodes/Initial.ts +++ b/src/nodes/Initial.ts @@ -85,7 +85,7 @@ export default class Initial extends SimpleExpression { return new BoolValue(this, evaluator.isInitialEvaluation()); } - evaluateTypeSet(_: Bind, __: TypeSet, current: TypeSet) { + evaluateTypeGuards(_: Bind, __: TypeSet, current: TypeSet) { return current; } diff --git a/src/nodes/Insert.ts b/src/nodes/Insert.ts index f12b5c276..e7d88f48d 100644 --- a/src/nodes/Insert.ts +++ b/src/nodes/Insert.ts @@ -288,16 +288,16 @@ export default class Insert extends Expression { return row instanceof StructureValue ? table.insert(this, row) : row; } - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, context: Context ) { if (this.table instanceof Expression) - this.table.evaluateTypeSet(bind, original, current, context); + this.table.evaluateTypeGuards(bind, original, current, context); if (this.row instanceof Expression) - this.row.evaluateTypeSet(bind, original, current, context); + this.row.evaluateTypeGuards(bind, original, current, context); return current; } diff --git a/src/nodes/Is.ts b/src/nodes/Is.ts index 1e78f6891..d50a6f8d0 100644 --- a/src/nodes/Is.ts +++ b/src/nodes/Is.ts @@ -124,7 +124,7 @@ export default class Is extends Expression { /** * Type checks narrow the set to the specified type, if contained in the set and if the check is on the same bind. * */ - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, _: TypeSet, current: TypeSet, diff --git a/src/nodes/IsLocale.ts b/src/nodes/IsLocale.ts index e62938337..8e40ed4ba 100644 --- a/src/nodes/IsLocale.ts +++ b/src/nodes/IsLocale.ts @@ -101,7 +101,11 @@ export default class IsLocale extends SimpleExpression { getDependencies(): Expression[] { return []; } - evaluateTypeSet(bind: Bind, original: TypeSet, current: TypeSet): TypeSet { + evaluateTypeGuards( + bind: Bind, + original: TypeSet, + current: TypeSet + ): TypeSet { return current; } diff --git a/src/nodes/ListAccess.ts b/src/nodes/ListAccess.ts index 79cfdedc6..c943a5440 100644 --- a/src/nodes/ListAccess.ts +++ b/src/nodes/ListAccess.ts @@ -185,16 +185,16 @@ export default class ListAccess extends Expression { return list.get(index); } - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, context: Context ) { if (this.list instanceof Expression) - this.list.evaluateTypeSet(bind, original, current, context); + this.list.evaluateTypeGuards(bind, original, current, context); if (this.index instanceof Expression) - this.index.evaluateTypeSet(bind, original, current, context); + this.index.evaluateTypeGuards(bind, original, current, context); return current; } diff --git a/src/nodes/ListLiteral.ts b/src/nodes/ListLiteral.ts index dd9cbc904..0d6e866ab 100644 --- a/src/nodes/ListLiteral.ts +++ b/src/nodes/ListLiteral.ts @@ -178,7 +178,7 @@ export default class ListLiteral extends Expression { return new ListValue(this, values); } - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, @@ -186,9 +186,9 @@ export default class ListLiteral extends Expression { ) { this.values.forEach((val) => { if (val instanceof Expression) - val.evaluateTypeSet(bind, original, current, context); + val.evaluateTypeGuards(bind, original, current, context); else if (val.list) - val.list.evaluateTypeSet(bind, original, current, context); + val.list.evaluateTypeGuards(bind, original, current, context); }); return current; } diff --git a/src/nodes/MapLiteral.ts b/src/nodes/MapLiteral.ts index 61a919ef4..4bbeadb9d 100644 --- a/src/nodes/MapLiteral.ts +++ b/src/nodes/MapLiteral.ts @@ -179,7 +179,7 @@ export default class MapLiteral extends Expression { return new MapValue(this, values); } - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, @@ -187,7 +187,7 @@ export default class MapLiteral extends Expression { ) { this.values.forEach((val) => { if (val instanceof Expression) - val.evaluateTypeSet(bind, original, current, context); + val.evaluateTypeGuards(bind, original, current, context); }); return current; } diff --git a/src/nodes/NoneLiteral.ts b/src/nodes/NoneLiteral.ts index d4e1519b2..950e9525d 100644 --- a/src/nodes/NoneLiteral.ts +++ b/src/nodes/NoneLiteral.ts @@ -80,7 +80,7 @@ export default class NoneLiteral extends Literal { return this.none; } - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, diff --git a/src/nodes/NumberLiteral.ts b/src/nodes/NumberLiteral.ts index a354f85b9..ebc88cb26 100644 --- a/src/nodes/NumberLiteral.ts +++ b/src/nodes/NumberLiteral.ts @@ -133,7 +133,7 @@ export default class NumberLiteral extends Literal { } } - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, diff --git a/src/nodes/Previous.ts b/src/nodes/Previous.ts index 903159e62..7e320cc26 100644 --- a/src/nodes/Previous.ts +++ b/src/nodes/Previous.ts @@ -190,16 +190,16 @@ export default class Previous extends Expression { : stream.range(this, num); } - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, context: Context ) { if (this.stream instanceof Expression) - this.stream.evaluateTypeSet(bind, original, current, context); + this.stream.evaluateTypeGuards(bind, original, current, context); if (this.number instanceof Expression) - this.number.evaluateTypeSet(bind, original, current, context); + this.number.evaluateTypeGuards(bind, original, current, context); return current; } diff --git a/src/nodes/Program.ts b/src/nodes/Program.ts index 6befdb811..5a31f22a2 100644 --- a/src/nodes/Program.ts +++ b/src/nodes/Program.ts @@ -102,7 +102,7 @@ export default class Program extends Expression { return this.expression.getType(context); } - evaluateTypeSet(_: Bind, __: TypeSet, current: TypeSet): TypeSet { + evaluateTypeGuards(_: Bind, __: TypeSet, current: TypeSet): TypeSet { return current; } diff --git a/src/nodes/PropertyBind.ts b/src/nodes/PropertyBind.ts index e9a2f376a..004bc4d26 100644 --- a/src/nodes/PropertyBind.ts +++ b/src/nodes/PropertyBind.ts @@ -155,7 +155,7 @@ export default class PropertyBind extends Expression { return newStructure; } - evaluateTypeSet(_: Bind, __: TypeSet, current: TypeSet) { + evaluateTypeGuards(_: Bind, __: TypeSet, current: TypeSet) { return current; } diff --git a/src/nodes/PropertyReference.ts b/src/nodes/PropertyReference.ts index 93d023bf0..c4199479a 100644 --- a/src/nodes/PropertyReference.ts +++ b/src/nodes/PropertyReference.ts @@ -12,7 +12,6 @@ import StructureType from './StructureType'; import Bind from './Bind'; import UnionType from './UnionType'; import type TypeSet from './TypeSet'; -import Conditional from './Conditional'; import Is from './Is'; import { PROPERTY_SYMBOL } from '@parser/Symbols'; import Sym from './Sym'; @@ -37,6 +36,7 @@ import FunctionDefinition from './FunctionDefinition'; import BasisType from './BasisType'; import type Locales from '../locale/Locales'; import BinaryEvaluate from './BinaryEvaluate'; +import getGuards from './getGuards'; export default class PropertyReference extends Expression { readonly structure: Expression; @@ -255,36 +255,21 @@ export default class PropertyReference extends Expression { ) { // Find any conditionals with type checks that refer to the value bound to this name. // Reverse them so they are in furthest to nearest ancestor, so we narrow types in execution order. - const guards = context - .getRoot(this) - ?.getAncestors(this) - ?.filter( - (a): a is Conditional => - // Guards must be conditionals - a instanceof Conditional && - // Don't include conditionals whose condition contain this; that would create a cycle - !a.condition.contains(this) && - // Guards must have references to this same property in a type check - a.condition.nodes( - (n): n is PropertyReference => - this.name !== undefined && - (context.source.root.getParent(n) instanceof - Is || - context.source.root.getParent( - n - ) instanceof BinaryEvaluate) && - n instanceof PropertyReference && - n.getSubjectType(context) instanceof - StructureType && - def === - ( - n.getSubjectType( - context - ) as StructureType - ).getDefinition(this.name.getName()) - ).length > 0 - ) - .reverse(); + const guards = getGuards( + this, + context, + (n) => + this.name !== undefined && + (context.source.root.getParent(n) instanceof Is || + context.source.root.getParent(n) instanceof + BinaryEvaluate) && + n instanceof PropertyReference && + n.getSubjectType(context) instanceof StructureType && + def === + ( + n.getSubjectType(context) as StructureType + ).getDefinition(this.name.getName()) + ); // Grab the furthest ancestor and evaluate possible types from there. const root = @@ -293,7 +278,7 @@ export default class PropertyReference extends Expression { : undefined; if (root !== undefined) { const possibleTypes = type.getTypeSet(context); - root.evaluateTypeSet( + root.evaluateTypeGuards( def, possibleTypes, possibleTypes, @@ -336,14 +321,14 @@ export default class PropertyReference extends Expression { ); } - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, context: Context ) { // Filter the types of the structure. - const possibleTypes = this.structure.evaluateTypeSet( + const possibleTypes = this.structure.evaluateTypeGuards( bind, original, current, diff --git a/src/nodes/Reaction.ts b/src/nodes/Reaction.ts index 6832ed62e..a746b13be 100644 --- a/src/nodes/Reaction.ts +++ b/src/nodes/Reaction.ts @@ -280,16 +280,16 @@ export default class Reaction extends Expression { return streamValue; } - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, context: Context ) { if (this.initial instanceof Expression) - this.initial.evaluateTypeSet(bind, original, current, context); + this.initial.evaluateTypeGuards(bind, original, current, context); if (this.next instanceof Expression) - this.next.evaluateTypeSet(bind, original, current, context); + this.next.evaluateTypeGuards(bind, original, current, context); return current; } diff --git a/src/nodes/Reference.ts b/src/nodes/Reference.ts index 4f85eac8c..ae02cc1fb 100644 --- a/src/nodes/Reference.ts +++ b/src/nodes/Reference.ts @@ -13,7 +13,6 @@ import type Definition from './Definition'; import Bind from './Bind'; import ReferenceCycle from '@conflicts/ReferenceCycle'; import Reaction from './Reaction'; -import Conditional from './Conditional'; import UnionType from './UnionType'; import type TypeSet from './TypeSet'; import Is from './Is'; @@ -36,6 +35,7 @@ import StreamDefinition from './StreamDefinition'; import FunctionType from './FunctionType'; import type Locales from '../locale/Locales'; import BinaryEvaluate from './BinaryEvaluate'; +import getGuards from './getGuards'; /** * A reference to some Definition. Can optionally take the definition which it refers, @@ -277,30 +277,22 @@ export default class Reference extends SimpleExpression { ) { // Find any conditionals with type checks that refer to the value bound to this name. // Reverse them so they are in furthest to nearest ancestor, so we narrow types in execution order. - const guards = context.source.root - .getAncestors(this) - ?.filter( - (a): a is Conditional => - a instanceof Conditional && - // Don't include conditionals whose condition contain this; that would create a cycle - !a.condition.contains(this) && - a.condition.nodes( - (n): n is Reference => - (context.source.root.getParent(n) instanceof - Is || - context.source.root.getParent(n) instanceof - BinaryEvaluate) && - n instanceof Reference && - definition === n.resolve(context) - ).length > 0 - ) - .reverse(); + const guards = getGuards( + this, + context, + (node) => + (context.source.root.getParent(node) instanceof Is || + context.source.root.getParent(node) instanceof + BinaryEvaluate) && + node instanceof Reference && + definition === node.resolve(context) + ); // Grab the furthest ancestor and evaluate possible types from there. const root = guards[0]; if (root !== undefined) { const possibleTypes = type.getTypeSet(context); - root.evaluateTypeSet( + root.evaluateTypeGuards( definition, possibleTypes, possibleTypes, @@ -312,7 +304,7 @@ export default class Reference extends SimpleExpression { return context.getReferenceType(this) ?? type; } - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, _: TypeSet, current: TypeSet, diff --git a/src/nodes/Select.ts b/src/nodes/Select.ts index 5a12c2324..064537650 100644 --- a/src/nodes/Select.ts +++ b/src/nodes/Select.ts @@ -300,18 +300,18 @@ export default class Select extends Expression { return new TableValue(this, newType, selected); } - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, context: Context ) { if (this.table instanceof Expression) - this.table.evaluateTypeSet(bind, original, current, context); + this.table.evaluateTypeGuards(bind, original, current, context); if (this.row instanceof Expression) - this.row.evaluateTypeSet(bind, original, current, context); + this.row.evaluateTypeGuards(bind, original, current, context); if (this.query instanceof Expression) - this.query.evaluateTypeSet(bind, original, current, context); + this.query.evaluateTypeGuards(bind, original, current, context); return current; } diff --git a/src/nodes/SetLiteral.ts b/src/nodes/SetLiteral.ts index 5441853b4..4c5a24aaa 100644 --- a/src/nodes/SetLiteral.ts +++ b/src/nodes/SetLiteral.ts @@ -134,7 +134,7 @@ export default class SetLiteral extends Expression { return new SetValue(this, values); } - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, @@ -142,7 +142,7 @@ export default class SetLiteral extends Expression { ) { this.values.forEach((val) => { if (val instanceof Expression) - val.evaluateTypeSet(bind, original, current, context); + val.evaluateTypeGuards(bind, original, current, context); }); return current; } diff --git a/src/nodes/SetOrMapAccess.ts b/src/nodes/SetOrMapAccess.ts index 53dfec247..c0ba8866e 100644 --- a/src/nodes/SetOrMapAccess.ts +++ b/src/nodes/SetOrMapAccess.ts @@ -176,16 +176,16 @@ export default class SetOrMapAccess extends Expression { else return setOrMap.has(this, key); } - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, context: Context ) { if (this.setOrMap instanceof Expression) - this.setOrMap.evaluateTypeSet(bind, original, current, context); + this.setOrMap.evaluateTypeGuards(bind, original, current, context); if (this.key instanceof Expression) - this.key.evaluateTypeSet(bind, original, current, context); + this.key.evaluateTypeGuards(bind, original, current, context); return current; } diff --git a/src/nodes/Source.ts b/src/nodes/Source.ts index 8f73b24da..a63e80e6a 100644 --- a/src/nodes/Source.ts +++ b/src/nodes/Source.ts @@ -896,7 +896,7 @@ export default class Source extends Expression { getDependencies(): Expression[] { return [this.expression]; } - evaluateTypeSet(_: Bind, __: TypeSet, current: TypeSet): TypeSet { + evaluateTypeGuards(_: Bind, __: TypeSet, current: TypeSet): TypeSet { return current; } diff --git a/src/nodes/StreamDefinition.ts b/src/nodes/StreamDefinition.ts index 86b42528b..5879fa4ae 100644 --- a/src/nodes/StreamDefinition.ts +++ b/src/nodes/StreamDefinition.ts @@ -242,7 +242,7 @@ export default class StreamDefinition extends DefinitionExpression { return value; } - evaluateTypeSet(_: Bind, __: TypeSet, current: TypeSet): TypeSet { + evaluateTypeGuards(_: Bind, __: TypeSet, current: TypeSet): TypeSet { return current; } diff --git a/src/nodes/StructureDefinition.ts b/src/nodes/StructureDefinition.ts index 28886ed12..912562b9a 100644 --- a/src/nodes/StructureDefinition.ts +++ b/src/nodes/StructureDefinition.ts @@ -452,14 +452,19 @@ export default class StructureDefinition extends DefinitionExpression { ); } - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, context: Context ) { if (this.expression instanceof Expression) - this.expression.evaluateTypeSet(bind, original, current, context); + this.expression.evaluateTypeGuards( + bind, + original, + current, + context + ); return current; } diff --git a/src/nodes/TableLiteral.ts b/src/nodes/TableLiteral.ts index ebe5455aa..48a5f82fc 100644 --- a/src/nodes/TableLiteral.ts +++ b/src/nodes/TableLiteral.ts @@ -171,7 +171,7 @@ export default class TableLiteral extends Expression { : this.getParent(context); } - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, @@ -179,7 +179,7 @@ export default class TableLiteral extends Expression { ) { this.rows.forEach((row) => { if (row instanceof Expression) - row.evaluateTypeSet(bind, original, current, context); + row.evaluateTypeGuards(bind, original, current, context); }); return current; } diff --git a/src/nodes/TextLiteral.ts b/src/nodes/TextLiteral.ts index 3a254ee85..b699e2187 100644 --- a/src/nodes/TextLiteral.ts +++ b/src/nodes/TextLiteral.ts @@ -172,7 +172,7 @@ export default class TextLiteral extends Literal { ); } - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, diff --git a/src/nodes/This.ts b/src/nodes/This.ts index 0bbe3e227..06ec543e5 100644 --- a/src/nodes/This.ts +++ b/src/nodes/This.ts @@ -148,7 +148,7 @@ export default class This extends SimpleExpression { ); } - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, diff --git a/src/nodes/UnaryEvaluate.ts b/src/nodes/UnaryEvaluate.ts index 37cdd15f7..88cc5c4a9 100644 --- a/src/nodes/UnaryEvaluate.ts +++ b/src/nodes/UnaryEvaluate.ts @@ -171,7 +171,7 @@ export default class UnaryEvaluate extends Expression { /** * Logical negations take the set complement of the current set from the original. * */ - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, @@ -181,7 +181,7 @@ export default class UnaryEvaluate extends Expression { if (this.getOperator() !== NOT_SYMBOL) return current; // Get the possible types of the operand. - const possible = this.input.evaluateTypeSet( + const possible = this.input.evaluateTypeGuards( bind, original, current, diff --git a/src/nodes/UnparsableExpression.ts b/src/nodes/UnparsableExpression.ts index 9b4dd9cf6..499d9cf00 100644 --- a/src/nodes/UnparsableExpression.ts +++ b/src/nodes/UnparsableExpression.ts @@ -47,7 +47,7 @@ export default class UnparsableExpression extends SimpleExpression { return new UnparsableType(this.unparsables); } - evaluateTypeSet(_: Bind, __: TypeSet, current: TypeSet) { + evaluateTypeGuards(_: Bind, __: TypeSet, current: TypeSet) { return current; } diff --git a/src/nodes/Update.ts b/src/nodes/Update.ts index 1c00386a0..46636ad43 100644 --- a/src/nodes/Update.ts +++ b/src/nodes/Update.ts @@ -356,18 +356,18 @@ export default class Update extends Expression { return new TableValue(this, table.type, rows); } - evaluateTypeSet( + evaluateTypeGuards( bind: Bind, original: TypeSet, current: TypeSet, context: Context ) { if (this.table instanceof Expression) - this.table.evaluateTypeSet(bind, original, current, context); + this.table.evaluateTypeGuards(bind, original, current, context); if (this.row instanceof Expression) - this.row.evaluateTypeSet(bind, original, current, context); + this.row.evaluateTypeGuards(bind, original, current, context); if (this.query instanceof Expression) - this.query.evaluateTypeSet(bind, original, current, context); + this.query.evaluateTypeGuards(bind, original, current, context); return current; } diff --git a/src/nodes/getGuards.ts b/src/nodes/getGuards.ts new file mode 100644 index 000000000..1b72f4d29 --- /dev/null +++ b/src/nodes/getGuards.ts @@ -0,0 +1,27 @@ +import Conditional from './Conditional'; +import type Context from './Context'; +import type PropertyReference from './PropertyReference'; +import type Reference from './Reference'; +import type Node from './Node'; + +export default function getGuards( + reference: Reference | PropertyReference, + context: Context, + conditionCheck: (node: Node) => boolean +) { + return ( + context + .getRoot(reference) + ?.getAncestors(reference) + ?.filter( + (a): a is Conditional => + // Guards must be conditionals + a instanceof Conditional && + // Don't include conditionals whose condition contain this; that would create a cycle + !a.condition.contains(reference) && + // Guards must have references to this same property in a type check + a.condition.nodes().some((node) => conditionCheck(node)) + ) + .reverse() ?? [] + ); +}