Skip to content

Commit

Permalink
Basic polymorphism.
Browse files Browse the repository at this point in the history
  • Loading branch information
amyjko committed Sep 19, 2023
1 parent 711b969 commit 178d26a
Show file tree
Hide file tree
Showing 11 changed files with 120 additions and 16 deletions.
21 changes: 16 additions & 5 deletions src/nodes/BasisType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,26 @@ export default abstract class BasisType extends Type {
.getStructureDefinition(this.getBasisTypeName());
}

/** All types have the structure type's functions. */
getAdditionalBasisScope(context: Context): Node | undefined {
return context.getBasis().getStructureDefinition('structure');
}

/**
* Get the in the basis structure definitions.
* Get basis functions and the structure type functions.
*/
getDefinitions(_: Node, context: Context): Definition[] {
return (
context
return [
// Get the functions defined on the base type
...(context
.getBasis()
.getStructureDefinition(this.getBasisTypeName())
?.getDefinitions(this) ?? []
);
?.getDefinitions(this) ?? []),
// Include the basis scope functions
...(this.getAdditionalBasisScope(context)?.getDefinitions(
_,
context
) ?? []),
];
}
}
9 changes: 7 additions & 2 deletions src/nodes/BinaryEvaluate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@ import evaluateCode from '../runtime/evaluate';

test.each([
['1 · 5', '1 · ""', BinaryEvaluate, IncompatibleInput],
['(1ms % 5) = 1ms', '(1ms % 5) = 1', BinaryEvaluate, IncompatibleInput, 1],
['(5ms ÷ 5) = 1ms', '(1ms ÷ 5) = 1', BinaryEvaluate, IncompatibleInput, 1],
[
'(1ms % 5) = 1ms',
'(1ms % 5) + "hi"',
BinaryEvaluate,
IncompatibleInput,
1,
],
['1 + 1', '1 + !', BinaryEvaluate, IncompatibleInput],
['1m + 1m', '1m + 1s', BinaryEvaluate, IncompatibleInput],
[
Expand Down
11 changes: 11 additions & 0 deletions src/nodes/Bind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,17 @@ export default class Bind extends Expression {
return value;
}

/** True if a name and the type matches */
isEquivalentTo(definition: Definition) {
return (
definition instanceof Bind &&
this.type &&
definition.type &&
this.type.isEqualTo(definition.type) &&
this.sharesName(definition)
);
}

getNodeLocale(translation: Locale) {
return translation.node.Bind;
}
Expand Down
16 changes: 16 additions & 0 deletions src/nodes/FunctionDefinition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,22 @@ export default class FunctionDefinition extends DefinitionExpression {
);
}

/** True if a name matches, the output matches, and the input type matches. */
isEquivalentTo(definition: Definition) {
return (
definition === this ||
(definition instanceof FunctionDefinition &&
this.output &&
definition.output &&
this.names.sharesName(definition.names) &&
this.output.isEqualTo(definition.output) &&
this.inputs.length === definition.inputs.length &&
this.inputs.every((input, index) =>
input.isEqualTo(definition.inputs[index])
))
);
}

evaluateTypeSet(
bind: Bind,
original: TypeSet,
Expand Down
12 changes: 12 additions & 0 deletions src/nodes/NameType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,18 @@ export default class NameType extends Type {
);
}

/**
* Override get scope to skip over all types, so we don't end up with funky infinite loops with
* other types that might try to resolve NameType. None of the types that might
* contain this can make definitions anyway.
*/
getScope(context: Context): Node | undefined {
return context
.getRoot(this)
?.getAncestors(this)
.find((node) => !(node instanceof Type));
}

isTypeVariable(context: Context) {
return this.resolve(context) instanceof TypeVariable;
}
Expand Down
6 changes: 6 additions & 0 deletions src/nodes/Source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import Root from './Root';
import Markup from './Markup';
import Purpose from '../concepts/Purpose';
import Tokens from '../parser/Tokens';
import type Definition from './Definition';

/** A document representing executable Wordplay code and it's various metadata, such as conflicts, tokens, and evaulator. */
export default class Source extends Expression {
Expand Down Expand Up @@ -127,6 +128,11 @@ export default class Source extends Expression {
return true;
}

/** Only equal if the same source */
isEquivalentTo(definition: Definition) {
return definition === this;
}

has(node: Node) {
return this.root.has(node);
}
Expand Down
5 changes: 5 additions & 0 deletions src/nodes/StreamDefinition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,11 @@ export default class StreamDefinition extends DefinitionExpression {
return current;
}

/** Only equal if the same stream definition. */
isEquivalentTo(definition: Definition) {
return definition === this;
}

getNodeLocale(translation: Locale) {
return translation.node.StreamDefinition;
}
Expand Down
5 changes: 5 additions & 0 deletions src/nodes/StructureDefinition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,11 @@ export default class StructureDefinition extends DefinitionExpression {
return this.names;
}

/** Only equal if the same structure definition. */
isEquivalentTo(definition: Definition) {
return definition === this;
}

getNodeLocale(locale: Locale) {
return locale.node.StructureDefinition;
}
Expand Down
11 changes: 9 additions & 2 deletions src/nodes/StructureType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,15 @@ export default class StructureType extends BasisType {
return this.structure.getDefinition(name);
}

getDefinitions(node: Node): Definition[] {
return [...this.structure.getDefinitions(node)];
/** Override to include this structure's definitions, but also the base structure definitions (e.g., =, ≠) */
getDefinitions(node: Node, context: Context): Definition[] {
return [
...this.structure.getDefinitions(node),
...(this.getAdditionalBasisScope(context)?.getDefinitions(
node,
context
) ?? []),
];
}

/** Compatible if it's the same structure definition, or the given type is a refinement of the given structure.*/
Expand Down
6 changes: 6 additions & 0 deletions src/nodes/TypeVariable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Type from './Type';
import Sym from './Sym';
import Token from './Token';
import { TYPE_SYMBOL } from '../parser/Symbols';
import type Definition from './Definition';

export default class TypeVariable extends Node {
readonly names: Names;
Expand Down Expand Up @@ -81,6 +82,11 @@ export default class TypeVariable extends Node {
return;
}

/** No type variables are ever */
isEquivalentTo(definition: Definition) {
return definition === this;
}

getNodeLocale(translation: Locale) {
return translation.node.TypeVariable;
}
Expand Down
34 changes: 27 additions & 7 deletions src/nodes/UnionType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import NoneType from './NoneType';
import Glyphs from '../lore/Glyphs';
import NodeRef from '../locale/NodeRef';
import TypePlaceholder from './TypePlaceholder';
import type Definition from './Definition';

export default class UnionType extends Type {
readonly left: Type;
Expand Down Expand Up @@ -161,13 +162,32 @@ export default class UnionType extends Type {
return [];
}

/** Override the base class: basis type scopes are their basis structure definitions. */
getScope(context: Context): Node | undefined {
// Get the scope of the left and right and only return it if it's the same.
// Otherwise, there is no overlapping scope.
const leftScope = this.left.getScope(context);
const rightScope = this.right.getScope(context);
return leftScope === rightScope ? leftScope : undefined;
getDefinitionsInScope(context: Context): Definition[] {
return this.getDefinitions(this, context);
}

/**
* Override to search for definitions on both the left and right types, and
* find the intersection of both sets, to allow for a degree of polymorphism. */
getDefinitions(anchor: Node, context: Context): Definition[] {
// Find the list of definitions in scope of each possible type.
// We generalize because literal types don't affect available definitions.
const definitionSets = this.generalize(context)
.getPossibleTypes(context)
.map((type) => type.getDefinitions(anchor, context));

// Find the definitions that intersect across each type's definition list.
// Do this by filtering the first set by all definitions for which all other sets have an equivalent definition.
// This is what allows for polymorphism.
const first = definitionSets[0];
const rest = definitionSets.slice(1);
return rest.length == 0
? first
: first.filter((def1) =>
rest.every((definitions) =>
definitions.some((def2) => def1.isEquivalentTo(def2))
)
);
}

getNodeLocale(translation: Locale) {
Expand Down

0 comments on commit 178d26a

Please sign in to comment.