Class Sameness

java.lang.Object
org.ek9lang.compiler.support.Sameness

public final class Sameness extends Object
THE vocabulary of B18 - "a type defines sameness once" - call this, do not re-derive it.

A type's SAMENESS is its <=> (an ordered type) or its == (an equatable-only type), chosen by the author. Every other comparison with the type itself (== <> < <= > >=) is generated from it and #? goes with it.

D24 (Steve, 2026-10-20; replaces B18 rule 4): WHERE the sameness is declared, and HOW, decides its reach.

  • WRITTEN BY HAND on an ABSTRACT base or a TRAIT (isHierarchyWide(IAggregateSymbol)) it is ONE sameness for the whole hierarchy - sealed: every sub-type inherits it and may not declare its own (E07182, sealedSameness(IAggregateSymbol, ISymbol)). This is the only way two different concrete types compare (a Shape ordering any two shapes by area).
  • DEFAULTED on an abstract base (follow-up Q1), or on a CONCRETE type, it is that type's own: a sub-type may declare or default its own over all its fields, inherited ones included, and values of DIFFERENT run-time types are never the same - == is false and <=> unset. The compiler puts that run-time-type check in front of every such comparison, hand-written or generated (needsRuntimeTypeCheck(IAggregateSymbol, MethodSymbol); Contract 22).

🔑 SELF means "any type this type is assignable to" - itself, a super, a trait, Any. A ==(Any) or an override ==(Base) compares the type with itself as surely as ==(Self) does; treating those as "other types" left the disagreement B18 removes reachable (Fable review F1). Operators taking a genuinely unrelated type (==(Integer), <(Date)) are not self-comparisons.

  • Field Details

    • SAMENESS_FAMILY

      public static final Set<String> SAMENESS_FAMILY
      The operators whose presence the type's sameness decides - never added by the bare default operator by name in phase 1, only once types are known (OperationFactory.completeSameness).
  • Method Details

    • isSelfComparison

      public static boolean isSelfComparison(IAggregateSymbol owner, MethodSymbol operator)
      True for an operator taking exactly one argument of a type the owner is assignable to.
    • isSamenessOperator

      public static boolean isSamenessOperator(IAggregateSymbol owner, MethodSymbol operator)
      True for a <=> or == comparing the owner with itself - a declaration of its sameness.
    • isDerivedComparison

      public static boolean isDerivedComparison(IAggregateSymbol owner, MethodSymbol operator)
      True for a <> < <= > >= comparing the owner with itself - always generated from the sameness, never written.
    • isHashCode

      public static boolean isHashCode(MethodSymbol operator)
      True for the zero-argument #?.
    • isHandWritten

      public static boolean isHandWritten(MethodSymbol operator)
      True when the developer wrote the operator's body (not default operator).
    • isExplicitlyDefaulted

      public static boolean isExplicitlyDefaulted(MethodSymbol operator)
      True for an explicit default operator X, as opposed to one added by the bare default operator.
    • isGenerated

      public static boolean isGenerated(MethodSymbol operator)
      True for a comparison generated from the type's sameness (never written or requested).
    • isHierarchyWide

      public static boolean isHierarchyWide(IAggregateSymbol owner)
      Is the sameness this type declares ONE sameness for its whole hierarchy (D24 rule 1)? Only when it is WRITTEN BY HAND on an ABSTRACT class or a TRAIT - an ordering over what every sub-type provides (area() <=> other.area()). A DEFAULTED sameness on an abstract base (D24 follow-up Q1, Steve 2026-10-20) compares only the base's own fields, so it acts as if the base were concrete: each concrete sub-type has its own over all its fields, and different run-time types are never the same. A concrete type's sameness is its own (D24 rule 2).
    • mayMixRuntimeTypes

      public static boolean mayMixRuntimeTypes(IAggregateSymbol type, ISymbol ek9Any)
      THE test behind E07187 (D24 Q4 REVISED, Steve 2026-10-20): can two values of this type be of DIFFERENT run-time types whose <=> is UNSET? Then <? and >? have no lesser or greater value to select, and an unset value cannot be built to stand in for one (a T() may be set, absent, private or abstract, and would run developer code).

      True for an open or abstract CLASS, RECORD or COMPONENT, or a TRAIT, of the developer's own, unless its sameness is the whole hierarchy's - written by hand on an abstract base or a trait (isHierarchyWide(IAggregateSymbol)), on the type itself or above it (sealedSameness(IAggregateSymbol, ISymbol)). Closed classes, records and components, enumerations, constrained types and the built-in types are never affected.

    • mixingKind

      public static String mixingKind(IAggregateSymbol type)
      For a message: how a type for which mayMixRuntimeTypes(IAggregateSymbol, ISymbol) holds can have values of different run-time types - "a trait", or "an abstract" / "an open" followed by "class", "record" or "component".
    • declaredSameness

      public static Optional<MethodSymbol> declaredSameness(IAggregateSymbol owner)
      The sameness the type itself declares - its own non-abstract <=> comparing it with itself, else its == - hand-written or defaulted, never one GENERATED from it (isGenerated(MethodSymbol)).
    • sealedSameness

      public static Optional<MethodSymbol> sealedSameness(IAggregateSymbol aggregate, ISymbol ek9Any)
      The SEALED sameness this type inherits - one written by hand on an abstract base or a trait above it (isHierarchyWide(IAggregateSymbol)) - if any. Such a type may not declare sameness of its own (E07182), and its #? goes with the inherited one (E07184). Same walk as inheritedSameness(IAggregateSymbol, ISymbol).
    • needsRuntimeTypeCheck

      public static boolean needsRuntimeTypeCheck(IAggregateSymbol owner, MethodSymbol operator)
      THE test of whether a sameness comparison - the type's == or <=>, hand-written or generated, or a generated ==/<> answered by its <=> - needs the run-time-type check in front of it (D24 rule 3, Contract 22): values of different run-time types are never the same. Only where the sameness is not hierarchy-wide (one is meant to compare across its sub-types), and only where the other value CAN be of another class: the owner is open, or the operator takes a wider type than the owner. A closed owner's own ==(Owner) only ever meets an Owner, so it is unchanged.
    • overriddenSignatures

      public static List<MethodSymbol> overriddenSignatures(IAggregateSymbol owner, ISymbol ek9Any)
      THE inherited sameness signatures a type with its own sameness OVERRIDES (D24 rule 3; N07 - the object decides, never the reference): for each of == and <=> the type declares taking exactly itself, every same-named comparison above it on its super CLASS chain (InheritedOperators.allAbove(IAggregateSymbol, String, ISymbol)) that takes a type the owner is assignable to and that the owner does not declare itself. The override answers through the type's own comparison when the other value is one of its own, else as values of different run-time types do (== false, <=> unset) - so held as Holder: Named(1, "x") compares as a Named. Like PolymorphicCopy's overrides, it is emitted from a DETACHED symbol, never defined in the scope.
      Parameters:
      owner - the type that declares its own sameness
      ek9Any - the Any type, which never contributes
      Returns:
      the inherited comparisons whose signatures the owner overrides, == then <=>
    • ownOperatorTaking

      public static Optional<MethodSymbol> ownOperatorTaking(IAggregateSymbol owner, String name, ISymbol parameterType)
      The owner's own non-abstract operator of this name taking exactly that one type, if any.
    • nearestSamenessAbove

      public static Optional<MethodSymbol> nearestSamenessAbove(IAggregateSymbol owner, ISymbol ek9Any)
      THE sameness a type would inherit through its super CLASS chain (D24 follow-up Q5, Steve 2026-10-20): the nearest concrete <=> comparing its owner with itself (InheritedOperators.nearestSelfComparisonAbove(IAggregateSymbol, String, ISymbol)) - the super is ORDERED - else the nearest such == - the super is EQUATABLE only. Empty when nothing above (but Any) declares sameness. Read by E07238 (a type adding fields that would inherit it) and by the bare default operator, which gives a type the same KIND of sameness as the super it builds on.
      Parameters:
      owner - the type whose super chain is walked (the type itself is not consulted)
      ek9Any - the Any type, which never contributes
      Returns:
      the nearest concrete <=> above, else the nearest concrete ==, if any
    • orderingSurvives

      public static boolean orderingSurvives(MethodSymbol comparison, ISymbol ek9Any)
      THE "does this <=> still order values once PRE_IR_CHECKS has run?" (D55, Steve 2026-10-20). A <=> written by hand always does; an explicit default operator <=> does when every property can be ordered (it can fail to only in a generic, where a property is a T that became a type with no <=> - D71). One the BARE default operator added survives only when every property of its owner can be ordered (HasRealComparator.allPropertiesOrderable(IAggregateSymbol)) AND the nearest <=> above it - the one its generated body calls for the inherited fields - survives too. Otherwise phase 5 retracts it, with the rest of the bare ordering family, and the type is equatable only: a sub-type follows its super. Below a Base whose bare <=> is retracted (a field's type has == only), a Leaf's bare <=> once compared only Leaf's own fields - Leaf(1, "x") == Leaf(2, "x") was TRUE while their #? differed.

      Read by phase 5's retraction (DefaultComparatorRecheckOrError), by E07186, E07190 and E07238 in phase 3, and by DefaultedComparisonDelegate.hasOwnComparator(IAggregateSymbol, ISymbol), so none of them can disagree about whether a type is ordered. The walk above reads the methods as DECLARED, a retracted one included, so the answer is the same before, during and after the retraction pass, whatever order it visits the types in.

      Parameters:
      comparison - a <=> comparing its owner with itself
      ek9Any - the Any type, which never contributes
      Returns:
      true when the comparison orders values after PRE_IR_CHECKS
    • isOrderable

      public static boolean isOrderable(IAggregateSymbol type, ISymbol ek9Any)
      THE "is this type orderable?" behind HasRealComparator: the nearest <=> it declares or inherits (super chain before Any, then its traits) that survives (orderingSurvives(MethodSymbol, ISymbol)). A bare one that does not survive is passed over, as if the default operator had never added it.
      Parameters:
      type - the type asked about
      ek9Any - the Any type, whose degenerate <=> never counts
      Returns:
      true when values of the type can be ordered
    • allPropertiesOrderable

      public static boolean allPropertiesOrderable(IAggregateSymbol aggregate, ISymbol ek9Any)
      THE "can every property of this type be ordered?" (isOrderable(IAggregateSymbol, ISymbol) for each property's type) - what a bare default operator's <=> needs of its own fields.
      Parameters:
      aggregate - the type whose properties are asked about
      ek9Any - the Any type
      Returns:
      true when every property's type is orderable
    • nearestSurvivingSamenessAbove

      public static Optional<MethodSymbol> nearestSurvivingSamenessAbove(IAggregateSymbol owner, ISymbol ek9Any)
      The sameness a type that declares none of its own gets from its super CLASS chain once PRE_IR_CHECKS has run (D55): nearestSamenessAbove(IAggregateSymbol, ISymbol), but a <=> that does not survive (orderingSurvives(MethodSymbol, ISymbol)) is passed over for the nearest == - the super is EQUATABLE only. What E07238 tells a type that adds fields to declare.
      Parameters:
      owner - the type whose super chain is walked (the type itself is not consulted)
      ek9Any - the Any type, which never contributes
      Returns:
      the nearest surviving concrete <=> above, else the nearest concrete ==, if any
    • bareOrderingRetracted

      public static boolean bareOrderingRetracted(IAggregateSymbol owner, ISymbol ek9Any)
      THE retraction decision for a type's BARE ordering family (<=> < <= > >= added by the bare default operator): its own bare <=> decides for the whole family through orderingSurvives(MethodSymbol, ISymbol); without one (the ordering family alone, beside a hand-written <=>) the type's own properties decide. Phase 5's retraction (DefaultComparatorRecheckOrError) acts on it, and isRetractedOrdering(MethodSymbol, ISymbol) answers the same question for one operator before or after that pass (D58), so a use and the retraction cannot disagree.
      Parameters:
      owner - the type whose bare ordering operators are asked about
      ek9Any - the Any type
      Returns:
      true when the type's bare ordering operators are (or will be) retracted
    • retractWithdrawnBareOrdering

      public static boolean retractWithdrawnBareOrdering(IAggregateSymbol aggregate, ISymbol ek9Any)
      THE retraction step (D55/D58/D62): when bareOrderingRetracted(IAggregateSymbol, ISymbol) says the type's bare ordering is withdrawn, mark each of its bare ordering operators (<=>, <, <=, >, >=) DEFAULT_OPERATOR_RETRACTED, which the type's method set then filters out, so none is generated. Phase 5 calls it for every type it can see (named types, parameterisations, dynamic classes); phase 7 calls it for the copy of a dynamic class it monomorphises in a generic body, which phase 5 never sees and whose captures only then have their concrete types (a T that orders, given a List). Never a second marking.
      Parameters:
      aggregate - the type to check
      ek9Any - the Any type
      Returns:
      true when operators were marked
    • isRetractedOrdering

      public static boolean isRetractedOrdering(MethodSymbol operator, ISymbol ek9Any)
      Is this operator a BARE ordering operator that phase 5 retracts (bareOrderingRetracted(IAggregateSymbol, ISymbol) of its owner)? Order-independent, so usable from POST_RESOLUTION_CHECKS on: a generic's body that uses <=> or < on T, parameterised with a type whose ordering is retracted, is E07620 at the parameterisation (D58, Steve 2026-10-21).
      Parameters:
      operator - the operator a use resolved to
      ek9Any - the Any type
      Returns:
      true when the operator will not be generated
    • isDegenerateDefaultedOrdering

      public static boolean isDegenerateDefaultedOrdering(MethodSymbol operator, ISymbol ek9Any)
      A defaulted ordering operator on a type whose <=> degenerates because a field cannot be ordered - the case a generic instantiation reaches without any retraction marker: Box of List (its template's default operator <=> is over a T that became a List). Its == falls back to field by field and it has no ordering (B18 rule 8); without this, first < second compiled and was always UNSET.
      Parameters:
      operator - an operator a use resolved to
      ek9Any - the Any type
      Returns:
      true when the operator is a defaulted ordering over a type that has no comparator of its own
    • isUnavailableOrdering

      public static boolean isUnavailableOrdering(MethodSymbol operator, ISymbol ek9Any)
      THE verdict on a call of an ordering operator written as an operator (first < second, a case < x): the operator it resolved to is one phase 5 retracts (isRetractedOrdering(MethodSymbol, ISymbol)) or one that degenerates (isDegenerateDefaultedOrdering(MethodSymbol, ISymbol)). Phase 5's RetractedComparatorUseOrError reads the retraction marker where this reads the methods as declared; phase 4 asks it for a generic's operator use where the type parameters are replaced (D71, D63 residual).
      Parameters:
      operator - the operator a use resolved to
      ek9Any - the Any type
      Returns:
      true when the operator is not available
    • withdrawnOrdering

      public static Optional<MethodSymbol> withdrawnOrdering(IAggregateSymbol type, ISymbol ek9Any)
      THE "does a use of this type's <=> rely on an ordering that is withdrawn?" (D58, Steve 2026-10-21): the nearest <=> declared on the type or above it - the one phase 3 resolved the use to - is one the bare default operator added, and it does not survive (orderingSurvives(MethodSymbol, ISymbol)). Every use that needs it without naming it - <? >?, a sort stage with no comparator, x in a ... b, a for range - asks here, after phase 5 has retracted it. It is NOT isOrderable(IAggregateSymbol, ISymbol): a sub-type whose own bare <=> is retracted below a super with a hand-written one is "orderable" through the super, yet the use resolved to the sub-type's own <=>(Sub), which is not generated (NoSuchMethodError) - the same answer the binary < gets from the retraction mark. Empty for any other type, so a type that never offered a bare ordering is never touched.
      Parameters:
      type - the type whose <=> a use needs
      ek9Any - the Any type
      Returns:
      the withdrawn bare <=> the use relied on, if any
    • unorderedDictEntryKey

      public static Optional<ISymbol> unorderedDictEntryKey(ISymbol type, ISymbol ek9Any)
      The key type that stops a DictEntry of (K, V) being ordered (Steve, 2026-10-21): a DictEntry orders by its KEY's <=>, then by its VALUE's when the keys are the same (the entries of one Dict have unique keys), so a key type that cannot be ordered (isOrderable(IAggregateSymbol, ISymbol); a function never can) leaves the entry unordered, and a comparator-less sort, <?, range or natural-order queue over it is E07620 - not the run-time "Sort cannot order" Exception it was. A conceptual T key (inside a generic's body) never counts here: a generic body's ordering of a type BUILT ON T (DictEntry of (K, Integer)) is recorded in phase 3 (ConceptualAccess.noteOrderingUse) and judged where K is substituted (phase4/ParameterisedTypeOrError), by THIS verdict on the replaced type (D71, D63 residual).
      Parameters:
      type - the type asked about; anything but a parameterisation of DictEntry answers empty
      ek9Any - the Any type
      Returns:
      the key type that cannot be ordered, if any
    • orderingWithdrawn

      public static boolean orderingWithdrawn(IAggregateSymbol type, ISymbol ek9Any)
      Does a use that needs the type's <=> without naming it (sort, <?, a range) rely on an ordering that is not there: a withdrawn bare <=> (withdrawnOrdering(IAggregateSymbol, ISymbol)) or a DictEntry whose key cannot be ordered (unorderedDictEntryKey(ISymbol, ISymbol))? THE test of phase 5's RetractedComparatorUseOrError; whyNotOrderable(IAggregateSymbol, ISymbol) words it.
      Parameters:
      type - the type whose <=> a use needs
      ek9Any - the Any type
      Returns:
      true when the use is E07620
    • unorderedPropertyBehind

      public static Optional<Sameness.UnorderedProperty> unorderedPropertyBehind(MethodSymbol comparison, ISymbol ek9Any)
      THE reason a bare <=> does not survive: the first property, from the owner of comparison upward through the <=>s each bare one builds on (read as DECLARED, so it answers the same after the retraction), whose type cannot be ordered (isOrderable(IAggregateSymbol, ISymbol)).
      Parameters:
      comparison - a bare <=>
      ek9Any - the Any type
      Returns:
      the field and its owner, if one is found
    • whyNotOrderable

      public static String whyNotOrderable(IAggregateSymbol type, ISymbol ek9Any)
      THE wording of why a type has no <=> although it asked for one with the bare default operator (D58): the field whose type cannot be ordered, the type declaring it and - only where that is a super - that a sub-type follows its super. True for a type with no super and for one below a retracted super.
      Parameters:
      type - the type whose <=> is not available
      ek9Any - the Any type
      Returns:
      a phrase to follow "is not available on 'X': "
    • inheritedSameness

      public static Optional<MethodSymbol> inheritedSameness(IAggregateSymbol aggregate, ISymbol ek9Any)
      The sameness this type INHERITS - from its super chain (stopping before Any) or any of its traits (its own, a super's, a trait's trait) - if any, sealed or not. A type that declares no sameness of its own compares through it, and so may not declare a #? alone (E07184).

      DEFENSIVE: this runs at the end of EXPLICIT_TYPE_SYMBOL_DEFINITION, before TYPE_HIERARCHY_CHECKS reports a circular hierarchy, so it walks with its own visited set rather than getAllTraits() (which recurses without one and overflowed on a loop). A broken hierarchy must neither error nor crash here.

    • sortOwnerAbove

      public static Optional<String> sortOwnerAbove(IAggregateSymbol aggregate)
      THE sort owner of a type (D52 residual, Steve 2026-10-21, option a): the type a stream sort groups its values by before comparing any two with <=>. It is the abstract base or trait that declares the hierarchy-wide <=> the type is sealed under (isHierarchyWide(IAggregateSymbol)) - there ONE ordering really does cover every sub-type, so they share a group. Anywhere else a type's sameness is its own (a concrete type's, a defaulted one on an abstract base, one inherited from a concrete super and guarded by the run-time-type check), so the type is its own owner and this answers empty: the type's own display name is then the owner, as run time reads it. Answered as the owner's display name (ConstructDisplayName.distinctOf(ISymbol), which keeps two owners parameterised by different dynamic classes apart), the form Ek9Sort orders by.

      Needs no Any: it is never hierarchy-wide, so the walk passes over it by genus. Read by IRConstruct, so every class carries the owner as IR data (Contract 22).

      Parameters:
      aggregate - the type about to be generated
      Returns:
      the display name of the hierarchy-wide <=>'s owner above the type, or empty when the type is its own owner