From fec245b0af5d5b008938c15372c38256cffd396f Mon Sep 17 00:00:00 2001 From: Patrick Roza Date: Sun, 21 Jun 2026 23:32:47 +0200 Subject: [PATCH 1/9] _patches: layer the .d.ts-emit schema-facade emitter onto Effect-TS/tsgo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-expresses our effect-app/typescript-go#2 feature (class/error/struct schema facade declaration emit) as three `_patches/` entries, authored against the exact `typescript-go` submodule commit this repo pins (dc37b5249) — the same commit our codex baseline branched from, so they apply cleanly: 029-transformers-declarations-effect-schema.patch (declaration transformer) 030-checker-emitresolver-effect-schema.patch (CreateTypeOf* resolver) 031-printer-emitresolver-effect-schema.patch (EmitResolver interface) The feature is purely additive (no overlap with Effect patches 001-028). After `setup-repo` applies 001-031 and regenerates diagnostics, `go build ./cmd/tsgo` yields a tsgo with Effect's LSP hooks AND our .d.ts emitter. Verified on the macs/scanner src tree: 0 errors, 145 Effect LSP diagnostics (hooks live), and 56 StructFacade + 10 OpaqueClassFacade + 46 OpaqueErrorFacadeClass (emitter live). Co-Authored-By: Claude Opus 4.8 (1M context) --- ...nsformers-declarations-effect-schema.patch | 920 ++++++++++++++++++ ...0-checker-emitresolver-effect-schema.patch | 242 +++++ ...1-printer-emitresolver-effect-schema.patch | 17 + 3 files changed, 1179 insertions(+) create mode 100644 _patches/029-transformers-declarations-effect-schema.patch create mode 100644 _patches/030-checker-emitresolver-effect-schema.patch create mode 100644 _patches/031-printer-emitresolver-effect-schema.patch diff --git a/_patches/029-transformers-declarations-effect-schema.patch b/_patches/029-transformers-declarations-effect-schema.patch new file mode 100644 index 00000000..c3758a68 --- /dev/null +++ b/_patches/029-transformers-declarations-effect-schema.patch @@ -0,0 +1,920 @@ +diff --git a/internal/transformers/declarations/transform.go b/internal/transformers/declarations/transform.go +index b6596e0d7..5cfe49b07 100644 +--- a/internal/transformers/declarations/transform.go ++++ b/internal/transformers/declarations/transform.go +@@ -351,6 +351,7 @@ func (tx *DeclarationTransformer) transformSourceFile(node *ast.SourceFile) *ast + statements := tx.Visitor().VisitNodes(node.Statements) + combinedStatements = tx.transformAndReplaceLatePaintedStatements(statements) + combinedStatements = tx.appendCjsExports(combinedStatements) ++ combinedStatements = tx.createEffectSchemaSourceFileDeclarations(combinedStatements) + combinedStatements.Loc = statements.Loc // setTextRange + if ast.IsExternalOrCommonJSModule(node) { + if ast.IsInJSFile(node.AsNode()) { +@@ -2277,6 +2278,907 @@ func (tx *DeclarationTransformer) transformEnumDeclaration(input *ast.EnumDeclar + ) + } + ++type effectSchemaRequestBaseInfo struct { ++ modelName string ++ brand *ast.Node ++} ++ ++func (tx *DeclarationTransformer) createEffectSchemaSourceFileDeclarations(statements *ast.StatementList) *ast.StatementList { ++ if statements == nil || tx.state.currentSourceFile == nil { ++ return statements ++ } ++ ++ modelNames := map[string]bool{} ++ existingNamespaces := map[string]bool{} ++ classes := map[string]*ast.Node{} ++ schemaClasses := tx.getEffectSchemaOriginalClasses() ++ ++ for _, statement := range statements.Nodes { ++ if className := getEffectSchemaClassName(statement); className != "" && ast.IsClassDeclaration(statement) { ++ classes[className] = statement ++ } ++ } ++ ++ for _, statement := range statements.Nodes { ++ if isEffectSchemaModelNamespace(statement) || isEffectSchemaMaterializedModelNamespace(statement, classes) { ++ name := moduleDeclarationIdentifierName(statement) ++ if name != "" && schemaClasses[name] != nil { ++ modelNames[name] = true ++ existingNamespaces[name] = true ++ } ++ } ++ } ++ ++ requestBaseInfos := map[string]effectSchemaRequestBaseInfo{} ++ for _, statement := range statements.Nodes { ++ if info, ok := getEffectSchemaRequestBaseInfo(statement); ok && classes[info.modelName] != nil && schemaClasses[info.modelName] == nil { ++ requestBaseInfos[info.modelName] = info ++ } ++ } ++ ++ for className, classDeclaration := range schemaClasses { ++ if !existingNamespaces[className] && tx.canCreateEffectSchemaGeneratedNamespace(classDeclaration) { ++ modelNames[className] = true ++ } ++ } ++ ++ // Top-level `const X = S.Struct(...)` / `S.TaggedStruct(...)` schema values. Faceted on ++ // the const itself (it is a value, not a class): the giant `S.Struct<{...}>` annotation ++ // becomes a compact `StructFacade<...>` plus a generated `interface X` (decoded Self) and ++ // a type-only `declare namespace X`. ++ structModelNames := map[string]bool{} ++ for name := range tx.getEffectSchemaOriginalStructs() { ++ if modelNames[name] { ++ continue ++ } ++ if _, ok := requestBaseInfos[name]; ok { ++ continue ++ } ++ if hasTopLevelInterface(statements, name) || hasTopLevelNamespace(statements, name) { ++ continue ++ } ++ if tx.canCreateEffectSchemaGeneratedStructNamespace(name) { ++ structModelNames[name] = true ++ } ++ } ++ ++ if len(modelNames) == 0 && len(requestBaseInfos) == 0 && len(structModelNames) == 0 { ++ return statements ++ } ++ ++ changed := false ++ next := make([]*ast.Node, 0, len(statements.Nodes)) ++ for _, statement := range statements.Nodes { ++ baseModelName := getEffectSchemaBaseModelName(statement) ++ if baseModelName != "" && modelNames[baseModelName] { ++ classDeclaration := schemaClasses[baseModelName] ++ if classDeclaration == nil { ++ classDeclaration = classes[baseModelName] ++ } ++ if classDeclaration != nil { ++ if updated := tx.updateEffectSchemaBaseDeclaration(statement, baseModelName, classDeclaration, needsEffectSchemaIntermediateClass(classDeclaration)); updated != nil { ++ changed = true ++ next = append(next, updated) ++ continue ++ } ++ } ++ } ++ ++ if requestBaseInfo, ok := requestBaseInfos[baseModelName]; ok { ++ if updated := tx.updateEffectSchemaRequestBaseDeclaration(statement, requestBaseInfo); updated != nil { ++ changed = true ++ next = append(next, updated) ++ continue ++ } ++ } ++ ++ if isEffectSchemaModelNamespace(statement) || isEffectSchemaMaterializedModelNamespace(statement, classes) { ++ name := moduleDeclarationIdentifierName(statement) ++ classDeclaration := schemaClasses[name] ++ if classDeclaration == nil { ++ classDeclaration = classes[name] ++ } ++ if classDeclaration != nil { ++ if updated := tx.updateEffectSchemaNamespaceDeclaration(statement, classDeclaration); updated != nil { ++ changed = true ++ next = append(next, updated) ++ continue ++ } ++ } ++ } ++ ++ className := getEffectSchemaClassName(statement) ++ if requestBaseInfos[className].modelName != "" { ++ classDeclaration := statement ++ var typeInterface *ast.Node ++ if !hasTopLevelInterface(statements, className) { ++ typeInterface = tx.createEffectSchemaTypeInterface(classDeclaration) ++ } ++ var namespace *ast.Node ++ if !hasTopLevelNamespace(statements, className) { ++ namespace = tx.createEffectSchemaGeneratedNamespaceDeclaration(className, classDeclaration) ++ } ++ if typeInterface != nil || namespace != nil { ++ changed = true ++ next = append(next, statement) ++ if typeInterface != nil { ++ next = append(next, typeInterface) ++ } ++ if namespace != nil { ++ next = append(next, namespace) ++ } ++ continue ++ } ++ } ++ ++ if className != "" && modelNames[className] { ++ classDeclaration := schemaClasses[className] ++ if classDeclaration == nil { ++ classDeclaration = statement ++ } ++ usesIntermediate := needsEffectSchemaIntermediateClass(classDeclaration) ++ updatedClass := statement ++ if usesIntermediate { ++ updatedClass = tx.updateEffectSchemaClassDeclaration(statement, className) ++ } ++ var typeInterface *ast.Node ++ if !hasTopLevelInterface(statements, className) { ++ typeInterface = tx.createEffectSchemaTypeInterface(classDeclaration) ++ } ++ var namespace *ast.Node ++ if !existingNamespaces[className] { ++ namespace = tx.createEffectSchemaGeneratedNamespaceDeclaration(className, classDeclaration) ++ } ++ if updatedClass != statement || typeInterface != nil || namespace != nil { ++ changed = true ++ if usesIntermediate { ++ next = append(next, tx.createEffectSchemaIntermediateClass(className)) ++ } ++ next = append(next, updatedClass) ++ if typeInterface != nil { ++ next = append(next, typeInterface) ++ } ++ if namespace != nil { ++ existingNamespaces[className] = true ++ next = append(next, namespace) ++ } ++ continue ++ } ++ } ++ ++ if structName := getEffectSchemaStructVariableName(statement); structName != "" && structModelNames[structName] { ++ if declarations := tx.createEffectSchemaStructDeclarations(statement, structName); declarations != nil { ++ changed = true ++ next = append(next, declarations...) ++ continue ++ } ++ } ++ ++ if isEffectSchemaStructCompanionTypeAlias(statement, structModelNames) { ++ // Dropped — replaced by the generated `interface X`. ++ changed = true ++ continue ++ } ++ ++ next = append(next, statement) ++ } ++ ++ if !changed { ++ return statements ++ } ++ return tx.Factory().NewNodeList(next) ++} ++ ++func (tx *DeclarationTransformer) getEffectSchemaOriginalClasses() map[string]*ast.Node { ++ classes := map[string]*ast.Node{} ++ for _, statement := range tx.state.currentSourceFile.Statements.Nodes { ++ if ast.IsClassDeclaration(statement) && statement.Name() != nil && hasEffectSchemaOpaqueHeritage(statement) { ++ classes[statement.Name().Text()] = statement ++ } ++ } ++ return classes ++} ++ ++// --- Struct/TaggedStruct const faceting --- ++ ++func (tx *DeclarationTransformer) getEffectSchemaOriginalStructs() map[string]bool { ++ structs := map[string]bool{} ++ for _, statement := range tx.state.currentSourceFile.Statements.Nodes { ++ name := getEffectSchemaStructVariableName(statement) ++ if name == "" { ++ continue ++ } ++ decl := statement.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes[0] ++ if decl.AsVariableDeclaration().Initializer != nil && isEffectSchemaStructInitializer(decl.AsVariableDeclaration().Initializer) { ++ structs[name] = true ++ } ++ } ++ return structs ++} ++ ++func isEffectSchemaStructInitializer(expression *ast.Node) bool { ++ if !ast.IsCallExpression(expression) { ++ return false ++ } ++ callee := expression.AsCallExpression().Expression ++ if !ast.IsPropertyAccessExpression(callee) || callee.Name() == nil { ++ return false ++ } ++ name := callee.Name().Text() ++ if name != "Struct" && name != "TaggedStruct" { ++ return false ++ } ++ left := callee.Expression() ++ return left != nil && ast.IsIdentifier(left) && (left.Text() == "S" || left.Text() == "Schema") ++} ++ ++func getEffectSchemaStructVariableName(statement *ast.Node) string { ++ if !ast.IsVariableStatement(statement) || statement.AsVariableStatement().DeclarationList == nil { ++ return "" ++ } ++ declarations := statement.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes ++ if len(declarations) != 1 || declarations[0].Name() == nil || !ast.IsIdentifier(declarations[0].Name()) { ++ return "" ++ } ++ return declarations[0].Name().Text() ++} ++ ++func (tx *DeclarationTransformer) getEffectSchemaSourceStructDeclaration(modelName string) *ast.Node { ++ for _, statement := range tx.state.currentSourceFile.Statements.Nodes { ++ if getEffectSchemaStructVariableName(statement) != modelName { ++ continue ++ } ++ decl := statement.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes[0] ++ if decl.AsVariableDeclaration().Initializer != nil && isEffectSchemaStructInitializer(decl.AsVariableDeclaration().Initializer) { ++ return decl ++ } ++ } ++ return nil ++} ++ ++// Reads a property (Encoded / Type / ~type.make.in / fields / services) off the source ++// struct value's type and serializes it; `never` services stay `never`. ++func (tx *DeclarationTransformer) materializeEffectSchemaStructProperty(modelName string, propertyName string) *ast.Node { ++ declaration := tx.getEffectSchemaSourceStructDeclaration(modelName) ++ if declaration == nil { ++ return nil ++ } ++ return tx.resolver.CreateTypeOfStructSchemaProperty(tx.EmitContext(), declaration, propertyName, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker) ++} ++ ++func (tx *DeclarationTransformer) canCreateEffectSchemaGeneratedStructNamespace(modelName string) bool { ++ return tx.materializeEffectSchemaStructProperty(modelName, "Encoded") != nil ++} ++ ++func (tx *DeclarationTransformer) createEffectSchemaStructInterfaceFromProperty(modelName string, propertyName string, declaredName string) *ast.Node { ++ typeNode := tx.materializeEffectSchemaStructProperty(modelName, propertyName) ++ if typeNode == nil { ++ return nil ++ } ++ if ast.IsTypeLiteralNode(typeNode) { ++ return tx.Factory().NewInterfaceDeclaration(nil, tx.Factory().NewIdentifier(declaredName), nil, nil, tx.Factory().NewNodeList(typeNode.AsTypeLiteralNode().Members.Nodes)) ++ } ++ return tx.Factory().NewTypeAliasDeclaration(nil, tx.Factory().NewIdentifier(declaredName), nil, typeNode) ++} ++ ++func (tx *DeclarationTransformer) createEffectSchemaStructServiceDeclaration(modelName string, name string) *ast.Node { ++ resolved := tx.materializeEffectSchemaStructProperty(modelName, name) ++ serviceType := resolved ++ if resolved == nil || resolved.Kind == ast.KindAnyKeyword { ++ serviceType = tx.Factory().NewKeywordTypeNode(ast.KindNeverKeyword) ++ } ++ return tx.Factory().NewTypeAliasDeclaration(nil, tx.Factory().NewIdentifier(name), nil, serviceType) ++} ++ ++func structHasExportModifier(statement *ast.Node) bool { ++ return ast.HasSyntacticModifier(statement, ast.ModifierFlagsExport) ++} ++ ++func (tx *DeclarationTransformer) effectSchemaStructModifiers(exported bool, includeDeclare bool) *ast.ModifierList { ++ modifiers := []*ast.Node{} ++ if exported { ++ modifiers = append(modifiers, tx.Factory().NewModifier(ast.KindExportKeyword)) ++ } ++ if includeDeclare { ++ modifiers = append(modifiers, tx.Factory().NewModifier(ast.KindDeclareKeyword)) ++ } ++ if len(modifiers) == 0 { ++ return nil ++ } ++ return tx.Factory().NewModifierList(modifiers) ++} ++ ++func (tx *DeclarationTransformer) createEffectSchemaGeneratedStructNamespace(modelName string, exported bool) *ast.Node { ++ fields := tx.createEffectSchemaStructInterfaceFromProperty(modelName, "fields", "Fields") ++ if fields == nil { ++ return nil ++ } ++ encoded := tx.createEffectSchemaStructInterfaceFromProperty(modelName, "Encoded", "Encoded") ++ if encoded == nil { ++ return nil ++ } ++ statements := []*ast.Node{fields, encoded} ++ if makeDeclaration := tx.createEffectSchemaStructInterfaceFromProperty(modelName, "~type.make.in", "Make"); makeDeclaration != nil { ++ statements = append(statements, makeDeclaration) ++ } ++ statements = append(statements, tx.createEffectSchemaStructServiceDeclaration(modelName, "DecodingServices")) ++ statements = append(statements, tx.createEffectSchemaStructServiceDeclaration(modelName, "EncodingServices")) ++ return tx.Factory().NewModuleDeclaration( ++ tx.effectSchemaStructModifiers(exported, true), ++ ast.KindNamespaceKeyword, ++ tx.Factory().NewIdentifier(modelName), ++ tx.Factory().NewModuleBlock(tx.Factory().NewNodeList(statements)), ++ ) ++} ++ ++// `import("#lib/StructFacade").StructFacade` — a self-contained import type resolved cross-package via ++// the api package's `#lib/*` subpath import. The scanner-local facade extends ++// `S.Struct`, so the value stays Workflow-compatible. ++func (tx *DeclarationTransformer) createEffectSchemaStructFacadeType(modelName string) *ast.Node { ++ member := func(name string) *ast.Node { ++ return tx.Factory().NewTypeReferenceNode(tx.Factory().NewQualifiedName(tx.Factory().NewIdentifier(modelName), tx.Factory().NewIdentifier(name)), nil) ++ } ++ typeArguments := tx.Factory().NewNodeList([]*ast.Node{ ++ tx.Factory().NewTypeReferenceNode(tx.Factory().NewIdentifier(modelName), nil), ++ member("Encoded"), ++ member("Make"), ++ member("DecodingServices"), ++ member("EncodingServices"), ++ member("Fields"), ++ }) ++ argument := tx.Factory().NewLiteralTypeNode(tx.Factory().NewStringLiteral("#lib/StructFacade", ast.TokenFlagsNone)) ++ return tx.Factory().NewImportTypeNode(false, argument, nil, tx.Factory().NewIdentifier("StructFacade"), typeArguments) ++} ++ ++func (tx *DeclarationTransformer) createEffectSchemaStructDeclarations(statement *ast.Node, modelName string) []*ast.Node { ++ typeNode := tx.materializeEffectSchemaStructProperty(modelName, "Type") ++ if typeNode == nil || !ast.IsTypeLiteralNode(typeNode) { ++ return nil ++ } ++ namespace := tx.createEffectSchemaGeneratedStructNamespace(modelName, structHasExportModifier(statement)) ++ if namespace == nil { ++ return nil ++ } ++ exported := structHasExportModifier(statement) ++ declaration := statement.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes[0] ++ updatedDeclaration := tx.Factory().UpdateVariableDeclaration( ++ declaration.AsVariableDeclaration(), ++ declaration.Name(), ++ declaration.AsVariableDeclaration().ExclamationToken, ++ tx.createEffectSchemaStructFacadeType(modelName), ++ declaration.AsVariableDeclaration().Initializer, ++ ) ++ declarations := tx.Factory().NewNodeList([]*ast.Node{updatedDeclaration}) ++ declarationList := tx.Factory().UpdateVariableDeclarationList(statement.AsVariableStatement().DeclarationList.AsVariableDeclarationList(), declarations, statement.AsVariableStatement().DeclarationList.Flags) ++ retypedConst := tx.Factory().UpdateVariableStatement(statement.AsVariableStatement(), statement.Modifiers(), declarationList) ++ typeInterface := tx.Factory().NewInterfaceDeclaration(tx.effectSchemaStructModifiers(exported, false), tx.Factory().NewIdentifier(modelName), nil, nil, tx.Factory().NewNodeList(typeNode.AsTypeLiteralNode().Members.Nodes)) ++ return []*ast.Node{retypedConst, typeInterface, namespace} ++} ++ ++func isEffectSchemaStructCompanionTypeAlias(statement *ast.Node, structModelNames map[string]bool) bool { ++ return ast.IsTypeAliasDeclaration(statement) && statement.Name() != nil && structModelNames[statement.Name().Text()] ++} ++ ++func hasEffectSchemaOpaqueHeritage(classDeclaration *ast.Node) bool { ++ heritageType := getFirstHeritageType(classDeclaration) ++ if heritageType == nil { ++ return false ++ } ++ expression := heritageType.AsExpressionWithTypeArguments().Expression ++ if expression == nil { ++ return false ++ } ++ if ast.IsCallExpression(expression) { ++ expression = expression.AsCallExpression().Expression ++ } ++ if ast.IsCallExpression(expression) { ++ expression = expression.AsCallExpression().Expression ++ } ++ return isEffectSchemaOpaqueExpression(expression) ++} ++ ++func getFirstHeritageType(classDeclaration *ast.Node) *ast.Node { ++ if classDeclaration == nil || !ast.IsClassDeclaration(classDeclaration) || classDeclaration.AsClassDeclaration().HeritageClauses == nil { ++ return nil ++ } ++ clauses := classDeclaration.AsClassDeclaration().HeritageClauses.Nodes ++ if len(clauses) == 0 || clauses[0].AsHeritageClause().Types == nil || len(clauses[0].AsHeritageClause().Types.Nodes) == 0 { ++ return nil ++ } ++ return clauses[0].AsHeritageClause().Types.Nodes[0] ++} ++ ++func isEffectSchemaOpaqueExpression(expression *ast.Node) bool { ++ return getEffectSchemaCtorFacadeName(expression) != "" ++} ++ ++// Maps a schema-model heritage constructor (S.Opaque(...), S.Class(...), S.ErrorClass(...), ...) ++// to the effect-app facade type its base should be rewritten to. Returns "" for non-model ++// constructors. Opaque family (incl. requests) -> OpaqueFacade; class family -> OpaqueClassFacade; ++// error family -> OpaqueErrorFacadeClass. ++func getEffectSchemaCtorFacadeName(expression *ast.Node) string { ++ if !ast.IsPropertyAccessExpression(expression) || expression.Name() == nil || expression.Expression() == nil { ++ return "" ++ } ++ left := expression.Expression() ++ if !ast.IsIdentifier(left) || (left.Text() != "S" && left.Text() != "Schema") { ++ return "" ++ } ++ switch expression.Name().Text() { ++ case "Opaque", "OpaqueFacade": ++ return "OpaqueFacade" ++ case "Class", "TaggedClass": ++ return "OpaqueClassFacade" ++ case "ErrorClass", "TaggedErrorClass": ++ return "OpaqueErrorFacadeClass" ++ default: ++ return "" ++ } ++} ++ ++func getEffectSchemaClassFacadeName(classDeclaration *ast.Node) string { ++ heritageType := getFirstHeritageType(classDeclaration) ++ if heritageType == nil { ++ return "OpaqueFacade" ++ } ++ expression := heritageType.AsExpressionWithTypeArguments().Expression ++ for expression != nil && ast.IsCallExpression(expression) { ++ expression = expression.AsCallExpression().Expression ++ } ++ if expression != nil { ++ if name := getEffectSchemaCtorFacadeName(expression); name != "" { ++ return name ++ } ++ } ++ return "OpaqueFacade" ++} ++ ++func isEffectSchemaModelNamespace(statement *ast.Node) bool { ++ if !ast.IsModuleDeclaration(statement) || statement.Name() == nil || !ast.IsIdentifier(statement.Name()) || statement.AsModuleDeclaration().Body == nil || statement.AsModuleDeclaration().Body.Kind != ast.KindModuleBlock { ++ return false ++ } ++ return core.Some(statement.AsModuleDeclaration().Body.AsModuleBlock().Statements.Nodes, isEffectSchemaStructNestedEncodedInterface) ++} ++ ++func isEffectSchemaMaterializedModelNamespace(statement *ast.Node, classes map[string]*ast.Node) bool { ++ name := moduleDeclarationIdentifierName(statement) ++ if name == "" || classes[name] == nil || statement.AsModuleDeclaration().Body == nil || statement.AsModuleDeclaration().Body.Kind != ast.KindModuleBlock { ++ return false ++ } ++ return core.Some(statement.AsModuleDeclaration().Body.AsModuleBlock().Statements.Nodes, isEffectSchemaEncodedInterface) ++} ++ ++func moduleDeclarationIdentifierName(statement *ast.Node) string { ++ if !ast.IsModuleDeclaration(statement) || statement.Name() == nil || !ast.IsIdentifier(statement.Name()) { ++ return "" ++ } ++ return statement.Name().Text() ++} ++ ++func isEffectSchemaStructNestedEncodedInterface(statement *ast.Node) bool { ++ if !ast.IsInterfaceDeclaration(statement) || statement.Name() == nil || statement.Name().Text() != "Encoded" || statement.AsInterfaceDeclaration().HeritageClauses == nil { ++ return false ++ } ++ clauses := statement.AsInterfaceDeclaration().HeritageClauses.Nodes ++ if len(clauses) != 1 || clauses[0].AsHeritageClause().Types == nil || len(clauses[0].AsHeritageClause().Types.Nodes) != 1 { ++ return false ++ } ++ heritageType := clauses[0].AsHeritageClause().Types.Nodes[0] ++ expression := heritageType.AsExpressionWithTypeArguments().Expression ++ typeArguments := heritageType.AsExpressionWithTypeArguments().TypeArguments ++ return ast.IsPropertyAccessExpression(expression) && ++ expression.Name() != nil && ++ expression.Name().Text() == "StructNestedEncoded" && ++ typeArguments != nil && ++ len(typeArguments.Nodes) == 1 && ++ typeArguments.Nodes[0].Kind == ast.KindTypeQuery ++} ++ ++func isEffectSchemaEncodedInterface(statement *ast.Node) bool { ++ return ast.IsInterfaceDeclaration(statement) && statement.Name() != nil && statement.Name().Text() == "Encoded" ++} ++ ++func (tx *DeclarationTransformer) canCreateEffectSchemaGeneratedNamespace(classDeclaration *ast.Node) bool { ++ return tx.createEffectSchemaEncodedDeclaration(classDeclaration) != nil ++} ++ ++func getEffectSchemaClassName(statement *ast.Node) string { ++ if ast.IsClassDeclaration(statement) && statement.Name() != nil { ++ return statement.Name().Text() ++ } ++ return "" ++} ++ ++func hasTopLevelInterface(statements *ast.StatementList, name string) bool { ++ return core.Some(statements.Nodes, func(statement *ast.Node) bool { ++ return ast.IsInterfaceDeclaration(statement) && statement.Name() != nil && statement.Name().Text() == name ++ }) ++} ++ ++func hasTopLevelNamespace(statements *ast.StatementList, name string) bool { ++ return core.Some(statements.Nodes, func(statement *ast.Node) bool { ++ return moduleDeclarationIdentifierName(statement) == name ++ }) ++} ++ ++func (tx *DeclarationTransformer) createEffectSchemaTypeInterface(classDeclaration *ast.Node) *ast.Node { ++ if classDeclaration == nil || classDeclaration.Name() == nil { ++ return nil ++ } ++ literal := tx.resolver.CreateTypeLiteralOfClassDeclaration(tx.EmitContext(), classDeclaration, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker) ++ if literal == nil || !ast.IsTypeLiteralNode(literal) { ++ return nil ++ } ++ return tx.Factory().NewInterfaceDeclaration( ++ tx.createEffectSchemaNamespaceModifiers(classDeclaration, false), ++ tx.Factory().NewIdentifier(classDeclaration.Name().Text()), ++ nil, ++ nil, ++ tx.Factory().NewNodeList(literal.AsTypeLiteralNode().Members.Nodes), ++ ) ++} ++ ++func (tx *DeclarationTransformer) updateEffectSchemaNamespaceDeclaration(namespace *ast.Node, classDeclaration *ast.Node) *ast.Node { ++ body := namespace.AsModuleDeclaration().Body ++ if body == nil || body.Kind != ast.KindModuleBlock { ++ return nil ++ } ++ existing := map[string]bool{} ++ kept := make([]*ast.Node, 0, len(body.AsModuleBlock().Statements.Nodes)) ++ for _, statement := range body.AsModuleBlock().Statements.Nodes { ++ if (ast.IsInterfaceDeclaration(statement) || ast.IsTypeAliasDeclaration(statement)) && statement.Name() != nil && statement.Name().Text() != "Encoded" { ++ name := statement.Name().Text() ++ existing[name] = true ++ if name == "Make" || name == "DecodingServices" || name == "EncodingServices" { ++ continue ++ } ++ } ++ kept = append(kept, statement) ++ } ++ ++ additions := []*ast.Node{} ++ if makeDeclaration := tx.createEffectSchemaMakeDeclaration(classDeclaration); makeDeclaration != nil { ++ additions = append(additions, makeDeclaration) ++ } ++ if decodingServices := tx.createEffectSchemaServiceDeclaration(classDeclaration, "DecodingServices"); decodingServices != nil { ++ additions = append(additions, decodingServices) ++ } ++ if encodingServices := tx.createEffectSchemaServiceDeclaration(classDeclaration, "EncodingServices"); encodingServices != nil { ++ additions = append(additions, encodingServices) ++ } ++ if len(additions) == 0 && len(existing) == 0 { ++ return nil ++ } ++ ++ statements := tx.Factory().NewNodeList(append(kept, additions...)) ++ moduleBlock := tx.Factory().UpdateModuleBlock(body.AsModuleBlock(), statements) ++ return tx.Factory().UpdateModuleDeclaration(namespace.AsModuleDeclaration(), namespace.Modifiers(), namespace.AsModuleDeclaration().Keyword, namespace.Name(), moduleBlock) ++} ++ ++func (tx *DeclarationTransformer) createEffectSchemaGeneratedNamespaceDeclaration(modelName string, classDeclaration *ast.Node) *ast.Node { ++ encoded := tx.createEffectSchemaEncodedDeclaration(classDeclaration) ++ if encoded == nil { ++ return nil ++ } ++ statements := []*ast.Node{encoded} ++ if makeDeclaration := tx.createEffectSchemaMakeDeclaration(classDeclaration); makeDeclaration != nil { ++ statements = append(statements, makeDeclaration) ++ } ++ if decodingServices := tx.createEffectSchemaServiceDeclaration(classDeclaration, "DecodingServices"); decodingServices != nil { ++ statements = append(statements, decodingServices) ++ } ++ if encodingServices := tx.createEffectSchemaServiceDeclaration(classDeclaration, "EncodingServices"); encodingServices != nil { ++ statements = append(statements, encodingServices) ++ } ++ return tx.Factory().NewModuleDeclaration( ++ tx.createEffectSchemaNamespaceModifiers(classDeclaration, true), ++ ast.KindNamespaceKeyword, ++ tx.Factory().NewIdentifier(modelName), ++ tx.Factory().NewModuleBlock(tx.Factory().NewNodeList(statements)), ++ ) ++} ++ ++func (tx *DeclarationTransformer) createEffectSchemaNamespaceModifiers(classDeclaration *ast.Node, includeDeclare bool) *ast.ModifierList { ++ modifiers := []*ast.Node{} ++ if ast.HasSyntacticModifier(classDeclaration, ast.ModifierFlagsExport) { ++ modifiers = append(modifiers, tx.Factory().NewModifier(ast.KindExportKeyword)) ++ } ++ if includeDeclare { ++ modifiers = append(modifiers, tx.Factory().NewModifier(ast.KindDeclareKeyword)) ++ } ++ if len(modifiers) == 0 { ++ return nil ++ } ++ return tx.Factory().NewModifierList(modifiers) ++} ++ ++func (tx *DeclarationTransformer) createEffectSchemaEncodedDeclaration(classDeclaration *ast.Node) *ast.Node { ++ encodedType := tx.resolver.CreateTypeOfClassStaticProperty(tx.EmitContext(), classDeclaration, "Encoded", tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker) ++ if encodedType == nil { ++ return nil ++ } ++ if ast.IsTypeLiteralNode(encodedType) { ++ return tx.Factory().NewInterfaceDeclaration(nil, tx.Factory().NewIdentifier("Encoded"), nil, nil, tx.Factory().NewNodeList(encodedType.AsTypeLiteralNode().Members.Nodes)) ++ } ++ return tx.Factory().NewTypeAliasDeclaration(nil, tx.Factory().NewIdentifier("Encoded"), nil, encodedType) ++} ++ ++func (tx *DeclarationTransformer) createEffectSchemaMakeDeclaration(classDeclaration *ast.Node) *ast.Node { ++ makeType := tx.resolver.CreateMakeTypeOfClassDeclaration(tx.EmitContext(), classDeclaration, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker) ++ if makeType == nil { ++ makeType = tx.resolver.CreateTypeOfClassStaticProperty(tx.EmitContext(), classDeclaration, "~type.make.in", tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker) ++ } ++ if makeType == nil { ++ return nil ++ } ++ if ast.IsTypeLiteralNode(makeType) { ++ return tx.Factory().NewInterfaceDeclaration(nil, tx.Factory().NewIdentifier("Make"), nil, nil, tx.Factory().NewNodeList(makeType.AsTypeLiteralNode().Members.Nodes)) ++ } ++ return tx.Factory().NewTypeAliasDeclaration(nil, tx.Factory().NewIdentifier("Make"), nil, makeType) ++} ++ ++func (tx *DeclarationTransformer) createEffectSchemaServiceDeclaration(classDeclaration *ast.Node, name string) *ast.Node { ++ resolved := tx.resolver.CreateTypeOfClassStaticProperty(tx.EmitContext(), classDeclaration, name, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker) ++ if resolved == nil { ++ return nil ++ } ++ serviceType := resolved ++ if resolved.Kind == ast.KindAnyKeyword { ++ serviceType = tx.Factory().NewKeywordTypeNode(ast.KindNeverKeyword) ++ } ++ return tx.Factory().NewTypeAliasDeclaration(nil, tx.Factory().NewIdentifier(name), nil, serviceType) ++} ++ ++func getEffectSchemaBaseModelName(statement *ast.Node) string { ++ if !ast.IsVariableStatement(statement) || statement.AsVariableStatement().DeclarationList == nil { ++ return "" ++ } ++ declarations := statement.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes ++ if len(declarations) != 1 || declarations[0].Name() == nil || !ast.IsIdentifier(declarations[0].Name()) { ++ return "" ++ } ++ name := declarations[0].Name().Text() ++ if !strings.HasSuffix(name, "_base") { ++ return "" ++ } ++ baseName := strings.TrimSuffix(name, "_base") ++ return strings.TrimPrefix(baseName, "__") ++} ++ ++func (tx *DeclarationTransformer) updateEffectSchemaBaseDeclaration(statement *ast.Node, modelName string, classDeclaration *ast.Node, usesIntermediateClass bool) *ast.Node { ++ declaration := statement.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes[0] ++ name := declaration.Name() ++ if usesIntermediateClass { ++ name = tx.Factory().NewIdentifier("__" + modelName + "_base") ++ } ++ updatedDeclaration := tx.Factory().UpdateVariableDeclaration( ++ declaration.AsVariableDeclaration(), ++ name, ++ declaration.AsVariableDeclaration().ExclamationToken, ++ tx.createEffectSchemaFacadeBaseType(modelName, classDeclaration, declaration.AsVariableDeclaration().Type), ++ declaration.AsVariableDeclaration().Initializer, ++ ) ++ declarations := tx.Factory().NewNodeList([]*ast.Node{updatedDeclaration}) ++ declarationList := tx.Factory().UpdateVariableDeclarationList(statement.AsVariableStatement().DeclarationList.AsVariableDeclarationList(), declarations, statement.AsVariableStatement().DeclarationList.Flags) ++ return tx.Factory().UpdateVariableStatement(statement.AsVariableStatement(), statement.Modifiers(), declarationList) ++} ++ ++func getEffectSchemaRequestBaseInfo(statement *ast.Node) (effectSchemaRequestBaseInfo, bool) { ++ if !ast.IsVariableStatement(statement) || statement.AsVariableStatement().DeclarationList == nil { ++ return effectSchemaRequestBaseInfo{}, false ++ } ++ declarations := statement.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes ++ if len(declarations) != 1 || declarations[0].Name() == nil || !ast.IsIdentifier(declarations[0].Name()) || declarations[0].Type() == nil { ++ return effectSchemaRequestBaseInfo{}, false ++ } ++ modelName := getEffectSchemaBaseModelName(statement) ++ if modelName == "" { ++ return effectSchemaRequestBaseInfo{}, false ++ } ++ opaqueType := getEffectSchemaRequestOpaqueType(declarations[0].Type(), modelName) ++ if opaqueType == nil || opaqueType.AsTypeReferenceNode().TypeArguments == nil || len(opaqueType.AsTypeReferenceNode().TypeArguments.Nodes) < 4 { ++ return effectSchemaRequestBaseInfo{}, false ++ } ++ return effectSchemaRequestBaseInfo{modelName: modelName, brand: opaqueType.AsTypeReferenceNode().TypeArguments.Nodes[3]}, true ++} ++ ++func getEffectSchemaRequestOpaqueType(typeNode *ast.Node, modelName string) *ast.Node { ++ if isEffectSchemaRequestOpaqueType(typeNode, modelName) { ++ return typeNode ++ } ++ if typeNode == nil || typeNode.Kind != ast.KindIntersectionType { ++ return nil ++ } ++ for _, part := range typeNode.AsIntersectionTypeNode().Types.Nodes { ++ if isEffectSchemaRequestOpaqueType(part, modelName) { ++ return part ++ } ++ } ++ return nil ++} ++ ++func isEffectSchemaRequestOpaqueType(typeNode *ast.Node, modelName string) bool { ++ if typeNode == nil || typeNode.Kind != ast.KindTypeReference { ++ return false ++ } ++ typeName := typeNode.AsTypeReferenceNode().TypeName ++ if typeName == nil || typeName.Kind != ast.KindQualifiedName { ++ return false ++ } ++ qualifiedName := typeName.AsQualifiedName() ++ if qualifiedName.Right.Text() != "Opaque" || qualifiedName.Left == nil || qualifiedName.Left.Kind != ast.KindIdentifier || qualifiedName.Left.Text() != "S" { ++ return false ++ } ++ typeArguments := typeNode.AsTypeReferenceNode().TypeArguments ++ if typeArguments == nil || len(typeArguments.Nodes) < 4 { ++ return false ++ } ++ return isEffectSchemaNamedTypeReference(typeArguments.Nodes[0], modelName) && isEffectSchemaExtendedSchemaNoEncodedType(typeArguments.Nodes[1]) ++} ++ ++func isEffectSchemaNamedTypeReference(typeNode *ast.Node, name string) bool { ++ return typeNode != nil && typeNode.Kind == ast.KindTypeReference && typeNode.AsTypeReferenceNode().TypeName.Kind == ast.KindIdentifier && typeNode.AsTypeReferenceNode().TypeName.Text() == name ++} ++ ++func isEffectSchemaExtendedSchemaNoEncodedType(typeNode *ast.Node) bool { ++ if typeNode == nil || typeNode.Kind != ast.KindTypeQuery { ++ return false ++ } ++ exprName := typeNode.AsTypeQueryNode().ExprName ++ if exprName == nil || exprName.Kind != ast.KindQualifiedName { ++ return false ++ } ++ qualifiedName := exprName.AsQualifiedName() ++ return qualifiedName.Right.Text() == "ExtendedSchemaNoEncoded" && qualifiedName.Left != nil && qualifiedName.Left.Kind == ast.KindIdentifier && qualifiedName.Left.Text() == "S" ++} ++ ++func (tx *DeclarationTransformer) updateEffectSchemaRequestBaseDeclaration(statement *ast.Node, info effectSchemaRequestBaseInfo) *ast.Node { ++ declaration := statement.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes[0] ++ updatedType := tx.updateEffectSchemaRequestBaseType(declaration.Type(), info) ++ if updatedType == nil { ++ return nil ++ } ++ updatedDeclaration := tx.Factory().UpdateVariableDeclaration( ++ declaration.AsVariableDeclaration(), ++ declaration.Name(), ++ declaration.AsVariableDeclaration().ExclamationToken, ++ updatedType, ++ declaration.AsVariableDeclaration().Initializer, ++ ) ++ declarations := tx.Factory().NewNodeList([]*ast.Node{updatedDeclaration}) ++ declarationList := tx.Factory().UpdateVariableDeclarationList(statement.AsVariableStatement().DeclarationList.AsVariableDeclarationList(), declarations, statement.AsVariableStatement().DeclarationList.Flags) ++ return tx.Factory().UpdateVariableStatement(statement.AsVariableStatement(), statement.Modifiers(), declarationList) ++} ++ ++func (tx *DeclarationTransformer) updateEffectSchemaRequestBaseType(typeNode *ast.Node, info effectSchemaRequestBaseInfo) *ast.Node { ++ if isEffectSchemaRequestOpaqueType(typeNode, info.modelName) { ++ return tx.createEffectSchemaFacadeTypeReference(info.modelName, info.brand, "OpaqueFacade") ++ } ++ if typeNode == nil || typeNode.Kind != ast.KindIntersectionType { ++ return nil ++ } ++ changed := false ++ types := make([]*ast.Node, 0, len(typeNode.AsIntersectionTypeNode().Types.Nodes)) ++ for _, part := range typeNode.AsIntersectionTypeNode().Types.Nodes { ++ if isEffectSchemaRequestOpaqueType(part, info.modelName) { ++ changed = true ++ types = append(types, tx.createEffectSchemaFacadeTypeReference(info.modelName, info.brand, "OpaqueFacade")) ++ } else { ++ types = append(types, part) ++ } ++ } ++ if !changed { ++ return nil ++ } ++ return tx.Factory().UpdateIntersectionTypeNode(typeNode.AsIntersectionTypeNode(), tx.Factory().NewNodeList(types)) ++} ++ ++func needsEffectSchemaIntermediateClass(classDeclaration *ast.Node) bool { ++ return classDeclaration != nil && classDeclaration.ClassLikeData() != nil && len(classDeclaration.ClassLikeData().Members.Nodes) > 0 ++} ++ ++func (tx *DeclarationTransformer) createEffectSchemaIntermediateClass(modelName string) *ast.Node { ++ return tx.Factory().NewClassDeclaration( ++ tx.Factory().NewModifierList([]*ast.Node{tx.Factory().NewModifier(ast.KindDeclareKeyword)}), ++ tx.Factory().NewIdentifier("__"+modelName), ++ nil, ++ tx.Factory().NewNodeList([]*ast.Node{ ++ tx.Factory().NewHeritageClause(ast.KindExtendsKeyword, tx.Factory().NewNodeList([]*ast.Node{ ++ tx.Factory().NewExpressionWithTypeArguments(tx.Factory().NewIdentifier("__"+modelName+"_base"), nil), ++ })), ++ }), ++ tx.Factory().NewNodeList([]*ast.Node{}), ++ ) ++} ++ ++func (tx *DeclarationTransformer) updateEffectSchemaClassDeclaration(classDeclaration *ast.Node, modelName string) *ast.Node { ++ return tx.Factory().UpdateClassDeclaration( ++ classDeclaration.AsClassDeclaration(), ++ classDeclaration.Modifiers(), ++ classDeclaration.Name(), ++ classDeclaration.AsClassDeclaration().TypeParameters, ++ tx.Factory().NewNodeList([]*ast.Node{ ++ tx.Factory().NewHeritageClause(ast.KindExtendsKeyword, tx.Factory().NewNodeList([]*ast.Node{ ++ tx.Factory().NewExpressionWithTypeArguments(tx.Factory().NewIdentifier("__"+modelName), nil), ++ })), ++ }), ++ classDeclaration.AsClassDeclaration().Members, ++ ) ++} ++ ++func (tx *DeclarationTransformer) createEffectSchemaFacadeBaseType(modelName string, classDeclaration *ast.Node, baseType *ast.Node) *ast.Node { ++ facadeName := getEffectSchemaClassFacadeName(classDeclaration) ++ brandType := tx.getEffectSchemaFacadeBrandType(baseType, facadeName) ++ return tx.Factory().NewIntersectionTypeNode(tx.Factory().NewNodeList([]*ast.Node{ ++ tx.createEffectSchemaFacadeTypeReference(modelName, brandType, facadeName), ++ tx.Factory().NewTypeLiteralNode(tx.Factory().NewNodeList(tx.createEffectSchemaStaticMembers(classDeclaration))), ++ })) ++} ++ ++// The facade's Brand (last type arg). For the class/error families the source base is ++// S.EnhancedClass — the 3rd arg is the brand (e.g. Cause.YieldableError ++// for errors); preserve it. The Opaque family carries no brand on the base, so use {}. ++func (tx *DeclarationTransformer) getEffectSchemaFacadeBrandType(baseType *ast.Node, facadeName string) *ast.Node { ++ empty := tx.Factory().NewTypeLiteralNode(tx.Factory().NewNodeList([]*ast.Node{})) ++ if facadeName == "OpaqueFacade" || baseType == nil { ++ return empty ++ } ++ typeNode := baseType ++ if ast.IsIntersectionTypeNode(typeNode) { ++ nodes := typeNode.AsIntersectionTypeNode().Types.Nodes ++ if len(nodes) == 0 { ++ return empty ++ } ++ typeNode = nodes[0] ++ } ++ if typeNode.Kind != ast.KindTypeReference || typeNode.AsTypeReferenceNode().TypeArguments == nil { ++ return empty ++ } ++ args := typeNode.AsTypeReferenceNode().TypeArguments.Nodes ++ if len(args) >= 3 { ++ return args[2] ++ } ++ return empty ++} ++ ++func (tx *DeclarationTransformer) createEffectSchemaFacadeTypeReference(modelName string, brandType *ast.Node, facadeName string) *ast.Node { ++ model := tx.Factory().NewIdentifier(modelName) ++ return tx.Factory().NewTypeReferenceNode( ++ tx.Factory().NewQualifiedName(tx.Factory().NewIdentifier("S"), tx.Factory().NewIdentifier(facadeName)), ++ tx.Factory().NewNodeList([]*ast.Node{ ++ tx.Factory().NewTypeReferenceNode(model, nil), ++ tx.Factory().NewTypeReferenceNode(tx.Factory().NewQualifiedName(model, tx.Factory().NewIdentifier("Encoded")), nil), ++ tx.Factory().NewTypeReferenceNode(tx.Factory().NewQualifiedName(model, tx.Factory().NewIdentifier("Make")), nil), ++ tx.Factory().NewTypeReferenceNode(tx.Factory().NewQualifiedName(model, tx.Factory().NewIdentifier("DecodingServices")), nil), ++ tx.Factory().NewTypeReferenceNode(tx.Factory().NewQualifiedName(model, tx.Factory().NewIdentifier("EncodingServices")), nil), ++ brandType, ++ }), ++ ) ++} ++ ++func (tx *DeclarationTransformer) createEffectSchemaStaticMembers(classDeclaration *ast.Node) []*ast.Node { ++ members := []*ast.Node{} ++ tx.addSchemaStaticMember(&members, classDeclaration, "fields", true) ++ tx.addSchemaStaticMember(&members, classDeclaration, "mapFields", false) ++ tx.addSchemaStaticMember(&members, classDeclaration, "to", true) ++ tx.addSchemaStaticMember(&members, classDeclaration, "from", true) ++ tx.addSchemaStaticMember(&members, classDeclaration, "copy", true) ++ return members ++} ++ ++func (tx *DeclarationTransformer) addSchemaStaticMember(members *[]*ast.Node, classDeclaration *ast.Node, name string, readonly bool) { ++ typeNode := tx.resolver.CreateTypeOfClassStaticProperty(tx.EmitContext(), classDeclaration, name, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker) ++ if typeNode == nil || typeNode.Kind == ast.KindAnyKeyword { ++ return ++ } ++ var modifiers *ast.ModifierList ++ if readonly { ++ modifiers = tx.Factory().NewModifierList([]*ast.Node{tx.Factory().NewModifier(ast.KindReadonlyKeyword)}) ++ } ++ *members = append(*members, tx.Factory().NewPropertySignatureDeclaration(modifiers, tx.Factory().NewIdentifier(name), nil, typeNode, nil)) ++} ++ + func (tx *DeclarationTransformer) ensureModifiers(node *ast.Node) *ast.ModifierList { + currentFlags := ast.GetCombinedModifierFlags(tx.EmitContext().ParseNode(node)) & ast.ModifierFlagsAll + newFlags := tx.ensureModifierFlags(node) diff --git a/_patches/030-checker-emitresolver-effect-schema.patch b/_patches/030-checker-emitresolver-effect-schema.patch new file mode 100644 index 00000000..8897f28e --- /dev/null +++ b/_patches/030-checker-emitresolver-effect-schema.patch @@ -0,0 +1,242 @@ +diff --git a/internal/checker/emitresolver.go b/internal/checker/emitresolver.go +index 7bf3719e5..f838be954 100644 +--- a/internal/checker/emitresolver.go ++++ b/internal/checker/emitresolver.go +@@ -12,6 +12,7 @@ import ( + "github.com/microsoft/typescript-go/internal/jsnum" + "github.com/microsoft/typescript-go/internal/nodebuilder" + "github.com/microsoft/typescript-go/internal/printer" ++ "github.com/microsoft/typescript-go/internal/scanner" + ) + + var _ printer.EmitResolver = (*EmitResolver)(nil) +@@ -1043,6 +1044,229 @@ func (r *EmitResolver) CreateTypeOfExpression(emitContext *printer.EmitContext, + return requestNodeBuilder.SerializeTypeForExpression(expression, enclosingDeclaration, flags|nodebuilder.FlagsMultilineObjectLiterals, internalFlags, tracker) + } + ++func (r *EmitResolver) CreateTypeOfTypeNode(emitContext *printer.EmitContext, typeNode *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node { ++ typeNode = emitContext.ParseNode(typeNode) ++ if typeNode == nil { ++ return emitContext.Factory.NewKeywordTypeNode(ast.KindAnyKeyword) ++ } ++ ++ r.checkerMu.Lock() ++ defer r.checkerMu.Unlock() ++ requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) ++ return requestNodeBuilder.TypeToTypeNode(r.checker.getTypeFromTypeNode(typeNode), enclosingDeclaration, flags|nodebuilder.FlagsMultilineObjectLiterals, internalFlags, tracker) ++} ++ ++func (r *EmitResolver) CreateTypeLiteralOfTypeNode(emitContext *printer.EmitContext, typeNode *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node { ++ typeNode = emitContext.ParseNode(typeNode) ++ if typeNode == nil { ++ return nil ++ } ++ ++ r.checkerMu.Lock() ++ defer r.checkerMu.Unlock() ++ return r.createTypeLiteralOfType(emitContext, r.checker.getTypeFromTypeNode(typeNode), enclosingDeclaration, flags, internalFlags, tracker) ++} ++ ++func (r *EmitResolver) CreateTypeLiteralOfClassDeclaration(emitContext *printer.EmitContext, declaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node { ++ declaration = emitContext.ParseNode(declaration) ++ if declaration == nil { ++ return nil ++ } ++ ++ r.checkerMu.Lock() ++ defer r.checkerMu.Unlock() ++ if schemaType := r.getTypeOfClassSchemaProperty(declaration, "Type"); schemaType != nil { ++ return r.createTypeLiteralOfType(emitContext, schemaType, enclosingDeclaration, flags, internalFlags, tracker) ++ } ++ symbol := r.checker.getSymbolOfDeclaration(declaration) ++ if symbol == nil { ++ return nil ++ } ++ return r.createTypeLiteralOfType(emitContext, r.checker.getDeclaredTypeOfSymbol(symbol), enclosingDeclaration, flags, internalFlags, tracker) ++} ++ ++func (r *EmitResolver) CreateMakeTypeOfClassDeclaration(emitContext *printer.EmitContext, declaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node { ++ declaration = emitContext.ParseNode(declaration) ++ if declaration == nil { ++ return nil ++ } ++ ++ r.checkerMu.Lock() ++ defer r.checkerMu.Unlock() ++ makeType := r.getTypeOfClassSchemaProperty(declaration, "~type.make.in") ++ typeType := r.getTypeOfClassSchemaProperty(declaration, "Type") ++ if makeType == nil || typeType == nil { ++ return nil ++ } ++ return r.createMakeTypeOfTypes(emitContext, makeType, typeType, enclosingDeclaration, flags, internalFlags, tracker) ++} ++ ++func (r *EmitResolver) CreateTypeOfClassStaticProperty(emitContext *printer.EmitContext, declaration *ast.Node, propertyName string, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node { ++ declaration = emitContext.ParseNode(declaration) ++ if declaration == nil { ++ return nil ++ } ++ ++ r.checkerMu.Lock() ++ defer r.checkerMu.Unlock() ++ propertyType := r.getTypeOfClassSchemaProperty(declaration, propertyName) ++ if propertyType == nil { ++ propertyType = r.getTypeOfClassStaticProperty(declaration, propertyName) ++ } ++ if propertyType == nil { ++ return nil ++ } ++ requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) ++ return requestNodeBuilder.TypeToTypeNode(propertyType, enclosingDeclaration, flags|nodebuilder.FlagsMultilineObjectLiterals|nodebuilder.FlagsUseFullyQualifiedType, internalFlags, tracker) ++} ++ ++func (r *EmitResolver) getTypeOfClassSchemaProperty(declaration *ast.Node, propertyName string) *Type { ++ schemaExpression := getClassSchemaExpression(declaration) ++ if schemaExpression == nil { ++ return nil ++ } ++ return r.getTypeOfSchemaExpressionProperty(schemaExpression, propertyName) ++} ++ ++func (r *EmitResolver) getTypeOfSchemaExpressionProperty(schemaExpression *ast.Node, propertyName string) *Type { ++ schemaType := r.checker.getTypeOfExpression(schemaExpression) ++ property := r.checker.getPropertyOfType(schemaType, propertyName) ++ if property == nil { ++ return nil ++ } ++ return r.checker.GetTypeOfSymbolAtLocation(property, schemaExpression) ++} ++ ++// Like CreateTypeOfClassStaticProperty, but for a `const X = S.Struct(...)` schema value: ++// reads propertyName (Encoded / Type / ~type.make.in / DecodingServices / ...) off the type ++// of the const's initializer and serializes the resolved type. Serializing the resolved type ++// keeps `never` as `never` and never synthesizes references that could fail to resolve. ++func (r *EmitResolver) CreateTypeOfStructSchemaProperty(emitContext *printer.EmitContext, declaration *ast.Node, propertyName string, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node { ++ declaration = emitContext.ParseNode(declaration) ++ if declaration == nil || !ast.IsVariableDeclaration(declaration) || declaration.AsVariableDeclaration().Initializer == nil { ++ return nil ++ } ++ r.checkerMu.Lock() ++ defer r.checkerMu.Unlock() ++ propertyType := r.getTypeOfSchemaExpressionProperty(declaration.AsVariableDeclaration().Initializer, propertyName) ++ if propertyType == nil { ++ return nil ++ } ++ structNodeBuilder := NewNodeBuilder(r.checker, emitContext) ++ return structNodeBuilder.TypeToTypeNode(propertyType, enclosingDeclaration, flags|nodebuilder.FlagsMultilineObjectLiterals|nodebuilder.FlagsUseFullyQualifiedType, internalFlags, tracker) ++} ++ ++func getClassSchemaExpression(declaration *ast.Node) *ast.Node { ++ if declaration == nil || !ast.IsClassDeclaration(declaration) || declaration.AsClassDeclaration().HeritageClauses == nil || len(declaration.AsClassDeclaration().HeritageClauses.Nodes) == 0 { ++ return nil ++ } ++ heritageClause := declaration.AsClassDeclaration().HeritageClauses.Nodes[0] ++ if heritageClause == nil || len(heritageClause.AsHeritageClause().Types.Nodes) == 0 { ++ return nil ++ } ++ expression := heritageClause.AsHeritageClause().Types.Nodes[0].AsExpressionWithTypeArguments().Expression ++ if expression == nil || !ast.IsCallExpression(expression) || expression.AsCallExpression().Arguments == nil || len(expression.AsCallExpression().Arguments.Nodes) == 0 { ++ return nil ++ } ++ return expression.AsCallExpression().Arguments.Nodes[0] ++} ++ ++func (r *EmitResolver) getTypeOfClassStaticProperty(declaration *ast.Node, propertyName string) *Type { ++ symbol := r.checker.getSymbolOfDeclaration(declaration) ++ if symbol == nil { ++ return nil ++ } ++ staticType := r.checker.getTypeOfSymbol(symbol) ++ property := r.checker.getPropertyOfType(staticType, propertyName) ++ if property == nil { ++ return nil ++ } ++ return r.checker.GetTypeOfSymbolAtLocation(property, declaration) ++} ++ ++func (r *EmitResolver) createTypeLiteralOfType(emitContext *printer.EmitContext, typ *Type, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node { ++ requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) ++ members := core.Map(r.checker.getPropertiesOfType(typ), func(property *ast.Symbol) *ast.Node { ++ propertyTypeNode := requestNodeBuilder.TypeToTypeNode(r.checker.getTypeOfSymbol(property), enclosingDeclaration, flags|nodebuilder.FlagsMultilineObjectLiterals|nodebuilder.FlagsUseFullyQualifiedType, internalFlags, tracker) ++ if propertyTypeNode == nil { ++ propertyTypeNode = emitContext.Factory.NewKeywordTypeNode(ast.KindAnyKeyword) ++ } ++ var optionalToken *ast.Node ++ if property.Flags&ast.SymbolFlagsOptional != 0 { ++ optionalToken = emitContext.Factory.NewToken(ast.KindQuestionToken) ++ } ++ return emitContext.Factory.NewPropertySignatureDeclaration( ++ emitContext.Factory.NewModifierList([]*ast.Node{emitContext.Factory.NewModifier(ast.KindReadonlyKeyword)}), ++ createPropertyName(emitContext, property.Name), ++ optionalToken, ++ propertyTypeNode, ++ nil, ++ ) ++ }) ++ return emitContext.Factory.NewTypeLiteralNode(emitContext.Factory.NewNodeList(members)) ++} ++ ++func (r *EmitResolver) createMakeTypeOfTypes(emitContext *printer.EmitContext, makeType *Type, typeType *Type, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node { ++ isVoidish := func(typ *Type) bool { return typ.flags&(TypeFlagsVoid|TypeFlagsUndefined) != 0 } ++ var makeTypes []*Type ++ if makeType.flags&TypeFlagsUnion != 0 { ++ makeTypes = makeType.AsUnionType().types ++ } ++ hasVoid := core.Some(makeTypes, isVoidish) ++ objectMakeType := makeType ++ if makeTypes != nil { ++ for _, typ := range makeTypes { ++ if len(r.checker.getPropertiesOfType(typ)) > 0 { ++ objectMakeType = typ ++ break ++ } ++ } ++ } ++ makeProperties := r.checker.getPropertiesOfType(objectMakeType) ++ if len(makeProperties) == 0 { ++ requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) ++ return requestNodeBuilder.TypeToTypeNode(makeType, enclosingDeclaration, flags|nodebuilder.FlagsMultilineObjectLiterals|nodebuilder.FlagsUseFullyQualifiedType, internalFlags, tracker) ++ } ++ typeProperties := make(map[string]*ast.Symbol) ++ for _, property := range r.checker.getPropertiesOfType(typeType) { ++ typeProperties[property.Name] = property ++ } ++ requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) ++ members := core.Map(makeProperties, func(property *ast.Symbol) *ast.Node { ++ source := property ++ if typeProperty := typeProperties[property.Name]; typeProperty != nil { ++ source = typeProperty ++ } ++ propertyTypeNode := requestNodeBuilder.TypeToTypeNode(r.checker.getTypeOfSymbol(source), enclosingDeclaration, flags|nodebuilder.FlagsMultilineObjectLiterals|nodebuilder.FlagsUseFullyQualifiedType, internalFlags, tracker) ++ if propertyTypeNode == nil { ++ propertyTypeNode = emitContext.Factory.NewKeywordTypeNode(ast.KindAnyKeyword) ++ } ++ var optionalToken *ast.Node ++ if property.Flags&ast.SymbolFlagsOptional != 0 { ++ optionalToken = emitContext.Factory.NewToken(ast.KindQuestionToken) ++ } ++ return emitContext.Factory.NewPropertySignatureDeclaration( ++ emitContext.Factory.NewModifierList([]*ast.Node{emitContext.Factory.NewModifier(ast.KindReadonlyKeyword)}), ++ createPropertyName(emitContext, property.Name), ++ optionalToken, ++ propertyTypeNode, ++ nil, ++ ) ++ }) ++ literal := emitContext.Factory.NewTypeLiteralNode(emitContext.Factory.NewNodeList(members)) ++ if hasVoid { ++ return emitContext.Factory.NewUnionTypeNode(emitContext.Factory.NewNodeList([]*ast.Node{literal, emitContext.Factory.NewKeywordTypeNode(ast.KindVoidKeyword)})) ++ } ++ return literal ++} ++ ++func createPropertyName(emitContext *printer.EmitContext, name string) *ast.Node { ++ if scanner.IsIdentifierText(name, core.LanguageVariantStandard) { ++ return emitContext.Factory.NewIdentifier(name) ++ } ++ return emitContext.Factory.NewStringLiteral(name, ast.TokenFlagsNone) ++} ++ + func (r *EmitResolver) CreateLateBoundIndexSignatures(emitContext *printer.EmitContext, container *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) []*ast.Node { + container = emitContext.ParseNode(container) + r.checkerMu.Lock() diff --git a/_patches/031-printer-emitresolver-effect-schema.patch b/_patches/031-printer-emitresolver-effect-schema.patch new file mode 100644 index 00000000..3dfb2465 --- /dev/null +++ b/_patches/031-printer-emitresolver-effect-schema.patch @@ -0,0 +1,17 @@ +diff --git a/internal/printer/emitresolver.go b/internal/printer/emitresolver.go +index 189fe88cc..5865ccdf0 100644 +--- a/internal/printer/emitresolver.go ++++ b/internal/printer/emitresolver.go +@@ -123,6 +123,12 @@ type EmitResolver interface { + CreateTypeParametersOfSignatureDeclaration(emitContext *EmitContext, signatureDeclaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) []*ast.Node + CreateLiteralConstValue(emitContext *EmitContext, node *ast.Node, tracker nodebuilder.SymbolTracker) *ast.Node + CreateTypeOfExpression(emitContext *EmitContext, expression *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node ++ CreateTypeOfTypeNode(emitContext *EmitContext, typeNode *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node ++ CreateTypeLiteralOfTypeNode(emitContext *EmitContext, typeNode *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node ++ CreateTypeLiteralOfClassDeclaration(emitContext *EmitContext, declaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node ++ CreateMakeTypeOfClassDeclaration(emitContext *EmitContext, declaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node ++ CreateTypeOfClassStaticProperty(emitContext *EmitContext, declaration *ast.Node, propertyName string, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node ++ CreateTypeOfStructSchemaProperty(emitContext *EmitContext, declaration *ast.Node, propertyName string, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node + CreateLateBoundIndexSignatures(emitContext *EmitContext, container *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) []*ast.Node + TryJSTypeNodeToTypeNode(emitContext *EmitContext, typeNode *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node + } From d2933ca2349d01bb0c8ee7949a37218cd3287de2 Mon Sep 17 00:00:00 2001 From: Patrick Roza Date: Mon, 22 Jun 2026 08:24:07 +0200 Subject: [PATCH 2/9] =?UTF-8?q?=5Fpatches:=20refresh=20=E2=80=94=20carry?= =?UTF-8?q?=20S.Class=20'identifier'=20static=20on=20facade=20base?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- _patches/029-transformers-declarations-effect-schema.patch | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/_patches/029-transformers-declarations-effect-schema.patch b/_patches/029-transformers-declarations-effect-schema.patch index c3758a68..739d19e6 100644 --- a/_patches/029-transformers-declarations-effect-schema.patch +++ b/_patches/029-transformers-declarations-effect-schema.patch @@ -1,5 +1,5 @@ diff --git a/internal/transformers/declarations/transform.go b/internal/transformers/declarations/transform.go -index b6596e0d7..5cfe49b07 100644 +index b6596e0d7..41cf2fc86 100644 --- a/internal/transformers/declarations/transform.go +++ b/internal/transformers/declarations/transform.go @@ -351,6 +351,7 @@ func (tx *DeclarationTransformer) transformSourceFile(node *ast.SourceFile) *ast @@ -10,7 +10,7 @@ index b6596e0d7..5cfe49b07 100644 combinedStatements.Loc = statements.Loc // setTextRange if ast.IsExternalOrCommonJSModule(node) { if ast.IsInJSFile(node.AsNode()) { -@@ -2277,6 +2278,907 @@ func (tx *DeclarationTransformer) transformEnumDeclaration(input *ast.EnumDeclar +@@ -2277,6 +2278,910 @@ func (tx *DeclarationTransformer) transformEnumDeclaration(input *ast.EnumDeclar ) } @@ -895,6 +895,9 @@ index b6596e0d7..5cfe49b07 100644 + +func (tx *DeclarationTransformer) createEffectSchemaStaticMembers(classDeclaration *ast.Node) []*ast.Node { + members := []*ast.Node{} ++ // `identifier` is an `S.Class` static (the facade base is `S.Bottom`, which lacks it); ++ // carry it so the faceted class type stays equal to the stock `EnhancedClass` one. ++ tx.addSchemaStaticMember(&members, classDeclaration, "identifier", true) + tx.addSchemaStaticMember(&members, classDeclaration, "fields", true) + tx.addSchemaStaticMember(&members, classDeclaration, "mapFields", false) + tx.addSchemaStaticMember(&members, classDeclaration, "to", true) From 4480bba48ca54ea89c2c29e0e7fba51f893dd2ef Mon Sep 17 00:00:00 2001 From: Patrick Roza Date: Mon, 22 Jun 2026 08:35:02 +0200 Subject: [PATCH 3/9] =?UTF-8?q?=5Fpatches:=20refresh=20=E2=80=94=20identif?= =?UTF-8?q?ier=20moved=20to=20effect-app=20facade=20(dropped=20from=20emit?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../029-transformers-declarations-effect-schema.patch | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/_patches/029-transformers-declarations-effect-schema.patch b/_patches/029-transformers-declarations-effect-schema.patch index 739d19e6..86bebd66 100644 --- a/_patches/029-transformers-declarations-effect-schema.patch +++ b/_patches/029-transformers-declarations-effect-schema.patch @@ -1,5 +1,5 @@ diff --git a/internal/transformers/declarations/transform.go b/internal/transformers/declarations/transform.go -index b6596e0d7..41cf2fc86 100644 +index b6596e0d7..a7cc40133 100644 --- a/internal/transformers/declarations/transform.go +++ b/internal/transformers/declarations/transform.go @@ -351,6 +351,7 @@ func (tx *DeclarationTransformer) transformSourceFile(node *ast.SourceFile) *ast @@ -895,9 +895,9 @@ index b6596e0d7..41cf2fc86 100644 + +func (tx *DeclarationTransformer) createEffectSchemaStaticMembers(classDeclaration *ast.Node) []*ast.Node { + members := []*ast.Node{} -+ // `identifier` is an `S.Class` static (the facade base is `S.Bottom`, which lacks it); -+ // carry it so the faceted class type stays equal to the stock `EnhancedClass` one. -+ tx.addSchemaStaticMember(&members, classDeclaration, "identifier", true) ++ // NOTE: `identifier` (generic `string`) is intentionally NOT emitted here — it lives on the ++ // facade interfaces (OpaqueFacade/OpaqueClassFacade/OpaqueErrorFacadeClass) in effect-app. ++ // Only per-model, precisely-typed statics belong here. + tx.addSchemaStaticMember(&members, classDeclaration, "fields", true) + tx.addSchemaStaticMember(&members, classDeclaration, "mapFields", false) + tx.addSchemaStaticMember(&members, classDeclaration, "to", true) From 3617faa079ff004359388c6c99e5d17b93d2d5ac Mon Sep 17 00:00:00 2001 From: Patrick Roza Date: Mon, 22 Jun 2026 09:03:11 +0200 Subject: [PATCH 4/9] =?UTF-8?q?=5Fpatches:=20refresh=20=E2=80=94=20emit=20?= =?UTF-8?q?S.StructFacade=20from=20effect-app?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- ...ansformers-declarations-effect-schema.patch | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/_patches/029-transformers-declarations-effect-schema.patch b/_patches/029-transformers-declarations-effect-schema.patch index 86bebd66..5fff18b1 100644 --- a/_patches/029-transformers-declarations-effect-schema.patch +++ b/_patches/029-transformers-declarations-effect-schema.patch @@ -1,5 +1,5 @@ diff --git a/internal/transformers/declarations/transform.go b/internal/transformers/declarations/transform.go -index b6596e0d7..a7cc40133 100644 +index b6596e0d7..24e8ea282 100644 --- a/internal/transformers/declarations/transform.go +++ b/internal/transformers/declarations/transform.go @@ -351,6 +351,7 @@ func (tx *DeclarationTransformer) transformSourceFile(node *ast.SourceFile) *ast @@ -10,7 +10,7 @@ index b6596e0d7..a7cc40133 100644 combinedStatements.Loc = statements.Loc // setTextRange if ast.IsExternalOrCommonJSModule(node) { if ast.IsInJSFile(node.AsNode()) { -@@ -2277,6 +2278,910 @@ func (tx *DeclarationTransformer) transformEnumDeclaration(input *ast.EnumDeclar +@@ -2277,6 +2278,912 @@ func (tx *DeclarationTransformer) transformEnumDeclaration(input *ast.EnumDeclar ) } @@ -347,10 +347,10 @@ index b6596e0d7..a7cc40133 100644 + ) +} + -+// `import("#lib/StructFacade").StructFacade` — a self-contained import type resolved cross-package via -+// the api package's `#lib/*` subpath import. The scanner-local facade extends -+// `S.Struct`, so the value stays Workflow-compatible. ++// `S.StructFacade` — ++// `StructFacade` is exported from effect-app (>= 4.0.0-beta.279), so it resolves through the ++// file's own `S` (effect-app/Schema) import, exactly like `S.OpaqueFacade`. It extends ++// `S.Struct`, so the faceted value stays Workflow-compatible. +func (tx *DeclarationTransformer) createEffectSchemaStructFacadeType(modelName string) *ast.Node { + member := func(name string) *ast.Node { + return tx.Factory().NewTypeReferenceNode(tx.Factory().NewQualifiedName(tx.Factory().NewIdentifier(modelName), tx.Factory().NewIdentifier(name)), nil) @@ -363,8 +363,10 @@ index b6596e0d7..a7cc40133 100644 + member("EncodingServices"), + member("Fields"), + }) -+ argument := tx.Factory().NewLiteralTypeNode(tx.Factory().NewStringLiteral("#lib/StructFacade", ast.TokenFlagsNone)) -+ return tx.Factory().NewImportTypeNode(false, argument, nil, tx.Factory().NewIdentifier("StructFacade"), typeArguments) ++ return tx.Factory().NewTypeReferenceNode( ++ tx.Factory().NewQualifiedName(tx.Factory().NewIdentifier("S"), tx.Factory().NewIdentifier("StructFacade")), ++ typeArguments, ++ ) +} + +func (tx *DeclarationTransformer) createEffectSchemaStructDeclarations(statement *ast.Node, modelName string) []*ast.Node { From b3c2f3ac1dd3298fae5508278e7d630ba2c7ae1b Mon Sep 17 00:00:00 2001 From: Patrick Roza Date: Mon, 22 Jun 2026 15:12:25 +0200 Subject: [PATCH 5/9] _patches: regenerate facade emit at full parity + version 7.0.0-dev+effect-app.5 Regenerate the .d.ts schema-facade patches verbatim from the canonical Go implementation in effect-app/typescript-go PR #2 (head a9dd01c, base dc37b524 == this submodule pin), restoring four improvements the earlier partial port dropped: - generated import normalization (prefer namespace imports over import("...")) - legacy `Encoded extends S.StructNestedEncoded` materialization - static-member rewriting (fields -> X.Fields, mapFields/copy signatures) - `Fields` interface in the generated namespace 029 transform.go +920 -> +1148; 030/031 unchanged in substance. Verified against scanner api decl build: exit 0, 0 StructNestedEncoded placeholders, 415 `Fields` interfaces emitted, Effect language service diagnostics live. Set the binary version suffix to semver build metadata on the upstream 7.x base: core.SetVersionSuffix("+effect-app.5") -> `tsgo --version` = 7.0.0-dev+effect-app.5 (replaces the old 0.14.x-effect-app.N scheme). Bump the counter per gz release. Source: https://github.com/effect-app/typescript-go/pull/2 Co-Authored-By: Claude Opus 4.8 (1M context) --- ...nsformers-declarations-effect-schema.patch | 284 ++++++++++++++++-- ...0-checker-emitresolver-effect-schema.patch | 19 +- ...1-printer-emitresolver-effect-schema.patch | 5 +- etscheckerhooks/init.go | 6 +- 4 files changed, 286 insertions(+), 28 deletions(-) diff --git a/_patches/029-transformers-declarations-effect-schema.patch b/_patches/029-transformers-declarations-effect-schema.patch index 5fff18b1..68cc876f 100644 --- a/_patches/029-transformers-declarations-effect-schema.patch +++ b/_patches/029-transformers-declarations-effect-schema.patch @@ -1,5 +1,5 @@ diff --git a/internal/transformers/declarations/transform.go b/internal/transformers/declarations/transform.go -index b6596e0d7..24e8ea282 100644 +index b6596e0d7..5bec4b75b 100644 --- a/internal/transformers/declarations/transform.go +++ b/internal/transformers/declarations/transform.go @@ -351,6 +351,7 @@ func (tx *DeclarationTransformer) transformSourceFile(node *ast.SourceFile) *ast @@ -10,7 +10,7 @@ index b6596e0d7..24e8ea282 100644 combinedStatements.Loc = statements.Loc // setTextRange if ast.IsExternalOrCommonJSModule(node) { if ast.IsInJSFile(node.AsNode()) { -@@ -2277,6 +2278,912 @@ func (tx *DeclarationTransformer) transformEnumDeclaration(input *ast.EnumDeclar +@@ -2277,6 +2278,1152 @@ func (tx *DeclarationTransformer) transformEnumDeclaration(input *ast.EnumDeclar ) } @@ -279,7 +279,7 @@ index b6596e0d7..24e8ea282 100644 + if declaration == nil { + return nil + } -+ return tx.resolver.CreateTypeOfStructSchemaProperty(tx.EmitContext(), declaration, propertyName, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker) ++ return tx.normalizeGeneratedImportedTypes(tx.resolver.CreateTypeOfStructSchemaProperty(tx.EmitContext(), declaration, propertyName, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker)) +} + +func (tx *DeclarationTransformer) canCreateEffectSchemaGeneratedStructNamespace(modelName string) bool { @@ -394,6 +394,94 @@ index b6596e0d7..24e8ea282 100644 + return []*ast.Node{retypedConst, typeInterface, namespace} +} + ++type generatedTypeImport struct { ++ importedName string ++ moduleSpecifier string ++} ++ ++func (tx *DeclarationTransformer) generatedTypeNamedImports() map[string]generatedTypeImport { ++ imports := map[string]generatedTypeImport{} ++ for _, statement := range tx.state.currentSourceFile.Statements.Nodes { ++ if !ast.IsImportDeclaration(statement) { ++ continue ++ } ++ decl := statement.AsImportDeclaration() ++ if decl.ModuleSpecifier == nil || !ast.IsStringLiteralLike(decl.ModuleSpecifier) || decl.ImportClause == nil || decl.ImportClause.AsImportClause().NamedBindings == nil { ++ continue ++ } ++ moduleSpecifier := decl.ModuleSpecifier.Text() ++ namedBindings := decl.ImportClause.AsImportClause().NamedBindings ++ if !ast.IsNamedImports(namedBindings) { ++ continue ++ } ++ for _, specifier := range namedBindings.AsNamedImports().Elements.Nodes { ++ importedName := specifier.Name().Text() ++ if specifier.AsImportSpecifier().PropertyName != nil { ++ importedName = specifier.AsImportSpecifier().PropertyName.Text() ++ } ++ imports[specifier.Name().Text()] = generatedTypeImport{importedName: importedName, moduleSpecifier: moduleSpecifier} ++ } ++ } ++ return imports ++} ++ ++func (tx *DeclarationTransformer) generatedTypeNamespaceImports() map[string]string { ++ imports := map[string]string{} ++ for _, statement := range tx.state.currentSourceFile.Statements.Nodes { ++ if !ast.IsImportDeclaration(statement) { ++ continue ++ } ++ decl := statement.AsImportDeclaration() ++ if decl.ModuleSpecifier == nil || !ast.IsStringLiteralLike(decl.ModuleSpecifier) || decl.ImportClause == nil || decl.ImportClause.AsImportClause().NamedBindings == nil { ++ continue ++ } ++ namedBindings := decl.ImportClause.AsImportClause().NamedBindings ++ if ast.IsNamespaceImport(namedBindings) { ++ imports[decl.ModuleSpecifier.Text()] = namedBindings.Name().Text() ++ } ++ } ++ return imports ++} ++ ++func (tx *DeclarationTransformer) normalizeGeneratedImportedTypes(typeNode *ast.Node) *ast.Node { ++ if typeNode == nil { ++ return nil ++ } ++ namedImports := tx.generatedTypeNamedImports() ++ if len(namedImports) == 0 { ++ return typeNode ++ } ++ namespaceImports := tx.generatedTypeNamespaceImports() ++ var visitor *ast.NodeVisitor ++ visitor = tx.EmitContext().NewNodeVisitor(func(node *ast.Node) *ast.Node { ++ if node != nil && node.Kind == ast.KindTypeReference { ++ typeReference := node.AsTypeReferenceNode() ++ if typeReference.TypeName != nil && typeReference.TypeName.Kind == ast.KindIdentifier { ++ imported := namedImports[typeReference.TypeName.Text()] ++ if imported.importedName == "" || imported.moduleSpecifier == "" { ++ return visitor.VisitEachChild(node) ++ } ++ typeArguments := visitor.VisitNodes(typeReference.TypeArguments) ++ if namespaceName := namespaceImports[imported.moduleSpecifier]; namespaceName != "" { ++ return tx.Factory().NewTypeReferenceNode( ++ tx.Factory().NewQualifiedName(tx.Factory().NewIdentifier(namespaceName), tx.Factory().NewIdentifier(imported.importedName)), ++ typeArguments, ++ ) ++ } ++ return tx.Factory().NewImportTypeNode( ++ false, ++ tx.Factory().NewLiteralTypeNode(tx.Factory().NewStringLiteral(imported.moduleSpecifier, ast.TokenFlagsNone)), ++ nil, ++ tx.Factory().NewIdentifier(imported.importedName), ++ typeArguments, ++ ) ++ } ++ } ++ return visitor.VisitEachChild(node) ++ }) ++ return visitor.VisitNode(typeNode) ++} ++ +func isEffectSchemaStructCompanionTypeAlias(statement *ast.Node, structModelNames map[string]bool) bool { + return ast.IsTypeAliasDeclaration(statement) && statement.Name() != nil && structModelNames[statement.Name().Text()] +} @@ -513,6 +601,28 @@ index b6596e0d7..24e8ea282 100644 + typeArguments.Nodes[0].Kind == ast.KindTypeQuery +} + ++func isEffectSchemaStructNestedEncodedInterfaceForModel(statement *ast.Node, modelName string) bool { ++ if !isEffectSchemaStructNestedEncodedInterface(statement) { ++ return false ++ } ++ typeQuery := statement.AsInterfaceDeclaration().HeritageClauses.Nodes[0].AsHeritageClause().Types.Nodes[0].AsExpressionWithTypeArguments().TypeArguments.Nodes[0] ++ exprName := typeQuery.AsTypeQueryNode().ExprName ++ return exprName != nil && ast.IsIdentifier(exprName) && exprName.Text() == modelName ++} ++ ++func (tx *DeclarationTransformer) materializeSchemaNestedEncoded(encoded *ast.Node) *ast.Node { ++ heritageType := encoded.AsInterfaceDeclaration().HeritageClauses.Nodes[0].AsHeritageClause().Types.Nodes[0] ++ return tx.resolver.CreateTypeLiteralOfTypeNode(tx.EmitContext(), heritageType, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker) ++} ++ ++func (tx *DeclarationTransformer) createEffectSchemaMaterializedEncodedDeclaration(encoded *ast.Node, classDeclaration *ast.Node) *ast.Node { ++ encodedType := tx.materializeSchemaNestedEncoded(encoded) ++ if encodedType == nil || !ast.IsTypeLiteralNode(encodedType) { ++ return nil ++ } ++ return tx.Factory().NewInterfaceDeclaration(encoded.Modifiers(), encoded.Name(), encoded.AsInterfaceDeclaration().TypeParameters, nil, tx.Factory().NewNodeList(encodedType.AsTypeLiteralNode().Members.Nodes)) ++} ++ +func isEffectSchemaEncodedInterface(statement *ast.Node) bool { + return ast.IsInterfaceDeclaration(statement) && statement.Name() != nil && statement.Name().Text() == "Encoded" +} @@ -544,7 +654,7 @@ index b6596e0d7..24e8ea282 100644 + if classDeclaration == nil || classDeclaration.Name() == nil { + return nil + } -+ literal := tx.resolver.CreateTypeLiteralOfClassDeclaration(tx.EmitContext(), classDeclaration, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker) ++ literal := tx.normalizeGeneratedImportedTypes(tx.resolver.CreateTypeLiteralOfClassDeclaration(tx.EmitContext(), classDeclaration, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker)) + if literal == nil || !ast.IsTypeLiteralNode(literal) { + return nil + } @@ -562,14 +672,27 @@ index b6596e0d7..24e8ea282 100644 + if body == nil || body.Kind != ast.KindModuleBlock { + return nil + } ++ replacedEncoded := false + existing := map[string]bool{} + kept := make([]*ast.Node, 0, len(body.AsModuleBlock().Statements.Nodes)) + for _, statement := range body.AsModuleBlock().Statements.Nodes { -+ if (ast.IsInterfaceDeclaration(statement) || ast.IsTypeAliasDeclaration(statement)) && statement.Name() != nil && statement.Name().Text() != "Encoded" { ++ if (ast.IsInterfaceDeclaration(statement) || ast.IsTypeAliasDeclaration(statement)) && statement.Name() != nil { + name := statement.Name().Text() -+ existing[name] = true -+ if name == "Make" || name == "DecodingServices" || name == "EncodingServices" { -+ continue ++ if name == "Encoded" { ++ var encodedDeclaration *ast.Node ++ if isEffectSchemaStructNestedEncodedInterfaceForModel(statement, moduleDeclarationIdentifierName(namespace)) { ++ encodedDeclaration = tx.createEffectSchemaMaterializedEncodedDeclaration(statement, classDeclaration) ++ } ++ if encodedDeclaration != nil { ++ kept = append(kept, encodedDeclaration) ++ replacedEncoded = true ++ continue ++ } ++ } else { ++ existing[name] = true ++ if name == "Make" || name == "DecodingServices" || name == "EncodingServices" { ++ continue ++ } + } + } + kept = append(kept, statement) @@ -585,7 +708,12 @@ index b6596e0d7..24e8ea282 100644 + if encodingServices := tx.createEffectSchemaServiceDeclaration(classDeclaration, "EncodingServices"); encodingServices != nil { + additions = append(additions, encodingServices) + } -+ if len(additions) == 0 && len(existing) == 0 { ++ if !existing["Fields"] { ++ if fieldsDeclaration := tx.createEffectSchemaFieldsDeclaration(classDeclaration); fieldsDeclaration != nil { ++ additions = append([]*ast.Node{fieldsDeclaration}, additions...) ++ } ++ } ++ if !replacedEncoded && len(additions) == 0 && len(existing) == 0 { + return nil + } + @@ -600,6 +728,9 @@ index b6596e0d7..24e8ea282 100644 + return nil + } + statements := []*ast.Node{encoded} ++ if fieldsDeclaration := tx.createEffectSchemaFieldsDeclaration(classDeclaration); fieldsDeclaration != nil { ++ statements = append(statements, fieldsDeclaration) ++ } + if makeDeclaration := tx.createEffectSchemaMakeDeclaration(classDeclaration); makeDeclaration != nil { + statements = append(statements, makeDeclaration) + } @@ -631,8 +762,19 @@ index b6596e0d7..24e8ea282 100644 + return tx.Factory().NewModifierList(modifiers) +} + ++func (tx *DeclarationTransformer) createEffectSchemaFieldsDeclaration(classDeclaration *ast.Node) *ast.Node { ++ fieldsType := tx.normalizeGeneratedImportedTypes(tx.resolver.CreateTypeOfClassStaticProperty(tx.EmitContext(), classDeclaration, "fields", tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker)) ++ if fieldsType == nil || fieldsType.Kind == ast.KindAnyKeyword { ++ return nil ++ } ++ if ast.IsTypeLiteralNode(fieldsType) { ++ return tx.Factory().NewInterfaceDeclaration(nil, tx.Factory().NewIdentifier("Fields"), nil, nil, tx.Factory().NewNodeList(fieldsType.AsTypeLiteralNode().Members.Nodes)) ++ } ++ return tx.Factory().NewTypeAliasDeclaration(nil, tx.Factory().NewIdentifier("Fields"), nil, fieldsType) ++} ++ +func (tx *DeclarationTransformer) createEffectSchemaEncodedDeclaration(classDeclaration *ast.Node) *ast.Node { -+ encodedType := tx.resolver.CreateTypeOfClassStaticProperty(tx.EmitContext(), classDeclaration, "Encoded", tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker) ++ encodedType := tx.normalizeGeneratedImportedTypes(tx.resolver.CreateTypeOfClassStaticProperty(tx.EmitContext(), classDeclaration, "Encoded", tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker)) + if encodedType == nil { + return nil + } @@ -643,9 +785,9 @@ index b6596e0d7..24e8ea282 100644 +} + +func (tx *DeclarationTransformer) createEffectSchemaMakeDeclaration(classDeclaration *ast.Node) *ast.Node { -+ makeType := tx.resolver.CreateMakeTypeOfClassDeclaration(tx.EmitContext(), classDeclaration, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker) ++ makeType := tx.normalizeGeneratedImportedTypes(tx.resolver.CreateMakeTypeOfClassDeclaration(tx.EmitContext(), classDeclaration, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker)) + if makeType == nil { -+ makeType = tx.resolver.CreateTypeOfClassStaticProperty(tx.EmitContext(), classDeclaration, "~type.make.in", tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker) ++ makeType = tx.normalizeGeneratedImportedTypes(tx.resolver.CreateTypeOfClassStaticProperty(tx.EmitContext(), classDeclaration, "~type.make.in", tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker)) + } + if makeType == nil { + return nil @@ -657,7 +799,7 @@ index b6596e0d7..24e8ea282 100644 +} + +func (tx *DeclarationTransformer) createEffectSchemaServiceDeclaration(classDeclaration *ast.Node, name string) *ast.Node { -+ resolved := tx.resolver.CreateTypeOfClassStaticProperty(tx.EmitContext(), classDeclaration, name, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker) ++ resolved := tx.normalizeGeneratedImportedTypes(tx.resolver.CreateTypeOfClassStaticProperty(tx.EmitContext(), classDeclaration, name, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker)) + if resolved == nil { + return nil + } @@ -850,7 +992,7 @@ index b6596e0d7..24e8ea282 100644 + brandType := tx.getEffectSchemaFacadeBrandType(baseType, facadeName) + return tx.Factory().NewIntersectionTypeNode(tx.Factory().NewNodeList([]*ast.Node{ + tx.createEffectSchemaFacadeTypeReference(modelName, brandType, facadeName), -+ tx.Factory().NewTypeLiteralNode(tx.Factory().NewNodeList(tx.createEffectSchemaStaticMembers(classDeclaration))), ++ tx.Factory().NewTypeLiteralNode(tx.Factory().NewNodeList(tx.createEffectSchemaStaticMembers(classDeclaration, modelName))), + })) +} + @@ -895,21 +1037,116 @@ index b6596e0d7..24e8ea282 100644 + ) +} + -+func (tx *DeclarationTransformer) createEffectSchemaStaticMembers(classDeclaration *ast.Node) []*ast.Node { ++func (tx *DeclarationTransformer) createEffectSchemaStaticMembers(classDeclaration *ast.Node, modelName string) []*ast.Node { + members := []*ast.Node{} ++ hasFields := tx.createEffectSchemaFieldsDeclaration(classDeclaration) != nil + // NOTE: `identifier` (generic `string`) is intentionally NOT emitted here — it lives on the + // facade interfaces (OpaqueFacade/OpaqueClassFacade/OpaqueErrorFacadeClass) in effect-app. + // Only per-model, precisely-typed statics belong here. -+ tx.addSchemaStaticMember(&members, classDeclaration, "fields", true) -+ tx.addSchemaStaticMember(&members, classDeclaration, "mapFields", false) -+ tx.addSchemaStaticMember(&members, classDeclaration, "to", true) -+ tx.addSchemaStaticMember(&members, classDeclaration, "from", true) -+ tx.addSchemaStaticMember(&members, classDeclaration, "copy", true) ++ tx.addSchemaStaticMember(&members, classDeclaration, "fields", true, core.IfElse(hasFields, modelName, "")) ++ tx.addSchemaStaticMember(&members, classDeclaration, "mapFields", false, core.IfElse(hasFields, modelName, "")) ++ tx.addSchemaStaticMember(&members, classDeclaration, "to", true, "") ++ tx.addSchemaStaticMember(&members, classDeclaration, "from", true, "") ++ tx.addSchemaStaticMember(&members, classDeclaration, "copy", true, modelName) + return members +} + -+func (tx *DeclarationTransformer) addSchemaStaticMember(members *[]*ast.Node, classDeclaration *ast.Node, name string, readonly bool) { -+ typeNode := tx.resolver.CreateTypeOfClassStaticProperty(tx.EmitContext(), classDeclaration, name, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker) ++func (tx *DeclarationTransformer) createEffectSchemaNamedMemberReference(modelName string, memberName string) *ast.Node { ++ return tx.Factory().NewTypeReferenceNode(tx.Factory().NewQualifiedName(tx.Factory().NewIdentifier(modelName), tx.Factory().NewIdentifier(memberName)), nil) ++} ++ ++func (tx *DeclarationTransformer) rewriteEffectSchemaStaticMemberType(typeNode *ast.Node, name string, modelName string) *ast.Node { ++ switch name { ++ case "fields": ++ return tx.createEffectSchemaNamedMemberReference(modelName, "Fields") ++ case "mapFields": ++ return tx.rewriteEffectSchemaMapFieldsType(typeNode, modelName) ++ case "copy": ++ return tx.rewriteEffectSchemaCopyType(typeNode, modelName) ++ default: ++ return typeNode ++ } ++} ++ ++func (tx *DeclarationTransformer) rewriteEffectSchemaMapFieldsType(typeNode *ast.Node, modelName string) *ast.Node { ++ if typeNode == nil || typeNode.Kind != ast.KindFunctionType { ++ return typeNode ++ } ++ mapFieldsType := typeNode.AsFunctionTypeNode() ++ if mapFieldsType.Parameters == nil || len(mapFieldsType.Parameters.Nodes) == 0 { ++ return typeNode ++ } ++ callbackParameter := mapFieldsType.Parameters.Nodes[0].AsParameterDeclaration() ++ callbackType := callbackParameter.Type ++ if callbackType == nil || callbackType.Kind != ast.KindFunctionType { ++ return typeNode ++ } ++ callbackFunctionType := callbackType.AsFunctionTypeNode() ++ if callbackFunctionType.Parameters == nil || len(callbackFunctionType.Parameters.Nodes) == 0 { ++ return typeNode ++ } ++ fieldsParameter := callbackFunctionType.Parameters.Nodes[0].AsParameterDeclaration() ++ updatedFieldsParameter := tx.Factory().UpdateParameterDeclaration( ++ fieldsParameter, ++ fieldsParameter.Modifiers(), ++ fieldsParameter.DotDotDotToken, ++ fieldsParameter.Name(), ++ fieldsParameter.QuestionToken, ++ tx.createEffectSchemaNamedMemberReference(modelName, "Fields"), ++ fieldsParameter.Initializer, ++ ) ++ updatedCallbackParameters := append([]*ast.Node{updatedFieldsParameter}, callbackFunctionType.Parameters.Nodes[1:]...) ++ updatedCallbackType := tx.Factory().UpdateFunctionTypeNode( ++ callbackFunctionType, ++ callbackFunctionType.TypeParameters, ++ tx.Factory().NewNodeList(updatedCallbackParameters), ++ callbackFunctionType.Type, ++ ) ++ updatedCallbackParameter := tx.Factory().UpdateParameterDeclaration( ++ callbackParameter, ++ callbackParameter.Modifiers(), ++ callbackParameter.DotDotDotToken, ++ callbackParameter.Name(), ++ callbackParameter.QuestionToken, ++ updatedCallbackType, ++ callbackParameter.Initializer, ++ ) ++ updatedMapFieldsParameters := append([]*ast.Node{updatedCallbackParameter}, mapFieldsType.Parameters.Nodes[1:]...) ++ return tx.Factory().UpdateFunctionTypeNode( ++ mapFieldsType, ++ mapFieldsType.TypeParameters, ++ tx.Factory().NewNodeList(updatedMapFieldsParameters), ++ mapFieldsType.Type, ++ ) ++} ++ ++func (tx *DeclarationTransformer) rewriteEffectSchemaCopyType(typeNode *ast.Node, modelName string) *ast.Node { ++ model := tx.Factory().NewTypeReferenceNode(tx.Factory().NewIdentifier(modelName), nil) ++ if ast.IsImportTypeNode(typeNode) { ++ importType := typeNode.AsImportTypeNode() ++ if importType.Qualifier != nil && rightmostEntityNameText(importType.Qualifier) == "StructuralCopyOrigin" { ++ return tx.Factory().UpdateImportTypeNode(importType, importType.IsTypeOf, importType.Argument, importType.Attributes, importType.Qualifier, tx.Factory().NewNodeList([]*ast.Node{model})) ++ } ++ return typeNode ++ } ++ if ast.IsTypeReferenceNode(typeNode) && rightmostEntityNameText(typeNode.AsTypeReferenceNode().TypeName) == "StructuralCopyOrigin" { ++ return tx.Factory().UpdateTypeReferenceNode(typeNode.AsTypeReferenceNode(), typeNode.AsTypeReferenceNode().TypeName, tx.Factory().NewNodeList([]*ast.Node{model})) ++ } ++ return typeNode ++} ++ ++func rightmostEntityNameText(name *ast.Node) string { ++ if ast.IsIdentifier(name) { ++ return name.Text() ++ } ++ if ast.IsQualifiedName(name) { ++ return name.AsQualifiedName().Right.Text() ++ } ++ return "" ++} ++ ++func (tx *DeclarationTransformer) addSchemaStaticMember(members *[]*ast.Node, classDeclaration *ast.Node, name string, readonly bool, modelName string) { ++ typeNode := tx.normalizeGeneratedImportedTypes(tx.resolver.CreateTypeOfClassStaticProperty(tx.EmitContext(), classDeclaration, name, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker)) + if typeNode == nil || typeNode.Kind == ast.KindAnyKeyword { + return + } @@ -917,6 +1154,9 @@ index b6596e0d7..24e8ea282 100644 + if readonly { + modifiers = tx.Factory().NewModifierList([]*ast.Node{tx.Factory().NewModifier(ast.KindReadonlyKeyword)}) + } ++ if modelName != "" { ++ typeNode = tx.rewriteEffectSchemaStaticMemberType(typeNode, name, modelName) ++ } + *members = append(*members, tx.Factory().NewPropertySignatureDeclaration(modifiers, tx.Factory().NewIdentifier(name), nil, typeNode, nil)) +} + diff --git a/_patches/030-checker-emitresolver-effect-schema.patch b/_patches/030-checker-emitresolver-effect-schema.patch index 8897f28e..1ac10ad0 100644 --- a/_patches/030-checker-emitresolver-effect-schema.patch +++ b/_patches/030-checker-emitresolver-effect-schema.patch @@ -1,5 +1,5 @@ diff --git a/internal/checker/emitresolver.go b/internal/checker/emitresolver.go -index 7bf3719e5..f838be954 100644 +index 7bf3719e5..a585c32e0 100644 --- a/internal/checker/emitresolver.go +++ b/internal/checker/emitresolver.go @@ -12,6 +12,7 @@ import ( @@ -10,7 +10,7 @@ index 7bf3719e5..f838be954 100644 ) var _ printer.EmitResolver = (*EmitResolver)(nil) -@@ -1043,6 +1044,229 @@ func (r *EmitResolver) CreateTypeOfExpression(emitContext *printer.EmitContext, +@@ -1043,6 +1044,244 @@ func (r *EmitResolver) CreateTypeOfExpression(emitContext *printer.EmitContext, return requestNodeBuilder.SerializeTypeForExpression(expression, enclosingDeclaration, flags|nodebuilder.FlagsMultilineObjectLiterals, internalFlags, tracker) } @@ -55,6 +55,21 @@ index 7bf3719e5..f838be954 100644 + return r.createTypeLiteralOfType(emitContext, r.checker.getDeclaredTypeOfSymbol(symbol), enclosingDeclaration, flags, internalFlags, tracker) +} + ++func (r *EmitResolver) CreateTypeLiteralOfClassStaticProperty(emitContext *printer.EmitContext, declaration *ast.Node, propertyName string, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node { ++ declaration = emitContext.ParseNode(declaration) ++ if declaration == nil { ++ return nil ++ } ++ ++ r.checkerMu.Lock() ++ defer r.checkerMu.Unlock() ++ propertyType := r.getTypeOfClassSchemaProperty(declaration, propertyName) ++ if propertyType == nil { ++ return nil ++ } ++ return r.createTypeLiteralOfType(emitContext, propertyType, enclosingDeclaration, flags, internalFlags, tracker) ++} ++ +func (r *EmitResolver) CreateMakeTypeOfClassDeclaration(emitContext *printer.EmitContext, declaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node { + declaration = emitContext.ParseNode(declaration) + if declaration == nil { diff --git a/_patches/031-printer-emitresolver-effect-schema.patch b/_patches/031-printer-emitresolver-effect-schema.patch index 3dfb2465..1921fb54 100644 --- a/_patches/031-printer-emitresolver-effect-schema.patch +++ b/_patches/031-printer-emitresolver-effect-schema.patch @@ -1,14 +1,15 @@ diff --git a/internal/printer/emitresolver.go b/internal/printer/emitresolver.go -index 189fe88cc..5865ccdf0 100644 +index 189fe88cc..e617e1171 100644 --- a/internal/printer/emitresolver.go +++ b/internal/printer/emitresolver.go -@@ -123,6 +123,12 @@ type EmitResolver interface { +@@ -123,6 +123,13 @@ type EmitResolver interface { CreateTypeParametersOfSignatureDeclaration(emitContext *EmitContext, signatureDeclaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) []*ast.Node CreateLiteralConstValue(emitContext *EmitContext, node *ast.Node, tracker nodebuilder.SymbolTracker) *ast.Node CreateTypeOfExpression(emitContext *EmitContext, expression *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node + CreateTypeOfTypeNode(emitContext *EmitContext, typeNode *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node + CreateTypeLiteralOfTypeNode(emitContext *EmitContext, typeNode *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node + CreateTypeLiteralOfClassDeclaration(emitContext *EmitContext, declaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node ++ CreateTypeLiteralOfClassStaticProperty(emitContext *EmitContext, declaration *ast.Node, propertyName string, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node + CreateMakeTypeOfClassDeclaration(emitContext *EmitContext, declaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node + CreateTypeOfClassStaticProperty(emitContext *EmitContext, declaration *ast.Node, propertyName string, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node + CreateTypeOfStructSchemaProperty(emitContext *EmitContext, declaration *ast.Node, propertyName string, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node diff --git a/etscheckerhooks/init.go b/etscheckerhooks/init.go index 255b70e9..2068ea54 100644 --- a/etscheckerhooks/init.go +++ b/etscheckerhooks/init.go @@ -16,8 +16,10 @@ import ( // init registers the Effect diagnostics callbacks with TypeScript-Go. func init() { - // Set the version suffix so that core.Version() includes the Effect version - core.SetVersionSuffix("+effect-tsgo." + etscore.EffectVersion) + // Set the version suffix so that core.Version() reports the effect-app build + // as semver build metadata on the upstream 7.x base, e.g. "7.0.0-dev+effect-app.5". + // Bump the build counter on each effect-app binary release (gz on effect-app/tsgo). + core.SetVersionSuffix("+effect-app.5") effectconfigraw.Register() // Register the after check source file callback checker.RegisterAfterCheckSourceFileCallback(afterCheckSourceFile) From 703c5f53fd86354bf12295c0214b906002ebf12f Mon Sep 17 00:00:00 2001 From: Patrick Roza Date: Mon, 22 Jun 2026 17:26:38 +0200 Subject: [PATCH 6/9] fix(declarations): keep companion type for faceted struct schemas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop the `export type X = typeof X.Type` companion of a `const X = S.Struct(...)` model only when the struct is actually faceted, and emit the decoded `Type` as a `type X = …` alias when it does not materialize as an object literal (newer effect Schema `.Type` resolves to a mapped type). Previously the builder bailed and the companion was dropped with no replacement, leaving `X` value-only and breaking cross-module `import { type X }` (TS2749) under faceted resolution. Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/struct-facade-companion-type.md | 18 +++++++++++ ...nsformers-declarations-effect-schema.patch | 32 ++++++++++++++----- 2 files changed, 42 insertions(+), 8 deletions(-) create mode 100644 .changeset/struct-facade-companion-type.md diff --git a/.changeset/struct-facade-companion-type.md b/.changeset/struct-facade-companion-type.md new file mode 100644 index 00000000..b4846741 --- /dev/null +++ b/.changeset/struct-facade-companion-type.md @@ -0,0 +1,18 @@ +--- +"@effect/tsgo": patch +--- + +declarations: keep a companion type for faceted struct schemas instead of dropping it + +The effect-schema struct facade transform dropped the `export type X = typeof X.Type` +companion of a `const X = S.Struct(...)` model, but only emitted the replacement +`interface X` when the decoded `Type` materialized as an object literal. Under newer +effect Schema types `.Type` can resolve to a mapped type (e.g. +`Struct.ReadonlySide<…, "Type">`), so the facade builder bailed, the const stayed +un-faceted, and the companion alias was dropped with no replacement — leaving `X` +value-only and breaking cross-module `import { type X }` usage (TS2749) under faceted +resolution. + +Now the builder emits the decoded `Type` as a `type X = …` alias when it is not an +object literal (keeping the `interface X` form when it is), and the companion alias is +only dropped when the struct was actually faceted; otherwise the original alias is kept. diff --git a/_patches/029-transformers-declarations-effect-schema.patch b/_patches/029-transformers-declarations-effect-schema.patch index 68cc876f..74630fd9 100644 --- a/_patches/029-transformers-declarations-effect-schema.patch +++ b/_patches/029-transformers-declarations-effect-schema.patch @@ -1,5 +1,5 @@ diff --git a/internal/transformers/declarations/transform.go b/internal/transformers/declarations/transform.go -index b6596e0d7..5bec4b75b 100644 +index b6596e0d7..2c6066b86 100644 --- a/internal/transformers/declarations/transform.go +++ b/internal/transformers/declarations/transform.go @@ -351,6 +351,7 @@ func (tx *DeclarationTransformer) transformSourceFile(node *ast.SourceFile) *ast @@ -10,7 +10,7 @@ index b6596e0d7..5bec4b75b 100644 combinedStatements.Loc = statements.Loc // setTextRange if ast.IsExternalOrCommonJSModule(node) { if ast.IsInJSFile(node.AsNode()) { -@@ -2277,6 +2278,1152 @@ func (tx *DeclarationTransformer) transformEnumDeclaration(input *ast.EnumDeclar +@@ -2277,6 +2278,1168 @@ func (tx *DeclarationTransformer) transformEnumDeclaration(input *ast.EnumDeclar ) } @@ -83,6 +83,7 @@ index b6596e0d7..5bec4b75b 100644 + } + + changed := false ++ facetedStructNames := map[string]bool{} + next := make([]*ast.Node, 0, len(statements.Nodes)) + for _, statement := range statements.Nodes { + baseModelName := getEffectSchemaBaseModelName(statement) @@ -184,6 +185,7 @@ index b6596e0d7..5bec4b75b 100644 + + if structName := getEffectSchemaStructVariableName(statement); structName != "" && structModelNames[structName] { + if declarations := tx.createEffectSchemaStructDeclarations(statement, structName); declarations != nil { ++ facetedStructNames[structName] = true + changed = true + next = append(next, declarations...) + continue @@ -191,9 +193,15 @@ index b6596e0d7..5bec4b75b 100644 + } + + if isEffectSchemaStructCompanionTypeAlias(statement, structModelNames) { -+ // Dropped — replaced by the generated `interface X`. -+ changed = true -+ continue ++ if facetedStructNames[statement.Name().Text()] { ++ // Dropped — replaced by the generated `interface X` (or `type X`). ++ changed = true ++ continue ++ } ++ // Faceting bailed for this struct (e.g. the decoded `Type` did not ++ // materialize): keep the original `type X = typeof X.Type` so `X` stays a ++ // usable type. Dropping it without a replacement would leave `X` value-only ++ // and break cross-module type usage under faceted resolution. + } + + next = append(next, statement) @@ -371,7 +379,7 @@ index b6596e0d7..5bec4b75b 100644 + +func (tx *DeclarationTransformer) createEffectSchemaStructDeclarations(statement *ast.Node, modelName string) []*ast.Node { + typeNode := tx.materializeEffectSchemaStructProperty(modelName, "Type") -+ if typeNode == nil || !ast.IsTypeLiteralNode(typeNode) { ++ if typeNode == nil { + return nil + } + namespace := tx.createEffectSchemaGeneratedStructNamespace(modelName, structHasExportModifier(statement)) @@ -390,8 +398,16 @@ index b6596e0d7..5bec4b75b 100644 + declarations := tx.Factory().NewNodeList([]*ast.Node{updatedDeclaration}) + declarationList := tx.Factory().UpdateVariableDeclarationList(statement.AsVariableStatement().DeclarationList.AsVariableDeclarationList(), declarations, statement.AsVariableStatement().DeclarationList.Flags) + retypedConst := tx.Factory().UpdateVariableStatement(statement.AsVariableStatement(), statement.Modifiers(), declarationList) -+ typeInterface := tx.Factory().NewInterfaceDeclaration(tx.effectSchemaStructModifiers(exported, false), tx.Factory().NewIdentifier(modelName), nil, nil, tx.Factory().NewNodeList(typeNode.AsTypeLiteralNode().Members.Nodes)) -+ return []*ast.Node{retypedConst, typeInterface, namespace} ++ var typeDeclaration *ast.Node ++ if ast.IsTypeLiteralNode(typeNode) { ++ typeDeclaration = tx.Factory().NewInterfaceDeclaration(tx.effectSchemaStructModifiers(exported, false), tx.Factory().NewIdentifier(modelName), nil, nil, tx.Factory().NewNodeList(typeNode.AsTypeLiteralNode().Members.Nodes)) ++ } else { ++ // The decoded `Type` did not materialize as an object literal (e.g. a union, ++ // intersection or mapped type). Emit it as a type alias so `X` still names the ++ // decoded type, rather than dropping it (which would leave `X` value-only). ++ typeDeclaration = tx.Factory().NewTypeAliasDeclaration(tx.effectSchemaStructModifiers(exported, false), tx.Factory().NewIdentifier(modelName), nil, typeNode) ++ } ++ return []*ast.Node{retypedConst, typeDeclaration, namespace} +} + +type generatedTypeImport struct { From a655ed4c5a70a30c3542b06f5bbc156388810257 Mon Sep 17 00:00:00 2001 From: Patrick Roza Date: Mon, 22 Jun 2026 17:38:08 +0200 Subject: [PATCH 7/9] chore(release): add cut-release script + bump to effect-app.6 _tools/cut-release.sh (pnpm release:cut) builds every platform tsgo binary, packages each as the gz asset that install-patched-compilers.mjs downloads, writes SHA256SUMS, and creates the effect-app/tsgo GitHub release. The version is derived from source (core version.go base + etscheckerhooks SetVersionSuffix) so the tag, release title and binary --version cannot drift; --dry-run and --skip-setup are supported. Bumps the build counter to +effect-app.6 for the struct-facade companion-type fix. Co-Authored-By: Claude Opus 4.8 (1M context) --- _tools/cut-release.sh | 119 ++++++++++++++++++++++++++++++++++++++++ etscheckerhooks/init.go | 2 +- package.json | 3 +- 3 files changed, 122 insertions(+), 2 deletions(-) create mode 100755 _tools/cut-release.sh diff --git a/_tools/cut-release.sh b/_tools/cut-release.sh new file mode 100755 index 00000000..3cd86848 --- /dev/null +++ b/_tools/cut-release.sh @@ -0,0 +1,119 @@ +#!/usr/bin/env bash +set -euo pipefail + +# cut-release.sh — build every platform `tsgo` binary, package each as a gzip, and +# create a GitHub release on effect-app/tsgo carrying them plus a SHA256SUMS file. +# +# This is the manual release path for the effect-app fork: the upstream `release.yml` +# only runs for `Effect-TS`-owned repos on a changeset merge, so fork binaries are cut +# from here. The produced assets are exactly what consumers download via +# scripts/install-patched-compilers.mjs: +# tsgo--.gz (e.g. tsgo-linux-x64.gz) +# tsgo-win32-.exe.gz (windows) +# SHA256SUMS (` ` per line) +# +# The version is derived from source so the tag, release title and the binary's own +# `--version` can never drift: +# core base : typescript-go/internal/core/version.go -> 7.0.0-dev +# suffix : etscheckerhooks/init.go SetVersionSuffix -> effect-app.6 +# => title 'tsgo 7.0.0-dev+effect-app.6' tag 'v7.0.0-dev-effect-app.6' +# Bump the suffix in etscheckerhooks/init.go before cutting a new release. +# +# Usage: +# cut-release.sh [--repo ] [--skip-setup] [--dry-run] [--notes ] +# --repo Target repository (default: effect-app/tsgo) +# --skip-setup Skip `pnpm setup-repo` (reuse the already-patched submodule tree) +# --dry-run Build + package, print the release command, but do not publish +# --notes Override the release notes body + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$REPO_ROOT" + +REPO="effect-app/tsgo" +DRY_RUN=false +SKIP_SETUP=false +NOTES="" +while [ $# -gt 0 ]; do + case "$1" in + --repo) REPO="$2"; shift 2 ;; + --skip-setup) SKIP_SETUP=true; shift ;; + --dry-run) DRY_RUN=true; shift ;; + --notes) NOTES="$2"; shift 2 ;; + *) echo "ERROR: unknown flag '$1'"; exit 1 ;; + esac +done + +# ── Derive version from source ──────────────────────────────────────────────── +core_base="$(sed -nE 's/.*var version = "([^"]+)".*/\1/p' typescript-go/internal/core/version.go | head -1)" +suffix="$(sed -nE 's/.*SetVersionSuffix\("\+(effect-app\.[0-9]+)"\).*/\1/p' etscheckerhooks/init.go | head -1)" +if [ -z "$core_base" ] || [ -z "$suffix" ]; then + echo "ERROR: could not derive version (core_base='$core_base' suffix='$suffix')" + exit 1 +fi +full_version="${core_base}+${suffix}" +tag="v${core_base}-${suffix}" +title="tsgo ${full_version}" +echo "==> Cutting ${title} (tag ${tag}) on ${REPO}" + +# npm platform-arch identifiers, matching install-patched-compilers.mjs asset names. +TARGETS=(darwin-arm64 darwin-x64 win32-x64 win32-arm64 linux-x64 linux-arm64 linux-arm) + +# ── Build ───────────────────────────────────────────────────────────────────── +if [ "$SKIP_SETUP" != true ]; then + echo "==> Applying patches (pnpm setup-repo --ci)" + pnpm setup-repo --ci +fi +echo "==> Cross-compiling ${#TARGETS[@]} targets" +pnpm release:prepare --skip-cli + +# ── Package gz assets + checksums ───────────────────────────────────────────── +STAGE="$(mktemp -d)" +trap 'rm -rf "$STAGE"' EXIT +assets=() +for t in "${TARGETS[@]}"; do + bin="tsgo"; gz="tsgo-${t}.gz" + case "$t" in win32-*) bin="tsgo.exe"; gz="tsgo-${t}.exe.gz" ;; esac + src="_packages/tsgo-${t}/lib/${bin}" + [ -s "$src" ] || { echo "ERROR: missing built binary $src"; exit 1; } + gzip -c -9 "$src" > "$STAGE/$gz" + assets+=("$STAGE/$gz") +done +( cd "$STAGE" && sha256sum tsgo-*.gz > SHA256SUMS ) +assets+=("$STAGE/SHA256SUMS") +echo "==> Packaged assets:" +( cd "$STAGE" && ls -la && echo "--- SHA256SUMS ---" && cat SHA256SUMS ) + +# ── Sanity check: the host-platform binary reports the derived version ───────── +host_os="$(go env GOOS)"; host_arch="$(go env GOARCH)" +case "${host_os}-${host_arch}" in + darwin-arm64) host_id=darwin-arm64 ;; darwin-amd64) host_id=darwin-x64 ;; + linux-amd64) host_id=linux-x64 ;; linux-arm64) host_id=linux-arm64 ;; + *) host_id="" ;; +esac +if [ -n "$host_id" ]; then + reported="$("_packages/tsgo-${host_id}/lib/tsgo" --version 2>/dev/null | awk '{print $2}')" + if [ "$reported" != "$full_version" ]; then + echo "ERROR: built binary reports '$reported', expected '$full_version' (bump etscheckerhooks/init.go?)" + exit 1 + fi + echo "==> Host binary version OK: $reported" +fi + +if [ -z "$NOTES" ]; then + NOTES="Effect language-service tsgo + .d.ts schema-facade emit (${full_version}). Built from PR #1." +fi + +if [ "$DRY_RUN" = true ]; then + echo "==> DRY RUN — would run:" + echo " gh release create $tag --repo $REPO --title \"$title\" ${assets[*]}" + exit 0 +fi + +# ── Publish ─────────────────────────────────────────────────────────────────── +if gh release view "$tag" --repo "$REPO" >/dev/null 2>&1; then + echo "==> Release $tag exists — uploading/overwriting assets" + gh release upload "$tag" --repo "$REPO" --clobber "${assets[@]}" +else + gh release create "$tag" --repo "$REPO" --title "$title" --notes "$NOTES" "${assets[@]}" +fi +echo "==> Released: https://github.com/${REPO}/releases/tag/${tag}" diff --git a/etscheckerhooks/init.go b/etscheckerhooks/init.go index 2068ea54..a444027e 100644 --- a/etscheckerhooks/init.go +++ b/etscheckerhooks/init.go @@ -19,7 +19,7 @@ func init() { // Set the version suffix so that core.Version() reports the effect-app build // as semver build metadata on the upstream 7.x base, e.g. "7.0.0-dev+effect-app.5". // Bump the build counter on each effect-app binary release (gz on effect-app/tsgo). - core.SetVersionSuffix("+effect-app.5") + core.SetVersionSuffix("+effect-app.6") effectconfigraw.Register() // Register the after check source file callback checker.RegisterAfterCheckSourceFileCallback(afterCheckSourceFile) diff --git a/package.json b/package.json index 0c14d05b..d248acca 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "build": "pnpm run build:go && pnpm run build:cli", "build:go": "CGO_ENABLED=0 go build -o tsgo ./typescript-go/cmd/tsgo", "build:cli": "pnpm --filter @effect/tsgo build", - "release:prepare": "bash _tools/release-prepare.sh" + "release:prepare": "bash _tools/release-prepare.sh", + "release:cut": "bash _tools/cut-release.sh" }, "devDependencies": { "@changesets/cli": "^2.30.0" From 52611517f7b3391c19f1f7a262ea9721e5f86f15 Mon Sep 17 00:00:00 2001 From: Patrick Roza Date: Mon, 22 Jun 2026 18:03:23 +0200 Subject: [PATCH 8/9] fix(emitresolver): emit static interfaces for struct facade Type/Encoded/Make MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit createTypeOfStructSchemaProperty rendered the decoded property via TypeToTypeNode, which under newer effect Schema types kept the deferred mapped helper (e.g. `Struct.ReadonlySide`) as a reference. That both regressed the facade's static-interface output and left the top-level `Type` a non-literal, so the struct facade builder bailed. Expand object-typed properties through createTypeLiteralOfType — the same path the class/request facades already use — so `X`, `Encoded`, `Make` and `Fields` emit as fully-expanded `interface`s; service channels (resolve to `never`) keep their direct node form. With static interfaces restored the declarations transformer no longer needs the type-alias fallback (reverted); the companion-alias drop stays gated on a struct actually being faceted. Bumps the build counter to +effect-app.7. Co-Authored-By: Claude Opus 4.8 (1M context) --- ...ansformers-declarations-effect-schema.patch | 18 +++++------------- ...30-checker-emitresolver-effect-schema.patch | 13 +++++++++++-- etscheckerhooks/init.go | 2 +- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/_patches/029-transformers-declarations-effect-schema.patch b/_patches/029-transformers-declarations-effect-schema.patch index 74630fd9..ed98789f 100644 --- a/_patches/029-transformers-declarations-effect-schema.patch +++ b/_patches/029-transformers-declarations-effect-schema.patch @@ -1,5 +1,5 @@ diff --git a/internal/transformers/declarations/transform.go b/internal/transformers/declarations/transform.go -index b6596e0d7..2c6066b86 100644 +index b6596e0d7..e396e08e3 100644 --- a/internal/transformers/declarations/transform.go +++ b/internal/transformers/declarations/transform.go @@ -351,6 +351,7 @@ func (tx *DeclarationTransformer) transformSourceFile(node *ast.SourceFile) *ast @@ -10,7 +10,7 @@ index b6596e0d7..2c6066b86 100644 combinedStatements.Loc = statements.Loc // setTextRange if ast.IsExternalOrCommonJSModule(node) { if ast.IsInJSFile(node.AsNode()) { -@@ -2277,6 +2278,1168 @@ func (tx *DeclarationTransformer) transformEnumDeclaration(input *ast.EnumDeclar +@@ -2277,6 +2278,1160 @@ func (tx *DeclarationTransformer) transformEnumDeclaration(input *ast.EnumDeclar ) } @@ -379,7 +379,7 @@ index b6596e0d7..2c6066b86 100644 + +func (tx *DeclarationTransformer) createEffectSchemaStructDeclarations(statement *ast.Node, modelName string) []*ast.Node { + typeNode := tx.materializeEffectSchemaStructProperty(modelName, "Type") -+ if typeNode == nil { ++ if typeNode == nil || !ast.IsTypeLiteralNode(typeNode) { + return nil + } + namespace := tx.createEffectSchemaGeneratedStructNamespace(modelName, structHasExportModifier(statement)) @@ -398,16 +398,8 @@ index b6596e0d7..2c6066b86 100644 + declarations := tx.Factory().NewNodeList([]*ast.Node{updatedDeclaration}) + declarationList := tx.Factory().UpdateVariableDeclarationList(statement.AsVariableStatement().DeclarationList.AsVariableDeclarationList(), declarations, statement.AsVariableStatement().DeclarationList.Flags) + retypedConst := tx.Factory().UpdateVariableStatement(statement.AsVariableStatement(), statement.Modifiers(), declarationList) -+ var typeDeclaration *ast.Node -+ if ast.IsTypeLiteralNode(typeNode) { -+ typeDeclaration = tx.Factory().NewInterfaceDeclaration(tx.effectSchemaStructModifiers(exported, false), tx.Factory().NewIdentifier(modelName), nil, nil, tx.Factory().NewNodeList(typeNode.AsTypeLiteralNode().Members.Nodes)) -+ } else { -+ // The decoded `Type` did not materialize as an object literal (e.g. a union, -+ // intersection or mapped type). Emit it as a type alias so `X` still names the -+ // decoded type, rather than dropping it (which would leave `X` value-only). -+ typeDeclaration = tx.Factory().NewTypeAliasDeclaration(tx.effectSchemaStructModifiers(exported, false), tx.Factory().NewIdentifier(modelName), nil, typeNode) -+ } -+ return []*ast.Node{retypedConst, typeDeclaration, namespace} ++ typeInterface := tx.Factory().NewInterfaceDeclaration(tx.effectSchemaStructModifiers(exported, false), tx.Factory().NewIdentifier(modelName), nil, nil, tx.Factory().NewNodeList(typeNode.AsTypeLiteralNode().Members.Nodes)) ++ return []*ast.Node{retypedConst, typeInterface, namespace} +} + +type generatedTypeImport struct { diff --git a/_patches/030-checker-emitresolver-effect-schema.patch b/_patches/030-checker-emitresolver-effect-schema.patch index 1ac10ad0..3ce42cae 100644 --- a/_patches/030-checker-emitresolver-effect-schema.patch +++ b/_patches/030-checker-emitresolver-effect-schema.patch @@ -1,5 +1,5 @@ diff --git a/internal/checker/emitresolver.go b/internal/checker/emitresolver.go -index 7bf3719e5..a585c32e0 100644 +index 7bf3719e5..51794253e 100644 --- a/internal/checker/emitresolver.go +++ b/internal/checker/emitresolver.go @@ -12,6 +12,7 @@ import ( @@ -10,7 +10,7 @@ index 7bf3719e5..a585c32e0 100644 ) var _ printer.EmitResolver = (*EmitResolver)(nil) -@@ -1043,6 +1044,244 @@ func (r *EmitResolver) CreateTypeOfExpression(emitContext *printer.EmitContext, +@@ -1043,6 +1044,253 @@ func (r *EmitResolver) CreateTypeOfExpression(emitContext *printer.EmitContext, return requestNodeBuilder.SerializeTypeForExpression(expression, enclosingDeclaration, flags|nodebuilder.FlagsMultilineObjectLiterals, internalFlags, tracker) } @@ -137,6 +137,15 @@ index 7bf3719e5..a585c32e0 100644 + if propertyType == nil { + return nil + } ++ // Emit the decoded property (Type/Encoded/Make/Fields) as a fully-expanded static ++ // type literal — the same shape the class/request facades produce — instead of the ++ // deferred mapped type (e.g. `Struct.ReadonlySide<…, "Type">`) the node builder would ++ // otherwise reference. This keeps the generated `interface X { … }` static and avoids ++ // re-instantiating the mapped helper at every use site. Non-object properties (the ++ // service channels resolve to `never`) keep their direct node form. ++ if propertyType.flags&TypeFlagsObject != 0 { ++ return r.createTypeLiteralOfType(emitContext, propertyType, enclosingDeclaration, flags, internalFlags, tracker) ++ } + structNodeBuilder := NewNodeBuilder(r.checker, emitContext) + return structNodeBuilder.TypeToTypeNode(propertyType, enclosingDeclaration, flags|nodebuilder.FlagsMultilineObjectLiterals|nodebuilder.FlagsUseFullyQualifiedType, internalFlags, tracker) +} diff --git a/etscheckerhooks/init.go b/etscheckerhooks/init.go index a444027e..9a5e8a13 100644 --- a/etscheckerhooks/init.go +++ b/etscheckerhooks/init.go @@ -19,7 +19,7 @@ func init() { // Set the version suffix so that core.Version() reports the effect-app build // as semver build metadata on the upstream 7.x base, e.g. "7.0.0-dev+effect-app.5". // Bump the build counter on each effect-app binary release (gz on effect-app/tsgo). - core.SetVersionSuffix("+effect-app.6") + core.SetVersionSuffix("+effect-app.7") effectconfigraw.Register() // Register the after check source file callback checker.RegisterAfterCheckSourceFileCallback(afterCheckSourceFile) From 0d6b94e1a57f6145cad2150ea106c85a805506be Mon Sep 17 00:00:00 2001 From: Patrick Roza Date: Mon, 22 Jun 2026 20:10:35 +0200 Subject: [PATCH 9/9] fix(declarations): facet bare-imported classes + static class namespace interfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Recognize bare named-import heritage constructors (`import { Class } from "effect-app/Schema"`) in addition to `S.Class(...)`, so those classes get the compact `OpaqueClassFacade` base instead of an inlined `EnhancedClass>`. Expand the class static properties (`Type`/`Encoded`/`Make`/`Fields`) to static `interface`s in `CreateTypeOfClassStaticProperty`, the same way the struct-facade path does — under effect #2442 these otherwise degrade to mapped `Struct.ReadonlySide` aliases. Callable members (services `never`, base `mapFields`/`copy`) keep their signatures via a has-properties guard. Bumps to +effect-app.8. Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/class-facade-static-interfaces.md | 20 ++++++++++++++ ...nsformers-declarations-effect-schema.patch | 27 +++++++++++++------ ...0-checker-emitresolver-effect-schema.patch | 23 +++++++++++++--- etscheckerhooks/init.go | 2 +- 4 files changed, 60 insertions(+), 12 deletions(-) create mode 100644 .changeset/class-facade-static-interfaces.md diff --git a/.changeset/class-facade-static-interfaces.md b/.changeset/class-facade-static-interfaces.md new file mode 100644 index 00000000..cd4c1a66 --- /dev/null +++ b/.changeset/class-facade-static-interfaces.md @@ -0,0 +1,20 @@ +--- +"@effect/tsgo": patch +--- + +declarations: facet bare-imported class schemas and emit static class namespace interfaces + +Two facade-emit gaps under newer effect Schema types: + +- Heritage detection only matched namespaced constructors (`S.Class(...)` / + `Schema.Opaque(...)`); a bare named import (`import { Class } from "effect-app/Schema"` + → `class X extends Class(...)`) was not recognized, so the class base kept its + fully-inlined `EnhancedClass>` instead of the compact + `OpaqueClassFacade`. Now both heritage shapes are recognized. + +- The class static-property resolver (`CreateTypeOfClassStaticProperty`) serialized + `Type`/`Encoded`/`Make`/`Fields` via the node builder, which under effect #2442 keeps + the deferred mapped helper (`Struct.ReadonlySide<…>`) as a reference — regressing the + generated namespace from static interfaces to mapped-type aliases. Now object-typed + properties are expanded to static `interface`s (matching the struct-facade path); + callable members (`never` services, the base `mapFields`/`copy`) keep their signatures. diff --git a/_patches/029-transformers-declarations-effect-schema.patch b/_patches/029-transformers-declarations-effect-schema.patch index ed98789f..4d866541 100644 --- a/_patches/029-transformers-declarations-effect-schema.patch +++ b/_patches/029-transformers-declarations-effect-schema.patch @@ -1,5 +1,5 @@ diff --git a/internal/transformers/declarations/transform.go b/internal/transformers/declarations/transform.go -index b6596e0d7..e396e08e3 100644 +index b6596e0d7..be48e2eb8 100644 --- a/internal/transformers/declarations/transform.go +++ b/internal/transformers/declarations/transform.go @@ -351,6 +351,7 @@ func (tx *DeclarationTransformer) transformSourceFile(node *ast.SourceFile) *ast @@ -10,7 +10,7 @@ index b6596e0d7..e396e08e3 100644 combinedStatements.Loc = statements.Loc // setTextRange if ast.IsExternalOrCommonJSModule(node) { if ast.IsInJSFile(node.AsNode()) { -@@ -2277,6 +2278,1160 @@ func (tx *DeclarationTransformer) transformEnumDeclaration(input *ast.EnumDeclar +@@ -2277,6 +2278,1171 @@ func (tx *DeclarationTransformer) transformEnumDeclaration(input *ast.EnumDeclar ) } @@ -532,14 +532,25 @@ index b6596e0d7..e396e08e3 100644 +// constructors. Opaque family (incl. requests) -> OpaqueFacade; class family -> OpaqueClassFacade; +// error family -> OpaqueErrorFacadeClass. +func getEffectSchemaCtorFacadeName(expression *ast.Node) string { -+ if !ast.IsPropertyAccessExpression(expression) || expression.Name() == nil || expression.Expression() == nil { -+ return "" -+ } -+ left := expression.Expression() -+ if !ast.IsIdentifier(left) || (left.Text() != "S" && left.Text() != "Schema") { ++ // The heritage constructor is either a namespaced access (`S.Class(...)` / ++ // `Schema.Opaque(...)`) or a bare named import (`import { Class } from ++ // "effect-app/Schema"` → `class X extends Class(...)`). Both forms name the ++ // same effect-app schema constructors; match on the constructor name in either ++ // shape (consistent with the namespace form trusting the `S`/`Schema` alias). ++ var ctorName string ++ switch { ++ case ast.IsPropertyAccessExpression(expression) && expression.Name() != nil && expression.Expression() != nil: ++ left := expression.Expression() ++ if !ast.IsIdentifier(left) || (left.Text() != "S" && left.Text() != "Schema") { ++ return "" ++ } ++ ctorName = expression.Name().Text() ++ case ast.IsIdentifier(expression): ++ ctorName = expression.Text() ++ default: + return "" + } -+ switch expression.Name().Text() { ++ switch ctorName { + case "Opaque", "OpaqueFacade": + return "OpaqueFacade" + case "Class", "TaggedClass": diff --git a/_patches/030-checker-emitresolver-effect-schema.patch b/_patches/030-checker-emitresolver-effect-schema.patch index 3ce42cae..4901936d 100644 --- a/_patches/030-checker-emitresolver-effect-schema.patch +++ b/_patches/030-checker-emitresolver-effect-schema.patch @@ -1,5 +1,5 @@ diff --git a/internal/checker/emitresolver.go b/internal/checker/emitresolver.go -index 7bf3719e5..51794253e 100644 +index 7bf3719e5..b4c50f58c 100644 --- a/internal/checker/emitresolver.go +++ b/internal/checker/emitresolver.go @@ -12,6 +12,7 @@ import ( @@ -10,7 +10,7 @@ index 7bf3719e5..51794253e 100644 ) var _ printer.EmitResolver = (*EmitResolver)(nil) -@@ -1043,6 +1044,253 @@ func (r *EmitResolver) CreateTypeOfExpression(emitContext *printer.EmitContext, +@@ -1043,6 +1044,270 @@ func (r *EmitResolver) CreateTypeOfExpression(emitContext *printer.EmitContext, return requestNodeBuilder.SerializeTypeForExpression(expression, enclosingDeclaration, flags|nodebuilder.FlagsMultilineObjectLiterals, internalFlags, tracker) } @@ -101,6 +101,19 @@ index 7bf3719e5..51794253e 100644 + if propertyType == nil { + return nil + } ++ // Emit the decoded property (Type/Encoded/Make/Fields) as a fully-expanded static ++ // type literal instead of the deferred mapped helper (e.g. `Struct.ReadonlySide<…, ++ // "Encoded">`) the node builder would otherwise reference — matching the struct-facade ++ // path. Keeps the generated namespace interfaces static and avoids re-instantiating ++ // the mapped helper at every use site. Non-object properties (service channels resolve ++ // to `never`, Make can be a void union) keep their direct node form. ++ // Expand only plain data objects. A callable type (e.g. the base's `mapFields` ++ // method, or `copy`) is also `TypeFlagsObject` but has no enumerable properties — ++ // expanding it would collapse the call signatures to `{}`, so leave those to the ++ // node builder which preserves the signature. ++ if propertyType.flags&TypeFlagsObject != 0 && len(r.checker.getPropertiesOfType(propertyType)) > 0 { ++ return r.createTypeLiteralOfType(emitContext, propertyType, enclosingDeclaration, flags, internalFlags, tracker) ++ } + requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) + return requestNodeBuilder.TypeToTypeNode(propertyType, enclosingDeclaration, flags|nodebuilder.FlagsMultilineObjectLiterals|nodebuilder.FlagsUseFullyQualifiedType, internalFlags, tracker) +} @@ -143,7 +156,11 @@ index 7bf3719e5..51794253e 100644 + // otherwise reference. This keeps the generated `interface X { … }` static and avoids + // re-instantiating the mapped helper at every use site. Non-object properties (the + // service channels resolve to `never`) keep their direct node form. -+ if propertyType.flags&TypeFlagsObject != 0 { ++ // Expand only plain data objects. A callable type (e.g. the base's `mapFields` ++ // method, or `copy`) is also `TypeFlagsObject` but has no enumerable properties — ++ // expanding it would collapse the call signatures to `{}`, so leave those to the ++ // node builder which preserves the signature. ++ if propertyType.flags&TypeFlagsObject != 0 && len(r.checker.getPropertiesOfType(propertyType)) > 0 { + return r.createTypeLiteralOfType(emitContext, propertyType, enclosingDeclaration, flags, internalFlags, tracker) + } + structNodeBuilder := NewNodeBuilder(r.checker, emitContext) diff --git a/etscheckerhooks/init.go b/etscheckerhooks/init.go index 9a5e8a13..644b1216 100644 --- a/etscheckerhooks/init.go +++ b/etscheckerhooks/init.go @@ -19,7 +19,7 @@ func init() { // Set the version suffix so that core.Version() reports the effect-app build // as semver build metadata on the upstream 7.x base, e.g. "7.0.0-dev+effect-app.5". // Bump the build counter on each effect-app binary release (gz on effect-app/tsgo). - core.SetVersionSuffix("+effect-app.7") + core.SetVersionSuffix("+effect-app.8") effectconfigraw.Register() // Register the after check source file callback checker.RegisterAfterCheckSourceFileCallback(afterCheckSourceFile)