Skip to content
This repository has been archived by the owner on Sep 19, 2023. It is now read-only.

Commit

Permalink
Consistently detect Invalid Arity on compile time
Browse files Browse the repository at this point in the history
  • Loading branch information
wschella committed Dec 14, 2018
1 parent b857452 commit 784b985
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 19 deletions.
2 changes: 1 addition & 1 deletion index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { AsyncEvaluator } from './lib/evaluators/AsyncEvaluator';
export { SimpleEvaluator } from './lib/evaluators/SimpleEvaluator';

export { ExpressionError } from './lib/util/Errors';
export { ExpressionError, isExpressionError } from './lib/util/Errors';
4 changes: 3 additions & 1 deletion lib/Transformation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,9 @@ function transformOperator(expr: Alg.OperatorExpression)
const op = expr.operator as C.SpecialOperator;
const args = expr.args.map((a) => transformAlgebra(a));
const func = specialFunctions.get(op);
if (!hasCorrectArity(args, func.arity)) { throw new Err.InvalidArity(args, op); }
if (!func.checkArity(args)) {
throw new Err.InvalidArity(args, op);
}
return new E.SpecialOperator(args, func.applyAsync, func.applySync);
} else {
if (!C.Operators.contains(expr.operator)) {
Expand Down
19 changes: 18 additions & 1 deletion lib/functions/FunctionClasses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,16 +145,33 @@ export class SpecialFunction {
arity: number;
applySync: E.SpecialApplicationSync;
applyAsync: E.SpecialApplicationAsync;
checkArity: (args: E.Expression[]) => boolean;

constructor(public operator: C.SpecialOperator, definition: SpecialDefinition) {
this.arity = definition.arity;
this.applySync = definition.applySync;
this.applyAsync = definition.applyAsync;
this.checkArity = definition.checkArity || defaultArityCheck(this.arity);
}
}

function defaultArityCheck(arity: number): (args: E.Expression[]) => boolean {
return (args: E.Expression[]): boolean => {
// Infinity is used to represent var-args, so it's always correct.
if (arity === Infinity) { return true; }

// If the function has overloaded arity, the actual arity needs to be present.
if (Array.isArray(arity)) {
return arity.indexOf(args.length) >= 0;
}

return args.length === arity;
};
}

export type SpecialDefinition = {
arity: number;
applySync: E.SpecialApplicationSync;
applyAsync: E.SpecialApplicationAsync;
applySync: E.SpecialApplicationSync;
checkArity?: (args: E.Expression[]) => boolean;
};
7 changes: 3 additions & 4 deletions lib/functions/SpecialFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,14 +157,12 @@ const logicalAnd = {
const sameTerm = {
arity: 2,
async applyAsync({ args, mapping, evaluate }: E.EvalContextAsync): PTerm {
if (args.length !== 2) { throw new Err.InvalidArity(args, C.SpecialOperator.SAME_TERM); }
const [leftExpr, rightExpr] = args.map((a) => evaluate(a, mapping));
const left = await leftExpr;
const right = await rightExpr;
return bool(left.toRDF().equals(right.toRDF()));
},
applySync({ args, mapping, evaluate }: E.EvalContextSync): Term {
if (args.length !== 2) { throw new Err.InvalidArity(args, C.SpecialOperator.SAME_TERM); }
const [left, right] = args.map((a) => evaluate(a, mapping));
return bool(left.toRDF().equals(right.toRDF()));
},
Expand All @@ -173,14 +171,13 @@ const sameTerm = {
// IN -------------------------------------------------------------------------
const inSPARQL = {
arity: Infinity,
checkArity(args: E.Expression[]) { return args.length >= 1; },
async applyAsync({ args, mapping, evaluate }: E.EvalContextAsync): PTerm {
if (args.length < 1) { throw new Err.InvalidArity(args, C.SpecialOperator.IN); }
const [leftExpr, ...remaining] = args;
const left = await evaluate(leftExpr, mapping);
return inRecursiveAsync(left, { args: remaining, mapping, evaluate }, []);
},
applySync({ args, mapping, evaluate }: E.EvalContextSync): Term {
if (args.length < 1) { throw new Err.InvalidArity(args, C.SpecialOperator.IN); }
const [leftExpr, ...remaining] = args;
const left = evaluate(leftExpr, mapping);
return inRecursiveSync(left, { args: remaining, mapping, evaluate }, []);
Expand Down Expand Up @@ -242,6 +239,7 @@ function inRecursiveSync(
// NOT IN ---------------------------------------------------------------------
const notInSPARQL = {
arity: Infinity,
checkArity(args: E.Expression[]) { return args.length >= 1; },
async applyAsync(context: E.EvalContextAsync): PTerm {
const _in = specialFunctions.get(C.SpecialOperator.IN);
const isIn = await _in.applyAsync(context);
Expand All @@ -262,6 +260,7 @@ export type SpecialDefinition = {
arity: number;
applyAsync: E.SpecialApplicationAsync;
applySync: E.SpecialApplicationSync; // TODO: Test these implementations
checkArity?: (args: E.Expression[]) => boolean;
};

const _specialDefinitions: { [key in C.SpecialOperator]: SpecialDefinition } = {
Expand Down
67 changes: 55 additions & 12 deletions lib/util/Errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,48 +7,86 @@ import * as C from './Consts';
import { Bindings } from '../Types';

/**
* This error will be thrown when an expression errors.
* This class of error will be thrown when an expression errors.
* Various reasons this could happen are:
* - invalid types for the given operator
* - unbound variables
* - invalid lexical forms
* - ...
*
* The distinction is made so that one can catch this specific type
* and handle it accordingly to the SPARQL spec (relevant for e.g. FILTER, EXTEND),
* while others (programming errors) can be re-thrown.
*
* @see isExpressionError
*/
export class ExpressionError extends Error { }

/**
* Checks whether a given error is an {@link ExpressionError}.
* Also useful for mocking in tests for covering all branches.
*
* @see ExpressionError
*/
export function isExpressionError(error: Error): boolean {
return error instanceof ExpressionError;
}

/**
* A literal has an invalid lexical form for the datatype it is accompanied by.
* This error is only thrown when the term is as function argument that requires
* a valid lexical form.
*/
export class InvalidLexicalForm extends ExpressionError {
constructor(public args: RDF.Term) {
super('Invalid lexical form');
constructor(public arg: RDF.Term) {
super(`Invalid lexical form '${pp(arg)}'`);
}
}

/**
* A variable in the expression was not bound.
*/
export class UnboundVariableError extends ExpressionError {
constructor(public variable: string, public bindings: Bindings) {
super(`Unbound variable ${pp(variable)}`);
super(`Unbound variable '${pp(variable)}'`);
}
}

/**
* An invalid term was being coerced to an Effective Boolean Value.
*
* See the {@link https://www.w3.org/TR/sparql11-query/#ebv | SPARQL docs}
* on EBVs.
*/
export class EBVCoercionError extends ExpressionError {
constructor(public arg: E.Term) {
super(`Cannot coerce term to EBV ${pp(arg)}`);
super(`Cannot coerce term to EBV '${pp(arg)}'`);
}
}

/**
* An equality test was done on literals with unsupported datatypes.
*
* See {@link https://www.w3.org/TR/sparql11-query/#func-RDFterm-equal | term equality spec}.
*/
export class RDFEqualTypeError extends ExpressionError {
constructor(public args: E.Expression[]) {
super('Equality test for literals with unsupported datatypes');
}
}

/**
* All the expressions in a COALESCE call threw errors.
*/
export class CoalesceError extends ExpressionError {
constructor(public errors: Error[]) {
super('All COALESCE arguments threw errors');
}
}

/**
* No arguments to an IN call where equal, and at least one threw an error.
*/
export class InError extends ExpressionError {
constructor(public errors: Array<Error | false>) {
super(
Expand All @@ -57,19 +95,18 @@ export class InError extends ExpressionError {
}
}

export class InvalidArity extends ExpressionError {
constructor(public args: E.Expression[], public op: C.Operator) {
super(`The amount of args don't match the arity of the operator '${pp(op)}'.`);
}
}

/**
* Literals were passed to an operator that doesn't support their datatypes.
*/
export class InvalidArgumentTypes extends ExpressionError {
// tslint:disable-next-line:no-any
constructor(public args: E.Expression[], public op: C.Operator | C.NamedOperator) {
super(`Argument types not valid for operator: '${pp(op)}' with '${pp(args)}`);
}
}

/**
* An invalid typecast happened.
*/
export class CastError<T> extends ExpressionError {
constructor(public arg: T, cast: C.TypeURL) {
super(`Invalid cast: '${pp(arg)}' to '${pp(cast)}'`);
Expand Down Expand Up @@ -98,6 +135,12 @@ export class UnimplementedError extends Error {
}
}

export class InvalidArity extends Error {
constructor(public args: E.Expression[], public op: C.Operator) {
super(`The amount of args don't match the arity of the operator '${pp(op)}'.`);
}
}

export class InvalidExpression<T> extends Error {
constructor(expr: T) {
super(`Invalid SPARQL Expression '${pp(expr)}'`);
Expand Down

0 comments on commit 784b985

Please sign in to comment.