Skip to content

Commit d6106b8

Browse files
committed
Merge branch 'bugfix/1.2.1'
2 parents 2f103d2 + c0ca76f commit d6106b8

File tree

15 files changed

+264
-59
lines changed

15 files changed

+264
-59
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
[Read translated version (en)](./translations/en/CHANGELOG.md)
22

3+
# 1.2.1
4+
5+
- Fix: メタデータ構文や属性、AiSONにラベル付きの式を記述してもエラーが発生しない問題を修正
6+
- Fix: 名前空間内において属性の値が評価されない問題を修正
7+
- Fix: 型引数で定義された型に型引数を与えてもエラーが発生しない問題を修正
8+
- Fix: 関数の型引数の名前が重複した場合に適切なエラーが発生しない問題を修正
9+
- Fix: 関数の引数や返り値に型注釈が無く、型引数の名前が重複した場合にエラーが発生しない問題を修正
10+
- Fix: break文のもつ値の式について不正な変数やreturn文、break文、continue文、型注釈がある場合に文法エラーにならない問題を修正
11+
- Fix: 型注釈における型引数や関数型の返り値に不正な型注釈がある場合に文法エラーにならない問題を修正
12+
313
# 1.2.0
414

515
- 関数`Obj:from_kvs`を追加

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
"description": "AiScript implementation",
66
"author": "syuilo <[email protected]>",
77
"license": "MIT",
8-
"repository": "https://github.com/syuilo/aiscript.git",
8+
"repository": {
9+
"type": "git",
10+
"url": "git+https://github.com/syuilo/aiscript.git"
11+
},
912
"homepage": "https://aiscript-dev.github.io/",
1013
"bugs": "https://github.com/syuilo/aiscript/issues",
1114
"exports": {
@@ -55,3 +58,4 @@
5558
"uuid": "11.1.0"
5659
}
5760
}
61+

src/interpreter/index.ts

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,9 @@ export class Interpreter {
258258

259259
const value = await this._eval(node.expr, nsScope, []);
260260
assertValue(value);
261+
262+
await this.evalAndSetAttr(node.attr, value, scope, []);
263+
261264
if (
262265
node.expr.type === 'fn'
263266
&& isFunction(value)
@@ -302,6 +305,9 @@ export class Interpreter {
302305

303306
const value = this._evalSync(node.expr, nsScope, []);
304307
assertValue(value);
308+
309+
this.evalAndSetAttrSync(node.attr, value, scope, []);
310+
305311
if (
306312
node.expr.type === 'fn'
307313
&& isFunction(value)
@@ -648,18 +654,7 @@ export class Interpreter {
648654
if (isControl(value)) {
649655
return value;
650656
}
651-
if (node.attr.length > 0) {
652-
const attrs: Value['attr'] = [];
653-
for (const nAttr of node.attr) {
654-
const value = await this._eval(nAttr.value, scope, callStack);
655-
assertValue(value);
656-
attrs.push({
657-
name: nAttr.name,
658-
value,
659-
});
660-
}
661-
value.attr = attrs;
662-
}
657+
await this.evalAndSetAttr(node.attr, value, scope, callStack);
663658
if (
664659
node.expr.type === 'fn'
665660
&& node.dest.type === 'identifier'
@@ -1192,18 +1187,7 @@ export class Interpreter {
11921187
if (isControl(value)) {
11931188
return value;
11941189
}
1195-
if (node.attr.length > 0) {
1196-
const attrs: Value['attr'] = [];
1197-
for (const nAttr of node.attr) {
1198-
const value = this._evalSync(nAttr.value, scope, callStack);
1199-
assertValue(value);
1200-
attrs.push({
1201-
name: nAttr.name,
1202-
value,
1203-
});
1204-
}
1205-
value.attr = attrs;
1206-
}
1190+
this.evalAndSetAttrSync(node.attr, value, scope, callStack);
12071191
if (
12081192
node.expr.type === 'fn'
12091193
&& node.dest.type === 'identifier'
@@ -1666,6 +1650,38 @@ export class Interpreter {
16661650
this.unpauseHandlers = [];
16671651
}
16681652

1653+
@autobind
1654+
private async evalAndSetAttr(attr: Ast.Attribute[], value: Value, scope: Scope, callStack: readonly CallInfo[]): Promise<void> {
1655+
if (attr.length > 0) {
1656+
const attrs: Value['attr'] = [];
1657+
for (const nAttr of attr) {
1658+
const value = await this._eval(nAttr.value, scope, callStack);
1659+
assertValue(value);
1660+
attrs.push({
1661+
name: nAttr.name,
1662+
value,
1663+
});
1664+
}
1665+
value.attr = attrs;
1666+
}
1667+
}
1668+
1669+
@autobind
1670+
private evalAndSetAttrSync(attr: Ast.Attribute[], value: Value, scope: Scope, callStack: readonly CallInfo[]): void {
1671+
if (attr.length > 0) {
1672+
const attrs: Value['attr'] = [];
1673+
for (const nAttr of attr) {
1674+
const value = this._evalSync(nAttr.value, scope, callStack);
1675+
assertValue(value);
1676+
attrs.push({
1677+
name: nAttr.name,
1678+
value,
1679+
});
1680+
}
1681+
value.attr = attrs;
1682+
}
1683+
}
1684+
16691685
@autobind
16701686
private define(scope: Scope, dest: Ast.Expression, value: Value, isMutable: boolean): void {
16711687
switch (dest.type) {

src/parser/plugins/validate-type.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
import { getTypeBySource } from '../../type.js';
22
import { visitNode } from '../visit.js';
3+
import { AiScriptSyntaxError } from '../../error.js';
34
import type * as Ast from '../../node.js';
45

6+
function validateTypeParams(node: Ast.Fn): void {
7+
const typeParamNames = new Set<string>();
8+
for (const typeParam of node.typeParams) {
9+
if (typeParamNames.has(typeParam.name)) {
10+
throw new AiScriptSyntaxError(`type parameter name ${typeParam.name} is duplicate`, node.loc.start);
11+
}
12+
typeParamNames.add(typeParam.name);
13+
}
14+
}
15+
516
function collectTypeParams(node: Ast.Node, ancestors: Ast.Node[]): Ast.TypeParam[] {
617
const items = [];
718
if (node.type === 'fn') {
8-
const typeParamNames = new Set<string>();
9-
for (const typeParam of node.typeParams) {
10-
if (typeParamNames.has(typeParam.name)) {
11-
throw new Error(`type parameter name ${typeParam.name} is duplicate`);
12-
}
13-
typeParamNames.add(typeParam.name);
14-
}
1519
items.push(...node.typeParams);
1620
}
1721
for (let i = ancestors.length - 1; i >= 0; i--) {
@@ -32,6 +36,7 @@ function validateNode(node: Ast.Node, ancestors: Ast.Node[]): Ast.Node {
3236
break;
3337
}
3438
case 'fn': {
39+
validateTypeParams(node);
3540
for (const param of node.params) {
3641
if (param.argType != null) {
3742
getTypeBySource(param.argType, collectTypeParams(node, ancestors));

src/parser/syntaxes/expressions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ function parseAtom(s: ITokenStream, isStatic: boolean): Ast.Expression {
288288
return expr;
289289
}
290290
case TokenKind.Sharp: {
291+
if (isStatic) break;
291292
return parseExprWithLabel(s);
292293
}
293294
}

src/parser/visit.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@ function visitNodeInner(node: Ast.Node, fn: (node: Ast.Node, ancestors: Ast.Node
152152
result.target = visitNodeInner(result.target, fn, ancestors) as Ast.Prop['target'];
153153
break;
154154
}
155+
case 'break': {
156+
if (result.expr != null) {
157+
result.expr = visitNodeInner(result.expr, fn, ancestors) as Ast.Break['expr'];
158+
}
159+
break;
160+
}
155161
case 'ns': {
156162
for (let i = 0; i < result.members.length; i++) {
157163
result.members[i] = visitNodeInner(result.members[i]!, fn, ancestors) as (typeof result.members)[number];
@@ -208,10 +214,17 @@ function visitNodeInner(node: Ast.Node, fn: (node: Ast.Node, ancestors: Ast.Node
208214
break;
209215
}
210216

217+
case 'namedTypeSource': {
218+
if (result.inner != null) {
219+
result.inner = visitNodeInner(result.inner, fn, ancestors) as Ast.NamedTypeSource['inner'];
220+
}
221+
break;
222+
}
211223
case 'fnTypeSource': {
212224
for (let i = 0; i < result.params.length; i++) {
213225
result.params[i] = visitNodeInner(result.params[i]!, fn, ancestors) as Ast.FnTypeSource['params'][number];
214226
}
227+
result.result = visitNodeInner(result.result, fn, ancestors) as Ast.FnTypeSource['result'];
215228
break;
216229
}
217230
case 'unionTypeSource': {
@@ -220,6 +233,23 @@ function visitNodeInner(node: Ast.Node, fn: (node: Ast.Node, ancestors: Ast.Node
220233
}
221234
break;
222235
}
236+
237+
case 'str':
238+
case 'num':
239+
case 'bool':
240+
case 'null':
241+
case 'identifier':
242+
case 'attr':
243+
case 'continue':
244+
case 'meta': {
245+
break; // nop
246+
}
247+
248+
default: {
249+
// exhaustiveness check
250+
result satisfies never;
251+
throw new Error('invalid node type');
252+
}
223253
}
224254

225255
ancestors.pop();

src/type.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ export function getTypeNameBySource(typeSource: Ast.TypeSource): string {
168168
export function getTypeBySource(typeSource: Ast.TypeSource, typeParams?: readonly Ast.TypeParam[]): Type {
169169
if (typeSource.type === 'namedTypeSource') {
170170
const typeParam = typeParams?.find((param) => param.name === typeSource.name);
171-
if (typeParam != null) {
171+
if (typeParam != null && typeSource.inner == null) {
172172
return T_PARAM(typeParam.name);
173173
}
174174

test/aison.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ greet()`)).toThrow();
6262
expect(() => AiSON.parse('{key: (3 + 5)}')).toThrow();
6363
});
6464

65+
test.concurrent('not allowed: labeled expression', () => {
66+
expect(() => AiSON.parse('#label: eval { 1 }')).toThrow();
67+
});
68+
6569
test.concurrent('not allowed: multiple statements (string)', () => {
6670
expect(() => AiSON.parse(`"hello"
6771

test/identifiers.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,10 +253,30 @@ const sampleCodes = Object.entries<[(definedName: string, referredName: string)
253253
}
254254
`, NULL],
255255

256+
break: [(definedName, referredName) =>
257+
`
258+
<: #label: eval {
259+
break #label eval {
260+
let ${definedName} = "ai"
261+
${referredName}
262+
}
263+
}
264+
`, STR('ai')],
265+
256266
typeParam: [(definedName, referredName) =>
257267
`
258268
@f<${definedName}>(x): ${referredName} { x }
259269
`, NULL],
270+
271+
innerType: [(definedName, referredName) =>
272+
`
273+
let x: arr<@<${definedName}>() => ${referredName}> = []
274+
`, NULL],
275+
276+
returnType: [(definedName, referredName) =>
277+
`
278+
let x: @() => @<${definedName}>() => ${referredName} = @() {}
279+
`, NULL],
260280
});
261281

262282
const parser = new Parser();
@@ -278,15 +298,18 @@ describe.each(
278298
parser.parse(sampleCode(wordCat, wordCat));
279299
});
280300

281-
test.concurrent.each(
301+
// グローバルの expect を使用すると expect.hasAssertions() が失敗するときがあるので、
302+
// ローカルの expect を使用する
303+
test.concurrent.for(
282304
identifierCases
283-
)('%s is allowed: %s', async (word, allowed) => {
305+
)('%s is allowed: %s', async ([word, allowed], { expect }) => {
284306
expect.hasAssertions();
285307
if (allowed) {
286308
const res = await exe(sampleCode(word, word));
287-
eq(res, expected);
309+
eq(res, expected, expect);
288310
} else {
289311
expect(() => parser.parse(sampleCode(word, word))).toThrow(AiScriptSyntaxError);
312+
await Promise.resolve(); // https://github.com/vitest-dev/vitest/issues/4750
290313
}
291314
});
292315

test/index.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55

66
import * as assert from 'assert';
7-
import { describe, test } from 'vitest';
7+
import { describe, expect, test } from 'vitest';
88
import { Parser, Interpreter, Ast } from '../src';
99
import { NUM, STR, NULL, ARR, OBJ, BOOL, TRUE, FALSE, ERROR ,FN_NATIVE } from '../src/interpreter/value';
1010
import { AiScriptSyntaxError, AiScriptRuntimeError, AiScriptIndexOutOfRangeError } from '../src/error';
@@ -948,6 +948,16 @@ describe('Attribute', () => {
948948
const attr = member.attr[0];
949949
assert.equal(attr.name, 'test');
950950
});
951+
952+
test.concurrent('non-static expression is not allowed', async () => {
953+
const parser = new Parser();
954+
expect(() => {
955+
parser.parse(`
956+
#[x #label: eval { 1 }]
957+
@f() {}
958+
`);
959+
}).toThrow();
960+
});
951961
});
952962

953963
describe('Location', () => {

0 commit comments

Comments
 (0)