第 13 章 批量编译 (ocamlc)

本章介绍 OCaml 批量编译器 ocamlc,它将 OCaml 源文件编译成字节码对象文件,并将这些对象文件链接起来生成独立的字节码可执行文件。这些可执行文件随后由字节码解释器 ocamlrun 执行。

1 编译器概述

ocamlc 命令的命令行界面类似于大多数 C 编译器的命令行界面。它接受几种类型的参数,并在处理完所有选项后按顺序处理这些参数。

链接阶段的输出是一个包含编译后的字节码的文件,该字节码可以由 OCaml 字节码解释器执行:名为 ocamlrun 的命令。如果 a.out 是链接阶段生成的文件的名称,则命令

        ocamlrun a.out arg1 arg2argn

执行包含在 a.out 中的编译后的代码,并将其作为参数传递给字符字符串 arg1argn。(有关更多详细信息,请参见第 ‍15 章。)

在大多数系统上,链接阶段生成的文件可以直接运行,如下所示:

        ./a.out arg1 arg2argn

生成的文件具有可执行位,并且能够自行启动字节码解释器。

编译器能够在其内部阶段输出一些信息。如果传递了选项 -bin-annot,它可以为编译单元的实现输出 .cmt 文件,为签名输出 .cmti 文件(请参阅下面对 -bin-annot 的描述)。每个这样的文件都包含一个类型化的抽象语法树 (AST),该树是在类型检查过程中生成的。该树包含有关源文件中每个术语的位置和特定类型的全部可用信息。如果类型检查不成功,则 AST 是部分的。

这些 .cmt.cmti 文件通常对代码检查工具很有用。

2 选项

ocamlc 识别以下命令行选项。选项 -pack-a-c-output-obj-output-complete-obj 是互斥的。

-a
使用命令行上给定的对象文件(.cmo 文件)构建库(.cma 文件),而不是将它们链接到可执行文件中。库的名称必须使用 -o 选项设置。

如果传递了 -custom-cclib-ccopt 选项,则这些选项将存储在生成的 .cma 库中。然后,使用该库进行链接会自动添加回 -custom-cclib-ccopt 选项,就像它们是在命令行上提供的一样,除非给出了 -noautolink 选项。

-absname
强制错误消息显示文件名的绝对路径。
-no-absname
不要尝试在错误消息中显示绝对文件名。
-annot
从 OCaml 4.11 开始已弃用。请改用 -bin-annot
-args filename
filename 中读取附加的以换行符结尾的命令行参数。
-args0 filename
filename 中读取附加的以空字符结尾的命令行参数。
-bin-annot
以二进制格式转储有关编译的详细信息(类型、绑定、尾递归调用等)。文件 src.ml(分别为 src.mli)的信息将放入文件 src.cmt(分别为 src.cmti)中。如果出现类型错误,则在错误之前转储类型检查器推断的所有信息。*.cmt*.cmti 文件由 -bin-annot 生成,包含的信息更多,并且比由 -annot 生成的文件更紧凑。
-c
仅编译。抑制编译的链接阶段。源代码文件将被转换为编译后的文件,但不会生成可执行文件。此选项对于单独编译模块很有用。
-cc ccomp
在“自定义运行时”模式下链接时使用 ccomp 作为 C 链接器(请参见 -custom 选项),并作为 C 编译器用于编译 .c 源文件。在链接由 C++ 编译器(如 g++clang++)生成的 C++ 对象文件时,建议使用 -cc c++
-cclib -llibname
在“自定义运行时”模式下链接时,将 -llibname 选项传递给 C 链接器(请参见 -custom 选项)。这会导致将给定的 C 库与程序链接起来。
-ccopt option
将给定的选项传递给 C 编译器和链接器。例如,在“自定义运行时”模式下链接时,-ccopt -Ldir 会导致 C 链接器在目录 dir 中搜索 C 库。(请参见 -custom 选项。)
-cmi-file filename
使用给定的接口文件对 ML 源文件进行类型检查以进行编译。当未指定此选项时,编译器会查找与正在编译的实现具有相同基本名称且位于同一目录中的 .mli 文件。如果找到这样的文件,编译器会在包含的目录中查找对应的 .cmi 文件,如果找不到则报告错误。
-color mode
启用或禁用编译器消息(尤其是警告和错误)中的颜色。支持以下模式
auto
使用启发式方法仅在输出支持颜色时启用颜色(与 ANSI 兼容的 tty 终端);
always
无条件启用颜色;
never
禁用颜色输出。

如果未提供 -color,则会考虑环境变量 OCAML_COLOR。其值与上述相同:auto/always/never。

如果未提供 -colorOCAML_COLOR 未设置,并且环境变量 NO_COLOR 已设置,则禁用颜色输出。否则,默认设置为 ’auto’,并且当前启发式方法检查 TERM 环境变量是否存在且不为空或为 dumb,以及 ’isatty(stderr)’ 是否成立。

-error-style mode
控制错误消息和警告的打印方式。支持以下模式
short
仅打印错误及其位置;
contextual
类似于 short,但也显示与错误位置相对应的源代码片段。
默认设置为 contextual

如果未提供 -error-style,则会考虑环境变量 OCAML_ERROR_STYLE。其值与上述相同:short/contextual。

-compat-32
检查生成的字节码可执行文件是否可以在 32 位平台上运行,如果不能则发出错误信号。这在 64 位机器上编译字节码时非常有用。
-config
打印 ocamlc 的版本号及其配置的详细摘要,然后退出。
-config-var var
打印 -config 输出中的特定配置变量的值,然后退出。如果变量不存在,则退出代码为非零。此选项仅从 OCaml 4.08 开始可用,因此脚本作者应该为旧版本提供备用方案。
-custom
以“自定义运行时”模式链接。在默认链接模式下,链接器会生成旨在与共享运行时系统 ocamlrun 一起执行的字节码。在自定义运行时模式下,链接器会生成一个包含运行时系统和程序字节码的输出文件。生成的文件更大,但可以直接执行,即使未安装 ocamlrun 命令。此外,“自定义运行时”模式允许使用第 ‍22 章中所述的用户定义 C 函数静态链接 OCaml 代码。
Unix:  绝不要对 ocamlc -custom 生成的可执行文件使用 strip 命令,这将删除可执行文件中的字节码部分。
Unix:  安全警告:绝不要对 ocamlc -custom 生成的可执行文件设置“setuid”或“setgid”位,这将使其容易受到攻击。
-depend ocamldep-args
计算依赖项,就像 ocamldep 命令一样。其余参数被解释为传递给 ocamldep 命令一样。
-dllib -llibname
安排在程序启动时由运行时系统 ocamlrun 动态加载 C 共享库 dlllibname.so (dlllibname.dll 在 Windows 下)。
-dllpath dir
将目录 dir 添加到共享 C 库的运行时搜索路径。在链接时,共享库将在标准搜索路径(与 -I 选项相对应的路径)中搜索。-dllpath 选项只是将 dir 存储在生成的执行文件中,ocamlrun 可以在其中找到它并按照第 ‍15.3 节中所述使用它。
-for-pack module-path
生成一个可以稍后作为使用 -pack 构造的编译单元的子模块(使用给定的访问路径)包含的对象文件 (.cmo)。例如,ocamlc -for-pack P -c A.ml 将生成 a..cmo,可以稍后与 ocamlc -pack -o P.cmo a.cmo 一起使用。注意:你仍然可以打包没有 -for-pack 编译的模块,但这种情况下,异常将以错误的名称打印。
-g
在编译和链接时添加调试信息。为了能够使用 ocamldebug 调试程序(参见第 ‍20 章),以及在程序因未捕获的异常终止时生成堆栈回溯(参见第 ‍15.2 节),需要此选项。
-no-g
不记录调试信息(默认)。
-i
在编译实现 (.ml 文件) 时,使编译器打印所有定义的名称(及其推断类型或定义)。不会生成编译后的文件 (.cmo.cmi 文件)。这对于检查编译器推断的类型很有用。此外,由于输出遵循接口的语法,因此它有助于为文件编写显式接口 (.mli 文件):只需将编译器的标准输出重定向到 .mli 文件,然后编辑该文件以删除所有未导出名称的声明。
-I directory
将给定目录添加到用于搜索编译后的接口文件 (.cmi)、编译后的目标代码文件 .cmo、库 (.cma) 和用 -cclib -lxxx 指定的 C 库的目录列表中。默认情况下,首先搜索当前目录,然后搜索标准库目录。使用 -I 添加的目录在当前目录之后搜索,按照它们在命令行上的给出顺序,但在标准库目录之前搜索。另请参见选项 -nostdlib

如果给定目录以 + 开头,则它相对于标准库目录。例如,-I +unix 将标准库的 unix 子目录添加到搜索路径。

-H directory
-I 行为相同,除了 (a) 程序可能无法直接引用以这种方式添加到搜索路径的模块,以及 (b) 这些目录在任何 -I 目录之后搜索。这使得能够为编译器提供当前程序的传递依赖项(其依赖项的依赖项)的编译后的接口和目标代码文件,而不允许它们静默地成为直接依赖项。
-impl filename
将文件 filename 编译为实现文件,即使其扩展名不是 .ml
-intf filename
将文件 filename 编译为接口文件,即使其扩展名不是 .mli
-intf-suffix string
将以 string 结尾的文件名识别为接口文件(而不是默认的 .mli)。
-labels
在类型中不忽略标签,标签可以在应用程序中使用,并且带标签的参数可以以任何顺序给出。这是默认设置。
-linkall
强制链接库中包含的所有模块。如果未给出此标志,则不链接未引用的模块。在构建库(选项 -a)时,设置 -linkall 选项将强制所有后续涉及该库的程序的链接链接库中包含的所有模块。在编译模块(选项 -c)时,设置 -linkall 选项将确保如果该模块被放入库中并且该库被链接,则该模块将始终被链接。
-make-runtime
构建一个自定义运行时系统(在选项 -o 指定的文件中),将命令行上给出的 C 对象文件和库合并到其中。这个自定义运行时系统可以稍后用于执行使用 ocamlc -use-runtime runtime-name 选项生成的字节码可执行文件。有关更多信息,请参见第 ‍22.1.6 节。
-match-context-rows
设置模式匹配编译期间优化使用的上下文行数。默认值为 32。较低的值会导致编译速度更快,但代码优化程度更低。这个高级选项旨在用于模式匹配密集型程序导致编译时间显着增加的情况。
-no-alias-deps
不记录模块别名的依赖项。有关更多信息,请参见第 ‍12.8 节。
-no-app-funct
禁用函子的应用行为。使用此选项,每个函子应用都会在其结果中生成新的类型,并且将同一个函子应用到同一个参数两次会导致两个不兼容的结构。
-noassert
不编译断言检查。请注意,特殊形式 assert false 始终被编译,因为它被专门类型化。当链接已经编译的文件时,此标志没有效果。
-noautolink
在链接 .cma 库时,忽略库中可能包含的 -custom-cclib-ccopt 选项(如果这些选项在构建库时给出)。这在库包含 C 库或 C 选项的错误规范时很有用;在这种情况下,在链接期间,设置 -noautolink 并通过命令行传递正确的 C 库和选项。
-nolabels
忽略类型中非可选的标签。标签不能在应用程序中使用,并且参数顺序变得严格。
-nostdlib
在搜索编译接口文件 (.cmi)、编译目标代码文件 (.cmo)、库 (.cma) 和用 -cclib -lxxx 指定的 C 库的目录列表中不包含标准库目录。另请参见选项 -I
-o 输出文件
指定要生成的输出文件的名称。对于可执行文件,默认输出名称是 Unix 下的 a.out 和 Windows 下的 camlprog.exe。如果给出了 -a 选项,则指定生成的库的名称。如果给出了 -pack 选项,则指定生成的打包对象文件的名称。如果给出了 -output-obj-output-complete-obj 选项,则指定生成的 object 文件的名称。如果给出了 -c 选项,则指定为命令行中出现的下一个源文件生成的 object 文件的名称。
-opaque
当本机编译器编译实现时,默认情况下它会生成一个 .cmx 文件,其中包含用于跨模块优化的信息。它还期望 .cmx 文件存在于当前编译源的依赖项中,并使用它们进行优化。从 OCaml 4.03 开始,如果编译器无法找到其中一个依赖项的 .cmx 文件,它会发出警告。

自 4.04 起可用的 -opaque 选项,禁用当前编译单元的跨模块优化信息。当编译 .mli 接口时,使用 -opaque 会标记编译的 .cmi 接口,以便后续对依赖它的模块的编译将不会依赖于相应的 .cmx 文件,也不会在它不存在时发出警告。当本机编译器编译 .ml 实现时,使用 -opaque 会生成一个不包含任何跨模块优化信息的 .cmx

使用此选项可能会降低生成的代码质量,但它会减少编译时间,无论是在干净的构建中还是在增量构建中。实际上,使用本机编译器,当编译单元的实现发生变化时,所有依赖它的单元可能都需要重新编译——因为跨模块信息可能已更改。如果实现发生变化的编译单元是在使用 -opaque 编译的,则无需进行此类重新编译。因此,此选项可用于,例如,获得更快的编辑-编译-测试反馈循环。

-open 模块
在处理接口或实现文件之前打开给定的模块。如果给出了多个 -open 选项,则按顺序处理它们,就像在每个文件的开头添加语句 open! Module1;; ... open! ModuleN;; 一样。
-output-obj
使链接器生成 C object 文件而不是字节码可执行文件。这在将 OCaml 代码包装为 C 库时很有用,可以从任何 C 程序调用。请参阅第 ‍22 章,第 ‍22.7.5 节。必须使用 -o 选项设置输出 object 文件的名称。此选项还可用于生成 C 源文件 (.c 扩展名) 或编译的共享/动态库 (.so 扩展名,Windows 下为 .dll)。
-output-complete-exe
通过链接包含字节码程序的 C object 文件、OCaml 运行时系统以及传递给 ocamlc 的任何其他静态 C 代码来构建一个自包含的可执行文件。最终效果类似于 -custom,只是字节码嵌入在 C 代码中,因此不再可供诸如 ocamldebug 之类的工具访问。另一方面,生成的二进制文件可以抵抗 strip
-output-complete-obj
-output-obj 选项相同,只是生成的 object 文件包括运行时和自动链接库。
-pack
构建一个字节码 object 文件 (.cmo 文件) 及其关联的编译接口 (.cmi),它将命令行上给出的 object 文件组合在一起,使它们看起来像是输出 .cmo 文件的子模块。必须使用 -o 选项给出输出 .cmo 文件的名称。例如,
        ocamlc -pack -o p.cmo a.cmo b.cmo c.cmo
生成编译文件 p.cmop.cmi,描述一个编译单元,它具有三个子模块 ABC,对应于 object 文件 a.cmob.cmoc.cmo 的内容。这些内容可以在程序的其余部分中作为 P.AP.BP.C 来引用。
-pp 命令
使编译器将给定的 命令 作为每个源文件的预处理器调用。命令 的输出被重定向到一个中间文件,该文件被编译。如果没有编译错误,中间文件随后会被删除。
-ppx 命令
在解析之后,通过预处理器 命令 传递抽象语法树。模块 Ast_mapper(在第 ‍30 章中描述: Ast_mapper )实现了预处理器的外部接口。
-principal
在类型检查期间检查信息路径,以确保所有类型都是以主要方式推导的。在使用带标签的参数和/或多态方法时,需要此标志才能确保编译器的未来版本能够正确推断类型,即使内部算法发生变化。在 -principal 模式下接受的所有程序在默认模式下也被接受,具有等效类型,但具有不同的二进制签名,这可能会减慢类型检查速度;但最好在发布源代码之前使用它一次。
-rectypes
在类型检查期间允许任意递归类型。默认情况下,只支持递归类型,其中递归通过对象类型进行。请注意,在使用此标志创建接口后,必须为所有依赖项再次使用它。
-runtime-variant 后缀
后缀 字符串添加到程序使用的运行时库的名称中。目前,只支持一个这样的后缀:d,并且只有在 OCaml 编译器使用选项 -with-debug-runtime 配置时才支持。此后缀给出运行时的调试版本,这对于调试低级代码(如 C 存根)中的指针问题很有用。
-safe-string
强制 stringbytes 类型之间的分离,从而使字符串只读。这是默认设置,自 OCaml 5.0 起强制执行。
-safer-matching
不要使用类型信息来优化模式匹配。这允许即使模式匹配被错误地假定为是穷举的,也能检测到匹配失败。这只会影响 GADT 和多态变体编译。
-short-paths
当类型在多个模块路径下可见时,在推断接口以及错误和警告消息中打印类型名称时使用最短的路径。以下划线 _ 开头或包含双下划线 __ 的标识符名称在计算其长度时会受到 +10 的惩罚。
-stop-after 通过
在给定的编译阶段后停止编译。当前支持的阶段为:parsingtyping
-strict-sequence
强制每个序列的左侧部分具有 unit 类型。
-strict-formats
拒绝在旧版格式实现中被接受的无效格式。您应该使用此标志来检测和修复此类无效格式,因为它们将被未来的 OCaml 版本拒绝。
-unboxed-types
当类型是可拆箱的(即只有一个参数的记录或只有一个参数的构造函数的具体数据类型)时,它将被拆箱,除非用 [@@ocaml.boxed] 进行注释。
-no-unboxed-types
当类型是可拆箱的时,它将被装箱,除非用 [@@ocaml.unboxed] 进行注释。这是默认设置。
-unsafe
关闭数组和字符串访问的边界检查(v.(i)s.[i] 结构)。因此,使用 -unsafe 编译的程序速度稍快,但并不安全:如果程序访问数组或字符串超出其边界,则任何事情都可能发生。此外,关闭整数除法和模运算中的零除数检查。使用 -unsafe,整数除以零会导致程序停止或继续执行,结果未定义,而不是引发 Division_by_zero 异常。
-unsafe-string
识别 stringbytes 类型,从而使字符串可写。这是为了与旧源代码兼容,不应与新软件一起使用。自 OCaml 5.0 起,此选项无条件地引发错误。
-use-runtime 运行时名称
生成一个字节码可执行文件,该文件可以在自定义运行时系统 运行时名称 上执行,该系统之前使用 ocamlc -make-runtime 运行时名称 构建。有关更多信息,请参见第 ‍22.1.6 节。
-v
打印编译器的版本号和标准库目录的位置,然后退出。
-verbose
在执行所有外部命令之前打印它们,特别是以 -custom 模式调用 C 编译器和链接器。这有助于调试 C 库问题。
-version-vnum
以简短形式打印编译器的版本号(例如 3.11.0),然后退出。
-w warning-list
启用、禁用或将参数 warning-list 指定的警告标记为致命。每个警告可以启用禁用,并且每个警告可以致命非致命。如果禁用警告,则不会显示它,也不会以任何方式影响编译(即使它是致命的)。如果启用警告,则编译器会在源代码触发它时正常显示它。如果它被启用并且是致命的,则编译器还会在显示它后停止并显示错误。

warning-list 参数是一系列警告说明符,它们之间没有分隔符。警告说明符是以下之一

+num
启用警告编号 num
-num
禁用警告编号 num
@num
启用并标记为致命警告编号 num
+num1..num2
启用给定范围内的警告。
-num1..num2
禁用给定范围内的警告。
@num1..num2
启用并标记为给定范围内的致命警告。
+letter
启用与 letter 相对应的警告集。该字母可以是大写或小写。
-letter
禁用与 letter 相对应的警告集。该字母可以是大写或小写。
@letter
启用并标记为与 letter 相对应的致命警告集。该字母可以是大写或小写。
uppercase-letter
启用与 uppercase-letter 相对应的警告集。
lowercase-letter
禁用与 lowercase-letter 相对应的警告集。

或者,warning-list 可以使用其助记符名称(见下文)指定单个警告,如下所示

+name
启用警告 name
-name
禁用警告 name
@name
启用并标记为致命警告 name

目前未定义的警告编号、字母和名称将被忽略。警告如下(每个编号后的名称指定该警告的助记符)。

1 comment-start
可疑的注释开始标记。
2 comment-not-end
可疑的注释结束标记。
3
已弃用,是 "deprecated" 警告的同义词。
4 fragile-match
脆弱的模式匹配:即使向匹配的变体类型之一添加了其他构造函数,匹配也将保持完整。
5 ignored-partial-application
部分应用函数:结果具有函数类型并被忽略的表达式。
6 labels-omitted
函数应用中省略的标签。
7 method-override
方法被重写。
8 partial-match
部分匹配:模式匹配中缺少的 case。
9 missing-record-field-pattern
记录模式中缺少字段。
10 non-unit-statement
序列左侧的表达式不具有类型 unit(并且不是函数,请参见警告编号 5)。
11 redundant-case
模式匹配中多余的 case(未使用的匹配 case)。
12 redundant-subpat
模式匹配中多余的子模式。
13 instance-variable-override
实例变量被重写。
14 illegal-backslash
字符串常量中非法的反斜杠转义。
15 implicit-public-methods
私有方法隐式地变为公共方法。
16 unerasable-optional-argument
不可擦除的可选参数。
17 undeclared-virtual-method
未声明的虚方法。
18 not-principal
非主要类型。
19 non-principal-labels
没有主要性的类型。
20 ignored-extra-argument
未使用的函数参数。
21 nonreturning-statement
非返回值语句。
22 preprocessor
预处理器警告。
23 useless-record-with
无用的记录 with 子句。
24 bad-module-name
错误的模块名称:源文件名不是有效的 OCaml 模块名称。
25
忽略:现在是警告 8 的一部分。
26 unused-var
可疑的未使用变量:未使用变量,它使用 letas 绑定,并且不以下划线 (_) 字符开头。
27 unused-var-strict
无害的未使用变量:未使用变量,它不使用 let 也不使用 as 绑定,并且不以下划线 (_) 字符开头。
28 wildcard-arg-to-constant-constr
作为常量构造函数参数给出的通配符模式。
29 eol-in-string
字符串常量中未转义的换行符(不可移植的代码)。
30 duplicate-definitions
两个相互递归类型中定义了两个具有相同名称的标签或构造函数。
31 module-linked-twice
同一个可执行文件中两次链接同一个模块。
I
gnored:现在是一个硬错误(从 5.1 开始)。
32 unused-value-declaration
未使用的值声明。(从 4.00 开始)
33 unused-open
未使用的 open 语句。(从 4.00 开始)
34 unused-type-declaration
未使用的类型声明。(从 4.00 开始)
35 unused-for-index
未使用的 for 循环索引。(从 4.00 开始)
36 unused-ancestor
未使用的祖先变量。(从 4.00 开始)
37 unused-constructor
未使用的构造函数。(从 4.00 开始)
38 unused-extension
未使用的扩展构造函数。(从 4.00 开始)
39 unused-rec-flag
未使用的 rec 标志。(从 4.00 开始)
40 name-out-of-scope
在作用域之外使用的构造函数或标签名称。(从 4.01 开始)
41 ambiguous-name
模棱两可的构造函数或标签名称。(从 4.01 开始)
42 disambiguated-name
消歧的构造函数或标签名称(兼容性警告)。(从 4.01 开始)
43 nonoptional-label
非可选标签用作可选标签。(从 4.01 开始)
44 open-shadow-identifier
open 语句隐藏了已定义的标识符。(从 4.01 开始)
45 open-shadow-label-constructor
open 语句隐藏了已定义的标签或构造函数。(从 4.01 开始)
46 bad-env-variable
环境变量中的错误。(从 4.01 开始)
47 attribute-payload
非法的属性有效负载。(从 4.02 开始)
48 eliminated-optional-arguments
隐式消除可选参数。(从 4.02 开始)
49 no-cmi-file
查找模块别名时缺少 cmi 文件。(从 4.02 开始)
50 unexpected-docstring
意外的文档注释。(从 4.03 开始)
51 wrong-tailcall-expectation
用不正确的 @tailcall 属性注释的函数调用。(从 4.03 开始)
52 fragile-literal-pattern (请参见 13.5.3)
脆弱的常量模式。(从 4.03 开始)
53 misplaced-attribute
属性不能出现在此上下文中。(从 4.03 开始)
54 duplicated-attribute
属性在一个表达式上使用多次。(从 4.03 开始)
55 inlining-impossible
内联不可能。(从 4.03 开始)
56 unreachable-case
模式匹配中无法到达的 case(基于类型信息)。(从 4.03 开始)
57 ambiguous-var-in-pattern-guard (请参见 13.5.4)
保护下的模棱两可的或模式变量。(从 4.03 开始)
58 no-cmx-file
缺少 cmx 文件。(从 4.03 开始)
59 flambda-assignment-to-non-mutable-value
对非可变值的赋值。(从 4.03 开始)
60 unused-module
未使用的模块声明。(从 4.04 开始)
61 unboxable-type-in-prim-decl
基本类型声明中的不可拆箱类型。(从 4.04 开始)
62 constraint-on-gadt
GADT 类型声明上的类型约束。(从 4.06 开始)
63 erroneous-printed-signature
错误的打印签名。(从 4.08 开始)
64 unsafe-array-syntax-without-parsing
-unsafe 与返回语法树的预处理器一起使用。(从 4.08 开始)
65 redefining-unit
定义新的 '()' 构造函数的类型声明。(从 4.08 开始)
66 unused-open-bang
未使用的 open! 语句。(从 4.08 开始)
67 unused-functor-parameter
未使用的函子参数。(从 4.10 开始)
68 match-on-mutable-state-prevent-uncurry
取决于可变状态的模式匹配阻止其余参数被展开。(从 4.12 开始)
69 unused-field
未使用的记录字段。(自 4.13 起)
70 missing-mli
缺少接口文件。(自 4.13 起)
71 unused-tmc-attribute
未使用的 @tail_mod_cons 属性。(自 4.14 起)
72 tmc-breaks-tailcall
@tail_mod_cons 转换将尾递归调用变成了非尾递归调用。(自 4.14 起)
73 generative-application-expects-unit
生成式函子被应用于空结构(struct end)而不是 ()。(自 5.1 起)
A
所有警告
C
警告 1、2。
D
警告 3 的别名。
E
警告 4 的别名。
F
警告 5 的别名。
K
警告 32、33、34、35、36、37、38、39。
L
警告 6 的别名。
M
警告 7 的别名。
P
警告 8 的别名。
R
警告 9 的别名。
S
警告 10 的别名。
U
警告 11、12。
V
警告 13 的别名。
X
警告 14、15、16、17、18、19、20、21、22、23、24、30。
Y
警告 26 的别名。
Z
警告 27 的别名。

默认设置为 -w +a-4-6-7-9-27-29-32..42-44-45-48-50-60。可以通过 ocamlc -help 查看。请注意,警告 5 和 10 并不总是触发,这取决于类型检查器的内部实现。

-warn-error warning-list
将参数 warning-list 中指定的警告标记为致命错误。当发出这些警告中的一个时,编译器将停止并报错。 warning-list-w 选项的含义相同:+ 符号(或大写字母)表示将相应的警告标记为致命错误,- 符号(或小写字母)将它们恢复为非致命警告,@ 符号同时启用并标记为致命错误相应的警告。

注意:不建议在生产代码中将警告集(即字母)用作 -warn-error 的参数,因为这会在未来版本的 OCaml 添加一些新的警告时破坏你的构建。

默认设置为 -warn-error -a(没有警告是致命的)。

-warn-help
显示所有可用警告编号的描述。
-where
打印标准库的位置,然后退出。
-with-runtime
在生成的程序中包含运行时系统。这是默认设置。
-without-runtime
编译器不会在生成的程序中包含运行时系统(也不包含对它的引用);它必须单独提供。
- file
file 作为文件名处理,即使它以破折号 (-) 字符开头。
-help--help
显示简短的用法摘要并退出。
上下文 CLI 控制

命令行选项的上下文控制

编译器命令行可以通过以下机制“从外部”进行修改。这些是实验性的,可能会发生变化。它们应该只用于实验和开发工作,而不是在发布的包中使用。

OCAMLPARAM (环境变量)
一组参数,将在命令行参数之前或之后插入。参数在 name=value 对的逗号分隔列表中指定。使用 _ 来指定命令行参数的位置,例如 a=x,_,b=y 表示 a=x 应该在解析参数之前执行,b=y 之后。最后,可以使用字符串的第一个字符指定一个备用分隔符,该字符位于 :|; , 集中。
ocaml_compiler_internal_params (stdlib 目录中的文件)
文件名到参数列表的映射,这些参数将添加到命令行(和 OCAMLPARAM)参数中。

3 模块和文件系统

本节简要说明与编译单元对应的模块名称和包含其编译接口和编译实现的文件名称之间的关系。

编译器总是通过获取源文件(.ml.mli 文件)的首字母大写的基本名称来推导出模块名称。也就是说,它剥离了任何前导目录名以及 .ml.mli 后缀;然后,它将第一个字母设置为大写,以符合模块名称必须大写的要求。例如,编译文件 mylib/misc.ml 提供了对名为 Misc 的模块的实现。其他编译单元可以在 Misc.name 名字下引用 mylib/misc.ml 中定义的组件;它们还可以执行 open Misc,然后使用非限定名称 name

编译器生成的 .cmi.cmo 文件与源文件具有相同的基本名称。因此,编译文件始终具有与其描述的模块(对于 .cmi 文件)或实现的模块(对于 .cmo 文件)的名称相同的基本名称(除了第一个字母的大小写)。

当编译器遇到对自由模块标识符 Mod 的引用时,它会在搜索路径中查找名为 Mod.cmimod.cmi 的文件,并加载该文件中包含的编译接口。因此,不建议重命名 .cmi 文件:.cmi 文件的名称必须始终与其实现的编译单元的名称相对应。如果保留了它们的基名,并将正确的 -I 选项提供给编译器,则可以将它们移动到另一个目录。如果编译器加载了已重命名的 .cmi 文件,它将发出错误。

另一方面,编译后的字节码文件(.cmo 文件)在创建后可以自由重命名。这是因为链接器从未尝试自行查找实现具有给定名称的模块的 .cmo 文件:相反,它依赖于用户手动提供 .cmo 文件列表。

4 常用错误

本节描述和解释最常见的错误消息。

找不到文件 filename
在当前目录或搜索路径的目录中都找不到指定的文件。 filename 是一个编译接口文件(.cmi 文件)或一个编译字节码文件(.cmo 文件)。如果 filename 格式为 mod.cmi,这意味着您正在尝试编译一个引用模块 mod 中标识符的文件,但您尚未为模块 mod 编译接口。解决方法:先编译 mod.mlimod.ml,以创建编译接口 mod.cmi

如果 filename 格式为 mod.cmo,这意味着您正在尝试链接一个尚不存在的字节码对象文件。解决方法:先编译 mod.ml

如果您的程序跨越多个目录,则此错误也可能出现,因为您没有指定要查找的目录。解决方法:在命令行中添加正确的 -I 选项。

编译接口 filename 已损坏
当编译器尝试读取结构错误的编译接口文件(.cmi 文件)时,它会生成此错误。这意味着在写入此 .cmi 文件时出现了一些错误:磁盘已满、编译器在文件创建过程中被中断,等等。如果 .cmi 文件在编译器创建后被修改,则也会出现此错误。解决方法:删除损坏的 .cmi 文件,并重新构建它。
此表达式类型为 t1,但被用作类型 t2
这是程序中最常见的类型错误。类型 t1 是表达式(错误消息中显示的程序部分)推断出的类型,通过查看表达式本身得出。类型 t2 是表达式上下文期望的类型;它是通过查看表达式值在程序其余部分中的使用方法来推断的。如果两种类型 t1t2 不兼容,则会产生上述错误。

在某些情况下,很难理解为什么两种类型 t1t2 不兼容。例如,编译器可能会报告“类型为 foo 的表达式不能与类型 foo 一起使用”,并且看起来这两种类型 foo 是兼容的。但这并不总是正确的。两个类型构造函数可以有相同的名称,但实际上代表不同的类型。如果类型构造函数被重新定义,就会发生这种情况。示例

        type foo = A | B
        let f = function A -> 0 | B -> 1
        type foo = C | D
        f C

这会导致错误消息“表达式 C 的类型 foo 不能与类型 foo 一起使用”。

此表达式的类型 t 包含无法泛化的类型变量
类型变量 ('a'b,…) 在类型 t 中可以处于两种状态之一:泛化(这意味着类型 t 对变量的所有可能实例化都是有效的)和非泛化(这意味着类型 t 仅对变量的一个实例化有效)。在 let 绑定 let name = expr 中,类型检查器通常会尽可能地泛化 expr 类型的类型变量。但是,这会导致与多态可变数据结构结合的不健全性(类型良好的程序可能会崩溃)。为了避免这种情况,泛化仅在绑定表达式 expr 属于“语法值”类别时才在 let 绑定中执行,该类别包括常量、标识符、函数、语法值的元组等。在所有其他情况下(例如,expr 是函数调用),可能会创建多态可变,因此对于出现在类型协变分支或非协变分支中的所有变量,泛化都被关闭。例如,如果非值的类型是 'a list,则该变量是可泛化的 (list 是一个协变类型构造函数),但在 'a list -> 'a list 中则不可泛化 (-> 的左分支是逆变) 或 'a ref 中 (ref 是非协变)。

类型中非泛化的类型变量在给定结构或编译单元(.ml 文件的内容或交互式会话)内部不会造成任何困难,但它们不能被允许出现在签名中或编译的接口 (.cmi 文件) 中,因为它们以后可能被不一致地使用。因此,当一个结构或编译单元定义一个值 name,其类型包含非泛化的类型变量时,编译器会发出一个错误。有两种方法可以解决此错误

  • 添加类型约束或 .mli 文件来为 name 提供单态类型(没有类型变量)。例如,而不是写
        let sort_int_list = List.sort Stdlib.compare
        (* inferred type 'a list -> 'a list, with 'a not generalized *)
    
        let sort_int_list = (List.sort Stdlib.compare : int list -> int list);;
    
  • 如果您真的需要 name 具有多态类型,请通过添加一个额外的参数将它的定义表达式变成一个函数。例如,而不是写
        let map_length = List.map Array.length
        (* inferred type 'a array list -> int list, with 'a not generalized *)
    
        let map_length lv = List.map Array.length lv
    
引用未定义的全局 mod
此错误出现在尝试链接一组不完整或排序不正确的文件时。您可能忘记在命令行中为名为 mod 的编译单元提供实现(通常,名为 mod.cmo 的文件,或包含该文件的库)。修复:将缺少的 .ml.cmo 文件添加到命令行。或者,您已为名为 mod 的模块提供实现,但它在命令行中的位置太晚:mod 的实现必须在引用 mod 的所有字节码对象文件之前。修复:更改命令行中 .ml.cmo 文件的顺序。

当然,如果您在模块之间有相互递归的函数,您将始终遇到此错误。也就是说,函数 Mod1.f 调用函数 Mod2.g,而函数 Mod2.g 调用函数 Mod1.f。在这种情况下,无论您在命令行中进行什么排列,程序都会在链接时被拒绝。修复

  • fg 放在同一个模块中。
  • 用另一个函数参数化一个函数。也就是说,而不是拥有
    mod1.ml:    let f x = ... Mod2.g ...
    mod2.ml:    let g y = ... Mod1.f ...
    
    定义
    mod1.ml:    let f g x = ... g ...
    mod2.ml:    let rec g y = ... Mod1.f g ...
    
    并在链接 mod1.cmo 之前链接 mod2.cmo
  • 使用引用来保存其中一个函数,如
    mod1.ml:    let forward_g =
                    ref((fun x -> failwith "forward_g") : <type>)
                let f x = ... !forward_g ...
    mod2.ml:    let g y = ... Mod1.f ...
                let _ = Mod1.forward_g := g
    
外部函数 f 不可用
此错误出现在尝试链接调用用 C 编写的外部函数的代码时。正如第 22 章所解释的那样,此类代码必须与实现所需 f C 函数的 C 库链接。如果所述 C 库不是共享库(DLL),则代码必须在“自定义运行时”模式下链接。修复:将所需的 C 库添加到命令行,并可能添加 -custom 选项。

5 警告参考

本节详细描述和解释了一些警告

5.1 警告 6:函数调用中省略的标签

OCaml 支持 labels-omitted 完整应用:如果函数具有已知的元数,所有参数都没有标签,并且它们的数目与非可选参数的数目匹配,则标签会被忽略,并且非可选参数会按照其定义顺序匹配。可选参数默认为空。

let f ~x ~y = x + y
let test = f 2 3

> let test = f 2 3
>            ^
> Warning 6 [labels-omitted]: labels x, y were omitted in the application of this function.

这种对 labels-omitted 应用的支持是在 OCaml 中添加标签时引入的,以简化标签在代码库中的逐步引入。然而,它具有削弱标签规则的缺点:如果您使用标签来防止调用者错误地重新排序两个相同类型的参数,标签省略将使这种错误再次成为可能。

警告 6 在使用标签省略应用时发出警告,以阻止其使用。当标签被引入时,此警告默认情况下未启用,因此用户会使用标签省略应用,通常不会注意到。

随着时间的推移,启用此警告以避免参数顺序错误已成为习惯做法。自 OCaml 4.13 以来,此警告默认情况下已启用。不再推荐使用标签省略应用,但希望保留这种过渡风格的用户可以明确地禁用该警告。

5.2 警告 9:记录模式中缺少的字段

在对记录进行模式匹配时,只匹配记录的几个字段可能很有用。省略字段可以通过隐式或显式地以 ; _ 结束记录模式来完成。但是,隐式字段省略与模式匹配穷尽性检查不符。启用警告 9 将优先考虑穷尽性检查而不是隐式字段省略的便利性,并在记录模式中对隐式字段省略发出警告。特别是,此警告可以帮助发现需要在向记录类型添加新字段后更新的穷尽性记录模式。

type 'a point = {x : 'a; y : 'a}
let dx { x } = x (* implicit field elision: trigger warning 9 *)
let dy { y; _ } = y (* explicit field elision: do not trigger warning 9 *)

5.3 警告 52:脆弱的常量模式

一些构造函数,例如异常构造函数 FailureInvalid_argument,将 string 值作为参数,该值保存用于用户的文本消息。

这些文本消息通常在时间上不稳定:构建这些构造函数的调用站点可能会在将来的版本中细化消息以使其更明确等。因此,匹配消息的精确值是危险的。例如,在 OCaml 4.02 之前,Array.iter2 会引发以下异常

  Invalid_argument "arrays must have the same length"

自 4.03 以来,它会引发更有用的消息

  Invalid_argument "Array.iter2: arrays must have the same length"

但这意味着任何形式的代码

  try ...
  with Invalid_argument "arrays must have the same length" -> ...

现在已损坏,并且可能遭受未捕获的异常。

警告 52 用于阻止用户一开始就编写这种脆弱的代码。它不会在每次匹配文字字符串时都出现,而仅在库作者在未来版本中可能更改构造函数参数值的情况下出现,方法是使用属性 ocaml.warn_on_literal_pattern(请参阅关于内置属性的手册部分 12.12.1

type t = | Foo of string [@ocaml.warn_on_literal_pattern] | Bar of string let no_warning = function | Bar "specific value" -> 0 | _ -> 1 let warning = function | Foo "specific value" -> 0 | _ -> 1
警告 52 [脆弱文字模式]:代码不应依赖于此构造函数参数的实际值。它们仅供参考,将来版本可能会更改。(请参阅手册部分 13.5.3)

特别是,所有带字符串参数的内置异常都设置了此属性:Invalid_argumentFailureSys_error 如果您匹配特定的字符串参数,所有这些都会引发此警告。

此外,具有包含字符串的结构化参数的内置异常也设置了该属性:Assert_failureMatch_failure 将对使用文字字符串来匹配其元组参数的第一个元素的模式引发警告。

如果您的代码引发了此警告,您不应更改测试特定字符串的方式以避免警告(例如,在右侧使用字符串相等而不是文字模式),因为您的代码将仍然很脆弱。您应该通过匹配所有可能的值来扩大模式的范围。

let warning = function
  | Foo _ -> 0
  | _ -> 1

这可能需要一些注意:如果受检者可能会返回相同模式的几种不同情况,或者引发同一异常的不同实例,您可能需要修改您的代码以将这些几种情况分开。

例如,

try (int_of_string count_str, bool_of_string choice_str) with
  | Failure "int_of_string" -> (0, true)
  | Failure "bool_of_string" -> (-1, false)

应该改写成更原子化的测试。例如,使用第 ‍11.6 节中记录的 exception 模式,可以这样写

match int_of_string count_str with
  | exception (Failure _) -> (0, true)
  | count ->
    begin match bool_of_string choice_str with
    | exception (Failure _) -> (-1, false)
    | choice -> (count, choice)
    end

只有当给定函数调用可能会引发具有相同构造函数但不同字符串值的不同异常时,这种转换才不可行。在这种情况下,您将不得不检查特定的字符串值。这是一种危险的 API 设计,应该避免:最好定义更精确的异常构造函数,而不是将有用信息存储在字符串中。

5.4 警告 57:带保护的不明确的或模式变量

OCaml 中或模式的语义是使用从左到右的偏差指定的:一个值 v 匹配模式 p | q 如果它匹配 pq,但如果它同时匹配两者,则匹配捕获的环境是 p 捕获的环境,而不是 q 捕获的环境。

虽然此属性通常很直观,但至少有一种特殊情况,可能期望不同的语义。考虑一个后面跟着 when 保护的模式:| ‍p ‍when ‍g ‍-> ‍e,例如

     | ((Const x, _) | (_, Const x)) when is_neutral x -> branch

语义很清楚:将受检者与模式匹配,如果匹配,则测试保护,如果保护通过,则采取该分支。特别地,考虑输入 (Const ‍a, Const ‍b),其中 a 未通过测试 is_neutral ‍a,而 b 通过了测试 is_neutral ‍b。使用从左到右的语义,上面的子句不会被其输入采用:将 (Const ‍a, Const ‍b) 与或模式匹配在左侧分支中成功,它返回环境 x ‍-> ‍a,然后测试保护 is_neutral ‍a 并失败,该分支不会被采用。

但是,这里可能认为另一种语义更自然:任何一方通过测试的配对都会采取该分支。使用这种语义,前面的代码片段将等效于

     | (Const x, _) when is_neutral x -> branch
     | (_, Const x) when is_neutral x -> branch

这不是 OCaml 采用的语义。

警告 57 专注于这些令人困惑的情况,其中指定的从左到右的语义与相对于特定保护的非确定性语义(任何分支都可以被采用)不一致。更准确地说,它会在保护使用“不明确”的变量时发出警告,这些变量由或模式的不同侧绑定到受检者的不同部分。