Skip to content

Recursive Type Field Validation + Suggest, compile, fail over, Improve Transform of Circular Types Generic type hint Circular<type> #61685

Closed as not planned

Description

🔎 Search Terms

Recursive Type, Recursive Type Definition, then some auto suggest, but nothing matched.

🕗 Version & Regression Information

Recursive Type Definition, few other field evaluation for assignment fails, but does correctly evaluate initialization of objects to a degree, feel there issue there still.

Latest v5.9.0-dev.20250511 doens't work
https://www.typescriptlang.org/play/?ts=5.0.4
Seems to be in 3,4 as well, been a while back on the sense pushing things again TS language.

https://www.typescriptlang.org/play/?ts=3.3.3#code/FASwdgLgpgTgZgQwMZQARIPYzQb2K1BALlTAFcBbAI1gBp9UqSBnCGcAc2AF9hRJYiFKgDCqPAQAmJctToMkLNpx59gEAJ4AHNAAURAQV0BJAEpQ4IkDCRkANghioAvAUzZUAMnENmZKiII0BxYGiSoABRiAD6o+kZmFlY29o4AlADaALoA3Dx56tp6hibmcMYAIi5xJYmW1rYOMAWYYKyo0Kwk8aUWlfiuEsQAjPSMRADknRATY34BQVAhMGGoGQwSBIREowwETBPMknCzDAD0ZwTMR3CTs6gXqACiMDBYqAAWCGCSdpyoIGYqAA7lgANacAB0e1Q80CwVCJHWWy2mxRUh2Y3Rj1hx2uxzutAelxebycAAlvr9-oCQeCoTCUYoJqd0QRHjdJjd7o9Se9KT8-mAOACgaCYBDhdC2Vd-PClojsozULwUVkGKr1dwChdppC4YtlhoMgAGLKQ4kERAgOxAiAYQhke2wsgcDhQVhQSR8VrtaYIa4gDhgL3VPUGhErU25YC+iAdD0QAPMIMhyS6N5aMOJ5Op0MAQlc5DsdlQAH4E6xc8GvZCqKgSCycpbrbaOg7MBQdNBYxg2vH-YGa+nMwAhbNVodpst15uPVt2jsYLs2qBAA

Example: When testing the type validation upon validation recursive instantiation of an object, all works find by the looks of things, the compiler picks up invalid keys.

interface core {
  a: number,
  b: string
}

interface C {
  d: number,
  c: string
}


type PCAPIRefCircular =  core & {
  subCategory:  (C | PCAPIRefCircular)[];
};

type PCAPIRefID = PCAPIRefCircular;

const test: PCAPIRefID
 = {
 a:1,
 b:'test',
 subCategory: [
  {
    a:1,
    b:'sdf',
  //  ssdf:'', // Error handling is working.
    subCategory: [
      {
        d:1,
      //  sdfssdf:'', // Error Handling is working.
        c:'',
       // sdf:'sdf', // Error Handling is working.
        subCategory:[]
      }
    ]
  }
 ]
};

//test.subCategory[0]. //  fails to auto suggested

const testassigned = test.subCategory[0];
const testassignedProp = testassigned != null ? testassigned.b : ''; // fails to compete
const testassignedPropB = testassigned?.b; // fails to compile

⏯ Playground Link

No response

💻 Code

interface core {
  a: number,
  b: string
}

interface C {
  d: number,
  c: string
}


type PCAPIRefCircular =  core & {
  subCategory:  (C | PCAPIRefCircular)[];
};

type PCAPIRefID = PCAPIRefCircular;

const test: PCAPIRefID
 = {
 a:1,
 b:'test',
 subCategory: [
  {
    a:1,
    b:'sdf',
  //  ssdf:'', // Error handling is working.
    subCategory: [
      {
        d:1,
      //  sdfssdf:'', // Error Handling is working.
        c:'',
       // sdf:'sdf', // Error Handling is working.
        subCategory:[]
      }
    ]
  }
 ]
};

//test.subCategory[0]. //  fails to auto suggested

const testassigned = test.subCategory[0];
const testassignedProp = testassigned != null ? testassigned.b : ''; // fails to compete
const testassignedPropB = testassigned?.b; // fails to compile

🙁 Actual behavior

It should have been able to auto suggestion both of the union structures and it should have then based
on the first unique appearing field form one of the union structures in the subsCategory fields,
been able to narrow down the type to the correct union type, against which to validate everything.

🙂 Expected behavior

I expect that auto suggest both the union structures and that it validates it against the one with the most matching fields with union definition.

I would also expect and know that you can internally should have been able to evaluate the reclusive type for the next level of iteration for the keys, that could have been chosen by evaluation the reclusive type by excluding its self reference logically. You could have implemented lazy evaluation. upon the key being expanded for subcategory, then you could upon what typed in for subcategory again, know which type was chosen and evaluate it again.

Additional information about the issue

The generic form of this also needs to work as well.
`.ts
type PCAPIRefCircular<TCore extends Record<any,any>,TRef extends Record<any,any>> = TCore & {
subCategory: (TRef | PCAPIRefCircular)[];
};

type PCAPIBoth = PCAPIRefCircular<PCAPIRefID | PCAPIRefEmbeded | PCAPIRefEmbededBoth >
`

As for reclusive type definition your hard code limit, that AWS with Amplify Relationships, triggers at 50 and apret from their types all fail for ReturnType, which makes things crazy difficult, but then wonder for some of the stuff if they understood Log(n) algorithms, as hang everything in the IDE at times crashes engine that have to crash delete issues types, crash vscode again, then be fine as engine fee buggy type removed form the engine, impossible to use there framework for alot of things and its like V6 of this.
https://github.com/wesleyolis/mongooseRelationalTypes - 2018 already with tung of tables and fields, compare what we have now and impossible to work, I had hack and deep dive there types to fix it., We had better suggestive types that program with compare to amplify and types from the db, aws returns undefined instead of null in many places also didn't get all the permutations correct to work with dbs, readonly, null, optional, default, and at db or dbwrapper level, who the drive default, it it force, few more permutations, then relationships. ...

So to the point, you can when evaluating reclusive types for validation, have 2 requirements in your engine,
The recursive type and actually physical object to validate. You perform recursive validation as long the object has keys and then stop, this happens if still does, as par ts 2, 3 I wrote generics that selft expansion validating.

Transform of Circular Types Generic type hint Circular
If it was recursive type and inspection or transform of another type, like alot more things, you can detect recursive type and take the approach above that I suggested, to get the next set of keys in the engine and then stop and place reference to transform recursive type definition, which should permanently solve your problem of infinite evaluation, because detect recursive types and transform them into new recursive types.
So introduce a new special type that inject in the compiler for recursive place holder.
Alternative instead having to monitor type names in dictionary for circular reference as you expaned.
could made introduce a new syntax, i.e type = NameOfType = {a:any, b:any, c: Circular

Implicitly you then add Circular reference types to a stack dictionary only for those which have the Circular generic wrapper notation, to speed up and reduce the computation overhead of detecting circular types.
You can then transform circular types and keep them circular avoiding the 50 level depth iteration problem.
But then again AWS evaluates array of setRelationships with everything, which is NXN transform and not ideal, instead of Log(n), hope they apply some algorithmic thinking next time to there types. then these operation times few how may times use, it as there no caching of single concrete reference.

Guess on could look at export wrapper take a snap shot, else need implemented SnapCacheType<> generic Hints to the compiler to speed things up after which could reset the 50 iteration limit again.

Look forward to see the improvement in support for Transforms Circular References to new types of that are still Circular References.

Activity

  1. RyanCavanaugh commented on May 12, 2025

    @RyanCavanaugh
    Member

    Wesley Walter Anton Oliver (@wesleyolis) Welcome back BTW!

    This example is working correctly so I'm not sure what you're getting at because it doesn't really have anything to do with the recursion.

    const testassigned = test.subCategory[0];
    const testassignedProp = testassigned != null ? testassigned.b : ''; // fails to compete
    const testassignedPropB = testassigned?.b; // fails to compile

    testassigned is of type PCAPIRefCircular | C; this is the correct type (it's test.subCategory[0], and subCategory is (PCAPIRefCircular | C)[].

    C does not have a b field, so you're not allowed to access it without a proper narrowing first. This is the expected behavior.

    You might be getting confused about how type annotations and narrowing interact. There isn't a specific union for the top-level type to narrow to, so test being initialized by something that is C at that location doesn't cause a narrowing. You may want to use a satisfies expression instead:

    const test
     = {
     a:1,
     b:'test',
     subCategory: [
      {
        a:1,
        b:'sdf',
        subCategory: [
          {
            d:1,
            c:'',
            subCategory:[]
          }
        ]
      }
     ]
    } satisfies PCAPIRefID;
    
    const testassigned = test.subCategory[0].b; // OK

    You've logged over 50 issues at this point, of which only a single one has been an actionable bug, the rest of which have been unactionable in one way or another. I'm going to ask that you wrap up your engagement here and try to stick to more well-trod ground; this is clearly not a good use of your time or ours.

  2. typescript-bot commented on May 15, 2025

    @typescript-bot
    Contributor

    This issue has been marked as "Question" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

  3. locked as resolved and limited conversation to collaborators on Oct 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    QuestionAn issue which isn't directly actionable in code

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions