Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance: TypeScriptの型を厳格化 #891

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions etc/aiscript.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -353,12 +353,14 @@ type FnTypeSource = NodeBase & {
type For = NodeBase & {
type: 'for';
label?: string;
var?: string;
from?: Expression;
to?: Expression;
times?: Expression;
for: Statement | Expression;
};
} & ({
var: string;
from: Expression;
to: Expression;
} | {
times: Expression;
});

// @public (undocumented)
function getLangVersion(input: string): string | null;
Expand Down Expand Up @@ -669,7 +671,7 @@ type Return = NodeBase & {

// @public (undocumented)
export class Scope {
constructor(layerdStates?: Scope['layerdStates'], parent?: Scope, name?: Scope['name'], nsName?: string);
constructor(layeredStates?: Scope['layeredStates'], parent?: Scope, name?: Scope['name'], nsName?: string);
add(name: string, variable: Variable): void;
assign(name: string, val: Value): void;
// (undocumented)
Expand Down
10 changes: 6 additions & 4 deletions src/error.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { TokenKind } from './parser/token.js';
import type { Pos } from './node.js';

export abstract class AiScriptError extends Error {
Expand All @@ -13,7 +12,7 @@
this.info = info;

// Maintains proper stack trace for where our error was thrown (only available on V8)
if (Error.captureStackTrace) {

Check warning on line 15 in src/error.ts

View workflow job for this annotation

GitHub Actions / lint

Unnecessary conditional, value is always truthy
Error.captureStackTrace(this, AiScriptError);
}
}
Expand All @@ -25,9 +24,12 @@
export class NonAiScriptError extends AiScriptError {
public name = 'Internal';
constructor(error: unknown) {
const message = String(
(error as { message?: unknown } | null | undefined)?.message ?? error,
);
let message: string;
if (error != null && typeof error === 'object' && 'message' in error) {
message = String(error.message);
} else {
message = String(error);
}
super(message, error);
}
}
Expand Down
71 changes: 34 additions & 37 deletions src/interpreter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import { Variable } from './variable.js';
import { Reference } from './reference.js';
import type { JsValue } from './util.js';
import type { Value, VFn, VUserFn } from './value.js';
import type { Value, VFn, VFnParam } from './value.js';

export type LogObject = {
scope?: string;
Expand Down Expand Up @@ -61,7 +61,7 @@
const q = args[0];
assertString(q);
if (this.opts.in == null) return NULL;
const a = await this.opts.in!(q.value);
const a = await this.opts.in(q.value);
return STR(a);
}),
};
Expand Down Expand Up @@ -280,15 +280,19 @@
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
return result ?? NULL;
} else {
const fnScope = fn.scope!.createChildScope();
const fnScope = fn.scope.createChildScope();
for (const [i, param] of fn.params.entries()) {
const arg = args[i];
if (!param.default) expectAny(arg);
this.define(fnScope, param.dest, arg ?? param.default!, true);
let arg = args[i];
if (!param.default) {
expectAny(arg);
} else if (!arg) {
arg = param.default;
}
this.define(fnScope, param.dest, arg, true);
}

const info: CallInfo = { name: fn.name ?? '<anonymous>', pos };
return unWrapRet(await this._run(fn.statements!, fnScope, [...callStack, info]));
return unWrapRet(await this._run(fn.statements, fnScope, [...callStack, info]));
}
}

Expand Down Expand Up @@ -411,7 +415,7 @@

case 'loop': {
// eslint-disable-next-line no-constant-condition
while (true) {

Check warning on line 418 in src/interpreter/index.ts

View workflow job for this annotation

GitHub Actions / lint

Unnecessary conditional, value is always truthy
const v = await this._run(node.statements, scope.createChildScope(), callStack);
if (v.type === 'break') {
if (v.label != null && v.label !== node.label) {
Expand All @@ -430,7 +434,7 @@
}

case 'for': {
if (node.times) {
if ('times' in node) {
const times = await this._eval(node.times, scope, callStack);
if (isControl(times)) {
return times;
Expand All @@ -452,19 +456,19 @@
}
}
} else {
const from = await this._eval(node.from!, scope, callStack);
const from = await this._eval(node.from, scope, callStack);
if (isControl(from)) {
return from;
}
const to = await this._eval(node.to!, scope, callStack);
const to = await this._eval(node.to, scope, callStack);
if (isControl(to)) {
return to;
}
assertNumber(from);
assertNumber(to);
for (let i = from.value; i < from.value + to.value; i++) {
const v = await this._eval(node.for, scope.createChildScope(new Map([
[node.var!, {
[node.var, {
isMutable: false,
value: NUM(i),
}],
Expand Down Expand Up @@ -632,8 +636,9 @@
return target;
}
if (isObject(target)) {
if (target.value.has(node.name)) {
return target.value.get(node.name)!;
const value = target.value.get(node.name);
if (value != null) {
return value;
} else {
return NULL;
}
Expand All @@ -660,8 +665,9 @@
return item;
} else if (isObject(target)) {
assertString(i);
if (target.value.has(i.value)) {
return target.value.get(i.value)!;
const value = target.value.get(i.value);
if (value != null) {
return value;
} else {
return NULL;
}
Expand Down Expand Up @@ -698,28 +704,21 @@
}

case 'fn': {
const params = await Promise.all(node.params.map(async (param) => {
return {
const params: VFnParam[] = [];
for (const param of node.params) {
const defaultValue = param.default ? await this._eval(param.default, scope, callStack) :
param.optional ? NULL :
undefined;
if (defaultValue != null && isControl(defaultValue)) {
return defaultValue;
}
params.push({
dest: param.dest,
default:
param.default ? await this._eval(param.default, scope, callStack) :
param.optional ? NULL :
undefined,
default: defaultValue,
// type: (TODO)
};
}));
const control = params
.map((param) => param.default)
.filter((value) => value != null)
.find(isControl);
if (control != null) {
return control;
});
}
return FN(
params as VUserFn['params'],
node.children,
scope,
);
return FN(params, node.children, scope);
}

case 'block': {
Expand Down Expand Up @@ -887,9 +886,7 @@

let v: Value | Control = NULL;

for (let i = 0; i < program.length; i++) {
const node = program[i]!;

for (const node of program) {
v = await this._eval(node, scope, callStack);
if (v.type === 'return') {
this.log('block:return', { scope: scope.name, val: v.value });
Expand Down Expand Up @@ -946,7 +943,7 @@
public pause(): void {
if (this.pausing) return;
let resolve: () => void;
const promise = new Promise<void>(r => { resolve = () => r(); });

Check warning on line 946 in src/interpreter/index.ts

View workflow job for this annotation

GitHub Actions / lint

Missing return type on function
this.pausing = { promise, resolve: resolve! };
for (const handler of this.pauseHandlers) {
handler();
Expand Down
43 changes: 21 additions & 22 deletions src/interpreter/primitive-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,21 @@
type VWithPP = VNum|VStr|VArr|VError;

const PRIMITIVE_PROPS: {
[key in VWithPP['type']]: { [key: string]: (target: Value) => Value }
[key in VWithPP['type']]: Map<string, (target: Value) => Value>;
} & {
[key in (Exclude<Value, VWithPP>)['type']]?: never;
} = {
num: {
num: new Map(Object.entries({
to_str: (target: VNum): VFn => FN_NATIVE(async (_, _opts) => {
return STR(target.value.toString());
}),

to_hex: (target: VNum): VFn => FN_NATIVE(async (_, _opts) => {
return STR(target.value.toString(16));
}),
},
})),

str: {
str: new Map(Object.entries({
to_num: (target: VStr): VFn => FN_NATIVE(async (_, _opts) => {
const parsed = parseInt(target.value, 10);
if (isNaN(parsed)) return NULL;
Expand Down Expand Up @@ -90,7 +92,7 @@
split: (target: VStr): VFn => FN_NATIVE(async ([splitter], _opts) => {
if (splitter) assertString(splitter);
if (splitter) {
return ARR(target.value.split(splitter ? splitter.value : '').map(s => STR(s)));

Check warning on line 95 in src/interpreter/primitive-props.ts

View workflow job for this annotation

GitHub Actions / lint

Unnecessary conditional, value is always truthy
} else {
return ARR(toArray(target.value).map(s => STR(s)));
}
Expand Down Expand Up @@ -168,9 +170,9 @@

return STR(target.value.padEnd(width.value, s));
}),
},
})),

arr: {
arr: new Map(Object.entries({
len: (target: VArr): VNum => NUM(target.value.length),

push: (target: VArr): VFn => FN_NATIVE(async ([val], _opts) => {
Expand Down Expand Up @@ -219,9 +221,8 @@

filter: (target: VArr): VFn => FN_NATIVE(async ([fn], opts) => {
assertFunction(fn);
const vals = [] as Value[];
for (let i = 0; i < target.value.length; i++) {
const item = target.value[i]!;
const vals: Value[] = [];
for (const [i, item] of target.value.entries()) {
const res = await opts.call(fn, [item, NUM(i)]);
assertBoolean(res);
if (res.value) vals.push(item);
Expand All @@ -243,8 +244,7 @@

find: (target: VArr): VFn => FN_NATIVE(async ([fn], opts) => {
assertFunction(fn);
for (let i = 0; i < target.value.length; i++) {
const item = target.value[i]!;
for (const [i, item] of target.value.entries()) {
const res = await opts.call(fn, [item, NUM(i)]);
assertBoolean(res);
if (res.value) return item;
Expand Down Expand Up @@ -312,7 +312,7 @@
return target;
}),

fill: (target: VArr): VFn => FN_NATIVE(async ([val, st, ed], opts) => {

Check warning on line 315 in src/interpreter/primitive-props.ts

View workflow job for this annotation

GitHub Actions / lint

'opts' is defined but never used. Allowed unused args must match /^_/u
const value = val ?? NULL;
const start = st && (assertNumber(st), st.value);
const end = ed && (assertNumber(ed), ed.value);
Expand All @@ -320,7 +320,7 @@
return target;
}),

repeat: (target: VArr): VFn => FN_NATIVE(async ([times], opts) => {

Check warning on line 323 in src/interpreter/primitive-props.ts

View workflow job for this annotation

GitHub Actions / lint

'opts' is defined but never used. Allowed unused args must match /^_/u
assertNumber(times);
try {
return ARR(Array(times.value).fill(target.value).flat());
Expand All @@ -331,7 +331,7 @@
}
}),

splice: (target: VArr): VFn => FN_NATIVE(async ([idx, rc, vs], opts) => {

Check warning on line 334 in src/interpreter/primitive-props.ts

View workflow job for this annotation

GitHub Actions / lint

'opts' is defined but never used. Allowed unused args must match /^_/u
assertNumber(idx);
const index = (idx.value < -target.value.length) ? 0
: (idx.value < 0) ? target.value.length + idx.value
Expand All @@ -347,7 +347,7 @@
return ARR(result);
}),

flat: (target: VArr): VFn => FN_NATIVE(async ([depth], opts) => {

Check warning on line 350 in src/interpreter/primitive-props.ts

View workflow job for this annotation

GitHub Actions / lint

'opts' is defined but never used. Allowed unused args must match /^_/u
depth = depth ?? NUM(1);
assertNumber(depth);
if (!Number.isInteger(depth.value)) throw new AiScriptRuntimeError('arr.flat expected integer, got non-integer');
Expand Down Expand Up @@ -382,8 +382,7 @@

every: (target: VArr): VFn => FN_NATIVE(async ([fn], opts) => {
assertFunction(fn);
for (let i = 0; i < target.value.length; i++) {
const item = target.value[i]!;
for (const [i, item] of target.value.entries()) {
const res = await opts.call(fn, [item, NUM(i)]);
assertBoolean(res);
if (!res.value) return FALSE;
Expand All @@ -393,8 +392,7 @@

some: (target: VArr): VFn => FN_NATIVE(async ([fn], opts) => {
assertFunction(fn);
for (let i = 0; i < target.value.length; i++) {
const item = target.value[i]!;
for (const [i, item] of target.value.entries()) {
const res = await opts.call(fn, [item, NUM(i)]);
assertBoolean(res);
if (res.value) return TRUE;
Expand Down Expand Up @@ -423,20 +421,21 @@
assertNumber(index);
return target.value.at(index.value) ?? otherwise ?? NULL;
}),
},
})),

error: {
error: new Map(Object.entries({
name: (target: VError): VStr => STR(target.value),

info: (target: VError): Value => target.info ?? NULL,
},
})),
} as const;

export function getPrimProp(target: Value, name: string): Value {
if (Object.hasOwn(PRIMITIVE_PROPS, target.type)) {
const props = PRIMITIVE_PROPS[target.type as VWithPP['type']];
if (Object.hasOwn(props, name)) {
return props[name]!(target);
const props = PRIMITIVE_PROPS[target.type];
if (props != null) {
const prop = props.get(name);
if (prop != null) {
return prop(target);
} else {
throw new AiScriptRuntimeError(`No such prop (${name}) in ${target.type}.`);
}
Expand Down
Loading
Loading