|
1 | 1 | // AST-based Pine Script Validator with Type Checking and Scope Analysis |
2 | 2 | import { Program, Statement, Expression, CallExpression, CallArgument, Identifier, Literal } from './ast'; |
3 | 3 | import { V6_FUNCTIONS, V6_NAMESPACES, PineItem } from '../../v6/v6-manual'; |
4 | | -import { PINE_FUNCTIONS_MERGED } from '../../v6/parameter-requirements-merged'; |
5 | | -import type { FunctionSignatureSpec, FunctionParameter } from '../../v6/parameter-requirements-generated'; |
| 4 | +import { PINE_FUNCTIONS_MERGED } from '../../v6/functions'; |
| 5 | +import type { FunctionSignatureSpec, FunctionParameter, FunctionOverload } from '../../v6/functions'; |
6 | 6 | import { PineType, TypeChecker } from './typeSystem'; |
7 | 7 | import { SymbolTable, Symbol as SymbolInfo } from './symbolTable'; |
8 | 8 |
|
@@ -877,6 +877,20 @@ export class AstValidator { |
877 | 877 | } |
878 | 878 | } |
879 | 879 |
|
| 880 | + // Check if function has overloads |
| 881 | + const spec = PINE_FUNCTIONS_MERGED[functionName]; |
| 882 | + if (spec && spec.overloads && spec.overloads.length > 0) { |
| 883 | + // Try to match against all overloads |
| 884 | + const result = this.tryMatchOverloads(call, functionName, spec.overloads, providedArgs, positionalArgs); |
| 885 | + if (!result.matched) { |
| 886 | + // Report errors from the best matching overload |
| 887 | + for (const error of result.errors) { |
| 888 | + this.addError(error.line, error.column, error.length, error.message, error.severity); |
| 889 | + } |
| 890 | + } |
| 891 | + return; |
| 892 | + } |
| 893 | + |
880 | 894 | // Check argument count |
881 | 895 | const totalCount = signature.parameters.length; |
882 | 896 |
|
@@ -978,6 +992,122 @@ export class AstValidator { |
978 | 992 | this.validateSpecialCases(call, functionName, args); |
979 | 993 | } |
980 | 994 |
|
| 995 | + /** |
| 996 | + * 尝试匹配所有函数重载 |
| 997 | + * 返回第一个匹配的重载,或所有重载都不匹配时返回最佳匹配的错误信息 |
| 998 | + */ |
| 999 | + private tryMatchOverloads( |
| 1000 | + call: CallExpression, |
| 1001 | + functionName: string, |
| 1002 | + overloads: FunctionOverload[], |
| 1003 | + providedArgs: Map<string, { arg: CallArgument; type: PineType }>, |
| 1004 | + positionalArgs: { arg: CallArgument; type: PineType }[] |
| 1005 | + ): { matched: boolean; errors: Array<{ line: number; column: number; length: number; message: string; severity: DiagnosticSeverity }> } { |
| 1006 | + |
| 1007 | + interface OverloadMatchResult { |
| 1008 | + overload: FunctionOverload; |
| 1009 | + errors: Array<{ line: number; column: number; length: number; message: string; severity: DiagnosticSeverity }>; |
| 1010 | + score: number; // 匹配分数,越低越好 |
| 1011 | + } |
| 1012 | + |
| 1013 | + const results: OverloadMatchResult[] = []; |
| 1014 | + |
| 1015 | + // 尝试每个重载 |
| 1016 | + for (const overload of overloads) { |
| 1017 | + const errors: Array<{ line: number; column: number; length: number; message: string; severity: DiagnosticSeverity }> = []; |
| 1018 | + let score = 0; |
| 1019 | + |
| 1020 | + // 构建参数列表 |
| 1021 | + const allParams = [ |
| 1022 | + ...overload.requiredParams.map(name => ({ name, optional: false })), |
| 1023 | + ...overload.optionalParams.map(name => ({ name, optional: true })) |
| 1024 | + ]; |
| 1025 | + |
| 1026 | + // 检查参数数量 |
| 1027 | + const providedCount = positionalArgs.length + providedArgs.size; |
| 1028 | + const requiredCount = overload.requiredParams.length; |
| 1029 | + const maxCount = allParams.length; |
| 1030 | + |
| 1031 | + if (positionalArgs.length > maxCount) { |
| 1032 | + errors.push({ |
| 1033 | + line: call.line, |
| 1034 | + column: call.column, |
| 1035 | + length: functionName.length, |
| 1036 | + message: `Too many arguments for '${functionName}'. Expected ${maxCount}, got ${positionalArgs.length}`, |
| 1037 | + severity: DiagnosticSeverity.Error |
| 1038 | + }); |
| 1039 | + score += 100; // 严重错误 |
| 1040 | + } |
| 1041 | + |
| 1042 | + // 检查每个参数 |
| 1043 | + for (let i = 0; i < allParams.length; i++) { |
| 1044 | + const param = allParams[i]; |
| 1045 | + |
| 1046 | + // 检查命名参数 |
| 1047 | + const namedArg = providedArgs.get(param.name); |
| 1048 | + if (namedArg) { |
| 1049 | + continue; // 命名参数提供了 |
| 1050 | + } |
| 1051 | + |
| 1052 | + // 检查位置参数 |
| 1053 | + if (i < positionalArgs.length) { |
| 1054 | + continue; // 位置参数提供了 |
| 1055 | + } |
| 1056 | + |
| 1057 | + // 参数未提供 |
| 1058 | + if (!param.optional) { |
| 1059 | + errors.push({ |
| 1060 | + line: call.line, |
| 1061 | + column: call.column, |
| 1062 | + length: functionName.length, |
| 1063 | + message: `Missing required parameter '${param.name}' for function '${functionName}'`, |
| 1064 | + severity: DiagnosticSeverity.Error |
| 1065 | + }); |
| 1066 | + score += 10; // 缺少必需参数 |
| 1067 | + } |
| 1068 | + } |
| 1069 | + |
| 1070 | + // 检查无效的命名参数 |
| 1071 | + for (const [name] of providedArgs.entries()) { |
| 1072 | + if (!allParams.some(p => p.name === name)) { |
| 1073 | + errors.push({ |
| 1074 | + line: call.line, |
| 1075 | + column: call.column, |
| 1076 | + length: name.length, |
| 1077 | + message: `Invalid parameter '${name}' for this overload`, |
| 1078 | + severity: DiagnosticSeverity.Error |
| 1079 | + }); |
| 1080 | + score += 5; // 无效参数 |
| 1081 | + } |
| 1082 | + } |
| 1083 | + |
| 1084 | + results.push({ overload, errors, score }); |
| 1085 | + |
| 1086 | + // 如果找到完美匹配,立即返回 |
| 1087 | + if (errors.length === 0) { |
| 1088 | + return { matched: true, errors: [] }; |
| 1089 | + } |
| 1090 | + } |
| 1091 | + |
| 1092 | + // 没有完美匹配,返回最佳匹配的错误 |
| 1093 | + results.sort((a, b) => a.score - b.score); |
| 1094 | + const bestMatch = results[0]; |
| 1095 | + |
| 1096 | + // 如果有多个重载,在错误消息中提示所有可能的签名 |
| 1097 | + if (overloads.length > 1 && bestMatch.errors.length > 0) { |
| 1098 | + const signatures = overloads.map(o => o.signature).join('\n '); |
| 1099 | + bestMatch.errors.push({ |
| 1100 | + line: call.line, |
| 1101 | + column: call.column, |
| 1102 | + length: functionName.length, |
| 1103 | + message: `Available overloads:\n ${signatures}`, |
| 1104 | + severity: DiagnosticSeverity.Information |
| 1105 | + }); |
| 1106 | + } |
| 1107 | + |
| 1108 | + return { matched: false, errors: bestMatch.errors }; |
| 1109 | + } |
| 1110 | + |
981 | 1111 | private validateSpecialCases( |
982 | 1112 | call: CallExpression, |
983 | 1113 | functionName: string, |
|
0 commit comments