Some more type safety. (0.8.0)
This commit is contained in:
parent
be8c179124
commit
a1a5d9fd6a
4 changed files with 276 additions and 15 deletions
|
|
@ -1,5 +1,5 @@
|
|||
cmake_minimum_required(VERSION 3.16)
|
||||
project(fun VERSION 0.7.4 LANGUAGES C)
|
||||
project(fun VERSION 0.8.0 LANGUAGES C)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
|
|
|
|||
72
examples/type_safety.fun
Normal file
72
examples/type_safety.fun
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
#!/usr/bin/env fun
|
||||
|
||||
/**
|
||||
* This file is part of the Fun programming language.
|
||||
* https://hanez.org/project/fun/
|
||||
*
|
||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the ISC license.
|
||||
* https://opensource.org/license/isc-license-txt
|
||||
*/
|
||||
|
||||
// Type Safety demonstration in Fun
|
||||
|
||||
print("== Dynamic variable (untyped) ==")
|
||||
x = 100
|
||||
print(typeof(x)) // -> "Number"
|
||||
print(x) // -> 100
|
||||
|
||||
x = "hello"
|
||||
print(typeof(x)) // -> "String"
|
||||
print(x) // -> "hello"
|
||||
|
||||
print("")
|
||||
print("== Typed variables remain type-stable ==")
|
||||
|
||||
// Strings must always be strings
|
||||
string s = "hello"
|
||||
print(typeof(s)) // -> "String"
|
||||
print(s) // -> "hello"
|
||||
// s = 42 // Uncomment to see runtime TypeError: expected String
|
||||
|
||||
// Booleans are Numbers constrained to 0 or 1 (clamped)
|
||||
boolean flag = 0
|
||||
print(typeof(flag)) // -> "Number"
|
||||
print(flag) // -> 0
|
||||
|
||||
flag = 2 // will be clamped to 1
|
||||
print(flag) // -> 1
|
||||
// flag = "no" // Uncomment to see runtime TypeError: expected Number for boolean
|
||||
|
||||
// number is a signed 64-bit integer and must remain Number
|
||||
number n = 123
|
||||
print(typeof(n)) // -> "Number"
|
||||
n = 456
|
||||
print(n) // -> 456
|
||||
// n = "oops" // Uncomment to see runtime TypeError: expected Number
|
||||
|
||||
print("")
|
||||
print("== Integer width clamping ==")
|
||||
|
||||
// Signed 8-bit: range [-128..127]
|
||||
int8 si = 130 // clamps to 127
|
||||
print(si) // -> 127
|
||||
si = -200 // clamps to -128
|
||||
print(si) // -> -128
|
||||
|
||||
// Unsigned 8-bit: range [0..255]
|
||||
uint8 u = 255
|
||||
print(u) // -> 255
|
||||
u = 300 // clamps to 255
|
||||
print(u) // -> 255
|
||||
// u = "bad" // Uncomment to see runtime TypeError: expected Number
|
||||
|
||||
print("")
|
||||
print("== Nil-typed variables must stay Nil ==")
|
||||
|
||||
nil nn = nil
|
||||
print(typeof(nn)) // -> "Nil"
|
||||
// nn = 0 // Uncomment to see runtime TypeError: expected Nil
|
||||
|
||||
print("")
|
||||
print("Done. Uncomment lines above to see type errors in action.")
|
||||
21
examples/type_safety_fails.fun
Normal file
21
examples/type_safety_fails.fun
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#!/usr/bin/env fun
|
||||
|
||||
/**
|
||||
* This file is part of the Fun programming language.
|
||||
* https://hanez.org/project/fun/
|
||||
*
|
||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the ISC license.
|
||||
* https://opensource.org/license/isc-license-txt
|
||||
*/
|
||||
|
||||
print("Reassigning typed variables to another type should fail")
|
||||
|
||||
string s = "hello"
|
||||
print(typeof(s)) // -> "String"
|
||||
print(s) // -> "hello"
|
||||
|
||||
print("Now trying to assign a number to a string variable...")
|
||||
s = 42 // Runtime TypeError: expected String (assignment rejected)
|
||||
|
||||
print("This line will not execute due to the type error")
|
||||
194
src/parser.c
194
src/parser.c
|
|
@ -62,6 +62,14 @@ static int g_err_col = 0;
|
|||
/* ---- compiler-generated temporary counter ---- */
|
||||
static int g_temp_counter = 0;
|
||||
|
||||
/* Declared type metadata encoding in types[]:
|
||||
0 = dynamic/untyped;
|
||||
positive/negative 8/16/32/64 = integers (negative means signed);
|
||||
TYPE_META_STRING/BOOLEAN/NIL mark non-integer enforced types. */
|
||||
#define TYPE_META_STRING 10001
|
||||
#define TYPE_META_BOOLEAN 10002
|
||||
#define TYPE_META_NIL 10003
|
||||
|
||||
static void parser_fail(size_t pos, const char *fmt, ...) {
|
||||
g_has_error = 1;
|
||||
g_err_pos = pos;
|
||||
|
|
@ -1767,6 +1775,17 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
|
|||
int decl_signed = (is_s8 || is_s16 || is_s32 || is_s64) ? 1 : 0;
|
||||
/* store decl bits with sign encoded: negative means signed (number is signed 64-bit) */
|
||||
if (decl_signed) decl_bits = -decl_bits;
|
||||
|
||||
/* declared type metadata: integers use decl_bits; string/boolean/nil use special markers */
|
||||
int decl_meta = decl_bits;
|
||||
if (is_string) {
|
||||
decl_meta = TYPE_META_STRING;
|
||||
} else if (is_boolean) {
|
||||
decl_meta = TYPE_META_BOOLEAN;
|
||||
} else if (is_nil) {
|
||||
decl_meta = TYPE_META_NIL;
|
||||
}
|
||||
|
||||
free(name);
|
||||
|
||||
/* read variable name */
|
||||
|
|
@ -1785,12 +1804,12 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
|
|||
if (existing >= 0) lidx = existing;
|
||||
else lidx = local_add(varname);
|
||||
if (lidx >= 0) {
|
||||
g_locals->types[lidx] = decl_bits; /* negative = signed, positive = unsigned width, 0 = non-integer */
|
||||
g_locals->types[lidx] = decl_meta; /* encoding: ±bits for integers; TYPE_META_* for non-integer enforced types; 0 = dynamic */
|
||||
}
|
||||
} else {
|
||||
gi = sym_index(varname);
|
||||
if (gi >= 0) {
|
||||
G.types[gi] = decl_bits;
|
||||
G.types[gi] = decl_meta;
|
||||
}
|
||||
}
|
||||
free(varname);
|
||||
|
|
@ -1802,11 +1821,87 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
|
|||
parser_fail(local_pos, "Expected initializer expression after '='");
|
||||
return;
|
||||
}
|
||||
/* clamp if integer type was declared */
|
||||
int abs_bits = decl_bits < 0 ? -decl_bits : decl_bits;
|
||||
if (abs_bits > 0) {
|
||||
bytecode_add_instruction(bc, (decl_bits < 0) ? OP_SCLAMP : OP_UCLAMP, abs_bits);
|
||||
|
||||
/* Enforce declared type on initializer */
|
||||
if (decl_meta == TYPE_META_STRING) {
|
||||
/* expect String */
|
||||
bytecode_add_instruction(bc, OP_DUP, 0);
|
||||
bytecode_add_instruction(bc, OP_TYPEOF, 0);
|
||||
int ciExp = bytecode_add_constant(bc, make_string("String"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ciExp);
|
||||
bytecode_add_instruction(bc, OP_EQ, 0);
|
||||
int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
||||
int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
|
||||
/* error block */
|
||||
bytecode_set_operand(bc, j_to_error, bc->instr_count);
|
||||
{
|
||||
int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected String"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
|
||||
bytecode_add_instruction(bc, OP_PRINT, 0);
|
||||
bytecode_add_instruction(bc, OP_HALT, 0);
|
||||
}
|
||||
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
|
||||
} else if (decl_meta == TYPE_META_BOOLEAN) {
|
||||
/* expect Number then clamp to 1 bit (unsigned) */
|
||||
bytecode_add_instruction(bc, OP_DUP, 0);
|
||||
bytecode_add_instruction(bc, OP_TYPEOF, 0);
|
||||
int ciNum = bytecode_add_constant(bc, make_string("Number"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ciNum);
|
||||
bytecode_add_instruction(bc, OP_EQ, 0);
|
||||
int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
||||
int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
|
||||
bytecode_set_operand(bc, j_to_error, bc->instr_count);
|
||||
{
|
||||
int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Number for boolean"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
|
||||
bytecode_add_instruction(bc, OP_PRINT, 0);
|
||||
bytecode_add_instruction(bc, OP_HALT, 0);
|
||||
}
|
||||
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
|
||||
/* clamp to 0/1 */
|
||||
bytecode_add_instruction(bc, OP_UCLAMP, 1);
|
||||
} else if (decl_meta == TYPE_META_NIL) {
|
||||
/* expect Nil */
|
||||
bytecode_add_instruction(bc, OP_DUP, 0);
|
||||
bytecode_add_instruction(bc, OP_TYPEOF, 0);
|
||||
int ciNil = bytecode_add_constant(bc, make_string("Nil"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ciNil);
|
||||
bytecode_add_instruction(bc, OP_EQ, 0);
|
||||
int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
||||
int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
|
||||
bytecode_set_operand(bc, j_to_error, bc->instr_count);
|
||||
{
|
||||
int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Nil"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
|
||||
bytecode_add_instruction(bc, OP_PRINT, 0);
|
||||
bytecode_add_instruction(bc, OP_HALT, 0);
|
||||
}
|
||||
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
|
||||
} else {
|
||||
/* integer widths: expect Number then clamp */
|
||||
int abs_bits = decl_bits < 0 ? -decl_bits : decl_bits;
|
||||
if (abs_bits > 0) {
|
||||
/* typeof == Number */
|
||||
bytecode_add_instruction(bc, OP_DUP, 0);
|
||||
bytecode_add_instruction(bc, OP_TYPEOF, 0);
|
||||
int ciNum = bytecode_add_constant(bc, make_string("Number"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ciNum);
|
||||
bytecode_add_instruction(bc, OP_EQ, 0);
|
||||
int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
||||
int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
|
||||
bytecode_set_operand(bc, j_to_error, bc->instr_count);
|
||||
{
|
||||
int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Number"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
|
||||
bytecode_add_instruction(bc, OP_PRINT, 0);
|
||||
bytecode_add_instruction(bc, OP_HALT, 0);
|
||||
}
|
||||
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
|
||||
|
||||
bytecode_add_instruction(bc, (decl_bits < 0) ? OP_SCLAMP : OP_UCLAMP, abs_bits);
|
||||
}
|
||||
}
|
||||
|
||||
if (lidx >= 0) {
|
||||
bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx);
|
||||
} else {
|
||||
|
|
@ -1995,17 +2090,90 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
|
|||
if (local_pos < len && src[local_pos] == '=') {
|
||||
local_pos++; /* '=' */
|
||||
if (emit_expression(bc, src, len, &local_pos)) {
|
||||
/* clamp to declared integer width if present */
|
||||
int decl_bits = 0;
|
||||
/* enforce declared type if present (0 = dynamic) */
|
||||
int meta = 0;
|
||||
if (lidx >= 0 && g_locals) {
|
||||
decl_bits = g_locals->types[lidx];
|
||||
meta = g_locals->types[lidx];
|
||||
} else if (gi >= 0) {
|
||||
decl_bits = G.types[gi];
|
||||
meta = G.types[gi];
|
||||
}
|
||||
int abs_bits = decl_bits < 0 ? -decl_bits : decl_bits;
|
||||
if (abs_bits > 0) {
|
||||
bytecode_add_instruction(bc, (decl_bits < 0) ? OP_SCLAMP : OP_UCLAMP, abs_bits);
|
||||
|
||||
if (meta == TYPE_META_STRING) {
|
||||
/* expect String */
|
||||
bytecode_add_instruction(bc, OP_DUP, 0);
|
||||
bytecode_add_instruction(bc, OP_TYPEOF, 0);
|
||||
int ciStr = bytecode_add_constant(bc, make_string("String"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ciStr);
|
||||
bytecode_add_instruction(bc, OP_EQ, 0);
|
||||
int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
||||
int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
|
||||
bytecode_set_operand(bc, j_to_error, bc->instr_count);
|
||||
{
|
||||
int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected String"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
|
||||
bytecode_add_instruction(bc, OP_PRINT, 0);
|
||||
bytecode_add_instruction(bc, OP_HALT, 0);
|
||||
}
|
||||
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
|
||||
} else if (meta == TYPE_META_BOOLEAN) {
|
||||
/* expect Number then clamp to 1 bit (unsigned) */
|
||||
bytecode_add_instruction(bc, OP_DUP, 0);
|
||||
bytecode_add_instruction(bc, OP_TYPEOF, 0);
|
||||
int ciNum = bytecode_add_constant(bc, make_string("Number"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ciNum);
|
||||
bytecode_add_instruction(bc, OP_EQ, 0);
|
||||
int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
||||
int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
|
||||
bytecode_set_operand(bc, j_to_error, bc->instr_count);
|
||||
{
|
||||
int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Number for boolean"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
|
||||
bytecode_add_instruction(bc, OP_PRINT, 0);
|
||||
bytecode_add_instruction(bc, OP_HALT, 0);
|
||||
}
|
||||
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
|
||||
bytecode_add_instruction(bc, OP_UCLAMP, 1);
|
||||
} else if (meta == TYPE_META_NIL) {
|
||||
/* expect Nil */
|
||||
bytecode_add_instruction(bc, OP_DUP, 0);
|
||||
bytecode_add_instruction(bc, OP_TYPEOF, 0);
|
||||
int ciNil = bytecode_add_constant(bc, make_string("Nil"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ciNil);
|
||||
bytecode_add_instruction(bc, OP_EQ, 0);
|
||||
int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
||||
int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
|
||||
bytecode_set_operand(bc, j_to_error, bc->instr_count);
|
||||
{
|
||||
int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Nil"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
|
||||
bytecode_add_instruction(bc, OP_PRINT, 0);
|
||||
bytecode_add_instruction(bc, OP_HALT, 0);
|
||||
}
|
||||
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
|
||||
} else if (meta != 0) {
|
||||
/* integer widths: expect Number then clamp to declared width */
|
||||
int abs_bits = meta < 0 ? -meta : meta;
|
||||
/* typeof == Number */
|
||||
bytecode_add_instruction(bc, OP_DUP, 0);
|
||||
bytecode_add_instruction(bc, OP_TYPEOF, 0);
|
||||
int ciNum = bytecode_add_constant(bc, make_string("Number"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ciNum);
|
||||
bytecode_add_instruction(bc, OP_EQ, 0);
|
||||
int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
||||
int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
|
||||
bytecode_set_operand(bc, j_to_error, bc->instr_count);
|
||||
{
|
||||
int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Number"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
|
||||
bytecode_add_instruction(bc, OP_PRINT, 0);
|
||||
bytecode_add_instruction(bc, OP_HALT, 0);
|
||||
}
|
||||
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
|
||||
|
||||
bytecode_add_instruction(bc, (meta < 0) ? OP_SCLAMP : OP_UCLAMP, abs_bits);
|
||||
}
|
||||
/* dynamic (meta==0): no enforcement */
|
||||
|
||||
if (lidx >= 0) {
|
||||
bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx);
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue