diff --git a/.agents/skills/testing-pilot-corpora-gate/SKILL.md b/.agents/skills/testing-pilot-corpora-gate/SKILL.md index 515bc6ded4..e9caa59904 100644 --- a/.agents/skills/testing-pilot-corpora-gate/SKILL.md +++ b/.agents/skills/testing-pilot-corpora-gate/SKILL.md @@ -186,8 +186,8 @@ gate's own helpers are package-private but reusable (`pilotCorporaGate.files(t)` `actionlint`, `shellcheck`, `python3 scripts/check-doc-links.py`, `gofmt`, `go vet`, `go run -C tools ./cmd/pilot-diff` (validators pre-downloaded; ~4min, prints e.g. -the headline the committed baseline holds — `921 file(s), 518 fully agreeing; 1098 agreed -diagnostic(s), 360 only ours, 2462 only the pilot's` at the `2026-09` pin, so read it from +the headline the committed baseline holds — `921 file(s), 519 fully agreeing; 1098 agreed +diagnostic(s), 359 only ours, 2462 only the pilot's` at the `2026-09` pin, so read it from `docs/project/pilot-differential-baseline.json` rather than from this line) and `make lint` (staticcheck+gosec, ~2min) all work. There is **no** `yamllint` and **no** `circleci` CLI, so `.circleci/config.yml` can only be parsed as YAML, not schema-validated — say so diff --git a/.agents/skills/testing-pilot-differential/SKILL.md b/.agents/skills/testing-pilot-differential/SKILL.md index 5f0c887dd2..ab269192b5 100644 --- a/.agents/skills/testing-pilot-differential/SKILL.md +++ b/.agents/skills/testing-pilot-differential/SKILL.md @@ -27,8 +27,9 @@ GNU-format diagnostics **relative to `--root`**. Consequences for testing: inputs keep its warning at `delta-v-budget.sysml:93`), then the unbounded-literal typing round (`passes/literal_infinity.sysml`: `*` bound to a Boolean, a String, a part and an enumeration is our `binding-connector-type-conformance` error where the pilot warns, four severity-only pairs, and - its `Integer[2] = *` count error is one only-ours row): `921 file(s), 518 fully agreeing; 1098 agreed, - 360 only ours, 2462 only the pilot's`, JSON totals `openSysMLDiagnostics 1469 / pilotDiagnostics 3571 / + its `Integer[2] = *` count error is one only-ours row), then the expose-only collisions round + (the `general-views-demo/vehicle.sysml:102` duplicate-imported-name warning retired): `921 file(s), 519 fully agreeing; 1098 agreed, + 359 only ours, 2462 only the pilot's`, JSON totals `openSysMLDiagnostics 1468 / pilotDiagnostics 3571 / severityMismatch 11`; the two only-ours rows the multiplicity rule added are the expected `action-step-multiplicity-not-fixed` warnings on `training/18. Action Performance/Action Performance Example.sysml:10` and `pilot-examples/Camera Example/Camera.sysml:4`. ~2 min wall, byte-identical across runs *and* after a from-scratch rebuild of @@ -152,8 +153,9 @@ warning (the `[1]` `RocketEquation` inputs still produce the warning at `action-step-multiplicity-not-fixed` warnings on `takePhoto[*]` in the training corpus and `takePicture[*]` in `Camera Example/Camera.sysml`, and the view concern framing round retired the six view-body `frame` `syntax` rows on `examples`, and the unbounded-literal typing round added -`passes/literal_infinity.sysml` (four severity-only pairs, one only-ours count error); a live run gives `921 file(s), 518 fully -agreeing; 1098 agreed, 360 only ours, 2462 only the pilot's`, byte-identical to the committed baseline, and +`passes/literal_infinity.sysml` (four severity-only pairs, one only-ours count error), and the expose-only collisions round retired +the `general-views-demo/vehicle.sysml:102` warning; a live run gives `921 file(s), 519 fully +agreeing; 1098 agreed, 359 only ours, 2462 only the pilot's`, byte-identical to the committed baseline, and `docs/project/pilot-differential.md`'s "Results" table matches. The prior rebaseline, when the Legend of the Red Dragon example left for its own repository, gave `920 file(s), 517 fully agreeing; 1098 agreed, 366 only ours, 2448 only the pilot's`. diff --git a/.agents/skills/testing-pilot-execution-referee/SKILL.md b/.agents/skills/testing-pilot-execution-referee/SKILL.md index a12b48d542..eb928177fb 100644 --- a/.agents/skills/testing-pilot-execution-referee/SKILL.md +++ b/.agents/skills/testing-pilot-execution-referee/SKILL.md @@ -154,8 +154,8 @@ pilot answers the representation's own. See `pilot-exec-diff: :: model no/such/model.sysml: stat : no such file or directory`. - **Additivity.** `go run -C tools ./cmd/pilot-diff` must still print the headline the - committed baseline holds (`921 file(s), 518 fully agreeing; 1098 agreed - diagnostic(s), 360 only ours, 2462 only the pilot's` at the `2026-09` pin — read it from the baseline JSON, not from this line, since each + committed baseline holds (`921 file(s), 519 fully agreeing; 1098 agreed + diagnostic(s), 359 only ours, 2462 only the pilot's` at the `2026-09` pin — read it from the baseline JSON, not from this line, since each fix round moves it) and `jq -S` diff clean against `docs/project/pilot-differential-baseline.json`; `git status --porcelain` empty at the end. diff --git a/.agents/skills/testing-pilot-xpect/SKILL.md b/.agents/skills/testing-pilot-xpect/SKILL.md index a8c383f121..729d66ba2e 100644 --- a/.agents/skills/testing-pilot-xpect/SKILL.md +++ b/.agents/skills/testing-pilot-xpect/SKILL.md @@ -425,7 +425,7 @@ census in `w5c_census_test.go` is live two ways: perturb one pinned triple (e.g. ## Regression neighbour `go run -C tools ./cmd/pilot-diff` (~1m12s) must still print the headline the *committed* baseline holds — -at the `2026-09` pin that is `921 file(s), 518 fully agreeing; 1098 agreed diagnostic(s), 360 +at the `2026-09` pin that is `921 file(s), 519 fully agreeing; 1098 agreed diagnostic(s), 359 only ours, 2462 only the pilot's`. Read the number out of `docs/project/pilot-differential-baseline.json` rather than trusting this line, since a landing fix round moves it. When the baseline is itself stale (it was at `19a3ce03`, holding 273 / 281 / 317), a diff --git a/README.md b/README.md index 7a2e76ebc4..2f5c0a93d6 100644 --- a/README.md +++ b/README.md @@ -348,11 +348,11 @@ The project is under active development, with the core infrastructure operationa **Measured against the pinned reference** (`PILOT_TAG=2026-09`, artifact `0.63.0`). Every number below is generated by `make docs-counts` from the committed baselines and gated; none of them is typed in by hand. -- **Corpus agreement:** 518 of 921 files agree diagnostic-by-diagnostic; 360 diagnostics are ours alone and 2462 the reference's alone, and the first number must be read by root: the aggregate includes candidate conformance differences, intentional execution-scope warnings on reference corpora and diagnostics from our own examples ([differential](docs/project/pilot-differential.md), `go run -C tools ./cmd/pilot-diff`). +- **Corpus agreement:** 519 of 921 files agree diagnostic-by-diagnostic; 359 diagnostics are ours alone and 2462 the reference's alone, and the first number must be read by root: the aggregate includes candidate conformance differences, intentional execution-scope warnings on reference corpora and diagnostics from our own examples ([differential](docs/project/pilot-differential.md), `go run -C tools ./cmd/pilot-diff`). - **Declared-diagnostic silence:** of the 514 declared `errors` rows in the reference's own Xpect suites, we report nothing for 0. 247 we report word-for-word; 248 wording-only and 7 location-only differences are agreement in substance and are not counted as gaps; 0 more we report as a warning and 2 elsewhere in the file ([Xpect oracle](docs/project/pilot-xpect.md), `go run -C tools ./cmd/pilot-xpect`). - **Scope agreement:** 230 of 230 declared scope assertions match exactly (same source). - **Permissiveness gaps:** of 349 invalid models we wrote ourselves, the reference rejects 5 that we accept by default, and 334 both reject; 5 further cases agree only when we are asked strictly. We authored every one of these cases ourselves, so the denominator measures the reach of our own corpus and not our conformance; agreement reached only under an opt-in strict mode is weaker evidence than agreement by default ([rejection oracle](docs/project/pilot-rejection.md), `go run -C tools ./cmd/pilot-reject`). -- **Declared errata:** the registry declares 13 defect(s) in the published reference material — 6 with a specification-derived correction, 7 documented without one, since no intended reading can be inferred ([OMG issues](docs/project/omg-issues.md), `tools/oracle/errata`). Every figure above is as published and stays the conformance statement; running the same oracles over the corrected text instead reports 519 of 921 files agreeing, 359 diagnostics ours alone and 2462 the reference's alone, 0 declared rows we are silent on, and 0 of 349 authored cases the reference alone rejects. The corrected figures are diagnostic only: an erratum never reclassifies a divergence category, and the published corpus is never edited. +- **Declared errata:** the registry declares 13 defect(s) in the published reference material — 6 with a specification-derived correction, 7 documented without one, since no intended reading can be inferred ([OMG issues](docs/project/omg-issues.md), `tools/oracle/errata`). Every figure above is as published and stays the conformance statement; running the same oracles over the corrected text instead reports 520 of 921 files agreeing, 358 diagnostics ours alone and 2462 the reference's alone, 0 declared rows we are silent on, and 0 of 349 authored cases the reference alone rejects. The corrected figures are diagnostic only: an erratum never reclassifies a divergence category, and the published corpus is never edited. - **Self-assessed surface:** the action, state-machine and classifier-behavior rows have no external referee at all — the four refereed figures above cannot see them, because the pinned artifact evaluates expressions but executes neither actions nor state machines. [Spec compliance](docs/project/spec-compliance.md) counts them. What these numbers cannot show: the OMG corpora are demonstrations rather than an official conformance suite; the differential is one-directional, comparing the diagnostics the two implementations report on the same files; the Xpect suites are the pilot authors' test intent rather than a certification oracle; and none of these is a percentage of the specification — no global compliance figure is claimed anywhere. @@ -364,7 +364,7 @@ What these numbers cannot show: the OMG corpora are demonstrations rather than a **Test coverage:** top-level `Test` functions (counted from the `_test.go` files, as `go test ./...` runs them) covering parsers, semantics, runtime (actions, states, instances, operators, validation), behind golden ASTs, negatives, execution conformance cases, golden traces, runtime robustness cases and gRPC conformance and robustness cases. The figures are counted from the tree when the documentation site is built into the test inventory of [spec compliance](docs/project/spec-compliance.md), never committed, so a branch adding a test does not rewrite this page. A test skips only for want of something the run did not provide, and says what: the held-image round trip declines a conformance case that creates no instance, a few gate on a PDF or Mermaid toolchain, a pinned pilot artifact, the PSSM suite, a locale, a case-insensitive filesystem or a live Flexo stack, and the OMG corpus gates skip until the corpora are downloaded unless asked to fail. **Parser coverage:** 110/110 bundled library files parse cleanly — the 94 official SysML v2 standard library files and the 16 non-normative OpenSysML extensions: `OpenSysML Libraries/AnalysisRecords.sysml`, `DiagramLayout.sysml`, `DocumentQueries.sysml`, `IdentityMetadata.sysml`, `MOSA.sysml`, `MigrationMetadata.sysml`, `OOSEM.sysml`, `OpenSysMLMathFunctions.kerml`, `OpenSysMLRenderings.sysml`, `RandomFunctions.kerml`, `Simulation.sysml`, `StateActivity.kerml`, `StateMachines.sysml`, `StateSpaceIntegration.sysml`, `Stochastic.sysml` and `SysMLValidation.sysml`. Conformance verified by [stdlib_conformance_test.go](internal/workspace/libs/stdlib_conformance_test.go). Grammar reference: [OMG Xtext grammar](https://github.com/Systems-Modeling/SysML-v2-Pilot-Implementation/tree/master/org.omg.kerml.xtext/src/org/omg/kerml/xtext). **Behavioral execution:** Calc/constraint/requirement/satisfy functional. Action/state executors handle nested invocation, control flow keywords, loop and conditional statements and the send statement (every conformance case passing). Coverage is self-assessed against the specification text and the normative library: the pinned OMG pilot implementation evaluates expressions but does not execute actions or state machines headlessly, so no external implementation currently adjudicates these rows. See [spec compliance](docs/project/spec-compliance.md). -**Reference differential:** 921 files compared diagnostic-by-diagnostic against the pinned OMG pilot implementation (`2026-09`), 518 in full agreement; every divergence is enumerated and adjudicated in [the differential](docs/project/pilot-differential.md), reproducible with `go run -C tools ./cmd/pilot-diff`. +**Reference differential:** 921 files compared diagnostic-by-diagnostic against the pinned OMG pilot implementation (`2026-09`), 519 in full agreement; every divergence is enumerated and adjudicated in [the differential](docs/project/pilot-differential.md), reproducible with `go run -C tools ./cmd/pilot-diff`. **Rejection oracle:** the reverse direction — do we reject what the reference rejects? 349 hand-written invalid models validated by both implementations, 339 rejected by both, 0 the pinned pilot rejects and we accept; the remainder only we reject — the control-node succession rules the pinned pilot leaves unimplemented and a non-Boolean succession guard it accepts once the standard library types it — and every permissiveness gap is enumerated with a reproducer and likely root cause in [the rejection oracle](docs/project/pilot-rejection.md), reproducible with `go run -C tools ./cmd/pilot-reject`. We wrote every case, so the count measures our coverage of the rejection surface, not our conformance — a sample, not a proof. **Training examples:** 100/100 files report no semantic errors, gated by `tests/corpus/testdata/training_examples_expected.txt`; the gate does not count execution-scope warnings. Download with `./scripts/download-training-examples.sh` (from the [OMG training directory](https://github.com/Systems-Modeling/SysML-v2-Pilot-Implementation/tree/master/sysml/src/training)). See [training examples](docs/project/training-examples.md) for analysis. **Semantic layer:** a complete implementation of runtime operators, feature chains and validation rules. See [examples/semantic-layer/](examples/semantic-layer/) for a full demonstration. diff --git a/changes/unreleased/expose-name-conflict.fixed.md b/changes/unreleased/expose-name-conflict.fixed.md new file mode 100644 index 0000000000..ddc45cf12f --- /dev/null +++ b/changes/unreleased/expose-name-conflict.fixed.md @@ -0,0 +1 @@ +- `name-conflict` no longer warns when the only memberships colliding under one name are those a view's `expose`s bring (`view v { expose a::**; expose b::**; }` with a `p` in both `a` and `b`): a view exposes each expose's own memberships, so both `p` are exposed and rendered. A collision a plain `import` or an inherited member takes part in is still reported. diff --git a/docs/internals/architecture.md b/docs/internals/architecture.md index 9e029e5c28..ec8e4d0edd 100644 --- a/docs/internals/architecture.md +++ b/docs/internals/architecture.md @@ -871,11 +871,11 @@ Every behavioral feature must have: **Measured against the pinned reference** (`PILOT_TAG=2026-09`, artifact `0.63.0`). Every number below is generated by `make docs-counts` from the committed baselines and gated; none of them is typed in by hand. -- **Corpus agreement:** 518 of 921 files agree diagnostic-by-diagnostic; 360 diagnostics are ours alone and 2462 the reference's alone, and the first number must be read by root: the aggregate includes candidate conformance differences, intentional execution-scope warnings on reference corpora and diagnostics from our own examples ([differential](../project/pilot-differential.md), `go run -C tools ./cmd/pilot-diff`). +- **Corpus agreement:** 519 of 921 files agree diagnostic-by-diagnostic; 359 diagnostics are ours alone and 2462 the reference's alone, and the first number must be read by root: the aggregate includes candidate conformance differences, intentional execution-scope warnings on reference corpora and diagnostics from our own examples ([differential](../project/pilot-differential.md), `go run -C tools ./cmd/pilot-diff`). - **Declared-diagnostic silence:** of the 514 declared `errors` rows in the reference's own Xpect suites, we report nothing for 0. 247 we report word-for-word; 248 wording-only and 7 location-only differences are agreement in substance and are not counted as gaps; 0 more we report as a warning and 2 elsewhere in the file ([Xpect oracle](../project/pilot-xpect.md), `go run -C tools ./cmd/pilot-xpect`). - **Scope agreement:** 230 of 230 declared scope assertions match exactly (same source). - **Permissiveness gaps:** of 349 invalid models we wrote ourselves, the reference rejects 5 that we accept by default, and 334 both reject; 5 further cases agree only when we are asked strictly. We authored every one of these cases ourselves, so the denominator measures the reach of our own corpus and not our conformance; agreement reached only under an opt-in strict mode is weaker evidence than agreement by default ([rejection oracle](../project/pilot-rejection.md), `go run -C tools ./cmd/pilot-reject`). -- **Declared errata:** the registry declares 13 defect(s) in the published reference material — 6 with a specification-derived correction, 7 documented without one, since no intended reading can be inferred ([OMG issues](../project/omg-issues.md), `tools/oracle/errata`). Every figure above is as published and stays the conformance statement; running the same oracles over the corrected text instead reports 519 of 921 files agreeing, 359 diagnostics ours alone and 2462 the reference's alone, 0 declared rows we are silent on, and 0 of 349 authored cases the reference alone rejects. The corrected figures are diagnostic only: an erratum never reclassifies a divergence category, and the published corpus is never edited. +- **Declared errata:** the registry declares 13 defect(s) in the published reference material — 6 with a specification-derived correction, 7 documented without one, since no intended reading can be inferred ([OMG issues](../project/omg-issues.md), `tools/oracle/errata`). Every figure above is as published and stays the conformance statement; running the same oracles over the corrected text instead reports 520 of 921 files agreeing, 358 diagnostics ours alone and 2462 the reference's alone, 0 declared rows we are silent on, and 0 of 349 authored cases the reference alone rejects. The corrected figures are diagnostic only: an erratum never reclassifies a divergence category, and the published corpus is never edited. - **Self-assessed surface:** the action, state-machine and classifier-behavior rows have no external referee at all — the four refereed figures above cannot see them, because the pinned artifact evaluates expressions but executes neither actions nor state machines. [Spec compliance](../project/spec-compliance.md) counts them. What these numbers cannot show: the OMG corpora are demonstrations rather than an official conformance suite; the differential is one-directional, comparing the diagnostics the two implementations report on the same files; the Xpect suites are the pilot authors' test intent rather than a certification oracle; and none of these is a percentage of the specification — no global compliance figure is claimed anywhere. diff --git a/docs/project/pilot-differential-baseline.json b/docs/project/pilot-differential-baseline.json index d0e90c3d6a..3279944e68 100644 --- a/docs/project/pilot-differential-baseline.json +++ b/docs/project/pilot-differential-baseline.json @@ -86,16 +86,16 @@ "digest": "sha256:ac6a1e3b3376227415fa16aec5bf1918312a69cad0d8d63b6adcda799a44c9b3" } ], - "recorded": "2026-10-27" + "recorded": "2026-10-28" }, "totals": { "files": 921, - "filesFullyAgreeing": 518, + "filesFullyAgreeing": 519, "agreement": 1098, "severityMismatch": 11, - "openSysMLOnly": 360, + "openSysMLOnly": 359, "pilotOnly": 2462, - "openSysMLDiagnostics": 1469, + "openSysMLDiagnostics": 1468, "pilotDiagnostics": 3571 }, "roots": [ @@ -17882,12 +17882,12 @@ "dir": "examples", "totals": { "files": 53, - "filesFullyAgreeing": 45, + "filesFullyAgreeing": 46, "agreement": 0, "severityMismatch": 0, - "openSysMLOnly": 9, + "openSysMLOnly": 8, "pilotOnly": 65, - "openSysMLDiagnostics": 9, + "openSysMLDiagnostics": 8, "pilotDiagnostics": 65 }, "files": [ @@ -17905,20 +17905,6 @@ } ] }, - { - "path": "general-views-demo/vehicle.sysml", - "agreement": [], - "severityMismatch": [], - "openSysMLOnly": [ - { - "line": 102, - "severity": "warning", - "category": "unmapped", - "count": 1 - } - ], - "pilotOnly": [] - }, { "path": "gridview-relationship-matrix.sysml", "agreement": [], @@ -18029,7 +18015,7 @@ "openSysMLOnly": [ { "line": 16, - "severity": "error", + "severity": "warning", "category": "unresolved-reference", "count": 1 } @@ -18769,11 +18755,6 @@ "message": "Duplicate of imported member name 'mass': SimpleVehicleModel::VehicleConfigurations::VehicleConfiguration_b::PartsTree::vehicle_b::mass (import VehicleConfigurations::VehicleConfiguration_b::PartsTree::**), SimpleVehicleModel::VehicleConfigurations::VehicleConfiguration_b::PartsTree::vehicle_b::fuelTank::mass (import VehicleConfigurations::VehicleConfiguration_b::PartsTree::**), SimpleVehicleModel::VehicleConfigurations::VehicleConfiguration_b::PartsTree::vehicle_b::frontAxleAssembly::mass (import VehicleConfigurations::VehicleConfiguration_b::PartsTree::**), SimpleVehicleModel::VehicleConfigurations::VehicleConfiguration_b::PartsTree::vehicle_b::rearAxleAssembly::mass (import VehicleConfigurations::VehicleConfiguration_b::PartsTree::**), SimpleVehicleModel::VehicleConfigurations::VehicleConfiguration_b::PartsTree::vehicle_b::transmission::mass (import VehicleConfigurations::VehicleConfiguration_b::PartsTree::**), SimpleVehicleModel::VehicleConfigurations::VehicleConfiguration_b::PartsTree::vehicle_b::driveshaft::mass (import VehicleConfigurations::VehicleConfiguration_b::PartsTree::**)", "count": 1 }, - { - "side": "opensysml", - "message": "Duplicate of imported member name 'mass': VehicleStructure::Vehicle::mass (import all VehicleStructure::**), VehicleStructure::vehicle::mass (import all VehicleStructure::**), VehicleStructure::heavyVehicle::mass (import all VehicleStructure::**)", - "count": 1 - }, { "side": "opensysml", "message": "Duplicate of imported member name 'serviceDiscovery': AHFCoreLib::ArrowheadCore::service_registry::serviceDiscovery (import AHFCoreLib::**), AHFCoreLib::ArrowheadCore::service_registry_DD::serviceDiscovery (import AHFCoreLib::**)", @@ -25473,12 +25454,12 @@ ], "totals": { "files": 921, - "filesFullyAgreeing": 519, + "filesFullyAgreeing": 520, "agreement": 1098, "severityMismatch": 11, - "openSysMLOnly": 359, + "openSysMLOnly": 358, "pilotOnly": 2462, - "openSysMLDiagnostics": 1468, + "openSysMLDiagnostics": 1467, "pilotDiagnostics": 3571 }, "findings": [ diff --git a/docs/project/pilot-differential.md b/docs/project/pilot-differential.md index d092fc99bf..ff5d2f80e5 100644 --- a/docs/project/pilot-differential.md +++ b/docs/project/pilot-differential.md @@ -255,9 +255,9 @@ nor double-counted as two independent disagreements. | `examples/pilot-corpora/kerml-examples` | 58 | 55 | 16 | 0 | 0 | 0 | 16 | 0 | | `examples/spec-examples` | 511 | 151 | 1091 | 3395 | 1038 | 6 | 47 | 2351 | | `tests/testdata` | 40 | 25 | 78 | 101 | 60 | 5 | 13 | 36 | -| `examples` | 53 | 45 | 9 | 65 | 0 | 0 | 9 | 65 | +| `examples` | 53 | 46 | 8 | 65 | 0 | 0 | 8 | 65 | | `tools/referee/diff/testdata` (probes) | 4 | 1 | 6 | 0 | 0 | 0 | 6 | 0 | -| **Total** | **921** | **518** | **1469** | **3571** | **1098** | **11** | **360** | **2462** | +| **Total** | **921** | **519** | **1468** | **3571** | **1098** | **11** | **359** | **2462** | **Read the `only ours` total by root, never as one number.** Step 2 removes nine resolver false positives from the reference's **own** corpora: `pilot-examples` 16 → **7** and @@ -304,7 +304,7 @@ errors — the pair sits in severity-only rather than either side changing what Per category, the only-ours totals are: `training` 1 `multiplicity`; `pilot-examples` 227 `unmapped`, 26 `unresolved-reference`, 2 `units`, 1 `kind-mismatch`, 1 `multiplicity`; `pilot-validation` 8 `unmapped`, 3 `unresolved-reference`; `kerml-examples` 13 `unmapped`, 3 `unresolved-reference`; `testdata` 10 `unmapped`, -1 `unresolved-reference`, 2 `multiplicity`; `examples` 5 +1 `unresolved-reference`, 2 `multiplicity`; `examples` 4 `unmapped`, 1 `kind-mismatch`, 2 `multiplicity`, 1 `unresolved-reference`; `probes` 6 `unmapped`; `spec-examples` 7 `syntax`, 38 `unresolved-reference`, 2 `unmapped`. Only-pilot: `testdata` 14 `kind-mismatch`, 17 `unmapped`, 3 `syntax`, 2 `unresolved-reference`; `pilot-examples` 10 `kind-mismatch`; `examples` 24 `unmapped`, 41 `kind-mismatch`; `spec-examples` 1359 `unmapped`, @@ -1239,11 +1239,11 @@ page's history. | Count | Now | |---|---:| -| overall: fully agreeing / only ours / our diagnostics | **518 / 360 / 1469** | +| overall: fully agreeing / only ours / our diagnostics | **519 / 359 / 1468** | | only pilot | **2462** | | pilot diagnostics | **3571** | | severity-only | **11** | -| unmapped, our side | **288** | +| unmapped, our side | **287** | | kerml-examples: only ours | **16** | | pilot-examples: only ours | **257** | | examples: only pilot | **65** | @@ -3271,6 +3271,23 @@ counts ([pilot-corpora.md](pilot-corpora.md), "Imported memberships the specific | `kerml-examples`: fully agreeing / only ours | 55 / 13 | **55 / 16** | | `testdata`: files / only ours | 22 / 11 | **22 / 12** | +### Expose-only collisions round + +A collision among the memberships a view's `expose`s bring is no longer warned: SysML +`ViewUsage::exposedElement` reads each Expose's own `importedMemberships`, so such a collision never +changes what the view exposes, and KerML 8.3.2.4.5 already leaves the colliding memberships out of +`membership`, the set `validateNamespaceDistinguishibility` ranges over. The memberships stay hidden +from names written in the view; a collision a plain `import` or an inherited member takes part in is +still warned. One row retires: `general-views-demo/vehicle.sysml:102`, where the single recursive +`expose VehicleStructure::**[…]` of `definitionView` reaches `Vehicle::mass`, `vehicle::mass` and +`heavyVehicle::mass`. The Annex A rows above come through recursive `import`s and stay. Nothing +else moves. + +| Count | Before | Now | +|---|---:|---:| +| overall: fully agreeing / only ours / our diagnostics | 518 / 360 / 1469 | **519 / 359 / 1468** | +| `examples`: fully agreeing / only ours | 45 / 9 | **46 / 8** | + ## Current branch movement and adjudications The settled control is a clean run of `466de743cbd46eaa6983fd8cf0cffc4097a2137f`, diff --git a/docs/project/spec-compliance.md b/docs/project/spec-compliance.md index aa831d037b..b5731630c1 100644 --- a/docs/project/spec-compliance.md +++ b/docs/project/spec-compliance.md @@ -2233,7 +2233,7 @@ semantics layer over the conjugation parity of the typing/specialization chain. | Implicit redefinition of behavior/step parameters by position (`out item image;` in `action focus : Focus` redefines `Focus::image`), KerML 7.4.7.2/7.4.7.3, SysML v2 7.17.2 | `semantics/redefinition.go` `Model.ImplicitParameterRedefinitions`, `Model.parametersOf`, reached from `semantics/model.go` `DirectSupertypes` | `semantics/redefinition_test.go`, `model/implicit_typing_test.go` `TestParameterRedefinitionAccompaniesTheImplicitBase`, `TestImplicitRedefinitionSuppliesInheritedMembers`, `passes/typecheck_expr_test.go` `TestExprRedeclaredParametersMatchByPositionNotName` (the invocation signature matches by the same rule) | ✅ Faithful (owned parameters in lexical order redefine the parameter at the same position of each general behavior or step, matching direction; parameters a single general behavior leaves un-redefined are inherited after the owned ones; the kind's standard library base still applies alongside the redefinition, since the redefined parameter may itself be untyped) | | Implicit redefinition of a result parameter (`return` redefines the general calculation's result whatever its position), SysML v2 7.19.2 | `semantics/redefinition.go` `Model.ImplicitParameterRedefinitions` (`ast.Usage.IsResult`, set by `parser/behavior.go` `parseResultMember`) | `semantics/redefinition_test.go` `TestImplicitResultParameterRedefinition` | ✅ Faithful | | A nested usage that is *not* a parameter and shares a name with an inherited feature | `resolve/distinguishability.go` `Resolver.checkInheritedNames`, `Resolver.checkInheritedAmbiguity`, `resolve/document.go` `ParameterizedByName`, surfaced by `passes/nameres.go` (code `name-conflict`; a warning by default, an error under strict conformance); the usage still only gets the standard library base of its kind from `semantics/implicit.go` | `passes/nameres_test.go` `TestNameResolutionPassReportsInheritedNameConflict`, `TestRedeclaredInheritedNameIsNoConflictWhenRedefined`, `TestInheritedNameConflictExemptsRedefiningFeatures`, `TestDistinctNestedNameIsNoConflict`, `model/implicit_typing_test.go` `TestLikeNamedUsageIsNotAnImplicitRedefinition`, `resolve/inherited_names_test.go` `TestRedeclaringAnInheritedNameConflictsUnlessItRedefines`, `TestRequirementParametersAreNotNameConflicts`, `TestRedefinitionTargetFoundTwoSupertypesUp` (the target is searched up the whole specialization chain, not only the direct supertype) | ⚠️ Approximate (SysML v2 7.6.1 and KerML 7.3.2.1 make this a name conflict, reported at the name-resolution tier where inheritance is known; a feature that redefines what it shares the name with — explicitly, or implicitly by parameter position — does not conflict. The subject, actors and stakeholders of a case or requirement redefine the inherited ones by name (SysML v2 §7.21.2, §7.22.2), which is not modelled and is not distinguishable from an ordinary feature at this tier, so the rule is not applied inside a case or requirement body at all (a concern and a viewpoint are requirements too) — a genuine conflict on an ordinary feature there goes unreported too. **Adjudicated: the rule and its severity now match the reference.** It is *name distinguishability*, reported as a **warning**, in the reference's own wording (`Duplicate of other owned member name`, `Duplicate of owned member name`, `Duplicate of other alias name`, `Duplicate of inherited member name`), over owned-vs-owned, alias-vs-owned, alias-vs-alias, owned-vs-inherited and inherited-vs-inherited pairs, skipping imports, constructed expressions and binding connectors: `resolve/distinguishability.go`, `resolve/inherited_names_test.go`, `passes/nameres_test.go:TestNameResolutionPassReportsInheritedNameConflict`. A name two supertypes contribute is reported on the subtype itself (`:TestNameInheritedFromTwoSupertypesIsReportedOnTheSubtype`), a feature an intermediate supertype redefines is still inherited under its name by that supertype's own subtypes (`:TestNameRedefinedByAnIntermediateSupertypeStillConflicts`), and a parameter arriving twice because one owner specializes the other is one feature, so it stays silent (`:TestParameterInheritedThroughItsOwnSupertypeIsNotAmbiguous`, against the warning its unrelated-supertype twin draws in `:TestParameterInheritedFromTwoUnrelatedSupertypesIsAmbiguous`) — each matched against the pinned validator. Members inherited from *library* supertypes are outside this rule; a separate pass covers them, `passes/w9c_inherited_name_conflict.go`. **Adjudicated:** a supertype's non-private *imported* memberships are inherited too, as KerML 8.3.3.1 and 8.4.3.2 read together require — `Resolver.importedMembers`, `resolve/inherited_names_test.go:TestNameImportedByASupertypeIsInherited`). Two members whose member elements' metaclasses conform in neither direction are distinguishable whatever their names (KerML 8.3.2.4.3 `Membership::isDistinguishableFrom`), so `part def A; attribute def A;` is clean where the pinned pilot still warns ([gap register #13](spec-pilot-gap-register.md#13-indistinguishable-memberships-severity-and-anonymous-performed-actions)) | -| Imported memberships that collide by name (two wildcard imports each bringing a `part def Engine`; a recursive `import V::**` reaching two nested `vb`; two `` short names; an imported member repeating one the type inherits), KerML 7.2.5.4 and 8.3.2.4.5 `Namespace::importedMemberships`: a membership whose name or short name conflicts with another imported one is hidden from the importing namespace — not a member of it, so an unqualified reference to the name is looked up in the enclosing namespaces and is unresolved where none has it (default and strict mode), a qualified name into the importing namespace does not reach it, a namespace re-exporting both imports brings none, and a specialization finds none through its general's protected imports; one element reached twice (one membership through two imports, an alias beside its target, a membership import beside a wildcard) is not a collision, an owned member of the name hides the imports and silences the warning, and an imported member repeating an inherited one is warned but not hidden, at the type or in its specializations: the inherited member resolves there and a specialization inherits both and is warned in turn | `resolve/distinguishability.go` `Resolver.importedCollisions` (the collision set, memoized per namespace, read by `Resolver.checkImportedNames` for the warning and by `hiddenImport`/`withoutHiddenImports` in `resolve/unqualified.go` `lookupImports`, `resolve/visibility.go` `lookupInheritedImports`, `resolve/qualified.go` and `resolve/filter.go` `namespaceChildren`, `AdmittedChildrenOf`, `AdmittedTopLevel` and `ImportedElements` for lookup, re-export and enumeration, so completion and `VisibleNames` offer no hidden name; `withoutHiddenImports` keeps an element another import of the namespace actually brings under the name by a route that does not hide it — what each import surfaces is decided by `eachImportMatch`, so a recursive import brings only what its subtree holds and an unrelated one revives nothing), surfaced by `passes/nameres.go` (code `name-conflict`, warning, once per name on the import bringing the later membership) and by the unresolved-reference error, whose `Resolver.UnresolvedName` names the hiding imports and the qualified names (`hiddenImportHint`); `Resolver.InvocationCandidates` reads a called name through the same collision set, so two imported `calc def pick` leave `pick(2)` unresolved with the hint and a hidden `Engine` qualifies nothing in `Engine::build()` (see "Invocation overload selection"); library content is left out of the computation, as the warning always left it | `tests/resolve/hidden_imports_test.go` (`TestHiddenImportedMemberships` — root, package, same element through two paths, alias, owned member, protected imports, re-export, enclosing scope, `Use::Engine`; `TestHiddenImportedMembershipsQualifiedNamesStay`, `TestHiddenImportedMembershipsShortName`, `TestHiddenImportedMembershipsRecursiveImport`, `TestInheritedAndImportedMembershipsHideNothing`, `TestHiddenImportedMembershipsOverloadSet`, `tests/resolve/inherited_names_test.go` `TestHiddenImportsOfAGeneralAreNotInherited`, `TestImportsCollidingWithInheritedNamesAreInherited`, `TestHiddenImportedMembershipsAcrossDocuments`, `TestHiddenImportedMembershipsQualifiedThroughReexport`, `TestHiddenImportedMembershipsQualifierInCall`, `TestHiddenImportedMembershipsBesideDirectImport`), `lsp/completion_test.go` (`TestCompletionOmitsHiddenRootImports`, `TestCompletionOmitsHiddenQualifiedMembers`, `TestCompletionOffersDirectImportBesideHidingReexport`), `model/scope_names_test.go` `TestVisibleNamesOmitHiddenImports`, `cmd/sysml/hidden_imports_test.go` (`TestValidateHiddenImportedName` default and strict exit status, `TestValidateHiddenImportedNameThroughReexport`), `resolve/distinguishability_test.go` `TestImportedMemberNamesIndistinguishable`, `TestImportedMemberNamesRecursiveImport`, `TestImportedMemberNamesDistinguishable`, `TestImportedMemberNamesShortNameAndImportAll`, `TestImportedMemberNamesOwnedRepeatAcrossDocuments`, `TestImportedMemberNamesAliasUnderAnotherName`, `TestImportedMemberNamesNestedInSameNamedTypes`; `passes/imported_name_conflict_test.go` `TestImportedNameConflicts`; `resolve/integration_test.go` `TestResolveGoldenImports` | ✅ Faithful for model content; the pilot binds the first import's member and reports nothing (see [spec-pilot-gap-register.md](spec-pilot-gap-register.md) §13). Deliberate readings: the standard library's own colliding pairs (`import ISQ::*`) are read first-match as the library itself relies on | +| Imported memberships that collide by name (two wildcard imports each bringing a `part def Engine`; a recursive `import V::**` reaching two nested `vb`; two `` short names; an imported member repeating one the type inherits), KerML 7.2.5.4 and 8.3.2.4.5 `Namespace::importedMemberships`: a membership whose name or short name conflicts with another imported one is hidden from the importing namespace — not a member of it, so an unqualified reference to the name is looked up in the enclosing namespaces and is unresolved where none has it (default and strict mode), a qualified name into the importing namespace does not reach it, a namespace re-exporting both imports brings none, and a specialization finds none through its general's protected imports; one element reached twice (one membership through two imports, an alias beside its target, a membership import beside a wildcard) is not a collision, an owned member of the name hides the imports and silences the warning, and an imported member repeating an inherited one is warned but not hidden, at the type or in its specializations: the inherited member resolves there and a specialization inherits both and is warned in turn; a collision among a view's exposes alone is hidden but not warned, as SysML `ViewUsage::exposedElement` reads each Expose's own memberships and still exposes every one | `resolve/distinguishability.go` `Resolver.importedCollisions` (the collision set, memoized per namespace, read by `Resolver.checkImportedNames` for the warning and by `hiddenImport`/`withoutHiddenImports` in `resolve/unqualified.go` `lookupImports`, `resolve/visibility.go` `lookupInheritedImports`, `resolve/qualified.go` and `resolve/filter.go` `namespaceChildren`, `AdmittedChildrenOf`, `AdmittedTopLevel` and `ImportedElements` for lookup, re-export and enumeration, so completion and `VisibleNames` offer no hidden name; `withoutHiddenImports` keeps an element another import of the namespace actually brings under the name by a route that does not hide it — what each import surfaces is decided by `eachImportMatch`, so a recursive import brings only what its subtree holds and an unrelated one revives nothing), surfaced by `passes/nameres.go` (code `name-conflict`, warning, once per name on the import bringing the later membership) and by the unresolved-reference error, whose `Resolver.UnresolvedName` names the hiding imports and the qualified names (`hiddenImportHint`); `Resolver.InvocationCandidates` reads a called name through the same collision set, so two imported `calc def pick` leave `pick(2)` unresolved with the hint and a hidden `Engine` qualifies nothing in `Engine::build()` (see "Invocation overload selection"); library content is left out of the computation, as the warning always left it | `tests/resolve/hidden_imports_test.go` (`TestHiddenImportedMemberships` — root, package, same element through two paths, alias, owned member, protected imports, re-export, enclosing scope, `Use::Engine`; `TestHiddenImportedMembershipsQualifiedNamesStay`, `TestHiddenImportedMembershipsShortName`, `TestHiddenImportedMembershipsRecursiveImport`, `TestInheritedAndImportedMembershipsHideNothing`, `TestHiddenImportedMembershipsOverloadSet`, `tests/resolve/inherited_names_test.go` `TestHiddenImportsOfAGeneralAreNotInherited`, `TestImportsCollidingWithInheritedNamesAreInherited`, `TestHiddenImportedMembershipsAcrossDocuments`, `TestHiddenImportedMembershipsQualifiedThroughReexport`, `TestHiddenImportedMembershipsQualifierInCall`, `TestHiddenImportedMembershipsBesideDirectImport`), `lsp/completion_test.go` (`TestCompletionOmitsHiddenRootImports`, `TestCompletionOmitsHiddenQualifiedMembers`, `TestCompletionOffersDirectImportBesideHidingReexport`), `model/scope_names_test.go` `TestVisibleNamesOmitHiddenImports`, `cmd/sysml/hidden_imports_test.go` (`TestValidateHiddenImportedName` default and strict exit status, `TestValidateHiddenImportedNameThroughReexport`), `resolve/distinguishability_test.go` `TestImportedMemberNamesIndistinguishable`, `TestImportedMemberNamesRecursiveImport`, `TestImportedMemberNamesDistinguishable`, `TestImportedMemberNamesShortNameAndImportAll`, `TestImportedMemberNamesOwnedRepeatAcrossDocuments`, `TestImportedMemberNamesAliasUnderAnotherName`, `TestImportedMemberNamesNestedInSameNamedTypes`; `passes/imported_name_conflict_test.go` `TestImportedNameConflicts`; `tests/resolve/expose_name_conflict_test.go` `TestExposedCollisionsAreNoNameConflict`, `TestExposedCollisionsBesideImportsAreNameConflicts`; `resolve/integration_test.go` `TestResolveGoldenImports` | ✅ Faithful for model content; the pilot binds the first import's member and reports nothing (see [spec-pilot-gap-register.md](spec-pilot-gap-register.md) §13). Deliberate readings: the standard library's own colliding pairs (`import ISQ::*`) are read first-match as the library itself relies on | | Effective name of an unnamed redefining feature (`in item;` in `action shoot : Shoot` is named `image`), KerML 7.3.4.5, SysML v2 7.6.5 | `symbols/builder.go` `effectiveIdent` for a declared redefinition; for an implicit one `resolve/unqualified.go` `Resolver.implicitlyNamedMember` (with `impliesNamingFeature`), reached from `walkUnqualifiedHiding` and `resolve/target.go` `memberChain`, over `symbols/scope.go` `Scope.AnonymousMembers` and `semantics/model.go` `DirectSupertypes` | `passes/nameres_test.go` `TestImplicitlyRedefiningParameterBindsRedefinedName`, `TestImplicitlyRedefiningParameterDoesNotBindItsKeyword`, `symbols/builder_test.go` `TestUnnamedRedefinitionTakesRedefinedName` | ✅ Faithful (the name is bound in the owning scope, so a sibling resolves it by simple name; an implicit redefinition's target is known only to the semantic model, so that binding is resolved lazily rather than when scopes are built) | | Implicit redefinition of connection/association ends by position (`connection : PressureSeat connect bead references t.bead to ...` redefines `PressureSeat::bead`), SysML v2 7.13.2, 7.14.2, KerML 7.4.6 | `semantics/connector.go` `Model.implicitEndRedefinitions`, `Model.endsOf`, reached from `semantics/model.go` `DirectSupertypes`; the ends themselves are declared by `symbols/builder.go` `buildConnectorEnds` (`ast.ConnectorEnd.DeclaredName`) and the arity check is `passes/constraint.go` `checkConnectorEndRedefinition` (`semantic/semantics/connector.go` `Model.UnmatchedConnectorEnds`) | `semantics/connector_test.go` (including `TestImplicitEndRedefinitionOfAssociationUsage`), `model/connector_ends_test.go` `TestConnectorEndNamesResolve`, `TestConnectorEndArityMismatch` | ✅ Faithful (an end of a `connect` clause that reference-subsets what it attaches to declares an end of the connector, in lexical order, and redefines the end at the same position of each connector the usage specializes; positions count every end of the clause, including one that only names what it attaches to; an explicit `:>>` adds the ends it names to that positional redefinition rather than replacing it, so an end that names `BinaryLinkObject::source` still masks its general's end at its own position, and an end past the general connector's last position is reported. Connection, interface, allocation, flow, succession, association and binding declarations are matched — a general whose own ends are not enumerable, such as an unparsed library type, suppresses the arity check rather than reporting) | | Reference subsetting contributes members (`perform action takePhoto references takePicture;`, `perform providePower.generateTorque;`) | `semantics/reference.go` `Model.ReferencedFeature`, `Model.MemberSources`, consumed by `semantics/members.go` `MembersOf`/`LookupMember`; targets resolved by `resolve/target.go` `ResolveTarget`/`ResolveReferenceTarget` | `semantics/reference_test.go`, `resolve/target_test.go`, `model/perform_reference_test.go`, `parse/perform_reference.golden`, `runtime/testdata/conformance/action_perform_reference.sysml`, `runtime/robustness_perform_references_test.go` (`perform_of_missing_action`, `perform_reference_cycle`) | ✅ Faithful (a member-contribution relation, deliberately **not** a generalization — see below) | diff --git a/docs/reference/diagnostics.md b/docs/reference/diagnostics.md index 3cbda0e026..1524af81ca 100644 --- a/docs/reference/diagnostics.md +++ b/docs/reference/diagnostics.md @@ -86,7 +86,11 @@ inherits or one import brings still selects by signature. An imported name hidden by an owned member of the same name, one membership reached through two imports (`import P::*` beside `import Q::*` where `Q` publicly re-imports `P`), an alias or a membership import of an element beside the membership that owns it, and the standard library's -own members are not reported, and none of these is hidden. An alias under another name (`alias Spare for P::x`) binds only +own members are not reported, and none of these is hidden. Nor is a collision among a view's +`expose`s alone (`view v { expose a::**; expose b::**; }` with a `p` in each): the names are +still hidden inside the view, but a view exposes each expose's own memberships (SysML +`ViewUsage::exposedElement`), so both `p` are exposed and rendered; a plain `import` or an +inherited member taking part in the collision is reported as ever. An alias under another name (`alias Spare for P::x`) binds only that name and does not stand in for `P::x`. The standard library has eight such pairs of its own, which `import ISQ::*` brings in without a diff --git a/internal/check/passes/imported_name_conflict_test.go b/internal/check/passes/imported_name_conflict_test.go index ad7bd61a3c..e03469066d 100644 --- a/internal/check/passes/imported_name_conflict_test.go +++ b/internal/check/passes/imported_name_conflict_test.go @@ -41,6 +41,11 @@ func TestImportedNameConflicts(t *testing.T) { []string{"3: Duplicate of imported member name 'x': A::x (import A::*), Base::x (inherited)"}}, // An owned redefinition hides both the inherited and the imported name. {"a.sysml", "package A { part x; }\npart def Base { part x; }\npart def Child :> Base { private import A::*; part x :>> x; }", nil}, + // A view's exposes colliding only among themselves are no conflict; + // a plain import beside them is. + {"a.sysml", "package V {\npart a { part p; }\npart b { part p; }\nview v { expose a::**; expose b::**; }\n}", nil}, + {"a.sysml", "package V {\npart a { part p; }\npart b { part p; }\nview v { private import a::*; expose b::*; }\n}", + []string{"4: Duplicate of imported member name 'p': V::a::p (import a::*), V::b::p (import all b::*)"}}, // Metaclasses that conform in neither direction are distinguishable here too. {"a.sysml", "package A { attribute x; }\npart def Base { part x; }\npart def Child :> Base { private import A::*; }", nil}, } { diff --git a/internal/semantic/resolve/distinguishability.go b/internal/semantic/resolve/distinguishability.go index 2ac38de1b5..3dfe07e5c1 100644 --- a/internal/semantic/resolve/distinguishability.go +++ b/internal/semantic/resolve/distinguishability.go @@ -26,11 +26,12 @@ func (r *Resolver) checkDistinguishability(scope *symbols.Scope) { r.checkImportedNames(scope) } -// importedMember is one membership an import surfaces into a namespace, with -// the import that brought it, or one the namespace inherits (imp nil). +// importedMember is a membership an import surfaces (imp nil when inherited); +// exposedOnly means every import reaching it is an Expose. type importedMember struct { - sym *symbols.Symbol - imp *ast.Import + sym *symbols.Symbol + imp *ast.Import + exposedOnly bool } // ownedName is a name among one namespace's own members. A repeat of it there @@ -109,13 +110,24 @@ func (r *Resolver) checkImportedNames(scope *symbols.Scope) { } } } - if len(kept) < 2 || imported == 0 { + if len(kept) < 2 || imported == 0 || exposedOnly(kept) { continue } r.duplicateImported(name, kept) } } +// exposedOnly reports whether every colliding membership comes through an +// Expose: a view exposes each Expose's own memberships (SysML ViewUsage::exposedElement). +func exposedOnly(members []importedMember) bool { + for _, member := range members { + if !member.exposedOnly { + return false + } + } + return true +} + // importCollisions is what the imports of one namespace bring, by name, and // the memberships KerML 7.2.5.4 hides among them: distinct elements two or // more imports bring under one name or short name, indistinguishable by @@ -218,7 +230,7 @@ func (r *Resolver) ownedNames(scope *symbols.Scope) []string { // be settled yet. func (r *Resolver) collectImported(scope *symbols.Scope, imports []*ast.Import) (*importCollisions, bool) { complete := r.inCondition == 0 - g := &importedGather{owned: map[string]bool{}, seen: map[boundName]bool{}, owners: map[ownedName]bool{}} + g := &importedGather{owned: map[string]bool{}, seen: map[boundName]int{}, owners: map[ownedName]bool{}} for _, name := range r.ownedNames(scope) { g.owned[name] = true } @@ -250,7 +262,7 @@ func (r *Resolver) collectImported(scope *symbols.Scope, imports []*ast.Import) // scope owns, and the memberships and owner-name pairs already gathered. type importedGather struct { owned map[string]bool - seen map[boundName]bool + seen map[boundName]int owners map[ownedName]bool } @@ -268,15 +280,21 @@ func (r *Resolver) gatherImported(scope *symbols.Scope, imp *ast.Import, g *impo } bound := boundName{key: key, name: name} owner := ownedName{owner: ownerKeyOf(sym.OwnerScope), name: name} - if g.owned[name] || g.seen[bound] || g.owners[owner] { + if i, ok := g.seen[bound]; ok { + if !imp.IsExpose { + c.byName[name][i].exposedOnly = false + } + continue + } + if g.owned[name] || g.owners[owner] { continue } - g.seen[bound] = true + g.seen[bound] = len(c.byName[name]) g.owners[owner] = true if _, ok := c.byName[name]; !ok { c.names = append(c.names, name) } - c.byName[name] = append(c.byName[name], importedMember{sym: sym, imp: imp}) + c.byName[name] = append(c.byName[name], importedMember{sym: sym, imp: imp, exposedOnly: imp.IsExpose}) } } } diff --git a/tests/resolve/expose_name_conflict_test.go b/tests/resolve/expose_name_conflict_test.go new file mode 100644 index 0000000000..feaaf1001c --- /dev/null +++ b/tests/resolve/expose_name_conflict_test.go @@ -0,0 +1,57 @@ +package resolve_test + +import ( + "testing" + + "github.com/Open-MBEE/OpenSysML/internal/semantic/semantics" + "github.com/Open-MBEE/OpenSysML/internal/semantic/symbols" +) + +const exposedParts = "package V {\n\tpart a { part p; }\n\tpart b { part p; }\n" + +// A collision among a view's exposes alone is no name conflict: the view exposes +// each Expose's own memberships (SysML ViewUsage::exposedElement). +func TestExposedCollisionsAreNoNameConflict(t *testing.T) { + r, _, scope := resolvedDoc(t, exposedParts+"\tview v { expose a::**; expose b::**; }\n}") + if warnings, errors := diagnosticsOf(r); len(warnings)+len(errors) != 0 { + t.Fatalf("warnings = %v, errors = %v, want none", warnings, errors) + } + view := local(t, local(t, scope, "V").Scope, "v") + exposed, err := semantics.NewModel(r).ExposedMembers(view) + if err != nil { + t.Fatal(err) + } + got := map[string]bool{} + for _, sym := range exposed { + got[symbols.FQNOf(sym)] = true + } + for _, want := range []string{"V::a::p", "V::b::p"} { + if !got[want] { + t.Errorf("%s is not exposed; exposed = %v", want, got) + } + } +} + +// A collision that a plain import or an inherited member takes part in is still +// reported in a view, as it changes what a name written there resolves to. +func TestExposedCollisionsBesideImportsAreNameConflicts(t *testing.T) { + cases := map[string]struct{ src, want string }{ + "import and expose": {exposedParts + "\tview v { private import a::*; expose b::*; }\n}", + "Duplicate of imported member name 'p': V::a::p (import a::*), V::b::p (import all b::*)"}, + "import of an exposed namespace": {exposedParts + "\tview v { expose a::*; expose b::*; private import a::*; }\n}", + "Duplicate of imported member name 'p': V::a::p (import all a::*), V::b::p (import all b::*)"}, + "inherited member": {"package V {\n\tpart a { part p; }\n\tview def VD { part p; }\n\tview v : VD { expose a::*; }\n}", + "Duplicate of imported member name 'p': V::a::p (import all a::*), V::VD::p (inherited)"}, + "two imports in a package": {exposedParts + "\tpackage U { private import a::*; private import b::*; }\n}", + "Duplicate of imported member name 'p': V::a::p (import a::*), V::b::p (import b::*)"}, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + r, _, _ := resolvedDoc(t, tc.src) + warnings, errors := diagnosticsOf(r) + if len(warnings) != 1 || warnings[0] != tc.want || len(errors) != 0 { + t.Errorf("warnings = %v, errors = %v, want %q", warnings, errors, tc.want) + } + }) + } +}