@@ -4525,6 +4525,16 @@ export function createChecker(program: Program, resolver: NameResolver): Checker
45254525 return undefined ;
45264526 }
45274527
4528+ // If the type is still being created (e.g., model A is Template<{t: B}> where B
4529+ // references A.t), check if we can find the member from its `is` base or spreads
4530+ // that are already resolved.
4531+ if ( type . creating && type . kind === "Model" ) {
4532+ const memberFromCreating = tryResolveMemberFromCreatingModel ( ctx , type , node . id . sv ) ;
4533+ if ( memberFromCreating ) {
4534+ return memberFromCreating ;
4535+ }
4536+ }
4537+
45284538 // Late-bind the container and its members.
45294539 switch ( type . kind ) {
45304540 case "Model" :
@@ -4542,6 +4552,178 @@ export function createChecker(program: Program, resolver: NameResolver): Checker
45424552 return getCanonicalResolvedMemberSymbol ( type , node . id . sv ) ;
45434553 }
45444554
4555+ /**
4556+ * Try to resolve a member from a model that is still being created.
4557+ * This handles the case where `model A is Template<{t: B}>` and B references `A.t`.
4558+ * The member 't' comes from the `is` base (the template instantiation) which may
4559+ * already have its properties resolved even though A hasn't copied them yet.
4560+ */
4561+ function tryResolveMemberFromCreatingModel (
4562+ ctx : CheckContext ,
4563+ model : Model ,
4564+ memberName : string ,
4565+ ) : Sym | undefined {
4566+ // First check if the model already has the property (from its own declared properties)
4567+ const existingProp = model . properties . get ( memberName ) ;
4568+ if ( existingProp ) {
4569+ return getTypeSymbol ( existingProp ) ?? undefined ;
4570+ }
4571+
4572+ // Check the model's AST node for `is` base and spreads
4573+ const modelNode = model . node ;
4574+ if ( ! modelNode || modelNode . kind !== SyntaxKind . ModelStatement ) {
4575+ return undefined ;
4576+ }
4577+
4578+ // If there's an `is` clause, resolve the `is` expression type.
4579+ // getTypeForNode will return a cached type if the `is` target was already checked.
4580+ if ( modelNode . is ) {
4581+ const isBaseType = getTypeForNode ( modelNode . is , ctx ) ;
4582+ if ( isBaseType && isBaseType . kind === "Model" ) {
4583+ // First check if the is-base already has the member (even if creating)
4584+ const prop = isBaseType . properties . get ( memberName ) ;
4585+ if ( prop ) {
4586+ return createLateBoundMemberSym ( model , prop , memberName , modelNode ) ;
4587+ }
4588+ // If the is-base is still creating, look at ITS spread sources recursively
4589+ if ( isBaseType . creating ) {
4590+ const found = findMemberInCreatingModelSources ( ctx , isBaseType , memberName ) ;
4591+ if ( found ) {
4592+ return createLateBoundMemberSym ( model , found , memberName , modelNode ) ;
4593+ }
4594+ }
4595+ }
4596+ }
4597+
4598+ // Check spread targets
4599+ for ( const propNode of modelNode . properties ) {
4600+ if ( propNode . kind === SyntaxKind . ModelSpreadProperty ) {
4601+ const spreadType = getTypeForNode ( propNode . target , ctx ) ;
4602+ if ( spreadType && spreadType . kind === "Model" ) {
4603+ const prop = spreadType . properties . get ( memberName ) ;
4604+ if ( prop ) {
4605+ return createLateBoundMemberSym ( model , prop , memberName , modelNode ) ;
4606+ }
4607+ if ( spreadType . creating ) {
4608+ const found = findMemberInCreatingModelSources ( ctx , spreadType , memberName ) ;
4609+ if ( found ) {
4610+ return createLateBoundMemberSym ( model , found , memberName , modelNode ) ;
4611+ }
4612+ }
4613+ }
4614+ }
4615+ }
4616+
4617+ return undefined ;
4618+ }
4619+
4620+ function createLateBoundMemberSym (
4621+ model : Model ,
4622+ prop : ModelProperty ,
4623+ memberName : string ,
4624+ containerNode : Node ,
4625+ ) : Sym | undefined {
4626+ const sym = createSymbol (
4627+ prop . node ?? containerNode ,
4628+ memberName ,
4629+ SymbolFlags . Member | SymbolFlags . Declaration | SymbolFlags . LateBound ,
4630+ model . symbol ,
4631+ ) ;
4632+ mutate ( sym ) . type = prop ;
4633+ if ( model . symbol ?. members ) {
4634+ const containerMembers : Mutable < SymbolTable > = resolver . getAugmentedSymbolTable (
4635+ model . symbol . members ,
4636+ ) ;
4637+ containerMembers . set ( memberName , sym ) ;
4638+ }
4639+ return sym ;
4640+ }
4641+
4642+ /**
4643+ * Search for a member in a model that's still being created by looking at its
4644+ * source models (spreads and `is` targets). This handles the case where:
4645+ * model Template<T> {...T}
4646+ * model A is Template<{t: B}>
4647+ * When A is creating and its properties haven't been copied yet, we can look
4648+ * at Template<{t:B}>'s spread sources to find property `t`.
4649+ */
4650+ function findMemberInCreatingModelSources (
4651+ ctx : CheckContext ,
4652+ model : Model ,
4653+ memberName : string ,
4654+ visited : Set < Model > = new Set ( ) ,
4655+ ) : ModelProperty | undefined {
4656+ if ( visited . has ( model ) ) return undefined ;
4657+ visited . add ( model ) ;
4658+
4659+ // Check own properties first (already resolved)
4660+ const ownProp = model . properties . get ( memberName ) ;
4661+ if ( ownProp ) return ownProp ;
4662+
4663+ // Look at the model's AST node for spread sources.
4664+ // Use the model's templateMapper when resolving references in its body,
4665+ // since template instances share the same AST node as the template declaration.
4666+ const modelNode = model . node ;
4667+ if ( ! modelNode ) return undefined ;
4668+
4669+ const resolveCtx = model . templateMapper ? ctx . withMapper ( model . templateMapper ) : ctx ;
4670+
4671+ if (
4672+ modelNode . kind === SyntaxKind . ModelStatement ||
4673+ modelNode . kind === SyntaxKind . ModelExpression
4674+ ) {
4675+ // Check direct property declarations that might not be resolved yet.
4676+ // If the model expression has `t: B` as a declared property but hasn't
4677+ // been fully checked, we can still resolve the property from its member symbol.
4678+ // Due to early linkType on properties, the symbol may already have a type.
4679+ if ( model . creating ) {
4680+ const memberSym = model . node ?. symbol ?. members
4681+ ? getMemberSymbol ( model . node . symbol , memberName )
4682+ : undefined ;
4683+ if ( memberSym ) {
4684+ const memberLinks = resolver . getSymbolLinks ( memberSym ) ;
4685+ if ( memberLinks . declaredType && memberLinks . declaredType . kind === "ModelProperty" ) {
4686+ return memberLinks . declaredType as ModelProperty ;
4687+ }
4688+ }
4689+ }
4690+
4691+ for ( const propNode of modelNode . properties ) {
4692+ if ( propNode . kind === SyntaxKind . ModelSpreadProperty ) {
4693+ const spreadType = getTypeForNode ( propNode . target , resolveCtx ) ;
4694+ if ( spreadType && spreadType . kind === "Model" ) {
4695+ const prop = spreadType . properties . get ( memberName ) ;
4696+ if ( prop ) return prop ;
4697+ if ( spreadType . creating ) {
4698+ const found = findMemberInCreatingModelSources ( ctx , spreadType , memberName , visited ) ;
4699+ if ( found ) return found ;
4700+ }
4701+ }
4702+ }
4703+ }
4704+
4705+ // For model statements, also check the `is` base
4706+ if ( modelNode . kind === SyntaxKind . ModelStatement && modelNode . is ) {
4707+ const isBaseType = getTypeForNode ( modelNode . is , resolveCtx ) ;
4708+ if ( isBaseType && isBaseType . kind === "Model" ) {
4709+ const prop = isBaseType . properties . get ( memberName ) ;
4710+ if ( prop ) return prop ;
4711+ if ( isBaseType . creating ) {
4712+ const found = findMemberInCreatingModelSources (
4713+ ctx ,
4714+ isBaseType ,
4715+ memberName ,
4716+ visited ,
4717+ ) ;
4718+ if ( found ) return found ;
4719+ }
4720+ }
4721+ }
4722+ }
4723+
4724+ return undefined ;
4725+ }
4726+
45454727 function getMemberKindName ( node : Node ) {
45464728 switch ( node . kind ) {
45474729 case SyntaxKind . ModelStatement :
@@ -5092,6 +5274,9 @@ export function createChecker(program: Program, resolver: NameResolver): Checker
50925274 derivedModels : [ ] ,
50935275 } ) ;
50945276 linkType ( ctx , links , type ) ;
5277+ // Set templateMapper early so that member lookups on this creating model
5278+ // can resolve spread targets through the correct mapper context.
5279+ linkMapper ( type , ctx . mapper ) ;
50955280
50965281 if ( node . symbol . members ) {
50975282 const members = resolver . getAugmentedSymbolTable ( node . symbol . members ) ;
@@ -5157,7 +5342,6 @@ export function createChecker(program: Program, resolver: NameResolver): Checker
51575342
51585343 decorators . push ( ...checkDecorators ( ctx , type , node ) ) ;
51595344
5160- linkMapper ( type , ctx . mapper ) ;
51615345 finishType ( type , { skipDecorators : ctx . hasFlags ( CheckFlags . InTemplateDeclaration ) } ) ;
51625346
51635347 lateBindMemberContainer ( type ) ;
0 commit comments