Skip to content

Commit

Permalink
Ensured type errors when a structure definition is given instead of a…
Browse files Browse the repository at this point in the history
… structure value.
  • Loading branch information
amyjko committed Jun 29, 2024
1 parent 909ddd1 commit 56081d4
Show file tree
Hide file tree
Showing 19 changed files with 166 additions and 32 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Dates are in `YYYY-MM-DD` format and versions are in [semantic versioning](http:
- [#503](https://github.com/wordplaydev/wordplay/issues/503). Prevent infinite loops in parser.
- [#504](https://github.com/wordplaydev/wordplay/issues/504). Account for non-fixed-width characters in caret positioning.
- [#488](https://github.com/wordplaydev/wordplay/issues/488). Added animations off indicator on stage.
- Ensured type errors when a structure definition is given instead of a structure value.

## 0.10.1 2024-06-22

Expand Down
1 change: 1 addition & 0 deletions src/basis/BasisConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type BasisTypeName =
| 'column'
| 'stream'
| 'streamdefinition'
| 'structuredefinition'
| 'conversion'
| 'function'
| 'union'
Expand Down
3 changes: 2 additions & 1 deletion src/locale/NodeTexts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ type NodeTexts = {
DuplicateName: {
conflict: ConflictText;
resolution: Template;
}
};
/** When a shared bind has a duplicate name that's shared. Description inputs: $1: The duplicate */
DuplicateShare: ConflictText;
/**
Expand Down Expand Up @@ -636,6 +636,7 @@ type NodeTexts = {
*/
UnimplementedInterface: InternalConflictText;
}>;
StructureDefinitionType: DescriptiveNodeText;
/**
* A table literal, e.g., `⎡a•# b•#⎦⎡1 2⎦`
* Description inputs: $1 = the number of rows
Expand Down
8 changes: 8 additions & 0 deletions src/locale/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,14 @@
"UnimplementedInterface": "I implement $1 but haven't implemented $2"
}
},
"StructureDefinitionType": {
"name": "structure definition",
"emotion": "kind",
"description": "definition of $1",
"doc": [
"I refer to some kind of @StructureDefinition. People don't usually refer to me directly"
]
},
"TableLiteral": {
"name": "table literal",
"description": "$1 row table",
Expand Down
13 changes: 10 additions & 3 deletions src/nodes/Evaluate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ import NameType from './NameType';
import { NonFunctionType } from './NonFunctionType';
import type Locales from '../locale/Locales';
import UnionType from './UnionType';
import NoExpressionType from './NoExpressionType';
import StructureDefinitionType from './StructureDefinitionType';

type Mapping = {
expected: Bind;
Expand Down Expand Up @@ -502,7 +504,12 @@ export default class Evaluate extends Expression {

// Figure out what type this expected input is. Resolve any type variables to concrete values.
if (given instanceof Expression) {
const givenType = given.getType(context);
// Don't rely on the bind's specified type, since it's not a reliable source of type information. Ask it's value directly.
const givenType =
given instanceof Bind
? given.value?.getType(context) ??
new NoExpressionType(given)
: given.getType(context);
if (!expectedType.accepts(givenType, context, given))
conflicts.push(
new IncompatibleInput(
Expand Down Expand Up @@ -603,8 +610,8 @@ export default class Evaluate extends Expression {

return type instanceof FunctionType && type.definition
? type.definition
: type instanceof StructureType
? type.definition
: type instanceof StructureDefinitionType
? type.type.definition
: type instanceof StreamDefinitionType
? type.definition
: undefined;
Expand Down
38 changes: 20 additions & 18 deletions src/nodes/Generics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { UnknownVariableType } from './UnknownVariableType';
import type ConversionDefinition from './ConversionDefinition';
import type Convert from './Convert';
import UnionType from './UnionType';
import StructureType from './StructureType';

export type EvaluationType = Evaluate | BinaryEvaluate | UnaryEvaluate;

Expand All @@ -33,13 +34,14 @@ export default function getConcreteExpectedType(
definition: FunctionDefinition | StructureDefinition | StreamDefinition,
input: Bind | undefined,
evaluation: EvaluationType,
context: Context
context: Context,
): Type {
let type;
// If the input is undefined, we're getting the output type of the function or structure.
if (input === undefined) {
if (definition instanceof StructureDefinition)
return definition.getType(context);
if (definition instanceof StructureDefinition) {
return new StructureType(definition);
}
const functionType = definition.getType(context);
if (
!(
Expand All @@ -55,7 +57,7 @@ export default function getConcreteExpectedType(
// Verify that the bind provided exists on the given evaluate.
if (!definition.inputs.includes(input))
throw Error(
"The Bind given doesn't exist on this function or structure. Something is broken!"
"The Bind given doesn't exist on this function or structure. Something is broken!",
);
// Get the bind's type, resolving any non-type variable references and converting any type variables to variable types.
type = input.getType(context);
Expand All @@ -77,7 +79,7 @@ export default function getConcreteExpectedType(
const abstractTypes: (NameType | NumberType)[] = type.nodes(
(n): n is NameType | NumberType =>
(n instanceof NameType && n.isTypeVariable(context)) ||
(n instanceof NumberType && n.hasDerivedUnit())
(n instanceof NumberType && n.hasDerivedUnit()),
);
moreAbstractTypes = abstractTypes.length > 0;
const nextAbstractType = abstractTypes[0];
Expand All @@ -89,12 +91,12 @@ export default function getConcreteExpectedType(
nextAbstractType,
definition,
evaluation,
context
context,
)
: getConcreteNumberInput(
nextAbstractType,
evaluation,
context
context,
);
// Clone the current type, replacing the abstract type with the concrete type.
type = type.replace(nextAbstractType, concreteType);
Expand All @@ -116,7 +118,7 @@ export default function getConcreteExpectedType(
function getConcreteNumberInput(
type: NumberType,
evaluation: EvaluationType,
context: Context
context: Context,
) {
// If the type is abstract, concretize it. Otherwise, just return the existing concrete type.
return type.unit instanceof Function
Expand All @@ -132,7 +134,7 @@ function getConcreteTypeVariable(
type: NameType,
definition: FunctionDefinition | StructureDefinition | StreamDefinition,
evaluation: EvaluationType,
context: Context
context: Context,
): Type {
if (definition instanceof StreamDefinition)
return new UnknownVariableType(evaluation);
Expand All @@ -147,7 +149,7 @@ function getConcreteTypeVariable(
// First, the easy case: let's see if the evaluate has a type input that defines this type variable. See if the type for the type variable was provided explicitly in the evaluation.
// What is the index of the type variable in the definition?
const typeVarIndex = definition.types?.variables.findIndex(
(v) => v === typeVariable
(v) => v === typeVariable,
);
if (typeVarIndex !== undefined && evaluation instanceof Evaluate) {
const typeInputs = evaluation.getTypeInputs();
Expand All @@ -173,7 +175,7 @@ function getConcreteTypeVariable(
const structureType = evaluation.fun.structure.getType(context);
const typeInput = structureType.resolveTypeVariable(
type.getName(),
context
context,
);
if (typeInput !== undefined) return typeInput;
}
Expand All @@ -183,7 +185,7 @@ function getConcreteTypeVariable(
const structureType = evaluation.left.getType(context);
const typeInput = structureType.resolveTypeVariable(
type.getName(),
context
context,
);
if (typeInput !== undefined) return typeInput;
}
Expand All @@ -202,7 +204,7 @@ function getConcreteTypeVariable(
if (input instanceof Bind && input.type !== undefined)
return canBeTypeVariable(typeVariable, input.type, context);
else return false;
}
},
);

// Let's see if any input is a function with a variable output
Expand All @@ -215,7 +217,7 @@ function getConcreteTypeVariable(
return canBeTypeVariable(
typeVariable,
input.type.output,
context
context,
);
} else return false;
});
Expand Down Expand Up @@ -246,7 +248,7 @@ function getConcreteTypeVariable(
i instanceof Bind &&
inputWithVariableType
.getNames()
.find((n) => i.hasName(n)) !== undefined
.find((n) => i.hasName(n)) !== undefined,
) as Bind | undefined;
if (namedInput !== undefined) {
// Infer the type of the type variable from the input's value expression.
Expand Down Expand Up @@ -292,7 +294,7 @@ export function getConcreteConversionTypeVariable(
type: NameType,
definition: ConversionDefinition,
convert: Convert,
context: Context
context: Context,
): Type {
// Get the type of the input on the convert.
const inputType = convert.expression.getType(context);
Expand All @@ -308,14 +310,14 @@ export function getConcreteConversionTypeVariable(
function canBeTypeVariable(
variable: TypeVariable,
type: Type,
context: Context
context: Context,
) {
const types = type instanceof UnionType ? type.enumerate() : [type];
// Are any of them type variables with a matching name?
return types.some(
(type) =>
type instanceof NameType &&
type.isTypeVariable(context) &&
variable.hasName(type.getName())
variable.hasName(type.getName()),
);
}
7 changes: 5 additions & 2 deletions src/nodes/NameType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,11 @@ export default class NameType extends Type {
// Type variable? If it has a constraint, return that type. Otherwise return a variable type.
else if (definition instanceof TypeVariable) {
if (definition.type) return definition.type;
else return new VariableType(definition);
}
else {
return new VariableType(definition);
}
} else if (definition instanceof StructureDefinition)
return new StructureType(definition, this.types?.types ?? []);
// Some other type? Get the definition's type.
else return definition.getType(context);
}
Expand Down
10 changes: 6 additions & 4 deletions src/nodes/PropertyBind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ import { buildBindings } from './Evaluate';
import ExceptionValue from '@values/ExceptionValue';
import Evaluation from '@runtime/Evaluation';
import StartEvaluation from '@runtime/StartEvaluation';
import StructureType from './StructureType';
import Bind from './Bind';
import InvalidProperty from '@conflicts/InvalidProperty';
import StructureDefinitionType from './StructureDefinitionType';

export default class PropertyBind extends Expression {
readonly reference: PropertyReference;
Expand Down Expand Up @@ -116,12 +116,14 @@ export default class PropertyBind extends Expression {
const bind = this.reference.resolve(context);
if (
bind instanceof Bind &&
structureType instanceof StructureType &&
!structureType.definition.inputs.some((input) =>
structureType instanceof StructureDefinitionType &&
!structureType.type.definition.inputs.some((input) =>
input.names.sharesName(bind.names),
)
)
conflicts.push(new InvalidProperty(structureType.definition, this));
conflicts.push(
new InvalidProperty(structureType.type.definition, this),
);

return conflicts;
}
Expand Down
2 changes: 1 addition & 1 deletion src/nodes/StreamDefinitionType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export default class StreamDefinitionType extends Type {
}

getBasisTypeName(): BasisTypeName {
return 'function';
return 'streamdefinition';
}

clone() {
Expand Down
5 changes: 3 additions & 2 deletions src/nodes/StructureDefinition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import type Step from '@runtime/Step';
import StructureDefinitionValue from '@values/StructureDefinitionValue';
import type Context from './Context';
import type Definition from './Definition';
import StructureType from './StructureType';
import Token from './Token';
import type TypeSet from './TypeSet';
import { UnimplementedInterface } from '@conflicts/UnimplementedInterface';
Expand Down Expand Up @@ -41,6 +40,8 @@ import Evaluate from './Evaluate';
import ExpressionPlaceholder from './ExpressionPlaceholder';
import DefinitionExpression from './DefinitionExpression';
import type Locales from '../locale/Locales';
import StructureDefinitionType from './StructureDefinitionType';
import StructureType from './StructureType';

export default class StructureDefinition extends DefinitionExpression {
readonly docs: Docs | undefined;
Expand Down Expand Up @@ -449,7 +450,7 @@ export default class StructureDefinition extends DefinitionExpression {
}

computeType(): Type {
return new StructureType(this, []);
return new StructureDefinitionType(new StructureType(this));
}

getDependencies(): Expression[] {
Expand Down
68 changes: 68 additions & 0 deletions src/nodes/StructureDefinitionType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import Type from './Type';
import type { BasisTypeName } from '../basis/BasisConstants';
import type TypeSet from './TypeSet';
import Glyphs from '../lore/Glyphs';
import type Spaces from '../parser/Spaces';
import type Locales from '../locale/Locales';
import type StructureType from './StructureType';

export default class StructureDefinitionType extends Type {
readonly type: StructureType;

constructor(definition: StructureType) {
super();

this.type = definition;
}

getDescriptor() {
return 'StructureDefinitionType';
}

getGrammar() {
return [];
}

computeConflicts() {
return [];
}

/** Compatible if it's the same structure definition, or the given type is a refinement of the given structure.*/
acceptsAll(types: TypeSet): boolean {
return types.list().every((type) => {
if (
type instanceof StructureDefinitionType &&
this.type.definition === type.type.definition
)
return true;
});
}

simplify() {
return this;
}

getDescriptionInputs(locales: Locales) {
return [locales.getName(this.type.definition.names)];
}

getBasisTypeName(): BasisTypeName {
return 'structuredefinition';
}

clone() {
return new StructureDefinitionType(this.type) as this;
}

toWordplay(_: Spaces | undefined) {
return `•${this.type.definition.names.toWordplay(_)}`;
}

getNodeLocale(locales: Locales) {
return locales.get((l) => l.node.StructureDefinitionType);
}

getGlyphs() {
return Glyphs.Type;
}
}
Loading

0 comments on commit 56081d4

Please sign in to comment.