Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 12 additions & 7 deletions src/parser/plugins/validate-type.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { getTypeBySource } from '../../type.js';
import { visitNode } from '../visit.js';
import { AiScriptSyntaxError } from '../../error.js';
import type * as Ast from '../../node.js';

function validateTypeParams(node: Ast.Fn): void {
const typeParamNames = new Set<string>();
for (const typeParam of node.typeParams) {
if (typeParamNames.has(typeParam.name)) {
throw new AiScriptSyntaxError(`type parameter name ${typeParam.name} is duplicate`, node.loc.start);
}
typeParamNames.add(typeParam.name);
}
}

function collectTypeParams(node: Ast.Node, ancestors: Ast.Node[]): Ast.TypeParam[] {
const items = [];
if (node.type === 'fn') {
const typeParamNames = new Set<string>();
for (const typeParam of node.typeParams) {
if (typeParamNames.has(typeParam.name)) {
throw new Error(`type parameter name ${typeParam.name} is duplicate`);
}
typeParamNames.add(typeParam.name);
}
items.push(...node.typeParams);
}
for (let i = ancestors.length - 1; i >= 0; i--) {
Expand All @@ -32,6 +36,7 @@ function validateNode(node: Ast.Node, ancestors: Ast.Node[]): Ast.Node {
break;
}
case 'fn': {
validateTypeParams(node);
for (const param of node.params) {
if (param.argType != null) {
getTypeBySource(param.argType, collectTypeParams(node, ancestors));
Expand Down
14 changes: 10 additions & 4 deletions test/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as assert from 'assert';
import { describe, test } from 'vitest';
import { describe, expect, test } from 'vitest';
import { utils } from '../src';
import { NUM, STR, NULL, ARR, OBJ, BOOL, TRUE, FALSE, ERROR ,FN_NATIVE } from '../src/interpreter/value';
import { AiScriptRuntimeError } from '../src/error';
import { AiScriptRuntimeError, AiScriptSyntaxError } from '../src/error';
import { exe, getMeta, eq } from './testutils';

describe('function types', () => {
Expand Down Expand Up @@ -98,9 +98,15 @@ describe('generics', () => {
});

test.concurrent('duplicate', async () => {
await assert.rejects(() => exe(`
await expect(() => exe(`
@f<T, T>(v: T) {}
`));
`)).rejects.toThrow(AiScriptSyntaxError);
});

test.concurrent('duplicate (no param and ret types)', async () => {
await expect(() => exe(`
@f<T, T>() {}
`)).rejects.toThrow(AiScriptSyntaxError);
});

test.concurrent('empty', async () => {
Expand Down
2 changes: 2 additions & 0 deletions unreleased/fix-duplicate-type-parameter-name-error.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Fix: 関数の型引数の名前が重複した場合に適切なエラーが発生しない問題を修正
- Fix: 関数の引数や返り値に型注釈が無く、型引数の名前が重複した場合にエラーが発生しない問題を修正
Loading