Skip to content

Implement resolveBinaryExpression #689

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
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
68 changes: 60 additions & 8 deletions src/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
Field,
FieldPrototype,
Global,
TypeDefinition
TypeDefinition, TypedElement
} from "./program";

import {
Expand Down Expand Up @@ -1188,6 +1188,30 @@ export class Resolver extends DiagnosticEmitter {
return null;
}

private binaryOperatorKindFromToken(token: Token): OperatorKind {
switch (token) {
case Token.PLUS: return OperatorKind.ADD;
case Token.MINUS: return OperatorKind.MINUS;
case Token.ASTERISK: return OperatorKind.MUL;
case Token.SLASH: return OperatorKind.DIV;
case Token.PERCENT: return OperatorKind.REM;
case Token.ASTERISK_ASTERISK: return OperatorKind.POW;
case Token.AMPERSAND: return OperatorKind.BITWISE_AND;
case Token.BAR: return OperatorKind.BITWISE_OR;
case Token.CARET: return OperatorKind.BITWISE_XOR;
case Token.LESSTHAN_LESSTHAN: return OperatorKind.BITWISE_SHL;
case Token.GREATERTHAN_GREATERTHAN: return OperatorKind.BITWISE_SHR;
case Token.GREATERTHAN_GREATERTHAN_GREATERTHAN: return OperatorKind.BITWISE_SHR_U;
case Token.EQUALS_EQUALS: return OperatorKind.EQ;
case Token.EXCLAMATION_EQUALS: return OperatorKind.NE;
case Token.GREATERTHAN: return OperatorKind.GT;
case Token.GREATERTHAN_EQUALS: return OperatorKind.GE;
case Token.LESSTHAN: return OperatorKind.LT;
case Token.LESSTHAN_EQUALS: return OperatorKind.LE;
default: return OperatorKind.INVALID;
}
}

/** Resolves a binary expression to the program element it refers to. */
resolveBinaryExpression(
/** The expression to resolve. */
Expand All @@ -1199,14 +1223,42 @@ export class Resolver extends DiagnosticEmitter {
/** How to proceed with eventualy diagnostics. */
reportMode: ReportMode = ReportMode.REPORT
): Element | null {
// TODO
if (reportMode == ReportMode.REPORT) {
this.error(
DiagnosticCode.Operation_not_supported,
expression.range
);
let left = expression.left;
let right = expression.right;
let leftElement = this.resolveExpression(left, flow, contextualType, reportMode);
if (!leftElement) return null;
let leftType = (<TypedElement>leftElement!).type;
Copy link
Member

Choose a reason for hiding this comment

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

If it's always a TypedElement, I think resolveExpression should have it as the return type. Unless it's not always one, ofc, which would mean it misses something here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's what I thought, but what's an example of something that does not resolve to a typedElement?

Copy link
Member

Choose a reason for hiding this comment

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

What can happen there for example is that if an expression referencing a class name is resolved, the element is a static ClassPrototype without a type. The same should be true for function names, because the function can be generic with multiple instances so a FunctionPrototype is returned. There might be others that I forgot.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see. Is it possible to circumvent this by using resolveType here instead of resolveExpression?

Copy link
Member

Choose a reason for hiding this comment

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

resolveType is meant for type nodes exclusively here, so I think it isn't applicable. If I'm not mistaken the resolver should actually return the ClassPrototype respectively FunctionProtoype here (since we want the element), and whatever the caller of resolveExpression is would deal with it appropriately.

let rightElement = this.resolveExpression(right, flow, leftType, reportMode);
if (!rightElement) return null;
let rightType = (<TypedElement>rightElement!).type;
let commonType: Type | null;
if (commonType = Type.commonDenominator(leftType, rightType, true)) {
Copy link
Member

Choose a reason for hiding this comment

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

There are some occasions where it doesn't work this way, like for shifts which always prefer the LHS. I think there should be a switch over all possible operators here that mimics what compileExpression etc. do. In this case, binaryOperatorKindFromToken can possibly be incorporated into the switch.

let typeClasses = this.program.typeClasses;
if (typeClasses.has(commonType.kind)) {
return typeClasses.get(commonType.kind);
} else if (commonType.classReference) {
let overload = commonType.classReference.lookupOverload(this.binaryOperatorKindFromToken(expression.operator));
if (!overload) {
if (reportMode == ReportMode.REPORT) {
this.error(
DiagnosticCode.Operation_not_supported,
expression.range
);
}
return null;
}
return commonType.classReference;
}
return null;
} else {
if (reportMode == ReportMode.REPORT) {
this.error(
DiagnosticCode.Operation_not_supported,
expression.range
);
}
return null;
}
return null;
}

/** Resolves a this expression to the program element it refers to. */
Expand Down
5 changes: 5 additions & 0 deletions tests/compiler/resolve-operator.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"asc_flags": [
"--runtime none"
]
}
270 changes: 270 additions & 0 deletions tests/compiler/resolve-operator.optimized.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
(module
(type $FUNCSIG$i (func (result i32)))
(type $FUNCSIG$ii (func (param i32) (result i32)))
(type $FUNCSIG$iii (func (param i32 i32) (result i32)))
(type $FUNCSIG$viii (func (param i32 i32 i32)))
(type $FUNCSIG$iij (func (param i32 i64) (result i32)))
(type $FUNCSIG$v (func))
(type $FUNCSIG$ij (func (param i64) (result i32)))
(memory $0 1)
(data (i32.const 8) "\02\00\00\00\01\00\00\00\01\00\00\00\02\00\00\000")
(global $~lib/rt/stub/startOffset (mut i32) (i32.const 0))
(global $~lib/rt/stub/offset (mut i32) (i32.const 0))
(export "memory" (memory $0))
(export "operatorAccessLiteral" (func $resolve-operator/operatorAccessLiteral))
(export "operatorAccess" (func $resolve-operator/operatorAccess))
(export "OperatorOverloadingAccess" (func $resolve-operator/OperatorOverloadingAccess))
(export "OperatorOverloadingSubclassAccess" (func $resolve-operator/OperatorOverloadingSubclassAccess))
(start $start)
(func $~lib/util/number/decimalCount32 (; 0 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
i32.const 1
i32.const 2
local.get $0
i32.const 10
i32.lt_u
select
i32.const 3
i32.const 4
i32.const 5
local.get $0
i32.const 10000
i32.lt_u
select
local.get $0
i32.const 1000
i32.lt_u
select
local.get $0
i32.const 100
i32.lt_u
select
i32.const 6
i32.const 7
local.get $0
i32.const 1000000
i32.lt_u
select
i32.const 8
i32.const 9
i32.const 10
local.get $0
i32.const 1000000000
i32.lt_u
select
local.get $0
i32.const 100000000
i32.lt_u
select
local.get $0
i32.const 10000000
i32.lt_u
select
local.get $0
i32.const 100000
i32.lt_u
select
)
(func $~lib/rt/stub/__alloc (; 1 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
(local $2 i32)
(local $3 i32)
(local $4 i32)
(local $5 i32)
local.get $0
i32.const 1073741808
i32.gt_u
if
unreachable
end
global.get $~lib/rt/stub/offset
i32.const 16
i32.add
local.tee $3
local.get $0
i32.const 1
local.get $0
i32.const 1
i32.gt_u
select
i32.add
i32.const 15
i32.add
i32.const -16
i32.and
local.tee $2
memory.size
local.tee $4
i32.const 16
i32.shl
i32.gt_u
if
local.get $4
local.get $2
local.get $3
i32.sub
i32.const 65535
i32.add
i32.const -65536
i32.and
i32.const 16
i32.shr_u
local.tee $5
local.get $4
local.get $5
i32.gt_s
select
memory.grow
i32.const 0
i32.lt_s
if
local.get $5
memory.grow
i32.const 0
i32.lt_s
if
unreachable
end
end
end
local.get $2
global.set $~lib/rt/stub/offset
local.get $3
i32.const 16
i32.sub
local.tee $2
local.get $1
i32.store offset=8
local.get $2
local.get $0
i32.store offset=12
local.get $3
)
(func $~lib/util/number/utoa_simple<u32> (; 2 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32)
(local $3 i32)
loop $continue|0
local.get $1
i32.const 10
i32.rem_u
local.set $3
local.get $1
i32.const 10
i32.div_u
local.set $1
local.get $2
i32.const 1
i32.sub
local.tee $2
i32.const 1
i32.shl
local.get $0
i32.add
local.get $3
i32.const 48
i32.add
i32.store16
local.get $1
br_if $continue|0
end
)
(func $~lib/util/number/utoa32 (; 3 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
(local $1 i32)
(local $2 i32)
local.get $0
i32.eqz
if
i32.const 24
return
end
local.get $0
call $~lib/util/number/decimalCount32
local.tee $1
i32.const 1
i32.shl
i32.const 1
call $~lib/rt/stub/__alloc
local.tee $2
local.get $0
local.get $1
call $~lib/util/number/utoa_simple<u32>
local.get $2
)
(func $resolve-operator/operatorAccessLiteral (; 4 ;) (type $FUNCSIG$i) (result i32)
i32.const 3
call $~lib/util/number/utoa32
)
(func $resolve-operator/operatorAccess (; 5 ;) (type $FUNCSIG$i) (result i32)
(local $0 i32)
(local $1 i32)
i32.const 3
call $~lib/util/number/decimalCount32
local.tee $0
i32.const 1
i32.shl
i32.const 1
call $~lib/rt/stub/__alloc
local.tee $1
i32.const 3
local.get $0
call $~lib/util/number/utoa_simple<u32>
local.get $1
)
(func $resolve-operator/Container#constructor (; 6 ;) (type $FUNCSIG$iij) (param $0 i32) (param $1 i64) (result i32)
local.get $0
i32.eqz
if
i32.const 8
i32.const 3
call $~lib/rt/stub/__alloc
local.set $0
end
local.get $0
i64.const 0
i64.store
local.get $0
local.get $1
i64.store
local.get $0
)
(func $resolve-operator/Container.add (; 7 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
i32.const 0
local.get $0
i64.load
local.get $1
i64.load
i64.add
call $resolve-operator/Container#constructor
)
(func $resolve-operator/OperatorOverloadingAccess (; 8 ;) (type $FUNCSIG$i) (result i32)
i32.const 0
i64.const 1
call $resolve-operator/Container#constructor
i32.const 0
i64.const 2
call $resolve-operator/Container#constructor
call $resolve-operator/Container.add
call $~lib/util/number/utoa32
)
(func $resolve-operator/SubContainer#constructor (; 9 ;) (type $FUNCSIG$ij) (param $0 i64) (result i32)
i32.const 8
i32.const 4
call $~lib/rt/stub/__alloc
local.get $0
call $resolve-operator/Container#constructor
)
(func $resolve-operator/OperatorOverloadingSubclassAccess (; 10 ;) (type $FUNCSIG$i) (result i32)
i64.const 1
call $resolve-operator/SubContainer#constructor
i64.const 2
call $resolve-operator/SubContainer#constructor
call $resolve-operator/Container.add
call $~lib/util/number/utoa32
)
(func $start (; 11 ;) (type $FUNCSIG$v)
i32.const 32
global.set $~lib/rt/stub/startOffset
global.get $~lib/rt/stub/startOffset
global.set $~lib/rt/stub/offset
)
(func $null (; 12 ;) (type $FUNCSIG$v)
nop
)
)
Loading