Coverage Report

Created: 2026-06-30 06:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/WasmEdge/lib/validator/component_validator.cpp
Line
Count
Source
1
// SPDX-License-Identifier: Apache-2.0
2
// SPDX-FileCopyrightText: Copyright The WasmEdge Authors
3
4
#include "common/errinfo.h"
5
#include "common/spdlog.h"
6
#include "validator/component_name.h"
7
#include "validator/validator.h"
8
9
#include <algorithm>
10
#include <unordered_set>
11
#include <variant>
12
13
namespace WasmEdge {
14
namespace Validator {
15
16
using namespace std::literals;
17
18
namespace {
19
0
std::string toLowerStr(std::string_view SV) {
20
0
  std::string Result(SV);
21
0
  std::transform(
22
0
      Result.begin(), Result.end(), Result.begin(),
23
0
      [](unsigned char C) { return static_cast<char>(std::tolower(C)); });
24
0
  return Result;
25
0
}
26
27
// Maps a component-side ExternDesc::DescType to its Sort::SortType.
28
// Returns nullopt for `CoreType` (= `(core module (type i))`), which has no
29
// representation in Sort::SortType; callers must handle that sort separately
30
// via Sort::CoreSortType::Module.
31
std::optional<AST::Component::Sort::SortType>
32
0
descTypeToSortType(AST::Component::ExternDesc::DescType DT) noexcept {
33
0
  switch (DT) {
34
0
  case AST::Component::ExternDesc::DescType::CoreType:
35
0
    return std::nullopt;
36
0
  case AST::Component::ExternDesc::DescType::FuncType:
37
0
    return AST::Component::Sort::SortType::Func;
38
0
  case AST::Component::ExternDesc::DescType::ValueBound:
39
0
    return AST::Component::Sort::SortType::Value;
40
0
  case AST::Component::ExternDesc::DescType::TypeBound:
41
0
    return AST::Component::Sort::SortType::Type;
42
0
  case AST::Component::ExternDesc::DescType::ComponentType:
43
0
    return AST::Component::Sort::SortType::Component;
44
0
  case AST::Component::ExternDesc::DescType::InstanceType:
45
0
    return AST::Component::Sort::SortType::Instance;
46
0
  default:
47
0
    assumingUnreachable();
48
0
  }
49
0
}
50
51
// Shallow sort-kind match between a Sort and an ExternDesc. The spec's
52
// instantiation / export-ascription rules require the supplied sortidx to be
53
// a subtype of the externdesc. Here we only enforce that the kind agrees;
54
// deep structural subtyping (record fields, func signatures, etc.) is not
55
// yet implemented and is the main remaining correctness gap at these sites.
56
bool sortMatchesDescType(const AST::Component::Sort &S,
57
0
                         AST::Component::ExternDesc::DescType DT) noexcept {
58
0
  auto Mapped = descTypeToSortType(DT);
59
0
  if (S.isCore()) {
60
0
    return !Mapped.has_value() &&
61
0
           S.getCoreSortType() == AST::Component::Sort::CoreSortType::Module;
62
0
  }
63
0
  return Mapped.has_value() && S.getSortType() == *Mapped;
64
0
}
65
66
// Fallback type-index lookup against an InstanceType's own local
67
// type-decl space (used when the outer ComponentContext scope doesn't
68
// own the InstanceType).
69
const AST::Component::InstanceType *
70
resolveNestedInstanceType(const AST::Component::InstanceType &Parent,
71
0
                          uint32_t TypeIdx) noexcept {
72
0
  uint32_t LocalIdx = 0;
73
0
  for (const auto &LocalDecl : Parent.getDecl()) {
74
0
    if (!LocalDecl.isType()) {
75
0
      continue;
76
0
    }
77
0
    if (LocalIdx == TypeIdx) {
78
0
      const auto *LocalDT = LocalDecl.getType();
79
0
      if (LocalDT != nullptr && LocalDT->isInstanceType()) {
80
0
        return &LocalDT->getInstanceType();
81
0
      }
82
0
      return nullptr;
83
0
    }
84
0
    LocalIdx++;
85
0
  }
86
0
  return nullptr;
87
0
}
88
89
// Resolve a type index in `Comp`'s own type index space to an InstanceType.
90
// Returns nullptr when the index does not refer to an inline InstanceType
91
// definition — callers treat nullptr as "no required shape" and fall back
92
// to inferred exports. TypeBound imports and outer-alias type imports
93
// currently fall through to nullptr; a more complete resolver would walk
94
// the alias chain to recover the underlying InstanceType.
95
const AST::Component::InstanceType *
96
resolveChildInstanceType(const AST::Component::Component &Comp,
97
0
                         uint32_t TypeIdx) {
98
0
  uint32_t CurrentIdx = 0;
99
0
  for (const auto &Sec : Comp.getSections()) {
100
0
    if (std::holds_alternative<AST::Component::TypeSection>(Sec)) {
101
0
      const auto &TSec = std::get<AST::Component::TypeSection>(Sec);
102
0
      for (const auto &DT : TSec.getContent()) {
103
0
        if (CurrentIdx == TypeIdx) {
104
0
          if (DT.isInstanceType()) {
105
0
            return &DT.getInstanceType();
106
0
          }
107
0
          return nullptr;
108
0
        }
109
0
        CurrentIdx++;
110
0
      }
111
0
    } else if (std::holds_alternative<AST::Component::ImportSection>(Sec)) {
112
0
      const auto &ISec = std::get<AST::Component::ImportSection>(Sec);
113
0
      for (const auto &Import : ISec.getContent()) {
114
0
        if (Import.getDesc().getDescType() ==
115
0
            AST::Component::ExternDesc::DescType::TypeBound) {
116
0
          if (CurrentIdx == TypeIdx) {
117
0
            return nullptr;
118
0
          }
119
0
          CurrentIdx++;
120
0
        }
121
0
      }
122
0
    } else if (std::holds_alternative<AST::Component::AliasSection>(Sec)) {
123
0
      const auto &ASec = std::get<AST::Component::AliasSection>(Sec);
124
0
      for (const auto &Alias : ASec.getContent()) {
125
0
        if (!Alias.getSort().isCore() &&
126
0
            Alias.getSort().getSortType() ==
127
0
                AST::Component::Sort::SortType::Type) {
128
0
          if (CurrentIdx == TypeIdx) {
129
0
            return nullptr;
130
0
          }
131
0
          CurrentIdx++;
132
0
        }
133
0
      }
134
0
    }
135
0
  }
136
0
  return nullptr;
137
0
}
138
139
// Validate that a name may appear at an export position: reject the
140
// `relative-url=` prefix (not part of the extern-name grammar) and any
141
// plainname/interfacename kind that isn't allowed on an export.
142
0
Expect<ComponentName> validateExportName(std::string_view Name) noexcept {
143
0
  if (Name.rfind("relative-url="sv, 0) == 0) {
144
0
    spdlog::error(ErrCode::Value::InvalidExternName);
145
0
    spdlog::error("    Export name '{}' is not a valid extern name"sv, Name);
146
0
    return Unexpect(ErrCode::Value::InvalidExternName);
147
0
  }
148
0
  EXPECTED_TRY(ComponentName CName, ComponentName::parse(Name));
149
0
  switch (CName.getKind()) {
150
0
  case ComponentNameKind::Label:
151
0
  case ComponentNameKind::Constructor:
152
0
  case ComponentNameKind::Method:
153
0
  case ComponentNameKind::Static:
154
0
  case ComponentNameKind::InterfaceType:
155
0
    return CName;
156
0
  default:
157
0
    spdlog::error(ErrCode::Value::InvalidExportName);
158
0
    spdlog::error("    Export name '{}' kind is not valid for exports"sv, Name);
159
0
    return Unexpect(ErrCode::Value::InvalidExportName);
160
0
  }
161
0
}
162
163
} // namespace
164
165
void Validator::populateInstanceFromType(
166
0
    uint32_t InstIdx, const AST::Component::InstanceType &IT) noexcept {
167
0
  for (const auto &Decl : IT.getDecl()) {
168
0
    if (!Decl.isExportDecl()) {
169
0
      continue;
170
0
    }
171
0
    const auto &Exp = Decl.getExport();
172
0
    const auto &ED = Exp.getExternDesc();
173
0
    auto ST = descTypeToSortType(ED.getDescType());
174
0
    if (!ST.has_value()) {
175
      // InstanceExport::ST has no `(core module)` variant — skip rather
176
      // than crash. TODO: extend InstanceExport with a core-sort alternative.
177
0
      spdlog::debug(
178
0
          "    populateInstanceFromType: skipping `(core module)` export "
179
0
          "'{}'"sv,
180
0
          Exp.getName());
181
0
      continue;
182
0
    }
183
0
    const AST::Component::InstanceType *NestedIT = nullptr;
184
0
    if (ED.getDescType() ==
185
0
        AST::Component::ExternDesc::DescType::InstanceType) {
186
0
      NestedIT = CompCtx.getInstanceType(ED.getTypeIndex());
187
0
      if (NestedIT == nullptr) {
188
0
        NestedIT = resolveNestedInstanceType(IT, ED.getTypeIndex());
189
0
      }
190
0
    }
191
    // Resource-typed exports carry a canonical id so an alias-export of
192
    // the type slot can preserve identity. `(sub resource)` introduces a
193
    // fresh id; `(eq i)` should inherit, but cross-scope resource lookup
194
    // inside an InstanceType body is not yet implemented (TODO).
195
0
    std::optional<uint64_t> ResourceId;
196
0
    if (ED.getDescType() == AST::Component::ExternDesc::DescType::TypeBound &&
197
0
        !ED.isEqType()) {
198
0
      ResourceId = CompCtx.allocateFreshResourceId();
199
0
    }
200
0
    CompCtx.addInstanceExport(InstIdx, Exp.getName(), *ST, NestedIT,
201
0
                              /*NestedInstIdx=*/std::nullopt, ResourceId);
202
0
  }
203
0
}
204
205
bool Validator::exportSatisfies(
206
    const AST::Component::InstanceType &RequiredCtx,
207
    const ComponentContext::InstanceExport &Provided,
208
0
    const AST::Component::ExternDesc &Required) const noexcept {
209
0
  auto RequiredST = descTypeToSortType(Required.getDescType());
210
0
  if (!RequiredST.has_value()) {
211
    // `(core module)` has no InstanceExport::ST variant — no constraint.
212
0
    return true;
213
0
  }
214
0
  if (Provided.ST != *RequiredST) {
215
0
    return false;
216
0
  }
217
  // Instance-on-instance: recurse if both sides resolve to an
218
  // InstanceType. Otherwise sort-kind match (pre-Phase-3 behaviour).
219
0
  if (Required.getDescType() ==
220
0
          AST::Component::ExternDesc::DescType::InstanceType &&
221
0
      Provided.IT != nullptr) {
222
0
    const auto *RequiredIT = CompCtx.getInstanceType(Required.getTypeIndex());
223
0
    if (RequiredIT == nullptr) {
224
      // Required's idx lives in RequiredCtx's local type-decls.
225
0
      RequiredIT =
226
0
          resolveNestedInstanceType(RequiredCtx, Required.getTypeIndex());
227
0
    }
228
0
    if (RequiredIT != nullptr) {
229
0
      return isInstanceSubtype(*Provided.IT, *RequiredIT);
230
0
    }
231
0
  }
232
0
  return true;
233
0
}
234
235
std::optional<std::string> Validator::findMissingRequiredExport(
236
    uint32_t ProvidedInstIdx,
237
0
    const AST::Component::InstanceType &RequiredIT) const noexcept {
238
0
  const auto &Exports = CompCtx.getInstance(ProvidedInstIdx).Exports;
239
0
  for (const auto &Decl : RequiredIT.getDecl()) {
240
0
    if (!Decl.isExportDecl()) {
241
0
      continue;
242
0
    }
243
0
    const auto &Exp = Decl.getExport();
244
0
    auto It = Exports.find(std::string(Exp.getName()));
245
0
    if (It == Exports.end()) {
246
0
      return std::string(Exp.getName());
247
0
    }
248
0
    if (!exportSatisfies(RequiredIT, It->second, Exp.getExternDesc())) {
249
0
      return std::string(Exp.getName());
250
0
    }
251
0
  }
252
0
  return std::nullopt;
253
0
}
254
255
bool Validator::isInstanceSubtype(
256
    const AST::Component::InstanceType &S,
257
0
    const AST::Component::InstanceType &T) const noexcept {
258
  // S subtype T iff every export declared by T is present in S with a
259
  // satisfying type. Build a quick (name → externdesc) lookup of S's
260
  // exports for the lookup.
261
0
  std::unordered_map<std::string, const AST::Component::ExternDesc *> SExports;
262
0
  for (const auto &Decl : S.getDecl()) {
263
0
    if (Decl.isExportDecl()) {
264
0
      const auto &E = Decl.getExport();
265
0
      SExports.emplace(std::string(E.getName()), &E.getExternDesc());
266
0
    }
267
0
  }
268
0
  for (const auto &Decl : T.getDecl()) {
269
0
    if (!Decl.isExportDecl()) {
270
0
      continue;
271
0
    }
272
0
    const auto &E = Decl.getExport();
273
0
    auto It = SExports.find(std::string(E.getName()));
274
0
    if (It == SExports.end()) {
275
0
      return false;
276
0
    }
277
0
    auto SKind = descTypeToSortType(It->second->getDescType());
278
0
    auto TKind = descTypeToSortType(E.getExternDesc().getDescType());
279
0
    if (SKind != TKind) {
280
0
      return false;
281
0
    }
282
    // Instance-on-instance: nested type indices on each side belong to
283
    // that side's own decls, so fall back via resolveNestedInstanceType.
284
0
    if (E.getExternDesc().getDescType() ==
285
0
        AST::Component::ExternDesc::DescType::InstanceType) {
286
0
      const auto *SubIT = CompCtx.getInstanceType(It->second->getTypeIndex());
287
0
      if (SubIT == nullptr) {
288
0
        SubIT = resolveNestedInstanceType(S, It->second->getTypeIndex());
289
0
      }
290
0
      const auto *ReqIT =
291
0
          CompCtx.getInstanceType(E.getExternDesc().getTypeIndex());
292
0
      if (ReqIT == nullptr) {
293
0
        ReqIT = resolveNestedInstanceType(T, E.getExternDesc().getTypeIndex());
294
0
      }
295
0
      if (SubIT != nullptr && ReqIT != nullptr &&
296
0
          !isInstanceSubtype(*SubIT, *ReqIT)) {
297
0
        return false;
298
0
      }
299
0
    }
300
0
  }
301
0
  return true;
302
0
}
303
304
Expect<void>
305
0
Validator::validate(const AST::Component::Component &Comp) noexcept {
306
0
  spdlog::warn("Component Model Validation is in active development."sv);
307
0
  CompCtx.reset();
308
0
  return validateComponent(Comp).and_then([&]() {
309
0
    const_cast<AST::Component::Component &>(Comp).setIsValidated();
310
0
    return Expect<void>{};
311
0
  });
312
0
}
313
314
Expect<void>
315
0
Validator::validateComponent(const AST::Component::Component &Comp) noexcept {
316
  // Validation enters a fresh component scope and walks sections in their
317
  // binary order. The per-sort index spaces are built incrementally as
318
  // definitions are validated, so sortidx references in later sections
319
  // only resolve against entries already introduced. Custom sections are
320
  // ignored. Nested components recurse through this same function.
321
0
  auto ReportError = [](auto E) {
322
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Component));
323
0
    return E;
324
0
  };
325
326
0
  CompCtx.enterComponent(&Comp);
327
0
  for (const auto &Sec : Comp.getSections()) {
328
0
    auto Func = [&](auto &&S) -> Expect<void> {
329
0
      using T = std::decay_t<decltype(S)>;
330
0
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
0
      } else {
333
0
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
0
      }
335
0
      return {};
336
0
    };
Unexecuted instantiation: component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::CustomSection const&>(WasmEdge::AST::CustomSection const&) const
Unexecuted instantiation: component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::CoreModuleSection const&>(WasmEdge::AST::Component::CoreModuleSection const&) const
Unexecuted instantiation: component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::CoreInstanceSection const&>(WasmEdge::AST::Component::CoreInstanceSection const&) const
Unexecuted instantiation: component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::CoreTypeSection const&>(WasmEdge::AST::Component::CoreTypeSection const&) const
Unexecuted instantiation: component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::ComponentSection const&>(WasmEdge::AST::Component::ComponentSection const&) const
Unexecuted instantiation: component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::InstanceSection const&>(WasmEdge::AST::Component::InstanceSection const&) const
Unexecuted instantiation: component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::AliasSection const&>(WasmEdge::AST::Component::AliasSection const&) const
Unexecuted instantiation: component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::TypeSection const&>(WasmEdge::AST::Component::TypeSection const&) const
Unexecuted instantiation: component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::CanonSection const&>(WasmEdge::AST::Component::CanonSection const&) const
Unexecuted instantiation: component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::StartSection const&>(WasmEdge::AST::Component::StartSection const&) const
Unexecuted instantiation: component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::ImportSection const&>(WasmEdge::AST::Component::ImportSection const&) const
Unexecuted instantiation: component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::ExportSection const&>(WasmEdge::AST::Component::ExportSection const&) const
337
0
    EXPECTED_TRY(std::visit(Func, Sec));
338
0
  }
339
0
  CompCtx.exitComponent();
340
0
  return {};
341
0
}
342
343
Expect<void>
344
0
Validator::validate(const AST::Component::CoreModuleSection &ModSec) noexcept {
345
0
  EXPECTED_TRY(validate(ModSec.getContent()).map_error([](auto E) {
346
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_CoreMod));
347
0
    return E;
348
0
  }));
349
0
  const_cast<AST::Module &>(ModSec.getContent()).setIsValidated();
350
0
  CompCtx.addCoreModule(ModSec.getContent());
351
0
  return {};
352
0
}
353
354
Expect<void> Validator::validate(
355
0
    const AST::Component::CoreInstanceSection &InstSec) noexcept {
356
0
  for (const auto &Inst : InstSec.getContent()) {
357
0
    EXPECTED_TRY(validate(Inst).map_error([](auto E) {
358
0
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_CoreInstance));
359
0
      return E;
360
0
    }));
361
0
  }
362
0
  return {};
363
0
}
364
365
Expect<void>
366
0
Validator::validate(const AST::Component::CoreTypeSection &TypeSec) noexcept {
367
0
  for (const auto &Type : TypeSec.getContent()) {
368
0
    EXPECTED_TRY(validate(Type).map_error([](auto E) {
369
0
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_CoreType));
370
0
      return E;
371
0
    }));
372
0
  }
373
0
  return {};
374
0
}
375
376
Expect<void>
377
0
Validator::validate(const AST::Component::ComponentSection &CompSec) noexcept {
378
0
  EXPECTED_TRY(validateComponent(CompSec.getContent()).map_error([](auto E) {
379
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Component));
380
0
    return E;
381
0
  }));
382
0
  CompCtx.addComponent(CompSec.getContent());
383
0
  return {};
384
0
}
385
386
Expect<void>
387
0
Validator::validate(const AST::Component::InstanceSection &InstSec) noexcept {
388
0
  for (const auto &Inst : InstSec.getContent()) {
389
0
    EXPECTED_TRY(validate(Inst).map_error([](auto E) {
390
0
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Instance));
391
0
      return E;
392
0
    }));
393
0
  }
394
0
  return {};
395
0
}
396
397
Expect<void>
398
0
Validator::validate(const AST::Component::AliasSection &AliasSec) noexcept {
399
0
  for (const auto &Alias : AliasSec.getContent()) {
400
0
    EXPECTED_TRY(validate(Alias).map_error([](auto E) {
401
0
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Alias));
402
0
      return E;
403
0
    }));
404
0
    const auto &Sort = Alias.getSort();
405
0
    const bool IsOuter =
406
0
        Alias.getTargetType() == AST::Component::Alias::TargetType::Outer;
407
0
    if (Sort.isCore()) {
408
0
      uint32_t NewCoreIdx =
409
0
          CompCtx.incCoreSortIndexSize(Sort.getCoreSortType());
410
      // Carry the outer-aliased module's slot so the alias stays enumerable
411
      // when instantiated.
412
0
      if (IsOuter && Sort.getCoreSortType() ==
413
0
                         AST::Component::Sort::CoreSortType::Module) {
414
0
        CompCtx.carryOuterCoreModule(NewCoreIdx, Alias.getOuter().first,
415
0
                                     Alias.getOuter().second);
416
0
      }
417
0
    } else {
418
0
      uint32_t NewIdx = CompCtx.incSortIndexSize(Sort.getSortType());
419
      // Component analogue of the outer core-module carry above.
420
0
      if (IsOuter &&
421
0
          Sort.getSortType() == AST::Component::Sort::SortType::Component) {
422
0
        CompCtx.carryOuterComponent(NewIdx, Alias.getOuter().first,
423
0
                                    Alias.getOuter().second);
424
0
      }
425
      // Outer-aliasing a resource type keeps the resource's identity in this
426
      // scope so later own/borrow and (eq i) checks treat the slot correctly.
427
0
      if (IsOuter &&
428
0
          Sort.getSortType() == AST::Component::Sort::SortType::Type) {
429
0
        CompCtx.carryOuterResource(NewIdx, Alias.getOuter().first,
430
0
                                   Alias.getOuter().second);
431
0
      }
432
      // If the alias creates a new instance entry out of an `alias export`
433
      // on another instance, propagate the source instance's export table
434
      // into the new slot so a subsequent `alias export` on this slot can
435
      // resolve nested exports.
436
0
      if (Alias.getTargetType() == AST::Component::Alias::TargetType::Export) {
437
0
        const auto SrcInstIdx = Alias.getExport().first;
438
0
        const auto &SrcName = Alias.getExport().second;
439
0
        const auto &SrcExports = CompCtx.getInstance(SrcInstIdx).Exports;
440
0
        auto It = SrcExports.find(std::string(SrcName));
441
0
        if (It != SrcExports.end()) {
442
0
          if (Sort.getSortType() == AST::Component::Sort::SortType::Instance) {
443
0
            if (It->second.IT != nullptr) {
444
0
              populateInstanceFromType(NewIdx, *It->second.IT);
445
0
            } else if (It->second.NestedInstIdx.has_value()) {
446
0
              const auto &NestedExports =
447
0
                  CompCtx.getInstance(*It->second.NestedInstIdx).Exports;
448
0
              for (const auto &[Name, IE] : NestedExports) {
449
0
                CompCtx.addInstanceExport(NewIdx, Name, IE.ST, IE.IT,
450
0
                                          IE.NestedInstIdx, IE.ResourceId);
451
0
              }
452
0
            }
453
0
          } else if (Sort.getSortType() ==
454
0
                         AST::Component::Sort::SortType::Type &&
455
0
                     It->second.ResourceId.has_value()) {
456
            // Alias-export of a resource type — same identity.
457
0
            CompCtx.addResource(NewIdx, {*It->second.ResourceId,
458
0
                                         /*LocallyDefined=*/false});
459
0
          }
460
0
        }
461
0
      }
462
0
    }
463
0
  }
464
0
  return {};
465
0
}
466
467
Expect<void>
468
0
Validator::validate(const AST::Component::TypeSection &TypeSec) noexcept {
469
0
  for (const auto &Type : TypeSec.getContent()) {
470
0
    EXPECTED_TRY(validate(Type).map_error([](auto E) {
471
0
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Type));
472
0
      return E;
473
0
    }));
474
0
  }
475
0
  return {};
476
0
}
477
478
Expect<void>
479
0
Validator::validate(const AST::Component::CanonSection &CanonSec) noexcept {
480
0
  for (const auto &C : CanonSec.getContent()) {
481
0
    EXPECTED_TRY(validate(C).map_error([](auto E) {
482
0
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Canon));
483
0
      return E;
484
0
    }));
485
0
  }
486
0
  return {};
487
0
}
488
489
Expect<void>
490
0
Validator::validate(const AST::Component::StartSection &StartSec) noexcept {
491
  // Validation steps:
492
  //   1. `f` is in bounds of the component func index space.
493
  //   2. `f`'s functype param arity equals |arg*| and result arity equals
494
  //      `r`. Per-argument value-type subtype is checked once subtype
495
  //      machinery exists (Phase 3).
496
  //   3. Each argument index is in bounds of the value index space.
497
  //      Per-value linearity (consume-once) is the responsibility of
498
  //      GAP-EX-4: the per-value `consumed` flag and the end-of-component
499
  //      "all consumed" pass. They are not yet wired here.
500
  //   4. The function's result types are appended to the value index space
501
  //      as fresh values, so later definitions can reference them.
502
0
  const auto &Start = StartSec.getContent();
503
504
  // 1. Function index bounds.
505
0
  const uint32_t FuncIdx = Start.getFunctionIndex();
506
0
  const uint32_t FuncSpaceSize =
507
0
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Func);
508
0
  if (FuncIdx >= FuncSpaceSize) {
509
0
    spdlog::error(ErrCode::Value::InvalidIndex);
510
0
    spdlog::error(
511
0
        "    Start: function index {} exceeds func index space size {}"sv,
512
0
        FuncIdx, FuncSpaceSize);
513
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Start));
514
0
    return Unexpect(ErrCode::Value::InvalidIndex);
515
0
  }
516
517
  // 2. Look up the func type. If null (e.g. imported component func without
518
  // populated FuncType yet), skip arity check — the bound check above is
519
  // enough for the moment.
520
0
  const AST::Component::FuncType *FT = CompCtx.getFunc(FuncIdx);
521
0
  if (FT != nullptr) {
522
0
    const auto Args = Start.getArguments();
523
0
    const auto &ParamList = FT->getParamList();
524
0
    if (Args.size() != ParamList.size()) {
525
0
      spdlog::error(ErrCode::Value::InvalidIndex);
526
0
      spdlog::error(
527
0
          "    Start: argument count {} does not match func {} param arity {}"sv,
528
0
          Args.size(), FuncIdx, ParamList.size());
529
0
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Start));
530
0
      return Unexpect(ErrCode::Value::InvalidIndex);
531
0
    }
532
0
    const uint32_t ResultArity =
533
0
        static_cast<uint32_t>(FT->getResultList().size());
534
0
    if (Start.getResult() != ResultArity) {
535
0
      spdlog::error(ErrCode::Value::InvalidIndex);
536
0
      spdlog::error(
537
0
          "    Start: declared result count {} does not match func {} result arity {}"sv,
538
0
          Start.getResult(), FuncIdx, ResultArity);
539
0
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Start));
540
0
      return Unexpect(ErrCode::Value::InvalidIndex);
541
0
    }
542
0
  }
543
544
  // 3. Argument indices must be in value index space bounds. Per-arg
545
  // consume-once tracking is deferred (GAP-EX-4).
546
0
  const uint32_t ValueSpaceSize =
547
0
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Value);
548
0
  for (const uint32_t ArgIdx : Start.getArguments()) {
549
0
    if (ArgIdx >= ValueSpaceSize) {
550
0
      spdlog::error(ErrCode::Value::InvalidIndex);
551
0
      spdlog::error(
552
0
          "    Start: argument value index {} exceeds value index space size {}"sv,
553
0
          ArgIdx, ValueSpaceSize);
554
0
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Start));
555
0
      return Unexpect(ErrCode::Value::InvalidIndex);
556
0
    }
557
0
  }
558
559
  // 4. Append result values to the value index space.
560
0
  for (uint32_t I = 0; I < Start.getResult(); ++I) {
561
0
    CompCtx.addValue();
562
0
  }
563
0
  return {};
564
0
}
565
566
Expect<void>
567
0
Validator::validate(const AST::Component::ImportSection &ImpSec) noexcept {
568
0
  for (const auto &Imp : ImpSec.getContent()) {
569
0
    EXPECTED_TRY(validate(Imp).map_error([](auto E) {
570
0
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Import));
571
0
      return E;
572
0
    }));
573
0
  }
574
0
  return {};
575
0
}
576
577
Expect<void>
578
0
Validator::validate(const AST::Component::ExportSection &ExpSec) noexcept {
579
0
  for (const auto &Exp : ExpSec.getContent()) {
580
0
    EXPECTED_TRY(validate(Exp).map_error([](auto E) {
581
0
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Export));
582
0
      return E;
583
0
    }));
584
0
  }
585
0
  return {};
586
0
}
587
588
Expect<void>
589
0
Validator::validate(const AST::Component::CoreInstance &Inst) noexcept {
590
0
  if (Inst.isInstantiateModule()) {
591
    // Instantiate module case.
592
593
    // Check the module index bound first.
594
0
    const uint32_t ModIdx = Inst.getModuleIndex();
595
0
    if (ModIdx >= CompCtx.getCoreSortIndexSize(
596
0
                      AST::Component::Sort::CoreSortType::Module)) {
597
0
      spdlog::error(ErrCode::Value::InvalidIndex);
598
0
      spdlog::error(
599
0
          "    CoreInstance: Module index {} exceeds available core modules {}"sv,
600
0
          ModIdx,
601
0
          CompCtx.getCoreSortIndexSize(
602
0
              AST::Component::Sort::CoreSortType::Module));
603
0
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance));
604
0
      return Unexpect(ErrCode::Value::InvalidIndex);
605
0
    }
606
    // Reject duplicate argument names on an instantiate expression. The
607
    // spec requires argument names to be strongly-unique per instantiation.
608
0
    {
609
0
      std::unordered_set<std::string_view> SeenArgs;
610
0
      for (const auto &Arg : Inst.getInstantiateArgs()) {
611
0
        if (!SeenArgs.insert(Arg.getName()).second) {
612
0
          spdlog::error(ErrCode::Value::ComponentDuplicateName);
613
0
          spdlog::error("    CoreInstance: Duplicate argument name '{}'"sv,
614
0
                        Arg.getName());
615
0
          spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance));
616
0
          return Unexpect(ErrCode::Value::ComponentDuplicateName);
617
0
        }
618
0
      }
619
0
    }
620
621
    // Imports + exports come from the raw Module (inline) or the
622
    // CoreModuleType (imported / aliased) — GAP-CI-1.
623
0
    const auto &CoreModSlot = CompCtx.getCoreModule(ModIdx);
624
0
    const auto *Mod = CoreModSlot.Body;
625
0
    const auto *ModTy = CoreModSlot.Type;
626
627
    // Required arg module-names (one per distinct CoreImportDecl module).
628
0
    std::vector<std::string_view> RequiredArgNames;
629
0
    if (Mod != nullptr) {
630
0
      for (const auto &Import : Mod->getImportSection().getContent()) {
631
0
        RequiredArgNames.push_back(Import.getModuleName());
632
0
      }
633
0
    } else if (ModTy != nullptr && ModTy->isModuleType()) {
634
0
      for (const auto &Decl : ModTy->getModuleType()) {
635
0
        if (Decl.isImport()) {
636
0
          RequiredArgNames.push_back(Decl.getImport().getModuleName());
637
0
        }
638
0
      }
639
0
    }
640
641
0
    auto Args = Inst.getInstantiateArgs();
642
0
    for (const auto ImportName : RequiredArgNames) {
643
0
      const auto ArgIt =
644
0
          std::find_if(Args.begin(), Args.end(), [&](const auto &Arg) {
645
0
            return Arg.getName() == ImportName;
646
0
          });
647
0
      if (ArgIt == Args.end()) {
648
0
        spdlog::error(ErrCode::Value::MissingArgument);
649
0
        spdlog::error(
650
0
            "    CoreInstance: Module index {} missing argument for import '{}'"sv,
651
0
            Inst.getModuleIndex(), ImportName);
652
0
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance));
653
0
        return Unexpect(ErrCode::Value::MissingArgument);
654
0
      }
655
0
    }
656
657
    // Allocate the core:instance and bind exports to it.
658
0
    uint32_t InstanceIdx = CompCtx.addCoreInstance();
659
0
    if (Mod != nullptr) {
660
0
      for (const auto &ExportDesc : Mod->getExportSection().getContent()) {
661
0
        CompCtx.addCoreInstanceExport(InstanceIdx, ExportDesc.getExternalName(),
662
0
                                      ExportDesc.getExternalType());
663
0
      }
664
0
    } else if (ModTy != nullptr && ModTy->isModuleType()) {
665
0
      for (const auto &Decl : ModTy->getModuleType()) {
666
0
        if (!Decl.isExport()) {
667
0
          continue;
668
0
        }
669
0
        const auto &Exp = Decl.getExport();
670
0
        const auto &ImpDesc = Exp.getImportDesc();
671
0
        ExternalType ET;
672
0
        if (ImpDesc.isFunc()) {
673
0
          ET = ExternalType::Function;
674
0
        } else if (ImpDesc.isTable()) {
675
0
          ET = ExternalType::Table;
676
0
        } else if (ImpDesc.isMemory()) {
677
0
          ET = ExternalType::Memory;
678
0
        } else if (ImpDesc.isGlobal()) {
679
0
          ET = ExternalType::Global;
680
0
        } else if (ImpDesc.isTag()) {
681
0
          ET = ExternalType::Tag;
682
0
        } else {
683
0
          continue;
684
0
        }
685
0
        CompCtx.addCoreInstanceExport(InstanceIdx, Exp.getName(), ET);
686
0
      }
687
0
    }
688
0
  } else if (Inst.isInlineExport()) {
689
    // Inline export case.
690
    // Allocate the core instance first, then register each inline export.
691
0
    uint32_t InstanceIdx = CompCtx.addCoreInstance();
692
693
    // Check the core:sort index bound and register the inline exports.
694
    // Inline-export names on a core instance must be strongly-unique.
695
0
    std::unordered_set<std::string_view> SeenExports;
696
0
    for (const auto &Export : Inst.getInlineExports()) {
697
0
      if (!SeenExports.insert(Export.getName()).second) {
698
0
        spdlog::error(ErrCode::Value::ComponentDuplicateName);
699
0
        spdlog::error("    CoreInstance: Duplicate inline-export name '{}'"sv,
700
0
                      Export.getName());
701
0
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance));
702
0
        return Unexpect(ErrCode::Value::ComponentDuplicateName);
703
0
      }
704
0
      const auto &Sort = Export.getSortIdx().getSort();
705
0
      uint32_t Idx = Export.getSortIdx().getIdx();
706
0
      assuming(Sort.isCore());
707
0
      if (Idx >= CompCtx.getCoreSortIndexSize(Sort.getCoreSortType())) {
708
        // The error message differs of the tag core sort.
709
0
        ErrCode::Value ErrValue = ErrCode::Value::InvalidIndex;
710
0
        if (Sort.getCoreSortType() == AST::Component::Sort::CoreSortType::Tag) {
711
0
          ErrValue = ErrCode::Value::UnknownCoreTag;
712
0
        }
713
0
        spdlog::error(ErrValue);
714
0
        spdlog::error(
715
0
            "    CoreInstance: Inline export '{}' refers to invalid index {}"sv,
716
0
            Export.getName(), Idx);
717
0
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance));
718
0
        return Unexpect(ErrValue);
719
0
      }
720
      // Map CoreSortType to ExternalType for the instance export map.
721
0
      ExternalType ET;
722
0
      switch (Sort.getCoreSortType()) {
723
0
      case AST::Component::Sort::CoreSortType::Func:
724
0
        ET = ExternalType::Function;
725
0
        break;
726
0
      case AST::Component::Sort::CoreSortType::Table:
727
0
        ET = ExternalType::Table;
728
0
        break;
729
0
      case AST::Component::Sort::CoreSortType::Memory:
730
0
        ET = ExternalType::Memory;
731
0
        break;
732
0
      case AST::Component::Sort::CoreSortType::Global:
733
0
        ET = ExternalType::Global;
734
0
        break;
735
0
      case AST::Component::Sort::CoreSortType::Tag:
736
0
        ET = ExternalType::Tag;
737
0
        break;
738
0
      default:
739
0
        spdlog::error(ErrCode::Value::InvalidIndex);
740
0
        spdlog::error(
741
0
            "    CoreInstance: Inline export '{}' has unsupported core sort"sv,
742
0
            Export.getName());
743
0
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance));
744
0
        return Unexpect(ErrCode::Value::InvalidIndex);
745
0
      }
746
0
      CompCtx.addCoreInstanceExport(InstanceIdx, Export.getName(), ET);
747
0
    }
748
0
  } else {
749
0
    assumingUnreachable();
750
0
  }
751
0
  return {};
752
0
}
753
754
Expect<void>
755
0
Validator::validate(const AST::Component::Instance &Inst) noexcept {
756
0
  if (Inst.isInstantiateModule()) {
757
    // Instantiate module case.
758
759
    // Check the component index bound first.
760
0
    const uint32_t CompIdx = Inst.getComponentIndex();
761
0
    if (CompIdx >=
762
0
        CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Component)) {
763
0
      spdlog::error(ErrCode::Value::InvalidIndex);
764
0
      spdlog::error(
765
0
          "    Instance: Component index {} exceeds available components {}"sv,
766
0
          CompIdx,
767
0
          CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Component));
768
0
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
769
0
      return Unexpect(ErrCode::Value::InvalidIndex);
770
0
    }
771
    // Reject duplicate argument names on an instantiate expression. The
772
    // spec requires argument names to be strongly-unique per instantiation.
773
0
    {
774
0
      std::unordered_set<std::string_view> SeenArgs;
775
0
      for (const auto &Arg : Inst.getInstantiateArgs()) {
776
0
        if (!SeenArgs.insert(Arg.getName()).second) {
777
0
          spdlog::error(ErrCode::Value::ComponentDuplicateName);
778
0
          spdlog::error("    Instance: Duplicate argument name '{}'"sv,
779
0
                        Arg.getName());
780
0
          spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
781
0
          return Unexpect(ErrCode::Value::ComponentDuplicateName);
782
0
        }
783
0
      }
784
0
    }
785
786
    // Source: raw Component (inline) or ComponentType (imported / aliased).
787
0
    const auto &CompSlot = CompCtx.getComponent(CompIdx);
788
0
    const auto *Comp = CompSlot.Body;
789
0
    const auto *CompTy = CompSlot.Type;
790
791
    // Verify each component import is satisfied by some instantiate arg.
792
0
    auto Args = Inst.getInstantiateArgs();
793
0
    auto checkImport =
794
0
        [&](std::string_view ImportName,
795
0
            const AST::Component::ExternDesc &ImportDesc) -> Expect<void> {
796
0
      const auto ArgIt =
797
0
          std::find_if(Args.begin(), Args.end(), [&](const auto &Arg) {
798
0
            return Arg.getName() == ImportName;
799
0
          });
800
0
      if (ArgIt == Args.end()) {
801
0
        spdlog::error(ErrCode::Value::MissingArgument);
802
0
        spdlog::error(
803
0
            "    Instance: Component index {} missing argument for import '{}'"sv,
804
0
            Inst.getComponentIndex(), ImportName);
805
0
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
806
0
        return Unexpect(ErrCode::Value::MissingArgument);
807
0
      }
808
0
      const auto &Sort = ArgIt->getIndex().getSort();
809
0
      const uint32_t Idx = ArgIt->getIndex().getIdx();
810
      // Only `core module` is admissible as a core-side import externdesc.
811
0
      if (Sort.isCore() && Sort.getCoreSortType() !=
812
0
                               AST::Component::Sort::CoreSortType::Module) {
813
0
        spdlog::error(ErrCode::Value::ArgTypeMismatch);
814
0
        spdlog::error("    Instance: Argument '{}' uses a core sort other than "
815
0
                      "`core module`, which no import externdesc can accept"sv,
816
0
                      ImportName);
817
0
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
818
0
        return Unexpect(ErrCode::Value::ArgTypeMismatch);
819
0
      }
820
0
      if (!sortMatchesDescType(Sort, ImportDesc.getDescType())) {
821
0
        spdlog::error(ErrCode::Value::ArgTypeMismatch);
822
0
        spdlog::error("    Instance: Argument '{}' sort mismatch for import"sv,
823
0
                      ImportName);
824
0
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
825
0
        return Unexpect(ErrCode::Value::ArgTypeMismatch);
826
0
      }
827
0
      if (Sort.isCore()) {
828
0
        if (Idx >= CompCtx.getCoreSortIndexSize(Sort.getCoreSortType())) {
829
0
          spdlog::error(ErrCode::Value::InvalidIndex);
830
0
          spdlog::error(
831
0
              "    Instance: Argument '{}' refers to invalid index {}"sv,
832
0
              ImportName, Idx);
833
0
          spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
834
0
          return Unexpect(ErrCode::Value::InvalidIndex);
835
0
        }
836
0
        return {};
837
0
      }
838
0
      if (Idx >= CompCtx.getSortIndexSize(Sort.getSortType())) {
839
0
        spdlog::error(ErrCode::Value::InvalidIndex);
840
0
        spdlog::error(
841
0
            "    Instance: Argument '{}' refers to invalid index {}"sv,
842
0
            ImportName, Idx);
843
0
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
844
0
        return Unexpect(ErrCode::Value::InvalidIndex);
845
0
      }
846
      // Partial subtype: for instance-typed imports, verify the provided
847
      // instance has every required export (raw-Component path only;
848
      // ComponentType path needs GAP-DECL-ED).
849
0
      if (Comp != nullptr &&
850
0
          ImportDesc.getDescType() ==
851
0
              AST::Component::ExternDesc::DescType::InstanceType &&
852
0
          Sort.getSortType() == AST::Component::Sort::SortType::Instance) {
853
0
        const auto *RequiredIT =
854
0
            resolveChildInstanceType(*Comp, ImportDesc.getTypeIndex());
855
0
        if (RequiredIT != nullptr) {
856
0
          if (auto Missing = findMissingRequiredExport(Idx, *RequiredIT)) {
857
0
            spdlog::error(ErrCode::Value::InstanceMissingExpectedExport);
858
0
            spdlog::error("    Instance: Argument '{}' missing required export "
859
0
                          "'{}' for import"sv,
860
0
                          ImportName, *Missing);
861
0
            spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
862
0
            return Unexpect(ErrCode::Value::InstanceMissingExpectedExport);
863
0
          }
864
0
        }
865
0
      }
866
0
      return {};
867
0
    };
868
0
    if (Comp != nullptr) {
869
0
      for (const auto &Sec : Comp->getSections()) {
870
0
        if (const auto *IS = std::get_if<AST::Component::ImportSection>(&Sec)) {
871
0
          for (const auto &Imp : IS->getContent()) {
872
0
            EXPECTED_TRY(checkImport(Imp.getName(), Imp.getDesc()));
873
0
          }
874
0
        }
875
0
      }
876
0
    } else if (CompTy != nullptr) {
877
0
      for (const auto &CD : CompTy->getDecl()) {
878
0
        if (CD.isImportDecl()) {
879
0
          const auto &ID = CD.getImport();
880
0
          EXPECTED_TRY(checkImport(ID.getName(), ID.getExternDesc()));
881
0
        }
882
0
      }
883
0
    }
884
885
    // Allocate the slot + populate exports so alias-export can resolve.
886
    // Raw-Component path resolves the IT for instance-typed exports;
887
    // ComponentType path registers sort-kind only (GAP-DECL-ED).
888
    // TODO (GAP-I-3): fresh ResourceIds + per-export resource remapping.
889
0
    uint32_t InstanceIdx = CompCtx.addInstance();
890
0
    if (Comp != nullptr) {
891
0
      for (const auto &Sec : Comp->getSections()) {
892
0
        const auto *ES = std::get_if<AST::Component::ExportSection>(&Sec);
893
0
        if (ES == nullptr) {
894
0
          continue;
895
0
        }
896
0
        for (const auto &Exp : ES->getContent()) {
897
0
          const auto &ExpSort = Exp.getSortIndex().getSort();
898
0
          if (ExpSort.isCore()) {
899
0
            continue;
900
0
          }
901
0
          const AST::Component::InstanceType *IT = nullptr;
902
0
          if (ExpSort.getSortType() ==
903
0
                  AST::Component::Sort::SortType::Instance &&
904
0
              Exp.getDesc().has_value() &&
905
0
              Exp.getDesc()->getDescType() ==
906
0
                  AST::Component::ExternDesc::DescType::InstanceType) {
907
0
            IT = resolveChildInstanceType(*Comp, Exp.getDesc()->getTypeIndex());
908
0
          }
909
0
          CompCtx.addInstanceExport(InstanceIdx, Exp.getName(),
910
0
                                    ExpSort.getSortType(), IT);
911
0
        }
912
0
      }
913
0
    } else if (CompTy != nullptr) {
914
0
      for (const auto &CD : CompTy->getDecl()) {
915
0
        if (!CD.isInstanceDecl()) {
916
0
          continue;
917
0
        }
918
0
        const auto &ID = CD.getInstance();
919
0
        if (!ID.isExportDecl()) {
920
0
          continue;
921
0
        }
922
0
        const auto &ED = ID.getExport();
923
0
        const auto OptST = descTypeToSortType(ED.getExternDesc().getDescType());
924
0
        if (!OptST.has_value()) {
925
0
          continue; // `(core module)` export — not a component-side entry.
926
0
        }
927
0
        CompCtx.addInstanceExport(InstanceIdx, ED.getName(), *OptST);
928
0
      }
929
0
    }
930
0
  } else if (Inst.isInlineExport()) {
931
    // Allocate the instance first so exports can be registered on it.
932
0
    uint32_t InstanceIdx = CompCtx.addInstance();
933
934
    // Check the sort index bound of the inline exports, then record each
935
    // export on the new instance with a NestedInstIdx fallback so alias
936
    // chains through inline-export instances can follow the source.
937
    // Inline-export names on a component instance must be strongly-unique.
938
0
    std::unordered_set<std::string_view> SeenExports;
939
0
    for (const auto &Export : Inst.getInlineExports()) {
940
0
      if (!SeenExports.insert(Export.getName()).second) {
941
0
        spdlog::error(ErrCode::Value::ComponentDuplicateName);
942
0
        spdlog::error("    Instance: Duplicate inline-export name '{}'"sv,
943
0
                      Export.getName());
944
0
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
945
0
        return Unexpect(ErrCode::Value::ComponentDuplicateName);
946
0
      }
947
0
      const auto &Sort = Export.getSortIdx().getSort();
948
0
      uint32_t Idx = Export.getSortIdx().getIdx();
949
0
      if (Sort.isCore()) {
950
0
        if (Idx >= CompCtx.getCoreSortIndexSize(Sort.getCoreSortType())) {
951
0
          spdlog::error(ErrCode::Value::InvalidIndex);
952
0
          spdlog::error(
953
0
              "    Instance: Inline export '{}' refers to invalid index {}"sv,
954
0
              Export.getName(), Idx);
955
0
          spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
956
0
          return Unexpect(ErrCode::Value::InvalidIndex);
957
0
        }
958
0
        continue;
959
0
      }
960
0
      if (Idx >= CompCtx.getSortIndexSize(Sort.getSortType())) {
961
0
        spdlog::error(ErrCode::Value::InvalidIndex);
962
0
        spdlog::error(
963
0
            "    Instance: Inline export '{}' refers to invalid index {}"sv,
964
0
            Export.getName(), Idx);
965
0
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
966
0
        return Unexpect(ErrCode::Value::InvalidIndex);
967
0
      }
968
0
      if (Sort.getSortType() == AST::Component::Sort::SortType::Type) {
969
0
        auto SubstitutedIdx =
970
0
            CompCtx.getSubstitutedType(std::string(Export.getName()));
971
0
        if (SubstitutedIdx.has_value() && Idx != SubstitutedIdx.value()) {
972
0
          spdlog::error(ErrCode::Value::InvalidTypeReference);
973
0
          spdlog::error(
974
0
              "    Instance: Inline export '{}' type index {} does not match substituted type index {}"sv,
975
0
              Export.getName(), Idx, *SubstitutedIdx);
976
0
          return Unexpect(ErrCode::Value::InvalidTypeReference);
977
0
        }
978
0
      }
979
0
      std::optional<uint32_t> NestedIdx;
980
0
      const AST::Component::InstanceType *PropagatedIT = nullptr;
981
0
      if (Sort.getSortType() == AST::Component::Sort::SortType::Instance) {
982
0
        NestedIdx = Idx;
983
        // GAP-I-5b: forward source's InstanceType so later ascription /
984
        // subtype checks have a concrete type. Populated only when the
985
        // source slot was bound via ExternDesc::InstanceType; inline-
986
        // export / instantiate sources read nullptr (Phase-3 follow-up).
987
0
        PropagatedIT = CompCtx.getInstance(Idx).Type;
988
0
      }
989
0
      CompCtx.addInstanceExport(InstanceIdx, Export.getName(),
990
0
                                Sort.getSortType(), PropagatedIT, NestedIdx);
991
0
    }
992
0
  } else {
993
0
    assumingUnreachable();
994
0
  }
995
0
  return {};
996
0
}
997
998
0
Expect<void> Validator::validate(const AST::Component::CoreAlias &A) noexcept {
999
  // CoreAlias is always an outer alias.
1000
0
  uint32_t Ct = A.getComponentJump();
1001
0
  uint32_t Idx = A.getIndex();
1002
1003
0
  uint32_t OutLinkCompCnt = 0;
1004
0
  const auto *TargetCtx = &CompCtx.getCurrentContext();
1005
0
  while (Ct > OutLinkCompCnt && TargetCtx != nullptr) {
1006
0
    TargetCtx = TargetCtx->Parent;
1007
0
    OutLinkCompCnt++;
1008
0
  }
1009
0
  if (TargetCtx == nullptr) {
1010
0
    spdlog::error(ErrCode::Value::InvalidIndex);
1011
    // The final hop is the one that ran off the top of the scope chain, so the
1012
    // number of actually-enclosing components is one less than the hop count.
1013
0
    spdlog::error(
1014
0
        "    CoreAlias: outer count {} exceeds enclosing component count {}"sv,
1015
0
        Ct, OutLinkCompCnt - 1);
1016
0
    return Unexpect(ErrCode::Value::InvalidIndex);
1017
0
  }
1018
1019
0
  const auto &Sort = A.getSort();
1020
0
  if (Sort.isCore()) {
1021
0
    if (Idx >= TargetCtx->getCoreSortIndexSize(Sort.getCoreSortType())) {
1022
0
      spdlog::error(ErrCode::Value::InvalidIndex);
1023
0
      spdlog::error("    CoreAlias: outer index {} out of bounds"sv, Idx);
1024
0
      return Unexpect(ErrCode::Value::InvalidIndex);
1025
0
    }
1026
0
    CompCtx.incCoreSortIndexSize(Sort.getCoreSortType());
1027
0
  }
1028
0
  return {};
1029
0
}
1030
1031
0
Expect<void> Validator::validate(const AST::Component::Alias &Alias) noexcept {
1032
0
  const auto &Sort = Alias.getSort();
1033
0
  switch (Alias.getTargetType()) {
1034
0
  case AST::Component::Alias::TargetType::Export: {
1035
0
    const auto Idx = Alias.getExport().first;
1036
0
    const auto &Name = Alias.getExport().second;
1037
1038
0
    if (Sort.isCore()) {
1039
0
      spdlog::error(ErrCode::Value::InvalidTypeReference);
1040
0
      spdlog::error("    Alias export: Mapping an export '{}' to core:sort"sv,
1041
0
                    Name);
1042
0
      return Unexpect(ErrCode::Value::InvalidTypeReference);
1043
0
    }
1044
1045
0
    if (Idx >=
1046
0
        CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Instance)) {
1047
0
      spdlog::error(ErrCode::Value::InvalidIndex);
1048
0
      spdlog::error(
1049
0
          "    Alias export: Export index {} exceeds available component instance index {}"sv,
1050
0
          Idx,
1051
0
          CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Instance));
1052
0
      return Unexpect(ErrCode::Value::InvalidIndex);
1053
0
    }
1054
1055
0
    const auto &InstExports = CompCtx.getInstance(Idx).Exports;
1056
0
    auto It = InstExports.find(std::string(Name));
1057
0
    if (It == InstExports.cend()) {
1058
0
      spdlog::error(ErrCode::Value::ExportNotFound);
1059
0
      spdlog::error(
1060
0
          "    Alias export: No matching export '{}' found in component instance index {}"sv,
1061
0
          Name, Idx);
1062
0
      return Unexpect(ErrCode::Value::ExportNotFound);
1063
0
    }
1064
1065
0
    if (It->second.ST != Sort.getSortType()) {
1066
0
      spdlog::error(ErrCode::Value::InvalidTypeReference);
1067
0
      spdlog::error("    Alias export: Type mapping mismatch for export '{}'"sv,
1068
0
                    Name);
1069
0
      return Unexpect(ErrCode::Value::InvalidTypeReference);
1070
0
    }
1071
0
    return {};
1072
0
  }
1073
0
  case AST::Component::Alias::TargetType::CoreExport: {
1074
0
    const auto Idx = Alias.getExport().first;
1075
0
    const auto &Name = Alias.getExport().second;
1076
1077
0
    if (!Sort.isCore()) {
1078
0
      spdlog::error(ErrCode::Value::InvalidTypeReference);
1079
0
      spdlog::error("    Alias core:export: Mapping a export '{}' to sort"sv,
1080
0
                    Name);
1081
0
      return Unexpect(ErrCode::Value::InvalidTypeReference);
1082
0
    }
1083
1084
0
    if (Idx >= CompCtx.getCoreSortIndexSize(
1085
0
                   AST::Component::Sort::CoreSortType::Instance)) {
1086
0
      spdlog::error(ErrCode::Value::InvalidIndex);
1087
0
      spdlog::error(
1088
0
          "    Alias core:export: Export index {} exceeds available core instance index {}"sv,
1089
0
          Idx,
1090
0
          CompCtx.getCoreSortIndexSize(
1091
0
              AST::Component::Sort::CoreSortType::Instance) -
1092
0
              1);
1093
0
      return Unexpect(ErrCode::Value::InvalidIndex);
1094
0
    }
1095
1096
0
    const auto &CoreExports = CompCtx.getCoreInstance(Idx);
1097
0
    auto It = CoreExports.find(std::string(Name));
1098
0
    if (It == CoreExports.end()) {
1099
0
      spdlog::error(ErrCode::Value::ExportNotFound);
1100
0
      spdlog::error(
1101
0
          "    Alias core:export: No matching export '{}' found in core instance index {}"sv,
1102
0
          Name, Idx);
1103
0
      return Unexpect(ErrCode::Value::ExportNotFound);
1104
0
    }
1105
1106
0
    const auto ExternTy = It->second;
1107
0
    AST::Component::Sort::CoreSortType ST;
1108
0
    switch (ExternTy) {
1109
0
    case ExternalType::Function:
1110
0
      ST = AST::Component::Sort::CoreSortType::Func;
1111
0
      break;
1112
0
    case ExternalType::Table:
1113
0
      ST = AST::Component::Sort::CoreSortType::Table;
1114
0
      break;
1115
0
    case ExternalType::Memory:
1116
0
      ST = AST::Component::Sort::CoreSortType::Memory;
1117
0
      break;
1118
0
    case ExternalType::Global:
1119
0
      ST = AST::Component::Sort::CoreSortType::Global;
1120
0
      break;
1121
0
    case ExternalType::Tag:
1122
0
      ST = AST::Component::Sort::CoreSortType::Tag;
1123
0
      break;
1124
0
    default:
1125
0
      spdlog::error(ErrCode::Value::InvalidTypeReference);
1126
0
      spdlog::error(
1127
0
          "    Alias core:export: Type mapping mismatch for export '{}'"sv,
1128
0
          Name);
1129
0
      return Unexpect(ErrCode::Value::InvalidTypeReference);
1130
0
    }
1131
0
    if (ST != Sort.getCoreSortType()) {
1132
      // The error message differs of the tag core sort.
1133
0
      ErrCode::Value ErrValue = ErrCode::Value::InvalidIndex;
1134
0
      if (Sort.getCoreSortType() == AST::Component::Sort::CoreSortType::Tag) {
1135
0
        ErrValue = ErrCode::Value::UnknownCoreTag;
1136
0
      }
1137
0
      spdlog::error(ErrValue);
1138
0
      spdlog::error(
1139
0
          "    Alias core:export: Type mapping mismatch for export '{}'"sv,
1140
0
          Name);
1141
0
      return Unexpect(ErrValue);
1142
0
    }
1143
0
    return {};
1144
0
  }
1145
0
  case AST::Component::Alias::TargetType::Outer: {
1146
0
    const auto Ct = Alias.getOuter().first;
1147
0
    const auto Idx = Alias.getOuter().second;
1148
1149
0
    uint32_t OutLinkCompCnt = 0;
1150
0
    const auto *TargetCtx = &CompCtx.getCurrentContext();
1151
0
    while (Ct > OutLinkCompCnt && TargetCtx != nullptr) {
1152
0
      TargetCtx = TargetCtx->Parent;
1153
0
      OutLinkCompCnt++;
1154
0
    }
1155
0
    if (TargetCtx == nullptr) {
1156
0
      spdlog::error(ErrCode::Value::InvalidIndex);
1157
      // The final hop is the one that ran off the top of the scope chain, so
1158
      // the number of actually-enclosing components is one less than the hop
1159
      // count.
1160
0
      spdlog::error(
1161
0
          "    Alias outer: Component out-link count {} is exceeding the enclosing component count {}"sv,
1162
0
          Ct, OutLinkCompCnt - 1);
1163
0
      return Unexpect(ErrCode::Value::InvalidIndex);
1164
0
    }
1165
1166
0
    if (Sort.isCore()) {
1167
0
      if (Sort.getCoreSortType() !=
1168
0
              AST::Component::Sort::CoreSortType::Module &&
1169
0
          Sort.getCoreSortType() != AST::Component::Sort::CoreSortType::Type) {
1170
0
        spdlog::error(ErrCode::Value::InvalidTypeReference);
1171
0
        spdlog::error(
1172
0
            "    Alias outer: Invalid core:sort for outer alias. Only type, module, or component are allowed."sv);
1173
0
        return Unexpect(ErrCode::Value::InvalidTypeReference);
1174
0
      }
1175
0
      if (Idx >= TargetCtx->getCoreSortIndexSize(Sort.getCoreSortType())) {
1176
0
        spdlog::error(ErrCode::Value::InvalidIndex);
1177
0
        spdlog::error(
1178
0
            "    Alias outer: core:sort index {} invalid in component context"sv,
1179
0
            Idx);
1180
0
        return Unexpect(ErrCode::Value::InvalidIndex);
1181
0
      }
1182
0
    } else {
1183
0
      if (Sort.getSortType() != AST::Component::Sort::SortType::Type &&
1184
0
          Sort.getSortType() != AST::Component::Sort::SortType::Component) {
1185
0
        spdlog::error(ErrCode::Value::InvalidTypeReference);
1186
0
        spdlog::error(
1187
0
            "    Alias outer: Invalid sort for outer alias. Only type, module, or component are allowed."sv);
1188
0
        return Unexpect(ErrCode::Value::InvalidTypeReference);
1189
0
      }
1190
0
      if (Idx >= TargetCtx->getSortIndexSize(Sort.getSortType())) {
1191
0
        spdlog::error(ErrCode::Value::InvalidIndex);
1192
0
        spdlog::error(
1193
0
            "    Alias outer: sort index {} invalid in component context"sv,
1194
0
            Idx);
1195
0
        return Unexpect(ErrCode::Value::InvalidIndex);
1196
0
      }
1197
      // Outer-aliasing a resource type is permitted: the alias preserves the
1198
      // resource's type identity rather than introducing a fresh generative
1199
      // resource, so a nested type/component can legitimately refer to a
1200
      // resource in an enclosing scope (e.g. an instance type referring to a
1201
      // resource imported by its enclosing component type).
1202
      //
1203
      // TODO: implement wasm-tools' full free-variable rule — reject an outer
1204
      // alias to a type whose transitive free variables include resources that
1205
      // would escape a crossed component boundary. That needs a free-variable
1206
      // walker over type bodies, tracked alongside the Phase 3/4 type-handle
1207
      // work (GAP-TH-1, GAP-EX-5).
1208
0
    }
1209
0
    return {};
1210
0
  }
1211
0
  default:
1212
0
    assumingUnreachable();
1213
0
  }
1214
0
}
1215
1216
Expect<void>
1217
0
Validator::validate(const AST::Component::CoreDefType &DType) noexcept {
1218
0
  if (DType.isRecType()) {
1219
    // Each sub-type in the rec group gets its own entry in core:type.
1220
0
    for (const auto &ST : DType.getSubTypes()) {
1221
0
      CompCtx.addCoreType(&ST);
1222
0
    }
1223
0
  } else if (DType.isModuleType()) {
1224
    // Module types are validated with an initially-empty type index space.
1225
0
    CompCtx.enterTypeDefinition();
1226
0
    for (const auto &Decl : DType.getModuleType()) {
1227
0
      EXPECTED_TRY(validate(Decl).map_error([](auto E) {
1228
0
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreDefType));
1229
0
        return E;
1230
0
      }));
1231
0
    }
1232
0
    CompCtx.exitComponent();
1233
    // Module type gets a core:type slot (Body=nullptr) bound to the
1234
    // CoreDefType so (core module (type i)) can recover the body — GAP-CI-1.
1235
0
    uint32_t NewTypeIdx = CompCtx.addCoreType();
1236
0
    CompCtx.setCoreModuleType(NewTypeIdx, &DType);
1237
0
  } else {
1238
0
    assumingUnreachable();
1239
0
  }
1240
0
  return {};
1241
0
}
1242
1243
Expect<void>
1244
0
Validator::validate(const AST::Component::DefType &DType) noexcept {
1245
0
  auto ReportError = [](auto E) {
1246
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_DefType));
1247
0
    return E;
1248
0
  };
1249
1250
0
  if (DType.isDefValType()) {
1251
0
    EXPECTED_TRY(validate(DType.getDefValType()).map_error(ReportError));
1252
0
  } else if (DType.isFuncType()) {
1253
0
    EXPECTED_TRY(validate(DType.getFuncType()).map_error(ReportError));
1254
0
  } else if (DType.isComponentType()) {
1255
0
    EXPECTED_TRY(validate(DType.getComponentType()).map_error(ReportError));
1256
0
  } else if (DType.isInstanceType()) {
1257
0
    EXPECTED_TRY(validate(DType.getInstanceType()).map_error(ReportError));
1258
0
  } else if (DType.isResourceType()) {
1259
0
    EXPECTED_TRY(validate(DType.getResourceType()).map_error(ReportError));
1260
0
  } else {
1261
0
    assumingUnreachable();
1262
0
  }
1263
  // addType records body/id/locality for resource DefTypes in one step.
1264
0
  CompCtx.addType(&DType);
1265
0
  return {};
1266
0
}
1267
1268
Expect<void>
1269
0
Validator::validate(const AST::Component::Canonical &Canon) noexcept {
1270
0
  switch (Canon.getOpCode()) {
1271
0
  case ComponentCanonOpCode::Lift:
1272
0
    return validateCanonLift(Canon);
1273
0
  case ComponentCanonOpCode::Lower:
1274
0
    return validateCanonLower(Canon);
1275
0
  case ComponentCanonOpCode::Resource__new:
1276
0
    return validateCanonResourceNew(Canon);
1277
0
  case ComponentCanonOpCode::Resource__rep:
1278
0
    return validateCanonResourceRep(Canon);
1279
0
  case ComponentCanonOpCode::Resource__drop:
1280
0
  case ComponentCanonOpCode::Resource__drop_async:
1281
0
    return validateCanonResourceDrop(Canon);
1282
0
  default:
1283
0
    spdlog::error(ErrCode::Value::ComponentNotImplValidator);
1284
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1285
0
    return Unexpect(ErrCode::Value::ComponentNotImplValidator);
1286
0
  }
1287
0
}
1288
1289
Expect<void> Validator::validateCanonOptions(
1290
    ComponentCanonOpCode Code,
1291
0
    Span<const AST::Component::CanonOpt> Opts) noexcept {
1292
0
  using OptCode = ComponentCanonOptCode;
1293
0
  using CanonOp = ComponentCanonOpCode;
1294
1295
  // Only canon lift/lower accept canonical options. Any other built-in
1296
  // (resource.new/rep/drop, drop_async, ...) must be invoked without options.
1297
0
  if (Code != CanonOp::Lift && Code != CanonOp::Lower && !Opts.empty()) {
1298
0
    spdlog::error(ErrCode::Value::InvalidCanonOption);
1299
0
    spdlog::error(
1300
0
        "    canonical options are not allowed for this canon built-in"sv);
1301
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1302
0
    return Unexpect(ErrCode::Value::InvalidCanonOption);
1303
0
  }
1304
1305
0
  bool HasEncoding = false;
1306
0
  bool HasMemory = false;
1307
0
  bool HasRealloc = false;
1308
0
  bool HasPostReturn = false;
1309
0
  bool HasAsync = false;
1310
0
  bool HasCallback = false;
1311
0
  bool HasAlwaysTaskReturn = false;
1312
0
  uint32_t ReallocIdx = 0;
1313
0
  uint32_t CallbackIdx = 0;
1314
0
  uint32_t PostReturnIdx = 0;
1315
0
  uint32_t MemoryIdx = 0;
1316
1317
0
  auto RejectDup = [&](const char *Name) -> Expect<void> {
1318
0
    spdlog::error(ErrCode::Value::InvalidCanonOption);
1319
0
    spdlog::error("    canonical option '{}' appears more than once"sv, Name);
1320
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1321
0
    return Unexpect(ErrCode::Value::InvalidCanonOption);
1322
0
  };
1323
0
  auto RejectSite = [&](const char *Name) -> Expect<void> {
1324
0
    spdlog::error(ErrCode::Value::InvalidCanonOption);
1325
0
    spdlog::error(
1326
0
        "    canonical option '{}' is not allowed in this canon built-in"sv,
1327
0
        Name);
1328
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1329
0
    return Unexpect(ErrCode::Value::InvalidCanonOption);
1330
0
  };
1331
1332
0
  for (const auto &Opt : Opts) {
1333
0
    switch (Opt.getCode()) {
1334
0
    case OptCode::Encode_UTF8:
1335
0
    case OptCode::Encode_UTF16:
1336
0
    case OptCode::Encode_Latin1:
1337
0
      if (HasEncoding) {
1338
0
        return RejectDup("string-encoding");
1339
0
      }
1340
0
      HasEncoding = true;
1341
0
      break;
1342
0
    case OptCode::Memory:
1343
0
      if (HasMemory) {
1344
0
        return RejectDup("memory");
1345
0
      }
1346
0
      HasMemory = true;
1347
0
      MemoryIdx = Opt.getIndex();
1348
0
      break;
1349
0
    case OptCode::Realloc:
1350
0
      if (HasRealloc) {
1351
0
        return RejectDup("realloc");
1352
0
      }
1353
0
      HasRealloc = true;
1354
0
      ReallocIdx = Opt.getIndex();
1355
0
      break;
1356
0
    case OptCode::PostReturn:
1357
0
      if (Code != CanonOp::Lift) {
1358
0
        return RejectSite("post-return");
1359
0
      }
1360
0
      if (HasPostReturn) {
1361
0
        return RejectDup("post-return");
1362
0
      }
1363
0
      HasPostReturn = true;
1364
0
      PostReturnIdx = Opt.getIndex();
1365
0
      break;
1366
0
    case OptCode::Async:
1367
0
      if (HasAsync) {
1368
0
        return RejectDup("async");
1369
0
      }
1370
0
      HasAsync = true;
1371
0
      break;
1372
0
    case OptCode::Callback:
1373
0
      if (Code != CanonOp::Lift) {
1374
0
        return RejectSite("callback");
1375
0
      }
1376
0
      if (HasCallback) {
1377
0
        return RejectDup("callback");
1378
0
      }
1379
0
      HasCallback = true;
1380
0
      CallbackIdx = Opt.getIndex();
1381
0
      break;
1382
0
    case OptCode::AlwaysTaskReturn:
1383
0
      if (Code != CanonOp::Lift) {
1384
0
        return RejectSite("always-task-return");
1385
0
      }
1386
0
      if (HasAlwaysTaskReturn) {
1387
0
        return RejectDup("always-task-return");
1388
0
      }
1389
0
      HasAlwaysTaskReturn = true;
1390
0
      break;
1391
0
    default:
1392
0
      spdlog::error(ErrCode::Value::UnknownCanonicalOption);
1393
0
      spdlog::error("    unknown canonical option code 0x{:02x}"sv,
1394
0
                    static_cast<unsigned>(Opt.getCode()));
1395
0
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1396
0
      return Unexpect(ErrCode::Value::UnknownCanonicalOption);
1397
0
    }
1398
0
  }
1399
1400
  // Structural rules.
1401
0
  if (HasPostReturn && HasAsync) {
1402
0
    spdlog::error(ErrCode::Value::InvalidCanonOption);
1403
0
    spdlog::error(
1404
0
        "    canonical options 'post-return' and 'async' are mutually exclusive"sv);
1405
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1406
0
    return Unexpect(ErrCode::Value::InvalidCanonOption);
1407
0
  }
1408
0
  if (HasCallback && !HasAsync) {
1409
0
    spdlog::error(ErrCode::Value::InvalidCanonOption);
1410
0
    spdlog::error("    canonical option 'callback' requires 'async'"sv);
1411
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1412
0
    return Unexpect(ErrCode::Value::InvalidCanonOption);
1413
0
  }
1414
0
  if (HasAlwaysTaskReturn && !HasAsync) {
1415
0
    spdlog::error(ErrCode::Value::InvalidCanonOption);
1416
0
    spdlog::error(
1417
0
        "    canonical option 'always-task-return' requires 'async'"sv);
1418
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1419
0
    return Unexpect(ErrCode::Value::InvalidCanonOption);
1420
0
  }
1421
0
  if (HasRealloc && !HasMemory) {
1422
0
    spdlog::error(ErrCode::Value::InvalidCanonOption);
1423
0
    spdlog::error(
1424
0
        "    canonical option 'realloc' requires 'memory' to also be specified"sv);
1425
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1426
0
    return Unexpect(ErrCode::Value::InvalidCanonOption);
1427
0
  }
1428
1429
  // Index bounds checks. Core func signature body checks (realloc/callback)
1430
  // deferred as GAP-C-5b once getCoreFunc signatures are populated.
1431
0
  if (HasMemory &&
1432
0
      MemoryIdx >= CompCtx.getCoreSortIndexSize(
1433
0
                       AST::Component::Sort::CoreSortType::Memory)) {
1434
0
    spdlog::error(ErrCode::Value::InvalidIndex);
1435
0
    spdlog::error(
1436
0
        "    canonical option 'memory': core memory index {} out of bounds"sv,
1437
0
        MemoryIdx);
1438
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1439
0
    return Unexpect(ErrCode::Value::InvalidIndex);
1440
0
  }
1441
0
  const uint32_t CoreFuncSpaceSize =
1442
0
      CompCtx.getCoreSortIndexSize(AST::Component::Sort::CoreSortType::Func);
1443
0
  if (HasRealloc && ReallocIdx >= CoreFuncSpaceSize) {
1444
0
    spdlog::error(ErrCode::Value::InvalidIndex);
1445
0
    spdlog::error(
1446
0
        "    canonical option 'realloc': core func index {} out of bounds"sv,
1447
0
        ReallocIdx);
1448
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1449
0
    return Unexpect(ErrCode::Value::InvalidIndex);
1450
0
  }
1451
0
  if (HasCallback && CallbackIdx >= CoreFuncSpaceSize) {
1452
0
    spdlog::error(ErrCode::Value::InvalidIndex);
1453
0
    spdlog::error(
1454
0
        "    canonical option 'callback': core func index {} out of bounds"sv,
1455
0
        CallbackIdx);
1456
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1457
0
    return Unexpect(ErrCode::Value::InvalidIndex);
1458
0
  }
1459
0
  if (HasPostReturn && PostReturnIdx >= CoreFuncSpaceSize) {
1460
0
    spdlog::error(ErrCode::Value::InvalidIndex);
1461
0
    spdlog::error(
1462
0
        "    canonical option 'post-return': core func index {} out of bounds"sv,
1463
0
        PostReturnIdx);
1464
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1465
0
    return Unexpect(ErrCode::Value::InvalidIndex);
1466
0
  }
1467
0
  return {};
1468
0
}
1469
1470
Expect<void>
1471
0
Validator::validateCanonLift(const AST::Component::Canonical &Canon) noexcept {
1472
0
  const uint32_t CoreFuncIdx = Canon.getIndex();
1473
0
  const uint32_t CoreFuncSpaceSize =
1474
0
      CompCtx.getCoreSortIndexSize(AST::Component::Sort::CoreSortType::Func);
1475
  // 1. Core func index bounds.
1476
0
  if (CoreFuncIdx >= CoreFuncSpaceSize) {
1477
0
    spdlog::error(ErrCode::Value::InvalidIndex);
1478
0
    spdlog::error(
1479
0
        "    canon lift: core func index {} exceeds core func index space size {}"sv,
1480
0
        CoreFuncIdx, CoreFuncSpaceSize);
1481
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1482
0
    return Unexpect(ErrCode::Value::InvalidIndex);
1483
0
  }
1484
0
  const uint32_t TypeIdx = Canon.getTargetIndex();
1485
0
  const uint32_t TypeSpaceSize =
1486
0
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type);
1487
  // 2. Target type index bounds.
1488
0
  if (TypeIdx >= TypeSpaceSize) {
1489
0
    spdlog::error(ErrCode::Value::InvalidIndex);
1490
0
    spdlog::error(
1491
0
        "    canon lift: type index {} exceeds type index space size {}"sv,
1492
0
        TypeIdx, TypeSpaceSize);
1493
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1494
0
    return Unexpect(ErrCode::Value::InvalidIndex);
1495
0
  }
1496
  // 3. Target type must be a component FuncType.
1497
0
  const auto *DT = CompCtx.getDefType(TypeIdx);
1498
0
  if (DT == nullptr) {
1499
    // Unresolved slot: the index was registered by an import or outer alias
1500
    // but the concrete definition has not been filled in yet.
1501
0
    spdlog::error(ErrCode::Value::InvalidTypeReference);
1502
0
    spdlog::error(
1503
0
        "    canon lift: target type index {} is an unresolved type slot"sv,
1504
0
        TypeIdx);
1505
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1506
0
    return Unexpect(ErrCode::Value::InvalidTypeReference);
1507
0
  }
1508
0
  if (!DT->isFuncType()) {
1509
0
    spdlog::error(ErrCode::Value::InvalidTypeReference);
1510
0
    spdlog::error(
1511
0
        "    canon lift: target type index {} does not reference a component func type"sv,
1512
0
        TypeIdx);
1513
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1514
0
    return Unexpect(ErrCode::Value::InvalidTypeReference);
1515
0
  }
1516
  // 4. Validate canonical options (Lift site allows all).
1517
0
  EXPECTED_TRY(validateCanonOptions(Canon.getOpCode(), Canon.getOptions()));
1518
  // 5. Allocate component func, binding the resolved FuncType. Full ABI
1519
  // signature match (flat_lifted) deferred as GAP-C-1b.
1520
0
  CompCtx.addFunc(&DT->getFuncType());
1521
0
  return {};
1522
0
}
1523
1524
Expect<void>
1525
0
Validator::validateCanonLower(const AST::Component::Canonical &Canon) noexcept {
1526
0
  const uint32_t FuncIdx = Canon.getIndex();
1527
0
  const uint32_t FuncSpaceSize =
1528
0
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Func);
1529
  // 1. Component func index bounds.
1530
0
  if (FuncIdx >= FuncSpaceSize) {
1531
0
    spdlog::error(ErrCode::Value::InvalidIndex);
1532
0
    spdlog::error(
1533
0
        "    canon lower: component func index {} exceeds func index space size {}"sv,
1534
0
        FuncIdx, FuncSpaceSize);
1535
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1536
0
    return Unexpect(ErrCode::Value::InvalidIndex);
1537
0
  }
1538
  // 2. Validate canonical options (per-site rules for Lower).
1539
0
  EXPECTED_TRY(validateCanonOptions(Canon.getOpCode(), Canon.getOptions()));
1540
  // 3. Allocate the resulting core func. Full ABI signature synthesis
1541
  // (flatten_functype for lower) deferred as GAP-C-2b.
1542
0
  CompCtx.addCoreFunc();
1543
0
  return {};
1544
0
}
1545
1546
Expect<void> Validator::validateCanonResourceNew(
1547
0
    const AST::Component::Canonical &Canon) noexcept {
1548
0
  const uint32_t Idx = Canon.getIndex();
1549
0
  const uint32_t TypeSpaceSize =
1550
0
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type);
1551
  // 1. Type index bounds.
1552
0
  if (Idx >= TypeSpaceSize) {
1553
0
    spdlog::error(ErrCode::Value::InvalidIndex);
1554
0
    spdlog::error(
1555
0
        "    canon resource.new: type index {} exceeds type index space size {}"sv,
1556
0
        Idx, TypeSpaceSize);
1557
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1558
0
    return Unexpect(ErrCode::Value::InvalidIndex);
1559
0
  }
1560
  // 2. Type must be a locally-defined resource.
1561
0
  const auto *RInfo = CompCtx.getResource(Idx);
1562
0
  if (RInfo == nullptr) {
1563
0
    spdlog::error(ErrCode::Value::InvalidTypeReference);
1564
0
    spdlog::error(
1565
0
        "    canon resource.new: type index {} does not reference a resource"sv,
1566
0
        Idx);
1567
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1568
0
    return Unexpect(ErrCode::Value::InvalidTypeReference);
1569
0
  }
1570
0
  if (!RInfo->LocallyDefined) {
1571
0
    spdlog::error(ErrCode::Value::InvalidTypeReference);
1572
0
    spdlog::error(
1573
0
        "    canon resource.new: type index {} is not locally defined (imported or outer-aliased resources are not allowed)"sv,
1574
0
        Idx);
1575
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1576
0
    return Unexpect(ErrCode::Value::InvalidTypeReference);
1577
0
  }
1578
  // 4. Validate canonical options.
1579
0
  EXPECTED_TRY(validateCanonOptions(Canon.getOpCode(), Canon.getOptions()));
1580
  // 5. Allocate the resulting core func with synthesized signature
1581
  // [i32] -> [i32] (rep i32 in, new handle out).
1582
0
  CompCtx.addCoreFunc(&CoreFuncType_I32_I32);
1583
0
  return {};
1584
0
}
1585
1586
Expect<void> Validator::validateCanonResourceRep(
1587
0
    const AST::Component::Canonical &Canon) noexcept {
1588
0
  const uint32_t Idx = Canon.getIndex();
1589
0
  const uint32_t TypeSpaceSize =
1590
0
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type);
1591
  // 1. Type index bounds.
1592
0
  if (Idx >= TypeSpaceSize) {
1593
0
    spdlog::error(ErrCode::Value::InvalidIndex);
1594
0
    spdlog::error(
1595
0
        "    canon resource.rep: type index {} exceeds type index space size {}"sv,
1596
0
        Idx, TypeSpaceSize);
1597
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1598
0
    return Unexpect(ErrCode::Value::InvalidIndex);
1599
0
  }
1600
  // 2. Type must be a locally-defined resource.
1601
0
  const auto *RInfo = CompCtx.getResource(Idx);
1602
0
  if (RInfo == nullptr) {
1603
0
    spdlog::error(ErrCode::Value::InvalidTypeReference);
1604
0
    spdlog::error(
1605
0
        "    canon resource.rep: type index {} does not reference a resource"sv,
1606
0
        Idx);
1607
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1608
0
    return Unexpect(ErrCode::Value::InvalidTypeReference);
1609
0
  }
1610
0
  if (!RInfo->LocallyDefined) {
1611
0
    spdlog::error(ErrCode::Value::InvalidTypeReference);
1612
0
    spdlog::error(
1613
0
        "    canon resource.rep: type index {} is not locally defined (imported or outer-aliased resources are not allowed)"sv,
1614
0
        Idx);
1615
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1616
0
    return Unexpect(ErrCode::Value::InvalidTypeReference);
1617
0
  }
1618
  // 4. Validate canonical options.
1619
0
  EXPECTED_TRY(validateCanonOptions(Canon.getOpCode(), Canon.getOptions()));
1620
  // 5. Allocate the resulting core func with synthesized signature
1621
  // [i32] -> [i32] (handle in, rep out).
1622
0
  CompCtx.addCoreFunc(&CoreFuncType_I32_I32);
1623
0
  return {};
1624
0
}
1625
1626
Expect<void> Validator::validateCanonResourceDrop(
1627
0
    const AST::Component::Canonical &Canon) noexcept {
1628
0
  const uint32_t Idx = Canon.getIndex();
1629
0
  const uint32_t TypeSpaceSize =
1630
0
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type);
1631
  // 1. Type index bounds.
1632
0
  if (Idx >= TypeSpaceSize) {
1633
0
    spdlog::error(ErrCode::Value::InvalidIndex);
1634
0
    spdlog::error(
1635
0
        "    canon resource.drop: type index {} exceeds type index space size {}"sv,
1636
0
        Idx, TypeSpaceSize);
1637
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1638
0
    return Unexpect(ErrCode::Value::InvalidIndex);
1639
0
  }
1640
  // 2. Type must be a resource type.
1641
0
  if (CompCtx.getResource(Idx) == nullptr) {
1642
0
    spdlog::error(ErrCode::Value::InvalidTypeReference);
1643
0
    spdlog::error(
1644
0
        "    canon resource.drop: type index {} does not reference a resource"sv,
1645
0
        Idx);
1646
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1647
0
    return Unexpect(ErrCode::Value::InvalidTypeReference);
1648
0
  }
1649
  // 3. resource.drop accepts both local and imported resources — no locality
1650
  // check.
1651
  // 4. Validate canonical options.
1652
0
  EXPECTED_TRY(validateCanonOptions(Canon.getOpCode(), Canon.getOptions()));
1653
  // 5. Allocate the resulting core func with synthesized signature
1654
  // [i32] -> [] (handle in, no result — matches the resource destructor
1655
  // shape required by validate(ResourceType)).
1656
0
  CompCtx.addCoreFunc(&CoreFuncType_I32_Void);
1657
0
  return {};
1658
0
}
1659
1660
0
Expect<void> Validator::validate(const AST::Component::Import &Im) noexcept {
1661
  // Validation steps:
1662
  //   1. Validate the externdesc and introduce the imported entity into
1663
  //      its sort's index space.
1664
  //   2. Parse the import name per the structured import-name grammar.
1665
  //   3. Enforce the annotated-name constraints ([constructor]/[method]/
1666
  //      [static] only on func imports).
1667
  //   4. Reject duplicate import names (strongly-unique across imports).
1668
  //
1669
  // The per-annotated-name structural checks (constructor result type,
1670
  // method `self` param, static resource name in scope) are not yet
1671
  // implemented and tracked as follow-ups below.
1672
  // Snapshot the type space size before validating the desc so we can
1673
  // recover the new type index allocated by a TypeBound (sub resource).
1674
0
  const uint32_t TypeSpaceBefore =
1675
0
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type);
1676
1677
0
  EXPECTED_TRY(validate(Im.getDesc()).map_error([](auto E) {
1678
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Import));
1679
0
    return E;
1680
0
  }));
1681
1682
0
  EXPECTED_TRY(ComponentName CName,
1683
0
               ComponentName::parse(Im.getName()).map_error([](auto E) {
1684
0
                 spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Import));
1685
0
                 return E;
1686
0
               }));
1687
1688
  // Annotated plainnames ([constructor], [method], [static]) can only appear
1689
  // on func imports.
1690
0
  switch (CName.getKind()) {
1691
0
  case ComponentNameKind::Constructor:
1692
0
  case ComponentNameKind::Method:
1693
0
  case ComponentNameKind::Static:
1694
0
    if (Im.getDesc().getDescType() !=
1695
0
        AST::Component::ExternDesc::DescType::FuncType) {
1696
0
      spdlog::error(ErrCode::Value::ComponentInvalidName);
1697
0
      spdlog::error("    Import: annotated name requires func type"sv);
1698
0
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Import));
1699
0
      return Unexpect(ErrCode::Value::ComponentInvalidName);
1700
0
    }
1701
0
    break;
1702
0
  default:
1703
0
    break;
1704
0
  }
1705
1706
  // Resolve the resource name referenced by an annotated name. The resource
1707
  // must have been previously introduced by a TypeBound import or export
1708
  // with a kebab-case label in this scope.
1709
  // TODO: extend to the full structural checks once func-type bodies are
1710
  // walked here:
1711
  //   - [constructor]R: result type must be (own $R).
1712
  //   - [method]R.f:    first param must be (borrow $R).
1713
  //   - [static]R.f:    no `self` param of (borrow $R).
1714
0
  std::string_view ResourceLabel;
1715
0
  switch (CName.getKind()) {
1716
0
  case ComponentNameKind::Constructor:
1717
0
    ResourceLabel = CName.getDetail().get<ConstructorDetail>().Label;
1718
0
    break;
1719
0
  case ComponentNameKind::Method:
1720
0
    ResourceLabel = CName.getDetail().get<MethodDetail>().Resource;
1721
0
    break;
1722
0
  case ComponentNameKind::Static:
1723
0
    ResourceLabel = CName.getDetail().get<StaticDetail>().Resource;
1724
0
    break;
1725
0
  default:
1726
0
    break;
1727
0
  }
1728
0
  if (!ResourceLabel.empty() && !CompCtx.hasResourceLabel(ResourceLabel)) {
1729
0
    spdlog::error(ErrCode::Value::ComponentInvalidName);
1730
0
    spdlog::error(
1731
0
        "    Import: annotated name references unknown resource '{}'"sv,
1732
0
        ResourceLabel);
1733
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Import));
1734
0
    return Unexpect(ErrCode::Value::ComponentInvalidName);
1735
0
  }
1736
1737
0
  if (!CompCtx.addImportedName(CName)) {
1738
0
    spdlog::error(ErrCode::Value::ComponentDuplicateName);
1739
0
    spdlog::error("    Import: Duplicate import name"sv);
1740
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Import));
1741
0
    return Unexpect(ErrCode::Value::ComponentDuplicateName);
1742
0
  }
1743
1744
  // If this import introduced a TypeBound resource with a label name,
1745
  // register the label so subsequent annotated names can reference it.
1746
0
  if (Im.getDesc().getDescType() ==
1747
0
          AST::Component::ExternDesc::DescType::TypeBound &&
1748
0
      CName.getKind() == ComponentNameKind::Label) {
1749
0
    CompCtx.addResourceLabel(Im.getName(), TypeSpaceBefore);
1750
0
  }
1751
1752
0
  return {};
1753
0
}
1754
1755
0
Expect<void> Validator::validate(const AST::Component::Export &Ex) noexcept {
1756
  // Validation steps:
1757
  //   1. `sortidx` is in-bounds and (for core sorts) must be `core module`.
1758
  //   2. If an `externdesc` ascription is present, it must be a supertype
1759
  //      of the inferred externdesc of the `sortidx` (kind-only check for
1760
  //      now; structural subtype is a follow-up).
1761
  //   3. The export name parses under the export-name grammar.
1762
  //   4. The export name is strongly-unique across exports.
1763
  //   5. The export introduces a new index aliasing the definition in the
1764
  //      component's own index space for its sort.
1765
  //
1766
  // Not yet enforced: transitive resource-avoidance in exported types;
1767
  // flipping the "consumed" flag for value exports.
1768
1769
  // Validate the sortidx bounds.
1770
0
  const auto &Sort = Ex.getSortIndex().getSort();
1771
0
  uint32_t Idx = Ex.getSortIndex().getIdx();
1772
0
  if (Sort.isCore()) {
1773
    // The externdesc grammar permits only `core module` as a component-level
1774
    // core export — no other core sort is exportable from a component.
1775
0
    if (Sort.getCoreSortType() != AST::Component::Sort::CoreSortType::Module) {
1776
0
      spdlog::error(ErrCode::Value::InvalidTypeReference);
1777
0
      spdlog::error(
1778
0
          "    Export: core sort other than `core module` is not allowed"sv);
1779
0
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export));
1780
0
      return Unexpect(ErrCode::Value::InvalidTypeReference);
1781
0
    }
1782
0
    if (Idx >= CompCtx.getCoreSortIndexSize(Sort.getCoreSortType())) {
1783
0
      spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds);
1784
0
      spdlog::error("    Export: sort index {} out of bounds"sv, Idx);
1785
0
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export));
1786
0
      return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds);
1787
0
    }
1788
0
  } else {
1789
0
    if (Idx >= CompCtx.getSortIndexSize(Sort.getSortType())) {
1790
0
      spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds);
1791
0
      spdlog::error("    Export: sort index {} out of bounds"sv, Idx);
1792
0
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export));
1793
0
      return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds);
1794
0
    }
1795
0
  }
1796
1797
  // If an externdesc ascription is present, the spec requires it to be a
1798
  // supertype of the inferred externdesc of the sortidx. For now we only
1799
  // enforce that the ascription's kind matches the sortidx's sort; full
1800
  // structural subtype between ascription and inferred type is not yet
1801
  // implemented.
1802
0
  if (Ex.getDesc().has_value() &&
1803
0
      !sortMatchesDescType(Sort, Ex.getDesc()->getDescType())) {
1804
0
    spdlog::error(ErrCode::Value::ExportAscriptionIncompatible);
1805
0
    spdlog::error(
1806
0
        "    Export: ascribed externdesc kind does not match sortidx sort"sv);
1807
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export));
1808
0
    return Unexpect(ErrCode::Value::ExportAscriptionIncompatible);
1809
0
  }
1810
1811
  // Validate name grammar, then enforce strong-uniqueness across exports.
1812
0
  EXPECTED_TRY(ComponentName CName,
1813
0
               validateExportName(Ex.getName()).map_error([](auto E) {
1814
0
                 spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export));
1815
0
                 return E;
1816
0
               }));
1817
0
  if (!CompCtx.addExportedName(CName)) {
1818
0
    spdlog::error(ErrCode::Value::ComponentDuplicateName);
1819
0
    spdlog::error("    Export: Duplicate export name '{}'"sv, Ex.getName());
1820
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export));
1821
0
    return Unexpect(ErrCode::Value::ComponentDuplicateName);
1822
0
  }
1823
1824
  // Every export introduces a new index that aliases the exported
1825
  // definition in the component's own index space for that sort. For
1826
  // instance-sort exports we also copy/build the export table so that a
1827
  // later alias-export against this slot can resolve its sub-exports.
1828
0
  if (Sort.isCore()) {
1829
0
    CompCtx.incCoreSortIndexSize(Sort.getCoreSortType());
1830
0
  } else {
1831
0
    const AST::Component::InstanceType *IT = nullptr;
1832
0
    const bool IsInst =
1833
0
        Sort.getSortType() == AST::Component::Sort::SortType::Instance;
1834
0
    const bool HasInstAscription =
1835
0
        IsInst && Ex.getDesc().has_value() &&
1836
0
        Ex.getDesc()->getDescType() ==
1837
0
            AST::Component::ExternDesc::DescType::InstanceType;
1838
0
    if (HasInstAscription) {
1839
0
      IT = CompCtx.getInstanceType(Ex.getDesc()->getTypeIndex());
1840
0
      if (IT != nullptr) {
1841
0
        if (auto Missing = findMissingRequiredExport(Idx, *IT)) {
1842
0
          spdlog::error(ErrCode::Value::ExportAscriptionIncompatible);
1843
0
          spdlog::error(
1844
0
              "    Export: ascribed instance type requires export '{}' "
1845
0
              "not present in inferred type"sv,
1846
0
              *Missing);
1847
0
          spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export));
1848
0
          return Unexpect(ErrCode::Value::ExportAscriptionIncompatible);
1849
0
        }
1850
0
      }
1851
0
    }
1852
0
    uint32_t NewIdx = CompCtx.incSortIndexSize(Sort.getSortType());
1853
0
    if (IsInst) {
1854
0
      if (IT != nullptr) {
1855
0
        populateInstanceFromType(NewIdx, *IT);
1856
0
      } else {
1857
        // Either no ascription, or the ascription's type index didn't
1858
        // resolve to an inline InstanceType here (cross-scope type-index
1859
        // resolution is not yet implemented). Copy the source instance's
1860
        // inferred exports so a later alias-export on this slot can still
1861
        // find them.
1862
0
        const auto &SrcExports = CompCtx.getInstance(Idx).Exports;
1863
0
        for (const auto &[Name, IE] : SrcExports) {
1864
0
          CompCtx.addInstanceExport(NewIdx, Name, IE.ST, IE.IT,
1865
0
                                    IE.NestedInstIdx);
1866
0
        }
1867
0
      }
1868
0
    }
1869
0
  }
1870
0
  return {};
1871
0
}
1872
1873
Expect<void>
1874
0
Validator::validate(const AST::Component::ExternDesc &Desc) noexcept {
1875
  // Validating an externdesc introduces a new entry into the index space
1876
  // matching the descriptor's sort:
1877
  //   * core module (CoreType) → core:module
1878
  //   * func                   → func
1879
  //   * value                  → value (consumed=false; not yet tracked)
1880
  //   * type (eq i)            → type aliased to i (resource property
1881
  //                              propagated when i refers to a resource)
1882
  //   * type (sub resource)    → fresh abstract resource type
1883
  //   * component / instance   → component / instance
1884
  //
1885
  // GAP-ED-1: bounds-check the referenced type index. The CoreType
1886
  // externdesc indexes the core:type space (the moduletype lives there);
1887
  // FuncType / ComponentType / InstanceType all index the component-level
1888
  // type space. Kind-of-type checks (e.g. that a FuncType index actually
1889
  // resolves to a component func type, not a record) are a follow-up that
1890
  // needs the type body to be retained on every type entry.
1891
0
  switch (Desc.getDescType()) {
1892
0
  case AST::Component::ExternDesc::DescType::CoreType: {
1893
0
    const uint32_t RefIdx = Desc.getTypeIndex();
1894
0
    const uint32_t CoreTypeSize =
1895
0
        CompCtx.getCoreSortIndexSize(AST::Component::Sort::CoreSortType::Type);
1896
0
    if (RefIdx >= CoreTypeSize) {
1897
0
      spdlog::error(ErrCode::Value::InvalidIndex);
1898
0
      spdlog::error(
1899
0
          "    ExternDesc: core type index {} exceeds core:type index space size {}"sv,
1900
0
          RefIdx, CoreTypeSize);
1901
0
      return Unexpect(ErrCode::Value::InvalidIndex);
1902
0
    }
1903
0
    break;
1904
0
  }
1905
0
  case AST::Component::ExternDesc::DescType::FuncType:
1906
0
  case AST::Component::ExternDesc::DescType::ComponentType:
1907
0
  case AST::Component::ExternDesc::DescType::InstanceType: {
1908
0
    const uint32_t RefIdx = Desc.getTypeIndex();
1909
0
    const uint32_t TypeSize =
1910
0
        CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type);
1911
0
    if (RefIdx >= TypeSize) {
1912
0
      spdlog::error(ErrCode::Value::InvalidIndex);
1913
0
      spdlog::error(
1914
0
          "    ExternDesc: referenced type index {} exceeds type index space size {}"sv,
1915
0
          RefIdx, TypeSize);
1916
0
      return Unexpect(ErrCode::Value::InvalidIndex);
1917
0
    }
1918
0
    break;
1919
0
  }
1920
0
  default:
1921
0
    break;
1922
0
  }
1923
1924
0
  switch (Desc.getDescType()) {
1925
0
  case AST::Component::ExternDesc::DescType::CoreType: {
1926
    // The CoreType externdesc is `(core module (type i))`, so it introduces
1927
    // a new core:module slot bound to the moduletype at core:type idx i.
1928
0
    const auto *CT = CompCtx.getCoreModuleType(Desc.getTypeIndex());
1929
0
    CompCtx.addCoreModule(CT);
1930
0
    break;
1931
0
  }
1932
0
  case AST::Component::ExternDesc::DescType::FuncType:
1933
0
    CompCtx.addFunc();
1934
0
    break;
1935
0
  case AST::Component::ExternDesc::DescType::ValueBound:
1936
0
    CompCtx.addValue();
1937
0
    break;
1938
0
  case AST::Component::ExternDesc::DescType::TypeBound:
1939
0
    if (Desc.isEqType()) {
1940
      // (type (eq i)) — alias type i
1941
0
      uint32_t RefIdx = Desc.getTypeIndex();
1942
0
      if (RefIdx >=
1943
0
          CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type)) {
1944
0
        spdlog::error(ErrCode::Value::InvalidIndex);
1945
0
        spdlog::error("    ExternDesc: eq type bound index {} out of bounds"sv,
1946
0
                      RefIdx);
1947
0
        return Unexpect(ErrCode::Value::InvalidIndex);
1948
0
      }
1949
      // (eq i): inherits the source resource's id; body lives on the
1950
      // shared registry entry.
1951
0
      uint32_t NewIdx = CompCtx.addType(nullptr, /*IsLocal=*/false);
1952
0
      if (const auto *SrcInfo = CompCtx.getResource(RefIdx)) {
1953
0
        CompCtx.addResource(NewIdx, {SrcInfo->Id, /*LocallyDefined=*/false});
1954
0
      }
1955
0
    } else {
1956
      // (sub resource): abstract import — fresh id with no body.
1957
0
      uint32_t NewIdx = CompCtx.addType(nullptr, /*IsLocal=*/false);
1958
0
      CompCtx.addResource(NewIdx, {CompCtx.allocateFreshResourceId(),
1959
0
                                   /*LocallyDefined=*/false});
1960
0
    }
1961
0
    break;
1962
0
  case AST::Component::ExternDesc::DescType::ComponentType: {
1963
    // Bind the new component slot to its ComponentType so a later
1964
    // instantiation can pull imports/exports from it (GAP-I-1).
1965
0
    const auto *CT = CompCtx.getComponentType(Desc.getTypeIndex());
1966
0
    CompCtx.addComponent(CT);
1967
0
    break;
1968
0
  }
1969
0
  case AST::Component::ExternDesc::DescType::InstanceType: {
1970
    // GAP-ED-2: populate exports from the referenced InstanceType so
1971
    // alias-export resolves. GAP-I-5b: also bind it to the slot.
1972
0
    const auto *IT = CompCtx.getInstanceType(Desc.getTypeIndex());
1973
0
    uint32_t InstIdx = CompCtx.addInstance(IT);
1974
0
    if (IT != nullptr) {
1975
0
      populateInstanceFromType(InstIdx, *IT);
1976
0
    }
1977
    // If the type index resolved against an outer scope's InstanceType,
1978
    // populating from it requires cross-scope walking (the rest of
1979
    // GAP-DECL-ED). For now the instance keeps an empty export table when
1980
    // the InstanceType body isn't visible in this scope.
1981
0
    break;
1982
0
  }
1983
0
  default:
1984
0
    assumingUnreachable();
1985
0
  }
1986
1987
0
  return {};
1988
0
}
1989
1990
Expect<void>
1991
0
Validator::validate(const AST::Component::CoreImportDesc &Desc) noexcept {
1992
0
  if (Desc.isFunc()) {
1993
0
    uint32_t TypeIdx = Desc.getTypeIndex();
1994
0
    if (TypeIdx >= CompCtx.getCoreSortIndexSize(
1995
0
                       AST::Component::Sort::CoreSortType::Type)) {
1996
0
      spdlog::error(ErrCode::Value::InvalidIndex);
1997
0
      spdlog::error("    CoreImportDesc: func type index {} out of bounds"sv,
1998
0
                    TypeIdx);
1999
0
      return Unexpect(ErrCode::Value::InvalidIndex);
2000
0
    }
2001
0
    CompCtx.addCoreFunc();
2002
0
  } else if (Desc.isTable()) {
2003
0
    CompCtx.addCoreTable();
2004
0
  } else if (Desc.isMemory()) {
2005
0
    CompCtx.addCoreMemory();
2006
0
  } else if (Desc.isGlobal()) {
2007
0
    CompCtx.addCoreGlobal();
2008
0
  } else if (Desc.isTag()) {
2009
0
    CompCtx.addCoreTag();
2010
0
  } else {
2011
0
    assumingUnreachable();
2012
0
  }
2013
0
  return {};
2014
0
}
2015
2016
Expect<void>
2017
0
Validator::validate(const AST::Component::CoreImportDecl &Decl) noexcept {
2018
0
  return validate(Decl.getImportDesc());
2019
0
}
2020
2021
Expect<void>
2022
0
Validator::validate(const AST::Component::CoreExportDecl &Decl) noexcept {
2023
0
  return validate(Decl.getImportDesc());
2024
0
}
2025
2026
Expect<void>
2027
0
Validator::validate(const AST::Component::CoreModuleDecl &Decl) noexcept {
2028
0
  auto ReportError = [](auto E) {
2029
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_CoreModule));
2030
0
    return E;
2031
0
  };
2032
2033
0
  if (Decl.isImport()) {
2034
0
    EXPECTED_TRY(validate(Decl.getImport()).map_error(ReportError));
2035
0
  } else if (Decl.isType()) {
2036
0
    EXPECTED_TRY(validate(*Decl.getType()).map_error(ReportError));
2037
0
  } else if (Decl.isAlias()) {
2038
0
    EXPECTED_TRY(validate(Decl.getAlias()).map_error(ReportError));
2039
0
  } else if (Decl.isExport()) {
2040
0
    EXPECTED_TRY(validate(Decl.getExport()).map_error(ReportError));
2041
0
  } else {
2042
0
    assumingUnreachable();
2043
0
  }
2044
0
  return {};
2045
0
}
2046
2047
Expect<void>
2048
0
Validator::validate(const AST::Component::ImportDecl &Decl) noexcept {
2049
0
  auto ReportError = [](auto E) {
2050
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Import));
2051
0
    return E;
2052
0
  };
2053
2054
  // Validate the extern descriptor (also increments sort index spaces).
2055
0
  EXPECTED_TRY(validate(Decl.getExternDesc()).map_error(ReportError));
2056
2057
  // Parse and validate the import name.
2058
0
  EXPECTED_TRY(ComponentName CName,
2059
0
               ComponentName::parse(Decl.getName()).map_error(ReportError));
2060
2061
  // Annotated plainnames can only appear on func imports.
2062
0
  switch (CName.getKind()) {
2063
0
  case ComponentNameKind::Constructor:
2064
0
  case ComponentNameKind::Method:
2065
0
  case ComponentNameKind::Static:
2066
0
    if (Decl.getExternDesc().getDescType() !=
2067
0
        AST::Component::ExternDesc::DescType::FuncType) {
2068
0
      spdlog::error(ErrCode::Value::ComponentInvalidName);
2069
0
      spdlog::error("    ImportDecl: annotated name requires func type"sv);
2070
0
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Import));
2071
0
      return Unexpect(ErrCode::Value::ComponentInvalidName);
2072
0
    }
2073
0
    break;
2074
0
  default:
2075
0
    break;
2076
0
  }
2077
2078
  // Check import name uniqueness.
2079
0
  if (!CompCtx.addImportedName(CName)) {
2080
0
    spdlog::error(ErrCode::Value::ComponentDuplicateName);
2081
0
    spdlog::error("    ImportDecl: Duplicate import name"sv);
2082
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Import));
2083
0
    return Unexpect(ErrCode::Value::ComponentDuplicateName);
2084
0
  }
2085
2086
0
  return {};
2087
0
}
2088
2089
Expect<void>
2090
0
Validator::validate(const AST::Component::ExportDecl &Decl) noexcept {
2091
  // Check export name grammar and uniqueness before mutating the index
2092
  // spaces so a duplicate or malformed name doesn't widen the scope's
2093
  // sort counts.
2094
0
  EXPECTED_TRY(ComponentName CName,
2095
0
               validateExportName(Decl.getName()).map_error([](auto E) {
2096
0
                 spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Export));
2097
0
                 return E;
2098
0
               }));
2099
0
  if (!CompCtx.addExportedName(CName)) {
2100
0
    spdlog::error(ErrCode::Value::ComponentDuplicateName);
2101
0
    spdlog::error("    ExportDecl: Duplicate export name '{}'"sv,
2102
0
                  Decl.getName());
2103
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Export));
2104
0
    return Unexpect(ErrCode::Value::ComponentDuplicateName);
2105
0
  }
2106
2107
  // Validate the extern descriptor (also increments sort index spaces and
2108
  // performs type-index bounds checking). For nested ExportDecls whose
2109
  // ExternDesc references a type index defined in an outer scope, the
2110
  // current scope's lookup may return nullptr — validate(ExternDesc) handles
2111
  // that gracefully by allocating an empty instance slot. Cross-scope
2112
  // type-index resolution (walking the parent CompCtxs chain to resolve
2113
  // names like `(export "f" (func (type 0)))` where type 0 lives outside
2114
  // the InstanceType body) is the structural follow-up tracked by the rest
2115
  // of GAP-DECL-ED / GAP-ED-1 / GAP-ED-2.
2116
0
  EXPECTED_TRY(validate(Decl.getExternDesc()).map_error([](auto E) {
2117
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Export));
2118
0
    return E;
2119
0
  }));
2120
2121
0
  return {};
2122
0
}
2123
2124
Expect<void>
2125
0
Validator::validate(const AST::Component::InstanceDecl &Decl) noexcept {
2126
0
  auto ReportError = [](auto E) {
2127
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Instance));
2128
0
    return E;
2129
0
  };
2130
2131
0
  if (Decl.isCoreType()) {
2132
0
    EXPECTED_TRY(validate(*Decl.getCoreType()).map_error(ReportError));
2133
0
  } else if (Decl.isType()) {
2134
0
    EXPECTED_TRY(validate(*Decl.getType()).map_error(ReportError));
2135
0
  } else if (Decl.isAlias()) {
2136
0
    EXPECTED_TRY(validate(Decl.getAlias()).map_error(ReportError));
2137
0
    const auto &A = Decl.getAlias();
2138
0
    const auto &Sort = A.getSort();
2139
0
    if (Sort.isCore()) {
2140
0
      CompCtx.incCoreSortIndexSize(Sort.getCoreSortType());
2141
0
    } else {
2142
0
      uint32_t NewInstIdx = CompCtx.incSortIndexSize(Sort.getSortType());
2143
      // Outer-aliasing a resource type keeps the resource's identity in this
2144
      // scope so later own/borrow and (eq i) checks treat the slot correctly.
2145
0
      if (A.getTargetType() == AST::Component::Alias::TargetType::Outer &&
2146
0
          Sort.getSortType() == AST::Component::Sort::SortType::Type) {
2147
0
        CompCtx.carryOuterResource(NewInstIdx, A.getOuter().first,
2148
0
                                   A.getOuter().second);
2149
0
      }
2150
      // When aliasing an instance export, propagate either the source
2151
      // instance's export table (for Instance targets) or the source
2152
      // export's resource identity (for Type targets that are resources)
2153
      // — mirrors the AliasSection handling.
2154
0
      if (A.getTargetType() == AST::Component::Alias::TargetType::Export) {
2155
0
        const auto SrcInstIdx = A.getExport().first;
2156
0
        const auto &SrcName = A.getExport().second;
2157
0
        const auto &SrcExports = CompCtx.getInstance(SrcInstIdx).Exports;
2158
0
        auto It = SrcExports.find(std::string(SrcName));
2159
0
        if (It != SrcExports.end()) {
2160
0
          if (Sort.getSortType() == AST::Component::Sort::SortType::Instance) {
2161
0
            if (It->second.IT != nullptr) {
2162
0
              populateInstanceFromType(NewInstIdx, *It->second.IT);
2163
0
            } else if (It->second.NestedInstIdx.has_value()) {
2164
0
              const auto &NestedExports =
2165
0
                  CompCtx.getInstance(*It->second.NestedInstIdx).Exports;
2166
0
              for (const auto &[Name, IE] : NestedExports) {
2167
0
                CompCtx.addInstanceExport(NewInstIdx, Name, IE.ST, IE.IT,
2168
0
                                          IE.NestedInstIdx, IE.ResourceId);
2169
0
              }
2170
0
            }
2171
0
          } else if (Sort.getSortType() ==
2172
0
                         AST::Component::Sort::SortType::Type &&
2173
0
                     It->second.ResourceId.has_value()) {
2174
0
            CompCtx.addResource(NewInstIdx, {*It->second.ResourceId,
2175
0
                                             /*LocallyDefined=*/false});
2176
0
          }
2177
0
        }
2178
0
      }
2179
0
    }
2180
0
  } else if (Decl.isExportDecl()) {
2181
0
    EXPECTED_TRY(validate(Decl.getExport()).map_error(ReportError));
2182
0
  } else {
2183
0
    assumingUnreachable();
2184
0
  }
2185
0
  return {};
2186
0
}
2187
2188
Expect<void>
2189
0
Validator::validate(const AST::Component::ComponentDecl &Decl) noexcept {
2190
0
  auto ReportError = [](auto E) {
2191
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Component));
2192
0
    return E;
2193
0
  };
2194
2195
0
  if (Decl.isImportDecl()) {
2196
0
    EXPECTED_TRY(validate(Decl.getImport()).map_error(ReportError));
2197
0
  } else if (Decl.isInstanceDecl()) {
2198
0
    EXPECTED_TRY(validate(Decl.getInstance()).map_error(ReportError));
2199
0
  } else {
2200
0
    assumingUnreachable();
2201
0
  }
2202
0
  return {};
2203
0
}
2204
2205
0
Expect<void> Validator::validate(const ComponentValType &VT) noexcept {
2206
0
  if (VT.getCode() == ComponentTypeCode::TypeIndex) {
2207
0
    uint32_t Idx = VT.getTypeIndex();
2208
0
    if (Idx >= CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type)) {
2209
0
      spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds);
2210
0
      spdlog::error("    ComponentValType: type index {} out of bounds"sv, Idx);
2211
0
      return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds);
2212
0
    }
2213
0
    const auto *DT = CompCtx.getDefType(Idx);
2214
0
    if (DT != nullptr && !DT->isDefValType() && !DT->isResourceType()) {
2215
0
      spdlog::error(ErrCode::Value::NotADefinedType);
2216
0
      spdlog::error(
2217
0
          "    ComponentValType: type index {} is not a defined value type"sv,
2218
0
          Idx);
2219
0
      return Unexpect(ErrCode::Value::NotADefinedType);
2220
0
    }
2221
0
  }
2222
0
  return {};
2223
0
}
2224
2225
Expect<void>
2226
0
Validator::validate(const AST::Component::DefValType &DVT) noexcept {
2227
0
  if (DVT.isOwnTy()) {
2228
0
    uint32_t Idx = DVT.getOwn().Idx;
2229
0
    if (Idx >= CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type)) {
2230
0
      spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds);
2231
0
      spdlog::error("    DefValType: own type index {} out of bounds"sv, Idx);
2232
0
      return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds);
2233
0
    }
2234
0
    if (CompCtx.getResource(Idx) == nullptr) {
2235
0
      spdlog::error(ErrCode::Value::NotADefinedType);
2236
0
      spdlog::error(
2237
0
          "    DefValType: own type index {} does not refer to a resource type"sv,
2238
0
          Idx);
2239
0
      return Unexpect(ErrCode::Value::NotADefinedType);
2240
0
    }
2241
0
  } else if (DVT.isBorrowTy()) {
2242
0
    uint32_t Idx = DVT.getBorrow().Idx;
2243
0
    if (Idx >= CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type)) {
2244
0
      spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds);
2245
0
      spdlog::error("    DefValType: borrow type index {} out of bounds"sv,
2246
0
                    Idx);
2247
0
      return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds);
2248
0
    }
2249
0
    if (CompCtx.getResource(Idx) == nullptr) {
2250
0
      spdlog::error(ErrCode::Value::NotADefinedType);
2251
0
      spdlog::error(
2252
0
          "    DefValType: borrow type index {} does not refer to a resource type"sv,
2253
0
          Idx);
2254
0
      return Unexpect(ErrCode::Value::NotADefinedType);
2255
0
    }
2256
0
  } else if (DVT.isRecordTy()) {
2257
0
    const auto &Rec = DVT.getRecord();
2258
0
    if (Rec.LabelTypes.empty()) {
2259
0
      spdlog::error(ErrCode::Value::InvalidTypeReference);
2260
0
      spdlog::error("    DefValType: record must have at least one field"sv);
2261
0
      return Unexpect(ErrCode::Value::InvalidTypeReference);
2262
0
    }
2263
0
    std::unordered_set<std::string> Seen;
2264
0
    for (const auto &LT : Rec.LabelTypes) {
2265
0
      if (LT.getLabel().empty()) {
2266
0
        spdlog::error(ErrCode::Value::NameCannotBeEmpty);
2267
0
        return Unexpect(ErrCode::Value::NameCannotBeEmpty);
2268
0
      }
2269
0
      if (!isKebabString(LT.getLabel())) {
2270
0
        spdlog::error(ErrCode::Value::ComponentInvalidName);
2271
0
        spdlog::error(
2272
0
            "    DefValType: record field '{}' is not valid kebab-case"sv,
2273
0
            LT.getLabel());
2274
0
        return Unexpect(ErrCode::Value::ComponentInvalidName);
2275
0
      }
2276
0
      if (!Seen.insert(toLowerStr(LT.getLabel())).second) {
2277
0
        spdlog::error(ErrCode::Value::RecordFieldNameConflicts);
2278
0
        spdlog::error("    DefValType: duplicate record field '{}'"sv,
2279
0
                      LT.getLabel());
2280
0
        return Unexpect(ErrCode::Value::RecordFieldNameConflicts);
2281
0
      }
2282
0
      EXPECTED_TRY(validate(LT.getValType()));
2283
0
    }
2284
0
  } else if (DVT.isVariantTy()) {
2285
0
    const auto &Var = DVT.getVariant();
2286
0
    if (Var.Cases.empty()) {
2287
0
      spdlog::error(ErrCode::Value::VariantMustHaveCase);
2288
0
      return Unexpect(ErrCode::Value::VariantMustHaveCase);
2289
0
    }
2290
0
    std::unordered_set<std::string> Seen;
2291
0
    for (const auto &C : Var.Cases) {
2292
0
      if (C.first.empty()) {
2293
0
        spdlog::error(ErrCode::Value::NameCannotBeEmpty);
2294
0
        return Unexpect(ErrCode::Value::NameCannotBeEmpty);
2295
0
      }
2296
0
      if (!isKebabString(C.first)) {
2297
0
        spdlog::error(ErrCode::Value::ComponentInvalidName);
2298
0
        spdlog::error(
2299
0
            "    DefValType: variant case '{}' is not valid kebab-case"sv,
2300
0
            C.first);
2301
0
        return Unexpect(ErrCode::Value::ComponentInvalidName);
2302
0
      }
2303
0
      if (!Seen.insert(toLowerStr(C.first)).second) {
2304
0
        spdlog::error(ErrCode::Value::VariantCaseNameConflicts);
2305
0
        spdlog::error("    DefValType: duplicate variant case '{}'"sv, C.first);
2306
0
        return Unexpect(ErrCode::Value::VariantCaseNameConflicts);
2307
0
      }
2308
0
      if (C.second.has_value()) {
2309
0
        EXPECTED_TRY(validate(*C.second));
2310
0
      }
2311
0
    }
2312
0
  } else if (DVT.isTupleTy()) {
2313
0
    if (DVT.getTuple().Types.empty()) {
2314
0
      spdlog::error(ErrCode::Value::InvalidTypeReference);
2315
0
      spdlog::error("    DefValType: tuple must have at least one element"sv);
2316
0
      return Unexpect(ErrCode::Value::InvalidTypeReference);
2317
0
    }
2318
0
    for (const auto &T : DVT.getTuple().Types) {
2319
0
      EXPECTED_TRY(validate(T));
2320
0
    }
2321
0
  } else if (DVT.isListTy()) {
2322
0
    EXPECTED_TRY(validate(DVT.getList().ValTy));
2323
0
  } else if (DVT.isOptionTy()) {
2324
0
    EXPECTED_TRY(validate(DVT.getOption().ValTy));
2325
0
  } else if (DVT.isResultTy()) {
2326
0
    const auto &R = DVT.getResult();
2327
0
    if (R.ValTy.has_value()) {
2328
0
      EXPECTED_TRY(validate(*R.ValTy));
2329
0
    }
2330
0
    if (R.ErrTy.has_value()) {
2331
0
      EXPECTED_TRY(validate(*R.ErrTy));
2332
0
    }
2333
0
  } else if (DVT.isFlagsTy()) {
2334
0
    const auto &Flags = DVT.getFlags();
2335
0
    if (Flags.Labels.empty()) {
2336
0
      spdlog::error(ErrCode::Value::InvalidTypeReference);
2337
0
      spdlog::error("    DefValType: flags must have at least one label"sv);
2338
0
      return Unexpect(ErrCode::Value::InvalidTypeReference);
2339
0
    }
2340
0
    if (Flags.Labels.size() > 32) {
2341
0
      spdlog::error(ErrCode::Value::CannotHaveMoreThan32Flags);
2342
0
      return Unexpect(ErrCode::Value::CannotHaveMoreThan32Flags);
2343
0
    }
2344
0
    std::unordered_set<std::string> Seen;
2345
0
    for (const auto &L : Flags.Labels) {
2346
0
      if (L.empty()) {
2347
0
        spdlog::error(ErrCode::Value::NameCannotBeEmpty);
2348
0
        return Unexpect(ErrCode::Value::NameCannotBeEmpty);
2349
0
      }
2350
0
      if (!isKebabString(L)) {
2351
0
        spdlog::error(ErrCode::Value::ComponentInvalidName);
2352
0
        spdlog::error(
2353
0
            "    DefValType: flags label '{}' is not valid kebab-case"sv, L);
2354
0
        return Unexpect(ErrCode::Value::ComponentInvalidName);
2355
0
      }
2356
0
      if (!Seen.insert(toLowerStr(L)).second) {
2357
0
        spdlog::error(ErrCode::Value::FlagNameConflicts);
2358
0
        spdlog::error("    DefValType: duplicate flags label '{}'"sv, L);
2359
0
        return Unexpect(ErrCode::Value::FlagNameConflicts);
2360
0
      }
2361
0
    }
2362
0
  } else if (DVT.isEnumTy()) {
2363
0
    const auto &Enm = DVT.getEnum();
2364
0
    if (Enm.Labels.empty()) {
2365
0
      spdlog::error(ErrCode::Value::InvalidTypeReference);
2366
0
      spdlog::error("    DefValType: enum must have at least one label"sv);
2367
0
      return Unexpect(ErrCode::Value::InvalidTypeReference);
2368
0
    }
2369
0
    std::unordered_set<std::string> Seen;
2370
0
    for (const auto &L : Enm.Labels) {
2371
0
      if (L.empty()) {
2372
0
        spdlog::error(ErrCode::Value::NameCannotBeEmpty);
2373
0
        return Unexpect(ErrCode::Value::NameCannotBeEmpty);
2374
0
      }
2375
0
      if (!isKebabString(L)) {
2376
0
        spdlog::error(ErrCode::Value::ComponentInvalidName);
2377
0
        spdlog::error(
2378
0
            "    DefValType: enum label '{}' is not valid kebab-case"sv, L);
2379
0
        return Unexpect(ErrCode::Value::ComponentInvalidName);
2380
0
      }
2381
0
      if (!Seen.insert(toLowerStr(L)).second) {
2382
0
        spdlog::error(ErrCode::Value::EnumTagNameConflicts);
2383
0
        spdlog::error("    DefValType: duplicate enum label '{}'"sv, L);
2384
0
        return Unexpect(ErrCode::Value::EnumTagNameConflicts);
2385
0
      }
2386
0
    }
2387
0
  } else if (DVT.isStreamTy()) {
2388
0
    if (DVT.getStream().ValTy.has_value()) {
2389
0
      EXPECTED_TRY(validate(*DVT.getStream().ValTy));
2390
0
    }
2391
0
  } else if (DVT.isFutureTy()) {
2392
0
    if (DVT.getFuture().ValTy.has_value()) {
2393
0
      EXPECTED_TRY(validate(*DVT.getFuture().ValTy));
2394
0
    }
2395
0
  }
2396
0
  return {};
2397
0
}
2398
2399
0
Expect<void> Validator::validate(const AST::Component::FuncType &FT) noexcept {
2400
  // Validate param names: kebab-case + unique
2401
0
  std::unordered_set<std::string_view> ParamNames;
2402
0
  for (const auto &P : FT.getParamList()) {
2403
0
    if (!P.getLabel().empty()) {
2404
0
      if (!isKebabString(P.getLabel())) {
2405
0
        spdlog::error(ErrCode::Value::ComponentInvalidName);
2406
0
        spdlog::error(
2407
0
            "    FuncType: parameter name '{}' is not valid kebab-case"sv,
2408
0
            P.getLabel());
2409
0
        return Unexpect(ErrCode::Value::ComponentInvalidName);
2410
0
      }
2411
0
      if (!ParamNames.insert(P.getLabel()).second) {
2412
0
        spdlog::error(ErrCode::Value::ComponentDuplicateName);
2413
0
        spdlog::error("    FuncType: duplicate parameter name '{}'"sv,
2414
0
                      P.getLabel());
2415
0
        return Unexpect(ErrCode::Value::ComponentDuplicateName);
2416
0
      }
2417
0
    }
2418
0
    EXPECTED_TRY(validate(P.getValType()));
2419
0
  }
2420
  // Reject transitive use of borrow in results
2421
0
  for (const auto &R : FT.getResultList()) {
2422
0
    EXPECTED_TRY(validate(R.getValType()));
2423
0
    if (containsBorrow(R.getValType())) {
2424
0
      spdlog::error(ErrCode::Value::InvalidTypeReference);
2425
0
      spdlog::error(
2426
0
          "    FuncType: borrow type not allowed in function results"sv);
2427
0
      return Unexpect(ErrCode::Value::InvalidTypeReference);
2428
0
    }
2429
0
  }
2430
0
  return {};
2431
0
}
2432
2433
Expect<void>
2434
0
Validator::validate(const AST::Component::InstanceType &IT) noexcept {
2435
  // Instance types are validated with an initially-empty index space.
2436
0
  CompCtx.enterTypeDefinition();
2437
0
  for (const auto &Decl : IT.getDecl()) {
2438
0
    EXPECTED_TRY(validate(Decl).map_error([](auto E) {
2439
0
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_DefType));
2440
0
      return E;
2441
0
    }));
2442
0
  }
2443
0
  CompCtx.exitComponent();
2444
0
  return {};
2445
0
}
2446
2447
Expect<void>
2448
0
Validator::validate(const AST::Component::ComponentType &CT) noexcept {
2449
  // Component types are validated with an initially-empty index space.
2450
0
  CompCtx.enterTypeDefinition();
2451
0
  for (const auto &Decl : CT.getDecl()) {
2452
0
    EXPECTED_TRY(validate(Decl).map_error([](auto E) {
2453
0
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_DefType));
2454
0
      return E;
2455
0
    }));
2456
0
  }
2457
0
  CompCtx.exitComponent();
2458
0
  return {};
2459
0
}
2460
2461
Expect<void>
2462
0
Validator::validate(const AST::Component::ResourceType &RT) noexcept {
2463
  // Resource types are not allowed inside componenttype/instancetype scopes.
2464
0
  if (CompCtx.isTypeDefinitionScope()) {
2465
0
    spdlog::error(ErrCode::Value::InvalidTypeReference);
2466
0
    spdlog::error("    ResourceType: resource types cannot be defined inside "
2467
0
                  "componenttype or instancetype"sv);
2468
0
    return Unexpect(ErrCode::Value::InvalidTypeReference);
2469
0
  }
2470
0
  if (RT.getDestructor().has_value()) {
2471
0
    uint32_t DtorIdx = *RT.getDestructor();
2472
0
    if (DtorIdx >= CompCtx.getCoreSortIndexSize(
2473
0
                       AST::Component::Sort::CoreSortType::Func)) {
2474
0
      spdlog::error(ErrCode::Value::InvalidIndex);
2475
0
      spdlog::error(
2476
0
          "    ResourceType: destructor core func index {} out of bounds"sv,
2477
0
          DtorIdx);
2478
0
      return Unexpect(ErrCode::Value::InvalidIndex);
2479
0
    }
2480
    // Verify the destructor's signature is `[i32] -> []`. The signature is
2481
    // only available for core funcs the validator has populated (canon
2482
    // resource.* synthesise it; aliased-from-core-module funcs are tracked
2483
    // as a TODO that needs CoreInstance export-type plumbing). Skip the
2484
    // check when the SubType pointer is null — the bounds check above
2485
    // already caught the obvious "no such func" mistake.
2486
    // TODO: also accept `[i64] -> []` when the resource rep is i64
2487
    // (memory64 proposal); needs ResourceType to carry rep size.
2488
0
    const AST::SubType *DtorST = CompCtx.getCoreFunc(DtorIdx);
2489
0
    if (DtorST != nullptr) {
2490
0
      const auto &DtorType = DtorST->getCompositeType();
2491
0
      const auto &ExpType = CoreFuncType_I32_Void.getCompositeType();
2492
0
      if (!DtorType.isFunc() ||
2493
0
          DtorType.getFuncType() != ExpType.getFuncType()) {
2494
0
        spdlog::error(ErrCode::Value::InvalidTypeReference);
2495
0
        spdlog::error(
2496
0
            "    ResourceType: destructor core func {} must have signature [i32] -> []"sv,
2497
0
            DtorIdx);
2498
0
        return Unexpect(ErrCode::Value::InvalidTypeReference);
2499
0
      }
2500
0
    }
2501
0
  }
2502
0
  return {};
2503
0
}
2504
2505
0
bool Validator::containsBorrow(const ComponentValType &VT) const noexcept {
2506
0
  if (VT.getCode() == ComponentTypeCode::Borrow) {
2507
0
    return true;
2508
0
  }
2509
0
  if (VT.getCode() != ComponentTypeCode::TypeIndex) {
2510
0
    return false;
2511
0
  }
2512
0
  uint32_t Idx = VT.getTypeIndex();
2513
0
  const auto *DT = CompCtx.getDefType(Idx);
2514
0
  if (DT == nullptr || !DT->isDefValType()) {
2515
0
    return false;
2516
0
  }
2517
0
  return containsBorrow(DT->getDefValType());
2518
0
}
2519
2520
bool Validator::containsBorrow(
2521
0
    const AST::Component::DefValType &DVT) const noexcept {
2522
0
  if (DVT.isBorrowTy()) {
2523
0
    return true;
2524
0
  }
2525
0
  if (DVT.isRecordTy()) {
2526
0
    for (const auto &F : DVT.getRecord().LabelTypes) {
2527
0
      if (containsBorrow(F.getValType())) {
2528
0
        return true;
2529
0
      }
2530
0
    }
2531
0
    return false;
2532
0
  }
2533
0
  if (DVT.isVariantTy()) {
2534
0
    for (const auto &C : DVT.getVariant().Cases) {
2535
0
      if (C.second.has_value() && containsBorrow(*C.second)) {
2536
0
        return true;
2537
0
      }
2538
0
    }
2539
0
    return false;
2540
0
  }
2541
0
  if (DVT.isListTy()) {
2542
0
    return containsBorrow(DVT.getList().ValTy);
2543
0
  }
2544
0
  if (DVT.isTupleTy()) {
2545
0
    for (const auto &T : DVT.getTuple().Types) {
2546
0
      if (containsBorrow(T)) {
2547
0
        return true;
2548
0
      }
2549
0
    }
2550
0
    return false;
2551
0
  }
2552
0
  if (DVT.isOptionTy()) {
2553
0
    return containsBorrow(DVT.getOption().ValTy);
2554
0
  }
2555
0
  if (DVT.isResultTy()) {
2556
0
    const auto &R = DVT.getResult();
2557
0
    return (R.ValTy.has_value() && containsBorrow(*R.ValTy)) ||
2558
0
           (R.ErrTy.has_value() && containsBorrow(*R.ErrTy));
2559
0
  }
2560
0
  if (DVT.isStreamTy()) {
2561
0
    return DVT.getStream().ValTy.has_value() &&
2562
0
           containsBorrow(*DVT.getStream().ValTy);
2563
0
  }
2564
0
  if (DVT.isFutureTy()) {
2565
0
    return DVT.getFuture().ValTy.has_value() &&
2566
0
           containsBorrow(*DVT.getFuture().ValTy);
2567
0
  }
2568
0
  return false; // PrimValType, OwnTy, FlagsTy, EnumTy
2569
0
}
2570
2571
} // namespace Validator
2572
} // namespace WasmEdge