Skip to content

Recursive types can depend on presence and source location of unused type declarationΒ #55758

Open
@CraigMacomber

Description

@CraigMacomber

πŸ”Ž Search Terms

recursive type, unused, compile error

πŸ•— Version & Regression Information

  • This changed between versions 3.3 (where this sample never compiles) and 3.5 (where it depends on the magic line). 5.2.2 works the same as 3.5.

⏯ Playground Link

https://www.typescriptlang.org/play?#code/PQKhCgAIUh1AnAhgB2QUwCaQJYDtKKQBmArrgMYAu2A9vpTQQDZM0Duk8aRa88eAc0gNhAT3QBnSACNuNLsIAWaUQQUY05Joi4YAdFBgAVRdikBbRAGs0UruRLwJ2AG5oCuLORoBae4+c3MUlIZBoJZ2kmNANoYHBKcXcjJIAlbl40CncAXkgACgBKSByAPkgjLjQAZXJlSwAeFPR0ni5s0oBucHBQCGhIAGEac2RsaOFsc3dECN5KJUQFgEEcKVnnAVxEKPcRfLQAD0osjAligCFY40YSCTQAGkhnUaZVDSI8GeC0AC5DSAAA0S6EgAH06porCVOGgAI4kbBcZYRbBbHbRIw0JpPACih3QVEwXUBhniIPcXARSLQKM2212WIaYNWRxOnikFyeF3KeUo8BIaG64C0GwqVVq9UQTXKAG9wABIZAkKLYciQby4CT8khUeT5ZWq9VcRAYOhvHC4Ig0X4VYqygC+4CdvWAQxQlEcthkNEoih+HiwAtw1Gmlut8Es1DoxHgIyU7mQOkQ0xO8AMwDd6XMNBcggTQLZpykzTQrUy2UBkEsNikfrMGpoGkbo3Gj0gaDc+GwRALlgEasgTC+MjQrA4De85mmIcwkBoJEoBlIFGj+EsyCaHeOxYqaQy7XIaFK+QpEltRkKF4lkMaRjliq4nvg+FwaA4lTQNVviFPSXO3Qupq2qQAAVhIdCpDQvowkUJTlOBdAAPLSKBmiUMKmZDCMM7ULgQgLgs9ZSMOb7zou87wNWub5tgRGMH67jSL6DDmPOvaMcQbYaogdzesRjbNiIuAwVOYzRBmbomA2DZuPAqhNhgtpcDmeb4ZAZB8VgFKQB8eB0bQWrPIoC5MFgIkLNIJrQmJ4xLIZBg6WC-aDnkVKIsiqLooy2IUjQvaIbgUG+k8pbloex7CsBCyBShaFUDCG75IFwWUIUnRAA

πŸ’» Code

/**
 * Wrapped in a function to allow referring to types before they are declared.
 * This makes recursive and co-recursive types possible.
 */
type TypeReference = () => TreeSchema<TypeReference>;

/**
 * Compile time assert that A is assignable to (extends) B.
 * To use, simply define a type:
 * `type _check = requireAssignableTo<T, Expected>;`
 */
type requireAssignableTo<_A extends B, B> = true;

class TreeSchema<T> {
	public constructor(public readonly info: T) {}
}

// Captures both type and runtime information from the parameter.
// Removing the `extends TypeReference` makes this code compile, even if the magic line below is commented out.
function map<T extends TypeReference>(types: T): TreeSchema<T> {
	return new TreeSchema(types);
}

const jsonRoot = () => jsonObject;

// Commenting out this line out or moving it to the bottom of the file causes this code to not compile.
// This is very odd: removing unused type definitions should not break compilation.
type _magic = requireAssignableTo<typeof jsonRoot, TypeReference>;

const jsonObject = map(jsonRoot);

πŸ™ Actual behavior

This code fails to compile if the "_magic" line (which declares an unused type) is removed, or moved to the bottom of the file.

πŸ™‚ Expected behavior

Ideally this code should compile, regardless of if the "_magic" line is present.
Additionally:

  1. the presence of an unused type definition should not impact type checking.
  2. the location of an type definition should not impact type checking.

Additional information about the issue

This was minified from https://github.com/microsoft/FluidFramework/blob/main/experimental/dds/tree2/src/domains/json/jsonDomainSchema.ts where I discovered that the type assertion I added caused the code to build without having to use the special recursive type workaround methods I added (which avoid using "extends" clauses which seem to break the recursive case). I'd love for cases like this to compile as it would make our API much nicer (the magic type assertion like to fix the build would be needed in our user's code and may require refactoring to add, so it's not a great workaround).

Metadata

Metadata

Assignees

Labels

Needs InvestigationThis issue needs a team member to investigate its status.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions