diff --git a/packages/jsii-rosetta/lib/jsii/jsii-utils.ts b/packages/jsii-rosetta/lib/jsii/jsii-utils.ts
index 40ad0aa827..05d363b05d 100644
--- a/packages/jsii-rosetta/lib/jsii/jsii-utils.ts
+++ b/packages/jsii-rosetta/lib/jsii/jsii-utils.ts
@@ -15,7 +15,7 @@ export function isStructType(type: ts.Type) {
);
}
-function hasFlag(flags: A, test: A) {
+export function hasFlag(flags: A, test: A) {
// tslint:disable-next-line:no-bitwise
return (flags & test) !== 0;
}
diff --git a/packages/jsii-rosetta/lib/languages/default.ts b/packages/jsii-rosetta/lib/languages/default.ts
index 36c79ce586..ed93de511b 100644
--- a/packages/jsii-rosetta/lib/languages/default.ts
+++ b/packages/jsii-rosetta/lib/languages/default.ts
@@ -1,6 +1,6 @@
import * as ts from 'typescript';
-import { isStructInterface, isStructType } from '../jsii/jsii-utils';
+import { hasFlag, isStructInterface, isStructType } from '../jsii/jsii-utils';
import { OTree, NO_SYNTAX } from '../o-tree';
import { AstRenderer, AstHandler, nimpl, CommentSyntax } from '../renderer';
import { voidExpressionString } from '../typescript/ast-utils';
@@ -132,7 +132,21 @@ export abstract class DefaultVisitor implements AstHandler {
public objectLiteralExpression(node: ts.ObjectLiteralExpression, context: AstRenderer): OTree {
const type = typeWithoutUndefinedUnion(context.inferredTypeOfExpression(node));
- const isUnknownType = !type || !type.symbol;
+ let isUnknownType = !type;
+ if (type && hasFlag(type.flags, ts.TypeFlags.Any)) {
+ // The type checker by itself won't tell us the difference between an `any` that
+ // was literally declared as a type in the code, vs an `any` it assumes because it
+ // can't find a function's type declaration.
+ //
+ // Search for the function's declaration and only if we can't find it,
+ // the type is actually unknown (otherwise it's a literal 'any').
+ const call = findEnclosingCallExpression(node);
+ const signature = call ? context.typeChecker.getResolvedSignature(call) : undefined;
+ if (!signature?.declaration) {
+ isUnknownType = true;
+ }
+ }
+
const isKnownStruct = type && isStructType(type);
if (isUnknownType) {
@@ -317,3 +331,14 @@ const UNARY_OPS: { [op in ts.PrefixUnaryOperator]: string } = {
[ts.SyntaxKind.TildeToken]: '~',
[ts.SyntaxKind.ExclamationToken]: '~',
};
+
+function findEnclosingCallExpression(node?: ts.Node): ts.CallLikeExpression | undefined {
+ while (node) {
+ if (ts.isCallLikeExpression(node)) {
+ return node;
+ }
+ node = node.parent;
+ }
+
+ return undefined;
+}
diff --git a/packages/jsii-rosetta/lib/typescript/types.ts b/packages/jsii-rosetta/lib/typescript/types.ts
index 1bc1d76188..a74a6f1fa7 100644
--- a/packages/jsii-rosetta/lib/typescript/types.ts
+++ b/packages/jsii-rosetta/lib/typescript/types.ts
@@ -90,9 +90,24 @@ export function inferMapElementType(
elements: ts.NodeArray,
renderer: AstRenderer,
): ts.Type | undefined {
- return typeIfSame(
- elements.map((el) => (ts.isPropertyAssignment(el) ? renderer.typeOfExpression(el.initializer) : undefined)),
- );
+ const nodes = elements.map(elementValueNode).filter(isDefined);
+ const types = nodes.map((x) => renderer.typeOfExpression(x));
+
+ return types.every((t) => isSameType(types[0], t)) ? types[0] : undefined;
+
+ function elementValueNode(el: ts.ObjectLiteralElementLike): ts.Expression | undefined {
+ if (ts.isPropertyAssignment(el)) {
+ return el.initializer;
+ }
+ if (ts.isShorthandPropertyAssignment(el)) {
+ return el.name;
+ }
+ return undefined;
+ }
+}
+
+function isSameType(a: ts.Type, b: ts.Type) {
+ return a.flags === b.flags && a.symbol?.name === b.symbol?.name;
}
function typeIfSame(types: Array): ts.Type | undefined {
@@ -120,3 +135,7 @@ export function arrayElementType(type: ts.Type): ts.Type | undefined {
}
return undefined;
}
+
+function isDefined(x: A): x is NonNullable {
+ return x !== undefined;
+}
diff --git a/packages/jsii-rosetta/test/translations/statements/vararg_any_call.cs b/packages/jsii-rosetta/test/translations/statements/vararg_any_call.cs
index 67563261ff..6d653a93e7 100644
--- a/packages/jsii-rosetta/test/translations/statements/vararg_any_call.cs
+++ b/packages/jsii-rosetta/test/translations/statements/vararg_any_call.cs
@@ -2,6 +2,6 @@ public void Test(Array _args)
{
}
-Test(new Struct { Key = "Value", Also = 1337 });
+Test(new Dictionary { { "Key", "Value" }, { "also", 1337 } });
-Test(new Struct { Key = "Value" }, new Struct { Also = 1337 });
+Test(new Dictionary { { "Key", "Value" } }, new Dictionary { { "also", 1337 } });
diff --git a/packages/jsii-rosetta/test/translations/structs/any_type_never_a_struct.cs b/packages/jsii-rosetta/test/translations/structs/any_type_never_a_struct.cs
new file mode 100644
index 0000000000..f153ccb3c3
--- /dev/null
+++ b/packages/jsii-rosetta/test/translations/structs/any_type_never_a_struct.cs
@@ -0,0 +1,3 @@
+FunctionThatTakesAnAny(new Dictionary {
+ { "argument", 5 }
+});
\ No newline at end of file
diff --git a/packages/jsii-rosetta/test/translations/structs/any_type_never_a_struct.java b/packages/jsii-rosetta/test/translations/structs/any_type_never_a_struct.java
new file mode 100644
index 0000000000..5393bf1b9a
--- /dev/null
+++ b/packages/jsii-rosetta/test/translations/structs/any_type_never_a_struct.java
@@ -0,0 +1,2 @@
+functionThatTakesAnAny(Map.of(
+ "argument", 5));
\ No newline at end of file
diff --git a/packages/jsii-rosetta/test/translations/structs/any_type_never_a_struct.py b/packages/jsii-rosetta/test/translations/structs/any_type_never_a_struct.py
new file mode 100644
index 0000000000..bab97133f8
--- /dev/null
+++ b/packages/jsii-rosetta/test/translations/structs/any_type_never_a_struct.py
@@ -0,0 +1,3 @@
+function_that_takes_an_any({
+ "argument": 5
+})
\ No newline at end of file
diff --git a/packages/jsii-rosetta/test/translations/structs/any_type_never_a_struct.ts b/packages/jsii-rosetta/test/translations/structs/any_type_never_a_struct.ts
new file mode 100644
index 0000000000..91dca55b13
--- /dev/null
+++ b/packages/jsii-rosetta/test/translations/structs/any_type_never_a_struct.ts
@@ -0,0 +1,6 @@
+/// !hide
+function functionThatTakesAnAny(opts: any) { }
+/// !show
+functionThatTakesAnAny({
+ argument: 5
+});
\ No newline at end of file