Skip to content

Conversation

@Kampfkarren
Copy link
Contributor

This adds in support for Explicit type parameter instantiation.

This PR is still a work in progress, but some important notes:

  1. Metatables with __call are not supported. While t<<A>>() is obvious what it should do with __call, it's not obvious what t<<A>> on its own would be.
  2. Intersection types are not supported at the moment either.

Both of these are possible to bring later.

@karl-police
Copy link
Contributor

😮
How did you learn about Conformance and all the other .cpp files?

typeParameters.push_back(resolveType(
scope,
typeOrPack.type,
true // todo soon: what does this (inTypeArguments) do?
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is checked in one place and I don't really understand what I should do:

            // If we're not in a type argument context, we need to create a constraint that expands this.
            // The dispatching of the above constraint will queue up additional constraints for nested
            // type function applications.
            if (!inTypeArguments)
                addConstraint(scope, ty->location, TypeAliasExpansionConstraint{/* target */ result});

Copy link
Contributor

@alexmccord alexmccord Aug 30, 2025

Choose a reason for hiding this comment

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

It's an outside-in expansion. If you have F<G<H<T>>>, you only need one constraint to expand the outermost type function application. Expansion of the outermost enqueues additional TypeAliasExpansionConstraint to the inner type arguments.

Say type F<T> = { tag: "F", payload: T }, type G<T> = { tag: "G", payload: T }, and type H<T> = { tag: "H", payload: T }.

First, F<G<H<T>>> produces a blocked type. Second, expansion of F gives us { tag: "F", payload: *blocked-type-0* } and enqueues a constraint to expand *blocked-type-0* with the result of G<H<T>>. Repeat this algorithm, and you get { tag: "F", payload: { tag: "G", payload: *blocked-type-1* } }. Repeat once again, and you get { tag: "F", payload: { tag: "G", payload: { tag: "H", payload: T } } }.

Doing it outside-in is important to tie the knot for cyclic type function applications. If you enqueue them all with nothing controlling the expansion, you get Turing completeness.

Copy link
Contributor

@alexmccord alexmccord Aug 30, 2025

Choose a reason for hiding this comment

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

In this case, you should be using false in both calls to resolveType and resolveTypePack in the getExplicitTypeIds function. They'll recurse and set that argument to true. Otherwise local x: F<T> = identity<<F<T>>>() will fail to type check! (because you have two different PendingExpansionTypes, and if they aren't pointer equal, they do not unify)

typeParameters.push_back(resolveType(
scope,
typeOrPack.type,
true // todo soon: what does this (inTypeArguments) do?
Copy link
Contributor

@alexmccord alexmccord Aug 30, 2025

Choose a reason for hiding this comment

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

Suggested change
true // todo soon: what does this (inTypeArguments) do?
/* inTypeArguments */ false

typePackParameters.push_back(resolveTypePack(
scope,
typeOrPack.typePack,
true // todo soon: what does this (inTypeArguments) do?
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
true // todo soon: what does this (inTypeArguments) do?
/* inTypeArguments */ false

Comment on lines 3258 to 3259
std::vector<TypeId> typeParameters;
std::vector<TypePackId> typePackParameters;
Copy link
Contributor

Choose a reason for hiding this comment

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

Also should be called arguments. Parameters are things you define. You supply arguments to each parameter.

function f(param1, param2) end
f(1, 2) -- two arguments

@Kampfkarren Kampfkarren marked this pull request as ready for review September 3, 2025 16:49
@aatxe aatxe self-assigned this Oct 4, 2025
Comment on lines +158 to +159
// FIXME: This triggers a GenericTypePackCountMismatch error, and it's not obvious if the
// code for explicit types is broken, or if subtyping is broken.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Probably subtyping tbf, but this might've been fixed since you wrote it.

Intersection,
};

InterestingEdgeCase interestingEdgeCase;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah, that would be fine. I think it probably belongs in the error message too, but we'll probably want to play with it more to see if that's actually true. Either way, will make it easier to test and debug anything interacting with intersection.

@aatxe aatxe merged commit 87133b6 into luau-lang:master Nov 18, 2025
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants