From 2f7adb9deb5f06e16a433afa3f64dded166e16f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Grabarz?= Date: Thu, 27 Jun 2024 17:11:28 +0200 Subject: [PATCH] Fix missing dropdowns in constructor subexpressions (#10382) Fixes #10341 image Also added a new version of vue devtools that is embedded into the dev app itself, and has much better performance than the browser plugin. --- app/gui2/package.json | 4 +- .../__tests__/widgetFunctionCallInfo.test.ts | 29 +- .../WidgetFunction/widgetFunctionCallInfo.ts | 55 +- .../src/components/widgets/DropdownWidget.vue | 2 +- app/gui2/src/util/callTree.ts | 4 +- app/gui2/vite.config.ts | 2 + app/ide-desktop/lib/client/package.json | 2 +- app/ide-desktop/lib/dashboard/package.json | 2 +- package-lock.json | 2549 ++++++----------- 9 files changed, 1005 insertions(+), 1644 deletions(-) diff --git a/app/gui2/package.json b/app/gui2/package.json index ecbc6db36e..b8d6679e5d 100644 --- a/app/gui2/package.json +++ b/app/gui2/package.json @@ -139,8 +139,8 @@ "tsx": "^4.7.1", "typescript": "~5.2.2", "unbzip2-stream": "^1.4.3", - "vite": "^4.4.9", - "vite-plugin-inspect": "^0.7.38", + "vite": "^5.3.1", + "vite-plugin-vue-devtools": "7.3.4", "vitest": "^1.3.1", "vue-react-wrapper": "^0.3.1", "vue-tsc": "^1.8.27" diff --git a/app/gui2/src/components/GraphEditor/widgets/WidgetFunction/__tests__/widgetFunctionCallInfo.test.ts b/app/gui2/src/components/GraphEditor/widgets/WidgetFunction/__tests__/widgetFunctionCallInfo.test.ts index 42fb050fcc..4875a47904 100644 --- a/app/gui2/src/components/GraphEditor/widgets/WidgetFunction/__tests__/widgetFunctionCallInfo.test.ts +++ b/app/gui2/src/components/GraphEditor/widgets/WidgetFunction/__tests__/widgetFunctionCallInfo.test.ts @@ -40,22 +40,24 @@ const staticMethod = { } test.each` - code | callSuggestion | subjectSpan | selfSpan | subjectType | methodName - ${'val1.method val2'} | ${method} | ${[0, 4]} | ${[0, 4]} | ${'local.Project.Type'} | ${'.method'} - ${'local.Project.Type.method val1 val2'} | ${method} | ${[0, 18]} | ${[26, 30]} | ${'local.Project.Type.type'} | ${'.method'} - ${'Type.method val1'} | ${method} | ${[0, 4]} | ${[12, 16]} | ${'local.Project.Type.type'} | ${'.method'} - ${'local.Project.Type.method'} | ${method} | ${[0, 18]} | ${[0, 18]} | ${'local.Project.Type.type'} | ${'.method'} - ${'local.Project.Type.static_method val1'} | ${staticMethod} | ${[0, 18]} | ${[0, 18]} | ${'local.Project.Type.type'} | ${'.static_method'} - ${'Type.Con val1'} | ${con} | ${[0, 4]} | ${[0, 4]} | ${'local.Project.Type.type'} | ${'.Con'} - ${'..Con val1'} | ${con} | ${null} | ${null} | ${null} | ${'.Con'} - ${'local.Project.module_method val1'} | ${moduleMethod} | ${[0, 13]} | ${[0, 13]} | ${'local.Project'} | ${'.module_method'} + code | callSuggestion | subjectSpan | attachedSpan | subjectType | methodName + ${'val1.method val2'} | ${method} | ${[0, 4]} | ${[0, 4]} | ${'local.Project.Type'} | ${'.method'} + ${'local.Project.Type.method val1 val2'} | ${method} | ${[0, 18]} | ${[26, 30]} | ${'local.Project.Type.type'} | ${'.method'} + ${'Type.method val1'} | ${method} | ${[0, 4]} | ${[12, 16]} | ${'local.Project.Type.type'} | ${'.method'} + ${'local.Project.Type.method'} | ${method} | ${[0, 18]} | ${null} | ${'local.Project.Type.type'} | ${'.method'} + ${'foo.method'} | ${method} | ${[0, 3]} | ${null} | ${'local.Project.Type.type'} | ${'.method'} + ${'foo.method'} | ${method} | ${[0, 3]} | ${[0, 3]} | ${'local.Project.Type'} | ${'.method'} + ${'local.Project.Type.static_method val1'} | ${staticMethod} | ${[0, 18]} | ${[0, 18]} | ${'local.Project.Type.type'} | ${'.static_method'} + ${'Type.Con val1'} | ${con} | ${[0, 4]} | ${[0, 4]} | ${'local.Project.Type.type'} | ${'.Con'} + ${'..Con val1'} | ${con} | ${null} | ${null} | ${null} | ${'.Con'} + ${'local.Project.module_method val1'} | ${moduleMethod} | ${[0, 13]} | ${[0, 13]} | ${'local.Project'} | ${'.module_method'} `( 'Visualization config for $code', - ({ code, callSuggestion, subjectSpan, selfSpan, subjectType, methodName }) => { + ({ code, callSuggestion, subjectSpan, attachedSpan, subjectType, methodName }) => { const spans = { entireFunction: [0, code.length] as [number, number], ...(subjectSpan != null ? { subject: subjectSpan as [number, number] } : {}), - ...(selfSpan != null ? { self: selfSpan as [number, number] } : {}), + ...(attachedSpan != null ? { attached: attachedSpan as [number, number] } : {}), } const { ast, eid, id } = parseWithSpans(code, spans) const line = ast.lines[0]?.expression @@ -101,10 +103,11 @@ test.each` if (typeof visConfig.value.expression === 'string') { expect(visConfig.value.expressionId).toBe(eid('entireFunction')) expect(visConfig.value.expression).toBe( - `_ -> ${WIDGETS_ENSO_MODULE}.${GET_WIDGETS_METHOD} ${callSuggestion.definedIn}`, + `_ -> ${WIDGETS_ENSO_MODULE}.${GET_WIDGETS_METHOD} ${callSuggestion.memberOf}`, ) + expect(eid('attached')).toBeUndefined() } else { - expect(visConfig.value.expressionId).toBe(eid('self')) + expect(visConfig.value.expressionId).toBe(eid('attached')) } expect(visConfig.value.positionalArgumentsExpressions![0]).toBe(methodName) expect(visConfig.value.positionalArgumentsExpressions![1]).toBe("['arg']") diff --git a/app/gui2/src/components/GraphEditor/widgets/WidgetFunction/widgetFunctionCallInfo.ts b/app/gui2/src/components/GraphEditor/widgets/WidgetFunction/widgetFunctionCallInfo.ts index 6117235289..d93aba8283 100644 --- a/app/gui2/src/components/GraphEditor/widgets/WidgetFunction/widgetFunctionCallInfo.ts +++ b/app/gui2/src/components/GraphEditor/widgets/WidgetFunction/widgetFunctionCallInfo.ts @@ -38,13 +38,8 @@ export function useWidgetFunctionCallInfo( useVisualizationData(config: Ref>): Ref | null> }, ) { - const methodCallInfo = computed(() => { - return getMethodCallInfoRecursively(toValue(input).value, graphDb) - }) - - const interpreted = computed(() => { - return interpretCall(toValue(input).value, methodCallInfo.value == null) - }) + const methodCallInfo = computed(() => getMethodCallInfoRecursively(toValue(input).value, graphDb)) + const interpreted = computed(() => interpretCall(toValue(input).value)) const subjectInfo = computed(() => { const analyzed = interpreted.value @@ -60,22 +55,31 @@ export function useWidgetFunctionCallInfo( return funcType != null && subjectInfo.value?.typename !== `${funcType}.type` }) - const selfArgumentExternalId = computed>(() => { + const widgetQuerySubjectExpressionId = computed>(() => { const analyzed = interpreted.value if (analyzed.kind === 'infix') { return analyzed.lhs?.externalId - } else if (methodCallInfo.value?.suggestion.selfType != null) { - const knownArguments = methodCallInfo.value?.suggestion?.arguments - const hasSelfArgument = knownArguments?.[0]?.name === 'self' - const selfArgument = - hasSelfArgument && !selfArgumentPreapplied.value ? - analyzed.args.find((a) => a.argName === 'self' || a.argName == null)?.argument - : getAccessOprSubject(analyzed.func) ?? analyzed.args[0]?.argument - - return selfArgument?.externalId - } else { - return null } + const knownArguments = methodCallInfo.value?.suggestion?.arguments + const hasKnownSelfArgument = knownArguments?.[0]?.name === 'self' + + // First we always want to attach the visualization to the `self` argument, + // whenever we can find an unambiguous expression for it. + if (hasKnownSelfArgument && !selfArgumentPreapplied.value) { + return analyzed.args.find((a) => a.argName === 'self' || a.argName == null)?.argument + ?.externalId + } + + // When no `self` argument can be resolved or it is already applied, attach to the access + // chain subject. This will correctly handle constructors and most common cases with not + // yet resolved methods. + const accessSubject = getAccessOprSubject(analyzed.func) + if (accessSubject) { + return accessSubject.externalId + } + // In other cases (e.g. autoscoped expression) there is no good existing + // expression to attach the visualization to. Fallback to synthetic type-based expression. + return null }) const visualizationConfig = computed>(() => { @@ -84,7 +88,6 @@ export function useWidgetFunctionCallInfo( methodCallInfo.value, ) - const selfArgId = selfArgumentExternalId.value const info = methodCallInfo.value if (!info) return null const annotatedArgs = info.suggestion.annotations @@ -95,9 +98,11 @@ export function useWidgetFunctionCallInfo( Ast.Vector.build(annotatedArgs, Ast.TextLiteral.new).code(), Ast.TextLiteral.new(JSON.stringify(args)).code(), ] - if (selfArgId != null) { + + const expressionId = widgetQuerySubjectExpressionId.value + if (expressionId != null) { return { - expressionId: selfArgId, + expressionId, visualizationModule: WIDGETS_ENSO_MODULE, expression: { module: WIDGETS_ENSO_MODULE, @@ -107,12 +112,12 @@ export function useWidgetFunctionCallInfo( positionalArgumentsExpressions, } } else { - // In the case when no self argument is present (for example in autoscoped constructor), - // we assume that this is a static function call. + // In the case when no clear subject expression exists (for example in autoscoped constructor), + // we assume that this is a static function call and create the subject by using resolved type name. return { expressionId: toValue(input).value.externalId, visualizationModule: WIDGETS_ENSO_MODULE, - expression: `_ -> ${WIDGETS_ENSO_MODULE}.${GET_WIDGETS_METHOD} ${info.suggestion.definedIn}`, + expression: `_ -> ${WIDGETS_ENSO_MODULE}.${GET_WIDGETS_METHOD} ${info.suggestion.memberOf ?? info.suggestion.definedIn}`, positionalArgumentsExpressions, } } diff --git a/app/gui2/src/components/widgets/DropdownWidget.vue b/app/gui2/src/components/widgets/DropdownWidget.vue index 1d778935cd..49df0a2013 100644 --- a/app/gui2/src/components/widgets/DropdownWidget.vue +++ b/app/gui2/src/components/widgets/DropdownWidget.vue @@ -73,7 +73,7 @@ export interface DropdownEntry {