diff --git a/gitnexus/src/core/ingestion/languages/cpp/adl.ts b/gitnexus/src/core/ingestion/languages/cpp/adl.ts index bb28c3853..fd65ae51e 100644 --- a/gitnexus/src/core/ingestion/languages/cpp/adl.ts +++ b/gitnexus/src/core/ingestion/languages/cpp/adl.ts @@ -21,8 +21,9 @@ * - `audit::Event& r`, `audit::Event&& rr` * - `std::vector` (template namespace + template-arg namespaces) * - * Function-pointer arguments, base-class associated namespaces, and the rest - * of the full closure are still deliberately excluded. + * Function-pointer arguments and the rest of the full closure are still + * deliberately excluded. V2 additionally walks class ancestors (via MRO), + * so base-class enclosing namespaces also contribute associated namespaces. * * The current implementation also short-circuits to ADL only when ordinary lookup is empty * (`findCallableBindingInScope` returned undefined). ISO C++ would @@ -259,10 +260,19 @@ function addAssociatedNamespaceForClassName( associatedNamespaces: Set, ): void { if (simpleClassName.length === 0) return; - const classDef = findCppClassDefBySimpleName(simpleClassName, scopes); - if (classDef === undefined) return; + const classLookup = findCppClassDefBySimpleName(simpleClassName, scopes); + if (classLookup === undefined) return; + const { classDef, ambiguous } = classLookup; const nsQName = classToNamespaceQualifiedName.get(classDef.nodeId); if (nsQName !== undefined) associatedNamespaces.add(nsQName); + // Preserve V1 collision behavior for the direct class namespace, but avoid + // amplifying a same-simple-name collision by walking an arbitrary class's + // full MRO chain. + if (ambiguous) return; + for (const ancestorDefId of scopes.methodDispatch.mroFor(classDef.nodeId)) { + const ancestorNsQName = classToNamespaceQualifiedName.get(ancestorDefId); + if (ancestorNsQName !== undefined) associatedNamespaces.add(ancestorNsQName); + } } /** Walk upward from a Class scope, finding the innermost enclosing @@ -357,17 +367,25 @@ function findNamespaceDefInScope(scope: { } /** Find a class-like def by simple name across the workspace. V1 - * arbitrary-pick on collisions (multiple classes share the simple name); - * C++ ADL strictness would require full type-driven lookup, but V1 - * trades that for simplicity. */ + * still arbitrary-picks the first class on collisions (multiple classes + * share the simple name), but reports the collision so callers can avoid + * amplifying that uncertainty (for example by skipping MRO expansion). + * C++ ADL strictness would require full type-driven lookup. */ function findCppClassDefBySimpleName( simpleName: string, scopes: ScopeResolutionIndexes, -): SymbolDefinition | undefined { +): { classDef: SymbolDefinition; ambiguous: boolean } | undefined { + let firstMatch: SymbolDefinition | undefined; for (const def of scopes.defs.byId.values()) { if (def.type !== 'Class' && def.type !== 'Struct' && def.type !== 'Interface') continue; const simple = def.qualifiedName?.split('.').pop() ?? def.qualifiedName ?? ''; - if (simple === simpleName) return def; + if (simple !== simpleName) continue; + if (firstMatch === undefined) { + firstMatch = def; + continue; + } + return { classDef: firstMatch, ambiguous: true }; } - return undefined; + if (firstMatch === undefined) return undefined; + return { classDef: firstMatch, ambiguous: false }; } diff --git a/gitnexus/src/core/ingestion/languages/cpp/captures.ts b/gitnexus/src/core/ingestion/languages/cpp/captures.ts index 338e9992b..a60ad1c1a 100644 --- a/gitnexus/src/core/ingestion/languages/cpp/captures.ts +++ b/gitnexus/src/core/ingestion/languages/cpp/captures.ts @@ -10,7 +10,7 @@ import { getTreeSitterBufferSize } from '../../constants.js'; import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js'; import { splitCppInclude, splitCppUsingDecl } from './import-decomposer.js'; import { computeCppDeclarationArity, computeCppCallArity } from './arity-metadata.js'; -import { markFileLocal } from './file-local-linkage.js'; +import { markCppAnonymousNamespaceRange, markFileLocal } from './file-local-linkage.js'; import { markCppDependentBase } from './two-phase-lookup.js'; import { markCppAdlSiteArgs, markCppAdlSiteNoAdl, type CppAdlArgInfo } from './adl.js'; import { markCppInlineNamespaceRange } from './inline-namespaces.js'; @@ -200,20 +200,37 @@ export function emitCppScopeCaptures( // namespace's source range so `populateCppInlineNamespaceScopes` // (during populateOwners) can match it back to the corresponding // Namespace scope. - if (grouped['@declaration.namespace'] !== undefined) { - const anchor = grouped['@declaration.namespace']!; - const nsNode = findNodeAtRange(tree.rootNode, anchor.range, 'namespace_definition'); - if (nsNode !== null && isInlineNamespace(nsNode)) { + // `@declaration.namespace` fires only for NAMED namespaces (the query + // requires a `name: (namespace_identifier)` child). Use the unconditional + // `@scope.namespace` capture so the anonymous-namespace branch also runs. + const namespaceScopeAnchor = grouped['@declaration.namespace'] ?? grouped['@scope.namespace']; + if (namespaceScopeAnchor !== undefined) { + const nsNode = findNodeAtRange( + tree.rootNode, + namespaceScopeAnchor.range, + 'namespace_definition', + ); + if (nsNode !== null) { // Range coords stored in the shared Range shape use 1-based // line numbers (see `ast-helpers.ts` rangeForNode where // `startPosition.row + 1` is applied). Match that convention so - // `populateCppInlineNamespaceScopes` can join against `Scope.range`. - markCppInlineNamespaceRange(filePath, { + // the populators can join against `Scope.range`. + const nsRange = { startLine: nsNode.startPosition.row + 1, startCol: nsNode.startPosition.column, endLine: nsNode.endPosition.row + 1, endCol: nsNode.endPosition.column, - }); + }; + if (isInlineNamespace(nsNode)) { + markCppInlineNamespaceRange(filePath, nsRange); + } + // Anonymous namespace: `namespace_definition` with no `name` field. + // Recorded so `expandCppWildcardNames` can propagate its members + // to including TUs even though their names are also `markFileLocal`'d + // (which blocks the global free-call fallback's cross-file path). + if ((nsNode.childForFieldName?.('name') ?? null) === null) { + markCppAnonymousNamespaceRange(filePath, nsRange); + } } } diff --git a/gitnexus/src/core/ingestion/languages/cpp/file-local-linkage.ts b/gitnexus/src/core/ingestion/languages/cpp/file-local-linkage.ts index e46327558..dd5fc8c0a 100644 --- a/gitnexus/src/core/ingestion/languages/cpp/file-local-linkage.ts +++ b/gitnexus/src/core/ingestion/languages/cpp/file-local-linkage.ts @@ -35,6 +35,33 @@ const fileLocalNames = new Map>(); */ const nonGloballyVisibleNodeIds = new Map>(); +/** + * Per-file set of source-range keys identifying `namespace { ... }` blocks. + * Resolved to `ScopeId`s in `populateCppAnonymousNamespaceScopes` and + * consumed via `isCppAnonymousNamespaceScope`. + * + * Anonymous namespaces have file-local linkage but, unlike `static`, their + * members propagate to any TU that `#include`s the declaring file — each + * including TU gets its own internal-linkage copy. So for wildcard import + * expansion (`expandCppWildcardNames`) we treat anonymous-namespace owned + * defs as if declared at the enclosing scope. Cross-file unqualified + * lookup that does NOT go through `#include` is still blocked by the + * `isFileLocal` mark recorded on the def's name. + */ +const anonymousNamespaceRangesByFile = new Map>(); +const anonymousNamespaceScopeIds = new Set(); + +interface RangeKeyShape { + readonly startLine: number; + readonly startCol: number; + readonly endLine: number; + readonly endCol: number; +} + +function rangeKey(r: RangeKeyShape): string { + return `${r.startLine}:${r.startCol}:${r.endLine}:${r.endCol}`; +} + /** Record a symbol name as file-local (static or anonymous namespace). */ export function markFileLocal(filePath: string, name: string): void { let names = fileLocalNames.get(filePath); @@ -50,10 +77,51 @@ export function isFileLocal(filePath: string, name: string): boolean { return fileLocalNames.get(filePath)?.has(name) ?? false; } +/** Capture-time: record an anonymous `namespace_definition` source range. */ +export function markCppAnonymousNamespaceRange(filePath: string, range: RangeKeyShape): void { + let set = anonymousNamespaceRangesByFile.get(filePath); + if (set === undefined) { + set = new Set(); + anonymousNamespaceRangesByFile.set(filePath, set); + } + set.add(rangeKey(range)); +} + +/** Predicate consumed by `populateCppNonGloballyVisible` and + * `expandCppWildcardNames` to exempt anonymous-namespace scopes from + * the cross-file unqualified-lookup exclusion that applies to ordinary + * named namespaces. */ +export function isCppAnonymousNamespaceScope(scopeId: ScopeId): boolean { + return anonymousNamespaceScopeIds.has(scopeId); +} + /** Clear tracked file-local names (call at start of each resolution pass). */ export function clearFileLocalNames(): void { fileLocalNames.clear(); nonGloballyVisibleNodeIds.clear(); + anonymousNamespaceRangesByFile.clear(); + anonymousNamespaceScopeIds.clear(); +} + +/** Resolve recorded anonymous-namespace source ranges to `ScopeId`s. + * Must run inside `populateOwners` BEFORE `populateCppNonGloballyVisible` + * consults the resolved set. */ +export function populateCppAnonymousNamespaceScopes(parsed: { + readonly filePath: string; + readonly scopes: readonly { + readonly id: ScopeId; + readonly kind: string; + readonly range: RangeKeyShape; + }[]; +}): void { + const ranges = anonymousNamespaceRangesByFile.get(parsed.filePath); + if (ranges === undefined || ranges.size === 0) return; + for (const scope of parsed.scopes) { + if (scope.kind !== 'Namespace') continue; + if (ranges.has(rangeKey(scope.range))) { + anonymousNamespaceScopeIds.add(scope.id); + } + } } /** @@ -87,6 +155,13 @@ export function populateCppNonGloballyVisible(parsed: { // ISO C++ `[namespace.def]/p4`. Skip them here so cross-file // unqualified lookup can still see their callable defs. if (scope.kind === 'Namespace' && isCppInlineNamespaceScope(scope.id)) continue; + // Anonymous namespaces give internal linkage but their contents are + // visible at the enclosing scope within the same TU and propagate to + // any TU that `#include`s the declaring file. The `isFileLocal` mark + // (recorded on the def's name in this file) still blocks cross-file + // unqualified lookup that does not go through #include, so dropping + // the structural visibility exclusion here is safe. + if (scope.kind === 'Namespace' && anonymousNamespaceScopeIds.has(scope.id)) continue; for (const def of scope.ownedDefs) { set.add(def.nodeId); } @@ -191,9 +266,18 @@ export function expandCppWildcardNames( // including TU. When the owning scope is unknown we default to // include (preserves prior behavior for any def whose structural // ownership wasn't recorded in `Scope.ownedDefs`). + // + // Anonymous namespaces are exempt: their members propagate to the + // enclosing scope of any TU that #includes the declaring file (each + // including TU gets its own internal-linkage copy per ISO C++). const ownerScope = ownerScopeByNodeId.get(def.nodeId); + const ownerIsAnonymousNamespace = + ownerScope !== undefined && + ownerScope.kind === 'Namespace' && + anonymousNamespaceScopeIds.has(ownerScope.id); if ( ownerScope !== undefined && + !ownerIsAnonymousNamespace && (ownerScope.kind === 'Namespace' || ownerScope.kind === 'Class') ) { continue; @@ -201,7 +285,11 @@ export function expandCppWildcardNames( const name = simpleName(def); if (name === '') continue; - if (isFileLocal(target.filePath, name)) continue; + // Same exemption for the `isFileLocal` mark — anonymous-namespace + // names are recorded as file-local to suppress the global free-call + // fallback's cross-file leak, but they MUST still propagate through + // wildcard import expansion to including TUs. + if (!ownerIsAnonymousNamespace && isFileLocal(target.filePath, name)) continue; if (seen.has(name)) continue; seen.add(name); names.push(name); diff --git a/gitnexus/src/core/ingestion/languages/cpp/scope-resolver.ts b/gitnexus/src/core/ingestion/languages/cpp/scope-resolver.ts index e91b14428..36bba7514 100644 --- a/gitnexus/src/core/ingestion/languages/cpp/scope-resolver.ts +++ b/gitnexus/src/core/ingestion/languages/cpp/scope-resolver.ts @@ -16,6 +16,7 @@ import { expandCppWildcardNames, isFileLocal, clearFileLocalNames, + populateCppAnonymousNamespaceScopes, populateCppNonGloballyVisible, isCppDefGloballyVisible, } from './file-local-linkage.js'; @@ -97,10 +98,11 @@ export const cppScopeResolver: ScopeResolver = { populateOwners: (parsed: ParsedFile) => { populateClassOwnedMembers(parsed); - // Resolve inline-namespace ranges (recorded at capture time) to - // ScopeIds BEFORE `populateCppNonGloballyVisible` runs, so the - // inline-namespace exemption sees the populated Set. + // Resolve inline- and anonymous-namespace ranges (recorded at capture + // time) to ScopeIds BEFORE `populateCppNonGloballyVisible` runs, so + // both exemptions see the populated Sets. populateCppInlineNamespaceScopes(parsed); + populateCppAnonymousNamespaceScopes(parsed); // Track namespace-nested and class-nested defs so the global free-call // fallback and wildcard expansion can suppress them as unqualified // cross-file callables. diff --git a/gitnexus/test/fixtures/lang-resolution/cpp-adl-base-associated-namespaces-collision/app.cpp b/gitnexus/test/fixtures/lang-resolution/cpp-adl-base-associated-namespaces-collision/app.cpp new file mode 100644 index 000000000..dfde8899f --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/cpp-adl-base-associated-namespaces-collision/app.cpp @@ -0,0 +1,14 @@ +#include "base_lib.h" + +namespace app { + struct Token : base_one::Base {}; + + void run() { + Token t; + collide(t); + } +} + +namespace other { + struct Token : base_two::Base {}; +} diff --git a/gitnexus/test/fixtures/lang-resolution/cpp-adl-base-associated-namespaces-collision/base_lib.h b/gitnexus/test/fixtures/lang-resolution/cpp-adl-base-associated-namespaces-collision/base_lib.h new file mode 100644 index 000000000..684f45aeb --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/cpp-adl-base-associated-namespaces-collision/base_lib.h @@ -0,0 +1,11 @@ +#pragma once + +namespace base_one { + struct Base {}; + void collide(Base); +} + +namespace base_two { + struct Base {}; + void collide(Base); +} diff --git a/gitnexus/test/fixtures/lang-resolution/cpp-adl-base-associated-namespaces-negative/app.cpp b/gitnexus/test/fixtures/lang-resolution/cpp-adl-base-associated-namespaces-negative/app.cpp new file mode 100644 index 000000000..fe5185b3d --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/cpp-adl-base-associated-namespaces-negative/app.cpp @@ -0,0 +1,16 @@ +#include "base_lib.h" + +namespace app { + struct HiddenDerived : HiddenBase {}; + struct MissingDerived : missing_ns::UnknownBase {}; + + void run_hidden() { + HiddenDerived d; + hidden_probe(d); + } + + void run_missing() { + MissingDerived d; + unresolved_probe(d); + } +} diff --git a/gitnexus/test/fixtures/lang-resolution/cpp-adl-base-associated-namespaces-negative/base_lib.h b/gitnexus/test/fixtures/lang-resolution/cpp-adl-base-associated-namespaces-negative/base_lib.h new file mode 100644 index 000000000..64f282ccf --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/cpp-adl-base-associated-namespaces-negative/base_lib.h @@ -0,0 +1,6 @@ +#pragma once + +namespace { + struct HiddenBase {}; + void hidden_probe(HiddenBase); +} diff --git a/gitnexus/test/fixtures/lang-resolution/cpp-adl-base-associated-namespaces/app.cpp b/gitnexus/test/fixtures/lang-resolution/cpp-adl-base-associated-namespaces/app.cpp new file mode 100644 index 000000000..3fc28f632 --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/cpp-adl-base-associated-namespaces/app.cpp @@ -0,0 +1,22 @@ +#include "base_lib.h" + +namespace app { + struct Derived : base_lib::Base {}; + struct MultiLevel : middle_lib::Mid {}; + struct DiamondDerived : diamond_lib::LeftBranch, diamond_lib::RightBranch {}; + + void run_single() { + Derived d; + log(d); + } + + void run_multi() { + MultiLevel m; + trace(m); + } + + void run_diamond() { + DiamondDerived d; + ping(d); + } +} diff --git a/gitnexus/test/fixtures/lang-resolution/cpp-adl-base-associated-namespaces/base_lib.h b/gitnexus/test/fixtures/lang-resolution/cpp-adl-base-associated-namespaces/base_lib.h new file mode 100644 index 000000000..c3abf7d44 --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/cpp-adl-base-associated-namespaces/base_lib.h @@ -0,0 +1,20 @@ +#pragma once + +namespace base_lib { + struct Base {}; + void log(Base); + + struct Root {}; + void trace(Root); +} + +namespace middle_lib { + struct Mid : base_lib::Root {}; +} + +namespace diamond_lib { + struct DiamondBase {}; + struct LeftBranch : DiamondBase {}; + struct RightBranch : DiamondBase {}; + void ping(DiamondBase); +} diff --git a/gitnexus/test/integration/resolvers/cpp.test.ts b/gitnexus/test/integration/resolvers/cpp.test.ts index 0c818b1f0..6e58c0a54 100644 --- a/gitnexus/test/integration/resolvers/cpp.test.ts +++ b/gitnexus/test/integration/resolvers/cpp.test.ts @@ -2127,6 +2127,92 @@ describe('C++ ADL — basic associated-namespace closure', () => { }); }); +describe('C++ ADL — base-class associated namespaces', () => { + let result: PipelineResult; + + beforeAll(async () => { + result = await runPipelineFromRepo( + path.join(FIXTURES, 'cpp-adl-base-associated-namespaces'), + () => {}, + ); + }, 60000); + + it('resolves log(d) to base_lib::log via ADL when Derived inherits from base_lib::Base', () => { + const calls = getRelationships(result, 'CALLS'); + const logCalls = calls.filter((c) => c.source === 'run_single' && c.target === 'log'); + expect(logCalls.length).toBe(1); + expect(logCalls[0].targetFilePath).toContain('base_lib.h'); + const targetNode = result.graph.getNode(logCalls[0].rel.targetId); + expect(logCalls[0].rel.targetId).toBe('Function:base_lib.h:log'); + expect(targetNode?.properties.parameterTypes).toEqual(['Base']); + }); + + it('resolves trace(m) via full MRO walk when MultiLevel inherits via middle_lib::Mid -> base_lib::Root', () => { + const calls = getRelationships(result, 'CALLS'); + const traceCalls = calls.filter((c) => c.source === 'run_multi' && c.target === 'trace'); + expect(traceCalls.length).toBe(1); + expect(traceCalls[0].targetFilePath).toContain('base_lib.h'); + const targetNode = result.graph.getNode(traceCalls[0].rel.targetId); + expect(traceCalls[0].rel.targetId).toBe('Function:base_lib.h:trace'); + expect(targetNode?.properties.parameterTypes).toEqual(['Root']); + }); + + it('diamond inheritance contributes base namespace once (no duplicate/crash)', () => { + const calls = getRelationships(result, 'CALLS'); + const pingCalls = calls.filter((c) => c.source === 'run_diamond' && c.target === 'ping'); + expect(pingCalls.length).toBe(1); + expect(pingCalls[0].targetFilePath).toContain('base_lib.h'); + const targetNode = result.graph.getNode(pingCalls[0].rel.targetId); + expect(pingCalls[0].rel.targetId).toBe('Function:base_lib.h:ping'); + expect(targetNode?.properties.parameterTypes).toEqual(['DiamondBase']); + }); +}); + +describe('C++ ADL — base-class namespace MRO with simple-name class collisions', () => { + let result: PipelineResult; + + beforeAll(async () => { + result = await runPipelineFromRepo( + path.join(FIXTURES, 'cpp-adl-base-associated-namespaces-collision'), + () => {}, + ); + }, 60000); + + it('does NOT emit CALLS for collide(t) when class-name lookup is ambiguous', () => { + const calls = getRelationships(result, 'CALLS'); + const collideCalls = calls.filter((c) => c.source === 'run' && c.target === 'collide'); + expect(collideCalls.length).toBe(0); + }); +}); + +describe('C++ ADL — base-class namespace mapping skips anonymous/unresolved bases', () => { + let result: PipelineResult; + + beforeAll(async () => { + result = await runPipelineFromRepo( + path.join(FIXTURES, 'cpp-adl-base-associated-namespaces-negative'), + () => {}, + ); + }, 60000); + + it('hidden_probe(d) still resolves via ordinary lookup when declaration is visible', () => { + const calls = getRelationships(result, 'CALLS'); + const hiddenProbeCalls = calls.filter( + (c) => c.source === 'run_hidden' && c.target === 'hidden_probe', + ); + expect(hiddenProbeCalls.length).toBe(1); + expect(hiddenProbeCalls[0].targetFilePath).toContain('base_lib.h'); + }); + + it('unresolved_probe(d) emits zero CALLS when base class cannot be resolved', () => { + const calls = getRelationships(result, 'CALLS'); + const unresolvedProbeCalls = calls.filter( + (c) => c.source === 'run_missing' && c.target === 'unresolved_probe', + ); + expect(unresolvedProbeCalls.length).toBe(0); + }); +}); + describe('C++ ADL — parenthesized name suppresses ADL', () => { let result: PipelineResult; diff --git a/gitnexus/test/integration/resolvers/helpers.ts b/gitnexus/test/integration/resolvers/helpers.ts index 396e7905b..63d59e3c9 100644 --- a/gitnexus/test/integration/resolvers/helpers.ts +++ b/gitnexus/test/integration/resolvers/helpers.ts @@ -122,12 +122,6 @@ const LEGACY_RESOLVER_PARITY_EXPECTED_FAILURES: Readonly::g() -> f() does NOT bind to Base::f (dependent base)', - // The legacy DAG path has no V1/V2 ADL boundary — pointer-typed - // arguments resolve via the workspace-wide simple-name walk. The - // scope-resolver V1 ADL pass excludes pointer args (closure rules - // deferred to V2) per plan 2026-05-13-001 U2 / R4. Scope-resolver- - // only correctness win; backporting is out of scope. - 'record(p) where p is audit::Event* emits zero CALLS — V1 ADL excludes pointer args', // The legacy DAG path has no ADL_AMBIGUOUS suppression sentinel. // When ADL surfaces multiple overloads that collide after C++ // int/long normalization, legacy picks the first match arbitrarily. diff --git a/gitnexus/test/unit/scope-resolution/resolver-parity-expected-failures.test.ts b/gitnexus/test/unit/scope-resolution/resolver-parity-expected-failures.test.ts index eae68e09d..7a830efa8 100644 --- a/gitnexus/test/unit/scope-resolution/resolver-parity-expected-failures.test.ts +++ b/gitnexus/test/unit/scope-resolution/resolver-parity-expected-failures.test.ts @@ -7,6 +7,11 @@ import { const csharpNamespaceRootImportTest = 'emits the using-import edge App/Program.cs -> Models/User.cs through the scope-resolution path'; +const cppBaseNamespaceAdlTests = [ + 'resolves log(d) to base_lib::log via ADL when Derived inherits from base_lib::Base', + 'resolves trace(m) via full MRO walk when MultiLevel inherits via middle_lib::Mid -> base_lib::Root', + 'diamond inheritance contributes base namespace once (no duplicate/crash)', +] as const; describe('resolver parity expected legacy failures', () => { it('uses the same env var convention as the parity workflow', () => { @@ -44,4 +49,14 @@ describe('resolver parity expected legacy failures', () => { ), ).toBe(false); }); + + it('does not mark cpp base-namespace ADL coverage as expected failures in legacy parity', () => { + for (const testName of cppBaseNamespaceAdlTests) { + expect( + isLegacyResolverParityExpectedFailure('cpp', testName, { + REGISTRY_PRIMARY_CPP: '0', + }), + ).toBe(false); + } + }); });