Skip to content

Support type variables in MaD typings #10205

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Aug 30, 2022
Merged

Support type variables in MaD typings #10205

merged 9 commits into from
Aug 30, 2022

Conversation

asgerf
Copy link
Contributor

@asgerf asgerf commented Aug 29, 2022

This adds some features related to generics, to support generating MaD typings from .d.ts files and similar.

Type variables

To track types through generics, we encode all the paths leading from some type to all uses of its type variables.
For example for this type,

interface Node<T> {
  value: T | undefined;
  resolve(): Promise<T>;
  next: Node<T>;
}

we'd generate the following rows in a table "type variable" table of form name;path:

Node.T;Member[value]
Node.T;Member[resolve].ReturnValue.Awaited
Node.T;Member[next].TypeVar[Node.T]

Suppose we want to record how to obtain an instance of ImportantObject from mylib, given this declaration

export function create(): Node<ImportantObject[]>;

we'd generate this type-definition row:

mylib;ImportantObject;mylib;;Member[init].ReturnValue.TypeVar[Node.T].ArrayElement

The rule is that the access path token TypeVar[name] can be expanded to any of the paths given by a name;path row with a matching name. Type variables are really just non-terminals in a context-free grammar, but since they have a 1:1 relationship with type variables, I decided to name them after this use-case.

Type-variable rows are thus similar to type-definitions in that the offer a way to reuse access paths, except they can occur anywhere in an access path, whereas type definitions can only occur at the beginning. Also, type-definitions are associated with a set of API nodes, whereas type variables are just intermediate steps and not themselves associated with API nodes (you can ask for all uses of a given type, but not for all uses of a given type variable).

Whythis types are no longer type variables

this types were previously encoded as an extra type variable, but this caused a lot of TypeVar tokens to occur and became really hard to understand. To demonstrate, suppose Node<T> from above had a method returning this:

interface Node<T> {
  ...
  foo(): this;
}

This meant TypeVar[Node.T] must be expanded to take into account that any number of foo() calls may occur initially:

Node.this;
Node.this;TypeVar[Node.this].Method[foo].ReturnValue
Node.T;TypeVar[Node.this].Member[value]
Node.T;TypeVar[Node.this].Member[resolve].ReturnValue.Awaited
Node.T;TypeVar[Node.this].Member[next].TypeVar[Node.T]

In practice these this types show up in a lot of places, like every time a type extending EventEmitter was used.

Type steps

Instead of the above, a this type gives rise to a "type step", which is a kind of step between API nodes, which we use for propagating type information. It could also be used as a value-preserving flow step, but for now we just propagate type information.

The Foo type above would give rise to this summary row:

mylib;Foo;;Member[foo].ReturnValue;type

meaning that whenever we see a call to x.foo() where the x might have type Foo, we add a type step from x to x.foo().

@asgerf asgerf added the no-change-note-required This PR does not need a change note label Aug 29, 2022
@asgerf asgerf marked this pull request as ready for review August 29, 2022 12:29
@asgerf asgerf requested review from a team as code owners August 29, 2022 12:29
Copy link
Contributor

@erik-krogh erik-krogh left a comment

Choose a reason for hiding this comment

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

I had a really hard figuring out how each of the predicates work, but I think it makes sense now.
(I did a whole lot of quick-evals using your test-case).

But there's one part that turned out empty in the quick-eval, and that part I still don't get.

Comment on lines +464 to +479
pragma[nomagic]
private predicate typeStepModel(string package, string type, AccessPath basePath, AccessPath output) {
summaryModel(package, type, basePath, "", output, "type")
}

pragma[nomagic]
private predicate typeStep(API::Node pred, API::Node succ) {
exists(string package, string type, AccessPath basePath, AccessPath output |
typeStepModel(package, type, basePath, output) and
pred = getNodeFromPath(package, type, basePath) and
succ = getNodeFromSubPath(pred, output)
)
}

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't really get these, and they also don't seem to be used in your tests?

A comment in getNodeFromPath() says that typeStep() is a receiver step, but I don't see where that comes from?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, there really should have been tests for this. Added some tests, which also revealed a bug in getNodeFromSubPath which I fixed.

A comment in getNodeFromPath() says that typeStep() is a receiver step, but I don't see where that comes from?

I renamed it from "receiver step" to "type step" and I missed that comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had to rebase to fix a conflict, but could you take a look at the commits called:

  • JS: add partially failing test
  • Also apply type step in getNodeFromSubPath
  • Fix typo: receiver step -> type step

erik-krogh
erik-krogh previously approved these changes Aug 30, 2022
@asgerf asgerf merged commit 5ad6c05 into github:main Aug 30, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
JS no-change-note-required This PR does not need a change note Python Ruby
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants