Skip to content
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

Add basic generics support #37

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft

Add basic generics support #37

wants to merge 6 commits into from

Conversation

heyitsaamir
Copy link

@heyitsaamir heyitsaamir commented Dec 1, 2020

This PR adds basic generics support to ts2gql.

Example

export interface MultipleGenerics<T, U> {
  objectT: T;
  arrayObjectU: U[];
  extraNonGenericProp: boolean;
}

export interface StringObjectNumberArray extends MultipleGenerics<string, number> {
  ownProp: boolean;
}

export interface StringObjectNumberArrayObjectNumberArray extends MultipleGenerics<StringObjectNumberArray, number> {
  ownProp: boolean;
}

results in:

type StringObjectNumberArray {
  arrayObjectU: [Float]
  extraNonGenericProp: Boolean
  objectT: String
  ownProp: Boolean
}

type StringObjectNumberArrayObjectNumberArray {
  arrayObjectU: [Float]
  extraNonGenericProp: Boolean
  objectT: StringObjectNumberArray
  ownProp: Boolean
}

Left to do:

  1. Tests for cases that will throw
  2. Adding a few more tests for multiple properties for the same type

Copy link
Contributor

@nevir nevir left a comment

Choose a reason for hiding this comment

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

High level review, structurally I think it looks pretty good. As to whether we think it's a good idea to support, I leave that to others ;)

Only niggle on the terminology side is that it's really "templated type" / "type templates" (affecting only the type system) rather than "generics" (tends to mean generated code per type in most contexts)

"port": 9229
}
]
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Awesome, thanks for setting this up!

for (const clause of node.typeParameters) {
const type = this.checker.getTypeAtLocation(clause);
const symbol = type.getSymbol();
if (symbol) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, in what case would you not have a symbol here? (seems like it might be worth an assertion that the symbol exists?)

if (typeRef.type === 'expressionWithTypeNode' && typeRef.referenceNodes.some(r => r.type === 'reference' && r.referenceNodes.length > 0)) {
console.error(type);
console.error(type.getSourceFile().fileName);
throw new Error(`Generic inheritance cannot have another generic. Try using a named type instead. ${node.getText()}`);
Copy link
Contributor

Choose a reason for hiding this comment

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

No nested generics? booo! :P

console.error(type.getSourceFile().fileName);
throw new Error(`Generic inheritance cannot have another generic. Try using a named type instead. ${node.getText()}`);
}
// get the symbol + typeref. if it's
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, you're leaving me hanging here

}

return this._addType(node, () => {
const inherits = [];
const typeParameters: string[] = [];
if (node.heritageClauses) {
Copy link
Contributor

Choose a reason for hiding this comment

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

For future PRs, you might also want to support type aliases. E.g.

type StringNumberArray = MultipleGenerics<string, number>;

(if we are likely to do this)

Copy link
Contributor

Choose a reason for hiding this comment

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

Could add a TODO comment for this if you plan to punt for now, @heyitsaamir

Comment on lines +284 to +293
if (m.type === 'property') {
if (m.signature.type === 'genericPropertyNode' && m.signature.signature === t) {
newGenericMapForNode.set(m.name, referenceNode)
} else if (m.signature.type === 'array') {
const arrayType = m.signature.elements[0];
if (arrayType.type === 'genericPropertyNode' && arrayType.signature === t) {
newGenericMapForNode.set(m.name, referenceNode)
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

You may end up wanting to make this its own (recursive) visitor in order to handle more complex types if we see them in use (e.g. things like foo: { bar: TSomeTemplateType }

if (m.signature.type === 'genericPropertyNode' && m.signature.signature === t) {
newGenericMapForNode.set(m.name, referenceNode)
} else if (m.signature.type === 'array') {
const arrayType = m.signature.elements[0];
Copy link
Contributor

Choose a reason for hiding this comment

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

Definitely will want to handle other elements (unions, I think) or assert this is the only case here

Copy link
Contributor

@finnigantime finnigantime left a comment

Choose a reason for hiding this comment

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

Awesome! LGTM so far.

source ./scripts/include/node.sh

npm run clean
tsc-watch "${@}"
Copy link
Contributor

Choose a reason for hiding this comment

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

nice!

}

return this._addType(node, () => {
const inherits = [];
const typeParameters: string[] = [];
if (node.heritageClauses) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could add a TODO comment for this if you plan to punt for now, @heyitsaamir

_referenceForSymbol(symbol:typescript.Symbol):types.ReferenceNode {
this._walkSymbol(symbol);
_referenceForSymbol(symbol: typescript.Symbol, node: typescript.TypeReferenceNode): types.ReferenceNode {
this._walkSymbol(symbol); //getTypeReferenceName
Copy link
Contributor

Choose a reason for hiding this comment

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

? delete?

return `enum ${this._name(name)} {\n${this._indent(node.values)}\n}`;
}

_emitExpression = (node:Types.Node):string => {
_emitExpression = (node: Types.Node, possibleGenericReferenceNode?: Types.ReferenceNode): string => {
Copy link
Contributor

Choose a reason for hiding this comment

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

why have the possible prefix? The ? already indicates the param may be undefined

})
.join(', ');
} else if (node.type === 'genericPropertyNode') {
if (!possibleGenericReferenceNode) {
throw new Error(`node ${node.signature} was not resolved`);
Copy link
Contributor

Choose a reason for hiding this comment

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

In what case is this expected? This error message doesn't point at the cause or resolution.

@@ -219,7 +234,7 @@ export default class Emitter {
if (interfaceNode.inherits.length > 1) {
throw new Error(`No support for multiple inheritence: ${JSON.stringify(interfaceNode.inherits)}`);
} else if (interfaceNode.inherits.length === 1) {
const supertype:Types.Node = this.types[interfaceNode.inherits[0]];
const supertype: Types.Node = this.types[interfaceNode.inherits[0].name];
Copy link
Contributor

Choose a reason for hiding this comment

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

was this an unrelated bug?

}

} else {
// throw
Copy link
Contributor

Choose a reason for hiding this comment

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

fill in

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.

3 participants