Osy#betaRun it on Osyrin: osyrin.com

Reference / Types

namespace

namespace Shop.Catalog;

Declares the namespace a file's types belong to, written once at the top of the file. It is optional — a file without one puts its types in the global namespace. Names resolve against the namespace a file is written in, then any imported namespaces, then the always-available `Osyrin` core.

stable4 examples compiled by CItypesnamespaceauthoring

Summary#

A namespace groups a file's types under a common name, so two parts of an app can each have an Order without colliding. Write it once, at the top of the file, terminated by a semicolon:

namespace Shop.Catalog;

It is optional. A file with no namespace declaration puts its types in the global namespace — which is exactly what a single-file app wants, and what every example in this reference assumes.

Signature#

namespace Shop;              // this file's types are Shop.*
entity Order { … }           // → Shop.Order

Description#

What it does to a name#

A type declared in a namespace gets that namespace as a prefix. namespace Shop; followed by entity Order declares Shop.Order — that is the type's real, full name, the one you use to refer to it from elsewhere.

Inside the file that declares it, you just write Order.

Two forms — file-scoped and braced#

Osy# supports both C# forms:

namespace Shop;                          // file-scoped — opens the whole file (preferred)

namespace Shop { entity Order { … } }    // braced — opens just its block
namespace Billing { entity Order { … } } // several braced namespaces may share one file
  • File-scoped (namespace X;) opens the whole file: it must come before every other declaration, and a file may declare at most one. Prefer it for a file that is all one namespace.
  • Braced (namespace X { … }) opens just its block, so a file may hold several and they may nest (namespace Shop { namespace Catalog { … } } puts a type in Shop.Catalog). This is what lets C# code paste in verbatim.

A file uses one form or the other, never both — mixing a file-scoped namespace X; with a braced namespace Y { … } in the same file is an error (as in C#).

How a name resolves#

When you write a bare name, it is looked for in this order — the first match wins, and no later step can make it ambiguous:

  1. The namespace you're in, then each enclosing one, working outward. Inside namespace Shop.Catalog; that means Shop.Catalog, then Shop, then the global namespace.
  2. The namespaces you imported with a using declaration — a package's, or one your own app declares. A using is PER FILE, and it imports that namespace's own types, never a child's: using Shop; does not bring in Shop.Orders.Order (write using Shop.Orders;). If two imports offer the same name, that is an error that names both — qualify the reference to say which you mean.
  3. The Osyrin core, the platform's own namespace, always available without importing anything.

Because step 1 comes first, a type you declared always wins over one you imported. To reach the imported one anyway, write its full name.

The same order holds for every kind of name — a type, a component, a control and a function alike. A component of your own named like one in the UI kit is the one your namespace's pages get:

namespace Acme.Lighting;
using Osysharp.Ui;

[Composable] component Badge(string text) { render { Text(text); } }

[Route("/")] component Home() {
  render { Badge(text: "on"); }      // Acme.Lighting.Badge — nearer than Osysharp.Ui.Badge
}

A namespace that encloses neither of two same-named declarations is still ambiguous there, and the error names both full spellings.

Two packages' functions of one name are told apart by their arguments, as C# picks an overload: a call whose arguments only ONE of them can take is that one, bare. Only a call that both could take — the same parameter list — has to be qualified, and the error says so naming both. A qualified call works anywhere a call does, and so does a using alias for the namespace:

using Rota.Kit;       // SignUp(Shift shift, string who)
using Accounts.Kit;   // SignUp(string email, string password)
using R = Rota.Kit;

string Join(Shift s) => SignUp(s, "ada");                 // Rota.Kit's — only it takes a Shift
string Register() => SignUp("[email protected]", "hunter22");    // Accounts.Kit's
string Drop() => Rota.Kit.Remove(1) + R.Remove(2);        // both kits have Remove(int): qualify, or alias

A package's own call looks only among the packages it imports. Code in a package resolves a bare call against that package and the ones its own package.osy names with use — never against everything the app happens to use beside it. So a kit that calls NameOf(person) from the one kit it depends on keeps compiling when an app also uses an unrelated kit with a NameOf of its own. For the app's own code the packages are the ones its app.osy names.

osy kits check warns a kit's author when the kit exports a function with the same name and parameters as a kit that is already published (KIT_NAME_PUBLISHED), since every app using both would have to qualify it. The thirteen built-in type names (Markdown, Json, DateTime, …) follow the same order: a declaration in an enclosing namespace wins, and everywhere else the bare name is the built-in — see [[types-name-shadowing#built-ins]].

The Osyrin namespace is the platform's#

Everything the platform ships lives under Osyrin. Its core types need no import. Its optional capabilities are sub-namespaces — Osysharp.Memory, Osysharp.Storage, Osysharp.Ui, … — that you first depend on with a use in your app { } manifest, then import with a using in each file that references their names. You cannot declare a type in Osyrin yourself.

Reaching another namespace#

A sibling namespace's types are not visible bare — qualify them, exactly as in C#:

namespace Billing;

int Count() {
  var orders = Shop.Order.Where(o => o.Code == "x").ToList();   // qualified
  return orders.Count;
}

Declaration names never contain a dot#

entity Shop.Order is an error. A dot in a declared name is how you'd spell a namespace, not how you declare one — use namespace Shop; at the top of the file.

Examples#

using Osysharp.Markdown;

// catalog.osy
namespace Shop;

entity Order {
  [Required] string Code;
  decimal Total;
}

int OpenOrders() {
  // `Order` resolves to Shop.Order — the namespace this file is written in.
  return Order.Where(o => o.Total > 0).ToList().Count;
}
using Osysharp.Markdown;

// billing.osy — a different namespace, so Shop.Order must be qualified.
namespace Billing;

entity Invoice {
  Shop.Order Source;
  decimal Amount;
}

Step 3 applies in every position a type name can appear, including a property's type — a core type needs no import there either:

using Osysharp.Markdown;

namespace Filing;

entity Attachment {
  // `MarkdownDocument` is `Osysharp.Markdown.MarkdownDocument`, found at step 3. Nothing is imported and nothing is qualified.
  MarkdownDocument Body;
}

A using of a namespace your own app declares, then its types by their bare names:

using Shop.Orders;

namespace Shop.Orders {
  public class Order { public string Code = ""; }
}

// This part of the file is in the global namespace; `Order` is found through the `using` above.
string CodeOf(Order order) {
  return order.Code;
}

See also#

Related

use

Declares a capability your app depends on, written inside the `app { }` manifest block. It provisions the capability…

type visibility (public / internal)

A top-level declaration carries a public or internal visibility that decides whether code outside its package can name…

Osysharp.Ui (the UI kit)

The bundled UI kit — ready-made styled controls like `Button`, the shared design-system vocabularies (`Tone`, `Size`)…