@helios-lang/compiler
Version:
Helios is a Domain Specific Language that compiles to Plutus-Core (i.e. Cardano on-chain validator scripts). Helios is a non-Haskell alternative to Plutus. With this library you can compile Helios scripts and build Cardano transactions, all you need to bu
186 lines (161 loc) • 6.28 kB
JavaScript
import { makeTypeError } from "@helios-lang/compiler-utils"
import { $ } from "@helios-lang/ir"
import { expectDefined } from "@helios-lang/type-utils"
import { TAB, ToIRContext } from "../codegen/index.js"
import { Scope } from "../scopes/index.js"
import { AnyType, DataEntity, VoidType } from "../typecheck/index.js"
import { CallExpr } from "./CallExpr.js"
import { ChainExpr } from "./ChainExpr.js"
import { DestructExpr } from "./DestructExpr.js"
import { Expr } from "./Expr.js"
import { PathExpr } from "./PathExpr.js"
/**
* @import { Site } from "@helios-lang/compiler-utils"
* @import { SourceMappedStringI } from "@helios-lang/ir"
* @import { TypeCheckContext } from "../index.js"
* @typedef {import("../typecheck/index.js").EvalEntity} EvalEntity
*/
const IR_ASSIGN_LAMBDA_ALIAS = "<assign>"
/**
* '... = ... ; ...' expression
*/
export class AssignExpr extends ChainExpr {
/**
* @type {Site}
*/
semicolonSite
/**
* @private
* @readonly
* @type {DestructExpr}
*/
_nameType
/**
* @param {Site} site
* @param {Site} semicolonSite
* @param {DestructExpr} nameType
* @param {Expr} upstreamExpr
* @param {Expr} downstreamExpr
*/
constructor(site, semicolonSite, nameType, upstreamExpr, downstreamExpr) {
super(site, upstreamExpr, downstreamExpr)
this.semicolonSite = semicolonSite
this._nameType = nameType
}
/**
* @param {TypeCheckContext} ctx
* @param {Scope} scope
* @returns {EvalEntity}
*/
evalInternal(ctx, scope) {
const subScope = new Scope(scope, scope.allowShadowing)
let upstreamVal = this.upstreamExpr.eval(ctx, scope)
if (upstreamVal && upstreamVal.asTyped) {
if (new VoidType().isBaseOf(upstreamVal.asTyped.type)) {
ctx.errors.type(
this.upstreamExpr.site,
"can't assign to unit type"
)
}
if (this._nameType.hasType() || this._nameType.isTuple()) {
this._nameType.evalInAssignExpr(
ctx,
subScope,
expectDefined(upstreamVal.asTyped.type.asType),
0
)
} else {
// enum variant type resulting from a constructor-like associated function must be cast back into its enum type
if (
(this.upstreamExpr instanceof CallExpr &&
this.upstreamExpr.fnExpr instanceof PathExpr) ||
(this.upstreamExpr instanceof PathExpr &&
!this.upstreamExpr.isLiteral())
) {
const upstreamType = upstreamVal.asTyped.type
if (upstreamType.asEnumMemberType) {
upstreamVal = new DataEntity(
upstreamType.asEnumMemberType.parentType
)
}
}
subScope.set(this._nameType.name, upstreamVal)
}
} else if (this._nameType.hasType()) {
// this is the fallback case if the upstream has itself a typeerror
this._nameType.evalInAssignExpr(ctx, subScope, undefined, 0)
} else {
ctx.errors.type(this.upstreamExpr.site, "rhs isn't an instance")
subScope.set(this._nameType.name, new DataEntity(new AnyType()))
}
const downstreamVal = this.downstreamExpr.eval(ctx, subScope)
subScope.assertAllUsed()
return downstreamVal
}
/**
*
* @param {ToIRContext} ctx
* @returns {SourceMappedStringI}
*/
toIR(ctx) {
let inner = this.downstreamExpr.toIR(ctx.tab())
if (this._nameType.isTuple() && this._nameType.isIgnored()) {
// TODO: get rid of this on the next major version release, while making sure the default approach is equally efficient (i.e. the callback call is properly inlined)
// keep using the old way of creating the IR in order to assure backwards compatibility
for (let i = this._nameType.children.length - 1; i >= 0; i--) {
// internally generates enum-member error IR
inner = this._nameType.children[i].wrapDestructIR(ctx, inner, i)
}
const ir = $([
this.upstreamExpr.toIR(ctx),
$(`(\n${ctx.indent + TAB}(`),
$(this._nameType.children.map((nt, i) => nt.toNameIR(i))).join(
", "
),
$(") "),
$("->", this.site.withDescription(IR_ASSIGN_LAMBDA_ALIAS)),
$(` {\n${ctx.indent}${TAB}${TAB}`),
inner,
$(`\n${ctx.indent + TAB}}\n${ctx.indent})`)
])
return ir
} else {
inner = this._nameType.wrapDestructIR(ctx, inner, 0)
let upstream = this.upstreamExpr.toIR(ctx)
// enum member run-time error IR
// TODO: should this be nestable
if (this._nameType.hasType()) {
const t = this._nameType.type
if (t.asEnumMemberType) {
upstream = $([
$("__helios__common__assert_constr_index("),
upstream,
$(`, ${t.asEnumMemberType.constrIndex})`)
])
}
}
return $([
$("("),
this._nameType.toNameIR(0), // wrapDestructIR depends on this name
$(") "),
$(
"->",
this.semicolonSite.withDescription(IR_ASSIGN_LAMBDA_ALIAS)
),
$(` {\n${ctx.indent}${TAB}`),
inner,
$(`\n${ctx.indent}}`),
$("(", this.site), // this is the call site
upstream,
$(")")
])
}
}
/**
* @returns {string}
*/
toString() {
let downstreamStr = this.downstreamExpr.toString()
return `${this._nameType.toString()} = ${this.upstreamExpr.toString()}; ${downstreamStr}`
}
}