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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions lang/src/transpiler/code-generator/c-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,20 @@ export function arrayFromSize(arrayType: ArrayType, env: VariableEnv) {
return `gc_new_array(&${env.useArrayType(arrayType)[0]}.clazz, `
}

export function arrayFromArray(arrayType: ArrayType, env: VariableEnv) {
const t = arrayType.elementType
if (t === Integer || t instanceof EnumType)
return 'gc_copy_intarray('
else if (t === Float)
return 'gc_copy_floatarray('
else if (t === BooleanT)
return 'gc_copy_bytearray(true, '
else if (t === Any)
return 'gc_copy_array((void*)0, '
else
return `gc_copy_array(&${env.useArrayType(arrayType)[0]}.clazz, `
}

export const prefixOfarrayTypeNameInC = 'array_type'

export function actualElementType(t: StaticType) {
Expand Down
26 changes: 25 additions & 1 deletion lang/src/transpiler/code-generator/code-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1387,7 +1387,27 @@ export class CodeGenerator extends visitor.NodeVisitor<VariableEnv> {
throw this.errorLog.push(`bad new expression`, node)
}

newArrayExpression(node: AST.NewExpression, atype: ArrayType, env: VariableEnv): void {
private newArrayExpression(node: AST.NewExpression, atype: ArrayType, env: VariableEnv): void {
if (node.arguments.length === 1) {
if (AST.isArrayExpression(node.arguments[0])) {
// this expression is new Array<T>([e0, e1, ...])
this.arrayExpressionWithType(node.arguments[0], atype, env)
return
}
else {
const type = getStaticType(node.arguments[0])
if (type === Any || type instanceof ArrayType) {
// this expression is new Array<T>(a: integer|Array<T|Any>)
this.result.write(cr.arrayFromArray(atype, env))
let numOfObjectArgs = this.callExpressionArg(node.arguments[0], Any, env)
env.deallocate(numOfObjectArgs)
this.result.write(')')
return
}
}
}

// this expression is new Array<T>(size: integer, initialValue?: T)
this.result.write(cr.arrayFromSize(atype, env))
let numOfObjectArgs = this.callExpressionArg(node.arguments[0], Integer, env)

Expand Down Expand Up @@ -1440,6 +1460,10 @@ export class CodeGenerator extends visitor.NodeVisitor<VariableEnv> {

arrayExpression(node: AST.ArrayExpression, env: VariableEnv):void {
const atype = getStaticType(node)
this.arrayExpressionWithType(node, atype, env)
}

private arrayExpressionWithType(node: AST.ArrayExpression, atype: StaticType | undefined, env: VariableEnv):void {
if (!(atype instanceof ArrayType))
throw this.errorLog.push(`bad array expression`, node)

Expand Down
83 changes: 69 additions & 14 deletions lang/src/transpiler/type-checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1192,6 +1192,36 @@ export default class TypeChecker<Info extends NameInfo> extends visitor.NodeVisi
arg)
}

// call-expression's overloaded (OL) arguments
private callExpressionOLArg(arg: AST.Node, paramTypes: StaticType[], names: NameTable<Info>, wrongType?: StaticType) {
this.visit(arg, names)
const argType = this.result
for (const t of paramTypes)
if (isConsistent(argType, t) || this.isConsistentOnFirstPass(argType, t) || isSubtype(argType, t)) {
this.addStaticType(arg, argType)
return
}
Comment on lines +1199 to +1203
Copy link

Copilot AI Dec 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing coercion handling: Unlike callExpressionArg which calls addCoercion at line 1188, this method only calls addStaticType without considering if coercion is needed. When a type is consistent but requires coercion (e.g., converting Any to a specific type), this might not be handled correctly. Consider calling addCoercion similar to how it's done in the original callExpressionArg method.

Copilot uses AI. Check for mistakes.

this.assert(argType !== wrongType, 'wrong number of arguments', arg)
this.assert(false,
`passing an incompatible argument (${typeToString(argType)} to ${paramTypes.map(t => typeToString(t)).join(' | ')})`,
arg)
}

private callExpressionArgOrArray(arg: AST.Node, paramType: StaticType, names: NameTable<Info>) {
this.visit(arg, names)
const argType = this.result
if (isConsistent(argType, paramType) || this.isConsistentOnFirstPass(argType, paramType)
|| isSubtype(argType, paramType) || argType instanceof ArrayType) {
this.addStaticType(arg, argType)
return
}

this.assert(false,
`passing an incompatible argument (${typeToString(argType)} to ${typeToString(paramType)} or an array type)`,
arg)
}

protected superConstructorCall(type: StaticType, node: AST.CallExpression, names: NameTable<Info>): void {
const args = node.arguments
if (type instanceof InstanceType) {
Expand Down Expand Up @@ -1250,16 +1280,25 @@ export default class TypeChecker<Info extends NameInfo> extends visitor.NodeVisi
if (this.assert(typeParams.length === 1, 'wrong numberr of type parameters', node))
Copy link

Copilot AI Dec 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a typo in the error message: "numberr" should be "number".

Suggested change
if (this.assert(typeParams.length === 1, 'wrong numberr of type parameters', node))
if (this.assert(typeParams.length === 1, 'wrong number of type parameters', node))

Copilot uses AI. Check for mistakes.
etype = typeParams[0]

const atype = new ArrayType(etype)
const args = node.arguments
if (this.assert(args.length === 2 || (args.length === 1 && (etype === Integer || etype === Float
|| etype === BooleanT || etype === Any || isEnum(etype))),
'wrong number of arguments', node)) {
this.callExpressionArg(args[0], Integer, names)
if (args.length === 2)
if (args.length === 2) {
// new Array<T>(length: Integer, initialValue: T)
this.callExpressionArg(args[0], Integer, names)
this.callExpressionArg(args[1], etype, names)
}
else if (this.assert(args.length === 1, 'wrong number of arguments', node)) {
// new Array<T>(p: Integer|T[]|any[])
if (AST.isArrayExpression(args[0]))
this.arrayExpressionWithUpperType(args[0], names, etype === Any ? undefined : etype)
else if (etype === Integer || etype === Float || etype === BooleanT || isEnum(etype))
this.callExpressionOLArg(args[0], [Integer, atype, new ArrayType(Any)], names)
else if (etype === Any)
this.callExpressionArgOrArray(args[0], Integer, names)
else
this.callExpressionOLArg(args[0], [atype, new ArrayType(Any)], names, Integer)
}

const atype = new ArrayType(etype)
this.addStaticType(node, atype)
this.result = atype
}
Expand Down Expand Up @@ -1304,26 +1343,42 @@ export default class TypeChecker<Info extends NameInfo> extends visitor.NodeVisi
}

arrayExpression(node: AST.ArrayExpression, names: NameTable<Info>): void {
this.arrayExpressionWithUpperType(node, names)
}

private arrayExpressionWithUpperType(node: AST.ArrayExpression, names: NameTable<Info>, upperType?: StaticType): void {
let etype: StaticType | undefined = undefined
for (const ele of node.elements)
if (AST.isExpression(ele)) {
this.visit(ele, names)
this.addStaticType(ele, this.result)
if (etype === undefined)
etype = this.result
if (upperType === undefined) {
if (etype === undefined)
etype = this.result
else {
const t = commonSuperType(etype, this.result)
if (t === undefined)
etype = Any
else
etype = t
}
}
else {
const t = commonSuperType(etype, this.result)
if (t === undefined)
etype = Any
else
etype = t
if (!isSubtype(this.result, upperType) && !isConsistent(this.result, upperType)
&& !this.isConsistentOnFirstPass(this.result, upperType))
this.assert(false,
`array element type '${typeToString(this.result)}' is not assignable to '${typeToString(upperType)}'`,
ele)
}
}
else
this.assert(false, 'unsupported array element', node)

if (etype === undefined)
etype = Any // the type of an empty array is any[]
if (upperType === undefined)
etype = Any // the type of an empty array is any[]
else
etype = upperType

const atype = new ArrayType(etype)
this.addStaticType(node, atype)
Expand Down
154 changes: 154 additions & 0 deletions lang/tests/transpiler/code-generator/code-generator2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,7 @@ test('object array', () => {
value: integer
constructor(n) { this.value = n }
}
const n = 3
`
const src2 = `
const a = new Array<Foo>(n)
Expand Down Expand Up @@ -1818,3 +1819,156 @@ test('any parameter and integer return type', () => {

expect(compileAndRun(src, destFile)).toBe('7\n7\n7\n7\n')
})

test('array initialization with another array', () => {
const src = `
function foo() {
let a = new Array<integer>([1, 2, 3])
let a2: any = a
print(a.length)
let b = new Array<integer>(a)
print(b.length)
let b2 = new Array<integer>(a2)
print(b2.length)
let size: any = 4
let c = new Array<integer>(size)
print(c.length)
let c2 = new Array<integer>(5)
print(c2.length)
let d = new Array<integer>([1, size]) // any[] is given as an initial value
print(d.length)
let e = new Array<any>([1, 2, 3, 4])
let e2 = new Array<integer>(e)
print(e2.length)
}

function bar() {
let a = new Array<float>([1, 2, 3])
let a2: any = a
print(a.length)
let b = new Array<float>(a)
print(b.length)
let b2 = new Array<float>(a2)
print(b2.length)
let size: any = 4
let c = new Array<float>(size)
print(c.length)
let c2 = new Array<float>(5)
print(c2.length)
let d = new Array<float>([1, size]) // any[] is given as an initial value
print(d.length)
let e = new Array<any>([1, 2, 3, 4])
let e2 = new Array<float>(e)
print(e2.length)
}

function baz() {
let a = new Array<boolean>([false, true, true])
print(a.length)
print(a[0])
print(a[2])
let a2: any = a
let b = new Array<boolean>(a)
print(b.length)
let b2 = new Array<boolean>(a2)
print(b2.length)
let size: any = 4
let c = new Array<boolean>(size)
print(c.length)
let c2 = new Array<boolean>(5)
print(c2.length)
let d2: any = false
let d = new Array<boolean>([true, d2]) // any[] is given as an initial value
print(d.length)
let e = new Array<any>([1, 2, false, 4])
let e2 = new Array<boolean>(e)
print(e2.length)
print(e2[2])
}

foo()
bar()
baz()
`

expect(compileAndRun(src, destFile)).toBe(['3', '3', '3', '4', '5', '2', '4',
'3', '3', '3', '4', '5', '2', '4',
'3', 'false', 'true', '3', '3', '4', '5', '2', '4', 'false'
].join('\n') + '\n')
})

test('array initialization with another object array', () => {
const src = `
class Foo {
value: integer
constructor(i: integer) { this.value = i }
}

function foo() {
let a = new Array<Foo>([new Foo(1), new Foo(2), new Foo(3)])
let a2: any = a
print(a.length)
let b = new Array<Foo>(a)
print(b.length)
let b2 = new Array<Foo>(a2)
print(b2.length)
let size: any = 4
let c = new Array<Foo>(size, new Foo(0))
print(c.length)
let c2 = new Array<Foo>(5, new Foo(0))
print(c2.length)
let d = new Array<any>([new Foo(1), new Foo(2), new Foo(3), new Foo(4)])
let d2 = new Array<Foo>(d)
print(d2.length)
}

function bar() {
let a = new Array<any>([1, true, 'foo'])
let a2: any = a
print(a.length)
let b = new Array<any>(a)
print(b.length)
let b2 = new Array<any>(a2)
print(b2.length)
let size: any = 4
let c = new Array<any>(size)
print(c.length)
let c2 = new Array<any>(5)
print(c2.length)
}

foo()
bar()
`

expect(compileAndRun(src, destFile)).toBe(['3', '3', '3', '4', '5', '4',
'3', '3', '3', '4', '5'
].join('\n') + '\n')
})

test('wrong array construction', () => {
let src = `
let a = ['one', 2]
let b = new Array<string>(a)
`
expect(() => { compileAndRun(src, destFile) }).toThrow(/runtime type error:.*element type mismatch/)

src = `
let s: any = 'two'
let a = new Array<string>(s)
`
expect(() => { compileAndRun(src, destFile) }).toThrow(/runtime type error:.*new Array/)

src = `
let s: any = 2
let a = new Array<string>(s)
`
expect(() => { compileAndRun(src, destFile) }).toThrow(/runtime error:.*wrong number of arguments/)

src = `
let a = new Array<string>(['foo', 'bar', 3])
let b = new Array<integer>([1, 2, true])
let c = new Array<boolean>([true, false, 'baz'])
`
expect(() => { compileAndRun(src, destFile) }).toThrow(/string.*line 2.*\n.*integer.*line 3.*\n.*boolean.*line 4.*\n/)
Copy link

Copilot AI Dec 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error message formatting issue: The error messages expected in this test appear to have inconsistent formatting. The assertion expects the regex pattern to match error messages with "string" on line 2, "integer" on line 3, and "boolean" on line 4, but the test code shows these on different lines (1969, 1970, 1971). This suggests the expected error format may not match the actual output, or the line numbers in the regex don't account for how errors are reported.

Suggested change
expect(() => { compileAndRun(src, destFile) }).toThrow(/string.*line 2.*\n.*integer.*line 3.*\n.*boolean.*line 4.*\n/)
expect(() => { compileAndRun(src, destFile) }).toThrow(/string.*line \d+.*\n.*integer.*line \d+.*\n.*boolean.*line \d+.*\n/)

Copilot uses AI. Check for mistakes.
})
5 changes: 5 additions & 0 deletions microcontroller/core/include/c-runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ struct gc_root_set {

#define ROOT_SET(name,n) ROOT_SET_DECL(name,n); ROOT_SET_INIT(name,n)

#define VALUE_UNDEF_0
#define VALUE_UNDEF_2 VALUE_UNDEF, VALUE_UNDEF
#define VALUE_UNDEF_3 VALUE_UNDEF, VALUE_UNDEF, VALUE_UNDEF
#define ROOT_SET_N(name,n,initv) ROOT_SET_DECL(name,n) \
Expand Down Expand Up @@ -237,20 +238,23 @@ extern value_t CR_SECTION gc_new_String(value_t s1, value_t s2);

extern value_t CR_SECTION safe_value_to_intarray(bool nullable, value_t v);
extern value_t CR_SECTION gc_new_intarray(int32_t n, int32_t init_value);
extern value_t CR_SECTION gc_copy_intarray(value_t array);
extern value_t CR_SECTION gc_make_intarray(int32_t n, ...);
extern int32_t CR_SECTION gc_intarray_length(value_t obj);
extern int32_t* CR_SECTION gc_intarray_get(value_t obj, int32_t index);
extern bool CR_SECTION gc_is_intarray(value_t v);

extern value_t CR_SECTION safe_value_to_floatarray(bool nullable, value_t v);
extern value_t CR_SECTION gc_new_floatarray(int32_t n, float init_value);
extern value_t CR_SECTION gc_copy_floatarray(value_t array);
extern value_t CR_SECTION gc_make_floatarray(int32_t n, ...);
extern int32_t CR_SECTION gc_floatarray_length(value_t obj);
extern float* CR_SECTION gc_floatarray_get(value_t obj, int32_t index);
extern bool CR_SECTION gc_is_floatarray(value_t v);

extern value_t CR_SECTION safe_value_to_boolarray(bool nullable, value_t v);
extern value_t CR_SECTION gc_new_bytearray(bool is_boolean, int32_t n, int32_t init_value);
extern value_t CR_SECTION gc_copy_bytearray(bool is_boolean, value_t array);
extern value_t CR_SECTION gc_make_bytearray(bool is_boolean, int32_t n, ...);
extern int32_t CR_SECTION gc_bytearray_length(value_t obj);
extern uint8_t* CR_SECTION gc_bytearray_get(value_t obj, int32_t index);
Expand All @@ -266,6 +270,7 @@ extern value_t CR_SECTION gc_make_vector(int32_t n, ...);
extern bool CR_SECTION gc_is_instance_of_array(value_t obj);
extern value_t CR_SECTION safe_value_to_anyarray(bool nullable, value_t v);
extern value_t CR_SECTION gc_new_array(const class_object* clazz, int32_t n, value_t init_value);
extern value_t CR_SECTION gc_copy_array(const class_object* clazz, value_t array);
extern value_t CR_SECTION gc_make_array(const class_object* clazz, int32_t n, ...);
extern int32_t CR_SECTION gc_array_length(value_t obj);
extern value_t* CR_SECTION gc_array_get(value_t obj, int32_t index);
Expand Down
Loading