From d8d41e20a76043256425c254abb450cfa2f276c7 Mon Sep 17 00:00:00 2001 From: Ryan McConnell Date: Tue, 25 Nov 2025 23:28:59 -0500 Subject: [PATCH 1/3] init --- compiler/concepts.nim | 18 ++++++++++++++++++ tests/concepts/tconceptsv2.nim | 15 +++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/compiler/concepts.nim b/compiler/concepts.nim index 7e547b19ce3c..af154741a06a 100644 --- a/compiler/concepts.nim +++ b/compiler/concepts.nim @@ -263,6 +263,22 @@ proc conceptsMatch(c: PContext, fc, ac: PType; m: var MatchCon): MatchKind = return mkNoMatch return mkSubset +proc isObjectSubtype(f, a: PType): bool = + var t = a + result = false + while t != nil: + t = t.baseClass + if t == nil: + break + t = t.skipTypes({tyPtr,tyRef}) + if t == nil: + break + if t.kind != tyObject: + break + if sameObjectTypes(f, t): + result = true + break + proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool = ## The heart of the concept matching process. 'f' is the formal parameter of some ## routine inside the concept that we're looking for. 'a' is the formal parameter @@ -327,6 +343,8 @@ proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool = result = a.base.sym == f.sym else: result = sameType(f, a) + if not(result) and f.kind == tyObject and a.kind == tyObject: + result = isObjectSubtype(f, a) of tyEmpty, tyString, tyCstring, tyPointer, tyNil, tyUntyped, tyTyped, tyVoid: result = a.skipTypes(ignorableForArgType).kind == f.kind of tyBool, tyChar, tyInt..tyUInt64: diff --git a/tests/concepts/tconceptsv2.nim b/tests/concepts/tconceptsv2.nim index 629ac1c876d7..d861c51c752c 100644 --- a/tests/concepts/tconceptsv2.nim +++ b/tests/concepts/tconceptsv2.nim @@ -585,3 +585,18 @@ block: discard assert (ref AObj[int]) is C + +block: + type + C = concept + proc x(a:Self, x: int) + StreamObj = object of RootObj + Stream = ref StreamObj + MemMapFileStreamObj = object of Stream + MemMapFileStream = ref MemMapFileStreamObj + + proc x(a: Stream, x: int) = discard + proc spring(x: C) = discard + + let test = MemMapFileStream() + spring(test) From 02c151a85204fe4a7fdfdfd7f971806efff477ff Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Thu, 27 Nov 2025 08:23:25 +0100 Subject: [PATCH 2/3] Update compiler/concepts.nim --- compiler/concepts.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/concepts.nim b/compiler/concepts.nim index af154741a06a..4329e4b4bfc1 100644 --- a/compiler/concepts.nim +++ b/compiler/concepts.nim @@ -343,7 +343,7 @@ proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool = result = a.base.sym == f.sym else: result = sameType(f, a) - if not(result) and f.kind == tyObject and a.kind == tyObject: + if not result and f.kind == tyObject and a.kind == tyObject: result = isObjectSubtype(f, a) of tyEmpty, tyString, tyCstring, tyPointer, tyNil, tyUntyped, tyTyped, tyVoid: result = a.skipTypes(ignorableForArgType).kind == f.kind From 8324b4b7a4c5251416d4659bd48cc5e50010e3fc Mon Sep 17 00:00:00 2001 From: Ryan McConnell Date: Sat, 29 Nov 2025 20:07:09 -0500 Subject: [PATCH 3/3] update manual --- doc/manual.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/manual.md b/doc/manual.md index 29006a753674..21abe9504c66 100644 --- a/doc/manual.md +++ b/doc/manual.md @@ -3008,6 +3008,12 @@ is more specific 2. if the concept is being compared with another concept the result is deferred to [Concept subset matching] 3. in any other case the concept is less specific then it's competitor +Currently, the concept evaluation mechanism evaluates to a successful match on the first acceptable candidate +for each defined binding. This has a couple of notable effects: + +- generic parameters are fulfilled by the first candidate match even if other candidates would also match and bind different parameters +- inheritable objects match as they do in normal overload resolution except the "depth" is not accounted for, because that would require calculating the minimum depth of any matching binding + Concept subset matching -------------------------