├── .gitignore ├── README.md ├── .github └── workflows │ └── test.yml ├── gleam.toml ├── manifest.toml ├── test └── tibe_test.gleam └── src └── tibe.gleam /.gitignore: -------------------------------------------------------------------------------- 1 | *.beam 2 | *.ez 3 | build 4 | erl_crash.dump 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tibe 2 | 3 | [Type Inference by Example](https://ahnfelt.medium.com/type-inference-by-example-793d83f98382) but implemented in Gleam. 4 | 5 | ```sh 6 | gleam test # Run the tests 7 | ``` 8 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | pull_request: 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: erlef/setup-beam@v1 16 | with: 17 | otp-version: "26.0.2" 18 | gleam-version: "0.30.4" 19 | - run: gleam format --check src test 20 | - run: gleam deps download 21 | - run: gleam test 22 | -------------------------------------------------------------------------------- /gleam.toml: -------------------------------------------------------------------------------- 1 | name = "tibe" 2 | version = "0.1.0" 3 | 4 | # Fill out these fields if you intend to generate HTML documentation or publish 5 | # your project to the Hex package manager. 6 | # 7 | # licences = ["Apache-2.0"] 8 | # description = "A Gleam library..." 9 | # repository = { type = "github", user = "username", repo = "project" } 10 | # links = [{ title = "Website", href = "https://gleam.run" }] 11 | 12 | [dependencies] 13 | gleam_stdlib = "~> 0.30" 14 | 15 | [dev-dependencies] 16 | gleeunit = "~> 0.10" 17 | -------------------------------------------------------------------------------- /manifest.toml: -------------------------------------------------------------------------------- 1 | # This file was generated by Gleam 2 | # You typically do not need to edit this file 3 | 4 | packages = [ 5 | { name = "gleam_stdlib", version = "0.30.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "03710B3DA047A3683117591707FCA19D32B980229DD8CE8B0603EB5B5144F6C3" }, 6 | { name = "gleeunit", version = "0.10.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "ECEA2DE4BE6528D36AFE74F42A21CDF99966EC36D7F25DEB34D47DD0F7977BAF" }, 7 | ] 8 | 9 | [requirements] 10 | gleam_stdlib = { version = "~> 0.30" } 11 | gleeunit = { version = "~> 0.10" } 12 | -------------------------------------------------------------------------------- /test/tibe_test.gleam: -------------------------------------------------------------------------------- 1 | import gleeunit 2 | import gleeunit/should 3 | import tibe.{ 4 | EApply, EArray, EFunctions, EInt, ELambda, ELet, ESemicolon, EString, 5 | EVariable, GenericFunction, GenericType, NotInScope, OccursError, Parameter, 6 | TConstructor, TVariable, TypeMismatch, 7 | } 8 | import gleam/list 9 | import gleam/map 10 | import gleam/option.{None, Some} 11 | 12 | pub fn main() { 13 | gleeunit.main() 14 | } 15 | 16 | pub fn scope_error_test() { 17 | should.equal( 18 | tibe.infer(initial_environment(), EVariable("x", [])), 19 | Error(NotInScope("x")), 20 | ) 21 | } 22 | 23 | pub fn function_test() { 24 | should.equal( 25 | tibe.infer( 26 | initial_environment(), 27 | ELambda( 28 | [Parameter("x", None), Parameter("y", None)], 29 | None, 30 | EApply(EVariable("+", []), [EVariable("x", []), EVariable("y", [])]), 31 | ), 32 | ), 33 | Ok(#( 34 | ELambda( 35 | [Parameter("x", int_type()), Parameter("y", int_type())], 36 | int_type(), 37 | EApply(EVariable("+", []), [EVariable("x", []), EVariable("y", [])]), 38 | ), 39 | TConstructor("Function2", [int_type(), int_type(), int_type()]), 40 | )), 41 | ) 42 | } 43 | 44 | pub fn function_annotated_test() { 45 | let t = TConstructor("Function2", [int_type(), int_type(), int_type()]) 46 | should.equal( 47 | tibe.infer( 48 | initial_environment(), 49 | ELambda( 50 | [Parameter("x", Some(int_type())), Parameter("y", Some(int_type()))], 51 | Some(int_type()), 52 | EApply(EVariable("+", []), [EVariable("x", []), EVariable("y", [])]), 53 | ), 54 | ), 55 | Ok(#( 56 | ELambda( 57 | [Parameter("x", int_type()), Parameter("y", int_type())], 58 | int_type(), 59 | EApply(EVariable("+", []), [EVariable("x", []), EVariable("y", [])]), 60 | ), 61 | t, 62 | )), 63 | ) 64 | } 65 | 66 | pub fn function_type_mismatch_test() { 67 | should.equal( 68 | tibe.infer( 69 | initial_environment(), 70 | EApply(EVariable("+", []), [EInt(10), EString("some_string")]), 71 | ), 72 | Error(TypeMismatch(int_type(), string_type())), 73 | ) 74 | } 75 | 76 | pub fn let_test() { 77 | should.equal( 78 | tibe.infer( 79 | initial_environment(), 80 | ELet( 81 | "x", 82 | None, 83 | EInt(10), 84 | EApply(EVariable("+", []), [EVariable("x", []), EVariable("x", [])]), 85 | ), 86 | ), 87 | Ok(#( 88 | ELet( 89 | "x", 90 | int_type(), 91 | EInt(10), 92 | EApply(EVariable("+", []), [EVariable("x", []), EVariable("x", [])]), 93 | ), 94 | int_type(), 95 | )), 96 | ) 97 | } 98 | 99 | pub fn let_annotated_test() { 100 | let t = int_type() 101 | should.equal( 102 | tibe.infer( 103 | initial_environment(), 104 | ELet( 105 | "x", 106 | Some(t), 107 | EInt(10), 108 | EApply(EVariable("+", []), [EVariable("x", []), EVariable("x", [])]), 109 | ), 110 | ), 111 | Ok(#( 112 | ELet( 113 | "x", 114 | t, 115 | EInt(10), 116 | EApply(EVariable("+", []), [EVariable("x", []), EVariable("x", [])]), 117 | ), 118 | t, 119 | )), 120 | ) 121 | } 122 | 123 | pub fn let_apply_test() { 124 | should.equal( 125 | tibe.infer( 126 | initial_environment(), 127 | ELet( 128 | "singleton", 129 | None, 130 | ELambda( 131 | [Parameter("x", None)], 132 | None, 133 | EArray(None, [EVariable("x", [])]), 134 | ), 135 | EApply(EVariable("singleton", []), [EInt(42)]), 136 | ), 137 | ), 138 | Ok(#( 139 | ELet( 140 | "singleton", 141 | TConstructor( 142 | "Function1", 143 | [int_type(), TConstructor("Array", [int_type()])], 144 | ), 145 | ELambda( 146 | [Parameter("x", int_type())], 147 | TConstructor("Array", [int_type()]), 148 | EArray(int_type(), [EVariable("x", [])]), 149 | ), 150 | EApply(EVariable("singleton", []), [EInt(42)]), 151 | ), 152 | TConstructor("Array", [int_type()]), 153 | )), 154 | ) 155 | } 156 | 157 | pub fn array_test() { 158 | should.equal( 159 | tibe.infer(initial_environment(), EArray(None, [EInt(10), EInt(20)])), 160 | Ok(#( 161 | EArray(int_type(), [EInt(10), EInt(20)]), 162 | TConstructor("Array", [int_type()]), 163 | )), 164 | ) 165 | } 166 | 167 | pub fn array_annotated_test() { 168 | let t = int_type() 169 | should.equal( 170 | tibe.infer(initial_environment(), EArray(Some(t), [EInt(10), EInt(20)])), 171 | Ok(#(EArray(t, [EInt(10), EInt(20)]), TConstructor("Array", [t]))), 172 | ) 173 | } 174 | 175 | pub fn array_type_mismatch_test() { 176 | should.equal( 177 | tibe.infer(initial_environment(), EArray(None, [EInt(10), EString("20")])), 178 | Error(TypeMismatch(int_type(), string_type())), 179 | ) 180 | } 181 | 182 | pub fn e1_test() { 183 | should.equal( 184 | tibe.infer( 185 | initial_environment(), 186 | EFunctions( 187 | [ 188 | GenericFunction( 189 | "singleton", 190 | None, 191 | ELambda( 192 | [Parameter("x", None)], 193 | None, 194 | EArray(None, [EVariable("x", [])]), 195 | ), 196 | ), 197 | ], 198 | ESemicolon( 199 | EApply(EVariable("singleton", []), [EInt(42)]), 200 | EApply(EVariable("singleton", []), [EString("foo")]), 201 | ), 202 | ), 203 | ), 204 | Ok(#( 205 | EFunctions( 206 | [ 207 | GenericFunction( 208 | "singleton", 209 | GenericType( 210 | ["GenericVar3"], 211 | TConstructor( 212 | "Function1", 213 | [ 214 | TConstructor("GenericVar3", []), 215 | TConstructor("Array", [TConstructor("GenericVar3", [])]), 216 | ], 217 | ), 218 | ), 219 | ELambda( 220 | [Parameter("x", TConstructor("GenericVar3", []))], 221 | TConstructor("Array", [TConstructor("GenericVar3", [])]), 222 | EArray(TConstructor("GenericVar3", []), [EVariable("x", [])]), 223 | ), 224 | ), 225 | ], 226 | ESemicolon( 227 | EApply(EVariable("singleton", [int_type()]), [EInt(42)]), 228 | EApply(EVariable("singleton", [string_type()]), [EString("foo")]), 229 | ), 230 | ), 231 | TConstructor("Array", [string_type()]), 232 | )), 233 | ) 234 | } 235 | 236 | pub fn e2_test() { 237 | should.equal( 238 | tibe.infer( 239 | initial_environment(), 240 | EFunctions( 241 | [ 242 | GenericFunction( 243 | "even", 244 | None, 245 | ELambda( 246 | [Parameter("x", None)], 247 | None, 248 | EApply( 249 | EVariable("odd", []), 250 | [EApply(EVariable("-", []), [EVariable("x", []), EInt(1)])], 251 | ), 252 | ), 253 | ), 254 | GenericFunction( 255 | "odd", 256 | None, 257 | ELambda( 258 | [Parameter("x", None)], 259 | None, 260 | EApply( 261 | EVariable("even", []), 262 | [EApply(EVariable("-", []), [EVariable("x", []), EInt(1)])], 263 | ), 264 | ), 265 | ), 266 | ], 267 | EApply(EVariable("even", []), [EInt(42)]), 268 | ), 269 | ), 270 | Ok(#( 271 | EFunctions( 272 | [ 273 | GenericFunction( 274 | "even", 275 | GenericType( 276 | ["GenericVar3"], 277 | TConstructor( 278 | "Function1", 279 | [int_type(), TConstructor("GenericVar3", [])], 280 | ), 281 | ), 282 | ELambda( 283 | [Parameter("x", int_type())], 284 | TConstructor("GenericVar3", []), 285 | EApply( 286 | EVariable("odd", []), 287 | [EApply(EVariable("-", []), [EVariable("x", []), EInt(1)])], 288 | ), 289 | ), 290 | ), 291 | GenericFunction( 292 | "odd", 293 | GenericType( 294 | ["GenericVar3"], 295 | TConstructor( 296 | "Function1", 297 | [int_type(), TConstructor("GenericVar3", [])], 298 | ), 299 | ), 300 | ELambda( 301 | [Parameter("x", int_type())], 302 | TConstructor("GenericVar3", []), 303 | EApply( 304 | EVariable("even", []), 305 | [EApply(EVariable("-", []), [EVariable("x", []), EInt(1)])], 306 | ), 307 | ), 308 | ), 309 | ], 310 | EApply(EVariable("even", [TVariable(14)]), [EInt(42)]), 311 | ), 312 | TVariable(14), 313 | )), 314 | ) 315 | // TODO: this seems wrong, why is a type variable returned from inference here? 316 | // But it's the same as in the tutorial's example, so... 317 | } 318 | 319 | pub fn e3_test() { 320 | should.equal( 321 | tibe.infer( 322 | initial_environment(), 323 | EFunctions( 324 | [ 325 | GenericFunction( 326 | "even", 327 | None, 328 | ELambda( 329 | [Parameter("x", None)], 330 | None, 331 | EApply( 332 | EVariable("if", []), 333 | [ 334 | EApply(EVariable("==", []), [EVariable("x", []), EInt(0)]), 335 | ELambda([], None, EVariable("true", [])), 336 | ELambda( 337 | [], 338 | None, 339 | EApply( 340 | EVariable("odd", []), 341 | [ 342 | EApply( 343 | EVariable("-", []), 344 | [EVariable("x", []), EInt(1)], 345 | ), 346 | ], 347 | ), 348 | ), 349 | ], 350 | ), 351 | ), 352 | ), 353 | GenericFunction( 354 | "odd", 355 | None, 356 | ELambda( 357 | [Parameter("x", None)], 358 | None, 359 | EApply( 360 | EVariable("if", []), 361 | [ 362 | EApply(EVariable("==", []), [EVariable("x", []), EInt(0)]), 363 | ELambda([], None, EVariable("false", [])), 364 | ELambda( 365 | [], 366 | None, 367 | EApply( 368 | EVariable("even", []), 369 | [ 370 | EApply( 371 | EVariable("-", []), 372 | [EVariable("x", []), EInt(1)], 373 | ), 374 | ], 375 | ), 376 | ), 377 | ], 378 | ), 379 | ), 380 | ), 381 | ], 382 | EApply(EVariable("even", []), [EInt(42)]), 383 | ), 384 | ), 385 | Ok(#( 386 | EFunctions( 387 | [ 388 | GenericFunction( 389 | "even", 390 | GenericType( 391 | [], 392 | TConstructor("Function1", [int_type(), bool_type()]), 393 | ), 394 | ELambda( 395 | [Parameter("x", int_type())], 396 | bool_type(), 397 | EApply( 398 | EVariable("if", [bool_type()]), 399 | [ 400 | EApply(EVariable("==", []), [EVariable("x", []), EInt(0)]), 401 | ELambda([], bool_type(), EVariable("true", [])), 402 | ELambda( 403 | [], 404 | bool_type(), 405 | EApply( 406 | EVariable("odd", []), 407 | [ 408 | EApply( 409 | EVariable("-", []), 410 | [EVariable("x", []), EInt(1)], 411 | ), 412 | ], 413 | ), 414 | ), 415 | ], 416 | ), 417 | ), 418 | ), 419 | GenericFunction( 420 | "odd", 421 | GenericType( 422 | [], 423 | TConstructor("Function1", [int_type(), bool_type()]), 424 | ), 425 | ELambda( 426 | [Parameter("x", int_type())], 427 | bool_type(), 428 | EApply( 429 | EVariable("if", [bool_type()]), 430 | [ 431 | EApply(EVariable("==", []), [EVariable("x", []), EInt(0)]), 432 | ELambda([], bool_type(), EVariable("false", [])), 433 | ELambda( 434 | [], 435 | bool_type(), 436 | EApply( 437 | EVariable("even", []), 438 | [ 439 | EApply( 440 | EVariable("-", []), 441 | [EVariable("x", []), EInt(1)], 442 | ), 443 | ], 444 | ), 445 | ), 446 | ], 447 | ), 448 | ), 449 | ), 450 | ], 451 | EApply(EVariable("even", []), [EInt(42)]), 452 | ), 453 | bool_type(), 454 | )), 455 | ) 456 | } 457 | 458 | pub fn e4_test() { 459 | should.equal( 460 | tibe.infer( 461 | initial_environment(), 462 | EFunctions( 463 | [ 464 | GenericFunction( 465 | "id", 466 | Some(GenericType( 467 | ["A"], 468 | TConstructor( 469 | "Function1", 470 | [TConstructor("A", []), TConstructor("A", [])], 471 | ), 472 | )), 473 | ELambda([Parameter("x", None)], None, EVariable("x", [])), 474 | ), 475 | ], 476 | EApply(EVariable("id", []), [EString("foo")]), 477 | ), 478 | ), 479 | Ok(#( 480 | EFunctions( 481 | [ 482 | GenericFunction( 483 | "id", 484 | GenericType( 485 | ["A"], 486 | TConstructor( 487 | "Function1", 488 | [TConstructor("A", []), TConstructor("A", [])], 489 | ), 490 | ), 491 | ELambda( 492 | [Parameter("x", TConstructor("A", []))], 493 | TConstructor("A", []), 494 | EVariable("x", []), 495 | ), 496 | ), 497 | ], 498 | EApply(EVariable("id", [string_type()]), [EString("foo")]), 499 | ), 500 | string_type(), 501 | )), 502 | ) 503 | } 504 | 505 | pub fn e5_test() { 506 | should.equal( 507 | tibe.infer( 508 | initial_environment(), 509 | ELambda( 510 | [Parameter("x", None)], 511 | None, 512 | EApply(EVariable("x", []), [EVariable("x", [])]), 513 | ), 514 | ), 515 | Error(OccursError( 516 | index: 3, 517 | t: TConstructor("Function1", [TVariable(3), TVariable(1)]), 518 | )), 519 | ) 520 | } 521 | 522 | pub fn e6_test() { 523 | should.equal( 524 | tibe.infer( 525 | initial_environment(), 526 | EFunctions( 527 | [ 528 | GenericFunction( 529 | "compose", 530 | None, 531 | ELambda( 532 | [Parameter("f", None), Parameter("g", None)], 533 | None, 534 | ELambda( 535 | [Parameter("x", None)], 536 | None, 537 | EApply( 538 | EVariable("f", []), 539 | [EApply(EVariable("g", []), [EVariable("x", [])])], 540 | ), 541 | ), 542 | ), 543 | ), 544 | ], 545 | EVariable("compose", []), 546 | ), 547 | ), 548 | Ok(#( 549 | EFunctions( 550 | [ 551 | GenericFunction( 552 | "compose", 553 | GenericType( 554 | ["GenericVar5", "GenericVar6", "GenericVar7"], 555 | TConstructor( 556 | "Function2", 557 | [ 558 | TConstructor( 559 | "Function1", 560 | [ 561 | TConstructor("GenericVar7", []), 562 | TConstructor("GenericVar5", []), 563 | ], 564 | ), 565 | TConstructor( 566 | "Function1", 567 | [ 568 | TConstructor("GenericVar6", []), 569 | TConstructor("GenericVar7", []), 570 | ], 571 | ), 572 | TConstructor( 573 | "Function1", 574 | [ 575 | TConstructor("GenericVar6", []), 576 | TConstructor("GenericVar5", []), 577 | ], 578 | ), 579 | ], 580 | ), 581 | ), 582 | ELambda( 583 | [ 584 | Parameter( 585 | "f", 586 | TConstructor( 587 | "Function1", 588 | [ 589 | TConstructor("GenericVar7", []), 590 | TConstructor("GenericVar5", []), 591 | ], 592 | ), 593 | ), 594 | Parameter( 595 | "g", 596 | TConstructor( 597 | "Function1", 598 | [ 599 | TConstructor("GenericVar6", []), 600 | TConstructor("GenericVar7", []), 601 | ], 602 | ), 603 | ), 604 | ], 605 | TConstructor( 606 | "Function1", 607 | [ 608 | TConstructor("GenericVar6", []), 609 | TConstructor("GenericVar5", []), 610 | ], 611 | ), 612 | ELambda( 613 | [Parameter("x", TConstructor("GenericVar6", []))], 614 | TConstructor("GenericVar5", []), 615 | EApply( 616 | EVariable("f", []), 617 | [EApply(EVariable("g", []), [EVariable("x", [])])], 618 | ), 619 | ), 620 | ), 621 | ), 622 | ], 623 | EVariable("compose", [TVariable(9), TVariable(10), TVariable(11)]), 624 | ), 625 | TConstructor( 626 | "Function2", 627 | [ 628 | TConstructor("Function1", [TVariable(11), TVariable(9)]), 629 | TConstructor("Function1", [TVariable(10), TVariable(11)]), 630 | TConstructor("Function1", [TVariable(10), TVariable(9)]), 631 | ], 632 | ), 633 | )), 634 | ) 635 | } 636 | 637 | fn initial_environment() { 638 | let env = 639 | list.fold( 640 | ["+", "-", "*", "/"], 641 | map.new(), 642 | fn(acc, name) { 643 | let t = TConstructor("Function2", [int_type(), int_type(), int_type()]) 644 | 645 | map.insert(acc, name, GenericType([], t)) 646 | }, 647 | ) 648 | 649 | let env = 650 | list.fold( 651 | ["false", "true"], 652 | env, 653 | fn(acc, name) { 654 | let t = bool_type() 655 | 656 | map.insert(acc, name, GenericType([], t)) 657 | }, 658 | ) 659 | 660 | let env = 661 | list.fold( 662 | ["==", "!=", "<", ">"], 663 | env, 664 | fn(acc, name) { 665 | let t = TConstructor("Function2", [int_type(), int_type(), bool_type()]) 666 | 667 | map.insert(acc, name, GenericType([], t)) 668 | }, 669 | ) 670 | let env = 671 | map.insert( 672 | env, 673 | "if", 674 | GenericType( 675 | ["T"], 676 | TConstructor( 677 | "Function3", 678 | [ 679 | bool_type(), 680 | TConstructor("Function0", [TConstructor("T", [])]), 681 | TConstructor("Function0", [TConstructor("T", [])]), 682 | TConstructor("T", []), 683 | ], 684 | ), 685 | ), 686 | ) 687 | env 688 | } 689 | 690 | fn int_type() { 691 | TConstructor("Int", []) 692 | } 693 | 694 | fn string_type() { 695 | TConstructor("String", []) 696 | } 697 | 698 | fn bool_type() { 699 | TConstructor("Bool", []) 700 | } 701 | -------------------------------------------------------------------------------- /src/tibe.gleam: -------------------------------------------------------------------------------- 1 | //// [Type Inference by Example](https://ahnfelt.medium.com/type-inference-by-example-793d83f98382) 2 | //// but implemented in Gleam. 3 | //// 4 | //// Part 7 5 | 6 | import gleam/map.{Map} 7 | import gleam/list 8 | import gleam/string 9 | import gleam/int 10 | import gleam/option.{None, Option, Some} 11 | import gleam/set.{Set} 12 | import gleam/result.{try} 13 | 14 | /// AST type representing our language. 15 | /// 16 | /// An Expression is the input of our typechecker program. 17 | /// In `infer_type` it is processed to Types, Substutions, Environment mappings, 18 | /// and Constraints. 19 | /// 20 | /// It is parametrized by Option(GenericType) and Option(Type) 21 | /// (optional type annotations), and after typechecking it is parametrized by 22 | /// GenericType and Type. 23 | pub type Expression(g, t) { 24 | /// A list of functions that may potentially call each other 25 | /// and are in scope in `body`. 26 | /// 27 | /// In a real programming language, recursive functions could be declared with 28 | /// `let rec`, for example. The body is not that important, it's useful here 29 | /// as an example usage of those functions to demonstrate how typechecking 30 | /// works. 31 | EFunctions(functions: List(GenericFunction(g, t)), body: Expression(g, t)) 32 | /// A lambda (local function) is defined by a list of parameter names 33 | /// and the expression it evaluates to (body). 34 | /// 35 | /// A lambda can optionally have its return type or parameter types 36 | /// annotated before type checking. These fields also hold inferred types 37 | /// after type checking. 38 | ELambda( 39 | parameters: List(Parameter(t)), 40 | return_type: t, 41 | body: Expression(g, t), 42 | ) 43 | /// An application is a function call. It is defined by a function expression 44 | /// which will be called, and the argument expressions being passed 45 | /// to this function. 46 | EApply(function: Expression(g, t), arguments: List(Expression(g, t))) 47 | /// An expression variable is a name that is bound to some value. 48 | /// This can be a function argument, a let binding, or some predefined value 49 | /// like the "+" abstraction. 50 | /// 51 | /// Because the type of the value this name refers to can be generic, 52 | /// after typechecking the variable will hold a list of types used 53 | /// to instantiate this type. 54 | EVariable(name: String, generics: List(Type)) 55 | /// A let expression allows to bind some value to a name, so that it can be 56 | /// accessed in the current scope (inside of let's body). 57 | /// It can have its type optionally annotated. 58 | /// Inferred type gets also set there after inference. 59 | ELet( 60 | name: String, 61 | value_type: t, 62 | value: Expression(g, t), 63 | body: Expression(g, t), 64 | ) 65 | /// Literal integer expression 66 | EInt(value: Int) 67 | /// Literal string expression 68 | EString(value: String) 69 | /// Literal array expression. Can have its item types optionally annotated 70 | /// (in `item_type`). This is also where inferred item type gets set. 71 | EArray(item_type: t, items: List(Expression(g, t))) 72 | /// By having a Semicolon expression, expressions can be joined together 73 | /// into a sequential list without a need for introducing bogus let bindigs. 74 | ESemicolon(before: Expression(g, t), after: Expression(g, t)) 75 | } 76 | 77 | /// This is a (potentially) generic and/or recursive function 78 | /// used in the EFunctions expression. It is defined by its name, 79 | /// optional generic type annotation and its lambda expression definition. 80 | /// After typechecking the function_type field will hold its inferred type. 81 | pub type GenericFunction(g, t) { 82 | GenericFunction(name: String, function_type: g, lambda: Expression(g, t)) 83 | } 84 | 85 | /// A generic type holds a list of its generic type parameter names 86 | /// (e.g. A, B, etc) and its uninstantiated type that refers to those names. 87 | pub type GenericType { 88 | GenericType(generics: List(String), uninstantiated_type: Type) 89 | } 90 | 91 | /// A parameter is a helper type used to define ELambda. 92 | pub type Parameter(t) { 93 | Parameter(name: String, parameter_type: t) 94 | } 95 | 96 | /// The Type type represents the types of our small language. 97 | /// 98 | /// It is used internally starting from the input (expected type) 99 | /// of `infer_type` throughout the constraint solving (unification) 100 | /// to substitution. At the end of our typechecker program, Expressions 101 | /// have their type fields filled in 102 | /// (we go from Expression(Option(GenericType), Option(Type)) 103 | /// to Expression(GenericType, Type). 104 | pub type Type { 105 | /// A TConstructor is a concrete / ordinary / monomorphic type, 106 | /// or a placeholder referring to a generic type parameter with the same name. 107 | /// This is what we want our types to look like after type inference. 108 | /// It is defined by a type name and a list of type parameters. 109 | /// For example, it can be a simple type like Int with no type parameters, 110 | /// or a Function1 type with 2 type parameters: its argument type 111 | /// and return type. 112 | /// 113 | /// In Ahnfelt's tutorial type_parameters field is called generics. 114 | TConstructor(name: String, type_parameters: List(Type)) 115 | /// A type variable is an internal typechecker representation 116 | /// for a non concrete type. It was not yet inferred or substituted 117 | /// to an ordinary type. 118 | TVariable(index: Int) 119 | } 120 | 121 | /// The context holds all data useful for type checking collected along the way. 122 | /// In Ahnfelt's tutorial it is not needed because it uses mutation 123 | /// and class-local variables. 124 | pub type Context { 125 | Context( 126 | environment: Environment, 127 | type_constraints: TypeConstraints, 128 | substitution: Substitution, 129 | ) 130 | } 131 | 132 | /// The environment maps bound value names to their types 133 | /// (either concrete types, or just type variables). 134 | /// For non-generic types, their generics list is just empty. 135 | pub type Environment = 136 | Map(String, GenericType) 137 | 138 | /// A constraint represents the dependency between two type variables 139 | /// or concrete types, that still needs to be checked. 140 | pub type Constraint { 141 | CEquality(t1: Type, t2: Type) 142 | } 143 | 144 | /// The list of type constraints is one of the results of the `infer_type` 145 | /// function, and get checked ("solved") using unification. 146 | pub type TypeConstraints = 147 | List(Constraint) 148 | 149 | /// Initially for each type variable index, the Substitution map 150 | /// points to itself (a TVariable with the same index). 151 | /// As the type constraints get solved in unification, 152 | /// it points to more concrete types for them (directly or indirectly 153 | /// through pointing to other type variables). 154 | /// At the end of type checking. it's used to fill in the untyped expression 155 | /// with types information. 156 | pub type Substitution = 157 | Map(Int, Type) 158 | 159 | pub type TypeCheckError { 160 | OccursError(index: Int, t: Type) 161 | TypeMismatch(t1: Type, t2: Type) 162 | NotInScope(name: String) 163 | } 164 | 165 | /// This is the entry point of our typechecker program. 166 | /// Given an initial environment (with some constants or functions) 167 | /// and an untyped (optionally annotated) expression, 168 | /// it returns a typed expression and its value type, 169 | /// or a typechecking error. 170 | pub fn infer( 171 | environment: Environment, 172 | expression: Expression(Option(GenericType), Option(Type)), 173 | ) -> Result(#(Expression(GenericType, Type), Type), TypeCheckError) { 174 | let context = 175 | Context( 176 | environment: environment, 177 | type_constraints: [], 178 | substitution: map.new(), 179 | ) 180 | let #(t, context) = fresh_type_variable(context) 181 | use #(expression, context) <- try(infer_type(expression, t, context)) 182 | use context <- try(solve_constraints(context)) 183 | Ok(#( 184 | substitute_expression(expression, context.substitution), 185 | substitute(t, context.substitution), 186 | )) 187 | } 188 | 189 | /// A function which takes an expression and its expected annotated type 190 | /// and recursively turns it into (potentially unsolved) type variables. 191 | /// To infer without checking, pass a fresh type variable as expected_type. 192 | /// Along the way it is introducing new functions or let bindings 193 | /// into the Environment, making sure that they don't escape the scope 194 | /// in which they are declared, and checking that referenced name bindings 195 | /// exist in the Environment. 196 | pub fn infer_type( 197 | expression: Expression(Option(GenericType), Option(Type)), 198 | expected_type: Type, 199 | context: Context, 200 | ) -> Result(#(Expression(GenericType, Type), Context), TypeCheckError) { 201 | case expression { 202 | EFunctions(functions: functions, body: body) -> { 203 | // For a group of (potentially) recursive functions, 204 | // when checking one of them, a different one (or itself) can be called 205 | // in its lambda body. So, to have them available when checking, 206 | // we first go through all functions and add their name with type 207 | // to the environment. The type is either an annotation (when present), 208 | // or a fresh type variable. 209 | // We also keep track of which functions had a type annotation. 210 | let #(recursive_environment, annotations_tracker, context) = 211 | list.fold( 212 | functions, 213 | #(context.environment, map.new(), context), 214 | fn(acc, function) { 215 | let #(recursive_environment, annotations_tracker, context) = acc 216 | let #(function_type, had_annotation, context) = case 217 | function.function_type 218 | { 219 | None -> { 220 | let #(t, context) = fresh_type_variable(context) 221 | #( 222 | GenericType(generics: [], uninstantiated_type: t), 223 | False, 224 | context, 225 | ) 226 | } 227 | Some(generic_function_type) -> #( 228 | generic_function_type, 229 | True, 230 | context, 231 | ) 232 | } 233 | #( 234 | map.insert(recursive_environment, function.name, function_type), 235 | map.insert(annotations_tracker, function.name, had_annotation), 236 | context, 237 | ) 238 | }, 239 | ) 240 | // For each function, we try to infer the type of its definition, 241 | // and accumulate the "typed" (likely still with type variables) 242 | // functions into a list. When inference or checking fails 243 | // in this process, we return the error. 244 | use #(ungeneralized_functions_reversed, recursive_functions_context) <- try(list.try_fold( 245 | functions, 246 | #([], context), 247 | fn(acc, function) { 248 | let #(functions, context) = acc 249 | let assert Ok(generic_function_type) = 250 | map.get(recursive_environment, function.name) 251 | use #(lambda, context) <- try(infer_type( 252 | function.lambda, 253 | generic_function_type.uninstantiated_type, 254 | Context(..context, environment: recursive_environment), 255 | )) 256 | Ok(#( 257 | [ 258 | GenericFunction( 259 | name: function.name, 260 | function_type: generic_function_type, 261 | lambda: lambda, 262 | ), 263 | ..functions 264 | ], 265 | context, 266 | )) 267 | }, 268 | )) 269 | let ungeneralized_functions = 270 | list.reverse(ungeneralized_functions_reversed) 271 | // We want to find the functions which don't have a type annotation 272 | // but are generic. To do that, we first try to solve type constraints 273 | // to confirm that the functions can typecheck at all. If they can't, 274 | // we return an error here. 275 | use recursive_functions_context <- try(solve_constraints( 276 | recursive_functions_context, 277 | )) 278 | let new_functions = 279 | list.map( 280 | ungeneralized_functions, 281 | fn(function) { 282 | let assert Ok(generic_function_type) = 283 | map.get(recursive_environment, function.name) 284 | let assert Ok(had_annotation) = 285 | map.get(annotations_tracker, function.name) 286 | case had_annotation { 287 | True -> function 288 | False -> { 289 | // If a function didn't have a type annotation, it could still 290 | // be generic, so we attempt to generalize it. 291 | let function_type = generic_function_type.uninstantiated_type 292 | let #(new_type_annotation, new_lambda) = 293 | generalize( 294 | context.environment, 295 | recursive_functions_context.substitution, 296 | function_type, 297 | function.lambda, 298 | ) 299 | GenericFunction( 300 | name: function.name, 301 | function_type: new_type_annotation, 302 | lambda: new_lambda, 303 | ) 304 | } 305 | } 306 | }, 307 | ) 308 | // Because some function types could have been generalized, 309 | // we prepare a new environment to check the body expression 310 | // (similar to recursive_functions_context but with generic functions 311 | // having their lists of generics in generic type filled in). 312 | let new_environment = 313 | list.fold( 314 | new_functions, 315 | context.environment, 316 | fn(acc, function) { 317 | map.insert(acc, function.name, function.function_type) 318 | }, 319 | ) 320 | let new_context = 321 | Context(..recursive_functions_context, environment: new_environment) 322 | // We try to infer / check the body expression (or return an error). 323 | use #(body, body_context) <- try(infer_type( 324 | body, 325 | expected_type, 326 | new_context, 327 | )) 328 | // We returned the typed EFunctions expression. 329 | // Because in our case the recursive functions were only in scope 330 | // for the body, and whole EFunctions expression evaluates to body, 331 | // we reset the environment to not have them. 332 | Ok(#( 333 | EFunctions(functions: new_functions, body: body), 334 | Context(..body_context, environment: context.environment), 335 | )) 336 | } 337 | ELambda(parameters: parameters, return_type: return_type, body: body) -> { 338 | let #(return_type, context) = type_or_fresh_variable(return_type, context) 339 | // We add lambda parameters to the environment for checking the body. 340 | // We also prepare a list of their types for convenience. 341 | let #(parameters_reversed, parameter_types_reversed, body_context) = 342 | list.fold( 343 | parameters, 344 | #([], [], context), 345 | fn(acc, parameter) { 346 | let #(parameters_acc, parameter_types_acc, context) = acc 347 | let Parameter(name: parameter_name, parameter_type: parameter_type) = 348 | parameter 349 | let #(t, context) = type_or_fresh_variable(parameter_type, context) 350 | let context = 351 | Context( 352 | ..context, 353 | environment: map.insert( 354 | context.environment, 355 | parameter_name, 356 | GenericType(generics: [], uninstantiated_type: t), 357 | ), 358 | ) 359 | #( 360 | [ 361 | Parameter(name: parameter_name, parameter_type: t), 362 | ..parameters_acc 363 | ], 364 | [t, ..parameter_types_acc], 365 | context, 366 | ) 367 | }, 368 | ) 369 | // We try to infer / check the type of lambda body, or return an error 370 | // if it fails. 371 | use #(body, body_context) <- try(infer_type( 372 | body, 373 | return_type, 374 | body_context, 375 | )) 376 | // We constrain the the expected type (which comes either from 377 | // user's function annotation or EApply function call) with inferred 378 | // function type. To do that, we first glue together parameter and return 379 | // types into the expected format. 380 | let type_parameters = 381 | list.reverse([return_type, ..parameter_types_reversed]) 382 | let type_name = 383 | string.join( 384 | ["Function", int.to_string(list.length(parameter_types_reversed))], 385 | with: "", 386 | ) 387 | let t = TConstructor(name: type_name, type_parameters: type_parameters) 388 | let context = 389 | constrain_type( 390 | expected_type, 391 | t, 392 | Context(..body_context, environment: context.environment), 393 | ) 394 | // We return the typed lambda expression. 395 | Ok(#( 396 | ELambda( 397 | parameters: list.reverse(parameters_reversed), 398 | return_type: return_type, 399 | body: body, 400 | ), 401 | context, 402 | )) 403 | } 404 | EApply(function: function, arguments: arguments) -> { 405 | // We prepare the expected function type using type variables 406 | // for each passed argument and expected_type (type of the whole EApply 407 | // expression) as the function call return type. 408 | let #(argument_types_reversed, context) = 409 | list.fold( 410 | arguments, 411 | #([], context), 412 | fn(acc, _arg) { 413 | let #(argument_types_acc, context) = acc 414 | let #(t, context) = fresh_type_variable(context) 415 | #([t, ..argument_types_acc], context) 416 | }, 417 | ) 418 | let type_name = 419 | string.join( 420 | ["Function", int.to_string(list.length(arguments))], 421 | with: "", 422 | ) 423 | let type_parameters = 424 | list.reverse([expected_type, ..argument_types_reversed]) 425 | let function_type = 426 | TConstructor(name: type_name, type_parameters: type_parameters) 427 | // We infer / check the type of the called function 428 | // (which could be just a function name, but could be an in-line lambda 429 | // or another function call that returns a lambda), and make sure to pass 430 | // the type of expected function as expected_type. We return an error 431 | // if it fails. 432 | use #(function, context) <- try(infer_type( 433 | function, 434 | function_type, 435 | context, 436 | )) 437 | // For each function argument, we try to infer its type 438 | // (or return an error). I think alternatively we could do that earlier 439 | // before inferring the type of the called function? The order may affect 440 | // which error would get returned in case of mismatched function and/or 441 | // parameter/argument types. 442 | use #(arguments_reversed, context) <- try( 443 | arguments 444 | |> list.zip(list.reverse(argument_types_reversed)) 445 | |> list.try_fold( 446 | #([], context), 447 | fn(acc, argument_with_type) { 448 | let #(arguments_acc, context) = acc 449 | let #(argument, argument_type) = argument_with_type 450 | use #(argument, context) <- try(infer_type( 451 | argument, 452 | argument_type, 453 | context, 454 | )) 455 | Ok(#([argument, ..arguments_acc], context)) 456 | }, 457 | ), 458 | ) 459 | // We return the typed function call expression. 460 | Ok(#( 461 | EApply(function: function, arguments: list.reverse(arguments_reversed)), 462 | context, 463 | )) 464 | } 465 | ELet(name: name, value_type: value_type, value: value, body: body) -> { 466 | let #(value_type, context) = type_or_fresh_variable(value_type, context) 467 | // We infer / check the type of binding value (or return an error), 468 | // and add it to the environment for inferring / checking the body. 469 | use #(value, context) <- try(infer_type(value, value_type, context)) 470 | let body_context = 471 | Context( 472 | ..context, 473 | environment: map.insert( 474 | context.environment, 475 | name, 476 | GenericType(generics: [], uninstantiated_type: value_type), 477 | ), 478 | ) 479 | // We try to infer the type of the let body (or return an error). 480 | use #(body, body_context) <- try(infer_type( 481 | body, 482 | expected_type, 483 | body_context, 484 | )) 485 | // We return the typed expression (without the name present in the 486 | // environment, since its scope is just the let body). 487 | Ok(#( 488 | ELet(name: name, value_type: value_type, value: value, body: body), 489 | Context(..body_context, environment: context.environment), 490 | )) 491 | } 492 | EVariable(name: x, generics: annotated_generics) -> 493 | // We look up the variable name in the environment (or return an error). 494 | case map.get(context.environment, x) { 495 | Error(_) -> Error(NotInScope(x)) 496 | Ok(GenericType( 497 | generics: generics, 498 | uninstantiated_type: uninstantiated_type, 499 | )) -> { 500 | // Since the variable name can refer to a value of generic type, 501 | // and to type check the usage of this variable 502 | // we will need a concrete type, we prepare a map from each generic 503 | // type placeholder (like A, B etc) to a type variable. 504 | let #(new_generics_reversed, instantiation, context) = 505 | list.fold( 506 | generics, 507 | #([], map.new(), context), 508 | fn(acc, name) { 509 | let #(new_generics_acc, instantiation, context) = acc 510 | let #(v, context) = fresh_type_variable(context) 511 | let instantiation = map.insert(instantiation, name, v) 512 | #([v, ..new_generics_acc], instantiation, context) 513 | }, 514 | ) 515 | let new_generics = list.reverse(new_generics_reversed) 516 | // We call a helper function to replace the generics in the type 517 | // fetched from the environment (type placeholders like A, B etc) 518 | // with previously prepared type variables. The result is the type 519 | // of our variable. 520 | let variable_type = 521 | instantiate(instantiation, uninstantiated_type, context) 522 | let context = case annotated_generics { 523 | [] -> context 524 | annotated_generics -> { 525 | // If the user supplied annotations for instantiating generics 526 | // on the variable usage, we make sure they provided the correct 527 | // number of generic type annotations. 528 | // TODO: this should be another possible error to be returned. 529 | let assert True = 530 | list.length(annotated_generics) == list.length(new_generics) 531 | // We constrain our type variables (replacements for generic 532 | // type placeholders) with the types the user provided. 533 | list.zip(annotated_generics, new_generics) 534 | |> list.fold( 535 | context, 536 | fn(context, el) { 537 | let #(annotation, type_variable) = el 538 | constrain_type(annotation, type_variable, context) 539 | }, 540 | ) 541 | } 542 | } 543 | // We constrain the instantiated type of our variable 544 | // with the type this variable usage is expected to have. 545 | let context = constrain_type(expected_type, variable_type, context) 546 | // We return the variable expression with type variables 547 | // in place of generics. 548 | Ok(#(EVariable(name: x, generics: new_generics), context)) 549 | } 550 | } 551 | EInt(value: v) -> 552 | // For Integer literal expressions all we need to do is constrain 553 | // the expected type with the Int type. 554 | Ok(#( 555 | EInt(value: v), 556 | constrain_type(expected_type, TConstructor("Int", []), context), 557 | )) 558 | EString(value: v) -> 559 | // Same as EInt 560 | Ok(#( 561 | EString(value: v), 562 | constrain_type(expected_type, TConstructor("String", []), context), 563 | )) 564 | EArray(item_type: item_type, items: items) -> { 565 | // Each item in an array will need to have the same type, 566 | // so if the type is not annotated we create a new variable 567 | // for checking that. 568 | let #(item_type, context) = type_or_fresh_variable(item_type, context) 569 | // For each item in the array we try to infer its type passing the 570 | // expected item type (or return an error). 571 | use #(items_reversed, context) <- try(list.try_fold( 572 | items, 573 | #([], context), 574 | fn(acc, item) { 575 | let #(items_acc, context) = acc 576 | use #(item, context) <- try(infer_type(item, item_type, context)) 577 | Ok(#([item, ..items_acc], context)) 578 | }, 579 | )) 580 | // We return the typed array expression. 581 | Ok(#( 582 | EArray(item_type: item_type, items: list.reverse(items_reversed)), 583 | constrain_type( 584 | expected_type, 585 | TConstructor("Array", [item_type]), 586 | context, 587 | ), 588 | )) 589 | } 590 | ESemicolon(before: before, after: after) -> { 591 | let #(before_type, context) = fresh_type_variable(context) 592 | // We only care about the type of the last expression 593 | // in an expressions sequence, so for checking the "before" expression 594 | // we pass in a fresh type variable as expected type. 595 | use #(new_before, context) <- try(infer_type(before, before_type, context)) 596 | use #(new_after, context) <- try(infer_type(after, expected_type, context)) 597 | // We return the typed expressions sequence. 598 | Ok(#(ESemicolon(before: new_before, after: new_after), context)) 599 | } 600 | } 601 | } 602 | 603 | // A helper function used to get a type annotation if the user supplied it, 604 | // or generate a fresh type variable if not. 605 | pub fn type_or_fresh_variable( 606 | maybe_type_annotation: Option(Type), 607 | context: Context, 608 | ) -> #(Type, Context) { 609 | case maybe_type_annotation { 610 | Some(type_annotation) -> #(type_annotation, context) 611 | None -> fresh_type_variable(context) 612 | } 613 | } 614 | 615 | /// A helper function used to assign a new index to a type variable, 616 | /// and initialize it correctly in the substitution map in the context. 617 | pub fn fresh_type_variable(context: Context) -> #(Type, Context) { 618 | let i = map.size(context.substitution) 619 | let t = TVariable(index: i) 620 | let context = 621 | Context(..context, substitution: map.insert(context.substitution, i, t)) 622 | #(t, context) 623 | } 624 | 625 | // A helper function used to add an equality constraint between two types 626 | // to the context. 627 | pub fn constrain_type(t1: Type, t2: Type, context: Context) -> Context { 628 | Context( 629 | ..context, 630 | type_constraints: [CEquality(t1, t2), ..context.type_constraints], 631 | ) 632 | } 633 | 634 | // A helper function used to replace the generic type placeholders 635 | // (like A, B etc) with some type according to the instantiation map. 636 | // It recurses to do that everywhere in the type. 637 | fn instantiate( 638 | instantiation: Map(String, Type), 639 | t: Type, 640 | context: Context, 641 | ) -> Type { 642 | case map.size(instantiation) == 0 { 643 | True -> t 644 | False -> 645 | case t { 646 | TVariable(index: i) -> 647 | case map.get(context.substitution, i) { 648 | Ok(substituted_t) if substituted_t != t -> 649 | instantiate(instantiation, substituted_t, context) 650 | _ -> t 651 | } 652 | TConstructor(name: name, type_parameters: type_parameters) -> 653 | case map.get(instantiation, name) { 654 | Ok(instantiation_type) -> { 655 | let assert True = type_parameters == [] 656 | instantiation_type 657 | } 658 | _ -> 659 | TConstructor( 660 | name: name, 661 | type_parameters: list.map( 662 | type_parameters, 663 | fn(type_parameter) { 664 | instantiate(instantiation, type_parameter, context) 665 | }, 666 | ), 667 | ) 668 | } 669 | t -> t 670 | } 671 | } 672 | } 673 | 674 | // A helper function used to replace some type variables inside a type 675 | // with generic type placeholders (like A, B, C etc) where possible. 676 | fn generalize( 677 | environment: Environment, 678 | substitution: Substitution, 679 | t: Type, 680 | expression: Expression(GenericType, Type), 681 | ) -> #(GenericType, Expression(GenericType, Type)) { 682 | let generic_type_variables = 683 | set.fold( 684 | free_in_environment(environment, substitution), 685 | free_in_type(environment, substitution, t), 686 | fn(acc, i) { set.delete(acc, i) }, 687 | ) 688 | let generic_type_variables = 689 | generic_type_variables 690 | |> set.to_list() 691 | |> list.sort(int.compare) 692 | let #(generic_names_reversed, local_substitution) = 693 | list.fold( 694 | generic_type_variables, 695 | #([], substitution), 696 | fn(acc, i) { 697 | let #(names, substitution) = acc 698 | // TODO: this should generate names like A, B, C etc 699 | let name = string.concat(["GenericVar", int.to_string(i)]) 700 | #( 701 | [name, ..names], 702 | map.insert( 703 | substitution, 704 | i, 705 | TConstructor(name: name, type_parameters: []), 706 | ), 707 | ) 708 | }, 709 | ) 710 | let new_expression = substitute_expression(expression, local_substitution) 711 | let new_type = substitute(t, local_substitution) 712 | #( 713 | GenericType( 714 | generics: list.reverse(generic_names_reversed), 715 | uninstantiated_type: new_type, 716 | ), 717 | new_expression, 718 | ) 719 | } 720 | 721 | fn free_in_type( 722 | environment: Environment, 723 | substitution: Substitution, 724 | t: Type, 725 | ) -> Set(Int) { 726 | case t { 727 | TVariable(index: i) -> 728 | case map.get(substitution, i) { 729 | Ok(substituted_t) if substituted_t != t -> 730 | free_in_type(environment, substitution, substituted_t) 731 | _ -> set.insert(set.new(), i) 732 | } 733 | TConstructor(name: _, type_parameters: type_parameters) -> 734 | list.fold( 735 | type_parameters, 736 | set.new(), 737 | fn(acc, t) { 738 | set.union(acc, free_in_type(environment, substitution, t)) 739 | }, 740 | ) 741 | } 742 | } 743 | 744 | fn free_in_environment( 745 | environment: Environment, 746 | substitution: Substitution, 747 | ) -> Set(Int) { 748 | map.values(environment) 749 | |> list.fold( 750 | set.new(), 751 | fn(acc, generic_type) { 752 | set.union( 753 | acc, 754 | free_in_type( 755 | environment, 756 | substitution, 757 | generic_type.uninstantiated_type, 758 | ), 759 | ) 760 | }, 761 | ) 762 | } 763 | 764 | /// A function which "solves" (and gets rid of) type constraints 765 | /// using unification, or returns unification errors. 766 | pub fn solve_constraints(context: Context) -> Result(Context, TypeCheckError) { 767 | use substitution <- try( 768 | context.type_constraints 769 | |> list.reverse() 770 | |> list.try_fold( 771 | context.substitution, 772 | fn(substitution, constraint) { 773 | let assert CEquality(t1, t2) = constraint 774 | unify(t1, t2, substitution) 775 | }, 776 | ), 777 | ) 778 | Ok(Context(..context, type_constraints: [], substitution: substitution)) 779 | } 780 | 781 | /// A function which checks that two types are (or can be) the same, 782 | /// returns an error if they cannot, and returns an updated Substitution 783 | /// mapping. 784 | pub fn unify( 785 | t1: Type, 786 | t2: Type, 787 | substitution: Substitution, 788 | ) -> Result(Substitution, TypeCheckError) { 789 | case t1, t2 { 790 | TConstructor(name: name1, type_parameters: generics1), TConstructor( 791 | name: name2, 792 | type_parameters: generics2, 793 | ) -> 794 | case name1 != name2 || list.length(generics1) != list.length(generics2) { 795 | True -> Error(TypeMismatch(t1, t2)) 796 | False -> 797 | list.zip(generics1, generics2) 798 | |> list.try_fold( 799 | substitution, 800 | fn(substitution, t) { 801 | let #(t1, t2) = t 802 | unify(t1, t2, substitution) 803 | }, 804 | ) 805 | } 806 | TVariable(index: i), TVariable(index: j) if i == j -> Ok(substitution) 807 | t1, t2 -> 808 | case follow(t1, substitution) { 809 | Ok(t) -> unify(t, t2, substitution) 810 | Error(Nil) -> 811 | case follow(t2, substitution) { 812 | Ok(t) -> unify(t1, t, substitution) 813 | Error(Nil) -> 814 | case t1, t2 { 815 | TVariable(index: i), _ -> 816 | case occurs_in(i, t2, substitution) { 817 | False -> Ok(map.insert(substitution, i, t2)) 818 | True -> Error(OccursError(i, t2)) 819 | } 820 | _, TVariable(index: i) -> 821 | case occurs_in(i, t1, substitution) { 822 | False -> Ok(map.insert(substitution, i, t1)) 823 | True -> Error(OccursError(i, t1)) 824 | } 825 | _, _ -> Error(TypeMismatch(t1, t2)) 826 | } 827 | } 828 | } 829 | } 830 | } 831 | 832 | /// A helper function used to get the type some type variable (to_follow) 833 | /// has been already unified with. 834 | pub fn follow(to_follow: Type, substitution: Substitution) -> Result(Type, Nil) { 835 | case to_follow { 836 | TVariable(index: i) -> { 837 | let assert Ok(t) = map.get(substitution, i) 838 | case t != to_follow { 839 | True -> Ok(t) 840 | False -> Error(Nil) 841 | } 842 | } 843 | _ -> Error(Nil) 844 | } 845 | } 846 | 847 | /// A function to help with checking that the types are not recursive. 848 | pub fn occurs_in(index: Int, t: Type, substitution: Substitution) -> Bool { 849 | case t { 850 | TVariable(index: i) -> 851 | case follow(t, substitution) { 852 | Ok(t) -> occurs_in(index, t, substitution) 853 | _ -> i == index 854 | } 855 | TConstructor(name: _, type_parameters: generics) -> 856 | case list.find(generics, occurs_in(index, _, substitution)) { 857 | Ok(_) -> True 858 | Error(Nil) -> False 859 | } 860 | } 861 | } 862 | 863 | /// A function to traverse an expression and reprace all type variables 864 | /// with concrete types at the end of typechecking (where possible) 865 | pub fn substitute_expression( 866 | expression: Expression(GenericType, Type), 867 | substitution: Substitution, 868 | ) -> Expression(GenericType, Type) { 869 | case expression { 870 | EFunctions(functions: functions, body: body) -> { 871 | let functions = 872 | list.map( 873 | functions, 874 | fn(function) { 875 | let GenericFunction( 876 | name: name, 877 | function_type: function_type, 878 | lambda: lambda, 879 | ) = function 880 | let function_type = 881 | GenericType( 882 | generics: function_type.generics, 883 | uninstantiated_type: substitute( 884 | function_type.uninstantiated_type, 885 | substitution, 886 | ), 887 | ) 888 | let lambda = substitute_expression(lambda, substitution) 889 | GenericFunction( 890 | name: name, 891 | function_type: function_type, 892 | lambda: lambda, 893 | ) 894 | }, 895 | ) 896 | let body = substitute_expression(body, substitution) 897 | EFunctions(functions: functions, body: body) 898 | } 899 | ELambda(parameters: parameters, return_type: return_type, body: body) -> { 900 | let return_type = substitute(return_type, substitution) 901 | let parameters = 902 | list.map( 903 | parameters, 904 | fn(parameter) { 905 | Parameter( 906 | ..parameter, 907 | parameter_type: substitute(parameter.parameter_type, substitution), 908 | ) 909 | }, 910 | ) 911 | let body = substitute_expression(body, substitution) 912 | ELambda(parameters: parameters, return_type: return_type, body: body) 913 | } 914 | EApply(function: function, arguments: arguments) -> { 915 | let function = substitute_expression(function, substitution) 916 | let arguments = 917 | list.map(arguments, substitute_expression(_, substitution)) 918 | EApply(function: function, arguments: arguments) 919 | } 920 | EVariable(name: name, generics: generics) -> { 921 | let new_generics = list.map(generics, substitute(_, substitution)) 922 | EVariable(name: name, generics: new_generics) 923 | } 924 | ELet(name: name, value_type: value_type, value: value, body: body) -> { 925 | let value_type = substitute(value_type, substitution) 926 | let value = substitute_expression(value, substitution) 927 | let body = substitute_expression(body, substitution) 928 | ELet(name: name, value_type: value_type, value: value, body: body) 929 | } 930 | EInt(_) -> expression 931 | EString(_) -> expression 932 | EArray(item_type: item_type, items: items) -> { 933 | let item_type = substitute(item_type, substitution) 934 | let items = list.map(items, substitute_expression(_, substitution)) 935 | EArray(item_type: item_type, items: items) 936 | } 937 | ESemicolon(before: before, after: after) -> { 938 | let before = substitute_expression(before, substitution) 939 | let after = substitute_expression(after, substitution) 940 | ESemicolon(before: before, after: after) 941 | } 942 | } 943 | } 944 | 945 | /// A function to recursively replace all type variables inside a type 946 | /// at the end of type checking with concrete types (where possible). 947 | pub fn substitute(t: Type, substitution: Substitution) -> Type { 948 | case t { 949 | TVariable(_) -> 950 | case follow(t, substitution) { 951 | Ok(t) -> substitute(t, substitution) 952 | _ -> t 953 | } 954 | TConstructor(name: name, type_parameters: generics) -> 955 | TConstructor( 956 | name: name, 957 | type_parameters: list.map(generics, substitute(_, substitution)), 958 | ) 959 | } 960 | } 961 | 962 | pub fn main() { 963 | Nil 964 | } 965 | --------------------------------------------------------------------------------