motoko-dot-notation-migration
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMotoko dot notation migration
Motoko 点表示法迁移
Purpose
目的
Convert Motoko function calls from the old style to the new dot notation, for all functions in the package whose first parameter is named .
Module.func(self, ...)self.func(...)coreselfThis skill was created from a successful migration of the project. It contains the complete function catalog for core 2.2.0, a battle-tested Python conversion script, and all the critical pitfalls that must be handled.
motoko-bitcoin将Motoko函数调用从旧的风格转换为新的点表示法,适用于包中所有第一个参数名为的函数。
Module.func(self, ...)self.func(...)coreself这个技能是从项目的成功迁移中创建的。它包含core 2.2.0的完整函数目录、经过实战检验的Python转换脚本,以及所有必须处理的关键陷阱。
motoko-bitcoinWhen to Use
使用场景
- When migrating a Motoko project from to
base, or upgradingcoreversionscore - When the user asks to convert function calls to dot notation
- When the user asks to modernize Motoko call syntax
- 当将Motoko项目从迁移到
base,或升级core版本时core - 当用户要求将函数调用转换为点表示法时
- 当用户要求现代化Motoko调用语法时
Background
背景
In Motoko's package (successor to ), many module functions declare their first parameter as . These functions support dot notation: instead of writing , you can write .
corebaseselfModule.func(self, arg2, arg3)self.func(arg2, arg3)Only functions whose first parameter is literally named support this. Factory functions like , , , etc. do NOT have as first parameter and must NOT be converted.
selfArray.tabulateArray.fromVarArrayBlob.fromArrayself在Motoko的包(的继任者)中,许多模块函数将第一个参数声明为。这些函数支持点表示法:无需编写,你可以写成。
corebaseselfModule.func(self, arg2, arg3)self.func(arg2, arg3)只有第一个参数字面名为的函数才支持此语法。像、、等工厂函数的第一个参数不是,绝对不能转换。
selfArray.tabulateArray.fromVarArrayBlob.fromArrayselfConversion Rules
转换规则
Zero-extra-arg functions
无额外参数的函数
Module.func(expr) → expr.func()
Module.func<T>(expr) → expr.func<T>()Module.func(expr) → expr.func()
Module.func<T>(expr) → expr.func<T>()Multi-arg functions
多参数函数
Module.func(expr, a, b) → expr.func(a, b)
Module.func<T>(expr, a) → expr.func<T>(a)Module.func(expr, a, b) → expr.func(a, b)
Module.func<T>(expr, a) → expr.func<T>(a)Nested calls chain naturally
嵌套调用可自然链式转换
Array.flatten(Array.map<T, U>(xs, f)) → xs.map<T, U>(f).flatten()
List.toArray(listExpr) → listExpr.toArray()
Blob.toArray(Text.encodeUtf8(t)) → t.encodeUtf8().toArray()Array.flatten(Array.map<T, U>(xs, f)) → xs.map<T, U>(f).flatten()
List.toArray(listExpr) → listExpr.toArray()
Blob.toArray(Text.encodeUtf8(t)) → t.encodeUtf8().toArray()Critical Pitfalls
关键陷阱
1. Operator Precedence — Dot Binds Tighter Than Infix
1. 运算符优先级 — 点运算符比中缀运算符绑定优先级更高
In Motoko, binds tighter than all infix operators (, , , , , , , , , , , , , ).
.>><<&|+-*/%#==!=andormotoko
// WRONG: dot binds to 24, not the whole expression
(value >> 24).toNat() // ← need parens
value >> 24.toNat() // ← BROKEN: parses as value >> (24.toNat())
// WRONG: dot binds to b, not the whole expression
(a & b).toNat() // ← need parens
a & b.toNat() // ← BROKEN
// OK: no operators at top level
myArray.flatten()
foo.bar.toNat()
someFunc(x, y).toText()Rule: If the self-expression contains ANY top-level infix operator, wrap it in parentheses before appending .
.func()在Motoko中,的绑定优先级高于所有中缀运算符(、、、、、、、、、、、、、)。
.>><<&|+-*/%#==!=andormotoko
// 错误:点运算符绑定到24,而非整个表达式
(value >> 24).toNat() // ← 需要加括号
value >> 24.toNat() // ← 失效:解析为value >> (24.toNat())
// 错误:点运算符绑定到b,而非整个表达式
(a & b).toNat() // ← 需要加括号
a & b.toNat() // ← 失效
// 正确:顶层无运算符
myArray.flatten()
foo.bar.toNat()
someFunc(x, y).toText()规则:如果self表达式包含任何顶层中缀运算符,在追加之前将其用括号包裹。
.func()2. Type Parameters Contain Commas — Track Angle Bracket Depth
2. 类型参数包含逗号 — 跟踪尖括号深度
When splitting at the first comma, you MUST track angle bracket depth. Type parameters like contain commas that are NOT argument separators.
Module.func(self, rest...)<><Nat64, [Nat8]>motoko
// The comma between Nat64 and [Nat8] is inside <>, not an argument separator
Array.map<Nat64, [Nat8]>(amounts, func(x) { ... })
// ^ THIS is the first real comma (after amounts)
// If you split at the first comma ignoring <>, you get:
// self = "Array.map<Nat64" ← WRONG, corrupted
// rest = "[Nat8]>(amounts..." ← WRONG, corruptedRule: In the comma-finder, track for just like for . Only decrement on when (to avoid confusing comparison operators with closing angle brackets).
depth_angle<>depth_paren()>depth_angle > 0在第一个逗号处拆分时,必须跟踪尖括号的深度。像这样的类型参数包含的逗号不是参数分隔符。
Module.func(self, rest...)<><Nat64, [Nat8]>motoko
// Nat64和[Nat8]之间的逗号在<>内部,不是参数分隔符
Array.map<Nat64, [Nat8]>(amounts, func(x) { ... })
// ^ 这才是第一个真正的逗号(在amounts之后)
// 如果忽略<>直接在第一个逗号处拆分,会得到:
// self = "Array.map<Nat64" ← 错误,已损坏
// rest = "[Nat8]>(amounts..." ← 错误,已损坏规则:在查找逗号时,像跟踪的一样跟踪的。仅当时才在遇到时递减深度(避免将比较运算符与闭合尖括号混淆)。
()depth_paren<>depth_angledepth_angle > 0>3. Functions That Must NOT Be Converted
3. 绝对不能转换的函数
These functions do NOT have as their first parameter. Do not convert them:
selfArray: tabulate, fromVarArray, fromIter, empty, repeat
Blob: fromArray, fromVarArray, empty
Text: fromArray, fromIter, fromVarArray
List: empty, repeat, tabulate, fromIter, fromArray, fromVarArray
VarArray: empty, repeat, fromArray, fromIter, tabulate
Nat: range, min, max, rangeByInclusive
Nat8/16/32/64: fromNat, fromIntWrap, fromNat16, fromNat32, fromNat64 (constructors)
Int: abs, fromNat, range, min, max
Char: fromNat32, fromText
Runtime: trapHowever, has a dot-notation equivalent: (via ). This is NOT handled by the dot-notation conversion script — it's a separate refactor (see motoko-core-code-improvements skill, Section H).
Array.fromVarArray(x)x.toArray()VarArray.toArray这些函数的第一个参数不是,请勿转换:
selfArray: tabulate, fromVarArray, fromIter, empty, repeat
Blob: fromArray, fromVarArray, empty
Text: fromArray, fromIter, fromVarArray
List: empty, repeat, tabulate, fromIter, fromArray, fromVarArray
VarArray: empty, repeat, fromArray, fromIter, tabulate
Nat: range, min, max, rangeByInclusive
Nat8/16/32/64: fromNat, fromIntWrap, fromNat16, fromNat32, fromNat64 (构造函数)
Int: abs, fromNat, range, min, max
Char: fromNat32, fromText
Runtime: trap不过,有对应的点表示法等价写法:(通过)。这不属于点表示法转换脚本的处理范围 — 它是单独的重构操作(参见motoko-core-code-improvements技能的H部分)。
Array.fromVarArray(x)x.toArray()VarArray.toArray4. Dot-Notation Requires the Type's Module to Be Imported
4. 点表示法要求导入类型对应的模块
When calling a method via dot-notation on a value (e.g., ), the module for that value's type () must still be imported, even though the name doesn't appear explicitly in the calling code.
someBlob.toArray()BlobBlobCommon examples where the import looks unused but is required:
- — for
Blob,.toArray()on Blob return values (e.g.,.size())Sha256.fromArray(...).toArray() - — for
Array,.flatten(),.foldLeft(),.map()on.sliceToArray()values[T] - — for
Naton Nat values from.toText()calls.size() - — for
VarArrayon.toArray()values (after converting[var T])Array.fromVarArray
If you remove these imports, compilation will fail with and a hint to import the module.
field X does not exist in type通过点表示法调用值的方法时(例如),该值类型()对应的模块仍必须导入,即使调用代码中未显式出现名称。
someBlob.toArray()BlobBlob常见的看似未使用但必须保留的导入示例:
- — 用于Blob返回值的
Blob、.toArray()(例如.size())Sha256.fromArray(...).toArray() - — 用于
Array值的[T]、.flatten()、.foldLeft()、.map().sliceToArray() - — 用于
Nat调用返回的Nat值的.size().toText() - — 用于
VarArray值的[var T](转换.toArray()之后)Array.fromVarArray
如果移除这些导入,编译会失败并提示,同时建议导入对应模块。
field X does not exist in type5. String Literals Must Be Skipped
5. 必须跳过字符串字面量
When scanning for operators or commas, skip over string literals () including escaped quotes (), so operators inside strings don't trigger false positives.
"..."\"扫描运算符或逗号时,跳过字符串字面量()包括转义引号(),避免字符串内部的运算符触发误判。
"..."\"6. Numeric Literals Are Safe Without Parens
6. 数值字面量无需括号即可安全使用
42.toText()0xFF.toNat()42.toText()0xFF.toNat()Function Catalog (core 2.2.0)
函数目录(core 2.2.0)
Zero-Extra-Arg Functions (self only)
无额外参数的函数(仅self)
These take exactly one argument named . Convert → .
selfModule.func(expr)expr.func()python
ZERO_EXTRA = [
# Nat8
('Nat8', 'toNat'), ('Nat8', 'toText'), ('Nat8', 'toNat16'),
('Nat8', 'toNat32'), ('Nat8', 'toNat64'),
# Nat16
('Nat16', 'toNat'), ('Nat16', 'toText'), ('Nat16', 'toNat8'),
('Nat16', 'toNat32'), ('Nat16', 'toNat64'),
# Nat32
('Nat32', 'toNat'), ('Nat32', 'toText'), ('Nat32', 'toNat8'),
('Nat32', 'toNat16'), ('Nat32', 'toNat64'), ('Nat32', 'toChar'),
# Nat64
('Nat64', 'toNat'), ('Nat64', 'toText'), ('Nat64', 'toNat8'),
('Nat64', 'toNat16'), ('Nat64', 'toNat32'),
# Nat
('Nat', 'toText'), ('Nat', 'toInt'), ('Nat', 'toFloat'),
('Nat', 'toNat8'), ('Nat', 'toNat16'), ('Nat', 'toNat32'), ('Nat', 'toNat64'),
# Int
('Int', 'toText'), ('Int', 'toNat'), ('Int', 'toFloat'),
('Int', 'toInt8'), ('Int', 'toInt16'), ('Int', 'toInt32'), ('Int', 'toInt64'),
# Char
('Char', 'toNat32'), ('Char', 'toText'), ('Char', 'isDigit'),
('Char', 'isAlphabetic'), ('Char', 'isWhitespace'),
('Char', 'isLowercase'), ('Char', 'isUppercase'),
# Blob
('Blob', 'toArray'), ('Blob', 'toVarArray'), ('Blob', 'isEmpty'),
('Blob', 'size'), ('Blob', 'hash'),
# Text (zero-arg self functions)
('Text', 'toArray'), ('Text', 'toIter'), ('Text', 'toVarArray'),
('Text', 'encodeUtf8'), ('Text', 'decodeUtf8'), ('Text', 'size'),
('Text', 'isEmpty'), ('Text', 'reverse'), ('Text', 'toText'),
# Array (zero-arg self functions)
('Array', 'toVarArray'), ('Array', 'size'), ('Array', 'isEmpty'),
('Array', 'keys'), ('Array', 'values'), ('Array', 'reverse'),
('Array', 'enumerate'),
# VarArray (zero-arg self functions)
('VarArray', 'toArray'), ('VarArray', 'clone'), ('VarArray', 'size'),
('VarArray', 'isEmpty'), ('VarArray', 'keys'), ('VarArray', 'values'),
('VarArray', 'reverse'), ('VarArray', 'enumerate'),
# List (zero-arg self functions)
('List', 'size'), ('List', 'toArray'), ('List', 'toVarArray'),
('List', 'isEmpty'), ('List', 'first'), ('List', 'last'),
('List', 'clone'), ('List', 'clear'), ('List', 'values'),
('List', 'keys'), ('List', 'enumerate'), ('List', 'reverse'),
('List', 'removeLast'),
]这些函数仅接受一个名为的参数。转换规则: → 。
selfModule.func(expr)expr.func()python
ZERO_EXTRA = [
# Nat8
('Nat8', 'toNat'), ('Nat8', 'toText'), ('Nat8', 'toNat16'),
('Nat8', 'toNat32'), ('Nat8', 'toNat64'),
# Nat16
('Nat16', 'toNat'), ('Nat16', 'toText'), ('Nat16', 'toNat8'),
('Nat16', 'toNat32'), ('Nat16', 'toNat64'),
# Nat32
('Nat32', 'toNat'), ('Nat32', 'toText'), ('Nat32', 'toNat8'),
('Nat32', 'toNat16'), ('Nat32', 'toNat64'), ('Nat32', 'toChar'),
# Nat64
('Nat64', 'toNat'), ('Nat64', 'toText'), ('Nat64', 'toNat8'),
('Nat64', 'toNat16'), ('Nat64', 'toNat32'),
# Nat
('Nat', 'toText'), ('Nat', 'toInt'), ('Nat', 'toFloat'),
('Nat', 'toNat8'), ('Nat', 'toNat16'), ('Nat', 'toNat32'), ('Nat', 'toNat64'),
# Int
('Int', 'toText'), ('Int', 'toNat'), ('Int', 'toFloat'),
('Int', 'toInt8'), ('Int', 'toInt16'), ('Int', 'toInt32'), ('Int', 'toInt64'),
# Char
('Char', 'toNat32'), ('Char', 'toText'), ('Char', 'isDigit'),
('Char', 'isAlphabetic'), ('Char', 'isWhitespace'),
('Char', 'isLowercase'), ('Char', 'isUppercase'),
# Blob
('Blob', 'toArray'), ('Blob', 'toVarArray'), ('Blob', 'isEmpty'),
('Blob', 'size'), ('Blob', 'hash'),
# Text (zero-arg self functions)
('Text', 'toArray'), ('Text', 'toIter'), ('Text', 'toVarArray'),
('Text', 'encodeUtf8'), ('Text', 'decodeUtf8'), ('Text', 'size'),
('Text', 'isEmpty'), ('Text', 'reverse'), ('Text', 'toText'),
# Array (zero-arg self functions)
('Array', 'toVarArray'), ('Array', 'size'), ('Array', 'isEmpty'),
('Array', 'keys'), ('Array', 'values'), ('Array', 'reverse'),
('Array', 'enumerate'),
# VarArray (zero-arg self functions)
('VarArray', 'toArray'), ('VarArray', 'clone'), ('VarArray', 'size'),
('VarArray', 'isEmpty'), ('VarArray', 'keys'), ('VarArray', 'values'),
('VarArray', 'reverse'), ('VarArray', 'enumerate'),
# List (zero-arg self functions)
('List', 'size'), ('List', 'toArray'), ('List', 'toVarArray'),
('List', 'isEmpty'), ('List', 'first'), ('List', 'last'),
('List', 'clone'), ('List', 'clear'), ('List', 'values'),
('List', 'keys'), ('List', 'enumerate'), ('List', 'reverse'),
('List', 'removeLast'),
]Multi-Extra-Arg Functions (self + more args)
带额外参数的函数(self + 更多参数)
These take plus additional arguments. Convert → .
selfModule.func(expr, a, b)expr.func(a, b)Note: Some of these CAN be called with just self (e.g. has no extra args). The multi-arg handler must handle both cases: if no comma is found after self, emit ; if comma is found, emit .
Array.flatten(xs)self.func()self.func(rest...)python
MULTI_EXTRA = [
# Array
('Array', 'flatten'), ('Array', 'map'), ('Array', 'filter'),
('Array', 'filterMap'), ('Array', 'flatMap'),
('Array', 'foldLeft'), ('Array', 'foldRight'),
('Array', 'concat'), ('Array', 'sliceToArray'),
('Array', 'forEach'), ('Array', 'mapEntries'),
('Array', 'sort'), ('Array', 'find'), ('Array', 'findIndex'),
('Array', 'equal'), ('Array', 'compare'),
('Array', 'all'), ('Array', 'any'),
('Array', 'indexOf'), ('Array', 'contains'),
('Array', 'toText'), ('Array', 'binarySearch'),
('Array', 'isSorted'), ('Array', 'mapResult'), ('Array', 'range'),
# VarArray
('VarArray', 'sortInPlace'), ('VarArray', 'map'), ('VarArray', 'filter'),
('VarArray', 'filterMap'), ('VarArray', 'flatMap'),
('VarArray', 'foldLeft'), ('VarArray', 'foldRight'),
('VarArray', 'concat'), ('VarArray', 'forEach'),
('VarArray', 'sort'), ('VarArray', 'find'), ('VarArray', 'findIndex'),
('VarArray', 'equal'), ('VarArray', 'compare'),
('VarArray', 'all'), ('VarArray', 'any'),
('VarArray', 'indexOf'), ('VarArray', 'contains'),
('VarArray', 'toText'), ('VarArray', 'mapResult'),
# Text
('Text', 'replace'), ('Text', 'tokens'), ('Text', 'contains'),
('Text', 'split'), ('Text', 'startsWith'), ('Text', 'endsWith'),
('Text', 'concat'), ('Text', 'map'), ('Text', 'flatMap'),
('Text', 'foldLeft'), ('Text', 'join'),
('Text', 'trimStart'), ('Text', 'trimEnd'), ('Text', 'trim'),
('Text', 'stripStart'), ('Text', 'stripEnd'),
# List
('List', 'add'), ('List', 'at'), ('List', 'get'), ('List', 'put'),
('List', 'map'), ('List', 'filter'), ('List', 'forEach'),
('List', 'sort'), ('List', 'find'), ('List', 'findIndex'),
# Blob
('Blob', 'compare'), ('Blob', 'equal'),
]这些函数接受以及其他参数。转换规则: → 。
selfModule.func(expr, a, b)expr.func(a, b)注意:其中一些函数也可以仅用self调用(例如没有额外参数)。多参数处理程序必须同时处理两种情况:如果self后没有逗号,则输出;如果有逗号,则输出。
Array.flatten(xs)self.func()self.func(rest...)python
MULTI_EXTRA = [
# Array
('Array', 'flatten'), ('Array', 'map'), ('Array', 'filter'),
('Array', 'filterMap'), ('Array', 'flatMap'),
('Array', 'foldLeft'), ('Array', 'foldRight'),
('Array', 'concat'), ('Array', 'sliceToArray'),
('Array', 'forEach'), ('Array', 'mapEntries'),
('Array', 'sort'), ('Array', 'find'), ('Array', 'findIndex'),
('Array', 'equal'), ('Array', 'compare'),
('Array', 'all'), ('Array', 'any'),
('Array', 'indexOf'), ('Array', 'contains'),
('Array', 'toText'), ('Array', 'binarySearch'),
('Array', 'isSorted'), ('Array', 'mapResult'), ('Array', 'range'),
# VarArray
('VarArray', 'sortInPlace'), ('VarArray', 'map'), ('VarArray', 'filter'),
('VarArray', 'filterMap'), ('VarArray', 'flatMap'),
('VarArray', 'foldLeft'), ('VarArray', 'foldRight'),
('VarArray', 'concat'), ('VarArray', 'forEach'),
('VarArray', 'sort'), ('VarArray', 'find'), ('VarArray', 'findIndex'),
('VarArray', 'equal'), ('VarArray', 'compare'),
('VarArray', 'all'), ('VarArray', 'any'),
('VarArray', 'indexOf'), ('VarArray', 'contains'),
('VarArray', 'toText'), ('VarArray', 'mapResult'),
# Text
('Text', 'replace'), ('Text', 'tokens'), ('Text', 'contains'),
('Text', 'split'), ('Text', 'startsWith'), ('Text', 'endsWith'),
('Text', 'concat'), ('Text', 'map'), ('Text', 'flatMap'),
('Text', 'foldLeft'), ('Text', 'join'),
('Text', 'trimStart'), ('Text', 'trimEnd'), ('Text', 'trim'),
('Text', 'stripStart'), ('Text', 'stripEnd'),
# List
('List', 'add'), ('List', 'at'), ('List', 'get'), ('List', 'put'),
('List', 'map'), ('List', 'filter'), ('List', 'forEach'),
('List', 'sort'), ('List', 'find'), ('List', 'findIndex'),
# Blob
('Blob', 'compare'), ('Blob', 'equal'),
]How to Update the Catalog for a New Core Version
如何为新版本Core更新函数目录
When the package is updated, new functions may have been added or signatures changed. Run this shell snippet to regenerate the catalog from the actual source:
corebash
#!/bin/bash当包更新时,可能会添加新函数或修改签名。运行以下shell代码片段,从实际源代码重新生成目录:
corebash
#!/bin/bashUsage: ./scan_self_functions.sh /path/to/.mops/core@X.Y.Z/src
Usage: ./scan_self_functions.sh /path/to/.mops/core@X.Y.Z/src
CORE_SRC="${1:-.mops/core@2.2.0/src}"
echo "=== Functions WITH self as first parameter (convert to dot notation) ==="
for f in "$CORE_SRC"/.mo; do
module=$(basename "$f" .mo)
# Match: public func name(self OR public let name = func(self
grep -nE '^\spublic\s+(func|let)\s+\w+' "$f" | while read -r line; do
# Extract function name
fname=$(echo "$line" | sed -E 's/.public\s+(func|let)\s+([a-zA-Z0-9_]+)./\2/')
# Check if first param is named 'self'
if echo "$line" | grep -qE '(\sself\s[:)]'; then
echo " ($module, $fname) -- has self"
elif echo "$line" | grep -qE '=\sfunc\s(\sself\s[:)]'; then
echo " ($module, $fname) -- has self (let)"
fi
done
done
echo ""
echo "=== Functions WITHOUT self (do NOT convert) ==="
for f in "$CORE_SRC"/.mo; do
module=$(basename "$f" .mo)
grep -nE '^\spublic\s+(func|let)\s+\w+' "$f" | while read -r line; do
fname=$(echo "$line" | sed -E 's/.public\s+(func|let)\s+([a-zA-Z0-9_]+)./\2/')
if ! echo "$line" | grep -qE '(\sself\s[:)]' &&
! echo "$line" | grep -qE '=\sfunc\s(\sself\s[:)]'; then echo " $module.$fname" fi done done
! echo "$line" | grep -qE '=\sfunc\s(\sself\s[:)]'; then echo " $module.$fname" fi done done
**Important:** Some `public let` declarations use inline lambda syntax:
```motoko
public let toNat = Prim.nat8ToNat; // No parens visible — check Prim binding
public let encodeUtf8 : Text -> Blob = Prim.encodeUtf8; // self is implicitFor bindings that reference Prim functions taking a single value, check the type signature: if it's , the single argument is . These support dot notation.
public letT -> UselfAfter scanning, classify each function:
- ZERO_EXTRA: Takes only (no other params). Goes in the zero-extra list.
self - MULTI_EXTRA: Takes plus other params. Goes in the multi-extra list.
self - NO_SELF: First param is NOT named . Do NOT convert.
self
Then update the and lists in the conversion script below, limiting to only the functions actually used in your project's source files (to avoid false matches on identically-named local functions).
ZERO_EXTRAMULTI_EXTRACORE_SRC="${1:-.mops/core@2.2.0/src}"
echo "=== 第一个参数为self的函数(转换为点表示法) ==="
for f in "$CORE_SRC"/.mo; do
module=$(basename "$f" .mo)
# 匹配: public func name(self 或 public let name = func(self
grep -nE '^\spublic\s+(func|let)\s+\w+' "$f" | while read -r line; do
# 提取函数名
fname=$(echo "$line" | sed -E 's/.public\s+(func|let)\s+([a-zA-Z0-9_]+)./\2/')
# 检查第一个参数是否名为'self'
if echo "$line" | grep -qE '(\sself\s[:)]'; then
echo " ($module, $fname) -- 包含self"
elif echo "$line" | grep -qE '=\sfunc\s(\sself\s[:)]'; then
echo " ($module, $fname) -- 包含self (let)"
fi
done
done
echo ""
echo "=== 不包含self的函数(请勿转换) ==="
for f in "$CORE_SRC"/.mo; do
module=$(basename "$f" .mo)
grep -nE '^\spublic\s+(func|let)\s+\w+' "$f" | while read -r line; do
fname=$(echo "$line" | sed -E 's/.public\s+(func|let)\s+([a-zA-Z0-9_]+)./\2/')
if ! echo "$line" | grep -qE '(\sself\s[:)]' &&
! echo "$line" | grep -qE '=\sfunc\s(\sself\s[:)]'; then echo " $module.$fname" fi done done
! echo "$line" | grep -qE '=\sfunc\s(\sself\s[:)]'; then echo " $module.$fname" fi done done
**重要提示**:一些`public let`声明使用内联lambda语法:
```motoko
public let toNat = Prim.nat8ToNat; // 看不到括号 — 检查Prim绑定
public let encodeUtf8 : Text -> Blob = Prim.encodeUtf8; // self是隐式的对于引用接受单个值的Prim函数的绑定,检查类型签名:如果是,则单个参数就是,这些函数支持点表示法。
public letT -> Uself扫描后,对每个函数进行分类:
- ZERO_EXTRA:仅接受(无其他参数),归入无额外参数列表。
self - MULTI_EXTRA:接受和其他参数,归入带额外参数列表。
self - NO_SELF:第一个参数不是,请勿转换。
self
然后更新转换脚本中的和列表,仅保留项目源文件中实际使用的函数(避免与同名本地函数误匹配)。
ZERO_EXTRAMULTI_EXTRAConversion Script
转换脚本
Save this as and run with . Edit the , lists and path as needed for your project.
convert_dot_notation.pypython3 convert_dot_notation.pyZERO_EXTRAMULTI_EXTRAsrc_dirpython
#!/usr/bin/env python3
"""
Convert Module.func(self, ...) calls to self.func(...) dot notation.
Usage:
1. Update ZERO_EXTRA and MULTI_EXTRA lists for your core version and project.
2. Set src_dir to your project's source directory.
3. Run: python3 convert_dot_notation.py
4. Run tests: npx mops test
5. If all pass, delete this script.
"""
import re
import os将以下代码保存为,并使用运行。根据你的项目需求,编辑、列表和路径。
convert_dot_notation.pypython3 convert_dot_notation.pyZERO_EXTRAMULTI_EXTRAsrc_dirpython
#!/usr/bin/env python3
"""
Convert Module.func(self, ...) calls to self.func(...) dot notation.
Usage:
1. Update ZERO_EXTRA and MULTI_EXTRA lists for your core version and project.
2. Set src_dir to your project's source directory.
3. Run: python3 convert_dot_notation.py
4. Run tests: npx mops test
5. If all pass, delete this script.
"""
import re
import os──────────────────────────────────────────────────────────────────────
──────────────────────────────────────────────────────────────────────
CONFIGURATION: Update these lists per your core version and project.
CONFIGURATION: Update these lists per your core version and project.
Only include (Module, func) pairs that are actually used in your code.
Only include (Module, func) pairs that are actually used in your code.
──────────────────────────────────────────────────────────────────────
──────────────────────────────────────────────────────────────────────
Functions that take ONLY self (zero extra args):
Functions that take ONLY self (zero extra args):
Module.func(self) -> self.func()
Module.func(self) -> self.func()
ZERO_EXTRA = [
('Nat8', 'toNat'), ('Nat8', 'toText'), ('Nat8', 'toNat32'), ('Nat8', 'toNat64'),
('Nat16', 'toNat'), ('Nat16', 'toText'), ('Nat16', 'toNat64'),
('Nat32', 'toNat'), ('Nat32', 'toText'), ('Nat32', 'toNat8'), ('Nat32', 'toNat64'),
('Nat64', 'toNat'), ('Nat64', 'toText'), ('Nat64', 'toNat8'), ('Nat64', 'toNat16'),
('Nat', 'toText'),
('Int', 'toText'), ('Int', 'toNat'),
('Char', 'toNat32'),
('Blob', 'toArray'),
('Text', 'toArray'), ('Text', 'encodeUtf8'), ('Text', 'decodeUtf8'),
]
ZERO_EXTRA = [
('Nat8', 'toNat'), ('Nat8', 'toText'), ('Nat8', 'toNat32'), ('Nat8', 'toNat64'),
('Nat16', 'toNat'), ('Nat16', 'toText'), ('Nat16', 'toNat64'),
('Nat32', 'toNat'), ('Nat32', 'toText'), ('Nat32', 'toNat8'), ('Nat32', 'toNat64'),
('Nat64', 'toNat'), ('Nat64', 'toText'), ('Nat64', 'toNat8'), ('Nat64', 'toNat16'),
('Nat', 'toText'),
('Int', 'toText'), ('Int', 'toNat'),
('Char', 'toNat32'),
('Blob', 'toArray'),
('Text', 'toArray'), ('Text', 'encodeUtf8'), ('Text', 'decodeUtf8'),
]
Functions that take self + possibly more args:
Functions that take self + possibly more args:
Module.func(self, rest...) -> self.func(rest...)
Module.func(self, rest...) -> self.func(rest...)
Module.func(self) -> self.func() (when no extra args)
Module.func(self) -> self.func() (when no extra args)
MULTI_EXTRA = [
('List', 'size'),
('List', 'toArray'),
('Array', 'flatten'),
('Array', 'map'),
('Array', 'filter'),
('Array', 'foldLeft'),
('Array', 'concat'),
('Array', 'sliceToArray'),
('Text', 'replace'),
('Text', 'tokens'),
('Text', 'contains'),
]
MULTI_EXTRA = [
('List', 'size'),
('List', 'toArray'),
('Array', 'flatten'),
('Array', 'map'),
('Array', 'filter'),
('Array', 'foldLeft'),
('Array', 'concat'),
('Array', 'sliceToArray'),
('Text', 'replace'),
('Text', 'tokens'),
('Text', 'contains'),
]
──────────────────────────────────────────────────────────────────────
──────────────────────────────────────────────────────────────────────
ENGINE: Do not modify below unless fixing bugs.
ENGINE: Do not modify below unless fixing bugs.
──────────────────────────────────────────────────────────────────────
──────────────────────────────────────────────────────────────────────
def needs_parens(s):
"""Check if an expression needs wrapping in parens for safe dot notation.
Returns True if the expression contains top-level infix operators that
would cause `.func()` to bind incorrectly (dot binds tighter).
"""
s = s.strip()
if not s:
return False
# Simple identifier: no parens
if re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', s):
return False
# Entire expression already wrapped in matching parens
if s[0] == '(' and s[-1] == ')':
depth = 0
for i, c in enumerate(s):
if c == '(':
depth += 1
elif c == ')':
depth -= 1
if depth == 0 and i < len(s) - 1:
break
else:
return False
# Entire expression is an array literal
if s[0] == '[' and s[-1] == ']':
depth = 0
for i, c in enumerate(s):
if c == '[':
depth += 1
elif c == ']':
depth -= 1
if depth == 0 and i < len(s) - 1:
break
else:
return False
# Numeric literal
if re.match(r'^[0-9][0-9a-fA-FxX_]*$', s):
return False
# Scan for top-level infix operators
depth_paren = 0
depth_bracket = 0
depth_brace = 0
i = 0
while i < len(s):
c = s[i]
if c == '(':
depth_paren += 1
elif c == ')':
depth_paren -= 1
elif c == '[':
depth_bracket += 1
elif c == ']':
depth_bracket -= 1
elif c == '{':
depth_brace += 1
elif c == '}':
depth_brace -= 1
elif c == '"':
# Skip string literals
i += 1
while i < len(s) and s[i] != '"':
if s[i] == '\\':
i += 1
i += 1
elif depth_paren == 0 and depth_bracket == 0 and depth_brace == 0:
rest = s[i:]
# Multi-char operators
if (rest.startswith('>>') or rest.startswith('<<') or
rest.startswith('==') or rest.startswith('!=') or
rest.startswith('and ') or rest.startswith('or ') or
rest.startswith('#')):
return True
# Single-char binary operators in infix position
if c in '&|^%' and i > 0:
return True
if c in '+-' and i > 0 and s[i-1] not in '(,;:=[<>':
return True
if c in '*/' and i > 0:
return True
i += 1
return Falsedef wrap_if_needed(s):
"""Wrap expression in parens if needed for dot notation."""
if needs_parens(s):
return '(' + s + ')'
return s
def find_matching_paren(s, start):
"""Find matching closing paren for opening paren at position start.
Returns the index of the closing paren, or -1 if not found.
"""
depth = 1
i = start + 1
while i < len(s) and depth > 0:
if s[i] == '(':
depth += 1
elif s[i] == ')':
depth -= 1
elif s[i] == '"':
# Skip string literal
i += 1
while i < len(s) and s[i] != '"':
if s[i] == '\':
i += 1
i += 1
i += 1
return i - 1 if depth == 0 else -1
def find_first_comma_at_depth0(s):
"""Find first comma at depth 0 (outside parens, brackets, braces, AND angle brackets).
CRITICAL: Must track <> depth so commas inside type parameters like
<Nat64, [Nat8]> are not treated as argument separators.
"""
depth_paren = 0
depth_bracket = 0
depth_brace = 0
depth_angle = 0
i = 0
while i < len(s):
c = s[i]
if c == '(':
depth_paren += 1
elif c == ')':
depth_paren -= 1
elif c == '[':
depth_bracket += 1
elif c == ']':
depth_bracket -= 1
elif c == '{':
depth_brace += 1
elif c == '}':
depth_brace -= 1
elif c == '<':
depth_angle += 1
elif c == '>' and depth_angle > 0:
# Only decrement if we're inside angle brackets.
# Bare > (comparison) should not affect depth.
depth_angle -= 1
elif c == '"':
i += 1
while i < len(s) and s[i] != '"':
if s[i] == '\\':
i += 1
i += 1
elif (c == ',' and depth_paren == 0 and depth_bracket == 0
and depth_brace == 0 and depth_angle == 0):
return i
i += 1
return -1def find_all_args(s):
"""Split content inside parens into (self_arg, rest_text).
rest_text includes everything after the first depth-0 comma (preserving whitespace).
"""
comma_pos = find_first_comma_at_depth0(s)
if comma_pos == -1:
return s.strip(), ""
else:
return s[:comma_pos].strip(), s[comma_pos + 1:]
def process_zero_extra(text, module, func):
"""Convert Module.func(arg) -> arg.func() for zero-extra-arg functions."""
pattern = re.compile(
r'\b' + re.escape(module) + r'.' + re.escape(func) + r'(?=[\s<(])')
result = text
offset = 0
changes = 0
while True:
m = pattern.search(result, offset)
if not m:
break
pos = m.end()
while pos < len(result) and result[pos] in ' \t\n':
pos += 1
# Optional type params <...>
type_params = ""
if pos < len(result) and result[pos] == '<':
depth = 1
tp_start = pos
pos += 1
while pos < len(result) and depth > 0:
if result[pos] == '<':
depth += 1
elif result[pos] == '>':
depth -= 1
pos += 1
type_params = result[tp_start:pos]
while pos < len(result) and result[pos] in ' \t\n':
pos += 1
if pos >= len(result) or result[pos] != '(':
offset = m.end()
continue
close = find_matching_paren(result, pos)
if close == -1:
offset = m.end()
continue
inner = result[pos + 1:close]
self_arg = inner.strip()
if not self_arg:
offset = m.end()
continue
wrapped = wrap_if_needed(self_arg)
replacement = f"{wrapped}.{func}{type_params}()"
result = result[:m.start()] + replacement + result[close + 1:]
offset = m.start() + len(replacement)
changes += 1
return result, changesdef process_multi_extra(text, module, func):
"""Convert Module.func(self, rest...) -> self.func(rest...) for multi-arg functions."""
pattern = re.compile(
r'\b' + re.escape(module) + r'.' + re.escape(func) + r'(?=[\s<(])')
result = text
offset = 0
changes = 0
while True:
m = pattern.search(result, offset)
if not m:
break
pos = m.end()
while pos < len(result) and result[pos] in ' \t\n':
pos += 1
type_params = ""
if pos < len(result) and result[pos] == '<':
depth = 1
tp_start = pos
pos += 1
while pos < len(result) and depth > 0:
if result[pos] == '<':
depth += 1
elif result[pos] == '>':
depth -= 1
pos += 1
type_params = result[tp_start:pos]
while pos < len(result) and result[pos] in ' \t\n':
pos += 1
if pos >= len(result) or result[pos] != '(':
offset = m.end()
continue
close = find_matching_paren(result, pos)
if close == -1:
offset = m.end()
continue
inner = result[pos + 1:close]
self_arg, rest = find_all_args(inner)
if not self_arg:
offset = m.end()
continue
wrapped = wrap_if_needed(self_arg)
if rest == "":
replacement = f"{wrapped}.{func}{type_params}()"
else:
replacement = f"{wrapped}.{func}{type_params}({rest})"
result = result[:m.start()] + replacement + result[close + 1:]
offset = m.start() + len(replacement)
changes += 1
return result, changesdef process_file(filepath):
with open(filepath, 'r') as f:
text = f.read()
total_changes = 0
for module, func in ZERO_EXTRA:
text, changes = process_zero_extra(text, module, func)
total_changes += changes
for module, func in MULTI_EXTRA:
text, changes = process_multi_extra(text, module, func)
total_changes += changes
if total_changes > 0:
with open(filepath, 'w') as f:
f.write(text)
print(f" {filepath}: {total_changes} changes")
return total_changesdef main():
# ── Change this to your project's source directory ──
src_dir = os.path.join(os.path.dirname(file), 'src')
total = 0
for root, dirs, files in os.walk(src_dir):
for f in sorted(files):
if f.endswith('.mo'):
filepath = os.path.join(root, f)
total += process_file(filepath)
print(f"\nTotal changes: {total}")
if name == 'main':
main()
undefineddef needs_parens(s):
"""Check if an expression needs wrapping in parens for safe dot notation.
Returns True if the expression contains top-level infix operators that
would cause `.func()` to bind incorrectly (dot binds tighter).
"""
s = s.strip()
if not s:
return False
# Simple identifier: no parens
if re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', s):
return False
# Entire expression already wrapped in matching parens
if s[0] == '(' and s[-1] == ')':
depth = 0
for i, c in enumerate(s):
if c == '(':
depth += 1
elif c == ')':
depth -= 1
if depth == 0 and i < len(s) - 1:
break
else:
return False
# Entire expression is an array literal
if s[0] == '[' and s[-1] == ']':
depth = 0
for i, c in enumerate(s):
if c == '[':
depth += 1
elif c == ']':
depth -= 1
if depth == 0 and i < len(s) - 1:
break
else:
return False
# Numeric literal
if re.match(r'^[0-9][0-9a-fA-FxX_]*$', s):
return False
# Scan for top-level infix operators
depth_paren = 0
depth_bracket = 0
depth_brace = 0
i = 0
while i < len(s):
c = s[i]
if c == '(':
depth_paren += 1
elif c == ')':
depth_paren -= 1
elif c == '[':
depth_bracket += 1
elif c == ']':
depth_bracket -= 1
elif c == '{':
depth_brace += 1
elif c == '}':
depth_brace -= 1
elif c == '"':
# Skip string literals
i += 1
while i < len(s) and s[i] != '"':
if s[i] == '\\':
i += 1
i += 1
elif depth_paren == 0 and depth_bracket == 0 and depth_brace == 0:
rest = s[i:]
# Multi-char operators
if (rest.startswith('>>') or rest.startswith('<<') or
rest.startswith('==') or rest.startswith('!=') or
rest.startswith('and ') or rest.startswith('or ') or
rest.startswith('#')):
return True
# Single-char binary operators in infix position
if c in '&|^%' and i > 0:
return True
if c in '+-' and i > 0 and s[i-1] not in '(,;:=[<>':
return True
if c in '*/' and i > 0:
return True
i += 1
return Falsedef wrap_if_needed(s):
"""Wrap expression in parens if needed for dot notation."""
if needs_parens(s):
return '(' + s + ')'
return s
def find_matching_paren(s, start):
"""Find matching closing paren for opening paren at position start.
Returns the index of the closing paren, or -1 if not found.
"""
depth = 1
i = start + 1
while i < len(s) and depth > 0:
if s[i] == '(':
depth += 1
elif s[i] == ')':
depth -= 1
elif s[i] == '"':
# Skip string literal
i += 1
while i < len(s) and s[i] != '"':
if s[i] == '\':
i += 1
i += 1
i += 1
return i - 1 if depth == 0 else -1
def find_first_comma_at_depth0(s):
"""Find first comma at depth 0 (outside parens, brackets, braces, AND angle brackets).
CRITICAL: Must track <> depth so commas inside type parameters like
<Nat64, [Nat8]> are not treated as argument separators.
"""
depth_paren = 0
depth_bracket = 0
depth_brace = 0
depth_angle = 0
i = 0
while i < len(s):
c = s[i]
if c == '(':
depth_paren += 1
elif c == ')':
depth_paren -= 1
elif c == '[':
depth_bracket += 1
elif c == ']':
depth_bracket -= 1
elif c == '{':
depth_brace += 1
elif c == '}':
depth_brace -= 1
elif c == '<':
depth_angle += 1
elif c == '>' and depth_angle > 0:
# Only decrement if we're inside angle brackets.
# Bare > (comparison) should not affect depth.
depth_angle -= 1
elif c == '"':
i += 1
while i < len(s) and s[i] != '"':
if s[i] == '\\':
i += 1
i += 1
elif (c == ',' and depth_paren == 0 and depth_bracket == 0
and depth_brace == 0 and depth_angle == 0):
return i
i += 1
return -1def find_all_args(s):
"""Split content inside parens into (self_arg, rest_text).
rest_text includes everything after the first depth-0 comma (preserving whitespace).
"""
comma_pos = find_first_comma_at_depth0(s)
if comma_pos == -1:
return s.strip(), ""
else:
return s[:comma_pos].strip(), s[comma_pos + 1:]
def process_zero_extra(text, module, func):
"""Convert Module.func(arg) -> arg.func() for zero-extra-arg functions."""
pattern = re.compile(
r'\b' + re.escape(module) + r'.' + re.escape(func) + r'(?=[\s<(])')
result = text
offset = 0
changes = 0
while True:
m = pattern.search(result, offset)
if not m:
break
pos = m.end()
while pos < len(result) and result[pos] in ' \t\n':
pos += 1
# Optional type params <...>
type_params = ""
if pos < len(result) and result[pos] == '<':
depth = 1
tp_start = pos
pos += 1
while pos < len(result) and depth > 0:
if result[pos] == '<':
depth += 1
elif result[pos] == '>':
depth -= 1
pos += 1
type_params = result[tp_start:pos]
while pos < len(result) and result[pos] in ' \t\n':
pos += 1
if pos >= len(result) or result[pos] != '(':
offset = m.end()
continue
close = find_matching_paren(result, pos)
if close == -1:
offset = m.end()
continue
inner = result[pos + 1:close]
self_arg = inner.strip()
if not self_arg:
offset = m.end()
continue
wrapped = wrap_if_needed(self_arg)
replacement = f"{wrapped}.{func}{type_params}()"
result = result[:m.start()] + replacement + result[close + 1:]
offset = m.start() + len(replacement)
changes += 1
return result, changesdef process_multi_extra(text, module, func):
"""Convert Module.func(self, rest...) -> self.func(rest...) for multi-arg functions."""
pattern = re.compile(
r'\b' + re.escape(module) + r'.' + re.escape(func) + r'(?=[\s<(])')
result = text
offset = 0
changes = 0
while True:
m = pattern.search(result, offset)
if not m:
break
pos = m.end()
while pos < len(result) and result[pos] in ' \t\n':
pos += 1
type_params = ""
if pos < len(result) and result[pos] == '<':
depth = 1
tp_start = pos
pos += 1
while pos < len(result) and depth > 0:
if result[pos] == '<':
depth += 1
elif result[pos] == '>':
depth -= 1
pos += 1
type_params = result[tp_start:pos]
while pos < len(result) and result[pos] in ' \t\n':
pos += 1
if pos >= len(result) or result[pos] != '(':
offset = m.end()
continue
close = find_matching_paren(result, pos)
if close == -1:
offset = m.end()
continue
inner = result[pos + 1:close]
self_arg, rest = find_all_args(inner)
if not self_arg:
offset = m.end()
continue
wrapped = wrap_if_needed(self_arg)
if rest == "":
replacement = f"{wrapped}.{func}{type_params}()"
else:
replacement = f"{wrapped}.{func}{type_params}({rest})"
result = result[:m.start()] + replacement + result[close + 1:]
offset = m.start() + len(replacement)
changes += 1
return result, changesdef process_file(filepath):
with open(filepath, 'r') as f:
text = f.read()
total_changes = 0
for module, func in ZERO_EXTRA:
text, changes = process_zero_extra(text, module, func)
total_changes += changes
for module, func in MULTI_EXTRA:
text, changes = process_multi_extra(text, module, func)
total_changes += changes
if total_changes > 0:
with open(filepath, 'w') as f:
f.write(text)
print(f" {filepath}: {total_changes} changes")
return total_changesdef main():
# ── Change this to your project's source directory ──
src_dir = os.path.join(os.path.dirname(file), 'src')
total = 0
for root, dirs, files in os.walk(src_dir):
for f in sorted(files):
if f.endswith('.mo'):
filepath = os.path.join(root, f)
total += process_file(filepath)
print(f"\nTotal changes: {total}")
if name == 'main':
main()
undefinedStep-by-Step Procedure
分步操作流程
-
Identify the core version from:
mops.tomlbashgrep 'core' mops.toml -
Scan the core source to build/verify the function catalog:bash
# List all public functions with self as first param grep -rnE 'public\s+(func|let)\s+\w+' .mops/core@*/src/*.mo | grep -E '\(\s*self\s*[:\)]' -
Determine which functions your project actually uses:bash
# Find all Module.func( patterns in your source grep -rnoE '\b(Array|Blob|Text|List|VarArray|Nat8?|Nat16|Nat32|Nat64|Int|Char)\.[a-zA-Z]+\(' src/ | \ sed 's/.*://' | sort | uniq -c | sort -rnOnly include used functions in the script to avoid false matches. -
Classify each used function as ZERO_EXTRA or MULTI_EXTRA by checking its signature in the core source.
-
Run the conversion:bash
python3 convert_dot_notation.py -
Run tests:bash
npx mops test -
If tests fail, check for:
- Operator precedence issues (missing parens around infix expressions)
- Type parameter comma splitting (the depth tracking bug)
<> - Functions that shouldn't have been converted (not in the list)
self
-
Clean up: Deleteafter all tests pass.
convert_dot_notation.py
-
从中确定core版本:
mops.tomlbashgrep 'core' mops.toml -
扫描core源代码以构建/验证函数目录:bash
# 列出所有第一个参数为self的公共函数 grep -rnE 'public\s+(func|let)\s+\w+' .mops/core@*/src/*.mo | grep -E '\(\s*self\s*[:\)]' -
确定项目实际使用的函数:bash
# 在源代码中查找所有Module.func(模式 grep -rnoE '\b(Array|Blob|Text|List|VarArray|Nat8?|Nat16|Nat32|Nat64|Int|Char)\.[a-zA-Z]+\(' src/ | \ sed 's/.*://' | sort | uniq -c | sort -rn仅在脚本中包含实际使用的函数,避免误匹配。 -
通过检查core源代码中的签名,将每个使用的函数分类为ZERO_EXTRA或MULTI_EXTRA。
-
运行转换脚本:bash
python3 convert_dot_notation.py -
运行测试:bash
npx mops test -
如果测试失败,检查以下问题:
- 运算符优先级问题(中缀表达式缺少括号)
- 类型参数逗号拆分错误(<>深度跟踪bug)
- 不应转换的函数被错误转换(不在self列表中)
-
清理:所有测试通过后,删除。
convert_dot_notation.py
Verification Checklist
验证检查清单
- All calls converted to
Module.func(self, ...)self.func(...) - No factory functions converted (,
tabulate,fromArray,fromVarArray,repeat,empty,range,fromNat, etc.)fromIntWrap - converted separately to
Array.fromVarArray(x)(not part of dot-notation script)x.toArray() - Infix expressions wrapped in parens: ,
(a >> b).toNat()(x & y).toText() - Type parameters preserved: not corrupted
xs.map<Nat64, [Nat8]>(f) - type annotations kept (compiler cannot infer them)
Array.tabulate<T> - Imports for dot-notation types still present (,
Blob,Array,Nat, etc.) even when module name doesn't appear explicitly in codeVarArray - Nested chains correct: not
xs.map<T, U>(f).flatten()xs.map<T.flatten( U>( - All tests pass with
npx mops test
- 所有调用已转换为
Module.func(self, ...)self.func(...) - 未转换工厂函数(、
tabulate、fromArray、fromVarArray、repeat、empty、range、fromNat等)fromIntWrap - 已单独转换为
Array.fromVarArray(x)(不属于点表示法脚本的处理范围)x.toArray() - 中缀表达式已用括号包裹:、
(a >> b).toNat()(x & y).toText() - 类型参数已保留:未损坏
xs.map<Nat64, [Nat8]>(f) - 类型注解已保留(编译器无法自动推断)
Array.tabulate<T> - 点表示法类型的导入仍存在(、
Blob、Array、Nat等),即使代码中未显式出现模块名称VarArray - 嵌套链式转换正确:而非
xs.map<T, U>(f).flatten()xs.map<T.flatten( U>( - 所有测试通过
npx mops test