add range constraint type
This commit is contained in:
@@ -181,19 +181,26 @@
|
|||||||
- disallow: `b :: undefined` since assigning undefined to something that can't change defeats the purpose
|
- disallow: `b :: undefined` since assigning undefined to something that can't change defeats the purpose
|
||||||
- disallow assigning `undefined` after declaration; use optionals and `none` for values that intentionally move back to an empty state
|
- disallow assigning `undefined` after declaration; use optionals and `none` for values that intentionally move back to an empty state
|
||||||
|
|
||||||
13. introduce a `float` type constraint (similar to `int`) (implemented)
|
13. introduce `float` and `range` type constraints (the `int` family generalized) (implemented)
|
||||||
- resolves a local binding to any float scalar (`f32`/`f64`) via static analysis; widens
|
- `float` resolves a local binding to any float scalar (`f32`/`f64`) via static analysis;
|
||||||
`f32` -> `f64` across assignments, mirroring how `int` picks the smallest fitting integer
|
widens `f32` -> `f64` across assignments, mirroring how `int` picks the smallest integer
|
||||||
- on a local declaration, integer literals satisfy `float` and default to `f64`
|
- on a local declaration, integer literals satisfy `float` and default to `f64`
|
||||||
(`pi float = 3` is `3.0`); a runtime integer (`x float = some_i32`) stays a
|
(`pi float = 3` is `3.0`); a runtime integer (`x float = some_i32`) stays a
|
||||||
`cannot implicitly convert` error
|
`cannot implicitly convert` error
|
||||||
- a local initializer whose numeric family doesn't satisfy the constraint now errors for
|
- `range` is now a spellable type/constraint: `r range :: 0..10` resolves to the inferred
|
||||||
both `int` and `float` instead of silently taking the initializer's natural type
|
range (element type preserved), and `func(start, end int) range { return start..end }`
|
||||||
- as with `int`, a constraint in a param/result position is a generic passthrough (it
|
monomorphizes the result per call. this replaces the prior `int`-as-passthrough hack that
|
||||||
forwards the inferred type unchanged, e.g. an identity `func(v int) int` over a range),
|
was the only way to forward a range through a function
|
||||||
so the literal-as-float and family checks apply to local bindings, not passthroughs
|
- `int`/`float`/`range` constraints now gate by family in every position (previously
|
||||||
|
params/results were unchecked generic passthroughs):
|
||||||
|
- a local initializer out of family errors instead of silently taking the natural type
|
||||||
|
- a param rejects an out-of-family argument (`cannot pass f64 to 'int' parameter 'x'`);
|
||||||
|
a `float` param accepts an integer-literal argument as f64 (e.g. `f(3)`)
|
||||||
|
- a function result is narrowed to the constraint's family
|
||||||
|
|
||||||
14. add slice-by-range
|
14. broaden type inference to surrounding context
|
||||||
|
|
||||||
|
15. add slice-by-range
|
||||||
- allow the use of a range in slice expressions:
|
- allow the use of a range in slice expressions:
|
||||||
```
|
```
|
||||||
excl_range range :: 0..10
|
excl_range range :: 0..10
|
||||||
@@ -203,8 +210,6 @@
|
|||||||
some_arr[incl_range] # slice by named inclusive range
|
some_arr[incl_range] # slice by named inclusive range
|
||||||
```
|
```
|
||||||
|
|
||||||
15. broaden type inference from surrounding context
|
|
||||||
|
|
||||||
## A word on multi-unwrap
|
## A word on multi-unwrap
|
||||||
|
|
||||||
Unwrap multiple optionals with `and`. This **short-circuits**: if the first optional is none, subsequent expressions are not evaluated.
|
Unwrap multiple optionals with `and`. This **short-circuits**: if the first optional is none, subsequent expressions are not evaluated.
|
||||||
|
|||||||
@@ -538,7 +538,14 @@ call_arg_expected :: proc(function: ast.Function, index: int) -> types.Type {
|
|||||||
if index < 0 || index >= len(function.params) {
|
if index < 0 || index >= len(function.params) {
|
||||||
return types.INVALID
|
return types.INVALID
|
||||||
}
|
}
|
||||||
return type_from_syntax(function.params[index].type)
|
declared := type_from_syntax(function.params[index].type)
|
||||||
|
// A `float` param defaults to f64 so an integer-literal argument builds as a
|
||||||
|
// float constant (e.g. `f(3)` -> 3.0), mirroring `pi float = 3` for locals.
|
||||||
|
// `int`/`range` constraints have no single default and keep building naturally.
|
||||||
|
if declared == types.FLOAT {
|
||||||
|
return types.F64
|
||||||
|
}
|
||||||
|
return declared
|
||||||
}
|
}
|
||||||
|
|
||||||
callable_arg_expected :: proc(function_type: types.Type, function_item: types.Node, store: ^types.Store, index: int) -> types.Type {
|
callable_arg_expected :: proc(function_type: types.Type, function_item: types.Node, store: ^types.Store, index: int) -> types.Type {
|
||||||
@@ -994,7 +1001,7 @@ find_spec :: proc(checker: ^Checker, template: ast.Function_Id, actual_args: []t
|
|||||||
if param_index < len(actual_args) {
|
if param_index < len(actual_args) {
|
||||||
actual = actual_args[param_index]
|
actual = actual_args[param_index]
|
||||||
}
|
}
|
||||||
if !types.equal(spec.args[param_index], specialized_param_type(param.type, actual)) {
|
if !types.equal(spec.args[param_index], specialized_param_type(checker, param.type, actual)) {
|
||||||
matches = false
|
matches = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -1006,10 +1013,14 @@ find_spec :: proc(checker: ^Checker, template: ast.Function_Id, actual_args: []t
|
|||||||
return INVALID_SPEC
|
return INVALID_SPEC
|
||||||
}
|
}
|
||||||
|
|
||||||
specialized_param_type :: proc(syntax: ast.Type_Syntax, actual: types.Type) -> types.Type {
|
// specialized_param_type maps a parameter's declared type to its monomorphized
|
||||||
|
// type for a given actual argument. A constraint param (`int`/`float`/`range`)
|
||||||
|
// resolves to the actual's family member (INVALID if out of family), so a call
|
||||||
|
// passing an out-of-family argument fails to specialize and is rejected.
|
||||||
|
specialized_param_type :: proc(checker: ^Checker, syntax: ast.Type_Syntax, actual: types.Type) -> types.Type {
|
||||||
declared := type_from_syntax(syntax)
|
declared := type_from_syntax(syntax)
|
||||||
if types.is_constraint(declared) {
|
if types.is_constraint(declared) {
|
||||||
return actual
|
return types.constraint_target(declared, actual, &checker.module.types)
|
||||||
}
|
}
|
||||||
return declared
|
return declared
|
||||||
}
|
}
|
||||||
@@ -1020,7 +1031,7 @@ can_specialize :: proc(checker: ^Checker, function: ast.Function, actual_args: [
|
|||||||
if index < len(actual_args) {
|
if index < len(actual_args) {
|
||||||
actual = actual_args[index]
|
actual = actual_args[index]
|
||||||
}
|
}
|
||||||
if !is_runtime_type(checker, specialized_param_type(param.type, actual)) {
|
if !is_runtime_type(checker, specialized_param_type(checker, param.type, actual)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1039,7 +1050,7 @@ ensure_spec :: proc(checker: ^Checker, template: ast.Function_Id, actual_args: [
|
|||||||
if index < len(actual_args) {
|
if index < len(actual_args) {
|
||||||
actual = actual_args[index]
|
actual = actual_args[index]
|
||||||
}
|
}
|
||||||
append(&signature, specialized_param_type(param.type, actual))
|
append(&signature, specialized_param_type(checker, param.type, actual))
|
||||||
}
|
}
|
||||||
result := type_from_syntax(function.result)
|
result := type_from_syntax(function.result)
|
||||||
if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT {
|
if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT {
|
||||||
@@ -1541,7 +1552,7 @@ merge_infer_local_type :: proc(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if types.is_constraint(local.declared) {
|
if types.is_constraint(local.declared) {
|
||||||
if !types.constraint_accepts(local.declared, inferred) {
|
if !types.constraint_accepts(local.declared, inferred, &checker.module.types) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !is_runtime_type(checker, local.type) {
|
if !is_runtime_type(checker, local.type) {
|
||||||
@@ -1553,7 +1564,7 @@ merge_infer_local_type :: proc(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
merged := types.widest(local.type, inferred)
|
merged := types.widest(local.type, inferred)
|
||||||
if types.constraint_accepts(local.declared, merged) {
|
if types.constraint_accepts(local.declared, merged, &checker.module.types) {
|
||||||
local.type = merged
|
local.type = merged
|
||||||
record_infer_local_type(local^, local_types)
|
record_infer_local_type(local^, local_types)
|
||||||
return true
|
return true
|
||||||
@@ -1608,7 +1619,7 @@ infer_statements :: proc(
|
|||||||
} else if types.is_constraint(declared_local) {
|
} else if types.is_constraint(declared_local) {
|
||||||
// Seed the binding in-family (INVALID on mismatch, which
|
// Seed the binding in-family (INVALID on mismatch, which
|
||||||
// build_block reports). FLOAT defaults integers to f64.
|
// build_block reports). FLOAT defaults integers to f64.
|
||||||
value_type = types.constraint_target(declared_local, value_type)
|
value_type = types.constraint_target(declared_local, value_type, &checker.module.types)
|
||||||
}
|
}
|
||||||
local := Infer_Local{
|
local := Infer_Local{
|
||||||
name=statement.name,
|
name=statement.name,
|
||||||
@@ -1753,10 +1764,10 @@ infer_spec_locals_and_result :: proc(
|
|||||||
result := types.INVALID
|
result := types.INVALID
|
||||||
infer_statements(checker, function.body, &locals, local_types, function.pkg, function.file, demanded, &result, result_hint)
|
infer_statements(checker, function.body, &locals, local_types, function.pkg, function.file, demanded, &result, result_hint)
|
||||||
if types.is_constraint(declared) {
|
if types.is_constraint(declared) {
|
||||||
// Params/results use a constraint as a generic passthrough (e.g. an
|
// Narrow the inferred result to the constraint's family; an out-of-family
|
||||||
// identity `func(v int) int` forwarding a range), so the result keeps
|
// result (e.g. returning a non-integer from an `int` function) yields
|
||||||
// the inferred type as-is rather than being narrowed to the family.
|
// INVALID and is rejected downstream.
|
||||||
return local_types, result
|
return local_types, types.constraint_target(declared, result, &checker.module.types)
|
||||||
}
|
}
|
||||||
return local_types, declared
|
return local_types, declared
|
||||||
}
|
}
|
||||||
@@ -3230,9 +3241,37 @@ build_expr :: proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
arg_violation := source.INVALID_DIAGNOSTIC
|
||||||
|
{
|
||||||
|
params := checker.ast_module.functions[frame.template].params
|
||||||
|
for index in 0..<len(params) {
|
||||||
|
if index >= len(stack[frame_index].arg_types) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
declared := type_from_syntax(params[index].type)
|
||||||
|
actual := stack[frame_index].arg_types[index]
|
||||||
|
if types.is_constraint(declared) && types.is_valid(actual) &&
|
||||||
|
!types.is_valid(types.constraint_target(declared, actual, &checker.module.types)) {
|
||||||
|
arg_violation = source.addf(
|
||||||
|
checker.diagnostics, expr.span,
|
||||||
|
"cannot pass %s to '%s' parameter '%s'",
|
||||||
|
types.name(actual), types.name(declared),
|
||||||
|
symbol_text(checker, params[index].name),
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
spec := find_spec(checker, frame.template, stack[frame_index].arg_types)
|
spec := find_spec(checker, frame.template, stack[frame_index].arg_types)
|
||||||
delete(stack[frame_index].arg_types, checker.allocator)
|
delete(stack[frame_index].arg_types, checker.allocator)
|
||||||
stack[frame_index].arg_types = nil
|
stack[frame_index].arg_types = nil
|
||||||
|
if arg_violation != source.INVALID_DIAGNOSTIC {
|
||||||
|
delete(stack[frame_index].built_args, checker.allocator)
|
||||||
|
stack[frame_index].built_args = nil
|
||||||
|
last = invalid_hir_expr(checker, expr.span, arg_violation)
|
||||||
|
_ = pop(&stack)
|
||||||
|
continue
|
||||||
|
}
|
||||||
if spec == INVALID_SPEC {
|
if spec == INVALID_SPEC {
|
||||||
id := source.addf(
|
id := source.addf(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ keyword_kind :: proc(text: string) -> token.Kind {
|
|||||||
case "bool": return .Keyword_Bool
|
case "bool": return .Keyword_Bool
|
||||||
case "int": return .Keyword_Int
|
case "int": return .Keyword_Int
|
||||||
case "float": return .Keyword_Float
|
case "float": return .Keyword_Float
|
||||||
|
case "range": return .Keyword_Range
|
||||||
case "i8": return .Keyword_I8
|
case "i8": return .Keyword_I8
|
||||||
case "i16": return .Keyword_I16
|
case "i16": return .Keyword_I16
|
||||||
case "i32": return .Keyword_I32
|
case "i32": return .Keyword_I32
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ invalid_expr :: proc(parser: ^Parser, span: source.Span, message: string) -> ast
|
|||||||
|
|
||||||
is_type_token :: proc(kind: token.Kind) -> bool {
|
is_type_token :: proc(kind: token.Kind) -> bool {
|
||||||
#partial switch kind {
|
#partial switch kind {
|
||||||
case .Keyword_Int, .Keyword_Float, .Keyword_I8, .Keyword_I16, .Keyword_I32, .Keyword_I64,
|
case .Keyword_Int, .Keyword_Float, .Keyword_Range, .Keyword_I8, .Keyword_I16, .Keyword_I32, .Keyword_I64,
|
||||||
.Keyword_U8, .Keyword_U16, .Keyword_U32, .Keyword_U64,
|
.Keyword_U8, .Keyword_U16, .Keyword_U32, .Keyword_U64,
|
||||||
.Keyword_Isize, .Keyword_Usize, .Keyword_F32, .Keyword_F64,
|
.Keyword_Isize, .Keyword_Usize, .Keyword_F32, .Keyword_F64,
|
||||||
.Keyword_C_Char, .Keyword_C_Schar, .Keyword_C_Uchar,
|
.Keyword_C_Char, .Keyword_C_Schar, .Keyword_C_Uchar,
|
||||||
@@ -219,6 +219,9 @@ parse_type :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
|||||||
case .Keyword_Float:
|
case .Keyword_Float:
|
||||||
advance(parser)
|
advance(parser)
|
||||||
return types.FLOAT
|
return types.FLOAT
|
||||||
|
case .Keyword_Range:
|
||||||
|
advance(parser)
|
||||||
|
return types.RANGE
|
||||||
case .Keyword_I8:
|
case .Keyword_I8:
|
||||||
advance(parser)
|
advance(parser)
|
||||||
return types.I8
|
return types.I8
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ Kind :: enum u8 {
|
|||||||
Keyword_Bool,
|
Keyword_Bool,
|
||||||
Keyword_Int,
|
Keyword_Int,
|
||||||
Keyword_Float,
|
Keyword_Float,
|
||||||
|
Keyword_Range,
|
||||||
Keyword_I8,
|
Keyword_I8,
|
||||||
Keyword_I16,
|
Keyword_I16,
|
||||||
Keyword_I32,
|
Keyword_I32,
|
||||||
|
|||||||
+21
-10
@@ -42,6 +42,7 @@ C_LONGDOUBLE :: Type(28)
|
|||||||
|
|
||||||
BOOL :: Type(29)
|
BOOL :: Type(29)
|
||||||
FLOAT :: Type(30)
|
FLOAT :: Type(30)
|
||||||
|
RANGE :: Type(31)
|
||||||
|
|
||||||
DYNAMIC_START :: Type(64)
|
DYNAMIC_START :: Type(64)
|
||||||
|
|
||||||
@@ -57,6 +58,7 @@ Kind :: enum u8 {
|
|||||||
Void,
|
Void,
|
||||||
Int_Constraint,
|
Int_Constraint,
|
||||||
Float_Constraint,
|
Float_Constraint,
|
||||||
|
Range_Constraint,
|
||||||
Scalar,
|
Scalar,
|
||||||
Array,
|
Array,
|
||||||
Pointer,
|
Pointer,
|
||||||
@@ -299,6 +301,8 @@ kind :: proc(value: Type, store: ^Store = nil) -> Kind {
|
|||||||
return .Int_Constraint
|
return .Int_Constraint
|
||||||
case FLOAT:
|
case FLOAT:
|
||||||
return .Float_Constraint
|
return .Float_Constraint
|
||||||
|
case RANGE:
|
||||||
|
return .Range_Constraint
|
||||||
case BOOL:
|
case BOOL:
|
||||||
return .Scalar
|
return .Scalar
|
||||||
}
|
}
|
||||||
@@ -338,15 +342,17 @@ is_bool :: proc(value: Type) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
is_constraint :: proc(value: Type) -> bool {
|
is_constraint :: proc(value: Type) -> bool {
|
||||||
return value == INT || value == FLOAT
|
return value == INT || value == FLOAT || value == RANGE
|
||||||
}
|
}
|
||||||
|
|
||||||
// constraint_target reports the concrete type a constraint binding takes for an
|
// constraint_target reports the concrete type a constraint binding (local, or a
|
||||||
// inferred value, or INVALID if the value's numeric family is incompatible.
|
// param/result monomorphized per call site) takes for an inferred value, or
|
||||||
// FLOAT accepts integers by defaulting them to f64: a constant integer becomes a
|
// INVALID if the value's family is incompatible. FLOAT accepts integers by
|
||||||
// float literal in build_constant_expr, while a runtime integer then fails the
|
// defaulting them to f64: a constant integer becomes a float literal in
|
||||||
// cross-family f64 coercion in coerce_expr (the intended mismatch error).
|
// build_constant_expr, while a runtime integer then fails the cross-family f64
|
||||||
constraint_target :: proc(constraint, inferred: Type) -> Type {
|
// coercion in coerce_expr (the intended mismatch error). RANGE accepts any range
|
||||||
|
// type, keeping its inferred element type.
|
||||||
|
constraint_target :: proc(constraint, inferred: Type, store: ^Store = nil) -> Type {
|
||||||
switch constraint {
|
switch constraint {
|
||||||
case INT:
|
case INT:
|
||||||
return inferred if is_concrete_integer(inferred) else INVALID
|
return inferred if is_concrete_integer(inferred) else INVALID
|
||||||
@@ -355,18 +361,22 @@ constraint_target :: proc(constraint, inferred: Type) -> Type {
|
|||||||
return inferred
|
return inferred
|
||||||
}
|
}
|
||||||
return F64 if is_concrete_integer(inferred) else INVALID
|
return F64 if is_concrete_integer(inferred) else INVALID
|
||||||
|
case RANGE:
|
||||||
|
return inferred if is_range(inferred, store) else INVALID
|
||||||
}
|
}
|
||||||
return INVALID
|
return INVALID
|
||||||
}
|
}
|
||||||
|
|
||||||
// constraint_accepts reports strict numeric-family membership, used when widening
|
// constraint_accepts reports strict family membership, used when widening a
|
||||||
// a constraint binding across assignments (no integer-to-float defaulting here).
|
// constraint binding across assignments (no integer-to-float defaulting here).
|
||||||
constraint_accepts :: proc(constraint, concrete: Type) -> bool {
|
constraint_accepts :: proc(constraint, concrete: Type, store: ^Store = nil) -> bool {
|
||||||
switch constraint {
|
switch constraint {
|
||||||
case INT:
|
case INT:
|
||||||
return is_concrete_integer(concrete)
|
return is_concrete_integer(concrete)
|
||||||
case FLOAT:
|
case FLOAT:
|
||||||
return is_float(concrete)
|
return is_float(concrete)
|
||||||
|
case RANGE:
|
||||||
|
return is_range(concrete, store)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -1175,6 +1185,7 @@ name :: proc(value: Type) -> string {
|
|||||||
case BOOL: return "bool"
|
case BOOL: return "bool"
|
||||||
case INT: return "int"
|
case INT: return "int"
|
||||||
case FLOAT: return "float"
|
case FLOAT: return "float"
|
||||||
|
case RANGE: return "range"
|
||||||
case I8: return "i8"
|
case I8: return "i8"
|
||||||
case I16: return "i16"
|
case I16: return "i16"
|
||||||
case I32: return "i32"
|
case I32: return "i32"
|
||||||
|
|||||||
+125
-3
@@ -4841,7 +4841,7 @@ main :: func() i32 {
|
|||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
equal_range_returns_infer_a_usable_result_type :: proc(t: ^testing.T) {
|
equal_range_returns_infer_a_usable_result_type :: proc(t: ^testing.T) {
|
||||||
text := `choose :: func(first bool) int {
|
text := `choose :: func(first bool) range {
|
||||||
if first {
|
if first {
|
||||||
return 0..1
|
return 0..1
|
||||||
}
|
}
|
||||||
@@ -4886,10 +4886,10 @@ main :: func() i32 {
|
|||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
for_loop_lowering_evaluates_once_and_avoids_index_bounds_checks :: proc(t: ^testing.T) {
|
for_loop_lowering_evaluates_once_and_avoids_index_bounds_checks :: proc(t: ^testing.T) {
|
||||||
text := `make_range :: func() int {
|
text := `make_range :: func() range {
|
||||||
return 0..2
|
return 0..2
|
||||||
}
|
}
|
||||||
make_array :: func() int {
|
make_array :: func() [2]i32 {
|
||||||
return [1, 2]
|
return [1, 2]
|
||||||
}
|
}
|
||||||
main :: func() i32 {
|
main :: func() i32 {
|
||||||
@@ -5339,6 +5339,128 @@ main :: func() void {
|
|||||||
testing.expect(t, found_convert_error)
|
testing.expect(t, found_convert_error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
range_constraint_local_resolves_to_inferred_range :: proc(t: ^testing.T) {
|
||||||
|
text := `make :: func() range {
|
||||||
|
r range :: 0..10
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
main :: func() void {
|
||||||
|
_ = make()
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
|
||||||
|
r_type, found := float_local_type(&hir_module, &symbols, "make", "r")
|
||||||
|
result_type, _ := float_result_type(&hir_module, &symbols, "make")
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect(t, found)
|
||||||
|
testing.expect(t, types.is_range(r_type, &hir_module.types))
|
||||||
|
testing.expect_value(t, types.child_type(r_type, &hir_module.types), types.I8)
|
||||||
|
testing.expect(t, types.is_range(result_type, &hir_module.types))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
range_constraint_param_and_result_monomorphize :: proc(t: ^testing.T) {
|
||||||
|
text := `pass :: func(r range) range {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
main :: func() void {
|
||||||
|
once :: 0..5
|
||||||
|
for pass(once) |v| {
|
||||||
|
_ = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
|
||||||
|
result_type, found := float_result_type(&hir_module, &symbols, "pass")
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect(t, found)
|
||||||
|
testing.expect(t, types.is_range(result_type, &hir_module.types))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
int_param_rejects_float_argument :: proc(t: ^testing.T) {
|
||||||
|
text := `take :: func(x int) int {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
main :: func() void {
|
||||||
|
_ = take(1.5)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
|
||||||
|
found_reject := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
found_reject = found_reject ||
|
||||||
|
strings.contains(diagnostic.message, "cannot pass f64 to 'int' parameter 'x'")
|
||||||
|
}
|
||||||
|
|
||||||
|
testing.expect(t, found_reject)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
float_param_accepts_integer_literal_argument :: proc(t: ^testing.T) {
|
||||||
|
text := `take :: func(x float) float {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
main :: func() void {
|
||||||
|
y f64 = take(3)
|
||||||
|
_ = y
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
|
||||||
|
result_type, found := float_result_type(&hir_module, &symbols, "take")
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect(t, found)
|
||||||
|
testing.expect_value(t, result_type, types.F64)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
undefined_accepts_concrete_runtime_annotations :: proc(t: ^testing.T) {
|
undefined_accepts_concrete_runtime_annotations :: proc(t: ^testing.T) {
|
||||||
text := `Point :: struct {
|
text := `Point :: struct {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Milestone 5: ranges and sequence for loops.
|
# Milestone 5: ranges and sequence for loops.
|
||||||
|
|
||||||
pass :: func(value int) int {
|
pass :: func(value range) range {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
make_range :: func(calls @mut i32, end usize) int {
|
make_range :: func(calls @mut i32, end usize) range {
|
||||||
calls^ = calls^ + 1
|
calls^ = calls^ + 1
|
||||||
return 0..end
|
return 0..end
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user