Skip to content
Open
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
56 changes: 48 additions & 8 deletions be-cpp/src/commonMain/kotlin/lang/temper/be/cpp/CppTranslator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ import lang.temper.type.MethodShape
import lang.temper.type.PropertyShape
import lang.temper.type.TypeDefinition
import lang.temper.type.TypeFormal
import lang.temper.type.TypeShape
import lang.temper.type.WellKnownTypes
import lang.temper.type.excludeNullAndBubble
import lang.temper.type2.NullableType
import lang.temper.type2.Nullity
import lang.temper.type2.Type2
Expand Down Expand Up @@ -329,10 +331,11 @@ class CppTranslator(
WellKnownTypes.listedTypeDefinition,
)
if (declDef !in listUpcastDefs || initDef !in listUpcastDefs) return translatedExpr
// Check if element types differ
// Check if element types differ. Compare whole types, not just definitions,
// so that a List<List<Derived>> is converted to a List<List<Base>> too.
val declElem = declaredTmpLType.bindings.firstOrNull() ?: return translatedExpr
val initElem = initExprType.bindings.firstOrNull() ?: return translatedExpr
if (declElem.definition == initElem.definition) return translatedExpr
if (declElem.withNullity(Nullity.NonNull) == initElem.withNullity(Nullity.NonNull)) return translatedExpr
// Element types differ — wrap with List::upcast<DeclaredElemType>
val declElemCpp = translateType2(declElem)
return cpp.callExpr(
Expand Down Expand Up @@ -1671,7 +1674,13 @@ class CppTranslator(
listOf(translateExpression(retExpr)),
)
}
else -> translateExpressionOrNull(retExpr)
else -> translateExpressionOrNull(retExpr)?.let { translated ->
wrapWithListUpcastIfNeeded(
translated,
retExpr?.type,
enclosingReturnType(stmt)?.let { excludeNullAndBubble(it) },
)
}
}
listOf(cpp.returnStmt(translatedRet))
}
Expand Down Expand Up @@ -1723,20 +1732,25 @@ class CppTranslator(
}
}
val setterName = if (useSetterMethod) setterMethodNames[propDotName] else null
val translatedRight = wrapWithListUpcastIfNeeded(
translateExpression(right),
right.type,
propertyType(lval),
)
if (setterName != null) {
// Call setter method instead of direct assignment
val call: Cpp.Expr = when (val subj = lval.subject) {
is TmpL.Expression -> cpp.callExpr(
cpp.op("->", translateExpression(subj), setterName),
translateExpression(right),
translatedRight,
)
is TmpL.ConnectedToTypeName -> cpp.callExpr(
cpp.scopedName(translateTypeName(subj), setterName),
translateExpression(right),
translatedRight,
)
is TmpL.TemperTypeName -> cpp.callExpr(
cpp.scopedName(translateTypeName(subj), setterName),
translateExpression(right),
translatedRight,
)
is TmpL.SuperSubject -> error("super property handled elsewhere")
}
Expand All @@ -1750,10 +1764,34 @@ class CppTranslator(
is TmpL.SuperSubject -> error("illegal super call for backed property")
}
return listOf(
cpp.exprStmt(cpp.op("=", lhs, translateExpression(right))),
cpp.exprStmt(cpp.op("=", lhs, translatedRight)),
)
}

/** The declared type of the property [lval] assigns, when its subject's type is known here. */
private fun propertyType(lval: TmpL.PropertyLValue): Type2? {
val shape = when (val subject = lval.subject) {
is TmpL.Expression -> subject.type.definition as? TypeShape
is TmpL.TypeSubject -> subject.typeName.sourceDefinition as? TypeShape
} ?: return null
val symbol = when (val property = lval.property) {
is TmpL.ExternalPropertyId -> property.name.dotNameText
is TmpL.InternalPropertyId -> property.name.name.toSymbol()?.text
}
return shape.properties.find { it.symbol.text == symbol }?.descriptor as? Type2
}

private fun enclosingReturnType(stmt: TmpL.Tree): Type2? {
var ancestor = stmt.parent
while (ancestor != null) {
if (ancestor is TmpL.FunctionLike) {
return ancestor.sig.returnType2
}
ancestor = ancestor.parent
}
return null
}

/** True if the block's control unconditionally leaves (so a trailing `break` is dead). */
private fun blockEndsWithControlLeave(block: TmpL.BlockStatement): Boolean =
when (block.statements.lastOrNull()) {
Expand Down Expand Up @@ -3025,7 +3063,9 @@ class CppTranslator(
listOf(translateExpression(initExpr)),
)
} else {
translateExpressionOrNull(initExpr)
translateExpressionOrNull(initExpr)?.let { translated ->
wrapWithListUpcastIfNeeded(translated, initExpr?.type, topLevel.descriptor)
}
}
// Always declare with null init and defer assignment
// to the init function to avoid SIOF.
Expand Down
31 changes: 26 additions & 5 deletions be-cpp/src/commonMain/resources/lang/temper/be/cpp/core/list.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,32 @@ namespace temper {
return list;
}

// Converts one element for upcast the way make converts one: implicitly,
// or by boxing a value into an AnyValue. An element that is itself a
// list, as in List<List<Derived>> to List<List<Base>>, is upcast in turn.
template<class Base, class Derived>
struct UpcastElem {
static Base apply(const Derived& elem) { return convert_elem<Base>(elem); }
};

template<class Base, class Derived>
struct UpcastElem<std::shared_ptr<std::vector<Base>>, std::shared_ptr<std::vector<Derived>>> {
static std::shared_ptr<std::vector<Base>> apply(const std::shared_ptr<std::vector<Derived>>& elem);
};

template<
class Base,
class Derived,
class = typename std::enable_if<
!std::is_same<Base, Derived>::value
&& std::is_convertible<Derived, Base>::value
>::type
class = typename std::enable_if<!std::is_same<Base, Derived>::value>::type
>
std::shared_ptr<std::vector<Base>> upcast(const std::shared_ptr<std::vector<Derived>>& src) {
if (src == nullptr) {
return nullptr;
}
std::shared_ptr<std::vector<Base>> result = std::make_shared<std::vector<Base>>();
result->reserve(src->size());
for (const Derived& elem : *src) {
result->push_back(elem);
result->push_back(UpcastElem<Base, Derived>::apply(elem));
}
return result;
}
Expand All @@ -70,6 +83,14 @@ namespace temper {
return src;
}

template<class Base, class Derived>
std::shared_ptr<std::vector<Base>>
UpcastElem<std::shared_ptr<std::vector<Base>>, std::shared_ptr<std::vector<Derived>>>::apply(
const std::shared_ptr<std::vector<Derived>>& elem
) {
return upcast<Base>(elem);
}

template<class Elem>
bool isEmpty(const std::shared_ptr<std::vector<Elem>>& list) {
return list->empty();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,18 @@ import lang.temper.name.ResolvedName
import lang.temper.type.Abstractness
import lang.temper.type.MethodKind
import lang.temper.type.MethodShape
import lang.temper.type.TypeFormal
import lang.temper.type.TypeShape
import lang.temper.type.WellKnownTypes
import lang.temper.type.excludeNullAndBubble
import lang.temper.type.isVoidLike
import lang.temper.type.mentionsInvalid
import lang.temper.type.simplify
import lang.temper.type2.DefinedNonNullType
import lang.temper.type2.Nullity
import lang.temper.type2.Signature2
import lang.temper.type2.Type2
import lang.temper.type2.withNullity
import lang.temper.type2.withType
import lang.temper.value.TBoolean
import lang.temper.value.TClass
Expand Down Expand Up @@ -640,7 +646,7 @@ class JavaTranslator(
activeInit().add(
Assign.assign(
left = fieldName.asNameExpr(t.pos),
right = expr(init),
right = upcastList(expr(init), init.type, t.descriptor),
).exprStatement(t.pos),
)
}
Expand Down Expand Up @@ -1617,7 +1623,10 @@ class JavaTranslator(
is TmpL.LocalFunctionDeclaration -> LocalFunctionDecl(t).toLocalVarDecl()
is TmpL.ModuleInitFailed -> moduleInitFailed(t)
is TmpL.YieldStatement -> TODO() // JavaSupportNetwork opts into TranslateToRegularFunction
is TmpL.ReturnStatement -> J.ReturnStatement(t.pos, t.expression?.let(::expr))
is TmpL.ReturnStatement -> J.ReturnStatement(
t.pos,
t.expression?.let { upcastList(expr(it), it.type, enclosingReturnType(t)) },
)
is TmpL.SetProperty -> setProperty(t)
is TmpL.ThrowStatement -> throwStmt(t)
is TmpL.TryStatement -> tryStmt(t)
Expand All @@ -1640,7 +1649,7 @@ class JavaTranslator(
t.pos,
type = varType(t),
name = names.lookupRegularLocalNameObj(t.name).outName.toIdentifier(t.name.pos),
expr = t.init?.let(::expr),
expr = t.init?.let { upcastList(expr(it), it.type, t.descriptor) },
)

private fun ifStmt(t: TmpL.IfStatement) = J.IfStatement(
Expand Down Expand Up @@ -1759,25 +1768,77 @@ class JavaTranslator(
private fun assignment(t: TmpL.Assignment): J.BlockLevelStatement =
Assign.assign(
leftHandSide(t.left),
expr(t.right),
upcastList(expr(t.right), t.right.type, t.type),
pos = t.pos,
).exprStatement(t.pos)

private fun setProperty(t: TmpL.SetProperty): J.BlockLevelStatement {
val left = t.left
// Static properties are not assignable.
val leftSubject = left.subject as TmpL.Expression
val right = upcastList(expr(t.right), t.right.type, propertyType(leftSubject, left.property))
return when (val prop = left.property) {
is TmpL.ExternalPropertyId ->
J.InstanceMethodInvocationExpr(
t.pos,
expr(leftSubject),
method = names.setterName(prop.name),
args = listOf(expr(t.right).asArgument(t.pos)),
args = listOf(right.asArgument(t.pos)),
).exprStatement()
is TmpL.InternalPropertyId ->
Assign.assign(expr(leftSubject).field(names.field(prop)), expr(t.right)).exprStatement(t.pos)
Assign.assign(expr(leftSubject).field(names.field(prop)), right).exprStatement(t.pos)
}
}

private fun propertyType(subject: TmpL.Expression, property: TmpL.PropertyId): Type2? {
val shape = (subject.type as? DefinedNonNullType)?.definition as? TypeShape ?: return null
val symbol = when (property) {
is TmpL.ExternalPropertyId -> property.name.dotNameText
is TmpL.InternalPropertyId -> property.name.name.toSymbol()?.text
}
return shape.properties.find { it.symbol.text == symbol }?.descriptor as? Type2
}

private fun enclosingReturnType(t: TmpL.Tree): Type2? {
var ancestor = t.parent
while (ancestor != null) {
if (ancestor is TmpL.FunctionLike) {
return ancestor.sig.returnType2
}
ancestor = ancestor.parent
}
return null
}

/**
* `List` is covariant in Temper, so a `List<Square>` may be used where a
* `List<Shape>` is wanted, but `java.util.List` is invariant and javac
* rejects the assignment. Wraps [translated] in
* `Core.<Shape>listUpcast(...)`, an unchecked view of the same list, when
* the element types differ.
*
* The element type is given explicitly so that javac does not infer a
* narrower one from a generic context. A wanted element type that
* mentions a type parameter is left alone because that parameter may
* not be in scope at the use site.
*/
private fun upcastList(translated: J.Expression, given: Type2?, wanted: Type2?): J.Expression {
val givenElement = listElementType(given) ?: return translated
val wantedElement = listElementType(wanted) ?: return translated
if (givenElement.withNullity(Nullity.NonNull) == wantedElement.withNullity(Nullity.NonNull)) {
return translated
}
if (mentionsTypeFormal(wantedElement)) { return translated }
val pos = translated.pos
val elementType = JavaType.fromFrontend(wantedElement, names).asReferenceType()
val (type, method) = temperListUpcast.split()
return J.StaticMethodInvocationExpr(
pos,
type = type.toQualIdent(pos.leftEdge),
typeArgs = J.TypeArguments(pos.leftEdge, listOf(elementType.toTypeArgAst(pos.leftEdge))),
method = J.Identifier(pos.leftEdge, method),
args = listOf(translated.asArgument()),
)
}

fun expr(x: TmpL.Expression): J.Expression = when (x) {
Expand Down Expand Up @@ -1935,7 +1996,7 @@ class JavaTranslator(
else -> calleeType?.valueFormalsExceptThis
}
return actuals.mapGenericIndexed { idx, actual ->
var expr = expr(actual)
var expr = upcastList(expr(actual), actual.type, calleeFormals?.getOrNull(idx)?.type)
if (calleeFormals != null && validInstanceMethodReferenceSubject(expr)) {
val actualSig = withType(
actual.passType,
Expand Down Expand Up @@ -2499,3 +2560,13 @@ internal fun potentiallyLongStringToJavaExpression(pos: Position, s: String): J.
args = emptyList(),
)
}

/** The element type of a `List`, ignoring whether the list itself is nullable or bubbly. */
private fun listElementType(type: Type2?): Type2? {
val list = type?.let { excludeNullAndBubble(it) } as? DefinedNonNullType ?: return null
if (list.definition != WellKnownTypes.listTypeDefinition) { return null }
return list.bindings.getOrNull(0)
}

private fun mentionsTypeFormal(type: Type2): Boolean =
type.definition is TypeFormal || type.bindings.any { mentionsTypeFormal(it) }
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ val temperBoxedEqRev = temperCore.qualifyKnownSafe("boxedEqRev")
val temperBubble = javaLangRuntimeException
val temperBubbleMethod = temperCore.qualifyKnownSafe("bubble")
val temperCast = temperCore.qualifyKnownSafe("cast")
val temperListUpcast = temperCore.qualifyKnownSafe("listUpcast")
val temperCastToNonNull = temperCore.qualifyKnownSafe("castToNonNull")
val temperConsoleClass = temperCore.qualifyKnownSafe("Console")
val temperGetConsoleMethod = temperCore.qualifyKnownSafe("getConsole")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,23 @@ private static <E> List<E> listCopyOfTrusted(List<E> elems) {
}
}

/**
* Temper's List is covariant, so a List of Square may be used as a List
* of Shape, but java.util.List is invariant. Nothing adds to or sets
* elements of a list through a Temper List, so viewing the same list
* with a wider element type cannot let a non-Square into it. The Temper
* frontend has checked that the element types are related, including
* for nested lists like a List of List of Square, which Java's
* {@code ? extends} cannot express, hence the unchecked parameter type.
* @param list a list, or null
* @return the same list, or null
* @param <E> the wider element type
*/
@SuppressWarnings("unchecked")
public static <E> List<E> listUpcast(List<?> list) {
return (List<E>) list;
}

/**
* @param target a ListBuilder instance
* @param elem the element to add
Expand Down
Loading
Loading