Skip to content

Commit

Permalink
Error handling enhancements (#300)
Browse files Browse the repository at this point in the history
* Move sanitizeStackTrace function from wollok-ts-cli

* Fix null parameter message - #268

* Enhance check not null parameters

* assertIsNotNull enhanced + RangeErrors from runtimeModel enhanced

* Enhancing TypeError messages

* assertIsString, assertIsBoolean enhancements

* Enhancing #268 for null, invalid type parameters

* Rename sanitize stack trace function

* Fix game custom errors

* Regression test for #288

* Fix #268 & #274

* Moving properties definitions from LSP-IDE

* Add tests for properties + pointing to language branch

* Fixed excludeNullish definition

* If CI is going to fail because of strict coverage, then back to master and let's wait for master language merge

* Removing checkValidClosure => now Closure returns void WKO instead of undefined

* WIP: fix error messages

* Add assertNotVoid

* Fix reifyWKO

* Unifying collections common functions

* Removing instance in messages

* We achieve showing the method with a void closure for clarity

* Implementing void validation + enhanced error messages

* Enhanced tests and messages

* Removing check for Void in validation

* Fix types

* Fix for types again

* Add helpers tests
  • Loading branch information
fdodino authored Nov 11, 2024
1 parent feb34d2 commit 8db4df1
Show file tree
Hide file tree
Showing 14 changed files with 932 additions and 246 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"test:coverage": "nyc --reporter=lcov npm run test",
"test:unit": "mocha --parallel -r ts-node/register/transpile-only test/**/*.test.ts",
"test:examples": "npm run test:wtest -- --root language/test/examples",
"test:game": "mocha --parallel -r ts-node/register/transpile-only test/**/game.test.ts",
"test:dynamicDiagram": "mocha --parallel -r ts-node/register/transpile-only test/dynamicDiagram.test.ts",
"test:helpers": "mocha --parallel -r ts-node/register/transpile-only test/helpers.test.ts",
"test:interpreter": "mocha --parallel -r ts-node/register/transpile-only test/interpreter.test.ts",
Expand Down
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const DICTIONARY_MODULE = 'wollok.lang.Dictionary'
export const OBJECT_MODULE = 'wollok.lang.Object'
export const EXCEPTION_MODULE = 'wollok.lang.Exception'
export const CLOSURE_MODULE = 'wollok.lang.Closure'
export const VOID_WKO = 'wollok.lang.void'

export const GAME_MODULE = 'wollok.game.game'

Expand Down
2 changes: 2 additions & 0 deletions src/extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ export const uniqueBy = <T>(collection: T[], property: keyof T): T[] =>
return uniques
}, [])

export const excludeNullish = <T>(list: (T | undefined)[]): T[] => list.filter((item): item is T => item !== undefined)

export const count = <T>(list: List<T>, condition: (element: T) => boolean): number => list.filter(condition).length

export function raise(error: Error): never { throw error }
Expand Down
74 changes: 54 additions & 20 deletions src/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { BOOLEAN_MODULE, CLOSURE_EVALUATE_METHOD, CLOSURE_TO_STRING_METHOD, INITIALIZE_METHOD, KEYWORDS, NUMBER_MODULE, OBJECT_MODULE, STRING_MODULE, WOLLOK_BASE_PACKAGE } from './constants'
import { BOOLEAN_MODULE, CLOSURE_EVALUATE_METHOD, CLOSURE_TO_STRING_METHOD, INITIALIZE_METHOD, KEYWORDS, NUMBER_MODULE, OBJECT_MODULE, STRING_MODULE, VOID_WKO, WOLLOK_BASE_PACKAGE } from './constants'
import { getPotentiallyUninitializedLazy } from './decorators'
import { count, is, isEmpty, last, List, match, notEmpty, otherwise, valueAsListOrEmpty, when } from './extensions'
import { count, is, isEmpty, last, List, match, notEmpty, otherwise, valueAsListOrEmpty, when, excludeNullish } from './extensions'
import { RuntimeObject, RuntimeValue } from './interpreter/runtimeModel'
import { Assignment, Body, Class, CodeContainer, Describe, Entity, Environment, Expression, Field, If, Import, Literal, LiteralValue, Method, Module, Name, NamedArgument, New, Node, Package, Parameter, ParameterizedType, Problem, Program, Reference, Referenciable, Return, Self, Send, Sentence, Singleton, Super, Test, Throw, Try, Variable } from './model'

export const LIBRARY_PACKAGES = ['wollok.lang', 'wollok.lib', 'wollok.game', 'wollok.vm', 'wollok.mirror']

// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
// HELPER FUNCTIONS FOR VALIDATIONS
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════

export const allParents = (module: Module): Module[] =>
module.supertypes.map(supertype => supertype.reference.target).flatMap(supertype => supertype?.hierarchy ?? [])

Expand Down Expand Up @@ -329,7 +331,7 @@ export const firstNodeWithProblems = (node: Node): Node | undefined => {

export const isError = (problem: Problem): boolean => problem.level === 'error'

export const parentModule = (node: Node): Module => (node.ancestors.find(ancestor => ancestor.is(Module))) as Module ?? node.environment.objectClass
export const parentModule = (node: Node): Module => getParentModule(node) ?? node.environment.objectClass

export const parentImport = (node: Node): Import | undefined => node.ancestors.find(ancestor => ancestor.is(Import)) as Import

Expand Down Expand Up @@ -380,23 +382,30 @@ export const methodByFQN = (environment: Environment, fqn: string): Method | und
return entity.lookupMethod(methodName, Number.parseInt(methodArity, 10))
}

export const sendDefinitions = (environment: Environment) => (send: Send): Method[] => {
try {
return match(send.receiver)(
when(Reference)(node => {
const target = node.target
return target && is(Singleton)(target) ?
valueAsListOrEmpty(target.lookupMethod(send.message, send.args.length))
: allMethodDefinitions(environment, send)
}),
when(New)(node => valueAsListOrEmpty(node.instantiated.target?.lookupMethod(send.message, send.args.length))),
when(Self)(_ => moduleFinderWithBackup(environment, send)(
(module) => valueAsListOrEmpty(module.lookupMethod(send.message, send.args.length))
)),
)
} catch (error) {
return allMethodDefinitions(environment, send)
export const sendDefinitions = (environment: Environment) => (send: Send): (Method | Field)[] => {
const originalDefinitions = (): Method[] => {
try {
return match(send.receiver)(
when(Reference)(node => {
const target = node.target
return target && is(Singleton)(target) ?
valueAsListOrEmpty(target.lookupMethod(send.message, send.args.length))
: allMethodDefinitions(environment, send)
}),
when(New)(node => valueAsListOrEmpty(node.instantiated.target?.lookupMethod(send.message, send.args.length))),
when(Self)(_ => moduleFinderWithBackup(environment, send)(
(module) => valueAsListOrEmpty(module.lookupMethod(send.message, send.args.length))
)),
)
} catch (error) {
return allMethodDefinitions(environment, send)
}
}
const getDefinitionFromSyntheticMethod = (method: Method) => {
return method.parent.allFields.find((field) => field.name === method.name && field.isProperty)
}

return excludeNullish<Method | Field>(originalDefinitions().map((method: Method) => method.isSynthetic ? getDefinitionFromSyntheticMethod(method) : method))
}

export const allMethodDefinitions = (environment: Environment, send: Send): Method[] => {
Expand Down Expand Up @@ -437,4 +446,29 @@ export const superMethodDefinition = (superNode: Super, methodModule: Module): M
return methodModule.lookupMethod(currentMethod.name, superNode.args.length, { lookupStartFQN: currentMethod.parent.fullyQualifiedName })
}

const getParentModule = (node: Node) => node.ancestors.find(is(Module)) as Module
const getParentModule = (node: Node): Module => node.ancestors.find(is(Module)) as Module

export const isVoid = (obj: RuntimeValue | RuntimeObject): boolean => obj?.module?.fullyQualifiedName === VOID_WKO

export const assertNotVoid = (value: RuntimeObject, errorMessage: string): void => {
if (isVoid(value)) {
throw new RangeError(errorMessage)
}
}

export const getExpressionFor = (node: Expression): string =>
match(node)(
when(Send)(nodeSend =>
`message ${nodeSend.message}/${nodeSend.args.length}`),
when(If)(_ => 'if expression'),
when(Reference)(nodeRef => `reference '${nodeRef.name}'`),
when(Literal)(nodeLiteral => `literal ${nodeLiteral.value}`),
when(Self)(_ => 'self'),
when(Expression)(_ => 'expression'),
)

export const showParameter = (obj: RuntimeObject): string =>
`"${obj.getShortRepresentation().trim() || obj.module.fullyQualifiedName}"`

export const getMethodContainer = (node: Node): Method | Program | Test | undefined =>
last(node.ancestors.filter(parent => parent.is(Method) || parent.is(Program) || parent.is(Test))) as unknown as Method | Program | Test
20 changes: 17 additions & 3 deletions src/interpreter/interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import WRENatives from '../wre/wre.natives'
import { Evaluation, Execution, ExecutionDefinition, Natives, RuntimeObject, RuntimeValue, WollokException } from './runtimeModel'
import * as parse from '../parser'
import { notEmpty } from '../extensions'
import { WOLLOK_EXTRA_STACK_TRACE_HEADER } from '../constants'
import { isVoid } from '../helpers'

export const interpret = (environment: Environment, natives: Natives): Interpreter => new Interpreter(Evaluation.build(environment, natives))

Expand Down Expand Up @@ -85,6 +87,18 @@ const successResult = (result: string): ExecutionResult => ({
errored: false,
})

export const getStackTraceSanitized = (e?: Error): string[] => {
const indexOfTsStack = e?.stack?.indexOf(WOLLOK_EXTRA_STACK_TRACE_HEADER)
const fullStack = e?.stack?.slice(0, indexOfTsStack ?? -1) ?? ''

return fullStack
.replaceAll('\t', ' ')
.replaceAll(' ', ' ')
.replaceAll(' ', ' ')
.split('\n')
.filter(stackTraceElement => stackTraceElement.trim())
}

export class Interpreter extends AbstractInterpreter {
constructor(evaluation: Evaluation) { super(evaluation) }

Expand Down Expand Up @@ -120,9 +134,9 @@ export function interprete(interpreter: Interpreter, line: string): ExecutionRes
}

const result = interpreter.exec(sentenceOrImport)
const stringResult = result
? result.showShortValue(interpreter)
: ''
const stringResult = !result || isVoid(result)
? ''
: result.showShortValue(interpreter)
return successResult(stringResult)
}

Expand Down
Loading

0 comments on commit 8db4df1

Please sign in to comment.