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
34 changes: 32 additions & 2 deletions BACKLOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -65,8 +65,38 @@ itself, and one gap in what the suite can see.

Do not start this autonomously.

7. **Module bounds.** `module M<T: Show>` is rejected with a clear message today. Needs
receiver rewriting during expansion, or type parameters on `ModuleInstanciation`.
7. **Module bounds.** `module M<T: Show>` is rejected with a clear message today, and
`TypeClassTests.boundOnModuleTypeParameterIsRejected` pins that. Tried and reverted; what follows
is why, because the earlier note here suggested a fix which cannot work.

Expansion copies the module body into the user and replaces the module's type parameters
**in type positions**. A requirement is called on the parameter itself — `T.show(x)` — and that
receiver is a name, resolved by `lookupBoundedTypeParam` through `lookupType`, so the replacement
never touches it.

Renaming the receiver to the using class's parameter, which is what "receiver rewriting during
expansion" meant, does not work. `NameResolution.nextScope` sends a `ModuleInstanciation` to
`attrModuleOrigin()` rather than to the class using it:

if (currentScope instanceof ModuleInstanciation) {
return nextScope(moduleInstanciation.attrModuleOrigin());
}

That is deliberate — a module body resolves in the module's own scope so it cannot capture the
names of whoever uses it — so the renamed receiver names something that scope cannot see. The
rename itself works: with it, the error moves from the rejection to `Could not find variable K`
at the dispatch, which is this scope rule and not a mistake in the rename.

That leaves the other half of the original note: **type parameters on `ModuleInstanciation`**. The
instantiation declares the parameter itself, bound to the argument, so the copied body keeps
saying `T` and `T` resolves without any rename. It needs `ModuleInstanciation` to carry type
parameters in the grammar, so it is a change to `wurstscript.parseq` and everything reading that
node, not a patch to the expander.

Worth knowing before starting: an argument which is a concrete type (`use Shower<int>`) is a
second case even then. A requirement is dispatched on a type parameter, so `int.show(x)` is not a
dispatch at all — that one has to resolve to the instance during expansion rather than resolve by
name.

9. **Keep `WURST_LANGUAGE.md` and `CHANGELOG.md` current** as items land — a standing practice
rather than a task to finish. `WURST_LANGUAGE.md` is tracked, at
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -451,6 +451,54 @@ public void aBoundedMethodParameterInAGenericClassIsRejectedForLua() {
.lines(SAME_NAMED_BOUNDED_PARAMETERS);
}

/**
* A module may carry a bounded type parameter, and a class using it supplies the argument. Using
* a module copies its body into the class, substituting the module's type parameters — and a
* requirement is called on the parameter itself, {@code T.show(x)}, which is a name rather than a
* type, so the substitution never reaches it.
*/
private static final String[] BOUND_ON_MODULE = {
"package test",
"native testSuccess()",
"interface Show<T:>",
" function show(T x) returns int",
"implements Show<int>",
" function show(int x) returns int",
" return x * 2",
"module Shower<T: Show>",
" T held",
" function shown() returns int",
" return T.show(held)",
"class Holder<K: Show>",
" use Shower<K>",
" construct(K k)",
" held = k",
"init",
" let h = new Holder<int>(21)",
" if h.shown() == 42",
" testSuccess()",
};

/**
* Rejected, and this pins that it is rejected clearly rather than mistranslated.
* <p>
* A module body resolves names in the module's own scope by design — {@code nextScope} sends a
* {@code ModuleInstanciation} to {@code attrModuleOrigin()} rather than to the class using it, so
* a module cannot capture the names of whoever uses it. The type replacement during expansion
* therefore reaches every {@code T} used as a type, and cannot reach the one in {@code T.show(x)}
* which is a name: renaming it to the using class's parameter produces a name that scope
* deliberately cannot see.
* <p>
* Making it work means the instantiation declaring the parameter itself, so the body keeps saying
* {@code T} and {@code T} resolves — type parameters on {@code ModuleInstanciation}, which is a
* grammar change. Backlog item 7.
*/
@Test
public void boundOnModuleTypeParameterIsRejected() {
testAssertErrorsLines(false, "Type class bounds are not supported on a module type parameter",
BOUND_ON_MODULE);
}

/** Each type argument picks its own instance, so one generic serves several types. */
@Test
public void twoInstancesOfOneClass() {
Expand Down
Loading