第 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 選項傳遞給編譯器(請參閱下方 -bin-annot 的說明),它可以針對編譯單元的實作輸出 .cmt 檔案,並針對簽名輸出 .cmti 檔案。每個此類檔案都包含一個類型化的抽象語法樹 (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 讀取額外的以 Null 字元終止的命令列引數。
-bin-annot
以二進位格式傾印關於編譯的詳細資訊(類型、繫結、尾呼叫等)。檔案 src.ml(或 src.mli)的資訊會放入檔案 src.cmt(或 src.cmti)中。如果發生類型錯誤,則傾印類型檢查器在錯誤之前推斷的所有資訊。-bin-annot 產生的 *.cmt*.cmti 檔案包含更多資訊,而且比 -annot 產生的檔案緊湊得多。
-c
僅編譯。抑制編譯的連結階段。原始碼檔案會轉為已編譯的檔案,但不會產生可執行檔案。此選項對於個別編譯模組很有用。
-cc ccomp
在「自訂執行期」模式下連結時(請參閱 -custom 選項),使用 ccomp 作為 C 連結器,並在編譯 .c 原始碼檔案時作為 C 編譯器。當連結由 C++ 編譯器(例如 g++clang++)產生的物件檔案時,建議使用 -cc c++
-cclib -llibname
在「自訂執行期」模式下連結時(請參閱 -custom 選項),將 -llibname 選項傳遞給 C 連結器。這會使指定的 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。

如果未提供 -color,且未設定 OCAML_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 章所述, 「自訂執行期」模式允許 OCaml 程式碼與使用者定義的 C 函數靜態連結。
Unix:  絕對不要對 ocamlc -custom 產生的可執行檔使用 strip 命令,這會移除可執行檔的位元組碼部分。
Unix:  安全性警告:絕對不要在 ocamlc -custom 產生的可執行檔上設定「setuid」或「setgid」位元,這會使它們容易受到攻擊。
-depend ocamldep-args
計算相依性,如同 ocamldep 命令會做的那樣。其餘的參數會被解釋為如同給定 ocamldep 命令一樣。
-dllib -llibname
安排在程式啟動時由執行期系統 ocamlrun 動態載入 C 共用函式庫 dlllibname.so(在 Windows 下為 dlllibname.dll)。
-dllpath dir
將目錄 dir 新增至共用 C 函式庫的執行期搜尋路徑。在連結時,共用函式庫會在標準搜尋路徑中搜尋(與 -I 選項對應的路徑)。-dllpath 選項只是將 dir 儲存在產生的可執行檔中,ocamlrun 可以在其中找到它並如第 15.3 節所述使用它。
-for-pack module-path
產生一個物件檔案 (.cmo),該檔案稍後可以作為使用 -pack 建構的編譯單元的子模組(使用給定的存取路徑)包含進去。例如,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 選項,則指定產生的物件檔案名稱。如果給定 -c 選項,則指定為命令列上下一個來源檔案產生的物件檔案名稱。
-opaque
當原生編譯器編譯實作時,預設情況下它會產生一個包含跨模組最佳化資訊的 .cmx 檔案。它還期望目前編譯的來源的相依性存在 .cmx 檔案,並將它們用於最佳化。自 OCaml 4.03 起,如果編譯器無法找到其中一個相依性的 .cmx 檔案,則會發出警告。

自 4.04 起可用的 -opaque 選項會停用目前編譯單元的跨模組最佳化資訊。在編譯 .mli 介面時,使用 -opaque 會標記已編譯的 .cmi 介面,以便後續編譯依賴它的模組不會依賴對應的 .cmx 檔案,也不會在它不存在時發出警告。當原生編譯器編譯 .ml 實作時,使用 -opaque 會產生一個不包含任何跨模組最佳化資訊的 .cmx

使用此選項可能會降低產生程式碼的品質,但會減少編譯時間,無論是全新建置還是增量建置。實際上,使用原生編譯器時,當編譯單元的實作變更時,所有依賴它的單元可能都需要重新編譯,因為跨模組資訊可能已變更。如果實作變更的編譯單元是使用 -opaque 編譯的,則不需要進行此類重新編譯。因此,可以使用此選項來更快地獲得編輯-編譯-測試回饋迴路。

-open 模組
在處理介面或實作檔案之前,開啟給定的模組。如果給定了多個 -open 選項,它們會按順序處理,就像在每個檔案的頂部添加了語句 open! 模組1;; ... open! 模組N;; 一樣。
-output-obj
導致連結器產生 C 物件檔案而不是位元組碼可執行檔案。這可用於將 OCaml 程式碼包裝為可從任何 C 程式呼叫的 C 函式庫。請參閱第22章,第22.7.5節。必須使用 -o 選項設定輸出物件檔案的名稱。此選項也可用於產生 C 來源檔案(.c 副檔名)或已編譯的共用/動態函式庫(.so 副檔名,Windows 下為 .dll)。
-output-complete-exe
透過連結包含位元組碼程式、OCaml 執行時系統以及給定給 ocamlc 的任何其他靜態 C 程式碼的 C 物件檔案來建構一個獨立的可執行檔。產生的效果與 -custom 類似,只是位元組碼嵌入在 C 程式碼中,因此諸如 ocamldebug 之類的工具無法再存取它。另一方面,產生的二進位檔案可以抵抗 strip
-output-complete-obj
-output-obj 選項相同,只是產生的物件檔案包含執行時和自動連結函式庫。
-pack
建構一個位元組碼物件檔案(.cmo 檔案)及其相關聯的已編譯介面(.cmi),該檔案結合命令列上給定的物件檔案,使其顯示為輸出 .cmo 檔案的子模組。輸出 .cmo 檔案的名稱必須使用 -o 選項給定。例如,
        ocamlc -pack -o p.cmo a.cmo b.cmo c.cmo
會產生已編譯的檔案 p.cmop.cmi,它們描述一個具有三個子模組 ABC 的編譯單元,對應於物件檔案 a.cmob.cmoc.cmo 的內容。這些內容可以在程式的其餘部分中參照為 P.AP.BP.C
-pp 命令
導致編譯器呼叫給定的 命令 作為每個原始檔的預處理器。命令的輸出會重新導向到一個中間檔案,該檔案會被編譯。如果沒有編譯錯誤,則中間檔案會在之後刪除。
-ppx 命令
在剖析後,透過預處理器 命令 管線化抽象語法樹。在第30章中描述的模組 Ast_mapper 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 階段
在指定的編譯階段後停止編譯。目前支援的階段有:parsing (語法分析)、typing (類型檢查)。
-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 runtime-name
產生一個可以在自訂執行時期系統 runtime-name 上執行的位元組碼執行檔,該執行時期系統是先前使用 ocamlc -make-runtime runtime-name 建立的。有關更多資訊,請參閱第 ‍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
部分比對:模式比對中缺少的情況。
9 missing-record-field-pattern
記錄模式中缺少欄位。
10 non-unit-statement
序列左側的表達式不具有 unit 類型(而且不是函式,請參閱警告編號 5)。
11 redundant-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
無害的未使用變數:未使用 letas 綁定的未使用變數,且不以底線 (_) 字元開頭。
28 wildcard-arg-to-constant-constr
萬用字元模式作為常數建構子的參數給出。
29 eol-in-string
字串常數中未逸出的行尾符號(不可移植的程式碼)。
30 duplicate-definitions
在兩個相互遞迴的類型中,定義了兩個名稱相同的標籤或建構子。
31 module-linked-twice
同一個執行檔中一個模組被連結兩次。
I
已忽略:現在是一個硬錯誤(自 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
在模式匹配中無法到達的情況(基於型別資訊)。(自 4.03 版起)
57 ambiguous-var-in-pattern-guard (請參閱 13.5.4)
在 guard 下含糊不清的 or 模式變數。(自 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
取決於可變狀態的模式匹配會阻止其餘引數被非 currying 化。(自 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 警告清單
將引數 警告清單 中指定的警告標記為致命。當發出這些警告之一時,編譯器將停止並出現錯誤。警告清單 的含義與 -w 選項相同:+ 號(或大寫字母)將對應的警告標記為致命,- 號(或小寫字母)將它們變回非致命警告,而 @ 號同時啟用並將對應的警告標記為致命。

注意:不建議在生產程式碼中使用警告集(即字母)作為 -warn-error 的引數,因為當未來版本的 OCaml 新增一些新警告時,這可能會中斷您的建置。

預設設定為 -warn-error -a(沒有警告是致命的)。

-warn-help
顯示所有可用警告編號的描述。
-where
列印標準程式庫的位置,然後退出。
-with-runtime
在產生的程式中包含執行時間系統。這是預設值。
-without-runtime
編譯器不會在產生的程式中包含執行時間系統(或對其的參考);必須單獨提供。
- 檔案
檔案 作為檔案名稱處理,即使它以破折號 (-) 字元開頭。
-help--help
顯示簡短的使用摘要並退出。
contextual-cli-control

命令列選項的上下文控制

編譯器命令列可以使用以下機制「從外部」修改。這些是實驗性的,可能會變更。它們僅應使用於實驗和開發工作,而不應在發佈的套件中使用。

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.名稱 下參考 mylib/misc.ml 中定義的元件;它們也可以執行 open Misc,然後使用非限定名稱 名稱

編譯器產生的 .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

這會產生錯誤訊息「型別為 foo 的表達式 C 無法與型別 foo 一起使用」。

此表達式的型別 t 包含無法泛化的型別變數
型別 t 中的型別變數('a'b、…)可能處於兩種狀態:已泛化(表示型別 t 對於變數的所有可能實例化都有效)和未泛化(表示型別 t 僅對變數的一個實例化有效)。在 let 綁定 let name = expr 中,型別檢查器通常會盡可能地泛化 expr 的型別中的所有型別變數。但是,這會導致不健全性(型別正確的程式可能會崩潰),並與多型的可變資料結構結合使用。為避免這種情況,只有在綁定的表達式 expr 屬於「語法值」類別時,才會在 let 綁定時執行泛化,該類別包括常數、識別符號、函數、語法值元組等。在所有其他情況下(例如,expr 是函數應用程式),可能會建立多型的可變變數,因此會關閉型別中所有反變或非變分支中出現的變數的泛化。例如,如果非值型別為 'a list,則變數是可泛化的(list 是協變型別建構子),但在 'a list -> 'a list 中則不能(-> 的左分支是反變的)或 'a refref 是非變的)。

型別中未泛化的型別變數在給定的結構或編譯單元(.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 ...
    
    並在 mod2.cmo 之前連結 mod1.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.

當將標籤新增至 OCaml 時,引入了此 labels-omitted 應用程式的支援,以簡化在程式碼庫中逐步引入標籤的過程。但是,它會削弱標籤原則的缺點:如果您使用標籤來防止呼叫者錯誤地重新排序兩個相同類型的參數,則省略標籤會使這個錯誤再次發生。

當使用省略標籤的應用程式時,警告 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
Warning 52 [fragile-literal-pattern]: 程式碼不應依賴此建構函式參數的實際值。它們僅供參考,並且可能會在未來的版本中變更。(請參閱手冊 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:守衛條件下含糊的 or 模式變數

OCaml 中 or 模式的語義是指定為由左至右的偏好:如果值 v 符合模式 pq,則值 v 符合模式 p | q,但如果同時符合兩者,則比對所捕獲的環境是由 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) 與 or 模式進行比對,在左分支中成功,它會傳回環境 x ‍-> ‍a,然後測試守衛條件 is_neutral ‍a,結果失敗,不會採用該分支。

然而,另一種語義在此可能會被認為更自然:任何一側通過測試的配對都會採用該分支。使用這種語義,先前的程式碼片段將等效於

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

這 *不是* OCaml 所採用的語義。

警告 57 專門針對這些令人困惑的情況,其中指定的由左至右語義與相對於特定守衛條件的非確定性語義 (可以採用任何分支) 不等效。更精確地說,當守衛條件使用「含糊」變數時,它會發出警告,這些變數透過 or 模式的不同側綁定到被檢查值的不同部分。