Files
codegraph/__tests__/scala-kind-projector.test.ts
Colby Mchenry fa43e259b2 fix(scala): a symbolic name in a type is a type, never an operator (#2217)
A kind-projector placeholder (`Align[Either[A, *]]`, `Functor[Map[K, ?]]`)
reached the resolver as a type reference named `*`, and exact or fuzzy
matching bound it to the one `*` in the project - algebra's
`Signed.Sign.*` operator - 567 times in cats. A symbolic name in a Scala
type now resolves only to a type or type alias of that name (cats'
`F ~> G`), and otherwise to nothing, whatever strategy runs.

cats -567; sttp unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:07:39 +00:00

54 lines
1.6 KiB
TypeScript

/**
* A Scala kind-projector placeholder in a type — `Align[Either[A, *]]`,
* `Functor[Map[K, ?]]` — names no method: cats' 567 `*` type arguments went
* to an algebra `Sign`'s `*` operator.
*/
import { describe, it, expect, afterAll, beforeAll } from 'vitest';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { CodeGraph } from '../src';
let root = '';
let cg: CodeGraph;
beforeAll(async () => {
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-scala-kp-'));
const files: Record<string, string> = {
'src/main/scala/algebra/Signed.scala': `package algebra
object Signed {
sealed abstract class Sign(val toInt: Int) {
def *(that: Sign): Sign = this
}
}
`,
'src/main/scala/cats/Align.scala': `package cats
trait Align[F[_]]
object Align {
implicit def catsAlignForEither[A]: Align[Either[A, *]] = null
}
`,
};
for (const [rel, content] of Object.entries(files)) {
fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true });
fs.writeFileSync(path.join(root, rel), content);
}
cg = await CodeGraph.init(root, { index: true });
});
afterAll(() => {
cg?.close();
if (root) fs.rmSync(root, { recursive: true, force: true });
});
describe('Scala kind-projector placeholders', () => {
it('are not references to a `*` method', () => {
const ids = cg.getNodesInFile('src/main/scala/cats/Align.scala').map((n) => n.id);
const targets = cg.getOutgoingEdgesFrom(ids).filter((e) => e.kind === 'references').map((e) => cg.getNode(e.target)!.name);
expect(targets).not.toContain('*');
});
});