From 87baf1c1d6ba64c70307b428378b8ef877419d88 Mon Sep 17 00:00:00 2001 From: Matthew Iselin Date: Sun, 9 Aug 2026 21:21:10 -0700 Subject: [PATCH] Harden native integer inference and promotion --- src/lib/ast/analysis.ml | 18 ++- src/lib/ast/analysis_specialize.ml | 3 +- src/lib/ast/analysis_types.ml | 60 ++++++- src/lib/ast/analysis_typing.ml | 248 ++++++++++++++++++++++++----- src/test/test_llvm_ir.ml | 48 ++++++ src/test/test_pipeline.ml | 22 ++- src/test/test_rc.ml | 1 + src/test/test_typing_semantics.ml | 51 ++++++ tests/inputs/numeric_inference.hv | 10 ++ 9 files changed, 412 insertions(+), 49 deletions(-) create mode 100644 tests/inputs/numeric_inference.hv diff --git a/src/lib/ast/analysis.ml b/src/lib/ast/analysis.ml index e21d998..8d33c4a 100644 --- a/src/lib/ast/analysis.ml +++ b/src/lib/ast/analysis.ml @@ -23,8 +23,9 @@ module Pipeline = struct cleaned : Core.parsed_program; } - let run_analyses ?(check_asserts = true) core = - let typing = Typing.run core in + let run_analyses ?(check_asserts = true) ?(target_profile = default_target_profile) + core = + let typing = Typing.run ~target_profile core in let assert_result = if check_asserts then Assert.run typing else ({ program = typing.program; diagnostics = [] } : Assert.result) @@ -76,8 +77,8 @@ module Pipeline = struct result.typing.diagnostics @ result.verify.diagnostics @ result.semantic.diagnostics @ result.asserts.diagnostics @ result.purity.diagnostics @ result.ownership.diagnostics - let run_core core = - let initial = run_analyses ~check_asserts:false core in + let run_core ?(target_profile = default_target_profile) core = + let initial = run_analyses ~check_asserts:false ~target_profile core in if has_errors (analysis_diagnostics initial) then initial else let specialized = Specialize.run initial.typing in @@ -90,13 +91,16 @@ module Pipeline = struct diagnostics = initial.typing.diagnostics @ specialized.diagnostics; }; } - else run_analyses specialized.program + else run_analyses ~target_profile specialized.program - let run_cst ?(search_dirs = []) ?sysroot ?import_text_resolver parsed = + let run_cst ?(search_dirs = []) ?sysroot ?import_text_resolver + ?(target_profile = default_target_profile) parsed = let expanded = Imports.expand_cst ~search_dirs ?sysroot ?import_text_resolver parsed in - let result = run_core (Convert.core_of_expanded_cst expanded.parsed) in + let result = + run_core ~target_profile (Convert.core_of_expanded_cst expanded.parsed) + in let typing = { result.typing with diff --git a/src/lib/ast/analysis_specialize.ml b/src/lib/ast/analysis_specialize.ml index 8073aa8..e86a919 100644 --- a/src/lib/ast/analysis_specialize.ml +++ b/src/lib/ast/analysis_specialize.ml @@ -465,7 +465,8 @@ module Specialize = struct inst.template.value.params.value.params inst.param_types in let temp_typed, _body_result = - Typing.analyze_function_body state.typed.program + Typing.analyze_function_body + ~target_profile:state.typed.target_profile state.typed.program ~active_specializations:[ function_id inst.template ] ~param_bindings inst.template in diff --git a/src/lib/ast/analysis_types.ml b/src/lib/ast/analysis_types.ml index e0c696c..bf4b720 100644 --- a/src/lib/ast/analysis_types.ml +++ b/src/lib/ast/analysis_types.ml @@ -35,6 +35,17 @@ type integer_shape = { signedness : signedness option; } +type target_profile = { + native_integer_bits : int; + c_integer_bits : int; +} + +let default_target_profile = + { + native_integer_bits = Sys.word_size; + c_integer_bits = min 32 Sys.word_size; + } + type metavar = { classes : type_class list; constant : constant_value option; @@ -111,6 +122,7 @@ type typing_result = { program : Core.parsed_program; annotations : annotations; diagnostics : diagnostic list; + target_profile : target_profile; } type semantic_result = { diagnostics : diagnostic list } @@ -288,6 +300,19 @@ let smallest_integer_type loc value = if value >= 0 then numeric_type loc Unsigned (exact_integer_bits value) else numeric_type loc Signed (exact_integer_bits value) +let integer_fits signedness bits value = + if bits <= 0 then false + else + match signedness with + | Signed -> + if bits >= Sys.int_size then true + else + let magnitude = 1 lsl (bits - 1) in + value >= -magnitude && value <= magnitude - 1 + | Unsigned -> + value >= 0 + && (bits >= Sys.int_size || value <= (1 lsl bits) - 1) + let equal_list eq a b = let rec loop xs ys = match (xs, ys) with @@ -473,6 +498,39 @@ let combine_matrix_kind (left : mat_type) (right : mat_type) = linear algebra libraries impractical. *) let resolved_arithmetic_binary_result op left right = match (op, left, right) with + (* TODO: Provide a strict-mode escape hatch before broadening implicit + variable-to-variable widening. Literal operands are checked against the + other operand first and never widen an operation merely to fit a value. *) + | ( Core.Add | Core.Subtract | Core.Multiply | Core.Divide | Core.Modulo + | Core.LeftShift | Core.RightShift | Core.BitwiseAnd | Core.BitwiseOr + | Core.BitwiseXor ), + ResolvedInt (left_signedness, left_bits), + ResolvedInt (right_signedness, right_bits) + when left_signedness = right_signedness -> + Some (ResolvedInt (left_signedness, max left_bits right_bits)) + | ( Core.Add | Core.Subtract | Core.Multiply | Core.Divide | Core.Modulo + | Core.LeftShift | Core.RightShift | Core.BitwiseAnd | Core.BitwiseOr + | Core.BitwiseXor ), + ResolvedInt (Signed, signed_bits), + ResolvedInt (Unsigned, unsigned_bits) + | ( Core.Add | Core.Subtract | Core.Multiply | Core.Divide | Core.Modulo + | Core.LeftShift | Core.RightShift | Core.BitwiseAnd | Core.BitwiseOr + | Core.BitwiseXor ), + ResolvedInt (Unsigned, unsigned_bits), + ResolvedInt (Signed, signed_bits) + when signed_bits > unsigned_bits -> + Some (ResolvedInt (Signed, signed_bits)) + | (Core.Add | Core.Subtract | Core.Multiply | Core.Divide | Core.Modulo), + ResolvedFloat, + ResolvedFloat -> + Some ResolvedFloat + | (Core.Add | Core.Subtract | Core.Multiply | Core.Divide | Core.Modulo), + ResolvedInt _, + ResolvedFloat + | (Core.Add | Core.Subtract | Core.Multiply | Core.Divide | Core.Modulo), + ResolvedFloat, + ResolvedInt _ -> + Some ResolvedFloat | ( Core.Add | Core.Subtract | Core.Multiply | Core.Divide | Core.Modulo ), ResolvedVec left, ResolvedVec right @@ -753,7 +811,7 @@ let wider_numeric_type loc (left : Core.haven_type) (right : Core.haven_type) = | Signed, _ | _, Signed -> Signed | Unsigned, Unsigned -> Unsigned in - numeric_type loc signedness (max 32 (max a.bits b.bits)) + numeric_type loc signedness (max a.bits b.bits) | _ -> left let lookup_named_type type_env name = String_map.find_opt name type_env diff --git a/src/lib/ast/analysis_typing.ml b/src/lib/ast/analysis_typing.ml index 5b7f36c..36e3544 100644 --- a/src/lib/ast/analysis_typing.ml +++ b/src/lib/ast/analysis_typing.ml @@ -10,6 +10,7 @@ module Typing = struct type_env : type_env; functions : Core.function_decl String_map.t; mutable active_specializations : string list; + target_profile : target_profile; } let add_diagnostic_with_category state category level loc message = @@ -347,8 +348,8 @@ module Typing = struct integer = None; }; } - | Core.Unary unary -> infer_unary state env expr.loc unary - | Core.Binary binary -> infer_binary state env expr.loc binary + | Core.Unary unary -> infer_unary state env ~expected_type expr.loc unary + | Core.Binary binary -> infer_binary state env ~expected_type expr.loc binary | Core.Block block -> infer_block state env ~result_expected:expected_type ~return_expected:None block | Core.Initializer init -> @@ -596,10 +597,20 @@ module Typing = struct let annotation = match literal.value with | Core.Integer value -> - let ty = smallest_integer_type loc value in + let signedness, bits = + match expected_type with + | Some (ResolvedInt (signedness, bits)) -> (signedness, bits) + | _ -> (Signed, state.target_profile.native_integer_bits) + in + if not (integer_fits signedness bits value) then + add_diagnostic state Error loc + (Printf.sprintf "integer literal %d does not fit %s%d" value + (match signedness with Signed -> "i" | Unsigned -> "u") + bits); + let ty = numeric_type loc signedness bits in { inferred_type = Some ty; - resolved_type = Some (ResolvedInt ((if value < 0 then Signed else Unsigned), exact_integer_bits value)); + resolved_type = Some (ResolvedInt (signedness, bits)); metavar = { classes = [ TypeClassNumeric ]; @@ -880,33 +891,166 @@ module Typing = struct "zero requires an explicit array, struct, vector, or matrix target type"; annotation_of_resolved loc expected_type - and infer_unary state env loc (unary : Core.unary) : expr_annotation = - let inner_ann = infer_value_expression state env unary.value.inner in - match unary.value.op with - | Core.Not -> - let ty = bool_type loc in + and infer_unary state env ~(expected_type : resolved_ty option) loc + (unary : Core.unary) : expr_annotation = + let infer_negated_integer value = + let signedness, bits = + match expected_type with + | Some (ResolvedInt (signedness, bits)) -> (signedness, bits) + | _ -> (Signed, state.target_profile.native_integer_bits) + in + let negated = -value in + if not (integer_fits signedness bits negated) then + add_diagnostic state Error loc + (Printf.sprintf "integer literal %d does not fit %s%d" negated + (match signedness with Signed -> "i" | Unsigned -> "u") + bits); + let inner_ty = numeric_type unary.value.inner.loc Unsigned bits in + let inner_ann = { - inferred_type = Some ty; - resolved_type = Some (ResolvedInt (Unsigned, 1)); - metavar = metavar_of_type ty; + inferred_type = Some inner_ty; + resolved_type = Some (ResolvedInt (Unsigned, bits)); + metavar = + { + classes = [ TypeClassNumeric ]; + constant = Some (ConstantInt value); + integer = + Some + { + exact_value = Some value; + minimum_bits = Some (exact_integer_bits value); + signedness = Some Unsigned; + }; + }; } - | Core.Negate | Core.Complement -> ( - match (inner_ann.inferred_type, inner_ann.resolved_type) with - | Some _ty, Some (ResolvedInt (_, bits)) -> - let ty = numeric_type loc Signed (max 32 bits) in - { inferred_type = Some ty; resolved_type = Some (ResolvedInt (Signed, max 32 bits)); metavar = metavar_of_type ty } - | Some ty, resolved_type -> - { inferred_type = Some ty; resolved_type; metavar = metavar_of_type ty } - | None, _ -> + in + ignore (record_expr state unary.value.inner inner_ann); + let ty = numeric_type loc signedness bits in + { + inferred_type = Some ty; + resolved_type = Some (ResolvedInt (signedness, bits)); + metavar = + { + classes = [ TypeClassNumeric ]; + constant = Some (ConstantInt negated); + integer = + Some + { + exact_value = Some negated; + minimum_bits = Some (exact_integer_bits negated); + signedness = Some signedness; + }; + }; + } + in + match (unary.value.op, unary.value.inner.value) with + | Core.Negate, Core.Literal { value = Core.Integer value; _ } -> + infer_negated_integer value + | _ -> + let inner_expected = + match unary.value.op with + | Core.Complement -> expected_type + | Core.Not | Core.Negate -> None + in + let inner_ann = + infer_value_expression state env ~expected_type:inner_expected + unary.value.inner + in + match unary.value.op with + | Core.Not -> + let ty = bool_type loc in { - inferred_type = None; - resolved_type = None; - metavar = { unknown_metavar with classes = [ TypeClassNumeric ] }; - }) + inferred_type = Some ty; + resolved_type = Some (ResolvedInt (Unsigned, 1)); + metavar = metavar_of_type ty; + } + | Core.Negate | Core.Complement -> ( + match (inner_ann.inferred_type, inner_ann.resolved_type) with + | Some _ty, Some (ResolvedInt (_, bits)) -> + let bits = max state.target_profile.native_integer_bits bits in + let ty = numeric_type loc Signed bits in + { + inferred_type = Some ty; + resolved_type = Some (ResolvedInt (Signed, bits)); + metavar = metavar_of_type ty; + } + | Some ty, resolved_type -> + { inferred_type = Some ty; resolved_type; metavar = metavar_of_type ty } + | None, _ -> + { + inferred_type = None; + resolved_type = None; + metavar = { unknown_metavar with classes = [ TypeClassNumeric ] }; + }) - and infer_binary state env loc (binary : Core.binary) : expr_annotation = - let left_ann = infer_value_expression state env binary.value.left in - let right_ann = infer_value_expression state env binary.value.right in + and infer_binary state env ~(expected_type : resolved_ty option) loc + (binary : Core.binary) : expr_annotation = + let integer_literal_value (expr : Core.expression) = + match expr.value with + | Core.Literal { value = Core.Integer value; _ } -> Some value + | Core.Unary + { + value = + { + op = Core.Negate; + inner = { value = Core.Literal { value = Core.Integer value; _ }; _ }; + }; + _; + } -> + Some (-value) + | _ -> None + in + let expected_integer = + match expected_type with + | Some (ResolvedInt _ as expected) -> Some expected + | _ -> None + in + let left_literal = integer_literal_value binary.value.left in + let right_literal = integer_literal_value binary.value.right in + let left_ann, right_ann = + match (left_literal, right_literal, expected_integer) with + | Some _, Some _, Some expected -> + ( infer_value_expression state env ~expected_type:(Some expected) + binary.value.left, + infer_value_expression state env ~expected_type:(Some expected) + binary.value.right ) + | Some _, Some _, None -> + let left_ann = infer_value_expression state env binary.value.left in + let right_ann = + infer_value_expression state env ~expected_type:left_ann.resolved_type + binary.value.right + in + (left_ann, right_ann) + | Some _, None, _ -> + let right_ann = infer_value_expression state env binary.value.right in + let left_ann = + infer_value_expression state env ~expected_type:right_ann.resolved_type + binary.value.left + in + (left_ann, right_ann) + | None, Some _, _ -> + let left_ann = infer_value_expression state env binary.value.left in + let right_ann = + infer_value_expression state env ~expected_type:left_ann.resolved_type + binary.value.right + in + (left_ann, right_ann) + | None, None, _ -> + ( infer_value_expression state env binary.value.left, + infer_value_expression state env binary.value.right ) + in + (match (left_ann.resolved_type, right_ann.resolved_type) with + | Some (ResolvedInt (left_signedness, _) as left_resolved), + Some (ResolvedInt (right_signedness, _) as right_resolved) + when left_signedness <> right_signedness + && binary.value.op <> Core.LogicAnd + && binary.value.op <> Core.LogicOr + && Option.is_none + (resolved_arithmetic_binary_result Core.Add left_resolved + right_resolved) -> + add_diagnostic state Error loc + "binary integer operands with different signedness require an explicit cast" + | _ -> ()); match binary.value.op with | Core.IsEqual | Core.NotEqual @@ -1105,10 +1249,34 @@ module Typing = struct and infer_call state env ~(expected_type : resolved_ty option) _loc (call : Core.call) : expr_annotation = - let infer_args_with_expected expected_args = + let infer_args_with_expected ?(vararg = false) expected_args = List.iteri (fun index (expr : Core.expression) -> - let expected = nth_or_none expected_args index in + let expected = + match nth_or_none expected_args index with + | Some _ as expected -> expected + | None when vararg -> ( + match expr.value with + | Core.Literal { value = Core.Integer _; _ } + | Core.Unary + { + value = + { + op = Core.Negate; + inner = + { + value = Core.Literal { value = Core.Integer _; _ }; + _; + }; + }; + _; + } -> + Some + (ResolvedInt + (Signed, state.target_profile.c_integer_bits)) + | _ -> None) + | None -> None + in ignore (infer_value_expression state env ~expected_type:expected expr)) call.value.params in @@ -1208,7 +1376,7 @@ module Typing = struct | Some ty -> ( match ty.value with | Core.FunctionType fn -> - infer_args_with_expected + infer_args_with_expected ~vararg:fn.value.vararg (List.map (fun expected_ty -> resolve_core_type state.type_env [] [] call.loc expected_ty) @@ -1238,7 +1406,7 @@ module Typing = struct | Some ty -> ( match ty.value with | Core.FunctionType fn -> - infer_args_with_expected + infer_args_with_expected ~vararg:fn.value.vararg (List.map (fun expected_ty -> resolve_core_type state.type_env [] [] call.loc expected_ty) @@ -1256,7 +1424,7 @@ module Typing = struct | Some ty -> ( match ty.value with | Core.FunctionType fn -> - infer_args_with_expected + infer_args_with_expected ~vararg:fn.value.vararg (List.map (fun expected_ty -> resolve_core_type state.type_env [] [] call.loc expected_ty) @@ -1363,7 +1531,7 @@ module Typing = struct { inferred_type = Some ty; resolved_type = Some resolved_type; metavar = metavar_of_type ty } | None, None, None, None -> unknown_expr_annotation - let make_state (program : Core.parsed_program) = + let make_state target_profile (program : Core.parsed_program) = let type_env = type_env_of_program program.program in { annotations = make_annotations (); @@ -1372,6 +1540,7 @@ module Typing = struct type_env; functions = collect_functions program.program; active_specializations = []; + target_profile; } let globals_env state = [ state.globals ] @@ -1405,9 +1574,10 @@ module Typing = struct infer_block state env ~result_expected:return_expected ~return_expected body | None -> unknown_expr_annotation - let analyze_function_body (program : Core.parsed_program) - ?(active_specializations = []) ?param_bindings (fn : Core.function_decl) = - let state = make_state program in + let analyze_function_body ?(target_profile = default_target_profile) + (program : Core.parsed_program) ?(active_specializations = []) ?param_bindings + (fn : Core.function_decl) = + let state = make_state target_profile program in let body_result = analyze_function_body_with_state state program ~active_specializations ?param_bindings fn @@ -1416,11 +1586,12 @@ module Typing = struct program; annotations = state.annotations; diagnostics = List.rev state.diagnostics_rev; + target_profile; }, body_result ) - let run (program : Core.parsed_program) = - let state = make_state program in + let run ?(target_profile = default_target_profile) (program : Core.parsed_program) = + let state = make_state target_profile program in collect_globals state program.program; let globals_env () = globals_env state in List.iter @@ -1509,5 +1680,6 @@ module Typing = struct program; annotations = state.annotations; diagnostics = List.rev state.diagnostics_rev; + target_profile; } end diff --git a/src/test/test_llvm_ir.ml b/src/test/test_llvm_ir.ml index 9a5dea4..02a1390 100644 --- a/src/test/test_llvm_ir.ml +++ b/src/test/test_llvm_ir.ml @@ -164,6 +164,54 @@ let run () = assert_true "constant casts should match the declared global type" (string_contains constant_cast_ir "@ZERO = internal constant i32 0"); + let native_integer_ir = + emit_ir + {| +pub impure fn printf(str fmt, *) -> i32; + +pub fn accumulator() -> i32 { + let mut value = 0; + value = value + 2; + value +} + +pub fn compare() -> i32 { + let left = 1; + let right = 2; + if left < right { 3 } else { 4 } +} + +pub impure fn print_literal() -> i32 { + printf("%d\n", 1) +} +|} + in + let native_ty = Printf.sprintf "i%d" Sys.word_size in + assert_true "unconstrained integer bindings should use the native word width" + (string_contains native_integer_ir + (Printf.sprintf "%%value = alloca %s" native_ty)); + assert_true "inferred accumulators should not truncate back to i1" + (not (string_contains native_integer_ir "trunc i32 %add to i1")); + assert_true "integer comparisons should use the promoted operand width" + (string_contains native_integer_ir (Printf.sprintf "icmp slt %s" native_ty)); + assert_true + "integer literals in C-style varargs should receive integer promotion" + (string_contains native_integer_ir "@printf(ptr @.str.0, i32 1)"); + + let contextual_integer_ir = + emit_ir + {| +pub fn add_literal(i16 value) -> i16 { value + 1 } +pub fn add_widths(i16 left, i32 right) -> i32 { left + right } +|} + in + assert_true "a fitting literal should retain the typed operand width" + (string_contains contextual_integer_ir "add i16"); + assert_true "same-signed typed operands should promote to the wider width" + (string_contains contextual_integer_ir "sext i16"); + assert_true "promoted typed operands should compute at the common width" + (string_contains contextual_integer_ir "add i32"); + let short_circuit_ir = emit_ir {| diff --git a/src/test/test_pipeline.ml b/src/test/test_pipeline.ml index b87c30e..7951fd7 100644 --- a/src/test/test_pipeline.ml +++ b/src/test/test_pipeline.ml @@ -12,13 +12,31 @@ let run () = (match binding_ann.inferred_type with | Some ty -> ( match ty.value with - | Core.NumericType { signedness = Haven.Token.Unsigned; bits = 3 } -> () - | _ -> failwith "expected let x = 5 to infer an unsigned 3-bit numeric type") + | Core.NumericType + { signedness = Haven.Token.Signed; bits = native_bits } + when native_bits = Sys.word_size -> + () + | _ -> failwith "expected let x = 5 to infer the signed native word type") | None -> failwith "expected inferred type for let binding"); (match binding_ann.metavar.integer with | Some { exact_value = Some 5; minimum_bits = Some 3; _ } -> () | _ -> failwith "expected integer metavar to record exact value and width"); + let target_profile : Analysis.target_profile = + { native_integer_bits = 16; c_integer_bits = 16 } + in + let typed_16 = + parse_to_core "pub fn main() -> void { let x = 5; }" + |> Analysis.Typing.run ~target_profile + in + let binding_16 = find_first_let_binding typed_16.program in + let binding_16_ann = + Hashtbl.find typed_16.annotations.bindings (Analysis.binding_id binding_16) + in + (match binding_16_ann.resolved_type with + | Some (Analysis.ResolvedInt (Haven.Token.Signed, 16)) -> () + | _ -> failwith "expected target profile to select a signed 16-bit default"); + let pipeline = Haven.Parser.parse_string "pub fn main() -> i32 { if !0 { 1 } else { 0 } }" |> Analysis.Pipeline.run_cst diff --git a/src/test/test_rc.ml b/src/test/test_rc.ml index b1b96f2..7ea59ea 100644 --- a/src/test/test_rc.ml +++ b/src/test/test_rc.ml @@ -55,6 +55,7 @@ let rc_cases = { name = "match_stmt"; expected_rc = 5 }; { name = "mat_extract"; expected_rc = 2 }; { name = "array_local"; expected_rc = 16 }; + { name = "numeric_inference"; expected_rc = 5 }; ] let opt_cases = diff --git a/src/test/test_typing_semantics.ml b/src/test/test_typing_semantics.ml index dd66d44..13f06f8 100644 --- a/src/test/test_typing_semantics.ml +++ b/src/test/test_typing_semantics.ml @@ -28,6 +28,57 @@ let run () = assert_diagnostic_category "typing diagnostic category" Analysis.TypeCheck bad_typing.diagnostics; + let fitting_literal = + parse_to_core "pub fn main() -> i8 { let i8 x = 1; x }" + |> Analysis.Pipeline.run_core + in + assert_no_diagnostics "compile-time fitting integer literal" + fitting_literal.typing.diagnostics; + + let minimum_signed_literal = + parse_to_core "pub fn main() -> i8 { -128 }" |> Analysis.Pipeline.run_core + in + assert_no_diagnostics "minimum signed integer literal" + minimum_signed_literal.typing.diagnostics; + + let out_of_range_literal = + parse_to_core "pub fn main() -> i8 { let i8 x = 128; x }" + |> Analysis.Pipeline.run_core + in + assert_has_diagnostics + "out-of-range integer literal should fail at compile time" + out_of_range_literal.typing.diagnostics; + assert_any_diagnostic_message_contains "out-of-range integer literal wording" + "integer literal 128 does not fit i8" + out_of_range_literal.typing.diagnostics; + + let contextual_binary_literal = + parse_to_core "pub fn add(i16 value) -> i16 { value + 1 }" + |> Analysis.Pipeline.run_core + in + assert_no_diagnostics "binary literal fitting the other operand" + contextual_binary_literal.typing.diagnostics; + + let widening_binary_literal = + parse_to_core "pub fn add(i16 value) -> i16 { value + 70000 }" + |> Analysis.Pipeline.run_core + in + assert_has_diagnostics "binary literal must not widen the other operand" + widening_binary_literal.typing.diagnostics; + assert_any_diagnostic_message_contains "binary literal no-widening wording" + "integer literal 70000 does not fit i16" + widening_binary_literal.typing.diagnostics; + + let mixed_signedness = + parse_to_core "pub fn add(i16 left, u16 right) -> i16 { left + right }" + |> Analysis.Pipeline.run_core + in + assert_has_diagnostics "mixed signedness should require an explicit cast" + mixed_signedness.typing.diagnostics; + assert_any_diagnostic_message_contains "mixed signedness wording" + "different signedness require an explicit cast" + mixed_signedness.typing.diagnostics; + let enum_pipeline = parse_to_core {| diff --git a/tests/inputs/numeric_inference.hv b/tests/inputs/numeric_inference.hv new file mode 100644 index 0000000..21d7212 --- /dev/null +++ b/tests/inputs/numeric_inference.hv @@ -0,0 +1,10 @@ +pub fn sut() -> i32 { + let mut accumulator = 0; + accumulator = accumulator + 2; + + let left = 1; + let right = 2; + let comparison = if left < right { 3 } else { 4 }; + + accumulator + comparison +}