Skip to content

Commit 8d56f75

Browse files
committed
Full enum support for Rust-like enums with anonymous structs or tuples and enums that are promoted constants.
1 parent 383e6a1 commit 8d56f75

File tree

16 files changed

+1936
-472
lines changed

16 files changed

+1936
-472
lines changed

Readme.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ The toolchain transforms Rust code into executable `.jar` files through several
2828
This backend currently supports a subset of Rust features:
2929

3030
* ✅ Compiling minimal `no_std` & `no_core` Rust programs (like an empty `main`) using the `jvm-unknown-unknown` target.
31-
* ✅ Compiling simple programs using basic `core` features (like the `is_even_plus_one` test) using the host target.
31+
* ✅ Compiling simple programs using basic `core` features (like other tests) using the host target but this codegen backend to produce JVM bytecode.
3232
* ✅ Basic integer arithmetic operations on all types of numbers:
3333
* Addition (`+`), Subtraction (`-`), Multiplication (`*`), Division (`/`), Remainder (`%`).
3434
* Checked addition, subtraction and multiplication returning `(result, overflowed_bool)` tuples (occurs in debug mode)
@@ -43,7 +43,7 @@ This backend currently supports a subset of Rust features:
4343
* ✅ Variable assignment including subfield and array index assignment, including nesting.
4444
* ✅ Arrays and slices, including inserting, accessing and mutating at a given index (supporting nesting).
4545
* ✅ Floats (`f32`, `f64`).
46-
* ✅ Structs, Tuples and Enums (currently only C-like enums, no anonymous structs or tuples yet) including nesting access/setting/mutation of fields.
46+
* ✅ Structs, Tuples and Enums (including traditional C-like enums but also Rust-like enums with anonymous structs and tuples in them) including nested access/setting/mutation of fields and array indices within these.
4747
* ✅ Generating executable `.jar` files for binary crates.
4848

4949
### Next Milestone:

library/src/main/kotlin/org/rustlang/core/Core.kt

+94-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package org.rustlang.core
22

3+
import java.lang.reflect.Field
4+
import java.util.Objects
5+
36
/**
47
* Core functions needed by the Rust JVM backend stdlib shim.
58
*/
@@ -65,21 +68,108 @@ public object Core {
6568

6669
@JvmStatic
6770
public fun eq(value1: Any?, value2: Any?): Boolean {
68-
// Check for equality.
69-
return value1 == value2
71+
// 1. Identity and Null checks
72+
if (value1 === value2) { // Same reference or both null
73+
return true
74+
}
75+
if (value1 == null || value2 == null) { // One is null, the other isn't
76+
return false
77+
}
78+
79+
// 2. Handle common Kotlin/Java types where '==' gives value equality
80+
// or specific content checks are needed.
81+
val class1 = value1::class.java
82+
val class2 = value2::class.java
83+
84+
if (class1 != class2) {
85+
// Optimization: If classes are different, they generally can't be equal
86+
// unless dealing with complex inheritance/interface scenarios not typical here.
87+
// We might miss edge cases like comparing an Int to a Long with the same value,
88+
// but Rust's PartialEq usually requires the types to be the same anyway.
89+
return false
90+
}
91+
92+
// Primitives/Wrappers and String -> rely on Kotlin's == (which calls .equals)
93+
if (value1 is Number || value1 is String || value1 is Boolean || value1 is Char) {
94+
return value1 == value2 // Uses overridden equals for these types
95+
}
96+
97+
// Array Types -> use contentEquals
98+
if (class1.isArray) {
99+
return when (value1) {
100+
is BooleanArray -> value1.contentEquals(value2 as BooleanArray)
101+
is ByteArray -> value1.contentEquals(value2 as ByteArray)
102+
is CharArray -> value1.contentEquals(value2 as CharArray)
103+
is ShortArray -> value1.contentEquals(value2 as ShortArray)
104+
is IntArray -> value1.contentEquals(value2 as IntArray)
105+
is LongArray -> value1.contentEquals(value2 as LongArray)
106+
is FloatArray -> value1.contentEquals(value2 as FloatArray)
107+
is DoubleArray -> value1.contentEquals(value2 as DoubleArray)
108+
is Array<*> -> value1.contentEquals(value2 as Array<*>) // For object arrays
109+
else -> false // Should not happen if class1.isArray is true
110+
}
111+
}
112+
113+
// 3. Custom Generated Types (Heuristic: Not standard Java/Kotlin & same class)
114+
// This assumes generated classes are not in standard packages.
115+
val packageName = class1.packageName ?: ""
116+
if (!packageName.startsWith("java.") && !packageName.startsWith("kotlin.")) {
117+
try {
118+
// Get all declared fields (including private) from the class definition
119+
val fields: Array<Field> = class1.declaredFields
120+
121+
for (field in fields) {
122+
// Allow accessing private fields for comparison
123+
field.isAccessible = true
124+
125+
// Recursively compare the field values
126+
val fieldValue1 = field.get(value1)
127+
val fieldValue2 = field.get(value2)
128+
129+
// Recursive Call
130+
if (!eq(fieldValue1, fieldValue2)) {
131+
// If any field is not equal, the objects are not equal
132+
return false
133+
}
134+
}
135+
// If all fields were equal, the objects are equal
136+
return true
137+
} catch (e: Exception) {
138+
// Log the exception if needed (e.g., IllegalAccessException)
139+
System.err.println("Reflection error during eq: ${e.message}")
140+
// Fallback: If reflection fails, assume not equal for safety
141+
return false
142+
}
143+
}
144+
145+
// 4. Fallback: If none of the above, assume not equal.
146+
return false
70147
}
71148

72149
@JvmStatic
73-
public fun core_panicking_panic(message: String?) {
150+
public fun core_panic(message: String?) {
74151
// This is a placeholder for the panic function.
75152
// In a real implementation, this would handle the panic appropriately.
76153
throw RuntimeException("Rust panic: " + (message ?: "<no message>"))
77154
}
78155

79156
@JvmStatic
80-
public fun core_panicking_assert_failed(message: String?) {
157+
public fun core_assert_failed(message: String?) {
81158
// This is a placeholder for the assert failed function.
82159
// In a real implementation, this would handle the assertion failure appropriately.
83160
throw AssertionError("Rust assertion failed: " + (message ?: "<no message>"))
84161
}
162+
163+
@JvmStatic
164+
public fun deref(value: Any?): Any? {
165+
// This is a placeholder for dereferencing.
166+
return value
167+
}
168+
169+
@JvmStatic
170+
fun core_starts_with(value: Any, prefixChar: Int): Boolean {
171+
// Convert int to char/String and call original or implement directly
172+
return value.toString().startsWith(Char(prefixChar).toString())
173+
}
174+
85175
}

shim-metadata-gen/core.json

+11-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
"descriptor": "([Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;",
88
"is_static": true
99
},
10+
"core_assert_failed": {
11+
"descriptor": "(Ljava/lang/String;)V",
12+
"is_static": true
13+
},
1014
"core_fmt_rt_argument_new_display": {
1115
"descriptor": "(Ljava/lang/Object;)Ljava/lang/String;",
1216
"is_static": true
@@ -23,12 +27,16 @@
2327
"descriptor": "(I)Ljava/lang/String;",
2428
"is_static": true
2529
},
26-
"core_panicking_assert_failed": {
30+
"core_panic": {
2731
"descriptor": "(Ljava/lang/String;)V",
2832
"is_static": true
2933
},
30-
"core_panicking_panic": {
31-
"descriptor": "(Ljava/lang/String;)V",
34+
"core_starts_with": {
35+
"descriptor": "(Ljava/lang/Object;I)Z",
36+
"is_static": true
37+
},
38+
"deref": {
39+
"descriptor": "(Ljava/lang/Object;)Ljava/lang/Object;",
3240
"is_static": true
3341
},
3442
"eq": {

src/lower1/control_flow.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ pub fn convert_basic_block<'tcx>(
102102
tcx,
103103
mir,
104104
data_types,
105+
&mut instructions,
105106
);
106107
instructions.push(oomir::Instruction::Return {
107108
operand: Some(return_operand),
@@ -116,7 +117,7 @@ pub fn convert_basic_block<'tcx>(
116117
}
117118
TerminatorKind::SwitchInt { discr, targets, .. } => {
118119
// --- GENERAL SwitchInt Handling ---
119-
let discr_operand = convert_operand(discr, tcx, mir, data_types);
120+
let discr_operand = convert_operand(discr, tcx, mir, data_types, &mut instructions);
120121
// Get the actual type of the discriminant from MIR local declarations
121122
let discr_ty = discr.ty(&mir.local_decls, tcx);
122123

@@ -159,7 +160,7 @@ pub fn convert_basic_block<'tcx>(
159160
let function_name = make_jvm_safe(format!("{:?}", func).as_str()); // Get function name - needs refinement to extract actual name
160161
let oomir_args = args
161162
.iter()
162-
.map(|arg| convert_operand(&arg.node, tcx, mir, data_types))
163+
.map(|arg| convert_operand(&arg.node, tcx, mir, data_types, &mut instructions))
163164
.collect();
164165
let dest = Some(format!("{:?}", destination.local));
165166

@@ -219,7 +220,8 @@ pub fn convert_basic_block<'tcx>(
219220
cond
220221
);
221222
// Condition is likely a constant itself
222-
condition_operand = convert_operand(cond, tcx, mir, data_types);
223+
condition_operand =
224+
convert_operand(cond, tcx, mir, data_types, &mut instructions);
223225
}
224226
// --- End of condition operand handling ---
225227

src/lower1/control_flow/rvalue.rs

+22-9
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ pub fn convert_rvalue_to_operand<'a>(
5959
}
6060
MirOperand::Constant(_) => {
6161
// Constant is already an operand, no extra instructions
62-
result_operand = convert_operand(mir_operand, tcx, mir, data_types);
62+
result_operand =
63+
convert_operand(mir_operand, tcx, mir, data_types, &mut instructions);
6364
}
6465
}
6566
}
@@ -71,7 +72,8 @@ pub fn convert_rvalue_to_operand<'a>(
7172

7273
if let rustc_middle::ty::TyKind::Array(elem_ty, _) = place_ty.kind() {
7374
let oomir_elem_type = ty_to_oomir_type(elem_ty.clone(), tcx, data_types);
74-
let oomir_elem_op = convert_operand(element_op, tcx, mir, data_types);
75+
let oomir_elem_op =
76+
convert_operand(element_op, tcx, mir, data_types, &mut instructions);
7577
let array_size = match len_const.kind() {
7678
ConstKind::Value(val) => {
7779
/* ... extract size ... */
@@ -80,6 +82,7 @@ pub fn convert_rvalue_to_operand<'a>(
8082
tcx.valtree_to_const_val(val),
8183
&val.ty(),
8284
tcx,
85+
data_types,
8386
))
8487
.unwrap_or(0)
8588
} // Simplified extraction
@@ -145,7 +148,7 @@ pub fn convert_rvalue_to_operand<'a>(
145148
let oomir_target_type = ty_to_oomir_type(*target_mir_ty, tcx, data_types);
146149
let source_mir_ty = operand.ty(&mir.local_decls, tcx);
147150
let oomir_source_type = ty_to_oomir_type(source_mir_ty, tcx, data_types);
148-
let oomir_operand = convert_operand(operand, tcx, mir, data_types);
151+
let oomir_operand = convert_operand(operand, tcx, mir, data_types, &mut instructions);
149152

150153
if oomir_target_type == oomir_source_type {
151154
println!("Info: Handling Rvalue::Cast (Same OOMIR Types) -> Temp Move.");
@@ -169,8 +172,8 @@ pub fn convert_rvalue_to_operand<'a>(
169172

170173
Rvalue::BinaryOp(bin_op, box (op1, op2)) => {
171174
let temp_binop_var = generate_temp_var_name(&base_temp_name);
172-
let oomir_op1 = convert_operand(op1, tcx, mir, data_types);
173-
let oomir_op2 = convert_operand(op2, tcx, mir, data_types);
175+
let oomir_op1 = convert_operand(op1, tcx, mir, data_types, &mut instructions);
176+
let oomir_op2 = convert_operand(op2, tcx, mir, data_types, &mut instructions);
174177
// Determine result type based on operands or destination hint
175178
let oomir_result_type = get_place_type(original_dest_place, mir, tcx, data_types);
176179

@@ -349,7 +352,8 @@ pub fn convert_rvalue_to_operand<'a>(
349352

350353
Rvalue::UnaryOp(operation, operand) => {
351354
let temp_unop_var = generate_temp_var_name(&base_temp_name);
352-
let oomir_src_operand = convert_operand(operand, tcx, mir, data_types);
355+
let oomir_src_operand =
356+
convert_operand(operand, tcx, mir, data_types, &mut instructions);
353357
// Determine result type (often same as operand, except for PtrMetadata)
354358
let oomir_result_type = match operation {
355359
UnOp::PtrMetadata => oomir::Type::I32,
@@ -474,7 +478,8 @@ pub fn convert_rvalue_to_operand<'a>(
474478
};
475479
let element_oomir_type =
476480
ty_to_oomir_type(element_mir_ty.clone(), tcx, data_types);
477-
let value_operand = convert_operand(mir_op, tcx, mir, data_types);
481+
let value_operand =
482+
convert_operand(mir_op, tcx, mir, data_types, &mut instructions);
478483
instructions.push(oomir::Instruction::SetField {
479484
object_var: temp_aggregate_var.clone(),
480485
field_name,
@@ -500,7 +505,8 @@ pub fn convert_rvalue_to_operand<'a>(
500505
});
501506
// Store elements into the temporary array
502507
for (i, mir_operand) in operands.iter().enumerate() {
503-
let value_operand = convert_operand(mir_operand, tcx, mir, data_types);
508+
let value_operand =
509+
convert_operand(mir_operand, tcx, mir, data_types, &mut instructions);
504510
let index_operand =
505511
oomir::Operand::Constant(oomir::Constant::I32(i as i32));
506512
instructions.push(oomir::Instruction::ArrayStore {
@@ -530,7 +536,13 @@ pub fn convert_rvalue_to_operand<'a>(
530536
let field_name = field_def.ident(tcx).to_string();
531537
let field_mir_ty = field_def.ty(tcx, substs);
532538
let field_oomir_type = ty_to_oomir_type(field_mir_ty, tcx, data_types);
533-
let value_operand = convert_operand(mir_operand, tcx, mir, data_types);
539+
let value_operand = convert_operand(
540+
mir_operand,
541+
tcx,
542+
mir,
543+
data_types,
544+
&mut instructions,
545+
);
534546
instructions.push(oomir::Instruction::SetField {
535547
object_var: temp_aggregate_var.clone(),
536548
field_name,
@@ -671,6 +683,7 @@ pub fn convert_rvalue_to_operand<'a>(
671683
tcx,
672684
mir,
673685
data_types,
686+
&mut instructions,
674687
);
675688
instructions.push(oomir::Instruction::SetField {
676689
object_var: temp_aggregate_var.clone(),

0 commit comments

Comments
 (0)