Coverage Report

Created: 2026-08-08 06:32

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
2.51k
std::string toLowerStr(std::string_view SV) {
20
2.51k
  std::string Result(SV);
21
2.51k
  std::transform(
22
2.51k
      Result.begin(), Result.end(), Result.begin(),
23
7.72k
      [](unsigned char C) { return static_cast<char>(std::tolower(C)); });
24
2.51k
  return Result;
25
2.51k
}
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
418
descTypeToSortType(AST::Component::ExternDesc::DescType DT) noexcept {
33
418
  switch (DT) {
34
0
  case AST::Component::ExternDesc::DescType::CoreType:
35
0
    return std::nullopt;
36
4
  case AST::Component::ExternDesc::DescType::FuncType:
37
4
    return AST::Component::Sort::SortType::Func;
38
401
  case AST::Component::ExternDesc::DescType::ValueBound:
39
401
    return AST::Component::Sort::SortType::Value;
40
12
  case AST::Component::ExternDesc::DescType::TypeBound:
41
12
    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
418
  }
49
418
}
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
17
                         AST::Component::ExternDesc::DescType DT) noexcept {
58
17
  auto Mapped = descTypeToSortType(DT);
59
17
  if (S.isCore()) {
60
1
    return !Mapped.has_value() &&
61
0
           S.getCoreSortType() == AST::Component::Sort::CoreSortType::Module;
62
1
  }
63
16
  return Mapped.has_value() && S.getSortType() == *Mapped;
64
17
}
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
1.81k
Expect<ComponentName> validateExportName(std::string_view Name) noexcept {
143
1.81k
  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
3.56k
  EXPECTED_TRY(ComponentName CName, ComponentName::parse(Name));
149
3.56k
  switch (CName.getKind()) {
150
1.01k
  case ComponentNameKind::Label:
151
1.34k
  case ComponentNameKind::Constructor:
152
1.34k
  case ComponentNameKind::Method:
153
1.40k
  case ComponentNameKind::Static:
154
1.75k
  case ComponentNameKind::InterfaceType:
155
1.75k
    return CName;
156
2
  default:
157
2
    spdlog::error(ErrCode::Value::InvalidExportName);
158
2
    spdlog::error("    Export name '{}' kind is not valid for exports"sv, Name);
159
2
    return Unexpect(ErrCode::Value::InvalidExportName);
160
3.56k
  }
161
3.56k
}
162
163
} // namespace
164
165
void Validator::populateInstanceFromType(
166
134
    uint32_t InstIdx, const AST::Component::InstanceType &IT) noexcept {
167
134
  for (const auto &Decl : IT.getDecl()) {
168
13
    if (!Decl.isExportDecl()) {
169
10
      continue;
170
10
    }
171
3
    const auto &Exp = Decl.getExport();
172
3
    const auto &ED = Exp.getExternDesc();
173
3
    auto ST = descTypeToSortType(ED.getDescType());
174
3
    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
3
    const AST::Component::InstanceType *NestedIT = nullptr;
184
3
    if (ED.getDescType() ==
185
3
        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
3
    std::optional<uint64_t> ResourceId;
196
3
    if (ED.getDescType() == AST::Component::ExternDesc::DescType::TypeBound &&
197
0
        !ED.isEqType()) {
198
0
      ResourceId = CompCtx.allocateFreshResourceId();
199
0
    }
200
3
    CompCtx.addInstanceExport(InstIdx, Exp.getName(), *ST, NestedIT,
201
3
                              /*NestedInstIdx=*/std::nullopt, ResourceId);
202
3
  }
203
134
}
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
5.17k
Validator::validate(const AST::Component::Component &Comp) noexcept {
306
5.17k
  spdlog::warn("Component Model Validation is in active development."sv);
307
5.17k
  CompCtx.reset();
308
5.17k
  return validateComponent(Comp).and_then([&]() {
309
2.69k
    const_cast<AST::Component::Component &>(Comp).setIsValidated();
310
2.69k
    return Expect<void>{};
311
2.69k
  });
312
5.17k
}
313
314
Expect<void>
315
10.1k
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
10.1k
  auto ReportError = [](auto E) {
322
2.48k
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Component));
323
2.48k
    return E;
324
2.48k
  };
325
326
10.1k
  CompCtx.enterComponent(&Comp);
327
7.83M
  for (const auto &Sec : Comp.getSections()) {
328
7.83M
    auto Func = [&](auto &&S) -> Expect<void> {
329
7.83M
      using T = std::decay_t<decltype(S)>;
330
7.83M
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
4.27M
      } else {
333
4.27M
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
4.27M
      }
335
4.26M
      return {};
336
7.83M
    };
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
3.56M
    auto Func = [&](auto &&S) -> Expect<void> {
329
3.56M
      using T = std::decay_t<decltype(S)>;
330
3.56M
      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
3.56M
      return {};
336
3.56M
    };
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
1.88k
    auto Func = [&](auto &&S) -> Expect<void> {
329
1.88k
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
1.88k
      } else {
333
1.88k
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
1.88k
      }
335
1.86k
      return {};
336
1.88k
    };
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
31.4k
    auto Func = [&](auto &&S) -> Expect<void> {
329
31.4k
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
31.4k
      } else {
333
31.4k
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
31.4k
      }
335
31.3k
      return {};
336
31.4k
    };
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
706k
    auto Func = [&](auto &&S) -> Expect<void> {
329
706k
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
706k
      } else {
333
706k
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
706k
      }
335
706k
      return {};
336
706k
    };
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.98k
    auto Func = [&](auto &&S) -> Expect<void> {
329
4.98k
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
4.98k
      } else {
333
4.98k
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
4.98k
      }
335
4.96k
      return {};
336
4.98k
    };
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
271k
    auto Func = [&](auto &&S) -> Expect<void> {
329
271k
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
271k
      } else {
333
271k
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
271k
      }
335
271k
      return {};
336
271k
    };
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
14.2k
    auto Func = [&](auto &&S) -> Expect<void> {
329
14.2k
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
14.2k
      } else {
333
14.2k
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
14.2k
      }
335
14.0k
      return {};
336
14.2k
    };
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
3.17M
    auto Func = [&](auto &&S) -> Expect<void> {
329
3.17M
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
3.17M
      } else {
333
3.17M
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
3.17M
      }
335
3.17M
      return {};
336
3.17M
    };
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
38.9k
    auto Func = [&](auto &&S) -> Expect<void> {
329
38.9k
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
38.9k
      } else {
333
38.9k
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
38.9k
      }
335
38.6k
      return {};
336
38.9k
    };
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
1.60k
    auto Func = [&](auto &&S) -> Expect<void> {
329
1.60k
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
1.60k
      } else {
333
1.60k
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
1.60k
      }
335
1.49k
      return {};
336
1.60k
    };
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
23.2k
    auto Func = [&](auto &&S) -> Expect<void> {
329
23.2k
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
23.2k
      } else {
333
23.2k
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
23.2k
      }
335
22.3k
      return {};
336
23.2k
    };
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
2.38k
    auto Func = [&](auto &&S) -> Expect<void> {
329
2.38k
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
2.38k
      } else {
333
2.38k
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
2.38k
      }
335
2.28k
      return {};
336
2.38k
    };
337
7.83M
    EXPECTED_TRY(std::visit(Func, Sec));
338
7.83M
  }
339
7.66k
  CompCtx.exitComponent();
340
7.66k
  return {};
341
10.1k
}
342
343
Expect<void>
344
1.88k
Validator::validate(const AST::Component::CoreModuleSection &ModSec) noexcept {
345
1.88k
  EXPECTED_TRY(validate(ModSec.getContent()).map_error([](auto E) {
346
1.86k
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_CoreMod));
347
1.86k
    return E;
348
1.86k
  }));
349
1.86k
  const_cast<AST::Module &>(ModSec.getContent()).setIsValidated();
350
1.86k
  CompCtx.addCoreModule(ModSec.getContent());
351
1.86k
  return {};
352
1.88k
}
353
354
Expect<void> Validator::validate(
355
31.4k
    const AST::Component::CoreInstanceSection &InstSec) noexcept {
356
31.4k
  for (const auto &Inst : InstSec.getContent()) {
357
31.0k
    EXPECTED_TRY(validate(Inst).map_error([](auto E) {
358
31.0k
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_CoreInstance));
359
31.0k
      return E;
360
31.0k
    }));
361
31.0k
  }
362
31.3k
  return {};
363
31.4k
}
364
365
Expect<void>
366
706k
Validator::validate(const AST::Component::CoreTypeSection &TypeSec) noexcept {
367
706k
  for (const auto &Type : TypeSec.getContent()) {
368
564k
    EXPECTED_TRY(validate(Type).map_error([](auto E) {
369
564k
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_CoreType));
370
564k
      return E;
371
564k
    }));
372
564k
  }
373
706k
  return {};
374
706k
}
375
376
Expect<void>
377
4.98k
Validator::validate(const AST::Component::ComponentSection &CompSec) noexcept {
378
4.98k
  EXPECTED_TRY(validateComponent(CompSec.getContent()).map_error([](auto E) {
379
4.96k
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Component));
380
4.96k
    return E;
381
4.96k
  }));
382
4.96k
  CompCtx.addComponent(CompSec.getContent());
383
4.96k
  return {};
384
4.98k
}
385
386
Expect<void>
387
271k
Validator::validate(const AST::Component::InstanceSection &InstSec) noexcept {
388
271k
  for (const auto &Inst : InstSec.getContent()) {
389
99.5k
    EXPECTED_TRY(validate(Inst).map_error([](auto E) {
390
99.5k
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Instance));
391
99.5k
      return E;
392
99.5k
    }));
393
99.5k
  }
394
271k
  return {};
395
271k
}
396
397
Expect<void>
398
14.2k
Validator::validate(const AST::Component::AliasSection &AliasSec) noexcept {
399
14.2k
  for (const auto &Alias : AliasSec.getContent()) {
400
13.8k
    EXPECTED_TRY(validate(Alias).map_error([](auto E) {
401
13.5k
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Alias));
402
13.5k
      return E;
403
13.5k
    }));
404
13.5k
    const auto &Sort = Alias.getSort();
405
13.5k
    const bool IsOuter =
406
13.5k
        Alias.getTargetType() == AST::Component::Alias::TargetType::Outer;
407
13.5k
    if (Sort.isCore()) {
408
6.36k
      uint32_t NewCoreIdx =
409
6.36k
          CompCtx.incCoreSortIndexSize(Sort.getCoreSortType());
410
      // Carry the outer-aliased module's slot so the alias stays enumerable
411
      // when instantiated.
412
6.36k
      if (IsOuter && Sort.getCoreSortType() ==
413
6.27k
                         AST::Component::Sort::CoreSortType::Module) {
414
0
        CompCtx.carryOuterCoreModule(NewCoreIdx, Alias.getOuter().first,
415
0
                                     Alias.getOuter().second);
416
0
      }
417
7.22k
    } else {
418
7.22k
      uint32_t NewIdx = CompCtx.incSortIndexSize(Sort.getSortType());
419
      // Component analogue of the outer core-module carry above.
420
7.22k
      if (IsOuter &&
421
6.05k
          Sort.getSortType() == AST::Component::Sort::SortType::Component) {
422
1.91k
        CompCtx.carryOuterComponent(NewIdx, Alias.getOuter().first,
423
1.91k
                                    Alias.getOuter().second);
424
1.91k
      }
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
7.22k
      if (IsOuter &&
428
6.05k
          Sort.getSortType() == AST::Component::Sort::SortType::Type) {
429
4.14k
        CompCtx.carryOuterResource(NewIdx, Alias.getOuter().first,
430
4.14k
                                   Alias.getOuter().second);
431
4.14k
      }
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
7.22k
      if (Alias.getTargetType() == AST::Component::Alias::TargetType::Export) {
437
1.17k
        const auto SrcInstIdx = Alias.getExport().first;
438
1.17k
        const auto &SrcName = Alias.getExport().second;
439
1.17k
        const auto &SrcExports = CompCtx.getInstance(SrcInstIdx).Exports;
440
1.17k
        auto It = SrcExports.find(std::string(SrcName));
441
1.17k
        if (It != SrcExports.end()) {
442
1.17k
          if (Sort.getSortType() == AST::Component::Sort::SortType::Instance) {
443
1.17k
            if (It->second.IT != nullptr) {
444
0
              populateInstanceFromType(NewIdx, *It->second.IT);
445
1.17k
            } else if (It->second.NestedInstIdx.has_value()) {
446
1.17k
              const auto &NestedExports =
447
1.17k
                  CompCtx.getInstance(*It->second.NestedInstIdx).Exports;
448
1.17k
              for (const auto &[Name, IE] : NestedExports) {
449
1.17k
                CompCtx.addInstanceExport(NewIdx, Name, IE.ST, IE.IT,
450
1.17k
                                          IE.NestedInstIdx, IE.ResourceId);
451
1.17k
              }
452
1.17k
            }
453
1.17k
          } 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
1.17k
        }
461
1.17k
      }
462
7.22k
    }
463
13.5k
  }
464
14.0k
  return {};
465
14.2k
}
466
467
Expect<void>
468
3.17M
Validator::validate(const AST::Component::TypeSection &TypeSec) noexcept {
469
3.17M
  for (const auto &Type : TypeSec.getContent()) {
470
3.14M
    EXPECTED_TRY(validate(Type).map_error([](auto E) {
471
3.14M
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Type));
472
3.14M
      return E;
473
3.14M
    }));
474
3.14M
  }
475
3.17M
  return {};
476
3.17M
}
477
478
Expect<void>
479
38.9k
Validator::validate(const AST::Component::CanonSection &CanonSec) noexcept {
480
38.9k
  for (const auto &C : CanonSec.getContent()) {
481
11.9k
    EXPECTED_TRY(validate(C).map_error([](auto E) {
482
11.9k
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Canon));
483
11.9k
      return E;
484
11.9k
    }));
485
11.9k
  }
486
38.6k
  return {};
487
38.9k
}
488
489
Expect<void>
490
1.60k
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
1.60k
  const auto &Start = StartSec.getContent();
503
504
  // 1. Function index bounds.
505
1.60k
  const uint32_t FuncIdx = Start.getFunctionIndex();
506
1.60k
  const uint32_t FuncSpaceSize =
507
1.60k
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Func);
508
1.60k
  if (FuncIdx >= FuncSpaceSize) {
509
53
    spdlog::error(ErrCode::Value::InvalidIndex);
510
53
    spdlog::error(
511
53
        "    Start: function index {} exceeds func index space size {}"sv,
512
53
        FuncIdx, FuncSpaceSize);
513
53
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Start));
514
53
    return Unexpect(ErrCode::Value::InvalidIndex);
515
53
  }
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
1.55k
  const AST::Component::FuncType *FT = CompCtx.getFunc(FuncIdx);
521
1.55k
  if (FT != nullptr) {
522
625
    const auto Args = Start.getArguments();
523
625
    const auto &ParamList = FT->getParamList();
524
625
    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
624
    const uint32_t ResultArity =
533
624
        static_cast<uint32_t>(FT->getResultList().size());
534
624
    if (Start.getResult() != ResultArity) {
535
25
      spdlog::error(ErrCode::Value::InvalidIndex);
536
25
      spdlog::error(
537
25
          "    Start: declared result count {} does not match func {} result arity {}"sv,
538
25
          Start.getResult(), FuncIdx, ResultArity);
539
25
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Start));
540
25
      return Unexpect(ErrCode::Value::InvalidIndex);
541
25
    }
542
624
  }
543
544
  // 3. Argument indices must be in value index space bounds. Per-arg
545
  // consume-once tracking is deferred (GAP-EX-4).
546
1.52k
  const uint32_t ValueSpaceSize =
547
1.52k
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Value);
548
1.52k
  for (const uint32_t ArgIdx : Start.getArguments()) {
549
674
    if (ArgIdx >= ValueSpaceSize) {
550
34
      spdlog::error(ErrCode::Value::InvalidIndex);
551
34
      spdlog::error(
552
34
          "    Start: argument value index {} exceeds value index space size {}"sv,
553
34
          ArgIdx, ValueSpaceSize);
554
34
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Start));
555
34
      return Unexpect(ErrCode::Value::InvalidIndex);
556
34
    }
557
674
  }
558
559
  // 4. Append result values to the value index space.
560
1.61G
  for (uint32_t I = 0; I < Start.getResult(); ++I) {
561
1.61G
    CompCtx.addValue();
562
1.61G
  }
563
1.49k
  return {};
564
1.52k
}
565
566
Expect<void>
567
23.2k
Validator::validate(const AST::Component::ImportSection &ImpSec) noexcept {
568
23.2k
  for (const auto &Imp : ImpSec.getContent()) {
569
7.24k
    EXPECTED_TRY(validate(Imp).map_error([](auto E) {
570
7.24k
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Import));
571
7.24k
      return E;
572
7.24k
    }));
573
7.24k
  }
574
22.3k
  return {};
575
23.2k
}
576
577
Expect<void>
578
2.38k
Validator::validate(const AST::Component::ExportSection &ExpSec) noexcept {
579
2.38k
  for (const auto &Exp : ExpSec.getContent()) {
580
915
    EXPECTED_TRY(validate(Exp).map_error([](auto E) {
581
915
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Export));
582
915
      return E;
583
915
    }));
584
915
  }
585
2.28k
  return {};
586
2.38k
}
587
588
Expect<void>
589
31.0k
Validator::validate(const AST::Component::CoreInstance &Inst) noexcept {
590
31.0k
  if (Inst.isInstantiateModule()) {
591
    // Instantiate module case.
592
593
    // Check the module index bound first.
594
241
    const uint32_t ModIdx = Inst.getModuleIndex();
595
241
    if (ModIdx >= CompCtx.getCoreSortIndexSize(
596
241
                      AST::Component::Sort::CoreSortType::Module)) {
597
42
      spdlog::error(ErrCode::Value::InvalidIndex);
598
42
      spdlog::error(
599
42
          "    CoreInstance: Module index {} exceeds available core modules {}"sv,
600
42
          ModIdx,
601
42
          CompCtx.getCoreSortIndexSize(
602
42
              AST::Component::Sort::CoreSortType::Module));
603
42
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance));
604
42
      return Unexpect(ErrCode::Value::InvalidIndex);
605
42
    }
606
    // Reject duplicate argument names on an instantiate expression. The
607
    // spec requires argument names to be strongly-unique per instantiation.
608
199
    {
609
199
      std::unordered_set<std::string_view> SeenArgs;
610
199
      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
199
    }
620
621
    // Imports + exports come from the raw Module (inline) or the
622
    // CoreModuleType (imported / aliased) — GAP-CI-1.
623
199
    const auto &CoreModSlot = CompCtx.getCoreModule(ModIdx);
624
199
    const auto *Mod = CoreModSlot.Body;
625
199
    const auto *ModTy = CoreModSlot.Type;
626
627
    // Required arg module-names (one per distinct CoreImportDecl module).
628
199
    std::vector<std::string_view> RequiredArgNames;
629
199
    if (Mod != nullptr) {
630
199
      for (const auto &Import : Mod->getImportSection().getContent()) {
631
1
        RequiredArgNames.push_back(Import.getModuleName());
632
1
      }
633
199
    } 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
199
    auto Args = Inst.getInstantiateArgs();
642
199
    for (const auto ImportName : RequiredArgNames) {
643
1
      const auto ArgIt =
644
1
          std::find_if(Args.begin(), Args.end(), [&](const auto &Arg) {
645
0
            return Arg.getName() == ImportName;
646
0
          });
647
1
      if (ArgIt == Args.end()) {
648
1
        spdlog::error(ErrCode::Value::MissingArgument);
649
1
        spdlog::error(
650
1
            "    CoreInstance: Module index {} missing argument for import '{}'"sv,
651
1
            Inst.getModuleIndex(), ImportName);
652
1
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance));
653
1
        return Unexpect(ErrCode::Value::MissingArgument);
654
1
      }
655
1
    }
656
657
    // Allocate the core:instance and bind exports to it.
658
198
    uint32_t InstanceIdx = CompCtx.addCoreInstance();
659
198
    if (Mod != nullptr) {
660
198
      for (const auto &ExportDesc : Mod->getExportSection().getContent()) {
661
0
        CompCtx.addCoreInstanceExport(InstanceIdx, ExportDesc.getExternalName(),
662
0
                                      ExportDesc.getExternalType());
663
0
      }
664
198
    } 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
30.8k
  } else if (Inst.isInlineExport()) {
689
    // Inline export case.
690
    // Allocate the core instance first, then register each inline export.
691
30.8k
    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
30.8k
    std::unordered_set<std::string_view> SeenExports;
696
30.8k
    for (const auto &Export : Inst.getInlineExports()) {
697
4.34k
      if (!SeenExports.insert(Export.getName()).second) {
698
3
        spdlog::error(ErrCode::Value::ComponentDuplicateName);
699
3
        spdlog::error("    CoreInstance: Duplicate inline-export name '{}'"sv,
700
3
                      Export.getName());
701
3
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance));
702
3
        return Unexpect(ErrCode::Value::ComponentDuplicateName);
703
3
      }
704
4.34k
      const auto &Sort = Export.getSortIdx().getSort();
705
4.34k
      uint32_t Idx = Export.getSortIdx().getIdx();
706
4.34k
      assuming(Sort.isCore());
707
4.34k
      if (Idx >= CompCtx.getCoreSortIndexSize(Sort.getCoreSortType())) {
708
        // The error message differs of the tag core sort.
709
49
        ErrCode::Value ErrValue = ErrCode::Value::InvalidIndex;
710
49
        if (Sort.getCoreSortType() == AST::Component::Sort::CoreSortType::Tag) {
711
1
          ErrValue = ErrCode::Value::UnknownCoreTag;
712
1
        }
713
49
        spdlog::error(ErrValue);
714
49
        spdlog::error(
715
49
            "    CoreInstance: Inline export '{}' refers to invalid index {}"sv,
716
49
            Export.getName(), Idx);
717
49
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance));
718
49
        return Unexpect(ErrValue);
719
49
      }
720
      // Map CoreSortType to ExternalType for the instance export map.
721
4.29k
      ExternalType ET;
722
4.29k
      switch (Sort.getCoreSortType()) {
723
4.29k
      case AST::Component::Sort::CoreSortType::Func:
724
4.29k
        ET = ExternalType::Function;
725
4.29k
        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
3
      default:
739
3
        spdlog::error(ErrCode::Value::InvalidIndex);
740
3
        spdlog::error(
741
3
            "    CoreInstance: Inline export '{}' has unsupported core sort"sv,
742
3
            Export.getName());
743
3
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance));
744
3
        return Unexpect(ErrCode::Value::InvalidIndex);
745
4.29k
      }
746
4.29k
      CompCtx.addCoreInstanceExport(InstanceIdx, Export.getName(), ET);
747
4.29k
    }
748
30.8k
  } else {
749
0
    assumingUnreachable();
750
0
  }
751
30.9k
  return {};
752
31.0k
}
753
754
Expect<void>
755
99.5k
Validator::validate(const AST::Component::Instance &Inst) noexcept {
756
99.5k
  if (Inst.isInstantiateModule()) {
757
    // Instantiate module case.
758
759
    // Check the component index bound first.
760
5.84k
    const uint32_t CompIdx = Inst.getComponentIndex();
761
5.84k
    if (CompIdx >=
762
5.84k
        CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Component)) {
763
99
      spdlog::error(ErrCode::Value::InvalidIndex);
764
99
      spdlog::error(
765
99
          "    Instance: Component index {} exceeds available components {}"sv,
766
99
          CompIdx,
767
99
          CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Component));
768
99
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
769
99
      return Unexpect(ErrCode::Value::InvalidIndex);
770
99
    }
771
    // Reject duplicate argument names on an instantiate expression. The
772
    // spec requires argument names to be strongly-unique per instantiation.
773
5.74k
    {
774
5.74k
      std::unordered_set<std::string_view> SeenArgs;
775
5.74k
      for (const auto &Arg : Inst.getInstantiateArgs()) {
776
4.99k
        if (!SeenArgs.insert(Arg.getName()).second) {
777
32
          spdlog::error(ErrCode::Value::ComponentDuplicateName);
778
32
          spdlog::error("    Instance: Duplicate argument name '{}'"sv,
779
32
                        Arg.getName());
780
32
          spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
781
32
          return Unexpect(ErrCode::Value::ComponentDuplicateName);
782
32
        }
783
4.99k
      }
784
5.74k
    }
785
786
    // Source: raw Component (inline) or ComponentType (imported / aliased).
787
5.71k
    const auto &CompSlot = CompCtx.getComponent(CompIdx);
788
5.71k
    const auto *Comp = CompSlot.Body;
789
5.71k
    const auto *CompTy = CompSlot.Type;
790
791
    // Verify each component import is satisfied by some instantiate arg.
792
5.71k
    auto Args = Inst.getInstantiateArgs();
793
5.71k
    auto checkImport =
794
5.71k
        [&](std::string_view ImportName,
795
5.71k
            const AST::Component::ExternDesc &ImportDesc) -> Expect<void> {
796
30
      const auto ArgIt =
797
100
          std::find_if(Args.begin(), Args.end(), [&](const auto &Arg) {
798
100
            return Arg.getName() == ImportName;
799
100
          });
800
30
      if (ArgIt == Args.end()) {
801
14
        spdlog::error(ErrCode::Value::MissingArgument);
802
14
        spdlog::error(
803
14
            "    Instance: Component index {} missing argument for import '{}'"sv,
804
14
            Inst.getComponentIndex(), ImportName);
805
14
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
806
14
        return Unexpect(ErrCode::Value::MissingArgument);
807
14
      }
808
16
      const auto &Sort = ArgIt->getIndex().getSort();
809
16
      const uint32_t Idx = ArgIt->getIndex().getIdx();
810
      // Only `core module` is admissible as a core-side import externdesc.
811
16
      if (Sort.isCore() && Sort.getCoreSortType() !=
812
5
                               AST::Component::Sort::CoreSortType::Module) {
813
4
        spdlog::error(ErrCode::Value::ArgTypeMismatch);
814
4
        spdlog::error("    Instance: Argument '{}' uses a core sort other than "
815
4
                      "`core module`, which no import externdesc can accept"sv,
816
4
                      ImportName);
817
4
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
818
4
        return Unexpect(ErrCode::Value::ArgTypeMismatch);
819
4
      }
820
12
      if (!sortMatchesDescType(Sort, ImportDesc.getDescType())) {
821
3
        spdlog::error(ErrCode::Value::ArgTypeMismatch);
822
3
        spdlog::error("    Instance: Argument '{}' sort mismatch for import"sv,
823
3
                      ImportName);
824
3
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
825
3
        return Unexpect(ErrCode::Value::ArgTypeMismatch);
826
3
      }
827
9
      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
9
      if (Idx >= CompCtx.getSortIndexSize(Sort.getSortType())) {
839
3
        spdlog::error(ErrCode::Value::InvalidIndex);
840
3
        spdlog::error(
841
3
            "    Instance: Argument '{}' refers to invalid index {}"sv,
842
3
            ImportName, Idx);
843
3
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
844
3
        return Unexpect(ErrCode::Value::InvalidIndex);
845
3
      }
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
6
      if (Comp != nullptr &&
850
6
          ImportDesc.getDescType() ==
851
6
              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
6
      return {};
867
6
    };
868
5.71k
    if (Comp != nullptr) {
869
9.43k
      for (const auto &Sec : Comp->getSections()) {
870
9.43k
        if (const auto *IS = std::get_if<AST::Component::ImportSection>(&Sec)) {
871
691
          for (const auto &Imp : IS->getContent()) {
872
29
            EXPECTED_TRY(checkImport(Imp.getName(), Imp.getDesc()));
873
29
          }
874
691
        }
875
9.43k
      }
876
3.28k
    } else if (CompTy != nullptr) {
877
884
      for (const auto &CD : CompTy->getDecl()) {
878
399
        if (CD.isImportDecl()) {
879
1
          const auto &ID = CD.getImport();
880
1
          EXPECTED_TRY(checkImport(ID.getName(), ID.getExternDesc()));
881
1
        }
882
399
      }
883
884
    }
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
5.68k
    uint32_t InstanceIdx = CompCtx.addInstance();
890
5.68k
    if (Comp != nullptr) {
891
9.32k
      for (const auto &Sec : Comp->getSections()) {
892
9.32k
        const auto *ES = std::get_if<AST::Component::ExportSection>(&Sec);
893
9.32k
        if (ES == nullptr) {
894
7.39k
          continue;
895
7.39k
        }
896
1.92k
        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
1.92k
      }
913
3.28k
    } else if (CompTy != nullptr) {
914
883
      for (const auto &CD : CompTy->getDecl()) {
915
398
        if (!CD.isInstanceDecl()) {
916
0
          continue;
917
0
        }
918
398
        const auto &ID = CD.getInstance();
919
398
        if (!ID.isExportDecl()) {
920
0
          continue;
921
0
        }
922
398
        const auto &ED = ID.getExport();
923
398
        const auto OptST = descTypeToSortType(ED.getExternDesc().getDescType());
924
398
        if (!OptST.has_value()) {
925
0
          continue; // `(core module)` export — not a component-side entry.
926
0
        }
927
398
        CompCtx.addInstanceExport(InstanceIdx, ED.getName(), *OptST);
928
398
      }
929
883
    }
930
93.6k
  } else if (Inst.isInlineExport()) {
931
    // Allocate the instance first so exports can be registered on it.
932
93.6k
    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
93.6k
    std::unordered_set<std::string_view> SeenExports;
939
93.6k
    for (const auto &Export : Inst.getInlineExports()) {
940
1.78k
      if (!SeenExports.insert(Export.getName()).second) {
941
6
        spdlog::error(ErrCode::Value::ComponentDuplicateName);
942
6
        spdlog::error("    Instance: Duplicate inline-export name '{}'"sv,
943
6
                      Export.getName());
944
6
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
945
6
        return Unexpect(ErrCode::Value::ComponentDuplicateName);
946
6
      }
947
1.77k
      const auto &Sort = Export.getSortIdx().getSort();
948
1.77k
      uint32_t Idx = Export.getSortIdx().getIdx();
949
1.77k
      if (Sort.isCore()) {
950
223
        if (Idx >= CompCtx.getCoreSortIndexSize(Sort.getCoreSortType())) {
951
28
          spdlog::error(ErrCode::Value::InvalidIndex);
952
28
          spdlog::error(
953
28
              "    Instance: Inline export '{}' refers to invalid index {}"sv,
954
28
              Export.getName(), Idx);
955
28
          spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
956
28
          return Unexpect(ErrCode::Value::InvalidIndex);
957
28
        }
958
195
        continue;
959
223
      }
960
1.55k
      if (Idx >= CompCtx.getSortIndexSize(Sort.getSortType())) {
961
37
        spdlog::error(ErrCode::Value::InvalidIndex);
962
37
        spdlog::error(
963
37
            "    Instance: Inline export '{}' refers to invalid index {}"sv,
964
37
            Export.getName(), Idx);
965
37
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
966
37
        return Unexpect(ErrCode::Value::InvalidIndex);
967
37
      }
968
1.51k
      if (Sort.getSortType() == AST::Component::Sort::SortType::Type) {
969
608
        auto SubstitutedIdx =
970
608
            CompCtx.getSubstitutedType(std::string(Export.getName()));
971
608
        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
608
      }
979
1.51k
      std::optional<uint32_t> NestedIdx;
980
1.51k
      const AST::Component::InstanceType *PropagatedIT = nullptr;
981
1.51k
      if (Sort.getSortType() == AST::Component::Sort::SortType::Instance) {
982
845
        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
845
        PropagatedIT = CompCtx.getInstance(Idx).Type;
988
845
      }
989
1.51k
      CompCtx.addInstanceExport(InstanceIdx, Export.getName(),
990
1.51k
                                Sort.getSortType(), PropagatedIT, NestedIdx);
991
1.51k
    }
992
93.6k
  } else {
993
0
    assumingUnreachable();
994
0
  }
995
99.2k
  return {};
996
99.5k
}
997
998
101
Expect<void> Validator::validate(const AST::Component::CoreAlias &A) noexcept {
999
  // CoreAlias is always an outer alias.
1000
101
  uint32_t Ct = A.getComponentJump();
1001
101
  uint32_t Idx = A.getIndex();
1002
1003
101
  uint32_t OutLinkCompCnt = 0;
1004
101
  const auto *TargetCtx = &CompCtx.getCurrentContext();
1005
154
  while (Ct > OutLinkCompCnt && TargetCtx != nullptr) {
1006
53
    TargetCtx = TargetCtx->Parent;
1007
53
    OutLinkCompCnt++;
1008
53
  }
1009
101
  if (TargetCtx == nullptr) {
1010
11
    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
11
    spdlog::error(
1014
11
        "    CoreAlias: outer count {} exceeds enclosing component count {}"sv,
1015
11
        Ct, OutLinkCompCnt - 1);
1016
11
    return Unexpect(ErrCode::Value::InvalidIndex);
1017
11
  }
1018
1019
90
  const auto &Sort = A.getSort();
1020
90
  if (Sort.isCore()) {
1021
90
    if (Idx >= TargetCtx->getCoreSortIndexSize(Sort.getCoreSortType())) {
1022
15
      spdlog::error(ErrCode::Value::InvalidIndex);
1023
15
      spdlog::error("    CoreAlias: outer index {} out of bounds"sv, Idx);
1024
15
      return Unexpect(ErrCode::Value::InvalidIndex);
1025
15
    }
1026
75
    CompCtx.incCoreSortIndexSize(Sort.getCoreSortType());
1027
75
  }
1028
75
  return {};
1029
90
}
1030
1031
14.0k
Expect<void> Validator::validate(const AST::Component::Alias &Alias) noexcept {
1032
14.0k
  const auto &Sort = Alias.getSort();
1033
14.0k
  switch (Alias.getTargetType()) {
1034
1.24k
  case AST::Component::Alias::TargetType::Export: {
1035
1.24k
    const auto Idx = Alias.getExport().first;
1036
1.24k
    const auto &Name = Alias.getExport().second;
1037
1038
1.24k
    if (Sort.isCore()) {
1039
2
      spdlog::error(ErrCode::Value::InvalidTypeReference);
1040
2
      spdlog::error("    Alias export: Mapping an export '{}' to core:sort"sv,
1041
2
                    Name);
1042
2
      return Unexpect(ErrCode::Value::InvalidTypeReference);
1043
2
    }
1044
1045
1.24k
    if (Idx >=
1046
1.24k
        CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Instance)) {
1047
49
      spdlog::error(ErrCode::Value::InvalidIndex);
1048
49
      spdlog::error(
1049
49
          "    Alias export: Export index {} exceeds available component instance index {}"sv,
1050
49
          Idx,
1051
49
          CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Instance));
1052
49
      return Unexpect(ErrCode::Value::InvalidIndex);
1053
49
    }
1054
1055
1.19k
    const auto &InstExports = CompCtx.getInstance(Idx).Exports;
1056
1.19k
    auto It = InstExports.find(std::string(Name));
1057
1.19k
    if (It == InstExports.cend()) {
1058
22
      spdlog::error(ErrCode::Value::ExportNotFound);
1059
22
      spdlog::error(
1060
22
          "    Alias export: No matching export '{}' found in component instance index {}"sv,
1061
22
          Name, Idx);
1062
22
      return Unexpect(ErrCode::Value::ExportNotFound);
1063
22
    }
1064
1065
1.17k
    if (It->second.ST != Sort.getSortType()) {
1066
3
      spdlog::error(ErrCode::Value::InvalidTypeReference);
1067
3
      spdlog::error("    Alias export: Type mapping mismatch for export '{}'"sv,
1068
3
                    Name);
1069
3
      return Unexpect(ErrCode::Value::InvalidTypeReference);
1070
3
    }
1071
1.17k
    return {};
1072
1.17k
  }
1073
150
  case AST::Component::Alias::TargetType::CoreExport: {
1074
150
    const auto Idx = Alias.getExport().first;
1075
150
    const auto &Name = Alias.getExport().second;
1076
1077
150
    if (!Sort.isCore()) {
1078
4
      spdlog::error(ErrCode::Value::InvalidTypeReference);
1079
4
      spdlog::error("    Alias core:export: Mapping a export '{}' to sort"sv,
1080
4
                    Name);
1081
4
      return Unexpect(ErrCode::Value::InvalidTypeReference);
1082
4
    }
1083
1084
146
    if (Idx >= CompCtx.getCoreSortIndexSize(
1085
146
                   AST::Component::Sort::CoreSortType::Instance)) {
1086
42
      spdlog::error(ErrCode::Value::InvalidIndex);
1087
42
      spdlog::error(
1088
42
          "    Alias core:export: Export index {} exceeds available core instance index {}"sv,
1089
42
          Idx,
1090
42
          CompCtx.getCoreSortIndexSize(
1091
42
              AST::Component::Sort::CoreSortType::Instance) -
1092
42
              1);
1093
42
      return Unexpect(ErrCode::Value::InvalidIndex);
1094
42
    }
1095
1096
104
    const auto &CoreExports = CompCtx.getCoreInstance(Idx);
1097
104
    auto It = CoreExports.find(std::string(Name));
1098
104
    if (It == CoreExports.end()) {
1099
11
      spdlog::error(ErrCode::Value::ExportNotFound);
1100
11
      spdlog::error(
1101
11
          "    Alias core:export: No matching export '{}' found in core instance index {}"sv,
1102
11
          Name, Idx);
1103
11
      return Unexpect(ErrCode::Value::ExportNotFound);
1104
11
    }
1105
1106
93
    const auto ExternTy = It->second;
1107
93
    AST::Component::Sort::CoreSortType ST;
1108
93
    switch (ExternTy) {
1109
93
    case ExternalType::Function:
1110
93
      ST = AST::Component::Sort::CoreSortType::Func;
1111
93
      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
93
    }
1131
93
    if (ST != Sort.getCoreSortType()) {
1132
      // The error message differs of the tag core sort.
1133
2
      ErrCode::Value ErrValue = ErrCode::Value::InvalidIndex;
1134
2
      if (Sort.getCoreSortType() == AST::Component::Sort::CoreSortType::Tag) {
1135
1
        ErrValue = ErrCode::Value::UnknownCoreTag;
1136
1
      }
1137
2
      spdlog::error(ErrValue);
1138
2
      spdlog::error(
1139
2
          "    Alias core:export: Type mapping mismatch for export '{}'"sv,
1140
2
          Name);
1141
2
      return Unexpect(ErrValue);
1142
2
    }
1143
91
    return {};
1144
93
  }
1145
12.6k
  case AST::Component::Alias::TargetType::Outer: {
1146
12.6k
    const auto Ct = Alias.getOuter().first;
1147
12.6k
    const auto Idx = Alias.getOuter().second;
1148
1149
12.6k
    uint32_t OutLinkCompCnt = 0;
1150
12.6k
    const auto *TargetCtx = &CompCtx.getCurrentContext();
1151
13.9k
    while (Ct > OutLinkCompCnt && TargetCtx != nullptr) {
1152
1.28k
      TargetCtx = TargetCtx->Parent;
1153
1.28k
      OutLinkCompCnt++;
1154
1.28k
    }
1155
12.6k
    if (TargetCtx == nullptr) {
1156
87
      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
87
      spdlog::error(
1161
87
          "    Alias outer: Component out-link count {} is exceeding the enclosing component count {}"sv,
1162
87
          Ct, OutLinkCompCnt - 1);
1163
87
      return Unexpect(ErrCode::Value::InvalidIndex);
1164
87
    }
1165
1166
12.5k
    if (Sort.isCore()) {
1167
6.29k
      if (Sort.getCoreSortType() !=
1168
6.29k
              AST::Component::Sort::CoreSortType::Module &&
1169
6.28k
          Sort.getCoreSortType() != AST::Component::Sort::CoreSortType::Type) {
1170
2
        spdlog::error(ErrCode::Value::InvalidTypeReference);
1171
2
        spdlog::error(
1172
2
            "    Alias outer: Invalid core:sort for outer alias. Only type, module, or component are allowed."sv);
1173
2
        return Unexpect(ErrCode::Value::InvalidTypeReference);
1174
2
      }
1175
6.29k
      if (Idx >= TargetCtx->getCoreSortIndexSize(Sort.getCoreSortType())) {
1176
14
        spdlog::error(ErrCode::Value::InvalidIndex);
1177
14
        spdlog::error(
1178
14
            "    Alias outer: core:sort index {} invalid in component context"sv,
1179
14
            Idx);
1180
14
        return Unexpect(ErrCode::Value::InvalidIndex);
1181
14
      }
1182
6.29k
    } else {
1183
6.25k
      if (Sort.getSortType() != AST::Component::Sort::SortType::Type &&
1184
1.91k
          Sort.getSortType() != AST::Component::Sort::SortType::Component) {
1185
1
        spdlog::error(ErrCode::Value::InvalidTypeReference);
1186
1
        spdlog::error(
1187
1
            "    Alias outer: Invalid sort for outer alias. Only type, module, or component are allowed."sv);
1188
1
        return Unexpect(ErrCode::Value::InvalidTypeReference);
1189
1
      }
1190
6.25k
      if (Idx >= TargetCtx->getSortIndexSize(Sort.getSortType())) {
1191
6
        spdlog::error(ErrCode::Value::InvalidIndex);
1192
6
        spdlog::error(
1193
6
            "    Alias outer: sort index {} invalid in component context"sv,
1194
6
            Idx);
1195
6
        return Unexpect(ErrCode::Value::InvalidIndex);
1196
6
      }
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
6.25k
    }
1209
12.5k
    return {};
1210
12.5k
  }
1211
0
  default:
1212
0
    assumingUnreachable();
1213
14.0k
  }
1214
14.0k
}
1215
1216
Expect<void>
1217
565k
Validator::validate(const AST::Component::CoreDefType &DType) noexcept {
1218
565k
  if (DType.isRecType()) {
1219
    // Each sub-type in the rec group gets its own entry in core:type.
1220
302k
    for (const auto &ST : DType.getSubTypes()) {
1221
302k
      CompCtx.addCoreType(&ST);
1222
302k
    }
1223
301k
  } else if (DType.isModuleType()) {
1224
    // Module types are validated with an initially-empty type index space.
1225
263k
    CompCtx.enterTypeDefinition();
1226
263k
    for (const auto &Decl : DType.getModuleType()) {
1227
2.31k
      EXPECTED_TRY(validate(Decl).map_error([](auto E) {
1228
2.31k
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreDefType));
1229
2.31k
        return E;
1230
2.31k
      }));
1231
2.31k
    }
1232
263k
    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
263k
    uint32_t NewTypeIdx = CompCtx.addCoreType();
1236
263k
    CompCtx.setCoreModuleType(NewTypeIdx, &DType);
1237
263k
  } else {
1238
0
    assumingUnreachable();
1239
0
  }
1240
564k
  return {};
1241
565k
}
1242
1243
Expect<void>
1244
3.14M
Validator::validate(const AST::Component::DefType &DType) noexcept {
1245
3.14M
  auto ReportError = [](auto E) {
1246
412
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_DefType));
1247
412
    return E;
1248
412
  };
1249
1250
3.14M
  if (DType.isDefValType()) {
1251
106k
    EXPECTED_TRY(validate(DType.getDefValType()).map_error(ReportError));
1252
3.04M
  } else if (DType.isFuncType()) {
1253
10.5k
    EXPECTED_TRY(validate(DType.getFuncType()).map_error(ReportError));
1254
3.03M
  } else if (DType.isComponentType()) {
1255
1.94M
    EXPECTED_TRY(validate(DType.getComponentType()).map_error(ReportError));
1256
1.94M
  } else if (DType.isInstanceType()) {
1257
1.05M
    EXPECTED_TRY(validate(DType.getInstanceType()).map_error(ReportError));
1258
1.05M
  } else if (DType.isResourceType()) {
1259
24.4k
    EXPECTED_TRY(validate(DType.getResourceType()).map_error(ReportError));
1260
24.4k
  } else {
1261
0
    assumingUnreachable();
1262
0
  }
1263
  // addType records body/id/locality for resource DefTypes in one step.
1264
3.14M
  CompCtx.addType(&DType);
1265
3.14M
  return {};
1266
3.14M
}
1267
1268
Expect<void>
1269
11.9k
Validator::validate(const AST::Component::Canonical &Canon) noexcept {
1270
11.9k
  switch (Canon.getOpCode()) {
1271
6.31k
  case ComponentCanonOpCode::Lift:
1272
6.31k
    return validateCanonLift(Canon);
1273
1.21k
  case ComponentCanonOpCode::Lower:
1274
1.21k
    return validateCanonLower(Canon);
1275
1.28k
  case ComponentCanonOpCode::Resource__new:
1276
1.28k
    return validateCanonResourceNew(Canon);
1277
1.56k
  case ComponentCanonOpCode::Resource__rep:
1278
1.56k
    return validateCanonResourceRep(Canon);
1279
920
  case ComponentCanonOpCode::Resource__drop:
1280
1.55k
  case ComponentCanonOpCode::Resource__drop_async:
1281
1.55k
    return validateCanonResourceDrop(Canon);
1282
12
  default:
1283
12
    spdlog::error(ErrCode::Value::ComponentNotImplValidator);
1284
12
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1285
12
    return Unexpect(ErrCode::Value::ComponentNotImplValidator);
1286
11.9k
  }
1287
11.9k
}
1288
1289
Expect<void> Validator::validateCanonOptions(
1290
    ComponentCanonOpCode Code,
1291
11.6k
    Span<const AST::Component::CanonOpt> Opts) noexcept {
1292
11.6k
  using OptCode = ComponentCanonOptCode;
1293
11.6k
  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
11.6k
  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
11.6k
  bool HasEncoding = false;
1306
11.6k
  bool HasMemory = false;
1307
11.6k
  bool HasRealloc = false;
1308
11.6k
  bool HasPostReturn = false;
1309
11.6k
  bool HasAsync = false;
1310
11.6k
  bool HasCallback = false;
1311
11.6k
  bool HasAlwaysTaskReturn = false;
1312
11.6k
  uint32_t ReallocIdx = 0;
1313
11.6k
  uint32_t CallbackIdx = 0;
1314
11.6k
  uint32_t PostReturnIdx = 0;
1315
11.6k
  uint32_t MemoryIdx = 0;
1316
1317
11.6k
  auto RejectDup = [&](const char *Name) -> Expect<void> {
1318
9
    spdlog::error(ErrCode::Value::InvalidCanonOption);
1319
9
    spdlog::error("    canonical option '{}' appears more than once"sv, Name);
1320
9
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1321
9
    return Unexpect(ErrCode::Value::InvalidCanonOption);
1322
9
  };
1323
11.6k
  auto RejectSite = [&](const char *Name) -> Expect<void> {
1324
3
    spdlog::error(ErrCode::Value::InvalidCanonOption);
1325
3
    spdlog::error(
1326
3
        "    canonical option '{}' is not allowed in this canon built-in"sv,
1327
3
        Name);
1328
3
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1329
3
    return Unexpect(ErrCode::Value::InvalidCanonOption);
1330
3
  };
1331
1332
11.6k
  for (const auto &Opt : Opts) {
1333
1.23k
    switch (Opt.getCode()) {
1334
608
    case OptCode::Encode_UTF8:
1335
1.07k
    case OptCode::Encode_UTF16:
1336
1.13k
    case OptCode::Encode_Latin1:
1337
1.13k
      if (HasEncoding) {
1338
5
        return RejectDup("string-encoding");
1339
5
      }
1340
1.13k
      HasEncoding = true;
1341
1.13k
      break;
1342
14
    case OptCode::Memory:
1343
14
      if (HasMemory) {
1344
1
        return RejectDup("memory");
1345
1
      }
1346
13
      HasMemory = true;
1347
13
      MemoryIdx = Opt.getIndex();
1348
13
      break;
1349
4
    case OptCode::Realloc:
1350
4
      if (HasRealloc) {
1351
1
        return RejectDup("realloc");
1352
1
      }
1353
3
      HasRealloc = true;
1354
3
      ReallocIdx = Opt.getIndex();
1355
3
      break;
1356
2
    case OptCode::PostReturn:
1357
2
      if (Code != CanonOp::Lift) {
1358
1
        return RejectSite("post-return");
1359
1
      }
1360
1
      if (HasPostReturn) {
1361
0
        return RejectDup("post-return");
1362
0
      }
1363
1
      HasPostReturn = true;
1364
1
      PostReturnIdx = Opt.getIndex();
1365
1
      break;
1366
71
    case OptCode::Async:
1367
71
      if (HasAsync) {
1368
1
        return RejectDup("async");
1369
1
      }
1370
70
      HasAsync = true;
1371
70
      break;
1372
2
    case OptCode::Callback:
1373
2
      if (Code != CanonOp::Lift) {
1374
1
        return RejectSite("callback");
1375
1
      }
1376
1
      if (HasCallback) {
1377
0
        return RejectDup("callback");
1378
0
      }
1379
1
      HasCallback = true;
1380
1
      CallbackIdx = Opt.getIndex();
1381
1
      break;
1382
5
    case OptCode::AlwaysTaskReturn:
1383
5
      if (Code != CanonOp::Lift) {
1384
1
        return RejectSite("always-task-return");
1385
1
      }
1386
4
      if (HasAlwaysTaskReturn) {
1387
1
        return RejectDup("always-task-return");
1388
1
      }
1389
3
      HasAlwaysTaskReturn = true;
1390
3
      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.23k
    }
1398
1.23k
  }
1399
1400
  // Structural rules.
1401
11.6k
  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
11.6k
  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
11.6k
  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
11.6k
  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
11.6k
  if (HasMemory &&
1432
11
      MemoryIdx >= CompCtx.getCoreSortIndexSize(
1433
11
                       AST::Component::Sort::CoreSortType::Memory)) {
1434
11
    spdlog::error(ErrCode::Value::InvalidIndex);
1435
11
    spdlog::error(
1436
11
        "    canonical option 'memory': core memory index {} out of bounds"sv,
1437
11
        MemoryIdx);
1438
11
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1439
11
    return Unexpect(ErrCode::Value::InvalidIndex);
1440
11
  }
1441
11.6k
  const uint32_t CoreFuncSpaceSize =
1442
11.6k
      CompCtx.getCoreSortIndexSize(AST::Component::Sort::CoreSortType::Func);
1443
11.6k
  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
11.6k
  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
11.6k
  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
11.6k
  return {};
1468
11.6k
}
1469
1470
Expect<void>
1471
6.31k
Validator::validateCanonLift(const AST::Component::Canonical &Canon) noexcept {
1472
6.31k
  const uint32_t CoreFuncIdx = Canon.getIndex();
1473
6.31k
  const uint32_t CoreFuncSpaceSize =
1474
6.31k
      CompCtx.getCoreSortIndexSize(AST::Component::Sort::CoreSortType::Func);
1475
  // 1. Core func index bounds.
1476
6.31k
  if (CoreFuncIdx >= CoreFuncSpaceSize) {
1477
43
    spdlog::error(ErrCode::Value::InvalidIndex);
1478
43
    spdlog::error(
1479
43
        "    canon lift: core func index {} exceeds core func index space size {}"sv,
1480
43
        CoreFuncIdx, CoreFuncSpaceSize);
1481
43
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1482
43
    return Unexpect(ErrCode::Value::InvalidIndex);
1483
43
  }
1484
6.27k
  const uint32_t TypeIdx = Canon.getTargetIndex();
1485
6.27k
  const uint32_t TypeSpaceSize =
1486
6.27k
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type);
1487
  // 2. Target type index bounds.
1488
6.27k
  if (TypeIdx >= TypeSpaceSize) {
1489
8
    spdlog::error(ErrCode::Value::InvalidIndex);
1490
8
    spdlog::error(
1491
8
        "    canon lift: type index {} exceeds type index space size {}"sv,
1492
8
        TypeIdx, TypeSpaceSize);
1493
8
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1494
8
    return Unexpect(ErrCode::Value::InvalidIndex);
1495
8
  }
1496
  // 3. Target type must be a component FuncType.
1497
6.26k
  const auto *DT = CompCtx.getDefType(TypeIdx);
1498
6.26k
  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
6.26k
  if (!DT->isFuncType()) {
1509
3
    spdlog::error(ErrCode::Value::InvalidTypeReference);
1510
3
    spdlog::error(
1511
3
        "    canon lift: target type index {} does not reference a component func type"sv,
1512
3
        TypeIdx);
1513
3
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1514
3
    return Unexpect(ErrCode::Value::InvalidTypeReference);
1515
3
  }
1516
  // 4. Validate canonical options (Lift site allows all).
1517
6.26k
  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
6.25k
  CompCtx.addFunc(&DT->getFuncType());
1521
6.25k
  return {};
1522
6.26k
}
1523
1524
Expect<void>
1525
1.21k
Validator::validateCanonLower(const AST::Component::Canonical &Canon) noexcept {
1526
1.21k
  const uint32_t FuncIdx = Canon.getIndex();
1527
1.21k
  const uint32_t FuncSpaceSize =
1528
1.21k
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Func);
1529
  // 1. Component func index bounds.
1530
1.21k
  if (FuncIdx >= FuncSpaceSize) {
1531
6
    spdlog::error(ErrCode::Value::InvalidIndex);
1532
6
    spdlog::error(
1533
6
        "    canon lower: component func index {} exceeds func index space size {}"sv,
1534
6
        FuncIdx, FuncSpaceSize);
1535
6
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1536
6
    return Unexpect(ErrCode::Value::InvalidIndex);
1537
6
  }
1538
  // 2. Validate canonical options (per-site rules for Lower).
1539
1.20k
  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.18k
  CompCtx.addCoreFunc();
1543
1.18k
  return {};
1544
1.20k
}
1545
1546
Expect<void> Validator::validateCanonResourceNew(
1547
1.28k
    const AST::Component::Canonical &Canon) noexcept {
1548
1.28k
  const uint32_t Idx = Canon.getIndex();
1549
1.28k
  const uint32_t TypeSpaceSize =
1550
1.28k
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type);
1551
  // 1. Type index bounds.
1552
1.28k
  if (Idx >= TypeSpaceSize) {
1553
32
    spdlog::error(ErrCode::Value::InvalidIndex);
1554
32
    spdlog::error(
1555
32
        "    canon resource.new: type index {} exceeds type index space size {}"sv,
1556
32
        Idx, TypeSpaceSize);
1557
32
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1558
32
    return Unexpect(ErrCode::Value::InvalidIndex);
1559
32
  }
1560
  // 2. Type must be a locally-defined resource.
1561
1.24k
  const auto *RInfo = CompCtx.getResource(Idx);
1562
1.24k
  if (RInfo == nullptr) {
1563
34
    spdlog::error(ErrCode::Value::InvalidTypeReference);
1564
34
    spdlog::error(
1565
34
        "    canon resource.new: type index {} does not reference a resource"sv,
1566
34
        Idx);
1567
34
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1568
34
    return Unexpect(ErrCode::Value::InvalidTypeReference);
1569
34
  }
1570
1.21k
  if (!RInfo->LocallyDefined) {
1571
2
    spdlog::error(ErrCode::Value::InvalidTypeReference);
1572
2
    spdlog::error(
1573
2
        "    canon resource.new: type index {} is not locally defined (imported or outer-aliased resources are not allowed)"sv,
1574
2
        Idx);
1575
2
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1576
2
    return Unexpect(ErrCode::Value::InvalidTypeReference);
1577
2
  }
1578
  // 4. Validate canonical options.
1579
1.21k
  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
1.21k
  CompCtx.addCoreFunc(&CoreFuncType_I32_I32);
1583
1.21k
  return {};
1584
1.21k
}
1585
1586
Expect<void> Validator::validateCanonResourceRep(
1587
1.56k
    const AST::Component::Canonical &Canon) noexcept {
1588
1.56k
  const uint32_t Idx = Canon.getIndex();
1589
1.56k
  const uint32_t TypeSpaceSize =
1590
1.56k
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type);
1591
  // 1. Type index bounds.
1592
1.56k
  if (Idx >= TypeSpaceSize) {
1593
40
    spdlog::error(ErrCode::Value::InvalidIndex);
1594
40
    spdlog::error(
1595
40
        "    canon resource.rep: type index {} exceeds type index space size {}"sv,
1596
40
        Idx, TypeSpaceSize);
1597
40
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1598
40
    return Unexpect(ErrCode::Value::InvalidIndex);
1599
40
  }
1600
  // 2. Type must be a locally-defined resource.
1601
1.52k
  const auto *RInfo = CompCtx.getResource(Idx);
1602
1.52k
  if (RInfo == nullptr) {
1603
32
    spdlog::error(ErrCode::Value::InvalidTypeReference);
1604
32
    spdlog::error(
1605
32
        "    canon resource.rep: type index {} does not reference a resource"sv,
1606
32
        Idx);
1607
32
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1608
32
    return Unexpect(ErrCode::Value::InvalidTypeReference);
1609
32
  }
1610
1.48k
  if (!RInfo->LocallyDefined) {
1611
2
    spdlog::error(ErrCode::Value::InvalidTypeReference);
1612
2
    spdlog::error(
1613
2
        "    canon resource.rep: type index {} is not locally defined (imported or outer-aliased resources are not allowed)"sv,
1614
2
        Idx);
1615
2
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1616
2
    return Unexpect(ErrCode::Value::InvalidTypeReference);
1617
2
  }
1618
  // 4. Validate canonical options.
1619
1.48k
  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.48k
  CompCtx.addCoreFunc(&CoreFuncType_I32_I32);
1623
1.48k
  return {};
1624
1.48k
}
1625
1626
Expect<void> Validator::validateCanonResourceDrop(
1627
1.55k
    const AST::Component::Canonical &Canon) noexcept {
1628
1.55k
  const uint32_t Idx = Canon.getIndex();
1629
1.55k
  const uint32_t TypeSpaceSize =
1630
1.55k
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type);
1631
  // 1. Type index bounds.
1632
1.55k
  if (Idx >= TypeSpaceSize) {
1633
50
    spdlog::error(ErrCode::Value::InvalidIndex);
1634
50
    spdlog::error(
1635
50
        "    canon resource.drop: type index {} exceeds type index space size {}"sv,
1636
50
        Idx, TypeSpaceSize);
1637
50
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1638
50
    return Unexpect(ErrCode::Value::InvalidIndex);
1639
50
  }
1640
  // 2. Type must be a resource type.
1641
1.50k
  if (CompCtx.getResource(Idx) == nullptr) {
1642
21
    spdlog::error(ErrCode::Value::InvalidTypeReference);
1643
21
    spdlog::error(
1644
21
        "    canon resource.drop: type index {} does not reference a resource"sv,
1645
21
        Idx);
1646
21
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1647
21
    return Unexpect(ErrCode::Value::InvalidTypeReference);
1648
21
  }
1649
  // 3. resource.drop accepts both local and imported resources — no locality
1650
  // check.
1651
  // 4. Validate canonical options.
1652
1.47k
  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
1.47k
  CompCtx.addCoreFunc(&CoreFuncType_I32_Void);
1657
1.47k
  return {};
1658
1.47k
}
1659
1660
7.24k
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
7.24k
  const uint32_t TypeSpaceBefore =
1675
7.24k
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type);
1676
1677
7.24k
  EXPECTED_TRY(validate(Im.getDesc()).map_error([](auto E) {
1678
7.16k
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Import));
1679
7.16k
    return E;
1680
7.16k
  }));
1681
1682
13.5k
  EXPECTED_TRY(ComponentName CName,
1683
13.5k
               ComponentName::parse(Im.getName()).map_error([](auto E) {
1684
13.5k
                 spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Import));
1685
13.5k
                 return E;
1686
13.5k
               }));
1687
1688
  // Annotated plainnames ([constructor], [method], [static]) can only appear
1689
  // on func imports.
1690
13.5k
  switch (CName.getKind()) {
1691
8
  case ComponentNameKind::Constructor:
1692
10
  case ComponentNameKind::Method:
1693
18
  case ComponentNameKind::Static:
1694
18
    if (Im.getDesc().getDescType() !=
1695
18
        AST::Component::ExternDesc::DescType::FuncType) {
1696
18
      spdlog::error(ErrCode::Value::ComponentInvalidName);
1697
18
      spdlog::error("    Import: annotated name requires func type"sv);
1698
18
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Import));
1699
18
      return Unexpect(ErrCode::Value::ComponentInvalidName);
1700
18
    }
1701
0
    break;
1702
6.41k
  default:
1703
6.41k
    break;
1704
13.5k
  }
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
6.41k
  std::string_view ResourceLabel;
1715
6.41k
  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
6.41k
  default:
1726
6.41k
    break;
1727
6.41k
  }
1728
6.41k
  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
6.41k
  if (!CompCtx.addImportedName(CName)) {
1738
135
    spdlog::error(ErrCode::Value::ComponentDuplicateName);
1739
135
    spdlog::error("    Import: Duplicate import name"sv);
1740
135
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Import));
1741
135
    return Unexpect(ErrCode::Value::ComponentDuplicateName);
1742
135
  }
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
6.27k
  if (Im.getDesc().getDescType() ==
1747
6.27k
          AST::Component::ExternDesc::DescType::TypeBound &&
1748
3.54k
      CName.getKind() == ComponentNameKind::Label) {
1749
3.51k
    CompCtx.addResourceLabel(Im.getName(), TypeSpaceBefore);
1750
3.51k
  }
1751
1752
6.27k
  return {};
1753
6.41k
}
1754
1755
915
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
915
  const auto &Sort = Ex.getSortIndex().getSort();
1771
915
  uint32_t Idx = Ex.getSortIndex().getIdx();
1772
915
  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
7
    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
5
    if (Idx >= CompCtx.getCoreSortIndexSize(Sort.getCoreSortType())) {
1783
4
      spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds);
1784
4
      spdlog::error("    Export: sort index {} out of bounds"sv, Idx);
1785
4
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export));
1786
4
      return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds);
1787
4
    }
1788
908
  } else {
1789
908
    if (Idx >= CompCtx.getSortIndexSize(Sort.getSortType())) {
1790
6
      spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds);
1791
6
      spdlog::error("    Export: sort index {} out of bounds"sv, Idx);
1792
6
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export));
1793
6
      return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds);
1794
6
    }
1795
908
  }
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
903
  if (Ex.getDesc().has_value() &&
1803
5
      !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.74k
  EXPECTED_TRY(ComponentName CName,
1813
1.74k
               validateExportName(Ex.getName()).map_error([](auto E) {
1814
1.74k
                 spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export));
1815
1.74k
                 return E;
1816
1.74k
               }));
1817
1.74k
  if (!CompCtx.addExportedName(CName)) {
1818
24
    spdlog::error(ErrCode::Value::ComponentDuplicateName);
1819
24
    spdlog::error("    Export: Duplicate export name '{}'"sv, Ex.getName());
1820
24
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export));
1821
24
    return Unexpect(ErrCode::Value::ComponentDuplicateName);
1822
24
  }
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
819
  if (Sort.isCore()) {
1829
0
    CompCtx.incCoreSortIndexSize(Sort.getCoreSortType());
1830
819
  } else {
1831
819
    const AST::Component::InstanceType *IT = nullptr;
1832
819
    const bool IsInst =
1833
819
        Sort.getSortType() == AST::Component::Sort::SortType::Instance;
1834
819
    const bool HasInstAscription =
1835
819
        IsInst && Ex.getDesc().has_value() &&
1836
0
        Ex.getDesc()->getDescType() ==
1837
0
            AST::Component::ExternDesc::DescType::InstanceType;
1838
819
    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
819
    uint32_t NewIdx = CompCtx.incSortIndexSize(Sort.getSortType());
1853
819
    if (IsInst) {
1854
22
      if (IT != nullptr) {
1855
0
        populateInstanceFromType(NewIdx, *IT);
1856
22
      } 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
22
        const auto &SrcExports = CompCtx.getInstance(Idx).Exports;
1863
22
        for (const auto &[Name, IE] : SrcExports) {
1864
10
          CompCtx.addInstanceExport(NewIdx, Name, IE.ST, IE.IT,
1865
10
                                    IE.NestedInstIdx);
1866
10
        }
1867
22
      }
1868
22
    }
1869
819
  }
1870
819
  return {};
1871
819
}
1872
1873
Expect<void>
1874
8.44k
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
8.44k
  switch (Desc.getDescType()) {
1892
197
  case AST::Component::ExternDesc::DescType::CoreType: {
1893
197
    const uint32_t RefIdx = Desc.getTypeIndex();
1894
197
    const uint32_t CoreTypeSize =
1895
197
        CompCtx.getCoreSortIndexSize(AST::Component::Sort::CoreSortType::Type);
1896
197
    if (RefIdx >= CoreTypeSize) {
1897
23
      spdlog::error(ErrCode::Value::InvalidIndex);
1898
23
      spdlog::error(
1899
23
          "    ExternDesc: core type index {} exceeds core:type index space size {}"sv,
1900
23
          RefIdx, CoreTypeSize);
1901
23
      return Unexpect(ErrCode::Value::InvalidIndex);
1902
23
    }
1903
174
    break;
1904
197
  }
1905
613
  case AST::Component::ExternDesc::DescType::FuncType:
1906
1.08k
  case AST::Component::ExternDesc::DescType::ComponentType:
1907
1.40k
  case AST::Component::ExternDesc::DescType::InstanceType: {
1908
1.40k
    const uint32_t RefIdx = Desc.getTypeIndex();
1909
1.40k
    const uint32_t TypeSize =
1910
1.40k
        CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type);
1911
1.40k
    if (RefIdx >= TypeSize) {
1912
47
      spdlog::error(ErrCode::Value::InvalidIndex);
1913
47
      spdlog::error(
1914
47
          "    ExternDesc: referenced type index {} exceeds type index space size {}"sv,
1915
47
          RefIdx, TypeSize);
1916
47
      return Unexpect(ErrCode::Value::InvalidIndex);
1917
47
    }
1918
1.36k
    break;
1919
1.40k
  }
1920
6.84k
  default:
1921
6.84k
    break;
1922
8.44k
  }
1923
1924
8.37k
  switch (Desc.getDescType()) {
1925
174
  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
174
    const auto *CT = CompCtx.getCoreModuleType(Desc.getTypeIndex());
1929
174
    CompCtx.addCoreModule(CT);
1930
174
    break;
1931
0
  }
1932
584
  case AST::Component::ExternDesc::DescType::FuncType:
1933
584
    CompCtx.addFunc();
1934
584
    break;
1935
3.19k
  case AST::Component::ExternDesc::DescType::ValueBound:
1936
3.19k
    CompCtx.addValue();
1937
3.19k
    break;
1938
3.64k
  case AST::Component::ExternDesc::DescType::TypeBound:
1939
3.64k
    if (Desc.isEqType()) {
1940
      // (type (eq i)) — alias type i
1941
132
      uint32_t RefIdx = Desc.getTypeIndex();
1942
132
      if (RefIdx >=
1943
132
          CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type)) {
1944
27
        spdlog::error(ErrCode::Value::InvalidIndex);
1945
27
        spdlog::error("    ExternDesc: eq type bound index {} out of bounds"sv,
1946
27
                      RefIdx);
1947
27
        return Unexpect(ErrCode::Value::InvalidIndex);
1948
27
      }
1949
      // (eq i): inherits the source resource's id; body lives on the
1950
      // shared registry entry.
1951
105
      uint32_t NewIdx = CompCtx.addType(nullptr, /*IsLocal=*/false);
1952
105
      if (const auto *SrcInfo = CompCtx.getResource(RefIdx)) {
1953
35
        CompCtx.addResource(NewIdx, {SrcInfo->Id, /*LocallyDefined=*/false});
1954
35
      }
1955
3.51k
    } else {
1956
      // (sub resource): abstract import — fresh id with no body.
1957
3.51k
      uint32_t NewIdx = CompCtx.addType(nullptr, /*IsLocal=*/false);
1958
3.51k
      CompCtx.addResource(NewIdx, {CompCtx.allocateFreshResourceId(),
1959
3.51k
                                   /*LocallyDefined=*/false});
1960
3.51k
    }
1961
3.62k
    break;
1962
3.62k
  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
464
    const auto *CT = CompCtx.getComponentType(Desc.getTypeIndex());
1966
464
    CompCtx.addComponent(CT);
1967
464
    break;
1968
3.64k
  }
1969
314
  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
314
    const auto *IT = CompCtx.getInstanceType(Desc.getTypeIndex());
1973
314
    uint32_t InstIdx = CompCtx.addInstance(IT);
1974
314
    if (IT != nullptr) {
1975
134
      populateInstanceFromType(InstIdx, *IT);
1976
134
    }
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
314
    break;
1982
3.64k
  }
1983
0
  default:
1984
0
    assumingUnreachable();
1985
8.37k
  }
1986
1987
8.35k
  return {};
1988
8.37k
}
1989
1990
Expect<void>
1991
2.04k
Validator::validate(const AST::Component::CoreImportDesc &Desc) noexcept {
1992
2.04k
  if (Desc.isFunc()) {
1993
46
    uint32_t TypeIdx = Desc.getTypeIndex();
1994
46
    if (TypeIdx >= CompCtx.getCoreSortIndexSize(
1995
46
                       AST::Component::Sort::CoreSortType::Type)) {
1996
36
      spdlog::error(ErrCode::Value::InvalidIndex);
1997
36
      spdlog::error("    CoreImportDesc: func type index {} out of bounds"sv,
1998
36
                    TypeIdx);
1999
36
      return Unexpect(ErrCode::Value::InvalidIndex);
2000
36
    }
2001
10
    CompCtx.addCoreFunc();
2002
1.99k
  } else if (Desc.isTable()) {
2003
603
    CompCtx.addCoreTable();
2004
1.39k
  } else if (Desc.isMemory()) {
2005
748
    CompCtx.addCoreMemory();
2006
748
  } else if (Desc.isGlobal()) {
2007
527
    CompCtx.addCoreGlobal();
2008
527
  } else if (Desc.isTag()) {
2009
119
    CompCtx.addCoreTag();
2010
119
  } else {
2011
0
    assumingUnreachable();
2012
0
  }
2013
2.00k
  return {};
2014
2.04k
}
2015
2016
Expect<void>
2017
798
Validator::validate(const AST::Component::CoreImportDecl &Decl) noexcept {
2018
798
  return validate(Decl.getImportDesc());
2019
798
}
2020
2021
Expect<void>
2022
1.24k
Validator::validate(const AST::Component::CoreExportDecl &Decl) noexcept {
2023
1.24k
  return validate(Decl.getImportDesc());
2024
1.24k
}
2025
2026
Expect<void>
2027
2.31k
Validator::validate(const AST::Component::CoreModuleDecl &Decl) noexcept {
2028
2.31k
  auto ReportError = [](auto E) {
2029
73
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_CoreModule));
2030
73
    return E;
2031
73
  };
2032
2033
2.31k
  if (Decl.isImport()) {
2034
798
    EXPECTED_TRY(validate(Decl.getImport()).map_error(ReportError));
2035
1.51k
  } else if (Decl.isType()) {
2036
171
    EXPECTED_TRY(validate(*Decl.getType()).map_error(ReportError));
2037
1.34k
  } else if (Decl.isAlias()) {
2038
101
    EXPECTED_TRY(validate(Decl.getAlias()).map_error(ReportError));
2039
1.24k
  } else if (Decl.isExport()) {
2040
1.24k
    EXPECTED_TRY(validate(Decl.getExport()).map_error(ReportError));
2041
1.24k
  } else {
2042
0
    assumingUnreachable();
2043
0
  }
2044
2.24k
  return {};
2045
2.31k
}
2046
2047
Expect<void>
2048
299
Validator::validate(const AST::Component::ImportDecl &Decl) noexcept {
2049
299
  auto ReportError = [](auto E) {
2050
18
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Import));
2051
18
    return E;
2052
18
  };
2053
2054
  // Validate the extern descriptor (also increments sort index spaces).
2055
299
  EXPECTED_TRY(validate(Decl.getExternDesc()).map_error(ReportError));
2056
2057
  // Parse and validate the import name.
2058
565
  EXPECTED_TRY(ComponentName CName,
2059
565
               ComponentName::parse(Decl.getName()).map_error(ReportError));
2060
2061
  // Annotated plainnames can only appear on func imports.
2062
565
  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
281
  default:
2075
281
    break;
2076
565
  }
2077
2078
  // Check import name uniqueness.
2079
281
  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
281
  return {};
2087
281
}
2088
2089
Expect<void>
2090
909
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
909
  EXPECTED_TRY(ComponentName CName,
2095
907
               validateExportName(Decl.getName()).map_error([](auto E) {
2096
907
                 spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Export));
2097
907
                 return E;
2098
907
               }));
2099
907
  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
907
  EXPECTED_TRY(validate(Decl.getExternDesc()).map_error([](auto E) {
2117
904
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Export));
2118
904
    return E;
2119
904
  }));
2120
2121
904
  return {};
2122
907
}
2123
2124
Expect<void>
2125
2.05k
Validator::validate(const AST::Component::InstanceDecl &Decl) noexcept {
2126
2.05k
  auto ReportError = [](auto E) {
2127
76
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Instance));
2128
76
    return E;
2129
76
  };
2130
2131
2.05k
  if (Decl.isCoreType()) {
2132
22
    EXPECTED_TRY(validate(*Decl.getCoreType()).map_error(ReportError));
2133
2.03k
  } else if (Decl.isType()) {
2134
912
    EXPECTED_TRY(validate(*Decl.getType()).map_error(ReportError));
2135
1.12k
  } else if (Decl.isAlias()) {
2136
212
    EXPECTED_TRY(validate(Decl.getAlias()).map_error(ReportError));
2137
195
    const auto &A = Decl.getAlias();
2138
195
    const auto &Sort = A.getSort();
2139
195
    if (Sort.isCore()) {
2140
0
      CompCtx.incCoreSortIndexSize(Sort.getCoreSortType());
2141
195
    } else {
2142
195
      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
195
      if (A.getTargetType() == AST::Component::Alias::TargetType::Outer &&
2146
195
          Sort.getSortType() == AST::Component::Sort::SortType::Type) {
2147
195
        CompCtx.carryOuterResource(NewInstIdx, A.getOuter().first,
2148
195
                                   A.getOuter().second);
2149
195
      }
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
195
      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
195
    }
2180
909
  } else if (Decl.isExportDecl()) {
2181
909
    EXPECTED_TRY(validate(Decl.getExport()).map_error(ReportError));
2182
909
  } else {
2183
0
    assumingUnreachable();
2184
0
  }
2185
1.97k
  return {};
2186
2.05k
}
2187
2188
Expect<void>
2189
1.77k
Validator::validate(const AST::Component::ComponentDecl &Decl) noexcept {
2190
1.77k
  auto ReportError = [](auto E) {
2191
52
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Component));
2192
52
    return E;
2193
52
  };
2194
2195
1.77k
  if (Decl.isImportDecl()) {
2196
299
    EXPECTED_TRY(validate(Decl.getImport()).map_error(ReportError));
2197
1.47k
  } else if (Decl.isInstanceDecl()) {
2198
1.47k
    EXPECTED_TRY(validate(Decl.getInstance()).map_error(ReportError));
2199
1.47k
  } else {
2200
0
    assumingUnreachable();
2201
0
  }
2202
1.71k
  return {};
2203
1.77k
}
2204
2205
20.8k
Expect<void> Validator::validate(const ComponentValType &VT) noexcept {
2206
20.8k
  if (VT.getCode() == ComponentTypeCode::TypeIndex) {
2207
16.6k
    uint32_t Idx = VT.getTypeIndex();
2208
16.6k
    if (Idx >= CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type)) {
2209
69
      spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds);
2210
69
      spdlog::error("    ComponentValType: type index {} out of bounds"sv, Idx);
2211
69
      return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds);
2212
69
    }
2213
16.5k
    const auto *DT = CompCtx.getDefType(Idx);
2214
16.5k
    if (DT != nullptr && !DT->isDefValType() && !DT->isResourceType()) {
2215
11
      spdlog::error(ErrCode::Value::NotADefinedType);
2216
11
      spdlog::error(
2217
11
          "    ComponentValType: type index {} is not a defined value type"sv,
2218
11
          Idx);
2219
11
      return Unexpect(ErrCode::Value::NotADefinedType);
2220
11
    }
2221
16.5k
  }
2222
20.7k
  return {};
2223
20.8k
}
2224
2225
Expect<void>
2226
106k
Validator::validate(const AST::Component::DefValType &DVT) noexcept {
2227
106k
  if (DVT.isOwnTy()) {
2228
739
    uint32_t Idx = DVT.getOwn().Idx;
2229
739
    if (Idx >= CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type)) {
2230
36
      spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds);
2231
36
      spdlog::error("    DefValType: own type index {} out of bounds"sv, Idx);
2232
36
      return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds);
2233
36
    }
2234
703
    if (CompCtx.getResource(Idx) == nullptr) {
2235
64
      spdlog::error(ErrCode::Value::NotADefinedType);
2236
64
      spdlog::error(
2237
64
          "    DefValType: own type index {} does not refer to a resource type"sv,
2238
64
          Idx);
2239
64
      return Unexpect(ErrCode::Value::NotADefinedType);
2240
64
    }
2241
105k
  } else if (DVT.isBorrowTy()) {
2242
882
    uint32_t Idx = DVT.getBorrow().Idx;
2243
882
    if (Idx >= CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type)) {
2244
43
      spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds);
2245
43
      spdlog::error("    DefValType: borrow type index {} out of bounds"sv,
2246
43
                    Idx);
2247
43
      return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds);
2248
43
    }
2249
839
    if (CompCtx.getResource(Idx) == nullptr) {
2250
21
      spdlog::error(ErrCode::Value::NotADefinedType);
2251
21
      spdlog::error(
2252
21
          "    DefValType: borrow type index {} does not refer to a resource type"sv,
2253
21
          Idx);
2254
21
      return Unexpect(ErrCode::Value::NotADefinedType);
2255
21
    }
2256
104k
  } else if (DVT.isRecordTy()) {
2257
508
    const auto &Rec = DVT.getRecord();
2258
508
    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
508
    std::unordered_set<std::string> Seen;
2264
511
    for (const auto &LT : Rec.LabelTypes) {
2265
511
      if (LT.getLabel().empty()) {
2266
2
        spdlog::error(ErrCode::Value::NameCannotBeEmpty);
2267
2
        return Unexpect(ErrCode::Value::NameCannotBeEmpty);
2268
2
      }
2269
509
      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
507
      if (!Seen.insert(toLowerStr(LT.getLabel())).second) {
2277
1
        spdlog::error(ErrCode::Value::RecordFieldNameConflicts);
2278
1
        spdlog::error("    DefValType: duplicate record field '{}'"sv,
2279
1
                      LT.getLabel());
2280
1
        return Unexpect(ErrCode::Value::RecordFieldNameConflicts);
2281
1
      }
2282
506
      EXPECTED_TRY(validate(LT.getValType()));
2283
506
    }
2284
104k
  } else if (DVT.isVariantTy()) {
2285
622
    const auto &Var = DVT.getVariant();
2286
622
    if (Var.Cases.empty()) {
2287
1
      spdlog::error(ErrCode::Value::VariantMustHaveCase);
2288
1
      return Unexpect(ErrCode::Value::VariantMustHaveCase);
2289
1
    }
2290
621
    std::unordered_set<std::string> Seen;
2291
621
    for (const auto &C : Var.Cases) {
2292
621
      if (C.first.empty()) {
2293
1
        spdlog::error(ErrCode::Value::NameCannotBeEmpty);
2294
1
        return Unexpect(ErrCode::Value::NameCannotBeEmpty);
2295
1
      }
2296
620
      if (!isKebabString(C.first)) {
2297
3
        spdlog::error(ErrCode::Value::ComponentInvalidName);
2298
3
        spdlog::error(
2299
3
            "    DefValType: variant case '{}' is not valid kebab-case"sv,
2300
3
            C.first);
2301
3
        return Unexpect(ErrCode::Value::ComponentInvalidName);
2302
3
      }
2303
617
      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
617
      if (C.second.has_value()) {
2309
538
        EXPECTED_TRY(validate(*C.second));
2310
538
      }
2311
617
    }
2312
103k
  } else if (DVT.isTupleTy()) {
2313
1.15k
    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
1.32k
    for (const auto &T : DVT.getTuple().Types) {
2319
1.32k
      EXPECTED_TRY(validate(T));
2320
1.32k
    }
2321
102k
  } else if (DVT.isListTy()) {
2322
1.74k
    EXPECTED_TRY(validate(DVT.getList().ValTy));
2323
100k
  } else if (DVT.isOptionTy()) {
2324
602
    EXPECTED_TRY(validate(DVT.getOption().ValTy));
2325
100k
  } else if (DVT.isResultTy()) {
2326
1.63k
    const auto &R = DVT.getResult();
2327
1.63k
    if (R.ValTy.has_value()) {
2328
608
      EXPECTED_TRY(validate(*R.ValTy));
2329
608
    }
2330
1.63k
    if (R.ErrTy.has_value()) {
2331
1.02k
      EXPECTED_TRY(validate(*R.ErrTy));
2332
1.02k
    }
2333
98.5k
  } else if (DVT.isFlagsTy()) {
2334
203
    const auto &Flags = DVT.getFlags();
2335
203
    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
203
    if (Flags.Labels.size() > 32) {
2341
0
      spdlog::error(ErrCode::Value::CannotHaveMoreThan32Flags);
2342
0
      return Unexpect(ErrCode::Value::CannotHaveMoreThan32Flags);
2343
0
    }
2344
203
    std::unordered_set<std::string> Seen;
2345
210
    for (const auto &L : Flags.Labels) {
2346
210
      if (L.empty()) {
2347
4
        spdlog::error(ErrCode::Value::NameCannotBeEmpty);
2348
4
        return Unexpect(ErrCode::Value::NameCannotBeEmpty);
2349
4
      }
2350
206
      if (!isKebabString(L)) {
2351
20
        spdlog::error(ErrCode::Value::ComponentInvalidName);
2352
20
        spdlog::error(
2353
20
            "    DefValType: flags label '{}' is not valid kebab-case"sv, L);
2354
20
        return Unexpect(ErrCode::Value::ComponentInvalidName);
2355
20
      }
2356
186
      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
186
    }
2362
98.3k
  } else if (DVT.isEnumTy()) {
2363
1.21k
    const auto &Enm = DVT.getEnum();
2364
1.21k
    if (Enm.Labels.empty()) {
2365
2
      spdlog::error(ErrCode::Value::InvalidTypeReference);
2366
2
      spdlog::error("    DefValType: enum must have at least one label"sv);
2367
2
      return Unexpect(ErrCode::Value::InvalidTypeReference);
2368
2
    }
2369
1.21k
    std::unordered_set<std::string> Seen;
2370
1.21k
    for (const auto &L : Enm.Labels) {
2371
1.21k
      if (L.empty()) {
2372
4
        spdlog::error(ErrCode::Value::NameCannotBeEmpty);
2373
4
        return Unexpect(ErrCode::Value::NameCannotBeEmpty);
2374
4
      }
2375
1.21k
      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
1.20k
      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
1.20k
    }
2387
97.1k
  } else if (DVT.isStreamTy()) {
2388
1.78k
    if (DVT.getStream().ValTy.has_value()) {
2389
664
      EXPECTED_TRY(validate(*DVT.getStream().ValTy));
2390
664
    }
2391
95.3k
  } else if (DVT.isFutureTy()) {
2392
1.07k
    if (DVT.getFuture().ValTy.has_value()) {
2393
242
      EXPECTED_TRY(validate(*DVT.getFuture().ValTy));
2394
242
    }
2395
1.07k
  }
2396
106k
  return {};
2397
106k
}
2398
2399
10.5k
Expect<void> Validator::validate(const AST::Component::FuncType &FT) noexcept {
2400
  // Validate param names: kebab-case + unique
2401
10.5k
  std::unordered_set<std::string_view> ParamNames;
2402
10.5k
  for (const auto &P : FT.getParamList()) {
2403
4.02k
    if (!P.getLabel().empty()) {
2404
2.01k
      if (!isKebabString(P.getLabel())) {
2405
18
        spdlog::error(ErrCode::Value::ComponentInvalidName);
2406
18
        spdlog::error(
2407
18
            "    FuncType: parameter name '{}' is not valid kebab-case"sv,
2408
18
            P.getLabel());
2409
18
        return Unexpect(ErrCode::Value::ComponentInvalidName);
2410
18
      }
2411
1.99k
      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.99k
    }
2418
4.00k
    EXPECTED_TRY(validate(P.getValType()));
2419
4.00k
  }
2420
  // Reject transitive use of borrow in results
2421
10.5k
  for (const auto &R : FT.getResultList()) {
2422
9.59k
    EXPECTED_TRY(validate(R.getValType()));
2423
9.58k
    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
9.58k
  }
2430
10.5k
  return {};
2431
10.5k
}
2432
2433
Expect<void>
2434
1.05M
Validator::validate(const AST::Component::InstanceType &IT) noexcept {
2435
  // Instance types are validated with an initially-empty index space.
2436
1.05M
  CompCtx.enterTypeDefinition();
2437
1.05M
  for (const auto &Decl : IT.getDecl()) {
2438
584
    EXPECTED_TRY(validate(Decl).map_error([](auto E) {
2439
584
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_DefType));
2440
584
      return E;
2441
584
    }));
2442
584
  }
2443
1.05M
  CompCtx.exitComponent();
2444
1.05M
  return {};
2445
1.05M
}
2446
2447
Expect<void>
2448
1.94M
Validator::validate(const AST::Component::ComponentType &CT) noexcept {
2449
  // Component types are validated with an initially-empty index space.
2450
1.94M
  CompCtx.enterTypeDefinition();
2451
1.94M
  for (const auto &Decl : CT.getDecl()) {
2452
1.77k
    EXPECTED_TRY(validate(Decl).map_error([](auto E) {
2453
1.77k
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_DefType));
2454
1.77k
      return E;
2455
1.77k
    }));
2456
1.77k
  }
2457
1.94M
  CompCtx.exitComponent();
2458
1.94M
  return {};
2459
1.94M
}
2460
2461
Expect<void>
2462
24.4k
Validator::validate(const AST::Component::ResourceType &RT) noexcept {
2463
  // Resource types are not allowed inside componenttype/instancetype scopes.
2464
24.4k
  if (CompCtx.isTypeDefinitionScope()) {
2465
1
    spdlog::error(ErrCode::Value::InvalidTypeReference);
2466
1
    spdlog::error("    ResourceType: resource types cannot be defined inside "
2467
1
                  "componenttype or instancetype"sv);
2468
1
    return Unexpect(ErrCode::Value::InvalidTypeReference);
2469
1
  }
2470
24.4k
  if (RT.getDestructor().has_value()) {
2471
163
    uint32_t DtorIdx = *RT.getDestructor();
2472
163
    if (DtorIdx >= CompCtx.getCoreSortIndexSize(
2473
163
                       AST::Component::Sort::CoreSortType::Func)) {
2474
2
      spdlog::error(ErrCode::Value::InvalidIndex);
2475
2
      spdlog::error(
2476
2
          "    ResourceType: destructor core func index {} out of bounds"sv,
2477
2
          DtorIdx);
2478
2
      return Unexpect(ErrCode::Value::InvalidIndex);
2479
2
    }
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
161
    const AST::SubType *DtorST = CompCtx.getCoreFunc(DtorIdx);
2489
161
    if (DtorST != nullptr) {
2490
44
      const auto &DtorType = DtorST->getCompositeType();
2491
44
      const auto &ExpType = CoreFuncType_I32_Void.getCompositeType();
2492
44
      if (!DtorType.isFunc() ||
2493
44
          DtorType.getFuncType() != ExpType.getFuncType()) {
2494
1
        spdlog::error(ErrCode::Value::InvalidTypeReference);
2495
1
        spdlog::error(
2496
1
            "    ResourceType: destructor core func {} must have signature [i32] -> []"sv,
2497
1
            DtorIdx);
2498
1
        return Unexpect(ErrCode::Value::InvalidTypeReference);
2499
1
      }
2500
44
    }
2501
161
  }
2502
24.4k
  return {};
2503
24.4k
}
2504
2505
18.7k
bool Validator::containsBorrow(const ComponentValType &VT) const noexcept {
2506
18.7k
  if (VT.getCode() == ComponentTypeCode::Borrow) {
2507
0
    return true;
2508
0
  }
2509
18.7k
  if (VT.getCode() != ComponentTypeCode::TypeIndex) {
2510
4.29k
    return false;
2511
4.29k
  }
2512
14.4k
  uint32_t Idx = VT.getTypeIndex();
2513
14.4k
  const auto *DT = CompCtx.getDefType(Idx);
2514
14.4k
  if (DT == nullptr || !DT->isDefValType()) {
2515
2.38k
    return false;
2516
2.38k
  }
2517
12.0k
  return containsBorrow(DT->getDefValType());
2518
14.4k
}
2519
2520
bool Validator::containsBorrow(
2521
12.0k
    const AST::Component::DefValType &DVT) const noexcept {
2522
12.0k
  if (DVT.isBorrowTy()) {
2523
1
    return true;
2524
1
  }
2525
12.0k
  if (DVT.isRecordTy()) {
2526
1.18k
    for (const auto &F : DVT.getRecord().LabelTypes) {
2527
1.18k
      if (containsBorrow(F.getValType())) {
2528
0
        return true;
2529
0
      }
2530
1.18k
    }
2531
1.18k
    return false;
2532
1.18k
  }
2533
10.8k
  if (DVT.isVariantTy()) {
2534
1.69k
    for (const auto &C : DVT.getVariant().Cases) {
2535
1.69k
      if (C.second.has_value() && containsBorrow(*C.second)) {
2536
0
        return true;
2537
0
      }
2538
1.69k
    }
2539
1.69k
    return false;
2540
1.69k
  }
2541
9.18k
  if (DVT.isListTy()) {
2542
1.53k
    return containsBorrow(DVT.getList().ValTy);
2543
1.53k
  }
2544
7.64k
  if (DVT.isTupleTy()) {
2545
1.23k
    for (const auto &T : DVT.getTuple().Types) {
2546
1.23k
      if (containsBorrow(T)) {
2547
0
        return true;
2548
0
      }
2549
1.23k
    }
2550
1.15k
    return false;
2551
1.15k
  }
2552
6.49k
  if (DVT.isOptionTy()) {
2553
71
    return containsBorrow(DVT.getOption().ValTy);
2554
71
  }
2555
6.42k
  if (DVT.isResultTy()) {
2556
3.24k
    const auto &R = DVT.getResult();
2557
3.24k
    return (R.ValTy.has_value() && containsBorrow(*R.ValTy)) ||
2558
3.24k
           (R.ErrTy.has_value() && containsBorrow(*R.ErrTy));
2559
3.24k
  }
2560
3.18k
  if (DVT.isStreamTy()) {
2561
522
    return DVT.getStream().ValTy.has_value() &&
2562
195
           containsBorrow(*DVT.getStream().ValTy);
2563
522
  }
2564
2.65k
  if (DVT.isFutureTy()) {
2565
627
    return DVT.getFuture().ValTy.has_value() &&
2566
73
           containsBorrow(*DVT.getFuture().ValTy);
2567
627
  }
2568
2.03k
  return false; // PrimValType, OwnTy, FlagsTy, EnumTy
2569
2.65k
}
2570
2571
} // namespace Validator
2572
} // namespace WasmEdge