Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions docs/errors/E0044.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# E0044: Integer Literal Out of Range

An integer literal does not fit in the type it was resolved to.

## Example

```starstream
fn main() {
let x: i8 = 300; // Error: integer literal `300` does not fit in type `i8`
}
```

```starstream
fn main() {
let y: u8 = -1; // Error: integer literal `-1` does not fit in type `u8`
}
```

## How to fix

Either use a literal value that fits within the target type's range, or use a wider type:

```starstream
fn main() {
let x: i8 = 100; // OK: 100 fits in i8 (-128..127)
let y: i16 = 300; // OK: 300 fits in i16 (-32768..32767)
let z: u8 = 255; // OK: 255 fits in u8 (0..255)
}
```

## Integer type ranges

| Type | Min | Max |
| ----- | -------------------------- | ------------------------- |
| `i8` | -128 | 127 |
| `i16` | -32768 | 32767 |
| `i32` | -2147483648 | 2147483647 |
| `i64` | -9223372036854775808 | 9223372036854775807 |
| `u8` | 0 | 255 |
| `u16` | 0 | 65535 |
| `u32` | 0 | 4294967295 |
| `u64` | 0 | 18446744073709551615 |
60 changes: 38 additions & 22 deletions docs/language-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ Calling an effectful or runtime function without the appropriate keyword is a ty

## Expression semantics

- Integer literals work in the obvious way.
- Integer literals are polymorphic: an unadorned numeric literal like `42` adopts the integer type determined by context (e.g. a type annotation or function parameter type). When no context constrains the type, the literal defaults to `i64`. A compile-time error is emitted if the literal value does not fit in the resolved type (e.g. `let x: i8 = 300` is an error).
- Boolean literals work in the obvious way.
- Struct literals `TypeName { field: expr, ... }` evaluate each field expression once and produce a record value. Field names must be unique; order is irrelevant.
- Enum constructors use `TypeName::Variant` with a previously declared enum name. Tuple-style payloads evaluate left-to-right and are stored without reordering.
Expand All @@ -385,12 +385,26 @@ Calling an effectful or runtime function without the appropriate keyword is a ty
- `runtime expr` wraps a runtime function call. Runtime functions access runtime-only information (e.g., block height) and must be explicitly marked at the call site. Using `runtime` on a non-runtime call is a type error.
- Variable names refer to a `let` declaration earlier in the current scope or
one of its parents, but not child scopes.
- Arithmetic operators: `+`, `-`, `*`, `/`, `%` work over integers in the usual
way.
- We assume wrapping signed 64-bit two's complement integers.
- `/` and `%` are floored. `%` has the same sign as the divisor.
- Unary `-` applies to integers. Unary `!` applies to booleans.
- Comparison operators: `==`, `!=`, `<`, `>`, `<=`, `>=` accept (integer, integer) or (boolean, boolean) and
- Arithmetic operators: `+`, `-`, `*`, `/`, `%` work over integers of the same type.
- Both operands must have the same integer type; cross-type arithmetic (e.g. `i32 + i64`) is a type error.
- The supported integer types and their ranges are:

| Type | Signed | Bits | Min | Max |
| ----- | ------ | ---- | -------------------------- | ------------------------- |
| `i8` | yes | 8 | -128 | 127 |
| `i16` | yes | 16 | -32768 | 32767 |
| `i32` | yes | 32 | -2147483648 | 2147483647 |
| `i64` | yes | 64 | -9223372036854775808 | 9223372036854775807 |
| `u8` | no | 8 | 0 | 255 |
| `u16` | no | 16 | 0 | 65535 |
| `u32` | no | 32 | 0 | 4294967295 |
| `u64` | no | 64 | 0 | 18446744073709551615 |

- Integer overflow and underflow is checked at runtime and traps.
- `/` and `%` are floored for signed types. `%` has the same sign as the divisor.
- For unsigned types, `/` and `%` are standard unsigned division and remainder.
- Unary `-` applies to signed integers only. Negating an unsigned integer is a type error. Unary `!` applies to booleans.
- Comparison operators: `==`, `!=`, `<`, `>`, `<=`, `>=` accept (integer, integer) of the same type or (boolean, boolean) and
produce booleans.
- The boolean operators `!`, `&&`, `||` accept booleans and produce
booleans.
Expand All @@ -399,29 +413,31 @@ Calling an effectful or runtime function without the appropriate keyword is a ty

| Syntax rule | Type rule | Value rule |
| --------------------------- | ------------------------------------------------------------------------- | ------------------------- |
| integer_literal | $\dfrac{}{Γ ⊢ integer\ literal : i64}$ | Integer literal |
| integer_literal | $\dfrac{}{Γ ⊢ integer\ literal : Int}$ where $Int$ is inferred from context, defaulting to $i64$ | Polymorphic integer literal |
| boolean_literal | $\dfrac{}{Γ ⊢ boolean\ literal : bool}$ | Boolean literal |
| identifier | $\dfrac{ident : T ∈ Γ}{Γ ⊢ ident : T}$ | Refers to `let` in scope |
| (expression) | $\dfrac{Γ ⊢ e : T}{Γ ⊢ (e) : T}$ | Identity |
| !expression | $\dfrac{Γ ⊢ e : bool}{Γ ⊢\ !e : bool}$ | Boolean inverse |
| -expression | $\dfrac{Γ ⊢ e : i64}{Γ ⊢ -e : i64}$ | Integer negation |
| expression \* expression | $\dfrac{Γ ⊢ lhs : i64 ∧ Γ ⊢ rhs : i64}{Γ ⊢ lhs * rhs : i64}$ | Integer multiplication |
| expression / expression | $\dfrac{Γ ⊢ lhs : i64 ∧ Γ ⊢ rhs : i64}{Γ ⊢ lhs / rhs : i64}$ | Integer floored division |
| expression % expression | $\dfrac{Γ ⊢ lhs : i64 ∧ Γ ⊢ rhs : i64}{Γ ⊢ lhs\ \%\ rhs : i64}$ | Integer floored remainder |
| expression + expression | $\dfrac{Γ ⊢ lhs : i64 ∧ Γ ⊢ rhs : i64}{Γ ⊢ lhs + rhs : i64}$ | Integer addition |
| expression - expression | $\dfrac{Γ ⊢ lhs : i64 ∧ Γ ⊢ rhs : i64}{Γ ⊢ lhs - rhs : i64}$ | Integer subtraction |
| expression < expression | $\dfrac{Γ ⊢ lhs : i64 ∧ Γ ⊢ rhs : i64}{Γ ⊢ lhs < rhs : bool}$ | Integer less-than |
| -expression | $\dfrac{Γ ⊢ e : Int,\ Int\ signed}{Γ ⊢ -e : Int}$ | Signed integer negation |
| expression \* expression | $\dfrac{Γ ⊢ lhs : Int ∧ Γ ⊢ rhs : Int}{Γ ⊢ lhs * rhs : Int}$ | Integer multiplication |
| expression / expression | $\dfrac{Γ ⊢ lhs : Int ∧ Γ ⊢ rhs : Int}{Γ ⊢ lhs / rhs : Int}$ | Integer division |
| expression % expression | $\dfrac{Γ ⊢ lhs : Int ∧ Γ ⊢ rhs : Int}{Γ ⊢ lhs\ \%\ rhs : Int}$ | Integer remainder |
| expression + expression | $\dfrac{Γ ⊢ lhs : Int ∧ Γ ⊢ rhs : Int}{Γ ⊢ lhs + rhs : Int}$ | Integer addition |
| expression - expression | $\dfrac{Γ ⊢ lhs : Int ∧ Γ ⊢ rhs : Int}{Γ ⊢ lhs - rhs : Int}$ | Integer subtraction |
| expression < expression | $\dfrac{Γ ⊢ lhs : Int ∧ Γ ⊢ rhs : Int}{Γ ⊢ lhs < rhs : bool}$ | Integer less-than |
| | $\dfrac{Γ ⊢ lhs : bool ∧ Γ ⊢ rhs : bool}{Γ ⊢ lhs < rhs : bool}$ | See [truth tables] |
| expression &lt;= expression | $\dfrac{Γ ⊢ lhs : i64 ∧ Γ ⊢ rhs : i64}{Γ ⊢ lhs <= rhs : bool}$ | Integer less-or-equal |
| expression &lt;= expression | $\dfrac{Γ ⊢ lhs : Int ∧ Γ ⊢ rhs : Int}{Γ ⊢ lhs <= rhs : bool}$ | Integer less-or-equal |
| | $\dfrac{Γ ⊢ lhs : bool ∧ Γ ⊢ rhs : bool}{Γ ⊢ lhs <= rhs : bool}$ | See [truth tables] |
| expression > expression | $\dfrac{Γ ⊢ lhs : i64 ∧ Γ ⊢ rhs : i64}{Γ ⊢ lhs > rhs : bool}$ | Integer greater-than |
| expression > expression | $\dfrac{Γ ⊢ lhs : Int ∧ Γ ⊢ rhs : Int}{Γ ⊢ lhs > rhs : bool}$ | Integer greater-than |
| | $\dfrac{Γ ⊢ lhs : bool ∧ Γ ⊢ rhs : bool}{Γ ⊢ lhs > rhs : bool}$ | See [truth tables] |
| expression >= expression | $\dfrac{Γ ⊢ lhs : i64 ∧ Γ ⊢ rhs : i64}{Γ ⊢ lhs >= rhs : bool}$ | Integer greater-or-equal |
| expression >= expression | $\dfrac{Γ ⊢ lhs : Int ∧ Γ ⊢ rhs : Int}{Γ ⊢ lhs >= rhs : bool}$ | Integer greater-or-equal |
| | $\dfrac{Γ ⊢ lhs : bool ∧ Γ ⊢ rhs : bool}{Γ ⊢ lhs >= rhs : bool}$ | See [truth tables] |
| expression == expression | $\dfrac{Γ ⊢ lhs : i64 ∧ Γ ⊢ rhs : i64}{Γ ⊢ lhs == rhs : bool}$ | Integer equality |
| expression == expression | $\dfrac{Γ ⊢ lhs : Int ∧ Γ ⊢ rhs : Int}{Γ ⊢ lhs == rhs : bool}$ | Integer equality |
| | $\dfrac{Γ ⊢ lhs : bool ∧ Γ ⊢ rhs : bool}{Γ ⊢ lhs == rhs : bool}$ | See [truth tables] |
| expression != expression | $\dfrac{Γ ⊢ lhs : i64 ∧ Γ ⊢ rhs : i64}{Γ ⊢ lhs \text{ != } rhs : bool}$ | Integer nonequality |
| expression != expression | $\dfrac{Γ ⊢ lhs : Int ∧ Γ ⊢ rhs : Int}{Γ ⊢ lhs \text{ != } rhs : bool}$ | Integer nonequality |
| | $\dfrac{Γ ⊢ lhs : bool ∧ Γ ⊢ rhs : bool}{Γ ⊢ lhs \text{ != } rhs : bool}$ | See [truth tables] |

In the rules above, $Int$ stands for any single integer type from `{i8, i16, i32, i64, u8, u16, u32, u64}`. Both operands of a binary operator must have the **same** integer type.
| expression && expression | $\dfrac{Γ ⊢ lhs : bool ∧ Γ ⊢ rhs : bool}{Γ ⊢ lhs\ \&\&\ rhs : bool}$ | Short-circuiting AND |
| expression \|\| expression | $\dfrac{Γ ⊢ lhs : bool ∧ Γ ⊢ rhs : bool}{Γ ⊢ lhs\ \|\|\ rhs : bool}$ | Short-circuiting OR |
| f(e₁, ..., eₙ) | $\dfrac{f : (T_1, ..., T_n) → R ∧ Γ ⊢ e_i : T_i}{Γ ⊢ f(e_1, ..., e_n) : R}$ | Function call |
Expand All @@ -436,7 +452,7 @@ Calling an effectful or runtime function without the appropriate keyword is a ty

### Overflow and underflow

Integer overflow and underflow wraps.
Integer overflow and underflow is checked at runtime and causes a trap (runtime error).

### Floored division and remainder

Expand Down Expand Up @@ -472,7 +488,7 @@ The remainder always has the sign of the right-hand side.
- Blocks introduce a new child scope for `let` statements.
- `let` statements add a new variable binding to the current scope and give it
an initial value based on its expression.
- Variables are integers.
- Variables may be integers (`i8`, `i16`, `i32`, `i64`, `u8`, `u16`, `u32`, `u64`), booleans, structs, or enums.
- Assignment statements look up a variable in the stack of scopes and change its current value to the result of evaluating the right-hand side.

# Not Yet Implemented
Expand Down
2 changes: 1 addition & 1 deletion starstream-compiler/src/docs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ pub enum TypeKind {
impl From<&Type> for TypeRef {
fn from(ty: &Type) -> Self {
match ty {
Type::Int => TypeRef::Primitive("i64".to_string()),
Type::Int(w) => TypeRef::Primitive(w.display_name().to_string()),
Type::Bool => TypeRef::Primitive("bool".to_string()),
Type::Unit => TypeRef::Primitive("()".to_string()),
Type::Var(id) => TypeRef::Primitive(id.as_str()),
Expand Down
2 changes: 1 addition & 1 deletion starstream-compiler/src/parser/primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub fn identifier<'a>() -> impl Parser<'a, &'a str, Identifier, Extra<'a>> {
pub fn integer_literal<'a>() -> impl Parser<'a, &'a str, Literal, Extra<'a>> + Clone {
text::int(10)
.map(|digits: &str| {
let value = digits.parse::<i64>().expect("integer literal");
let value = digits.parse::<i128>().expect("integer literal");
Literal::Integer(value)
})
.boxed()
Expand Down
6 changes: 3 additions & 3 deletions starstream-compiler/src/typecheck/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ impl BuiltinRegistry {
"blockHeight".to_string(),
BuiltinFunction {
params: vec![],
return_type: Type::Int,
return_type: Type::int(),
effect: EffectKind::Runtime,
},
);
Expand All @@ -105,7 +105,7 @@ impl BuiltinRegistry {
"currentSlot".to_string(),
BuiltinFunction {
params: vec![],
return_type: Type::Int,
return_type: Type::int(),
effect: EffectKind::Runtime,
},
);
Expand All @@ -129,7 +129,7 @@ mod tests {
.expect("blockHeight should exist");

assert_eq!(func.params, vec![]);
assert_eq!(func.return_type, Type::Int);
assert_eq!(func.return_type, Type::int());
assert_eq!(func.effect, EffectKind::Runtime);
}

Expand Down
2 changes: 1 addition & 1 deletion starstream-compiler/src/typecheck/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ fn free_type_vars_type(ty: &Type, out: &mut HashSet<TypeVarId>) {
}
}
}
Type::Int | Type::Bool | Type::Unit => {}
Type::Int(_) | Type::Bool | Type::Unit => {}
}
}

Expand Down
15 changes: 14 additions & 1 deletion starstream-compiler/src/typecheck/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,11 @@ pub enum TypeErrorKind {
},
/// A function was declared with a return type in a position where it shouldn't have one.
ReturnTypeNotAllowed,
/// An integer literal is out of range for its resolved type.
LiteralOutOfRange {
value: i128,
ty: Type,
},
}

impl TypeErrorKind {
Expand Down Expand Up @@ -361,7 +366,8 @@ impl TypeErrorKind {
TypeErrorKind::RuntimeRequiresRuntime => "E0040",
TypeErrorKind::RuntimeWithoutKeyword { .. } => "E0041",
TypeErrorKind::WrongGenericArity { .. } => "E0042",
TypeErrorKind::ReturnTypeNotAllowed { .. } => "E0043",
TypeErrorKind::ReturnTypeNotAllowed => "E0043",
TypeErrorKind::LiteralOutOfRange { .. } => "E0044",
}
}
}
Expand Down Expand Up @@ -686,6 +692,13 @@ impl fmt::Display for TypeErrorKind {
TypeErrorKind::ReturnTypeNotAllowed => {
write!(f, "return type not allowed on this function")
}
TypeErrorKind::LiteralOutOfRange { value, ty } => {
write!(
f,
"integer literal `{value}` does not fit in type `{}`",
ty.to_compact_string()
)
}
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions starstream-compiler/src/typecheck/exhaustiveness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pub enum SimplePat {
/// Simplified literal for exhaustiveness checking
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum SimpleLiteral {
Int(i64),
Int(i128),
Bool(bool),
Unit,
}
Expand Down Expand Up @@ -79,7 +79,7 @@ impl CtorSet {
// For primitive types (Int, Unit) and type variables, there are no constructors.
// A wildcard pattern always covers these types completely.
// We represent this as an empty constructor set.
Type::Int | Type::Unit | Type::Var(_) => Some(Self::infinite()),
Type::Int(_) | Type::Unit | Type::Var(_) => Some(Self::infinite()),
Type::Function { .. } | Type::Tuple(_) => Some(Self::infinite()),
}
}
Expand Down Expand Up @@ -834,7 +834,7 @@ mod tests {
if arity == 0 {
EnumVariantType::unit(vname)
} else {
EnumVariantType::tuple(vname, vec![Type::Int; arity])
EnumVariantType::tuple(vname, vec![Type::int(); arity])
}
})
.collect(),
Expand Down
Loading