-
Notifications
You must be signed in to change notification settings - Fork 246
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
fix(rosetta): Python translation for implements
is wrong
#3280
Changes from 2 commits
909df6f
ed5001a
28a3e9e
b42f110
0f4de87
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
import * as ts from 'typescript'; | ||
|
||
import { analyzeObjectLiteral, ObjectLiteralStruct } from '../jsii/jsii-types'; | ||
import { isNamedLikeStruct } from '../jsii/jsii-utils'; | ||
import { isNamedLikeStruct, isJsiiProtocolType } from '../jsii/jsii-utils'; | ||
import { OTree, NO_SYNTAX } from '../o-tree'; | ||
import { AstRenderer, AstHandler, nimpl, CommentSyntax } from '../renderer'; | ||
import { voidExpressionString } from '../typescript/ast-utils'; | ||
|
@@ -153,6 +153,22 @@ export abstract class DefaultVisitor<C> implements AstHandler<C> { | |
context.report(unsup, `Use of ${ts.SyntaxKind[unsup.kind]} in an object literal is not supported.`); | ||
} | ||
|
||
const anyMembersFunctions = node.properties.some((p) => | ||
ts.isPropertyAssignment(p) | ||
? isExpressionOfFunctionType(context.typeChecker, p.initializer) | ||
: ts.isShorthandPropertyAssignment(p) | ||
? isExpressionOfFunctionType(context.typeChecker, p.name) | ||
: false, | ||
); | ||
|
||
const inferredType = context.inferredTypeOfExpression(node); | ||
if ((inferredType && isJsiiProtocolType(context.typeChecker, inferredType)) || anyMembersFunctions) { | ||
context.report( | ||
node, | ||
`You cannot use an object literal to make an instance of an interface. Define a class instead.`, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is fine for most languages, but do python users know to use the decorator I guess a correct example translation in Python will go a long ways in documenting that... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This message is intended for example authors, who are authoring in TypeScript. At that point, we're not even talking about Python yet. (By the way, the same limitations [sorta] holds for C#, and technically it would be possible to define and instantiate an anonymous class implementing an interface in Java, but Rosetta doesn't support it. There's also no point to supporting it in Java as C# and Python won't be able to do the same trick) |
||
); | ||
} | ||
|
||
const lit = analyzeObjectLiteral(context.typeChecker, node); | ||
|
||
switch (lit.kind) { | ||
|
@@ -342,3 +358,8 @@ const UNARY_OPS: { [op in ts.PrefixUnaryOperator]: string } = { | |
[ts.SyntaxKind.TildeToken]: '~', | ||
[ts.SyntaxKind.ExclamationToken]: '!', | ||
}; | ||
|
||
function isExpressionOfFunctionType(typeChecker: ts.TypeChecker, expr: ts.Expression) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you document this function and For example, an expression of function is something like |
||
const type = typeChecker.getTypeAtLocation(expr).getNonNullableType(); | ||
return type.getCallSignatures().length > 0; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,7 @@ import { | |
JsiiSymbol, | ||
simpleName, | ||
namespaceName, | ||
isJsiiProtocolType, | ||
} from '../jsii/jsii-utils'; | ||
import { jsiiTargetParameter } from '../jsii/packages'; | ||
import { TargetLanguage } from '../languages/target-language'; | ||
|
@@ -23,7 +24,7 @@ import { | |
} from '../typescript/ast-utils'; | ||
import { ImportStatement } from '../typescript/imports'; | ||
import { parameterAcceptsUndefined } from '../typescript/types'; | ||
import { startsWithUppercase, flat, sortBy, groupBy, fmap } from '../util'; | ||
import { startsWithUppercase, sortBy, groupBy, fmap } from '../util'; | ||
import { DefaultVisitor } from './default'; | ||
|
||
interface StructVar { | ||
|
@@ -102,7 +103,7 @@ export class PythonVisitor extends DefaultVisitor<PythonLanguageContext> { | |
* Bump this when you change something in the implementation to invalidate | ||
* existing cached translations. | ||
*/ | ||
public static readonly VERSION = '1'; | ||
public static readonly VERSION = '2'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would just like a sanity check here for something I think is okay. We ignore language versioning for infused snippets in #3291. But that is okay because we infuse each time and there is no way the version changes between the time we infuse and extract in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exactly, the version is not going to change. |
||
|
||
public readonly language = TargetLanguage.PYTHON; | ||
public readonly defaultContext = {}; | ||
|
@@ -532,10 +533,18 @@ export class PythonVisitor extends DefaultVisitor<PythonLanguageContext> { | |
} | ||
|
||
public classDeclaration(node: ts.ClassDeclaration, context: PythonVisitorContext): OTree { | ||
const heritage = flat(Array.from(node.heritageClauses ?? []).map((h) => Array.from(h.types))).map((t) => | ||
context.convert(t.expression), | ||
const allHeritageClauses = Array.from(node.heritageClauses ?? []).flatMap((h) => Array.from(h.types)); | ||
|
||
// List of booleans matching `allHeritage` array | ||
const isJsii = allHeritageClauses.map( | ||
(e) => | ||
fmap(context.typeOfExpression(e.expression), (type) => isJsiiProtocolType(context.typeChecker, type)) ?? false, | ||
); | ||
const hasHeritage = heritage.length > 0; | ||
|
||
const jsiiImplements = allHeritageClauses.filter((_, i) => isJsii[i]); | ||
|
||
const inlineHeritage = allHeritageClauses.filter((_, i) => !isJsii[i]); | ||
const hasHeritage = inlineHeritage.length > 0; | ||
|
||
const members = context.updateContext({ inClass: true }).convertAll(node.members); | ||
if (members.length === 0) { | ||
|
@@ -544,10 +553,11 @@ export class PythonVisitor extends DefaultVisitor<PythonLanguageContext> { | |
|
||
const ret = new OTree( | ||
[ | ||
...jsiiImplements.flatMap((i) => ['@jsii.implements(', context.convert(i.expression), ')\n']), | ||
'class ', | ||
node.name ? context.textOf(node.name) : '???', | ||
hasHeritage ? '(' : '', | ||
...heritage, | ||
...inlineHeritage.map((t) => context.convert(t.expression)), | ||
hasHeritage ? ')' : '', | ||
': ', | ||
], | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
class MyClass : IResolvable | ||
{ | ||
public object Resolve() | ||
{ | ||
return 42; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
public class MyClass implements IResolvable { | ||
public Object resolve() { | ||
return 42; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
@jsii.implements(IResolvable) | ||
class MyClass: | ||
def resolve(self): | ||
return 42 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/// !hide | ||
/// fake-from-jsii | ||
interface IResolvable { | ||
resolve(): any; | ||
} | ||
/// !show | ||
|
||
class MyClass implements IResolvable { | ||
public resolve(): any { | ||
return 42; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this checking for? I understand the
anyMembersFunctions
piece, but don't understand whatisJsiiProtocolType
is checking for.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Is this type a non-struct
interface
type that comes from jsii"interface
: we useinterface
s for 2 purposes, and we constantly have to make distinctions between them. The terms I commonly use are "structs" and "protocols" (though we also use data types for the first, and "behavioral interfaces" for the second).