diff --git a/src/parser.rs b/src/parser.rs index bf84141..065b804 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -69,6 +69,9 @@ pub enum AstNode { // Empty values Null, + // Unit type + Unit, + // Operators Equal, NotEqual, @@ -102,8 +105,9 @@ pub enum AstNode { is_mutable: bool, }, While { - condition: NodeId, - block: NodeId, + cond_block: Option<(NodeId, NodeId)>, + short_flag: Option, + long_flag: Option, }, For { variable: NodeId, @@ -133,6 +137,10 @@ pub enum AstNode { params: Option, block: NodeId, }, + Alias { + new_name: NodeId, + old_name: NodeId, + }, /// Long flag ('--' + one or more letters) FlagLong, @@ -376,9 +384,14 @@ impl Parser { self.record_or_closure() } else if self.is_lparen() { self.lparen(); - let output = self.expression(); - self.rparen(); - output + if self.is_rparen() { + self.rparen(); + self.create_node(AstNode::Unit, span_start, span_start + 2) + } else { + let output = self.expression(); + self.rparen(); + output + } } else if self.is_lsquare() { self.list_or_table() } else if self.is_keyword(b"true") || self.is_keyword(b"false") { @@ -917,6 +930,10 @@ impl Parser { let pattern_result = self.simple_expression(NAME_STRICT); + if self.is_comma() { + self.next(); + } + match_arms.push((pattern, pattern_result)); } else if self.is_newline() { self.next(); @@ -1280,6 +1297,8 @@ impl Parser { code_body.push(self.continue_statement()); } else if self.is_keyword(b"break") { code_body.push(self.break_statement()); + } else if self.is_keyword(b"alias") { + code_body.push(self.alias_statement()); } else { let exp_span_start = self.position(); let expression = self.expression_or_assignment(); @@ -1314,11 +1333,24 @@ impl Parser { let span_start = self.position(); self.keyword(b"while"); + if self.is_operator() { + // TODO: flag parsing + self.next(); + } + let condition = self.expression(); let block = self.block(BlockContext::Curlies); let span_end = self.get_span_end(block); - self.create_node(AstNode::While { condition, block }, span_start, span_end) + self.create_node( + AstNode::While { + cond_block: Some((condition, block)), + short_flag: None, + long_flag: None, + }, + span_start, + span_end, + ) } pub fn for_statement(&mut self) -> NodeId { @@ -1391,6 +1423,17 @@ impl Parser { self.create_node(AstNode::Break, span_start, span_end) } + pub fn alias_statement(&mut self) -> NodeId { + let _span = span!(); + let span_start = self.position(); + self.keyword(b"alias"); + let new_name = self.name(); + self.equals(); + let old_name = self.name(); + let span_end = self.get_span_end(old_name); + self.create_node(AstNode::Alias { new_name, old_name }, span_start, span_end) + } + pub fn is_operator(&mut self) -> bool { match self.peek() { Some(Token { diff --git a/src/resolver.rs b/src/resolver.rs index 4d32b38..3fa52a3 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -238,6 +238,12 @@ impl<'a> Resolver<'a> { self.resolve_block(block, block_id, Some(def_scope)); } + AstNode::Alias { + new_name, + old_name: _, + } => { + self.define_decl(new_name); + } AstNode::Params(ref params) => { for param in params { if let AstNode::Param { name, .. } = self.compiler.ast_nodes[param.0] { @@ -256,7 +262,10 @@ impl<'a> Resolver<'a> { self.resolve_node(initializer); self.define_variable(variable_name, is_mutable) } - AstNode::While { condition, block } => { + AstNode::While { + cond_block: Some((condition, block)), + .. + } => { self.resolve_node(condition); self.resolve_node(block); } 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..6f48f2f --- /dev/null +++ b/src/snapshots/new_nu_parser__test__node_output@alias.nu.snap @@ -0,0 +1,41 @@ +--- +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 (26 to 29) "bar" +8: Name (32 to 35) "foo" +9: Alias { new_name: NodeId(7), old_name: NodeId(8) } (20 to 35) +10: Name (37 to 40) "bar" +11: Int (41 to 42) "1" +12: Call { parts: [NodeId(10), NodeId(11)] } (41 to 42) +13: Block(BlockId(1)) (0 to 43) +==== SCOPE ==== +0: Frame Scope, node_id: NodeId(13) + decls: [ bar: NodeId(7), foo: NodeId(0) ] +1: Frame Scope, node_id: NodeId(5) + variables: [ a: NodeId(1) ] +==== TYPES ==== +0: unknown +1: unknown +2: any +3: none +4: unknown +5: unknown +6: none +7: unknown +8: unknown +9: none +10: unknown +11: int +12: any +13: any 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..30eb70c 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,22 +60,22 @@ 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: () -27: () +20: int +21: forbidden +22: string +23: string +24: int +25: list +26: list +27: none 28: unknown 29: string 30: 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..c52610c --- /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: none +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..c9d1fd9 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: () -23: () -24: () +19: int +20: list> +21: list +22: list +23: none +24: none 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..59b0353 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) ] @@ -46,36 +47,33 @@ input_file: tests/for.nu ==== TYPES ==== 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 -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 +2: none +3: int +4: int +5: int +6: int +7: list +8: int +9: forbidden +10: int +11: forbidden +12: int +13: int +14: none +15: none +16: none +17: int +18: int +19: int +20: int +21: list +22: int +23: forbidden +24: int +25: forbidden +26: int +27: int +28: none +29: none +30: none +31: none 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..23bd2b2 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" @@ -46,35 +47,36 @@ input_file: tests/for_break_continue.nu ==== TYPES ==== 0: int 1: int -2: () -3: unknown -4: unknown -5: unknown -6: unknown -7: unknown -8: unknown -9: unknown -10: unknown -11: unknown +2: none +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 +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 -31: () +21: oneof +22: int +23: forbidden +24: int +25: forbidden +26: int +27: int +28: none +29: none +30: none +31: none ==== 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..b8eddcf 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" @@ -33,21 +34,21 @@ input_file: tests/if_.nu ==== TYPES ==== 0: int 1: int -2: () +2: none 3: int 4: forbidden 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..65e45ec 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,24 +45,24 @@ 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: () -13: () +9: list +10: forbidden +11: list +12: list +13: none 14: unknown 15: unknown 16: unknown -17: unknown +17: forbidden 18: list -19: unknown -20: unknown -21: unknown -22: () -23: () -24: () +19: list +20: forbidden +21: list +22: list +23: none +24: none ==== TYPE ERRORS ==== Error (NodeId 7): list must have only one type parameter (to allow selection of types, use oneof -- WIP) Error (NodeId 17): list must have one type parameter 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..0529e90 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" @@ -15,6 +16,6 @@ input_file: tests/let_.nu ==== TYPES ==== 0: int 1: int -2: () +2: none 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..87c7600 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" @@ -41,31 +42,31 @@ input_file: tests/let_mismatch.nu 1: unknown 2: number 3: int -4: () +4: none 5: any 6: unknown 7: any 8: string -9: () +9: none 10: string 11: unknown 12: string 13: int -14: () +14: none 15: list> 16: unknown 17: unknown 18: unknown 19: int -20: unknown +20: forbidden 21: list -22: unknown +22: forbidden 23: list> 24: string 25: list 26: list> -27: () -28: () +27: none +28: none ==== TYPE ERRORS ==== Error (NodeId 13): initializer does not match declared type Error (NodeId 26): initializer does not match declared type 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..d8fcd22 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) @@ -15,9 +16,9 @@ input_file: tests/literals.nu 0: Frame Scope, node_id: NodeId(6) (empty) ==== TYPES ==== 0: bool -1: nothing +1: () 2: int 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..d1edc28 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" @@ -29,7 +30,7 @@ input_file: tests/loop.nu ==== TYPES ==== 0: int 1: int -2: () +2: none 3: unknown 4: unknown 5: unknown @@ -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..1a2bb6b --- /dev/null +++ b/src/snapshots/new_nu_parser__test__node_output@match.nu.snap @@ -0,0 +1,63 @@ +--- +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: Int (47 to 48) "2" +8: Variable (62 to 63) "w" +9: Int (66 to 67) "3" +10: Let { variable_name: NodeId(8), ty: None, initializer: NodeId(9), is_mutable: false } (58 to 67) +11: Int (72 to 73) "2" +12: Plus (74 to 75) +13: Variable (76 to 78) "$w" +14: BinaryOp { lhs: NodeId(11), op: NodeId(12), rhs: NodeId(13) } (72 to 78) +15: Block(BlockId(0)) (58 to 81) +16: Closure { params: None, block: NodeId(15) } (52 to 82) +17: Int (86 to 87) "3" +18: Null (91 to 95) +19: String (99 to 100) "_" +20: Unit (104 to 106) +21: Match { target: NodeId(4), match_arms: [(NodeId(5), NodeId(6)), (NodeId(7), NodeId(16)), (NodeId(17), NodeId(18)), (NodeId(19), NodeId(20))] } (21 to 108) +22: Let { variable_name: NodeId(3), ty: None, initializer: NodeId(21), is_mutable: false } (11 to 108) +23: Block(BlockId(1)) (0 to 109) +==== SCOPE ==== +0: Frame Scope, node_id: NodeId(23) + variables: [ foo: NodeId(3), x: NodeId(0) ] +1: Frame Scope, node_id: NodeId(15) + variables: [ w: NodeId(8) ] +==== TYPES ==== +0: int +1: int +2: none +3: oneof<(), closure, string> +4: int +5: int +6: string +7: int +8: int +9: int +10: none +11: int +12: forbidden +13: int +14: int +15: int +16: closure +17: int +18: () +19: string +20: () +21: oneof<(), closure, string> +22: none +23: none +==== TYPE ERRORS ==== +Error (NodeId 19): The types do not match 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@mut_.nu.snap b/src/snapshots/new_nu_parser__test__node_output@mut_.nu.snap index 1c9b477..22dea32 100644 --- a/src/snapshots/new_nu_parser__test__node_output@mut_.nu.snap +++ b/src/snapshots/new_nu_parser__test__node_output@mut_.nu.snap @@ -2,6 +2,7 @@ source: src/test.rs expression: evaluate_example(path) input_file: tests/mut_.nu +snapshot_kind: text --- ==== COMPILER ==== 0: Variable (4 to 5) "x" @@ -25,12 +26,12 @@ input_file: tests/mut_.nu 1: unknown 2: int 3: int -4: () +4: none 5: int 6: forbidden 7: int 8: forbidden 9: int 10: int -11: () -12: () +11: none +12: none 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..51ae70a 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,16 +28,16 @@ input_file: tests/reparse.nu 0: closure 1: unknown 2: any -3: unknown +3: forbidden 4: unknown -5: () +5: unknown 6: closure -7: () +7: none 8: unknown 9: unknown 10: unknown 11: unknown -12: () -13: () +12: none +13: none ==== TYPE ERRORS ==== Error (NodeId 11): unsupported ast node 'Record { pairs: [(NodeId(9), NodeId(10))] }' in typechecker 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@unit.nu.snap b/src/snapshots/new_nu_parser__test__node_output@unit.nu.snap new file mode 100644 index 0000000..595fec2 --- /dev/null +++ b/src/snapshots/new_nu_parser__test__node_output@unit.nu.snap @@ -0,0 +1,14 @@ +--- +source: src/test.rs +expression: evaluate_example(path) +input_file: tests/unit.nu +snapshot_kind: text +--- +==== COMPILER ==== +0: Unit (0 to 2) +1: Block(BlockId(0)) (0 to 3) +==== SCOPE ==== +0: Frame Scope, node_id: NodeId(1) (empty) +==== TYPES ==== +0: () +1: () 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..a190aea --- /dev/null +++ b/src/snapshots/new_nu_parser__test__node_output@while.nu.snap @@ -0,0 +1,28 @@ +--- +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 { cond_block: Some((NodeId(6), NodeId(11))), short_flag: None, long_flag: None } (10 to 35) +13: Name (44 to 45) "h" +14: Call { parts: [NodeId(13)] } (45 to 45) +15: Garbage (45 to 46) +16: Block(BlockId(1)) (45 to 46) +17: While { cond_block: Some((NodeId(14), NodeId(16))), short_flag: None, long_flag: None } (37 to 46) +18: Block(BlockId(2)) (0 to 46) +==== COMPILER ERRORS ==== +Error (NodeId 15): expected: left bracket '{' diff --git a/src/typechecker.rs b/src/typechecker.rs index c0bcae0..3d87d53 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 @@ -24,16 +29,17 @@ pub enum Type { None, Any, Number, - Nothing, + Unit, Int, Float, Bool, String, Binary, - Block, Closure, List(TypeId), Stream(TypeId), + OneOf(OneOfId), + Error, } pub struct Types { @@ -50,19 +56,19 @@ pub const FORBIDDEN_TYPE: TypeId = TypeId(1); pub const NONE_TYPE: TypeId = TypeId(2); pub const ANY_TYPE: TypeId = TypeId(3); pub const NUMBER_TYPE: TypeId = TypeId(4); -pub const NOTHING_TYPE: TypeId = TypeId(5); +pub const UNIT_TYPE: TypeId = TypeId(5); pub const INT_TYPE: TypeId = TypeId(6); 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 @@ -92,18 +100,19 @@ impl<'a> Typechecker<'a> { Type::None, Type::Any, Type::Number, - Type::Nothing, + Type::Unit, Type::Int, Type::Float, 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 { @@ -178,7 +187,7 @@ impl<'a> Typechecker<'a> { fn typecheck_node(&mut self, node_id: NodeId) { match self.compiler.ast_nodes[node_id.0] { AstNode::Null => { - self.set_node_type_id(node_id, NOTHING_TYPE); + self.set_node_type_id(node_id, UNIT_TYPE); } AstNode::Int => { self.set_node_type_id(node_id, INT_TYPE); @@ -192,14 +201,27 @@ impl<'a> Typechecker<'a> { AstNode::String => { self.set_node_type_id(node_id, STRING_TYPE); } + AstNode::Unit => { + self.set_node_type_id(node_id, UNIT_TYPE); + } AstNode::Params(ref params) => { 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 +267,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 +315,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 { @@ -297,7 +351,89 @@ impl<'a> Typechecker<'a> { return_ty, block, } => self.typecheck_def(name, params, return_ty, block, node_id), + AstNode::Alias { new_name, old_name } => { + self.typecheck_alias(new_name, old_name, 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 { cond_block, .. } => { + if let Some((condition, block)) = cond_block { + self.typecheck_node(condition); + + self.typecheck_node(block); + if self.type_id_of(block) != NONE_TYPE { + self.error("Blocks in looping constructs cannot return values", block); + } + + // 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)); + } + } else { + self.set_node_type_id(node_id, NONE_TYPE); + } + } + 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, UNIT_TYPE); + } + } + } _ => self.error( format!( "unsupported ast node '{:?}' in typechecker", @@ -308,6 +444,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 +589,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 +615,35 @@ 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_alias(&mut self, new_name: NodeId, old_name: NodeId, node_id: NodeId) { + self.set_node_type_id(node_id, NONE_TYPE); + + // set input/output types for the command + let decl_id_new = self + .compiler + .decl_resolution + .get(&new_name) + .expect("missing declared new name for alias"); + + let decl_id_old = self.compiler.decl_resolution.get(&old_name); + + self.decl_types[decl_id_new.0] = decl_id_old.map_or( + vec![InOutType { + in_type: ANY_TYPE, + out_type: BYTE_STREAM_TYPE, + }], + |decl_id| self.decl_types[decl_id.0].clone(), + ); + } + 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 { @@ -528,7 +744,7 @@ impl<'a> Typechecker<'a> { // b"glob" => SyntaxShape::GlobPattern, b"int" => INT_TYPE, // _ if bytes.starts_with(b"list") => parse_list_shape(working_set, bytes, span, use_loc), - b"nothing" => NOTHING_TYPE, + b"nothing" => UNIT_TYPE, b"number" => NUMBER_TYPE, // b"path" => SyntaxShape::Filepath, // b"range" => SyntaxShape::Range, @@ -558,12 +774,11 @@ impl<'a> Typechecker<'a> { Type::None => NONE_TYPE, Type::Any => ANY_TYPE, Type::Number => NUMBER_TYPE, - Type::Nothing => NOTHING_TYPE, + Type::Unit => UNIT_TYPE, Type::Int => INT_TYPE, 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, _ => { @@ -610,16 +825,15 @@ impl<'a> Typechecker<'a> { match ty { Type::Unknown => "unknown".to_string(), Type::Forbidden => "forbidden".to_string(), - Type::None => "()".to_string(), + Type::None => "none".to_string(), Type::Any => "any".to_string(), Type::Number => "number".to_string(), - Type::Nothing => "nothing".to_string(), + Type::Unit => "()".to_string(), Type::Int => "int".to_string(), Type::Float => "float".to_string(), 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 +841,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 +880,16 @@ impl<'a> Typechecker<'a> { ), op, ); + self.set_node_type_id(op, ERROR_TYPE); + } + + /// Add types to the btreeset while resolving OneOf type to constituent types + 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..0cabf08 --- /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/unit.nu b/tests/unit.nu new file mode 100644 index 0000000..6a452c1 --- /dev/null +++ b/tests/unit.nu @@ -0,0 +1 @@ +() diff --git a/tests/while.nu b/tests/while.nu new file mode 100644 index 0000000..c63de08 --- /dev/null +++ b/tests/while.nu @@ -0,0 +1,6 @@ +mut x = 0 +while 1 < 2 { + $x += 1 +} + +while -h