Coverage Report

Created: 2026-07-16 07:09

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
847
std::string toLowerStr(std::string_view SV) {
20
847
  std::string Result(SV);
21
847
  std::transform(
22
847
      Result.begin(), Result.end(), Result.begin(),
23
954
      [](unsigned char C) { return static_cast<char>(std::tolower(C)); });
24
847
  return Result;
25
847
}
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
6
descTypeToSortType(AST::Component::ExternDesc::DescType DT) noexcept {
33
6
  switch (DT) {
34
0
  case AST::Component::ExternDesc::DescType::CoreType:
35
0
    return std::nullopt;
36
1
  case AST::Component::ExternDesc::DescType::FuncType:
37
1
    return AST::Component::Sort::SortType::Func;
38
4
  case AST::Component::ExternDesc::DescType::ValueBound:
39
4
    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
1
  case AST::Component::ExternDesc::DescType::InstanceType:
45
1
    return AST::Component::Sort::SortType::Instance;
46
0
  default:
47
0
    assumingUnreachable();
48
6
  }
49
6
}
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
2
                         AST::Component::ExternDesc::DescType DT) noexcept {
58
2
  auto Mapped = descTypeToSortType(DT);
59
2
  if (S.isCore()) {
60
0
    return !Mapped.has_value() &&
61
0
           S.getCoreSortType() == AST::Component::Sort::CoreSortType::Module;
62
0
  }
63
2
  return Mapped.has_value() && S.getSortType() == *Mapped;
64
2
}
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
840
Expect<ComponentName> validateExportName(std::string_view Name) noexcept {
143
840
  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
1.57k
  EXPECTED_TRY(ComponentName CName, ComponentName::parse(Name));
149
1.57k
  switch (CName.getKind()) {
150
70
  case ComponentNameKind::Label:
151
381
  case ComponentNameKind::Constructor:
152
381
  case ComponentNameKind::Method:
153
417
  case ComponentNameKind::Static:
154
731
  case ComponentNameKind::InterfaceType:
155
731
    return CName;
156
1
  default:
157
1
    spdlog::error(ErrCode::Value::InvalidExportName);
158
1
    spdlog::error("    Export name '{}' kind is not valid for exports"sv, Name);
159
1
    return Unexpect(ErrCode::Value::InvalidExportName);
160
1.57k
  }
161
1.57k
}
162
163
} // namespace
164
165
void Validator::populateInstanceFromType(
166
43
    uint32_t InstIdx, const AST::Component::InstanceType &IT) noexcept {
167
43
  for (const auto &Decl : IT.getDecl()) {
168
10
    if (!Decl.isExportDecl()) {
169
10
      continue;
170
10
    }
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
43
}
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
3.49k
Validator::validate(const AST::Component::Component &Comp) noexcept {
306
3.49k
  spdlog::warn("Component Model Validation is in active development."sv);
307
3.49k
  CompCtx.reset();
308
3.49k
  return validateComponent(Comp).and_then([&]() {
309
1.68k
    const_cast<AST::Component::Component &>(Comp).setIsValidated();
310
1.68k
    return Expect<void>{};
311
1.68k
  });
312
3.49k
}
313
314
Expect<void>
315
7.73k
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
7.73k
  auto ReportError = [](auto E) {
322
1.82k
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Component));
323
1.82k
    return E;
324
1.82k
  };
325
326
7.73k
  CompCtx.enterComponent(&Comp);
327
668k
  for (const auto &Sec : Comp.getSections()) {
328
668k
    auto Func = [&](auto &&S) -> Expect<void> {
329
668k
      using T = std::decay_t<decltype(S)>;
330
668k
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
364k
      } else {
333
364k
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
364k
      }
335
363k
      return {};
336
668k
    };
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
Line
Count
Source
328
303k
    auto Func = [&](auto &&S) -> Expect<void> {
329
303k
      using T = std::decay_t<decltype(S)>;
330
303k
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
      } else {
333
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
      }
335
303k
      return {};
336
303k
    };
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
Line
Count
Source
328
990
    auto Func = [&](auto &&S) -> Expect<void> {
329
990
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
990
      } else {
333
990
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
990
      }
335
987
      return {};
336
990
    };
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
Line
Count
Source
328
1.82k
    auto Func = [&](auto &&S) -> Expect<void> {
329
1.82k
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
1.82k
      } else {
333
1.82k
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
1.82k
      }
335
1.79k
      return {};
336
1.82k
    };
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
Line
Count
Source
328
56.7k
    auto Func = [&](auto &&S) -> Expect<void> {
329
56.7k
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
56.7k
      } else {
333
56.7k
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
56.7k
      }
335
56.6k
      return {};
336
56.7k
    };
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
Line
Count
Source
328
4.23k
    auto Func = [&](auto &&S) -> Expect<void> {
329
4.23k
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
4.23k
      } else {
333
4.23k
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
4.23k
      }
335
4.22k
      return {};
336
4.23k
    };
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
Line
Count
Source
328
10.6k
    auto Func = [&](auto &&S) -> Expect<void> {
329
10.6k
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
10.6k
      } else {
333
10.6k
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
10.6k
      }
335
10.5k
      return {};
336
10.6k
    };
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
Line
Count
Source
328
1.11k
    auto Func = [&](auto &&S) -> Expect<void> {
329
1.11k
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
1.11k
      } else {
333
1.11k
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
1.11k
      }
335
1.02k
      return {};
336
1.11k
    };
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
Line
Count
Source
328
266k
    auto Func = [&](auto &&S) -> Expect<void> {
329
266k
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
266k
      } else {
333
266k
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
266k
      }
335
266k
      return {};
336
266k
    };
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
Line
Count
Source
328
9.74k
    auto Func = [&](auto &&S) -> Expect<void> {
329
9.74k
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
9.74k
      } else {
333
9.74k
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
9.74k
      }
335
9.51k
      return {};
336
9.74k
    };
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
Line
Count
Source
328
237
    auto Func = [&](auto &&S) -> Expect<void> {
329
237
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
237
      } else {
333
237
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
237
      }
335
168
      return {};
336
237
    };
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
Line
Count
Source
328
9.43k
    auto Func = [&](auto &&S) -> Expect<void> {
329
9.43k
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
9.43k
      } else {
333
9.43k
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
9.43k
      }
335
8.60k
      return {};
336
9.43k
    };
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
Line
Count
Source
328
3.10k
    auto Func = [&](auto &&S) -> Expect<void> {
329
3.10k
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
3.10k
      } else {
333
3.10k
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
3.10k
      }
335
2.96k
      return {};
336
3.10k
    };
337
668k
    EXPECTED_TRY(std::visit(Func, Sec));
338
668k
  }
339
5.91k
  CompCtx.exitComponent();
340
5.91k
  return {};
341
7.73k
}
342
343
Expect<void>
344
990
Validator::validate(const AST::Component::CoreModuleSection &ModSec) noexcept {
345
990
  EXPECTED_TRY(validate(ModSec.getContent()).map_error([](auto E) {
346
987
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_CoreMod));
347
987
    return E;
348
987
  }));
349
987
  const_cast<AST::Module &>(ModSec.getContent()).setIsValidated();
350
987
  CompCtx.addCoreModule(ModSec.getContent());
351
987
  return {};
352
990
}
353
354
Expect<void> Validator::validate(
355
1.82k
    const AST::Component::CoreInstanceSection &InstSec) noexcept {
356
1.82k
  for (const auto &Inst : InstSec.getContent()) {
357
1.64k
    EXPECTED_TRY(validate(Inst).map_error([](auto E) {
358
1.64k
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_CoreInstance));
359
1.64k
      return E;
360
1.64k
    }));
361
1.64k
  }
362
1.79k
  return {};
363
1.82k
}
364
365
Expect<void>
366
56.7k
Validator::validate(const AST::Component::CoreTypeSection &TypeSec) noexcept {
367
56.7k
  for (const auto &Type : TypeSec.getContent()) {
368
45.3k
    EXPECTED_TRY(validate(Type).map_error([](auto E) {
369
45.3k
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_CoreType));
370
45.3k
      return E;
371
45.3k
    }));
372
45.3k
  }
373
56.6k
  return {};
374
56.7k
}
375
376
Expect<void>
377
4.23k
Validator::validate(const AST::Component::ComponentSection &CompSec) noexcept {
378
4.23k
  EXPECTED_TRY(validateComponent(CompSec.getContent()).map_error([](auto E) {
379
4.22k
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Component));
380
4.22k
    return E;
381
4.22k
  }));
382
4.22k
  CompCtx.addComponent(CompSec.getContent());
383
4.22k
  return {};
384
4.23k
}
385
386
Expect<void>
387
10.6k
Validator::validate(const AST::Component::InstanceSection &InstSec) noexcept {
388
10.6k
  for (const auto &Inst : InstSec.getContent()) {
389
9.23k
    EXPECTED_TRY(validate(Inst).map_error([](auto E) {
390
9.23k
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Instance));
391
9.23k
      return E;
392
9.23k
    }));
393
9.23k
  }
394
10.5k
  return {};
395
10.6k
}
396
397
Expect<void>
398
1.11k
Validator::validate(const AST::Component::AliasSection &AliasSec) noexcept {
399
1.11k
  for (const auto &Alias : AliasSec.getContent()) {
400
839
    EXPECTED_TRY(validate(Alias).map_error([](auto E) {
401
742
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Alias));
402
742
      return E;
403
742
    }));
404
742
    const auto &Sort = Alias.getSort();
405
742
    const bool IsOuter =
406
742
        Alias.getTargetType() == AST::Component::Alias::TargetType::Outer;
407
742
    if (Sort.isCore()) {
408
84
      uint32_t NewCoreIdx =
409
84
          CompCtx.incCoreSortIndexSize(Sort.getCoreSortType());
410
      // Carry the outer-aliased module's slot so the alias stays enumerable
411
      // when instantiated.
412
84
      if (IsOuter && Sort.getCoreSortType() ==
413
84
                         AST::Component::Sort::CoreSortType::Module) {
414
0
        CompCtx.carryOuterCoreModule(NewCoreIdx, Alias.getOuter().first,
415
0
                                     Alias.getOuter().second);
416
0
      }
417
658
    } else {
418
658
      uint32_t NewIdx = CompCtx.incSortIndexSize(Sort.getSortType());
419
      // Component analogue of the outer core-module carry above.
420
658
      if (IsOuter &&
421
557
          Sort.getSortType() == AST::Component::Sort::SortType::Component) {
422
26
        CompCtx.carryOuterComponent(NewIdx, Alias.getOuter().first,
423
26
                                    Alias.getOuter().second);
424
26
      }
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
658
      if (IsOuter &&
428
557
          Sort.getSortType() == AST::Component::Sort::SortType::Type) {
429
531
        CompCtx.carryOuterResource(NewIdx, Alias.getOuter().first,
430
531
                                   Alias.getOuter().second);
431
531
      }
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
658
      if (Alias.getTargetType() == AST::Component::Alias::TargetType::Export) {
437
101
        const auto SrcInstIdx = Alias.getExport().first;
438
101
        const auto &SrcName = Alias.getExport().second;
439
101
        const auto &SrcExports = CompCtx.getInstance(SrcInstIdx).Exports;
440
101
        auto It = SrcExports.find(std::string(SrcName));
441
101
        if (It != SrcExports.end()) {
442
101
          if (Sort.getSortType() == AST::Component::Sort::SortType::Instance) {
443
101
            if (It->second.IT != nullptr) {
444
0
              populateInstanceFromType(NewIdx, *It->second.IT);
445
101
            } else if (It->second.NestedInstIdx.has_value()) {
446
101
              const auto &NestedExports =
447
101
                  CompCtx.getInstance(*It->second.NestedInstIdx).Exports;
448
101
              for (const auto &[Name, IE] : NestedExports) {
449
101
                CompCtx.addInstanceExport(NewIdx, Name, IE.ST, IE.IT,
450
101
                                          IE.NestedInstIdx, IE.ResourceId);
451
101
              }
452
101
            }
453
101
          } 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
101
        }
461
101
      }
462
658
    }
463
742
  }
464
1.02k
  return {};
465
1.11k
}
466
467
Expect<void>
468
266k
Validator::validate(const AST::Component::TypeSection &TypeSec) noexcept {
469
277k
  for (const auto &Type : TypeSec.getContent()) {
470
277k
    EXPECTED_TRY(validate(Type).map_error([](auto E) {
471
277k
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Type));
472
277k
      return E;
473
277k
    }));
474
277k
  }
475
266k
  return {};
476
266k
}
477
478
Expect<void>
479
9.74k
Validator::validate(const AST::Component::CanonSection &CanonSec) noexcept {
480
9.74k
  for (const auto &C : CanonSec.getContent()) {
481
2.73k
    EXPECTED_TRY(validate(C).map_error([](auto E) {
482
2.73k
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Canon));
483
2.73k
      return E;
484
2.73k
    }));
485
2.73k
  }
486
9.51k
  return {};
487
9.74k
}
488
489
Expect<void>
490
237
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
237
  const auto &Start = StartSec.getContent();
503
504
  // 1. Function index bounds.
505
237
  const uint32_t FuncIdx = Start.getFunctionIndex();
506
237
  const uint32_t FuncSpaceSize =
507
237
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Func);
508
237
  if (FuncIdx >= FuncSpaceSize) {
509
55
    spdlog::error(ErrCode::Value::InvalidIndex);
510
55
    spdlog::error(
511
55
        "    Start: function index {} exceeds func index space size {}"sv,
512
55
        FuncIdx, FuncSpaceSize);
513
55
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Start));
514
55
    return Unexpect(ErrCode::Value::InvalidIndex);
515
55
  }
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
182
  const AST::Component::FuncType *FT = CompCtx.getFunc(FuncIdx);
521
182
  if (FT != nullptr) {
522
119
    const auto Args = Start.getArguments();
523
119
    const auto &ParamList = FT->getParamList();
524
119
    if (Args.size() != ParamList.size()) {
525
1
      spdlog::error(ErrCode::Value::InvalidIndex);
526
1
      spdlog::error(
527
1
          "    Start: argument count {} does not match func {} param arity {}"sv,
528
1
          Args.size(), FuncIdx, ParamList.size());
529
1
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Start));
530
1
      return Unexpect(ErrCode::Value::InvalidIndex);
531
1
    }
532
118
    const uint32_t ResultArity =
533
118
        static_cast<uint32_t>(FT->getResultList().size());
534
118
    if (Start.getResult() != ResultArity) {
535
11
      spdlog::error(ErrCode::Value::InvalidIndex);
536
11
      spdlog::error(
537
11
          "    Start: declared result count {} does not match func {} result arity {}"sv,
538
11
          Start.getResult(), FuncIdx, ResultArity);
539
11
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Start));
540
11
      return Unexpect(ErrCode::Value::InvalidIndex);
541
11
    }
542
118
  }
543
544
  // 3. Argument indices must be in value index space bounds. Per-arg
545
  // consume-once tracking is deferred (GAP-EX-4).
546
170
  const uint32_t ValueSpaceSize =
547
170
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Value);
548
170
  for (const uint32_t ArgIdx : Start.getArguments()) {
549
6
    if (ArgIdx >= ValueSpaceSize) {
550
2
      spdlog::error(ErrCode::Value::InvalidIndex);
551
2
      spdlog::error(
552
2
          "    Start: argument value index {} exceeds value index space size {}"sv,
553
2
          ArgIdx, ValueSpaceSize);
554
2
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Start));
555
2
      return Unexpect(ErrCode::Value::InvalidIndex);
556
2
    }
557
6
  }
558
559
  // 4. Append result values to the value index space.
560
636M
  for (uint32_t I = 0; I < Start.getResult(); ++I) {
561
636M
    CompCtx.addValue();
562
636M
  }
563
168
  return {};
564
170
}
565
566
Expect<void>
567
9.43k
Validator::validate(const AST::Component::ImportSection &ImpSec) noexcept {
568
9.43k
  for (const auto &Imp : ImpSec.getContent()) {
569
4.71k
    EXPECTED_TRY(validate(Imp).map_error([](auto E) {
570
4.71k
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Import));
571
4.71k
      return E;
572
4.71k
    }));
573
4.71k
  }
574
8.60k
  return {};
575
9.43k
}
576
577
Expect<void>
578
3.10k
Validator::validate(const AST::Component::ExportSection &ExpSec) noexcept {
579
3.10k
  for (const auto &Exp : ExpSec.getContent()) {
580
826
    EXPECTED_TRY(validate(Exp).map_error([](auto E) {
581
826
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Export));
582
826
      return E;
583
826
    }));
584
826
  }
585
2.96k
  return {};
586
3.10k
}
587
588
Expect<void>
589
1.64k
Validator::validate(const AST::Component::CoreInstance &Inst) noexcept {
590
1.64k
  if (Inst.isInstantiateModule()) {
591
    // Instantiate module case.
592
593
    // Check the module index bound first.
594
26
    const uint32_t ModIdx = Inst.getModuleIndex();
595
26
    if (ModIdx >= CompCtx.getCoreSortIndexSize(
596
26
                      AST::Component::Sort::CoreSortType::Module)) {
597
26
      spdlog::error(ErrCode::Value::InvalidIndex);
598
26
      spdlog::error(
599
26
          "    CoreInstance: Module index {} exceeds available core modules {}"sv,
600
26
          ModIdx,
601
26
          CompCtx.getCoreSortIndexSize(
602
26
              AST::Component::Sort::CoreSortType::Module));
603
26
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance));
604
26
      return Unexpect(ErrCode::Value::InvalidIndex);
605
26
    }
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
1.62k
  } else if (Inst.isInlineExport()) {
689
    // Inline export case.
690
    // Allocate the core instance first, then register each inline export.
691
1.62k
    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
1.62k
    std::unordered_set<std::string_view> SeenExports;
696
1.62k
    for (const auto &Export : Inst.getInlineExports()) {
697
8
      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
8
      const auto &Sort = Export.getSortIdx().getSort();
705
8
      uint32_t Idx = Export.getSortIdx().getIdx();
706
8
      assuming(Sort.isCore());
707
8
      if (Idx >= CompCtx.getCoreSortIndexSize(Sort.getCoreSortType())) {
708
        // The error message differs of the tag core sort.
709
7
        ErrCode::Value ErrValue = ErrCode::Value::InvalidIndex;
710
7
        if (Sort.getCoreSortType() == AST::Component::Sort::CoreSortType::Tag) {
711
1
          ErrValue = ErrCode::Value::UnknownCoreTag;
712
1
        }
713
7
        spdlog::error(ErrValue);
714
7
        spdlog::error(
715
7
            "    CoreInstance: Inline export '{}' refers to invalid index {}"sv,
716
7
            Export.getName(), Idx);
717
7
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance));
718
7
        return Unexpect(ErrValue);
719
7
      }
720
      // Map CoreSortType to ExternalType for the instance export map.
721
1
      ExternalType ET;
722
1
      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
1
      default:
739
1
        spdlog::error(ErrCode::Value::InvalidIndex);
740
1
        spdlog::error(
741
1
            "    CoreInstance: Inline export '{}' has unsupported core sort"sv,
742
1
            Export.getName());
743
1
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance));
744
1
        return Unexpect(ErrCode::Value::InvalidIndex);
745
1
      }
746
0
      CompCtx.addCoreInstanceExport(InstanceIdx, Export.getName(), ET);
747
0
    }
748
1.62k
  } else {
749
0
    assumingUnreachable();
750
0
  }
751
1.61k
  return {};
752
1.64k
}
753
754
Expect<void>
755
9.23k
Validator::validate(const AST::Component::Instance &Inst) noexcept {
756
9.23k
  if (Inst.isInstantiateModule()) {
757
    // Instantiate module case.
758
759
    // Check the component index bound first.
760
1.40k
    const uint32_t CompIdx = Inst.getComponentIndex();
761
1.40k
    if (CompIdx >=
762
1.40k
        CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Component)) {
763
49
      spdlog::error(ErrCode::Value::InvalidIndex);
764
49
      spdlog::error(
765
49
          "    Instance: Component index {} exceeds available components {}"sv,
766
49
          CompIdx,
767
49
          CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Component));
768
49
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
769
49
      return Unexpect(ErrCode::Value::InvalidIndex);
770
49
    }
771
    // Reject duplicate argument names on an instantiate expression. The
772
    // spec requires argument names to be strongly-unique per instantiation.
773
1.35k
    {
774
1.35k
      std::unordered_set<std::string_view> SeenArgs;
775
1.35k
      for (const auto &Arg : Inst.getInstantiateArgs()) {
776
9
        if (!SeenArgs.insert(Arg.getName()).second) {
777
3
          spdlog::error(ErrCode::Value::ComponentDuplicateName);
778
3
          spdlog::error("    Instance: Duplicate argument name '{}'"sv,
779
3
                        Arg.getName());
780
3
          spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
781
3
          return Unexpect(ErrCode::Value::ComponentDuplicateName);
782
3
        }
783
9
      }
784
1.35k
    }
785
786
    // Source: raw Component (inline) or ComponentType (imported / aliased).
787
1.35k
    const auto &CompSlot = CompCtx.getComponent(CompIdx);
788
1.35k
    const auto *Comp = CompSlot.Body;
789
1.35k
    const auto *CompTy = CompSlot.Type;
790
791
    // Verify each component import is satisfied by some instantiate arg.
792
1.35k
    auto Args = Inst.getInstantiateArgs();
793
1.35k
    auto checkImport =
794
1.35k
        [&](std::string_view ImportName,
795
1.35k
            const AST::Component::ExternDesc &ImportDesc) -> Expect<void> {
796
9
      const auto ArgIt =
797
9
          std::find_if(Args.begin(), Args.end(), [&](const auto &Arg) {
798
0
            return Arg.getName() == ImportName;
799
0
          });
800
9
      if (ArgIt == Args.end()) {
801
9
        spdlog::error(ErrCode::Value::MissingArgument);
802
9
        spdlog::error(
803
9
            "    Instance: Component index {} missing argument for import '{}'"sv,
804
9
            Inst.getComponentIndex(), ImportName);
805
9
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
806
9
        return Unexpect(ErrCode::Value::MissingArgument);
807
9
      }
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
1.35k
    if (Comp != nullptr) {
869
2.72k
      for (const auto &Sec : Comp->getSections()) {
870
2.72k
        if (const auto *IS = std::get_if<AST::Component::ImportSection>(&Sec)) {
871
191
          for (const auto &Imp : IS->getContent()) {
872
8
            EXPECTED_TRY(checkImport(Imp.getName(), Imp.getDesc()));
873
8
          }
874
191
        }
875
2.72k
      }
876
1.31k
    } else if (CompTy != nullptr) {
877
23
      for (const auto &CD : CompTy->getDecl()) {
878
5
        if (CD.isImportDecl()) {
879
1
          const auto &ID = CD.getImport();
880
1
          EXPECTED_TRY(checkImport(ID.getName(), ID.getExternDesc()));
881
1
        }
882
5
      }
883
23
    }
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
1.34k
    uint32_t InstanceIdx = CompCtx.addInstance();
890
1.34k
    if (Comp != nullptr) {
891
2.71k
      for (const auto &Sec : Comp->getSections()) {
892
2.71k
        const auto *ES = std::get_if<AST::Component::ExportSection>(&Sec);
893
2.71k
        if (ES == nullptr) {
894
2.17k
          continue;
895
2.17k
        }
896
538
        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
538
      }
913
1.30k
    } else if (CompTy != nullptr) {
914
22
      for (const auto &CD : CompTy->getDecl()) {
915
4
        if (!CD.isInstanceDecl()) {
916
0
          continue;
917
0
        }
918
4
        const auto &ID = CD.getInstance();
919
4
        if (!ID.isExportDecl()) {
920
0
          continue;
921
0
        }
922
4
        const auto &ED = ID.getExport();
923
4
        const auto OptST = descTypeToSortType(ED.getExternDesc().getDescType());
924
4
        if (!OptST.has_value()) {
925
0
          continue; // `(core module)` export — not a component-side entry.
926
0
        }
927
4
        CompCtx.addInstanceExport(InstanceIdx, ED.getName(), *OptST);
928
4
      }
929
22
    }
930
7.82k
  } else if (Inst.isInlineExport()) {
931
    // Allocate the instance first so exports can be registered on it.
932
7.82k
    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
7.82k
    std::unordered_set<std::string_view> SeenExports;
939
7.82k
    for (const auto &Export : Inst.getInlineExports()) {
940
348
      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
348
      const auto &Sort = Export.getSortIdx().getSort();
948
348
      uint32_t Idx = Export.getSortIdx().getIdx();
949
348
      if (Sort.isCore()) {
950
22
        if (Idx >= CompCtx.getCoreSortIndexSize(Sort.getCoreSortType())) {
951
22
          spdlog::error(ErrCode::Value::InvalidIndex);
952
22
          spdlog::error(
953
22
              "    Instance: Inline export '{}' refers to invalid index {}"sv,
954
22
              Export.getName(), Idx);
955
22
          spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
956
22
          return Unexpect(ErrCode::Value::InvalidIndex);
957
22
        }
958
0
        continue;
959
22
      }
960
326
      if (Idx >= CompCtx.getSortIndexSize(Sort.getSortType())) {
961
17
        spdlog::error(ErrCode::Value::InvalidIndex);
962
17
        spdlog::error(
963
17
            "    Instance: Inline export '{}' refers to invalid index {}"sv,
964
17
            Export.getName(), Idx);
965
17
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
966
17
        return Unexpect(ErrCode::Value::InvalidIndex);
967
17
      }
968
309
      if (Sort.getSortType() == AST::Component::Sort::SortType::Type) {
969
39
        auto SubstitutedIdx =
970
39
            CompCtx.getSubstitutedType(std::string(Export.getName()));
971
39
        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
39
      }
979
309
      std::optional<uint32_t> NestedIdx;
980
309
      const AST::Component::InstanceType *PropagatedIT = nullptr;
981
309
      if (Sort.getSortType() == AST::Component::Sort::SortType::Instance) {
982
270
        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
270
        PropagatedIT = CompCtx.getInstance(Idx).Type;
988
270
      }
989
309
      CompCtx.addInstanceExport(InstanceIdx, Export.getName(),
990
309
                                Sort.getSortType(), PropagatedIT, NestedIdx);
991
309
    }
992
7.82k
  } else {
993
0
    assumingUnreachable();
994
0
  }
995
9.13k
  return {};
996
9.23k
}
997
998
55
Expect<void> Validator::validate(const AST::Component::CoreAlias &A) noexcept {
999
  // CoreAlias is always an outer alias.
1000
55
  uint32_t Ct = A.getComponentJump();
1001
55
  uint32_t Idx = A.getIndex();
1002
1003
55
  uint32_t OutLinkCompCnt = 0;
1004
55
  const auto *TargetCtx = &CompCtx.getCurrentContext();
1005
116
  while (Ct > OutLinkCompCnt && TargetCtx != nullptr) {
1006
61
    TargetCtx = TargetCtx->Parent;
1007
61
    OutLinkCompCnt++;
1008
61
  }
1009
55
  if (TargetCtx == nullptr) {
1010
18
    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
18
    spdlog::error(
1014
18
        "    CoreAlias: outer count {} exceeds enclosing component count {}"sv,
1015
18
        Ct, OutLinkCompCnt - 1);
1016
18
    return Unexpect(ErrCode::Value::InvalidIndex);
1017
18
  }
1018
1019
37
  const auto &Sort = A.getSort();
1020
37
  if (Sort.isCore()) {
1021
37
    if (Idx >= TargetCtx->getCoreSortIndexSize(Sort.getCoreSortType())) {
1022
20
      spdlog::error(ErrCode::Value::InvalidIndex);
1023
20
      spdlog::error("    CoreAlias: outer index {} out of bounds"sv, Idx);
1024
20
      return Unexpect(ErrCode::Value::InvalidIndex);
1025
20
    }
1026
17
    CompCtx.incCoreSortIndexSize(Sort.getCoreSortType());
1027
17
  }
1028
17
  return {};
1029
37
}
1030
1031
842
Expect<void> Validator::validate(const AST::Component::Alias &Alias) noexcept {
1032
842
  const auto &Sort = Alias.getSort();
1033
842
  switch (Alias.getTargetType()) {
1034
138
  case AST::Component::Alias::TargetType::Export: {
1035
138
    const auto Idx = Alias.getExport().first;
1036
138
    const auto &Name = Alias.getExport().second;
1037
1038
138
    if (Sort.isCore()) {
1039
5
      spdlog::error(ErrCode::Value::InvalidTypeReference);
1040
5
      spdlog::error("    Alias export: Mapping an export '{}' to core:sort"sv,
1041
5
                    Name);
1042
5
      return Unexpect(ErrCode::Value::InvalidTypeReference);
1043
5
    }
1044
1045
133
    if (Idx >=
1046
133
        CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Instance)) {
1047
27
      spdlog::error(ErrCode::Value::InvalidIndex);
1048
27
      spdlog::error(
1049
27
          "    Alias export: Export index {} exceeds available component instance index {}"sv,
1050
27
          Idx,
1051
27
          CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Instance));
1052
27
      return Unexpect(ErrCode::Value::InvalidIndex);
1053
27
    }
1054
1055
106
    const auto &InstExports = CompCtx.getInstance(Idx).Exports;
1056
106
    auto It = InstExports.find(std::string(Name));
1057
106
    if (It == InstExports.cend()) {
1058
3
      spdlog::error(ErrCode::Value::ExportNotFound);
1059
3
      spdlog::error(
1060
3
          "    Alias export: No matching export '{}' found in component instance index {}"sv,
1061
3
          Name, Idx);
1062
3
      return Unexpect(ErrCode::Value::ExportNotFound);
1063
3
    }
1064
1065
103
    if (It->second.ST != Sort.getSortType()) {
1066
2
      spdlog::error(ErrCode::Value::InvalidTypeReference);
1067
2
      spdlog::error("    Alias export: Type mapping mismatch for export '{}'"sv,
1068
2
                    Name);
1069
2
      return Unexpect(ErrCode::Value::InvalidTypeReference);
1070
2
    }
1071
101
    return {};
1072
103
  }
1073
35
  case AST::Component::Alias::TargetType::CoreExport: {
1074
35
    const auto Idx = Alias.getExport().first;
1075
35
    const auto &Name = Alias.getExport().second;
1076
1077
35
    if (!Sort.isCore()) {
1078
1
      spdlog::error(ErrCode::Value::InvalidTypeReference);
1079
1
      spdlog::error("    Alias core:export: Mapping a export '{}' to sort"sv,
1080
1
                    Name);
1081
1
      return Unexpect(ErrCode::Value::InvalidTypeReference);
1082
1
    }
1083
1084
34
    if (Idx >= CompCtx.getCoreSortIndexSize(
1085
34
                   AST::Component::Sort::CoreSortType::Instance)) {
1086
32
      spdlog::error(ErrCode::Value::InvalidIndex);
1087
32
      spdlog::error(
1088
32
          "    Alias core:export: Export index {} exceeds available core instance index {}"sv,
1089
32
          Idx,
1090
32
          CompCtx.getCoreSortIndexSize(
1091
32
              AST::Component::Sort::CoreSortType::Instance) -
1092
32
              1);
1093
32
      return Unexpect(ErrCode::Value::InvalidIndex);
1094
32
    }
1095
1096
2
    const auto &CoreExports = CompCtx.getCoreInstance(Idx);
1097
2
    auto It = CoreExports.find(std::string(Name));
1098
2
    if (It == CoreExports.end()) {
1099
2
      spdlog::error(ErrCode::Value::ExportNotFound);
1100
2
      spdlog::error(
1101
2
          "    Alias core:export: No matching export '{}' found in core instance index {}"sv,
1102
2
          Name, Idx);
1103
2
      return Unexpect(ErrCode::Value::ExportNotFound);
1104
2
    }
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
669
  case AST::Component::Alias::TargetType::Outer: {
1146
669
    const auto Ct = Alias.getOuter().first;
1147
669
    const auto Idx = Alias.getOuter().second;
1148
1149
669
    uint32_t OutLinkCompCnt = 0;
1150
669
    const auto *TargetCtx = &CompCtx.getCurrentContext();
1151
685
    while (Ct > OutLinkCompCnt && TargetCtx != nullptr) {
1152
16
      TargetCtx = TargetCtx->Parent;
1153
16
      OutLinkCompCnt++;
1154
16
    }
1155
669
    if (TargetCtx == nullptr) {
1156
13
      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
13
      spdlog::error(
1161
13
          "    Alias outer: Component out-link count {} is exceeding the enclosing component count {}"sv,
1162
13
          Ct, OutLinkCompCnt - 1);
1163
13
      return Unexpect(ErrCode::Value::InvalidIndex);
1164
13
    }
1165
1166
656
    if (Sort.isCore()) {
1167
95
      if (Sort.getCoreSortType() !=
1168
95
              AST::Component::Sort::CoreSortType::Module &&
1169
94
          Sort.getCoreSortType() != AST::Component::Sort::CoreSortType::Type) {
1170
3
        spdlog::error(ErrCode::Value::InvalidTypeReference);
1171
3
        spdlog::error(
1172
3
            "    Alias outer: Invalid core:sort for outer alias. Only type, module, or component are allowed."sv);
1173
3
        return Unexpect(ErrCode::Value::InvalidTypeReference);
1174
3
      }
1175
92
      if (Idx >= TargetCtx->getCoreSortIndexSize(Sort.getCoreSortType())) {
1176
8
        spdlog::error(ErrCode::Value::InvalidIndex);
1177
8
        spdlog::error(
1178
8
            "    Alias outer: core:sort index {} invalid in component context"sv,
1179
8
            Idx);
1180
8
        return Unexpect(ErrCode::Value::InvalidIndex);
1181
8
      }
1182
561
    } else {
1183
561
      if (Sort.getSortType() != AST::Component::Sort::SortType::Type &&
1184
29
          Sort.getSortType() != AST::Component::Sort::SortType::Component) {
1185
2
        spdlog::error(ErrCode::Value::InvalidTypeReference);
1186
2
        spdlog::error(
1187
2
            "    Alias outer: Invalid sort for outer alias. Only type, module, or component are allowed."sv);
1188
2
        return Unexpect(ErrCode::Value::InvalidTypeReference);
1189
2
      }
1190
559
      if (Idx >= TargetCtx->getSortIndexSize(Sort.getSortType())) {
1191
2
        spdlog::error(ErrCode::Value::InvalidIndex);
1192
2
        spdlog::error(
1193
2
            "    Alias outer: sort index {} invalid in component context"sv,
1194
2
            Idx);
1195
2
        return Unexpect(ErrCode::Value::InvalidIndex);
1196
2
      }
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
559
    }
1209
641
    return {};
1210
656
  }
1211
0
  default:
1212
0
    assumingUnreachable();
1213
842
  }
1214
842
}
1215
1216
Expect<void>
1217
45.7k
Validator::validate(const AST::Component::CoreDefType &DType) noexcept {
1218
45.7k
  if (DType.isRecType()) {
1219
    // Each sub-type in the rec group gets its own entry in core:type.
1220
22.9k
    for (const auto &ST : DType.getSubTypes()) {
1221
22.8k
      CompCtx.addCoreType(&ST);
1222
22.8k
    }
1223
22.9k
  } else if (DType.isModuleType()) {
1224
    // Module types are validated with an initially-empty type index space.
1225
22.8k
    CompCtx.enterTypeDefinition();
1226
22.8k
    for (const auto &Decl : DType.getModuleType()) {
1227
846
      EXPECTED_TRY(validate(Decl).map_error([](auto E) {
1228
846
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreDefType));
1229
846
        return E;
1230
846
      }));
1231
846
    }
1232
22.7k
    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
22.7k
    uint32_t NewTypeIdx = CompCtx.addCoreType();
1236
22.7k
    CompCtx.setCoreModuleType(NewTypeIdx, &DType);
1237
22.7k
  } else {
1238
0
    assumingUnreachable();
1239
0
  }
1240
45.7k
  return {};
1241
45.7k
}
1242
1243
Expect<void>
1244
277k
Validator::validate(const AST::Component::DefType &DType) noexcept {
1245
277k
  auto ReportError = [](auto E) {
1246
278
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_DefType));
1247
278
    return E;
1248
278
  };
1249
1250
277k
  if (DType.isDefValType()) {
1251
30.5k
    EXPECTED_TRY(validate(DType.getDefValType()).map_error(ReportError));
1252
246k
  } else if (DType.isFuncType()) {
1253
6.05k
    EXPECTED_TRY(validate(DType.getFuncType()).map_error(ReportError));
1254
240k
  } else if (DType.isComponentType()) {
1255
57.9k
    EXPECTED_TRY(validate(DType.getComponentType()).map_error(ReportError));
1256
182k
  } else if (DType.isInstanceType()) {
1257
178k
    EXPECTED_TRY(validate(DType.getInstanceType()).map_error(ReportError));
1258
178k
  } else if (DType.isResourceType()) {
1259
4.46k
    EXPECTED_TRY(validate(DType.getResourceType()).map_error(ReportError));
1260
4.46k
  } else {
1261
0
    assumingUnreachable();
1262
0
  }
1263
  // addType records body/id/locality for resource DefTypes in one step.
1264
277k
  CompCtx.addType(&DType);
1265
277k
  return {};
1266
277k
}
1267
1268
Expect<void>
1269
2.73k
Validator::validate(const AST::Component::Canonical &Canon) noexcept {
1270
2.73k
  switch (Canon.getOpCode()) {
1271
735
  case ComponentCanonOpCode::Lift:
1272
735
    return validateCanonLift(Canon);
1273
1.30k
  case ComponentCanonOpCode::Lower:
1274
1.30k
    return validateCanonLower(Canon);
1275
110
  case ComponentCanonOpCode::Resource__new:
1276
110
    return validateCanonResourceNew(Canon);
1277
52
  case ComponentCanonOpCode::Resource__rep:
1278
52
    return validateCanonResourceRep(Canon);
1279
465
  case ComponentCanonOpCode::Resource__drop:
1280
494
  case ComponentCanonOpCode::Resource__drop_async:
1281
494
    return validateCanonResourceDrop(Canon);
1282
42
  default:
1283
42
    spdlog::error(ErrCode::Value::ComponentNotImplValidator);
1284
42
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1285
42
    return Unexpect(ErrCode::Value::ComponentNotImplValidator);
1286
2.73k
  }
1287
2.73k
}
1288
1289
Expect<void> Validator::validateCanonOptions(
1290
    ComponentCanonOpCode Code,
1291
2.52k
    Span<const AST::Component::CanonOpt> Opts) noexcept {
1292
2.52k
  using OptCode = ComponentCanonOptCode;
1293
2.52k
  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
2.52k
  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
2.52k
  bool HasEncoding = false;
1306
2.52k
  bool HasMemory = false;
1307
2.52k
  bool HasRealloc = false;
1308
2.52k
  bool HasPostReturn = false;
1309
2.52k
  bool HasAsync = false;
1310
2.52k
  bool HasCallback = false;
1311
2.52k
  bool HasAlwaysTaskReturn = false;
1312
2.52k
  uint32_t ReallocIdx = 0;
1313
2.52k
  uint32_t CallbackIdx = 0;
1314
2.52k
  uint32_t PostReturnIdx = 0;
1315
2.52k
  uint32_t MemoryIdx = 0;
1316
1317
2.52k
  auto RejectDup = [&](const char *Name) -> Expect<void> {
1318
3
    spdlog::error(ErrCode::Value::InvalidCanonOption);
1319
3
    spdlog::error("    canonical option '{}' appears more than once"sv, Name);
1320
3
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1321
3
    return Unexpect(ErrCode::Value::InvalidCanonOption);
1322
3
  };
1323
2.52k
  auto RejectSite = [&](const char *Name) -> Expect<void> {
1324
4
    spdlog::error(ErrCode::Value::InvalidCanonOption);
1325
4
    spdlog::error(
1326
4
        "    canonical option '{}' is not allowed in this canon built-in"sv,
1327
4
        Name);
1328
4
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1329
4
    return Unexpect(ErrCode::Value::InvalidCanonOption);
1330
4
  };
1331
1332
2.52k
  for (const auto &Opt : Opts) {
1333
1.30k
    switch (Opt.getCode()) {
1334
942
    case OptCode::Encode_UTF8:
1335
1.06k
    case OptCode::Encode_UTF16:
1336
1.18k
    case OptCode::Encode_Latin1:
1337
1.18k
      if (HasEncoding) {
1338
2
        return RejectDup("string-encoding");
1339
2
      }
1340
1.18k
      HasEncoding = true;
1341
1.18k
      break;
1342
1
    case OptCode::Memory:
1343
1
      if (HasMemory) {
1344
0
        return RejectDup("memory");
1345
0
      }
1346
1
      HasMemory = true;
1347
1
      MemoryIdx = Opt.getIndex();
1348
1
      break;
1349
1
    case OptCode::Realloc:
1350
1
      if (HasRealloc) {
1351
0
        return RejectDup("realloc");
1352
0
      }
1353
1
      HasRealloc = true;
1354
1
      ReallocIdx = Opt.getIndex();
1355
1
      break;
1356
1
    case OptCode::PostReturn:
1357
1
      if (Code != CanonOp::Lift) {
1358
1
        return RejectSite("post-return");
1359
1
      }
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
107
    case OptCode::Async:
1367
107
      if (HasAsync) {
1368
1
        return RejectDup("async");
1369
1
      }
1370
106
      HasAsync = true;
1371
106
      break;
1372
1
    case OptCode::Callback:
1373
1
      if (Code != CanonOp::Lift) {
1374
1
        return RejectSite("callback");
1375
1
      }
1376
0
      if (HasCallback) {
1377
0
        return RejectDup("callback");
1378
0
      }
1379
0
      HasCallback = true;
1380
0
      CallbackIdx = Opt.getIndex();
1381
0
      break;
1382
2
    case OptCode::AlwaysTaskReturn:
1383
2
      if (Code != CanonOp::Lift) {
1384
2
        return RejectSite("always-task-return");
1385
2
      }
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
1.30k
    }
1398
1.30k
  }
1399
1400
  // Structural rules.
1401
2.51k
  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
2.51k
  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
2.51k
  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
2.51k
  if (HasRealloc && !HasMemory) {
1422
1
    spdlog::error(ErrCode::Value::InvalidCanonOption);
1423
1
    spdlog::error(
1424
1
        "    canonical option 'realloc' requires 'memory' to also be specified"sv);
1425
1
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1426
1
    return Unexpect(ErrCode::Value::InvalidCanonOption);
1427
1
  }
1428
1429
  // Index bounds checks. Core func signature body checks (realloc/callback)
1430
  // deferred as GAP-C-5b once getCoreFunc signatures are populated.
1431
2.51k
  if (HasMemory &&
1432
1
      MemoryIdx >= CompCtx.getCoreSortIndexSize(
1433
1
                       AST::Component::Sort::CoreSortType::Memory)) {
1434
1
    spdlog::error(ErrCode::Value::InvalidIndex);
1435
1
    spdlog::error(
1436
1
        "    canonical option 'memory': core memory index {} out of bounds"sv,
1437
1
        MemoryIdx);
1438
1
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1439
1
    return Unexpect(ErrCode::Value::InvalidIndex);
1440
1
  }
1441
2.51k
  const uint32_t CoreFuncSpaceSize =
1442
2.51k
      CompCtx.getCoreSortIndexSize(AST::Component::Sort::CoreSortType::Func);
1443
2.51k
  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
2.51k
  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
2.51k
  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
2.51k
  return {};
1468
2.51k
}
1469
1470
Expect<void>
1471
735
Validator::validateCanonLift(const AST::Component::Canonical &Canon) noexcept {
1472
735
  const uint32_t CoreFuncIdx = Canon.getIndex();
1473
735
  const uint32_t CoreFuncSpaceSize =
1474
735
      CompCtx.getCoreSortIndexSize(AST::Component::Sort::CoreSortType::Func);
1475
  // 1. Core func index bounds.
1476
735
  if (CoreFuncIdx >= CoreFuncSpaceSize) {
1477
2
    spdlog::error(ErrCode::Value::InvalidIndex);
1478
2
    spdlog::error(
1479
2
        "    canon lift: core func index {} exceeds core func index space size {}"sv,
1480
2
        CoreFuncIdx, CoreFuncSpaceSize);
1481
2
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1482
2
    return Unexpect(ErrCode::Value::InvalidIndex);
1483
2
  }
1484
733
  const uint32_t TypeIdx = Canon.getTargetIndex();
1485
733
  const uint32_t TypeSpaceSize =
1486
733
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type);
1487
  // 2. Target type index bounds.
1488
733
  if (TypeIdx >= TypeSpaceSize) {
1489
2
    spdlog::error(ErrCode::Value::InvalidIndex);
1490
2
    spdlog::error(
1491
2
        "    canon lift: type index {} exceeds type index space size {}"sv,
1492
2
        TypeIdx, TypeSpaceSize);
1493
2
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1494
2
    return Unexpect(ErrCode::Value::InvalidIndex);
1495
2
  }
1496
  // 3. Target type must be a component FuncType.
1497
731
  const auto *DT = CompCtx.getDefType(TypeIdx);
1498
731
  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
1
    spdlog::error(ErrCode::Value::InvalidTypeReference);
1502
1
    spdlog::error(
1503
1
        "    canon lift: target type index {} is an unresolved type slot"sv,
1504
1
        TypeIdx);
1505
1
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1506
1
    return Unexpect(ErrCode::Value::InvalidTypeReference);
1507
1
  }
1508
730
  if (!DT->isFuncType()) {
1509
1
    spdlog::error(ErrCode::Value::InvalidTypeReference);
1510
1
    spdlog::error(
1511
1
        "    canon lift: target type index {} does not reference a component func type"sv,
1512
1
        TypeIdx);
1513
1
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1514
1
    return Unexpect(ErrCode::Value::InvalidTypeReference);
1515
1
  }
1516
  // 4. Validate canonical options (Lift site allows all).
1517
729
  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
729
  CompCtx.addFunc(&DT->getFuncType());
1521
729
  return {};
1522
729
}
1523
1524
Expect<void>
1525
1.30k
Validator::validateCanonLower(const AST::Component::Canonical &Canon) noexcept {
1526
1.30k
  const uint32_t FuncIdx = Canon.getIndex();
1527
1.30k
  const uint32_t FuncSpaceSize =
1528
1.30k
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Func);
1529
  // 1. Component func index bounds.
1530
1.30k
  if (FuncIdx >= FuncSpaceSize) {
1531
8
    spdlog::error(ErrCode::Value::InvalidIndex);
1532
8
    spdlog::error(
1533
8
        "    canon lower: component func index {} exceeds func index space size {}"sv,
1534
8
        FuncIdx, FuncSpaceSize);
1535
8
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1536
8
    return Unexpect(ErrCode::Value::InvalidIndex);
1537
8
  }
1538
  // 2. Validate canonical options (per-site rules for Lower).
1539
1.29k
  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
1.28k
  CompCtx.addCoreFunc();
1543
1.28k
  return {};
1544
1.29k
}
1545
1546
Expect<void> Validator::validateCanonResourceNew(
1547
110
    const AST::Component::Canonical &Canon) noexcept {
1548
110
  const uint32_t Idx = Canon.getIndex();
1549
110
  const uint32_t TypeSpaceSize =
1550
110
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type);
1551
  // 1. Type index bounds.
1552
110
  if (Idx >= TypeSpaceSize) {
1553
26
    spdlog::error(ErrCode::Value::InvalidIndex);
1554
26
    spdlog::error(
1555
26
        "    canon resource.new: type index {} exceeds type index space size {}"sv,
1556
26
        Idx, TypeSpaceSize);
1557
26
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1558
26
    return Unexpect(ErrCode::Value::InvalidIndex);
1559
26
  }
1560
  // 2. Type must be a locally-defined resource.
1561
84
  const auto *RInfo = CompCtx.getResource(Idx);
1562
84
  if (RInfo == nullptr) {
1563
31
    spdlog::error(ErrCode::Value::InvalidTypeReference);
1564
31
    spdlog::error(
1565
31
        "    canon resource.new: type index {} does not reference a resource"sv,
1566
31
        Idx);
1567
31
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1568
31
    return Unexpect(ErrCode::Value::InvalidTypeReference);
1569
31
  }
1570
53
  if (!RInfo->LocallyDefined) {
1571
9
    spdlog::error(ErrCode::Value::InvalidTypeReference);
1572
9
    spdlog::error(
1573
9
        "    canon resource.new: type index {} is not locally defined (imported or outer-aliased resources are not allowed)"sv,
1574
9
        Idx);
1575
9
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1576
9
    return Unexpect(ErrCode::Value::InvalidTypeReference);
1577
9
  }
1578
  // 4. Validate canonical options.
1579
44
  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
44
  CompCtx.addCoreFunc(&CoreFuncType_I32_I32);
1583
44
  return {};
1584
44
}
1585
1586
Expect<void> Validator::validateCanonResourceRep(
1587
52
    const AST::Component::Canonical &Canon) noexcept {
1588
52
  const uint32_t Idx = Canon.getIndex();
1589
52
  const uint32_t TypeSpaceSize =
1590
52
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type);
1591
  // 1. Type index bounds.
1592
52
  if (Idx >= TypeSpaceSize) {
1593
33
    spdlog::error(ErrCode::Value::InvalidIndex);
1594
33
    spdlog::error(
1595
33
        "    canon resource.rep: type index {} exceeds type index space size {}"sv,
1596
33
        Idx, TypeSpaceSize);
1597
33
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1598
33
    return Unexpect(ErrCode::Value::InvalidIndex);
1599
33
  }
1600
  // 2. Type must be a locally-defined resource.
1601
19
  const auto *RInfo = CompCtx.getResource(Idx);
1602
19
  if (RInfo == nullptr) {
1603
10
    spdlog::error(ErrCode::Value::InvalidTypeReference);
1604
10
    spdlog::error(
1605
10
        "    canon resource.rep: type index {} does not reference a resource"sv,
1606
10
        Idx);
1607
10
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1608
10
    return Unexpect(ErrCode::Value::InvalidTypeReference);
1609
10
  }
1610
9
  if (!RInfo->LocallyDefined) {
1611
8
    spdlog::error(ErrCode::Value::InvalidTypeReference);
1612
8
    spdlog::error(
1613
8
        "    canon resource.rep: type index {} is not locally defined (imported or outer-aliased resources are not allowed)"sv,
1614
8
        Idx);
1615
8
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1616
8
    return Unexpect(ErrCode::Value::InvalidTypeReference);
1617
8
  }
1618
  // 4. Validate canonical options.
1619
1
  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
1
  CompCtx.addCoreFunc(&CoreFuncType_I32_I32);
1623
1
  return {};
1624
1
}
1625
1626
Expect<void> Validator::validateCanonResourceDrop(
1627
494
    const AST::Component::Canonical &Canon) noexcept {
1628
494
  const uint32_t Idx = Canon.getIndex();
1629
494
  const uint32_t TypeSpaceSize =
1630
494
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type);
1631
  // 1. Type index bounds.
1632
494
  if (Idx >= TypeSpaceSize) {
1633
36
    spdlog::error(ErrCode::Value::InvalidIndex);
1634
36
    spdlog::error(
1635
36
        "    canon resource.drop: type index {} exceeds type index space size {}"sv,
1636
36
        Idx, TypeSpaceSize);
1637
36
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1638
36
    return Unexpect(ErrCode::Value::InvalidIndex);
1639
36
  }
1640
  // 2. Type must be a resource type.
1641
458
  if (CompCtx.getResource(Idx) == nullptr) {
1642
6
    spdlog::error(ErrCode::Value::InvalidTypeReference);
1643
6
    spdlog::error(
1644
6
        "    canon resource.drop: type index {} does not reference a resource"sv,
1645
6
        Idx);
1646
6
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1647
6
    return Unexpect(ErrCode::Value::InvalidTypeReference);
1648
6
  }
1649
  // 3. resource.drop accepts both local and imported resources — no locality
1650
  // check.
1651
  // 4. Validate canonical options.
1652
452
  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
452
  CompCtx.addCoreFunc(&CoreFuncType_I32_Void);
1657
452
  return {};
1658
452
}
1659
1660
4.71k
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
4.71k
  const uint32_t TypeSpaceBefore =
1675
4.71k
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type);
1676
1677
4.71k
  EXPECTED_TRY(validate(Im.getDesc()).map_error([](auto E) {
1678
4.64k
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Import));
1679
4.64k
    return E;
1680
4.64k
  }));
1681
1682
8.63k
  EXPECTED_TRY(ComponentName CName,
1683
8.63k
               ComponentName::parse(Im.getName()).map_error([](auto E) {
1684
8.63k
                 spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Import));
1685
8.63k
                 return E;
1686
8.63k
               }));
1687
1688
  // Annotated plainnames ([constructor], [method], [static]) can only appear
1689
  // on func imports.
1690
8.63k
  switch (CName.getKind()) {
1691
0
  case ComponentNameKind::Constructor:
1692
2
  case ComponentNameKind::Method:
1693
12
  case ComponentNameKind::Static:
1694
12
    if (Im.getDesc().getDescType() !=
1695
12
        AST::Component::ExternDesc::DescType::FuncType) {
1696
12
      spdlog::error(ErrCode::Value::ComponentInvalidName);
1697
12
      spdlog::error("    Import: annotated name requires func type"sv);
1698
12
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Import));
1699
12
      return Unexpect(ErrCode::Value::ComponentInvalidName);
1700
12
    }
1701
0
    break;
1702
3.98k
  default:
1703
3.98k
    break;
1704
8.63k
  }
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
3.98k
  std::string_view ResourceLabel;
1715
3.98k
  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
3.98k
  default:
1726
3.98k
    break;
1727
3.98k
  }
1728
3.98k
  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
3.98k
  if (!CompCtx.addImportedName(CName)) {
1738
108
    spdlog::error(ErrCode::Value::ComponentDuplicateName);
1739
108
    spdlog::error("    Import: Duplicate import name"sv);
1740
108
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Import));
1741
108
    return Unexpect(ErrCode::Value::ComponentDuplicateName);
1742
108
  }
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
3.87k
  if (Im.getDesc().getDescType() ==
1747
3.87k
          AST::Component::ExternDesc::DescType::TypeBound &&
1748
1.19k
      CName.getKind() == ComponentNameKind::Label) {
1749
1.17k
    CompCtx.addResourceLabel(Im.getName(), TypeSpaceBefore);
1750
1.17k
  }
1751
1752
3.87k
  return {};
1753
3.98k
}
1754
1755
826
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
826
  const auto &Sort = Ex.getSortIndex().getSort();
1771
826
  uint32_t Idx = Ex.getSortIndex().getIdx();
1772
826
  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
3
    if (Sort.getCoreSortType() != AST::Component::Sort::CoreSortType::Module) {
1776
2
      spdlog::error(ErrCode::Value::InvalidTypeReference);
1777
2
      spdlog::error(
1778
2
          "    Export: core sort other than `core module` is not allowed"sv);
1779
2
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export));
1780
2
      return Unexpect(ErrCode::Value::InvalidTypeReference);
1781
2
    }
1782
1
    if (Idx >= CompCtx.getCoreSortIndexSize(Sort.getCoreSortType())) {
1783
1
      spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds);
1784
1
      spdlog::error("    Export: sort index {} out of bounds"sv, Idx);
1785
1
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export));
1786
1
      return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds);
1787
1
    }
1788
823
  } else {
1789
823
    if (Idx >= CompCtx.getSortIndexSize(Sort.getSortType())) {
1790
13
      spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds);
1791
13
      spdlog::error("    Export: sort index {} out of bounds"sv, Idx);
1792
13
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export));
1793
13
      return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds);
1794
13
    }
1795
823
  }
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
810
  if (Ex.getDesc().has_value() &&
1803
2
      !sortMatchesDescType(Sort, Ex.getDesc()->getDescType())) {
1804
1
    spdlog::error(ErrCode::Value::ExportAscriptionIncompatible);
1805
1
    spdlog::error(
1806
1
        "    Export: ascribed externdesc kind does not match sortidx sort"sv);
1807
1
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export));
1808
1
    return Unexpect(ErrCode::Value::ExportAscriptionIncompatible);
1809
1
  }
1810
1811
  // Validate name grammar, then enforce strong-uniqueness across exports.
1812
1.51k
  EXPECTED_TRY(ComponentName CName,
1813
1.51k
               validateExportName(Ex.getName()).map_error([](auto E) {
1814
1.51k
                 spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export));
1815
1.51k
                 return E;
1816
1.51k
               }));
1817
1.51k
  if (!CompCtx.addExportedName(CName)) {
1818
10
    spdlog::error(ErrCode::Value::ComponentDuplicateName);
1819
10
    spdlog::error("    Export: Duplicate export name '{}'"sv, Ex.getName());
1820
10
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export));
1821
10
    return Unexpect(ErrCode::Value::ComponentDuplicateName);
1822
10
  }
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
692
  if (Sort.isCore()) {
1829
0
    CompCtx.incCoreSortIndexSize(Sort.getCoreSortType());
1830
692
  } else {
1831
692
    const AST::Component::InstanceType *IT = nullptr;
1832
692
    const bool IsInst =
1833
692
        Sort.getSortType() == AST::Component::Sort::SortType::Instance;
1834
692
    const bool HasInstAscription =
1835
692
        IsInst && Ex.getDesc().has_value() &&
1836
0
        Ex.getDesc()->getDescType() ==
1837
0
            AST::Component::ExternDesc::DescType::InstanceType;
1838
692
    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
692
    uint32_t NewIdx = CompCtx.incSortIndexSize(Sort.getSortType());
1853
692
    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
692
  }
1870
692
  return {};
1871
692
}
1872
1873
Expect<void>
1874
4.76k
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
4.76k
  switch (Desc.getDescType()) {
1892
119
  case AST::Component::ExternDesc::DescType::CoreType: {
1893
119
    const uint32_t RefIdx = Desc.getTypeIndex();
1894
119
    const uint32_t CoreTypeSize =
1895
119
        CompCtx.getCoreSortIndexSize(AST::Component::Sort::CoreSortType::Type);
1896
119
    if (RefIdx >= CoreTypeSize) {
1897
10
      spdlog::error(ErrCode::Value::InvalidIndex);
1898
10
      spdlog::error(
1899
10
          "    ExternDesc: core type index {} exceeds core:type index space size {}"sv,
1900
10
          RefIdx, CoreTypeSize);
1901
10
      return Unexpect(ErrCode::Value::InvalidIndex);
1902
10
    }
1903
109
    break;
1904
119
  }
1905
332
  case AST::Component::ExternDesc::DescType::FuncType:
1906
457
  case AST::Component::ExternDesc::DescType::ComponentType:
1907
628
  case AST::Component::ExternDesc::DescType::InstanceType: {
1908
628
    const uint32_t RefIdx = Desc.getTypeIndex();
1909
628
    const uint32_t TypeSize =
1910
628
        CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type);
1911
628
    if (RefIdx >= TypeSize) {
1912
70
      spdlog::error(ErrCode::Value::InvalidIndex);
1913
70
      spdlog::error(
1914
70
          "    ExternDesc: referenced type index {} exceeds type index space size {}"sv,
1915
70
          RefIdx, TypeSize);
1916
70
      return Unexpect(ErrCode::Value::InvalidIndex);
1917
70
    }
1918
558
    break;
1919
628
  }
1920
4.01k
  default:
1921
4.01k
    break;
1922
4.76k
  }
1923
1924
4.68k
  switch (Desc.getDescType()) {
1925
109
  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
109
    const auto *CT = CompCtx.getCoreModuleType(Desc.getTypeIndex());
1929
109
    CompCtx.addCoreModule(CT);
1930
109
    break;
1931
0
  }
1932
268
  case AST::Component::ExternDesc::DescType::FuncType:
1933
268
    CompCtx.addFunc();
1934
268
    break;
1935
2.75k
  case AST::Component::ExternDesc::DescType::ValueBound:
1936
2.75k
    CompCtx.addValue();
1937
2.75k
    break;
1938
1.26k
  case AST::Component::ExternDesc::DescType::TypeBound:
1939
1.26k
    if (Desc.isEqType()) {
1940
      // (type (eq i)) — alias type i
1941
45
      uint32_t RefIdx = Desc.getTypeIndex();
1942
45
      if (RefIdx >=
1943
45
          CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type)) {
1944
5
        spdlog::error(ErrCode::Value::InvalidIndex);
1945
5
        spdlog::error("    ExternDesc: eq type bound index {} out of bounds"sv,
1946
5
                      RefIdx);
1947
5
        return Unexpect(ErrCode::Value::InvalidIndex);
1948
5
      }
1949
      // (eq i): inherits the source resource's id; body lives on the
1950
      // shared registry entry.
1951
40
      uint32_t NewIdx = CompCtx.addType(nullptr, /*IsLocal=*/false);
1952
40
      if (const auto *SrcInfo = CompCtx.getResource(RefIdx)) {
1953
4
        CompCtx.addResource(NewIdx, {SrcInfo->Id, /*LocallyDefined=*/false});
1954
4
      }
1955
1.21k
    } else {
1956
      // (sub resource): abstract import — fresh id with no body.
1957
1.21k
      uint32_t NewIdx = CompCtx.addType(nullptr, /*IsLocal=*/false);
1958
1.21k
      CompCtx.addResource(NewIdx, {CompCtx.allocateFreshResourceId(),
1959
1.21k
                                   /*LocallyDefined=*/false});
1960
1.21k
    }
1961
1.25k
    break;
1962
1.25k
  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
123
    const auto *CT = CompCtx.getComponentType(Desc.getTypeIndex());
1966
123
    CompCtx.addComponent(CT);
1967
123
    break;
1968
1.26k
  }
1969
167
  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
167
    const auto *IT = CompCtx.getInstanceType(Desc.getTypeIndex());
1973
167
    uint32_t InstIdx = CompCtx.addInstance(IT);
1974
167
    if (IT != nullptr) {
1975
43
      populateInstanceFromType(InstIdx, *IT);
1976
43
    }
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
167
    break;
1982
1.26k
  }
1983
0
  default:
1984
0
    assumingUnreachable();
1985
4.68k
  }
1986
1987
4.68k
  return {};
1988
4.68k
}
1989
1990
Expect<void>
1991
733
Validator::validate(const AST::Component::CoreImportDesc &Desc) noexcept {
1992
733
  if (Desc.isFunc()) {
1993
35
    uint32_t TypeIdx = Desc.getTypeIndex();
1994
35
    if (TypeIdx >= CompCtx.getCoreSortIndexSize(
1995
35
                       AST::Component::Sort::CoreSortType::Type)) {
1996
25
      spdlog::error(ErrCode::Value::InvalidIndex);
1997
25
      spdlog::error("    CoreImportDesc: func type index {} out of bounds"sv,
1998
25
                    TypeIdx);
1999
25
      return Unexpect(ErrCode::Value::InvalidIndex);
2000
25
    }
2001
10
    CompCtx.addCoreFunc();
2002
698
  } else if (Desc.isTable()) {
2003
135
    CompCtx.addCoreTable();
2004
563
  } else if (Desc.isMemory()) {
2005
237
    CompCtx.addCoreMemory();
2006
326
  } else if (Desc.isGlobal()) {
2007
306
    CompCtx.addCoreGlobal();
2008
306
  } else if (Desc.isTag()) {
2009
20
    CompCtx.addCoreTag();
2010
20
  } else {
2011
0
    assumingUnreachable();
2012
0
  }
2013
708
  return {};
2014
733
}
2015
2016
Expect<void>
2017
198
Validator::validate(const AST::Component::CoreImportDecl &Decl) noexcept {
2018
198
  return validate(Decl.getImportDesc());
2019
198
}
2020
2021
Expect<void>
2022
535
Validator::validate(const AST::Component::CoreExportDecl &Decl) noexcept {
2023
535
  return validate(Decl.getImportDesc());
2024
535
}
2025
2026
Expect<void>
2027
846
Validator::validate(const AST::Component::CoreModuleDecl &Decl) noexcept {
2028
846
  auto ReportError = [](auto E) {
2029
65
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_CoreModule));
2030
65
    return E;
2031
65
  };
2032
2033
846
  if (Decl.isImport()) {
2034
198
    EXPECTED_TRY(validate(Decl.getImport()).map_error(ReportError));
2035
648
  } else if (Decl.isType()) {
2036
58
    EXPECTED_TRY(validate(*Decl.getType()).map_error(ReportError));
2037
590
  } else if (Decl.isAlias()) {
2038
55
    EXPECTED_TRY(validate(Decl.getAlias()).map_error(ReportError));
2039
535
  } else if (Decl.isExport()) {
2040
535
    EXPECTED_TRY(validate(Decl.getExport()).map_error(ReportError));
2041
535
  } else {
2042
0
    assumingUnreachable();
2043
0
  }
2044
781
  return {};
2045
846
}
2046
2047
Expect<void>
2048
26
Validator::validate(const AST::Component::ImportDecl &Decl) noexcept {
2049
26
  auto ReportError = [](auto E) {
2050
15
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Import));
2051
15
    return E;
2052
15
  };
2053
2054
  // Validate the extern descriptor (also increments sort index spaces).
2055
26
  EXPECTED_TRY(validate(Decl.getExternDesc()).map_error(ReportError));
2056
2057
  // Parse and validate the import name.
2058
23
  EXPECTED_TRY(ComponentName CName,
2059
23
               ComponentName::parse(Decl.getName()).map_error(ReportError));
2060
2061
  // Annotated plainnames can only appear on func imports.
2062
23
  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
11
  default:
2075
11
    break;
2076
23
  }
2077
2078
  // Check import name uniqueness.
2079
11
  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
11
  return {};
2087
11
}
2088
2089
Expect<void>
2090
31
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
31
  EXPECTED_TRY(ComponentName CName,
2095
29
               validateExportName(Decl.getName()).map_error([](auto E) {
2096
29
                 spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Export));
2097
29
                 return E;
2098
29
               }));
2099
29
  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
29
  EXPECTED_TRY(validate(Decl.getExternDesc()).map_error([](auto E) {
2117
28
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Export));
2118
28
    return E;
2119
28
  }));
2120
2121
28
  return {};
2122
29
}
2123
2124
Expect<void>
2125
513
Validator::validate(const AST::Component::InstanceDecl &Decl) noexcept {
2126
513
  auto ReportError = [](auto E) {
2127
30
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Instance));
2128
30
    return E;
2129
30
  };
2130
2131
513
  if (Decl.isCoreType()) {
2132
346
    EXPECTED_TRY(validate(*Decl.getCoreType()).map_error(ReportError));
2133
346
  } else if (Decl.isType()) {
2134
133
    EXPECTED_TRY(validate(*Decl.getType()).map_error(ReportError));
2135
133
  } else if (Decl.isAlias()) {
2136
3
    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
31
  } else if (Decl.isExportDecl()) {
2181
31
    EXPECTED_TRY(validate(Decl.getExport()).map_error(ReportError));
2182
31
  } else {
2183
0
    assumingUnreachable();
2184
0
  }
2185
483
  return {};
2186
513
}
2187
2188
Expect<void>
2189
234
Validator::validate(const AST::Component::ComponentDecl &Decl) noexcept {
2190
234
  auto ReportError = [](auto E) {
2191
32
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Component));
2192
32
    return E;
2193
32
  };
2194
2195
234
  if (Decl.isImportDecl()) {
2196
26
    EXPECTED_TRY(validate(Decl.getImport()).map_error(ReportError));
2197
208
  } else if (Decl.isInstanceDecl()) {
2198
208
    EXPECTED_TRY(validate(Decl.getInstance()).map_error(ReportError));
2199
208
  } else {
2200
0
    assumingUnreachable();
2201
0
  }
2202
202
  return {};
2203
234
}
2204
2205
13.2k
Expect<void> Validator::validate(const ComponentValType &VT) noexcept {
2206
13.2k
  if (VT.getCode() == ComponentTypeCode::TypeIndex) {
2207
10.2k
    uint32_t Idx = VT.getTypeIndex();
2208
10.2k
    if (Idx >= CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type)) {
2209
74
      spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds);
2210
74
      spdlog::error("    ComponentValType: type index {} out of bounds"sv, Idx);
2211
74
      return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds);
2212
74
    }
2213
10.1k
    const auto *DT = CompCtx.getDefType(Idx);
2214
10.1k
    if (DT != nullptr && !DT->isDefValType() && !DT->isResourceType()) {
2215
14
      spdlog::error(ErrCode::Value::NotADefinedType);
2216
14
      spdlog::error(
2217
14
          "    ComponentValType: type index {} is not a defined value type"sv,
2218
14
          Idx);
2219
14
      return Unexpect(ErrCode::Value::NotADefinedType);
2220
14
    }
2221
10.1k
  }
2222
13.1k
  return {};
2223
13.2k
}
2224
2225
Expect<void>
2226
30.5k
Validator::validate(const AST::Component::DefValType &DVT) noexcept {
2227
30.5k
  if (DVT.isOwnTy()) {
2228
421
    uint32_t Idx = DVT.getOwn().Idx;
2229
421
    if (Idx >= CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type)) {
2230
20
      spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds);
2231
20
      spdlog::error("    DefValType: own type index {} out of bounds"sv, Idx);
2232
20
      return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds);
2233
20
    }
2234
401
    if (CompCtx.getResource(Idx) == nullptr) {
2235
26
      spdlog::error(ErrCode::Value::NotADefinedType);
2236
26
      spdlog::error(
2237
26
          "    DefValType: own type index {} does not refer to a resource type"sv,
2238
26
          Idx);
2239
26
      return Unexpect(ErrCode::Value::NotADefinedType);
2240
26
    }
2241
30.1k
  } else if (DVT.isBorrowTy()) {
2242
183
    uint32_t Idx = DVT.getBorrow().Idx;
2243
183
    if (Idx >= CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type)) {
2244
19
      spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds);
2245
19
      spdlog::error("    DefValType: borrow type index {} out of bounds"sv,
2246
19
                    Idx);
2247
19
      return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds);
2248
19
    }
2249
164
    if (CompCtx.getResource(Idx) == nullptr) {
2250
14
      spdlog::error(ErrCode::Value::NotADefinedType);
2251
14
      spdlog::error(
2252
14
          "    DefValType: borrow type index {} does not refer to a resource type"sv,
2253
14
          Idx);
2254
14
      return Unexpect(ErrCode::Value::NotADefinedType);
2255
14
    }
2256
29.9k
  } else if (DVT.isRecordTy()) {
2257
450
    const auto &Rec = DVT.getRecord();
2258
450
    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
450
    std::unordered_set<std::string> Seen;
2264
450
    for (const auto &LT : Rec.LabelTypes) {
2265
450
      if (LT.getLabel().empty()) {
2266
1
        spdlog::error(ErrCode::Value::NameCannotBeEmpty);
2267
1
        return Unexpect(ErrCode::Value::NameCannotBeEmpty);
2268
1
      }
2269
449
      if (!isKebabString(LT.getLabel())) {
2270
2
        spdlog::error(ErrCode::Value::ComponentInvalidName);
2271
2
        spdlog::error(
2272
2
            "    DefValType: record field '{}' is not valid kebab-case"sv,
2273
2
            LT.getLabel());
2274
2
        return Unexpect(ErrCode::Value::ComponentInvalidName);
2275
2
      }
2276
447
      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
447
      EXPECTED_TRY(validate(LT.getValType()));
2283
447
    }
2284
29.5k
  } else if (DVT.isVariantTy()) {
2285
147
    const auto &Var = DVT.getVariant();
2286
147
    if (Var.Cases.empty()) {
2287
3
      spdlog::error(ErrCode::Value::VariantMustHaveCase);
2288
3
      return Unexpect(ErrCode::Value::VariantMustHaveCase);
2289
3
    }
2290
144
    std::unordered_set<std::string> Seen;
2291
144
    for (const auto &C : Var.Cases) {
2292
144
      if (C.first.empty()) {
2293
1
        spdlog::error(ErrCode::Value::NameCannotBeEmpty);
2294
1
        return Unexpect(ErrCode::Value::NameCannotBeEmpty);
2295
1
      }
2296
143
      if (!isKebabString(C.first)) {
2297
4
        spdlog::error(ErrCode::Value::ComponentInvalidName);
2298
4
        spdlog::error(
2299
4
            "    DefValType: variant case '{}' is not valid kebab-case"sv,
2300
4
            C.first);
2301
4
        return Unexpect(ErrCode::Value::ComponentInvalidName);
2302
4
      }
2303
139
      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
139
      if (C.second.has_value()) {
2309
90
        EXPECTED_TRY(validate(*C.second));
2310
90
      }
2311
139
    }
2312
29.3k
  } else if (DVT.isTupleTy()) {
2313
212
    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
367
    for (const auto &T : DVT.getTuple().Types) {
2319
367
      EXPECTED_TRY(validate(T));
2320
367
    }
2321
29.1k
  } else if (DVT.isListTy()) {
2322
1.47k
    EXPECTED_TRY(validate(DVT.getList().ValTy));
2323
27.6k
  } else if (DVT.isOptionTy()) {
2324
278
    EXPECTED_TRY(validate(DVT.getOption().ValTy));
2325
27.4k
  } else if (DVT.isResultTy()) {
2326
1.69k
    const auto &R = DVT.getResult();
2327
1.69k
    if (R.ValTy.has_value()) {
2328
500
      EXPECTED_TRY(validate(*R.ValTy));
2329
500
    }
2330
1.69k
    if (R.ErrTy.has_value()) {
2331
1.19k
      EXPECTED_TRY(validate(*R.ErrTy));
2332
1.19k
    }
2333
25.7k
  } else if (DVT.isFlagsTy()) {
2334
95
    const auto &Flags = DVT.getFlags();
2335
95
    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
95
    if (Flags.Labels.size() > 32) {
2341
0
      spdlog::error(ErrCode::Value::CannotHaveMoreThan32Flags);
2342
0
      return Unexpect(ErrCode::Value::CannotHaveMoreThan32Flags);
2343
0
    }
2344
95
    std::unordered_set<std::string> Seen;
2345
102
    for (const auto &L : Flags.Labels) {
2346
102
      if (L.empty()) {
2347
5
        spdlog::error(ErrCode::Value::NameCannotBeEmpty);
2348
5
        return Unexpect(ErrCode::Value::NameCannotBeEmpty);
2349
5
      }
2350
97
      if (!isKebabString(L)) {
2351
9
        spdlog::error(ErrCode::Value::ComponentInvalidName);
2352
9
        spdlog::error(
2353
9
            "    DefValType: flags label '{}' is not valid kebab-case"sv, L);
2354
9
        return Unexpect(ErrCode::Value::ComponentInvalidName);
2355
9
      }
2356
88
      if (!Seen.insert(toLowerStr(L)).second) {
2357
1
        spdlog::error(ErrCode::Value::FlagNameConflicts);
2358
1
        spdlog::error("    DefValType: duplicate flags label '{}'"sv, L);
2359
1
        return Unexpect(ErrCode::Value::FlagNameConflicts);
2360
1
      }
2361
88
    }
2362
25.6k
  } else if (DVT.isEnumTy()) {
2363
179
    const auto &Enm = DVT.getEnum();
2364
179
    if (Enm.Labels.empty()) {
2365
1
      spdlog::error(ErrCode::Value::InvalidTypeReference);
2366
1
      spdlog::error("    DefValType: enum must have at least one label"sv);
2367
1
      return Unexpect(ErrCode::Value::InvalidTypeReference);
2368
1
    }
2369
178
    std::unordered_set<std::string> Seen;
2370
186
    for (const auto &L : Enm.Labels) {
2371
186
      if (L.empty()) {
2372
6
        spdlog::error(ErrCode::Value::NameCannotBeEmpty);
2373
6
        return Unexpect(ErrCode::Value::NameCannotBeEmpty);
2374
6
      }
2375
180
      if (!isKebabString(L)) {
2376
7
        spdlog::error(ErrCode::Value::ComponentInvalidName);
2377
7
        spdlog::error(
2378
7
            "    DefValType: enum label '{}' is not valid kebab-case"sv, L);
2379
7
        return Unexpect(ErrCode::Value::ComponentInvalidName);
2380
7
      }
2381
173
      if (!Seen.insert(toLowerStr(L)).second) {
2382
1
        spdlog::error(ErrCode::Value::EnumTagNameConflicts);
2383
1
        spdlog::error("    DefValType: duplicate enum label '{}'"sv, L);
2384
1
        return Unexpect(ErrCode::Value::EnumTagNameConflicts);
2385
1
      }
2386
173
    }
2387
25.4k
  } else if (DVT.isStreamTy()) {
2388
1.39k
    if (DVT.getStream().ValTy.has_value()) {
2389
341
      EXPECTED_TRY(validate(*DVT.getStream().ValTy));
2390
341
    }
2391
24.0k
  } else if (DVT.isFutureTy()) {
2392
721
    if (DVT.getFuture().ValTy.has_value()) {
2393
91
      EXPECTED_TRY(validate(*DVT.getFuture().ValTy));
2394
91
    }
2395
721
  }
2396
30.3k
  return {};
2397
30.5k
}
2398
2399
6.05k
Expect<void> Validator::validate(const AST::Component::FuncType &FT) noexcept {
2400
  // Validate param names: kebab-case + unique
2401
6.05k
  std::unordered_set<std::string_view> ParamNames;
2402
6.05k
  for (const auto &P : FT.getParamList()) {
2403
2.88k
    if (!P.getLabel().empty()) {
2404
1.44k
      if (!isKebabString(P.getLabel())) {
2405
15
        spdlog::error(ErrCode::Value::ComponentInvalidName);
2406
15
        spdlog::error(
2407
15
            "    FuncType: parameter name '{}' is not valid kebab-case"sv,
2408
15
            P.getLabel());
2409
15
        return Unexpect(ErrCode::Value::ComponentInvalidName);
2410
15
      }
2411
1.42k
      if (!ParamNames.insert(P.getLabel()).second) {
2412
2
        spdlog::error(ErrCode::Value::ComponentDuplicateName);
2413
2
        spdlog::error("    FuncType: duplicate parameter name '{}'"sv,
2414
2
                      P.getLabel());
2415
2
        return Unexpect(ErrCode::Value::ComponentDuplicateName);
2416
2
      }
2417
1.42k
    }
2418
2.86k
    EXPECTED_TRY(validate(P.getValType()));
2419
2.86k
  }
2420
  // Reject transitive use of borrow in results
2421
6.03k
  for (const auto &R : FT.getResultList()) {
2422
5.61k
    EXPECTED_TRY(validate(R.getValType()));
2423
5.59k
    if (containsBorrow(R.getValType())) {
2424
1
      spdlog::error(ErrCode::Value::InvalidTypeReference);
2425
1
      spdlog::error(
2426
1
          "    FuncType: borrow type not allowed in function results"sv);
2427
1
      return Unexpect(ErrCode::Value::InvalidTypeReference);
2428
1
    }
2429
5.59k
  }
2430
6.01k
  return {};
2431
6.03k
}
2432
2433
Expect<void>
2434
178k
Validator::validate(const AST::Component::InstanceType &IT) noexcept {
2435
  // Instance types are validated with an initially-empty index space.
2436
178k
  CompCtx.enterTypeDefinition();
2437
178k
  for (const auto &Decl : IT.getDecl()) {
2438
305
    EXPECTED_TRY(validate(Decl).map_error([](auto E) {
2439
305
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_DefType));
2440
305
      return E;
2441
305
    }));
2442
305
  }
2443
178k
  CompCtx.exitComponent();
2444
178k
  return {};
2445
178k
}
2446
2447
Expect<void>
2448
57.9k
Validator::validate(const AST::Component::ComponentType &CT) noexcept {
2449
  // Component types are validated with an initially-empty index space.
2450
57.9k
  CompCtx.enterTypeDefinition();
2451
57.9k
  for (const auto &Decl : CT.getDecl()) {
2452
234
    EXPECTED_TRY(validate(Decl).map_error([](auto E) {
2453
234
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_DefType));
2454
234
      return E;
2455
234
    }));
2456
234
  }
2457
57.9k
  CompCtx.exitComponent();
2458
57.9k
  return {};
2459
57.9k
}
2460
2461
Expect<void>
2462
4.46k
Validator::validate(const AST::Component::ResourceType &RT) noexcept {
2463
  // Resource types are not allowed inside componenttype/instancetype scopes.
2464
4.46k
  if (CompCtx.isTypeDefinitionScope()) {
2465
2
    spdlog::error(ErrCode::Value::InvalidTypeReference);
2466
2
    spdlog::error("    ResourceType: resource types cannot be defined inside "
2467
2
                  "componenttype or instancetype"sv);
2468
2
    return Unexpect(ErrCode::Value::InvalidTypeReference);
2469
2
  }
2470
4.46k
  if (RT.getDestructor().has_value()) {
2471
5
    uint32_t DtorIdx = *RT.getDestructor();
2472
5
    if (DtorIdx >= CompCtx.getCoreSortIndexSize(
2473
5
                       AST::Component::Sort::CoreSortType::Func)) {
2474
5
      spdlog::error(ErrCode::Value::InvalidIndex);
2475
5
      spdlog::error(
2476
5
          "    ResourceType: destructor core func index {} out of bounds"sv,
2477
5
          DtorIdx);
2478
5
      return Unexpect(ErrCode::Value::InvalidIndex);
2479
5
    }
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
4.45k
  return {};
2503
4.46k
}
2504
2505
9.70k
bool Validator::containsBorrow(const ComponentValType &VT) const noexcept {
2506
9.70k
  if (VT.getCode() == ComponentTypeCode::Borrow) {
2507
0
    return true;
2508
0
  }
2509
9.70k
  if (VT.getCode() != ComponentTypeCode::TypeIndex) {
2510
2.03k
    return false;
2511
2.03k
  }
2512
7.66k
  uint32_t Idx = VT.getTypeIndex();
2513
7.66k
  const auto *DT = CompCtx.getDefType(Idx);
2514
7.66k
  if (DT == nullptr || !DT->isDefValType()) {
2515
1.97k
    return false;
2516
1.97k
  }
2517
5.69k
  return containsBorrow(DT->getDefValType());
2518
7.66k
}
2519
2520
bool Validator::containsBorrow(
2521
5.69k
    const AST::Component::DefValType &DVT) const noexcept {
2522
5.69k
  if (DVT.isBorrowTy()) {
2523
1
    return true;
2524
1
  }
2525
5.69k
  if (DVT.isRecordTy()) {
2526
233
    for (const auto &F : DVT.getRecord().LabelTypes) {
2527
233
      if (containsBorrow(F.getValType())) {
2528
0
        return true;
2529
0
      }
2530
233
    }
2531
233
    return false;
2532
233
  }
2533
5.45k
  if (DVT.isVariantTy()) {
2534
53
    for (const auto &C : DVT.getVariant().Cases) {
2535
53
      if (C.second.has_value() && containsBorrow(*C.second)) {
2536
0
        return true;
2537
0
      }
2538
53
    }
2539
53
    return false;
2540
53
  }
2541
5.40k
  if (DVT.isListTy()) {
2542
849
    return containsBorrow(DVT.getList().ValTy);
2543
849
  }
2544
4.55k
  if (DVT.isTupleTy()) {
2545
316
    for (const auto &T : DVT.getTuple().Types) {
2546
316
      if (containsBorrow(T)) {
2547
0
        return true;
2548
0
      }
2549
316
    }
2550
268
    return false;
2551
268
  }
2552
4.28k
  if (DVT.isOptionTy()) {
2553
18
    return containsBorrow(DVT.getOption().ValTy);
2554
18
  }
2555
4.27k
  if (DVT.isResultTy()) {
2556
2.30k
    const auto &R = DVT.getResult();
2557
2.30k
    return (R.ValTy.has_value() && containsBorrow(*R.ValTy)) ||
2558
2.30k
           (R.ErrTy.has_value() && containsBorrow(*R.ErrTy));
2559
2.30k
  }
2560
1.96k
  if (DVT.isStreamTy()) {
2561
645
    return DVT.getStream().ValTy.has_value() &&
2562
272
           containsBorrow(*DVT.getStream().ValTy);
2563
645
  }
2564
1.31k
  if (DVT.isFutureTy()) {
2565
346
    return DVT.getFuture().ValTy.has_value() &&
2566
82
           containsBorrow(*DVT.getFuture().ValTy);
2567
346
  }
2568
972
  return false; // PrimValType, OwnTy, FlagsTy, EnumTy
2569
1.31k
}
2570
2571
} // namespace Validator
2572
} // namespace WasmEdge