fix(inflection): correct -ves singularization (derives -> derife) and audit the rule table - #108
Merged
Merged
Conversation
… audit the rule table
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
…till singularize A blanket is$ guard kept "apis" from reaching "api", and plural 'is$ -> ises' turned it into "apises", renaming generated objects downstream.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
inflection.singular('derives')returnedderife— which is what namedDerifein constructive-db's generated SDK/CLI — because the rule table carries the Rails-derived([^fo])ves$ -> \1fe. The rule is backwards:-vesnormally just drops thes, and the f/fe stems are the exception. So the generic rule is inverted and the stems are enumerated, ininflection_rules/fixtures/1589249334312_fixture.sql:The stems are matched as whole words / compound tails rather than bare suffix groups: a
(li)ves$group also fires onolives -> olife,(el)ves$ondelves -> delf, and(c)leaves$oncleaves -> cleaf— hence the(^|[^o])lives$/(^|[^c])leaves$forms. Plural is symmetric: only a stem takes-ves, everything else falls through to the final$ -> srule (cafe -> cafes,roof -> roofs,belief -> beliefs).Three more bugs found while sweeping the table against the full 104k-word
wamericandictionary (dictionary as oracle: output must be a real word, and plural→singular→plural must round-trip):((a)naly|(b)a|...)ses$ -> \1\2sis— PostgreSQL numbers those nested groups differently from node.jsinflection, soanalyses -> analyasis. Flattened to(analy|ba|diagno|...)ses$ -> \1sis.([m|l])ice$ -> \1ouse— the character class also matches a literal|, sopolice -> polouse,service -> servouse,chalice -> chalouse. Anchored to^(m|l|dorm|titm|woodl)ice$withice$guarded as already-singular; ditto([m|l])ouse$, which turnedspouse -> spiceandblouse -> blice.us, buts$ -> ''stripped them:census -> censu,radius -> radiu. Added aus$guard, preceded by a rule for the-unouns whose plural genuinely is-us(menus -> menu,haikus -> haiku), plusfocuses -> focusandlenses -> lens.The
-isguard has to stay anchored. The same reasoning suggests a blanket('singular', 'is$', NULL)+('plural', 'is$', E'ises'), and that is true of English words but not of identifiers: constructive-db's platform schema has a table namedapis, so those two rules stoppedsingular('apis')from reachingapiand madeplural('apis')=apises, renaming generated objects downstream (apis_pkey->apises_pkey,tg_apis_catalog_sync->tg_apises_catalog_sync). The plural rule is dropped; the singular guard issis$(analysis/basis/thesis/diagnosis) plus an anchored^(iris|chassis|axis|oasis|...)$word list, so no guard can ever suffix-match an identifier.Rule placement matters generally:
inflection.singular/pluralloop over an unorderedSELECT ... WHERE type=$1, so evaluation order is the fixture's insertion order — every guard above is inserted ahead of the generic fallback it must beat.The matching JS fix is constructive-io/dev-utils#99 (inflekt, merged); over ~9.6k common words the two implementations agree on all but 41 singulars, each a lexical case only inflekt's dictionary-generated table can know (
cookies -> cookienotcooky, invariants likejeans/gas/atlas) or an uncountable only the SQL side lists.__tests__/inflection.test.tsgoes 110 -> 151 cases, covering the-vesfamilies in both directions,-ice,-is/-us,analyses,apis, and dotted identifiers (metaschema_public.derives).sql/pgpm-inflection--0.33.0.sqlis regenerated withpgpm package; the version is untouched.Consumer-side vendor sync (and the end-to-end check that generated platform modules no longer drift): constructive-io/constructive-db#2624.
Link to Devin session: https://app.devin.ai/sessions/60b2dc2dcb71403f9388d9785a9a78cf
Requested by: @pyramation