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
202 changes: 100 additions & 102 deletions BACKLOG.md

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,35 @@
Lua reaches such a class through the interface it implements, so no call names the instantiation and the
construction is what the specialisation is taken from.

- A module's type parameter may now carry a type class bound, and the class using the module supplies the
argument:

module Shower<T: Show>
T held
function shown() returns string
return T.show(held)

class Holder<K: Show>
use Shower<K>

Using a module copies its body into the class and replaces the module's type parameters wherever they
are used as types. The receiver in `T.show(held)` is a name rather than a type, so the replacement never
reached it and the bound was rejected. The instantiation now declares the parameters and records the
arguments chosen for them, so that name resolves and says what it stands for. The argument must satisfy
the bound, which is reported at the `use`. This works on both targets.

- On the Lua target, a bounded generic class can now be subclassed, and a requirement can be dispatched
from inside a constructor. A generic object stays erased there and only the paths needing a concrete
type are specialised, so the concrete type has to reach those paths rather than the object: a
specialised method is bound to the class its objects are allocated from, a call which names its target
— `super.m()` is one — takes the instantiation from the class its receiver is used as, and a function
of a generic class is matched against that class's type variables rather than being read as having
none of its own. A specialisation nothing allocates is no longer emitted at all.

One shape remains unsupported on Lua: a method combining its own type parameters with those of the
generic class owning it, though it is no longer rejected outright — the arity check it tripped over
counted the class's type arguments against a call that had only supplied the method's.

- Added new pseudo-natives for debugging memory leaks:

// returns the maximum type id, can be usd to
Expand Down
6 changes: 6 additions & 0 deletions de.peeeq.wurstscript/parserspec/wurstscript.parseq
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,13 @@ ClassSlot =
ConstructorDef(@ignoreForEquality de.peeeq.wurstscript.parser.WPos source, Modifiers modifiers, WParameters parameters, SuperConstructorCall superConstructorCall, WStatements body)
| OnDestroyDef(@ignoreForEquality de.peeeq.wurstscript.parser.WPos source, WStatements body)
| ModuleUse(@ignoreForEquality de.peeeq.wurstscript.parser.WPos source, Identifier moduleNameId, TypeExprList typeArgs)
// Carries the module's type parameters, and the arguments chosen for them, so a name inside the
// copied body still resolves: a requirement of a bound is called on the parameter itself, which
// is a name rather than a type, and a module body resolves names in the module's own scope
// rather than the user's. They are declarations, not parameters left to infer, which is why the
// instantiation is not an AstElementWithTypeParameters.
| ModuleInstanciation(@ignoreForEquality de.peeeq.wurstscript.parser.WPos source, Modifiers modifiers, Identifier nameId,
TypeParamDefs typeParameters, TypeExprList typeArgs,
ClassDefs innerClasses, FuncDefs methods, GlobalVarDefs vars, ConstructorDefs constructors,
ModuleInstanciations p_moduleInstanciations, ModuleUses moduleUses, OnDestroyDef onDestroy)
| ClassMember
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -964,7 +964,7 @@ public LuaCompilationUnit transformProgToLua() {
timeTaker.endPhase();

beginPhase(13, "prepare lua dispatch");
LuaDispatchPreparation.prepare(imProg);
LuaDispatchPreparation.prepare(imProg, imTranslator);
timeTaker.endPhase();

beginPhase(14, "translate to lua");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,25 @@ private static ModuleInstanciations expandModules(ClassOrModule m, List<ClassOrM

WPos source = moduleUse.getSource().artificial();
WPos idSource = moduleUse.getModuleNameId().getSource().artificial();
// The instantiation declares the module's type parameters, so a name inside the copied
// body still resolves to something. Types have already been replaced by the arguments;
// what is left needing a name is a requirement called on the parameter itself.
TypeParamDefs instanciationTypeParams = Ast.TypeParamDefs();
for (TypeParamDef moduleParam : usedModule.getTypeParameters()) {
instanciationTypeParams.add(moduleParam.copy());
}
// Resolved rather than copied: an argument names something in the user's scope, and a
// module instantiation resolves names in the module's own. Resolving here keeps the type
// and needs no scope afterwards, which is what TypeExprResolved is for.
TypeExprList instanciationTypeArgs = Ast.TypeExprList();
for (int i = 0; i < numTypeArgs; i++) {
TypeExpr arg = moduleUse.getTypeArgs().get(i);
instanciationTypeArgs.add(Ast.TypeExprResolved(arg.getSource().artificial(), arg.attrTyp()));
}
ModuleInstanciation mi = Ast.ModuleInstanciation(source, Ast.Modifiers(),
Ast.Identifier(idSource, usedModule.getName()),
instanciationTypeParams,
instanciationTypeArgs,
smartCopy(usedModule.getInnerClasses(), typeReplacements),
smartCopy(usedModule.getMethods(), typeReplacements),
smartCopy(usedModule.getVars(), typeReplacements),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,13 @@ public static WurstType calculate(final ExprBinary term) {
case MOD_INT:
case JASS_MOD_INT:
case DIV_INT:
// The left operand's type is returned deliberately, so that `real r = 7 div 2` compiles.
// caseMathOperation below does the opposite for + - * /, collapsing two int literals to
// int precisely so that `real r = 1 + 1` is an error, and the difference between the two
// is easy to read as an oversight here. It is not: these operators are integer-only, an
// int literal is a subtype of real, and narrowing the result would break assignments
// which compile today. ExpressionTests.integerDivisionOfLiteralsIsStillAssignableToReal
// pins it, and OptimizerTests.realFormatting_consistent_fromIntOps depends on it.
if (leftType.isSubtypeOf(WurstTypeInt.instance(), term) && rightType.isSubtypeOf(WurstTypeInt.instance(), term)) {
return leftType;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import de.peeeq.wurstscript.types.WurstType;
import de.peeeq.wurstscript.types.WurstTypeTypeParam;
import org.eclipse.jdt.annotation.Nullable;
import de.peeeq.wurstscript.types.WurstTypeBoundTypeParam;

public class AttrImplicitParameter {

Expand Down Expand Up @@ -97,9 +98,17 @@ public static boolean isTypeClassDispatch(Element e) {
return false;
}
Expr left = hasReceiver.getLeft();
return left != null
&& left.attrTyp() instanceof WurstTypeTypeParam tp
&& tp.isStaticRef();
if (left == null) {
return false;
}
WurstType leftType = left.attrTyp();
if (leftType instanceof WurstTypeTypeParam tp) {
return tp.isStaticRef();
}
// A parameter of a module instantiation denotes the argument bound to it, so the receiver is
// a binding rather than the parameter itself. It still names a type parameter, which is what
// makes this a dispatch.
return leftType instanceof WurstTypeBoundTypeParam bound && bound.isStaticRef();
}

static OptExpr getFunctionCallImplicitParameter(FunctionCall e, FuncLink calledFunc, boolean showError) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import de.peeeq.wurstscript.types.WurstTypeEnum;
import de.peeeq.wurstscript.types.WurstTypeModule;
import org.eclipse.jdt.annotation.Nullable;
import de.peeeq.wurstscript.types.WurstTypeBoundTypeParam;

/**
* this attribute find the variable definition for every variable reference
Expand Down Expand Up @@ -136,7 +137,7 @@ private static boolean isMethodCallReceiver(NameRef node) {
if (!(typeDef instanceof TypeParamDef tp) || !TypeClassConstraints.hasBounds(tp)) {
return null;
}
WurstTypeTypeParam typ = new WurstTypeTypeParam(tp).asStaticRef();
WurstType typ = staticRefTypeFor(tp, node);
return new OtherLink(Visibility.LOCAL, varName, typ) {
@Override
public de.peeeq.wurstscript.jassIm.ImExpr translate(NameRef e, ImTranslator t, ImFunction f) {
Expand All @@ -146,6 +147,34 @@ public de.peeeq.wurstscript.jassIm.ImExpr translate(NameRef e, ImTranslator t, I
};
}

/**
* The type a bounded parameter's name denotes in receiver position.
* <p>
* A parameter declared on a module instantiation stands for the argument the user supplied, so it
* denotes that type bound to this parameter: the requirement's own parameter types then substitute
* to the argument rather than to a name only the module can see. Everywhere else the parameter
* stands for itself.
*/
private static WurstType staticRefTypeFor(TypeParamDef tp, NameRef node) {
WurstType argument = moduleInstanciationArgument(tp);
if (argument != null) {
return new WurstTypeBoundTypeParam(tp, argument, node).asStaticRef();
}
return new WurstTypeTypeParam(tp).asStaticRef();
}

/** The argument a module instantiation supplied for this parameter, or null if it is not one. */
private static @Nullable WurstType moduleInstanciationArgument(TypeParamDef tp) {
if (!(tp.getParent() != null && tp.getParent().getParent() instanceof ModuleInstanciation mi)) {
return null;
}
int index = mi.getTypeParameters().indexOf(tp);
if (index < 0 || index >= mi.getTypeArgs().size()) {
return null;
}
return mi.getTypeArgs().get(index).attrTyp();
}

private static @Nullable NameLink lookupImplicitClosureSelf(NameRef node, boolean showErrors) {
ExprClosure closure = node.attrNearestExprClosure();
if (closure == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import com.google.common.collect.ImmutableSetMultimap;
import de.peeeq.wurstscript.ast.*;

import java.util.Collections;
import java.util.List;

public class TypeNameLinks {

public static ImmutableMultimap<String, TypeLink> calculate(ClassOrModuleOrModuleInstanciation c) {
Expand Down Expand Up @@ -94,13 +97,27 @@ public static ImmutableMultimap<String, TypeLink> calculate(WStatements statemen
}

private static void addTypeParametersIfAny(ImmutableMultimap.Builder<String, TypeLink> result, WScope c) {
if (c instanceof AstElementWithTypeParameters) {
AstElementWithTypeParameters wtp = (AstElementWithTypeParameters) c;
for (TypeParamDef i : wtp.getTypeParameters()) {
result.put(i.getName(), TypeLink.create(i, c));
}
for (TypeParamDef i : declaredTypeParameters(c)) {
result.put(i.getName(), TypeLink.create(i, c));
}
}

/**
* The type parameter names a scope introduces.
* <p>
* A module instantiation declares the module's parameters so that a receiver written on one
* still resolves once the body has been copied out of the module's scope. It is not an
* {@link AstElementWithTypeParameters}: the arguments are recorded on the instantiation, so
* these are names to look up rather than variables for a call to infer.
*/
private static List<TypeParamDef> declaredTypeParameters(WScope c) {
if (c instanceof AstElementWithTypeParameters wtp) {
return wtp.getTypeParameters();
}
if (c instanceof ModuleInstanciation mi) {
return mi.getTypeParameters();
}
return Collections.emptyList();
}

private static void addJassTypes(ImmutableMultimap.Builder<String, TypeLink> result, CompilationUnit cu) {
Expand Down
Loading
Loading