Duplicate same-module type definitions (self-recursive newtype + real defn) when generating against GitHub's OpenAPI spec — 11 collisions, 493 compile errors
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
- #1012 de @z23cc — cerrado sin fusionar
Evaluación
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Aptitud para principiantes
- 45/100
Línea de trabajo
Start with Generator::generate_tokens and the typify-impl 0.6.2 renamer, using the GitHub OpenAPI reproduction and the three reduced specs described in the issue. Compare generated out.rs and cargo check output, then trace how seen-names state behaves across the cross-cutting references. Done means the 11 duplicate definitions and resulting compilation errors no longer occur for the reproduction.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Summary
When running progenitor (which uses typify internally) against GitHub's published OpenAPI 3.0.3 spec (9.1 MB, 1183 operations, 5193 generated types), typify emits two top-level type definitions with the same name in the same module for 11 of the 5193 types. The first definition is invariably a self-recursive newtype that wraps Option<Self>, which is itself an unsized recursive type. Compilation fails with 493 errors in the generated lib, all traceable to these 11 duplicate-name shadowings.
pub struct AutoMerge(pub ::std::option::Option<AutoMerge>); // ← bad placeholder, self-recursive
// ...60 lines later, same module...
pub struct AutoMerge { // ← real definition
pub commit_message: ::std::string::String,
pub commit_title: ::std::string::String,
pub enabled_by: SimpleUser,
pub merge_method: AutoMergeMergeMethod,
}
The first form is impossible (would have infinite size), so the placeholder shouldn't exist at all.
Error histogram (cargo check on the generated lib)
| Errors | Code | Meaning |
|---|---|---|
| 111 | E0119 | Conflicting trait impls (Debug, Display, Serialize, Deserialize) |
| 92 | E0599 | No associated item (enum variant lookups against the shadowing struct) |
| 83 | E0592 | Duplicate definitions with name builder (each duplicate has its own builder) |
| 63 | E0560 | Struct has no field X (call-sites assume the first defn's fields) |
| 42 | E0609 | No field X on type &T (same root) |
| 19 | E0428 | Name defined multiple times |
| 4 | E0072 | Recursive type has infinite size (the struct Foo(Option<Foo>) newtype) |
| 3 | E0391 | Cycle detected when computing layout |
| 493 | Total |
The 11 duplicated types
All are defined exactly twice in mod types {} in the generated lib:
AuthorAssociation AutoMerge CodespaceMachine IssueComment
IssueField IssueType LicenseSimple Milestone
NullableSecretScanningFirstDetectedLocation
ProjectsV2StatusUpdate SimpleCommit TeamSimple
Cross-referencing against the input spec, the 11 split into two patterns:
Pattern A: schema has top-level nullable: true (4 of 11)
auto-merge, author-association, issue-field, issue-type are each declared once in components.schemas with nullable: true at the schema root, e.g.
components:
schemas:
auto-merge:
type: object
nullable: true
properties:
commit_message: { type: string }
commit_title: { type: string }
enabled_by: { $ref: '#/components/schemas/simple-user' }
merge_method: { type: string, enum: [merge, squash, rebase] }
required: [enabled_by, merge_method, commit_title, commit_message]
Pattern B: nullable-X sibling schema with identical structure (7 of 11)
The spec defines both foo and nullable-foo as separate component schemas with identical bodies. Example: codespace-machine and nullable-codespace-machine are byte-for-byte the same except for the key. Affected types in this pattern: codespace-machine, issue-comment, license-simple, milestone, projects-v2-status-update, simple-commit, team-simple, plus nullable-secret-scanning-first-detected-location which exists only in the nullable form.
Reproduction
Generator: progenitor 0.14 → typify-impl 0.6.2 via Generator::generate_tokens (no CLI feature involved).
let raw = std::fs::read_to_string("api.github.com.yaml")?;
let spec: openapiv3::OpenAPI = serde_yaml::from_str(&raw)?;
let mut s = progenitor::GenerationSettings::default();
s.with_interface(progenitor::InterfaceStyle::Builder);
let tokens = progenitor::Generator::new(&s).generate_tokens(&spec)?;
let formatted = prettyplease::unparse(&syn::parse2(tokens)?);
std::fs::write("out.rs", formatted)?;
Then:
grep -E "^ pub (struct|enum) " out.rs | sort | uniq -c | awk '$1 > 1'
returns the 11 names above.
What I tried for minimization (and failed)
I built three reduced specs covering the two patterns:
- A schema with top-level
nullable: true, referenced once. - A
foo+nullable-foosibling pair, referenced once each. - A
nullable: trueschema referenced throughallOf.
In all three, typify's renamer correctly disambiguates by suffixing the inner type (e.g. pub struct AutoMerge(Option<AutoMergeInner>); pub struct AutoMergeInner { ... }) — the bug does not reproduce on a single-spec, two-or-three-schema reduction. The trigger seems to require many cross-cutting $refs and/or the renamer's seen-names state interacting across multiple processing contexts. I'd value pointers from a maintainer on which axes to bisect; if useful I can run instrumented experiments and report back.
Discovered via
pp, an OpenAPI → installable Rust CLI generator I'm building on top of progenitor. pp does spec normalization before handing the spec to progenitor (dedups media types, drops colliding enum values / property names, strips unsupported schema types, etc.), so the spec going into progenitor is clean. Generation itself succeeds without panic; only the produced Rust source fails to compile.
Why this matters
Past the well-known "needs a downgrade for OpenAPI 3.1" hurdle, this is the most common blocker I've seen when pointing progenitor at large real-world specs that use a "nullable variant" naming convention — a pattern common in vendor-published specs.
Happy to share the full generated out.rs (43 MB), a Cargo project that reproduces, or run any diagnostic patch a maintainer wants.
- Lenguaje dominante
- Rust
- Estrellas
- 907
- Forks
- 115
- Merge medio
- 20 h 24 min
- PR fusionados (30 d)
- 15
Preparar el entorno
Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de oxidecomputer/typify
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
oxidecomputer/typify#1077 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Dificultad 3/5 1-2 días Aptitud para principiantes 55/100
oxidecomputer/typify#1075 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Dificultad 4/5 3-5 días Aptitud para principiantes 50/100
oxidecomputer/typify#1060 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 5/5 Más de una semana Aptitud para principiantes 48/100
oxidecomputer/typify#1059 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 3/5 1-2 días Aptitud para principiantes 65/100
oxidecomputer/typify#1022 · 1 comentario ·
Los mantenedores suelen responder en 1 día
Todos los issues de oxidecomputer/typify
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 62/100
Los mantenedores suelen responder en 1 día
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 90/100
chroma-core/chroma#7879 ·
Los mantenedores suelen responder en 1 día
-
priority middle
Dificultad 1/5 Menos de una hora Aptitud para principiantes 72/100
KATO-Hiro/AtCoderClans#12838 ·
Los mantenedores suelen responder en 1 día
-
clap_complete env (PowerShell): values after a space don't complete in Windows PowerShell 5.1Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
Los mantenedores suelen responder en 1 día
-
enhancement
Dificultad 2/5 1-3 horas Aptitud para principiantes 74/100
Los mantenedores suelen responder en 1 día