Repository navigation
Conversation
…ld types
A field can now declare a different type for reading and for writing:
---@Class LuaEntity
---@field (read) force LuaForce
---@field (write) force ForceID
Reads, hover and narrowing use the read type; assignments and table
literals are checked against the write type. A plain `---@field` still
covers both directions, and a one-sided declaration falls back to its type
for both.
Refs LuaLS#1298
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
中文:vm.compileWriteNode 在 source.bindDocs 存在时直接返回 readNode,但 bindDocs 的语义是“绑定文档”而非“该字段被显式注解”,这可能导致带 ---@type 等绑定的赋值语句跳过 (write) 类型检查。建议确认该分支的意图,或改为仅在字段本身有显式类型注解时才回退。
English: vm.compileWriteNode returns readNode early whenever source.bindDocs is present, but bindDocs means "has bound docs" rather than "the field is explicitly annotated", which may cause assignments with ---@type-style bindings to skip (write) type checking. Please confirm the intent of this branch, or only fall back when the field itself has an explicit type annotation.
中文:vm.filterFieldsByAccess 中,当过滤后 hasDocField 为 false 时返回原始 results,但此时 filtered 可能已丢弃了部分 (write)/(read) 字段。若 results 中同时存在非 doc.field 的普通结果和 doc.field,逻辑上应保留过滤结果而非回退到全部结果,否则 (read)/(write) 区分在混合场景下失效。建议补充混合来源的测试。
English: In vm.filterFieldsByAccess, when hasDocField is false after filtering, the original results is returned, but filtered may already have dropped some (write)/(read) fields. If results contains both non-doc.field results and doc.field entries, the filtered result should be kept instead of falling back to all results; otherwise the (read)/(write) distinction breaks in mixed scenarios. Please add tests covering mixed sources.
中文:script/core/completion/completion.lua 中新增分支在 last 为写入访问且 src 为 doc.field 且非 write 时直接 fields[name] = src 并 goto CONTINUE,这会绕过后续的 getDeprecated 检查,导致已废弃的 (read) 字段仍出现在写入补全中。建议在该分支前也检查废弃状态。
English: In script/core/completion/completion.lua, the new branch assigns fields[name] = src and goto CONTINUE when last is a write access, src is a doc.field, and it is not a write field. This bypasses the subsequent getDeprecated check, so deprecated (read) fields still appear in write completions. Please also check deprecation before this branch.
中文:script/core/hover/label.lua 的 asField 中,当 writeNode ~= vm.compileNode(setter) 时用 (field) 作为标题并展示写入类型,但未区分读/写悬停上下文,可能导致在读取位置悬停时显示写入类型。建议根据当前悬停位置的实际访问类型(读或写)决定展示哪个节点。
English: In asField of script/core/hover/label.lua, when writeNode ~= vm.compileNode(setter), it uses (field) as the title and shows the write type, but it does not distinguish read/write hover context, which may show the write type when hovering at a read position. Please decide which node to display based on the actual access type (read or write) at the hover position.
中文:script/vm/type.lua 的 checkTableShape 中新增 ::nextField:: 标签,但 goto nextField 只跳过当前字段检查;同时 writeKeys 仅收集 (write) 字段,若某 key 只有 (read) 声明而无 (write),表字面量仍按 (read) 类型检查,这与“表字面量是写入”的注释语义不一致。建议明确单侧声明在表字面量场景下的预期行为并补充测试。
English: In checkTableShape of script/vm/type.lua, a new ::nextField:: label is added, but goto nextField only skips the current field check. Also, writeKeys only collects (write) fields; if a key has only a (read) declaration and no (write), the table literal is still checked against the (read) type, which contradicts the comment that "a table literal writes its fields". Please clarify the expected behavior for one-sided declarations in table literals and add tests.
中文:script/core/hover/table.lua 的 getNodeMap 中,readableKeys 基于 vm.viewKey 构建,但后续循环用 keyMap[key] 过滤;若某 key 在 readableKeys 中但不在 keyMap 中,逻辑无影响,但若 viewKey 对同一字段返回不同结果(如依赖 uri),可能出现不一致。建议复用同一 key 计算方式并补充 (read)/(write) 同时存在时的悬停表格测试。
English: In getNodeMap of script/core/hover/table.lua, readableKeys is built from vm.viewKey, but the later loop filters by keyMap[key]. If a key is in readableKeys but not in keyMap, there is no effect, but if viewKey returns different results for the same field (e.g. depending on uri), inconsistency may arise. Please reuse the same key computation and add hover-table tests for fields declared with both (read) and (write).
中文:script/parser/luadoc.lua 中 result.docAttr = parseDocAttr(result) 在 result 尚未设置 parent 等字段时调用,需确认 parseDocAttr 不依赖 result 的后续字段;同时 guide.lua 将 docAttr 加入 doc.field 的 childMap,需确保 AST 遍历与格式化(如 doc.field 的 range/finish 计算)不会因新增子节点而改变既有输出。
English: In script/parser/luadoc.lua, result.docAttr = parseDocAttr(result) is called before result has fields like parent set; please confirm parseDocAttr does not depend on later fields of result. Also, guide.lua adds docAttr to the childMap of doc.field; please ensure AST traversal and formatting (e.g. doc.field range/finish computation) are not changed by the new child node.
中文:文档方面,en-us 已补充 (read)/(write) 说明与示例,但 es-419、ja-jp、pt-br、zh-cn、zh-tw 仅更新了语法行,未同步说明文字与示例,且 pt-br/zh-cn/zh-tw 的语法行原本缺少 [scope],本次也未补上,导致各语言文档不一致。建议至少同步说明段落,并统一 [scope] 的呈现。
English: For documentation, en-us adds the (read)/(write) description and example, but es-419, ja-jp, pt-br, zh-cn, and zh-tw only update the syntax line without the description text or example. Also, the syntax lines in pt-br/zh-cn/zh-tw originally lacked [scope] and still do, making the localized docs inconsistent. Please at least sync the description paragraph and unify the [scope] presentation.
中文:changelog.md 中新增条目提到 ---@field (read) / ---@field (write),但未说明单侧声明(仅 (read) 或仅 (write))的行为,而实现中单侧声明对读写均生效。建议在 changelog 或文档中明确该语义,避免用户误解。
English: The new entry in changelog.md mentions ---@field (read) / ---@field (write) but does not explain the behavior of one-sided declarations (only (read) or only (write)), while the implementation makes one-sided declarations apply to both reads and writes. Please clarify this semantics in the changelog or docs to avoid user confusion.
中文:测试覆盖方面,新增测试覆盖了基本读写分离、表字面量、悬停、补全等场景,但缺少:(read)/(write) 与 private/protected 等 scope 组合、与 ---@deprecated 组合、以及 vm.filterFieldsByAccess 混合来源(doc.field 与普通字段)的单元测试。建议补充这些边界用例。
English: For test coverage, the new tests cover basic read/write separation, table literals, hover, and completion, but lack: combinations of (read)/(write) with scopes like private/protected, combinations with ---@deprecated, and unit tests for vm.filterFieldsByAccess with mixed sources (doc.field and plain fields). Please add these edge cases.
Summary
Adds read and write attributes to
---@field, so a field can declare one type for reads and a different type for writes. Plain---@fieldbehaves exactly as before.Addresses #1298 (and its duplicate #2874).
Motivation
APIs often accept a wider type on write than they return on read. The Factorio runtime API does this throughout. For example,
LuaEntity.forcereads asLuaForcebut acceptsForceID(string|uint8|LuaForce). With a single field type, annotators have two bad options:assign-type-mismatchdiagnostics on valid writes, orDesign
---@class (exact),---@alias (partial)and---@enum (key), through the existingparseDocAttr. It needs no new tag, and(cannot start a field name or a[T]index key, so it is unambiguous.(read)declaration.vm.compileNodeon a field access always resolves to the read type. As a result,e.force = "player"; e.forcestill reads asLuaForcerather than the write type.vm.compileWriteNodereturns the(write)type. It is used byassign-type-mismatchfor assignments and table literals, by the table-shape check, and by hover on an assignment.obj.n = obj.n + 1reads with the read type and checks the result against the write type.(read)or only(write)uses that type for both directions. This keeps the change additive: nothing new is diagnosed.nevertype today, so these are left for a follow-up.Changes
script/parser/luadoc.lua,script/parser/guide.lua: parsedocAttrondoc.fieldand add it to the child map, so semantic tokens highlight it.script/vm/doc.lua: addvm.getFieldAccessandvm.filterFieldsByAccess.script/vm/compiler.lua: add an optional access filter tovm.compileByParentNode(defaultread), and addvm.compileWriteNode.script/vm/type.lua: the table-shape check prefers(write)declarations.script/core/diagnostics/assign-type-mismatch.lua: checks against the write type.script/core/diagnostics/duplicate-doc-field.lua: a(read)+(write)pair is not a duplicate. Plain +(read), or two(read)lines, still are.script/core/hover/label.lua,script/core/hover/table.lua: hover shows the type for the direction involved, and the class field list shows read types.script/core/completion/completion.lua: prefers the readable declaration.locale/en-us/script.lua(LUADOC_DESC_FIELD) gets the new syntax and an example.changelog.mdgets aNEWentry.Tests
test/parser_test/ast/LuaDoc.lua: thedoc.fieldAST withdocAttr.test/type_inference/common.lua: reads, a read after a write, and both one-sided fallbacks.test/diagnostics/assign-type-mismatch.lua: valid and invalid writes, table literals, a compound update, and fallbacks.test/diagnostics/duplicate-doc-field.lua: a pair is allowed; true duplicates are still reported.test/diagnostics/param-type-mismatch.lua: passing a read field to a typed parameter.test/hover/init.lua: a read and a write.The full suite passes:
bin/lua-language-server test.lua.Known limitations
obj[k] = vwith a typedk) still use the read type.🤖 Generated with Claude Code