关于 motoko-core-code-improvements
面向 Motoko 项目的开发技能,在测试通过后进行不改变行为的可读性与一致性改进。它按步骤指导整理导入、采用点表示法及其他机械式修改,并要求分阶段构建和测试。
主要功能
- 01分组并排序导入
- 02谨慎清理未使用的导入
- 03采用受支持的点表示法
- 04简化单表达式函数的 return
- 05通过构建和测试验证修改
适用场景
- 01测试通过后整理 Motoko 代码
- 02统一项目文件中的导入格式
- 03审查安全且不改变行为的重构
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ research-ag/ motoko-skills --skill motoko-core-code-improvements安装后在 Claude Code 中输入/motoko-core-code-improvements确认已加载。
详细介绍
目的与范围
在测试通过后使用此技能,提升代码可读性和一致性,同时不改变行为。
此技能专注于保持语义不变的机械性改进:
- 将导入聚合为几个区块:(1)
mo:core/...;(2)来自 mops 或类似第三方来源的其他mo:*/...;(3)本地项目模块;每个文件内的各区块按字母顺序排序 - 在
mo:core中可用时,优先使用点表示法 - 清理确实未使用的
import行,同时注意点表示法带来的隐式需求 - 在单表达式函数中移除多余的
return - 适当将常量 ASCII 字符串直接赋值给 Blob
安全第一:
- 每类改进单独执行;每完成一类就提交,以隔离差异
- 优先使用脚本化且便于审查的更改;使用下方提供的审计检查
- 每完成一类都重新构建;如有测试,也要运行
AI 快速检查清单(请勿跳过)
- 前置条件
- 项目已基于
mo:core编译通过(参见迁移技能)。仅当仍有引用时才保留mo:base;否则应已移除 base 依赖。 - 确保 Motoko 和 dfx 版本符合迁移技能要求(moc ≥ 1.3.0,dfx ≥ 0.31)。
- 改进顺序(建议)
- A. 移除单表达式函数中的
return - B. 在可用处改用点表示法 — 参见 Motoko 点表示法迁移技能(
skills/dot-notation-migration/SKILL.md) - C. 确保为点表示法导入必要的
mo:core模块 — 参见 Motoko 点表示法迁移技能(导入映射) - D. 清理未使用的导入(对点表示法相关导入持谨慎态度)
- E. 缩短本地(同级)导入路径(适用时省略
./前缀) - F. 将导入聚合为三个区块,并在每个文件内按字母顺序排列:(1)
mo:core/...;(2)来自 mops/第三方的其他mo:*/...;(3)本地项目模块 - G. 适当将常量 ASCII 字符串直接赋值给 Blob
- 每一步之后进行验证
- 构建所有 canister 或软件包
- 使用提供的命令进行 grep/审计
- 保持差异精简且易读
验收标准
- 不引入编译错误或警告。运行
find src -type f -name "*.mo" -print0 | xargs -0 -n1 $(mops toolchain bin moc) --check $(mops sources)进行验证(尤其检查是否缺少导入)。 - 不改变行为;除风格调整外,公共接口保持不变
- 导入已聚合为三个区块——(1)
mo:core/...;(2)来自 mops/第三方的其他mo:*/...;(3)本地项目模块——且各区块均按字母顺序排列;没有确实未使用的import - 在直接支持点表示法的地方,一致地使用点表示法
- 常量 Text 字符串直接赋值给
Blob,不再冗余调用Text.encodeUtf8
A)在单表达式函数中移除 return
模式
// Before
func f(x : T) : U { return <expr>; };
// After
func f(x : T) : U { <expr> };
说明
- 仅当函数体由单条
return <expr>;语句组成时应用。 - 不要转换多语句函数体,也不要转换包含
try、label、switch或会导致控制流不同的await的函数体。 - 函数有多条
return语句时(例如switch分支中的提前返回,如return null),不得移除任何return。 - 单条非终结
return(提前/条件退出): 如果函数恰好有一条return,但它不是函数体的最后一条语句(例如,if或switch分支中的提前返回,后面还有一个顺延执行的最终表达式),则函数有两条不同的返回路径。不要移除提前返回语句。相反,也要为最终表达式添加显式的return,这样函数中总共有两个return关键字——每条退出路径各一个。这能让两条控制流退出路径都明确且一致。// Before — one explicit return, one implicit fall-through return func lookup(k : Key) : ?V { if (cache.contains(k)) { return cache.get(k) }; table.find(k) }; // After — both exit paths use `return` func lookup(k : Key) : ?V { if (cache.contains(k)) { return cache.get(k) }; return table.find(k); }; - 函数末尾的
return switch (...) { ... }没问题。 每个分支块都以要返回的表达式结尾(分支内没有return关键字)。当所有分支都正常产生值时,这是首选写法。// OK — all cases produce values, no traps or throws return switch (decode(data)) { case (#ok key) { (key.x, key.y) }; case (#err msg) { #err(msg) }; }; - 例外:如果某个分支包含等同于返回的语句(
Runtime.trap或throw),且该语句代表真正的函数退出,则将return放入分支块中。 移除switch前的return,然后只在产生值的分支块中添加return。包含Runtime.trap或throw的分支不需要return。这样可以清楚地表明哪些分支会返回,哪些分支会中止:// Avoid — trap is hidden inside return switch return switch (decode(data)) { case (#ok key) { (key.x, key.y) }; case (#err msg) { Runtime.trap(msg) }; }; // Prefer — return only in value-producing cases switch (decode(data)) { case (#ok key) { return (key.x, key.y) }; case (#err msg) { Runtime.trap(msg) }; }; - 不可达的 trap 不等同于返回。 如果使用了
Runtime.trap("unreachable"),或者注释或消息表明该分支只有在出现 Bug 时才会执行,那么该 trap 就不是正常的函数退出,而是一种防御性断言。在这种情况下,使用return switch没问题,不应将return放入分支块中:// OK — the trap just guards an impossible case return switch (Jacobi.fromNat(x, y, 1, curve)) { case (null) Runtime.trap("unreachable"); case (?point) point; };
自动化(示例)
- 搜索候选项:
grep -rn "func \\w\\+(.*) *:.*{ *return .*; *};" . --include="*.mo" | grep -v \.mops - 检查匹配项;然后使用编辑器或脚本替换。
脚本安全要求(根据实际迁移经验总结)
- 简单的逐行
return计数器并不足够。必须通过基于字符的括号深度解析来追踪函数边界。 - 嵌套函数:当函数体内包含嵌套的
func声明时,跳过嵌套函数的整个函数体——只统计直接位于外层函数作用域中的return。 - 准确检测函数边界:使用基于字符的扫描,处理字符串字面量(跳过包括
\"转义在内的"...")、注释(//行注释和/* ... */块注释),并追踪括号深度,以找到每个函数真正的闭合}。 - 计数规则:统计外层函数体中的所有
return,包括嵌套控制流块(如switch、if、for和while)中的return,但排除嵌套func函数体中的return。只有当总数恰好为 1,且这条return是函数体直接作用域中的终结语句(在它之后到闭合}之前只能有空白、注释和可选的;)时,函数才可以安全地改写。如果唯一的return是提前/条件退出,则函数还存在一条通过顺延执行最终表达式的隐式返回路径——保留两者不动(脚本不会移除该return;请按照上方的风格说明,手动为顺延表达式添加显式的return)。
经过实战检验的 Python 脚本
将其保存为项目根目录下的 remove_returns.py,运行 python3 remove_returns.py,然后删除该脚本。
#!/usr/bin/env python3
"""
Remove terminal `return` from Motoko functions that have exactly one return
statement in their direct body (excluding nested functions).
Usage:
1. Set src_dirs to match your project layout.
2. Run: python3 remove_returns.py
3. Run tests: npx mops test
4. Delete this script after confirming all tests pass.
Safety:
- Tracks function boundaries via character-level brace-depth parsing.
- Skips string literals ("..." with \" escapes) and comments (// and /* */).
- Detects nested `func` declarations and skips their bodies entirely.
- Only removes the `return` keyword (+ trailing space) from functions with
exactly 1 return at any depth within the direct body.
"""
import re
import glob
import os
# ── Configuration ──────────────────────────────────────────────────────
# Directories to process (relative to script location or cwd).
SRC_DIRS = ["src", "test", "bench"]
# ──────────────────────────────────────────────────────────────────────
def find_func_bodies(text):
"""
Yield (body_start, body_end) for every top-level and nested function
found in `text`. body_start is the index of the opening '{' of the
function body; body_end is the index of the matching closing '}'.
Handles:
- String literals (skips content inside "...")
- Line comments (// ...)
- Block comments (/* ... */)
- Nested braces
"""
i = 0
n = len(text)
while i < n:
# Skip string literals
if text[i] == '"':
i += 1
while i < n and text[i] != '"':
if text[i] == '\\':
i += 1 # skip escaped char
i += 1
i += 1 # skip closing "
continue
# Skip line comments
if text[i] == '/' and i + 1 < n and text[i + 1] == '/':
i += 2
while i < n and text[i] != '\n':
i += 1
continue
# Skip block comments
if text[i] == '/' and i + 1 < n and text[i + 1] == '*':
i += 2
while i < n and not (text[i] == '*' and i + 1 < n and text[i + 1] == '/'):
i += 1
i += 2 # skip */
continue
# Look for 'func' keyword at a word boundary
if text[i:i+4] == 'func' and (i == 0 or not text[i-1].isalnum() and text[i-1] != '_'):
after = text[i+4:i+5] if i + 4 < n else ''
if after == '' or not (after.isalnum() or after == '_'):
# Found a func keyword. Scan forward to find the opening '{'.
j = i + 4
while j < n:
if text[j] == '"':
j += 1
while j < n and text[j] != '"':
if text[j] == '\\':
j += 1
j += 1
j += 1
continue
if text[j] == '{':
# Found the opening brace of the function body.
brace_start = j
depth = 1
j += 1
while j < n and depth > 0:
if text[j] == '"':
j += 1
while j < n and text[j] != '"':
if text[j] == '\\':
j += 1
j += 1
j += 1
continue
if text[j] == '/' and j + 1 < n and text[j + 1] == '/':
j += 2
while j < n and text[j] != '\n':
j += 1
continue
if text[j] == '/' and j + 1 < n and text[j + 1] == '*':
j += 2
while j < n and not (text[j] == '*' and j + 1 < n and text[j + 1] == '/'):
j += 1
j += 2
continue
if text[j] == '{':
depth += 1
elif text[j] == '}':
depth -= 1
j += 1
brace_end = j - 1 # index of closing '}'
yield (brace_start, brace_end)
i = j
break
if text[j] == '=' or text[j] == ';':
# func ... = expr; (no body) or forward decl
i = j + 1
break
j += 1
else:
i = j
continue
i += 1
def count_returns_in_direct_body(text, body_start, body_end):
"""
Count `return` statements that are directly inside this function body
(not inside nested functions). Returns list of (return_keyword_start,
return_keyword_end) positions.
"""
body = text[body_start + 1 : body_end] # content between { and }
offset = body_start + 1
# First, find all nested func bodies within this body so we can skip them.
nested_ranges = []
for ns, ne in find_func_bodies(body):
# Adjust to absolute positions
nested_ranges.append((ns + offset, ne + offset))
def is_inside_nested(pos):
for ns, ne in nested_ranges:
if ns <= pos <= ne:
return True
return False
# Now scan for `return` keywords in the body, skipping nested funcs.
returns = []
i = 0
while i < len(body):
# Skip strings
if body[i] == '"':
i += 1
while i < len(body) and body[i] != '"':
if body[i] == '\\':
i += 1
i += 1
i += 1
continue
# Skip line comments
if body[i] == '/' and i + 1 < len(body) and body[i + 1] == '/':
i += 2
while i < len(body) and body[i] != '\n':
i += 1
continue
# Skip block comments
if body[i] == '/' and i + 1 < len(body) and body[i + 1] == '*':
i += 2
while i < len(body) and not (body[i] == '*' and i + 1 < len(body) and body[i + 1] == '/'):
i += 1
i += 2
continue
# Check for 'return' keyword
if body[i:i+6] == 'return' and (i == 0 or not body[i-1].isalnum() and body[i-1] != '_'):
after = body[i+6:i+7] if i + 6 < len(body) else ''
if after == '' or not (after.isalnum() or after == '_'):
abs_pos = i + offset
if not is_inside_nested(abs_pos):
returns.append((abs_pos, abs_pos + 6))
i += 6
continue
i += 1
return returns
def process_file(filepath):
with open(filepath, 'r') as f:
text = f.read()
original = text
removals = 0
# Collect all function bodies
func_bodies = list(find_func_bodies(text))
# For each function, check if it has exactly 1 return in its direct body
# Process in reverse order to preserve indices when editing
edits = [] # list of (start, end) of "return " to remove
for body_start, body_end in func_bodies:
returns = count_returns_in_direct_body(text, body_start, body_end)
if len(returns) == 1:
ret_start, ret_end = returns[0]
# Verify this return is the terminal statement of the direct body:
# scan past the return's expression (tracking brackets/strings/comments)
# to its terminating ';' (or body_end), then ensure only whitespace,
# comments, and optional semicolons remain before body_end.
n = len(text)
j = ret_end
depth = 0
stmt_end = body_end # position after the return statement
while j < body_end:
c = text[j]
if c == '"':
j += 1
while j < body_end and text[j] != '"':
if text[j] == '\\':
j += 1
j += 1
j += 1
continue
if c == '/' and j + 1 < body_end and text[j + 1] == '/':
j += 2
while j < body_end and text[j] != '\n':
j += 1
continue
if c == '/' and j + 1 < body_end and text[j + 1] == '*':
j += 2
while j < body_end and not (text[j] == '*' and j + 1 < body_end and text[j + 1] == '/'):
j += 1
j += 2
continue
if c in '({[':
depth += 1
elif c in ')}]':
depth -= 1
elif c == ';' and depth == 0:
stmt_end = j + 1
break
elif c == '\n' and depth == 0:
# Statement terminated by newline (no semicolon)
stmt_end = j
break
j += 1
else:
stmt_end = body_end
# Now skip whitespace, comments, and stray semicolons after the return
k = stmt_end
terminal = True
while k < body_end:
c = text[k]
if c.isspace() or c == ';':
k += 1
continue
if c == '/' and k + 1 < body_end and text[k + 1] == '/':
k += 2
while k < body_end and text[k] != '\n':
k += 1
continue
if c == '/' and k + 1 < body_end and text[k + 1] == '*':
k += 2
while k < body_end and not (text[k] == '*' and k + 1 < body_end and text[k + 1] == '/'):
k += 1
k += 2
continue
# Found other code after the return — not terminal.
terminal = False
break
if not terminal:
continue
# Remove "return " (keyword + trailing space)
if ret_end < len(text) and text[ret_end] == ' ':
edits.append((ret_start, ret_end + 1))
else:
edits.append((ret_start, ret_end))
# Apply edits in reverse order to preserve positions
edits.sort(key=lambda x: x[0], reverse=True)
for start, end in edits:
text = text[:start] + text[end:]
removals += 1
if text != original:
with open(filepath, 'w') as f:
f.write(text)
return removals
def main():
total = 0
files_modified = 0
for src_dir in SRC_DIRS:
for filepath in sorted(glob.glob(os.path.join(src_dir, "**/*.mo"), recursive=True)):
r = process_file(filepath)
if r > 0:
print(f" {filepath}: {r} returns removed")
total += r
files_modified += 1
print(f"\nTotal: {total} returns removed in {files_modified} files")
if __name__ == '__main__':
main()
B) 点表示法转换
有关所有 Motoko 点表示法规则、自动化脚本和注意事项,请参阅专门的技能文档:
- skills/dot-notation-migration/SKILL.md
本文件有意不重复这些说明。请使用专门的技能文档应用点表示法更改,然后继续执行此处的导入清理(D 节)和导入排序(F 节)。
C) 点表示法的导入要求
有关点表示法相关的导入映射和规则,请使用专门的技能文档:
- skills/dot-notation-migration/SKILL.md
本文件有意不重复导入映射。按照该技能文档应用点表示法更改后,请继续执行 D 节(清理未使用的导入)和 F 节(导入排序)。
D) 安全清理未使用的导入
目标
- 移除之前重构后确实未使用的导入,但不要移除点表示法隐式依赖的模块。
实际情况
- 编辑器工具(VSCode Motoko 扩展)能正确标记未使用的导入,并会考虑点表示法。CLI 检测可能更棘手。
常见误报(看似未使用但实际必需的导入)
Blob— 当对Blob值调用.toArray()、.size()、.isEmpty()、.hash()时需要(例如,Sha256.fromArray(...).toArray()、hmac.sum().toArray())Array— 当对[T]值调用.flatten()、.foldLeft()、.sliceToArray()、.map()、.filter()等方法时需要(例如,[arr1, arr2].flatten())Nat— 当对来自.size()的Nat值调用.toText()时需要(例如,arr.size().toText())VarArray— 当对[var T]值调用.toArray()时需要- 规则:如果对某个模块类型的值调用了任何点表示法方法,那么即使模块名称从未在代码中显式出现,也必须导入该模块。
处理方法
-
通过编辑器处理
- 在 VSCode 中打开工作区。对每个
*.mo文件,接受快速修复以移除标记为未使用的导入。检查差异。
- 在 VSCode 中打开工作区。对每个
-
借助编译器/LSP 批量处理
- 通过 VSCode 扩展使用 Motoko 语言服务器显示所有诊断;在支持的情况下批量应用代码操作。
-
借助脚本进行保守移除
- 编写一个简单脚本,以便:
- 解析每个
import ... "mo:core/XYZ"; - 搜索文件中是否存在
XYZ.,或是否存在映射到XYZ的已知点表示法模式(参见 Dot-Notation Migration Skill 的导入映射) - 如果两者都不存在,则标记该行可移除
- 解析每个
- 删除前手动检查标记的行
- 编写一个简单脚本,以便:
审查辅助方法
- 清理后,搜索模块名称从未出现且没有映射点表示法模式的
import行。 - 构建项目。如果移除了必需模块,点表示法调用将在编译时失败——恢复导入并完善规则。
E) 缩短本地(同级)导入路径
-
尽可能对本地导入使用裸模块名称:
"Bech32",而不是"./Bech32"。两者都能正确解析,但裸名称更简洁,也更符合惯用写法。// Good import Bech32 "Bech32"; import Script "Script"; import Types "Types"; // Acceptable (cross-directory) import ByteUtils "../ByteUtils"; import Curves "../ec/Curves"; // Avoid (unnecessary ./ prefix for siblings) import Bech32 "./Bech32"; -
对于跨目录导入,必须使用相对路径
../,这是可以接受的。
F) 按分区汇总并按字母顺序排列导入项
原因
- 统一的排序方式可减少合并冲突,加快审查速度。清晰的分组便于浏览,也能避免将外部模块与本地模块混在一起。
分区(按以下顺序排列,每个分区之间空一行)
- mo:core 导入项
- 所有路径以 "mo:core/..." 开头的导入项(包括
mo:core/Types)。
- 所有路径以 "mo:core/..." 开头的导入项(包括
- 其他 mo:* 第三方导入项(mops 或类似项)
- 所有不是
mo:core/...的mo:...导入项(例如,mo:uuid/UUID、mo:sha2/SHA256等)。
- 所有不是
- 本地项目模块
- 裸模块名称导入项,如
"Bech32"、"Common"(优先),或相对路径导入项,如"../ByteUtils"、"./Script"。 - 同级导入项优先使用不带
./前缀的裸模块名称(例如,使用"Bech32"而不是"./Bech32")。两种方式都可用,但裸名称更简洁。 - 按导入项所使用的本地名称排序本地项目模块,而不是按导入路径中的模块名称排序。
- 裸模块名称导入项,如
排序规则(分别应用于每个分区)
- 按模块导入时使用的本地名称按字母顺序排序
- 保留导入样式(模块导入与命名类型导入)。
- 同一路径的多个命名类型导入项保持在同一行,不作更改。
- 如果代码库风格偏好,可在每个分区上方添加注释标题(Core、Third-party、Local)。
示例
// Before (mixed)
import Runtime "mo:core/Runtime";
import { type Result } "mo:core/Types";
import SHA256 "mo:sha2/SHA256";
import Map "mo:core/Map";
import BitVec "mo:bitvec/BitVec";
import Utils "../lib/Utils";
import Logger "./Logger";
// After (aggregated and sorted per section)
//// Core
import Map "mo:core/Map";
import Runtime "mo:core/Runtime";
import { type Result } "mo:core/Types";
//// Third‑party (mops)
import BitVec "mo:bitvec/BitVec";
import SHA256 "mo:sha2/SHA256";
//// Local
import Logger "Logger";
import Utils "../lib/Utils";
轻量级自动化思路(逐文件)
- 收集文件顶部的所有导入行。
- 按路径前缀将其划分为三个分区。
- 按导入项所使用的本地名称,对每个分区按字母顺序排序
- 按 Core → Third-party → Local 的顺序重新输出各分区,并在分区之间空一行。
- 保留所有非导入注释的原有位置,除非它们明显属于某个分区标题。
G) 常量直接将字符串赋值给 Blob
模式
// Before
let b : Blob = Text.encodeUtf8("hello");
// After
let b : Blob = "hello";
原因
- 对于常量 Text 字符串,Motoko 编译器允许直接赋值给
Blob类型。 - 结果与
Text.encodeUtf8相同,但代码更简洁,也避免了显式调用函数。
示例
// Good: direct assignment
let blobs = [
"strategy",
Text.encodeUtf8(Nat.toText(slot)),
];
// Avoid: redundant encoding for constant string
let blobs = [
Text.encodeUtf8("strategy"),
Text.encodeUtf8(Nat.toText(slot)),
];
实用自动化方案(可选)
这些是可选的起点。有可用的编辑器集成重构功能时,优先使用。务必检查差异。
- 查找单行返回函数
rg -n --glob '!**/.mops/**' --glob '**/*.mo' "func [A-Za-z_][A-Za-z0-9_]*\(.*\) *:.*\{ *return .*; *};"
- 点表示法转换与候选项检测
- 完整的自动化与 grep 方案请参阅专用技能:
- skills/dot-notation-migration/SKILL.md
- 标记可能未使用的核心导入项(保守处理)
# Rough heuristic: list imports, then search for name or dot‑patterns
rg -n --glob '!**/.mops/**' --glob '**/*.mo' '^import .*"mo:core/([A-Za-z/]+)";' -o -r '$1'
# For each file, ensure presence of module references OR mapped dot‑patterns before removal
- 将导入项汇总并按分区排序(编辑器宏)
- 选中文件顶部的所有
import行 → 分成三个分区(Core、Third-party mo:*、Local)→ 按路径对每组按字母顺序排序 → 在分区之间插入空行 → 保持导入样式不变。
Agent 策略(面向 AI 助手)
- 开始改进前,确认项目可基于
mo:core构建。 - 逐个处理文件。对于每个文件:
- A. 移除单表达式
return形式 - B. 按 skills/dot-notation-migration/SKILL.md 应用点表示法
- C. 为引入的点表示法确保具备所需导入项(参见 skills/dot-notation-migration/SKILL.md 中的导入映射)
- D. 移除确实未使用的导入项(遵循专用技能中的点表示法导入映射)
- E. 缩短本地(同级)导入路径(适用时移除
./前缀) - F. 将导入项汇总到三个分区,并按字母顺序排列各分区(Core → Third-party mo:* → Local)
- G. 当目标类型为
Blob时,将Text.encodeUtf8("<literal>")替换为"<literal>"
- A. 移除单表达式
- 每处理完一个文件后:编译;如果因缺少导入项而失败,则恢复并标记映射
- 每完成代码库中的一个类别后:运行完整构建(对于 MOPS 软件包,使用
find src -type f -name "*.mo" -print0 | xargs -0 -n1 $(mops toolchain bin moc) --check $(mops sources)),并可选择运行测试 - 简要报告所做的更改,以及为人工审查而暂缓处理的边界情况
H) 将 Array.fromVarArray(x) 转换为 x.toArray()
模式
// Before
Array.fromVarArray(buf)
Array.fromVarArray<Nat8>(buf)
// After
buf.toArray()
说明
Array.fromVarArray是一个工厂函数(第一个参数不是self),因此点表示法迁移脚本不会自动转换它。这需要单独转换。VarArray.toArray()是对应的点表示法形式,定义在[var T]值上。- 转换后,检查是否可以移除
Array导入(它可能仍用于Array.tabulate、[T]上的Array.flatten点表示法等)。 - 移除可选的类型参数:
Array.fromVarArray<Nat8>(buf)→buf.toArray()(类型会根据变长数组推断)。
I) Array.tabulate 通常需要类型注解
- 不要移除
Array.tabulate<T>(...)调用中的类型注解。 - Motoko 编译器通常无法推断元素类型,尤其是在回调使用
fromNat、算术运算或其他可能返回多种数值类型的表达式时。 - 在实际项目中,移除注解导致 24 个测试文件中有 13 个失败,错误类似于
expression of type [Any] cannot produce expected type [Nat8]。 - 保留注解:
Array.tabulate<Nat8>(n, func i { ... })。
边缘情况与注意事项
- 有关所有点表示法的行为、方法可用性、工厂函数与方法的区别,以及可变性说明,请参阅:
- skills/dot-notation-migration/SKILL.md
- 汇总导入时,将来自
mo:core/Types的具名类型导入保留在mo:core分组中;排序规则请参阅 F 节。 - 本地导入:同级文件优先使用裸模块名(
"Bech32"),而非相对路径("./Bech32")。两者都能正确解析,但裸模块名更简洁。 - 在
src/中使用"../src/Bech32"这样的导入路径是不正确的——同级文件应使用"Bech32",跨目录引用应使用"../SubDir/Module"。
验证与签核
- 更改后成功构建所有 canister。
- 运行静态审计:
- 确认没有
import行被编辑器或启发式脚本标记为未使用(考虑点表示法的需求后再判断)。 - 抽查数组、映射、集合和文本操作是否在适合的地方使用点表示法。
- 确认没有
- 差异应保持机械式;不得更改公共 API 或行为。
附录:点表示法参考
完整且持续维护的点表示法目录、自动化脚本和导入映射,请参阅:
- skills/dot-notation-migration/SKILL.md