From 5808a76b230b59315cdb9178b7ea395dda4006fc Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Sun, 27 Sep 2026 14:26:42 +0800 Subject: [PATCH] =?UTF-8?q?fix(vipeye-internal):=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=B3=A8=E8=A7=A3=E5=86=85=20static=20=E5=89=8D=E7=BC=80?= =?UTF-8?q?=EF=BC=9B*args=20=E5=9C=A8=E6=9C=AA=20import=20t=20=E6=97=B6?= =?UTF-8?q?=E4=BB=8D=E6=98=AF=20va=5Flist=20=E6=8C=87=E9=92=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `x: static T = v`(ViperOS VKernel/Kernel/drivers/** 全仓写法,如 `buf_head: static uint32 = 0`)脱糖为与语句起始形态相同的 `t.CStatic | T`, 并在声明节点打 isViperStatic,跨模块取参检查同判据;此前 `static` 在注解里 被当普通名引用,误报「"static" is not defined」。软关键字边界不变 (`static: int` / `static = 1` 仍按标识符),tmLanguage 一并高亮注解内 static - `getViperCPtrType` 增加 builtins `rawptr` 回退:文件没有 `import t` 时 `*args` 不再退回 Python tuple 语义,`viperlib.vsnprintf(&buf, 1024, fmt, args)` 不再误报「tuple 不能赋给参数 ap(str)」(与 T* 注解 / [SIZE]T 同机制) - 回归用例 +3:注解内 static 解析/展示/跨模块拦截 + 软关键字边界、 未 import t 的 *args 透传;jest 805 -> 808 --- .gitignore | 1 + .../src/analyzer/typeEvaluator.ts | 16 ++++ packages/vipeye-internal/src/parser/parser.ts | 69 ++++++++++++++- .../src/tests/viperStaticModifier.test.ts | 86 +++++++++++++++++++ .../src/tests/viperVariadicArgs.test.ts | 63 ++++++++++++++ packages/vscode-vipeye/CHANGELOG.md | 4 +- .../syntaxes/viper.tmLanguage.json | 2 +- 7 files changed, 238 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index cb4c53a4..6a176212 100644 --- a/.gitignore +++ b/.gitignore @@ -154,6 +154,7 @@ packages/vipeye-internal/src/tests/__viper_type_binop_bound__/ packages/vipeye-internal/src/tests/__viper_const_ptr_deref__/ packages/vipeye-internal/src/tests/__viper_const_ptr_index__/ packages/vipeye-internal/src/tests/__viper_const_ptr_iter__/ +packages/vipeye-internal/src/tests/__viper_variadic_noimport__/ # perfCompare.py cached commit bundles /build/perfCompare/ diff --git a/packages/vipeye-internal/src/analyzer/typeEvaluator.ts b/packages/vipeye-internal/src/analyzer/typeEvaluator.ts index f3bbf041..c2b61dc6 100644 --- a/packages/vipeye-internal/src/analyzer/typeEvaluator.ts +++ b/packages/vipeye-internal/src/analyzer/typeEvaluator.ts @@ -8746,6 +8746,22 @@ export function createTypeEvaluator( } } } + + // 回退:builtins stub 的编译器内置 `rawptr`(= t.CPtr,见 builtins.vpi)。 + // 文件没 `import t` 时(如 ViperOS 的 serial.vp)也要能用 `*args` 的 + // va_list 指针语义,与 `T*` 注解 / `[SIZE]T` 一致(不强制源码 import t)。 + const rawptrSymbolWithScope = lookUpSymbolRecursive(node, 'rawptr', /* honorCodeFlow */ true); + if (rawptrSymbolWithScope) { + const rawptrType = getEffectiveTypeOfSymbolForUsage( + rawptrSymbolWithScope.symbol, + /* usageNode */ undefined, + /* useLastDecl */ true + ).type; + if (rawptrType && isInstantiableClass(rawptrType)) { + return convertToInstance(ClassType.specialize(rawptrType, [UnknownType.create()])); + } + } + return undefined; } diff --git a/packages/vipeye-internal/src/parser/parser.ts b/packages/vipeye-internal/src/parser/parser.ts index 6acd9236..0e5d84f4 100644 --- a/packages/vipeye-internal/src/parser/parser.ts +++ b/packages/vipeye-internal/src/parser/parser.ts @@ -257,6 +257,10 @@ export class Parser { // 不因 `*` 之后是行尾还是下一行内容而在两条解析路径间摇摆 // (见 _isAtViperPointerTypeSuffix 对换行的处理)。 private _isParsingViperTypeDescriptorInner = false; + // Viper 扩展:本次注解解析里消费过 `static` 类型描述符(`x: static T = v`)。 + // 注解解析结束后由 _parseExpressionStatement 读取一次并清零:命中时把该声明 + // 标为 static 符号(isViperStatic),供「禁止跨模块取参」检查使用。 + private _viperStaticDescriptorInAnnotation = false; // Viper 扩展:当前所处的类声明形态,用于校验 vtable 修饰方法的出现位置: // `virtual def` 仅允许位于 class / virtual class 内; // `novirtual def` 允许位于 class / virtual class / novirtual class 内。 @@ -5121,8 +5125,18 @@ export class Parser { if (this._consumeTokenIfType(TokenType.Colon)) { // 保留原始赋值目标(NameNode),后续 `*=` 拆分重包装时避免嵌套 TypeAnnotationNode。 const targetExpr = leftExpr; + // Viper 扩展:注解内 `static` 描述符(`x: static T = v`)→ 该声明是 static 符号。 + this._viperStaticDescriptorInAnnotation = false; annotationExpr = this._parseTypeAnnotation(); - leftExpr = TypeAnnotationNode.create(targetExpr, annotationExpr); + const annotationNode = TypeAnnotationNode.create(targetExpr, annotationExpr); + leftExpr = annotationNode; + if (this._viperStaticDescriptorInAnnotation) { + annotationNode.d.isViperStatic = true; + if (targetExpr.nodeType === ParseNodeType.Name) { + targetExpr.d.isViperStatic = true; + } + this._viperStaticDescriptorInAnnotation = false; + } if ( !this._parseOptions.isStubFile && @@ -5367,6 +5381,10 @@ export class Parser { if (attributeNode) { return attributeNode; } + const staticNode = this._parseViperStaticTypeDescriptorPrefix(); + if (staticNode) { + return staticNode; + } const descriptorNode = this._parseViperTypeDescriptorPrefix(); if (descriptorNode) { return descriptorNode; @@ -5374,6 +5392,55 @@ export class Parser { return this._parseViperBracketArrayTypeIfNeeded(); } + // Viper 扩展:注解位置的 `static` 类型描述符前缀 `x: static T = v`。 + // 与语句起始形态 `static x: T = v`(见 _parseViperStaticAnnotatedStatement) + // 脱糖为同一形状 `t.CStatic | T`(存储类标记:TypeSet 语义里只作修饰, + // 不改变数据类型;展示为 `static T`)。ViperOS 全仓写法都是注解内前缀, + // 故两种位置都支持。 + // + // `static` 是**软关键字**:只有「后随类型原子起点」时才在此消费, + // 其余(`x: static`、`def f(static: int)`、`self.static`、`x: static.y`) + // 仍按普通标识符处理(与 TPC `_IsStaticModifier` 的软关键字边界同口径)。 + // 消费成功后置 _viperStaticDescriptorInAnnotation,供 _parseExpressionStatement + // 把该变量声明标为 static 符号(禁止跨模块取参)。 + private _parseViperStaticTypeDescriptorPrefix(): ExpressionNode | undefined { + if (this._parseOptions.dialect !== 'viper') { + return undefined; + } + + const staticToken = this._peekToken(); + if (staticToken.type !== TokenType.Identifier) { + return undefined; + } + if (this._fileContents!.slice(staticToken.start, staticToken.start + staticToken.length) !== 'static') { + return undefined; + } + if ( + !this._isViperTypeAtomStartToken(this._peekToken(1)) && + !this._isAtViperAttributePrefix(1) + ) { + return undefined; + } + + this._getNextToken(); // 消费 static + + // 同 const / volatile:解析被修饰的主体期间禁止就地消费指针后缀 `*`, + // 使 `static int*` 恒为 `(CStatic | int)*`。 + const wasDescriptorInner = this._isParsingViperTypeDescriptorInner; + this._isParsingViperTypeDescriptorInner = true; + const innerType = this._parseViperTypeAnnotationCore(); + this._isParsingViperTypeDescriptorInner = wasDescriptorInner; + + this._viperStaticDescriptorInAnnotation = true; + + return BinaryOperationNode.create( + this._createViperStaticMarker(staticToken.start), + innerType, + this._createViperSyntheticBarToken(staticToken), + OperatorType.BitwiseOr + ); + } + // Viper 扩展:注解位置的 `__attribute__(...)` 修饰器前缀。 // 解析层脱糖为等价的联合类型注解,与手写 `T | t.attr.section(".x")` 的 AST 同形 // (编译器按 AST 扫描属性:TPC HandlesAssign._ExtractGlobalAttrs / diff --git a/packages/vipeye-internal/src/tests/viperStaticModifier.test.ts b/packages/vipeye-internal/src/tests/viperStaticModifier.test.ts index a090071e..ad652bc3 100644 --- a/packages/vipeye-internal/src/tests/viperStaticModifier.test.ts +++ b/packages/vipeye-internal/src/tests/viperStaticModifier.test.ts @@ -263,3 +263,89 @@ def f(static: int) -> int: require('fs').rmSync(testRoot, { recursive: true, force: true }); } }); + +// ViperOS 全仓实际写法是**注解内前缀**(`buf_head: static uint32 = 0`, +// 见 VKernel/Kernel/drivers/**),此前 LSP 只认语句起始形态 `static x: T`, +// 导致 `static` 在注解里被当普通名引用 → 误报「"static" is not defined」。 +test('注解内 static 前缀(x: static T = v)解析、展示与跨模块拦截', () => { + const { program, configOptions, uris, testRoot, serviceProvider } = buildProgram({ + 'lib.vp': `buf_head: static int = 0 + +def touch() -> int: + return buf_head +`, + 'main.vp': `import lib + +def use() -> int: + return lib.buf_head +`, + }); + + try { + const libDiags = getDiagnostics(program, configOptions, uris['lib.vp']); + console.log('=== 注解内 static 诊断 ==='); + libDiags.forEach((d) => console.log(` L${d.line} [category=${d.category}] ${d.message}`)); + + assert.strictEqual( + libDiags.filter((d) => d.category === 0).length, + 0, + `注解内 static 不应报语法/未定义错误:${JSON.stringify(libDiags)}` + ); + assert.strictEqual( + libDiags.filter((d) => d.message.includes('static 符号')).length, + 0, + `同模块自引用不应报 static 跨模块错误:${JSON.stringify(libDiags)}` + ); + + // 注解脱糖为 `t.CStatic | int`,与语句起始形态一样展示为 `static int`。 + const types = collectAnnotatedVarTypes(program, uris['lib.vp']); + console.log('=== 注解变量类型 ==='); + types.forEach((v, k) => console.log(` ${k}: ${v}`)); + assert( + types.get('buf_head')!.includes('static') && types.get('buf_head')!.includes('int'), + `buf_head 应展示为 static int,实际: ${types.get('buf_head')}` + ); + + // 跨模块取参仍须报错(与语句起始形态同判据)。 + const mainDiags = getDiagnostics(program, configOptions, uris['main.vp']); + console.log('=== 跨模块取参诊断 ==='); + mainDiags.forEach((d) => console.log(` L${d.line} [category=${d.category}] ${d.message}`)); + const staticDiags = mainDiags.filter((d) => d.message.includes('static 符号')); + assert.strictEqual( + staticDiags.length, + 1, + `lib.buf_head 取用应报一条 static 跨模块错误:${JSON.stringify(mainDiags)}` + ); + assert( + staticDiags[0].message.includes('lib.buf_head'), + `诊断文案应含 lib.buf_head:${JSON.stringify(staticDiags)}` + ); + } finally { + program.dispose(); + serviceProvider.dispose(); + require('fs').rmSync(testRoot, { recursive: true, force: true }); + } +}); + +test('注解内 static 仍受软关键字边界约束:static 单独作为注解名不变', () => { + const code = `static: int = 0 +static = 1 +`; + const { program, configOptions, uris, testRoot, serviceProvider } = buildProgram({ 'main.vp': code }); + + try { + const diagnostics = getDiagnostics(program, configOptions, uris['main.vp']); + console.log('=== 注解内 static 边界诊断 ==='); + diagnostics.forEach((d) => console.log(` L${d.line} [category=${d.category}] ${d.message}`)); + + assert.strictEqual( + diagnostics.filter((d) => d.category === 0).length, + 0, + `static 作为普通标识符/变量名不应报语法错误:${JSON.stringify(diagnostics)}` + ); + } finally { + program.dispose(); + serviceProvider.dispose(); + require('fs').rmSync(testRoot, { recursive: true, force: true }); + } +}); diff --git a/packages/vipeye-internal/src/tests/viperVariadicArgs.test.ts b/packages/vipeye-internal/src/tests/viperVariadicArgs.test.ts index 6fe89223..6239c170 100644 --- a/packages/vipeye-internal/src/tests/viperVariadicArgs.test.ts +++ b/packages/vipeye-internal/src/tests/viperVariadicArgs.test.ts @@ -128,6 +128,69 @@ test('viperlib.vp 中 *args 透传 vsnprintf 不报 tuple 类型错误', () => { serviceProvider.dispose(); }); +/* + * ViperOS VKernel/Kernel/drivers/serial/uart/serial.vp 场景: + * export def printf(fmt: const str, *args) -> int: + * ... + * n: int = viperlib.vsnprintf(&buf, 1024, fmt, args) + * 该文件**没有** `import t`,此前 getViperCPtrType 只认源码可见的 `t.CPtr`, + * 取不到就退回 Python 语义的 tuple → 误报 + * 「无法将"tuple"类型的参数分配给函数"vsnprintf"中类型为"str"的参数"ap"」。 + * 修法:回退 builtins stub 的 `rawptr`(= t.CPtr,与 `T*` 注解同机制)。 + */ +test('未 import t 的文件里 *args 仍是 va_list 指针(serial.vp 场景)', () => { + const fs = require('fs'); + const testRoot = path.join(__dirname, '__viper_variadic_noimport__'); + fs.rmSync(testRoot, { recursive: true, force: true }); + fs.mkdirSync(testRoot, { recursive: true }); + const filePath = path.join(testRoot, 'main.vp'); + fs.writeFileSync( + filePath, + `import viperlib + +export def printf(fmt: const str, *args) -> int: + if fmt is None: + return 0 + buf: [1024]char + n: int = viperlib.vsnprintf(&buf, 1024, fmt, args) + return n +`, + 'utf8' + ); + + const { program, serviceProvider, uri, configOptions } = buildProgram(filePath, testRoot, [ + TRANSPYC_INCLUDES, + ]); + + try { + const diagnostics = getDiagnostics(program, configOptions, uri); + console.log('=== *args 无 import t 诊断 ==='); + diagnostics.forEach((d) => console.log(` ${d.range} [category=${d.category}] ${d.message}`)); + + const variadicErrors = diagnostics.filter( + (d) => + d.message.includes('tuple') || + d.message.includes('参数 "ap"') || + d.message.includes('is not defined') + ); + assert.strictEqual( + variadicErrors.length, + 0, + `*args 应绑定为 va_list 指针(rawptr),不应报 tuple/未定义:${JSON.stringify(variadicErrors)}` + ); + + assert.strictEqual( + diagnostics.filter((d) => d.category === 0).length, + 0, + `不应有任何 error:${JSON.stringify(diagnostics)}` + ); + } finally { + program.dispose(); + serviceProvider.dispose(); + fs.rmSync(testRoot, { recursive: true, force: true }); + } +}); + test('memhub.vp 中 viperio.Buf.__sizeof__() 不报 Argument missing for parameter "self"', () => { const { program, serviceProvider, uri, configOptions } = buildProgram(MEMHUB_PATH); diff --git a/packages/vscode-vipeye/CHANGELOG.md b/packages/vscode-vipeye/CHANGELOG.md index 3d5da67b..a218be55 100644 --- a/packages/vscode-vipeye/CHANGELOG.md +++ b/packages/vscode-vipeye/CHANGELOG.md @@ -29,11 +29,13 @@ - Lowercase **`callable[[A, B], R]`** is now supported in a type annotation and means exactly `t.Callable[[A, B], R]` — all three spellings (`callable[...]`, `Callable[...]`, `t.Callable[...]`) and all the usual argument forms (`callable[[int, str], bool]`, `callable[..., bool]`, `callable[[], bool]`, as a parameter, return or variable annotation) evaluate to the same callable type. The parser rewrites the bare name `callable` to `Callable` **only in type-annotation position**, so the built-in function is untouched: `callable(x)` / `if callable(x):` still behave as before. `Callable` is declared in the built-in `builtins.vpi` stub (`Callable: t.CTypedef = t.Callable`, next to `rawptr` / `CConst`), so no `import t` is required. Previously `callable[[int], bool]` silently degraded to the type of the `callable` *function* (the Viper-dialect rule that ignores explicit type arguments on a callable), and bare `Callable` was reported as undefined - `__attribute__` no longer requires the file to `import t` / `import c`: the `t` / `c` module roots that `__attribute__(...)` desugars into are synthesized as **implicit module names** (zero-length, marked on the AST node — the compiler's attribute handling is AST-level and never checks symbol visibility either). When such a synthetic name cannot be resolved through the lexical scope chain, it falls back to the same-named binding in the built-in `builtins.vpi` stub; the fallback only applies to the desugared nodes, so a hand-written bare `t` / `c` in a file without the import still reports "未定义" - **Analysis progress in the status bar**: while the language server builds the dependency graph and type-checks the workspace, the bottom-left status bar now shows `$(sync~spin) C:\xxx\1.vp (4/90) 4.44% loading...` — the file currently being analyzed, how many files have been processed out of how many, and the percentage. The worker reports per-file progress (throttled to one message every 50 ms) through a new `analysisProgress` background message and a new `viper/analysisStatus` notification; the client shows a left-aligned `StatusBarItem` and hides it as soon as analysis finishes (or when there is nothing to analyze). Previously the only feedback was the work-done progress bar for open-files analysis, which said nothing about which file was being processed, so a long full-workspace pass was indistinguishable from a hung server -- The **`static` modifier** is now supported (LSP side only; TransPyC/TransPyV are unchanged). It is a *soft* keyword: `static def f(...)` / `static fn f(...)` marks a function (recorded on `FunctionNode.d.viperModifier`, exactly like `virtual`) and `static x: T = v` marks a variable (the annotation is rewritten to `t.CStatic | T`, the same tree TransPyC's `_parse_type_ann` builds by inserting `t.CStatic` at the left of the union, so `static SECRET: int` renders as `static int`). Anywhere else `static` stays an ordinary identifier — `static = 1`, `static: int`, `static(x)`, `def f(static: int)` and `self.static` all parse as before. Its meaning is C's **internal linkage**: a static symbol **cannot be used across modules**. Referencing one from another module is reported as `'lib.helper' 是 static 符号,禁止跨模块使用(static 仅限定义模块内可见)` for both `mod.name` / `mod.fn()` (checked in the module member-access path) and `from mod import name` (checked on the import statement), while same-module references are allowed — the decision is made per declaration file, so a self-import is not flagged either. `static` is also offered by completion and highlighted as a modifier (`storage.modifier.viper`, matched only in front of `def`-family or `name :`) +- The **`static` modifier** is now supported (LSP side only; TransPyC/TransPyV are unchanged). It is a *soft* keyword: `static def f(...)` / `static fn f(...)` marks a function (recorded on `FunctionNode.d.viperModifier`, exactly like `virtual`) and `static x: T = v` marks a variable (the annotation is rewritten to `t.CStatic | T`, the same tree TransPyC's `_parse_type_ann` builds by inserting `t.CStatic` at the left of the union, so `static SECRET: int` renders as `static int`). Anywhere else `static` stays an ordinary identifier — `static = 1`, `static: int`, `static(x)`, `def f(static: int)` and `self.static` all parse as before. Its meaning is C's **internal linkage**: a static symbol **cannot be used across modules**. Referencing one from another module is reported as `'lib.helper' 是 static 符号,禁止跨模块使用(static 仅限定义模块内可见)` for both `mod.name` / `mod.fn()` (checked in the module member-access path) and `from mod import name` (checked on the import statement), while same-module references are allowed — the decision is made per declaration file, so a self-import is not flagged either. `static` is also offered by completion and highlighted as a modifier (`storage.modifier.viper`, matched in front of a `def`-family keyword or a type atom) - The `f` prefix of an f-string is now highlighted as `storage.type.string.viper` (blue in the default dark theme) instead of being left unstyled. The rule matches the `r` / `u` / `b` / `f` prefix combination immediately preceding a quote, so it covers `f"..."`, `f'''...'''` and mixed spellings like `rf"..."`; a bare `r`/`f`/`b` identifier not followed by a quote is unaffected, and the string body keeps the usual `string.quoted.*` scope - The **size of a fixed-length array type may be any integer constant expression** now: `part_types: [MAX_VOLUMES * MAX_PARTITIONS]uint8`, `part_lit: [MAX_VOLUMES * 2]uint8`, `part_left: [MAX_VOLUMES - 1]uint8` and `part_shift: [1 << 3]uint8` all parse and check cleanly. Two bugs made this fail before: the parser parsed the `[SIZE]` part while still flagged as a type annotation, so `_parseArithmeticTerm` deliberately stopped at the `*` (it is the C pointer suffix marker there) — the bracket was reported as "“[" 未关闭" and the leftover `* MAX_PARTITIONS]uint8` as "类型表达式中不允许使用解包运算符"; and the type evaluator then evaluated that arithmetic with `EvalFlags.TypeExpression`, reporting "类型表达式中不允许使用二元运算符". The size is now parsed as the value expression it is (the flag is cleared for its duration), and the evaluator allows arithmetic/bitwise operators in a type expression when **both operands are Viper integers** (the case the enclosing comment already described, e.g. `t.CArray[T, MAX_ORDER + 1]`); non-integer operands (`C1 + C2`) are still reported - **`const` pointers are read-only for dereference assignment**: `lfn: const str` (`CConst | str`, i.e. `const char *`) now allows dereferencing — `*lfn` and `lfn[0]` evaluate to `char` — but writing through the dereference is reported as "不能给 const 指针的解引用赋值("const str" 指向的数据为只读)". This covers **both** spellings, `*lfn = char(65)` (dereference) and `lfn[0] = char(65)` (**subscript assignment**), which share the same diagnostic. Previously the dereference itself was rejected with a bogus "类型"const str"不支持运算符"*"" (the `CConst` modifier in the union was mistaken for a non-pointer operand), and there was no read-only check at all. A non-`const` pointer (`buf: str`) still accepts `*buf = char(65)` and `buf[0] = char(65)` - C type descriptors are **skipped** when checking iterability, so a `const` pointer can be iterated: `for ch in path:` with `path: const str` (and `const t.CPtr`) no longer reports "“CConst”不可迭代 / 未定义“__iter__”方法". The predicate now recognizes every Viper pointer (CPtr, `str`, `bytes`) rather than only `CPtr`, which is why `const str` had been missed — `CConst` fell through to the "not iterable" path +- The `static` modifier is also accepted **inside a type annotation**: `buf_head: static int = 0` — the spelling actually used throughout ViperOS (`VKernel/Kernel/drivers/**`). It desugars to the very same `t.CStatic | T` and marks the declaration as a static symbol, so it renders as `static int` and the cross-module rule applies identically; previously the `static` inside the annotation was read as an ordinary name and reported `"static" is not defined`. `static` stays a **soft** keyword in this position too — it is consumed only when a type atom follows, so `static` as a variable name (`static: int = 0`, `static = 1`) is untouched. It is now highlighted as a modifier inside annotations as well +- `*args` is bound to the `va_list` **pointer** even when the file doesn't `import t`: the Viper var-arg rule used to look up `t.CPtr` through the source-visible `t` module only, so in a file like ViperOS `drivers/serial/uart/serial.vp` (no `import t`) it fell back to Python's `tuple` semantics and `viperlib.vsnprintf(&buf, 1024, fmt, args)` was reported as "无法将"tuple"类型的参数分配给函数"vsnprintf"中类型为"str"的参数"ap"". The lookup now falls back to the compiler built-in `rawptr` from `builtins.vpi` (= `t.CPtr`), the same mechanism `T*` annotations and `[SIZE]T` already use ## 1.1.425 diff --git a/packages/vscode-vipeye/syntaxes/viper.tmLanguage.json b/packages/vscode-vipeye/syntaxes/viper.tmLanguage.json index 443550c3..03e2de94 100644 --- a/packages/vscode-vipeye/syntaxes/viper.tmLanguage.json +++ b/packages/vscode-vipeye/syntaxes/viper.tmLanguage.json @@ -55,7 +55,7 @@ }, { "name": "storage.modifier.viper", - "match": "\\bstatic\\b(?=\\s+(?:def|fn|fun|func|function)\\b|\\s+[A-Za-z_][A-Za-z0-9_]*\\s*:)" + "match": "\\bstatic\\b(?=\\s+(?:def|fn|fun|func|function)\\b|\\s*[A-Za-z_\\[])" }, { "name": "storage.modifier.viper",