From eba4f27259d1c4d549e1079d7326fc8dc70f90bb Mon Sep 17 00:00:00 2001 From: Md Isfarul Haque Date: Tue, 17 Dec 2024 02:32:55 +0900 Subject: [PATCH] Improve type-checking (#19) Adds type-checking to the following cases: - block outputs (reflect the type of the last statement, none otherwise) - parameters in def (any if not specified, else the specified type) - if else (reflect the type of the blocks if the blocks are equal, if they are unequal then forbidden, and the if branch type otherwise) --------- Signed-off-by: innocentzero --- ...nu_parser__test__node_output@alias.nu.snap | 25 ++ ...test__node_output@binary_ops_exact.nu.snap | 3 +- ...t__node_output@binary_ops_mismatch.nu.snap | 15 +- ...t__node_output@binary_ops_subtypes.nu.snap | 3 +- ...nu_parser__test__node_output@calls.nu.snap | 21 +- ...parser__test__node_output@closure3.nu.snap | 61 +++++ ...w_nu_parser__test__node_output@def.nu.snap | 19 +- ...w_nu_parser__test__node_output@for.nu.snap | 62 +++-- ...st__node_output@for_break_continue.nu.snap | 52 ++-- ...w_nu_parser__test__node_output@if_.nu.snap | 13 +- ...rser__test__node_output@invalid_if.nu.snap | 28 ++ ...r__test__node_output@invalid_types.nu.snap | 21 +- ..._nu_parser__test__node_output@let_.nu.snap | 3 +- ...er__test__node_output@let_mismatch.nu.snap | 5 +- ..._nu_parser__test__node_output@list.nu.snap | 3 +- ...parser__test__node_output@literals.nu.snap | 3 +- ..._nu_parser__test__node_output@loop.nu.snap | 3 +- ...nu_parser__test__node_output@match.nu.snap | 49 ++++ ..._nu_parser__test__node_output@math.nu.snap | 3 +- ..._test__node_output@math_precedence.nu.snap | 3 +- ...u_parser__test__node_output@record.nu.snap | 3 +- ..._parser__test__node_output@record2.nu.snap | 3 +- ..._parser__test__node_output@record3.nu.snap | 3 +- ..._parser__test__node_output@reparse.nu.snap | 5 +- ...u_parser__test__node_output@string.nu.snap | 3 +- ...test__node_output@string_operation.nu.snap | 3 +- ...nu_parser__test__node_output@table.nu.snap | 3 +- ...u_parser__test__node_output@table2.nu.snap | 3 +- ...nu_parser__test__node_output@while.nu.snap | 40 +++ src/typechecker.rs | 240 ++++++++++++++++-- tests/closure3.nu | 3 + tests/for.nu | 2 +- tests/invalid_if.nu | 5 + tests/match.nu | 11 + tests/while.nu | 4 + 35 files changed, 592 insertions(+), 134 deletions(-) create mode 100644 src/snapshots/new_nu_parser__test__node_output@alias.nu.snap create mode 100644 src/snapshots/new_nu_parser__test__node_output@closure3.nu.snap create mode 100644 src/snapshots/new_nu_parser__test__node_output@invalid_if.nu.snap create mode 100644 src/snapshots/new_nu_parser__test__node_output@match.nu.snap create mode 100644 src/snapshots/new_nu_parser__test__node_output@while.nu.snap create mode 100644 tests/closure3.nu create mode 100644 tests/invalid_if.nu create mode 100644 tests/match.nu create mode 100644 tests/while.nu diff --git a/src/snapshots/new_nu_parser__test__node_output@alias.nu.snap b/src/snapshots/new_nu_parser__test__node_output@alias.nu.snap new file mode 100644 index 0000000..79badab --- /dev/null +++ b/src/snapshots/new_nu_parser__test__node_output@alias.nu.snap @@ -0,0 +1,25 @@ +--- +source: src/test.rs +expression: evaluate_example(path) +input_file: tests/alias.nu +snapshot_kind: text +--- +==== COMPILER ==== +0: Name (4 to 7) "foo" +1: Name (9 to 10) "a" +2: Param { name: NodeId(1), ty: None } (9 to 10) +3: Params([NodeId(2)]) (8 to 11) +4: Variable (14 to 16) "$a" +5: Block(BlockId(0)) (12 to 18) +6: Def { name: NodeId(0), params: NodeId(3), return_ty: None, block: NodeId(5) } (0 to 18) +7: Name (20 to 25) "alias" +8: Name (26 to 29) "bar" +9: Garbage (30 to 31) +10: String (32 to 35) "foo" +11: Call { parts: [NodeId(7), NodeId(8), NodeId(9), NodeId(10)] } (26 to 35) +12: Name (37 to 40) "bar" +13: Int (41 to 42) "1" +14: Call { parts: [NodeId(12), NodeId(13)] } (41 to 42) +15: Block(BlockId(1)) (0 to 43) +==== COMPILER ERRORS ==== +Error (NodeId 9): incomplete expression diff --git a/src/snapshots/new_nu_parser__test__node_output@binary_ops_exact.nu.snap b/src/snapshots/new_nu_parser__test__node_output@binary_ops_exact.nu.snap index dff4179..cb8c98e 100644 --- a/src/snapshots/new_nu_parser__test__node_output@binary_ops_exact.nu.snap +++ b/src/snapshots/new_nu_parser__test__node_output@binary_ops_exact.nu.snap @@ -2,6 +2,7 @@ source: src/test.rs expression: evaluate_example(path) input_file: tests/binary_ops_exact.nu +snapshot_kind: text --- ==== COMPILER ==== 0: Int (0 to 1) "1" @@ -50,4 +51,4 @@ input_file: tests/binary_ops_exact.nu 18: forbidden 19: bool 20: bool -21: () +21: bool diff --git a/src/snapshots/new_nu_parser__test__node_output@binary_ops_mismatch.nu.snap b/src/snapshots/new_nu_parser__test__node_output@binary_ops_mismatch.nu.snap index 1c9fa1a..44a614a 100644 --- a/src/snapshots/new_nu_parser__test__node_output@binary_ops_mismatch.nu.snap +++ b/src/snapshots/new_nu_parser__test__node_output@binary_ops_mismatch.nu.snap @@ -2,6 +2,7 @@ source: src/test.rs expression: evaluate_example(path) input_file: tests/binary_ops_mismatch.nu +snapshot_kind: text --- ==== COMPILER ==== 0: String (0 to 3) ""a"" @@ -21,18 +22,18 @@ input_file: tests/binary_ops_mismatch.nu 0: Frame Scope, node_id: NodeId(12) (empty) ==== TYPES ==== 0: string -1: forbidden +1: error 2: float -3: unknown +3: error 4: string -5: forbidden +5: error 6: float -7: unknown +7: error 8: bool -9: forbidden +9: error 10: string -11: unknown -12: () +11: error +12: error ==== TYPE ERRORS ==== Error (NodeId 1): type mismatch: unsupported addition between string and float Error (NodeId 5): type mismatch: unsupported append between string and float diff --git a/src/snapshots/new_nu_parser__test__node_output@binary_ops_subtypes.nu.snap b/src/snapshots/new_nu_parser__test__node_output@binary_ops_subtypes.nu.snap index ea97b25..180d535 100644 --- a/src/snapshots/new_nu_parser__test__node_output@binary_ops_subtypes.nu.snap +++ b/src/snapshots/new_nu_parser__test__node_output@binary_ops_subtypes.nu.snap @@ -2,6 +2,7 @@ source: src/test.rs expression: evaluate_example(path) input_file: tests/binary_ops_subtypes.nu +snapshot_kind: text --- ==== COMPILER ==== 0: Int (0 to 1) "1" @@ -94,4 +95,4 @@ input_file: tests/binary_ops_subtypes.nu 40: list 41: list> 42: list> -43: () +43: list> diff --git a/src/snapshots/new_nu_parser__test__node_output@calls.nu.snap b/src/snapshots/new_nu_parser__test__node_output@calls.nu.snap index fb5bdbc..04a601d 100644 --- a/src/snapshots/new_nu_parser__test__node_output@calls.nu.snap +++ b/src/snapshots/new_nu_parser__test__node_output@calls.nu.snap @@ -2,6 +2,7 @@ source: src/test.rs expression: evaluate_example(path) input_file: tests/calls.nu +snapshot_kind: text --- ==== COMPILER ==== 0: Name (0 to 4) "spam" @@ -59,21 +60,21 @@ input_file: tests/calls.nu 9: unknown 10: unknown 11: string -12: unknown +12: string 13: unknown 14: unknown 15: string -16: unknown +16: string 17: unknown 18: unknown 19: int -20: unknown -21: unknown -22: unknown -23: unknown -24: unknown -25: list -26: () +20: int +21: forbidden +22: string +23: string +24: int +25: list +26: list 27: () 28: unknown 29: string @@ -83,4 +84,4 @@ input_file: tests/calls.nu 33: string 34: int 35: any -36: () +36: any diff --git a/src/snapshots/new_nu_parser__test__node_output@closure3.nu.snap b/src/snapshots/new_nu_parser__test__node_output@closure3.nu.snap new file mode 100644 index 0000000..958f620 --- /dev/null +++ b/src/snapshots/new_nu_parser__test__node_output@closure3.nu.snap @@ -0,0 +1,61 @@ +--- +source: src/test.rs +expression: evaluate_example(path) +input_file: tests/closure3.nu +snapshot_kind: text +--- +==== COMPILER ==== +0: Variable (4 to 11) "closure" +1: Name (16 to 17) "a" +2: Name (19 to 22) "int" +3: Type { name: NodeId(2), params: None, optional: false } (19 to 22) +4: Param { name: NodeId(1), ty: Some(NodeId(3)) } (16 to 22) +5: Name (24 to 25) "b" +6: Name (27 to 30) "int" +7: Type { name: NodeId(6), params: None, optional: false } (27 to 30) +8: Param { name: NodeId(5), ty: Some(NodeId(7)) } (24 to 30) +9: Params([NodeId(4), NodeId(8)]) (15 to 31) +10: Variable (32 to 34) "$a" +11: Plus (35 to 36) +12: Variable (37 to 39) "$b" +13: LessThan (40 to 41) +14: Int (42 to 43) "5" +15: BinaryOp { lhs: NodeId(10), op: NodeId(11), rhs: NodeId(12) } (32 to 39) +16: BinaryOp { lhs: NodeId(15), op: NodeId(13), rhs: NodeId(14) } (32 to 43) +17: Block(BlockId(0)) (32 to 43) +18: Closure { params: Some(NodeId(9)), block: NodeId(17) } (14 to 44) +19: Let { variable_name: NodeId(0), ty: None, initializer: NodeId(18), is_mutable: false } (0 to 44) +20: Name (46 to 52) "filter" +21: Variable (53 to 61) "$closure" +22: Call { parts: [NodeId(20), NodeId(21)] } (53 to 61) +23: Block(BlockId(1)) (0 to 62) +==== SCOPE ==== +0: Frame Scope, node_id: NodeId(23) + variables: [ closure: NodeId(0) ] +1: Frame Scope, node_id: NodeId(17) + variables: [ a: NodeId(1), b: NodeId(5) ] +==== TYPES ==== +0: closure +1: unknown +2: unknown +3: int +4: int +5: unknown +6: unknown +7: int +8: int +9: forbidden +10: int +11: forbidden +12: int +13: forbidden +14: int +15: int +16: bool +17: bool +18: closure +19: () +20: unknown +21: closure +22: stream +23: stream diff --git a/src/snapshots/new_nu_parser__test__node_output@def.nu.snap b/src/snapshots/new_nu_parser__test__node_output@def.nu.snap index 468665c..578a66a 100644 --- a/src/snapshots/new_nu_parser__test__node_output@def.nu.snap +++ b/src/snapshots/new_nu_parser__test__node_output@def.nu.snap @@ -2,6 +2,7 @@ source: src/test.rs expression: evaluate_example(path) input_file: tests/def.nu +snapshot_kind: text --- ==== COMPILER ==== 0: Name (4 to 7) "foo" @@ -41,22 +42,22 @@ input_file: tests/def.nu 3: unknown 4: unknown 5: int -6: unknown +6: int 7: unknown 8: unknown 9: unknown 10: unknown 11: int -12: unknown +12: forbidden 13: list -14: unknown +14: forbidden 15: list> -16: unknown -17: unknown +16: list> +17: forbidden 18: unknown -19: unknown -20: unknown -21: list -22: () +19: int +20: list> +21: list +22: list 23: () 24: () diff --git a/src/snapshots/new_nu_parser__test__node_output@for.nu.snap b/src/snapshots/new_nu_parser__test__node_output@for.nu.snap index 4018b62..9e8eec1 100644 --- a/src/snapshots/new_nu_parser__test__node_output@for.nu.snap +++ b/src/snapshots/new_nu_parser__test__node_output@for.nu.snap @@ -2,6 +2,7 @@ source: src/test.rs expression: evaluate_example(path) input_file: tests/for.nu +snapshot_kind: text --- ==== COMPILER ==== 0: Variable (4 to 5) "x" @@ -35,7 +36,7 @@ input_file: tests/for.nu 28: BinaryOp { lhs: NodeId(22), op: NodeId(23), rhs: NodeId(27) } (73 to 85) 29: Block(BlockId(1)) (67 to 87) 30: For { variable: NodeId(17), range: NodeId(21), block: NodeId(29) } (49 to 87) -31: Block(BlockId(2)) (0 to 87) +31: Block(BlockId(2)) (0 to 88) ==== SCOPE ==== 0: Frame Scope, node_id: NodeId(31) variables: [ x: NodeId(0) ] @@ -47,35 +48,32 @@ input_file: tests/for.nu 0: int 1: int 2: () -3: unknown -4: unknown -5: unknown -6: unknown -7: unknown -8: unknown -9: unknown -10: unknown -11: unknown -12: unknown -13: unknown -14: unknown -15: unknown -16: unknown -17: unknown -18: unknown -19: unknown -20: unknown -21: unknown -22: unknown -23: unknown -24: unknown -25: unknown -26: unknown -27: unknown -28: unknown -29: unknown -30: unknown +3: int +4: int +5: int +6: int +7: list +8: int +9: forbidden +10: int +11: forbidden +12: int +13: int +14: () +15: () +16: () +17: int +18: int +19: int +20: int +21: list +22: int +23: forbidden +24: int +25: forbidden +26: int +27: int +28: () +29: () +30: () 31: () -==== TYPE ERRORS ==== -Error (NodeId 16): unsupported ast node 'For { variable: NodeId(3), range: NodeId(7), block: NodeId(15) }' in typechecker -Error (NodeId 30): unsupported ast node 'For { variable: NodeId(17), range: NodeId(21), block: NodeId(29) }' in typechecker diff --git a/src/snapshots/new_nu_parser__test__node_output@for_break_continue.nu.snap b/src/snapshots/new_nu_parser__test__node_output@for_break_continue.nu.snap index 0d5310c..d16f9b1 100644 --- a/src/snapshots/new_nu_parser__test__node_output@for_break_continue.nu.snap +++ b/src/snapshots/new_nu_parser__test__node_output@for_break_continue.nu.snap @@ -2,6 +2,7 @@ source: src/test.rs expression: evaluate_example(path) input_file: tests/for_break_continue.nu +snapshot_kind: text --- ==== COMPILER ==== 0: Variable (4 to 5) "x" @@ -47,34 +48,35 @@ input_file: tests/for_break_continue.nu 0: int 1: int 2: () -3: unknown -4: unknown -5: unknown -6: unknown -7: unknown -8: unknown -9: unknown -10: unknown -11: unknown +3: int +4: int +5: int +6: int +7: list +8: int +9: forbidden +10: int +11: bool 12: unknown 13: unknown -14: unknown -15: unknown -16: unknown -17: unknown -18: unknown +14: oneof<(), unknown> +15: int +16: forbidden +17: int +18: bool 19: unknown 20: unknown -21: unknown -22: unknown -23: unknown -24: unknown -25: unknown -26: unknown -27: unknown -28: unknown -29: unknown -30: unknown +21: oneof<(), unknown> +22: int +23: forbidden +24: int +25: forbidden +26: int +27: int +28: () +29: () +30: () 31: () ==== TYPE ERRORS ==== -Error (NodeId 30): unsupported ast node 'For { variable: NodeId(3), range: NodeId(7), block: NodeId(29) }' in typechecker +Error (NodeId 12): unsupported ast node 'Break' in typechecker +Error (NodeId 19): unsupported ast node 'Continue' in typechecker diff --git a/src/snapshots/new_nu_parser__test__node_output@if_.nu.snap b/src/snapshots/new_nu_parser__test__node_output@if_.nu.snap index c50f3a8..4b2ae9e 100644 --- a/src/snapshots/new_nu_parser__test__node_output@if_.nu.snap +++ b/src/snapshots/new_nu_parser__test__node_output@if_.nu.snap @@ -2,6 +2,7 @@ source: src/test.rs expression: evaluate_example(path) input_file: tests/if_.nu +snapshot_kind: text --- ==== COMPILER ==== 0: Variable (4 to 5) "x" @@ -39,15 +40,15 @@ input_file: tests/if_.nu 5: int 6: bool 7: int -8: () +8: int 9: int 10: forbidden 11: int 12: bool 13: int -14: () +14: int 15: int -16: () -17: unknown -18: unknown -19: () +16: int +17: int +18: int +19: int diff --git a/src/snapshots/new_nu_parser__test__node_output@invalid_if.nu.snap b/src/snapshots/new_nu_parser__test__node_output@invalid_if.nu.snap new file mode 100644 index 0000000..31b0d0d --- /dev/null +++ b/src/snapshots/new_nu_parser__test__node_output@invalid_if.nu.snap @@ -0,0 +1,28 @@ +--- +source: src/test.rs +expression: evaluate_example(path) +input_file: tests/invalid_if.nu +snapshot_kind: text +--- +==== COMPILER ==== +0: Int (3 to 4) "1" +1: Int (9 to 10) "4" +2: Block(BlockId(0)) (5 to 13) +3: Int (22 to 23) "3" +4: Block(BlockId(1)) (18 to 25) +5: If { condition: NodeId(0), then_block: NodeId(2), else_block: Some(NodeId(4)) } (0 to 25) +6: Block(BlockId(2)) (0 to 26) +==== SCOPE ==== +0: Frame Scope, node_id: NodeId(6) (empty) +1: Frame Scope, node_id: NodeId(2) (empty) +2: Frame Scope, node_id: NodeId(4) (empty) +==== TYPES ==== +0: int +1: int +2: int +3: int +4: int +5: error +6: error +==== TYPE ERRORS ==== +Error (NodeId 0): The condition for if branch is not a boolean diff --git a/src/snapshots/new_nu_parser__test__node_output@invalid_types.nu.snap b/src/snapshots/new_nu_parser__test__node_output@invalid_types.nu.snap index 60b7014..c1c4eaf 100644 --- a/src/snapshots/new_nu_parser__test__node_output@invalid_types.nu.snap +++ b/src/snapshots/new_nu_parser__test__node_output@invalid_types.nu.snap @@ -2,6 +2,7 @@ source: src/test.rs expression: evaluate_example(path) input_file: tests/invalid_types.nu +snapshot_kind: text --- ==== COMPILER ==== 0: Name (4 to 7) "foo" @@ -44,22 +45,22 @@ input_file: tests/invalid_types.nu 4: int 5: unknown 6: string -7: unknown +7: forbidden 8: list -9: unknown -10: unknown -11: unknown -12: () +9: list +10: forbidden +11: list +12: list 13: () 14: unknown 15: unknown 16: unknown -17: unknown +17: forbidden 18: list -19: unknown -20: unknown -21: unknown -22: () +19: list +20: forbidden +21: list +22: list 23: () 24: () ==== TYPE ERRORS ==== diff --git a/src/snapshots/new_nu_parser__test__node_output@let_.nu.snap b/src/snapshots/new_nu_parser__test__node_output@let_.nu.snap index 57dfe74..509718d 100644 --- a/src/snapshots/new_nu_parser__test__node_output@let_.nu.snap +++ b/src/snapshots/new_nu_parser__test__node_output@let_.nu.snap @@ -2,6 +2,7 @@ source: src/test.rs expression: evaluate_example(path) input_file: tests/let_.nu +snapshot_kind: text --- ==== COMPILER ==== 0: Variable (4 to 5) "x" @@ -17,4 +18,4 @@ input_file: tests/let_.nu 1: int 2: () 3: int -4: () +4: int diff --git a/src/snapshots/new_nu_parser__test__node_output@let_mismatch.nu.snap b/src/snapshots/new_nu_parser__test__node_output@let_mismatch.nu.snap index ba27212..fa35d31 100644 --- a/src/snapshots/new_nu_parser__test__node_output@let_mismatch.nu.snap +++ b/src/snapshots/new_nu_parser__test__node_output@let_mismatch.nu.snap @@ -2,6 +2,7 @@ source: src/test.rs expression: evaluate_example(path) input_file: tests/let_mismatch.nu +snapshot_kind: text --- ==== COMPILER ==== 0: Variable (4 to 5) "x" @@ -57,9 +58,9 @@ input_file: tests/let_mismatch.nu 17: unknown 18: unknown 19: int -20: unknown +20: forbidden 21: list -22: unknown +22: forbidden 23: list> 24: string 25: list diff --git a/src/snapshots/new_nu_parser__test__node_output@list.nu.snap b/src/snapshots/new_nu_parser__test__node_output@list.nu.snap index 1be9295..9c3caf4 100644 --- a/src/snapshots/new_nu_parser__test__node_output@list.nu.snap +++ b/src/snapshots/new_nu_parser__test__node_output@list.nu.snap @@ -2,6 +2,7 @@ source: src/test.rs expression: evaluate_example(path) input_file: tests/list.nu +snapshot_kind: text --- ==== COMPILER ==== 0: Int (1 to 2) "1" @@ -48,4 +49,4 @@ input_file: tests/list.nu 17: int 18: string 19: list -20: () +20: list diff --git a/src/snapshots/new_nu_parser__test__node_output@literals.nu.snap b/src/snapshots/new_nu_parser__test__node_output@literals.nu.snap index d9751e2..c5ed093 100644 --- a/src/snapshots/new_nu_parser__test__node_output@literals.nu.snap +++ b/src/snapshots/new_nu_parser__test__node_output@literals.nu.snap @@ -2,6 +2,7 @@ source: src/test.rs expression: evaluate_example(path) input_file: tests/literals.nu +snapshot_kind: text --- ==== COMPILER ==== 0: True (1 to 5) @@ -20,4 +21,4 @@ input_file: tests/literals.nu 3: string 4: string 5: list -6: () +6: list diff --git a/src/snapshots/new_nu_parser__test__node_output@loop.nu.snap b/src/snapshots/new_nu_parser__test__node_output@loop.nu.snap index 8606860..31e10e0 100644 --- a/src/snapshots/new_nu_parser__test__node_output@loop.nu.snap +++ b/src/snapshots/new_nu_parser__test__node_output@loop.nu.snap @@ -2,6 +2,7 @@ source: src/test.rs expression: evaluate_example(path) input_file: tests/loop.nu +snapshot_kind: text --- ==== COMPILER ==== 0: Variable (4 to 5) "x" @@ -43,6 +44,6 @@ input_file: tests/loop.nu 13: unknown 14: unknown 15: unknown -16: () +16: unknown ==== TYPE ERRORS ==== Error (NodeId 15): unsupported ast node 'Loop { block: NodeId(14) }' in typechecker diff --git a/src/snapshots/new_nu_parser__test__node_output@match.nu.snap b/src/snapshots/new_nu_parser__test__node_output@match.nu.snap new file mode 100644 index 0000000..296fd4a --- /dev/null +++ b/src/snapshots/new_nu_parser__test__node_output@match.nu.snap @@ -0,0 +1,49 @@ +--- +source: src/test.rs +expression: evaluate_example(path) +input_file: tests/match.nu +snapshot_kind: text +--- +==== COMPILER ==== +0: Variable (4 to 5) "x" +1: Int (8 to 9) "1" +2: Let { variable_name: NodeId(0), ty: None, initializer: NodeId(1), is_mutable: false } (0 to 9) +3: Variable (15 to 18) "foo" +4: Variable (27 to 29) "$x" +5: Int (34 to 35) "1" +6: String (39 to 44) ""one"" +7: Garbage (44 to 45) +8: Let { variable_name: NodeId(3), ty: None, initializer: NodeId(7), is_mutable: false } (11 to 45) +9: Int (48 to 49) "2" +10: Garbage (50 to 52) +11: Variable (63 to 64) "w" +12: Int (67 to 68) "3" +13: Let { variable_name: NodeId(11), ty: None, initializer: NodeId(12), is_mutable: false } (59 to 68) +14: Int (73 to 74) "2" +15: Plus (75 to 76) +16: Variable (77 to 79) "$w" +17: BinaryOp { lhs: NodeId(14), op: NodeId(15), rhs: NodeId(16) } (73 to 79) +18: Block(BlockId(0)) (59 to 82) +19: Closure { params: None, block: NodeId(18) } (53 to 83) +20: Garbage (83 to 84) +21: Int (87 to 88) "3" +22: Garbage (89 to 91) +23: Null (92 to 96) +24: Garbage (96 to 97) +25: Name (100 to 101) "_" +26: Garbage (102 to 104) +27: Garbage (106 to 107) +28: Garbage (107 to 108) +29: Call { parts: [NodeId(25), NodeId(26), NodeId(27)] } (102 to 108) +30: Garbage (109 to 110) +31: Block(BlockId(1)) (0 to 111) +==== COMPILER ERRORS ==== +Error (NodeId 7): expected match arm in match +Error (NodeId 10): incomplete expression +Error (NodeId 20): incomplete expression +Error (NodeId 22): incomplete expression +Error (NodeId 24): incomplete expression +Error (NodeId 26): incomplete expression +Error (NodeId 27): incomplete expression +Error (NodeId 28): expected: right paren ')' +Error (NodeId 30): incomplete expression diff --git a/src/snapshots/new_nu_parser__test__node_output@math.nu.snap b/src/snapshots/new_nu_parser__test__node_output@math.nu.snap index d7545ba..bfec583 100644 --- a/src/snapshots/new_nu_parser__test__node_output@math.nu.snap +++ b/src/snapshots/new_nu_parser__test__node_output@math.nu.snap @@ -2,6 +2,7 @@ source: src/test.rs expression: evaluate_example(path) input_file: tests/math.nu +snapshot_kind: text --- ==== COMPILER ==== 0: Int (0 to 1) "3" @@ -16,4 +17,4 @@ input_file: tests/math.nu 1: forbidden 2: int 3: int -4: () +4: int diff --git a/src/snapshots/new_nu_parser__test__node_output@math_precedence.nu.snap b/src/snapshots/new_nu_parser__test__node_output@math_precedence.nu.snap index 1fddd32..12c6fa7 100644 --- a/src/snapshots/new_nu_parser__test__node_output@math_precedence.nu.snap +++ b/src/snapshots/new_nu_parser__test__node_output@math_precedence.nu.snap @@ -2,6 +2,7 @@ source: src/test.rs expression: evaluate_example(path) input_file: tests/math_precedence.nu +snapshot_kind: text --- ==== COMPILER ==== 0: Int (0 to 1) "1" @@ -28,4 +29,4 @@ input_file: tests/math_precedence.nu 7: int 8: int 9: int -10: () +10: int diff --git a/src/snapshots/new_nu_parser__test__node_output@record.nu.snap b/src/snapshots/new_nu_parser__test__node_output@record.nu.snap index af8add3..78342df 100644 --- a/src/snapshots/new_nu_parser__test__node_output@record.nu.snap +++ b/src/snapshots/new_nu_parser__test__node_output@record.nu.snap @@ -2,6 +2,7 @@ source: src/test.rs expression: evaluate_example(path) input_file: tests/record.nu +snapshot_kind: text --- ==== COMPILER ==== 0: String (1 to 2) "a" @@ -18,6 +19,6 @@ input_file: tests/record.nu 2: unknown 3: unknown 4: unknown -5: () +5: unknown ==== TYPE ERRORS ==== Error (NodeId 4): unsupported ast node 'Record { pairs: [(NodeId(0), NodeId(1)), (NodeId(2), NodeId(3))] }' in typechecker diff --git a/src/snapshots/new_nu_parser__test__node_output@record2.nu.snap b/src/snapshots/new_nu_parser__test__node_output@record2.nu.snap index b7812b7..997c199 100644 --- a/src/snapshots/new_nu_parser__test__node_output@record2.nu.snap +++ b/src/snapshots/new_nu_parser__test__node_output@record2.nu.snap @@ -2,6 +2,7 @@ source: src/test.rs expression: evaluate_example(path) input_file: tests/record2.nu +snapshot_kind: text --- ==== COMPILER ==== 0: String (1 to 4) ""a"" @@ -18,6 +19,6 @@ input_file: tests/record2.nu 2: unknown 3: unknown 4: unknown -5: () +5: unknown ==== TYPE ERRORS ==== Error (NodeId 4): unsupported ast node 'Record { pairs: [(NodeId(0), NodeId(1)), (NodeId(2), NodeId(3))] }' in typechecker diff --git a/src/snapshots/new_nu_parser__test__node_output@record3.nu.snap b/src/snapshots/new_nu_parser__test__node_output@record3.nu.snap index 07ce9db..a112130 100644 --- a/src/snapshots/new_nu_parser__test__node_output@record3.nu.snap +++ b/src/snapshots/new_nu_parser__test__node_output@record3.nu.snap @@ -2,6 +2,7 @@ source: src/test.rs expression: evaluate_example(path) input_file: tests/record3.nu +snapshot_kind: text --- ==== COMPILER ==== 0: String (2 to 3) "a" @@ -18,6 +19,6 @@ input_file: tests/record3.nu 2: unknown 3: unknown 4: unknown -5: () +5: unknown ==== TYPE ERRORS ==== Error (NodeId 4): unsupported ast node 'Record { pairs: [(NodeId(0), NodeId(1)), (NodeId(2), NodeId(3))] }' in typechecker diff --git a/src/snapshots/new_nu_parser__test__node_output@reparse.nu.snap b/src/snapshots/new_nu_parser__test__node_output@reparse.nu.snap index 98782b5..cf35634 100644 --- a/src/snapshots/new_nu_parser__test__node_output@reparse.nu.snap +++ b/src/snapshots/new_nu_parser__test__node_output@reparse.nu.snap @@ -2,6 +2,7 @@ source: src/test.rs expression: evaluate_example(path) input_file: tests/reparse.nu +snapshot_kind: text --- ==== COMPILER ==== 0: Variable (4 to 5) "x" @@ -27,9 +28,9 @@ input_file: tests/reparse.nu 0: closure 1: unknown 2: any -3: unknown +3: forbidden 4: unknown -5: () +5: unknown 6: closure 7: () 8: unknown diff --git a/src/snapshots/new_nu_parser__test__node_output@string.nu.snap b/src/snapshots/new_nu_parser__test__node_output@string.nu.snap index 7f829ee..795baaa 100644 --- a/src/snapshots/new_nu_parser__test__node_output@string.nu.snap +++ b/src/snapshots/new_nu_parser__test__node_output@string.nu.snap @@ -2,6 +2,7 @@ source: src/test.rs expression: evaluate_example(path) input_file: tests/string.nu +snapshot_kind: text --- ==== COMPILER ==== 0: String (0 to 13) ""hello world"" @@ -12,4 +13,4 @@ input_file: tests/string.nu ==== TYPES ==== 0: string 1: string -2: () +2: string diff --git a/src/snapshots/new_nu_parser__test__node_output@string_operation.nu.snap b/src/snapshots/new_nu_parser__test__node_output@string_operation.nu.snap index e7ddb36..155175f 100644 --- a/src/snapshots/new_nu_parser__test__node_output@string_operation.nu.snap +++ b/src/snapshots/new_nu_parser__test__node_output@string_operation.nu.snap @@ -2,6 +2,7 @@ source: src/test.rs expression: evaluate_example(path) input_file: tests/string_operation.nu +snapshot_kind: text --- ==== COMPILER ==== 0: String (0 to 5) ""abc"" @@ -16,4 +17,4 @@ input_file: tests/string_operation.nu 1: forbidden 2: string 3: string -4: () +4: string diff --git a/src/snapshots/new_nu_parser__test__node_output@table.nu.snap b/src/snapshots/new_nu_parser__test__node_output@table.nu.snap index e3f8aad..8b8cdd9 100644 --- a/src/snapshots/new_nu_parser__test__node_output@table.nu.snap +++ b/src/snapshots/new_nu_parser__test__node_output@table.nu.snap @@ -2,6 +2,7 @@ source: src/test.rs expression: evaluate_example(path) input_file: tests/table.nu +snapshot_kind: text --- ==== COMPILER ==== 0: String (7 to 10) ""a"" @@ -28,6 +29,6 @@ input_file: tests/table.nu 7: unknown 8: unknown 9: unknown -10: () +10: unknown ==== TYPE ERRORS ==== Error (NodeId 9): unsupported ast node 'Table { header: NodeId(2), rows: [NodeId(5), NodeId(8)] }' in typechecker diff --git a/src/snapshots/new_nu_parser__test__node_output@table2.nu.snap b/src/snapshots/new_nu_parser__test__node_output@table2.nu.snap index cf44476..1c8eb5d 100644 --- a/src/snapshots/new_nu_parser__test__node_output@table2.nu.snap +++ b/src/snapshots/new_nu_parser__test__node_output@table2.nu.snap @@ -2,6 +2,7 @@ source: src/test.rs expression: evaluate_example(path) input_file: tests/table2.nu +snapshot_kind: text --- ==== COMPILER ==== 0: String (7 to 8) "a" @@ -28,6 +29,6 @@ input_file: tests/table2.nu 7: unknown 8: unknown 9: unknown -10: () +10: unknown ==== TYPE ERRORS ==== Error (NodeId 9): unsupported ast node 'Table { header: NodeId(2), rows: [NodeId(5), NodeId(8)] }' in typechecker diff --git a/src/snapshots/new_nu_parser__test__node_output@while.nu.snap b/src/snapshots/new_nu_parser__test__node_output@while.nu.snap new file mode 100644 index 0000000..520476d --- /dev/null +++ b/src/snapshots/new_nu_parser__test__node_output@while.nu.snap @@ -0,0 +1,40 @@ +--- +source: src/test.rs +expression: evaluate_example(path) +input_file: tests/while.nu +snapshot_kind: text +--- +==== COMPILER ==== +0: Variable (4 to 5) "x" +1: Int (8 to 9) "0" +2: Let { variable_name: NodeId(0), ty: None, initializer: NodeId(1), is_mutable: true } (0 to 9) +3: Int (16 to 17) "1" +4: LessThan (18 to 19) +5: Int (20 to 21) "2" +6: BinaryOp { lhs: NodeId(3), op: NodeId(4), rhs: NodeId(5) } (16 to 21) +7: Variable (26 to 28) "$x" +8: AddAssignment (29 to 31) +9: Int (32 to 33) "1" +10: BinaryOp { lhs: NodeId(7), op: NodeId(8), rhs: NodeId(9) } (26 to 33) +11: Block(BlockId(0)) (22 to 35) +12: While { condition: NodeId(6), block: NodeId(11) } (10 to 35) +13: Block(BlockId(1)) (0 to 36) +==== SCOPE ==== +0: Frame Scope, node_id: NodeId(13) + variables: [ x: NodeId(0) ] +1: Frame Scope, node_id: NodeId(11) (empty) +==== TYPES ==== +0: int +1: int +2: () +3: int +4: forbidden +5: int +6: bool +7: int +8: forbidden +9: int +10: () +11: () +12: () +13: () diff --git a/src/typechecker.rs b/src/typechecker.rs index c0bcae0..27dd9f9 100644 --- a/src/typechecker.rs +++ b/src/typechecker.rs @@ -1,8 +1,10 @@ use crate::compiler::Compiler; use crate::errors::{Severity, SourceError}; use crate::parser::{AstNode, NodeId}; +use std::cmp::Ordering; +use std::collections::HashSet; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct TypeId(pub usize); /// Input/output type pair of a closure/command @@ -12,6 +14,9 @@ pub struct InOutType { pub out_type: TypeId, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct OneOfId(pub usize); + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Type { /// Any node that hasn't been touched by the typechecker will have this type @@ -30,10 +35,11 @@ pub enum Type { Bool, String, Binary, - Block, Closure, List(TypeId), Stream(TypeId), + OneOf(OneOfId), + Error, } pub struct Types { @@ -56,13 +62,13 @@ pub const FLOAT_TYPE: TypeId = TypeId(7); pub const BOOL_TYPE: TypeId = TypeId(8); pub const STRING_TYPE: TypeId = TypeId(9); pub const BINARY_TYPE: TypeId = TypeId(10); -pub const BLOCK_TYPE: TypeId = TypeId(11); -pub const CLOSURE_TYPE: TypeId = TypeId(12); +pub const CLOSURE_TYPE: TypeId = TypeId(11); // Common composite types can be hardcoded as well, like list: -pub const LIST_ANY_TYPE: TypeId = TypeId(13); -pub const BYTE_STREAM_TYPE: TypeId = TypeId(14); +pub const LIST_ANY_TYPE: TypeId = TypeId(12); +pub const BYTE_STREAM_TYPE: TypeId = TypeId(13); +pub const ERROR_TYPE: TypeId = TypeId(14); pub struct Typechecker<'a> { /// Immutable reference to a compiler after the name binding pass @@ -73,6 +79,8 @@ pub struct Typechecker<'a> { /// Types of nodes. Each type in this vector matches a node in compiler.ast_nodes at the same position. pub node_types: Vec, + /// Types used for `OneOf`. Each value in this vector matches with the index in OneOfId + pub oneof_types: Vec>, /// Type of each Variable in compiler.variables, indexed by VarId pub variable_types: Vec, /// Input/output type pairs of each declaration in compiler.decls, indexed by DeclId @@ -98,12 +106,13 @@ impl<'a> Typechecker<'a> { Type::Bool, Type::String, Type::Binary, - Type::Block, Type::Closure, Type::List(ANY_TYPE), Type::Stream(BINARY_TYPE), + Type::Error, ], node_types: vec![UNKNOWN_TYPE; compiler.ast_nodes.len()], + oneof_types: Vec::new(), variable_types: vec![UNKNOWN_TYPE; compiler.variables.len()], decl_types: vec![ vec![InOutType { @@ -196,10 +205,20 @@ impl<'a> Typechecker<'a> { for param in params { self.typecheck_node(*param); } + // Params are not supposed to be evaluated + self.set_node_type_id(node_id, FORBIDDEN_TYPE); } - AstNode::Param { name: _, ty } => { + AstNode::Param { name, ty } => { if let Some(ty) = ty { self.typecheck_node(ty); + + let var_id = self + .compiler + .var_resolution + .get(&name) + .expect("missing resolved variable"); + self.variable_types[var_id.0] = self.type_id_of(ty); + self.set_node_type_id(node_id, self.type_id_of(ty)); } else { self.set_node_type_id(node_id, ANY_TYPE); } @@ -245,14 +264,20 @@ impl<'a> Typechecker<'a> { } } AstNode::Block(block_id) => { - // TODO: input/output types let block = &self.compiler.blocks[block_id.0]; for inner_node_id in &block.nodes { self.typecheck_node(*inner_node_id); } - self.set_node_type_id(node_id, NONE_TYPE); + // Block type is the type of the last statement, since blocks + // by themselves aren't supposed to be typed + let block_type = block + .nodes + .last() + .map_or(NONE_TYPE, |node_id| self.type_id_of(*node_id)); + + self.set_node_type_id(node_id, block_type); } AstNode::Closure { params, block } => { // TODO: input/output types @@ -287,8 +312,34 @@ impl<'a> Typechecker<'a> { self.typecheck_node(condition); self.typecheck_node(then_block); + let then_type_id = self.type_id_of(then_block); + let mut else_type = None; + if let Some(else_blk) = else_block { self.typecheck_node(else_blk); + else_type = Some(self.type_of(else_blk)); + } + + let mut types = HashSet::new(); + self.add_resolved_types(&mut types, &then_type_id); + + if let Some(Type::OneOf(id)) = else_type { + types.extend(self.oneof_types[id.0].iter()); + } else if else_type.is_none() { + types.insert(NONE_TYPE); + } else { + types.insert(self.type_id_of(else_block.expect("Already checked"))); + } + + // the condition should always evaluate to a boolean + if self.type_of(condition) != Type::Bool { + self.error("The condition for if branch is not a boolean", condition); + self.set_node_type_id(node_id, ERROR_TYPE); + } else if types.len() > 1 { + self.oneof_types.push(types); + self.set_node_type(node_id, Type::OneOf(OneOfId(self.oneof_types.len() - 1))); + } else { + self.set_node_type_id(node_id, *types.iter().next().expect("Can't be empty")); } } AstNode::Def { @@ -298,6 +349,81 @@ impl<'a> Typechecker<'a> { block, } => self.typecheck_def(name, params, return_ty, block, node_id), AstNode::Call { ref parts } => self.typecheck_call(parts, node_id), + AstNode::For { + variable, + range, + block, + } => { + // We don't need to typecheck variable after this + self.typecheck_node(range); + + let var_id = self + .compiler + .var_resolution + .get(&variable) + .expect("missing resolved variable"); + if let Type::List(type_id) = self.type_of(range) { + self.variable_types[var_id.0] = type_id; + self.set_node_type_id(variable, type_id); + } else { + self.variable_types[var_id.0] = ANY_TYPE; + self.set_node_type_id(variable, ERROR_TYPE); + self.error("For loop range is not a list", range); + } + + self.typecheck_node(block); + if self.type_id_of(block) != NONE_TYPE { + self.error("Blocks in looping constructs cannot return values", block); + } + + if self.type_id_of(node_id) != ERROR_TYPE { + self.set_node_type_id(node_id, NONE_TYPE); + } + } + AstNode::While { condition, block } => { + self.typecheck_node(block); + if self.type_id_of(block) != NONE_TYPE { + self.error("Blocks in looping constructs cannot return values", block); + } + + self.typecheck_node(condition); + + // the condition should always evaluate to a boolean + if self.type_of(condition) != Type::Bool { + self.error("The condition for while loop is not a boolean", condition); + self.set_node_type_id(node_id, ERROR_TYPE); + } else { + self.set_node_type_id(node_id, self.type_id_of(block)); + } + } + AstNode::Match { + ref target, + ref match_arms, + } => { + // Check all the output types of match + let output_types = self.typecheck_match(target, match_arms); + match output_types.len().cmp(&1) { + Ordering::Greater => { + self.oneof_types.push(output_types); + self.set_node_type( + node_id, + Type::OneOf(OneOfId(self.oneof_types.len() - 1)), + ); + } + Ordering::Equal => { + self.set_node_type_id( + node_id, + *output_types + .iter() + .next() + .expect("Will contain one element"), + ); + } + Ordering::Less => { + self.set_node_type_id(node_id, NOTHING_TYPE); + } + } + } _ => self.error( format!( "unsupported ast node '{:?}' in typechecker", @@ -308,6 +434,63 @@ impl<'a> Typechecker<'a> { } } + fn typecheck_match( + &mut self, + target: &NodeId, + match_arms: &Vec<(NodeId, NodeId)>, + ) -> HashSet { + self.typecheck_node(*target); + + let mut output_types = HashSet::new(); + // typecheck each node + let target_id = self.type_id_of(*target); + for (match_node, result_node) in match_arms { + self.typecheck_node(*match_node); + self.typecheck_node(*result_node); + + let match_id = self.type_id_of(*match_node); + match (self.type_of(*target), self.type_of(*match_node)) { + // First is of type Any which will always match + (Type::Any, _) => { + self.add_resolved_types(&mut output_types, &self.type_id_of(*result_node)); + } + // Same as above but for second + (_, Type::Any) => { + self.add_resolved_types(&mut output_types, &self.type_id_of(*result_node)); + } + // the second is one of the possible types of the first + (Type::OneOf(id), _) if self.oneof_types[id.0].contains(&match_id) => { + self.add_resolved_types(&mut output_types, &self.type_id_of(*result_node)); + } + // the first is one of the possible types of the second + (_, Type::OneOf(id)) if self.oneof_types[id.0].contains(&target_id) => { + self.add_resolved_types(&mut output_types, &self.type_id_of(*result_node)); + } + // the both the target and the one matched against are + // oneof then we need to check if they have any type in common + (Type::OneOf(id1), Type::OneOf(id2)) => { + if self.oneof_types[id1.0] + .intersection(&self.oneof_types[id2.0]) + .count() + != 0 + { + self.add_resolved_types(&mut output_types, &self.type_id_of(*result_node)); + } else { + self.error("The target to be matched against and the possible types of the matched arm are completely disjoint", *match_node); + } + } + // Check if the two types can be matched + (target_id, match_id) if is_type_compatible(target_id, match_id) => { + self.add_resolved_types(&mut output_types, &self.type_id_of(*result_node)); + } + _ => { + self.error("The types do not match", *match_node); + } + } + } + output_types + } + fn typecheck_binary_op(&mut self, lhs: NodeId, op: NodeId, rhs: NodeId, node_id: NodeId) { self.typecheck_node(lhs); self.typecheck_node(rhs); @@ -396,6 +579,8 @@ impl<'a> Typechecker<'a> { if let Some(ty) = out_type { self.set_node_type(node_id, ty); + } else { + self.set_node_type_id(node_id, ERROR_TYPE); } } @@ -420,14 +605,14 @@ impl<'a> Typechecker<'a> { self.decl_types[decl_id.0] = vec![InOutType { in_type: ANY_TYPE, - out_type: ANY_TYPE, + out_type: self.type_id_of(block), }]; } fn typecheck_call(&mut self, parts: &[NodeId], node_id: NodeId) { let num_name_parts = if let Some(decl_id) = self.compiler.decl_resolution.get(&node_id) { - // TODO: The type will be `oneof` - self.node_types[node_id.0] = ANY_TYPE; + // TODO: The type should be `oneof` + self.set_node_type_id(node_id, ANY_TYPE); self.compiler.decls[decl_id.0].name().split(' ').count() } else { @@ -563,7 +748,6 @@ impl<'a> Typechecker<'a> { Type::Float => FLOAT_TYPE, Type::Bool => BOOL_TYPE, Type::String => STRING_TYPE, - Type::Block => BLOCK_TYPE, Type::Closure => CLOSURE_TYPE, Type::List(ANY_TYPE) => LIST_ANY_TYPE, _ => { @@ -619,7 +803,6 @@ impl<'a> Typechecker<'a> { Type::Bool => "bool".to_string(), Type::Binary => "binary".to_string(), Type::String => "string".to_string(), - Type::Block => "block".to_string(), Type::Closure => "closure".to_string(), Type::List(subtype_id) => { format!("list<{}>", self.type_to_string(*subtype_id)) @@ -627,6 +810,24 @@ impl<'a> Typechecker<'a> { Type::Stream(subtype_id) => { format!("stream<{}>", self.type_to_string(*subtype_id)) } + Type::OneOf(id) => { + let mut fmt = "oneof<".to_string(); + let mut types: Vec<_> = self.oneof_types[id.0] + .iter() + .map(|ty| self.type_to_string(*ty) + ", ") + .collect(); + types.sort(); + for ty in &types { + fmt += ty; + } + if !types.is_empty() { + fmt.pop(); + fmt.pop(); + } + fmt.push('>'); + fmt + } + Type::Error => "error".to_string(), } } @@ -648,6 +849,15 @@ impl<'a> Typechecker<'a> { ), op, ); + self.set_node_type_id(op, ERROR_TYPE); + } + + fn add_resolved_types(&mut self, types: &mut HashSet, ty: &TypeId) { + if let Type::OneOf(id) = self.types[ty.0] { + types.extend(self.oneof_types[id.0].clone()); + } else { + types.insert(*ty); + } } } diff --git a/tests/closure3.nu b/tests/closure3.nu new file mode 100644 index 0000000..4d2567c --- /dev/null +++ b/tests/closure3.nu @@ -0,0 +1,3 @@ +let closure = {|a :int, b :int| $a + $b < 5} + +filter $closure diff --git a/tests/for.nu b/tests/for.nu index 0724208..bd54fde 100644 --- a/tests/for.nu +++ b/tests/for.nu @@ -5,4 +5,4 @@ for i in [1 2 3] { for $i in [4 5 6] { $x = $x + $i -} \ No newline at end of file +} diff --git a/tests/invalid_if.nu b/tests/invalid_if.nu new file mode 100644 index 0000000..8d248e4 --- /dev/null +++ b/tests/invalid_if.nu @@ -0,0 +1,5 @@ +if 1 { + 4 +} else { + 3 +} diff --git a/tests/match.nu b/tests/match.nu new file mode 100644 index 0000000..a476811 --- /dev/null +++ b/tests/match.nu @@ -0,0 +1,11 @@ +let x = 1 + +let foo = match $x { + 1 => "one", + 2 => { + let w = 3 + 2 + $w + }, + 3 => null, + _ => (), +} diff --git a/tests/while.nu b/tests/while.nu new file mode 100644 index 0000000..3d2ea51 --- /dev/null +++ b/tests/while.nu @@ -0,0 +1,4 @@ +mut x = 0 +while 1 < 2 { + $x += 1 +}