├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature-request.md │ └── bug.md └── PULL_REQUEST_TEMPLATE.md ├── editors └── vscode │ ├── .vscodeignore │ ├── icon.png │ ├── languages │ ├── mlb │ │ ├── language-configuration.json │ │ ├── snippets.json │ │ └── syntax.json │ ├── sml-nj-cm │ │ ├── snippets.json │ │ ├── language-configuration.json │ │ └── syntax.json │ └── sml │ │ ├── language-configuration.json │ │ └── snippets.json │ ├── tsconfig.json │ └── src │ └── main.ts ├── crates ├── tests │ ├── src │ │ ├── hover.rs │ │ ├── deviations │ │ │ ├── lunar_ml.rs │ │ │ ├── successor_ml.rs │ │ │ ├── successor_ml │ │ │ │ ├── exp_row_pun.rs │ │ │ │ ├── or_pat.rs │ │ │ │ ├── do_dec.rs │ │ │ │ ├── opt_semi.rs │ │ │ │ └── withtype_spec.rs │ │ │ ├── implicit_ty_var.rs │ │ │ └── mlton.rs │ │ ├── sep.rs │ │ ├── input.rs │ │ ├── deviations.rs │ │ ├── docs.rs │ │ ├── incomplete.rs │ │ ├── circularity.rs │ │ ├── symbolic.rs │ │ ├── unused.rs │ │ ├── use_builtin.rs │ │ ├── cannot_rebind.rs │ │ ├── datatype_copy.rs │ │ ├── local.rs │ │ ├── well_known.rs │ │ ├── forbid_opaque_asc.rs │ │ ├── infix_without_op.rs │ │ ├── check │ │ │ └── input.rs │ │ ├── goto_def.rs │ │ ├── mismatched_fields.rs │ │ ├── dupe │ │ │ └── spec.rs │ │ ├── lib.rs │ │ ├── input │ │ │ ├── misc.rs │ │ │ └── diagnostics.rs │ │ ├── shadow.rs │ │ ├── num_record.rs │ │ ├── generalize.rs │ │ ├── fixity.rs │ │ ├── sep │ │ │ └── extra.rs │ │ ├── pat │ │ │ └── or.rs │ │ ├── completions.rs │ │ ├── ty_escape.rs │ │ ├── big.rs │ │ └── literal.rs │ ├── Cargo.toml │ └── build.rs ├── sml-dynamics │ ├── src │ │ └── lib.rs │ └── Cargo.toml ├── lex-util │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── block_comment.rs ├── sml-file │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── sml-namespace │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── sml-hir-lower │ ├── src │ │ ├── lib.rs │ │ └── root.rs │ └── Cargo.toml ├── sml-lab │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── cov-mark │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── sml-path │ └── Cargo.toml ├── panic-hook │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── sml-comment │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── sml-statics │ ├── src │ │ ├── config.rs │ │ ├── unify.rs │ │ ├── top_dec │ │ │ ├── env_syms.rs │ │ │ └── ty_con_paths.rs │ │ ├── error │ │ │ └── suggestion.rs │ │ ├── lib.rs │ │ └── util.rs │ └── Cargo.toml ├── paths-glob │ └── Cargo.toml ├── sml-fixity │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── slash-var-path │ └── Cargo.toml ├── millet-ls │ ├── src │ │ └── main.rs │ └── Cargo.toml ├── sml-ty-var-scope │ └── Cargo.toml ├── sml-scon │ └── Cargo.toml ├── config │ ├── src │ │ ├── lib.rs │ │ ├── tool.rs │ │ ├── lang.rs │ │ └── init.rs │ └── Cargo.toml ├── sml-naive-fmt │ └── Cargo.toml ├── sml-hir │ └── Cargo.toml ├── sml-symbol-kind │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── sml-syntax │ ├── Cargo.toml │ └── build.rs ├── mlb-hir │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── sml-lex │ └── Cargo.toml ├── cm-syntax │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── lower.rs ├── input │ ├── src │ │ ├── types.rs │ │ └── topo.rs │ └── Cargo.toml ├── mlb-syntax │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── sml-parse │ ├── Cargo.toml │ └── src │ │ ├── root.rs │ │ └── lib.rs ├── lang-srv │ ├── src │ │ ├── state.rs │ │ ├── response.rs │ │ ├── capabilities.rs │ │ └── lib.rs │ └── Cargo.toml ├── sml-file-syntax │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── millet-cli │ └── Cargo.toml ├── sml-dynamics-tests │ └── Cargo.toml ├── sml-statics-types │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ ├── item.rs │ │ ├── data.rs │ │ ├── mode.rs │ │ └── disallow.rs ├── mlb-statics │ └── Cargo.toml └── analysis │ ├── Cargo.toml │ └── src │ └── matcher.rs ├── rustfmt.toml ├── .cargo └── config.toml ├── docs ├── diagnostics.md ├── diagnostics │ ├── 1019.md │ ├── 4038.md │ ├── 1005.md │ ├── 4004.md │ ├── 6001.md │ ├── 4028.md │ ├── 1007.md │ ├── 1008.md │ ├── 4026.md │ ├── 2010.md │ ├── 1006.md │ ├── 4037.md │ ├── 5029.md │ ├── 1009.md │ ├── 1011.md │ ├── 3007.md │ ├── 4006.md │ ├── 4035.md │ ├── 1015.md │ ├── 1014.md │ ├── 4007.md │ ├── 4021.md │ ├── 1001.md │ ├── 5008.md │ ├── 1016.md │ ├── 3009.md │ ├── 5020.md │ ├── 3008.md │ ├── 4027.md │ ├── 1010.md │ ├── 4032.md │ ├── 3004.md │ ├── 4020.md │ ├── 2005.md │ ├── 4036.md │ ├── 4003.md │ ├── 1020.md │ ├── 1013.md │ ├── 3002.md │ ├── 4001.md │ ├── 1021.md │ ├── 3003.md │ ├── 1012.md │ ├── 5036.md │ ├── 4017.md │ ├── 4019.md │ ├── 4009.md │ ├── 4023.md │ ├── 5027.md │ ├── 1002.md │ ├── 5042.md │ ├── 2001.md │ ├── 4005.md │ ├── 5037.md │ ├── 5999.md │ ├── 1022.md │ ├── 4022.md │ ├── 3001.md │ ├── 4008.md │ ├── 4024.md │ ├── 2002.md │ ├── 4034.md │ ├── 5031.md │ ├── 4016.md │ ├── 5003.md │ ├── 2006.md │ ├── 1018.md │ ├── 4029.md │ ├── 5002.md │ ├── 4030.md │ ├── 4033.md │ ├── 5016.md │ ├── 5019.md │ ├── 5010.md │ ├── 5004.md │ ├── 5007.md │ ├── 2004.md │ ├── 5030.md │ ├── 3005.md │ ├── 4013.md │ ├── 5032.md │ ├── 3006.md │ ├── 2003.md │ ├── 5041.md │ ├── 4031.md │ ├── 5024.md │ ├── 5039.md │ ├── 5009.md │ ├── 5015.md │ ├── 2007.md │ ├── 5026.md │ ├── 5035.md │ ├── 5014.md │ ├── 4011.md │ ├── 5018.md │ ├── 5021.md │ ├── 1017.md │ ├── 5023.md │ ├── 4010.md │ ├── 5025.md │ ├── 4002.md │ ├── 2009.md │ ├── 1003.md │ ├── 5040.md │ ├── 4014.md │ ├── 5022.md │ ├── 2008.md │ ├── 5038.md │ ├── 5012.md │ ├── 5013.md │ ├── 5043.md │ ├── 4025.md │ ├── 4015.md │ ├── 5017.md │ └── 4018.md ├── errors.md ├── SECURITY.md ├── README.md └── CONTRIBUTING.md ├── .gitignore ├── .vscode ├── tasks.json ├── settings.json └── launch.json ├── xtask └── Cargo.toml └── LICENSE-MIT.md /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /editors/vscode/.vscodeignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | src 3 | tsconfig.json 4 | -------------------------------------------------------------------------------- /crates/tests/src/hover.rs: -------------------------------------------------------------------------------- 1 | //! Hover tests. 2 | 3 | mod doc; 4 | mod ty; 5 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2024" 2 | tab_spaces = 2 3 | use_small_heuristics = "Max" 4 | -------------------------------------------------------------------------------- /editors/vscode/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azdavis/millet/HEAD/editors/vscode/icon.png -------------------------------------------------------------------------------- /crates/tests/src/deviations/lunar_ml.rs: -------------------------------------------------------------------------------- 1 | //! Special features for Lunar ML. 2 | 3 | mod es_import; 4 | -------------------------------------------------------------------------------- /crates/tests/src/sep.rs: -------------------------------------------------------------------------------- 1 | //! Tests about separators like `,` and `;`. 2 | 3 | mod extra; 4 | mod trailing; 5 | -------------------------------------------------------------------------------- /editors/vscode/languages/mlb/language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "blockComment": ["(*", "*)"] 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = "run --package xtask --" 3 | 4 | [target.aarch64-unknown-linux-gnu] 5 | linker = "aarch64-linux-gnu-gcc" 6 | -------------------------------------------------------------------------------- /docs/diagnostics.md: -------------------------------------------------------------------------------- 1 | # Diagnostics 2 | 3 | Documentation for all the diagnostics which Millet can emit has moved to the [`diagnostics/`](./diagnostics/) folder. 4 | -------------------------------------------------------------------------------- /crates/tests/src/input.rs: -------------------------------------------------------------------------------- 1 | //! Low-level tests for analysis input. 2 | 3 | mod cm; 4 | mod config; 5 | mod diagnostics; 6 | mod misc; 7 | mod mlb; 8 | mod slash_var_path; 9 | -------------------------------------------------------------------------------- /crates/tests/src/deviations.rs: -------------------------------------------------------------------------------- 1 | //! Known deviations (or not) from the Definition. 2 | 3 | mod implicit_ty_var; 4 | mod lunar_ml; 5 | mod mlton; 6 | mod smlnj; 7 | mod successor_ml; 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /*.tmp 2 | /binary 3 | /editors/vscode/CHANGELOG.md 4 | /editors/vscode/LICENSE.md 5 | /editors/vscode/node_modules 6 | /editors/vscode/out 7 | /editors/vscode/vsix 8 | /target 9 | -------------------------------------------------------------------------------- /docs/diagnostics/1019.md: -------------------------------------------------------------------------------- 1 | # 1019 2 | 3 | When run as a CLI, there was an invalid or missing argument or option. 4 | 5 | ## To fix 6 | 7 | Run `--help` to see permitted options and arguments. 8 | -------------------------------------------------------------------------------- /docs/diagnostics/4038.md: -------------------------------------------------------------------------------- 1 | # 4038 2 | 3 | An ES import didn't provide a name for an import with a string. 4 | 5 | ## To fix 6 | 7 | Use the `as ` form of the import to name the import. 8 | -------------------------------------------------------------------------------- /docs/errors.md: -------------------------------------------------------------------------------- 1 | # Diagnostics 2 | 3 | Documentation for all the errors and warnings, collectively "diagnostics", which Millet can emit has moved to the [`diagnostics/`](./diagnostics/) folder. 4 | -------------------------------------------------------------------------------- /docs/diagnostics/1005.md: -------------------------------------------------------------------------------- 1 | # 1005 2 | 3 | A group file manually specified was not either a `.mlb` (ML Basis) or `.cm` (SML/NJ CM) file. 4 | 5 | ## To fix 6 | 7 | Only specify paths that end in `.mlb` or `.cm` as group paths. 8 | -------------------------------------------------------------------------------- /docs/diagnostics/4004.md: -------------------------------------------------------------------------------- 1 | # 4004 2 | 3 | A real literal was invalid. 4 | 5 | ## To fix 6 | 7 | If you get this error, please file an issue against Millet. I'd be interested if this error is possible to hit. I've never seen it emitted before. 8 | -------------------------------------------------------------------------------- /docs/diagnostics/6001.md: -------------------------------------------------------------------------------- 1 | # 6001 2 | 3 | A comment prevented automatic formatting of a SML file. 4 | 5 | ## To fix 6 | 7 | Try one of the following: 8 | 9 | - Move, merge, or remove the comment. 10 | - Disable automatic formatting. 11 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 4 | 5 | ## Motivation 6 | 7 | 8 | 9 | ## Tests 10 | 11 | 12 | -------------------------------------------------------------------------------- /crates/sml-dynamics/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! The dynamic semantics, aka, running a program. 2 | 3 | mod display; 4 | mod dynamics; 5 | mod pat_match; 6 | mod step; 7 | mod types; 8 | 9 | pub use dynamics::{Dynamics, Progress}; 10 | pub use types::Cx; 11 | -------------------------------------------------------------------------------- /crates/tests/src/deviations/successor_ml.rs: -------------------------------------------------------------------------------- 1 | //! Tests for Successor ML features, which extend the Definition. 2 | 3 | mod do_dec; 4 | mod exp_row_pun; 5 | mod opt_bar; 6 | mod opt_semi; 7 | mod or_pat; 8 | mod vector; 9 | mod withtype_spec; 10 | -------------------------------------------------------------------------------- /crates/lex-util/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lex-util" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | test = false 13 | doctest = false 14 | -------------------------------------------------------------------------------- /crates/sml-file/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sml-file" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | doctest = false 13 | test = false 14 | -------------------------------------------------------------------------------- /docs/diagnostics/4028.md: -------------------------------------------------------------------------------- 1 | # 4028 2 | 3 | There was a trailing comma or semicolon. 4 | 5 | ```sml 6 | val x = (1, 2,) 7 | (** ^ trailing `,` *) 8 | ``` 9 | 10 | ## To fix 11 | 12 | Remove the trailing separator. 13 | 14 | ```sml 15 | val x = (1, 2) 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/diagnostics/1007.md: -------------------------------------------------------------------------------- 1 | # 1007 2 | 3 | The Millet config file had an invalid version. 4 | 5 | This will error: 6 | 7 | ```toml 8 | version = 2 9 | ``` 10 | 11 | ## To fix 12 | 13 | Only use version 1 in the config file. 14 | 15 | ```toml 16 | version = 1 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/diagnostics/1008.md: -------------------------------------------------------------------------------- 1 | # 1008 2 | 3 | There was an error when parsing a SML/NJ CM file. 4 | 5 | ## To fix 6 | 7 | Use only the subset of CM syntax Millet understands. 8 | 9 | Some features, like the "preprocessor", tool options, and string paths, are not supported. 10 | -------------------------------------------------------------------------------- /crates/sml-namespace/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sml-namespace" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | doctest = false 13 | test = false 14 | -------------------------------------------------------------------------------- /docs/diagnostics/4026.md: -------------------------------------------------------------------------------- 1 | # 4026 2 | 3 | There was a `fun` with no parameters. 4 | 5 | ```sml 6 | fun totoro = 3 7 | (** + requires at least 1 parameter *) 8 | ``` 9 | 10 | ## To fix 11 | 12 | Add at least one parameter. 13 | 14 | ```sml 15 | fun totoro x = x + 3 16 | ``` 17 | -------------------------------------------------------------------------------- /editors/vscode/languages/sml-nj-cm/snippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "`group` desc": { 3 | "prefix": "group", 4 | "body": ["Group is", " ${1:...}"] 5 | }, 6 | "`library` desc": { 7 | "prefix": "library", 8 | "body": ["Library", " ${1:...}", "is", " ${2:...}"] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /editors/vscode/languages/sml-nj-cm/language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "lineComment": ";", 4 | "blockComment": ["(*", "*)"] 5 | }, 6 | "brackets": [["(", ")"]], 7 | "autoClosingPairs": [{ "open": "(", "close": ")" }], 8 | "surroundingPairs": [["(", ")"]] 9 | } 10 | -------------------------------------------------------------------------------- /docs/diagnostics/2010.md: -------------------------------------------------------------------------------- 1 | # 2010 2 | 3 | A name started with an underscore. 4 | 5 | ```sml 6 | val _foo = 3 7 | (** ^^^^ name cannot start with a `_` *) 8 | ``` 9 | 10 | ## To fix 11 | 12 | Change the name, perhaps by removing the leading underscore. 13 | 14 | ```sml 15 | val foo = 3 16 | ``` 17 | -------------------------------------------------------------------------------- /crates/sml-hir-lower/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Lowering AST into HIR. 2 | 3 | #![allow(clippy::too_many_lines, clippy::single_match_else)] 4 | 5 | mod common; 6 | mod dec; 7 | mod exp; 8 | mod pat; 9 | mod root; 10 | mod ty; 11 | mod util; 12 | 13 | pub use root::get; 14 | pub use util::{Error, Lower, Ptrs}; 15 | -------------------------------------------------------------------------------- /docs/diagnostics/1006.md: -------------------------------------------------------------------------------- 1 | # 1006 2 | 3 | Millet could not parse the config file as valid TOML. 4 | 5 | ## To fix 6 | 7 | Ensure the [TOML][] syntax is valid, and that the config file is of the [expected format][config]. 8 | 9 | [toml]: https://toml.io/en/ 10 | [config]: /docs/manual.md#millettoml 11 | -------------------------------------------------------------------------------- /docs/diagnostics/4037.md: -------------------------------------------------------------------------------- 1 | # 4037 2 | 3 | A `.sig` file didn't contain exactly one `signature`, or a `.fun` file didn't contain exactly one `functor`. 4 | 5 | ## To fix 6 | 7 | - Make it so the file defines only one of the expected type of top declaration. 8 | - Rename the file to have a generic `.sml` suffix. 9 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build-vscode-extension", 6 | "group": "build", 7 | "type": "shell", 8 | "command": "cargo xtask dist", 9 | "presentation": { 10 | "reveal": "silent" 11 | } 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /docs/diagnostics/5029.md: -------------------------------------------------------------------------------- 1 | # 5029 2 | 3 | There was an unused variable. 4 | 5 | ```sml 6 | fun ignoreVar x = 3 7 | (** ^ unused value: `x` *) 8 | ``` 9 | 10 | ## To fix 11 | 12 | Use the variable, or do not define it. 13 | 14 | ```sml 15 | fun useVar x = x + 3 16 | fun doNotBindVar _ = 3 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/diagnostics/1009.md: -------------------------------------------------------------------------------- 1 | # 1009 2 | 3 | There was an error when parsing a ML Basis file. 4 | 5 | ## To fix 6 | 7 | Use only the subset of MLB syntax Millet understands. 8 | 9 | Some features, like string paths, are not supported. 10 | 11 | Other features, like most kinds of annotations, are parsed but ignored. 12 | -------------------------------------------------------------------------------- /docs/diagnostics/1011.md: -------------------------------------------------------------------------------- 1 | # 1011 2 | 3 | There was a duplicate name in a ML Basis file. 4 | 5 | For instance, `structure A and structure A` will trigger this error. 6 | 7 | ## To fix 8 | 9 | - Use different names. 10 | - Or, remove the `and`. This will induce shadowing. 11 | 12 | See also error [5002](./5002.md). 13 | -------------------------------------------------------------------------------- /docs/diagnostics/3007.md: -------------------------------------------------------------------------------- 1 | # 3007 2 | 3 | There was an unnecessary usage of `op`. 4 | 5 | ```sml 6 | exception op E 7 | (** ^^ unnecessary `op` *) 8 | val op x = 3 9 | (** ^^ unnecessary `op` *) 10 | ``` 11 | 12 | ## To fix 13 | 14 | Remove the `op`. 15 | 16 | ```sml 17 | exception E 18 | val x = 3 19 | ``` 20 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.rulers": [100], 3 | "files.exclude": { 4 | "editors/vscode/node_modules": true, 5 | "editors/vscode/out": true, 6 | "target": true 7 | }, 8 | "markdown.validate.enabled": true, 9 | "millet.server.enable": false, 10 | "rust-analyzer.check.command": "clippy" 11 | } 12 | -------------------------------------------------------------------------------- /crates/sml-lab/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sml-lab" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | doctest = false 13 | test = false 14 | 15 | [dependencies] 16 | str-util.workspace = true 17 | -------------------------------------------------------------------------------- /docs/diagnostics/4006.md: -------------------------------------------------------------------------------- 1 | # 4006 2 | 3 | There were multiple `...` rest pattern rows. 4 | 5 | ```sml 6 | val {a, ..., ...} = {a = 1, b = "hi"} 7 | (** ^^^^^^^^^^^^^ multiple `...` *) 8 | ``` 9 | 10 | ## To fix 11 | 12 | Only provide at most one `...` row. 13 | 14 | ```sml 15 | val {a, ...} = {a = 1, b = "hi"} 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/diagnostics/4035.md: -------------------------------------------------------------------------------- 1 | # 4035 2 | 3 | The empty record/tuple expression/pattern was written as `{}`, but it is usually written as `()`. 4 | 5 | ```sml 6 | fun noOp {} = {} 7 | (** + usually written as `()` *) 8 | ``` 9 | 10 | ## To fix 11 | 12 | Replace `{}` with `()`. 13 | 14 | ```sml 15 | fun noOp () = () 16 | ``` 17 | -------------------------------------------------------------------------------- /crates/cov-mark/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cov-mark" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | test = false 13 | doctest = false 14 | 15 | [dependencies] 16 | fast-hash.workspace = true 17 | -------------------------------------------------------------------------------- /crates/sml-path/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sml-path" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | doctest = false 13 | test = false 14 | 15 | [dependencies] 16 | str-util.workspace = true 17 | -------------------------------------------------------------------------------- /docs/diagnostics/1015.md: -------------------------------------------------------------------------------- 1 | # 1015 2 | 3 | The glob pattern for `workspace.root` matched no paths. 4 | 5 | This means Millet cannot determine what the workspace root(s) should be. 6 | 7 | ## To fix 8 | 9 | - Change the pattern to match at least one path. 10 | - Or create at least one file that would be matched by the pattern. 11 | -------------------------------------------------------------------------------- /crates/panic-hook/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "panic-hook" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | test = false 13 | doctest = false 14 | 15 | [dependencies] 16 | better-panic.workspace = true 17 | -------------------------------------------------------------------------------- /docs/diagnostics/1014.md: -------------------------------------------------------------------------------- 1 | # 1014 2 | 3 | There was a glob pattern parse error in `workspace.root` in the config file. 4 | 5 | Currently, Millet uses the [`glob` crate](https://docs.rs/glob/latest/glob) for glob parsing. This may change in the future. 6 | 7 | ## To fix 8 | 9 | Consult the docs linked above for valid glob syntax. 10 | -------------------------------------------------------------------------------- /docs/diagnostics/4007.md: -------------------------------------------------------------------------------- 1 | # 4007 2 | 3 | There was a non-`...` pattern row after a `...` pattern row. 4 | 5 | ```sml 6 | val {..., b} = {a = 1, b = "hi"} 7 | (** ^^^^^^^^ `...` must come last *) 8 | ``` 9 | 10 | ## To fix 11 | 12 | Put the `...` pattern row last. 13 | 14 | ```sml 15 | val {b, ...} = {a = 1, b = "hi"} 16 | ``` 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Propose a new feature 4 | labels: enhancement 5 | --- 6 | 7 | ## Problem 8 | 9 | 10 | 11 | ## Solution 12 | 13 | 14 | -------------------------------------------------------------------------------- /crates/sml-comment/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sml-comment" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | test = false 13 | doctest = false 14 | 15 | [dependencies] 16 | sml-syntax.path = "../sml-syntax" 17 | -------------------------------------------------------------------------------- /crates/sml-statics/src/config.rs: -------------------------------------------------------------------------------- 1 | //! See [`Cfg`]. 2 | 3 | /// Configuration. 4 | #[derive(Debug, Default, Clone, Copy)] 5 | pub(crate) struct Cfg { 6 | /// Mark things as defined. 7 | /// 8 | /// This is for then emitting warnings for things that were marked as defined but then not used. 9 | pub(crate) mark_defined: bool, 10 | } 11 | -------------------------------------------------------------------------------- /docs/diagnostics/4021.md: -------------------------------------------------------------------------------- 1 | # 4021 2 | 3 | There was a declaration with `eqtype`. 4 | 5 | ```sml 6 | eqtype num = int 7 | (** + `eqtype` not allowed here *) 8 | ``` 9 | 10 | `eqtype` is allowed on specifications, not declarations. 11 | 12 | ## To fix 13 | 14 | Change `eqtype` to `type`. 15 | 16 | ```sml 17 | type num = int 18 | ``` 19 | -------------------------------------------------------------------------------- /crates/tests/src/docs.rs: -------------------------------------------------------------------------------- 1 | //! Tests to make sure Millet behaves as expected on the public documentation. 2 | 3 | use crate::check::markdown::check; 4 | 5 | #[test] 6 | fn primitives() { 7 | check(include_str!("../../../docs/primitives.md")); 8 | } 9 | 10 | #[test] 11 | fn tokens() { 12 | check(include_str!("../../../docs/tokens.md")); 13 | } 14 | -------------------------------------------------------------------------------- /docs/diagnostics/1001.md: -------------------------------------------------------------------------------- 1 | # 1001 2 | 3 | Millet failed to perform file or directory I/O with the filesystem. It could be that the path in question: 4 | 5 | - does not exist. 6 | - has insufficient permissions. 7 | - is a directory when a file was expected, or vice versa. 8 | 9 | ## To fix 10 | 11 | Inspect the error for the underlying cause. 12 | -------------------------------------------------------------------------------- /crates/paths-glob/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "paths-glob" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | test = false 13 | doctest = false 14 | 15 | [dependencies] 16 | glob.workspace = true 17 | paths.workspace = true 18 | -------------------------------------------------------------------------------- /docs/diagnostics/5008.md: -------------------------------------------------------------------------------- 1 | # 5008 2 | 3 | There was a duplicate label. 4 | 5 | ```sml 6 | val x = { a = 1, a = 2 } 7 | (** ^^^^^^^^^^^^^^^^ duplicate label: `a` *) 8 | ``` 9 | 10 | ## To fix 11 | 12 | Use differently named labels, or remove one of the record rows. 13 | 14 | ```sml 15 | val x = { a = 1, b = 2 } 16 | val x = { a = 1 } 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/diagnostics/1016.md: -------------------------------------------------------------------------------- 1 | # 1016 2 | 3 | There was a `funsig` export kind in a SML/NJ CM file. 4 | 5 | Millet does not support `funsig` exports in CM files, nor does support `funsig` in SML source files. This is because `funsig` is not standard SML as defined in its Definition. 6 | 7 | ## To fix 8 | 9 | Avoid using `funsig` in exports and in SML code. 10 | -------------------------------------------------------------------------------- /docs/diagnostics/3009.md: -------------------------------------------------------------------------------- 1 | # 3009 2 | 3 | A sub-expression was missing required parentheses. 4 | 5 | ```sml 6 | val seven = 3 + if 5 > 2 then 4 else 1 7 | (** ^^ parentheses required around `if` expressions here *) 8 | ``` 9 | 10 | ## To fix 11 | 12 | Add parentheses. 13 | 14 | ```sml 15 | val seven = 3 + (if 5 > 2 then 4 else 1) 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/diagnostics/5020.md: -------------------------------------------------------------------------------- 1 | # 5020 2 | 3 | In an exception copy declaration, the right-hand side was not an exception. 4 | 5 | ```sml 6 | val e = 3 7 | exception Nope = e 8 | (** + not an exception: `e` *) 9 | ``` 10 | 11 | ## To fix 12 | 13 | Only use exceptions on the right-hand side. 14 | 15 | ```sml 16 | exception E 17 | exception Nope = E 18 | ``` 19 | -------------------------------------------------------------------------------- /crates/sml-fixity/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sml-fixity" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | doctest = false 13 | test = false 14 | 15 | [dependencies] 16 | fast-hash.workspace = true 17 | str-util.workspace = true 18 | -------------------------------------------------------------------------------- /docs/diagnostics/3008.md: -------------------------------------------------------------------------------- 1 | # 3008 2 | 3 | There was a unmatched closing delimiter, like `)` or `end`. 4 | 5 | ```sml 6 | val oops = 3] 7 | (** ^ unmatched closing delimiter *) 8 | ``` 9 | 10 | ## To fix 11 | 12 | Remove the stray delimiter, or match it with an opening delimiter. 13 | 14 | ```sml 15 | val remove = 3 16 | val addOpening = [3] 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/diagnostics/4027.md: -------------------------------------------------------------------------------- 1 | # 4027 2 | 3 | There was an expression sequence with no expressions. 4 | 5 | ```sml 6 | val _ = let val x = 4 in end 7 | (** + requires at least 1 expression *) 8 | ``` 9 | 10 | ## To fix 11 | 12 | Add at least one expression. 13 | 14 | ```sml 15 | val a = let val x = 4 in x + 1 end 16 | val b = let val x = 3 in 1; 2; x end 17 | ``` 18 | -------------------------------------------------------------------------------- /crates/slash-var-path/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "slash-var-path" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | test = false 13 | doctest = false 14 | 15 | [dependencies] 16 | fast-hash.workspace = true 17 | str-util.workspace = true 18 | -------------------------------------------------------------------------------- /docs/diagnostics/1010.md: -------------------------------------------------------------------------------- 1 | # 1010 2 | 3 | There was a cycle between files. 4 | 5 | For instance, a cycle occurs if a group file attempts to include itself. As another example, a cycle also occurs if: 6 | 7 | - A group file `X` attempts to include a group file `Y`, and 8 | - that file `Y` also attempts to include `X`. 9 | 10 | ## To fix 11 | 12 | Break the cycle. 13 | -------------------------------------------------------------------------------- /docs/diagnostics/4032.md: -------------------------------------------------------------------------------- 1 | # 4032 2 | 3 | The right-hand side of an `exception` copy declaration must be a path, not an arbitrary expression. 4 | 5 | ```sml 6 | exception A 7 | fun get () = A 8 | exception B = get () 9 | (** ^^^^^^ must be a path *) 10 | ``` 11 | 12 | ## To fix 13 | 14 | Use a path. 15 | 16 | ```sml 17 | exception A 18 | exception B = A 19 | ``` 20 | -------------------------------------------------------------------------------- /crates/millet-ls/src/main.rs: -------------------------------------------------------------------------------- 1 | //! A thin wrapper around [`lang_srv`]. 2 | 3 | fn main() -> anyhow::Result<()> { 4 | panic_hook::install(); 5 | env_logger::try_init_from_env(env_logger::Env::default().default_filter_or("error"))?; 6 | log::info!("start up millet lsp server"); 7 | lang_srv::run_stdio()?; 8 | log::info!("shut down millet lsp server"); 9 | Ok(()) 10 | } 11 | -------------------------------------------------------------------------------- /crates/sml-ty-var-scope/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sml-ty-var-scope" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | test = false 13 | doctest = false 14 | 15 | [dependencies] 16 | fast-hash.workspace = true 17 | 18 | sml-hir.path = "../sml-hir" 19 | -------------------------------------------------------------------------------- /docs/diagnostics/3004.md: -------------------------------------------------------------------------------- 1 | # 3004 2 | 3 | A fixity declaration was negative. 4 | 5 | ```sml 6 | infix ~3 foo 7 | (** ^^ fixity is negative *) 8 | ``` 9 | 10 | ## To fix 11 | 12 | Only use non-negative fixities. 13 | 14 | Zero is a valid fixity. In fact, zero is implied when a fixity number is not given. 15 | 16 | ```sml 17 | infix 3 foo 18 | infix 0 bar 19 | infix quz 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/diagnostics/4020.md: -------------------------------------------------------------------------------- 1 | # 4020 2 | 3 | There was a declaration with a `sharing type`. 4 | 5 | ```sml 6 | val x = 3 sharing type t = u 7 | (** ^^^^^^^^^^^^^^^^^^ `sharing type` not allowed here *) 8 | ``` 9 | 10 | `sharing type` is allowed on specifications, not declarations. 11 | 12 | ## To fix 13 | 14 | Remove the `sharing type`. 15 | 16 | ```sml 17 | val x = 3 18 | ``` 19 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "extension", 6 | "type": "extensionHost", 7 | "request": "launch", 8 | "runtimeExecutable": "${execPath}", 9 | "args": ["--extensionDevelopmentPath=${workspaceFolder}/editors/vscode"], 10 | "preLaunchTask": "build-vscode-extension" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /crates/sml-scon/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sml-scon" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | doctest = false 13 | test = false 14 | 15 | [dependencies] 16 | num-bigint.workspace = true 17 | num-traits.workspace = true 18 | str-util.workspace = true 19 | -------------------------------------------------------------------------------- /docs/diagnostics/2005.md: -------------------------------------------------------------------------------- 1 | # 2005 2 | 3 | A `word` literal was negative. Words cannot be negative. 4 | 5 | 6 | 7 | ```sml 8 | val neg = ~0w123 9 | (** ^^^^^^ negative word literal *) 10 | ``` 11 | 12 | ## To fix 13 | 14 | Use a different type, like `int`, or remove the negative sign. 15 | 16 | ```sml 17 | val negInt = ~123 18 | val posWord = 0w123 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/diagnostics/4036.md: -------------------------------------------------------------------------------- 1 | # 4036 2 | 3 | The empty record/tuple type was written as `{}`, but it is usually written as `unit`. 4 | 5 | ```sml 6 | signature S = sig 7 | val get : {} -> int 8 | (** + usually written as `unit` *) 9 | end 10 | ``` 11 | 12 | ## To fix 13 | 14 | Replace `{}` with `unit`. 15 | 16 | ```sml 17 | signature S = sig 18 | val get : unit -> int 19 | end 20 | ``` 21 | -------------------------------------------------------------------------------- /crates/tests/src/incomplete.rs: -------------------------------------------------------------------------------- 1 | //! Incomplete tokens. 2 | 3 | use crate::check::check; 4 | 5 | #[test] 6 | fn num_lit() { 7 | check( 8 | r" 9 | val _ = 0x 10 | (** ^^ missing digits in number literal *) 11 | ", 12 | ); 13 | } 14 | 15 | #[test] 16 | fn ty_var() { 17 | check( 18 | r" 19 | datatype ' guh = no 20 | (** ^ incomplete type variable *) 21 | ", 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /docs/diagnostics/4003.md: -------------------------------------------------------------------------------- 1 | # 4003 2 | 3 | An integer (`int` or `word`) literal was invalid. This can happen when it is too large. 4 | 5 | ```sml 6 | val n = 0w123456789123456789123456789 7 | (** ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid literal: number too large to fit in target type *) 8 | ``` 9 | 10 | ## To fix 11 | 12 | Use smaller literals. 13 | 14 | ```sml 15 | val n = 0w123456789 16 | ``` 17 | -------------------------------------------------------------------------------- /editors/vscode/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": "src", 4 | "noEmit": true, 5 | "module": "CommonJS", 6 | "esModuleInterop": true, 7 | "strict": true, 8 | "noUnusedLocals": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "skipLibCheck": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtask" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [[bin]] 12 | name = "xtask" 13 | test = false 14 | 15 | [dependencies] 16 | tempfile = "3.8.0" 17 | 18 | anyhow.workspace = true 19 | flate2.workspace = true 20 | pico-args.workspace = true 21 | -------------------------------------------------------------------------------- /crates/sml-hir-lower/src/root.rs: -------------------------------------------------------------------------------- 1 | //! Lowering a whole program. 2 | 3 | use crate::util::{Lower, St}; 4 | use sml_syntax::ast; 5 | 6 | /// Does the conversion. 7 | #[must_use] 8 | pub fn get(lang: &config::lang::Language, file_kind: sml_file::Kind, root: &ast::Root) -> Lower { 9 | let mut st = St::new(lang, file_kind); 10 | let idx = crate::dec::get_top_dec(&mut st, root); 11 | st.finish(idx) 12 | } 13 | -------------------------------------------------------------------------------- /docs/diagnostics/1020.md: -------------------------------------------------------------------------------- 1 | # 1020 2 | 3 | A filesystem path had invalid UTF-8. 4 | 5 | In general, paths on most modern systems can be almost arbitrary byte sequences, save for treating path separators like `/` specially and disallowing NUL bytes. However, in various places in Millet, we depend on paths being valid UTF-8. 6 | 7 | ## To fix 8 | 9 | Rename the implicated file to a valid UTF-8 filename. 10 | -------------------------------------------------------------------------------- /crates/config/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Configuration. 2 | 3 | pub mod file; 4 | pub mod init; 5 | pub mod lang; 6 | pub mod tool; 7 | 8 | /// How many lines a diagnostic message may have. 9 | #[derive(Debug, Default, Clone, Copy)] 10 | pub enum DiagnosticLines { 11 | /// Error messages may not have newlines. 12 | #[default] 13 | One, 14 | /// Error messages may (or may not) have newlines. 15 | Many, 16 | } 17 | -------------------------------------------------------------------------------- /docs/diagnostics/1013.md: -------------------------------------------------------------------------------- 1 | # 1013 2 | 3 | In a SML/NJ CM file, there was a `source(path)` export whose path was not in the list of members (the bit after the `is`). 4 | 5 | ```text 6 | Library 7 | source(foo.sml) 8 | is 9 | bar.sml 10 | ``` 11 | 12 | ## To fix 13 | 14 | Include the path in the list of members. 15 | 16 | ```text 17 | Library 18 | source(foo.sml) 19 | is 20 | foo.sml 21 | bar.sml 22 | ``` 23 | -------------------------------------------------------------------------------- /docs/diagnostics/3002.md: -------------------------------------------------------------------------------- 1 | # 3002 2 | 3 | A name that was declared infix was used as non-infix without the required preceding `op` keyword. 4 | 5 | ```sml 6 | val _ = + (2, 3) 7 | (** ^ infix name used as non-infix without `op` *) 8 | ``` 9 | 10 | ## To fix 11 | 12 | Use the name infix, or add `op` to temporarily make the name non-infix. 13 | 14 | ```sml 15 | val _ = 2 + 3 16 | val _ = op+ (2, 3) 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/diagnostics/4001.md: -------------------------------------------------------------------------------- 1 | # 4001 2 | 3 | In a `fun` binding with multiple cases, the cases did not all name the same function. 4 | 5 | ```sml 6 | fun jonathan 1 = 2 7 | | dio _ = 3 8 | (** ^^^ expected a function clause for `jonathan`, found one for `dio` *) 9 | ``` 10 | 11 | ## To fix 12 | 13 | Use a consistent name for the function. 14 | 15 | ```sml 16 | fun jonathan 1 = 2 17 | | jonathan _ = 3 18 | ``` 19 | -------------------------------------------------------------------------------- /crates/config/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "config" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | test = false 13 | doctest = false 14 | 15 | [dependencies] 16 | fast-hash.workspace = true 17 | serde.workspace = true 18 | str-util.workspace = true 19 | 20 | sml-path.path = "../sml-path" 21 | -------------------------------------------------------------------------------- /docs/diagnostics/1021.md: -------------------------------------------------------------------------------- 1 | # 1021 2 | 3 | An SML path specified in the Millet config file had an empty component. 4 | 5 | SML paths should be a non-empty string of characters separated by `.`, and there should not be nothing between consecutive `.`. So a path like `Foo.Bar..Quz.flub` will trigger this error. The empty path will also trigger this error. 6 | 7 | ## To fix 8 | 9 | Avoid consecutive `.` or the empty string in paths. 10 | -------------------------------------------------------------------------------- /docs/diagnostics/3003.md: -------------------------------------------------------------------------------- 1 | # 3003 2 | 3 | A fixity declaration was invalid. 4 | 5 | This can happen when the fixity is too large. 6 | 7 | ```sml 8 | infix 123456789123456789 foo 9 | (** ^^^^^^^^^^^^^^^^^^ invalid fixity: number too large to fit in target type *) 10 | ``` 11 | 12 | ## To fix 13 | 14 | Only use small-ish fixities. 15 | 16 | Fixities larger than 16 are discouraged. 17 | 18 | ```sml 19 | infix 9 foo 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/diagnostics/1012.md: -------------------------------------------------------------------------------- 1 | # 1012 2 | 3 | In a `millet.toml` config file, a key of the `diagnostics` table was not a valid error code. 4 | 5 | A valid error code will be a positive integer. This will error: 6 | 7 | ```toml 8 | [diagnostics] 9 | foo.severity = "ignore" 10 | ``` 11 | 12 | ## To fix 13 | 14 | Make the key of the `diagnostics` table an error code. 15 | 16 | ```toml 17 | [diagnostics] 18 | 5034.severity = "ignore" 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/diagnostics/5036.md: -------------------------------------------------------------------------------- 1 | # 5036 2 | 3 | There was a `case` on a `bool` expression. 4 | 5 | ```sml 6 | fun mainCharacter old = 7 | case old of 8 | (** ^^^^^^^^^^^ `case` on a `bool` *) 9 | true => "porco rosso" 10 | | false => "nausicaa" 11 | ``` 12 | 13 | ## To fix 14 | 15 | Use `if` instead. 16 | 17 | ```sml 18 | fun mainCharacter old = 19 | if old then 20 | "porco rosso" 21 | else 22 | "nausicaa" 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/diagnostics/4017.md: -------------------------------------------------------------------------------- 1 | # 4017 2 | 3 | There was an unnecessary semicolon. 4 | 5 | ```sml 6 | val x = 3; 7 | (** ^ unnecessary `;` *) 8 | val y = "hi"; 9 | (** ^ unnecessary `;` *) 10 | ``` 11 | 12 | Semicolons are used in a REPL setting to indicate the end of input, but are unnecessary in most cases in source files. 13 | 14 | ## To fix 15 | 16 | Remove the semicolon. 17 | 18 | ```sml 19 | val x = 3 20 | val y = "hi" 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/diagnostics/4019.md: -------------------------------------------------------------------------------- 1 | # 4019 2 | 3 | There was a declaration or expression record row missing its right-hand side. 4 | 5 | ```sml 6 | val x : int 7 | (** ^^^^^^^ missing right-hand side of declaration *) 8 | ``` 9 | 10 | The above example is valid specification syntax for signatures, but is not a valid declaration. 11 | 12 | ## To fix 13 | 14 | Provide a right-hand side for the declaration. 15 | 16 | ```sml 17 | val x : int = 3 18 | ``` 19 | -------------------------------------------------------------------------------- /crates/tests/src/circularity.rs: -------------------------------------------------------------------------------- 1 | //! Circularity, aka the occurs check. 2 | 3 | use crate::check::check; 4 | 5 | #[test] 6 | fn return_self() { 7 | check( 8 | r" 9 | fun f _ = f 10 | (** ^^^^^^^^^^^ circular type: `?a` occurs in `_ -> ?a` *) 11 | ", 12 | ); 13 | } 14 | 15 | #[test] 16 | fn apply_self() { 17 | check( 18 | r" 19 | fun f x = x x 20 | (** ^ circular type: `?a` occurs in `?a -> ?b` *) 21 | ", 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /docs/diagnostics/4009.md: -------------------------------------------------------------------------------- 1 | # 4009 2 | 3 | There was an `open` or `include` without operands. 4 | 5 | ```sml 6 | signature S = sig 7 | include 8 | (** + requires at least 1 operand *) 9 | end 10 | ``` 11 | 12 | ## To fix 13 | 14 | Give the empty `open` or `include` some operands, or delete them. 15 | 16 | ```sml 17 | signature A = sig 18 | val x : int 19 | end 20 | 21 | signature S = sig 22 | include A 23 | val y : string 24 | end 25 | ``` 26 | -------------------------------------------------------------------------------- /docs/diagnostics/4023.md: -------------------------------------------------------------------------------- 1 | # 4023 2 | 3 | There was a non-specification declaration in a specification context. 4 | 5 | ```sml 6 | signature S = sig 7 | val y : int 8 | fun inc x = x + 1 9 | (** + non-specification not allowed here *) 10 | end 11 | ``` 12 | 13 | ## To fix 14 | 15 | Move the declaration out of the signature, or remove it. 16 | 17 | ```sml 18 | signature S = sig 19 | val y : int 20 | end 21 | 22 | fun inc x = x + 1 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/diagnostics/5027.md: -------------------------------------------------------------------------------- 1 | # 5027 2 | 3 | There was a type hole. 4 | 5 | ```sml 6 | type thing = ... list * int 7 | (** ^^^ type hole *) 8 | ``` 9 | 10 | Type holes can be either `...` or `_`. 11 | 12 | ```sml 13 | type func = int -> _ 14 | (** ^ type hole *) 15 | ``` 16 | 17 | ## To fix 18 | 19 | Replace the hole with a real type. 20 | 21 | ```sml 22 | type thing = string list * int 23 | type func = int -> bool 24 | ``` 25 | -------------------------------------------------------------------------------- /crates/sml-naive-fmt/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sml-naive-fmt" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | test = false 13 | doctest = false 14 | 15 | [dependencies] 16 | fast-hash.workspace = true 17 | text-size-util.workspace = true 18 | 19 | sml-comment.path = "../sml-comment" 20 | sml-syntax.path = "../sml-syntax" 21 | -------------------------------------------------------------------------------- /crates/sml-hir/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sml-hir" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | doctest = false 13 | test = false 14 | 15 | [dependencies] 16 | la-arena.workspace = true 17 | str-util.workspace = true 18 | 19 | sml-lab.path = "../sml-lab" 20 | sml-path.path = "../sml-path" 21 | sml-scon.path = "../sml-scon" 22 | -------------------------------------------------------------------------------- /crates/millet-ls/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "millet-ls" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [[bin]] 12 | name = "millet-ls" 13 | test = false 14 | 15 | [dependencies] 16 | anyhow.workspace = true 17 | env_logger.workspace = true 18 | log.workspace = true 19 | 20 | lang-srv.path = "../lang-srv" 21 | panic-hook.path = "../panic-hook" 22 | -------------------------------------------------------------------------------- /crates/sml-symbol-kind/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sml-symbol-kind" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | doctest = false 13 | test = false 14 | 15 | [dependencies] 16 | sml-namespace.path = "../sml-namespace" 17 | sml-statics-types.path = "../sml-statics-types" 18 | 19 | [features] 20 | sync = ["sml-statics-types/sync"] 21 | -------------------------------------------------------------------------------- /crates/tests/src/symbolic.rs: -------------------------------------------------------------------------------- 1 | //! Symbolic names. 2 | 3 | use crate::check::check; 4 | 5 | #[test] 6 | fn symbolic_structure() { 7 | check( 8 | r" 9 | structure % = struct end 10 | ", 11 | ); 12 | } 13 | 14 | #[test] 15 | fn symbolic_signature() { 16 | check( 17 | r" 18 | signature % = sig end 19 | ", 20 | ); 21 | } 22 | 23 | #[test] 24 | fn symbolic_functor() { 25 | check( 26 | r" 27 | functor % () = struct end 28 | ", 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /docs/diagnostics/1002.md: -------------------------------------------------------------------------------- 1 | # 1002 2 | 3 | **NOTE:** This error is not currently emitted. It may be in the future. 4 | 5 | A path wasn't contained in the root directory. 6 | 7 | Millet requires all the files to be analyzed be ultimately contained within a single, "root" directory. Millet thus refuses to analyze files outside this root. 8 | 9 | ## To fix 10 | 11 | Try one of the following: 12 | 13 | - Move the file into the root. 14 | - Do not reference the file. 15 | -------------------------------------------------------------------------------- /docs/diagnostics/5042.md: -------------------------------------------------------------------------------- 1 | # 5042 2 | 3 | A function literal (lambda) expression of the form `fn x => f x` can be simplified to `f`. 4 | 5 | ```sml 6 | val xs = List.map (fn n => Int.toString n) [1986, 8, 2] 7 | (** ^^^^^^^^^^^^^^^^^^^^^^ can be simplified *) 8 | ``` 9 | 10 | This is called eta reduction. 11 | 12 | ## To fix 13 | 14 | Replace the expression as suggested. 15 | 16 | ```sml 17 | val xs = List.map Int.toString [1986, 8, 2] 18 | ``` 19 | -------------------------------------------------------------------------------- /docs/diagnostics/2001.md: -------------------------------------------------------------------------------- 1 | # 2001 2 | 3 | There was an invalid character in the source file. 4 | 5 | 6 | 7 | ```sml 8 | val 空条承太郎 = 3 9 | (** ^ invalid source character *) 10 | ``` 11 | 12 | Only certain ASCII characters may appear in names and the like. 13 | 14 | ## To fix 15 | 16 | Use only allowed source characters. 17 | 18 | Only ASCII characters, but not all ASCII characters, are allowed. 19 | 20 | ```sml 21 | val kujoJotaro = 3 22 | ``` 23 | -------------------------------------------------------------------------------- /docs/diagnostics/4005.md: -------------------------------------------------------------------------------- 1 | # 4005 2 | 3 | A numeric label (as for a record) was invalid. This can happen when it was non-positive (i.e. negative or zero), or too large. 4 | 5 | ```sml 6 | val x = { 123456789123456789123456789 = "hi" } 7 | (** ^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid numeric label: number too large to fit in target type *) 8 | ``` 9 | 10 | ## To fix 11 | 12 | Use small positive numbers for labels. 13 | 14 | ```sml 15 | val x = { 3 = "hi" } 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/diagnostics/5037.md: -------------------------------------------------------------------------------- 1 | # 5037 2 | 3 | A function literal expression ("lambda") was applied to an argument. 4 | 5 | ```sml 6 | val _ = (fn x => x + 1) 3 7 | (** ^^^^^^^^^^^^^^^^^ applying a function literal to an argument *) 8 | ``` 9 | 10 | ## To fix 11 | 12 | "Inline" the literal, or use something like `let` or `case`. 13 | 14 | ```sml 15 | val inlined = 3 + 1 16 | 17 | val usingLet = 18 | let 19 | val x = 3 20 | in 21 | x + 1 22 | end 23 | ``` 24 | -------------------------------------------------------------------------------- /crates/sml-dynamics/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sml-dynamics" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | test = false 13 | doctest = false 14 | 15 | [dependencies] 16 | fast-hash.workspace = true 17 | fmt-util.workspace = true 18 | str-util.workspace = true 19 | 20 | sml-hir.path = "../sml-hir" 21 | sml-statics-types.path = "../sml-statics-types" 22 | -------------------------------------------------------------------------------- /crates/sml-syntax/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sml-syntax" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | test = false 13 | doctest = false 14 | 15 | [dependencies] 16 | rowan.workspace = true 17 | token.workspace = true 18 | 19 | [build-dependencies] 20 | code-h2-md-map.workspace = true 21 | fast-hash.workspace = true 22 | syntax-gen.workspace = true 23 | -------------------------------------------------------------------------------- /crates/tests/src/unused.rs: -------------------------------------------------------------------------------- 1 | //! Warnings about unused items. 2 | 3 | use crate::check::check_with_warnings; 4 | 5 | #[test] 6 | fn smoke() { 7 | check_with_warnings( 8 | r" 9 | fun f x = () 10 | (** ^ unused value: `x` *) 11 | ", 12 | ); 13 | } 14 | 15 | #[test] 16 | fn or_both_used() { 17 | check_with_warnings( 18 | r" 19 | datatype t = A of int | B of int 20 | 21 | fun toInt (x : t) : int = 22 | let val (A y | B y) = x 23 | in y end 24 | ", 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /docs/diagnostics/5999.md: -------------------------------------------------------------------------------- 1 | # 5999 2 | 3 | There was an occurrence of an unsupported SML construct. 4 | 5 | ```sml 6 | abstype t = T with val _ = 3 end 7 | (** + unsupported: `abstype` declarations *) 8 | ``` 9 | 10 | At time of writing, Millet does not support `abstype` declarations. 11 | 12 | ## To fix 13 | 14 | Avoid unsupported constructs. 15 | 16 | `abstype` is not used much in modern SML. It can often be replaced with a combination of `datatype`, `structure`, and `signature`. 17 | -------------------------------------------------------------------------------- /docs/diagnostics/1022.md: -------------------------------------------------------------------------------- 1 | # 1022 2 | 3 | A `Library` CM file had an empty export list. 4 | 5 | ``` 6 | Library is 7 | a.sml 8 | b.cm 9 | ``` 10 | 11 | ## To fix 12 | 13 | Either define exports between `Library` and `is`: 14 | 15 | ``` 16 | Library 17 | structure A 18 | signature B 19 | is 20 | a.sml 21 | b.cm 22 | ``` 23 | 24 | Or use a `Group`: 25 | 26 | ``` 27 | Group is 28 | a.sml 29 | b.cm 30 | ``` 31 | 32 | A `Group` with no listed exports exports everything. 33 | -------------------------------------------------------------------------------- /docs/diagnostics/4022.md: -------------------------------------------------------------------------------- 1 | # 4022 2 | 3 | There was a declaration hole. 4 | 5 | ```sml 6 | structure S = struct ... end 7 | (** ^^^ declaration hole *) 8 | ``` 9 | 10 | Declaration holes are written `...`. 11 | 12 | Sometimes `...` is used in declaration position as a placeholder or filler in examples. 13 | 14 | ## To fix 15 | 16 | Replace or remove the hole. 17 | 18 | ```sml 19 | structure Replaced = struct val x = 3 end 20 | structure Removed = struct end 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/diagnostics/3001.md: -------------------------------------------------------------------------------- 1 | # 3001 2 | 3 | A name that was not declared infix was used as infix. 4 | 5 | 6 | 7 | ```sml 8 | datatype t = C of int * int 9 | fun bad (a C b) = a + b 10 | (** ^ non-infix name used as infix *) 11 | ``` 12 | 13 | ## To fix 14 | 15 | Use the name as non-infix, or declare the name as infix. 16 | 17 | ```sml 18 | datatype t = C of int * int 19 | fun useAsNonInfix (C (a, b)) = a + b 20 | infix C 21 | fun useAsInfix (a C b) = a + b 22 | ``` 23 | -------------------------------------------------------------------------------- /crates/mlb-hir/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mlb-hir" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | test = false 13 | doctest = false 14 | 15 | [dependencies] 16 | fast-hash.workspace = true 17 | paths.workspace = true 18 | str-util.workspace = true 19 | text-size-util.workspace = true 20 | 21 | sml-file.path = "../sml-file" 22 | sml-namespace.path = "../sml-namespace" 23 | -------------------------------------------------------------------------------- /crates/sml-lex/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sml-lex" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | test = false 13 | doctest = false 14 | 15 | [dependencies] 16 | diagnostic.workspace = true 17 | text-size-util.workspace = true 18 | token.workspace = true 19 | 20 | cov-mark.path = "../cov-mark" 21 | lex-util.path = "../lex-util" 22 | sml-syntax.path = "../sml-syntax" 23 | -------------------------------------------------------------------------------- /crates/cm-syntax/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cm-syntax" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | test = false 13 | doctest = false 14 | 15 | [dependencies] 16 | paths.workspace = true 17 | str-util.workspace = true 18 | text-size-util.workspace = true 19 | 20 | lex-util.path = "../lex-util" 21 | slash-var-path.path = "../slash-var-path" 22 | sml-file.path = "../sml-file" 23 | -------------------------------------------------------------------------------- /docs/diagnostics/4008.md: -------------------------------------------------------------------------------- 1 | # 4008 2 | 3 | **NOTE:** This diagnostic is no longer emitted. 4 | 5 | There was a bar (aka `|`) before the first: 6 | 7 | - Case in a `fun` declaration. 8 | - Case in a `fn`, `case`, or `handle` expression. 9 | - Constructor in a `datatype` declaration or case. 10 | 11 | ```sml 12 | datatype d = 13 | | Chihiro 14 | (** + preceding `|` *) 15 | | Sheeta 16 | ``` 17 | 18 | ## To fix 19 | 20 | Remove the first bar. 21 | 22 | ```sml 23 | datatype d = 24 | Chihiro 25 | | Sheeta 26 | ``` 27 | -------------------------------------------------------------------------------- /editors/vscode/languages/mlb/snippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "`let` exp": { 3 | "prefix": "let", 4 | "body": ["let", " ${1:...}", "in", " ${2:...}", "end"] 5 | }, 6 | "`bas` exp": { 7 | "prefix": "bas", 8 | "body": ["bas", " ${1:...}", "end"] 9 | }, 10 | "`local` dec": { 11 | "prefix": "local", 12 | "body": ["local", " ${1:...}", "in", " ${2:...}", "end"] 13 | }, 14 | "`ann` dec": { 15 | "prefix": "ann", 16 | "body": ["ann \"${1:...}\" in", " ${2:...}", "end"] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /crates/input/src/types.rs: -------------------------------------------------------------------------------- 1 | //! Pervasive types. 2 | 3 | use fast_hash::FxHashMap; 4 | 5 | /// A mapping to override diagnostic severity. 6 | pub type Severities = FxHashMap>; 7 | 8 | /// A description of how to check a group of source files. 9 | #[derive(Debug)] 10 | pub struct Group { 11 | /// A lowered `BasDec`, describing the group. 12 | pub bas_dec: mlb_hir::BasDec, 13 | /// A position DB for the group file that yielded the dec. 14 | pub pos_db: text_pos::PositionDb, 15 | } 16 | -------------------------------------------------------------------------------- /docs/diagnostics/4024.md: -------------------------------------------------------------------------------- 1 | # 4024 2 | 3 | The left-hand side of an `as` pattern was neither a name nor a typed name. 4 | 5 | ```sml 6 | fun f x = 7 | case x of 8 | 3 as y => y 9 | (** ^ left-hand side of `as` pattern must be a name *) 10 | | _ => x 11 | ``` 12 | 13 | The syntax for `as` patterns is ` (: )? as `. 14 | 15 | ## To fix 16 | 17 | Ensure the left hand side of the `as` is either a name or a typed name. 18 | 19 | ```sml 20 | fun f x = 21 | case x of 22 | y as 3 => y 23 | | _ => x 24 | ``` 25 | -------------------------------------------------------------------------------- /crates/tests/src/use_builtin.rs: -------------------------------------------------------------------------------- 1 | //! The `use` builtin. 2 | 3 | use crate::check::check_with_warnings; 4 | 5 | #[test] 6 | fn literal() { 7 | check_with_warnings( 8 | r#" 9 | val () = use "foo.sml" 10 | (** ^^^^^^^^^^^^^ no additional definitions from "foo.sml" brought into scope *) 11 | "#, 12 | ); 13 | } 14 | 15 | #[test] 16 | fn non_literal() { 17 | check_with_warnings( 18 | r#" 19 | val s = "bar.sml" 20 | val () = use s 21 | (** ^^^^^ no additional definitions brought into scope *) 22 | "#, 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /docs/diagnostics/2002.md: -------------------------------------------------------------------------------- 1 | # 2002 2 | 3 | There was an unclosed comment. This means an open comment delimiter `(*` was not matched by a later close comment delimiter `*)`. 4 | 5 | 6 | 7 | ```sml 8 | val kujo = 3 9 | (* a comment that doesn't end 10 | val josuke = 4 11 | ``` 12 | 13 | ## To fix 14 | 15 | Close the comment with `*)`. 16 | 17 | Note that comments may be nested. 18 | 19 | ```sml 20 | val kujo = 3 21 | (* a comment that ends *) 22 | val josuke = 4 23 | ``` 24 | -------------------------------------------------------------------------------- /crates/panic-hook/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A panic hook with good backtraces and an exhortation to file a bug report. 2 | 3 | const VERSION: &str = env!("CARGO_PKG_VERSION"); 4 | const ISSUES_URL: &str = "https://github.com/azdavis/millet/issues"; 5 | 6 | /// Installs the hook. Call this at the beginning of `fn main()`. 7 | pub fn install() { 8 | let msg = format!("Millet ({VERSION}) crashed. We would appreciate a bug report: {ISSUES_URL}"); 9 | better_panic::Settings::new().message(msg).verbosity(better_panic::Verbosity::Medium).install(); 10 | } 11 | -------------------------------------------------------------------------------- /docs/diagnostics/4034.md: -------------------------------------------------------------------------------- 1 | # 4034 2 | 3 | There was a `local` declaration without declarations in the `local ... in`. 4 | 5 | ```sml 6 | local 7 | (** + overly complex *) 8 | in 9 | val x = 3 10 | val y = 4 11 | end 12 | ``` 13 | 14 | ## To fix 15 | 16 | Replace the `local` declaration with only the `in ... end` declarations. 17 | 18 | ```sml 19 | val x = 3 20 | val y = 4 21 | ``` 22 | 23 | Or, add declarations to the `local ... in`. 24 | 25 | ```sml 26 | local 27 | val z = 2 28 | in 29 | val x = z + 1 30 | val y = z * z 31 | end 32 | ``` 33 | -------------------------------------------------------------------------------- /docs/diagnostics/5031.md: -------------------------------------------------------------------------------- 1 | # 5031 2 | 3 | A `sharing type` was invalid. 4 | 5 | 6 | 7 | ```sml 8 | signature SIG = sig 9 | type a = int 10 | (** + cannot share type `a` as `int` *) 11 | type b = int 12 | sharing type a = b 13 | end 14 | ``` 15 | 16 | Here, types `a` and `b` are already defined as type aliases, so they cannot be shared with `sharing type`. It is immaterial that `a` and `b` happen to be defined to be the same type. 17 | 18 | ## To fix 19 | 20 | Make sure the types being shared are not already defined. 21 | -------------------------------------------------------------------------------- /crates/mlb-syntax/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mlb-syntax" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | test = false 13 | doctest = false 14 | 15 | [dependencies] 16 | paths.workspace = true 17 | str-util.workspace = true 18 | text-size-util.workspace = true 19 | 20 | lex-util.path = "../lex-util" 21 | slash-var-path.path = "../slash-var-path" 22 | sml-file.path = "../sml-file" 23 | sml-namespace.path = "../sml-namespace" 24 | -------------------------------------------------------------------------------- /editors/vscode/languages/sml/language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "blockComment": ["(*", "*)"] 4 | }, 5 | "brackets": [ 6 | ["(", ")"], 7 | ["[", "]"], 8 | ["{", "}"] 9 | ], 10 | "autoClosingPairs": [ 11 | { "open": "(", "close": ")" }, 12 | { "open": "[", "close": "]" }, 13 | { "open": "{", "close": "}" }, 14 | { "open": "\"", "close": "\"", "notIn": ["string"] } 15 | ], 16 | "surroundingPairs": [ 17 | ["(", ")"], 18 | ["[", "]"], 19 | ["{", "}"], 20 | ["\"", "\""] 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /crates/tests/src/cannot_rebind.rs: -------------------------------------------------------------------------------- 1 | //! Errors about re-binding special names. 2 | 3 | use crate::check::check; 4 | 5 | #[test] 6 | fn apply_eq() { 7 | check( 8 | r" 9 | fun apply_eq (op =) a b = a = b 10 | (** + cannot re-bind name: `=` *) 11 | ", 12 | ); 13 | } 14 | 15 | #[test] 16 | fn ref_ctor() { 17 | check( 18 | r" 19 | datatype no = ref 20 | (** + cannot re-bind name: `ref` *) 21 | ", 22 | ); 23 | } 24 | 25 | #[test] 26 | fn val_it() { 27 | check( 28 | r" 29 | val it = 3 30 | (** + cannot re-bind name: `it` *) 31 | ", 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /crates/sml-parse/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sml-parse" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | doctest = false 13 | test = false 14 | 15 | [dependencies] 16 | diagnostic.workspace = true 17 | event-parse.workspace = true 18 | fast-hash.workspace = true 19 | str-util.workspace = true 20 | text-size-util.workspace = true 21 | token.workspace = true 22 | 23 | sml-fixity.path = "../sml-fixity" 24 | sml-syntax.path = "../sml-syntax" 25 | -------------------------------------------------------------------------------- /docs/diagnostics/4016.md: -------------------------------------------------------------------------------- 1 | # 4016 2 | 3 | There was a `case` expression with only one arm. 4 | 5 | ```sml 6 | datatype d = D of int 7 | fun toInt x = case x of D y => y 8 | (** ^^^^^^^^^ `case` with only one arm *) 9 | ``` 10 | 11 | - An exhaustive case with only one arm is better expressed as an irrefutable binding, e.g. with `val`. 12 | - A non-exhaustive case with only one arm should be made exhaustive. 13 | 14 | ## To fix 15 | 16 | Rewrite the `case` as something else, or add more arms. 17 | 18 | ```sml 19 | datatype d = D of int 20 | fun toInt (D y) = y 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/diagnostics/5003.md: -------------------------------------------------------------------------------- 1 | # 5003 2 | 3 | Something was requested by a signature, but not present in the structure that is attempting to ascribe to that signature. 4 | 5 | ```sml 6 | signature SIG = sig 7 | val x : int 8 | val y : string 9 | end 10 | 11 | structure Str : SIG = struct 12 | (** ^^^^^^ missing value required by signature: `x` *) 13 | val y = "oops" 14 | end 15 | ``` 16 | 17 | ## To fix 18 | 19 | Provide definitions for the missing items. 20 | 21 | See also [5034](./5034.md) for a particular case in which this error may confusingly appear. 22 | -------------------------------------------------------------------------------- /crates/mlb-syntax/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Processing ML Basis files. 2 | //! 3 | //! From [the `MLton` docs](http://mlton.org/MLBasis). 4 | 5 | mod lex; 6 | mod parse; 7 | mod types; 8 | 9 | pub use types::{BasDec, BasExp, Error, NamesSeq, ParsedPath, PathKind, Result}; 10 | 11 | /// Process the contents of a ML Basis file. 12 | /// 13 | /// # Errors 14 | /// 15 | /// If the contents of the file were invalid, or if the env doesn't define all the path vars. 16 | pub fn get(s: &str, env: &slash_var_path::Env) -> Result { 17 | let tokens = lex::get(s)?; 18 | parse::get(&tokens, env) 19 | } 20 | -------------------------------------------------------------------------------- /docs/diagnostics/2006.md: -------------------------------------------------------------------------------- 1 | # 2006 2 | 3 | A `char` literal contained more (or less) than 1 byte. 4 | 5 | ```sml 6 | val tooBig = #"hello there" 7 | (** ^^^^^^^^^^^^^^ character literal must have length 1 *) 8 | val tooSmall = #"" 9 | (** ^^^ character literal must have length 1 *) 10 | val nonAscii = #"あ" 11 | (** ^^^^ character literal must have length 1 *) 12 | ``` 13 | 14 | ## To fix 15 | 16 | Make the character literal 1 byte long, or use a string literal. 17 | 18 | ```sml 19 | val justRight = #"h" 20 | val greeting = "hello there" 21 | val empty = "" 22 | ``` 23 | -------------------------------------------------------------------------------- /docs/diagnostics/1018.md: -------------------------------------------------------------------------------- 1 | # 1018 2 | 3 | Millet couldn't initialize the workspace root. 4 | 5 | When the Millet language server starts up, the client. i.e. the editor, e.g. VS Code, sends the server an initialization message containing a file URL of the currently open folder, if there is one. This is the workspace root URL. 6 | 7 | Millet will attempt to parse this URL into a real directory and process the files inside it. If parsing the URL or opening the directory fails, Millet may emit this error. 8 | 9 | ## To fix 10 | 11 | Inspect the workspace root URL/underlying error message for more details. 12 | -------------------------------------------------------------------------------- /docs/diagnostics/4029.md: -------------------------------------------------------------------------------- 1 | # 4029 2 | 3 | There was an occurrence of a disallowed language construct. 4 | 5 | The language construct may have been explicitly disallowed in `millet.toml`, or it may have been implicitly disallowed by default (for some Successor ML features). 6 | 7 | ## To fix 8 | 9 | Either allow the thing in `millet.toml`, or remove the occurrence of the thing from the code. 10 | 11 | Consult [the manual](/docs/manual.md#language) to see how to edit your `millet.toml` to allow the item. 12 | 13 | Some constructs may not be allowable because support for them in Millet is not implemented. 14 | -------------------------------------------------------------------------------- /docs/diagnostics/5002.md: -------------------------------------------------------------------------------- 1 | # 5002 2 | 3 | There was a duplicate of something. 4 | 5 | This may occur when using `and` to declare many things at once. 6 | 7 | ```sml 8 | val x = 3 9 | and x = 4 10 | (** ^ duplicate value: `x` *) 11 | ``` 12 | 13 | It may also occur when binding the same name more than once in a pattern. 14 | 15 | 16 | 17 | ```sml 18 | fun add (x, x) = x + x 19 | (** ^ duplicate value: `x` *) 20 | ``` 21 | 22 | ## To fix 23 | 24 | Use different names, or avoid `and`. (The latter induces shadowing.) 25 | 26 | ```sml 27 | val x = 3 28 | val x = 4 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/diagnostics/4030.md: -------------------------------------------------------------------------------- 1 | # 4030 2 | 3 | There was an `open` at the top level. 4 | 5 | ```sml 6 | open List 7 | (** + top-level `open` *) 8 | val xs = filter (fn x => x > 10) [1988, 4, 16] 9 | ``` 10 | 11 | This is not allowed when using SML/NJ Compilation Manager (CM), and is generally discouraged anyway. 12 | 13 | ## To fix 14 | 15 | Contain the scope of the `open`, or avoid use of `open` altogether and use qualified names. 16 | 17 | ```sml 18 | local 19 | open List 20 | in 21 | val xs = filter (fn x => x > 10) [1988, 4, 16] 22 | end 23 | 24 | val ys = List.filter (fn x => x < 10) [1988, 4, 16] 25 | ``` 26 | -------------------------------------------------------------------------------- /docs/diagnostics/4033.md: -------------------------------------------------------------------------------- 1 | # 4033 2 | 3 | There was a `let` expression without declarations in the `let ... in`. 4 | 5 | ```sml 6 | val x = 4 * let in 2 + 3 end 7 | (** ^^^ overly complex *) 8 | ``` 9 | 10 | ## To fix 11 | 12 | Replace the `let` expression with its final expression, possibly wrapped in parentheses if there were multiple expressions in the sequence or if parentheses are otherwise needed for parsing precedence reasons. 13 | 14 | ```sml 15 | val x = 4 * (2 + 3) 16 | ``` 17 | 18 | Or, add declarations in the `let ... in`. 19 | 20 | ```sml 21 | val y = 4 * let val z = 2 in z + 3 end 22 | ``` 23 | -------------------------------------------------------------------------------- /docs/diagnostics/5016.md: -------------------------------------------------------------------------------- 1 | # 5016 2 | 3 | An invalid name was used as the left hand side of an `as` pattern. 4 | 5 | As-patterns allow binding the entirety of a pattern `p` to a name `n` with the pattern `n as p`. However, the name `n` may not, for instance, already exist as a non-value: 6 | 7 | ```sml 8 | exception e 9 | fun f x = 10 | case x of 11 | e as [_] => 1 :: e 12 | (** ^^^^^^^^ invalid `as` pat name: `e` *) 13 | | _ => [] 14 | ``` 15 | 16 | ## To fix 17 | 18 | Use a valid name. 19 | 20 | ```sml 21 | exception e 22 | fun f x = 23 | case x of 24 | y as [_] => 1 :: y 25 | | _ => [] 26 | ``` 27 | -------------------------------------------------------------------------------- /crates/lang-srv/src/state.rs: -------------------------------------------------------------------------------- 1 | //! The main mutable state of the language server. 2 | 3 | use crate::cx::Cx; 4 | use fast_hash::FxHashSet; 5 | use lsp_types::Url; 6 | 7 | pub(crate) enum Mode { 8 | /// We have a workspace root. 9 | Root(Box), 10 | /// We have no workspace root. 11 | NoRoot, 12 | } 13 | 14 | pub(crate) struct Root { 15 | pub(crate) path: paths::CleanPathBuf, 16 | pub(crate) input: input::Input, 17 | } 18 | 19 | pub struct St { 20 | pub(crate) mode: Mode, 21 | pub(crate) cx: Cx, 22 | pub(crate) analysis: analysis::Analysis, 23 | pub(crate) has_diagnostics: FxHashSet, 24 | } 25 | -------------------------------------------------------------------------------- /docs/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security 2 | 3 | Millet performs I/O to read arbitrary user input and, in the case of formatting **only**, also overwrite user files. 4 | 5 | When using smlfmt as the formatting engine, it searches for a binary called `smlfmt` in your PATH and executes it. 6 | 7 | Millet should never access the network. 8 | 9 | A user could probably "DOS" themselves by asking Millet to analyze a massive or complicated set of files, but we don't really consider this to be a security threat. 10 | 11 | That being said, if there is a security issue with Millet, let us know at: 12 | 13 | ariel DOT z DOT davis AT icloud DOT com 14 | -------------------------------------------------------------------------------- /docs/diagnostics/5019.md: -------------------------------------------------------------------------------- 1 | # 5019 2 | 3 | The wrong number of type arguments was passed to a type-level function. 4 | 5 | ```sml 6 | type ('a, 'b) pair = 'a * 'b 7 | type nope = int pair 8 | (** ^^^^^^^^ expected 2 type arguments, found 1 *) 9 | ``` 10 | 11 | `datatype`s, like `'a list`, also define type-level functions. 12 | 13 | ```sml 14 | val xs : list = [] 15 | (** ^^^^ expected 1 type argument, found 0 *) 16 | ``` 17 | 18 | ## To fix 19 | 20 | Pass the correct number of type arguments. 21 | 22 | ```sml 23 | type ('a, 'b) pair = 'a * 'b 24 | type yep = (int, string) pair 25 | val xs : yep list = [] 26 | ``` 27 | -------------------------------------------------------------------------------- /docs/diagnostics/5010.md: -------------------------------------------------------------------------------- 1 | # 5010 2 | 3 | A pattern in a `case` expression or similar (like `handle`) was not reachable. 4 | 5 | Patterns in a `case` are tried from top to bottom. If a pattern further up the `case` always matches certain values, then the lower pattern will never be reached. Thus, the lower pattern is unreachable. 6 | 7 | ```sml 8 | fun f x = 9 | case x of 10 | 1 => 2 11 | | 1 => 3 12 | (** ^ unreachable pattern *) 13 | | _ => 4 14 | ``` 15 | 16 | ## To fix 17 | 18 | Try: 19 | 20 | - Making the higher pattern different or more specific, so the lower pattern may be reached. 21 | - Removing the lower pattern. 22 | -------------------------------------------------------------------------------- /crates/lex-util/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Utilities for lexing SML-adjacent languages. 2 | 3 | pub mod block_comment; 4 | pub mod string; 5 | 6 | /// Returns whether `b` is a whitespace character for our purposes. 7 | #[must_use] 8 | pub fn is_whitespace(b: u8) -> bool { 9 | b.is_ascii_whitespace() || b == 0xb 10 | } 11 | 12 | /// Advances `idx` until `bs` is out of bytes or `p` no longer holds. 13 | pub fn advance_while

(idx: &mut usize, bs: &[u8], p: P) 14 | where 15 | P: Fn(u8) -> bool, 16 | { 17 | while let Some(&b) = bs.get(*idx) { 18 | if p(b) { 19 | *idx += 1; 20 | } else { 21 | break; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /crates/sml-file-syntax/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sml-file-syntax" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | test = false 13 | doctest = false 14 | 15 | [dependencies] 16 | elapsed.workspace = true 17 | text-pos.workspace = true 18 | 19 | config.path = "../config" 20 | sml-file.path = "../sml-file" 21 | sml-fixity.path = "../sml-fixity" 22 | sml-hir-lower.path = "../sml-hir-lower" 23 | sml-lex.path = "../sml-lex" 24 | sml-parse.path = "../sml-parse" 25 | sml-ty-var-scope.path = "../sml-ty-var-scope" 26 | -------------------------------------------------------------------------------- /docs/diagnostics/5004.md: -------------------------------------------------------------------------------- 1 | # 5004 2 | 3 | Something was not requested by a signature, but was present in the structure that is attempting to ascribe to that signature. 4 | 5 | Usually, this is allowed, but it is forbidden for `datatype` declarations. 6 | 7 | ```sml 8 | structure S 9 | : sig datatype d = Pazu end 10 | = struct datatype d = Pazu | Sosuke end 11 | (** ^^^^^^ extra value not present in signature: `Sosuke` *) 12 | ``` 13 | 14 | ## To fix 15 | 16 | Ensure only the requested items are defined. 17 | 18 | ```sml 19 | structure S 20 | : sig datatype d = Pazu end 21 | = struct datatype d = Pazu end 22 | ``` 23 | -------------------------------------------------------------------------------- /docs/diagnostics/5007.md: -------------------------------------------------------------------------------- 1 | # 5007 2 | 3 | **NOTE**: This diagnostic is no longer emitted. 4 | 5 | A function application expression was encountered, but the function expression did not have a function type. 6 | 7 | 8 | 9 | ```sml 10 | val _ = "foo" 3 11 | (** ^^^^^ expected a function type, found string *) 12 | ``` 13 | 14 | In this example, we attempt to treat the string `"foo"` as a function and apply it to the argument `3`. 15 | 16 | This error is a special case of 5006, specialized for the common case of function application. 17 | 18 | ## To fix 19 | 20 | Only apply functions to arguments. 21 | -------------------------------------------------------------------------------- /docs/diagnostics/2004.md: -------------------------------------------------------------------------------- 1 | # 2004 2 | 3 | A `string` literal was not closed. String literals start and end with `"`. 4 | 5 | 6 | 7 | ```sml 8 | val greeting = "hello there 9 | (** ^ unclosed string literal *) 10 | ``` 11 | 12 | ## To fix 13 | 14 | Close the string literal with `"`. 15 | 16 | ```sml 17 | val greeting = "hello there" 18 | ``` 19 | 20 | This error may occur when trying to embed `"` in a string literal. To embed `"` in a string literal, use `\"`. 21 | 22 | ```sml 23 | val greeting = "he jumped down and said \"hello there\" to the general." 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/diagnostics/5030.md: -------------------------------------------------------------------------------- 1 | # 5030 2 | 3 | A type variable bound at a `val` or `fun` declaration was used in the right-hand side of a `type` or `datatype` declaration. 4 | 5 | 6 | 7 | ```sml 8 | fun 'a foo (x : 'a) = 9 | let 10 | type t = 'a * 'a 11 | (** ^^ type variable bound at `val` or `fun` not allowed here *) 12 | in 13 | (x, x) : t 14 | end 15 | ``` 16 | 17 | ## To fix 18 | 19 | Bind the type variable at the `type` or `datatype`, or remove it from the right-hand side. 20 | 21 | ```sml 22 | fun 'a foo (x : 'a) = 23 | let 24 | type 'b t = 'b * 'b 25 | in 26 | (x, x) : 'a t 27 | end 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/diagnostics/3005.md: -------------------------------------------------------------------------------- 1 | # 3005 2 | 3 | Consecutive infix names with the same fixity, but different associativity, were used without parentheses to disambiguate. 4 | 5 | ```sml 6 | infix << 7 | infixr >> 8 | fun a << b = a + b 9 | fun a >> b = a * b 10 | val _ = 1 << 2 >> 3 11 | (** ^^ consecutive infix names with same fixity but different associativity *) 12 | ``` 13 | 14 | It's not clear if this should be parsed as `(1 << 2) >> 3` or `1 << (2 >> 3)`. 15 | 16 | ## To fix 17 | 18 | Do one of the following: 19 | 20 | - Add parentheses to disambiguate. 21 | - Use `op` to disambiguate. 22 | - Use different fixities. 23 | - Use the same associativity. 24 | -------------------------------------------------------------------------------- /docs/diagnostics/4013.md: -------------------------------------------------------------------------------- 1 | # 4013 2 | 3 | A specification used syntax only available in declarations. 4 | 5 | ```sml 6 | signature MAPPABLE = sig 7 | type 'a t 8 | val ('a, 'b) map : ('a -> 'b) -> ('a t -> 'b t) 9 | (** ^^^^^^^^ specification uses declaration syntax not allowed here *) 10 | end 11 | ``` 12 | 13 | Some examples of declaration-only syntax: 14 | 15 | - Explicit type variable sequences, as above. 16 | - `op` and `rec`. 17 | - Patterns other than `name : ty`. 18 | 19 | ## To fix 20 | 21 | Remove the disallowed syntax. 22 | 23 | ```sml 24 | signature MAPPABLE = sig 25 | type 'a t 26 | val map : ('a -> 'b) -> ('a t -> 'b t) 27 | end 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/diagnostics/5032.md: -------------------------------------------------------------------------------- 1 | # 5032 2 | 3 | A `where type` was invalid. 4 | 5 | ```sml 6 | signature BAD = sig 7 | (** + cannot realize type `t` as `int` *) 8 | type t 9 | end 10 | where type t = int 11 | where type t = string 12 | ``` 13 | 14 | This error also arises when using `type a = b` in signatures, because that is syntactic sugar for a usage of `where type`. 15 | 16 | ```sml 17 | signature BAD = sig 18 | (** + cannot realize type `t` as `int` *) 19 | type t = int 20 | end 21 | where type t = string 22 | ``` 23 | 24 | ## To fix 25 | 26 | Make sure types are not being `where type`'d multiple types, as in the above examples. 27 | -------------------------------------------------------------------------------- /docs/diagnostics/3006.md: -------------------------------------------------------------------------------- 1 | # 3006 2 | 3 | The parser expected something, but it didn't find it. For example, in this case, the parser expected a name after the `structure` keyword, but found the `val` keyword instead. 4 | 5 | ```sml 6 | structure val platinum = 3 7 | (** ^^^ expected a name *) 8 | ``` 9 | 10 | This is probably the most common kind of parse error. 11 | 12 | ## To fix 13 | 14 | One bit of advice is this: Since the parser tries to continue parsing a file even in the face of errors, it may find further errors after the first one. But these errors may be all ultimately because of that first error. So, try looking at the first error in the file first. 15 | -------------------------------------------------------------------------------- /crates/sml-hir-lower/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sml-hir-lower" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | doctest = false 13 | test = false 14 | 15 | [dependencies] 16 | diagnostic.workspace = true 17 | fast-hash.workspace = true 18 | str-util.workspace = true 19 | text-size-util.workspace = true 20 | 21 | config.path = "../config" 22 | cov-mark.path = "../cov-mark" 23 | lex-util.path = "../lex-util" 24 | sml-file.path = "../sml-file" 25 | sml-hir.path = "../sml-hir" 26 | sml-path.path = "../sml-path" 27 | sml-syntax.path = "../sml-syntax" 28 | -------------------------------------------------------------------------------- /docs/diagnostics/2003.md: -------------------------------------------------------------------------------- 1 | # 2003 2 | 3 | A type variable name was incomplete. 4 | 5 | ```sml 6 | val xs : ' list = [] 7 | (** ^ incomplete type variable *) 8 | ``` 9 | 10 | To be complete, a type variable name must be the following in sequence: 11 | 12 | 1. One or more ticks: `'` 13 | 1. An alphabetic character: `A-Z` or `a-z` 14 | 1. Zero or more alphanumeric characters or ticks: `A-Z`, `a-z`, `0-9`, or `'` 15 | 16 | ## To fix 17 | 18 | Use a valid type variable name. 19 | 20 | Type variable names are usually one letter and start with `'a`, `'b`, etc, but they can be as long as you would like. 21 | 22 | ```sml 23 | val xs : 'a list = [] 24 | val xs : 'element list = [] 25 | ``` 26 | -------------------------------------------------------------------------------- /crates/sml-symbol-kind/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Given a `ValInfo`, return the kind of symbol this val is. 2 | 3 | use sml_statics_types::info::{IdStatus, ValInfo}; 4 | use sml_statics_types::ty::{TyData, Tys}; 5 | 6 | /// Gets the symbol kind. 7 | #[must_use] 8 | pub fn get(tys: &Tys, val_info: &ValInfo) -> sml_namespace::SymbolKind { 9 | match val_info.id_status { 10 | IdStatus::Con => sml_namespace::SymbolKind::Constructor, 11 | IdStatus::Exn(_) => sml_namespace::SymbolKind::Exception, 12 | IdStatus::Val => match tys.data(val_info.ty_scheme.ty) { 13 | TyData::Fn(_) => sml_namespace::SymbolKind::Function, 14 | _ => sml_namespace::SymbolKind::Value, 15 | }, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /docs/diagnostics/5041.md: -------------------------------------------------------------------------------- 1 | # 5041 2 | 3 | An item was disallowed in the Millet config file. 4 | 5 | For instance, given this Millet config file: 6 | 7 | ```toml 8 | version = 1 9 | [language.val] 10 | "List.tabulate" = false 11 | ``` 12 | 13 | 14 | 15 | The following SML file will error: 16 | 17 | ```sml 18 | val tab = List.tabulate 19 | (** ^^^^^^^^^^^^^ disallowed *) 20 | ``` 21 | 22 | This will also error: 23 | 24 | 25 | 26 | ```sml 27 | local 28 | open List 29 | in 30 | val tab = tabulate 31 | (** ^^^^^^^^ disallowed *) 32 | end 33 | ``` 34 | 35 | ## To fix 36 | 37 | Do not reference disallowed items. 38 | -------------------------------------------------------------------------------- /docs/diagnostics/4031.md: -------------------------------------------------------------------------------- 1 | # 4031 2 | 3 | There was an occurrence of `:>` in a context where only `:` is expected. 4 | 5 | ```sml 6 | val x :> int = 3 7 | (** ^^ not allowed here *) 8 | ``` 9 | 10 | `:>` is for opaque ascription, which only applies to structures and signatures in certain contexts. `:` is for both transparent ascription as well as type annotation. 11 | 12 | Functor arguments can only be transparently ascribed, and values and patterns can only be annotated with types, not be ascribed opaquely (or transparently, for that matter). Thus, in those contexts, only `:` is appropriate, not `:>`. 13 | 14 | ## To fix 15 | 16 | Use `:` instead. 17 | 18 | ```sml 19 | val x : int = 3 20 | ``` 21 | -------------------------------------------------------------------------------- /crates/millet-cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "millet-cli" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [[bin]] 12 | name = "millet-cli" 13 | test = false 14 | 15 | [dependencies] 16 | codespan-reporting.workspace = true 17 | diagnostic.workspace = true 18 | env_logger.workspace = true 19 | paths.workspace = true 20 | pico-args.workspace = true 21 | text-pos.workspace = true 22 | text-size-util.workspace = true 23 | 24 | analysis.path = "../analysis" 25 | config.path = "../config" 26 | input.path = "../input" 27 | panic-hook.path = "../panic-hook" 28 | sml-naive-fmt.path = "../sml-naive-fmt" 29 | -------------------------------------------------------------------------------- /crates/sml-parse/src/root.rs: -------------------------------------------------------------------------------- 1 | //! Parsing a full program. 2 | 3 | use crate::parser::{ErrorKind, Expected, Parser}; 4 | use sml_syntax::kind::SyntaxKind as SK; 5 | 6 | pub(crate) fn root(p: &mut Parser<'_>, fe: &mut sml_fixity::Env) { 7 | let entered = p.enter(); 8 | while let Some(tok) = p.peek() { 9 | if !crate::dec::dec(p, fe, crate::util::InfixErr::Yes) { 10 | // avoid infinite loop 11 | let ek = match tok.kind { 12 | SK::RRound | SK::RCurly | SK::RSquare | SK::EndKw => ErrorKind::UnmatchedClosingDelimiter, 13 | _ => ErrorKind::Expected(Expected::Item), 14 | }; 15 | p.error(ek); 16 | p.bump(); 17 | } 18 | } 19 | p.exit(entered, SK::Root); 20 | } 21 | -------------------------------------------------------------------------------- /docs/diagnostics/5024.md: -------------------------------------------------------------------------------- 1 | # 5024 2 | 3 | Not all or pattern alternatives bound the same names. 4 | 5 | ```sml 6 | datatype t = Foo of int | Bar of int 7 | 8 | fun toInt (x : t) : int = 9 | let val (Foo y | Bar _) = x 10 | (** ^^^^^ `y` was bound in one alternative, but not in another *) 11 | in y end 12 | ``` 13 | 14 | ## To fix 15 | 16 | Ensure all alternatives bind the same names. The types must also match. 17 | 18 | ```sml 19 | datatype t = Foo of int | Bar of int 20 | 21 | fun toInt (x : t) : int = 22 | let val (Foo y | Bar y) = x 23 | in y end 24 | ``` 25 | 26 | Note that or patterns are not permitted by the Definition, though they are a common extension, implemented by SML/NJ and MLton. 27 | -------------------------------------------------------------------------------- /crates/sml-dynamics-tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sml-dynamics-tests" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | doctest = false 13 | 14 | [dev-dependencies] 15 | pretty_assertions.workspace = true 16 | str-util.workspace = true 17 | 18 | config.path = "../config" 19 | sml-dynamics.path = "../sml-dynamics" 20 | sml-file-syntax.path = "../sml-file-syntax" 21 | sml-file.path = "../sml-file" 22 | sml-fixity.path = "../sml-fixity" 23 | sml-hir.path = "../sml-hir" 24 | sml-path.path = "../sml-path" 25 | sml-statics-types.path = "../sml-statics-types" 26 | sml-statics.path = "../sml-statics" 27 | -------------------------------------------------------------------------------- /docs/diagnostics/5039.md: -------------------------------------------------------------------------------- 1 | # 5039 2 | 3 | An expression that will never `raise` was surrounded with a `handle`. This means none of the arms of the `handle` will be reachable. 4 | 5 | ```sml 6 | val a = 4 handle Overflow => 5 7 | (** ^^^^^^^^^^^^^^^^^^^^^^ unreachable `handle` *) 8 | ``` 9 | 10 | ## To fix 11 | 12 | Make the handled expression actually (possibly) raise something, or remove the `handle`. 13 | 14 | ```sml 15 | val b = (4 + 123123123) handle Overflow => 5 16 | val c = 4 17 | ``` 18 | 19 | Note that the analysis is not sophisticated. 20 | 21 | ```sml 22 | fun four () = 4 23 | 24 | (* won't actually raise, but we don't warn for the unreachable `handle` *) 25 | val _ = four () handle Overflow => 5 26 | ``` 27 | -------------------------------------------------------------------------------- /crates/sml-statics-types/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sml-statics-types" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | test = false 13 | doctest = false 14 | 15 | [dependencies] 16 | chain-map.workspace = true 17 | code-h2-md-map.workspace = true 18 | drop_bomb.workspace = true 19 | fast-hash.workspace = true 20 | fmt-util.workspace = true 21 | idx.workspace = true 22 | paths.workspace = true 23 | str-util.workspace = true 24 | 25 | config.path = "../config" 26 | cov-mark.path = "../cov-mark" 27 | sml-hir.path = "../sml-hir" 28 | sml-path.path = "../sml-path" 29 | 30 | [features] 31 | sync = ["chain-map/sync"] 32 | -------------------------------------------------------------------------------- /docs/diagnostics/5009.md: -------------------------------------------------------------------------------- 1 | # 5009 2 | 3 | A real literal was used as a pattern. 4 | 5 | 6 | 7 | ```sml 8 | fun f (x : real) : int = 9 | case x of 10 | 1.2 => 3 11 | (** ^^^ real literal used as a pattern *) 12 | | _ => 4 13 | ``` 14 | 15 | ## To fix 16 | 17 | Consider checking that the given real is within some epsilon value of the desired real. 18 | 19 | ```sml 20 | val eps = 0.01 21 | 22 | fun f (x : real) : int = 23 | if Real.abs (x - 1.2) <= eps then 24 | 3 25 | else 26 | 4 27 | ``` 28 | 29 | Usage of `Real.==` to check for equality between reals is discouraged, due to [limitations](https://0.30000000000000004.com) around representing floating-point (aka, `real`) numbers on most architectures. 30 | -------------------------------------------------------------------------------- /crates/tests/src/deviations/successor_ml/exp_row_pun.rs: -------------------------------------------------------------------------------- 1 | //! Tests for expression row punning, where `{foo}` is equivalent to `{foo = foo}`. 2 | 3 | use crate::check::{check, check_multi, raw}; 4 | 5 | #[test] 6 | fn default_disallow() { 7 | check( 8 | r" 9 | fun incB {a, b, c} = {a, b = b + 1, c} 10 | (** ^ expression row punning *) 11 | ", 12 | ); 13 | } 14 | 15 | #[test] 16 | fn config_allow() { 17 | let config = r" 18 | version = 1 19 | language.successor-ml.exp-row-pun = true 20 | "; 21 | let sml = r" 22 | fun incB {a, b, c} = {a, b = b + 1, c} 23 | val _ = incB 24 | (** ^^^^ hover: { a : ?a, b : int, c : ?b } -> { a : ?a, b : int, c : ?b } *) 25 | "; 26 | check_multi(raw::singleton(config, sml)); 27 | } 28 | -------------------------------------------------------------------------------- /docs/diagnostics/5015.md: -------------------------------------------------------------------------------- 1 | # 5015 2 | 3 | A constructor had no argument in a pattern match, but it was defined to have an argument in the `datatype`. 4 | 5 | ```sml 6 | datatype d = A | B of int 7 | 8 | fun f x = 9 | case x of 10 | A => 1 11 | | B => 2 12 | (** ^ missing argument for constructor pattern *) 13 | ``` 14 | 15 | ## To fix 16 | 17 | Try one of the following: 18 | 19 | - Add an argument to the pattern: 20 | 21 | ```sml 22 | datatype d = A | B of int 23 | 24 | fun f x = 25 | case x of 26 | A => 1 27 | | B _ => 2 28 | ``` 29 | 30 | - Define the constructor to not have an argument: 31 | 32 | ```sml 33 | datatype d = A | B 34 | 35 | fun f x = 36 | case x of 37 | A => 1 38 | | B => 2 39 | ``` 40 | -------------------------------------------------------------------------------- /crates/sml-lab/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [`Lab`], a label, as for a record or tuple. 2 | 3 | use std::fmt; 4 | use str_util::Name; 5 | 6 | /// A record/tuple label. 7 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 8 | pub enum Lab { 9 | /// A named label. 10 | Name(Name), 11 | /// A numeric label. 12 | Num(usize), 13 | } 14 | 15 | impl fmt::Display for Lab { 16 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 17 | match self { 18 | Self::Name(name) => name.fmt(f), 19 | Self::Num(n) => n.fmt(f), 20 | } 21 | } 22 | } 23 | 24 | impl Lab { 25 | /// Return the numeric label for one greater than the number passed. 26 | #[must_use] 27 | pub fn tuple(idx: usize) -> Self { 28 | Self::Num(idx + 1) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug 3 | about: Report a bug 4 | labels: bug 5 | --- 6 | 7 | ## Environment 8 | 9 | - Millet version: 10 | - Editor version: 11 | - OS version: 12 | 13 | ## Attestation 14 | 15 | To avoid filing duplicate issues, I checked: 16 | 17 | - [ ] The [known issues section of the manual](/docs/manual.md#known-issues) 18 | - [ ] The [existing issue tracker](https://github.com/azdavis/millet/issues) 19 | 20 | ## Steps to reproduce 21 | 22 | 23 | 24 | ## Expected behavior 25 | 26 | 27 | 28 | ## Actual behavior 29 | 30 | 31 | -------------------------------------------------------------------------------- /crates/config/src/tool.rs: -------------------------------------------------------------------------------- 1 | //! See [`Tool`]. 2 | 3 | use serde::Deserialize; 4 | 5 | /// A default-`true` `bool`. 6 | /// 7 | /// Handy for when you have a type containing a `bool` that you want to derive `Deserialize` on, but 8 | /// you also want the `bool` to default to `true` if not provided when deserializing. 9 | /// 10 | /// In such a case, you can have the field be type `Tool` instead of `bool`, and then tag the field 11 | /// with `#[serde(default)]`. 12 | #[derive(Debug, Clone, Copy, Deserialize)] 13 | pub struct Tool(pub bool); 14 | 15 | impl Default for Tool { 16 | fn default() -> Self { 17 | Self(true) 18 | } 19 | } 20 | 21 | impl std::ops::Not for Tool { 22 | type Output = bool; 23 | 24 | fn not(self) -> Self::Output { 25 | !self.0 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /docs/diagnostics/2007.md: -------------------------------------------------------------------------------- 1 | # 2007 2 | 3 | A number (`int`, `word`, or `real`) literal was incomplete. For instance, a word literal that starts with `0w`, but then with no digits following, is incomplete. Or a real literal that has no digits after the decimal point, marked with `.`, or exponent, marked with `e` or `E`. 4 | 5 | 6 | 7 | ```sml 8 | val x : word = 0w 9 | (** ^^ missing digits in number literal *) 10 | val y : real = 1. 11 | (** ^^ missing digits in number literal *) 12 | val z : real = 1e 13 | (** ^^ missing digits in number literal *) 14 | ``` 15 | 16 | ## To fix 17 | 18 | Add some digits to complete the number literal. 19 | 20 | ```sml 21 | val x : word = 0w123 22 | val y : real = 1.123 23 | val z : real = 1e123 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/diagnostics/5026.md: -------------------------------------------------------------------------------- 1 | # 5026 2 | 3 | There was an expression hole. 4 | 5 | ```sml 6 | val answer = if ... then "yes" else "no" 7 | (** ^^^ expression hole with type `bool` *) 8 | ``` 9 | 10 | The error message contains information about the type of the hole given the surrounding context. For instance, in the above example, the hole is reported to have type `bool` because it is being used as the condition to an `if` expression. 11 | 12 | Expression holes can either be `...` or `_`. 13 | 14 | ```sml 15 | fun f _ = _ + 5 16 | (** ^ expression hole with type `int` *) 17 | val ans = f 3 18 | ``` 19 | 20 | ## To fix 21 | 22 | Replace the hole with a real expression of the correct type. 23 | 24 | ```sml 25 | val answer = if 3 < 4 then "yes" else "no" 26 | fun f x = x + 5 27 | ``` 28 | -------------------------------------------------------------------------------- /docs/diagnostics/5035.md: -------------------------------------------------------------------------------- 1 | # 5035 2 | 3 | There was a call to `@`, the list append function, with a discouraged argument. 4 | 5 | 6 | 7 | ```sml 8 | fun overlyComplicatedId xs = 9 | [] @ xs 10 | (** + calling `@` with an empty list *) 11 | 12 | fun overlyComplicatedId' xs = 13 | xs @ [] 14 | (** + calling `@` with an empty list *) 15 | 16 | fun overlyComplicatedCons x xs = 17 | [x] @ xs 18 | (** + calling `@` with a singleton list *) 19 | ``` 20 | 21 | These expressions can be simplified: 22 | 23 | | Complex | Simple | 24 | | ---------- | --------- | 25 | | `[] @ xs` | `xs` | 26 | | `xs @ []` | `xs` | 27 | | `[x] @ xs` | `x :: xs` | 28 | 29 | ## To fix 30 | 31 | Simplify the expressions. See the above table. 32 | -------------------------------------------------------------------------------- /crates/lang-srv/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lang-srv" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | test = false 13 | doctest = false 14 | 15 | [dependencies] 16 | anyhow.workspace = true 17 | crossbeam-channel.workspace = true 18 | diagnostic.workspace = true 19 | elapsed.workspace = true 20 | fast-hash.workspace = true 21 | log.workspace = true 22 | lsp-server.workspace = true 23 | lsp-types.workspace = true 24 | paths.workspace = true 25 | serde.workspace = true 26 | serde_json.workspace = true 27 | text-pos.workspace = true 28 | 29 | analysis.path = "../analysis" 30 | config.path = "../config" 31 | input.path = "../input" 32 | sml-namespace.path = "../sml-namespace" 33 | -------------------------------------------------------------------------------- /docs/diagnostics/5014.md: -------------------------------------------------------------------------------- 1 | # 5014 2 | 3 | A constructor had an argument in a pattern match, but it was defined to have no argument in the `datatype`. 4 | 5 | ```sml 6 | datatype d = A | B of int 7 | 8 | fun f x = 9 | case x of 10 | A y => y + 1 11 | (** ^^^ unexpected argument for constructor pattern *) 12 | | B z => z - 1 13 | ``` 14 | 15 | ## To fix 16 | 17 | Try one of the following: 18 | 19 | - Remove the argument from the pattern: 20 | 21 | ```sml 22 | datatype d = A | B of int 23 | 24 | fun f x = 25 | case x of 26 | A => 1 27 | | B z => z - 1 28 | ``` 29 | 30 | - Define the constructor to have an argument: 31 | 32 | ```sml 33 | datatype d = A of int | B of int 34 | 35 | fun f x = 36 | case x of 37 | A y => y + 1 38 | | B z => z - 1 39 | ``` 40 | -------------------------------------------------------------------------------- /crates/cm-syntax/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Processing the syntax of [SML/NJ Compilation Manager][spec] files. 2 | //! 3 | //! Some features are not supported. 4 | //! 5 | //! [spec]: https://www.smlnj.org/doc/CM/new.pdf 6 | 7 | mod lex; 8 | mod lower; 9 | mod parse; 10 | mod types; 11 | 12 | pub use types::{ 13 | Class, CmFile, CmFileKind, Error, Export, Namespace, PathKind, PathOrMinus, PathOrStdBasis, 14 | Result, 15 | }; 16 | 17 | /// Turn the contents of a CM file into exports and members. 18 | /// 19 | /// # Errors 20 | /// 21 | /// If the CM file contents was invalid, or the env didn't define all the path variables. 22 | pub fn get(s: &str, env: &slash_var_path::Env) -> Result { 23 | let tokens = lex::get(s)?; 24 | let root = parse::get(&tokens, env)?; 25 | let file = lower::get(root)?; 26 | Ok(file) 27 | } 28 | -------------------------------------------------------------------------------- /crates/config/src/lang.rs: -------------------------------------------------------------------------------- 1 | //! Parsed configuration for the language. 2 | 3 | use crate::file; 4 | use fast_hash::FxHashSet; 5 | 6 | /// Parsed configuration for the language. 7 | #[derive(Debug, Default, Clone)] 8 | pub struct Language { 9 | /// Whether fixity declarations can take effect across files. 10 | pub fixity_across_files: bool, 11 | /// Configuration for declarations. 12 | pub dec: file::Dec, 13 | /// Configuration for expressions. 14 | pub exp: file::Exp, 15 | /// Disallowed value paths. 16 | pub val: FxHashSet, 17 | /// Disallowed structure paths. 18 | pub structure: FxHashSet, 19 | /// Configuration for Successor ML features. 20 | pub successor_ml: file::SuccessorMl, 21 | /// Configuration for Lunar ML features. 22 | pub lunar_ml: file::LunarMl, 23 | } 24 | -------------------------------------------------------------------------------- /crates/sml-statics-types/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Types and fundamental operations for static analysis. 2 | 3 | #![allow(clippy::too_many_lines, clippy::single_match_else)] 4 | 5 | mod data; 6 | 7 | pub mod def; 8 | pub mod disallow; 9 | pub mod display; 10 | pub mod env; 11 | pub mod equality; 12 | pub mod generalize; 13 | pub mod info; 14 | pub mod item; 15 | pub mod mode; 16 | pub mod overload; 17 | pub mod sym; 18 | pub mod ty; 19 | pub mod unify; 20 | pub mod util; 21 | 22 | /// The overall mutable state when typechecking. 23 | /// 24 | /// Called `St` as a short for `State`, as is common, but also conveniently contains exactly two 25 | /// fields: the `Syms` (S) and the `Tys` (T). 26 | #[derive(Debug, Default, Clone)] 27 | pub struct St { 28 | /// The syms. 29 | pub syms: sym::Syms, 30 | /// The tys. 31 | pub tys: ty::Tys, 32 | } 33 | -------------------------------------------------------------------------------- /docs/diagnostics/4011.md: -------------------------------------------------------------------------------- 1 | # 4011 2 | 3 | `op` does not work with `andalso` or `orelse`. 4 | 5 | ```sml 6 | fun bigAnd bs = List.foldl (op andalso) true bs 7 | (** ^^^^^^^^^^ `andalso` and `orelse` not allowed with `op` *) 8 | fun bigOr bs = List.foldl (op orelse) false bs 9 | (** ^^^^^^^^^ `andalso` and `orelse` not allowed with `op` *) 10 | ``` 11 | 12 | `andalso` and `orelse` are SML keywords, and short-circuit. They are not infix identifiers. Because of this, they do not work with `op`. 13 | 14 | ## To fix 15 | 16 | Use an anonymous `fn` expression (aka a lambda) or helper function. 17 | 18 | ```sml 19 | infix && || 20 | fun (a && b) = a andalso b 21 | fun (a || b) = a orelse b 22 | fun bigAnd bs = List.foldl (op &&) true bs 23 | fun bigOr bs = List.foldl (op ||) false bs 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/diagnostics/5018.md: -------------------------------------------------------------------------------- 1 | # 5018 2 | 3 | In a `val rec` binding, the expression must be a literal `fn` expression. 4 | 5 | ```sml 6 | val rec x = x + 3 7 | (** + the expression for a `val rec` was not a `fn` *) 8 | ``` 9 | 10 | It is an error even if the expression does not use the recursive binding. 11 | 12 | ```sml 13 | val rec x = 3 14 | (** + the expression for a `val rec` was not a `fn` *) 15 | ``` 16 | 17 | It is also an error even if the expression has function type. 18 | 19 | ```sml 20 | val mkAdd3 = fn () => fn x => x + 3 21 | val rec add3 = mkAdd3 () 22 | (** + the expression for a `val rec` was not a `fn` *) 23 | ``` 24 | 25 | ## To fix 26 | 27 | Ensure the expression is a literal `fn` expression. 28 | 29 | ```sml 30 | val rec add3 = fn n => n + 3 31 | val rec fact = fn n => if n = 0 then 1 else n * fact (n - 1) 32 | ``` 33 | -------------------------------------------------------------------------------- /crates/sml-statics-types/src/item.rs: -------------------------------------------------------------------------------- 1 | //! See [`Item`]. 2 | 3 | use std::fmt; 4 | 5 | /// A kind of SML language construct. 6 | #[derive(Debug, Clone, Copy)] 7 | pub enum Item { 8 | /// A value. 9 | Val, 10 | /// A type. 11 | Ty, 12 | /// A type variable. 13 | TyVar, 14 | /// A structure. 15 | Struct, 16 | /// A signature. 17 | Sig, 18 | /// A functor. 19 | Functor, 20 | } 21 | 22 | impl fmt::Display for Item { 23 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 24 | match self { 25 | Item::Val => f.write_str("value"), 26 | Item::Ty => f.write_str("type"), 27 | Item::TyVar => f.write_str("type variable"), 28 | Item::Struct => f.write_str("structure"), 29 | Item::Sig => f.write_str("signature"), 30 | Item::Functor => f.write_str("functor"), 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Docs 2 | 3 | This is documentation for Millet. 4 | 5 | - [Architecture](./ARCHITECTURE.md): A high-level description of how the code is organized and what each component does. 6 | - [Changelog](./CHANGELOG.md): A non-exhaustive list of changes to each version. 7 | - [Code of conduct](./CODE_OF_CONDUCT.md): How we expect all project contributors to behave. 8 | - [Contributing](./CONTRIBUTING.md): How to contribute to the project. 9 | - [Diagnostics](./diagnostics): Every error Millet can emit, what it means, and how to fix it. 10 | - [Manual](./manual.md): How to install and use Millet. 11 | - [Primitive](./primitives.md): Information about SML primitives, like `int`. 12 | - [Security](./SECURITY.md): The security policy for Millet. 13 | - [Tokens](./tokens.md): Information about (almost) every token in SML. This is built-in to Millet itself. 14 | -------------------------------------------------------------------------------- /docs/diagnostics/5021.md: -------------------------------------------------------------------------------- 1 | # 5021 2 | 3 | Certain names may not be rebound. These names are: 4 | 5 | | Name | Definition | 6 | | ------- | -------------------------------------------------- | 7 | | `true` | logical truth | 8 | | `false` | logical falsity | 9 | | `nil` | the empty list constructor | 10 | | `::` | the non-empty list constructor | 11 | | `ref` | the reference constructor | 12 | | `=` | the polymorphic equality function | 13 | | `it` | the value of the last expression entered in a REPL | 14 | 15 | ```sml 16 | val it = 3 17 | (** ^^ cannot re-bind name: `it` *) 18 | ``` 19 | 20 | ## To fix 21 | 22 | Do not attempt to rebind these names. 23 | -------------------------------------------------------------------------------- /docs/diagnostics/1017.md: -------------------------------------------------------------------------------- 1 | # 1017 2 | 3 | An export was undefined. 4 | 5 | Millet uses either SML/NJ CM files or ML Basis files (aka "group" files) to know what SML source files to analyze, and in what order. Both of these group file types allow for listing "exports", which are generally the names of structures, signatures, or functors. 6 | 7 | This error will be emitted when a group file lists an export not defined by the source files. 8 | 9 | For instance, given these two files: 10 | 11 | ```sml 12 | (* a.sml *) 13 | structure Foo = struct end 14 | ``` 15 | 16 | ```text 17 | (* sources.mlb *) 18 | local 19 | a.sml 20 | in 21 | structure Bar 22 | end 23 | ``` 24 | 25 | Millet will emit this error because `structure Bar` is not defined in `a.sml`. 26 | 27 | ## To fix 28 | 29 | - Define the export in source files. 30 | - Or remove the export in the group file. 31 | -------------------------------------------------------------------------------- /docs/diagnostics/5023.md: -------------------------------------------------------------------------------- 1 | # 5023 2 | 3 | A record type couldn't be fully resolved, due to the use of a `...` pattern row with insufficient surrounding context. 4 | 5 | ```sml 6 | fun getX {x, ...} = x 7 | (** + cannot resolve `...` in record type: `{ x : _, ... }` *) 8 | ``` 9 | 10 | SML lacks row polymorphism, so the above example function does not typecheck. 11 | 12 | This error may arise when using `#` selectors. 13 | 14 | ```sml 15 | fun addFooBar x = #foo x + #bar x 16 | (** + cannot resolve `...` in record type: `{ bar : , foo : , ... }` *) 17 | ``` 18 | 19 | ## To fix 20 | 21 | Consider adding a type annotation. 22 | 23 | ```sml 24 | type t = {x : int, y : bool, z : string} 25 | fun getX ({x, ...} : t) = x 26 | ``` 27 | 28 | An alternative would be to avoid `...` pattern rows altogether. 29 | 30 | ```sml 31 | fun addFooBar {foo, bar} = foo + bar 32 | ``` 33 | -------------------------------------------------------------------------------- /docs/diagnostics/4010.md: -------------------------------------------------------------------------------- 1 | # 4010 2 | 3 | A structure-level declaration, i.e. one that starts with one of: 4 | 5 | - `structure` 6 | - `signature` 7 | - `functor` 8 | 9 | occurred in a disallowed position, like inside a regular declaration. 10 | 11 | ```sml 12 | val s = 13 | let 14 | structure Integer = Int 15 | (** ^^^^^^^^^^^^^^^^^^^^^^^ structure-level declaration not allowed here *) 16 | val x = 3 17 | in 18 | Integer.toString x 19 | end 20 | ``` 21 | 22 | This error is also emitted for `include` specifications occurring in declaration position. 23 | 24 | ```sml 25 | include LIST 26 | (** + not allowed here *) 27 | ``` 28 | 29 | ## To fix 30 | 31 | Move the declaration to an allowed position. 32 | 33 | ```sml 34 | structure Integer = Int 35 | val s = 36 | let 37 | val x = 3 38 | in 39 | Integer.toString x 40 | end 41 | 42 | signature L = sig 43 | include LIST 44 | end 45 | ``` 46 | -------------------------------------------------------------------------------- /crates/sml-statics-types/src/data.rs: -------------------------------------------------------------------------------- 1 | //! See [`Map`]. 2 | 3 | use fast_hash::FxHashMap; 4 | 5 | /// A two-way mapping between interned indices and data. 6 | #[derive(Debug, Clone)] 7 | pub(crate) struct Map { 8 | idx: FxHashMap, 9 | data: Vec, 10 | } 11 | 12 | impl Map { 13 | pub(crate) fn get_idx(&mut self, data: T) -> idx::Idx 14 | where 15 | T: Clone + Eq + std::hash::Hash, 16 | { 17 | if let Some(&ret) = self.idx.get(&data) { 18 | return ret; 19 | } 20 | let ret = idx::Idx::new(self.data.len()); 21 | assert!(self.idx.insert(data.clone(), ret).is_none()); 22 | self.data.push(data); 23 | ret 24 | } 25 | 26 | pub(crate) fn get_data(&self, idx: idx::Idx) -> &T { 27 | &self.data[idx.to_usize()] 28 | } 29 | } 30 | 31 | impl Default for Map { 32 | fn default() -> Self { 33 | Self { idx: FxHashMap::default(), data: Vec::new() } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /crates/tests/src/deviations/successor_ml/or_pat.rs: -------------------------------------------------------------------------------- 1 | //! Tests for allowing/disallowing or patterns. 2 | 3 | use crate::check::{check, check_multi, raw}; 4 | 5 | #[test] 6 | fn default_allow() { 7 | check( 8 | r" 9 | datatype d = A of int | B of int 10 | fun f (A x | B x) = x 11 | ", 12 | ); 13 | } 14 | 15 | #[test] 16 | fn config_allow() { 17 | let config = r" 18 | version = 1 19 | language.successor-ml.or-pat = true 20 | "; 21 | let sml = r" 22 | datatype d = A of int | B of int 23 | fun f (A x | B x) = x 24 | "; 25 | check_multi(raw::singleton(config, sml)); 26 | } 27 | 28 | #[test] 29 | fn config_disallow() { 30 | let config = r" 31 | version = 1 32 | language.successor-ml.or-pat = false 33 | "; 34 | let sml = r" 35 | datatype d = A of int | B of int 36 | fun f (A x | B x) = x 37 | (** ^^^^^^^^^ disallowed Successor ML feature: or patterns *) 38 | "; 39 | check_multi(raw::singleton(config, sml)); 40 | } 41 | -------------------------------------------------------------------------------- /crates/cov-mark/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Coverage markers. 2 | 3 | use fast_hash::FxHashSet; 4 | use std::sync::{LazyLock, Mutex}; 5 | 6 | thread_local! { 7 | static MAP: LazyLock>> = LazyLock::new(|| Mutex::new(FxHashSet::default())); 8 | } 9 | 10 | /// Hit the named marker. 11 | /// 12 | /// This no-ops when `debug_assertions` is off. 13 | /// 14 | /// # Panics 15 | /// 16 | /// Upon internal error. 17 | pub fn hit(s: &'static str) { 18 | if cfg!(debug_assertions) { 19 | MAP.with(|m| m.lock().unwrap().insert(s)); 20 | } 21 | } 22 | 23 | /// Checks if the marker was previously hit. 24 | /// 25 | /// This no-ops when `debug_assertions` is off. 26 | /// 27 | /// # Panics 28 | /// 29 | /// If the marker was not previously hit or internal error. 30 | pub fn check(s: &'static str) { 31 | if cfg!(debug_assertions) { 32 | MAP.with(|m| assert!(m.lock().unwrap().contains(s), "{s} not hit")); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /crates/sml-statics/src/unify.rs: -------------------------------------------------------------------------------- 1 | //! A thin wrapper around the inner `unify`. 2 | 3 | use crate::error::ErrorKind; 4 | use crate::st::St; 5 | use sml_statics_types::ty::Ty; 6 | use sml_statics_types::unify; 7 | 8 | pub(crate) fn unify(st: &mut St<'_>, idx: sml_hir::Idx, want: Ty, got: Ty) { 9 | match unify_no_emit(st.syms_tys, st.info.mode, want, got) { 10 | Ok(()) => {} 11 | Err(e) => st.err(idx, e), 12 | } 13 | } 14 | 15 | pub(crate) fn unify_no_emit( 16 | st: &mut sml_statics_types::St, 17 | mode: sml_statics_types::mode::Mode, 18 | want: Ty, 19 | got: Ty, 20 | ) -> Result<(), ErrorKind> { 21 | if mode.is_path_order() { 22 | return Ok(()); 23 | } 24 | unify::unify(&mut st.tys, &st.syms, want, got).map_err(|err| match err { 25 | unify::Error::Circularity(circ) => ErrorKind::Circularity(circ), 26 | unify::Error::Incompatible(reason) => ErrorKind::IncompatibleTys(reason, want, got), 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /docs/diagnostics/5025.md: -------------------------------------------------------------------------------- 1 | # 5025 2 | 3 | A `signature` or `functor` declaration occurred in a disallowed position, like inside `struct ... end`. 4 | 5 | ```sml 6 | structure Str = struct 7 | signature SIG = sig end 8 | (** + `signature` or `functor` not allowed here *) 9 | functor Func() = struct end 10 | (** + `signature` or `functor` not allowed here *) 11 | end 12 | ``` 13 | 14 | ## To fix 15 | 16 | Declare the signature or functor at the top level. 17 | 18 | ```sml 19 | structure Str = struct end 20 | signature SIG = sig end 21 | functor Func() = struct end 22 | ``` 23 | 24 | Although not permitted by the Definition, Millet also allows defining the signature or functor in a `local`. 25 | 26 | ```sml 27 | local 28 | signature SIG = sig val y : int end 29 | functor Func(val x : int) : SIG = struct val y = x + 2 end 30 | in 31 | structure S4 : SIG = Func(val x = 4) 32 | structure S7 : SIG = Func(val x = 7) 33 | end 34 | ``` 35 | -------------------------------------------------------------------------------- /crates/tests/src/datatype_copy.rs: -------------------------------------------------------------------------------- 1 | //! Datatype copy tests. 2 | 3 | use crate::check::check; 4 | 5 | #[test] 6 | fn smoke() { 7 | check( 8 | r" 9 | datatype guh = A | B 10 | datatype heh = datatype guh 11 | fun f (x: heh): int = 12 | case x of 13 | A => 1 14 | | B => 2 15 | val _ = f A + f B 16 | ", 17 | ); 18 | } 19 | 20 | #[test] 21 | fn exn() { 22 | check( 23 | r" 24 | datatype ok = datatype exn 25 | ", 26 | ); 27 | } 28 | 29 | #[test] 30 | fn int() { 31 | check( 32 | r" 33 | datatype yes = datatype int 34 | ", 35 | ); 36 | } 37 | 38 | #[test] 39 | fn with_ty_var() { 40 | check( 41 | r" 42 | datatype vec = datatype list 43 | val _: int vec = [1, 2] 44 | ", 45 | ); 46 | } 47 | 48 | #[test] 49 | fn structure() { 50 | check( 51 | r" 52 | structure S = struct datatype a = A end 53 | datatype b = datatype S.a 54 | val _ = A: b 55 | val _ = A: S.a 56 | val _ = S.A: b 57 | val _ = S.A: S.a 58 | ", 59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /crates/sml-statics/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sml-statics" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | test = false 13 | doctest = false 14 | 15 | [dependencies] 16 | chain-map.workspace = true 17 | diagnostic.workspace = true 18 | elapsed.workspace = true 19 | fast-hash.workspace = true 20 | fmt-util.workspace = true 21 | log.workspace = true 22 | paths.workspace = true 23 | pattern-match.workspace = true 24 | str-util.workspace = true 25 | uniq.workspace = true 26 | 27 | config.path = "../config" 28 | cov-mark.path = "../cov-mark" 29 | sml-hir.path = "../sml-hir" 30 | sml-namespace.path = "../sml-namespace" 31 | sml-path.path = "../sml-path" 32 | sml-statics-types.path = "../sml-statics-types" 33 | sml-symbol-kind.path = "../sml-symbol-kind" 34 | 35 | [features] 36 | sync = ["chain-map/sync", "sml-statics-types/sync", "sml-symbol-kind/sync"] 37 | -------------------------------------------------------------------------------- /crates/sml-statics/src/top_dec/env_syms.rs: -------------------------------------------------------------------------------- 1 | //! Traversing the `Sym`s in an `Env`. 2 | 3 | use sml_statics_types::{env::Env, info::ValEnv, sym::Sym, ty::Tys, util::ty_syms}; 4 | 5 | /// Calls `f` for every sym in the env. 6 | /// 7 | /// Putting `f` last allows calls with the closure constructed at the call site to be formatted 8 | /// across fewer lines. 9 | pub(crate) fn get(tys: &Tys, env: &Env, f: &mut F) 10 | where 11 | F: FnMut(Sym), 12 | { 13 | for (_, env) in env.str_env.iter() { 14 | get(tys, env, f); 15 | } 16 | for (_, ty_info) in env.ty_env.iter() { 17 | ty_syms(tys, ty_info.ty_scheme.ty, f); 18 | val_env_syms(tys, &ty_info.val_env, f); 19 | } 20 | for (_, val_info) in env.val_env.iter() { 21 | ty_syms(tys, val_info.ty_scheme.ty, f); 22 | } 23 | } 24 | 25 | fn val_env_syms(tys: &Tys, val_env: &ValEnv, f: &mut F) { 26 | for (_, val_info) in val_env.iter() { 27 | ty_syms(tys, val_info.ty_scheme.ty, f); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /crates/tests/src/local.rs: -------------------------------------------------------------------------------- 1 | //! `local`. 2 | 3 | use crate::check::check; 4 | 5 | #[test] 6 | fn use_later_dec() { 7 | check( 8 | r" 9 | val _ = 10 | let 11 | local 12 | val x = 3 13 | in 14 | val y = x + 2 15 | val z = y + x 16 | end 17 | in 18 | y + z 19 | end 20 | ", 21 | ); 22 | } 23 | 24 | #[test] 25 | fn does_not_escape_dec() { 26 | check( 27 | r" 28 | val _ = 29 | let 30 | local 31 | val x = 3 32 | in 33 | end 34 | in 35 | x 36 | (** ^ undefined value: `x` *) 37 | end 38 | ", 39 | ); 40 | } 41 | 42 | #[test] 43 | fn use_later_str_dec() { 44 | check( 45 | r" 46 | local 47 | val x = 3 48 | in 49 | val y = x + 2 50 | val z = y + x 51 | end 52 | val _ = y + z 53 | ", 54 | ); 55 | } 56 | 57 | #[test] 58 | fn does_not_escape_str_dec() { 59 | check( 60 | r" 61 | local 62 | val x = 3 63 | in 64 | end 65 | val _ = x 66 | (** ^ undefined value: `x` *) 67 | ", 68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /docs/diagnostics/4002.md: -------------------------------------------------------------------------------- 1 | # 4002 2 | 3 | In a `fun` binding with multiple cases, the cases did not all have the same number of patterns. 4 | 5 | ```sml 6 | fun foo 6 = 8 7 | | foo x y z = x + y + z 8 | (** ^^^^^ expected 1 pattern, found 3 *) 9 | ``` 10 | 11 | This error can occur when there were no parentheses around what should have been a single constructor-with-argument pattern: 12 | 13 | ```sml 14 | datatype nat = Z | S of nat 15 | 16 | fun toInt Z = 0 17 | | toInt S n = 1 + toInt n 18 | (** ^^^ expected 1 pattern, found 2 *) 19 | ``` 20 | 21 | In this example, `S` and `n` are each treated as separate argument patterns for `toInt`. 22 | 23 | ## To fix 24 | 25 | Use a consistent number of patterns across all cases. 26 | 27 | ```sml 28 | fun foo 6 = 8 29 | | foo x = x + 3 30 | ``` 31 | 32 | For constructors with arguments, use parentheses. 33 | 34 | ```sml 35 | datatype nat = Z | S of nat 36 | 37 | fun toInt Z = 0 38 | | toInt (S n) = 1 + toInt n 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/diagnostics/2009.md: -------------------------------------------------------------------------------- 1 | # 2009 2 | 3 | There was a non-whitespace character in a string continuation. 4 | 5 | 6 | 7 | ```sml 8 | val s = 9 | "this string is\ not 10 | \ valid because there\ are 11 | \ non-whitespace\ characters 12 | \ in the continuations" 13 | ``` 14 | 15 | String literals permit the sequence `\...\`, where `...` represents 1 or more whitespace characters. The sequence is ignored. We dub such sequences "string continuations", since they are often used to "continue" strings across lines. 16 | 17 | ## To fix 18 | 19 | Ensure the string continuations contain only whitespace. 20 | 21 | Millet recognizes all characters defined in the Definition as whitespace, as well as some others, like carriage return (common on Windows). 22 | 23 | ```sml 24 | val s = 25 | "this string is\ 26 | \ valid because there are only\ 27 | \ whitespace characters\ 28 | \ in the continuations" 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/diagnostics/1003.md: -------------------------------------------------------------------------------- 1 | # 1003 2 | 3 | There were multiple group files. 4 | 5 | A group file is one of: 6 | 7 | - A SML/NJ Compilation Manager (`.cm`) file. 8 | - A ML Basis (`.mlb`) file. 9 | 10 | Millet uses group files to know what source files (aka SML files) to analyze, and in what order. 11 | 12 | In the absence of a `millet.toml` config file, Millet looks for **exactly one** group contained **directly** in the root directory (i.e. the directory onto which editor is opened). 13 | 14 | If Millet finds **more that one** group file in the root directory, it emits this error. 15 | 16 | ## To fix 17 | 18 | Select which group you want to be the "root" group file with a `millet.toml` [config file][config], contained **directly** in the root directory. 19 | 20 | In this example, we have a minimal `millet.toml` that defines the single root group file to be `foo.cm`: 21 | 22 | ```toml 23 | version = 1 24 | workspace.root = "foo.cm" 25 | ``` 26 | 27 | [config]: /docs/manual.md#configuration 28 | -------------------------------------------------------------------------------- /docs/diagnostics/5040.md: -------------------------------------------------------------------------------- 1 | # 5040 2 | 3 | There was a declaration with no effect. 4 | 5 | If a declaration is known to never introduce any bindings or cause a side effect, then it has no effect. 6 | 7 | ```sml 8 | val _ = 4 9 | (** + no effect *) 10 | ``` 11 | 12 | ## To fix 13 | 14 | Cause the declaration to have an effect, or remove it. 15 | 16 | ```sml 17 | (* has an effect because it adds a binding *) 18 | val a = 4 19 | 20 | (* has an effect because it might raise (a side effect) *) 21 | val _ = 123123123 + 456456456 22 | 23 | (* has an effect because it prints to stdout (a side effect) *) 24 | val _ = print "hi" 25 | 26 | (* has an effect because it adds a binding *) 27 | fun loop () = loop () 28 | 29 | (* has an effect because it loops forever (a side effect) *) 30 | val _ = loop () 31 | ``` 32 | 33 | Note that the analysis is not sophisticated. 34 | 35 | ```sml 36 | fun four () = 4 37 | 38 | (* won't actually have an effect, but we don't warn for no effect *) 39 | val _ = four () 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/diagnostics/4014.md: -------------------------------------------------------------------------------- 1 | # 4014 2 | 3 | There were unnecessary parentheses around something. 4 | 5 | ```sml 6 | val n = (3) 7 | (** ^^^ unnecessary parentheses *) 8 | fun inc (x) = x + 1 9 | (** ^^^ unnecessary parentheses *) 10 | type t = (int) 11 | (** ^^^^^ unnecessary parentheses *) 12 | ``` 13 | 14 | Many things are "atomic", meaning they do not need parentheses around them to override precedence. 15 | 16 | | Example | Atomic? | 17 | | --------------- | ------- | 18 | | `false` | Yes | 19 | | `(1, "hi")` | Yes | 20 | | `[1, 3]` | Yes | 21 | | `1 + 2` | No | 22 | | `print "hi"` | No | 23 | | `fn x => x + 1` | No | 24 | 25 | Note that e.g. `op +` is technically atomic, but this error is not issued for parentheses around it, because using parentheses around usages of `op` is somewhat idiomatic. 26 | 27 | ## To fix 28 | 29 | Remove the parentheses. 30 | 31 | ```sml 32 | val n = 3 33 | fun inc x = x + 1 34 | type t = int 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/diagnostics/5022.md: -------------------------------------------------------------------------------- 1 | # 5022 2 | 3 | Names have "statuses", which can be one of: 4 | 5 | - exception 6 | - constructor 7 | - value 8 | 9 | These statuses must be compatible for the purposes of matching a structure against a signature. 10 | 11 | ```sml 12 | exception Foo 13 | 14 | structure S 15 | : sig exception E end 16 | = struct val E = Foo end 17 | (** ^^^^^^ incompatible identifier statuses: `E` *) 18 | ``` 19 | 20 | The most specific status is "exception", and the least is "value". This is because an exception constructor is also generally a constructor, and all constructors are values. 21 | 22 | Generally, for the identifier statuses to be compatible, the status of the identifier provided in the structure must be at least as specific as the one requested in the signature. 23 | 24 | ## To fix 25 | 26 | Ensure the names have compatible statuses. 27 | 28 | ```sml 29 | exception Foo 30 | 31 | structure S 32 | : sig exception E end 33 | = struct exception E = Foo end 34 | ``` 35 | -------------------------------------------------------------------------------- /crates/input/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "input" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | test = false 13 | doctest = false 14 | 15 | [dependencies] 16 | diagnostic.workspace = true 17 | fast-hash.workspace = true 18 | paths.workspace = true 19 | str-util.workspace = true 20 | text-pos.workspace = true 21 | text-size-util.workspace = true 22 | toml.workspace = true 23 | topo-sort.workspace = true 24 | 25 | cm-syntax.path = "../cm-syntax" 26 | config.path = "../config" 27 | cov-mark.path = "../cov-mark" 28 | mlb-hir.path = "../mlb-hir" 29 | mlb-syntax.path = "../mlb-syntax" 30 | paths-glob.path = "../paths-glob" 31 | slash-var-path.path = "../slash-var-path" 32 | sml-file-syntax.path = "../sml-file-syntax" 33 | sml-file.path = "../sml-file" 34 | sml-fixity.path = "../sml-fixity" 35 | sml-namespace.path = "../sml-namespace" 36 | sml-path.path = "../sml-path" 37 | sml-syntax.path = "../sml-syntax" 38 | -------------------------------------------------------------------------------- /crates/tests/src/well_known.rs: -------------------------------------------------------------------------------- 1 | //! "Well-known" types that are available at the top level and should be reported unqualified. 2 | //! 3 | //! Tests should: 4 | //! 5 | //! 1. Cause a type error and assert that the type is reported unqualified. 6 | //! 2. Contain a witness to the fact that the type is really available unqualified. 7 | 8 | use crate::check::check_with_std_basis; 9 | 10 | #[test] 11 | fn option() { 12 | check_with_std_basis( 13 | r" 14 | val () = SOME () 15 | (** + expected `unit`, found `unit option` *) 16 | 17 | type 'a witness = 'a option 18 | ", 19 | ); 20 | } 21 | 22 | #[test] 23 | fn array() { 24 | check_with_std_basis( 25 | r" 26 | val () = Array.fromList [()] 27 | (** + expected `unit`, found `unit array` *) 28 | 29 | type 'a witness = 'a array 30 | ", 31 | ); 32 | } 33 | 34 | #[test] 35 | fn vector() { 36 | check_with_std_basis( 37 | r" 38 | val () = Vector.fromList [()] 39 | (** + expected `unit`, found `unit vector` *) 40 | 41 | type 'a witness = 'a vector 42 | ", 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /crates/tests/src/deviations/successor_ml/do_dec.rs: -------------------------------------------------------------------------------- 1 | //! Tests for `do e`, which is the same as `val () = e`. 2 | 3 | use crate::check::{check, check_multi, raw}; 4 | 5 | #[test] 6 | fn default_disallow() { 7 | check( 8 | r#" 9 | fun print _ = () 10 | do print "hi" 11 | (** ^^^^^^^^^^^^^ disallowed *) 12 | "#, 13 | ); 14 | } 15 | 16 | #[test] 17 | fn config_allow() { 18 | let config = r" 19 | version = 1 20 | language.successor-ml.do-dec = true 21 | "; 22 | let sml = r#" 23 | fun print _ = () 24 | do print "hi" 25 | 26 | val x = 27 | let 28 | val y = 4 29 | do print "hi... " 30 | val z = y + 2 31 | do print "bye" 32 | in 33 | y * z * z 34 | end 35 | 36 | do () 37 | "#; 38 | check_multi(raw::singleton(config, sml)); 39 | } 40 | 41 | #[test] 42 | fn exp_unit_ty() { 43 | let config = r" 44 | version = 1 45 | language.successor-ml.do-dec = true 46 | "; 47 | let sml = r#" 48 | do "hi" 49 | (** + expected `unit`, found `string` *) 50 | "#; 51 | check_multi(raw::singleton(config, sml)); 52 | } 53 | -------------------------------------------------------------------------------- /crates/tests/src/forbid_opaque_asc.rs: -------------------------------------------------------------------------------- 1 | //! Tests for forbidding opaque ascription. 2 | 3 | use crate::check::check; 4 | 5 | #[test] 6 | fn pat() { 7 | check( 8 | r" 9 | val x :> int = 3 10 | (** ^^ not allowed here *) 11 | ", 12 | ); 13 | } 14 | 15 | #[test] 16 | fn exp() { 17 | check( 18 | r" 19 | val x = 3 :> int 20 | (** ^^ not allowed here *) 21 | ", 22 | ); 23 | } 24 | 25 | #[test] 26 | fn fun_ret() { 27 | check( 28 | r" 29 | fun f x :> int = 3 30 | (** ^^ not allowed here *) 31 | ", 32 | ); 33 | } 34 | 35 | #[test] 36 | fn ty_row() { 37 | check( 38 | r" 39 | type foo = { 40 | x : int, 41 | y :> string 42 | (** ^^ not allowed here *) 43 | } 44 | ", 45 | ); 46 | } 47 | 48 | #[test] 49 | fn lab_row() { 50 | check( 51 | r" 52 | fun f {a :> int as b} = a + b 53 | (** ^^ not allowed here *) 54 | ", 55 | ); 56 | } 57 | 58 | #[test] 59 | fn functor_arg() { 60 | check( 61 | r" 62 | functor F (S :> sig end) = struct end 63 | (** ^^ not allowed here *) 64 | ", 65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /crates/tests/src/deviations/implicit_ty_var.rs: -------------------------------------------------------------------------------- 1 | //! Tests involving type variables which may be bound implicitly, where the behavior differs between 2 | //! some SML implementations. 3 | //! 4 | //! Tested with: 5 | //! 6 | //! - smlnj: 110.99.3 7 | //! - mlton: 20210117 8 | 9 | use crate::check::check; 10 | 11 | /// - smlnj: error 12 | /// - mlton: no error 13 | #[test] 14 | fn datatype() { 15 | check( 16 | r" 17 | fun f x = 18 | let 19 | datatype t = Poly of 'a 20 | (** ^^ not allowed here *) 21 | in 22 | Poly x; 4 23 | end 24 | ", 25 | ); 26 | } 27 | 28 | /// - smlnj: error 29 | /// - mlton: no error 30 | #[test] 31 | fn typ() { 32 | check( 33 | r" 34 | fun f x = 35 | let 36 | type t = 'a 37 | (** ^^ not allowed here *) 38 | in 39 | x: t 40 | end 41 | ", 42 | ); 43 | } 44 | 45 | /// smlnj and mlton: no error 46 | #[test] 47 | fn exception() { 48 | check( 49 | r" 50 | fun f x = 51 | let 52 | exception Poly of 'a 53 | in 54 | raise Poly x 55 | end 56 | ", 57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /crates/tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tests" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | doctest = false 13 | 14 | [build-dependencies] 15 | quote.workspace = true 16 | proc-macro2.workspace = true 17 | write-rs-tokens.workspace = true 18 | 19 | [dev-dependencies] 20 | diagnostic.workspace = true 21 | env_logger.workspace = true 22 | fast-hash.workspace = true 23 | log.workspace = true 24 | paths.workspace = true 25 | pretty_assertions.workspace = true 26 | pulldown-cmark.workspace = true 27 | serde_json.workspace = true 28 | str-util.workspace = true 29 | text-pos.workspace = true 30 | 31 | analysis = { path = "../analysis", features = ["sync"] } # @ignore 32 | cm-syntax.path = "../cm-syntax" 33 | config.path = "../config" 34 | cov-mark.path = "../cov-mark" 35 | input.path = "../input" 36 | mlb-syntax.path = "../mlb-syntax" 37 | slash-var-path.path = "../slash-var-path" 38 | sml-file.path = "../sml-file" 39 | sml-syntax.path = "../sml-syntax" 40 | -------------------------------------------------------------------------------- /crates/tests/src/infix_without_op.rs: -------------------------------------------------------------------------------- 1 | //! Using infix operators without `op`. 2 | 3 | use crate::check::check; 4 | 5 | #[test] 6 | fn exp() { 7 | check( 8 | r#" 9 | datatype d = D of int * string 10 | val D _ = D (3, "hi") 11 | infix D 12 | val _ D _ = D (3, "hi") 13 | (** ^ infix name used as non-infix without `op`: `D` *) 14 | "#, 15 | ); 16 | } 17 | 18 | #[test] 19 | fn pat() { 20 | check( 21 | r#" 22 | datatype d = D of int * string 23 | val D _ = D (3, "hi") 24 | infix D 25 | val D _ = 3 D "hi" 26 | (** ^ infix name used as non-infix without `op` *) 27 | "#, 28 | ); 29 | } 30 | 31 | #[test] 32 | fn fun_head() { 33 | // with some effort, this could be the actual infix without op error 34 | check( 35 | r" 36 | fun f (_, _) = 1 37 | fun _ g _ = 2 38 | (** ^ expected a name *) 39 | infix h 40 | fun _ h _ = 3 41 | ", 42 | ); 43 | } 44 | 45 | #[test] 46 | fn cons_not_atomic() { 47 | check( 48 | r" 49 | fun map f [] = [] 50 | | map f x::xs = f x :: map f xs 51 | (** ^^ infix name used as non-infix without `op` *) 52 | ", 53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /LICENSE-MIT.md: -------------------------------------------------------------------------------- 1 | # The MIT License 2 | 3 | Copyright 2020 Ariel Davis. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /crates/mlb-statics/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mlb-statics" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | test = false 13 | doctest = false 14 | 15 | [dependencies] 16 | diagnostic.workspace = true 17 | fast-hash.workspace = true 18 | paths.workspace = true 19 | sml-libs.workspace = true 20 | str-util.workspace = true 21 | text-pos.workspace = true 22 | text-size-util.workspace = true 23 | 24 | config.path = "../config" 25 | cov-mark.path = "../cov-mark" 26 | mlb-hir.path = "../mlb-hir" 27 | sml-comment.path = "../sml-comment" 28 | sml-file-syntax.path = "../sml-file-syntax" 29 | sml-file.path = "../sml-file" 30 | sml-fixity.path = "../sml-fixity" 31 | sml-hir-lower.path = "../sml-hir-lower" 32 | sml-hir.path = "../sml-hir" 33 | sml-namespace.path = "../sml-namespace" 34 | sml-statics-types.path = "../sml-statics-types" 35 | sml-statics.path = "../sml-statics" 36 | sml-syntax.path = "../sml-syntax" 37 | 38 | [features] 39 | sync = ["sml-statics/sync", "sml-statics-types/sync"] 40 | -------------------------------------------------------------------------------- /crates/sml-syntax/build.rs: -------------------------------------------------------------------------------- 1 | //! Generating Rust code from the ungrammar. 2 | 3 | use fast_hash::{FxHashMap, map}; 4 | 5 | fn main() { 6 | let doc = code_h2_md_map::get(include_str!("../../docs/tokens.md"), |tok| { 7 | // NOTE: this used to be a sml code block, but the VS Code Markdown viewer doesn't do well with 8 | // a horizontal rule followed by a code block. 9 | format!("Token: `{tok}`\n") 10 | }); 11 | let doc: FxHashMap<_, _> = doc.iter().map(|(&k, v)| (k, v.as_str())).collect(); 12 | let special = map([ 13 | ("Name", "a name"), 14 | ("TyVar", "a type variable"), 15 | ("IntLit", "an integer literal"), 16 | ("RealLit", "a real literal"), 17 | ("WordLit", "a word literal"), 18 | ("CharLit", "a character literal"), 19 | ("StringLit", "a string literal"), 20 | ]); 21 | let options = syntax_gen::Options { 22 | lang: "SML", 23 | trivia: &["Whitespace", "BlockComment", "Invalid"], 24 | grammar: include_str!("syntax.ungram"), 25 | doc: &doc, 26 | special: &special, 27 | file: file!(), 28 | }; 29 | syntax_gen::get(&options); 30 | } 31 | -------------------------------------------------------------------------------- /crates/sml-statics-types/src/mode.rs: -------------------------------------------------------------------------------- 1 | //! See [`Mode`]. 2 | 3 | /// The mode for checking. 4 | #[derive(Debug, Clone, Copy)] 5 | pub enum Mode { 6 | /// Regular checking. The default. 7 | Regular(Option), 8 | /// Built-in library checking. Notably, ascription structure expressions will not check to see if 9 | /// they actually match the signature. 10 | /// 11 | /// The string is the name of the built-in library file. 12 | BuiltinLib(&'static str), 13 | /// Only used for path ordering. 14 | /// 15 | /// Since path ordering only cares about structure-level name resolution, we can skip lots of 16 | /// statics checks in this mode for better performance. We probably don't actually skip all the 17 | /// checks that we conceivably could skip, but that's ok from a correctness standpoint. 18 | PathOrder, 19 | /// Populate extra info for running the dynamics. 20 | Dynamics, 21 | } 22 | 23 | impl Mode { 24 | /// Returns whether this is the "path order" mode. 25 | #[must_use] 26 | pub fn is_path_order(&self) -> bool { 27 | matches!(self, Mode::PathOrder) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /crates/tests/src/deviations/successor_ml/opt_semi.rs: -------------------------------------------------------------------------------- 1 | //! Tests for a trailing `;` at the end of a `let` expression sequence. 2 | 3 | use crate::check::{check, check_multi, raw}; 4 | 5 | #[test] 6 | fn e1_default_disallow() { 7 | check( 8 | r" 9 | val _ = let in 1; end 10 | (** ^ trailing `;` *) 11 | ", 12 | ); 13 | } 14 | 15 | #[test] 16 | fn e2_default_disallow() { 17 | check( 18 | r" 19 | val _ = let in 1; 2; end 20 | (** ^ trailing `;` *) 21 | ", 22 | ); 23 | } 24 | 25 | #[test] 26 | fn config_allow() { 27 | let config = r" 28 | version = 1 29 | language.successor-ml.opt-semi = true 30 | "; 31 | let sml = r" 32 | val () = let in 1; end 33 | val () = let in 1; 2; end 34 | "; 35 | check_multi(raw::singleton(config, sml)); 36 | } 37 | 38 | #[test] 39 | fn unit_ty() { 40 | let config = r" 41 | version = 1 42 | language.successor-ml.opt-semi = true 43 | "; 44 | let sml = r" 45 | val x = let in 1; end 46 | fun oop (y: int) = y + 1 47 | val _ = oop x 48 | (** ^ expected `int`, found `unit` *) 49 | "; 50 | check_multi(raw::singleton(config, sml)); 51 | } 52 | -------------------------------------------------------------------------------- /docs/diagnostics/2008.md: -------------------------------------------------------------------------------- 1 | # 2008 2 | 3 | A string escape was invalid. 4 | 5 | ```sml 6 | val s = "this doesn't work: \c" 7 | (** ^ invalid string escape *) 8 | ``` 9 | 10 | String escapes start with `\`. Valid simple escapes are: 11 | 12 | | Escape | Meaning | 13 | | ------ | --------------- | 14 | | `\a` | Alert | 15 | | `\b` | Backspace | 16 | | `\t` | Horizontal tab | 17 | | `\n` | Newline | 18 | | `\v` | Vertical tab | 19 | | `\f` | Form feed | 20 | | `\r` | Carriage return | 21 | | `\"` | `"` | 22 | | `\\` | `\` | 23 | 24 | Valid complex escapes are: 25 | 26 | - `\^C`, where `C` is a character in the ASCII range 64 (`A`) to 95 (`_`). This is useful for ASCII control characters, like `\^[`. 27 | - `\DDD`, where each `D` is a decimal digit: `0-9`. 28 | - `\uXXXX`, where each `X` is a hexadecimal digit: `0-9`, `A-F`, or `a-f`. 29 | 30 | ## To fix 31 | 32 | Only use valid escapes. 33 | 34 | Consult the above to see what escapes are valid. 35 | 36 | ```sml 37 | val s = "this has\na newline" 38 | ``` 39 | -------------------------------------------------------------------------------- /crates/sml-parse/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Parsing tokens into a concrete syntax tree. 2 | 3 | #![allow(clippy::too_many_lines)] 4 | 5 | mod dec; 6 | mod exp; 7 | mod pat; 8 | mod root; 9 | mod ty; 10 | mod util; 11 | 12 | pub mod parser; 13 | 14 | use sml_syntax::ast::{AstNode as _, Root}; 15 | use sml_syntax::kind::SyntaxKind as SK; 16 | use token::Token; 17 | 18 | /// The result of a parse. 19 | #[derive(Debug)] 20 | pub struct Parse { 21 | /// The root. 22 | pub root: Root, 23 | /// The errors encountered when parsing. 24 | pub errors: Vec, 25 | } 26 | 27 | /// Returns a parse of the tokens. 28 | /// 29 | /// # Panics 30 | /// 31 | /// If casting the root node to a Root failed (an internal error). 32 | pub fn get(tokens: &[Token<'_, SK>], fe: &mut sml_fixity::Env) -> Parse { 33 | let mut p = parser::Parser::new(tokens); 34 | root::root(&mut p, fe); 35 | let mut sink = event_parse::rowan_sink::RowanSink::default(); 36 | p.finish(&mut sink); 37 | let (node, errors) = sink.finish(); 38 | Parse { root: Root::cast(node).unwrap(), errors: errors.into_iter().map(parser::Error).collect() } 39 | } 40 | -------------------------------------------------------------------------------- /crates/sml-statics-types/src/disallow.rs: -------------------------------------------------------------------------------- 1 | //! See [`Disallow`]. 2 | 3 | use crate::item::Item; 4 | use std::fmt; 5 | 6 | /// A way in which something is not allowed. 7 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 8 | pub enum Disallow { 9 | /// It was directly disallowed by name, not transitively. 10 | Directly, 11 | } 12 | 13 | impl fmt::Display for Disallow { 14 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 15 | match self { 16 | Disallow::Directly => f.write_str("directly"), 17 | } 18 | } 19 | } 20 | 21 | /// An error when trying to disallow a path. 22 | #[derive(Debug)] 23 | pub enum Error { 24 | /// We tried to disallow something that doesn't exist. 25 | Undefined(Item, str_util::Name), 26 | /// We tried to disallow something that was already disallowed. 27 | Already(Disallow), 28 | } 29 | 30 | impl fmt::Display for Error { 31 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 32 | match &self { 33 | Self::Undefined(item, name) => write!(f, "undefined {item}: `{name}`"), 34 | Self::Already(d) => write!(f, "already disallowed {d}"), 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /docs/diagnostics/5038.md: -------------------------------------------------------------------------------- 1 | # 5038 2 | 3 | There was a usage of the top-level `use` function, which has implementation-defined semantics. 4 | 5 | 6 | 7 | ```sml 8 | val () = use "foo.sml" 9 | (** ^^^^^^^^^^^^^ `use` ignored *) 10 | val x = Foo.bar + 3 11 | ``` 12 | 13 | This function is provided by some SML implementations, like SML/NJ, but not others, like MLton. When it is provided, its approximate semantics are to "load" the contents of the SML file with the given name, bringing all of its definitions into scope in the file that called `use`. 14 | 15 | This is sometimes used for simple multi-file SML projects. However, Millet only supports using "group files", aka ML Basis and SML/NJ CM, to coordinate multi-file SML projects. 16 | 17 | ## To fix 18 | 19 | Use group files to inform Millet about the dependencies between SML files. 20 | 21 | If you wish to also compile and run your SML project with group files, you can remove the calls to `use`. If, however, you only want to use group files for Millet, and still want to use `use` when compiling your project, you can ignore this error in your `millet.toml`. 22 | -------------------------------------------------------------------------------- /editors/vscode/languages/sml-nj-cm/syntax.json: -------------------------------------------------------------------------------- 1 | { 2 | "scopeName": "source.sml-nj-cm", 3 | "patterns": [ 4 | { "include": "#lineComment" }, 5 | { "include": "#blockComment" }, 6 | { "include": "#preprocessorStart" }, 7 | { "include": "#controlKeyword" }, 8 | { "include": "#otherKeyword" } 9 | ], 10 | "repository": { 11 | "lineComment": { 12 | "name": "comment.line.sml-nj-cm", 13 | "begin": ";", 14 | "end": "$" 15 | }, 16 | "blockComment": { 17 | "name": "comment.block.sml-nj-cm", 18 | "begin": "\\(\\*", 19 | "end": "\\*\\)", 20 | "patterns": [{ "include": "#blockComment" }] 21 | }, 22 | "preprocessorStart": { 23 | "name": "keyword.control.directive.sml-nj-cm", 24 | "match": "^#(elif|else|endif|error|if)" 25 | }, 26 | "controlKeyword": { 27 | "name": "keyword.control.sml-nj-cm", 28 | "match": "\\b(group|Group|GROUP|library|Library|LIBRARY|source|Source|SOURCE|is|IS)\\b" 29 | }, 30 | "otherKeyword": { 31 | "name": "keyword.other.sml-nj-cm", 32 | "match": "\\b(functor|funsig|signature|structure)\\b" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for your interest in contributing to Millet! 4 | 5 | Note that this project has a [Code of Conduct][coc]. We expect all members of the community (including community leaders) will adhere to this code. 6 | 7 | - Got a general question? Want to discuss the project? Head to the [Discord][discord]. 8 | - Found a bug? Have a feature request? File an [issue][]. 9 | - Before opening a new issue, please search for existing issues, to see if your issue has already been filed. 10 | - Prefer using one of the existing issue templates. 11 | - Please follow the prompts in the issue templates to help you fill it out. 12 | - Note that we reserve the right to [reject][no] feature requests. 13 | - Want to contribute code? Fork the project and open a PR. 14 | - Necessary local dependencies are described in the README. 15 | - Your PR must pass CI to be merged. 16 | - To try locally, run `cargo xtask ci`. 17 | 18 | [coc]: /docs/CODE_OF_CONDUCT.md 19 | [discord]: https://discord.gg/hgPSUby2Ny 20 | [issue]: https://github.com/azdavis/millet/issues/new/choose 21 | [no]: https://opensource.guide/best-practices/#learning-to-say-no 22 | -------------------------------------------------------------------------------- /crates/analysis/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "analysis" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | publish.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [lib] 12 | test = false 13 | doctest = false 14 | 15 | [dependencies] 16 | diagnostic.workspace = true 17 | elapsed.workspace = true 18 | fast-hash.workspace = true 19 | fmt-util.workspace = true 20 | paths.workspace = true 21 | str-util.workspace = true 22 | text-pos.workspace = true 23 | text-size-util.workspace = true 24 | 25 | config.path = "../config" 26 | input.path = "../input" 27 | mlb-statics.path = "../mlb-statics" 28 | sml-file-syntax.path = "../sml-file-syntax" 29 | sml-fixity.path = "../sml-fixity" 30 | sml-naive-fmt.path = "../sml-naive-fmt" 31 | sml-hir.path = "../sml-hir" 32 | sml-namespace.path = "../sml-namespace" 33 | sml-statics.path = "../sml-statics" 34 | sml-statics-types.path = "../sml-statics-types" 35 | sml-symbol-kind.path = "../sml-symbol-kind" 36 | sml-syntax.path = "../sml-syntax" 37 | 38 | [features] 39 | sync = [ 40 | "mlb-statics/sync", 41 | "sml-statics/sync", 42 | "sml-statics-types/sync", 43 | "sml-symbol-kind/sync", 44 | ] 45 | -------------------------------------------------------------------------------- /docs/diagnostics/5012.md: -------------------------------------------------------------------------------- 1 | # 5012 2 | 3 | A binding, like with `val`, was not exhaustive. 4 | 5 | This is effectively the same error as 5011, but for singular bindings. 6 | 7 | ```sml 8 | datatype d = A of string | B of int | C of bool 9 | 10 | fun f (x : d) : string = 11 | let 12 | val A y = x 13 | (** ^^^ non-exhaustive binding: missing `B _`, `C _` *) 14 | in 15 | y 16 | end 17 | ``` 18 | 19 | The pattern in a `val` binding ought to be "irrefutable", to wit, it alone ought to match all possible values of the type of the expression. For example: 20 | 21 | - The wildcard pattern `_` is irrefutable. 22 | - Variable patterns like `x` and `y` are irrefutable. 23 | - Tuple patterns composed of irrefutable patterns are irrefutable. 24 | - If a `datatype` has only one constructor, then a pattern of that constructor, additionally with an irrefutable pattern argument if one is needed, is irrefutable. 25 | 26 | ## To fix 27 | 28 | Use a `case` or similar instead. 29 | 30 | ```sml 31 | datatype d = A of string | B of int | C of bool 32 | 33 | fun f (x : d) : string = 34 | case x of 35 | A s => s 36 | | B i => Int.toString i 37 | | C b => Bool.toString b 38 | ``` 39 | -------------------------------------------------------------------------------- /crates/tests/src/check/input.rs: -------------------------------------------------------------------------------- 1 | //! Text input with in-memory files. 2 | 3 | use std::collections::BTreeMap; 4 | use std::sync::LazyLock; 5 | 6 | /// Get an input and path store from an iterator of (filename, contents). 7 | pub(crate) fn get<'a, I>(iter: I) -> (input::Input, paths::Store) 8 | where 9 | I: IntoIterator, 10 | { 11 | _ = env_logger::builder().is_test(true).try_init(); 12 | let mut map = BTreeMap::::default(); 13 | for (name, contents) in iter { 14 | let path = ROOT.as_clean_path().join(name); 15 | assert!(map.insert(path, contents.to_owned()).is_none(), "duplicate key: {name}"); 16 | } 17 | let fs = paths::MemoryFileSystem::new(map); 18 | let mut store = paths::Store::new(); 19 | let input = input::Input::new(&fs, &mut store, ROOT.as_clean_path()); 20 | (input, store) 21 | } 22 | 23 | /// The real, canonical root file system path, aka `/`. Performs I/O on first access. But this 24 | /// shouldn't fail because the root should be readable. (Otherwise, where are these tests being 25 | /// run?) 26 | pub(crate) static ROOT: LazyLock = 27 | LazyLock::new(paths::MemoryFileSystem::root); 28 | -------------------------------------------------------------------------------- /crates/sml-statics/src/top_dec/ty_con_paths.rs: -------------------------------------------------------------------------------- 1 | //! Collect all the paths to ty cons in an env. 2 | 3 | use crate::get_env::{GetEnvResult, get_env}; 4 | use fast_hash::FxHashSet; 5 | use sml_statics_types::env::Env; 6 | 7 | pub(crate) fn get(env: &Env, path: &sml_path::Path) -> GetEnvResult> { 8 | let got_env = get_env(env, path.all_names()); 9 | let val = got_env.val.map(|env| { 10 | let mut ty_cons = FxHashSet::::default(); 11 | go(&mut Vec::new(), &mut ty_cons, env); 12 | ty_cons 13 | }); 14 | GetEnvResult { val, disallow: got_env.disallow } 15 | } 16 | 17 | fn go(prefix: &mut Vec, ac: &mut FxHashSet, env: &Env) { 18 | ac.extend(env.ty_env.iter().map(|(name, _)| sml_path::Path::new(prefix.clone(), name.clone()))); 19 | for (name, env) in env.str_env.iter() { 20 | prefix.push(name.clone()); 21 | go(prefix, ac, env); 22 | prefix.pop().unwrap(); 23 | } 24 | } 25 | 26 | /// Joins two sequential paths into one. 27 | pub(crate) fn join_paths(p1: &sml_path::Path, p2: &sml_path::Path) -> sml_path::Path { 28 | sml_path::Path::new(p1.all_names().chain(p2.prefix()).cloned(), p2.last().clone()) 29 | } 30 | -------------------------------------------------------------------------------- /crates/analysis/src/matcher.rs: -------------------------------------------------------------------------------- 1 | //! Displaying a matcher, for the `fill case` code action. 2 | 3 | use fmt_util::sep_seq; 4 | use std::fmt; 5 | 6 | pub(crate) fn display( 7 | starting_bar: bool, 8 | variants: &[(str_util::Name, bool)], 9 | ) -> impl fmt::Display { 10 | CaseDisplay { starting_bar, variants } 11 | } 12 | 13 | struct CaseDisplay<'a> { 14 | starting_bar: bool, 15 | variants: &'a [(str_util::Name, bool)], 16 | } 17 | 18 | impl fmt::Display for CaseDisplay<'_> { 19 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 20 | write!(f, " ")?; 21 | if self.starting_bar { 22 | write!(f, "| ")?; 23 | } else { 24 | write!(f, " ")?; 25 | } 26 | let iter = self.variants.iter().map(|&(ref name, has_arg)| ArmDisplay { name, has_arg }); 27 | sep_seq(f, "\n | ", iter)?; 28 | writeln!(f) 29 | } 30 | } 31 | 32 | struct ArmDisplay<'a> { 33 | name: &'a str_util::Name, 34 | has_arg: bool, 35 | } 36 | 37 | impl fmt::Display for ArmDisplay<'_> { 38 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 39 | write!(f, "{}", self.name)?; 40 | if self.has_arg { 41 | write!(f, " _")?; 42 | } 43 | write!(f, " => _") 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /crates/sml-namespace/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Different namespaces for various SML language definition constructs. 2 | 3 | use std::fmt; 4 | 5 | /// A namespace for "module-level" items. 6 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 7 | pub enum Module { 8 | /// `structure` 9 | Structure, 10 | /// `signature` 11 | Signature, 12 | /// `functor` 13 | Functor, 14 | } 15 | 16 | impl fmt::Display for Module { 17 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 18 | match self { 19 | Module::Structure => f.write_str("structure"), 20 | Module::Signature => f.write_str("signature"), 21 | Module::Functor => f.write_str("functor"), 22 | } 23 | } 24 | } 25 | 26 | /// A kind of symbol. 27 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 28 | pub enum SymbolKind { 29 | /// A `structure`. 30 | Structure, 31 | /// A `signature`. 32 | Signature, 33 | /// A `functor`. 34 | Functor, 35 | /// A `type` or `datatype`. 36 | Type, 37 | /// A `datatype` constructor. 38 | Constructor, 39 | /// An `exception`. 40 | Exception, 41 | /// A value with function type. 42 | Function, 43 | /// A regular value. 44 | Value, 45 | } 46 | -------------------------------------------------------------------------------- /crates/cm-syntax/src/lower.rs: -------------------------------------------------------------------------------- 1 | //! Lowering a parsed CM file and check it for validity. 2 | 3 | use crate::types::{ 4 | Class, CmFile, Error, ErrorKind, ParseRoot, ParsedPath, PathOrStdBasis, Result, 5 | }; 6 | use text_size_util::WithRange; 7 | 8 | pub(crate) fn get(root: ParseRoot) -> Result { 9 | let mut paths = Vec::>::new(); 10 | for member in root.members { 11 | let cls = member.class(); 12 | let path = match member.pathname.val { 13 | PathOrStdBasis::Path(p) => p, 14 | PathOrStdBasis::StdBasis => continue, 15 | }; 16 | let kind = match cls { 17 | Some(class) => match class.val { 18 | Class::PathKind(k) => k, 19 | Class::Other(s) => { 20 | return Err(Error::new(ErrorKind::UnsupportedClass(path, s), class.range)); 21 | } 22 | }, 23 | None => { 24 | return Err(Error::new(ErrorKind::CouldNotDetermineClass(path), member.pathname.range)); 25 | } 26 | }; 27 | paths.push(WithRange { val: ParsedPath { kind, path }, range: member.pathname.range }); 28 | } 29 | Ok(CmFile { 30 | kind: root.kind, 31 | first_token_range: root.first_token_range, 32 | export: root.export, 33 | paths, 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /crates/lex-util/src/block_comment.rs: -------------------------------------------------------------------------------- 1 | //! Handling nested block comments delimited with `(*` and `*)`. 2 | 3 | /// A marker signifying a block comment was consumed. 4 | #[derive(Debug)] 5 | pub struct Consumed; 6 | 7 | /// An error for an unclosed comment. 8 | #[derive(Debug)] 9 | pub struct UnclosedError; 10 | 11 | /// Requires `bs.get(*idx) == Some(&b)`. 12 | /// 13 | /// # Errors 14 | /// 15 | /// If the comment was not closed. 16 | pub fn get(idx: &mut usize, b: u8, bs: &[u8]) -> Result, UnclosedError> { 17 | debug_assert_eq!(bs.get(*idx), Some(&b)); 18 | if b == b'(' && bs.get(*idx + 1) == Some(&b'*') { 19 | *idx += 2; 20 | let mut level = 1_usize; 21 | loop { 22 | match (bs.get(*idx), bs.get(*idx + 1)) { 23 | (Some(&b'('), Some(&b'*')) => { 24 | *idx += 2; 25 | level += 1; 26 | } 27 | (Some(&b'*'), Some(&b')')) => { 28 | *idx += 2; 29 | level -= 1; 30 | if level == 0 { 31 | return Ok(Some(Consumed)); 32 | } 33 | } 34 | (Some(_), Some(_)) => *idx += 1, 35 | (_, None) => return Err(UnclosedError), 36 | (None, Some(_)) => unreachable!("cannot have a byte after EOF"), 37 | } 38 | } 39 | } 40 | Ok(None) 41 | } 42 | -------------------------------------------------------------------------------- /crates/tests/src/goto_def.rs: -------------------------------------------------------------------------------- 1 | //! Tests for go to def. 2 | 3 | use crate::check::check; 4 | 5 | #[test] 6 | fn val() { 7 | check( 8 | r" 9 | val x = 3 10 | (** ^ def: x *) 11 | val y = x 12 | (** ^ use: x *) 13 | ", 14 | ); 15 | } 16 | 17 | #[test] 18 | fn fun() { 19 | check( 20 | r" 21 | fun foo () = () 22 | (** ^^^^^^^^^^^ def: foo *) 23 | val y = foo 24 | (** ^^^ use: foo *) 25 | ", 26 | ); 27 | } 28 | 29 | #[test] 30 | fn type_() { 31 | check( 32 | r" 33 | type bar = int 34 | (** + def: bar *) 35 | val y : bar = 3 36 | (** ^^^ use: bar *) 37 | ", 38 | ); 39 | } 40 | 41 | #[test] 42 | fn datatype_con() { 43 | check( 44 | r" 45 | datatype foo = Bar | Quz of int 46 | (** + def: Bar *) 47 | val y = Bar 48 | (** ^^^ use: Bar *) 49 | ", 50 | ); 51 | } 52 | 53 | #[test] 54 | fn structure() { 55 | check( 56 | r" 57 | structure A = struct end 58 | (** + def: A *) 59 | structure B = A 60 | (** ^ use: A *) 61 | ", 62 | ); 63 | } 64 | 65 | #[test] 66 | fn structure_open() { 67 | check( 68 | r" 69 | structure A1 = struct 70 | structure A2 = struct end 71 | (** + def: A2 *) 72 | end 73 | 74 | structure B1 = struct 75 | open A1 76 | structure B2 = A2 77 | (** ^^ use: A2 *) 78 | end 79 | ", 80 | ); 81 | } 82 | -------------------------------------------------------------------------------- /crates/tests/build.rs: -------------------------------------------------------------------------------- 1 | //! Make the diagnostics tests. 2 | 3 | use quote::{format_ident, quote}; 4 | use std::path::Path; 5 | 6 | fn root_dir() -> &'static Path { 7 | Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap() 8 | } 9 | 10 | fn main() { 11 | let entries = std::fs::read_dir(root_dir().join("docs").join("diagnostics")).unwrap(); 12 | let tests = entries.map(|entry| { 13 | let entry = entry.unwrap(); 14 | let path = entry.path(); 15 | let number = path.file_stem().unwrap().to_str().unwrap(); 16 | let name = format_ident!("d{number}"); 17 | // NOTE: this has 5 parents (aka ..) in the path. we are depending on OUT_DIR being 5 levels 18 | // deep from the repo root. at time of writing the out dir is always like this: 19 | // 20 | // target/debug/build/tests-XXXXXX/out 21 | // 22 | // this might be a fragile assumption (cough cough Hyrum's Law). 23 | let include_path = format!("../../../../../docs/diagnostics/{number}.md"); 24 | quote! { 25 | #[test] 26 | fn #name() { 27 | check(include_str!(#include_path)); 28 | } 29 | } 30 | }); 31 | let all = quote! { 32 | use crate::check::markdown::check; 33 | 34 | #(#tests)* 35 | }; 36 | write_rs_tokens::go(all, "diagnostics.rs"); 37 | } 38 | -------------------------------------------------------------------------------- /crates/tests/src/mismatched_fields.rs: -------------------------------------------------------------------------------- 1 | //! Tests when two record/tuple types have mismatched fields. 2 | 3 | use crate::check::check; 4 | 5 | #[test] 6 | fn missing_1() { 7 | check( 8 | r" 9 | val a : { x : int, y: string } = { x = 3 } 10 | (** + missing 1 field: `y` *) 11 | ", 12 | ); 13 | } 14 | 15 | #[test] 16 | fn extra_1() { 17 | check( 18 | r#" 19 | val a : { x : int, y: string } = { x = 3, y = "hi", z = false } 20 | (** + has 1 extra field: `z` *) 21 | "#, 22 | ); 23 | } 24 | 25 | #[test] 26 | fn both_1() { 27 | check( 28 | r#" 29 | val a : { x : int, y: string } = { y = "hi", w = false } 30 | (** + missing 1 field: `x`, and has 1 extra field: `w` *) 31 | "#, 32 | ); 33 | } 34 | 35 | #[test] 36 | fn missing_2() { 37 | check( 38 | r" 39 | val a : { x : int, y: string } = () 40 | (** + missing 2 fields: `x`, `y` *) 41 | ", 42 | ); 43 | } 44 | 45 | #[test] 46 | fn extra_2() { 47 | check( 48 | r#" 49 | val a : { x : int, y: string } = { x = 3, y = "hi", z = false, w = 1.1 } 50 | (** + has 2 extra fields: `w`, `z` *) 51 | "#, 52 | ); 53 | } 54 | 55 | #[test] 56 | fn both_2() { 57 | check( 58 | r" 59 | val a : { x : int, y: string } = { z = false, w = 1.1 } 60 | (** + missing 2 fields: `x`, `y`, and has 2 extra fields: `w`, `z` *) 61 | ", 62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /docs/diagnostics/5013.md: -------------------------------------------------------------------------------- 1 | # 5013 2 | 3 | A pattern match treated a value as if it were a pattern. 4 | 5 | ```sml 6 | structure S = struct 7 | val x = 3 8 | end 9 | 10 | fun f y = 11 | case y of 12 | S.x => 1 13 | (** ^^^ value binding used as a pattern *) 14 | | 4 => 5 15 | | _ => 6 16 | ``` 17 | 18 | This error is emitted when a path (in this example `S.x`) is encountered, but the path is a value binding, not a constructor. 19 | 20 | Only values that are known to be constructors are allowed in patterns. All constructors are values, but not all values are constructors. 21 | 22 | In this example, SML does not, for instance, enter the case arm for `1` if `y` is equal to whatever value got bound to `S.x` (in this case `3`). 23 | 24 | ## To fix 25 | 26 | Try one of the following: 27 | 28 | - If the value is known to be a particular constructor, use that constructor verbatim in the pattern match. 29 | 30 | ```sml 31 | fun f y = 32 | case y of 33 | 3 => 1 34 | | 4 => 5 35 | | _ => 6 36 | ``` 37 | 38 | - Check for equality another way, for instance with `=`. 39 | 40 | ```sml 41 | structure S = struct 42 | val x = 3 43 | end 44 | 45 | fun f y = 46 | if y = S.x then 47 | 1 48 | else if y = 4 then 49 | 5 50 | else 51 | 6 52 | ``` 53 | -------------------------------------------------------------------------------- /crates/lang-srv/src/response.rs: -------------------------------------------------------------------------------- 1 | //! Respond to requests. 2 | 3 | use crate::{convert, cx, state::St}; 4 | use lsp_server::Response; 5 | 6 | pub(crate) fn handle(st: &mut St, res: Response) { 7 | log::info!("got response: {res:?}"); 8 | let Some(data) = st.cx.req_queue.outgoing.complete(res.id.clone()) else { 9 | log::warn!("received response for non-queued request: {res:?}"); 10 | return; 11 | }; 12 | let Some(code) = data else { 13 | log::info!("no error code associated with this request"); 14 | return; 15 | }; 16 | let Some(val) = res.result else { 17 | log::info!("user did not click to look at the error URL"); 18 | return; 19 | }; 20 | let item = match serde_json::from_value::(val) { 21 | Ok(x) => x, 22 | Err(e) => { 23 | log::error!("registered an error code, but got no message action item: {e}"); 24 | return; 25 | } 26 | }; 27 | if item.title != cx::HELP_FIX { 28 | log::warn!("unknown item.title: {}", item.title); 29 | return; 30 | } 31 | st.cx.send_request::( 32 | lsp_types::ShowDocumentParams { 33 | uri: convert::error_url(code), 34 | external: Some(true), 35 | take_focus: Some(true), 36 | selection: None, 37 | }, 38 | None, 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /crates/tests/src/dupe/spec.rs: -------------------------------------------------------------------------------- 1 | //! Duplicate specifications. 2 | 3 | use crate::check::check; 4 | 5 | #[test] 6 | fn val() { 7 | check( 8 | r" 9 | signature S = sig 10 | val x: int val x: int 11 | (** ^^^^^^^^^^ duplicate value: `x` *) 12 | end 13 | ", 14 | ); 15 | } 16 | 17 | #[test] 18 | fn ty() { 19 | check( 20 | r" 21 | signature S = sig 22 | type t type t 23 | (** ^^^^^^ duplicate type: `t` *) 24 | end 25 | ", 26 | ); 27 | } 28 | 29 | #[test] 30 | fn eq_ty() { 31 | check( 32 | r" 33 | signature S = sig 34 | eqtype t eqtype t 35 | (** ^^^^^^^^ duplicate type: `t` *) 36 | end 37 | ", 38 | ); 39 | } 40 | 41 | #[test] 42 | fn datatype() { 43 | check( 44 | r" 45 | signature S = sig 46 | datatype t = A datatype t = B 47 | (** ^^^^^^^^^^^^^^ duplicate type: `t` *) 48 | end 49 | ", 50 | ); 51 | } 52 | 53 | #[test] 54 | fn exception() { 55 | check( 56 | r" 57 | signature S = sig 58 | exception E exception E 59 | (** ^^^^^^^^^^^ duplicate value: `E` *) 60 | end 61 | ", 62 | ); 63 | } 64 | 65 | #[test] 66 | fn structure() { 67 | check( 68 | r" 69 | signature S = sig 70 | structure A : sig end structure A : sig end 71 | (** ^^^^^^^^^^^^^^^^^^^^^ duplicate structure: `A` *) 72 | end 73 | ", 74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /crates/tests/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Tests. 2 | //! 3 | //! - If you're not sure where to put a test, put it in misc. 4 | //! - If you have many similar tests, put them in an existing or new module. 5 | 6 | #![cfg(test)] 7 | #![allow(clippy::single_match_else)] 8 | 9 | // #[allow(clippy::pedantic, missing_debug_implementations, missing_docs)] 10 | mod diagnostics { 11 | include!(concat!(env!("OUT_DIR"), "/diagnostics.rs")); 12 | } 13 | 14 | mod basis_path_var; 15 | mod big; 16 | mod cannot_rebind; 17 | mod check; 18 | mod circularity; 19 | mod common; 20 | mod completions; 21 | mod datatype_copy; 22 | mod deviations; 23 | mod disallow; 24 | mod docs; 25 | mod dupe; 26 | mod empty; 27 | mod equality; 28 | mod exn; 29 | mod fixity; 30 | mod forbid_opaque_asc; 31 | mod functor; 32 | mod generalize; 33 | mod goto_def; 34 | mod hover; 35 | mod incomplete; 36 | mod infix_without_op; 37 | mod input; 38 | mod literal; 39 | mod local; 40 | mod matching; 41 | mod misc; 42 | mod mismatched_fields; 43 | mod num_record; 44 | mod open; 45 | mod overload; 46 | mod pat; 47 | mod repo; 48 | mod rest_pat; 49 | mod rust; 50 | mod sep; 51 | mod shadow; 52 | mod sig; 53 | mod sig_fun_file; 54 | mod smoke; 55 | mod std_basis; 56 | mod symbolic; 57 | mod ty_escape; 58 | mod ty_var; 59 | mod unused; 60 | mod use_builtin; 61 | mod val_rec; 62 | mod well_known; 63 | -------------------------------------------------------------------------------- /crates/tests/src/input/misc.rs: -------------------------------------------------------------------------------- 1 | //! Misc input tests. 2 | 3 | use crate::{check::check_bad_input, input::cm}; 4 | 5 | #[test] 6 | fn no_root_group_empty() { 7 | check_bad_input("", "no *.cm, *.mlb", []); 8 | } 9 | 10 | #[test] 11 | fn no_root_group_wrong_ext() { 12 | check_bad_input("", "no *.cm, *.mlb", [("foo.txt", "hey")]); 13 | } 14 | 15 | #[test] 16 | fn multiple_root_groups_err() { 17 | check_bad_input( 18 | "a.cm", 19 | "multiple *.cm or *.mlb files", 20 | [("a.cm", cm::EMPTY), ("b.cm", cm::EMPTY)], 21 | ); 22 | } 23 | 24 | #[test] 25 | fn self_cycle() { 26 | check_bad_input("a.cm", "there is a cycle", [("a.cm", "Group is a.cm")]); 27 | } 28 | 29 | #[test] 30 | fn mlb_cm_err() { 31 | check_bad_input("a.cm", "multiple *.cm or *.mlb files", [("a.cm", cm::EMPTY), ("a.mlb", "")]); 32 | } 33 | 34 | #[test] 35 | fn undefined_path_var_root() { 36 | check_bad_input("s.mlb", "undefined path variable: `FOO`", [("s.mlb", "$(FOO).sml")]); 37 | } 38 | 39 | #[test] 40 | fn undefined_path_var_import() { 41 | let config = r#" 42 | version = 1 43 | workspace.root = "a.mlb" 44 | "#; 45 | check_bad_input( 46 | "b.mlb", 47 | "undefined path variable: `BAR`", 48 | [(config::file::PATH, config), ("a.mlb", "b.mlb"), ("b.mlb", "$(BAR).sml")], 49 | ); 50 | cov_mark::check("undefined_path_var_import"); 51 | } 52 | -------------------------------------------------------------------------------- /crates/tests/src/shadow.rs: -------------------------------------------------------------------------------- 1 | //! Shadowing, as in `let a = false; let a = 2; let b = a + 3`. 2 | 3 | use crate::check::check; 4 | 5 | #[test] 6 | fn val() { 7 | check( 8 | r#" 9 | val x = "hey" 10 | val x = 3 11 | val _: int = x 12 | "#, 13 | ); 14 | } 15 | 16 | #[test] 17 | fn type_() { 18 | check( 19 | r" 20 | type t = string 21 | type t = int 22 | val _: t = 3 23 | ", 24 | ); 25 | } 26 | 27 | #[test] 28 | fn datatype() { 29 | check( 30 | r" 31 | datatype t = One 32 | datatype t = Two 33 | val _: t = Two 34 | ", 35 | ); 36 | } 37 | 38 | #[test] 39 | fn structure() { 40 | check( 41 | r#" 42 | structure S = struct val x = "hey" end 43 | structure S = struct val x = 3 end 44 | val _: int = S.x 45 | "#, 46 | ); 47 | } 48 | 49 | #[test] 50 | fn exception() { 51 | check( 52 | r" 53 | exception E 54 | exception E of int 55 | val _ = E: unit 56 | (** ^^^^^^^ expected `unit`, found `int -> exn` *) 57 | ", 58 | ); 59 | } 60 | 61 | #[test] 62 | fn signature() { 63 | check( 64 | r" 65 | signature SIG = sig end 66 | signature SIG = sig type t end 67 | 68 | structure S: SIG = struct type t = int end 69 | val _ = 3 : S.t 70 | ", 71 | ); 72 | } 73 | 74 | #[test] 75 | fn functor() { 76 | check( 77 | r" 78 | functor F() = struct end 79 | functor F() = struct type t = int end 80 | 81 | structure S = F() 82 | val _ = 3 : S.t 83 | ", 84 | ); 85 | } 86 | -------------------------------------------------------------------------------- /crates/tests/src/num_record.rs: -------------------------------------------------------------------------------- 1 | //! Tests that witness the fact that tuples, records, and `unit` are all eventually records. 2 | 3 | use crate::check::check; 4 | 5 | #[test] 6 | fn record_is_tuple() { 7 | check( 8 | r" 9 | type t = { 1: int, 2: bool } 10 | val _ = (): t 11 | (** ^^^^^ expected `int * bool`, found `unit` *) 12 | ", 13 | ); 14 | } 15 | 16 | #[test] 17 | fn zero_not_label() { 18 | check( 19 | r" 20 | type t = { 0: int, 1: bool } 21 | (** ^ invalid numeric label: numeric labels start at 1 *) 22 | ", 23 | ); 24 | } 25 | 26 | #[test] 27 | fn not_tuple_if_only_one_label() { 28 | check( 29 | r" 30 | type t = { 1: int } 31 | val _ = (): t 32 | (** ^^^^^ expected `{ 1 : int }`, found `unit` *) 33 | ", 34 | ); 35 | } 36 | 37 | #[test] 38 | fn not_tuple_if_not_all_labels_in_range() { 39 | check( 40 | r" 41 | type t = { 1: int, 3: bool } 42 | val _ = (): t 43 | (** ^^^^^ expected `{ 1 : int, 3 : bool }`, found `unit` *) 44 | ", 45 | ); 46 | } 47 | 48 | #[test] 49 | fn neg_label() { 50 | check( 51 | r" 52 | type t = { ~3: int, 1: bool } 53 | (** ^^ invalid numeric label: invalid digit found in string *) 54 | ", 55 | ); 56 | } 57 | 58 | #[test] 59 | fn hex_label() { 60 | check( 61 | r" 62 | type t = { 0x3: int, 1: bool } 63 | (** ^^^ invalid numeric label: invalid digit found in string *) 64 | ", 65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /crates/tests/src/generalize.rs: -------------------------------------------------------------------------------- 1 | //! Tests targeted at the "generalize" step of the algorithm we use to implement HM. 2 | 3 | use crate::check::check; 4 | 5 | #[test] 6 | fn no_over_generalize_infer_val() { 7 | check( 8 | r" 9 | fun id x = 10 | let val ret = x 11 | in ret end 12 | 13 | val y = id () 14 | val _ = y 15 | (** ^ hover: unit *) 16 | val z = id 3 17 | val _ = z 18 | (** ^ hover: int *) 19 | ", 20 | ); 21 | } 22 | 23 | #[test] 24 | fn no_over_generalize_infer_fun() { 25 | check( 26 | r" 27 | fun id x = 28 | let fun get () = x 29 | in get () end 30 | 31 | val y = id () 32 | val _ = y 33 | (** ^ hover: unit *) 34 | val z = id 3 35 | val _ = z 36 | (** ^ hover: int *) 37 | ", 38 | ); 39 | } 40 | 41 | #[test] 42 | fn no_over_generalize_fixed() { 43 | check( 44 | r" 45 | fun 'a id (x : 'a) = 46 | let fun get () = x 47 | in get () end 48 | 49 | val y = id () 50 | val _ = y 51 | (** ^ hover: unit *) 52 | val z = id 3 53 | val _ = z 54 | (** ^ hover: int *) 55 | ", 56 | ); 57 | } 58 | 59 | #[test] 60 | fn through_list() { 61 | check( 62 | r" 63 | exception E 64 | 65 | fun go x = 66 | let val y = (fn [a] => a | _ => raise E) [x] 67 | in y end 68 | ", 69 | ); 70 | } 71 | 72 | #[test] 73 | fn recurse() { 74 | check( 75 | r" 76 | fun go n = 77 | if n <= 0 then () 78 | else let val ret = go (n - 1) in ret end 79 | ", 80 | ); 81 | } 82 | -------------------------------------------------------------------------------- /crates/tests/src/deviations/successor_ml/withtype_spec.rs: -------------------------------------------------------------------------------- 1 | //! Tests for `withtype` in specifications. 2 | 3 | use crate::check::{check, check_multi, raw}; 4 | 5 | #[test] 6 | fn default_disallow() { 7 | check( 8 | r" 9 | signature STREAM = 10 | sig 11 | datatype 'a u = Nil | Cons of 'a * 'a t 12 | withtype 'a t = unit -> 'a u 13 | (** ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `withtype` in specifications *) 14 | end 15 | ", 16 | ); 17 | } 18 | 19 | #[test] 20 | fn config_allow() { 21 | let config = r" 22 | version = 1 23 | language.successor-ml.sig-withtype = true 24 | "; 25 | let sml = r" 26 | signature STREAM = 27 | sig 28 | datatype 'a u = Nil | Cons of 'a * 'a t 29 | withtype 'a t = unit -> 'a u 30 | end 31 | 32 | structure Stream : STREAM = 33 | struct 34 | datatype 'a u = Nil | Cons of 'a * (unit -> 'a u) 35 | type 'a t = unit -> 'a u 36 | end 37 | "; 38 | check_multi(raw::singleton(config, sml)); 39 | } 40 | 41 | #[test] 42 | fn config_allow_withtype_dec() { 43 | let config = r" 44 | version = 1 45 | language.successor-ml.sig-withtype = true 46 | "; 47 | let sml = r" 48 | signature STREAM = 49 | sig 50 | datatype 'a u = Nil | Cons of 'a * 'a t 51 | withtype 'a t = unit -> 'a u 52 | end 53 | 54 | structure Stream : STREAM = 55 | struct 56 | datatype 'a u = Nil | Cons of 'a * 'a t 57 | withtype 'a t = unit -> 'a u 58 | end 59 | "; 60 | check_multi(raw::singleton(config, sml)); 61 | } 62 | -------------------------------------------------------------------------------- /crates/sml-file/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Handling different kinds of SML files. 2 | 3 | use std::fmt; 4 | 5 | /// A kind of SML file. 6 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 7 | pub enum Kind { 8 | /// Regular SML files. 9 | Sml, 10 | /// Files that contain signature definitions (usually just one). 11 | Sig, 12 | /// Files that contain functor definitions (usually just one). 13 | Fun, 14 | } 15 | 16 | impl Kind { 17 | /// Returns whether this is the `Sig` or `Fun` variants. 18 | #[must_use] 19 | pub fn is_sig_or_fun(&self) -> bool { 20 | matches!(*self, Self::Sig | Self::Fun) 21 | } 22 | } 23 | 24 | /// The error returned from `from_str` for [`Kind`]. 25 | #[derive(Debug)] 26 | pub struct KindFromStrError(()); 27 | 28 | impl std::str::FromStr for Kind { 29 | type Err = KindFromStrError; 30 | 31 | fn from_str(s: &str) -> Result { 32 | if s.eq_ignore_ascii_case("sml") { 33 | return Ok(Kind::Sml); 34 | } 35 | if s.eq_ignore_ascii_case("sig") { 36 | return Ok(Kind::Sig); 37 | } 38 | if s.eq_ignore_ascii_case("fun") { 39 | return Ok(Kind::Fun); 40 | } 41 | Err(KindFromStrError(())) 42 | } 43 | } 44 | 45 | impl fmt::Display for Kind { 46 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 47 | let s = match self { 48 | Kind::Sml => "sml", 49 | Kind::Sig => "sig", 50 | Kind::Fun => "fun", 51 | }; 52 | f.write_str(s) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /crates/sml-statics/src/error/suggestion.rs: -------------------------------------------------------------------------------- 1 | //! See [`get`]. 2 | 3 | /// Suggests a bit of syntax that may be similar to the input. 4 | pub(crate) fn get(s: &str) -> Option<&'static str> { 5 | let ret = match s { 6 | "func" | "function" | "def" => "fun", 7 | "lambda" => "fn", 8 | "const" | "var" => "val", 9 | "enum" => "datatype", 10 | "begin" => "local", 11 | "module" | "namespace" => "structure", 12 | "match" | "switch" => "case", 13 | "elsif" | "elif" => "else if", 14 | "integer" => "int", 15 | "Integer" => "Int", 16 | "boolean" => "bool", 17 | "Boolean" => "Bool", 18 | "character" | "rune" => "char", 19 | "Character" | "Rune" => "Char", 20 | "uint" | "unsigned" => "word", 21 | "Uint" | "UInt" | "Unsigned" => "Word", 22 | "void" => "unit", 23 | "float" | "double" => "real", 24 | "Float" | "Double" => "Real", 25 | "str" => "string", 26 | "Str" => "String", 27 | "vec" => "vector", 28 | "Vec" => "Vector", 29 | "Optional" | "Maybe" => "option", 30 | // nil is the empty list 31 | "none" | "None" | "Nothing" | "null" | "NULL" | "nullptr" | "undefined" => "NONE", 32 | "some" | "Some" | "Just" => "SOME", 33 | "True" | "TRUE" | "YES" => "true", 34 | "False" | "FALSE" | "NO" => "false", 35 | "==" | "===" => "=", 36 | "!=" | "!==" => "<>", 37 | "&&" => "andalso", 38 | "||" | "or" => "orelse", 39 | _ => return None, 40 | }; 41 | Some(ret) 42 | } 43 | -------------------------------------------------------------------------------- /docs/diagnostics/5043.md: -------------------------------------------------------------------------------- 1 | # 5043 2 | 3 | A suspicious pattern match was detected. 4 | 5 | Users of SML unfamiliar with the semantics of pattern matching may sometimes try to use pattern matching to check an expression for equality with an existing binding, like this: 6 | 7 | ```sml 8 | val x = 3 9 | 10 | fun check y = 11 | case y of 12 | x => x 13 | (** ^ does not check any part of the matched expression for equality *) 14 | | _ => 2 15 | (** ^ unreachable pattern *) 16 | ``` 17 | 18 | In this example, the pattern `x` unconditionally matches the expression `y` because variable patterns always match. The fact that there is an existing binding `x` with the same type already in scope makes no difference. Pattern matching does not check that `y` is equal to the existing `x`. 19 | 20 | Because of this, the next pattern match arm with the pattern `_` is unreachable. 21 | 22 | This warning also occurs in similar situations like this: 23 | 24 | ```sml 25 | datatype d = 26 | A of int 27 | | B of string 28 | 29 | val x = 3 30 | 31 | fun check y = 32 | case y of 33 | A x => x 34 | (** ^^^ does not check any part of the matched expression for equality *) 35 | | _ => 2 36 | ``` 37 | 38 | But note in this case, the second match arm is reachable. 39 | 40 | ## To fix 41 | 42 | - To check that two values are equal in SML, you may be able to use `=`, the polymorphic equality function. 43 | - To use pattern matching as normal but avoid this warning, rename the bindings. 44 | -------------------------------------------------------------------------------- /crates/tests/src/fixity.rs: -------------------------------------------------------------------------------- 1 | //! Fixities (like `infix` and `nonfix`.) 2 | 3 | use crate::check::check; 4 | 5 | #[test] 6 | fn smoke() { 7 | check( 8 | r" 9 | infix 0 a b c 10 | infixr 0 d e f 11 | nonfix g h i 12 | ", 13 | ); 14 | } 15 | 16 | #[test] 17 | fn negative() { 18 | check( 19 | r" 20 | infix ~3 bad 21 | (** ^^ fixity is negative *) 22 | ", 23 | ); 24 | } 25 | 26 | #[test] 27 | fn real() { 28 | check( 29 | r" 30 | infix 1.1 bad 31 | (** ^^^ expected a name or integer literal *) 32 | ", 33 | ); 34 | } 35 | 36 | #[test] 37 | fn word() { 38 | check( 39 | r" 40 | infix 0w1 bad 41 | (** ^^^ expected a name or integer literal *) 42 | ", 43 | ); 44 | } 45 | 46 | #[test] 47 | fn hex() { 48 | check( 49 | r" 50 | infix 0x1 bad 51 | (** ^^^ invalid fixity: invalid digit found in string *) 52 | ", 53 | ); 54 | } 55 | 56 | #[test] 57 | fn nonfix() { 58 | check( 59 | r" 60 | nonfix + = * 61 | 62 | val _: bool = = (1, 2) 63 | val _: int = + (1, 2) 64 | val _: int = * (3, 4) 65 | ", 66 | ); 67 | } 68 | 69 | #[test] 70 | fn not_infix() { 71 | check( 72 | r" 73 | datatype t = C of int * int 74 | fun f (_ C _) = 2 75 | (** ^ non-infix name used as infix: `C` *) 76 | ", 77 | ); 78 | } 79 | 80 | #[test] 81 | fn same_fixity_diff_assoc() { 82 | check( 83 | r" 84 | infix << 85 | infixr >> 86 | val _ = 1 << 2 >> 3 87 | (** ^^ consecutive infix names with same fixity but different associativity *) 88 | ", 89 | ); 90 | } 91 | -------------------------------------------------------------------------------- /crates/tests/src/input/diagnostics.rs: -------------------------------------------------------------------------------- 1 | //! Tests for config for diagnostics. 2 | 3 | use crate::check::{check_bad_input, check_multi}; 4 | 5 | #[test] 6 | fn ok() { 7 | let config = r#" 8 | version = 1 9 | [diagnostics] 10 | 1001.severity = "error" 11 | 1002.severity = "warning" 12 | 1003.severity = "ignore" 13 | "#; 14 | check_multi([("a.mlb", ""), (config::file::PATH, config)]); 15 | } 16 | 17 | #[test] 18 | fn unknown() { 19 | let config = r#" 20 | version = 1 21 | [diagnostics] 22 | 1001.severity = "Warning" 23 | "#; 24 | check_bad_input( 25 | config::file::PATH, 26 | "unknown variant `Warning`", 27 | [("a.mlb", ""), (config::file::PATH, config)], 28 | ); 29 | } 30 | 31 | #[test] 32 | fn ignore_op_andalso() { 33 | let config = r#" 34 | version = 1 35 | [diagnostics] 36 | 4011.severity = "ignore" 37 | "#; 38 | check_multi([(config::file::PATH, config), ("a.mlb", "a.sml"), ("a.sml", "val _ = op andalso")]); 39 | } 40 | 41 | #[test] 42 | fn ignore_mlb_undefined() { 43 | let config = r#" 44 | version = 1 45 | [diagnostics] 46 | 1017.severity = "ignore" 47 | "#; 48 | check_multi([(config::file::PATH, config), ("a.mlb", "structure Foo")]); 49 | } 50 | 51 | #[test] 52 | fn error_paren() { 53 | let config = r#" 54 | version = 1 55 | [diagnostics] 56 | 4014.severity = "error" 57 | "#; 58 | let sml = r" 59 | val _ = (1) 60 | (** ^^^ unnecessary parentheses *) 61 | "; 62 | check_multi([(config::file::PATH, config), ("a.mlb", "a.sml"), ("a.sml", sml)]); 63 | } 64 | -------------------------------------------------------------------------------- /docs/diagnostics/4025.md: -------------------------------------------------------------------------------- 1 | # 4025 2 | 3 | A name bound in a pattern in a matcher (e.g. `case`) was the same as the name of a `fun` declaration that contained the pattern. 4 | 5 | ```sml 6 | fun foo x = 7 | case x of 8 | 0 => 1 9 | | foo => foo 10 | (** ^^^ name bound in pattern inside a `case` matches name of a `fun` that contains the `case` *) 11 | ``` 12 | 13 | This is at best a possibly confusing case of shadowing. 14 | 15 | This warning occurs in the following somewhat common scenario: 16 | 17 | 18 | 19 | ```sml 20 | fun foo 0 y = y 21 | | foo 1 y = 22 | case y of 23 | 0 => 1 24 | | 2 => 3 25 | | _ => 4 26 | | foo x y = x + y 27 | (** ^^^ name bound in pattern inside a `case` matches name of a `fun` that contains the `case` *) 28 | ``` 29 | 30 | This looks like a `fun` with many cases, one of which has an inner `case`. However, most SML parsers (including Millet) attempt to parse the final `foo x y` as part of the `case` instead as part of the `fun`. This leads to confusing errors, often [3001](./3001.md) and [3002](./3002.md). 31 | 32 | ## To fix 33 | 34 | Try one of the following: 35 | 36 | - Put parentheses around the inner matcher (i.e. `case`, `fn`, or `handle`). 37 | - Rename the binding to avoid shadowing. 38 | 39 | ```sml 40 | fun foo 0 y = y 41 | | foo 1 y = 42 | (case y of 43 | 0 => 1 44 | | 2 => 3 45 | | _ => 4) 46 | | foo x y = x + y 47 | ``` 48 | -------------------------------------------------------------------------------- /editors/vscode/languages/sml/snippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "`let` exp": { 3 | "prefix": "let", 4 | "body": ["let", " ${1:...}", "in", " ${2:...}", "end"] 5 | }, 6 | "`case` exp": { 7 | "prefix": "case", 8 | "body": ["case ${1:_} of", " ${2:_} => ${3:_}", "| ${4:_} => ${5:_}"] 9 | }, 10 | "`if` exp": { 11 | "prefix": "if", 12 | "body": ["if ${1:_} then", " ${2:_}", "else", " ${3:_}"] 13 | }, 14 | "`handle` exp tail": { 15 | "prefix": "handle", 16 | "body": ["handle", " ${2:_} => ${3:_}"] 17 | }, 18 | "`local` dec": { 19 | "prefix": "local", 20 | "body": ["local", " ${1:...}", "in", " ${2:...}", "end"] 21 | }, 22 | "`datatype` dec": { 23 | "prefix": "datatype", 24 | "body": ["datatype ${1:name} = ${2:Name}"] 25 | }, 26 | "`fun` dec": { 27 | "prefix": "fun", 28 | "body": ["fun ${1:name} (${2:_} : ${3:_}) : ${4:_} = ${5:_}"] 29 | }, 30 | "`structure` dec": { 31 | "prefix": "structure", 32 | "body": ["structure ${1:Name} = struct", " ${2:...}", "end"] 33 | }, 34 | "`signature` dec": { 35 | "prefix": "signature", 36 | "body": ["signature ${1:NAME} = sig", " ${2:...}", "end"] 37 | }, 38 | "`functor` dec": { 39 | "prefix": "functor", 40 | "body": [ 41 | "functor ${1:Name} (${2:Arg}: ${3:SIG}) = struct", 42 | " ${4:...}", 43 | "end" 44 | ] 45 | }, 46 | "`print` with newline exp": { 47 | "prefix": "println", 48 | "body": ["print (${1:exp} ^ \"\\n\")"] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /crates/config/src/init.rs: -------------------------------------------------------------------------------- 1 | //! Configuration options sent when the language server starts. 2 | //! 3 | //! The initialization options are a subset of the VS Code config, but rearranged and renamed 4 | //! slightly. Consult the implementation of the VS Code extension to see what options are sent. 5 | //! Additionally, consult the documentation for the VS Code configuration to see what types the 6 | //! configuration options must be. 7 | 8 | #![allow(missing_docs)] 9 | 10 | use crate::tool::Tool; 11 | use serde::Deserialize; 12 | 13 | /// @sync(init-options) 14 | #[derive(Debug, Default, Deserialize)] 15 | pub struct Options { 16 | #[serde(default)] 17 | pub token_hover: Tool, 18 | #[serde(default)] 19 | pub fs_watcher: Tool, 20 | #[serde(default)] 21 | pub format: FormatEngine, 22 | #[serde(default)] 23 | pub diagnostics: DiagnosticsOptions, 24 | } 25 | 26 | #[derive(Debug, Default, Deserialize)] 27 | pub struct DiagnosticsOptions { 28 | #[serde(default)] 29 | pub on_change: bool, 30 | #[serde(default)] 31 | pub more_info_hint: Tool, 32 | #[serde(default)] 33 | pub ignore: DiagnosticsIgnore, 34 | } 35 | 36 | #[derive(Debug, Default, Clone, Copy, Deserialize)] 37 | #[serde(rename_all = "kebab-case")] 38 | pub enum DiagnosticsIgnore { 39 | None, 40 | #[default] 41 | AfterSyntax, 42 | All, 43 | } 44 | 45 | #[derive(Debug, Default, Clone, Copy, Deserialize)] 46 | #[serde(rename_all = "kebab-case")] 47 | pub enum FormatEngine { 48 | #[default] 49 | None, 50 | Naive, 51 | Smlfmt, 52 | } 53 | -------------------------------------------------------------------------------- /editors/vscode/languages/mlb/syntax.json: -------------------------------------------------------------------------------- 1 | { 2 | "scopeName": "source.mlb", 3 | "patterns": [ 4 | { "include": "#blockComment" }, 5 | { "include": "#string" }, 6 | { "include": "#keyword" } 7 | ], 8 | "repository": { 9 | "blockComment": { 10 | "name": "comment.block.mlb", 11 | "begin": "\\(\\*", 12 | "end": "\\*\\)", 13 | "patterns": [{ "include": "#blockComment" }] 14 | }, 15 | "string": { 16 | "name": "string.quoted.double.mlb", 17 | "begin": "\"", 18 | "end": "\"", 19 | "patterns": [{ "include": "#escape" }] 20 | }, 21 | "escape": { 22 | "patterns": [ 23 | { "include": "#simpleEscape" }, 24 | { "include": "#upCaratEscape" }, 25 | { "include": "#threeDigitEscape" }, 26 | { "include": "#uEscape" } 27 | ] 28 | }, 29 | "simpleEscape": { 30 | "name": "constant.character.escape.mlb", 31 | "match": "\\\\[abtnvfr\\\\\"]" 32 | }, 33 | "upCaratEscape": { 34 | "name": "constant.character.escape.mlb", 35 | "match": "\\\\\\^[A-_]" 36 | }, 37 | "threeDigitEscape": { 38 | "name": "constant.character.escape.mlb", 39 | "match": "\\\\[0-9]{3}" 40 | }, 41 | "uEscape": { 42 | "name": "constant.character.escape.mlb", 43 | "match": "\\\\u[0-9A-Fa-f]{4}" 44 | }, 45 | "keyword": { 46 | "name": "keyword.other.mlb", 47 | "match": "\\b(and|ann|bas|basis|end|functor|in|let|local|open|signature|structure)\\b" 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /crates/lang-srv/src/capabilities.rs: -------------------------------------------------------------------------------- 1 | //! See [`get`]. 2 | 3 | /// Returns the capabilities of the server. 4 | pub(crate) fn get() -> lsp_types::ServerCapabilities { 5 | lsp_types::ServerCapabilities { 6 | text_document_sync: Some(lsp_types::TextDocumentSyncCapability::Options( 7 | lsp_types::TextDocumentSyncOptions { 8 | open_close: Some(true), 9 | change: Some(lsp_types::TextDocumentSyncKind::INCREMENTAL), 10 | will_save: Some(false), 11 | will_save_wait_until: Some(false), 12 | save: Some(lsp_types::TextDocumentSyncSaveOptions::SaveOptions(lsp_types::SaveOptions { 13 | include_text: Some(false), 14 | })), 15 | }, 16 | )), 17 | hover_provider: Some(lsp_types::HoverProviderCapability::Simple(true)), 18 | definition_provider: Some(lsp_types::OneOf::Left(true)), 19 | type_definition_provider: Some(lsp_types::TypeDefinitionProviderCapability::Simple(true)), 20 | code_action_provider: Some(lsp_types::CodeActionProviderCapability::Simple(true)), 21 | document_formatting_provider: Some(lsp_types::OneOf::Left(true)), 22 | document_symbol_provider: Some(lsp_types::OneOf::Left(true)), 23 | references_provider: Some(lsp_types::OneOf::Left(true)), 24 | completion_provider: Some(lsp_types::CompletionOptions { 25 | trigger_characters: Some(vec![".".to_owned()]), 26 | ..lsp_types::CompletionOptions::default() 27 | }), 28 | inlay_hint_provider: Some(lsp_types::OneOf::Left(true)), 29 | ..Default::default() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /crates/tests/src/deviations/mlton.rs: -------------------------------------------------------------------------------- 1 | //! See [`MLton` docs](http://mlton.org/UnresolvedBugs) about unresolved bugs. 2 | 3 | use crate::check::check; 4 | 5 | #[test] 6 | fn fun_bar_case() { 7 | // NOTE: the specific error doesn't matter a whole lot, this just illustrates that we (along with 8 | // every sml impl) require `()` to disambiguate this case (literally). 9 | check( 10 | r" 11 | fun f 0 y = 12 | case x of 13 | 1 => 2 14 | | _ => 3 15 | | f _ y = 4 16 | (** ^ non-infix name used as infix *) 17 | ", 18 | ); 19 | } 20 | 21 | #[test] 22 | fn rebind_ctor() { 23 | check( 24 | r" 25 | datatype uh = Uh 26 | val rec Uh = fn () => () 27 | ", 28 | ); 29 | cov_mark::check("rebind_ctor"); 30 | } 31 | 32 | #[test] 33 | fn functor_re_typecheck_or_not_1() { 34 | check( 35 | r#" 36 | fun id x = x 37 | functor F (X: sig type t end) = struct 38 | val f = id id 39 | (** ^^^^^ cannot bind expansive polymorphic expression *) 40 | end 41 | structure A = F (struct type t = int end) 42 | structure B = F (struct type t = bool end) 43 | val _ = A.f 10 44 | val _ = B.f "dude" 45 | "#, 46 | ); 47 | } 48 | 49 | #[test] 50 | fn functor_re_typecheck_or_not_2() { 51 | check( 52 | r" 53 | fun id x = x 54 | functor F (X: sig type t end) = struct 55 | val f = id id 56 | (** ^^^^^ cannot bind expansive polymorphic expression *) 57 | end 58 | structure A = F (struct type t = int end) 59 | structure B = F (struct type t = bool end) 60 | val _ = A.f 10 61 | val _ = B.f false 62 | ", 63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /crates/sml-file-syntax/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! See [`SourceFileSyntax`]. 2 | 3 | /// A source file analyzed at the purely syntactic level. 4 | #[derive(Debug)] 5 | pub struct SourceFileSyntax { 6 | /// The position database for this file. 7 | pub pos_db: text_pos::PositionDb, 8 | /// Lex errors from the file. 9 | pub lex_errors: Vec, 10 | /// The lossless concrete syntax tree. 11 | pub parse: sml_parse::Parse, 12 | /// The lowered HIR. 13 | pub lower: sml_hir_lower::Lower, 14 | /// The kind of source file this is. 15 | pub kind: sml_file::Kind, 16 | } 17 | 18 | impl SourceFileSyntax { 19 | /// Starts processing a single source file. 20 | pub fn new( 21 | fix_env: &mut sml_fixity::Env, 22 | lang: &config::lang::Language, 23 | kind: sml_file::Kind, 24 | contents: &str, 25 | ) -> Self { 26 | elapsed::log("SourceFileSyntax::new", || { 27 | let (lex_errors, parse) = Self::lex_and_parse(fix_env, contents); 28 | let mut lower = sml_hir_lower::get(lang, kind, &parse.root); 29 | sml_ty_var_scope::get(&mut lower.arenas, &lower.root); 30 | Self { pos_db: text_pos::PositionDb::new(contents), lex_errors, parse, lower, kind } 31 | }) 32 | } 33 | 34 | /// Lex and parse a source file. 35 | pub fn lex_and_parse( 36 | fix_env: &mut sml_fixity::Env, 37 | contents: &str, 38 | ) -> (Vec, sml_parse::Parse) { 39 | let lexed = sml_lex::get(contents); 40 | let parse = sml_parse::get(&lexed.tokens, fix_env); 41 | (lexed.errors, parse) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /crates/tests/src/sep/extra.rs: -------------------------------------------------------------------------------- 1 | //! Extra separators. 2 | 3 | use crate::check::check; 4 | 5 | #[test] 6 | fn exp_arg() { 7 | check( 8 | r" 9 | val _ = (1,,2) 10 | (** ^ expected an expression *) 11 | ", 12 | ); 13 | } 14 | 15 | #[test] 16 | fn exp_row() { 17 | check( 18 | r" 19 | val _ = {a = 1,, b = 2} 20 | (** ^ expected a label *) 21 | ", 22 | ); 23 | } 24 | 25 | #[test] 26 | fn pat_arg() { 27 | check( 28 | r" 29 | val _ = fn (x,,y) => () 30 | (** ^ expected a pattern *) 31 | ", 32 | ); 33 | } 34 | 35 | #[test] 36 | fn pat_row() { 37 | check( 38 | r" 39 | val _ = fn {a = _,, b = _} => () 40 | (** ^ expected a name *) 41 | ", 42 | ); 43 | } 44 | 45 | #[test] 46 | fn ty_arg() { 47 | check( 48 | r" 49 | type ('a, 'b) foo = unit 50 | val _ : (int,, string) foo = () 51 | (** ^ expected a type *) 52 | ", 53 | ); 54 | } 55 | 56 | #[test] 57 | fn ty_row() { 58 | check( 59 | r" 60 | val _ : {a: int,, b: int} = {a = 1, b = 2} 61 | (** ^ expected a label *) 62 | ", 63 | ); 64 | } 65 | 66 | #[test] 67 | fn ty_var() { 68 | check( 69 | r" 70 | type ('a,,'b) foo = unit 71 | (** ^ expected a type variable *) 72 | ", 73 | ); 74 | } 75 | 76 | #[test] 77 | fn let_exp() { 78 | check( 79 | r" 80 | val _ = let in 1;; 2 end 81 | (** ^ expected an expression *) 82 | ", 83 | ); 84 | } 85 | 86 | #[test] 87 | fn seq_exp() { 88 | check( 89 | r" 90 | val _ = (1;; 2) 91 | (** ^ expected an expression *) 92 | ", 93 | ); 94 | } 95 | -------------------------------------------------------------------------------- /crates/lang-srv/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A language server for Standard ML. 2 | 3 | #![allow(clippy::single_match_else)] 4 | 5 | mod capabilities; 6 | mod convert; 7 | mod cx; 8 | mod diagnostics; 9 | mod helpers; 10 | mod init; 11 | mod notification; 12 | mod request; 13 | mod response; 14 | mod state; 15 | 16 | fn run_inner( 17 | conn: &lsp_server::Connection, 18 | init: lsp_types::InitializeParams, 19 | ) -> anyhow::Result<()> { 20 | log::info!("start up main loop: {init:#?}"); 21 | let mut st = init::init(init, conn.sender.clone()); 22 | for msg in &conn.receiver { 23 | match msg { 24 | lsp_server::Message::Request(req) => { 25 | if conn.handle_shutdown(&req)? { 26 | log::info!("shut down main loop"); 27 | return Ok(()); 28 | } 29 | request::handle(&mut st, req); 30 | } 31 | lsp_server::Message::Response(res) => response::handle(&mut st, res), 32 | lsp_server::Message::Notification(notif) => notification::handle(&mut st, notif), 33 | } 34 | } 35 | Ok(()) 36 | } 37 | 38 | /// Runs the language server over stdio. 39 | /// 40 | /// # Errors 41 | /// 42 | /// If the language server encountered an error. 43 | pub fn run_stdio() -> anyhow::Result<()> { 44 | let (connection, io_threads) = lsp_server::Connection::stdio(); 45 | let params = connection.initialize(serde_json::to_value(capabilities::get())?)?; 46 | run_inner(&connection, serde_json::from_value(params)?)?; 47 | // if we don't drop this, then the join hangs 48 | drop(connection); 49 | io_threads.join()?; 50 | Ok(()) 51 | } 52 | -------------------------------------------------------------------------------- /docs/diagnostics/4015.md: -------------------------------------------------------------------------------- 1 | # 4015 2 | 3 | There was an overly complex expression involving `bool`s. 4 | 5 | ```sml 6 | fun booleanIdentity x = 7 | if x then true else false 8 | (** + overly complex `bool` expression *) 9 | ``` 10 | 11 | An expression is "overly complex" if it involves `if`, `andalso`, or `orelse`, and contains a `bool` literal `true` or `false`. Such expressions can always be simplified. For example: 12 | 13 | | Complex | Simple | 14 | | --------------------------- | ----------------- | 15 | | `if x then true else false` | `x` | 16 | | `if x then false else true` | `not x` | 17 | | `if x then y else false` | `x andalso y` | 18 | | `if x then y else true` | `not x orelse y` | 19 | | `if x then true else y` | `x orelse y` | 20 | | `if x then false else y` | `not x andalso y` | 21 | | `if true then x else y` | `x` | 22 | | `if false then x else y` | `y` | 23 | | `x orelse true` | `(x; true)` | 24 | | `x orelse false` | `x` | 25 | | `x andalso true` | `x` | 26 | | `x andalso false` | `(x; false)` | 27 | | `true orelse x` | `true` | 28 | | `false orelse x` | `x` | 29 | | `true andalso x` | `x` | 30 | | `false andalso x` | `false` | 31 | 32 | Note also for any `b`, `(x; b)` can be further simplified to `b` if evaluating `x` has no side effects. 33 | 34 | ## To fix 35 | 36 | Simplify the expression. Consult the table above to see how. 37 | -------------------------------------------------------------------------------- /crates/tests/src/pat/or.rs: -------------------------------------------------------------------------------- 1 | //! note that we do not require () around the or pattern alternatives, while SML/NJ appears to. 2 | 3 | use crate::check::check; 4 | 5 | #[test] 6 | fn smoke() { 7 | check( 8 | r" 9 | val _ = 10 | case 123 of 11 | 1 | 2 => false 12 | | _ => true 13 | ", 14 | ); 15 | } 16 | 17 | #[test] 18 | fn not_all_same_name() { 19 | check( 20 | r" 21 | datatype t = A of int | B of int 22 | fun f (A x | B y) = x 23 | (** ^^^ `x` was bound in one alternative, but not in another *) 24 | ", 25 | ); 26 | } 27 | 28 | #[test] 29 | fn not_all_same_ty() { 30 | check( 31 | r" 32 | datatype t = A of int | B of string 33 | fun f (A x | B x) = x 34 | (** ^^^ expected `string`, found `int` *) 35 | ", 36 | ); 37 | } 38 | 39 | #[test] 40 | fn exhaustive() { 41 | check( 42 | r" 43 | datatype t = A of int | B of int 44 | fun f (A x | B x) = x 45 | ", 46 | ); 47 | } 48 | 49 | #[test] 50 | fn in_ctor() { 51 | check( 52 | r" 53 | datatype t = A of int | B of int 54 | fun f x = 55 | case x of 56 | A (1 | 2) => 1 57 | | A (3 | 4) | B (5 | 6) => 2 58 | | B (7 | 8 | 9 | 10) => 3 59 | | _ => 4 60 | ", 61 | ); 62 | } 63 | 64 | #[test] 65 | fn unreachable_smoke() { 66 | check( 67 | r" 68 | fun f x = 69 | case x of 70 | 1 | 1 => 1 71 | (** ^ unreachable pattern *) 72 | | _ => 2 73 | ", 74 | ); 75 | } 76 | 77 | #[test] 78 | fn unreachable_complex() { 79 | check( 80 | r" 81 | datatype t = A of int | B of int 82 | fun f x = 83 | case x of 84 | A (1 | 2) => 1 85 | | B 3 | A 2 => 2 86 | (** ^ unreachable pattern *) 87 | | _ => 2 88 | ", 89 | ); 90 | } 91 | -------------------------------------------------------------------------------- /crates/tests/src/completions.rs: -------------------------------------------------------------------------------- 1 | //! Tests for completions. 2 | 3 | use crate::check::{check, raw}; 4 | 5 | #[test] 6 | fn smoke() { 7 | check( 8 | r" 9 | val foo = 3 10 | 11 | (** vv undefined *) 12 | val _ = aa 13 | (** ^ completions(with-std): foo *) 14 | ", 15 | ); 16 | } 17 | 18 | #[test] 19 | fn in_struct() { 20 | check( 21 | r#" 22 | structure Foo = struct 23 | val bar = 3 24 | val quz = "hi" 25 | end 26 | 27 | (** vvv expected a name *) 28 | val _ = Foo. val 29 | (** ^ completions: bar, quz *) 30 | "#, 31 | ); 32 | } 33 | 34 | #[test] 35 | fn nested() { 36 | check( 37 | r" 38 | structure A = struct 39 | val x = 3 40 | structure B = struct 41 | val y = 4 42 | end 43 | end 44 | 45 | (** vvv expected a name *) 46 | val _ = A.B. val 47 | (** ^ completions: y *) 48 | ", 49 | ); 50 | } 51 | 52 | #[test] 53 | fn eof() { 54 | let sml = r" 55 | val a = 1 and b = 2 and c = 3 56 | (** v completions(with-std): a, b, c *) 57 | val _ = x"; 58 | let opts = raw::Opts { 59 | std_basis: raw::StdBasis::Minimal, 60 | outcome: raw::Outcome::Pass, 61 | limit: raw::Limit::All, 62 | min_severity: diagnostic::Severity::Error, 63 | expected_input: raw::ExpectedInput::Good, 64 | }; 65 | raw::get(raw::one_file_fs(sml), opts); 66 | } 67 | 68 | #[test] 69 | fn in_path() { 70 | // the completions should be auto-narrowed to just `foo` by the editor 71 | check( 72 | r" 73 | structure S = struct 74 | val foo = () 75 | val bar = () 76 | end 77 | 78 | (** vvv undefined value: `f` *) 79 | val _ = S.f 80 | (** ^ completions: foo, bar *) 81 | ", 82 | ); 83 | } 84 | -------------------------------------------------------------------------------- /crates/tests/src/ty_escape.rs: -------------------------------------------------------------------------------- 1 | //! Type names escaping the scope in which they are valid. 2 | 3 | use crate::check::check; 4 | 5 | #[test] 6 | fn smoke() { 7 | check( 8 | r" 9 | val _ = let datatype bad = guh in guh end 10 | (** ^^^ type escapes its scope: `bad` *) 11 | ", 12 | ); 13 | } 14 | 15 | #[test] 16 | fn seq() { 17 | check( 18 | r" 19 | val _ = let datatype foo = bar val quz = bar in (bar; 3 + 3; quz) end 20 | (** ^^^ type escapes its scope: `foo` *) 21 | ", 22 | ); 23 | } 24 | 25 | #[test] 26 | fn does_not_escape() { 27 | check( 28 | r" 29 | val _ = let datatype foo = bar val quz = bar in (bar; 3 + 3; quz; 123) end 30 | ", 31 | ); 32 | } 33 | 34 | #[test] 35 | fn branch() { 36 | check( 37 | r#" 38 | val _ = let datatype d = D in if 3 < 4 then [] else [(3, D, "hi")] end 39 | (** ^^^ type escapes its scope: `d` *) 40 | "#, 41 | ); 42 | } 43 | 44 | #[test] 45 | fn shadow() { 46 | check( 47 | r" 48 | datatype t = One 49 | val _ = let datatype t = Two in Two end 50 | (** ^^^ type escapes its scope: `t` *) 51 | ", 52 | ); 53 | } 54 | 55 | #[test] 56 | fn ok() { 57 | check( 58 | r" 59 | datatype t = One 60 | val _ = let datatype t = Two in One end 61 | ", 62 | ); 63 | } 64 | 65 | #[test] 66 | fn exn_ctor() { 67 | check( 68 | r" 69 | val ex = 70 | let 71 | exception E 72 | in 73 | E 74 | end 75 | ", 76 | ); 77 | } 78 | 79 | #[test] 80 | fn raise_datatype() { 81 | check( 82 | r" 83 | val _ = 84 | let 85 | datatype d = D 86 | exception E of d 87 | in 88 | (* cannot be specifically matched against *) 89 | fn () => raise E D 90 | end 91 | ", 92 | ); 93 | } 94 | -------------------------------------------------------------------------------- /docs/diagnostics/5017.md: -------------------------------------------------------------------------------- 1 | # 5017 2 | 3 | A type escapes the scope in which it is valid. 4 | 5 | Here, the type `d` is only available for the scope of the `let` expression, but the `let` expression would cause a value of type `d` to "escape" the `let` and be bound to `x`, outside the `let`. 6 | 7 | ```sml 8 | val x = 9 | let datatype d = D in D end 10 | (** ^^^ type escapes its scope: `d` *) 11 | ``` 12 | 13 | This can also occur for explicit type variables, as in this example: 14 | 15 | 16 | 17 | ```sml 18 | fun f b x = 19 | (** + type escapes its scope: `'a` *) 20 | let 21 | fun g (y : 'a) = if b then x else y 22 | in 23 | () 24 | end 25 | ``` 26 | 27 | Here, `x` and `y` are both inferred to have type `'a`. Note that: 28 | 29 | - `'a` is implicitly bound at `fun g`. 30 | - `x` is bound before `fun g`. 31 | 32 | ## To fix 33 | 34 | Try one of the following: 35 | 36 | - Extend the scope of the type: 37 | 38 | ```sml 39 | datatype d = D 40 | val x = D 41 | ``` 42 | 43 | - Do not allow its values to escape its scope: 44 | 45 | ```sml 46 | val x = 47 | let 48 | datatype d = D 49 | in 50 | 4 51 | end 52 | ``` 53 | 54 | - Remove explicit type variable annotations: 55 | 56 | 57 | 58 | ```sml 59 | fun f b x = 60 | let 61 | fun g y = if b then x else y 62 | in 63 | () 64 | end 65 | ``` 66 | 67 | - Change where type variables are bound: 68 | 69 | 70 | 71 | ```sml 72 | fun 'a f b x = 73 | let 74 | fun g (y : 'a) = if b then x else y 75 | in 76 | () 77 | end 78 | ``` 79 | -------------------------------------------------------------------------------- /crates/input/src/topo.rs: -------------------------------------------------------------------------------- 1 | //! Run a topo sort on a sequence of `BasDec`s at paths. 2 | 3 | use paths::PathId; 4 | use std::collections::BTreeSet; 5 | 6 | pub(crate) fn check<'a, I>(iter: I) -> Result<(), topo_sort::graph::CycleError> 7 | where 8 | I: Iterator, 9 | { 10 | let graph: topo_sort::graph::Graph<_> = iter 11 | .map(|(path, bas_dec)| { 12 | let mut ac = BTreeSet::::new(); 13 | bas_dec_paths(&mut ac, bas_dec); 14 | (path, ac) 15 | }) 16 | .collect(); 17 | topo_sort::graph::get(&graph)?; 18 | Ok(()) 19 | } 20 | 21 | fn bas_dec_paths(ac: &mut BTreeSet, dec: &mlb_hir::BasDec) { 22 | match dec { 23 | mlb_hir::BasDec::Open(_) | mlb_hir::BasDec::Export(_, _, _) => {} 24 | mlb_hir::BasDec::Path(p, _) => { 25 | ac.insert(*p); 26 | } 27 | mlb_hir::BasDec::SourcePathSet(paths) => ac.extend(paths.iter().map(|&(x, _)| x)), 28 | mlb_hir::BasDec::Basis(_, exp) => bas_exp_paths(ac, exp), 29 | mlb_hir::BasDec::Local(local_dec, in_dec) => { 30 | bas_dec_paths(ac, local_dec); 31 | bas_dec_paths(ac, in_dec); 32 | } 33 | mlb_hir::BasDec::Ann(_, dec) => bas_dec_paths(ac, dec), 34 | mlb_hir::BasDec::Seq(decs) => { 35 | for dec in decs { 36 | bas_dec_paths(ac, dec); 37 | } 38 | } 39 | } 40 | } 41 | 42 | fn bas_exp_paths(ac: &mut BTreeSet, exp: &mlb_hir::BasExp) { 43 | match exp { 44 | mlb_hir::BasExp::Bas(dec) => bas_dec_paths(ac, dec), 45 | mlb_hir::BasExp::Name(_) => {} 46 | mlb_hir::BasExp::Let(dec, exp) => { 47 | bas_dec_paths(ac, dec); 48 | bas_exp_paths(ac, exp); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /crates/tests/src/big.rs: -------------------------------------------------------------------------------- 1 | //! Big expectation, big expectation. Ooh, you and me, we got big expectations. 2 | 3 | use crate::check::check_multi; 4 | 5 | #[test] 6 | fn fst() { 7 | let files = [ 8 | ( 9 | "millet.toml", 10 | r#" 11 | version = 1 12 | [workspace] 13 | root = "*/sources.cm" 14 | [workspace.path-vars] 15 | hashida = { value = "itaru" } 16 | okabe = { path = "rintarou" } 17 | shiina = { workspace-path = "mayuri" } 18 | "#, 19 | ), 20 | ( 21 | "rintarou.sml", 22 | r" 23 | structure R = struct 24 | val date = 20100728 25 | end 26 | ", 27 | ), 28 | ( 29 | "hw1/mayuri.sml", 30 | r" 31 | structure M = struct 32 | val idx = 2 33 | end 34 | ", 35 | ), 36 | ( 37 | "hw1/sources.cm", 38 | r" 39 | Group is 40 | a.sml 41 | b.sml 42 | $shiina.sml 43 | $okabe.sml 44 | ", 45 | ), 46 | ( 47 | "hw1/a.sml", 48 | r#" 49 | structure A = struct 50 | val bird = "polly" 51 | end 52 | "#, 53 | ), 54 | ( 55 | "hw1/b.sml", 56 | r" 57 | val _ = A.bird 58 | 59 | val _ = M.idx + 1 + R.date 60 | ", 61 | ), 62 | ( 63 | "hw2/mayuri.sml", 64 | r#" 65 | structure M = struct 66 | val msg = "tut-tu-ru!" 67 | end 68 | "#, 69 | ), 70 | ( 71 | "hw2/sources.cm", 72 | r" 73 | Group is 74 | c.sml 75 | $shiina.sml 76 | $okabe.sml 77 | d.sml 78 | ", 79 | ), 80 | ( 81 | "hw2/d.sml", 82 | r#" 83 | structure D = struct 84 | val sound = "beep" 85 | end 86 | 87 | val _ = (if M.msg = "E" then 1 else 2) + R.date 88 | "#, 89 | ), 90 | ( 91 | "hw2/c.sml", 92 | r" 93 | val _ = D.sound 94 | ", 95 | ), 96 | ]; 97 | check_multi(files); 98 | } 99 | -------------------------------------------------------------------------------- /crates/sml-statics/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Static analysis. 2 | //! 3 | //! With help from [this article][1]. 4 | //! 5 | //! [1]: http://dev.stephendiehl.com/fun/006_hindley_milner.html 6 | 7 | #![allow(clippy::too_many_lines, clippy::single_match_else)] 8 | 9 | mod compatible; 10 | mod config; 11 | mod dec; 12 | mod error; 13 | mod exp; 14 | mod get_env; 15 | mod pat; 16 | mod pat_match; 17 | mod st; 18 | mod top_dec; 19 | mod ty; 20 | mod unify; 21 | mod util; 22 | 23 | pub mod basis; 24 | pub mod info; 25 | pub mod path_order; 26 | 27 | pub use error::Error; 28 | 29 | /// The result of statics. 30 | #[derive(Debug)] 31 | pub struct Statics { 32 | /// The information about the top decs. 33 | pub info: info::Info, 34 | /// The errors from the top decs. 35 | pub errors: Vec, 36 | /// Id statuses for path expressions. Only populated when the mode is Dynamics. 37 | pub exp_id_statuses: sml_statics_types::info::IdStatusMap, 38 | /// Id statuses for path patterns. Only populated when the mode is Dynamics. 39 | pub pat_id_statuses: sml_statics_types::info::IdStatusMap, 40 | } 41 | 42 | /// Does the checks on the root. 43 | pub fn get( 44 | syms_tys: &mut sml_statics_types::St, 45 | bs: &basis::Bs, 46 | mode: sml_statics_types::mode::Mode, 47 | arenas: &sml_hir::Arenas, 48 | root: &[sml_hir::StrDecIdx], 49 | ) -> Statics { 50 | elapsed::log("sml_statics::get", || { 51 | let mut st = st::St::new(mode, syms_tys); 52 | st.info.bs = top_dec::get(&mut st, bs, arenas, root); 53 | let errors = st.finish(); 54 | Statics { 55 | info: st.info, 56 | errors, 57 | exp_id_statuses: st.exp_id_statuses, 58 | pat_id_statuses: st.pat_id_statuses, 59 | } 60 | }) 61 | } 62 | -------------------------------------------------------------------------------- /docs/diagnostics/4018.md: -------------------------------------------------------------------------------- 1 | # 4018 2 | 3 | There were multiple type annotations on a single overall pattern. 4 | 5 | ```sml 6 | fun add ((x : int, y : int) : int * int) = x + y 7 | (** ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ multiple types on one pattern *) 8 | ``` 9 | 10 | This error will occur in situations like this: 11 | 12 | ```sml 13 | val inc = 14 | fn (x : int) : int => x + 1 15 | (** ^^^^^^^^^^^^^^^ multiple types on one pattern *) 16 | ``` 17 | 18 | This may look like it is annotating both the input and output types of this `fn` as `int`, but actually it is annotating the input as `int` twice, redundantly. 19 | 20 | Indeed, the below similar code triggers this error, as well as another error that `bool` and `int` are incompatible types. This gives a clue as to what is happening: we are actually trying to annotate `x` as both `bool` and `int`. 21 | 22 | ```sml 23 | val greaterThanFive = 24 | (** - expected `bool`, found `int` *) 25 | fn (x : int) : bool => x > 5 26 | (** ^^^^^^^^^^^^^^^^ multiple types on one pattern *) 27 | ``` 28 | 29 | ## To fix 30 | 31 | Use at most one type annotation per pattern. 32 | 33 | In the first example, either of the following would work: 34 | 35 | ```sml 36 | fun addAnnotateEach (x : int, y : int) = x + y 37 | fun addAnnotatePair ((x, y) : int * int) = x + y 38 | ``` 39 | 40 | For the `fn` examples, it may work to: 41 | 42 | - Annotate the type elsewhere, e.g. with a `val` annotation: 43 | 44 | ```sml 45 | val inc : int -> int = fn x => x + 1 46 | ``` 47 | 48 | - Annotate the body of the function: 49 | 50 | ```sml 51 | val inc = fn (x : int) => (x + 1) : int 52 | ``` 53 | 54 | - Skip annotating the return type: 55 | 56 | ```sml 57 | val inc = fn (x : int) => x + 1 58 | ``` 59 | -------------------------------------------------------------------------------- /crates/sml-fixity/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Types concerning SML infix operators, precedence, and associativity. 2 | 3 | use fast_hash::{FxHashMap, map_with_capacity}; 4 | use std::sync::LazyLock; 5 | 6 | /// A mapping from names to (in)fixities. 7 | pub type Env = FxHashMap; 8 | 9 | /// The default infix operators in the std basis. 10 | pub static STD_BASIS: LazyLock = LazyLock::new(|| { 11 | let ops_arr: [(Infix, &[&str]); 6] = [ 12 | (Infix::left(7), &["*", "/", "div", "mod"]), 13 | (Infix::left(6), &["+", "-", "^"]), 14 | (Infix::right(5), &["::", "@"]), 15 | (Infix::left(4), &["=", "<>", ">", ">=", "<", "<="]), 16 | (Infix::left(3), &[":=", "o"]), 17 | (Infix::left(0), &["before"]), 18 | ]; 19 | let mut ret = map_with_capacity(ops_arr.iter().map(|(_, names)| names.len()).sum()); 20 | for (info, names) in ops_arr { 21 | for &name in names { 22 | ret.insert(str_util::Name::new(name), info); 23 | } 24 | } 25 | ret 26 | }); 27 | 28 | /// Information about an infix name. 29 | #[derive(Debug, Clone, Copy)] 30 | pub struct Infix { 31 | /// The precedence. 32 | pub prec: u16, 33 | /// The associativity. 34 | pub assoc: Assoc, 35 | } 36 | 37 | impl Infix { 38 | /// Returns a new `Infix` with left associativity. 39 | #[must_use] 40 | pub fn left(prec: u16) -> Self { 41 | Self { prec, assoc: Assoc::Left } 42 | } 43 | 44 | /// Returns a new `Infix` with right associativity. 45 | #[must_use] 46 | pub fn right(prec: u16) -> Self { 47 | Self { prec, assoc: Assoc::Right } 48 | } 49 | } 50 | 51 | /// Associativity for infix operators. 52 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 53 | pub enum Assoc { 54 | /// `infix` 55 | Left, 56 | /// `infixr` 57 | Right, 58 | } 59 | -------------------------------------------------------------------------------- /crates/tests/src/literal.rs: -------------------------------------------------------------------------------- 1 | //! Literals. 2 | 3 | use crate::check::check; 4 | 5 | #[test] 6 | fn char_big() { 7 | check( 8 | r#" 9 | val _ = #"あ" 10 | (** ^^^^ character literal must have length 1 *) 11 | "#, 12 | ); 13 | } 14 | 15 | #[test] 16 | fn char_small() { 17 | check( 18 | r#" 19 | val _ = #"" 20 | (** ^^^ character literal must have length 1 *) 21 | "#, 22 | ); 23 | } 24 | 25 | #[test] 26 | fn int_big() { 27 | check( 28 | r" 29 | val _ = 123123123123123123123123132131 30 | ", 31 | ); 32 | } 33 | 34 | #[test] 35 | fn real_missing_digits() { 36 | check( 37 | r" 38 | val _ = 123. 39 | (** ^^^^ missing digits in number literal *) 40 | ", 41 | ); 42 | } 43 | 44 | #[test] 45 | fn string_continuation_non_whitespace() { 46 | check( 47 | r#" 48 | val _ = "bad \ bad \ bad" 49 | (** ^ non-whitespace in string continuation *) 50 | "#, 51 | ); 52 | } 53 | 54 | #[test] 55 | fn negative_word() { 56 | check( 57 | r" 58 | val _ = ~0w1 59 | (** ^^^^ negative word literal *) 60 | ", 61 | ); 62 | } 63 | 64 | #[test] 65 | fn string_unclosed() { 66 | check( 67 | r#" 68 | val _ = "bad 69 | (** + unclosed string literal *) 70 | "#, 71 | ); 72 | } 73 | 74 | #[test] 75 | fn basic_escape() { 76 | check( 77 | r#" 78 | val _: char list = [#"\a", #"\b", #"\t", #"\n", #"\v", #"\f", #"\r", #"\\", #"\""] 79 | val _: string list = [ "\a", "\b", "\t", "\n", "\v", "\f", "\r", "\\", "\""] 80 | "#, 81 | ); 82 | } 83 | 84 | #[test] 85 | fn complex_escape() { 86 | check( 87 | r#" 88 | val _: string list = [ "\^A", "\^[", "\^_", "\123", "\244", "\u0000", "\uBEEF", "\uf00f"] 89 | val _: char list = [#"\^A", #"\^[", #"\^_", #"\123", #"\244", #"\u0000"] (* too long *) 90 | "#, 91 | ); 92 | } 93 | -------------------------------------------------------------------------------- /editors/vscode/src/main.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import * as vscode from "vscode"; 3 | import { 4 | LanguageClient, 5 | type LanguageClientOptions, 6 | type ServerOptions, 7 | } from "vscode-languageclient/node"; 8 | 9 | let client: LanguageClient | null = null; 10 | 11 | export async function activate(cx: vscode.ExtensionContext) { 12 | if (client !== null) { 13 | return; 14 | } 15 | const config = vscode.workspace.getConfiguration("millet"); 16 | if (!config.get("server.enable")) { 17 | return; 18 | } 19 | const ext = process.platform === "win32" ? ".exe" : ""; 20 | const configPath = config.get("server.path"); 21 | const serverOpts: ServerOptions = { 22 | command: 23 | typeof configPath === "string" && configPath.length !== 0 24 | ? configPath 25 | : cx.asAbsolutePath(path.join("out", "millet-ls" + ext)), 26 | }; 27 | const clientOpts: LanguageClientOptions = { 28 | documentSelector: [{ scheme: "file", language: "sml" }], 29 | // @sync(init-options) 30 | initializationOptions: { 31 | token_hover: config.get("server.hover.token.enable"), 32 | fs_watcher: config.get("server.fileSystemWatcher.enable"), 33 | format: config.get("format.engine"), 34 | diagnostics: { 35 | on_change: config.get("server.diagnostics.onChange.enable"), 36 | more_info_hint: config.get("server.diagnostics.moreInfoHint.enable"), 37 | ignore: config.get("server.diagnostics.ignore"), 38 | }, 39 | }, 40 | }; 41 | client = new LanguageClient("millet", serverOpts, clientOpts); 42 | await client.start(); 43 | } 44 | 45 | export async function deactivate() { 46 | if (client === null) { 47 | return; 48 | } 49 | await client.stop(); 50 | client = null; 51 | } 52 | -------------------------------------------------------------------------------- /crates/sml-comment/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Extracting interesting comments from SML files. 2 | 3 | use sml_syntax::kind::{SyntaxKind as SK, SyntaxNode, SyntaxToken}; 4 | 5 | /// Gets the comment above this node, then parses the doc string out of it. 6 | #[must_use] 7 | pub fn doc_comment_above(node: &SyntaxNode) -> Option { 8 | let tok = comment_above(node)?; 9 | let mut lines: Vec<_> = tok 10 | .text() 11 | .lines() 12 | .filter_map(|line| { 13 | let (_, s) = line.split_once('*')?; 14 | Some(s.strip_prefix(' ').unwrap_or(s)) 15 | }) 16 | .collect(); 17 | let is_doc_comment = !lines.is_empty() && lines.remove(0) == "!" && lines.pop()? == ")"; 18 | is_doc_comment.then(|| lines.join("\n")) 19 | } 20 | 21 | /// Returns the comment "above" this node. The node must be one that comments may be "above", like 22 | /// `val` or `fun` or `and`. 23 | #[must_use] 24 | pub fn comment_above(node: &SyntaxNode) -> Option { 25 | let mut tok = node.first_token()?; 26 | let mut saw_one = false; 27 | loop { 28 | match tok.kind() { 29 | SK::BlockComment => return Some(tok), 30 | SK::DotDotDot 31 | | SK::AndKw 32 | | SK::ValKw 33 | | SK::FunKw 34 | | SK::TypeKw 35 | | SK::EqtypeKw 36 | | SK::DatatypeKw 37 | | SK::AbstypeKw 38 | | SK::ExceptionKw 39 | | SK::OpenKw 40 | | SK::InfixKw 41 | | SK::InfixrKw 42 | | SK::NonfixKw 43 | | SK::DoKw 44 | | SK::LocalKw 45 | | SK::StructureKw 46 | | SK::SignatureKw 47 | | SK::FunctorKw 48 | | SK::IncludeKw => { 49 | if saw_one { 50 | return None; 51 | } 52 | saw_one = true; 53 | } 54 | _ => {} 55 | } 56 | tok = tok.prev_token()?; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /crates/mlb-hir/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! HIR for MLB. 2 | 3 | use fast_hash::FxHashSet; 4 | use sml_namespace::Module; 5 | use text_size_util::WithRange; 6 | 7 | /// A basis declaration. 8 | #[derive(Debug)] 9 | pub enum BasDec { 10 | /// `basis = ` 11 | Basis(WithRange, Box), 12 | /// `open ` 13 | Open(WithRange), 14 | /// `local in end` 15 | Local(Box, Box), 16 | /// `structure `, etc. 17 | Export(Module, WithRange, WithRange), 18 | /// `ann in end` 19 | Ann(Annotation, Box), 20 | /// A file path. 21 | Path(paths::PathId, PathKind), 22 | /// Used by CM only. 23 | SourcePathSet(FxHashSet<(paths::PathId, sml_file::Kind)>), 24 | /// A sequence of declarations. 25 | Seq(Vec), 26 | } 27 | 28 | impl BasDec { 29 | /// Returns a sequence of decs. 30 | /// 31 | /// # Panics 32 | /// 33 | /// If there was an internal error. 34 | #[must_use] 35 | pub fn seq(mut decs: Vec) -> Self { 36 | if decs.len() == 1 { decs.pop().unwrap() } else { Self::Seq(decs) } 37 | } 38 | } 39 | 40 | /// A basis expression. 41 | #[derive(Debug)] 42 | pub enum BasExp { 43 | /// `bas end` 44 | Bas(BasDec), 45 | /// `Foo`, etc. 46 | Name(WithRange), 47 | /// `let in end` 48 | Let(BasDec, Box), 49 | } 50 | 51 | /// A kind of path. 52 | #[derive(Debug, Clone, Copy)] 53 | pub enum PathKind { 54 | /// An SML source path. 55 | Source(sml_file::Kind), 56 | /// A group path, like MLB or CM. 57 | Group, 58 | } 59 | 60 | /// An annotation Millet knows about. 61 | #[derive(Debug, Clone, Copy)] 62 | pub enum Annotation { 63 | /// Ignore all diagnostics. 64 | DiagnosticsIgnore(bool), 65 | /// Ignore the whole bas dec. 66 | Ignore, 67 | } 68 | -------------------------------------------------------------------------------- /crates/sml-statics/src/util.rs: -------------------------------------------------------------------------------- 1 | //! Misc utilities for inserting into maps and constructing records. 2 | 3 | use crate::{error::ErrorKind, st::St}; 4 | use chain_map::ChainMap; 5 | use sml_statics_types::item::Item; 6 | use sml_statics_types::ty::{RecordData, Ty}; 7 | 8 | /// @def(6), @def(39), @def(49) 9 | pub(crate) fn record( 10 | st: &mut St<'_>, 11 | idx: sml_hir::Idx, 12 | rows: &[(sml_hir::Lab, T)], 13 | mut f: F, 14 | ) -> RecordData 15 | where 16 | T: Copy, 17 | F: FnMut(&mut St<'_>, &sml_hir::Lab, T) -> Ty, 18 | { 19 | let mut ty_rows = RecordData::new(); 20 | for (lab, val) in rows { 21 | let ty = f(st, lab, *val); 22 | match ty_rows.insert(lab.clone(), ty) { 23 | None => {} 24 | Some(_) => st.err(idx, ErrorKind::DuplicateLab(lab.clone())), 25 | } 26 | } 27 | ty_rows 28 | } 29 | 30 | /// inserts `(name, val)` into the map, but returns `Some(e)` if `name` was already a key, where `e` 31 | /// is an error describing this transgression. 32 | pub(crate) fn ins_no_dupe( 33 | map: &mut ChainMap, 34 | name: str_util::Name, 35 | val: V, 36 | item: Item, 37 | ) -> Option { 38 | (!map.insert(name.clone(), val)).then_some(ErrorKind::Duplicate(item, name)) 39 | } 40 | 41 | /// inerts a name that is not one of the special reserved names like `true`. 42 | pub(crate) fn ins_check_name( 43 | map: &mut ChainMap, 44 | name: str_util::Name, 45 | val: V, 46 | item: Item, 47 | ) -> Option { 48 | check_name(&name).or_else(|| ins_no_dupe(map, name, val, item)) 49 | } 50 | 51 | /// returns an error iff this name is not allowed to be (re)bound 52 | pub(crate) fn check_name(name: &str_util::Name) -> Option { 53 | let no = matches!(name.as_str(), "true" | "false" | "nil" | "::" | "ref" | "=" | "it"); 54 | no.then(|| ErrorKind::InvalidRebindName(name.clone())) 55 | } 56 | --------------------------------------------------------------------------------