From 8e4b2ebca478b78edddfb6b7ea59d526b22a2889 Mon Sep 17 00:00:00 2001 From: bialger Date: Fri, 3 Oct 2025 07:40:29 +0300 Subject: [PATCH 01/17] docs: enhance README.md and reference documentation for types and nullability Revise the README.md to clarify the distinction between fundamental types and primitive reference types, emphasizing their characteristics and usage. Update the nullable types section to specify that fundamental types cannot be made nullable and improve examples for null handling. Adjust related documentation across various reference files to ensure consistency in terminology and enhance overall clarity for users. --- README.md | 80 +++++---- docs/reference/code_examples.md | 118 +++++++------ docs/reference/expressions_and_operators.md | 10 +- docs/reference/lexical_structure.md | 15 +- docs/reference/nullable.md | 56 +++---- docs/reference/syntax.md | 6 +- docs/reference/types.md | 173 +++++++++++++++----- 7 files changed, 283 insertions(+), 175 deletions(-) diff --git a/README.md b/README.md index 7521dbe..fa41408 100644 --- a/README.md +++ b/README.md @@ -30,12 +30,15 @@ Ovum is a strongly statically typed, single-threaded language focused on safety, --- -## [Types and Nullability](docs/reference/types.md) +## [Types](docs/reference/types.md) and [Nullability](docs/reference/nullable.md) -- Nullable types: append `?` (e.g., `Int?`). -- Safe call `?.`, Elvis `?:`, non‑null `!!`. -- Type test `is`, cast `as` (downcast yields nullable type). -- Explicit cast to `Bool` allowed for any value. +- **Fundamental types**: `int`, `float`, `byte`, `char`, `bool`, `pointer` (value types, not nullable, not Objects) +- **Primitive reference types**: `Int`, `Float`, `Byte`, `Char`, `Bool`, `Pointer` (reference wrappers, nullable, Objects) +- **Implicit conversion**: Literals convert to primitives (`val count: Int = 0`) +- **Nullable types**: Append `?` to reference types only (e.g., `Int?`, `String?`) +- **Safe call `?.`**, **Elvis `?:`** for null handling +- **Type test `is`**, **cast `as`** (downcast yields nullable type) +- **Copy assignment `:=`** for deep copying reference types --- @@ -51,9 +54,9 @@ Ovum is a strongly statically typed, single-threaded language focused on safety, - Arithmetic: `+ - * / %` - Comparison: `== != < <= > >=` - Boolean: `&& || ! xor` (short‑circuit `&&`/`||`). -- Assignment: `=` +- Assignment: `=` (reference assignment), `:=` (copy assignment) - Member/calls: `. ()` and safe `?.` -- Null handling: `?. ?: !!` +- Null handling: `?. ?:` - Type ops: `as`, `is` - Namespace: `::` @@ -77,7 +80,7 @@ Ovum is a strongly statically typed, single-threaded language focused on safety, - Pipeline: `.ovum` → bytecode → Ovum VM. - GC for memory safety; JIT compiles hot paths. - Single‑threaded execution model. -- Architectures: amd64, arm64. Numeric widths: `Int` 8 bytes, `Float` 8 bytes. +- Architectures: amd64, arm64. Numeric widths: `int` 8 bytes, `float` 8 bytes. - Entry point: `Main(args: StringArray): Int`. Build & Run (conceptual): write `.ovum`, compile (parse, type‑check, enforce const/pure), run on VM (JIT + GC). @@ -98,7 +101,7 @@ Build & Run (conceptual): write `.ovum`, compile (parse, type‑check, enforce c Only inside `unsafe { ... }`: - Global `var` and `static var` writes. -- Const/mutable casts; `Pointer`, address‑of, dereference. +- Const/mutable casts; `pointer`, address‑of, dereference. - Manual destructor calls. - `sys::Interope`; casting any value to (const or mutable) `ByteArray`. @@ -111,35 +114,40 @@ Only inside `unsafe { ... }`: ```ovum // .ovum file fun Main(args: StringArray): Int { - val count: Int = args.Length() + val count: Int = args.Length() // Built-in returns Int sys::Print("Args count: " + count.ToString()) - return 0 + return 0 // Implicit conversion from literal } ``` ### Pure functions with caching ```ovum -pure fun Fib(n: Int): Int { +pure fun Fib(n: int): int { if (n <= 1) return n - return Fib(n - 1) + Fib(n - 2) + val fib1: int = Fib(n - 1) + val fib2: int = Fib(n - 2) + return fib1 + fib2 } ``` -### `is`, `as`, `!!` and ByteArray casts +### `is`, `as` and ByteArray casts ```ovum fun DemoCasts(obj: Object): Void { if (obj is Point) { - val p: Point = (obj as Point)!! // nullable cast + assert - sys::Print(p.ToString()) + val p: Point? = obj as Point + if (p != null) { + val nonNullP: Point = p ?: Point(0, 0) // Use Elvis operator + sys::Print(nonNullP.ToString()) + } } // Bool cast - val b1: Bool = (0 as Bool) // false - val b2: Bool = (42 as Bool) // true - val b3: Bool = (obj as Bool) // always true - val b4: Bool = ((obj as Point) as Bool) // true if obj is a Point + val b1: Bool = 0 as bool // false + val b2: Bool = 42 as bool // true + val b3: Bool = obj as bool // always true + val b4: Bool = (obj as Point) as bool // true if obj is a Point // Unsafe: raw byte views unsafe { @@ -170,11 +178,14 @@ class DefinedFunctional { } val AddNullable: CustomFunctional = pure fun(a: Int?, b: Int?): Int { - return (a ?: 0) + (b ?: 0) + val aVal: int = a ?: 0 // Implicit conversion from Int? to int + val bVal: int = b ?: 0 + return Int(aVal + bVal) // Implicit conversion from int to Int } fun Main(args: StringArray): Int { - return AddNullable(2, DefinedFunctional(-1)(2)) + // Constructor call then functional call via `call` + return AddNullable(2, DefinedFunctional(-1)(2)) // Implicit conversion from literals } ``` @@ -198,29 +209,30 @@ class Multiplier implements ICalculator { } } -pure fun ProcessNumbers(calc: ICalculator, numbers: IntArray): Int { - var result: Int = 0 +pure fun ProcessNumbers(calc: ICalculator, numbers: IntArray): int { + var result: int = 0 for (num in numbers) { - result = result + calc.Calculate(num, 2) + val calcResult: Int = calc.Calculate(num, 2) // Implicit conversion from literal + result = result + calcResult // Implicit conversion } return result } -fun Main(args: StringArray): Int { +fun Main(args: StringArray): int { val numbers: IntArray = IntArray(3) - numbers[0] = 5 - numbers[1] = 10 - numbers[2] = 15 + numbers[0] := 5 // Implicit conversion from literal + numbers[1] := 10 + numbers[2] := 15 val adder: ICalculator = Adder() val multiplier: ICalculator = Multiplier() - val sumResult: Int = ProcessNumbers(adder, numbers) - val productResult: Int = ProcessNumbers(multiplier, numbers) + val sumResult: int = ProcessNumbers(adder, numbers) + val productResult: int = ProcessNumbers(multiplier, numbers) - sys::Print("Sum result: " + sumResult.ToString()) - sys::Print("Product result: " + productResult.ToString()) + sys::Print("Sum result: " + Int(sumResult).ToString()) + sys::Print("Product result: " + Int(productResult).ToString()) - return 0 + return 0 // Implicit conversion from literal } ``` diff --git a/docs/reference/code_examples.md b/docs/reference/code_examples.md index 711160e..2400276 100644 --- a/docs/reference/code_examples.md +++ b/docs/reference/code_examples.md @@ -7,7 +7,7 @@ Here are some code examples to help you get started with Ovum. ```ovum // .ovum file fun Main(args: StringArray): Int { - val count: Int = args.Length() + val count: Int = args.Length() // Built-in returns Int sys::Print("Args count: " + count.ToString()) return 0 } @@ -20,14 +20,14 @@ fun DemoNulls(): Void { val a: Int? = null val b: Int? = 5 - val sum: Int = (a ?: 0) + (b ?: 0) // Elvis - sys::Print("Sum = " + sum.ToString()) + val aVal: int = a ?: 0 + val bVal: int = b ?: 0 + val sum: int = aVal + bVal + sys::Print("Sum = " + Int(sum).ToString()) val name: String? = null - sys::Print("Name length = " + (name?.Length() ?: 0).ToString()) - - val mustNotBeNull: Int = (b!!) // ok - // val crash: Int = (a!!) // aborts (unhandleable) + val length: int = (name?.Length() ?: 0) as int // Built-in returns Int + sys::Print("Name length = " + Int(length).ToString()) } ``` @@ -67,24 +67,29 @@ interface IComparable { fun IsLess(other: Object): Bool } interface IHashable { fun GetHash(): Int } class Point implements IStringConvertible, IComparable, IHashable { - public val X: Int - public val Y: Int + public val X: int + public val Y: int - public fun Point(x: Int, y: Int): Point { this.X = x; this.Y = y; return this; } + public fun Point(x: int, y: int): Point { this.X = x; this.Y = y; return this; } public override fun ToString(): String { - return "(" + X.ToString() + ", " + Y.ToString() + ")" + return "(" + Int(X).ToString() + ", " + Int(Y).ToString() + ")" } public override fun IsLess(other: Object): Bool { if (!(other is Point)) return false - val p: Point = (other as Point)!! // safe after is + !! - if (this.X != p.X) return this.X < p.X - return this.Y < p.Y + val p: Point? = other as Point + if (p != null) { + val nonNullP: Point = p ?: Point(0, 0) // Use Elvis operator + if (this.X != nonNullP.X) return this.X < nonNullP.X + return this.Y < nonNullP.Y + } + return false } public override fun GetHash(): Int { - return (X * 1315423911) ^ (Y * 2654435761) + val hash: int = (X * 1315423911) ^ (Y * 2654435761) + return Int(hash) } } ``` @@ -92,27 +97,32 @@ class Point implements IStringConvertible, IComparable, IHashable { ## 5) Pure Functions with Caching ```ovum -pure fun Fib(n: Int): Int { +pure fun Fib(n: int): int { if (n <= 1) return n - return Fib(n - 1) + Fib(n - 2) + val fib1: int = Fib(n - 1) + val fib2: int = Fib(n - 2) + return fib1 + fib2 } // For user-defined reference types as parameters, implement IComparable. ``` -## 6) `is`, `as`, `!!`, and ByteArray Casts +## 6) `is`, `as`, and ByteArray Casts ```ovum fun DemoCasts(obj: Object): Void { if (obj is Point) { - val p: Point = (obj as Point)!! // nullable cast + assert - sys::Print(p.ToString()) + val p: Point? = obj as Point + if (p != null) { + val nonNullP: Point = p ?: Point(0, 0) // Use Elvis operator + sys::Print(nonNullP.ToString()) + } } - // Bool cast - val b1: Bool = (0 as Bool) // false - val b2: Bool = (42 as Bool) // true - val b3: Bool = (obj as Bool) // always true - val b4: Bool = ((obj as Point) as Bool) // true if obj is a Point + // bool cast + val b1: Bool = 0 as bool // false + val b2: Bool = 42 as bool // true + val b3: Bool = obj as bool // always true + val b4: Bool = (obj as Point) as bool // true if obj is a Point // Unsafe: raw byte views unsafe { @@ -155,7 +165,7 @@ fun Main(args: StringArray): Int { ```ovum fun DemoControlFlow(): Void { - var i: Int = 0 + var i: int = 0 // While loop with break and continue while (i < 10) { @@ -166,15 +176,15 @@ fun DemoControlFlow(): Void { if (i == 7) { break // Exit loop } - sys::Print("i = " + i.ToString()) + sys::Print("i = " + Int(i).ToString()) i = i + 1 } // For loop over array val numbers: IntArray = IntArray(3) - numbers[0] = 10 - numbers[1] = 20 - numbers[2] = 30 + numbers[0] := 10 + numbers[1] := 20 + numbers[2] := 30 for (num in numbers) { sys::Print("Number: " + num.ToString()) @@ -189,13 +199,13 @@ fun DemoUnsafeOperations(): Void { // Unsafe block for low-level operations unsafe { // Global mutable state (unsafe) - static var globalCounter: Int = 0 + static var globalCounter: int = 0 globalCounter = globalCounter + 1 // Pointer operations (unsafe) val obj: Point = Point(10, 20) - val ptr: Pointer = &obj // address-of - val deref: Object = *ptr // dereference to Object, Pointer is not typed + val ptr: pointer = &obj // address-of + val deref: Object = *ptr // dereference to Object, pointer is not typed // ByteArray casting (unsafe) val bytes: ByteArray = (obj as ByteArray) @@ -203,8 +213,8 @@ fun DemoUnsafeOperations(): Void { // Foreign function interface (unsafe) val input: ByteArray = "Hello".ToUtf8Bytes() - val output: ByteArray = ByteArray(4) - val result: Int = sys::Interope("libc.so", "strlen", input, output) + val output: ByteArray = ByteArray(8) + val result: int = sys::Interope("libc.so", "strlen", input, output) } } ``` @@ -230,8 +240,11 @@ class User { fun ProcessUsers(users: UserList): Void { for (i in 0..users.Length()) { - val user: User = (users[i] as User)!! - sys::Print("User " + user.Id.ToString() + ": " + user.Name) + val user: User? = users[i] as User + if (user != null) { + val nonNullUser: User = user ?: User(0, "Unknown") // Use Elvis operator + sys::Print("User " + nonNullUser.Id.ToString() + ": " + nonNullUser.Name) + } } } ``` @@ -241,10 +254,10 @@ fun ProcessUsers(users: UserList): Void { ```ovum class DatabaseConnection { - private val ConnectionId: Int - private val IsConnected: Bool + private val ConnectionId: int + private val IsConnected: bool - public fun DatabaseConnection(id: Int): DatabaseConnection { + public fun DatabaseConnection(id: int): DatabaseConnection { this.ConnectionId = id this.IsConnected = true // Establish database connection @@ -261,7 +274,7 @@ class DatabaseConnection { public destructor(): Void { if (IsConnected) { // Close database connection - sys::Print("Closing connection " + ConnectionId.ToString()) + sys::Print("Closing connection " + Int(ConnectionId).ToString()) } } } @@ -296,28 +309,29 @@ class Multiplier implements ICalculator { } } -pure fun ProcessNumbers(calc: ICalculator, numbers: IntArray): Int { - var result: Int = 0 +pure fun ProcessNumbers(calc: ICalculator, numbers: IntArray): int { + var result: int = 0 for (num in numbers) { - result = result + calc.Calculate(num, 2) + val calcResult: Int = calc.Calculate(num, 2) + result = result + calcResult } return result } -fun Main(args: StringArray): Int { +fun Main(args: StringArray): int { val numbers: IntArray = IntArray(3) - numbers[0] = 5 - numbers[1] = 10 - numbers[2] = 15 + numbers[0] := 5 + numbers[1] := 10 + numbers[2] := 15 val adder: ICalculator = Adder() val multiplier: ICalculator = Multiplier() - val sumResult: Int = ProcessNumbers(adder, numbers) - val productResult: Int = ProcessNumbers(multiplier, numbers) + val sumResult: int = ProcessNumbers(adder, numbers) + val productResult: int = ProcessNumbers(multiplier, numbers) - sys::Print("Sum result: " + sumResult.ToString()) - sys::Print("Product result: " + productResult.ToString()) + sys::Print("Sum result: " + Int(sumResult).ToString()) + sys::Print("Product result: " + Int(productResult).ToString()) return 0 } diff --git a/docs/reference/expressions_and_operators.md b/docs/reference/expressions_and_operators.md index 1c8043c..e56a42f 100644 --- a/docs/reference/expressions_and_operators.md +++ b/docs/reference/expressions_and_operators.md @@ -21,11 +21,12 @@ Expressions in Ovum include literal values, variable references, function calls, * `&&` (logical AND) - short-circuit evaluation * `||` (logical OR) - short-circuit evaluation * `!` (negation) - unary operator -* `xor` (exclusive OR) - infix operator on `Bool` +* `xor` (exclusive OR) - infix operator on `bool` -## Assignment Operator +## Assignment Operators -* `=` (assignment) - assigns a value to a mutable variable or field. The left-hand side must be a mutable variable or field. +* `=` (reference assignment) - assigns a reference to a mutable variable or field. The left-hand side must be a mutable variable or field. +* `:=` (copy assignment) - performs deep copy for reference types. Creates a new object with the same content as the source. ## Member Access @@ -37,13 +38,12 @@ Expressions in Ovum include literal values, variable references, function calls, ## Type Operations * `expr as Type` - explicit cast (downcast yields nullable type) -* `expr is Type` - type test (returns `Bool`) +* `expr is Type` - type test (returns `bool`) ## Null Handling * `expr?.member` - safe call (calls only if expr is not null) * `expr ?: default` - Elvis operator (returns expr if not null, otherwise default) -* `expr!!` - non-null assertion (throws error if expr is null) ## Namespace Resolution diff --git a/docs/reference/lexical_structure.md b/docs/reference/lexical_structure.md index a8c79bf..c207aee 100644 --- a/docs/reference/lexical_structure.md +++ b/docs/reference/lexical_structure.md @@ -24,8 +24,8 @@ Ovum reserves certain words like `fun`, `class`, `interface`, `var`, `override`, * **Arithmetic**: `+`, `-`, `*`, `/`, `%` * **Comparison**: `==`, `!=`, `<`, `<=`, `>`, `>=` * **Boolean logic**: `&&` (logical AND), `||` (logical OR), `!` (negation), `xor` (exclusive OR) -* **Assignment**: `=` -* **Null handling**: `?.` (safe call), `?:` (Elvis), `!!` (non-null assertion) +* **Assignment**: `=` (reference assignment), `:=` (copy assignment) +* **Null handling**: `?.` (safe call), `?:` (Elvis) * **Type operations**: `as` (cast), `is` (type test) * **Punctuation**: `,` (comma), `;` (semicolon), `:` (colon), `()` (parentheses), `{}` (braces), `[]` (brackets) * **Namespace resolution**: `::` @@ -87,14 +87,16 @@ TypeAliasDecl ::= "typealias" Identifier "=" Type ";" ; Type ::= NullableType | NonNullType ; NullableType ::= NonNullType "?" ; -NonNullType ::= PrimitiveType +NonNullType ::= FundamentalType + | PrimitiveRefType | "String" | "IntArray" | "FloatArray" | "BoolArray" | "CharArray" | "ByteArray" | "PointerArray" | "ObjectArray" | "StringArray" | Identifier ; // class/interface names (non-primitive) -PrimitiveType ::= "Int" | "Float" | "Bool" | "Char" | "Byte" | "Pointer" ; +FundamentalType ::= "int" | "float" | "bool" | "char" | "byte" | "pointer" ; +PrimitiveRefType ::= "Int" | "Float" | "Bool" | "Char" | "Byte" | "Pointer" ; Block ::= "{" { Statement } "}" ; Statement ::= VarDeclStmt | ExprStmt | ReturnStmt | IfStmt | WhileStmt | ForStmt | UnsafeStmt | Block ; @@ -108,7 +110,7 @@ ForStmt ::= "for" "(" Identifier "in" Expression ")" Statement ; UnsafeStmt ::= "unsafe" Block ; Expression ::= Assignment ; -Assignment ::= ElvisExpr [ "=" Assignment ] ; +Assignment ::= ElvisExpr [ ("=" | ":=") Assignment ] ; ElvisExpr ::= OrExpr [ "?:" ElvisExpr ] ; // right-assoc @@ -129,8 +131,7 @@ PostfixOp ::= "." Identifier | "." Identifier "(" [ ArgList ] ")" | "(" [ ArgList ] ")" // function call or callable object call | "as" Type // explicit cast; downcast yields nullable type - | "is" Type // type test → Bool - | "!!" // non-null assertion + | "is" Type // type test → bool | "?." Identifier [ "(" [ ArgList ] ")" ] // safe call chain | "?." "(" [ ArgList ] ")" // safe callable object call ; diff --git a/docs/reference/nullable.md b/docs/reference/nullable.md index da03e80..248ea24 100644 --- a/docs/reference/nullable.md +++ b/docs/reference/nullable.md @@ -4,23 +4,24 @@ This document describes how nullable types work in Ovum, including their restric ## Creating Nullable Types -Append `?` to make a type **nullable**: `Int?`, `String?`, `Point?`. Nullable types are passed by reference and can hold either a value or `null`. +Append `?` to make a **reference type** nullable: `Int?`, `String?`, `Point?`. **Fundamental types** (`int`, `float`, `bool`, `char`, `byte`, `pointer`) cannot be made nullable. ```ovum val nullableInt: Int? = null val nullableString: String? = "Hello" val nullablePoint: Point? = Point(10, 20) + +// val invalidNullable: int? = null // ERROR: Fundamental types cannot be nullable ``` ## Method Call Restrictions -**Important**: You cannot directly call methods on nullable types using `.` - you must use the safe call operator `?.` or non-null assertion `!!`. +**Important**: You cannot directly call methods on nullable types using `.` - you must use the safe call operator `?.`. ```ovum val nullableString: String? = "Hello" // val length: Int = nullableString.Length() // ERROR: Cannot call method directly on nullable val safeLength: Int = nullableString?.Length() ?: 0 // Correct: Use safe call -val forcedLength: Int = nullableString!!.Length() // Correct: Use non-null assertion ``` ## Null Handling Operators @@ -47,32 +48,26 @@ val nullableString: String? = null val result: String = nullableString ?: "default" // Uses "default" if nullableString is null ``` -### Non-null Assertion (`!!`) - -`x!!` throws an unhandleable error if `x == null`. Use with caution - only when you're certain the value is not null. - -```ovum -val nullableInt: Int? = 42 -val mustExist: Int = nullableInt!! // Safe - nullableInt is not null - -// val crash: Int = (null as Int?)!! // ERROR: Will abort the program -``` ## Type Casting ### Cast to Bool -Any value can be explicitly cast to `Bool`: +Any value can be explicitly cast to `bool`: -* **Primitives**: zero → `false`, non-zero → `true` -* **Non-primitives**: `true` iff the reference is a valid (non-null, live) object +* **Fundamentals, primitive reference types**: zero → `false`, non-zero → `true` +* **Non-primitive reference types and nullable primitives**: `true` iff the reference is a valid (non-null, live) object ```ovum val nullableInt: Int? = null -val isNull: Bool = (nullableInt as Bool) // false (null is falsy) +val isNull: bool = (nullableInt as bool) // false (null is falsy) -val someInt: Int? = 42 -val isNotNull: Bool = (someInt as Bool) // true (non-null is truthy) +val someInt: Int? = 42 // Implicit conversion from literal +val isNotNull: bool = (someInt as bool) // true (non-null is truthy) + +// Converting nullable primitives to fundamentals +val nullablePrimitive: Int? = 42 // Implicit conversion from literal +val fundamentalValue: int = (nullablePrimitive as Int) as int // Two-step conversion ``` ## Chaining Operations @@ -85,7 +80,9 @@ val nameLength: Int = person?.Name?.Length() ?: 0 // Equivalent to: val nameLength: Int = if (person != null && person.Name != null) { - person.Name.Length() + val nonNullPerson: Person = person ?: Person("Unknown") // Use Elvis operator + val nonNullName: String = nonNullPerson.Name ?: "Unknown" // Use Elvis operator + nonNullName.Length() } else { 0 } @@ -97,21 +94,18 @@ All nullable types support the same operators but cannot directly call methods: ```ovum val nullableString: String? = "Hello" -val nullableInt: Int? = 42 +val nullableInt: Int? = 42 // Implicit conversion from literal // Safe operations -val safeLength: Int = nullableString?.Length() ?: 0 +val safeLength: int = (nullableString?.Length() ?: 0) as int // Built-in returns Int val safeToString: String = nullableInt?.ToString() ?: "null" - -// Unsafe operations (will crash if null) -val forcedLength: Int = nullableString!!.Length() -val forcedToString: String = nullableInt!!.ToString() ``` ## Best Practices -1. **Prefer safe calls** over non-null assertions when possible -2. **Use Elvis operator** to provide sensible defaults -3. **Avoid non-null assertions** unless you're certain the value exists -4. **Chain operations** for cleaner null handling code -5. **Consider using `if` statements** for complex null checks instead of deeply nested safe calls +1. **Always use safe calls** (`?.`) for nullable types +2. **Use Elvis operator** (`?:`) to provide sensible defaults +3. **Chain operations** for cleaner null handling code +4. **Consider using `if` statements** for complex null checks instead of deeply nested safe calls +5. **Use copy assignment** (`:=`) when you need independent copies of nullable objects +6. **Convert to fundamentals** when you need value semantics: `(nullablePrimitive as PrimitiveType) as fundamentalType` diff --git a/docs/reference/syntax.md b/docs/reference/syntax.md index f5a7611..d0a987e 100644 --- a/docs/reference/syntax.md +++ b/docs/reference/syntax.md @@ -74,11 +74,13 @@ class DefinedFunctional { } val AddNullable: CustomFunctional = pure fun(a: Int?, b: Int?): Int { - return (a ?: 0) + (b ?: 0) + val aVal: int = a ?: 0 // Conversion from Int? to int + val bVal: int = b ?: 0 + return aVal + bVal } fun Main(args: StringArray): Int { // Constructor call then functional call via `call` - return AddNullable(2, DefinedFunctional(-1)(2)) + return AddNullable(2, DefinedFunctional(-1)(2)) // Implicit conversion from literals } ``` diff --git a/docs/reference/types.md b/docs/reference/types.md index d23fe15..c9ce84b 100644 --- a/docs/reference/types.md +++ b/docs/reference/types.md @@ -2,34 +2,57 @@ Ovum has a rich type system with primitive types and user-defined types. The type system is static and does not permit implicit type coercions (an `Int` won't automatically become a `Float` without an explicit cast, for example). -## Primitive Types +## Fundamental Types + +Fundamental types are passed by value and represent the basic building blocks of the language: ### Numeric Types -* **`Int`** (8 bytes) - 64-bit signed integer +* **`int`** (8 bytes) - 64-bit signed integer * Literals: `42`, `-17`, `0x1A` (hex), `0b1010` (binary) -* **`Float`** (8 bytes) - 64-bit floating-point number (IEEE 754 double precision) +* **`float`** (8 bytes) - 64-bit floating-point number (IEEE 754 double precision) * Literals: `3.14`, `2.0e10`, `1.5E-3`, `.5`, `5.` * Special values: `Infinity`, `-Infinity`, `NaN` -* **`Byte`** (1 byte) - 8-bit unsigned integer +* **`byte`** (1 byte) - 8-bit unsigned integer * Literals: `255`, `0x00`, `0b11111111` ### Character and Boolean Types -* **`Char`** - single Unicode character (UTF-32) +* **`char`** - single Unicode character (UTF-32) * Literals: `'A'`, `'中'`, `'\n'`, `'\t'`, `'\0'` -* **`Bool`** - Boolean value (`true`, `false`) - * Any value can be explicitly cast to `Bool` +* **`bool`** - Boolean value (`true`, `false`) + * Any value can be explicitly cast to `bool` ### Low-Level Types -* **`Pointer`** - raw memory address *(only meaningful in `unsafe` code)* +* **`pointer`** - raw memory address *(only meaningful in `unsafe` code)* * Used for FFI and low-level memory operations -> **Nullable Primitives**: Any primitive type can be made nullable by appending `?` (e.g., `Int?`, `Float?`, `Bool?`). Nullable primitives are reference types. +> **Fundamental Type Constraints**: Fundamental types cannot be made nullable (`int?` is invalid). They are not `Object` types and cannot be stored in `ObjectArray` or cast to `Object`. To convert nullable primitives to fundamentals, cast to primitive first: `(nullableInt as Int) as int`. + +## Primitive Reference Types + +Primitive reference types are built-in reference wrappers around fundamental types, passed by reference: + +### Numeric Reference Types + +* **`Int`** - reference wrapper for `int` values +* **`Float`** - reference wrapper for `float` values +* **`Byte`** - reference wrapper for `byte` values + +### Character and Boolean Reference Types + +* **`Char`** - reference wrapper for `char` values +* **`Bool`** - reference wrapper for `bool` values + +### Low-Level Reference Types + +* **`Pointer`** - reference wrapper for `pointer` values *(only meaningful in `unsafe` code)* + +> **Nullable Primitives**: Any primitive reference type can be made nullable by appending `?` (e.g., `Int?`, `Float?`, `Bool?`). ## Reference Types @@ -48,12 +71,12 @@ Ovum has a rich type system with primitive types and user-defined types. The typ Ovum provides specialized array classes for different element types (no generics/templates): **Primitive Arrays:** -* `IntArray` - array of `Int` values -* `FloatArray` - array of `Float` values -* `BoolArray` - array of `Bool` values -* `CharArray` - array of `Char` values -* `ByteArray` - array of `Byte` values -* `PointerArray` - array of `Pointer` values +* `IntArray` - array of `Int` reference wrappers +* `FloatArray` - array of `Float` reference wrappers +* `BoolArray` - array of `Bool` reference wrappers +* `CharArray` - array of `Char` reference wrappers +* `ByteArray` - array of `Byte` reference wrappers +* `PointerArray` - array of `Pointer` reference wrappers **Object Arrays:** * `ObjectArray` - array of any `Object`-derived types @@ -61,7 +84,7 @@ Ovum provides specialized array classes for different element types (no generics **Array Creation:** ```ovum -val numbers: IntArray = IntArray(10) // Create array of size 10 +val numbers: IntArray = IntArray(10) // Create array of Int reference wrappers val names: StringArray = StringArray(5) // Create string array of size 5 val objects: ObjectArray = ObjectArray(3) // Create object array of size 3 ``` @@ -80,6 +103,26 @@ fun ProcessUser(id: UserId, name: UserName): Void { ``` +## Assignment Operators + +### Reference Assignment (`=`) + +The standard assignment operator assigns references for reference types: + +```ovum +val original: String = "Hello" +val reference: String = original // Both variables point to the same string +``` + +### Copy Assignment (`:=`) + +The copy assignment operator performs deep copy for reference types: + +```ovum +val original: String = "Hello" +val copy: String := original // Creates a new string with the same content +``` + ## Type Casting ### Explicit Casting @@ -87,30 +130,49 @@ fun ProcessUser(id: UserId, name: UserName): Void { Use the `as` operator for explicit casting: ```ovum -val intValue: Int = 42 -val floatValue: Float = (intValue as Float) // Int to Float -val stringValue: String = (intValue as String) // Int to String +val intValue: int = 42 +val floatValue: float = (intValue as float) // int to float -val floatNum: Float = 3.14 -val intNum: Int = (floatNum as Int) // Float to Int (truncates) +val floatNum: float = 3.14 +val intNum: int = (floatNum as int) // float to int (truncates) + +// Implicit bidirectional casting between fundamental and primitive reference types +val fundamentalInt: int = 42 +val primitiveInt: Int = fundamentalInt // Implicit: int -> Int +val backToFundamental: int = primitiveInt // Implicit: Int -> int + +// Implicit conversion from literals to primitive types +val count: Int = 0 // Implicit: int literal -> Int +val flag: Bool = true // Implicit: bool literal -> Bool +val pi: Float = 3.14 // Implicit: float literal -> Float + +// Arithmetic works seamlessly +val sum: Int = 10 + 20 // Int + Int = Int (implicit conversion from literals) +val result: int = sum + 5 // Int + int = int (implicit conversion) ``` ### Boolean Casting -Any value can be explicitly cast to `Bool`: +Any value can be explicitly cast to `bool`: ```ovum -val intVal: Int = 42 -val boolVal: Bool = (intVal as Bool) // true (non-zero) +val intVal: int = 42 +val boolVal: bool = (intVal as bool) // true (non-zero) -val zeroInt: Int = 0 -val falseBool: Bool = (zeroInt as Bool) // false (zero) +val zeroInt: int = 0 +val falseBool: bool = (zeroInt as bool) // false (zero) val nullString: String? = null -val nullBool: Bool = (nullString as Bool) // false (null) +val nullBool: bool = (nullString as bool) // false (null) + +// With primitive reference types (implicit conversion) +val primitiveInt: Int = 42 // Implicit conversion from literal +val primitiveBool: bool = primitiveInt // Implicit: Int -> bool +val boolRef: Bool = true // Implicit: bool literal -> Bool ``` -**Rules:** Primitives: zero → `false`, non-zero → `true`. References: `null` → `false`, non-null → `true` +**Rules:** Fundamentals and primitive reference types: zero → `false`, non-zero → `true`. +References: `null` → `false`, non-null → `true` ### Unsafe Casting @@ -126,17 +188,21 @@ unsafe { ## Passing Semantics -**Primitive types** are passed by value (copied): +**Fundamental types** (`int`, `float`, `byte`, `char`, `bool`, `pointer`) are passed by value (copied): ```ovum -fun ModifyInt(x: Int): Void { +fun ModifyInt(x: int): Void { x = x + 1 // Only modifies the local copy } ``` -**Reference types** are passed by reference: +**Primitive reference types** (`Int`, `Float`, `Byte`, `Char`, `Bool`, `Pointer`) and **all other reference types** (including `String`, arrays, and user-defined types) are passed by reference: ```ovum +fun ModifyIntRef(var x: Int): Void { + x = x + 1 // Implicit conversion: Int + int -> Int +} + fun ModifyArray(arr: IntArray): Void { - arr[0] = 999 // Modifies the original array + arr[0] := Int(999) // Use := for deep copy assignment } ``` @@ -156,19 +222,26 @@ fun CanReassign(var str: String): Void { **Static typing:** Every variable and expression has a type checked at compile time **No implicit conversions:** Explicit casting required between different types **Type safety:** Prevents many common errors -**Nullable types:** Any type can be made nullable by appending `?` +**Nullable types:** Any reference type (including primitive reference types) can be made nullable by appending `?`. Fundamental types cannot be nullable. ```ovum -val x: Int = 42 +val x: int = 42 val y: String = "Hello" -// val z: Int = x + y // ERROR: Cannot add Int and String +// val z: int = x + y // ERROR: Cannot add int and String + +val intVal: int = 42 +val floatVal: float = 3.14 +val result: float = (intVal as float) + floatVal // OK: Explicit conversion -val intVal: Int = 42 -val floatVal: Float = 3.14 -val result: Float = (intVal as Float) + floatVal // OK: Explicit conversion +// Using primitive reference types (implicit conversion) +val refInt: Int = 42 // Implicit conversion from literal +val refFloat: Float = 3.14 // Implicit conversion from literal +val sum: Int = refInt + refFloat // Implicit: Int + Float -> Int +val fundamentalSum: int = sum + 10 // Implicit: Int + int -> int -val nullableInt: Int? = null -val nullableString: String? = "Hello" +// Converting nullable primitives to fundamentals +val nullableInt: Int? = 42 // Implicit conversion from literal +val fundamentalFromNullable: int = (nullableInt ?: 0) as int // Two-step conversion ``` ## Pure Function Constraints @@ -189,11 +262,23 @@ Type information is preserved at runtime for reference types: ```ovum fun ProcessObject(obj: Object): Void { if (obj is String) { - val str: String = (obj as String)!! - sys::Print("String length: " + str.Length().ToString()) + val str: String? = obj as String + if (str != null) { + val nonNullStr: String = str ?: "default" // Use Elvis operator + sys::Print("String length: " + nonNullStr.Length().ToString()) + } } else if (obj is IntArray) { - val arr: IntArray = (obj as IntArray)!! - sys::Print("Array size: " + arr.Length().ToString()) + val arr: IntArray? = obj as IntArray + if (arr != null) { + val nonNullArr: IntArray = arr ?: IntArray(0) // Use Elvis operator + sys::Print("Array size: " + nonNullArr.Length().ToString()) + } + } else if (obj is Int) { + val intRef: Int? = obj as Int + if (intRef != null) { + val nonNullInt: Int = intRef ?: 0 // Use Elvis operator + sys::Print("Int value: " + nonNullInt.ToString()) // Implicit conversion to string + } } } ``` From 84534ece2f58cf31b61f690300672d0df22fbfc4 Mon Sep 17 00:00:00 2001 From: bialger Date: Fri, 3 Oct 2025 16:06:40 +0300 Subject: [PATCH 02/17] docs: clarify type system characteristics in types.md Update the documentation to specify that explicit casting is required between different types, with the exception of conversions between primitive reference types and their corresponding fundamentals. Adjust code examples to reflect this change, enhancing clarity and understanding of the type system. --- docs/reference/types.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reference/types.md b/docs/reference/types.md index c9ce84b..ed1bdc4 100644 --- a/docs/reference/types.md +++ b/docs/reference/types.md @@ -220,7 +220,7 @@ fun CanReassign(var str: String): Void { ## Type System Characteristics **Static typing:** Every variable and expression has a type checked at compile time -**No implicit conversions:** Explicit casting required between different types +**No implicit conversions except builtin wrappers:** Explicit casting required between different types, except for conversions between primitive reference types and their corresponding fundamentals. **Type safety:** Prevents many common errors **Nullable types:** Any reference type (including primitive reference types) can be made nullable by appending `?`. Fundamental types cannot be nullable. @@ -236,7 +236,7 @@ val result: float = (intVal as float) + floatVal // OK: Explicit conversion // Using primitive reference types (implicit conversion) val refInt: Int = 42 // Implicit conversion from literal val refFloat: Float = 3.14 // Implicit conversion from literal -val sum: Int = refInt + refFloat // Implicit: Int + Float -> Int +val sum: Int = refInt + (refFloat as Int) // Explicit conversion val fundamentalSum: int = sum + 10 // Implicit: Int + int -> int // Converting nullable primitives to fundamentals From ecd27eaa6e5e93add8af7f54a5e44f03072eec26 Mon Sep 17 00:00:00 2001 From: bialger Date: Fri, 3 Oct 2025 16:17:14 +0300 Subject: [PATCH 03/17] ci: add code style and quality checks to CI workflow Introduce new jobs for code style checking with clang-format and code quality checking with clang-tidy in the CI workflow. The style check identifies formatting issues in C++ source files and comments on them, while the quality check runs clang-tidy to detect errors and warnings, providing feedback on code quality. This enhancement aims to maintain code standards and improve overall code quality in the project. --- .github/workflows/ci_tests.yml | 151 +++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index b46f230..bdfafea 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -123,3 +123,154 @@ jobs: working-directory: ./cmake-build/tests run: | valgrind --leak-check=full --track-origins=yes --error-exitcode=1 ./ovum_tests + + style-check: + name: Code style check with clang-format + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install clang-format + run: | + sudo apt-get update && sudo apt-get -y install clang-format + + - name: Check code style + run: | + # Find all C++ source files + find . -name "*.cpp" -o -name "*.hpp" -o -name "*.c" -o -name "*.h" | \ + grep -v "./build/" | grep -v "./cmake-build" | grep -v "./_deps/" | \ + xargs clang-format --dry-run --Werror + + - name: Comment on style issues + if: failure() + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const { execSync } = require('child_process'); + + try { + // Get list of files that need formatting + const files = execSync('find . -name "*.cpp" -o -name "*.hpp" -o -name "*.c" -o -name "*.h" | grep -v "./build/" | grep -v "./cmake-build" | grep -v "./_deps/"', { encoding: 'utf8' }).trim().split('\n'); + + let comment = '## 🎨 Code Style Issues Found\n\n'; + comment += 'The following files have formatting issues:\n\n'; + + for (const file of files) { + try { + const result = execSync(`clang-format --dry-run --Werror "${file}" 2>&1`, { encoding: 'utf8' }); + } catch (error) { + comment += `- \`${file}\`: Formatting issues detected\n`; + } + } + + comment += '\nPlease run `clang-format -i ` to fix formatting issues.'; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: comment + }); + } catch (error) { + console.log('Could not create comment:', error.message); + } + + code-quality-check: + name: Code quality check with clang-tidy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install clang-tidy + run: | + sudo apt-get update && sudo apt-get -y install clang-tidy + + - name: Create CMake cache + run: | + cmake -S . -B cmake-build-tidy -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + + - name: Run clang-tidy + run: | + # Find all C++ source files + find . -name "*.cpp" -o -name "*.hpp" | \ + grep -v "./build/" | grep -v "./cmake-build" | grep -v "./_deps/" | \ + xargs clang-tidy -p cmake-build-tidy --warnings-as-errors=* --format-style=file || true + + - name: Count warnings and errors + id: count_issues + run: | + # Run clang-tidy and capture output + find . -name "*.cpp" -o -name "*.hpp" | \ + grep -v "./build/" | grep -v "./cmake-build" | grep -v "./_deps/" | \ + xargs clang-tidy -p cmake-build-tidy --format-style=file > tidy_output.txt 2>&1 || true + + # Count errors and warnings + errors=$(grep -c "error:" tidy_output.txt || echo "0") + warnings=$(grep -c "warning:" tidy_output.txt || echo "0") + + echo "errors=$errors" >> $GITHUB_OUTPUT + echo "warnings=$warnings" >> $GITHUB_OUTPUT + + # Fail if more than 3 warnings or any errors + if [ "$errors" -gt 0 ] || [ "$warnings" -gt 3 ]; then + echo "clang-tidy found $errors errors and $warnings warnings" + exit 1 + fi + + - name: Comment on quality issues + if: failure() + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + + try { + let comment = '## 🔍 Code Quality Issues Found\n\n'; + + if (fs.existsSync('tidy_output.txt')) { + const output = fs.readFileSync('tidy_output.txt', 'utf8'); + const lines = output.split('\n'); + + let currentFile = ''; + let hasIssues = false; + + for (const line of lines) { + if (line.includes('error:') || line.includes('warning:')) { + const parts = line.split(':'); + if (parts.length >= 4) { + const file = parts[0]; + const lineNum = parts[1]; + const message = parts.slice(3).join(':').trim(); + + if (file !== currentFile) { + if (hasIssues) comment += '\n'; + comment += `### \`${file}\`\n\n`; + currentFile = file; + hasIssues = true; + } + + const issueType = line.includes('error:') ? '❌ Error' : '⚠️ Warning'; + comment += `- **Line ${lineNum}**: ${issueType} - ${message}\n`; + } + } + } + + if (!hasIssues) { + comment += 'No specific issues found in the output.'; + } + } else { + comment += 'Could not read clang-tidy output.'; + } + + comment += '\n\nPlease review and fix the issues above.'; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: comment + }); + } catch (error) { + console.log('Could not create comment:', error.message); + } From f9efdd9906817cc723460d133e94df8ec33dda6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=91=D0=B8=D0=B3=D1=83=D0=BB=D0=BE=D0=B2?= <42319615+bialger@users.noreply.github.com> Date: Fri, 3 Oct 2025 16:24:52 +0300 Subject: [PATCH 04/17] Clarify implicit conversion rules --- docs/reference/types.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/reference/types.md b/docs/reference/types.md index ed1bdc4..12e346c 100644 --- a/docs/reference/types.md +++ b/docs/reference/types.md @@ -220,7 +220,7 @@ fun CanReassign(var str: String): Void { ## Type System Characteristics **Static typing:** Every variable and expression has a type checked at compile time -**No implicit conversions except builtin wrappers:** Explicit casting required between different types, except for conversions between primitive reference types and their corresponding fundamentals. +**Limited implicit conversions:** The compiler only performs implicit conversions between a primitive reference type and its matching fundamental (for example, `Int` ↔ `int`). Any conversion across different primitive families—such as `Int` to `Float` or `Float` to `int`—must use an explicit cast. **Type safety:** Prevents many common errors **Nullable types:** Any reference type (including primitive reference types) can be made nullable by appending `?`. Fundamental types cannot be nullable. @@ -233,11 +233,11 @@ val intVal: int = 42 val floatVal: float = 3.14 val result: float = (intVal as float) + floatVal // OK: Explicit conversion -// Using primitive reference types (implicit conversion) -val refInt: Int = 42 // Implicit conversion from literal -val refFloat: Float = 3.14 // Implicit conversion from literal -val sum: Int = refInt + (refFloat as Int) // Explicit conversion -val fundamentalSum: int = sum + 10 // Implicit: Int + int -> int +// Using primitive reference types (implicit conversion between wrappers and fundamentals) +val refInt: Int = 42 // Implicit conversion from literal to Int +val refFloat: Float = 3.14 // Implicit conversion from literal to Float +val sum: Int = refInt + (refFloat as Int) // Requires explicit narrowing +val fundamentalSum: int = sum + 10 // Implicit: Int -> int when assigning to a fundamental // Converting nullable primitives to fundamentals val nullableInt: Int? = 42 // Implicit conversion from literal From a5a5c90b4f6e36422f72542c53a3699085b683a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=91=D0=B8=D0=B3=D1=83=D0=BB=D0=BE=D0=B2?= <42319615+bialger@users.noreply.github.com> Date: Fri, 3 Oct 2025 16:38:43 +0300 Subject: [PATCH 05/17] Fix MinGW CI configuration --- .github/workflows/ci_tests.yml | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index bdfafea..ba87a7a 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -10,10 +10,16 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Install MinGW toolchain + shell: powershell + run: | + choco install mingw --yes --no-progress + echo "C:\tools\mingw64\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + - name: Create CMake cache run: | - cmake -S . -B cmake-build-release -DCMAKE_BUILD_TYPE=Release -G "Unix Makefiles" - cmake -S . -B cmake-build-debug -DCMAKE_BUILD_TYPE=Debug -G "Unix Makefiles" + cmake -S . -B cmake-build-release -DCMAKE_BUILD_TYPE=Release -G "MinGW Makefiles" + cmake -S . -B cmake-build-debug -DCMAKE_BUILD_TYPE=Debug -G "MinGW Makefiles" - name: Build main target shell: bash @@ -27,14 +33,15 @@ jobs: - name: Run program working-directory: .\cmake-build-release + shell: bash run: | - .\ovum.exe --help + ./ovum.exe --help - name: Run tests working-directory: .\cmake-build-debug + shell: bash run: | - echo "Currently unable to run tests on Windows Latest MinGW. See https://gitmemories.com/cristianadam/HelloWorld/issues/12 and https://github.com/microsoft/vscode-cmake-tools/issues/2451" - % .\ovum_tests.exe + ./ovum_tests.exe build-matrix: name: Tests and application run on ${{ matrix.config.name }} From 5ae6f8aa0786ee09b39371afc3e735be4d95bbb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=91=D0=B8=D0=B3=D1=83=D0=BB=D0=BE=D0=B2?= <42319615+bialger@users.noreply.github.com> Date: Fri, 3 Oct 2025 16:38:51 +0300 Subject: [PATCH 06/17] Improve clang tooling workflows --- .github/workflows/ci_tests.yml | 72 ++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index ba87a7a..f6f25b7 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -142,37 +142,58 @@ jobs: sudo apt-get update && sudo apt-get -y install clang-format - name: Check code style + shell: bash run: | - # Find all C++ source files - find . -name "*.cpp" -o -name "*.hpp" -o -name "*.c" -o -name "*.h" | \ - grep -v "./build/" | grep -v "./cmake-build" | grep -v "./_deps/" | \ - xargs clang-format --dry-run --Werror + mapfile -t files < <(git ls-files '*.c' '*.cpp' '*.h' '*.hpp') + + if [ "${#files[@]}" -eq 0 ]; then + echo "No C/C++ files to check." + exit 0 + fi + + clang-format --dry-run --Werror "${files[@]}" 2>format_output.txt || { + cat format_output.txt + exit 1 + } - name: Comment on style issues - if: failure() + if: failure() && github.event_name == 'pull_request' uses: actions/github-script@v7 with: script: | const fs = require('fs'); const { execSync } = require('child_process'); - + try { // Get list of files that need formatting - const files = execSync('find . -name "*.cpp" -o -name "*.hpp" -o -name "*.c" -o -name "*.h" | grep -v "./build/" | grep -v "./cmake-build" | grep -v "./_deps/"', { encoding: 'utf8' }).trim().split('\n'); - + const rawFiles = execSync('git ls-files "*.c" "*.cpp" "*.h" "*.hpp"', { encoding: 'utf8' }).trim(); + + if (!rawFiles) { + console.log('No files require formatting checks.'); + return; + } + + const files = rawFiles.split('\n'); + let comment = '## 🎨 Code Style Issues Found\n\n'; comment += 'The following files have formatting issues:\n\n'; - + let hasIssues = false; + for (const file of files) { try { const result = execSync(`clang-format --dry-run --Werror "${file}" 2>&1`, { encoding: 'utf8' }); } catch (error) { comment += `- \`${file}\`: Formatting issues detected\n`; + hasIssues = true; } } - - comment += '\nPlease run `clang-format -i ` to fix formatting issues.'; - + + if (!hasIssues) { + comment += 'No files with formatting issues were detected.'; + } else { + comment += '\nPlease run `clang-format -i ` to fix formatting issues.'; + } + github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, @@ -198,35 +219,38 @@ jobs: cmake -S . -B cmake-build-tidy -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=ON - name: Run clang-tidy + shell: bash run: | - # Find all C++ source files - find . -name "*.cpp" -o -name "*.hpp" | \ - grep -v "./build/" | grep -v "./cmake-build" | grep -v "./_deps/" | \ - xargs clang-tidy -p cmake-build-tidy --warnings-as-errors=* --format-style=file || true + mapfile -t files < <(git ls-files '*.c' '*.cpp') + + if [ "${#files[@]}" -eq 0 ]; then + echo "No C/C++ files to analyze." + touch tidy_output.txt + exit 0 + fi + + clang-tidy "${files[@]}" -p cmake-build-tidy --format-style=file > tidy_output.txt 2>&1 || true + touch tidy_output.txt - name: Count warnings and errors id: count_issues run: | - # Run clang-tidy and capture output - find . -name "*.cpp" -o -name "*.hpp" | \ - grep -v "./build/" | grep -v "./cmake-build" | grep -v "./_deps/" | \ - xargs clang-tidy -p cmake-build-tidy --format-style=file > tidy_output.txt 2>&1 || true - # Count errors and warnings errors=$(grep -c "error:" tidy_output.txt || echo "0") warnings=$(grep -c "warning:" tidy_output.txt || echo "0") - + echo "errors=$errors" >> $GITHUB_OUTPUT echo "warnings=$warnings" >> $GITHUB_OUTPUT - + # Fail if more than 3 warnings or any errors if [ "$errors" -gt 0 ] || [ "$warnings" -gt 3 ]; then echo "clang-tidy found $errors errors and $warnings warnings" + cat tidy_output.txt exit 1 fi - name: Comment on quality issues - if: failure() + if: failure() && github.event_name == 'pull_request' uses: actions/github-script@v7 with: script: | From a9ea7c83377c8060863089dccb84069cc2d96d3d Mon Sep 17 00:00:00 2001 From: bialger Date: Fri, 3 Oct 2025 16:52:57 +0300 Subject: [PATCH 07/17] refactor: clean up code formatting and organization across multiple files - Adjusted formatting in MyClass.cpp and MyClass.hpp for consistency. - Updated include order in ui_functions.cpp and ui_functions.hpp for clarity. - Reorganized includes in test_functions.hpp and unit_tests.cpp to maintain structure. - Improved comment formatting in ProjectIntegrationTestSuite.hpp. - Disabled test execution in Windows MinGW CI due to compatibility issues. --- .github/workflows/ci_tests.yml | 3 ++- lib/mylib/MyClass.cpp | 3 ++- lib/mylib/MyClass.hpp | 6 +++--- lib/ui/ui_functions.cpp | 2 +- lib/ui/ui_functions.hpp | 6 +++--- tests/test_functions.hpp | 4 ++-- tests/test_suites/ProjectIntegrationTestSuite.hpp | 2 +- tests/unit_tests.cpp | 4 ++-- 8 files changed, 16 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index f6f25b7..e9e1ad3 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -41,7 +41,8 @@ jobs: working-directory: .\cmake-build-debug shell: bash run: | - ./ovum_tests.exe + # ./ovum_tests.exe + echo "Tests are not run on Windows MinGW due to issues with the test runner" build-matrix: name: Tests and application run on ${{ matrix.config.name }} diff --git a/lib/mylib/MyClass.cpp b/lib/mylib/MyClass.cpp index 0fa82bf..7af8b55 100644 --- a/lib/mylib/MyClass.cpp +++ b/lib/mylib/MyClass.cpp @@ -1,6 +1,7 @@ #include "MyClass.hpp" -MyClass::MyClass(std::ostream& out) : out_(out) {} +MyClass::MyClass(std::ostream& out) : out_(out) { +} void MyClass::Print(const std::string& str) { out_ << str; diff --git a/lib/mylib/MyClass.hpp b/lib/mylib/MyClass.hpp index d2e41c0..2847929 100644 --- a/lib/mylib/MyClass.hpp +++ b/lib/mylib/MyClass.hpp @@ -4,13 +4,13 @@ #include class MyClass { - public: +public: explicit MyClass(std::ostream& out); void Print(const std::string& str); - private: +private: std::ostream& out_; }; -#endif //MYCLASS_HPP_ +#endif // MYCLASS_HPP_ diff --git a/lib/ui/ui_functions.cpp b/lib/ui/ui_functions.cpp index d60d467..81859b6 100644 --- a/lib/ui/ui_functions.cpp +++ b/lib/ui/ui_functions.cpp @@ -1,5 +1,5 @@ -#include "ui_functions.hpp" #include "lib/mylib/MyClass.hpp" +#include "ui_functions.hpp" int32_t StartConsoleUI(const std::vector& args, std::ostream& out) { if (args.size() < 2) { diff --git a/lib/ui/ui_functions.hpp b/lib/ui/ui_functions.hpp index 8d06352..5409bec 100644 --- a/lib/ui/ui_functions.hpp +++ b/lib/ui/ui_functions.hpp @@ -1,10 +1,10 @@ #ifndef UI_FUNCTIONS_HPP_ #define UI_FUNCTIONS_HPP_ -#include -#include #include +#include +#include int32_t StartConsoleUI(const std::vector& args, std::ostream& out); -#endif //UI_FUNCTIONS_HPP_ +#endif // UI_FUNCTIONS_HPP_ diff --git a/tests/test_functions.hpp b/tests/test_functions.hpp index 50a78bd..8c154f8 100644 --- a/tests/test_functions.hpp +++ b/tests/test_functions.hpp @@ -1,9 +1,9 @@ #ifndef TESTFUNCTIONS_HPP_ #define TESTFUNCTIONS_HPP_ -#include #include +#include std::vector SplitString(const std::string& str); -#endif //TESTFUNCTIONS_HPP_ \ No newline at end of file +#endif // TESTFUNCTIONS_HPP_ diff --git a/tests/test_suites/ProjectIntegrationTestSuite.hpp b/tests/test_suites/ProjectIntegrationTestSuite.hpp index 1b1d0bf..0ecab1e 100644 --- a/tests/test_suites/ProjectIntegrationTestSuite.hpp +++ b/tests/test_suites/ProjectIntegrationTestSuite.hpp @@ -13,4 +13,4 @@ struct ProjectIntegrationTestSuite : public testing::Test { // special test stru void TearDown() override; // method that is called at the end of every test }; -#endif //TEMPORARYDIRECTORYTESTSUITE_HPP_ +#endif // TEMPORARYDIRECTORYTESTSUITE_HPP_ diff --git a/tests/unit_tests.cpp b/tests/unit_tests.cpp index 8fd17f8..fdee417 100644 --- a/tests/unit_tests.cpp +++ b/tests/unit_tests.cpp @@ -1,8 +1,8 @@ #include #include -#include "test_functions.hpp" // include your library here #include "lib/mylib/MyClass.hpp" +#include "test_functions.hpp" // include your library here TEST(MyLibUnitTestSuite, BasicTest1) { std::ostringstream out; @@ -12,4 +12,4 @@ TEST(MyLibUnitTestSuite, BasicTest1) { ASSERT_EQ(out_by_words.size(), 2); ASSERT_EQ(out_by_words[0], "Hello,"); ASSERT_EQ(out_by_words[1], "World!"); -} \ No newline at end of file +} From 0f48d2c893706f64cbcc88b12223aa587d0a3f45 Mon Sep 17 00:00:00 2001 From: bialger Date: Fri, 3 Oct 2025 16:56:31 +0300 Subject: [PATCH 08/17] Enhance clang-tidy integration in CI workflow - Improved handling of empty clang-tidy output by ensuring tidy_output.txt is created and readable. - Added checks for empty output to default error and warning counts to zero. - Enhanced output formatting for error and warning counts, ensuring clean integer values. - Added logging for found errors and warnings to improve visibility in CI results. --- .github/workflows/ci_tests.yml | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index e9e1ad3..8d85d62 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -226,23 +226,43 @@ jobs: if [ "${#files[@]}" -eq 0 ]; then echo "No C/C++ files to analyze." - touch tidy_output.txt + echo "" > tidy_output.txt exit 0 fi + echo "Running clang-tidy on ${#files[@]} files..." clang-tidy "${files[@]}" -p cmake-build-tidy --format-style=file > tidy_output.txt 2>&1 || true - touch tidy_output.txt + + # Ensure file exists and is readable + if [ ! -f tidy_output.txt ]; then + echo "" > tidy_output.txt + fi - name: Count warnings and errors id: count_issues run: | - # Count errors and warnings - errors=$(grep -c "error:" tidy_output.txt || echo "0") - warnings=$(grep -c "warning:" tidy_output.txt || echo "0") + # Count errors and warnings - handle empty file case + if [ ! -s tidy_output.txt ]; then + errors=0 + warnings=0 + else + errors=$(grep -c "error:" tidy_output.txt 2>/dev/null || echo "0") + warnings=$(grep -c "warning:" tidy_output.txt 2>/dev/null || echo "0") + fi + + # Ensure we have clean integer values + errors=$(echo "$errors" | tr -d '\n' | head -c 10) + warnings=$(echo "$warnings" | tr -d '\n' | head -c 10) + + # Default to 0 if empty or non-numeric + errors=${errors:-0} + warnings=${warnings:-0} echo "errors=$errors" >> $GITHUB_OUTPUT echo "warnings=$warnings" >> $GITHUB_OUTPUT + echo "Found $errors errors and $warnings warnings" + # Fail if more than 3 warnings or any errors if [ "$errors" -gt 0 ] || [ "$warnings" -gt 3 ]; then echo "clang-tidy found $errors errors and $warnings warnings" From 9f061758a2cfb7b834ed76197bdba8e68aeabce0 Mon Sep 17 00:00:00 2001 From: bialger Date: Fri, 3 Oct 2025 07:40:29 +0300 Subject: [PATCH 09/17] docs: enhance README.md and reference documentation for types and nullability Revise the README.md to clarify the distinction between fundamental types and primitive reference types, emphasizing their characteristics and usage. Update the nullable types section to specify that fundamental types cannot be made nullable and improve examples for null handling. Adjust related documentation across various reference files to ensure consistency in terminology and enhance overall clarity for users. --- README.md | 80 +++++---- docs/reference/code_examples.md | 118 +++++++------ docs/reference/expressions_and_operators.md | 10 +- docs/reference/lexical_structure.md | 15 +- docs/reference/nullable.md | 56 +++---- docs/reference/syntax.md | 6 +- docs/reference/types.md | 173 +++++++++++++++----- 7 files changed, 283 insertions(+), 175 deletions(-) diff --git a/README.md b/README.md index 7521dbe..fa41408 100644 --- a/README.md +++ b/README.md @@ -30,12 +30,15 @@ Ovum is a strongly statically typed, single-threaded language focused on safety, --- -## [Types and Nullability](docs/reference/types.md) +## [Types](docs/reference/types.md) and [Nullability](docs/reference/nullable.md) -- Nullable types: append `?` (e.g., `Int?`). -- Safe call `?.`, Elvis `?:`, non‑null `!!`. -- Type test `is`, cast `as` (downcast yields nullable type). -- Explicit cast to `Bool` allowed for any value. +- **Fundamental types**: `int`, `float`, `byte`, `char`, `bool`, `pointer` (value types, not nullable, not Objects) +- **Primitive reference types**: `Int`, `Float`, `Byte`, `Char`, `Bool`, `Pointer` (reference wrappers, nullable, Objects) +- **Implicit conversion**: Literals convert to primitives (`val count: Int = 0`) +- **Nullable types**: Append `?` to reference types only (e.g., `Int?`, `String?`) +- **Safe call `?.`**, **Elvis `?:`** for null handling +- **Type test `is`**, **cast `as`** (downcast yields nullable type) +- **Copy assignment `:=`** for deep copying reference types --- @@ -51,9 +54,9 @@ Ovum is a strongly statically typed, single-threaded language focused on safety, - Arithmetic: `+ - * / %` - Comparison: `== != < <= > >=` - Boolean: `&& || ! xor` (short‑circuit `&&`/`||`). -- Assignment: `=` +- Assignment: `=` (reference assignment), `:=` (copy assignment) - Member/calls: `. ()` and safe `?.` -- Null handling: `?. ?: !!` +- Null handling: `?. ?:` - Type ops: `as`, `is` - Namespace: `::` @@ -77,7 +80,7 @@ Ovum is a strongly statically typed, single-threaded language focused on safety, - Pipeline: `.ovum` → bytecode → Ovum VM. - GC for memory safety; JIT compiles hot paths. - Single‑threaded execution model. -- Architectures: amd64, arm64. Numeric widths: `Int` 8 bytes, `Float` 8 bytes. +- Architectures: amd64, arm64. Numeric widths: `int` 8 bytes, `float` 8 bytes. - Entry point: `Main(args: StringArray): Int`. Build & Run (conceptual): write `.ovum`, compile (parse, type‑check, enforce const/pure), run on VM (JIT + GC). @@ -98,7 +101,7 @@ Build & Run (conceptual): write `.ovum`, compile (parse, type‑check, enforce c Only inside `unsafe { ... }`: - Global `var` and `static var` writes. -- Const/mutable casts; `Pointer`, address‑of, dereference. +- Const/mutable casts; `pointer`, address‑of, dereference. - Manual destructor calls. - `sys::Interope`; casting any value to (const or mutable) `ByteArray`. @@ -111,35 +114,40 @@ Only inside `unsafe { ... }`: ```ovum // .ovum file fun Main(args: StringArray): Int { - val count: Int = args.Length() + val count: Int = args.Length() // Built-in returns Int sys::Print("Args count: " + count.ToString()) - return 0 + return 0 // Implicit conversion from literal } ``` ### Pure functions with caching ```ovum -pure fun Fib(n: Int): Int { +pure fun Fib(n: int): int { if (n <= 1) return n - return Fib(n - 1) + Fib(n - 2) + val fib1: int = Fib(n - 1) + val fib2: int = Fib(n - 2) + return fib1 + fib2 } ``` -### `is`, `as`, `!!` and ByteArray casts +### `is`, `as` and ByteArray casts ```ovum fun DemoCasts(obj: Object): Void { if (obj is Point) { - val p: Point = (obj as Point)!! // nullable cast + assert - sys::Print(p.ToString()) + val p: Point? = obj as Point + if (p != null) { + val nonNullP: Point = p ?: Point(0, 0) // Use Elvis operator + sys::Print(nonNullP.ToString()) + } } // Bool cast - val b1: Bool = (0 as Bool) // false - val b2: Bool = (42 as Bool) // true - val b3: Bool = (obj as Bool) // always true - val b4: Bool = ((obj as Point) as Bool) // true if obj is a Point + val b1: Bool = 0 as bool // false + val b2: Bool = 42 as bool // true + val b3: Bool = obj as bool // always true + val b4: Bool = (obj as Point) as bool // true if obj is a Point // Unsafe: raw byte views unsafe { @@ -170,11 +178,14 @@ class DefinedFunctional { } val AddNullable: CustomFunctional = pure fun(a: Int?, b: Int?): Int { - return (a ?: 0) + (b ?: 0) + val aVal: int = a ?: 0 // Implicit conversion from Int? to int + val bVal: int = b ?: 0 + return Int(aVal + bVal) // Implicit conversion from int to Int } fun Main(args: StringArray): Int { - return AddNullable(2, DefinedFunctional(-1)(2)) + // Constructor call then functional call via `call` + return AddNullable(2, DefinedFunctional(-1)(2)) // Implicit conversion from literals } ``` @@ -198,29 +209,30 @@ class Multiplier implements ICalculator { } } -pure fun ProcessNumbers(calc: ICalculator, numbers: IntArray): Int { - var result: Int = 0 +pure fun ProcessNumbers(calc: ICalculator, numbers: IntArray): int { + var result: int = 0 for (num in numbers) { - result = result + calc.Calculate(num, 2) + val calcResult: Int = calc.Calculate(num, 2) // Implicit conversion from literal + result = result + calcResult // Implicit conversion } return result } -fun Main(args: StringArray): Int { +fun Main(args: StringArray): int { val numbers: IntArray = IntArray(3) - numbers[0] = 5 - numbers[1] = 10 - numbers[2] = 15 + numbers[0] := 5 // Implicit conversion from literal + numbers[1] := 10 + numbers[2] := 15 val adder: ICalculator = Adder() val multiplier: ICalculator = Multiplier() - val sumResult: Int = ProcessNumbers(adder, numbers) - val productResult: Int = ProcessNumbers(multiplier, numbers) + val sumResult: int = ProcessNumbers(adder, numbers) + val productResult: int = ProcessNumbers(multiplier, numbers) - sys::Print("Sum result: " + sumResult.ToString()) - sys::Print("Product result: " + productResult.ToString()) + sys::Print("Sum result: " + Int(sumResult).ToString()) + sys::Print("Product result: " + Int(productResult).ToString()) - return 0 + return 0 // Implicit conversion from literal } ``` diff --git a/docs/reference/code_examples.md b/docs/reference/code_examples.md index 711160e..2400276 100644 --- a/docs/reference/code_examples.md +++ b/docs/reference/code_examples.md @@ -7,7 +7,7 @@ Here are some code examples to help you get started with Ovum. ```ovum // .ovum file fun Main(args: StringArray): Int { - val count: Int = args.Length() + val count: Int = args.Length() // Built-in returns Int sys::Print("Args count: " + count.ToString()) return 0 } @@ -20,14 +20,14 @@ fun DemoNulls(): Void { val a: Int? = null val b: Int? = 5 - val sum: Int = (a ?: 0) + (b ?: 0) // Elvis - sys::Print("Sum = " + sum.ToString()) + val aVal: int = a ?: 0 + val bVal: int = b ?: 0 + val sum: int = aVal + bVal + sys::Print("Sum = " + Int(sum).ToString()) val name: String? = null - sys::Print("Name length = " + (name?.Length() ?: 0).ToString()) - - val mustNotBeNull: Int = (b!!) // ok - // val crash: Int = (a!!) // aborts (unhandleable) + val length: int = (name?.Length() ?: 0) as int // Built-in returns Int + sys::Print("Name length = " + Int(length).ToString()) } ``` @@ -67,24 +67,29 @@ interface IComparable { fun IsLess(other: Object): Bool } interface IHashable { fun GetHash(): Int } class Point implements IStringConvertible, IComparable, IHashable { - public val X: Int - public val Y: Int + public val X: int + public val Y: int - public fun Point(x: Int, y: Int): Point { this.X = x; this.Y = y; return this; } + public fun Point(x: int, y: int): Point { this.X = x; this.Y = y; return this; } public override fun ToString(): String { - return "(" + X.ToString() + ", " + Y.ToString() + ")" + return "(" + Int(X).ToString() + ", " + Int(Y).ToString() + ")" } public override fun IsLess(other: Object): Bool { if (!(other is Point)) return false - val p: Point = (other as Point)!! // safe after is + !! - if (this.X != p.X) return this.X < p.X - return this.Y < p.Y + val p: Point? = other as Point + if (p != null) { + val nonNullP: Point = p ?: Point(0, 0) // Use Elvis operator + if (this.X != nonNullP.X) return this.X < nonNullP.X + return this.Y < nonNullP.Y + } + return false } public override fun GetHash(): Int { - return (X * 1315423911) ^ (Y * 2654435761) + val hash: int = (X * 1315423911) ^ (Y * 2654435761) + return Int(hash) } } ``` @@ -92,27 +97,32 @@ class Point implements IStringConvertible, IComparable, IHashable { ## 5) Pure Functions with Caching ```ovum -pure fun Fib(n: Int): Int { +pure fun Fib(n: int): int { if (n <= 1) return n - return Fib(n - 1) + Fib(n - 2) + val fib1: int = Fib(n - 1) + val fib2: int = Fib(n - 2) + return fib1 + fib2 } // For user-defined reference types as parameters, implement IComparable. ``` -## 6) `is`, `as`, `!!`, and ByteArray Casts +## 6) `is`, `as`, and ByteArray Casts ```ovum fun DemoCasts(obj: Object): Void { if (obj is Point) { - val p: Point = (obj as Point)!! // nullable cast + assert - sys::Print(p.ToString()) + val p: Point? = obj as Point + if (p != null) { + val nonNullP: Point = p ?: Point(0, 0) // Use Elvis operator + sys::Print(nonNullP.ToString()) + } } - // Bool cast - val b1: Bool = (0 as Bool) // false - val b2: Bool = (42 as Bool) // true - val b3: Bool = (obj as Bool) // always true - val b4: Bool = ((obj as Point) as Bool) // true if obj is a Point + // bool cast + val b1: Bool = 0 as bool // false + val b2: Bool = 42 as bool // true + val b3: Bool = obj as bool // always true + val b4: Bool = (obj as Point) as bool // true if obj is a Point // Unsafe: raw byte views unsafe { @@ -155,7 +165,7 @@ fun Main(args: StringArray): Int { ```ovum fun DemoControlFlow(): Void { - var i: Int = 0 + var i: int = 0 // While loop with break and continue while (i < 10) { @@ -166,15 +176,15 @@ fun DemoControlFlow(): Void { if (i == 7) { break // Exit loop } - sys::Print("i = " + i.ToString()) + sys::Print("i = " + Int(i).ToString()) i = i + 1 } // For loop over array val numbers: IntArray = IntArray(3) - numbers[0] = 10 - numbers[1] = 20 - numbers[2] = 30 + numbers[0] := 10 + numbers[1] := 20 + numbers[2] := 30 for (num in numbers) { sys::Print("Number: " + num.ToString()) @@ -189,13 +199,13 @@ fun DemoUnsafeOperations(): Void { // Unsafe block for low-level operations unsafe { // Global mutable state (unsafe) - static var globalCounter: Int = 0 + static var globalCounter: int = 0 globalCounter = globalCounter + 1 // Pointer operations (unsafe) val obj: Point = Point(10, 20) - val ptr: Pointer = &obj // address-of - val deref: Object = *ptr // dereference to Object, Pointer is not typed + val ptr: pointer = &obj // address-of + val deref: Object = *ptr // dereference to Object, pointer is not typed // ByteArray casting (unsafe) val bytes: ByteArray = (obj as ByteArray) @@ -203,8 +213,8 @@ fun DemoUnsafeOperations(): Void { // Foreign function interface (unsafe) val input: ByteArray = "Hello".ToUtf8Bytes() - val output: ByteArray = ByteArray(4) - val result: Int = sys::Interope("libc.so", "strlen", input, output) + val output: ByteArray = ByteArray(8) + val result: int = sys::Interope("libc.so", "strlen", input, output) } } ``` @@ -230,8 +240,11 @@ class User { fun ProcessUsers(users: UserList): Void { for (i in 0..users.Length()) { - val user: User = (users[i] as User)!! - sys::Print("User " + user.Id.ToString() + ": " + user.Name) + val user: User? = users[i] as User + if (user != null) { + val nonNullUser: User = user ?: User(0, "Unknown") // Use Elvis operator + sys::Print("User " + nonNullUser.Id.ToString() + ": " + nonNullUser.Name) + } } } ``` @@ -241,10 +254,10 @@ fun ProcessUsers(users: UserList): Void { ```ovum class DatabaseConnection { - private val ConnectionId: Int - private val IsConnected: Bool + private val ConnectionId: int + private val IsConnected: bool - public fun DatabaseConnection(id: Int): DatabaseConnection { + public fun DatabaseConnection(id: int): DatabaseConnection { this.ConnectionId = id this.IsConnected = true // Establish database connection @@ -261,7 +274,7 @@ class DatabaseConnection { public destructor(): Void { if (IsConnected) { // Close database connection - sys::Print("Closing connection " + ConnectionId.ToString()) + sys::Print("Closing connection " + Int(ConnectionId).ToString()) } } } @@ -296,28 +309,29 @@ class Multiplier implements ICalculator { } } -pure fun ProcessNumbers(calc: ICalculator, numbers: IntArray): Int { - var result: Int = 0 +pure fun ProcessNumbers(calc: ICalculator, numbers: IntArray): int { + var result: int = 0 for (num in numbers) { - result = result + calc.Calculate(num, 2) + val calcResult: Int = calc.Calculate(num, 2) + result = result + calcResult } return result } -fun Main(args: StringArray): Int { +fun Main(args: StringArray): int { val numbers: IntArray = IntArray(3) - numbers[0] = 5 - numbers[1] = 10 - numbers[2] = 15 + numbers[0] := 5 + numbers[1] := 10 + numbers[2] := 15 val adder: ICalculator = Adder() val multiplier: ICalculator = Multiplier() - val sumResult: Int = ProcessNumbers(adder, numbers) - val productResult: Int = ProcessNumbers(multiplier, numbers) + val sumResult: int = ProcessNumbers(adder, numbers) + val productResult: int = ProcessNumbers(multiplier, numbers) - sys::Print("Sum result: " + sumResult.ToString()) - sys::Print("Product result: " + productResult.ToString()) + sys::Print("Sum result: " + Int(sumResult).ToString()) + sys::Print("Product result: " + Int(productResult).ToString()) return 0 } diff --git a/docs/reference/expressions_and_operators.md b/docs/reference/expressions_and_operators.md index 1c8043c..e56a42f 100644 --- a/docs/reference/expressions_and_operators.md +++ b/docs/reference/expressions_and_operators.md @@ -21,11 +21,12 @@ Expressions in Ovum include literal values, variable references, function calls, * `&&` (logical AND) - short-circuit evaluation * `||` (logical OR) - short-circuit evaluation * `!` (negation) - unary operator -* `xor` (exclusive OR) - infix operator on `Bool` +* `xor` (exclusive OR) - infix operator on `bool` -## Assignment Operator +## Assignment Operators -* `=` (assignment) - assigns a value to a mutable variable or field. The left-hand side must be a mutable variable or field. +* `=` (reference assignment) - assigns a reference to a mutable variable or field. The left-hand side must be a mutable variable or field. +* `:=` (copy assignment) - performs deep copy for reference types. Creates a new object with the same content as the source. ## Member Access @@ -37,13 +38,12 @@ Expressions in Ovum include literal values, variable references, function calls, ## Type Operations * `expr as Type` - explicit cast (downcast yields nullable type) -* `expr is Type` - type test (returns `Bool`) +* `expr is Type` - type test (returns `bool`) ## Null Handling * `expr?.member` - safe call (calls only if expr is not null) * `expr ?: default` - Elvis operator (returns expr if not null, otherwise default) -* `expr!!` - non-null assertion (throws error if expr is null) ## Namespace Resolution diff --git a/docs/reference/lexical_structure.md b/docs/reference/lexical_structure.md index a8c79bf..c207aee 100644 --- a/docs/reference/lexical_structure.md +++ b/docs/reference/lexical_structure.md @@ -24,8 +24,8 @@ Ovum reserves certain words like `fun`, `class`, `interface`, `var`, `override`, * **Arithmetic**: `+`, `-`, `*`, `/`, `%` * **Comparison**: `==`, `!=`, `<`, `<=`, `>`, `>=` * **Boolean logic**: `&&` (logical AND), `||` (logical OR), `!` (negation), `xor` (exclusive OR) -* **Assignment**: `=` -* **Null handling**: `?.` (safe call), `?:` (Elvis), `!!` (non-null assertion) +* **Assignment**: `=` (reference assignment), `:=` (copy assignment) +* **Null handling**: `?.` (safe call), `?:` (Elvis) * **Type operations**: `as` (cast), `is` (type test) * **Punctuation**: `,` (comma), `;` (semicolon), `:` (colon), `()` (parentheses), `{}` (braces), `[]` (brackets) * **Namespace resolution**: `::` @@ -87,14 +87,16 @@ TypeAliasDecl ::= "typealias" Identifier "=" Type ";" ; Type ::= NullableType | NonNullType ; NullableType ::= NonNullType "?" ; -NonNullType ::= PrimitiveType +NonNullType ::= FundamentalType + | PrimitiveRefType | "String" | "IntArray" | "FloatArray" | "BoolArray" | "CharArray" | "ByteArray" | "PointerArray" | "ObjectArray" | "StringArray" | Identifier ; // class/interface names (non-primitive) -PrimitiveType ::= "Int" | "Float" | "Bool" | "Char" | "Byte" | "Pointer" ; +FundamentalType ::= "int" | "float" | "bool" | "char" | "byte" | "pointer" ; +PrimitiveRefType ::= "Int" | "Float" | "Bool" | "Char" | "Byte" | "Pointer" ; Block ::= "{" { Statement } "}" ; Statement ::= VarDeclStmt | ExprStmt | ReturnStmt | IfStmt | WhileStmt | ForStmt | UnsafeStmt | Block ; @@ -108,7 +110,7 @@ ForStmt ::= "for" "(" Identifier "in" Expression ")" Statement ; UnsafeStmt ::= "unsafe" Block ; Expression ::= Assignment ; -Assignment ::= ElvisExpr [ "=" Assignment ] ; +Assignment ::= ElvisExpr [ ("=" | ":=") Assignment ] ; ElvisExpr ::= OrExpr [ "?:" ElvisExpr ] ; // right-assoc @@ -129,8 +131,7 @@ PostfixOp ::= "." Identifier | "." Identifier "(" [ ArgList ] ")" | "(" [ ArgList ] ")" // function call or callable object call | "as" Type // explicit cast; downcast yields nullable type - | "is" Type // type test → Bool - | "!!" // non-null assertion + | "is" Type // type test → bool | "?." Identifier [ "(" [ ArgList ] ")" ] // safe call chain | "?." "(" [ ArgList ] ")" // safe callable object call ; diff --git a/docs/reference/nullable.md b/docs/reference/nullable.md index da03e80..248ea24 100644 --- a/docs/reference/nullable.md +++ b/docs/reference/nullable.md @@ -4,23 +4,24 @@ This document describes how nullable types work in Ovum, including their restric ## Creating Nullable Types -Append `?` to make a type **nullable**: `Int?`, `String?`, `Point?`. Nullable types are passed by reference and can hold either a value or `null`. +Append `?` to make a **reference type** nullable: `Int?`, `String?`, `Point?`. **Fundamental types** (`int`, `float`, `bool`, `char`, `byte`, `pointer`) cannot be made nullable. ```ovum val nullableInt: Int? = null val nullableString: String? = "Hello" val nullablePoint: Point? = Point(10, 20) + +// val invalidNullable: int? = null // ERROR: Fundamental types cannot be nullable ``` ## Method Call Restrictions -**Important**: You cannot directly call methods on nullable types using `.` - you must use the safe call operator `?.` or non-null assertion `!!`. +**Important**: You cannot directly call methods on nullable types using `.` - you must use the safe call operator `?.`. ```ovum val nullableString: String? = "Hello" // val length: Int = nullableString.Length() // ERROR: Cannot call method directly on nullable val safeLength: Int = nullableString?.Length() ?: 0 // Correct: Use safe call -val forcedLength: Int = nullableString!!.Length() // Correct: Use non-null assertion ``` ## Null Handling Operators @@ -47,32 +48,26 @@ val nullableString: String? = null val result: String = nullableString ?: "default" // Uses "default" if nullableString is null ``` -### Non-null Assertion (`!!`) - -`x!!` throws an unhandleable error if `x == null`. Use with caution - only when you're certain the value is not null. - -```ovum -val nullableInt: Int? = 42 -val mustExist: Int = nullableInt!! // Safe - nullableInt is not null - -// val crash: Int = (null as Int?)!! // ERROR: Will abort the program -``` ## Type Casting ### Cast to Bool -Any value can be explicitly cast to `Bool`: +Any value can be explicitly cast to `bool`: -* **Primitives**: zero → `false`, non-zero → `true` -* **Non-primitives**: `true` iff the reference is a valid (non-null, live) object +* **Fundamentals, primitive reference types**: zero → `false`, non-zero → `true` +* **Non-primitive reference types and nullable primitives**: `true` iff the reference is a valid (non-null, live) object ```ovum val nullableInt: Int? = null -val isNull: Bool = (nullableInt as Bool) // false (null is falsy) +val isNull: bool = (nullableInt as bool) // false (null is falsy) -val someInt: Int? = 42 -val isNotNull: Bool = (someInt as Bool) // true (non-null is truthy) +val someInt: Int? = 42 // Implicit conversion from literal +val isNotNull: bool = (someInt as bool) // true (non-null is truthy) + +// Converting nullable primitives to fundamentals +val nullablePrimitive: Int? = 42 // Implicit conversion from literal +val fundamentalValue: int = (nullablePrimitive as Int) as int // Two-step conversion ``` ## Chaining Operations @@ -85,7 +80,9 @@ val nameLength: Int = person?.Name?.Length() ?: 0 // Equivalent to: val nameLength: Int = if (person != null && person.Name != null) { - person.Name.Length() + val nonNullPerson: Person = person ?: Person("Unknown") // Use Elvis operator + val nonNullName: String = nonNullPerson.Name ?: "Unknown" // Use Elvis operator + nonNullName.Length() } else { 0 } @@ -97,21 +94,18 @@ All nullable types support the same operators but cannot directly call methods: ```ovum val nullableString: String? = "Hello" -val nullableInt: Int? = 42 +val nullableInt: Int? = 42 // Implicit conversion from literal // Safe operations -val safeLength: Int = nullableString?.Length() ?: 0 +val safeLength: int = (nullableString?.Length() ?: 0) as int // Built-in returns Int val safeToString: String = nullableInt?.ToString() ?: "null" - -// Unsafe operations (will crash if null) -val forcedLength: Int = nullableString!!.Length() -val forcedToString: String = nullableInt!!.ToString() ``` ## Best Practices -1. **Prefer safe calls** over non-null assertions when possible -2. **Use Elvis operator** to provide sensible defaults -3. **Avoid non-null assertions** unless you're certain the value exists -4. **Chain operations** for cleaner null handling code -5. **Consider using `if` statements** for complex null checks instead of deeply nested safe calls +1. **Always use safe calls** (`?.`) for nullable types +2. **Use Elvis operator** (`?:`) to provide sensible defaults +3. **Chain operations** for cleaner null handling code +4. **Consider using `if` statements** for complex null checks instead of deeply nested safe calls +5. **Use copy assignment** (`:=`) when you need independent copies of nullable objects +6. **Convert to fundamentals** when you need value semantics: `(nullablePrimitive as PrimitiveType) as fundamentalType` diff --git a/docs/reference/syntax.md b/docs/reference/syntax.md index f5a7611..d0a987e 100644 --- a/docs/reference/syntax.md +++ b/docs/reference/syntax.md @@ -74,11 +74,13 @@ class DefinedFunctional { } val AddNullable: CustomFunctional = pure fun(a: Int?, b: Int?): Int { - return (a ?: 0) + (b ?: 0) + val aVal: int = a ?: 0 // Conversion from Int? to int + val bVal: int = b ?: 0 + return aVal + bVal } fun Main(args: StringArray): Int { // Constructor call then functional call via `call` - return AddNullable(2, DefinedFunctional(-1)(2)) + return AddNullable(2, DefinedFunctional(-1)(2)) // Implicit conversion from literals } ``` diff --git a/docs/reference/types.md b/docs/reference/types.md index d23fe15..c9ce84b 100644 --- a/docs/reference/types.md +++ b/docs/reference/types.md @@ -2,34 +2,57 @@ Ovum has a rich type system with primitive types and user-defined types. The type system is static and does not permit implicit type coercions (an `Int` won't automatically become a `Float` without an explicit cast, for example). -## Primitive Types +## Fundamental Types + +Fundamental types are passed by value and represent the basic building blocks of the language: ### Numeric Types -* **`Int`** (8 bytes) - 64-bit signed integer +* **`int`** (8 bytes) - 64-bit signed integer * Literals: `42`, `-17`, `0x1A` (hex), `0b1010` (binary) -* **`Float`** (8 bytes) - 64-bit floating-point number (IEEE 754 double precision) +* **`float`** (8 bytes) - 64-bit floating-point number (IEEE 754 double precision) * Literals: `3.14`, `2.0e10`, `1.5E-3`, `.5`, `5.` * Special values: `Infinity`, `-Infinity`, `NaN` -* **`Byte`** (1 byte) - 8-bit unsigned integer +* **`byte`** (1 byte) - 8-bit unsigned integer * Literals: `255`, `0x00`, `0b11111111` ### Character and Boolean Types -* **`Char`** - single Unicode character (UTF-32) +* **`char`** - single Unicode character (UTF-32) * Literals: `'A'`, `'中'`, `'\n'`, `'\t'`, `'\0'` -* **`Bool`** - Boolean value (`true`, `false`) - * Any value can be explicitly cast to `Bool` +* **`bool`** - Boolean value (`true`, `false`) + * Any value can be explicitly cast to `bool` ### Low-Level Types -* **`Pointer`** - raw memory address *(only meaningful in `unsafe` code)* +* **`pointer`** - raw memory address *(only meaningful in `unsafe` code)* * Used for FFI and low-level memory operations -> **Nullable Primitives**: Any primitive type can be made nullable by appending `?` (e.g., `Int?`, `Float?`, `Bool?`). Nullable primitives are reference types. +> **Fundamental Type Constraints**: Fundamental types cannot be made nullable (`int?` is invalid). They are not `Object` types and cannot be stored in `ObjectArray` or cast to `Object`. To convert nullable primitives to fundamentals, cast to primitive first: `(nullableInt as Int) as int`. + +## Primitive Reference Types + +Primitive reference types are built-in reference wrappers around fundamental types, passed by reference: + +### Numeric Reference Types + +* **`Int`** - reference wrapper for `int` values +* **`Float`** - reference wrapper for `float` values +* **`Byte`** - reference wrapper for `byte` values + +### Character and Boolean Reference Types + +* **`Char`** - reference wrapper for `char` values +* **`Bool`** - reference wrapper for `bool` values + +### Low-Level Reference Types + +* **`Pointer`** - reference wrapper for `pointer` values *(only meaningful in `unsafe` code)* + +> **Nullable Primitives**: Any primitive reference type can be made nullable by appending `?` (e.g., `Int?`, `Float?`, `Bool?`). ## Reference Types @@ -48,12 +71,12 @@ Ovum has a rich type system with primitive types and user-defined types. The typ Ovum provides specialized array classes for different element types (no generics/templates): **Primitive Arrays:** -* `IntArray` - array of `Int` values -* `FloatArray` - array of `Float` values -* `BoolArray` - array of `Bool` values -* `CharArray` - array of `Char` values -* `ByteArray` - array of `Byte` values -* `PointerArray` - array of `Pointer` values +* `IntArray` - array of `Int` reference wrappers +* `FloatArray` - array of `Float` reference wrappers +* `BoolArray` - array of `Bool` reference wrappers +* `CharArray` - array of `Char` reference wrappers +* `ByteArray` - array of `Byte` reference wrappers +* `PointerArray` - array of `Pointer` reference wrappers **Object Arrays:** * `ObjectArray` - array of any `Object`-derived types @@ -61,7 +84,7 @@ Ovum provides specialized array classes for different element types (no generics **Array Creation:** ```ovum -val numbers: IntArray = IntArray(10) // Create array of size 10 +val numbers: IntArray = IntArray(10) // Create array of Int reference wrappers val names: StringArray = StringArray(5) // Create string array of size 5 val objects: ObjectArray = ObjectArray(3) // Create object array of size 3 ``` @@ -80,6 +103,26 @@ fun ProcessUser(id: UserId, name: UserName): Void { ``` +## Assignment Operators + +### Reference Assignment (`=`) + +The standard assignment operator assigns references for reference types: + +```ovum +val original: String = "Hello" +val reference: String = original // Both variables point to the same string +``` + +### Copy Assignment (`:=`) + +The copy assignment operator performs deep copy for reference types: + +```ovum +val original: String = "Hello" +val copy: String := original // Creates a new string with the same content +``` + ## Type Casting ### Explicit Casting @@ -87,30 +130,49 @@ fun ProcessUser(id: UserId, name: UserName): Void { Use the `as` operator for explicit casting: ```ovum -val intValue: Int = 42 -val floatValue: Float = (intValue as Float) // Int to Float -val stringValue: String = (intValue as String) // Int to String +val intValue: int = 42 +val floatValue: float = (intValue as float) // int to float -val floatNum: Float = 3.14 -val intNum: Int = (floatNum as Int) // Float to Int (truncates) +val floatNum: float = 3.14 +val intNum: int = (floatNum as int) // float to int (truncates) + +// Implicit bidirectional casting between fundamental and primitive reference types +val fundamentalInt: int = 42 +val primitiveInt: Int = fundamentalInt // Implicit: int -> Int +val backToFundamental: int = primitiveInt // Implicit: Int -> int + +// Implicit conversion from literals to primitive types +val count: Int = 0 // Implicit: int literal -> Int +val flag: Bool = true // Implicit: bool literal -> Bool +val pi: Float = 3.14 // Implicit: float literal -> Float + +// Arithmetic works seamlessly +val sum: Int = 10 + 20 // Int + Int = Int (implicit conversion from literals) +val result: int = sum + 5 // Int + int = int (implicit conversion) ``` ### Boolean Casting -Any value can be explicitly cast to `Bool`: +Any value can be explicitly cast to `bool`: ```ovum -val intVal: Int = 42 -val boolVal: Bool = (intVal as Bool) // true (non-zero) +val intVal: int = 42 +val boolVal: bool = (intVal as bool) // true (non-zero) -val zeroInt: Int = 0 -val falseBool: Bool = (zeroInt as Bool) // false (zero) +val zeroInt: int = 0 +val falseBool: bool = (zeroInt as bool) // false (zero) val nullString: String? = null -val nullBool: Bool = (nullString as Bool) // false (null) +val nullBool: bool = (nullString as bool) // false (null) + +// With primitive reference types (implicit conversion) +val primitiveInt: Int = 42 // Implicit conversion from literal +val primitiveBool: bool = primitiveInt // Implicit: Int -> bool +val boolRef: Bool = true // Implicit: bool literal -> Bool ``` -**Rules:** Primitives: zero → `false`, non-zero → `true`. References: `null` → `false`, non-null → `true` +**Rules:** Fundamentals and primitive reference types: zero → `false`, non-zero → `true`. +References: `null` → `false`, non-null → `true` ### Unsafe Casting @@ -126,17 +188,21 @@ unsafe { ## Passing Semantics -**Primitive types** are passed by value (copied): +**Fundamental types** (`int`, `float`, `byte`, `char`, `bool`, `pointer`) are passed by value (copied): ```ovum -fun ModifyInt(x: Int): Void { +fun ModifyInt(x: int): Void { x = x + 1 // Only modifies the local copy } ``` -**Reference types** are passed by reference: +**Primitive reference types** (`Int`, `Float`, `Byte`, `Char`, `Bool`, `Pointer`) and **all other reference types** (including `String`, arrays, and user-defined types) are passed by reference: ```ovum +fun ModifyIntRef(var x: Int): Void { + x = x + 1 // Implicit conversion: Int + int -> Int +} + fun ModifyArray(arr: IntArray): Void { - arr[0] = 999 // Modifies the original array + arr[0] := Int(999) // Use := for deep copy assignment } ``` @@ -156,19 +222,26 @@ fun CanReassign(var str: String): Void { **Static typing:** Every variable and expression has a type checked at compile time **No implicit conversions:** Explicit casting required between different types **Type safety:** Prevents many common errors -**Nullable types:** Any type can be made nullable by appending `?` +**Nullable types:** Any reference type (including primitive reference types) can be made nullable by appending `?`. Fundamental types cannot be nullable. ```ovum -val x: Int = 42 +val x: int = 42 val y: String = "Hello" -// val z: Int = x + y // ERROR: Cannot add Int and String +// val z: int = x + y // ERROR: Cannot add int and String + +val intVal: int = 42 +val floatVal: float = 3.14 +val result: float = (intVal as float) + floatVal // OK: Explicit conversion -val intVal: Int = 42 -val floatVal: Float = 3.14 -val result: Float = (intVal as Float) + floatVal // OK: Explicit conversion +// Using primitive reference types (implicit conversion) +val refInt: Int = 42 // Implicit conversion from literal +val refFloat: Float = 3.14 // Implicit conversion from literal +val sum: Int = refInt + refFloat // Implicit: Int + Float -> Int +val fundamentalSum: int = sum + 10 // Implicit: Int + int -> int -val nullableInt: Int? = null -val nullableString: String? = "Hello" +// Converting nullable primitives to fundamentals +val nullableInt: Int? = 42 // Implicit conversion from literal +val fundamentalFromNullable: int = (nullableInt ?: 0) as int // Two-step conversion ``` ## Pure Function Constraints @@ -189,11 +262,23 @@ Type information is preserved at runtime for reference types: ```ovum fun ProcessObject(obj: Object): Void { if (obj is String) { - val str: String = (obj as String)!! - sys::Print("String length: " + str.Length().ToString()) + val str: String? = obj as String + if (str != null) { + val nonNullStr: String = str ?: "default" // Use Elvis operator + sys::Print("String length: " + nonNullStr.Length().ToString()) + } } else if (obj is IntArray) { - val arr: IntArray = (obj as IntArray)!! - sys::Print("Array size: " + arr.Length().ToString()) + val arr: IntArray? = obj as IntArray + if (arr != null) { + val nonNullArr: IntArray = arr ?: IntArray(0) // Use Elvis operator + sys::Print("Array size: " + nonNullArr.Length().ToString()) + } + } else if (obj is Int) { + val intRef: Int? = obj as Int + if (intRef != null) { + val nonNullInt: Int = intRef ?: 0 // Use Elvis operator + sys::Print("Int value: " + nonNullInt.ToString()) // Implicit conversion to string + } } } ``` From b62696f51effae3e05a09472d0aae955492d6fc7 Mon Sep 17 00:00:00 2001 From: bialger Date: Fri, 3 Oct 2025 16:06:40 +0300 Subject: [PATCH 10/17] docs: clarify type system characteristics in types.md Update the documentation to specify that explicit casting is required between different types, with the exception of conversions between primitive reference types and their corresponding fundamentals. Adjust code examples to reflect this change, enhancing clarity and understanding of the type system. --- docs/reference/types.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reference/types.md b/docs/reference/types.md index c9ce84b..ed1bdc4 100644 --- a/docs/reference/types.md +++ b/docs/reference/types.md @@ -220,7 +220,7 @@ fun CanReassign(var str: String): Void { ## Type System Characteristics **Static typing:** Every variable and expression has a type checked at compile time -**No implicit conversions:** Explicit casting required between different types +**No implicit conversions except builtin wrappers:** Explicit casting required between different types, except for conversions between primitive reference types and their corresponding fundamentals. **Type safety:** Prevents many common errors **Nullable types:** Any reference type (including primitive reference types) can be made nullable by appending `?`. Fundamental types cannot be nullable. @@ -236,7 +236,7 @@ val result: float = (intVal as float) + floatVal // OK: Explicit conversion // Using primitive reference types (implicit conversion) val refInt: Int = 42 // Implicit conversion from literal val refFloat: Float = 3.14 // Implicit conversion from literal -val sum: Int = refInt + refFloat // Implicit: Int + Float -> Int +val sum: Int = refInt + (refFloat as Int) // Explicit conversion val fundamentalSum: int = sum + 10 // Implicit: Int + int -> int // Converting nullable primitives to fundamentals From d28089f46e0c646ce64e77849ab111f5480eaf75 Mon Sep 17 00:00:00 2001 From: bialger Date: Fri, 3 Oct 2025 16:17:14 +0300 Subject: [PATCH 11/17] ci: add code style and quality checks to CI workflow Introduce new jobs for code style checking with clang-format and code quality checking with clang-tidy in the CI workflow. The style check identifies formatting issues in C++ source files and comments on them, while the quality check runs clang-tidy to detect errors and warnings, providing feedback on code quality. This enhancement aims to maintain code standards and improve overall code quality in the project. --- .github/workflows/ci_tests.yml | 151 +++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index b46f230..bdfafea 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -123,3 +123,154 @@ jobs: working-directory: ./cmake-build/tests run: | valgrind --leak-check=full --track-origins=yes --error-exitcode=1 ./ovum_tests + + style-check: + name: Code style check with clang-format + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install clang-format + run: | + sudo apt-get update && sudo apt-get -y install clang-format + + - name: Check code style + run: | + # Find all C++ source files + find . -name "*.cpp" -o -name "*.hpp" -o -name "*.c" -o -name "*.h" | \ + grep -v "./build/" | grep -v "./cmake-build" | grep -v "./_deps/" | \ + xargs clang-format --dry-run --Werror + + - name: Comment on style issues + if: failure() + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const { execSync } = require('child_process'); + + try { + // Get list of files that need formatting + const files = execSync('find . -name "*.cpp" -o -name "*.hpp" -o -name "*.c" -o -name "*.h" | grep -v "./build/" | grep -v "./cmake-build" | grep -v "./_deps/"', { encoding: 'utf8' }).trim().split('\n'); + + let comment = '## 🎨 Code Style Issues Found\n\n'; + comment += 'The following files have formatting issues:\n\n'; + + for (const file of files) { + try { + const result = execSync(`clang-format --dry-run --Werror "${file}" 2>&1`, { encoding: 'utf8' }); + } catch (error) { + comment += `- \`${file}\`: Formatting issues detected\n`; + } + } + + comment += '\nPlease run `clang-format -i ` to fix formatting issues.'; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: comment + }); + } catch (error) { + console.log('Could not create comment:', error.message); + } + + code-quality-check: + name: Code quality check with clang-tidy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install clang-tidy + run: | + sudo apt-get update && sudo apt-get -y install clang-tidy + + - name: Create CMake cache + run: | + cmake -S . -B cmake-build-tidy -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + + - name: Run clang-tidy + run: | + # Find all C++ source files + find . -name "*.cpp" -o -name "*.hpp" | \ + grep -v "./build/" | grep -v "./cmake-build" | grep -v "./_deps/" | \ + xargs clang-tidy -p cmake-build-tidy --warnings-as-errors=* --format-style=file || true + + - name: Count warnings and errors + id: count_issues + run: | + # Run clang-tidy and capture output + find . -name "*.cpp" -o -name "*.hpp" | \ + grep -v "./build/" | grep -v "./cmake-build" | grep -v "./_deps/" | \ + xargs clang-tidy -p cmake-build-tidy --format-style=file > tidy_output.txt 2>&1 || true + + # Count errors and warnings + errors=$(grep -c "error:" tidy_output.txt || echo "0") + warnings=$(grep -c "warning:" tidy_output.txt || echo "0") + + echo "errors=$errors" >> $GITHUB_OUTPUT + echo "warnings=$warnings" >> $GITHUB_OUTPUT + + # Fail if more than 3 warnings or any errors + if [ "$errors" -gt 0 ] || [ "$warnings" -gt 3 ]; then + echo "clang-tidy found $errors errors and $warnings warnings" + exit 1 + fi + + - name: Comment on quality issues + if: failure() + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + + try { + let comment = '## 🔍 Code Quality Issues Found\n\n'; + + if (fs.existsSync('tidy_output.txt')) { + const output = fs.readFileSync('tidy_output.txt', 'utf8'); + const lines = output.split('\n'); + + let currentFile = ''; + let hasIssues = false; + + for (const line of lines) { + if (line.includes('error:') || line.includes('warning:')) { + const parts = line.split(':'); + if (parts.length >= 4) { + const file = parts[0]; + const lineNum = parts[1]; + const message = parts.slice(3).join(':').trim(); + + if (file !== currentFile) { + if (hasIssues) comment += '\n'; + comment += `### \`${file}\`\n\n`; + currentFile = file; + hasIssues = true; + } + + const issueType = line.includes('error:') ? '❌ Error' : '⚠️ Warning'; + comment += `- **Line ${lineNum}**: ${issueType} - ${message}\n`; + } + } + } + + if (!hasIssues) { + comment += 'No specific issues found in the output.'; + } + } else { + comment += 'Could not read clang-tidy output.'; + } + + comment += '\n\nPlease review and fix the issues above.'; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: comment + }); + } catch (error) { + console.log('Could not create comment:', error.message); + } From 9dabc0707a0ec21c1fc77586ebf431257921638e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=91=D0=B8=D0=B3=D1=83=D0=BB=D0=BE=D0=B2?= <42319615+bialger@users.noreply.github.com> Date: Fri, 3 Oct 2025 16:24:52 +0300 Subject: [PATCH 12/17] Clarify implicit conversion rules --- docs/reference/types.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/reference/types.md b/docs/reference/types.md index ed1bdc4..12e346c 100644 --- a/docs/reference/types.md +++ b/docs/reference/types.md @@ -220,7 +220,7 @@ fun CanReassign(var str: String): Void { ## Type System Characteristics **Static typing:** Every variable and expression has a type checked at compile time -**No implicit conversions except builtin wrappers:** Explicit casting required between different types, except for conversions between primitive reference types and their corresponding fundamentals. +**Limited implicit conversions:** The compiler only performs implicit conversions between a primitive reference type and its matching fundamental (for example, `Int` ↔ `int`). Any conversion across different primitive families—such as `Int` to `Float` or `Float` to `int`—must use an explicit cast. **Type safety:** Prevents many common errors **Nullable types:** Any reference type (including primitive reference types) can be made nullable by appending `?`. Fundamental types cannot be nullable. @@ -233,11 +233,11 @@ val intVal: int = 42 val floatVal: float = 3.14 val result: float = (intVal as float) + floatVal // OK: Explicit conversion -// Using primitive reference types (implicit conversion) -val refInt: Int = 42 // Implicit conversion from literal -val refFloat: Float = 3.14 // Implicit conversion from literal -val sum: Int = refInt + (refFloat as Int) // Explicit conversion -val fundamentalSum: int = sum + 10 // Implicit: Int + int -> int +// Using primitive reference types (implicit conversion between wrappers and fundamentals) +val refInt: Int = 42 // Implicit conversion from literal to Int +val refFloat: Float = 3.14 // Implicit conversion from literal to Float +val sum: Int = refInt + (refFloat as Int) // Requires explicit narrowing +val fundamentalSum: int = sum + 10 // Implicit: Int -> int when assigning to a fundamental // Converting nullable primitives to fundamentals val nullableInt: Int? = 42 // Implicit conversion from literal From ddbe2a0dceea9e6e442c95e7dd86667552158143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=91=D0=B8=D0=B3=D1=83=D0=BB=D0=BE=D0=B2?= <42319615+bialger@users.noreply.github.com> Date: Fri, 3 Oct 2025 16:38:43 +0300 Subject: [PATCH 13/17] Fix MinGW CI configuration --- .github/workflows/ci_tests.yml | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index bdfafea..ba87a7a 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -10,10 +10,16 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Install MinGW toolchain + shell: powershell + run: | + choco install mingw --yes --no-progress + echo "C:\tools\mingw64\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + - name: Create CMake cache run: | - cmake -S . -B cmake-build-release -DCMAKE_BUILD_TYPE=Release -G "Unix Makefiles" - cmake -S . -B cmake-build-debug -DCMAKE_BUILD_TYPE=Debug -G "Unix Makefiles" + cmake -S . -B cmake-build-release -DCMAKE_BUILD_TYPE=Release -G "MinGW Makefiles" + cmake -S . -B cmake-build-debug -DCMAKE_BUILD_TYPE=Debug -G "MinGW Makefiles" - name: Build main target shell: bash @@ -27,14 +33,15 @@ jobs: - name: Run program working-directory: .\cmake-build-release + shell: bash run: | - .\ovum.exe --help + ./ovum.exe --help - name: Run tests working-directory: .\cmake-build-debug + shell: bash run: | - echo "Currently unable to run tests on Windows Latest MinGW. See https://gitmemories.com/cristianadam/HelloWorld/issues/12 and https://github.com/microsoft/vscode-cmake-tools/issues/2451" - % .\ovum_tests.exe + ./ovum_tests.exe build-matrix: name: Tests and application run on ${{ matrix.config.name }} From 0bdb6360619029a29a4d562f8c066e8fbc77d233 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=91=D0=B8=D0=B3=D1=83=D0=BB=D0=BE=D0=B2?= <42319615+bialger@users.noreply.github.com> Date: Fri, 3 Oct 2025 16:38:51 +0300 Subject: [PATCH 14/17] Improve clang tooling workflows --- .github/workflows/ci_tests.yml | 72 ++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index ba87a7a..f6f25b7 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -142,37 +142,58 @@ jobs: sudo apt-get update && sudo apt-get -y install clang-format - name: Check code style + shell: bash run: | - # Find all C++ source files - find . -name "*.cpp" -o -name "*.hpp" -o -name "*.c" -o -name "*.h" | \ - grep -v "./build/" | grep -v "./cmake-build" | grep -v "./_deps/" | \ - xargs clang-format --dry-run --Werror + mapfile -t files < <(git ls-files '*.c' '*.cpp' '*.h' '*.hpp') + + if [ "${#files[@]}" -eq 0 ]; then + echo "No C/C++ files to check." + exit 0 + fi + + clang-format --dry-run --Werror "${files[@]}" 2>format_output.txt || { + cat format_output.txt + exit 1 + } - name: Comment on style issues - if: failure() + if: failure() && github.event_name == 'pull_request' uses: actions/github-script@v7 with: script: | const fs = require('fs'); const { execSync } = require('child_process'); - + try { // Get list of files that need formatting - const files = execSync('find . -name "*.cpp" -o -name "*.hpp" -o -name "*.c" -o -name "*.h" | grep -v "./build/" | grep -v "./cmake-build" | grep -v "./_deps/"', { encoding: 'utf8' }).trim().split('\n'); - + const rawFiles = execSync('git ls-files "*.c" "*.cpp" "*.h" "*.hpp"', { encoding: 'utf8' }).trim(); + + if (!rawFiles) { + console.log('No files require formatting checks.'); + return; + } + + const files = rawFiles.split('\n'); + let comment = '## 🎨 Code Style Issues Found\n\n'; comment += 'The following files have formatting issues:\n\n'; - + let hasIssues = false; + for (const file of files) { try { const result = execSync(`clang-format --dry-run --Werror "${file}" 2>&1`, { encoding: 'utf8' }); } catch (error) { comment += `- \`${file}\`: Formatting issues detected\n`; + hasIssues = true; } } - - comment += '\nPlease run `clang-format -i ` to fix formatting issues.'; - + + if (!hasIssues) { + comment += 'No files with formatting issues were detected.'; + } else { + comment += '\nPlease run `clang-format -i ` to fix formatting issues.'; + } + github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, @@ -198,35 +219,38 @@ jobs: cmake -S . -B cmake-build-tidy -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=ON - name: Run clang-tidy + shell: bash run: | - # Find all C++ source files - find . -name "*.cpp" -o -name "*.hpp" | \ - grep -v "./build/" | grep -v "./cmake-build" | grep -v "./_deps/" | \ - xargs clang-tidy -p cmake-build-tidy --warnings-as-errors=* --format-style=file || true + mapfile -t files < <(git ls-files '*.c' '*.cpp') + + if [ "${#files[@]}" -eq 0 ]; then + echo "No C/C++ files to analyze." + touch tidy_output.txt + exit 0 + fi + + clang-tidy "${files[@]}" -p cmake-build-tidy --format-style=file > tidy_output.txt 2>&1 || true + touch tidy_output.txt - name: Count warnings and errors id: count_issues run: | - # Run clang-tidy and capture output - find . -name "*.cpp" -o -name "*.hpp" | \ - grep -v "./build/" | grep -v "./cmake-build" | grep -v "./_deps/" | \ - xargs clang-tidy -p cmake-build-tidy --format-style=file > tidy_output.txt 2>&1 || true - # Count errors and warnings errors=$(grep -c "error:" tidy_output.txt || echo "0") warnings=$(grep -c "warning:" tidy_output.txt || echo "0") - + echo "errors=$errors" >> $GITHUB_OUTPUT echo "warnings=$warnings" >> $GITHUB_OUTPUT - + # Fail if more than 3 warnings or any errors if [ "$errors" -gt 0 ] || [ "$warnings" -gt 3 ]; then echo "clang-tidy found $errors errors and $warnings warnings" + cat tidy_output.txt exit 1 fi - name: Comment on quality issues - if: failure() + if: failure() && github.event_name == 'pull_request' uses: actions/github-script@v7 with: script: | From 90cd35da12e51003fdc156af8d1e5fddec2f6078 Mon Sep 17 00:00:00 2001 From: bialger Date: Fri, 3 Oct 2025 16:52:57 +0300 Subject: [PATCH 15/17] refactor: clean up code formatting and organization across multiple files - Adjusted formatting in MyClass.cpp and MyClass.hpp for consistency. - Updated include order in ui_functions.cpp and ui_functions.hpp for clarity. - Reorganized includes in test_functions.hpp and unit_tests.cpp to maintain structure. - Improved comment formatting in ProjectIntegrationTestSuite.hpp. - Disabled test execution in Windows MinGW CI due to compatibility issues. --- .github/workflows/ci_tests.yml | 3 ++- lib/mylib/MyClass.cpp | 3 ++- lib/mylib/MyClass.hpp | 6 +++--- lib/ui/ui_functions.cpp | 2 +- lib/ui/ui_functions.hpp | 6 +++--- tests/test_functions.hpp | 4 ++-- tests/test_suites/ProjectIntegrationTestSuite.hpp | 2 +- tests/unit_tests.cpp | 4 ++-- 8 files changed, 16 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index f6f25b7..e9e1ad3 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -41,7 +41,8 @@ jobs: working-directory: .\cmake-build-debug shell: bash run: | - ./ovum_tests.exe + # ./ovum_tests.exe + echo "Tests are not run on Windows MinGW due to issues with the test runner" build-matrix: name: Tests and application run on ${{ matrix.config.name }} diff --git a/lib/mylib/MyClass.cpp b/lib/mylib/MyClass.cpp index 0fa82bf..7af8b55 100644 --- a/lib/mylib/MyClass.cpp +++ b/lib/mylib/MyClass.cpp @@ -1,6 +1,7 @@ #include "MyClass.hpp" -MyClass::MyClass(std::ostream& out) : out_(out) {} +MyClass::MyClass(std::ostream& out) : out_(out) { +} void MyClass::Print(const std::string& str) { out_ << str; diff --git a/lib/mylib/MyClass.hpp b/lib/mylib/MyClass.hpp index d2e41c0..2847929 100644 --- a/lib/mylib/MyClass.hpp +++ b/lib/mylib/MyClass.hpp @@ -4,13 +4,13 @@ #include class MyClass { - public: +public: explicit MyClass(std::ostream& out); void Print(const std::string& str); - private: +private: std::ostream& out_; }; -#endif //MYCLASS_HPP_ +#endif // MYCLASS_HPP_ diff --git a/lib/ui/ui_functions.cpp b/lib/ui/ui_functions.cpp index d60d467..81859b6 100644 --- a/lib/ui/ui_functions.cpp +++ b/lib/ui/ui_functions.cpp @@ -1,5 +1,5 @@ -#include "ui_functions.hpp" #include "lib/mylib/MyClass.hpp" +#include "ui_functions.hpp" int32_t StartConsoleUI(const std::vector& args, std::ostream& out) { if (args.size() < 2) { diff --git a/lib/ui/ui_functions.hpp b/lib/ui/ui_functions.hpp index 8d06352..5409bec 100644 --- a/lib/ui/ui_functions.hpp +++ b/lib/ui/ui_functions.hpp @@ -1,10 +1,10 @@ #ifndef UI_FUNCTIONS_HPP_ #define UI_FUNCTIONS_HPP_ -#include -#include #include +#include +#include int32_t StartConsoleUI(const std::vector& args, std::ostream& out); -#endif //UI_FUNCTIONS_HPP_ +#endif // UI_FUNCTIONS_HPP_ diff --git a/tests/test_functions.hpp b/tests/test_functions.hpp index 50a78bd..8c154f8 100644 --- a/tests/test_functions.hpp +++ b/tests/test_functions.hpp @@ -1,9 +1,9 @@ #ifndef TESTFUNCTIONS_HPP_ #define TESTFUNCTIONS_HPP_ -#include #include +#include std::vector SplitString(const std::string& str); -#endif //TESTFUNCTIONS_HPP_ \ No newline at end of file +#endif // TESTFUNCTIONS_HPP_ diff --git a/tests/test_suites/ProjectIntegrationTestSuite.hpp b/tests/test_suites/ProjectIntegrationTestSuite.hpp index 1b1d0bf..0ecab1e 100644 --- a/tests/test_suites/ProjectIntegrationTestSuite.hpp +++ b/tests/test_suites/ProjectIntegrationTestSuite.hpp @@ -13,4 +13,4 @@ struct ProjectIntegrationTestSuite : public testing::Test { // special test stru void TearDown() override; // method that is called at the end of every test }; -#endif //TEMPORARYDIRECTORYTESTSUITE_HPP_ +#endif // TEMPORARYDIRECTORYTESTSUITE_HPP_ diff --git a/tests/unit_tests.cpp b/tests/unit_tests.cpp index 8fd17f8..fdee417 100644 --- a/tests/unit_tests.cpp +++ b/tests/unit_tests.cpp @@ -1,8 +1,8 @@ #include #include -#include "test_functions.hpp" // include your library here #include "lib/mylib/MyClass.hpp" +#include "test_functions.hpp" // include your library here TEST(MyLibUnitTestSuite, BasicTest1) { std::ostringstream out; @@ -12,4 +12,4 @@ TEST(MyLibUnitTestSuite, BasicTest1) { ASSERT_EQ(out_by_words.size(), 2); ASSERT_EQ(out_by_words[0], "Hello,"); ASSERT_EQ(out_by_words[1], "World!"); -} \ No newline at end of file +} From 0ad13b365ce845d613545528d55ae39c47572298 Mon Sep 17 00:00:00 2001 From: bialger Date: Fri, 3 Oct 2025 16:56:31 +0300 Subject: [PATCH 16/17] Enhance clang-tidy integration in CI workflow - Improved handling of empty clang-tidy output by ensuring tidy_output.txt is created and readable. - Added checks for empty output to default error and warning counts to zero. - Enhanced output formatting for error and warning counts, ensuring clean integer values. - Added logging for found errors and warnings to improve visibility in CI results. --- .github/workflows/ci_tests.yml | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index e9e1ad3..8d85d62 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -226,23 +226,43 @@ jobs: if [ "${#files[@]}" -eq 0 ]; then echo "No C/C++ files to analyze." - touch tidy_output.txt + echo "" > tidy_output.txt exit 0 fi + echo "Running clang-tidy on ${#files[@]} files..." clang-tidy "${files[@]}" -p cmake-build-tidy --format-style=file > tidy_output.txt 2>&1 || true - touch tidy_output.txt + + # Ensure file exists and is readable + if [ ! -f tidy_output.txt ]; then + echo "" > tidy_output.txt + fi - name: Count warnings and errors id: count_issues run: | - # Count errors and warnings - errors=$(grep -c "error:" tidy_output.txt || echo "0") - warnings=$(grep -c "warning:" tidy_output.txt || echo "0") + # Count errors and warnings - handle empty file case + if [ ! -s tidy_output.txt ]; then + errors=0 + warnings=0 + else + errors=$(grep -c "error:" tidy_output.txt 2>/dev/null || echo "0") + warnings=$(grep -c "warning:" tidy_output.txt 2>/dev/null || echo "0") + fi + + # Ensure we have clean integer values + errors=$(echo "$errors" | tr -d '\n' | head -c 10) + warnings=$(echo "$warnings" | tr -d '\n' | head -c 10) + + # Default to 0 if empty or non-numeric + errors=${errors:-0} + warnings=${warnings:-0} echo "errors=$errors" >> $GITHUB_OUTPUT echo "warnings=$warnings" >> $GITHUB_OUTPUT + echo "Found $errors errors and $warnings warnings" + # Fail if more than 3 warnings or any errors if [ "$errors" -gt 0 ] || [ "$warnings" -gt 3 ]; then echo "clang-tidy found $errors errors and $warnings warnings" From f5f339bf2276a43dae73c09f4dff7623f3f6f277 Mon Sep 17 00:00:00 2001 From: bialger Date: Sun, 5 Oct 2025 22:43:28 +0300 Subject: [PATCH 17/17] docs: update nullable type handling and examples in reference documentation - Removed non-null assertion examples from nullable types documentation to emphasize safe call usage. - Updated design documentation to reflect the removal of non-null assertion from nullable types description. - Modified object model documentation to replace non-null assertions with safe call alternatives in casting examples, enhancing clarity on safe operations. --- docs/reference/builtin_types.md | 3 +-- docs/reference/design.md | 2 +- docs/reference/object_model.md | 10 +++++----- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/reference/builtin_types.md b/docs/reference/builtin_types.md index 6565b19..2ace559 100644 --- a/docs/reference/builtin_types.md +++ b/docs/reference/builtin_types.md @@ -36,13 +36,12 @@ val hash: Int = greeting.GetHash() Any primitive type can be made nullable by appending `?` (e.g., `Int?`, `String?`). Nullable types are passed by reference and can hold either a value or `null`. -**Important**: You cannot directly call methods on nullable types using `.` - you must use the safe call operator `?.` or non-null assertion `!!`. +**Important**: You cannot directly call methods on nullable types using `.` - you must use the safe call operator `?.`. ```ovum val nullableString: String? = "Hello" // val length: Int = nullableString.Length() // ERROR: Cannot call method directly on nullable val safeLength: Int = nullableString?.Length() ?: 0 // Correct: Use safe call -val forcedLength: Int = nullableString!!.Length() // Correct: Use non-null assertion ``` ## Array Types diff --git a/docs/reference/design.md b/docs/reference/design.md index 9086ba0..461ce30 100644 --- a/docs/reference/design.md +++ b/docs/reference/design.md @@ -16,7 +16,7 @@ Ovum's core design principles center around: ## Key Design Points * **Strong static typing** with **immutability by default** (`var` required for mutation). -* **Nullable types** and Kotlin-style null-handling: `Type?`, safe calls `?.`, Elvis `?:`, non-null assertion `!!`. +* **Nullable types** and Kotlin-style null-handling: `Type?`, safe calls `?.`, Elvis `?:`. * **Pure functions** (no side effects, VM-level result caching). * **Classes & interfaces** diff --git a/docs/reference/object_model.md b/docs/reference/object_model.md index 75fa1f9..19a175b 100644 --- a/docs/reference/object_model.md +++ b/docs/reference/object_model.md @@ -68,7 +68,7 @@ class Point implements IComparable { public override fun IsLess(other: Object): Bool { if (!(other is Point)) return false - val p: Point = (other as Point)!! + val p: Point = (other as Point) ?: Point(0, 0) if (this.X != p.X) return this.X < p.X return this.Y < p.Y } @@ -289,13 +289,13 @@ val comparable: IComparable = point // Upcast to interface // Downcasting with type test if (obj is Point) { - val p: Point = (obj as Point)!! // Safe after type test + val p: Point = (obj as Point) ?: Point(0, 0) sys::Print("Point: " + p.ToString()) } // Type test operator if (shape is ColoredRectangle) { - val rect: ColoredRectangle = (shape as ColoredRectangle)!! + val rect: ColoredRectangle = (shape as ColoredRectangle) ?: ColoredRectangle(0, 0, "red") sys::Print("Rectangle color: " + rect.GetColor()) } ``` @@ -383,7 +383,7 @@ class ResourceManager { **Type Safety:** - Use type tests before casting (`is` before `as`) -- Prefer safe operations (`?.` and `?:` over `!!`) +- Prefer safe operations - Handle nullable types properly ```ovum @@ -395,7 +395,7 @@ interface IWritable { fun Write(content: String): Void } fun SafeProcessObject(obj: Object?): Void { val result: String = obj?.ToString() ?: "null" if (obj is Person) { - val person: Person = (obj as Person)!! + val person: Person = (obj as Person) ?: Person("Unknown") sys::Print("Person: " + person.ToString()) } }