Skip to content

Enhance: TypeScriptの型を厳格化 #891

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

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Changes from all 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
@@ -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;
@@ -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)
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 {
@@ -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

GitHub Actions / lint

Unnecessary conditional, value is always truthy
Error.captureStackTrace(this, AiScriptError);
}
}
@@ -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);
}
}
71 changes: 34 additions & 37 deletions src/interpreter/index.ts
Original file line number Diff line number Diff line change
@@ -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;
@@ -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);
}),
};
@@ -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]));
}
}

@@ -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

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) {
@@ -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;
@@ -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),
}],
@@ -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;
}
@@ -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;
}
@@ -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': {
@@ -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 });
@@ -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

GitHub Actions / lint

Missing return type on function
this.pausing = { promise, resolve: resolve! };
for (const handler of this.pauseHandlers) {
handler();
43 changes: 21 additions & 22 deletions src/interpreter/primitive-props.ts
Original file line number Diff line number Diff line change
@@ -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;
@@ -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

GitHub Actions / lint

Unnecessary conditional, value is always truthy
} else {
return ARR(toArray(target.value).map(s => STR(s)));
}
@@ -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) => {
@@ -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);
@@ -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;
@@ -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

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);
@@ -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

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());
@@ -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

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
@@ -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

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');
@@ -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;
@@ -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;
@@ -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}.`);
}
Loading

Unchanged files with check annotations Beta

assertString(json);
try {
return jsToVal(JSON.parse(json.value));
} catch (e) {

Check warning on line 162 in src/interpreter/lib/std.ts

GitHub Actions / lint

'e' is defined but never used. Allowed unused caught errors must match /^_/u
return ERROR('not_json');
}
}),
assertString(str);
try {
JSON.parse(str.value);
} catch (e) {

Check warning on line 171 in src/interpreter/lib/std.ts

GitHub Actions / lint

'e' is defined but never used. Allowed unused caught errors must match /^_/u
return BOOL(false);
}
return BOOL(true);