motoko-dot-notation-migration

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Motoko dot notation migration

Motoko 点表示法迁移

Purpose

目的

Convert Motoko function calls from the old
Module.func(self, ...)
style to the new
self.func(...)
dot notation, for all functions in the
core
package whose first parameter is named
self
.
This skill was created from a successful migration of the
motoko-bitcoin
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函数调用从旧的
Module.func(self, ...)
风格转换为新的
self.func(...)
点表示法,适用于
core
包中所有第一个参数名为
self
的函数。
这个技能是从
motoko-bitcoin
项目的成功迁移中创建的。它包含core 2.2.0的完整函数目录、经过实战检验的Python转换脚本,以及所有必须处理的关键陷阱。

When to Use

使用场景

  • When migrating a Motoko project from
    base
    to
    core
    , or upgrading
    core
    versions
  • 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
core
package (successor to
base
), many module functions declare their first parameter as
self
. These functions support dot notation: instead of writing
Module.func(self, arg2, arg3)
, you can write
self.func(arg2, arg3)
.
Only functions whose first parameter is literally named
self
support this.
Factory functions like
Array.tabulate
,
Array.fromVarArray
,
Blob.fromArray
, etc. do NOT have
self
as first parameter and must NOT be converted.
在Motoko的
core
包(
base
的继任者)中,许多模块函数将第一个参数声明为
self
。这些函数支持点表示法:无需编写
Module.func(self, arg2, arg3)
,你可以写成
self.func(arg2, arg3)
只有第一个参数字面名为
self
的函数才支持此语法
。像
Array.tabulate
Array.fromVarArray
Blob.fromArray
等工厂函数的第一个参数不是
self
,绝对不能转换。

Conversion 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 (
>>
,
<<
,
&
,
|
,
+
,
-
,
*
,
/
,
%
,
#
,
==
,
!=
,
and
,
or
).
motoko
// 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中,
.
的绑定优先级高于所有中缀运算符(
>>
<<
&
|
+
-
*
/
%
#
==
!=
and
or
)。
motoko
// 错误:点运算符绑定到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
Module.func(self, rest...)
at the first comma, you MUST track
<>
angle bracket depth. Type parameters like
<Nat64, [Nat8]>
contain commas that are NOT argument separators.
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, corrupted
Rule: In the comma-finder, track
depth_angle
for
<>
just like
depth_paren
for
()
. Only decrement on
>
when
depth_angle > 0
(to avoid confusing comparison operators with closing angle brackets).
在第一个逗号处拆分
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_angle
。仅当
depth_angle > 0
时才在遇到
>
时递减深度(避免将比较运算符与闭合尖括号混淆)。

3. Functions That Must NOT Be Converted

3. 绝对不能转换的函数

These functions do NOT have
self
as their first parameter. Do not convert them:
Array: 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: trap
However,
Array.fromVarArray(x)
has a dot-notation equivalent:
x.toArray()
(via
VarArray.toArray
). This is NOT handled by the dot-notation conversion script — it's a separate refactor (see motoko-core-code-improvements skill, Section H).
这些函数的第一个参数不是
self
,请勿转换:
Array: 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
不过,
Array.fromVarArray(x)
有对应的点表示法等价写法:
x.toArray()
(通过
VarArray.toArray
)。这不属于点表示法转换脚本的处理范围 — 它是单独的重构操作(参见motoko-core-code-improvements技能的H部分)。

4. Dot-Notation Requires the Type's Module to Be Imported

4. 点表示法要求导入类型对应的模块

When calling a method via dot-notation on a value (e.g.,
someBlob.toArray()
), the module for that value's type (
Blob
) must still be imported, even though the name
Blob
doesn't appear explicitly in the calling code.
Common examples where the import looks unused but is required:
  • Blob
    — for
    .toArray()
    ,
    .size()
    on Blob return values (e.g.,
    Sha256.fromArray(...).toArray()
    )
  • Array
    — for
    .flatten()
    ,
    .foldLeft()
    ,
    .map()
    ,
    .sliceToArray()
    on
    [T]
    values
  • Nat
    — for
    .toText()
    on Nat values from
    .size()
    calls
  • VarArray
    — for
    .toArray()
    on
    [var T]
    values (after converting
    Array.fromVarArray
    )
If you remove these imports, compilation will fail with
field X does not exist in type
and a hint to import the module.
通过点表示法调用值的方法时(例如
someBlob.toArray()
),该值类型(
Blob
)对应的模块仍必须导入,即使调用代码中未显式出现
Blob
名称。
常见的看似未使用但必须保留的导入示例:
  • Blob
    — 用于Blob返回值的
    .toArray()
    .size()
    (例如
    Sha256.fromArray(...).toArray()
  • Array
    — 用于
    [T]
    值的
    .flatten()
    .foldLeft()
    .map()
    .sliceToArray()
  • Nat
    — 用于
    .size()
    调用返回的Nat值的
    .toText()
  • VarArray
    — 用于
    [var T]
    值的
    .toArray()
    (转换
    Array.fromVarArray
    之后)
如果移除这些导入,编译会失败并提示
field X does not exist in type
,同时建议导入对应模块。

5. 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()
and
0xFF.toNat()
are valid Motoko — numeric literals can receive dot notation directly. No parens needed.
42.toText()
0xFF.toNat()
是有效的Motoko语法 — 数值字面量可直接使用点表示法,无需括号。

Function Catalog (core 2.2.0)

函数目录(core 2.2.0)

Zero-Extra-Arg Functions (self only)

无额外参数的函数(仅self)

These take exactly one argument named
self
. Convert
Module.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'),
]
这些函数仅接受一个名为
self
的参数。转换规则:
Module.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
self
plus additional arguments. Convert
Module.func(expr, a, b)
expr.func(a, b)
.
Note: Some of these CAN be called with just self (e.g.
Array.flatten(xs)
has no extra args). The multi-arg handler must handle both cases: if no comma is found after self, emit
self.func()
; if comma is found, emit
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'),
]
这些函数接受
self
以及其他参数。转换规则:
Module.func(expr, a, b)
expr.func(a, b)
注意:其中一些函数也可以仅用self调用(例如
Array.flatten(xs)
没有额外参数)。多参数处理程序必须同时处理两种情况:如果self后没有逗号,则输出
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
core
package is updated, new functions may have been added or signatures changed. Run this shell snippet to regenerate the catalog from the actual source:
bash
#!/bin/bash
core
包更新时,可能会添加新函数或修改签名。运行以下shell代码片段,从实际源代码重新生成目录:
bash
#!/bin/bash

Usage: ./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

**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 implicit
For
public let
bindings that reference Prim functions taking a single value, check the type signature: if it's
T -> U
, the single argument is
self
. These support dot notation.
After scanning, classify each function:
  1. ZERO_EXTRA: Takes only
    self
    (no other params). Goes in the zero-extra list.
  2. MULTI_EXTRA: Takes
    self
    plus other params. Goes in the multi-extra list.
  3. NO_SELF: First param is NOT named
    self
    . Do NOT convert.
Then update the
ZERO_EXTRA
and
MULTI_EXTRA
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).
CORE_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

**重要提示**:一些`public let`声明使用内联lambda语法:
```motoko
public let toNat = Prim.nat8ToNat;  // 看不到括号 — 检查Prim绑定
public let encodeUtf8 : Text -> Blob = Prim.encodeUtf8;  // self是隐式的
对于引用接受单个值的Prim函数的
public let
绑定,检查类型签名:如果是
T -> U
,则单个参数就是
self
,这些函数支持点表示法。
扫描后,对每个函数进行分类:
  1. ZERO_EXTRA:仅接受
    self
    (无其他参数),归入无额外参数列表。
  2. MULTI_EXTRA:接受
    self
    和其他参数,归入带额外参数列表。
  3. NO_SELF:第一个参数不是
    self
    ,请勿转换。
然后更新转换脚本中的
ZERO_EXTRA
MULTI_EXTRA
列表,仅保留项目源文件中实际使用的函数(避免与同名本地函数误匹配)。

Conversion Script

转换脚本

Save this as
convert_dot_notation.py
and run with
python3 convert_dot_notation.py
. Edit the
ZERO_EXTRA
,
MULTI_EXTRA
lists and
src_dir
path as needed for your project.
python
#!/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.py
,并使用
python3 convert_dot_notation.py
运行。根据你的项目需求,编辑
ZERO_EXTRA
MULTI_EXTRA
列表和
src_dir
路径。
python
#!/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 False
def 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 -1
def 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, changes
def 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, changes
def 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_changes
def 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()
undefined
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 False
def 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 -1
def 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, changes
def 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, changes
def 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_changes
def 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()
undefined

Step-by-Step Procedure

分步操作流程

  1. Identify the core version from
    mops.toml
    :
    bash
    grep 'core' mops.toml
  2. 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*[:\)]'
  3. 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 -rn
    Only include used functions in the script to avoid false matches.
  4. Classify each used function as ZERO_EXTRA or MULTI_EXTRA by checking its signature in the core source.
  5. Run the conversion:
    bash
    python3 convert_dot_notation.py
  6. Run tests:
    bash
    npx mops test
  7. 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
      self
      list)
  8. Clean up: Delete
    convert_dot_notation.py
    after all tests pass.
  1. mops.toml
    中确定core版本
    bash
    grep 'core' mops.toml
  2. 扫描core源代码以构建/验证函数目录
    bash
    # 列出所有第一个参数为self的公共函数
    grep -rnE 'public\s+(func|let)\s+\w+' .mops/core@*/src/*.mo | grep -E '\(\s*self\s*[:\)]'
  3. 确定项目实际使用的函数
    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
    仅在脚本中包含实际使用的函数,避免误匹配。
  4. 通过检查core源代码中的签名,将每个使用的函数分类为ZERO_EXTRA或MULTI_EXTRA
  5. 运行转换脚本
    bash
    python3 convert_dot_notation.py
  6. 运行测试
    bash
    npx mops test
  7. 如果测试失败,检查以下问题
    • 运算符优先级问题(中缀表达式缺少括号)
    • 类型参数逗号拆分错误(<>深度跟踪bug)
    • 不应转换的函数被错误转换(不在self列表中)
  8. 清理:所有测试通过后,删除
    convert_dot_notation.py

Verification Checklist

验证检查清单

  • All
    Module.func(self, ...)
    calls converted to
    self.func(...)
  • No factory functions converted (
    tabulate
    ,
    fromArray
    ,
    fromVarArray
    ,
    repeat
    ,
    empty
    ,
    range
    ,
    fromNat
    ,
    fromIntWrap
    , etc.)
  • Array.fromVarArray(x)
    converted separately to
    x.toArray()
    (not part of dot-notation script)
  • Infix expressions wrapped in parens:
    (a >> b).toNat()
    ,
    (x & y).toText()
  • Type parameters preserved:
    xs.map<Nat64, [Nat8]>(f)
    not corrupted
  • Array.tabulate<T>
    type annotations kept (compiler cannot infer them)
  • Imports for dot-notation types still present (
    Blob
    ,
    Array
    ,
    Nat
    ,
    VarArray
    , etc.) even when module name doesn't appear explicitly in code
  • Nested chains correct:
    xs.map<T, U>(f).flatten()
    not
    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